diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 0a0e73e0ccc..06f85897f1a 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -129,4 +129,16 @@ })(window); + gl.utils.isElementVisibleInViewport = function(el) { + var rect = el.getBoundingClientRect(); + var height = Math.max(document.documentElement.clientHeight, window.innerHeight); + return !(rect.bottom - 110 < 0 || rect.top - height >= 0); // -110 for sticky GitLab navigation header + } + + gl.utils.animateToElement = function($el) { + return $('body, html').animate({ + scrollTop: $el.offset().top - 110 + }, 200); + } + }).call(this); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a8b9a352870..1f30368472d 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -63,7 +63,7 @@ // change note in UI after update $(document).on("ajax:success", "form.edit-note", this.updateNote); // Edit note link - $(document).on("click", ".js-note-edit", this.showEditForm); + $(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-button", this.updateCloseButton); @@ -466,6 +466,9 @@ var $html, $note_li; // Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html); + + $('.note-edit-form').insertBefore('.notes-form'); + gl.utils.localTimeAgo($('.js-timeago', $html)); $html.renderGFM(); $html.find('.js-task-list-container').taskList('enable'); @@ -480,48 +483,73 @@ }; + Notes.prototype.checkContentToAllowEditing = function($el) { + var initialContent = $el.find('.original-note-content').text().trim(); + var currentContent = $el.find('.note-textarea').val(); + var isAllowed = true; + + if (currentContent === initialContent) { + this.removeNoteEditForm($el); + $el.find('.js-md-write-button').trigger('click'); + } + else { + var $buttons = $el.find('.note-form-actions'); + var isButtonsVisible = gl.utils.isElementVisibleInViewport($buttons[0]); + var isWidgetVisible = gl.utils.isElementVisibleInViewport($el[0]); + + if (!isButtonsVisible || !isWidgetVisible) { + gl.utils.animateToElement($el); + } + + $el.find('.js-edit-warning').show(); + $el.find('.js-md-write-button').trigger('click'); + isAllowed = false; + } + + return isAllowed; + } + + /* Called in response to clicking the edit note link 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 - */ - + */ Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) { - var $noteText, done, form, note; e.preventDefault(); - note = $(this).closest(".note"); - note.addClass("is-editting"); - form = note.find(".note-edit-form"); - form.addClass('current-note-edit-form'); - // Show the attachment delete link - note.find(".js-note-attachment-delete").show(); - done = function($noteText) { - var noteTextVal; - // Neat little trick to put the cursor at the end - noteTextVal = $noteText.val(); - // Store the original note text in a data attribute to retrieve if a user cancels edit. - form.find('form.edit-note').data('original-note', noteTextVal); - return $noteText.val('').val(noteTextVal); - }; - new GLForm(form); - if ((scrollTo != null) && (myLastNote != null)) { - // scroll to the bottom - // so the open of the last element doesn't make a jump - $('html, body').scrollTop($(document).height()); - return $('html, body').animate({ - scrollTop: myLastNote.offset().top - 150 - }, 500, function() { - var $noteText; - $noteText = form.find(".js-note-text"); - $noteText.focus(); - return done($noteText); - }); - } else { - $noteText = form.find('.js-note-text'); - $noteText.focus(); - return done($noteText); + + var $currentlyEditing = $('.note.is-editting'); + if ($currentlyEditing.length) { + var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing); + + if (!isEditAllowed) { + return; + } } + + var note = $(e.target).closest('.note'); + var $editForm = $('.note-edit-form'); + var $originalContentEl = note.find('.original-note-content'); + var originalContent = $originalContentEl.text().trim(); + var postUrl = $originalContentEl.data('post-url'); + var form = note.find('.note-edit-form'); + var $noteText = form.find('.js-note-text'); + var noteTextVal = $noteText.val(); // Neat little trick to put the cursor at the end + + note.addClass('is-editting'); + $editForm.insertAfter(note.find('.note-text')); + $editForm.find('.js-note-text').val(originalContent); + $editForm.find('form').attr('action', postUrl); + + form.addClass('current-note-edit-form'); + note.find('.js-note-attachment-delete').show(); // Show the attachment delete link + new GLForm(form); + + $noteText.focus(); + // Store the original note text in a data attribute to retrieve if a user cancels edit. + form.find('form.edit-note').data('original-note', noteTextVal); + $noteText.val('').val(noteTextVal); }; @@ -532,15 +560,17 @@ */ Notes.prototype.cancelEdit = function(e) { - var note; e.preventDefault(); - note = $(e.target).closest('.note'); + var note = $(e.target).closest('.note'); + note.find('.js-edit-warning').hide(); + note.find('.js-md-write-button').trigger('click'); + $('.note-edit-form').insertBefore('.notes-form'); return this.removeNoteEditForm(note); }; + Notes.prototype.removeNoteEditForm = function(note) { - var form; - form = note.find(".current-note-edit-form"); + var form = note.find(".current-note-edit-form"); note.removeClass("is-editting"); form.removeClass("current-note-edit-form"); // Replace markdown textarea text with original note text. diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 21d72791e81..4eb81473e2b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -601,3 +601,12 @@ ul.notes { position: relative; } } + + +.note-edit-warning.settings-message { + display: none; + position: relative; + top: 1px; + left: 7px; + padding: 5px 10px; +} diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 8620f492282..245a7c74ebf 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -7,5 +7,7 @@ .note-form-actions.clearfix = f.submit 'Save Comment', class: 'btn btn-nr btn-save js-comment-button' + %span.settings-message.note-edit-warning.js-edit-warning + Finish editing this message first! %button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' } Cancel diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index eb869ea85bf..fc4cb3f4a6d 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -67,7 +67,8 @@ = note.redacted_note_html = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable - = render 'projects/notes/edit_form', note: note + .original-note-content.hidden{data: {post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore}} + #{note.note} .note-awards = render 'award_emoji/awards_block', awardable: note, inline: false - if note.system diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml index 022578bd6db..64d26d9ad50 100644 --- a/app/views/projects/notes/_notes.html.haml +++ b/app/views/projects/notes/_notes.html.haml @@ -6,3 +6,6 @@ = render 'discussions/discussion', discussion: discussion - else = render partial: "projects/notes/note", collection: @notes, as: :note + += render 'projects/notes/edit_form', note: @notes[0] += hidden_field_tag :authenticity_token, form_authenticity_token