Commit 81e9c284 authored by Bryce Johnson's avatar Bryce Johnson

Render add-diff-note button with server.

This commit moves the rendering of the button back to the server, and
shows/hides it using opacity rather than display. It also removes the
transform applied to the button on hover (scale). Previously, both of these
factors automatically triggered a reflow, which creates a performance
bottleneck on pages with larger DOM size.

MR: !12103
parent 066a6c8b
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
import './lib/utils/url_utility'; import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
let isBound = false; let isBound = false;
...@@ -8,8 +9,10 @@ let isBound = false; ...@@ -8,8 +9,10 @@ let isBound = false;
class Diff { class Diff {
constructor() { constructor() {
const $diffFile = $('.files .diff-file'); const $diffFile = $('.files .diff-file');
$diffFile.singleFileDiff(); $diffFile.singleFileDiff();
$diffFile.filesCommentButton();
FilesCommentButton.init($diffFile);
$diffFile.each((index, file) => new gl.ImageFile(file)); $diffFile.each((index, file) => new gl.ImageFile(file));
......
...@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({
const notesCount = this.notesCount; const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container') $(this.$el).closest('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0) .toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container') .nextUntil('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0); .toggleClass('no-comment-btn', notesCount > 0);
}, },
toggleDiscussionsToggleState() { toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
/* global FilesCommentButton */
/* global notes */ /* global notes */
let $commentButtonTemplate; /* Developer beware! Do not add logic to showButton or hideButton
* that will force a reflow. Doing so will create a signficant performance
window.FilesCommentButton = (function() { * bottleneck for pages with large diffs. For a comprehensive list of what
var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS; * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
*/
COMMENT_BUTTON_CLASS = '.add-diff-note';
const LINE_NUMBER_CLASS = 'diff-line-num';
LINE_HOLDER_CLASS = '.line_holder'; const UNFOLDABLE_LINE_CLASS = 'js-unfold';
const NO_COMMENT_CLASS = 'no-comment-btn';
LINE_NUMBER_CLASS = 'diff-line-num'; const EMPTY_CELL_CLASS = 'empty-cell';
const OLD_LINE_CLASS = 'old_line';
LINE_CONTENT_CLASS = 'line_content'; const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`;
const DIFF_CONTAINER_SELECTOR = '.files';
UNFOLDABLE_LINE_CLASS = 'js-unfold'; const DIFF_EXPANDED_CLASS = 'diff-expanded';
EMPTY_CELL_CLASS = 'empty-cell'; export default {
init($diffFile) {
OLD_LINE_CLASS = 'old_line'; /* Caching is used only when the following members are *true*. This is because there are likely to be
* differently configured versions of diffs in the same session. However if these values are true, they
LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content"; * will be true in all cases */
TEXT_FILE_SELECTOR = '.text-file'; if (!this.userCanCreateNote) {
// data-can-create-note is an empty string when true, otherwise undefined
function FilesCommentButton(filesContainerElement) { this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
this.render = this.render.bind(this);
this.hideButton = this.hideButton.bind(this);
this.isParallelView = notes.isParallelView();
filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
.on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
}
FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
$currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('js-no-comment-btn')) return;
lineContentElement = this.getLineContent($currentTarget);
buttonParentElement = this.getButtonParent($currentTarget);
if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
$button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
buttonParentElement.addClass('is-over')
.nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
if ($button.length) {
return;
} }
textFileElement = this.getTextFileElement($currentTarget); if (typeof notes !== 'undefined' && !this.isParallelView) {
buttonParentElement.append(this.buildButton({ this.isParallelView = notes.isParallelView && notes.isParallelView();
discussionID: lineContentElement.attr('data-discussion-id'),
lineType: lineContentElement.attr('data-line-type'),
noteableType: textFileElement.attr('data-noteable-type'),
noteableID: textFileElement.attr('data-noteable-id'),
commitID: textFileElement.attr('data-commit-id'),
noteType: lineContentElement.attr('data-note-type'),
// LegacyDiffNote
lineCode: lineContentElement.attr('data-line-code'),
// DiffNote
position: lineContentElement.attr('data-position')
}));
};
FilesCommentButton.prototype.hideButton = function(e) {
var $currentTarget = $(e.currentTarget);
var buttonParentElement = this.getButtonParent($currentTarget);
buttonParentElement.removeClass('is-over')
.nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
};
FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
return $commentButtonTemplate.clone().attr({
'data-discussion-id': buttonAttributes.discussionID,
'data-line-type': buttonAttributes.lineType,
'data-noteable-type': buttonAttributes.noteableType,
'data-noteable-id': buttonAttributes.noteableID,
'data-commit-id': buttonAttributes.commitID,
'data-note-type': buttonAttributes.noteType,
// LegacyDiffNote
'data-line-code': buttonAttributes.lineCode,
// DiffNote
'data-position': buttonAttributes.position
});
};
FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
return hoveredElement.closest(TEXT_FILE_SELECTOR);
};
FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
return hoveredElement;
}
if (!this.isParallelView) {
return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
} else {
return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
} }
};
FilesCommentButton.prototype.getButtonParent = function(hoveredElement) { if (this.userCanCreateNote) {
if (!this.isParallelView) { $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
if (hoveredElement.hasClass(OLD_LINE_CLASS)) { .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
return hoveredElement;
}
return hoveredElement.parent().find("." + OLD_LINE_CLASS);
} else {
if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
return hoveredElement;
}
return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
} }
}; },
FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) { showButton(isParallelView, e) {
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS); const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
};
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) { if (!this.validateButtonParent(buttonParentElement)) return;
return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
};
return FilesCommentButton; buttonParentElement.classList.add('is-over');
})(); buttonParentElement.nextElementSibling.classList.add('is-over');
},
$.fn.filesCommentButton = function() { hideButton(isParallelView, e) {
$commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'); const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
if (!(this && (this.parent().data('can-create-note') != null))) { buttonParentElement.classList.remove('is-over');
return; buttonParentElement.nextElementSibling.classList.remove('is-over');
} },
return this.each(function() {
if (!$.data(this, 'filesCommentButton')) { getButtonParent(hoveredElement, isParallelView) {
return $.data(this, 'filesCommentButton', new FilesCommentButton($(this))); if (isParallelView) {
if (!hoveredElement.classList.contains(LINE_NUMBER_CLASS)) {
return hoveredElement.previousElementSibling;
}
} else if (!hoveredElement.classList.contains(OLD_LINE_CLASS)) {
return hoveredElement.parentNode.querySelector(`.${OLD_LINE_CLASS}`);
} }
}); return hoveredElement;
},
validateButtonParent(buttonParentElement) {
return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
!buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
!buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
!buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS);
},
}; };
...@@ -829,6 +829,8 @@ export default class Notes { ...@@ -829,6 +829,8 @@ export default class Notes {
*/ */
setupDiscussionNoteForm(dataHolder, form) { setupDiscussionNoteForm(dataHolder, form) {
// setup note target // setup note target
const diffFileData = dataHolder.closest('.text-file');
var discussionID = dataHolder.data('discussionId'); var discussionID = dataHolder.data('discussionId');
if (discussionID) { if (discussionID) {
...@@ -839,9 +841,10 @@ export default class Notes { ...@@ -839,9 +841,10 @@ export default class Notes {
form.attr('data-line-code', dataHolder.data('lineCode')); form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType')); form.find('#line_type').val(dataHolder.data('lineType'));
form.find('#note_noteable_type').val(dataHolder.data('noteableType')); form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
form.find('#note_noteable_id').val(dataHolder.data('noteableId')); form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
form.find('#note_commit_id').val(dataHolder.data('commitId')); form.find('#note_commit_id').val(diffFileData.data('commitId'));
form.find('#note_type').val(dataHolder.data('noteType')); form.find('#note_type').val(dataHolder.data('noteType'));
// LegacyDiffNote // LegacyDiffNote
......
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
import FilesCommentButton from './files_comment_button';
(function() { (function() {
window.SingleFileDiff = (function() { window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
...@@ -78,6 +80,8 @@ ...@@ -78,6 +80,8 @@
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
} }
FilesCommentButton.init($(_this.file));
if (cb) cb(); if (cb) cb();
}; };
})(this)); })(this));
......
...@@ -476,6 +476,7 @@ ...@@ -476,6 +476,7 @@
height: 19px; height: 19px;
width: 19px; width: 19px;
margin-left: -15px; margin-left: -15px;
z-index: 100;
&:hover { &:hover {
.diff-comment-avatar, .diff-comment-avatar,
...@@ -491,7 +492,7 @@ ...@@ -491,7 +492,7 @@
transform: translateX((($i * $x-pos) - $x-pos)); transform: translateX((($i * $x-pos) - $x-pos));
&:hover { &:hover {
transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2); transform: translateX((($i * $x-pos) - $x-pos));
} }
} }
} }
...@@ -542,6 +543,7 @@ ...@@ -542,6 +543,7 @@
height: 19px; height: 19px;
padding: 0; padding: 0;
transition: transform .1s ease-out; transition: transform .1s ease-out;
z-index: 100;
svg { svg {
position: absolute; position: absolute;
...@@ -555,10 +557,6 @@ ...@@ -555,10 +557,6 @@
fill: $white-light; fill: $white-light;
} }
&:hover {
transform: scale(1.2);
}
&:focus { &:focus {
outline: 0; outline: 0;
} }
......
...@@ -628,8 +628,14 @@ ul.notes { ...@@ -628,8 +628,14 @@ ul.notes {
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
.line_holder .is-over:not(.no-comment-btn) {
.add-diff-note {
opacity: 1;
}
}
.add-diff-note { .add-diff-note {
display: none; opacity: 0;
margin-top: -2px; margin-top: -2px;
border-radius: 50%; border-radius: 50%;
background: $white-light; background: $white-light;
...@@ -642,13 +648,11 @@ ul.notes { ...@@ -642,13 +648,11 @@ ul.notes {
width: 23px; width: 23px;
height: 23px; height: 23px;
border: 1px solid $blue-500; border: 1px solid $blue-500;
transition: transform .1s ease-in-out;
&:hover { &:hover {
background: $blue-500; background: $blue-500;
border-color: $blue-600; border-color: $blue-600;
color: $white-light; color: $white-light;
transform: scale(1.15);
} }
&:active { &:active {
......
...@@ -47,6 +47,18 @@ module NotesHelper ...@@ -47,6 +47,18 @@ module NotesHelper
data data
end end
def add_diff_note_button(line_code, position, line_type)
return if @diff_notes_disabled
button_tag '',
class: 'add-diff-note js-add-diff-note-button',
type: 'submit', name: 'button',
data: diff_view_line_data(line_code, position, line_type),
title: 'Add a comment to this line' do
icon('comment-o')
end
end
def link_to_reply_discussion(discussion, line_type = nil) def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user return unless current_user
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
- if plain - if plain
= link_text = link_text
- else - else
= add_diff_note_button(line_code, diff_file.position(line), type)
%a{ href: "##{line_code}", data: { linenumber: link_text } } %a{ href: "##{line_code}", data: { linenumber: link_text } }
- discussion = line_discussions.try(:first) - discussion = line_discussions.try(:first)
- if discussion && discussion.resolvable? && !plain - if discussion && discussion.resolvable? && !plain
...@@ -29,7 +30,7 @@ ...@@ -29,7 +30,7 @@
= link_text = link_text
- else - else
%a{ href: "##{line_code}", data: { linenumber: link_text } } %a{ href: "##{line_code}", data: { linenumber: link_text } }
%td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< %td.line_content.noteable_line{ class: type }<
- if email - if email
%pre= line.text %pre= line.text
- else - else
......
/ Side-by-side diff view / Side-by-side diff view
.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } .text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
%table %table
- diff_file.parallel_diff_lines.each do |line| - diff_file.parallel_diff_lines.each do |line|
...@@ -18,11 +19,12 @@ ...@@ -18,11 +19,12 @@
- left_line_code = diff_file.line_code(left) - left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left) - left_position = diff_file.position(left)
%td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
= add_diff_note_button(left_line_code, left_position, 'old')
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } } %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- discussion_left = discussions_left.try(:first) - discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable? - if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id } %diff-note-avatars{ "discussion-id" => discussion_left.id }
%td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text) %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text)
- else - else
%td.old_line.diff-line-num.empty-cell %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel %td.line_content.parallel
...@@ -38,11 +40,12 @@ ...@@ -38,11 +40,12 @@
- right_line_code = diff_file.line_code(right) - right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right) - right_position = diff_file.position(right)
%td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
= add_diff_note_button(right_line_code, right_position, 'new')
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } } %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- discussion_right = discussions_right.try(:first) - discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable? - if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id } %diff-note-avatars{ "discussion-id" => discussion_right.id }
%td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text) %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text)
- else - else
%td.old_line.diff-line-num.empty-cell %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel %td.line_content.parallel
......
...@@ -232,7 +232,7 @@ module SharedDiffNote ...@@ -232,7 +232,7 @@ module SharedDiffNote
end end
def click_parallel_diff_line(code, line_type) def click_parallel_diff_line(code, line_type)
find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover' find(".line_holder.parallel .diff-line-num[id='#{code}']").trigger 'mouseover'
find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click' find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
end end
end end
...@@ -129,7 +129,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do ...@@ -129,7 +129,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
before do before do
large_diff.find('.diff-line-num', match: :prefer_exact).hover large_diff.find('.diff-line-num', match: :prefer_exact).hover
large_diff.find('.add-diff-note').click large_diff.find('.add-diff-note', match: :prefer_exact).click
large_diff.find('.note-textarea').send_keys comment_text large_diff.find('.note-textarea').send_keys comment_text
large_diff.find_button('Comment').click large_diff.find_button('Comment').click
wait_for_requests wait_for_requests
......
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