Commit e394ad13 authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge branch 'single-edit-comment-widget-2' into 'master'

Refactor discussion edit widget to have only one at a time.

Closes #23227

See merge request !8356
parents 5c46a459 36412ee2
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
autosize(this.textarea); autosize(this.textarea);
// form and textarea event listeners // form and textarea event listeners
this.addEventListeners(); this.addEventListeners();
gl.text.init(this.form);
} }
gl.text.init(this.form);
// hide discard button // hide discard button
this.form.find('.js-note-discard').hide(); this.form.find('.js-note-discard').hide();
return this.form.show(); return this.form.show();
......
...@@ -106,8 +106,9 @@ ...@@ -106,8 +106,9 @@
); );
}; };
gl.utils.getPagePath = function() { gl.utils.getPagePath = function(index) {
return $('body').data('page').split(':')[0]; index = index || 0;
return $('body').data('page').split(':')[index];
}; };
gl.utils.parseUrl = function (url) { gl.utils.parseUrl = function (url) {
...@@ -127,6 +128,17 @@ ...@@ -127,6 +128,17 @@
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
}; };
gl.utils.scrollToElement = function($el) {
var top = $el.offset().top;
gl.navBarHeight = gl.navBarHeight || $('.navbar-gitlab').height();
gl.navLinksHeight = gl.navLinksHeight || $('.nav-links').height();
gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();
return $('body, html').animate({
scrollTop: top - (gl.navBarHeight + gl.navLinksHeight + gl.mrTabsHeight)
}, 200);
};
})(window); })(window);
}).call(this); }).call(this);
...@@ -52,6 +52,12 @@ ...@@ -52,6 +52,12 @@
this.setupMainTargetNoteForm(); this.setupMainTargetNoteForm();
this.initTaskList(); this.initTaskList();
this.collapseLongCommitList(); this.collapseLongCommitList();
// 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()
.addClass('mr-note-edit-form').insertAfter('.note-edit-form');
}
} }
Notes.prototype.addBinding = function() { Notes.prototype.addBinding = function() {
...@@ -63,7 +69,7 @@ ...@@ -63,7 +69,7 @@
// change note in UI after update // change note in UI after update
$(document).on("ajax:success", "form.edit-note", this.updateNote); $(document).on("ajax:success", "form.edit-note", this.updateNote);
// Edit note link // 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); $(document).on("click", ".note-edit-cancel", this.cancelEdit);
// Reopen and close actions for Issue/MR combined with note form submit // Reopen and close actions for Issue/MR combined with note form submit
$(document).on("click", ".js-comment-button", this.updateCloseButton); $(document).on("click", ".js-comment-button", this.updateCloseButton);
...@@ -466,6 +472,7 @@ ...@@ -466,6 +472,7 @@
var $html, $note_li; var $html, $note_li;
// Convert returned HTML to a jQuery object so we can modify it further // Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html); $html = $(note.html);
this.revertNoteEditForm();
gl.utils.localTimeAgo($('.js-timeago', $html)); gl.utils.localTimeAgo($('.js-timeago', $html));
$html.renderGFM(); $html.renderGFM();
$html.find('.js-task-list-container').taskList('enable'); $html.find('.js-task-list-container').taskList('enable');
...@@ -480,48 +487,56 @@ ...@@ -480,48 +487,56 @@
}; };
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);
}
else {
var $buttons = $el.find('.note-form-actions');
var isWidgetVisible = gl.utils.isInViewport($el.get(0));
if (!isWidgetVisible) {
gl.utils.scrollToElement($el);
}
$el.find('.js-edit-warning').show();
isAllowed = false;
}
return isAllowed;
}
/* /*
Called in response to clicking the edit note link Called in response to clicking the edit note link
Replaces the note text with the note edit form 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 Adds a data attribute to the form with the original content of the note for cancellations
*/ */
Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) { Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
var $noteText, done, form, note;
e.preventDefault(); e.preventDefault();
note = $(this).closest(".note");
note.addClass("is-editting"); var $target = $(e.target);
form = note.find(".note-edit-form"); var $editForm = $(this.getEditFormSelector($target));
form.addClass('current-note-edit-form'); var $note = $target.closest('.note');
// Show the attachment delete link var $currentlyEditing = $('.note.is-editting:visible');
note.find(".js-note-attachment-delete").show();
done = function($noteText) { if ($currentlyEditing.length) {
var noteTextVal; var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
// Neat little trick to put the cursor at the end
noteTextVal = $noteText.val(); if (!isEditAllowed) {
// Store the original note text in a data attribute to retrieve if a user cancels edit. return;
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);
} }
$note.find('.js-note-attachment-delete').show();
$editForm.addClass('current-note-edit-form');
$note.addClass('is-editting');
this.putEditFormInPlace($target);
}; };
...@@ -532,19 +547,41 @@ ...@@ -532,19 +547,41 @@
*/ */
Notes.prototype.cancelEdit = function(e) { Notes.prototype.cancelEdit = function(e) {
var note;
e.preventDefault(); e.preventDefault();
note = $(e.target).closest('.note'); var $target = $(e.target);
var note = $target.closest('.note');
note.find('.js-edit-warning').hide();
this.revertNoteEditForm($target);
return this.removeNoteEditForm(note); return this.removeNoteEditForm(note);
}; };
Notes.prototype.revertNoteEditForm = function($target) {
$target = $target || $('.note.is-editting:visible');
var selector = this.getEditFormSelector($target);
var $editForm = $(selector);
$editForm.insertBefore('.notes-form');
$editForm.find('.js-comment-button').enable();
$editForm.find('.js-edit-warning').hide();
};
Notes.prototype.getEditFormSelector = function($el) {
var selector = '.note-edit-form:not(.mr-note-edit-form)';
if ($el.parents('#diffs').length) {
selector = '.note-edit-form.mr-note-edit-form';
}
return selector;
};
Notes.prototype.removeNoteEditForm = function(note) { Notes.prototype.removeNoteEditForm = function(note) {
var form; var form = note.find('.current-note-edit-form');
form = note.find(".current-note-edit-form"); note.removeClass('is-editting');
note.removeClass("is-editting"); form.removeClass('current-note-edit-form');
form.removeClass("current-note-edit-form"); form.find('.js-edit-warning').hide();
// Replace markdown textarea text with original note text. // Replace markdown textarea text with original note text.
return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note')); return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
}; };
...@@ -837,15 +874,44 @@ ...@@ -837,15 +874,44 @@
Notes.prototype.initTaskList = function() { Notes.prototype.initTaskList = function() {
this.enableTaskList(); this.enableTaskList();
return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList); return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this));
}; };
Notes.prototype.enableTaskList = function() { Notes.prototype.enableTaskList = function() {
return $('.note .js-task-list-container').taskList('enable'); return $('.note .js-task-list-container').taskList('enable');
}; };
Notes.prototype.updateTaskList = function() { Notes.prototype.putEditFormInPlace = function($el) {
return $('form', this).submit(); 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 GLForm($editForm.find('form'));
$editForm.find('form').attr('action', postUrl);
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
$editForm.find('.js-note-text').focus().val(originalContent);
$editForm.find('.js-md-write-button').trigger('click');
$editForm.find('.referenced-users').hide();
}
Notes.prototype.updateTaskList = function(e) {
var $target = $(e.target);
var $list = $target.closest('.js-task-list-container');
var $editForm = $(this.getEditFormSelector($target));
var $note = $list.closest('.note');
this.putEditFormInPlace($list);
$editForm.find('#note_note').val($note.find('.original-task-list').val());
$('form', $list).submit();
}; };
Notes.prototype.updateNotesCount = function(updateCount) { Notes.prototype.updateNotesCount = function(updateCount) {
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
.new-note, .new-note,
.note-edit-form { .note-edit-form {
.note-form-actions { .note-form-actions {
position: relative;
margin-top: $gl-padding; margin-top: $gl-padding;
} }
...@@ -265,3 +266,18 @@ ...@@ -265,3 +266,18 @@
} }
} }
} }
.note-edit-warning.settings-message {
display: none;
padding: 5px 10px;
position: absolute;
left: 127px;
top: 2px;
@media (max-width: $screen-xs-max) {
position: relative;
top: 0;
left: 0;
margin-bottom: 10px;
}
}
...@@ -588,13 +588,11 @@ ul.notes { ...@@ -588,13 +588,11 @@ ul.notes {
// Merge request notes in diffs // Merge request notes in diffs
.diff-file { .diff-file {
// Diff is side by side // Diff is side by side
.notes_content.parallel .note-header .note-headline-light { .notes_content.parallel .note-header .note-headline-light {
display: block; display: block;
position: relative; position: relative;
} }
// Diff is inline // Diff is inline
.notes_content .note-header .note-headline-light { .notes_content .note-header .note-headline-light {
display: inline-block; display: inline-block;
......
.note-edit-form .note-edit-form
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f| = form_tag '#', method: :put, remote: true, class: 'edit-note common-note-form js-quick-submit' do
= note_target_fields(note) = hidden_field_tag :authenticity_token, form_authenticity_token
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do = hidden_field_tag :target_id, '', class: 'js-form-target-id'
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..." = hidden_field_tag :target_type, '', class: 'js-form-target-type'
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview', referenced_users: true } do
= render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints' = render 'projects/notes/hints'
.note-form-actions.clearfix .note-form-actions.clearfix
= f.submit 'Save Comment', class: 'btn btn-nr btn-save js-comment-button' .settings-message.note-edit-warning.js-edit-warning
Finish editing this message first!
= submit_tag 'Save Comment', class: 'btn btn-nr btn-save js-comment-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' } %button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
Cancel Cancel
...@@ -67,7 +67,9 @@ ...@@ -67,7 +67,9 @@
= note.redacted_note_html = note.redacted_note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable - 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}
%textarea.hidden.js-task-list-field.original-task-list #{note.note}
.note-awards .note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false = render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system - if note.system
......
%ul#notes-list.notes.main-notes-list.timeline %ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes" = render "projects/notes/notes"
= render 'projects/notes/edit_form'
%ul.notes.notes-form.timeline %ul.notes.notes-form.timeline
%li.timeline-entry %li.timeline-entry
.flash-container.timeline-content .flash-container.timeline-content
......
---
title: Refactored note edit form to improve frontend performance on MR and Issues
pages, especially pages with has a lot of discussions in it
merge_request: 8356
author:
...@@ -70,16 +70,15 @@ describe 'Comments', feature: true do ...@@ -70,16 +70,15 @@ describe 'Comments', feature: true do
end end
describe 'when editing a note', js: true do describe 'when editing a note', js: true do
it 'contains the hidden edit form' do it 'there should be a hidden edit form' do
page.within("#note_#{note.id}") do is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1)
is_expected.to have_css('.note-edit-form', visible: false) is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1)
end
end end
describe 'editing the note' do describe 'editing the note' do
before do before do
find('.note').hover find('.note').hover
find(".js-note-edit").click find('.js-note-edit').click
end end
it 'shows the note edit form and hide the note body' do it 'shows the note edit form and hide the note body' do
...@@ -90,14 +89,29 @@ describe 'Comments', feature: true do ...@@ -90,14 +89,29 @@ describe 'Comments', feature: true do
end end
end end
# TODO: fix after 7.7 release it 'should reset the edit note form textarea with the original content of the note if cancelled' do
# it "should reset the edit note form textarea with the original content of the note if cancelled" do within('.current-note-edit-form') do
# within(".current-note-edit-form") do fill_in 'note[note]', with: 'Some new content'
# fill_in "note[note]", with: "Some new content" find('.btn-cancel').click
# find(".btn-cancel").click expect(find('.js-note-text', visible: false).text).to eq ''
# expect(find(".js-note-text", visible: false).text).to eq note.note end
# end end
# end
it 'allows using markdown buttons after saving a note and then trying to edit it again' do
page.within('.current-note-edit-form') do
fill_in 'note[note]', with: 'This is the new content'
find('.btn-save').click
end
find('.note').hover
find('.js-note-edit').click
page.within('.current-note-edit-form') do
expect(find('#note_note').value).to eq('This is the new content')
find('.js-md:first-child').click
expect(find('#note_note').value).to eq('This is the new content****')
end
end
it 'appends the edited at time to the note' do it 'appends the edited at time to the note' do
page.within('.current-note-edit-form') do page.within('.current-note-edit-form') do
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
fixture.load(commentsTemplate); fixture.load(commentsTemplate);
gl.utils.disableButtonIfEmptyField = _.noop; gl.utils.disableButtonIfEmptyField = _.noop;
window.project_uploads_path = 'http://test.host/uploads'; window.project_uploads_path = 'http://test.host/uploads';
$('body').data('page', 'projects:issues:show');
}); });
describe('task lists', function() { describe('task lists', function() {
......
...@@ -32,9 +32,9 @@ ...@@ -32,9 +32,9 @@
return expect(Mousetrap.pause).toHaveBeenCalled(); return expect(Mousetrap.pause).toHaveBeenCalled();
}); });
return it('removes textarea styling', function() { return it('removes textarea styling', function() {
$('textarea').attr('style', 'height: 400px'); $('.notes-form textarea').attr('style', 'height: 400px');
enterZen(); enterZen();
return expect('textarea').not.toHaveAttr('style'); return expect($('.notes-form textarea')).not.toHaveAttr('style');
}); });
}); });
describe('in use', function() { describe('in use', function() {
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
}); });
return it('exits on Escape', function() { return it('exits on Escape', function() {
escapeKeydown(); escapeKeydown();
return expect($('.zen-backdrop')).not.toHaveClass('fullscreen'); return expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen');
}); });
}); });
return describe('on exit', function() { return describe('on exit', function() {
...@@ -64,15 +64,15 @@ ...@@ -64,15 +64,15 @@
}); });
enterZen = function() { enterZen = function() {
return $('.js-zen-enter').click(); return $('.notes-form .js-zen-enter').click();
}; };
exitZen = function() { // Ohmmmmmmm exitZen = function() {
return $('.js-zen-leave').click(); return $('.notes-form .js-zen-leave').click();
}; };
escapeKeydown = function() { escapeKeydown = function() {
return $('textarea').trigger($.Event('keydown', { return $('.notes-form textarea').trigger($.Event('keydown', {
keyCode: 27 keyCode: 27
})); }));
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment