550 lines
17 KiB
CoffeeScript
550 lines
17 KiB
CoffeeScript
#= require autosave
|
|
#= require dropzone
|
|
#= require dropzone_input
|
|
#= require gfm_auto_complete
|
|
#= require jquery.atwho
|
|
#= require task_list
|
|
|
|
class @Notes
|
|
@interval: null
|
|
|
|
constructor: (notes_url, note_ids, last_fetched_at, view) ->
|
|
@notes_url = notes_url
|
|
@note_ids = note_ids
|
|
@last_fetched_at = last_fetched_at
|
|
@view = view
|
|
@noteable_url = document.URL
|
|
@initRefresh()
|
|
@setupMainTargetNoteForm()
|
|
@cleanBinding()
|
|
@addBinding()
|
|
@initTaskList()
|
|
|
|
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
|
|
|
|
# change note in UI after update
|
|
$(document).on "ajax:success", "form.edit_note", @updateNote
|
|
|
|
# Edit note link
|
|
$(document).on "click", ".js-note-edit", @showEditForm
|
|
$(document).on "click", ".note-edit-cancel", @cancelEdit
|
|
|
|
# Reopen and close actions for Issue/MR combined with note form submit
|
|
$(document).on "click", ".js-note-target-reopen", @targetReopen
|
|
$(document).on "click", ".js-note-target-close", @targetClose
|
|
$(document).on "click", ".js-comment-button", @updateCloseButton
|
|
$(document).on "keyup", ".js-note-text", @updateTargetButtons
|
|
|
|
# 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
|
|
$(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
|
|
$(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
|
|
|
|
# update the file name when an attachment is selected
|
|
$(document).on "change", ".js-note-attachment-input", @updateFormAttachment
|
|
|
|
# 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
|
|
|
|
# hide diff note form
|
|
$(document).on "click", ".js-close-discussion-note-form", @cancelDiscussionForm
|
|
|
|
# fetch notes when tab becomes visible
|
|
$(document).on "visibilitychange", @visibilityChange
|
|
|
|
cleanBinding: ->
|
|
$(document).off "ajax:success", ".js-main-target-form"
|
|
$(document).off "ajax:success", ".js-discussion-note-form"
|
|
$(document).off "ajax:success", "form.edit_note"
|
|
$(document).off "click", ".js-note-edit"
|
|
$(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"
|
|
$(document).off "ajax:success", ".js-main-target-form"
|
|
$(document).off "click", ".js-discussion-reply-button"
|
|
$(document).off "click", ".js-add-diff-note-button"
|
|
$(document).off "visibilitychange"
|
|
$(document).off "keyup", ".js-note-text"
|
|
$(document).off "click", ".js-note-target-reopen"
|
|
$(document).off "click", ".js-note-target-close"
|
|
|
|
$('.note .js-task-list-container').taskList('disable')
|
|
$(document).off 'tasklist:changed', '.note .js-task-list-container'
|
|
|
|
initRefresh: ->
|
|
clearInterval(Notes.interval)
|
|
Notes.interval = setInterval =>
|
|
@refresh()
|
|
, 15000
|
|
|
|
refresh: ->
|
|
unless document.hidden or (@noteable_url != document.URL)
|
|
@getContent()
|
|
|
|
getContent: ->
|
|
$.ajax
|
|
url: @notes_url
|
|
data: "last_fetched_at=" + @last_fetched_at
|
|
dataType: "json"
|
|
success: (data) =>
|
|
notes = data.notes
|
|
@last_fetched_at = data.last_fetched_at
|
|
$.each notes, (i, note) =>
|
|
@renderNote(note)
|
|
|
|
|
|
###
|
|
Render note in main comments area.
|
|
|
|
Note: for rendering inline notes use renderDiscussionNote
|
|
###
|
|
renderNote: (note) ->
|
|
unless note.valid
|
|
if note.award
|
|
flash = new Flash('You have already used this award emoji!', 'alert')
|
|
flash.pinTo('.header-content')
|
|
return
|
|
|
|
# render note if it not present in loaded list
|
|
# or skip if rendered
|
|
if @isNewNote(note) && !note.award
|
|
@note_ids.push(note.id)
|
|
$('ul.main-notes-list').
|
|
append(note.html).
|
|
syntaxHighlight()
|
|
@initTaskList()
|
|
|
|
if note.award
|
|
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
|
|
awards_handler.scrollToAwards()
|
|
|
|
###
|
|
Check if note does not exists on page
|
|
###
|
|
isNewNote: (note) ->
|
|
$.inArray(note.id, @note_ids) == -1
|
|
|
|
isParallelView: ->
|
|
@view == 'parallel'
|
|
|
|
###
|
|
Render note in discussion area.
|
|
|
|
Note: for rendering inline notes use renderDiscussionNote
|
|
###
|
|
renderDiscussionNote: (note) ->
|
|
@note_ids.push(note.id)
|
|
form = $("form[rel='" + note.discussion_id + "']")
|
|
row = form.closest("tr")
|
|
note_html = $(note.html)
|
|
note_html.syntaxHighlight()
|
|
|
|
# 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()
|
|
|
|
# Add note to 'Changes' page discussions
|
|
$(".notes[rel='" + note.discussion_id + "']").append note_html
|
|
|
|
# Init discussion on 'Discussion' page if it is merge request page
|
|
if $('body').attr('data-page').indexOf('projects:merge_request') == 0
|
|
discussion_html = $(note.discussion_with_diff_html)
|
|
discussion_html.syntaxHighlight()
|
|
$('ul.main-notes-list').append(discussion_html)
|
|
else
|
|
# append new note to all matching discussions
|
|
$(".notes[rel='" + note.discussion_id + "']").append note_html
|
|
|
|
# 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
|
|
form.find(".js-md-write-button").click()
|
|
form.find(".js-note-text").val("").trigger "input"
|
|
|
|
form.find(".js-note-text").data("autosave").reset()
|
|
|
|
reenableTargetFormSubmitButton: ->
|
|
form = $(".js-main-target-form")
|
|
|
|
form.find(".js-note-text").trigger "input"
|
|
|
|
###
|
|
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"
|
|
form.find('.div-dropzone').remove()
|
|
|
|
# setup preview buttons
|
|
form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
|
|
previewButton = form.find(".js-md-preview-button")
|
|
|
|
textarea = form.find(".js-note-text")
|
|
|
|
textarea.on "input", ->
|
|
if $(this).val().trim() isnt ""
|
|
previewButton.removeClass("turn-off").addClass "turn-on"
|
|
else
|
|
previewButton.removeClass("turn-on").addClass "turn-off"
|
|
|
|
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()
|
|
]
|
|
|
|
# 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()
|
|
new DropzoneInput(form)
|
|
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.
|
|
###
|
|
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
|
|
$note_li = $('.note-row-' + note.id)
|
|
$note_li.replaceWith($html)
|
|
|
|
###
|
|
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
|
|
###
|
|
showEditForm: (e) ->
|
|
e.preventDefault()
|
|
note = $(this).closest(".note")
|
|
note.find(".note-body > .note-text").hide()
|
|
note.find(".note-header").hide()
|
|
base_form = note.find(".note-edit-form")
|
|
form = base_form.clone().insertAfter(base_form)
|
|
form.addClass('current-note-edit-form gfm-form')
|
|
form.find('.div-dropzone').remove()
|
|
|
|
# Show the attachment delete link
|
|
note.find(".js-note-attachment-delete").show()
|
|
|
|
# Setup markdown form
|
|
GitLab.GfmAutoComplete.setup()
|
|
new DropzoneInput(form)
|
|
|
|
form.show()
|
|
textarea = form.find("textarea")
|
|
textarea.focus()
|
|
|
|
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
|
|
# The textarea has the correct value, Chrome just won't show it unless we
|
|
# modify it, so let's clear it and re-set it!
|
|
value = textarea.val()
|
|
textarea.val ""
|
|
textarea.val value
|
|
|
|
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
|
|
|
|
###
|
|
Called in response to clicking the edit note link
|
|
|
|
Hides edit form
|
|
###
|
|
cancelEdit: (e) ->
|
|
e.preventDefault()
|
|
note = $(this).closest(".note")
|
|
note.find(".note-body > .note-text").show()
|
|
note.find(".note-header").show()
|
|
note.find(".current-note-edit-form").remove()
|
|
|
|
###
|
|
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")
|
|
note_id = note.attr('id')
|
|
|
|
$('.note[id="' + note_id + '"]').each ->
|
|
note = $(this)
|
|
notes = note.closest(".notes")
|
|
count = notes.closest(".notes_holder").find(".discussion-notes-count")
|
|
|
|
# check if this is the last note for this line
|
|
if notes.find(".note").length is 1
|
|
|
|
# for discussions
|
|
notes.closest(".discussion").remove()
|
|
|
|
# for diff lines
|
|
notes.closest("tr").remove()
|
|
else
|
|
# update notes count
|
|
count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
|
|
|
|
note.remove()
|
|
|
|
###
|
|
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()
|
|
note.find(".note-body > .note-text").show()
|
|
note.find(".note-header").show()
|
|
note.find(".current-note-edit-form").remove()
|
|
|
|
###
|
|
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")
|
|
replyLink = $(e.target).closest(".js-discussion-reply-button")
|
|
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")
|
|
form.find("#line_type").val dataHolder.data("lineType")
|
|
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()
|
|
link = e.currentTarget
|
|
form = $(".js-new-note-form")
|
|
row = $(link).closest("tr")
|
|
nextRow = row.next()
|
|
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
|
|
else
|
|
# add a notes row and insert the form
|
|
row.after rowCssToAdd
|
|
addForm = true
|
|
|
|
if addForm
|
|
newForm = form.clone()
|
|
newForm.appendTo row.next().find(targetContent)
|
|
|
|
# show the form
|
|
@setupDiscussionNoteForm $(link), newForm
|
|
|
|
###
|
|
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")
|
|
|
|
form.find(".js-note-text").data("autosave").reset()
|
|
|
|
# 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()
|
|
|
|
|
|
cancelDiscussionForm: (e) =>
|
|
e.preventDefault()
|
|
form = $(".js-new-note-form")
|
|
form = $(e.target).closest(".js-discussion-note-form")
|
|
@removeDiscussionNoteForm(form)
|
|
|
|
###
|
|
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
|
|
|
|
###
|
|
Called when the tab visibility changes
|
|
###
|
|
visibilityChange: =>
|
|
@refresh()
|
|
|
|
targetReopen: (e) =>
|
|
@submitNoteForm($(e.target).parents('form'))
|
|
|
|
targetClose: (e) =>
|
|
@submitNoteForm($(e.target).parents('form'))
|
|
|
|
submitNoteForm: (form) =>
|
|
noteText = form.find(".js-note-text").val()
|
|
if noteText.trim().length > 0
|
|
form.submit()
|
|
|
|
updateCloseButton: (e) =>
|
|
textarea = $(e.target)
|
|
form = textarea.parents('form')
|
|
form.find('.js-note-target-close').text('Close')
|
|
|
|
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')
|
|
else
|
|
form.find('.js-note-target-reopen').text('Reopen')
|
|
form.find('.js-note-target-close').text('Close')
|
|
|
|
initTaskList: ->
|
|
@enableTaskList()
|
|
$(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList
|
|
|
|
enableTaskList: ->
|
|
$('.note .js-task-list-container').taskList('enable')
|
|
|
|
updateTaskList: ->
|
|
$('form', this).submit()
|