notes.js.coffee 16.5 KB
Newer Older
1
#= require autosave
R
Robert Speicher 已提交
2
#= require autosize
3 4 5 6 7 8
#= require dropzone
#= require dropzone_input
#= require gfm_auto_complete
#= require jquery.atwho
#= require task_list

C
Ciro Santilli 已提交
9
class @Notes
10 11
  @interval: null

12
  constructor: (notes_url, note_ids, last_fetched_at, view) ->
13 14
    @notes_url = notes_url
    @note_ids = note_ids
15
    @last_fetched_at = last_fetched_at
16
    @view = view
17
    @noteable_url = document.URL
18 19 20 21
    @initRefresh()
    @setupMainTargetNoteForm()
    @cleanBinding()
    @addBinding()
22
    @initTaskList()
23 24 25 26 27 28

  addBinding: ->
    # add note to UI after creation
    $(document).on "ajax:success", ".js-main-target-form", @addNote
    $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote

29
    # change note in UI after update
30 31 32
    $(document).on "ajax:success", "form.edit_note", @updateNote

    # Edit note link
33
    $(document).on "click", ".js-note-edit", @showEditForm
34 35
    $(document).on "click", ".note-edit-cancel", @cancelEdit

36
    # Reopen and close actions for Issue/MR combined with note form submit
37
    $(document).on "click", ".js-comment-button", @updateCloseButton
38 39
    $(document).on "keyup", ".js-note-text", @updateTargetButtons

40 41 42 43 44 45 46
    # remove a note (in general)
    $(document).on "click", ".js-note-delete", @removeNote

    # delete note attachment
    $(document).on "click", ".js-note-attachment-delete", @removeAttachment

    # reset main target form after submit
47 48
    $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
    $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
49

50 51 52
    # update the file name when an attachment is selected
    $(document).on "change", ".js-note-attachment-input", @updateFormAttachment

53 54 55 56 57 58
    # reply to diff/discussion notes
    $(document).on "click", ".js-discussion-reply-button", @replyToDiscussionNote

    # add diff note
    $(document).on "click", ".js-add-diff-note-button", @addDiffNote

59 60 61
    # hide diff note form
    $(document).on "click", ".js-close-discussion-note-form", @cancelDiscussionForm

62 63 64
    # fetch notes when tab becomes visible
    $(document).on "visibilitychange", @visibilityChange

65 66 67 68
  cleanBinding: ->
    $(document).off "ajax:success", ".js-main-target-form"
    $(document).off "ajax:success", ".js-discussion-note-form"
    $(document).off "ajax:success", "form.edit_note"
69
    $(document).off "click", ".js-note-edit"
70 71 72 73
    $(document).off "click", ".note-edit-cancel"
    $(document).off "click", ".js-note-delete"
    $(document).off "click", ".js-note-attachment-delete"
    $(document).off "ajax:complete", ".js-main-target-form"
74
    $(document).off "ajax:success", ".js-main-target-form"
75 76
    $(document).off "click", ".js-discussion-reply-button"
    $(document).off "click", ".js-add-diff-note-button"
77
    $(document).off "visibilitychange"
78 79 80
    $(document).off "keyup", ".js-note-text"
    $(document).off "click", ".js-note-target-reopen"
    $(document).off "click", ".js-note-target-close"
81

82 83 84
    $('.note .js-task-list-container').taskList('disable')
    $(document).off 'tasklist:changed', '.note .js-task-list-container'

85
  initRefresh: ->
86 87
    clearInterval(Notes.interval)
    Notes.interval = setInterval =>
88 89 90 91
      @refresh()
    , 15000

  refresh: ->
92 93
    unless document.hidden or (@noteable_url != document.URL)
      @getContent()
94 95 96 97

  getContent: ->
    $.ajax
      url: @notes_url
98
      data: "last_fetched_at=" + @last_fetched_at
99 100 101
      dataType: "json"
      success: (data) =>
        notes = data.notes
102
        @last_fetched_at = data.last_fetched_at
103
        $.each notes, (i, note) =>
104
          @renderNote(note)
105 106 107 108 109 110 111 112


  ###
  Render note in main comments area.

  Note: for rendering inline notes use renderDiscussionNote
  ###
  renderNote: (note) ->
113
    unless note.valid
114
      if note.award
115
        flash = new Flash('You have already used this award emoji!', 'alert')
116
        flash.pinTo('.header-content')
117 118
      return

119 120
    # render note if it not present in loaded list
    # or skip if rendered
V
Valery Sizov 已提交
121
    if @isNewNote(note) && !note.award
122
      @note_ids.push(note.id)
123 124 125
      $('ul.main-notes-list').
        append(note.html).
        syntaxHighlight()
126
      @initTaskList()
127

V
Valery Sizov 已提交
128
    if note.award
129
      awards_handler.addAwardToEmojiBar(note.note)
130
      awards_handler.scrollToAwards()
V
Valery Sizov 已提交
131

132 133 134 135 136 137
  ###
  Check if note does not exists on page
  ###
  isNewNote: (note) ->
    $.inArray(note.id, @note_ids) == -1

138 139
  isParallelView: ->
    @view == 'parallel'
140 141 142 143 144 145 146

  ###
  Render note in discussion area.

  Note: for rendering inline notes use renderDiscussionNote
  ###
  renderDiscussionNote: (note) ->
147
    @note_ids.push(note.id)
148 149
    form = $("form[rel='" + note.discussion_id + "']")
    row = form.closest("tr")
150 151
    note_html = $(note.html)
    note_html.syntaxHighlight()
152 153 154 155 156 157 158 159 160

    # is this the first note of discussion?
    if row.is(".js-temp-notes-holder")
      # insert the note and the reply button after the temp row
      row.after note.discussion_html

      # remove the note (will be added again below)
      row.next().find(".note").remove()

161
      # Add note to 'Changes' page discussions
162
      $(".notes[rel='" + note.discussion_id + "']").append note_html
163

164 165
      # Init discussion on 'Discussion' page if it is merge request page
      if $('body').attr('data-page').indexOf('projects:merge_request') == 0
166 167 168
        discussion_html = $(note.discussion_with_diff_html)
        discussion_html.syntaxHighlight()
        $('ul.main-notes-list').append(discussion_html)
169 170
    else
      # append new note to all matching discussions
171
      $(".notes[rel='" + note.discussion_id + "']").append note_html
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

    # cleanup after successfully creating a diff/discussion note
    @removeDiscussionNoteForm(form)

  ###
  Called in response the main target form has been successfully submitted.

  Removes any errors.
  Resets text and preview.
  Resets buttons.
  ###
  resetMainTargetForm: ->
    form = $(".js-main-target-form")

    # remove validation errors
    form.find(".js-errors").remove()

    # reset text and preview
V
Vinnie Okada 已提交
190
    form.find(".js-md-write-button").click()
191 192
    form.find(".js-note-text").val("").trigger "input"

193 194
    form.find(".js-note-text").data("autosave").reset()

195 196 197 198 199
  reenableTargetFormSubmitButton: ->
    form = $(".js-main-target-form")

    form.find(".js-note-text").trigger "input"

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
  ###
  Shows the main form and does some setup on it.

  Sets some hidden fields in the form.
  ###
  setupMainTargetNoteForm: ->

    # find the form
    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
    @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: (form) ->
    disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
    form.removeClass "js-new-note-form"
236
    form.find('.div-dropzone').remove()
237 238

    # setup preview buttons
V
Vinnie Okada 已提交
239 240
    form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
    previewButton = form.find(".js-md-preview-button")
241 242 243 244

    textarea = form.find(".js-note-text")

    textarea.on "input", ->
245 246 247 248 249
      if $(this).val().trim() isnt ""
        previewButton.removeClass("turn-off").addClass "turn-on"
      else
        previewButton.removeClass("turn-on").addClass "turn-off"

R
Robert Speicher 已提交
250
    autosize(textarea)
251 252 253 254 255 256 257
    new Autosave textarea, [
      "Note"
      form.find("#note_commit_id").val()
      form.find("#note_line_code").val()
      form.find("#note_noteable_type").val()
      form.find("#note_noteable_id").val()
    ]
258 259 260 261

    # remove notify commit author checkbox for non-commit notes
    form.find(".js-notify-commit-author").remove()  if form.find("#note_noteable_type").val() isnt "Commit"
    GitLab.GfmAutoComplete.setup()
262
    new DropzoneInput(form)
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
    form.show()

  ###
  Called in response to the new note form being submitted

  Adds new note to list.
  ###
  addNote: (xhr, note, status) =>
    @renderNote(note)

  ###
  Called in response to the new note form being submitted

  Adds new note to list.
  ###
  addDiscussionNote: (xhr, note, status) =>
    @renderDiscussionNote(note)

  ###
  Called in response to the edit note form being submitted

  Updates the current note field.
  ###
286 287 288 289 290 291 292
  updateNote: (_xhr, note, _status) =>
    # Convert returned HTML to a jQuery object so we can modify it further
    $html = $(note.html)
    $html.syntaxHighlight()
    $html.find('.js-task-list-container').taskList('enable')

    # Find the note's `li` element by ID and replace it with the updated HTML
293
    $note_li = $('.note-row-' + note.id)
294
    $note_li.replaceWith($html)
295 296 297 298 299 300 301 302

  ###
  Called in response to clicking the edit note link

  Replaces the note text with the note edit form
  Adds a hidden div with the original content of the note to fill the edit note form with
  if the user cancels
  ###
303 304 305
  showEditForm: (e) ->
    e.preventDefault()
    note = $(this).closest(".note")
306
    note.find(".note-body > .note-text").hide()
307
    note.find(".note-header").hide()
308 309
    base_form = note.find(".note-edit-form")
    form = base_form.clone().insertAfter(base_form)
310
    form.addClass('current-note-edit-form gfm-form')
311
    form.find('.div-dropzone').remove()
312 313 314

    # Show the attachment delete link
    note.find(".js-note-attachment-delete").show()
315 316

    # Setup markdown form
317
    GitLab.GfmAutoComplete.setup()
318 319
    new DropzoneInput(form)

320
    form.show()
321 322
    textarea = form.find("textarea")
    textarea.focus()
323
    autosize(textarea)
324

325
    # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
326
    # The textarea has the correct value, Chrome just won't show it unless we
327 328 329 330
    # modify it, so let's clear it and re-set it!
    value = textarea.val()
    textarea.val ""
    textarea.val value
331

332
    disableButtonIfEmptyField textarea, form.find(".js-comment-button")
333 334 335 336 337 338 339 340 341

  ###
  Called in response to clicking the edit note link

  Hides edit form
  ###
  cancelEdit: (e) ->
    e.preventDefault()
    note = $(this).closest(".note")
342
    note.find(".note-body > .note-text").show()
343 344
    note.find(".note-header").show()
    note.find(".current-note-edit-form").remove()
345 346 347 348 349 350 351 352 353

  ###
  Called in response to deleting a note of any kind.

  Removes the actual note from view.
  Removes the whole discussion if the last note is being removed.
  ###
  removeNote: ->
    note = $(this).closest(".note")
354
    note_id = note.attr('id')
355

356 357 358
    $('.note[id="' + note_id + '"]').each ->
      note = $(this)
      notes = note.closest(".notes")
359
      count = notes.closest(".issuable-details").find(".notes-tab .badge")
360

361 362
      # check if this is the last note for this line
      if notes.find(".note").length is 1
363

364 365
        # for discussions
        notes.closest(".discussion").remove()
366

367 368
        # for diff lines
        notes.closest("tr").remove()
369 370 371 372

      # update notes count
      oldNum = parseInt(count.text())
      count.text(oldNum - 1)
373 374

      note.remove()
375 376 377 378 379 380 381 382 383 384

  ###
  Called in response to clicking the delete attachment link

  Removes the attachment wrapper view, including image tag if it exists
  Resets the note editing form
  ###
  removeAttachment: ->
    note = $(this).closest(".note")
    note.find(".note-attachment").remove()
385
    note.find(".note-body > .note-text").show()
386 387
    note.find(".note-header").show()
    note.find(".current-note-edit-form").remove()
388 389 390 391 392 393 394 395

  ###
  Called when clicking on the "reply" button for a diff line.

  Shows the note form below the notes.
  ###
  replyToDiscussionNote: (e) =>
    form = $(".js-new-note-form")
396
    replyLink = $(e.target).closest(".js-discussion-reply-button")
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
    replyLink.hide()

    # insert the form after the button
    form.clone().insertAfter replyLink

    # show the form
    @setupDiscussionNoteForm(replyLink, replyLink.next("form"))

  ###
  Shows the diff or discussion form and does some setup on it.

  Sets some hidden fields in the form.

  Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
  and "noteableId" data attributes set.
  ###
  setupDiscussionNoteForm: (dataHolder, form) =>
    # setup note target
    form.attr "rel", dataHolder.data("discussionId")
416
    form.find("#line_type").val dataHolder.data("lineType")
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
    form.find("#note_commit_id").val dataHolder.data("commitId")
    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")
    @setupNoteForm form
    form.find(".js-note-text").focus()
    form.addClass "js-discussion-note-form"

  ###
  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: (e) =>
    e.preventDefault()
D
Dmitriy Zaporozhets 已提交
433
    link = e.currentTarget
434 435 436
    form = $(".js-new-note-form")
    row = $(link).closest("tr")
    nextRow = row.next()
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
    hasNotes = nextRow.is(".notes_holder")
    addForm = false
    targetContent = ".notes_content"
    rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>"

    # In parallel view, look inside the correct left/right pane
    if @isParallelView()
      lineType = $(link).data("lineType")
      targetContent += "." + lineType
      rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"

    if hasNotes
      notesContent = nextRow.find(targetContent)
      if notesContent.length
        replyButton = notesContent.find(".js-discussion-reply-button:visible")
        if replyButton.length
          e.target = replyButton[0]
          $.proxy(@replyToDiscussionNote, replyButton[0], e).call()
        else
          # In parallel view, the form may not be present in one of the panes
          noteForm = notesContent.find(".js-discussion-note-form")
          if noteForm.length == 0
            addForm = true
460 461
    else
      # add a notes row and insert the form
462 463 464 465 466 467
      row.after rowCssToAdd
      addForm = true

    if addForm
      newForm = form.clone()
      newForm.appendTo row.next().find(targetContent)
468 469

      # show the form
470
      @setupDiscussionNoteForm $(link), newForm
471 472 473 474 475 476 477 478 479 480

  ###
  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.
  ###
  removeDiscussionNoteForm: (form)->
    row = form.closest("tr")

481 482
    form.find(".js-note-text").data("autosave").reset()

483 484 485 486 487 488 489 490 491
    # show the reply button (will only work for replies)
    form.prev(".js-discussion-reply-button").show()
    if row.is(".js-temp-notes-holder")
      # remove temporary row for diff lines
      row.remove()
    else
      # only remove the form
      form.remove()

492 493 494 495 496 497 498

  cancelDiscussionForm: (e) =>
    e.preventDefault()
    form = $(".js-new-note-form")
    form = $(e.target).closest(".js-discussion-note-form")
    @removeDiscussionNoteForm(form)

499 500 501 502 503 504 505 506 507 508 509 510
  ###
  Called after an attachment file has been selected.

  Updates the file name for the selected attachment.
  ###
  updateFormAttachment: ->
    form = $(this).closest("form")

    # get only the basename
    filename = $(this).val().replace(/^.*[\\\/]/, "")
    form.find(".js-attachment-filename").text filename

511 512 513 514 515 516
  ###
  Called when the tab visibility changes
  ###
  visibilityChange: =>
    @refresh()

517 518 519 520 521
  updateCloseButton: (e) =>
    textarea = $(e.target)
    form = textarea.parents('form')
    form.find('.js-note-target-close').text('Close')

522 523 524 525 526 527
  updateTargetButtons: (e) =>
    textarea = $(e.target)
    form = textarea.parents('form')
    if textarea.val().trim().length > 0
      form.find('.js-note-target-reopen').text('Comment & reopen')
      form.find('.js-note-target-close').text('Comment & close')
528 529
      form.find('.js-note-target-reopen').addClass('btn-comment-and-reopen')
      form.find('.js-note-target-close').addClass('btn-comment-and-close')
530 531 532
    else
      form.find('.js-note-target-reopen').text('Reopen')
      form.find('.js-note-target-close').text('Close')
533 534
      form.find('.js-note-target-reopen').removeClass('btn-comment-and-reopen')
      form.find('.js-note-target-close').removeClass('btn-comment-and-close')
535 536 537 538 539

  initTaskList: ->
    @enableTaskList()
    $(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList

540 541 542
  enableTaskList: ->
    $('.note .js-task-list-container').taskList('enable')

543 544
  updateTaskList: ->
    $('form', this).submit()