notes.js 15.4 KB
Newer Older
1
var NoteList = {
gitlabhq's avatar
gitlabhq committed
2

3 4 5 6
  notes_path: null,
  target_params: null,
  target_id: 0,
  target_type: null,
7
  top_id: 0,
8 9
  bottom_id: 0,
  loading_more_disabled: false,
10
  reversed: false,
11

Riyad Preukschas's avatar
Riyad Preukschas committed
12
  init: function(tid, tt, path) {
13 14 15 16 17 18
    NoteList.notes_path = path + ".js";
    NoteList.target_id = tid;
    NoteList.target_type = tt;
    NoteList.reversed = $("#notes-list").is(".reversed");
    NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id;

19 20
    NoteList.setupMainTargetNoteForm();

21
    if(NoteList.reversed) {
22 23
      var form = $(".js-main-target-form");
      form.find(".buttons, .note_options").hide();
24
      var textarea = form.find(".js-note-text");
Riyad Preukschas's avatar
Riyad Preukschas committed
25 26
      textarea.css("height", "40px");
      textarea.on("focus", function(){
27
        textarea.css("height", "80px");
28
        form.find(".buttons, .note_options").show();
Riyad Preukschas's avatar
Riyad Preukschas committed
29 30
      });
    }
Riyad Preukschas's avatar
Riyad Preukschas committed
31

Riyad Preukschas's avatar
Riyad Preukschas committed
32
    // get initial set of notes
33
    NoteList.getContent();
34

35
    // add a new diff note
Riyad Preukschas's avatar
Riyad Preukschas committed
36 37 38 39
    $(document).on("click",
                    ".js-add-diff-note-button",
                    NoteList.addDiffNote);

40
    // reply to diff/discussion notes
Riyad Preukschas's avatar
Riyad Preukschas committed
41 42 43 44
    $(document).on("click",
                    ".js-discussion-reply-button",
                    NoteList.replyToDiscussionNote);

45 46 47 48 49
    // setup note preview
    $(document).on("click",
                    ".js-note-preview-button",
                    NoteList.previewNote);

50 51 52 53 54
    // update the file name when an attachment is selected
    $(document).on("change",
                   ".js-note-attachment-input",
                   NoteList.updateFormAttachment);

Riyad Preukschas's avatar
Riyad Preukschas committed
55 56 57 58 59 60 61 62 63 64
    // hide diff note form
    $(document).on("click",
                    ".js-close-discussion-note-form",
                    NoteList.removeDiscussionNoteForm);

    // remove a note (in general)
    $(document).on("click",
                    ".js-note-delete",
                    NoteList.removeNote);

65
    // reset main target form after submit
Riyad Preukschas's avatar
Riyad Preukschas committed
66 67
    $(document).on("ajax:complete",
                   ".js-main-target-form",
68
                   NoteList.resetMainTargetForm);
gitlabhq's avatar
gitlabhq committed
69

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
70

71 72 73 74
    $(document).on("click",
      ".js-choose-note-attachment-button",
      NoteList.chooseNoteAttachment);
  },
75 76


Riyad Preukschas's avatar
Riyad Preukschas committed
77
  /**
78
   * When clicking on buttons
Riyad Preukschas's avatar
Riyad Preukschas committed
79 80
   */

81 82 83 84 85 86 87
  /**
   * Called when clicking on the "add a comment" button on the side of a diff line.
   *
   * Inserts a temporary row for the form below the line.
   * Sets up the form and shows it.
   */
  addDiffNote: function(e) {
88 89
    e.preventDefault();

90
    // find the form
91
    var form = $(".js-new-note-form");
92 93 94 95 96
    var row = $(this).closest("tr");
    var nextRow = row.next();

    // does it already have notes?
    if (nextRow.is(".notes_holder")) {
Riyad Preukschas's avatar
Riyad Preukschas committed
97 98
      $.proxy(NoteList.replyToDiscussionNote,
              nextRow.find(".js-discussion-reply-button")
99 100 101 102 103 104 105
             ).call();
    } else {
      // add a notes row and insert the form
      row.after('<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"></td></tr>');
      form.clone().appendTo(row.next().find(".notes_content"));

      // show the form
Riyad Preukschas's avatar
Riyad Preukschas committed
106
      NoteList.setupDiscussionNoteForm($(this), row.next().find("form"));
107 108 109
    }
  },

110 111 112 113 114 115 116 117 118 119 120
  /**
   * Called when clicking the "Choose File" button.
   * 
   * Opesn the file selection dialog.
   */
  chooseNoteAttachment: function() {
    var form = $(this).closest("form");

    form.find(".js-note-attachment-input").click();
  },

121 122 123 124 125 126 127 128
  /**
   * Shows the note preview.
   *
   * Lets the server render GFM into Html and displays it.
   *
   * Note: uses the Toggler behavior to toggle preview/edit views/buttons
   */
  previewNote: function(e) {
129 130
    e.preventDefault();

131 132
    var form = $(this).closest("form");
    var preview = form.find('.js-note-preview');
133
    var noteText = form.find('.js-note-text').val();
134

135
    if(noteText.trim().length === 0) {
136
      preview.text('Nothing to preview.');
137
    } else {
138
      preview.text('Loading...');
139
      $.post($(this).data('url'), {note: noteText})
140 141 142 143 144 145
        .success(function(previewData) {
          preview.html(previewData);
        });
    }
  },

146 147 148 149 150 151
  /**
   * Called in response to "cancel" on a diff note form.
   * 
   * Shows the reply button again.
   * Removes the form and if necessary it's temporary row.
   */
152
  removeDiscussionNoteForm: function() {
153 154 155 156
    var form = $(this).closest("form");
    var row = form.closest("tr");

    // show the reply button (will only work for replys)
Riyad Preukschas's avatar
Riyad Preukschas committed
157
    form.prev(".js-discussion-reply-button").show();
158 159

    if (row.is(".js-temp-notes-holder")) {
Riyad Preukschas's avatar
Riyad Preukschas committed
160
      // remove temporary row for diff lines
161 162 163 164 165 166 167 168 169 170 171
      row.remove();
    } else {
      // only remove the form
      form.remove();
    }
  },

  /**
   * Called in response to deleting a note of any kind.
   *
   * Removes the actual note from view.
172
   * Removes the whole discussion if the last note is being removed.
173 174
   */
  removeNote: function() {
175 176 177 178 179 180 181 182 183 184 185 186 187
    var note = $(this).closest(".note");
    var notes = note.closest(".notes");

    // check if this is the last note for this line
    if (notes.find(".note").length === 1) {
      // for discussions
      notes.closest(".discussion").remove();

      // for diff lines
      notes.closest("tr").remove();
    }

    note.remove();
188 189 190 191 192 193 194 195
    NoteList.updateVotes();
  },

  /**
   * Called when clicking on the "reply" button for a diff line.
   *
   * Shows the note form below the notes.
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
196
  replyToDiscussionNote: function() {
197
    // find the form
198
    var form = $(".js-new-note-form");
199 200 201 202 203 204 205

    // hide reply button
    $(this).hide();
    // insert the form after the button
    form.clone().insertAfter($(this));

    // show the form
Riyad Preukschas's avatar
Riyad Preukschas committed
206
    NoteList.setupDiscussionNoteForm($(this), $(this).next("form"));
207 208
  },

209

210
  /**
211 212 213 214
   * Helper for inserting and setting up note forms.
   */


Riyad Preukschas's avatar
Riyad Preukschas committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
  /**
   * Called in response to creating a note failing validation.
   *
   * Adds the rendered errors to the respective form.
   * If "discussionId" is null or undefined, the main target form is assumed.
   */
  errorsOnForm: function(errorsHtml, discussionId) {
    // find the form
    if (discussionId) {
      var form = $("form[rel='"+discussionId+"']");
    } else {
      var form = $(".js-main-target-form");
    }

    form.find(".js-errors").remove();
    form.prepend(errorsHtml);

    form.find(".js-note-text").focus();
  },


236 237
  /**
   * Shows the diff/discussion form and does some setup on it.
238 239 240
   *
   * Sets some hidden fields in the form.
   *
241 242
   * Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
   *       and "noteableId" data attributes set.
243
   */
244
  setupDiscussionNoteForm: function(dataHolder, form) {
245
    // setup note target
246
    form.attr("rel", dataHolder.data("discussionId"));
247
    form.find("#note_commit_id").val(dataHolder.data("commitId"));
248 249 250
    form.find("#note_line_code").val(dataHolder.data("lineCode"));
    form.find("#note_noteable_type").val(dataHolder.data("noteableType"));
    form.find("#note_noteable_id").val(dataHolder.data("noteableId"));
251

252
    NoteList.setupNoteForm(form);
253

254
    form.find(".js-note-text").focus();
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
  },

  /**
   * Shows the main form and does some setup on it.
   *
   * Sets some hidden fields in the form.
   */
  setupMainTargetNoteForm: function() {
    // find the form
    var form = $(".js-new-note-form");
    // insert the form after the button
    form.clone().replaceAll($(".js-main-target-form"));

    form = form.prev("form");

    // show the form
    NoteList.setupNoteForm(form);

    // fix classes
    form.removeClass("js-new-note-form");
    form.addClass("js-main-target-form");

    // remove unnecessary fields and buttons
    form.find("#note_line_code").remove();
    form.find(".js-close-discussion-note-form").remove();
  },

  /**
   * General note form setup.
   *
   * * deactivates the submit button when text is empty
   * * hides the preview button when text is empty
   * * setup GFM auto complete
   * * show the form
   */
  setupNoteForm: function(form) {
    disableButtonIfEmptyField(form.find(".js-note-text"), form.find(".js-comment-button"));

293 294
    form.removeClass("js-new-note-form");

295
    // setup preview buttons
296 297
    form.find(".js-note-edit-button, .js-note-preview-button")
        .tooltip({ placement: 'left' });
298

299
    previewButton = form.find(".js-note-preview-button");
300 301
    form.find(".js-note-text").on("input", function() {
      if ($(this).val().trim() !== "") {
302
        previewButton.removeClass("turn-off").addClass("turn-on");
303
      } else {
304
        previewButton.removeClass("turn-on").addClass("turn-off");
305 306 307
      }
    });

308 309 310 311 312
    // remove notify commit author checkbox for non-commit notes
    if (form.find("#note_noteable_type").val() !== "Commit") {
      form.find(".js-notify-commit-author").remove();
    }

313
    GitLab.GfmAutoComplete.setup();
314 315 316 317 318

    form.show();
  },


319
  /**
320 321
   * Handle loading the initial set of notes.
   * And set up loading more notes when scrolling to the bottom of the page.
322
   */
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
323 324


325
  /**
326
   * Gets an inital set of notes.
327
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
328 329
  getContent: function() {
    $.ajax({
330 331
      url: NoteList.notes_path,
      data: NoteList.target_params,
332 333
      complete: function(){ $('.js-notes-busy').removeClass("loading")},
      beforeSend: function() { $('.js-notes-busy').addClass("loading") },
Riyad Preukschas's avatar
Riyad Preukschas committed
334 335 336
      dataType: "script"
    });
  },
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
337

338 339 340 341
  /**
   * Called in response to getContent().
   * Replaces the content of #notes-list with the given html.
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
342
  setContent: function(newNoteIds, html) {
343 344
    NoteList.top_id = newNoteIds.first();
    NoteList.bottom_id = newNoteIds.last();
Riyad Preukschas's avatar
Riyad Preukschas committed
345 346
    $("#notes-list").html(html);

Riyad Preukschas's avatar
Riyad Preukschas committed
347
    // for the wall
348
    if (NoteList.reversed) {
Riyad Preukschas's avatar
Riyad Preukschas committed
349
      // init infinite scrolling
350
      NoteList.initLoadMore();
Riyad Preukschas's avatar
Riyad Preukschas committed
351 352

      // init getting new notes
353
      NoteList.initRefreshNew();
Riyad Preukschas's avatar
Riyad Preukschas committed
354 355
    }
  },
356 357 358


  /**
359
   * Handle loading more notes when scrolling to the bottom of the page.
360
   * The id of the last note in the list is in NoteList.bottom_id.
361
   *
362
   * Set up refreshing only new notes after all notes have been loaded.
363
   */
364 365 366 367 368


  /**
   * Initializes loading more notes when scrolling to the bottom of the page.
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
369 370 371 372 373 374 375 376 377 378 379 380 381
  initLoadMore: function() {
    $(document).endlessScroll({
      bottomPixels: 400,
      fireDelay: 1000,
      fireOnce:true,
      ceaseFire: function() {
        return NoteList.loading_more_disabled;
      },
      callback: function(i) {
        NoteList.getMore();
      }
    });
  },
382 383 384 385

  /**
   * Gets an additional set of notes.
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
386 387 388 389
  getMore: function() {
    // only load more notes if there are no "new" notes
    $('.loading').show();
    $.ajax({
390 391
      url: NoteList.notes_path,
      data: NoteList.target_params + "&loading_more=1&" + (NoteList.reversed ? "before_id" : "after_id") + "=" + NoteList.bottom_id,
392 393
      complete: function(){ $('.js-notes-busy').removeClass("loading")},
      beforeSend: function() { $('.js-notes-busy').addClass("loading") },
Riyad Preukschas's avatar
Riyad Preukschas committed
394 395 396
      dataType: "script"
    });
  },
397

398 399 400 401
  /**
   * Called in response to getMore().
   * Append notes to #notes-list.
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
402 403
  appendMoreNotes: function(newNoteIds, html) {
    var lastNewNoteId = newNoteIds.last();
404 405
    if(lastNewNoteId != NoteList.bottom_id) {
      NoteList.bottom_id = lastNewNoteId;
Riyad Preukschas's avatar
Riyad Preukschas committed
406 407 408
      $("#notes-list").append(html);
    }
  },
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
409

410 411 412 413
  /**
   * Called in response to getMore().
   * Disables loading more notes when scrolling to the bottom of the page.
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
414
  finishedLoadingMore: function() {
415
    NoteList.loading_more_disabled = true;
416

Riyad Preukschas's avatar
Riyad Preukschas committed
417
    // make sure we are up to date
418
    NoteList.updateVotes();
Riyad Preukschas's avatar
Riyad Preukschas committed
419
  },
420 421 422 423 424 425 426


  /**
   * Handle refreshing and adding of new notes.
   *
   * New notes are all notes that are created after the site has been loaded.
   * The "old" notes are in #notes-list the "new" ones will be in #new-notes-list.
427
   * The id of the last "old" note is in NoteList.bottom_id.
428 429 430 431 432
   */


  /**
   * Initializes getting new notes every n seconds.
Riyad Preukschas's avatar
Riyad Preukschas committed
433 434
   *
   * Note: only used on wall.
435
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
436 437 438
  initRefreshNew: function() {
    setInterval("NoteList.getNew()", 10000);
  },
439 440

  /**
441
   * Gets the new set of notes.
Riyad Preukschas's avatar
Riyad Preukschas committed
442 443
   *
   * Note: only used on wall.
444
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
445 446
  getNew: function() {
    $.ajax({
447 448
      url: NoteList.notes_path,
      data: NoteList.target_params + "&loading_new=1&after_id=" + (NoteList.reversed ? NoteList.top_id : NoteList.bottom_id),
Riyad Preukschas's avatar
Riyad Preukschas committed
449 450 451
      dataType: "script"
    });
  },
452 453 454 455

  /**
   * Called in response to getNew().
   * Replaces the content of #new-notes-list with the given html.
Riyad Preukschas's avatar
Riyad Preukschas committed
456 457
   *
   * Note: only used on wall.
458
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
459 460
  replaceNewNotes: function(newNoteIds, html) {
    $("#new-notes-list").html(html);
461
    NoteList.updateVotes();
Riyad Preukschas's avatar
Riyad Preukschas committed
462
  },
463 464

  /**
Riyad Preukschas's avatar
Riyad Preukschas committed
465
   * Adds a single common note to #notes-list.
466
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
467
  appendNewNote: function(id, html) {
468 469
    $("#notes-list").append(html);
    NoteList.updateVotes();
Riyad Preukschas's avatar
Riyad Preukschas committed
470
  },
471

472
  /**
Riyad Preukschas's avatar
Riyad Preukschas committed
473
   * Adds a single discussion note to #notes-list.
Riyad Preukschas's avatar
Riyad Preukschas committed
474 475
   *
   * Also removes the corresponding form.
476
   */
477
  appendNewDiscussionNote: function(discussionId, diffRowHtml, noteHtml) {
Riyad Preukschas's avatar
Riyad Preukschas committed
478 479 480
    var form = $("form[rel='"+discussionId+"']");
    var row = form.closest("tr");

481 482
    // is this the first note of discussion?
    if (row.is(".js-temp-notes-holder")) {
Riyad Preukschas's avatar
Riyad Preukschas committed
483
      // insert the note and the reply button after the temp row
484
      row.after(diffRowHtml);
Riyad Preukschas's avatar
Riyad Preukschas committed
485
      // remove the note (will be added again below)
486 487 488 489 490
      row.next().find(".note").remove();
    }

    // append new note to all matching discussions
    $(".notes[rel='"+discussionId+"']").append(noteHtml);
Riyad Preukschas's avatar
Riyad Preukschas committed
491 492 493

    // cleanup after successfully creating a diff/discussion note
    $.proxy(NoteList.removeDiscussionNoteForm, form).call();
494 495
  },

Riyad Preukschas's avatar
Riyad Preukschas committed
496 497 498 499 500 501 502 503 504
  /**
   * Adds a single wall note to #new-notes-list.
   *
   * Note: only used on wall.
   */
  appendNewWallNote: function(id, html) {
    $("#new-notes-list").prepend(html);
  },

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
  /**
   * Called in response the main target form has been successfully submitted.
   *
   * Removes any errors.
   * Resets text and preview.
   * Resets buttons.
   */
  resetMainTargetForm: function(){
    var form = $(this);

    // remove validation errors
    form.find(".js-errors").remove();

    // reset text and preview
    var previewContainer = form.find(".js-toggler-container.note_text_and_preview");
    if (previewContainer.is(".on")) {
      previewContainer.removeClass("on");
    }
    form.find(".js-note-text").val("").trigger("input");
  },

  /**
   * Called after an attachment file has been selected.
   *
   * Updates the file name for the selected attachment.
   */
  updateFormAttachment: function() {
    var form = $(this).closest("form");

    // get only the basename
    var filename = $(this).val().replace(/^.*[\\\/]/, '');

    form.find(".js-attachment-filename").text(filename);
  },

540 541 542 543 544 545 546 547
  /**
   * Recalculates the votes and updates them (if they are displayed at all).
   *
   * Assumes all relevant notes are displayed (i.e. there are no more notes to
   * load via getMore()).
   * Might produce inaccurate results when not all notes have been loaded and a
   * recalculation is triggered (e.g. when deleting a note).
   */
Riyad Preukschas's avatar
Riyad Preukschas committed
548 549
  updateVotes: function() {
    var votes = $("#votes .votes");
550
    var notes = $("#notes-list .note .vote");
Riyad Preukschas's avatar
Riyad Preukschas committed
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565

    // only update if there is a vote display
    if (votes.size()) {
      var upvotes = notes.filter(".upvote").size();
      var downvotes = notes.filter(".downvote").size();
      var votesCount = upvotes + downvotes;
      var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0;
      var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0;

      // change vote bar lengths
      votes.find(".bar-success").css("width", upvotesPercent+"%");
      votes.find(".bar-danger").css("width", downvotesPercent+"%");
      // replace vote numbers
      votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
      votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
566
    }
Riyad Preukschas's avatar
Riyad Preukschas committed
567
  }
568
};