summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/notes.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/notes.js')
-rw-r--r--app/assets/javascripts/notes.js164
1 files changed, 99 insertions, 65 deletions
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 7ba44835741..62a46733cc4 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -12,7 +12,6 @@ require('./autosave');
window.autosize = require('vendor/autosize');
window.Dropzone = require('dropzone');
require('./dropzone_input');
-require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho
require('vendor/jquery.atwho');
require('./task_list');
@@ -24,18 +23,18 @@ const normalizeNewlines = function(str) {
(function() {
this.Notes = (function() {
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
- const REGEX_SLASH_COMMANDS = /^\/\w+/gm;
+ const REGEX_SLASH_COMMANDS = /^\/\w+.*$/gm;
Notes.interval = null;
- function Notes(notes_url, note_ids, last_fetched_at, view) {
+ function Notes(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this);
this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
- this.addDiffNote = this.addDiffNote.bind(this);
+ this.onAddDiffNote = this.onAddDiffNote.bind(this);
this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
- this.replyToDiscussionNote = this.replyToDiscussionNote.bind(this);
+ this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
this.removeNote = this.removeNote.bind(this);
this.cancelEdit = this.cancelEdit.bind(this);
this.updateNote = this.updateNote.bind(this);
@@ -47,9 +46,11 @@ const normalizeNewlines = function(str) {
this.keydownNoteText = this.keydownNoteText.bind(this);
this.toggleCommitList = this.toggleCommitList.bind(this);
this.postComment = this.postComment.bind(this);
+ this.clearFlashWrapper = this.clearFlash.bind(this);
this.notes_url = notes_url;
this.note_ids = note_ids;
+ this.enableGFM = enableGFM;
// Used to keep track of updated notes while people are editing things
this.updatedNotesTrackingMap = {};
this.last_fetched_at = last_fetched_at;
@@ -57,6 +58,7 @@ const normalizeNewlines = function(str) {
this.notesCountBadge || (this.notesCountBadge = $(".issuable-details").find(".notes-tab .badge"));
this.basePollingInterval = 15000;
this.maxPollingSteps = 4;
+ this.flashErrors = [];
this.cleanBinding();
this.addBinding();
@@ -100,9 +102,9 @@ const normalizeNewlines = function(str) {
// update the file name when an attachment is selected
$(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
// reply to diff/discussion notes
- $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
+ $(document).on("click", ".js-discussion-reply-button", this.onReplyToDiscussionNote);
// add diff note
- $(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
+ $(document).on("click", ".js-add-diff-note-button", this.onAddDiffNote);
// hide diff note form
$(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
// toggle commit list
@@ -298,14 +300,14 @@ const normalizeNewlines = function(str) {
if (!noteEntity.valid) {
if (noteEntity.errors.commands_only) {
- new Flash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
+ this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
this.refresh();
}
return;
}
const $note = $notesList.find(`#note_${noteEntity.id}`);
- if (this.isNewNote(noteEntity)) {
+ if (Notes.isNewNote(noteEntity, this.note_ids)) {
this.note_ids.push(noteEntity.id);
const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
@@ -318,7 +320,7 @@ const normalizeNewlines = function(str) {
return this.updateNotesCount(1);
}
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
- else if (this.isUpdatedNote(noteEntity, $note)) {
+ else if (Notes.isUpdatedNote(noteEntity, $note)) {
const isEditing = $note.hasClass('is-editing');
const initialContent = normalizeNewlines(
$note.find('.original-note-content').text().trim()
@@ -346,23 +348,6 @@ const normalizeNewlines = function(str) {
}
};
- /*
- Check if note does not exists on page
- */
-
- Notes.prototype.isNewNote = function(noteEntity) {
- return $.inArray(noteEntity.id, this.note_ids) === -1;
- };
-
- Notes.prototype.isUpdatedNote = function(noteEntity, $note) {
- // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
- const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
- const currentNoteText = normalizeNewlines(
- $note.find('.original-note-content').text().trim()
- );
- return sanitizedNoteNote !== currentNoteText;
- };
-
Notes.prototype.isParallelView = function() {
return Cookies.get('diff_view') === 'parallel';
};
@@ -375,7 +360,7 @@ const normalizeNewlines = function(str) {
Notes.prototype.renderDiscussionNote = function(noteEntity, $form) {
var discussionContainer, form, row, lineType, diffAvatarContainer;
- if (!this.isNewNote(noteEntity)) {
+ if (!Notes.isNewNote(noteEntity, this.note_ids)) {
return;
}
this.note_ids.push(noteEntity.id);
@@ -522,7 +507,7 @@ const normalizeNewlines = function(str) {
Notes.prototype.setupNoteForm = function(form) {
var textarea, key;
- new gl.GLForm(form);
+ new gl.GLForm(form, this.enableGFM);
textarea = form.find(".js-note-text");
key = [
"Note",
@@ -551,14 +536,14 @@ const normalizeNewlines = function(str) {
return this.renderNote(note);
};
- Notes.prototype.addNoteError = ($form) => {
+ Notes.prototype.addNoteError = function($form) {
let formParentTimeline;
if ($form.hasClass('js-main-target-form')) {
formParentTimeline = $form.parents('.timeline');
} else if ($form.hasClass('js-discussion-note-form')) {
formParentTimeline = $form.closest('.discussion-notes').find('.notes');
}
- return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline);
+ return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline);
};
Notes.prototype.updateNoteError = $parentTimeline => new Flash('Your comment could not be updated! Please check your network connection and try again.');
@@ -593,12 +578,12 @@ const normalizeNewlines = function(str) {
Updates the current note field.
*/
- Notes.prototype.updateNote = function(_xhr, noteEntity, _status) {
+ Notes.prototype.updateNote = function(noteEntity, $targetNote) {
var $noteEntityEl, $note_li;
// Convert returned HTML to a jQuery object so we can modify it further
$noteEntityEl = $(noteEntity.html);
$noteEntityEl.addClass('fade-in-full');
- this.revertNoteEditForm();
+ this.revertNoteEditForm($targetNote);
gl.utils.localTimeAgo($('.js-timeago', $noteEntityEl));
$noteEntityEl.renderGFM();
$noteEntityEl.find('.js-task-list-container').taskList('enable');
@@ -794,10 +779,14 @@ const normalizeNewlines = function(str) {
Shows the note form below the notes.
*/
- Notes.prototype.replyToDiscussionNote = function(e) {
+ Notes.prototype.onReplyToDiscussionNote = function(e) {
+ this.replyToDiscussionNote(e.target);
+ };
+
+ Notes.prototype.replyToDiscussionNote = function(target) {
var form, replyLink;
form = this.cleanForm(this.formClone.clone());
- replyLink = $(e.target).closest(".js-discussion-reply-button");
+ replyLink = $(target).closest(".js-discussion-reply-button");
// insert the form after the button
replyLink
.closest('.discussion-reply-holder')
@@ -867,35 +856,52 @@ const normalizeNewlines = function(str) {
Sets up the form and shows it.
*/
- Notes.prototype.addDiffNote = function(e) {
- var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
+ Notes.prototype.onAddDiffNote = function(e) {
e.preventDefault();
- $link = $(e.currentTarget || e.target);
+ const $link = $(e.currentTarget || e.target);
+ const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
+ this.toggleDiffNote({
+ target: $link,
+ lineType: $link.data('lineType'),
+ showReplyInput
+ });
+ };
+
+ Notes.prototype.toggleDiffNote = function({
+ target,
+ lineType,
+ forceShow,
+ showReplyInput = false,
+ }) {
+ var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
+ $link = $(target);
row = $link.closest("tr");
- nextRow = row.next();
- hasNotes = nextRow.is(".notes_holder");
+ const nextRow = row.next();
+ let targetRow = row;
+ if (nextRow.is('.notes_holder')) {
+ targetRow = nextRow;
+ }
+
+ hasNotes = targetRow.is(".notes_holder");
addForm = false;
- notesContentSelector = ".notes_content";
+ let lineTypeSelector = '';
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
- isDiffCommentAvatar = $link.hasClass('js-diff-comment-avatar');
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
- lineType = $link.data("lineType");
- notesContentSelector += "." + lineType;
+ lineTypeSelector = `.${lineType}`;
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line old\"></td><td class=\"notes_content parallel old\"><div class=\"content\"></div></td><td class=\"notes_line new\"></td><td class=\"notes_content parallel new\"><div class=\"content\"></div></td></tr>";
}
- notesContentSelector += " .content";
- notesContent = nextRow.find(notesContentSelector);
+ const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
+ let notesContent = targetRow.find(notesContentSelector);
- if (hasNotes && !isDiffCommentAvatar) {
- nextRow.show();
- notesContent = nextRow.find(notesContentSelector);
+ if (hasNotes && showReplyInput) {
+ targetRow.show();
+ notesContent = targetRow.find(notesContentSelector);
if (notesContent.length) {
notesContent.show();
replyButton = notesContent.find(".js-discussion-reply-button:visible");
if (replyButton.length) {
- e.target = replyButton[0];
- $.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
+ this.replyToDiscussionNote(replyButton[0]);
} else {
// In parallel view, the form may not be present in one of the panes
noteForm = notesContent.find(".js-discussion-note-form");
@@ -904,19 +910,19 @@ const normalizeNewlines = function(str) {
}
}
}
- } else if (!isDiffCommentAvatar) {
+ } else if (showReplyInput) {
// add a notes row and insert the form
row.after(rowCssToAdd);
- nextRow = row.next();
- notesContent = nextRow.find(notesContentSelector);
+ targetRow = row.next();
+ notesContent = targetRow.find(notesContentSelector);
addForm = true;
} else {
- nextRow.show();
- notesContent.toggle(!notesContent.is(':visible'));
+ const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
+ const isForced = forceShow === true || forceShow === false;
+ const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
- if (!nextRow.find('.content:not(:empty)').is(':visible')) {
- nextRow.hide();
- }
+ targetRow.toggle(showNow);
+ notesContent.toggle(showNow);
}
if (addForm) {
@@ -1101,6 +1107,15 @@ const normalizeNewlines = function(str) {
});
};
+ Notes.prototype.addFlash = function(...flashParams) {
+ this.flashErrors.push(new Flash(...flashParams));
+ };
+
+ Notes.prototype.clearFlash = function() {
+ this.flashErrors.forEach(flash => flash.flashContainer.remove());
+ this.flashErrors = [];
+ };
+
Notes.prototype.cleanForm = function($form) {
// Remove JS classes that are not needed here
$form
@@ -1115,6 +1130,25 @@ const normalizeNewlines = function(str) {
return $form;
};
+ /**
+ * Check if note does not exists on page
+ */
+ Notes.isNewNote = function(noteEntity, noteIds) {
+ return $.inArray(noteEntity.id, noteIds) === -1;
+ };
+
+ /**
+ * Check if $note already contains the `noteEntity` content
+ */
+ Notes.isUpdatedNote = function(noteEntity, $note) {
+ // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
+ const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
+ const currentNoteText = normalizeNewlines(
+ $note.find('.original-note-content').text().trim()
+ );
+ return sanitizedNoteEntityText !== currentNoteText;
+ };
+
Notes.checkMergeRequestStatus = function() {
if (gl.utils.getPagePath(1) === 'merge_requests') {
gl.mrWidget.checkStatus();
@@ -1170,6 +1204,7 @@ const normalizeNewlines = function(str) {
*/
Notes.prototype.createPlaceholderNote = function({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname }) {
const discussionClass = isDiscussionNote ? 'discussion' : '';
+ const escapedFormContent = _.escape(formContent);
const $tempNote = $(
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
<div class="timeline-entry-inner">
@@ -1183,14 +1218,11 @@ const normalizeNewlines = function(str) {
<span class="hidden-xs">${currentUserFullname}</span>
<span class="note-headline-light">@${currentUsername}</span>
</a>
- <span class="note-headline-light">
- <i class="fa fa-spinner fa-spin" aria-label="Comment is being posted" aria-hidden="true"></i>
- </span>
</div>
</div>
<div class="note-body">
<div class="note-text">
- <p>${formContent}</p>
+ <p>${escapedFormContent}</p>
</div>
</div>
</div>
@@ -1281,6 +1313,8 @@ const normalizeNewlines = function(str) {
.then((note) => {
// Submission successful! remove placeholder
$notesContainer.find(`#${uniqueId}`).remove();
+ // Clear previous form errors
+ this.clearFlashWrapper();
// Check if this was discussion comment
if (isDiscussionForm) {
@@ -1320,7 +1354,7 @@ const normalizeNewlines = function(str) {
// Show form again on UI on failure
if (isDiscussionForm && $notesContainer.length) {
const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
- $.proxy(this.replyToDiscussionNote, replyButton[0], { target: replyButton[0] }).call();
+ this.replyToDiscussionNote(replyButton[0]);
$form = $notesContainer.parent().find('form');
}
@@ -1370,7 +1404,7 @@ const normalizeNewlines = function(str) {
gl.utils.ajaxPost(formAction, formData)
.then((note) => {
// Submission successful! render final note element
- this.updateNote(null, note, null);
+ this.updateNote(note, $editingNote);
})
.fail(() => {
// Submission failed, revert back to original note