gitlab-org--gitlab-foss/app/assets/javascripts/notes.js

1532 lines
55 KiB
JavaScript
Raw Normal View History

/* eslint-disable no-restricted-properties, func-names, space-before-function-paren,
no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase,
no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line,
default-case, prefer-template, consistent-return, no-alert, no-return-assign,
no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new,
brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow,
newline-per-chained-call, no-useless-escape */
/* global Flash */
/* global Autosave */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
2016-07-24 20:45:11 +00:00
2017-05-03 17:41:16 +00:00
import $ from 'jquery';
2017-03-11 06:45:34 +00:00
import Cookies from 'js-cookie';
import autosize from 'vendor/autosize';
import Dropzone from 'dropzone';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import CommentTypeToggle from './comment_type_toggle';
import './autosave';
import './dropzone_input';
import './task_list';
2017-03-11 06:45:34 +00:00
window.autosize = autosize;
window.Dropzone = Dropzone;
2016-07-24 20:45:11 +00:00
2017-05-03 17:41:16 +00:00
const normalizeNewlines = function(str) {
return str.replace(/\r\n/g, '\n');
};
2016-07-24 20:45:11 +00:00
(function() {
this.Notes = (function() {
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
2016-07-24 20:45:11 +00:00
Notes.interval = null;
function Notes(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
2017-05-08 19:09:34 +00:00
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.onAddDiffNote = this.onAddDiffNote.bind(this);
2017-05-08 19:09:34 +00:00
this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
2017-05-08 19:09:34 +00:00
this.removeNote = this.removeNote.bind(this);
this.cancelEdit = this.cancelEdit.bind(this);
this.updateNote = this.updateNote.bind(this);
this.addDiscussionNote = this.addDiscussionNote.bind(this);
this.addNoteError = this.addNoteError.bind(this);
this.addNote = this.addNote.bind(this);
this.resetMainTargetForm = this.resetMainTargetForm.bind(this);
this.refresh = this.refresh.bind(this);
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.onHashChange = this.onHashChange.bind(this);
2017-05-03 17:41:16 +00:00
2016-07-24 20:45:11 +00:00
this.notes_url = notes_url;
this.note_ids = note_ids;
this.enableGFM = enableGFM;
2017-05-03 17:41:16 +00:00
// Used to keep track of updated notes while people are editing things
this.updatedNotesTrackingMap = {};
2016-07-24 20:45:11 +00:00
this.last_fetched_at = last_fetched_at;
this.noteable_url = document.URL;
this.notesCountBadge || (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
2016-07-24 20:45:11 +00:00
this.basePollingInterval = 15000;
this.maxPollingSteps = 4;
2017-05-03 17:41:16 +00:00
2016-07-24 20:45:11 +00:00
this.cleanBinding();
this.addBinding();
this.setPollingInterval();
this.setupMainTargetNoteForm();
this.taskList = new gl.TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes'
});
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
this.collapseLongCommitList();
this.setViewType(view);
// We are in the Merge Requests page so we need another edit form for Changes tab
if (gl.utils.getPagePath(1) === 'merge_requests') {
$('.note-edit-form').clone()
2016-12-08 21:49:32 +00:00
.addClass('mr-note-edit-form').insertAfter('.note-edit-form');
}
2016-07-24 20:45:11 +00:00
}
Notes.prototype.setViewType = function(view) {
this.view = Cookies.get('diff_view') || view;
};
2016-07-24 20:45:11 +00:00
Notes.prototype.addBinding = function() {
// Edit note link
$(document).on('click', '.js-note-edit', this.showEditForm.bind(this));
$(document).on('click', '.note-edit-cancel', this.cancelEdit);
// Reopen and close actions for Issue/MR combined with note form submit
$(document).on('click', '.js-comment-submit-button', this.postComment);
$(document).on('click', '.js-comment-save-button', this.updateComment);
$(document).on('keyup input', '.js-note-text', this.updateTargetButtons);
// resolve a discussion
2017-05-05 10:57:29 +00:00
$(document).on('click', '.js-comment-resolve-button', this.postComment);
// remove a note (in general)
$(document).on('click', '.js-note-delete', this.removeNote);
// delete note attachment
$(document).on('click', '.js-note-attachment-delete', this.removeAttachment);
// reset main target form when clicking discard
$(document).on('click', '.js-note-discard', this.resetMainTargetForm);
// 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.onReplyToDiscussionNote);
// add diff note
$(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote);
// hide diff note form
$(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
// toggle commit list
$(document).on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
// fetch notes when tab becomes visible
$(document).on('visibilitychange', this.visibilityChange);
// when issue status changes, we need to refresh data
$(document).on('issuable:change', this.refresh);
2017-05-05 10:57:29 +00:00
// ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
$(document).on('ajax:success', '.js-main-target-form', this.addNote);
$(document).on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
$(document).on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
$(document).on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
// when a key is clicked on the notes
$(document).on('keydown', '.js-note-text', this.keydownNoteText);
// When the URL fragment/hash has changed, `#note_xxx`
return $(window).on('hashchange', this.onHashChange);
2016-07-24 20:45:11 +00:00
};
Notes.prototype.cleanBinding = function() {
$(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('click', '.js-discussion-reply-button');
$(document).off('click', '.js-add-diff-note-button');
$(document).off('visibilitychange');
$(document).off('keyup input', '.js-note-text');
$(document).off('click', '.js-note-target-reopen');
$(document).off('click', '.js-note-target-close');
$(document).off('click', '.js-note-discard');
$(document).off('keydown', '.js-note-text');
$(document).off('click', '.js-comment-resolve-button');
$(document).off('click', '.system-note-commit-list-toggler');
$(document).off('ajax:success', '.js-main-target-form');
$(document).off('ajax:success', '.js-discussion-note-form');
$(document).off('ajax:complete', '.js-main-target-form');
$(window).off('hashchange', this.onHashChange);
2016-07-24 20:45:11 +00:00
};
2017-04-07 13:09:15 +00:00
Notes.initCommentTypeToggle = function (form) {
const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
const noteTypeInput = form.querySelector('#note_type');
const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
const closeButton = form.querySelector('.js-note-target-close');
2017-04-07 21:21:21 +00:00
const reopenButton = form.querySelector('.js-note-target-reopen');
2017-04-07 13:09:15 +00:00
const commentTypeToggle = new CommentTypeToggle({
dropdownTrigger,
dropdownList,
noteTypeInput,
submitButton,
closeButton,
reopenButton,
});
2017-04-07 13:09:15 +00:00
commentTypeToggle.initDroplab();
};
2016-07-24 20:45:11 +00:00
Notes.prototype.keydownNoteText = function(e) {
var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
if (gl.utils.isMetaKey(e)) {
2016-07-24 20:45:11 +00:00
return;
}
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
2016-07-24 20:45:11 +00:00
$textarea = $(e.target);
// Edit previous note when UP arrow is hit
2016-07-24 20:45:11 +00:00
switch (e.which) {
case 38:
if ($textarea.val() !== '') {
return;
}
myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, .notes_holder, #notes'));
2016-07-24 20:45:11 +00:00
if (myLastNote.length) {
myLastNoteEditBtn = myLastNote.find('.js-note-edit');
return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
}
break;
// Cancel creating diff note or editing any note when ESCAPE is hit
2016-07-24 20:45:11 +00:00
case 27:
discussionNoteForm = $textarea.closest('.js-discussion-note-form');
if (discussionNoteForm.length) {
if ($textarea.val() !== '') {
if (!confirm('Are you sure you want to cancel creating this comment?')) {
return;
}
}
this.removeDiscussionNoteForm(discussionNoteForm);
return;
}
editNote = $textarea.closest('.note');
if (editNote.length) {
originalText = $textarea.closest('form').data('original-note');
newText = $textarea.val();
if (originalText !== newText) {
if (!confirm('Are you sure you want to cancel editing this comment?')) {
return;
}
}
return this.removeNoteEditForm(editNote);
}
}
};
Notes.prototype.initRefresh = function() {
clearInterval(Notes.interval);
return Notes.interval = setInterval((function(_this) {
return function() {
return _this.refresh();
};
})(this), this.pollingInterval);
};
Notes.prototype.refresh = function() {
2017-03-13 22:13:12 +00:00
if (!document.hidden) {
2016-07-24 20:45:11 +00:00
return this.getContent();
}
};
Notes.prototype.getContent = function() {
if (this.refreshing) {
return;
}
this.refreshing = true;
return $.ajax({
url: this.notes_url,
headers: { 'X-Last-Fetched-At': this.last_fetched_at },
dataType: 'json',
2016-07-24 20:45:11 +00:00
success: (function(_this) {
return function(data) {
var notes;
notes = data.notes;
_this.last_fetched_at = data.last_fetched_at;
_this.setPollingInterval(data.notes.length);
return $.each(notes, function(i, note) {
_this.renderNote(note);
2016-07-24 20:45:11 +00:00
});
};
})(this)
}).always((function(_this) {
return function() {
return _this.refreshing = false;
};
})(this));
};
/*
Increase @pollingInterval up to 120 seconds on every function call,
if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
will reset to @basePollingInterval.
2016-07-24 20:45:11 +00:00
Note: this function is used to gradually increase the polling interval
if there aren't new notes coming from the server
*/
Notes.prototype.setPollingInterval = function(shouldReset) {
var nthInterval;
if (shouldReset == null) {
shouldReset = true;
}
nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
if (shouldReset) {
this.pollingInterval = this.basePollingInterval;
} else if (this.pollingInterval < nthInterval) {
this.pollingInterval *= 2;
}
return this.initRefresh();
};
Notes.prototype.handleQuickActions = function(noteEntity) {
var votesBlock;
2017-05-03 17:41:16 +00:00
if (noteEntity.commands_changes) {
if ('merge' in noteEntity.commands_changes) {
2017-05-09 04:15:34 +00:00
Notes.checkMergeRequestStatus();
}
2017-05-03 17:41:16 +00:00
if ('emoji_award' in noteEntity.commands_changes) {
votesBlock = $('.js-awards-block').eq(0);
2017-05-03 17:41:16 +00:00
gl.awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
return gl.awardsHandler.scrollToAwards();
}
}
};
Notes.prototype.setupNewNote = function($note) {
// Update datetime format on the recent note
gl.utils.localTimeAgo($note.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
// This stops the note highlight, #note_xxx`, from being removed after real time update
// The `:target` selector does not re-evaluate after we replace element in the DOM
Notes.updateNoteTargetSelector($note);
this.$noteToCleanHighlight = $note;
};
Notes.prototype.onHashChange = function() {
if (this.$noteToCleanHighlight) {
Notes.updateNoteTargetSelector(this.$noteToCleanHighlight);
}
this.$noteToCleanHighlight = null;
};
Notes.updateNoteTargetSelector = function($note) {
const hash = gl.utils.getLocationHash();
// Needs to be an explicit true/false for the jQuery `toggleClass(force)`
const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
$note.toggleClass('target', addTargetClass);
};
2016-07-24 20:45:11 +00:00
/*
Render note in main comments area.
2016-07-24 20:45:11 +00:00
Note: for rendering inline notes use renderDiscussionNote
*/
2017-05-03 17:41:16 +00:00
Notes.prototype.renderNote = function(noteEntity, $form, $notesList = $('.main-notes-list')) {
if (noteEntity.discussion_html) {
2017-05-03 17:41:16 +00:00
return this.renderDiscussionNote(noteEntity, $form);
}
2017-05-03 17:41:16 +00:00
if (!noteEntity.valid) {
if (noteEntity.errors.commands_only) {
this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
this.refresh();
2016-07-24 20:45:11 +00:00
}
return;
}
2017-05-03 17:41:16 +00:00
const $note = $notesList.find(`#note_${noteEntity.id}`);
if (Notes.isNewNote(noteEntity, this.note_ids)) {
2017-05-03 17:41:16 +00:00
this.note_ids.push(noteEntity.id);
if ($notesList.length) {
$notesList.find('.system-note.being-posted').remove();
}
2017-05-03 17:41:16 +00:00
const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
this.setupNewNote($newNote);
this.refresh();
2016-07-24 20:45:11 +00:00
return this.updateNotesCount(1);
}
2017-05-03 17:41:16 +00:00
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
else if (Notes.isUpdatedNote(noteEntity, $note)) {
2017-05-03 17:41:16 +00:00
const isEditing = $note.hasClass('is-editing');
const initialContent = normalizeNewlines(
$note.find('.original-note-content').text().trim()
);
const $textarea = $note.find('.js-note-text');
const currentContent = $textarea.val();
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
const isTextareaUntouched = currentContent === initialContent || currentContent === sanitizedNoteNote;
if (isEditing && isTextareaUntouched) {
$textarea.val(noteEntity.note);
this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
}
else if (isEditing && !isTextareaUntouched) {
this.putConflictEditWarningInPlace(noteEntity, $note);
this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
}
else {
const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
this.setupNewNote($updatedNote);
2017-05-03 17:41:16 +00:00
}
}
2016-07-24 20:45:11 +00:00
};
Notes.prototype.isParallelView = function() {
return Cookies.get('diff_view') === 'parallel';
2016-07-24 20:45:11 +00:00
};
/*
Render note in discussion area.
2016-07-24 20:45:11 +00:00
Note: for rendering inline notes use renderDiscussionNote
*/
2017-05-03 17:41:16 +00:00
Notes.prototype.renderDiscussionNote = function(noteEntity, $form) {
var discussionContainer, form, row, lineType, diffAvatarContainer;
if (!Notes.isNewNote(noteEntity, this.note_ids)) {
2016-07-24 20:45:11 +00:00
return;
}
2017-05-03 17:41:16 +00:00
this.note_ids.push(noteEntity.id);
form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
row = form.closest('tr');
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion?
2017-05-03 17:41:16 +00:00
discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
2017-03-31 01:33:45 +00:00
if (!discussionContainer.length) {
discussionContainer = form.closest('.discussion').find('.notes');
2016-07-24 20:45:11 +00:00
}
if (discussionContainer.length === 0) {
2017-05-03 17:41:16 +00:00
if (noteEntity.diff_discussion_html) {
var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
// insert the note and the reply button after the temp row
row.after($discussion);
} else {
// Merge new discussion HTML in
var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
var contentContainerClass = '.' + $notes.closest('.notes_content')
.attr('class')
.split(' ')
.join('.');
row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
}
}
// Init discussion on 'Discussion' page if it is merge request page
2017-05-03 17:41:16 +00:00
const page = $('body').attr('data-page');
if ((page && page.indexOf('projects:merge_request') !== -1) || !noteEntity.diff_discussion_html) {
2017-05-03 17:41:16 +00:00
Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
2016-07-24 20:45:11 +00:00
}
} else {
// append new note to all matching discussions
2017-05-03 17:41:16 +00:00
Notes.animateAppendNote(noteEntity.html, discussionContainer);
2016-07-24 20:45:11 +00:00
}
2017-05-03 17:41:16 +00:00
if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
gl.diffNotesCompileComponents();
2017-05-03 17:41:16 +00:00
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
}
gl.utils.localTimeAgo($('.js-timeago'), false);
2017-05-09 04:15:34 +00:00
Notes.checkMergeRequestStatus();
2016-07-24 20:45:11 +00:00
return this.updateNotesCount(1);
};
Notes.prototype.getLineHolder = function(changesDiscussionContainer) {
return $(changesDiscussionContainer).closest('.notes_holder')
.prevAll('.line_holder')
.first()
.get(0);
};
2017-05-03 17:41:16 +00:00
Notes.prototype.renderDiscussionAvatar = function(diffAvatarContainer, noteEntity) {
var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
if (!avatarHolder.length) {
avatarHolder = document.createElement('diff-note-avatars');
2017-05-03 17:41:16 +00:00
avatarHolder.setAttribute('discussion-id', noteEntity.discussion_id);
diffAvatarContainer.append(avatarHolder);
gl.diffNotesCompileComponents();
}
if (commentButton.length) {
commentButton.remove();
}
};
2016-07-24 20:45:11 +00:00
/*
Called in response the main target form has been successfully submitted.
2016-07-24 20:45:11 +00:00
Removes any errors.
Resets text and preview.
Resets buttons.
*/
Notes.prototype.resetMainTargetForm = function(e) {
var form;
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();
2016-08-31 14:32:48 +00:00
var event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
form.find('.js-autosize')[0].dispatchEvent(event);
this.updateTargetButtons(e);
2016-07-24 20:45:11 +00:00
};
Notes.prototype.reenableTargetFormSubmitButton = function() {
var form;
form = $('.js-main-target-form');
return form.find('.js-note-text').trigger('input');
2016-07-24 20:45:11 +00:00
};
/*
Shows the main form and does some setup on it.
2016-07-24 20:45:11 +00:00
Sets some hidden fields in the form.
*/
Notes.prototype.setupMainTargetNoteForm = function() {
var form;
// find the form
form = $('.js-new-note-form');
// Set a global clone of the form for later cloning
2016-07-24 20:45:11 +00:00
this.formClone = form.clone();
// show the form
2016-07-24 20:45:11 +00:00
this.setupNoteForm(form);
// fix classes
form.removeClass('js-new-note-form');
form.addClass('js-main-target-form');
form.find('#note_line_code').remove();
form.find('#note_position').remove();
form.find('#note_type').val('');
form.find('#in_reply_to_discussion_id').remove();
form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
this.parentTimeline = form.parents('.timeline');
if (form.length) {
2017-04-07 13:09:15 +00:00
Notes.initCommentTypeToggle(form.get(0));
}
2016-07-24 20:45:11 +00:00
};
/*
General note form setup.
2016-07-24 20:45:11 +00:00
deactivates the submit button when text is empty
hides the preview button when text is empty
setup GFM auto complete
show the form
*/
Notes.prototype.setupNoteForm = function(form) {
var textarea, key;
new gl.GLForm(form, this.enableGFM);
textarea = form.find('.js-note-text');
key = [
'Note',
form.find('#note_noteable_type').val(),
form.find('#note_noteable_id').val(),
form.find('#note_commit_id').val(),
form.find('#note_type').val(),
form.find('#in_reply_to_discussion_id').val(),
// LegacyDiffNote
form.find('#note_line_code').val(),
// DiffNote
form.find('#note_position').val()
];
return new Autosave(textarea, key);
2016-07-24 20:45:11 +00:00
};
/*
Called in response to the new note form being submitted
2016-07-24 20:45:11 +00:00
Adds new note to list.
*/
2017-05-05 10:57:29 +00:00
Notes.prototype.addNote = function($form, note) {
2016-07-24 20:45:11 +00:00
return this.renderNote(note);
};
Notes.prototype.addNoteError = function($form) {
2017-05-05 10:57:29 +00:00
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 this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline);
2016-07-24 20:45:11 +00:00
};
2017-05-05 10:57:29 +00:00
Notes.prototype.updateNoteError = $parentTimeline => new Flash('Your comment could not be updated! Please check your network connection and try again.');
2016-07-24 20:45:11 +00:00
/*
Called in response to the new note form being submitted
2016-07-24 20:45:11 +00:00
Adds new note to list.
*/
2017-05-05 10:57:29 +00:00
Notes.prototype.addDiscussionNote = function($form, note, isNewDiffComment) {
if ($form.attr('data-resolve-all') != null) {
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
var projectPath = $form.data('project-path');
var discussionId = $form.data('discussion-id');
var mergeRequestId = $form.data('noteable-iid');
if (ResolveService != null) {
ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
}
}
2017-03-31 01:33:45 +00:00
this.renderNote(note, $form);
// cleanup after successfully creating a diff/discussion note
2017-05-05 10:57:29 +00:00
if (isNewDiffComment) {
this.removeDiscussionNoteForm($form);
}
2016-07-24 20:45:11 +00:00
};
/*
Called in response to the edit note form being submitted
2016-07-24 20:45:11 +00:00
Updates the current note field.
*/
Notes.prototype.updateNote = function(noteEntity, $targetNote) {
2017-05-05 10:57:29 +00:00
var $noteEntityEl, $note_li;
// Convert returned HTML to a jQuery object so we can modify it further
2017-05-05 10:57:29 +00:00
$noteEntityEl = $(noteEntity.html);
$noteEntityEl.addClass('fade-in-full');
this.revertNoteEditForm($targetNote);
2017-05-05 10:57:29 +00:00
$noteEntityEl.renderGFM();
// Find the note's `li` element by ID and replace it with the updated HTML
2017-05-03 17:41:16 +00:00
$note_li = $('.note-row-' + noteEntity.id);
2017-05-05 10:57:29 +00:00
$note_li.replaceWith($noteEntityEl);
this.setupNewNote($noteEntityEl);
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
2016-07-24 20:45:11 +00:00
};
Notes.prototype.checkContentToAllowEditing = function($el) {
var initialContent = $el.find('.original-note-content').text().trim();
2017-05-03 17:41:16 +00:00
var currentContent = $el.find('.js-note-text').val();
var isAllowed = true;
if (currentContent === initialContent) {
this.removeNoteEditForm($el);
}
else {
var $buttons = $el.find('.note-form-actions');
2017-01-02 17:14:43 +00:00
var isWidgetVisible = gl.utils.isInViewport($el.get(0));
if (!isWidgetVisible) {
2017-01-04 21:44:00 +00:00
gl.utils.scrollToElement($el);
}
2017-05-03 17:41:16 +00:00
$el.find('.js-finish-edit-warning').show();
isAllowed = false;
}
return isAllowed;
};
2016-07-24 20:45:11 +00:00
/*
Called in response to clicking the edit note link
2016-07-24 20:45:11 +00:00
Replaces the note text with the note edit form
Adds a data attribute to the form with the original content of the note for cancellations
*/
2016-07-24 20:45:11 +00:00
Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
e.preventDefault();
var $target = $(e.target);
var $editForm = $(this.getEditFormSelector($target));
var $note = $target.closest('.note');
2017-05-03 17:41:16 +00:00
var $currentlyEditing = $('.note.is-editing:visible');
if ($currentlyEditing.length) {
var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
if (!isEditAllowed) {
return;
}
}
$note.find('.js-note-attachment-delete').show();
$editForm.addClass('current-note-edit-form');
2017-05-03 17:41:16 +00:00
$note.addClass('is-editing');
this.putEditFormInPlace($target);
2016-07-24 20:45:11 +00:00
};
/*
Called in response to clicking the edit note link
2016-07-24 20:45:11 +00:00
Hides edit form and restores the original note text to the editor textarea.
*/
Notes.prototype.cancelEdit = function(e) {
e.preventDefault();
2017-05-03 17:41:16 +00:00
const $target = $(e.target);
const $note = $target.closest('.note');
const noteId = $note.attr('data-note-id');
this.revertNoteEditForm($target);
2017-05-03 17:41:16 +00:00
if (this.updatedNotesTrackingMap[noteId]) {
const $newNote = $(this.updatedNotesTrackingMap[noteId].html);
$note.replaceWith($newNote);
this.setupNewNote($newNote);
// Now that we have taken care of the update, clear it out
delete this.updatedNotesTrackingMap[noteId];
2017-05-03 17:41:16 +00:00
}
else {
$note.find('.js-finish-edit-warning').hide();
this.removeNoteEditForm($note);
}
2016-07-24 20:45:11 +00:00
};
Notes.prototype.revertNoteEditForm = function($target) {
2017-05-03 17:41:16 +00:00
$target = $target || $('.note.is-editing:visible');
var selector = this.getEditFormSelector($target);
var $editForm = $(selector);
2016-11-14 20:12:51 +00:00
$editForm.insertBefore('.notes-form');
2017-05-05 10:57:29 +00:00
$editForm.find('.js-comment-save-button').enable();
2017-05-03 17:41:16 +00:00
$editForm.find('.js-finish-edit-warning').hide();
};
2016-11-14 20:12:51 +00:00
Notes.prototype.getEditFormSelector = function($el) {
2016-12-08 21:49:32 +00:00
var selector = '.note-edit-form:not(.mr-note-edit-form)';
if ($el.parents('#diffs').length) {
2016-12-08 21:49:32 +00:00
selector = '.note-edit-form.mr-note-edit-form';
}
return selector;
};
2017-05-03 17:41:16 +00:00
Notes.prototype.removeNoteEditForm = function($note) {
var form = $note.find('.current-note-edit-form');
$note.removeClass('is-editing');
form.removeClass('current-note-edit-form');
2017-05-03 17:41:16 +00:00
form.find('.js-finish-edit-warning').hide();
// Replace markdown textarea text with original note text.
return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
2016-07-24 20:45:11 +00:00
};
/*
Called in response to deleting a note of any kind.
2016-07-24 20:45:11 +00:00
Removes the actual note from view.
Removes the whole discussion if the last note is being removed.
*/
Notes.prototype.removeNote = function(e) {
var noteElId, noteId, dataNoteId, $note, lineHolder;
$note = $(e.currentTarget).closest('.note');
noteElId = $note.attr('id');
noteId = $note.attr('data-note-id');
lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
.closest('.notes_holder')
.prev('.line_holder');
$(`.note[id="${noteElId}"]`).each((function(_this) {
// A same note appears in the "Discussion" and in the "Changes" tab, we have
// to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
// where $('#noteId') would return only one.
2016-07-24 20:45:11 +00:00
return function(i, el) {
2017-05-03 17:41:16 +00:00
var $note, $notes;
$note = $(el);
$notes = $note.closest('.discussion-notes');
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (gl.diffNoteApps[noteElId]) {
gl.diffNoteApps[noteElId].$destroy();
}
}
2017-05-03 17:41:16 +00:00
$note.remove();
// check if this is the last note for this line
if ($notes.find('.note').length === 0) {
var notesTr = $notes.closest('tr');
// "Discussions" tab
$notes.closest('.timeline-entry').remove();
// The notes tr can contain multiple lists of notes, like on the parallel diff
if (notesTr.find('.discussion-notes').length > 1) {
2017-05-03 17:41:16 +00:00
$notes.remove();
} else {
notesTr.remove();
}
2016-07-24 20:45:11 +00:00
}
};
})(this));
2017-05-09 04:15:34 +00:00
Notes.checkMergeRequestStatus();
2016-07-24 20:45:11 +00:00
return this.updateNotesCount(-1);
};
/*
Called in response to clicking the delete attachment link
2016-07-24 20:45:11 +00:00
Removes the attachment wrapper view, including image tag if it exists
Resets the note editing form
*/
Notes.prototype.removeAttachment = function() {
const $note = $(this).closest('.note');
$note.find('.note-attachment').remove();
$note.find('.note-body > .note-text').show();
$note.find('.note-header').show();
return $note.find('.current-note-edit-form').remove();
2016-07-24 20:45:11 +00:00
};
/*
Called when clicking on the "reply" button for a diff line.
2016-07-24 20:45:11 +00:00
Shows the note form below the notes.
*/
Notes.prototype.onReplyToDiscussionNote = function(e) {
this.replyToDiscussionNote(e.target);
};
Notes.prototype.replyToDiscussionNote = function(target) {
2016-07-24 20:45:11 +00:00
var form, replyLink;
2017-04-06 13:48:10 +00:00
form = this.cleanForm(this.formClone.clone());
replyLink = $(target).closest('.js-discussion-reply-button');
// insert the form after the button
replyLink
.closest('.discussion-reply-holder')
.hide()
.after(form);
// show the form
2016-07-24 20:45:11 +00:00
return this.setupDiscussionNoteForm(replyLink, form);
};
/*
Shows the diff or discussion form and does some setup on it.
2016-07-24 20:45:11 +00:00
Sets some hidden fields in the form.
Note: dataHolder must have the "discussionId" and "lineCode" data attributes set.
2016-07-24 20:45:11 +00:00
*/
Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
// setup note target
var discussionID = dataHolder.data('discussionId');
2017-03-31 01:33:45 +00:00
if (discussionID) {
form.attr('data-discussion-id', discussionID);
form.find('#in_reply_to_discussion_id').val(discussionID);
2017-03-31 01:33:45 +00:00
}
form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType'));
form.find('#note_noteable_type').val(dataHolder.data('noteableType'));
form.find('#note_noteable_id').val(dataHolder.data('noteableId'));
form.find('#note_commit_id').val(dataHolder.data('commitId'));
form.find('#note_type').val(dataHolder.data('noteType'));
// LegacyDiffNote
form.find('#note_line_code').val(dataHolder.data('lineCode'));
// DiffNote
form.find('#note_position').val(dataHolder.attr('data-position'));
2016-07-24 20:45:11 +00:00
form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancel-text'));
2016-07-27 09:17:05 +00:00
form.find('.js-note-target-close').remove();
form.find('.js-note-new-discussion').remove();
2016-07-24 20:45:11 +00:00
this.setupNoteForm(form);
2017-03-31 21:52:38 +00:00
form
.removeClass('js-main-target-form')
.addClass('discussion-form js-discussion-note-form');
2017-03-31 21:52:38 +00:00
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
var $commentBtn = form.find('comment-and-resolve-btn');
2017-04-07 18:37:12 +00:00
$commentBtn.attr(':discussion-id', `'${discussionID}'`);
2016-11-07 10:05:58 +00:00
gl.diffNotesCompileComponents();
}
form.find('.js-note-text').focus();
form
.find('.js-comment-resolve-button')
2017-03-31 01:33:45 +00:00
.attr('data-discussion-id', discussionID);
2016-07-24 20:45:11 +00:00
};
/*
Called when clicking on the "add a comment" button on the side of a diff line.
2016-07-24 20:45:11 +00:00
Inserts a temporary row for the form below the line.
Sets up the form and shows it.
*/
Notes.prototype.onAddDiffNote = function(e) {
2016-07-24 20:45:11 +00:00
e.preventDefault();
const link = e.currentTarget || e.target;
const $link = $(link);
const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
this.toggleDiffNote({
target: $link,
lineType: link.dataset.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');
const nextRow = row.next();
let targetRow = row;
if (nextRow.is('.notes_holder')) {
targetRow = nextRow;
}
hasNotes = nextRow.is('.notes_holder');
2016-07-24 20:45:11 +00:00
addForm = false;
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>';
// In parallel view, look inside the correct left/right pane
2016-07-24 20:45:11 +00:00
if (this.isParallelView()) {
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>';
2016-07-24 20:45:11 +00:00
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
if (hasNotes && showReplyInput) {
targetRow.show();
notesContent = targetRow.find(notesContentSelector);
2016-07-24 20:45:11 +00:00
if (notesContent.length) {
notesContent.show();
replyButton = notesContent.find('.js-discussion-reply-button:visible');
2016-07-24 20:45:11 +00:00
if (replyButton.length) {
this.replyToDiscussionNote(replyButton[0]);
2016-07-24 20:45:11 +00:00
} else {
// In parallel view, the form may not be present in one of the panes
noteForm = notesContent.find('.js-discussion-note-form');
2016-07-24 20:45:11 +00:00
if (noteForm.length === 0) {
addForm = true;
}
}
}
} else if (showReplyInput) {
// add a notes row and insert the form
2016-07-24 20:45:11 +00:00
row.after(rowCssToAdd);
targetRow = row.next();
notesContent = targetRow.find(notesContentSelector);
2016-07-24 20:45:11 +00:00
addForm = true;
} else {
const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
const isForced = forceShow === true || forceShow === false;
const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
targetRow.toggle(showNow);
notesContent.toggle(showNow);
2016-07-24 20:45:11 +00:00
}
2016-07-24 20:45:11 +00:00
if (addForm) {
newForm = this.cleanForm(this.formClone.clone());
newForm.appendTo(notesContent);
// show the form
2016-07-24 20:45:11 +00:00
return this.setupDiscussionNoteForm($link, newForm);
}
};
/*
Called in response to "cancel" on a diff note form.
2016-07-24 20:45:11 +00:00
Shows the reply button again.
Removes the form and if necessary it's temporary row.
*/
Notes.prototype.removeDiscussionNoteForm = function(form) {
var glForm, row;
row = form.closest('tr');
2016-07-24 20:45:11 +00:00
glForm = form.data('gl-form');
glForm.destroy();
form.find('.js-note-text').data('autosave').reset();
// show the reply button (will only work for replies)
form
.prev('.discussion-reply-holder')
.show();
if (row.is('.js-temp-notes-holder')) {
// remove temporary row for diff lines
2016-07-24 20:45:11 +00:00
return row.remove();
} else {
// only remove the form
2016-07-24 20:45:11 +00:00
return form.remove();
}
};
Notes.prototype.cancelDiscussionForm = function(e) {
var form;
e.preventDefault();
form = $(e.target).closest('.js-discussion-note-form');
2016-07-24 20:45:11 +00:00
return this.removeDiscussionNoteForm(form);
};
/*
Called after an attachment file has been selected.
2016-07-24 20:45:11 +00:00
Updates the file name for the selected attachment.
*/
Notes.prototype.updateFormAttachment = function() {
var filename, form;
form = $(this).closest('form');
// get only the basename
filename = $(this).val().replace(/^.*[\\\/]/, '');
return form.find('.js-attachment-filename').text(filename);
2016-07-24 20:45:11 +00:00
};
/*
Called when the tab visibility changes
*/
Notes.prototype.visibilityChange = function() {
return this.refresh();
};
Notes.prototype.updateTargetButtons = function(e) {
var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
textarea = $(e.target);
form = textarea.parents('form');
reopenbtn = form.find('.js-note-target-reopen');
closebtn = form.find('.js-note-target-close');
discardbtn = form.find('.js-note-discard');
2017-04-07 13:09:15 +00:00
2016-07-24 20:45:11 +00:00
if (textarea.val().trim().length > 0) {
2017-04-07 13:09:15 +00:00
reopentext = reopenbtn.attr('data-alternative-text');
2017-04-06 09:28:25 +00:00
closetext = closebtn.attr('data-alternative-text');
2016-07-24 20:45:11 +00:00
if (reopenbtn.text() !== reopentext) {
reopenbtn.text(reopentext);
}
if (closebtn.text() !== closetext) {
closebtn.text(closetext);
}
if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
reopenbtn.addClass('btn-comment-and-reopen');
}
if (closebtn.is(':not(.btn-comment-and-close)')) {
closebtn.addClass('btn-comment-and-close');
}
if (discardbtn.is(':hidden')) {
return discardbtn.show();
}
} else {
reopentext = reopenbtn.data('original-text');
closetext = closebtn.data('original-text');
if (reopenbtn.text() !== reopentext) {
reopenbtn.text(reopentext);
}
if (closebtn.text() !== closetext) {
closebtn.text(closetext);
}
if (reopenbtn.is('.btn-comment-and-reopen')) {
reopenbtn.removeClass('btn-comment-and-reopen');
}
if (closebtn.is('.btn-comment-and-close')) {
closebtn.removeClass('btn-comment-and-close');
}
if (discardbtn.is(':visible')) {
return discardbtn.hide();
}
}
};
Notes.prototype.putEditFormInPlace = function($el) {
var $editForm = $(this.getEditFormSelector($el));
var $note = $el.closest('.note');
$editForm.insertAfter($note.find('.note-text'));
var $originalContentEl = $note.find('.original-note-content');
var originalContent = $originalContentEl.text().trim();
var postUrl = $originalContentEl.data('post-url');
var targetId = $originalContentEl.data('target-id');
var targetType = $originalContentEl.data('target-type');
new gl.GLForm($editForm.find('form'), this.enableGFM);
$editForm.find('form')
.attr('action', postUrl)
.attr('data-remote', 'true');
2017-01-02 17:14:43 +00:00
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
$editForm.find('.js-note-text').focus().val(originalContent);
2017-01-04 21:44:00 +00:00
$editForm.find('.js-md-write-button').trigger('click');
$editForm.find('.referenced-users').hide();
};
2017-05-03 17:41:16 +00:00
Notes.prototype.putConflictEditWarningInPlace = function(noteEntity, $note) {
if ($note.find('.js-conflict-edit-warning').length === 0) {
const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
This comment has changed since you started editing, please review the
<a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
updated comment
</a>
to ensure information is not lost
</div>`);
$alert.insertAfter($note.find('.note-text'));
}
};
2016-07-24 20:45:11 +00:00
Notes.prototype.updateNotesCount = function(updateCount) {
return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
2016-07-24 20:45:11 +00:00
};
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
Notes.prototype.toggleCommitList = function(e) {
const $element = $(e.currentTarget);
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
$element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 06:50:28 +00:00
$closestSystemCommitList.toggleClass('hide-shade');
};
/**
Scans system notes with `ul` elements in system note body
then collapse long commit list pushed by user to make it less
intrusive.
*/
Notes.prototype.collapseLongCommitList = function() {
const systemNotes = $('#notes-list').find('li.system-note').has('ul');
$.each(systemNotes, function(index, systemNote) {
const $systemNote = $(systemNote);
const headerMessage = $systemNote.find('.note-text').find('p:first').text().replace(':', '');
$systemNote.find('.note-header .system-note-message').html(headerMessage);
if ($systemNote.find('li').length > MAX_VISIBLE_COMMIT_LIST_COUNT) {
$systemNote.find('.note-text').addClass('system-note-commit-list');
$systemNote.find('.system-note-commit-list-toggler').show();
} else {
$systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
}
});
};
Notes.prototype.addFlash = function(...flashParams) {
this.flashInstance = new Flash(...flashParams);
};
Notes.prototype.clearFlash = function() {
if (this.flashInstance && this.flashInstance.flashContainer) {
this.flashInstance.flashContainer.hide();
this.flashInstance = null;
}
};
Notes.prototype.cleanForm = function($form) {
// Remove JS classes that are not needed here
$form
.find('.js-comment-type-dropdown')
.removeClass('btn-group');
// Remove dropdown
$form
.find('.dropdown-menu')
.remove();
return $form;
2017-04-06 13:48:10 +00:00
};
/**
* 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').first().text().trim()
);
return sanitizedNoteEntityText !== currentNoteText;
};
2017-05-09 04:15:34 +00:00
Notes.checkMergeRequestStatus = function() {
if (gl.utils.getPagePath(1) === 'merge_requests') {
gl.mrWidget.checkStatus();
}
};
2017-05-03 17:41:16 +00:00
Notes.animateAppendNote = function(noteHtml, $notesList) {
const $note = $(noteHtml);
2017-05-05 10:57:29 +00:00
$note.addClass('fade-in-full').renderGFM();
$notesList.append($note);
2017-05-03 17:41:16 +00:00
return $note;
};
Notes.animateUpdateNote = function(noteHtml, $note) {
const $updatedNote = $(noteHtml);
$updatedNote.addClass('fade-in').renderGFM();
$note.replaceWith($updatedNote);
return $updatedNote;
};
2017-05-05 10:57:29 +00:00
/**
* Get data from Form attributes to use for saving/submitting comment.
*/
Notes.prototype.getFormData = function($form) {
return {
formData: $form.serialize(),
formContent: _.escape($form.find('.js-note-text').val()),
2017-05-05 10:57:29 +00:00
formAction: $form.attr('action'),
};
};
/**
* Identify if comment has any quick actions
2017-05-05 10:57:29 +00:00
*/
Notes.prototype.hasQuickActions = function(formContent) {
return REGEX_QUICK_ACTIONS.test(formContent);
2017-05-05 10:57:29 +00:00
};
/**
* Remove quick actions and leave comment with pure message
2017-05-05 10:57:29 +00:00
*/
Notes.prototype.stripQuickActions = function(formContent) {
return formContent.replace(REGEX_QUICK_ACTIONS, '').trim();
2017-05-05 10:57:29 +00:00
};
/**
* Gets appropriate description from quick actions found in provided `formContent`
*/
Notes.prototype.getQuickActionDescription = function (formContent, availableQuickActions = []) {
let tempFormContent;
// Identify executed quick actions from `formContent`
const executedCommands = availableQuickActions.filter((command, index) => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(formContent);
});
if (executedCommands && executedCommands.length) {
if (executedCommands.length > 1) {
tempFormContent = 'Applying multiple commands';
} else {
const commandDescription = executedCommands[0].description.toLowerCase();
tempFormContent = `Applying command to ${commandDescription}`;
}
} else {
tempFormContent = 'Applying command';
}
return tempFormContent;
};
2017-05-05 10:57:29 +00:00
/**
* Create placeholder note DOM element populated with comment body
* that we will show while comment is being posted.
* Once comment is _actually_ posted on server, we will have final element
* in response that we will show in place of this temporary element.
*/
Notes.prototype.createPlaceholderNote = function ({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
2017-05-05 10:57:29 +00:00
const discussionClass = isDiscussionNote ? 'discussion' : '';
const $tempNote = $(
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
<div class="timeline-entry-inner">
<div class="timeline-icon">
<a href="/${currentUsername}">
<img class="avatar s40" src="${currentUserAvatar}">
</a>
2017-05-05 10:57:29 +00:00
</div>
<div class="timeline-content ${discussionClass}">
<div class="note-header">
<div class="note-header-info">
<a href="/${currentUsername}">
<span class="hidden-xs">${currentUserFullname}</span>
<span class="note-headline-light">@${currentUsername}</span>
</a>
</div>
</div>
<div class="note-body">
<div class="note-text">
<p>${formContent}</p>
2017-05-05 10:57:29 +00:00
</div>
</div>
</div>
</div>
</li>`
);
return $tempNote;
};
/**
* Create Placeholder System Note DOM element populated with quick action description
*/
Notes.prototype.createPlaceholderSystemNote = function ({ formContent, uniqueId }) {
const $tempNote = $(
`<li id="${uniqueId}" class="note system-note timeline-entry being-posted fade-in-half">
<div class="timeline-entry-inner">
<div class="timeline-content">
<i>${formContent}</i>
</div>
</div>
</li>`
);
return $tempNote;
};
2017-05-05 10:57:29 +00:00
/**
* This method does following tasks step-by-step whenever a new comment
* is submitted by user (both main thread comments as well as discussion comments).
*
* 1) Get Form metadata
* 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
* 3) Build temporary placeholder element (using `createPlaceholderNote`)
* 4) Show placeholder note on UI
* 5) Perform network request to submit the note using `gl.utils.ajaxPost`
* a) If request is successfully completed
* 1. Remove placeholder element
* 2. Show submitted Note element
* 3. Perform post-submit errands
* a. Mark discussion as resolved if comment submission was for resolve.
* b. Reset comment form to original state.
* b) If request failed
* 1. Remove placeholder element
* 2. Show error Flash message about failure
*/
Notes.prototype.postComment = function(e) {
e.preventDefault();
// Get Form metadata
const $submitBtn = $(e.target);
let $form = $submitBtn.parents('form');
const $closeBtn = $form.find('.js-note-target-close');
const isDiscussionNote = $submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
const isMainForm = $form.hasClass('js-main-target-form');
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
const { formData, formContent, formAction } = this.getFormData($form);
let noteUniqueId;
let systemNoteUniqueId;
let hasQuickActions = false;
2017-05-05 10:57:29 +00:00
let $notesContainer;
let tempFormContent;
// Get reference to notes container based on type of comment
if (isDiscussionForm) {
$notesContainer = $form.parent('.discussion-notes').find('.notes');
} else if (isMainForm) {
$notesContainer = $('ul.main-notes-list');
}
// If comment is to resolve discussion, disable submit buttons while
// comment posting is finished.
if (isDiscussionResolve) {
$submitBtn.disable();
$form.find('.js-comment-submit-button').disable();
}
tempFormContent = formContent;
if (this.hasQuickActions(formContent)) {
tempFormContent = this.stripQuickActions(formContent);
hasQuickActions = true;
2017-05-05 10:57:29 +00:00
}
// Show placeholder note
2017-05-05 10:57:29 +00:00
if (tempFormContent) {
noteUniqueId = _.uniqueId('tempNote_');
2017-05-05 10:57:29 +00:00
$notesContainer.append(this.createPlaceholderNote({
formContent: tempFormContent,
uniqueId: noteUniqueId,
2017-05-05 10:57:29 +00:00
isDiscussionNote,
currentUsername: gon.current_username,
currentUserFullname: gon.current_user_fullname,
currentUserAvatar: gon.current_user_avatar_url,
}));
}
// Show placeholder system note
if (hasQuickActions) {
systemNoteUniqueId = _.uniqueId('tempSystemNote_');
$notesContainer.append(this.createPlaceholderSystemNote({
formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
uniqueId: systemNoteUniqueId,
2017-05-05 10:57:29 +00:00
}));
}
// Clear the form textarea
if ($notesContainer.length) {
if (isMainForm) {
this.resetMainTargetForm(e);
} else if (isDiscussionForm) {
this.removeDiscussionNoteForm($form);
}
}
/* eslint-disable promise/catch-or-return */
// Make request to submit comment on server
gl.utils.ajaxPost(formAction, formData)
.then((note) => {
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
// Reset cached commands list when command is applied
if (hasQuickActions) {
$form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
}
// Clear previous form errors
this.clearFlashWrapper();
2017-05-05 10:57:29 +00:00
// Check if this was discussion comment
if (isDiscussionForm) {
// Remove flash-container
$notesContainer.find('.flash-container').remove();
// If comment intends to resolve discussion, do the same.
if (isDiscussionResolve) {
$form
.attr('data-discussion-id', $submitBtn.data('discussion-id'))
.attr('data-resolve-all', 'true')
.attr('data-project-path', $submitBtn.data('project-path'));
}
// Show final note element on UI
this.addDiscussionNote($form, note, $notesContainer.length === 0);
// append flash-container to the Notes list
if ($notesContainer.length) {
$notesContainer.append('<div class="flash-container" style="display: none;"></div>');
}
} else if (isMainForm) { // Check if this was main thread comment
// Show final note element on UI and perform form and action buttons cleanup
this.addNote($form, note);
this.reenableTargetFormSubmitButton(e);
}
if (note.commands_changes) {
this.handleQuickActions(note);
2017-05-05 10:57:29 +00:00
}
$form.trigger('ajax:success', [note]);
}).fail(() => {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
if (hasQuickActions) {
$notesContainer.find(`#${systemNoteUniqueId}`).remove();
}
2017-05-05 10:57:29 +00:00
// Show form again on UI on failure
if (isDiscussionForm && $notesContainer.length) {
const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
this.replyToDiscussionNote(replyButton[0]);
2017-05-05 10:57:29 +00:00
$form = $notesContainer.parent().find('form');
}
$form.find('.js-note-text').val(formContent);
this.reenableTargetFormSubmitButton(e);
this.addNoteError($form);
});
return $closeBtn.text($closeBtn.data('original-text'));
};
/**
* This method does following tasks step-by-step whenever an existing comment
* is updated by user (both main thread comments as well as discussion comments).
*
* 1) Get Form metadata
* 2) Update note element with new content
* 3) Perform network request to submit the updated note using `gl.utils.ajaxPost`
* a) If request is successfully completed
* 1. Show submitted Note element
* b) If request failed
* 1. Revert Note element to original content
* 2. Show error Flash message about failure
*/
Notes.prototype.updateComment = function(e) {
e.preventDefault();
// Get Form metadata
const $submitBtn = $(e.target);
const $form = $submitBtn.parents('form');
const $closeBtn = $form.find('.js-note-target-close');
const $editingNote = $form.parents('.note.is-editing');
const $noteBody = $editingNote.find('.js-task-list-container');
const $noteBodyText = $noteBody.find('.note-text');
const { formData, formContent, formAction } = this.getFormData($form);
// Cache original comment content
const cachedNoteBodyText = $noteBodyText.html();
// Show updated comment content temporarily
$noteBodyText.html(_.escape(formContent));
$editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
2017-05-05 10:57:29 +00:00
$editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
/* eslint-disable promise/catch-or-return */
// Make request to update comment on server
gl.utils.ajaxPost(formAction, formData)
.then((note) => {
// Submission successful! render final note element
this.updateNote(note, $editingNote);
2017-05-05 10:57:29 +00:00
})
.fail(() => {
// Submission failed, revert back to original note
$noteBodyText.html(_.escape(cachedNoteBodyText));
2017-05-05 10:57:29 +00:00
$editingNote.removeClass('being-posted fade-in');
$editingNote.find('.fa.fa-spinner').remove();
// Show Flash message about failure
this.updateNoteError();
});
return $closeBtn.text($closeBtn.data('original-text'));
};
2016-07-24 20:45:11 +00:00
return Notes;
})();
}).call(window);