From 41f57b7e592e5ca715537316e505bf4bbcfcb5cc Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 5 Sep 2017 17:09:24 -0300 Subject: [PATCH 001/211] Image diff comments --- lib/gitlab/diff/formatters/base_formatter.rb | 18 +++++++++++ lib/gitlab/diff/formatters/text_formatter.rb | 11 +++++++ lib/gitlab/diff/position.rb | 34 ++++++++++++-------- 3 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 lib/gitlab/diff/formatters/base_formatter.rb create mode 100644 lib/gitlab/diff/formatters/text_formatter.rb diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb new file mode 100644 index 000000000000..e271f0ac10f2 --- /dev/null +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -0,0 +1,18 @@ +module Gitlab + module Diff + module Formatters + class BaseFormatter + def initialize + end + + def key + raise NotImplementedError + end + + def to_h + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb new file mode 100644 index 000000000000..598c30879961 --- /dev/null +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -0,0 +1,11 @@ +module Gitlab + module Diff + module Formatters + class TextFormatter < BaseFormatter + def key + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index f80afb20f0c1..b2dbcbbe56b3 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -3,6 +3,8 @@ module Gitlab module Diff class Position + FORMATTER_CLASS_SUFIX = "_formatter".freeze + attr_reader :old_path attr_reader :new_path attr_reader :old_line @@ -10,6 +12,7 @@ module Gitlab attr_reader :base_sha attr_reader :start_sha attr_reader :head_sha + attr_reader :component_type def initialize(attrs = {}) if diff_file = attrs[:diff_file] @@ -30,8 +33,9 @@ module Gitlab @start_sha = attrs[:start_sha] @head_sha = attrs[:head_sha] - @old_line = attrs[:old_line] - @new_line = attrs[:new_line] + formatter_class = Gitlab::Diff::Formatters.const_get(formatter_class_name) + + formatter = formatter_class.new(coder) end # `Gitlab::Diff::Position` objects are stored as serialized attributes in @@ -57,17 +61,17 @@ module Gitlab other.is_a?(self.class) && key == other.key end - def to_h - { - old_path: old_path, - new_path: new_path, - old_line: old_line, - new_line: new_line, - base_sha: base_sha, - start_sha: start_sha, - head_sha: head_sha - } - end + # def to_h + # { + # old_path: old_path, + # new_path: new_path, + # old_line: old_line, + # new_line: new_line, + # base_sha: base_sha, + # start_sha: start_sha, + # head_sha: head_sha + # } + # end def inspect %(#<#{self.class}:#{object_id} #{to_h}>) @@ -149,6 +153,10 @@ module Gitlab diff_refs.compare_in(repository.project).diffs(paths: paths, expanded: true).diff_files.first end + + def formatter_class_name + ("text" + FORMATTER_CLASS_SUFIX).classify + end end end end -- 2.22.0 From bd6c648d5617cefeba9521e238cb19f326e6b62f Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 14 Sep 2017 18:46:16 -0300 Subject: [PATCH 002/211] add image formatter class --- lib/gitlab/diff/file.rb | 5 ++ lib/gitlab/diff/formatters/base_formatter.rb | 41 ++++++++++- lib/gitlab/diff/formatters/image_formatter.rb | 37 ++++++++++ lib/gitlab/diff/formatters/text_formatter.rb | 24 +++++- lib/gitlab/diff/position.rb | 73 ++++++------------- 5 files changed, 126 insertions(+), 54 deletions(-) create mode 100644 lib/gitlab/diff/formatters/image_formatter.rb diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 1dabd4ebdd08..284a153a66c1 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -35,6 +35,11 @@ module Gitlab new_path: new_path, old_line: line.old_line, new_line: line.new_line, + file_type: "image", + width: 100, + height: 100, + x_axis: 1, + y_axis: 100, diff_refs: diff_refs ) end diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index e271f0ac10f2..95df8518d162 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -2,16 +2,53 @@ module Gitlab module Diff module Formatters class BaseFormatter - def initialize + attr_reader :old_path + attr_reader :new_path + attr_reader :base_sha + attr_reader :start_sha + attr_reader :head_sha + + def initialize(attrs) + if diff_file = attrs[:diff_file] + attrs[:diff_refs] = diff_file.diff_refs + attrs[:old_path] = diff_file.old_path + attrs[:new_path] = diff_file.new_path + end + + if diff_refs = attrs[:diff_refs] + attrs[:base_sha] = diff_refs.base_sha + attrs[:start_sha] = diff_refs.start_sha + attrs[:head_sha] = diff_refs.head_sha + end + + @old_path = attrs[:old_path] + @new_path = attrs[:new_path] + @base_sha = attrs[:base_sha] + @start_sha = attrs[:start_sha] + @head_sha = attrs[:head_sha] end def key + [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || "")] + end + + def complete? raise NotImplementedError end - def to_h + def position_type raise NotImplementedError end + + def to_h + { + base_sha: base_sha, + start_sha: start_sha, + head_sha: head_sha, + old_path: old_path, + new_path: new_path + } + end end end end diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb new file mode 100644 index 000000000000..62ec27017178 --- /dev/null +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -0,0 +1,37 @@ +module Gitlab + module Diff + module Formatters + class ImageFormatter < BaseFormatter + attr_reader :width + attr_reader :height + attr_reader :x_axis + attr_reader :y_axis + + def initialize(attrs) + @x_axis = attrs[:x_axis] + @y_axis = attrs[:y_axis] + @width = attrs[:width] + @height = attrs[:height] + + super(attrs) + end + + def key + @key ||= super.push(x_axis, y_axis) + end + + def complete? + x_axis && y_axis && width && height + end + + def to_h + super.merge(width: width, height: height, x_axis: x_axis, y_axis: y_axis) + end + + def position_type + :image + end + end + end + end +end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index 598c30879961..d31b9a48908e 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -2,8 +2,30 @@ module Gitlab module Diff module Formatters class TextFormatter < BaseFormatter + attr_reader :old_line + attr_reader :new_line + + def initialize(attrs) + @old_line = attrs[:old_line] + @new_line = attrs[:new_line] + + super(attrs) + end + def key - raise NotImplementedError + @key ||= super.push(old_line, new_line) + end + + def complete? + old_line || new_line + end + + def to_h + super.merge(old_line: old_line, new_line: new_line) + end + + def position_type + :text end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index b2dbcbbe56b3..ae737e476b84 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -3,39 +3,21 @@ module Gitlab module Diff class Position - FORMATTER_CLASS_SUFIX = "_formatter".freeze + FORMATTER_CLASS_SUFFIX = "_formatter".freeze - attr_reader :old_path - attr_reader :new_path - attr_reader :old_line - attr_reader :new_line - attr_reader :base_sha - attr_reader :start_sha - attr_reader :head_sha - attr_reader :component_type + attr_accessor :formatter - def initialize(attrs = {}) - if diff_file = attrs[:diff_file] - attrs[:diff_refs] = diff_file.diff_refs - attrs[:old_path] = diff_file.old_path - attrs[:new_path] = diff_file.new_path - end - - if diff_refs = attrs[:diff_refs] - attrs[:base_sha] = diff_refs.base_sha - attrs[:start_sha] = diff_refs.start_sha - attrs[:head_sha] = diff_refs.head_sha - end - - @old_path = attrs[:old_path] - @new_path = attrs[:new_path] - @base_sha = attrs[:base_sha] - @start_sha = attrs[:start_sha] - @head_sha = attrs[:head_sha] + delegate :old_path, + :old_line, + :new_line, + :new_path, + :base_sha, + :start_sha, + :head_sha, + :component_type, :to => :formatter - formatter_class = Gitlab::Diff::Formatters.const_get(formatter_class_name) - - formatter = formatter_class.new(coder) + def initialize(attrs = {}) + @formatter = get_formatter_class(attrs[:file_type]).new(attrs) end # `Gitlab::Diff::Position` objects are stored as serialized attributes in @@ -50,41 +32,27 @@ module Gitlab end def encode_with(coder) - coder['attributes'] = self.to_h + coder['attributes'] = formatter.to_h end def key - @key ||= [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || ""), old_line, new_line] + formatter.key end def ==(other) other.is_a?(self.class) && key == other.key end - # def to_h - # { - # old_path: old_path, - # new_path: new_path, - # old_line: old_line, - # new_line: new_line, - # base_sha: base_sha, - # start_sha: start_sha, - # head_sha: head_sha - # } - # end - def inspect - %(#<#{self.class}:#{object_id} #{to_h}>) + %(#<#{self.class}:#{object_id} #{formatter.to_h}>) end def complete? - file_path.present? && - (old_line || new_line) && - diff_refs.complete? + file_path.present? && formatter.complete? && diff_refs.complete? end def to_json(opts = nil) - JSON.generate(self.to_h, opts) + JSON.generate(formatter.to_h, opts) end def type @@ -154,8 +122,11 @@ module Gitlab diff_refs.compare_in(repository.project).diffs(paths: paths, expanded: true).diff_files.first end - def formatter_class_name - ("text" + FORMATTER_CLASS_SUFIX).classify + def get_formatter_class(type) + type ||= "text" + class_name = (type + FORMATTER_CLASS_SUFFIX).classify + + Gitlab::Diff::Formatters.const_get(class_name) end end end -- 2.22.0 From 93d9a4e8801da9cc273f30b3dea4d0caa8d4bcc1 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 15 Sep 2017 17:15:52 -0300 Subject: [PATCH 003/211] Remove wrong properties from helper --- lib/gitlab/diff/file.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 284a153a66c1..1dabd4ebdd08 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -35,11 +35,6 @@ module Gitlab new_path: new_path, old_line: line.old_line, new_line: line.new_line, - file_type: "image", - width: 100, - height: 100, - x_axis: 1, - y_axis: 100, diff_refs: diff_refs ) end -- 2.22.0 From 14bf3c059496cf0471fef1efb5516039abdc71ba Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 18 Sep 2017 16:44:47 -0300 Subject: [PATCH 004/211] Add image point object --- lib/gitlab/diff/image_point.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 lib/gitlab/diff/image_point.rb diff --git a/lib/gitlab/diff/image_point.rb b/lib/gitlab/diff/image_point.rb new file mode 100644 index 000000000000..6f413ba7dac8 --- /dev/null +++ b/lib/gitlab/diff/image_point.rb @@ -0,0 +1,23 @@ +module Gitlab + module Diff + class ImagePoint + attr_reader :width, :height, :x_axis, :y_axis + + def initialize(width, height, x_axis, y_axis) + @width = width + @height = height + @x_axis = x_axis + @y_axis = y_axis + end + + def as_json(opts = nil) + { + width: width, + height: height, + x_axis: x_axis, + y_axis: y_axis + } + end + end + end +end -- 2.22.0 From ef92f444f985444117f969f0c84a2fe5baf069ab Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 19 Sep 2017 16:37:00 -0300 Subject: [PATCH 005/211] Add image comment button --- app/helpers/notes_helper.rb | 10 +++++----- .../projects/diffs/viewers/_image.html.haml | 6 ++++++ lib/gitlab/diff/file.rb | 18 ++++++++++++------ lib/gitlab/diff/image_point.rb | 9 +++++++++ lib/gitlab/diff/line.rb | 5 +++++ lib/gitlab/diff/line_code.rb | 4 ++-- lib/gitlab/diff/position.rb | 2 +- 7 files changed, 40 insertions(+), 14 deletions(-) diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index ce028195e519..726576a001ad 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -29,12 +29,12 @@ module NotesHelper @new_diff_note_attrs.slice(:noteable_id, :noteable_type, :commit_id) end - def diff_view_line_data(line_code, position, line_type) + def diff_view_position_data(position_code, position, type) return if @diff_notes_disabled data = { - line_code: line_code, - line_type: line_type + line_code: position_code, + line_type: type } if @use_legacy_diff_notes @@ -47,13 +47,13 @@ module NotesHelper data end - def add_diff_note_button(line_code, position, line_type) + def add_diff_note_button(position_code, position, type=nil) 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), + data: diff_view_position_data(position_code, position, type), title: 'Add a comment to this line' do icon('comment-o') end diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 018795568949..b3cfef9538a5 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -4,6 +4,12 @@ - blob_raw_path = diff_file_blob_raw_path(diff_file) - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) +/ For test test purposes +- image_point = Gitlab::Diff::ImagePoint.new(300,300, 1, 300) # Point on top left corner +- position_code = diff_file.line_code(image_point) +.comment-test-button + = add_diff_note_button(position_code, diff_file.position(image_point, :image)) + - if diff_file.new_file? || diff_file.deleted_file? .image %span.wrap diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 1dabd4ebdd08..9818bd564635 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -27,22 +27,28 @@ module Gitlab @fallback_diff_refs = fallback_diff_refs end - def position(line) + def position(line, file_type=:text) return unless diff_refs Position.new( + file_type: file_type, old_path: old_path, new_path: new_path, - old_line: line.old_line, - new_line: line.new_line, + # Move these into separate objects + old_line: line.try(:old_line), + new_line: line.try(:new_line), + x_axis: line.try(:x_axis), + y_axis: line.try(:y_axis), + width: line.try(:width), + height: line.try(:height), diff_refs: diff_refs ) end - def line_code(line) - return if line.meta? + def line_code(marker) + return if marker.meta? - Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) + Gitlab::Diff::LineCode.generate(file_path, marker.key_attributes) end def line_for_line_code(code) diff --git a/lib/gitlab/diff/image_point.rb b/lib/gitlab/diff/image_point.rb index 6f413ba7dac8..c0512289651c 100644 --- a/lib/gitlab/diff/image_point.rb +++ b/lib/gitlab/diff/image_point.rb @@ -10,6 +10,11 @@ module Gitlab @y_axis = y_axis end + # Create superclass method with NotImplemented + def key_attributes + [x_axis, y_axis] + end + def as_json(opts = nil) { width: width, @@ -18,6 +23,10 @@ module Gitlab y_axis: y_axis } end + # Move to parent class + def meta? + false + end end end end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 0603141e4417..c6b1d72a20e3 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -15,6 +15,11 @@ module Gitlab new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos]) end + # Used to build position code and group discussions + def key_attributes + [old_pos, new_pos] + end + def serialize_keys @serialize_keys ||= %i(text type index old_pos new_pos) end diff --git a/lib/gitlab/diff/line_code.rb b/lib/gitlab/diff/line_code.rb index f3578ab3d351..4b07228ddcd5 100644 --- a/lib/gitlab/diff/line_code.rb +++ b/lib/gitlab/diff/line_code.rb @@ -1,8 +1,8 @@ module Gitlab module Diff class LineCode - def self.generate(file_path, new_line_position, old_line_position) - "#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}" + def self.generate(file_path, key_attributes) + "#{Digest::SHA1.hexdigest(file_path)}_#{key_attributes[0]}_#{key_attributes[1]}" end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index ae737e476b84..bf80b82f11b2 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -124,7 +124,7 @@ module Gitlab def get_formatter_class(type) type ||= "text" - class_name = (type + FORMATTER_CLASS_SUFFIX).classify + class_name = (type.to_s + FORMATTER_CLASS_SUFFIX).classify Gitlab::Diff::Formatters.const_get(class_name) end -- 2.22.0 From 6a372101de078c01e5ccf62dd0d0dd1119f7deac Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 11:29:50 -0500 Subject: [PATCH 006/211] [skip ci] Add test to create image comment --- app/assets/javascripts/notes.js | 33 ++++++++++++++++++- app/assets/stylesheets/pages/diff.scss | 10 ++++++ app/helpers/notes_helper.rb | 8 ++++- .../projects/diffs/viewers/_image.html.haml | 7 ++-- app/views/shared/notes/_form.html.haml | 4 +++ 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index f5f7bb4653db..dd35d5a76c74 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -41,6 +41,7 @@ export default class Notes { this.visibilityChange = this.visibilityChange.bind(this); this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this); this.onAddDiffNote = this.onAddDiffNote.bind(this); + this.onAddImageDiffNote = this.onAddImageDiffNote.bind(this); this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this); this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this); this.removeNote = this.removeNote.bind(this); @@ -113,6 +114,8 @@ export default class Notes { $(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote); // add diff note $(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote); + // add diff note for images + $(document).on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote); // hide diff note form $(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm); // toggle commit list @@ -139,6 +142,7 @@ export default class Notes { $(document).off('click', '.js-note-attachment-delete'); $(document).off('click', '.js-discussion-reply-button'); $(document).off('click', '.js-add-diff-note-button'); + $(document).off('click', '.js-add-image-diff-note-button'); $(document).off('visibilitychange'); $(document).off('keyup input', '.js-note-text'); $(document).off('click', '.js-note-target-reopen'); @@ -522,6 +526,7 @@ export default class Notes { // fix classes form.removeClass('js-new-note-form'); form.addClass('js-main-target-form'); + form.find('#component_type').remove(); form.find('#note_line_code').remove(); form.find('#note_position').remove(); form.find('#note_type').val(''); @@ -560,7 +565,9 @@ export default class Notes { form.find('#note_line_code').val(), // DiffNote - form.find('#note_position').val() + form.find('#note_position').val(), + + form.find('#component_type').val(), ]; return new Autosave(textarea, key); } @@ -866,6 +873,11 @@ export default class Notes { // DiffNote form.find('#note_position').val(dataHolder.attr('data-position')); + const componentType = dataHolder.attr('data-component-type'); + if (componentType) { + form.find('#component_type').val(componentType); + } + 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')); form.find('.js-note-target-close').remove(); form.find('.js-note-new-discussion').remove(); @@ -906,6 +918,25 @@ export default class Notes { }); } + onAddImageDiffNote(e) { + const $link = $(e.currentTarget || e.target); + const newForm = this.cleanForm(this.formClone.clone()); + // debugger + newForm.appendTo($link.closest('.diff-viewer').find('.note-container')); + // // show the form + return this.setupDiscussionNoteForm($link, newForm); + + // 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 + // }); + } + toggleDiffNote({ target, lineType, diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 951580ea1fe6..d2ba264c97d7 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -647,3 +647,13 @@ text-overflow: ellipsis; white-space: nowrap; } + +.note-container { + background-color: $gray-light; + border-top: 1px solid $white-normal; +} + +.diff-file .note-container .new-note { + margin-left: 100px; + border-left: 1px solid $white-normal; +} diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 726576a001ad..624e0b38fe6d 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -29,7 +29,7 @@ module NotesHelper @new_diff_note_attrs.slice(:noteable_id, :noteable_type, :commit_id) end - def diff_view_position_data(position_code, position, type) + def diff_view_position_data(position_code, position, type, component_type: 'text') return if @diff_notes_disabled data = { @@ -44,6 +44,12 @@ module NotesHelper data[:position] = position.to_json end + # Use text or image, todo: make this better + data[:component_type] = component_type + + + puts component_type + data end diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index b3cfef9538a5..60d36f414ee2 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -5,17 +5,16 @@ - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) / For test test purposes -- image_point = Gitlab::Diff::ImagePoint.new(300,300, 1, 300) # Point on top left corner +- image_point = Gitlab::Diff::ImagePoint.new(300, 300, 0, 0) # Point on top left corner - position_code = diff_file.line_code(image_point) -.comment-test-button - = add_diff_note_button(position_code, diff_file.position(image_point, :image)) - if diff_file.new_file? || diff_file.deleted_file? .image %span.wrap - .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') } + .frame.js-add-image-diff-note-button{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil, component_type: 'image') } = image_tag(blob_raw_path, alt: diff_file.file_path) %p.image-info= number_to_human_size(blob.size) + .note-container - else .image .two-up.view diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index 725bf9165923..a15734ce5af7 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -12,6 +12,9 @@ = hidden_field_tag :in_reply_to_discussion_id = hidden_field_tag :note_project_id + -# ImageDiff + = hidden_field_tag :component_type + = note_target_fields(@note) = f.hidden_field :noteable_type = f.hidden_field :noteable_id @@ -24,6 +27,7 @@ -# DiffNote = f.hidden_field :position + = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do = render 'projects/zen', f: f, attr: :note, -- 2.22.0 From df95687ec1d1a11c18794eaf81816bf4ec68e487 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 12:33:49 -0500 Subject: [PATCH 007/211] [skip ci] pass diff_view_data to image viewer --- app/assets/javascripts/notes.js | 38 +++++++++++-------- .../projects/diffs/viewers/_image.html.haml | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index dd35d5a76c74..5419c87f2c23 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -847,7 +847,11 @@ export default class Notes { */ setupDiscussionNoteForm(dataHolder, form) { // setup note target - const diffFileData = dataHolder.closest('.text-file'); + let diffFileData = dataHolder.closest('.text-file'); + + if (diffFileData.length === 0) { + diffFileData = dataHolder.closest('.image'); + } var discussionID = dataHolder.data('discussionId'); @@ -920,21 +924,25 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); - const newForm = this.cleanForm(this.formClone.clone()); + + const container = event.target.parentElement; + const x = event.offsetX ? (event.offsetX) : event.pageX - container.offsetLeft; + const y = event.offsetY ? (event.offsetY) : event.pageY - container.offsetTop; + + console.log(x, y) // debugger - newForm.appendTo($link.closest('.diff-viewer').find('.note-container')); - // // show the form - return this.setupDiscussionNoteForm($link, newForm); - - // 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 - // }); + + $(container).append(``) + + const noteContainer = $link.closest('.diff-viewer').find('.note-container'); + + if (noteContainer.find('form').length === 0) { + const newForm = this.cleanForm(this.formClone.clone()); + newForm.appendTo($link.closest('.diff-viewer').find('.note-container')); + return this.setupDiscussionNoteForm($link, newForm); + } else { + // change coordinates of existing form + } } toggleDiffNote({ diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 60d36f414ee2..f7ed06752533 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -9,7 +9,7 @@ - position_code = diff_file.line_code(image_point) - if diff_file.new_file? || diff_file.deleted_file? - .image + .image{ data: diff_view_data } %span.wrap .frame.js-add-image-diff-note-button{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil, component_type: 'image') } = image_tag(blob_raw_path, alt: diff_file.file_path) -- 2.22.0 From 3648890cf09da70c09fe64dc5576c3f39160c4ce Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 15:25:09 -0500 Subject: [PATCH 008/211] [skip ci] Add image diff helper --- app/assets/images/icon_image_comment.svg | 36 ++++++++++ app/assets/images/icon_image_comment_dark.svg | 36 ++++++++++ .../javascripts/commit/image_diff_helper.js | 69 +++++++++++++++++++ app/assets/javascripts/notes.js | 19 +++-- app/assets/stylesheets/pages/diff.scss | 17 +++++ .../projects/diffs/viewers/_image.html.haml | 4 +- 6 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 app/assets/images/icon_image_comment.svg create mode 100644 app/assets/images/icon_image_comment_dark.svg create mode 100644 app/assets/javascripts/commit/image_diff_helper.js diff --git a/app/assets/images/icon_image_comment.svg b/app/assets/images/icon_image_comment.svg new file mode 100644 index 000000000000..4f4a4608d770 --- /dev/null +++ b/app/assets/images/icon_image_comment.svg @@ -0,0 +1,36 @@ + +cursor +Created using Figma + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/icon_image_comment_dark.svg b/app/assets/images/icon_image_comment_dark.svg new file mode 100644 index 000000000000..e2364a2f89f5 --- /dev/null +++ b/app/assets/images/icon_image_comment_dark.svg @@ -0,0 +1,36 @@ + +cursor_active +Created using Figma + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js new file mode 100644 index 000000000000..47962c670e4a --- /dev/null +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -0,0 +1,69 @@ +export function getTargetSelection(event) { + const target = event.target; + const container = target.parentElement; + const x = event.offsetX ? (event.offsetX) : event.pageX - container.offsetLeft; + const y = event.offsetY ? (event.offsetY) : event.pageY - container.offsetTop; + + const width = target.width; + const height = target.height; + + const actualWidth = target.naturalWidth; + const actualHeight = target.naturalHeight; + + const widthRatio = actualWidth / width; + const heightRatio = actualHeight / height; + + // TODO: x, y contains the top left selection of the cursor + // and does not equate to the pointy part of the comment image + // Need to determine if we need to do offset calculations + + return { + browser: { + x, + y, + width, + height, + }, + actual: { + x: x * widthRatio, + y: y * heightRatio, + width: actualWidth, + height: actualHeight, + } + }; +} + +export function setLineCodeCoordinates(el, x, y) { + const lineCode = el.dataset.lineCode; + + // TODO: Temporarily remove the trailing numbers that define the x and y coordinates + // Until backend strips this out for us + const lineCodeWithoutCoordinates = lineCode.match(/^(.*?)_/)[0]; + + el.dataset.lineCode = `${lineCodeWithoutCoordinates}${x}_${y}`; +} + +export function setPositionCoordinates(el, x, y) { + const position = el.dataset.position; + const positionObject = JSON.parse(position); + positionObject.x_axis = x; + positionObject.y_axis = y; + + el.dataset.position = JSON.stringify(positionObject); +} + +export function setCommentSelectionIndicator(containerEl, x, y) { + const button = document.createElement('button'); + button.classList.add('btn-transparent', 'comment-selection'); + button.setAttribute('type', 'button'); + button.style.left = `${x}px`; + button.style.top = `${y}px`; + + const image = document.createElement('img'); + image.classList.add('image-comment-dark'); + image.src = '/assets/icon_image_comment_dark.svg'; + image.alt = 'comment selection indicator'; + + button.appendChild(image); + containerEl.appendChild(button); +} \ No newline at end of file diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 5419c87f2c23..ed3607d7d5a3 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -23,6 +23,7 @@ import loadAwardsHandler from './awards_handler'; import './autosave'; import './dropzone_input'; import TaskList from './task_list'; +import * as imageDiffHelper from './commit/image_diff_helper'; window.autosize = autosize; window.Dropzone = Dropzone; @@ -925,14 +926,20 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); - const container = event.target.parentElement; - const x = event.offsetX ? (event.offsetX) : event.pageX - container.offsetLeft; - const y = event.offsetY ? (event.offsetY) : event.pageY - container.offsetTop; + const selection = imageDiffHelper.getTargetSelection(event); - console.log(x, y) - // debugger + const $container = $(event.target.parentElement); + const $commentSelection = $container.find('.comment-selection'); - $(container).append(``) + if ($commentSelection.length !== 0) { + $commentSelection.css('left', selection.browser.x); + $commentSelection.css('top', selection.browser.y); + } else { + imageDiffHelper.setCommentSelectionIndicator($container[0], selection.browser.x, selection.browser.y); + } + + imageDiffHelper.setLineCodeCoordinates($link[0], selection.actual.x, selection.actual.y); + imageDiffHelper.setPositionCoordinates($link[0], selection.actual.x, selection.actual.y); const noteContainer = $link.closest('.diff-viewer').find('.note-container'); diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index d2ba264c97d7..6b93ae33b63d 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -657,3 +657,20 @@ margin-left: 100px; border-left: 1px solid $white-normal; } + +.frame.click-to-comment { + position: relative; + cursor: url(icon_image_comment.svg), auto; + + .comment-selection { + position: absolute; + + .image-comment-dark { + background-image: inherit; + border: none; + background-position: inherit; + background-size: inherit; + max-width: inherit; + } + } +} diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index f7ed06752533..c296daad9101 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -11,8 +11,8 @@ - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } %span.wrap - .frame.js-add-image-diff-note-button{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil, component_type: 'image') } - = image_tag(blob_raw_path, alt: diff_file.file_path) + .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil, component_type: 'image') } + = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) %p.image-info= number_to_human_size(blob.size) .note-container - else -- 2.22.0 From bc029418fe95bd06239fc9ce2369b273c0b87af9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 17:06:54 -0500 Subject: [PATCH 009/211] [skip ci] code cleanup for frontend --- app/assets/javascripts/commit/image_diff_helper.js | 8 ++++---- app/assets/stylesheets/pages/diff.scss | 1 + app/helpers/notes_helper.rb | 3 --- app/views/shared/notes/_form.html.haml | 1 - 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index 47962c670e4a..0d984d0df069 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -29,7 +29,7 @@ export function getTargetSelection(event) { y: y * heightRatio, width: actualWidth, height: actualHeight, - } + }, }; } @@ -40,7 +40,7 @@ export function setLineCodeCoordinates(el, x, y) { // Until backend strips this out for us const lineCodeWithoutCoordinates = lineCode.match(/^(.*?)_/)[0]; - el.dataset.lineCode = `${lineCodeWithoutCoordinates}${x}_${y}`; + el.setAttribute('dataset-line-code', `${lineCodeWithoutCoordinates}${x}_${y}`); } export function setPositionCoordinates(el, x, y) { @@ -49,7 +49,7 @@ export function setPositionCoordinates(el, x, y) { positionObject.x_axis = x; positionObject.y_axis = y; - el.dataset.position = JSON.stringify(positionObject); + el.setAttribute('data-position', JSON.stringify(positionObject)); } export function setCommentSelectionIndicator(containerEl, x, y) { @@ -66,4 +66,4 @@ export function setCommentSelectionIndicator(containerEl, x, y) { button.appendChild(image); containerEl.appendChild(button); -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 6b93ae33b63d..48ea9742d1da 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -666,6 +666,7 @@ position: absolute; .image-comment-dark { + // Override styling set by .image .frame background-image: inherit; border: none; background-position: inherit; diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 624e0b38fe6d..3134379bd1d4 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -47,9 +47,6 @@ module NotesHelper # Use text or image, todo: make this better data[:component_type] = component_type - - puts component_type - data end diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index a15734ce5af7..bffe4f88d3bc 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -27,7 +27,6 @@ -# DiffNote = f.hidden_field :position - = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do = render 'projects/zen', f: f, attr: :note, -- 2.22.0 From e377054aa73a74b3cf9c12cf0cfdf190e1846212 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 17:22:40 -0500 Subject: [PATCH 010/211] [skip ci] pass component type in options data attribute --- app/assets/javascripts/commit/image_diff_helper.js | 3 ++- app/assets/javascripts/notes.js | 10 +--------- app/helpers/notes_helper.rb | 5 +---- app/views/projects/diffs/viewers/_image.html.haml | 2 +- app/views/shared/notes/_form.html.haml | 3 --- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index 0d984d0df069..89e13bc27ef0 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -43,11 +43,12 @@ export function setLineCodeCoordinates(el, x, y) { el.setAttribute('dataset-line-code', `${lineCodeWithoutCoordinates}${x}_${y}`); } -export function setPositionCoordinates(el, x, y) { +export function setPositionDataAttribute(el, x, y) { const position = el.dataset.position; const positionObject = JSON.parse(position); positionObject.x_axis = x; positionObject.y_axis = y; + positionObject.component_type = 'image'; el.setAttribute('data-position', JSON.stringify(positionObject)); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index ed3607d7d5a3..c5064f5bfd0e 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -527,7 +527,6 @@ export default class Notes { // fix classes form.removeClass('js-new-note-form'); form.addClass('js-main-target-form'); - form.find('#component_type').remove(); form.find('#note_line_code').remove(); form.find('#note_position').remove(); form.find('#note_type').val(''); @@ -567,8 +566,6 @@ export default class Notes { // DiffNote form.find('#note_position').val(), - - form.find('#component_type').val(), ]; return new Autosave(textarea, key); } @@ -878,11 +875,6 @@ export default class Notes { // DiffNote form.find('#note_position').val(dataHolder.attr('data-position')); - const componentType = dataHolder.attr('data-component-type'); - if (componentType) { - form.find('#component_type').val(componentType); - } - 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')); form.find('.js-note-target-close').remove(); form.find('.js-note-new-discussion').remove(); @@ -939,7 +931,7 @@ export default class Notes { } imageDiffHelper.setLineCodeCoordinates($link[0], selection.actual.x, selection.actual.y); - imageDiffHelper.setPositionCoordinates($link[0], selection.actual.x, selection.actual.y); + imageDiffHelper.setPositionDataAttribute($link[0], selection.actual.x, selection.actual.y); const noteContainer = $link.closest('.diff-viewer').find('.note-container'); diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 3134379bd1d4..726576a001ad 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -29,7 +29,7 @@ module NotesHelper @new_diff_note_attrs.slice(:noteable_id, :noteable_type, :commit_id) end - def diff_view_position_data(position_code, position, type, component_type: 'text') + def diff_view_position_data(position_code, position, type) return if @diff_notes_disabled data = { @@ -44,9 +44,6 @@ module NotesHelper data[:position] = position.to_json end - # Use text or image, todo: make this better - data[:component_type] = component_type - data end diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index c296daad9101..a071de8e761c 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -11,7 +11,7 @@ - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } %span.wrap - .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil, component_type: 'image') } + .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil) } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) %p.image-info= number_to_human_size(blob.size) .note-container diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index bffe4f88d3bc..725bf9165923 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -12,9 +12,6 @@ = hidden_field_tag :in_reply_to_discussion_id = hidden_field_tag :note_project_id - -# ImageDiff - = hidden_field_tag :component_type - = note_target_fields(@note) = f.hidden_field :noteable_type = f.hidden_field :noteable_id -- 2.22.0 From 78966d575ff0193373ad7e743d5f6e180cd3df50 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 17:31:01 -0500 Subject: [PATCH 011/211] [skip ci] save width and height to position data attribute --- app/assets/javascripts/commit/image_diff_helper.js | 10 +++++++--- app/assets/javascripts/notes.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index 89e13bc27ef0..42a78c1083e7 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -25,8 +25,9 @@ export function getTargetSelection(event) { height, }, actual: { - x: x * widthRatio, - y: y * heightRatio, + // Round x, y so that we don't need to deal with decimals + x: Math.round(x * widthRatio), + y: Math.round(y * heightRatio), width: actualWidth, height: actualHeight, }, @@ -43,11 +44,14 @@ export function setLineCodeCoordinates(el, x, y) { el.setAttribute('dataset-line-code', `${lineCodeWithoutCoordinates}${x}_${y}`); } -export function setPositionDataAttribute(el, x, y) { +export function setPositionDataAttribute(el, options) { + const { x, y, width, height } = options; const position = el.dataset.position; const positionObject = JSON.parse(position); positionObject.x_axis = x; positionObject.y_axis = y; + positionObject.width = width; + positionObject.height = height; positionObject.component_type = 'image'; el.setAttribute('data-position', JSON.stringify(positionObject)); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index c5064f5bfd0e..1dec7ba33e71 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -931,7 +931,7 @@ export default class Notes { } imageDiffHelper.setLineCodeCoordinates($link[0], selection.actual.x, selection.actual.y); - imageDiffHelper.setPositionDataAttribute($link[0], selection.actual.x, selection.actual.y); + imageDiffHelper.setPositionDataAttribute($link[0], selection.actual); const noteContainer = $link.closest('.diff-viewer').find('.note-container'); -- 2.22.0 From 70d8a453ff9b7116a5c36227f680e79b9717cf0d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 21 Sep 2017 18:32:27 -0500 Subject: [PATCH 012/211] [skip ci] Add stop propagation for comment selection indicator --- app/assets/javascripts/commit/image_diff_helper.js | 2 ++ app/assets/javascripts/notes.js | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index 42a78c1083e7..c676421a925d 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -71,4 +71,6 @@ export function setCommentSelectionIndicator(containerEl, x, y) { button.appendChild(image); containerEl.appendChild(button); + + return button; } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 1dec7ba33e71..737f92f5a836 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -918,16 +918,18 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); - const selection = imageDiffHelper.getTargetSelection(event); - const $container = $(event.target.parentElement); const $commentSelection = $container.find('.comment-selection'); + const selection = imageDiffHelper.getTargetSelection(event); + // Set comment selection indicator if ($commentSelection.length !== 0) { $commentSelection.css('left', selection.browser.x); $commentSelection.css('top', selection.browser.y); } else { - imageDiffHelper.setCommentSelectionIndicator($container[0], selection.browser.x, selection.browser.y); + const button = imageDiffHelper.setCommentSelectionIndicator($container[0], selection.browser.x, selection.browser.y); + // TODO: Use button to focus the comment input + $(button).on('click', e => e.stopPropagation()); } imageDiffHelper.setLineCodeCoordinates($link[0], selection.actual.x, selection.actual.y); @@ -938,7 +940,7 @@ export default class Notes { if (noteContainer.find('form').length === 0) { const newForm = this.cleanForm(this.formClone.clone()); newForm.appendTo($link.closest('.diff-viewer').find('.note-container')); - return this.setupDiscussionNoteForm($link, newForm); + this.setupDiscussionNoteForm($link, newForm); } else { // change coordinates of existing form } -- 2.22.0 From 230d8768c57a4544cc61c2a7afd6d78e40a6f5b7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 09:55:15 -0500 Subject: [PATCH 013/211] [skip ci] Fix invalid attribute name --- app/assets/javascripts/commit/image_diff_helper.js | 2 +- app/assets/javascripts/notes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index c676421a925d..7d11d2132b41 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -41,7 +41,7 @@ export function setLineCodeCoordinates(el, x, y) { // Until backend strips this out for us const lineCodeWithoutCoordinates = lineCode.match(/^(.*?)_/)[0]; - el.setAttribute('dataset-line-code', `${lineCodeWithoutCoordinates}${x}_${y}`); + el.setAttribute('data-line-code', `${lineCodeWithoutCoordinates}${x}_${y}`); } export function setPositionDataAttribute(el, options) { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 737f92f5a836..6cc62362f0fa 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -942,7 +942,7 @@ export default class Notes { newForm.appendTo($link.closest('.diff-viewer').find('.note-container')); this.setupDiscussionNoteForm($link, newForm); } else { - // change coordinates of existing form + // TODO: change coordinates of existing form } } -- 2.22.0 From 39e5e27c333fbd4e8a6474a7555ffdd0a3f7b6c4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 10:28:29 -0500 Subject: [PATCH 014/211] [skip ci] change comment form metadata when new coordinate is selected --- app/assets/javascripts/notes.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 6cc62362f0fa..da27db69802a 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -870,7 +870,7 @@ export default class Notes { form.find('#note_type').val(dataHolder.data('noteType')); // LegacyDiffNote - form.find('#note_line_code').val(dataHolder.data('lineCode')); + form.find('#note_line_code').val(dataHolder.attr('data-line-code')); // DiffNote form.find('#note_position').val(dataHolder.attr('data-position')); @@ -932,18 +932,22 @@ export default class Notes { $(button).on('click', e => e.stopPropagation()); } + // Setup coordinates data for comment form imageDiffHelper.setLineCodeCoordinates($link[0], selection.actual.x, selection.actual.y); imageDiffHelper.setPositionDataAttribute($link[0], selection.actual); - const noteContainer = $link.closest('.diff-viewer').find('.note-container'); + // Setup comment form + let newForm; + const $noteContainer = $link.closest('.diff-viewer').find('.note-container'); - if (noteContainer.find('form').length === 0) { - const newForm = this.cleanForm(this.formClone.clone()); - newForm.appendTo($link.closest('.diff-viewer').find('.note-container')); - this.setupDiscussionNoteForm($link, newForm); + if ($noteContainer.find('form').length === 0) { + newForm = this.cleanForm(this.formClone.clone()); + newForm.appendTo($noteContainer); } else { - // TODO: change coordinates of existing form + newForm = $noteContainer.find('form'); } + + this.setupDiscussionNoteForm($link, newForm); } toggleDiffNote({ -- 2.22.0 From 3695940b3e84b87ab50a222ccc3986461051fff1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 10:42:19 -0500 Subject: [PATCH 015/211] [skip ci] focus comment textarea when comment selection indicator is clicked --- app/assets/javascripts/commit/image_diff_helper.js | 10 ++++++++++ app/assets/javascripts/notes.js | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index 7d11d2132b41..65fa1b7c5dd9 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -74,3 +74,13 @@ export function setCommentSelectionIndicator(containerEl, x, y) { return button; } + +export function commentSelectionIndicatorOnClick(e) { + // Prevent from triggering onAddImageDiffNote in notes.js + e.stopPropagation(); + + const button = e.currentTarget; + const diffViewer = button.closest('.diff-viewer'); + const textarea = diffViewer.querySelector('.note-container form .note-textarea'); + textarea.focus(); +} diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index da27db69802a..9a208c2c774b 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -928,8 +928,7 @@ export default class Notes { $commentSelection.css('top', selection.browser.y); } else { const button = imageDiffHelper.setCommentSelectionIndicator($container[0], selection.browser.x, selection.browser.y); - // TODO: Use button to focus the comment input - $(button).on('click', e => e.stopPropagation()); + $(button).on('click', imageDiffHelper.commentSelectionIndicatorOnClick); } // Setup coordinates data for comment form -- 2.22.0 From 7491f4e04ca1fd7fa2fb04cd1d1ec9fd43f97ac9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 11:32:27 -0500 Subject: [PATCH 016/211] [skip ci] refactor image diff frontend --- .../javascripts/image_diff/image_diff.js | 25 +++++++++++++++++++ .../image_diff_helper.js | 6 +++-- app/assets/javascripts/notes.js | 20 +++------------ 3 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 app/assets/javascripts/image_diff/image_diff.js rename app/assets/javascripts/{commit => image_diff}/image_diff_helper.js (93%) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js new file mode 100644 index 000000000000..6d7e5013beef --- /dev/null +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -0,0 +1,25 @@ +import * as imageDiffHelper from './image_diff_helper'; + +export function setCommentSelectionIndicator(event) { + const container = event.target.parentElement; + const commentSelection = container.querySelector('.comment-selection'); + const selection = imageDiffHelper.getTargetSelection(event); + + if (commentSelection) { + commentSelection.style.left = `${selection.browser.x}px`; + commentSelection.style.top = `${selection.browser.y}px`; + } else { + const button = imageDiffHelper + .addCommentSelectionIndicator(container, selection.browser); + + button.addEventListener('click', imageDiffHelper.commentSelectionIndicatorOnClick); + } +} + +export function setupCoordinatesData(event) { + const el = event.currentTarget; + const selection = imageDiffHelper.getTargetSelection(event); + + imageDiffHelper.setLineCodeCoordinates(el, selection.actual); + imageDiffHelper.setPositionDataAttribute(el, selection.actual); +} diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js similarity index 93% rename from app/assets/javascripts/commit/image_diff_helper.js rename to app/assets/javascripts/image_diff/image_diff_helper.js index 65fa1b7c5dd9..c4a3e71da075 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -34,7 +34,8 @@ export function getTargetSelection(event) { }; } -export function setLineCodeCoordinates(el, x, y) { +export function setLineCodeCoordinates(el, coordinate) { + const { x, y } = coordinate; const lineCode = el.dataset.lineCode; // TODO: Temporarily remove the trailing numbers that define the x and y coordinates @@ -57,7 +58,8 @@ export function setPositionDataAttribute(el, options) { el.setAttribute('data-position', JSON.stringify(positionObject)); } -export function setCommentSelectionIndicator(containerEl, x, y) { +export function addCommentSelectionIndicator(containerEl, coordinate) { + const { x, y } = coordinate; const button = document.createElement('button'); button.classList.add('btn-transparent', 'comment-selection'); button.setAttribute('type', 'button'); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 9a208c2c774b..ea610fe7f788 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -23,7 +23,7 @@ import loadAwardsHandler from './awards_handler'; import './autosave'; import './dropzone_input'; import TaskList from './task_list'; -import * as imageDiffHelper from './commit/image_diff_helper'; +import * as imageDiff from './image_diff/image_diff'; window.autosize = autosize; window.Dropzone = Dropzone; @@ -918,22 +918,8 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); - const $container = $(event.target.parentElement); - const $commentSelection = $container.find('.comment-selection'); - const selection = imageDiffHelper.getTargetSelection(event); - - // Set comment selection indicator - if ($commentSelection.length !== 0) { - $commentSelection.css('left', selection.browser.x); - $commentSelection.css('top', selection.browser.y); - } else { - const button = imageDiffHelper.setCommentSelectionIndicator($container[0], selection.browser.x, selection.browser.y); - $(button).on('click', imageDiffHelper.commentSelectionIndicatorOnClick); - } - - // Setup coordinates data for comment form - imageDiffHelper.setLineCodeCoordinates($link[0], selection.actual.x, selection.actual.y); - imageDiffHelper.setPositionDataAttribute($link[0], selection.actual); + imageDiff.setCommentSelectionIndicator(e); + imageDiff.setupCoordinatesData(e); // Setup comment form let newForm; -- 2.22.0 From a4df73c7ca555852623a631f9701f9c866185ef5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 11:34:17 -0500 Subject: [PATCH 017/211] [skip ci] rename setCommentSelectionIndicator to showCommentSelectionIndicator --- app/assets/javascripts/image_diff/image_diff.js | 2 +- app/assets/javascripts/notes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 6d7e5013beef..6b660037e705 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,6 +1,6 @@ import * as imageDiffHelper from './image_diff_helper'; -export function setCommentSelectionIndicator(event) { +export function showCommentSelectionIndicator(event) { const container = event.target.parentElement; const commentSelection = container.querySelector('.comment-selection'); const selection = imageDiffHelper.getTargetSelection(event); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index ea610fe7f788..45e16ea64de2 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -918,7 +918,7 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); - imageDiff.setCommentSelectionIndicator(e); + imageDiff.showCommentSelectionIndicator(e); imageDiff.setupCoordinatesData(e); // Setup comment form -- 2.22.0 From 7ea9515a7b168fcd3384aeac5222d8d446c09e52 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 11:48:02 -0500 Subject: [PATCH 018/211] [skip ci] rename commentSelectionIndicator to commentIndicator --- app/assets/javascripts/image_diff/image_diff.js | 14 +++++++------- .../javascripts/image_diff/image_diff_helper.js | 8 ++++---- app/assets/javascripts/notes.js | 2 +- app/assets/stylesheets/pages/diff.scss | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 6b660037e705..fbe7036e4fd1 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,18 +1,18 @@ import * as imageDiffHelper from './image_diff_helper'; -export function showCommentSelectionIndicator(event) { +export function showCommentIndicator(event) { const container = event.target.parentElement; - const commentSelection = container.querySelector('.comment-selection'); + const commentIndicator = container.querySelector('.comment-indicator'); const selection = imageDiffHelper.getTargetSelection(event); - if (commentSelection) { - commentSelection.style.left = `${selection.browser.x}px`; - commentSelection.style.top = `${selection.browser.y}px`; + if (commentIndicator) { + commentIndicator.style.left = `${selection.browser.x}px`; + commentIndicator.style.top = `${selection.browser.y}px`; } else { const button = imageDiffHelper - .addCommentSelectionIndicator(container, selection.browser); + .addCommentIndicator(container, selection.browser); - button.addEventListener('click', imageDiffHelper.commentSelectionIndicatorOnClick); + button.addEventListener('click', imageDiffHelper.commentIndicatorOnClick); } } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index c4a3e71da075..eea4b4ade342 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -58,10 +58,10 @@ export function setPositionDataAttribute(el, options) { el.setAttribute('data-position', JSON.stringify(positionObject)); } -export function addCommentSelectionIndicator(containerEl, coordinate) { +export function addCommentIndicator(containerEl, coordinate) { const { x, y } = coordinate; const button = document.createElement('button'); - button.classList.add('btn-transparent', 'comment-selection'); + button.classList.add('btn-transparent', 'comment-indicator'); button.setAttribute('type', 'button'); button.style.left = `${x}px`; button.style.top = `${y}px`; @@ -69,7 +69,7 @@ export function addCommentSelectionIndicator(containerEl, coordinate) { const image = document.createElement('img'); image.classList.add('image-comment-dark'); image.src = '/assets/icon_image_comment_dark.svg'; - image.alt = 'comment selection indicator'; + image.alt = 'comment indicator'; button.appendChild(image); containerEl.appendChild(button); @@ -77,7 +77,7 @@ export function addCommentSelectionIndicator(containerEl, coordinate) { return button; } -export function commentSelectionIndicatorOnClick(e) { +export function commentIndicatorOnClick(e) { // Prevent from triggering onAddImageDiffNote in notes.js e.stopPropagation(); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 45e16ea64de2..96d0b0d5c432 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -918,7 +918,7 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); - imageDiff.showCommentSelectionIndicator(e); + imageDiff.showCommentIndicator(e); imageDiff.setupCoordinatesData(e); // Setup comment form diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 48ea9742d1da..e5329a226851 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -662,7 +662,7 @@ position: relative; cursor: url(icon_image_comment.svg), auto; - .comment-selection { + .comment-indicator { position: absolute; .image-comment-dark { -- 2.22.0 From 8689d095eab1b9821fd6218eab7f4c4e6accc519 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 11:48:21 -0500 Subject: [PATCH 019/211] [skip ci] add hideCommentIndicator --- app/assets/javascripts/image_diff/image_diff.js | 9 +++++++++ app/assets/javascripts/notes.js | 3 +++ 2 files changed, 12 insertions(+) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index fbe7036e4fd1..80be0fdfd0d8 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -16,6 +16,15 @@ export function showCommentIndicator(event) { } } +export function hideCommentIndicator(event) { + const container = event.target.closest('.diff-viewer'); + const commentIndicator = container.querySelector('.comment-indicator'); + + if (commentIndicator) { + commentIndicator.remove(); + } +} + export function setupCoordinatesData(event) { const el = event.currentTarget; const selection = imageDiffHelper.getTargetSelection(event); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 96d0b0d5c432..6accfc9016f9 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1030,6 +1030,9 @@ export default class Notes { var form; e.preventDefault(); form = $(e.target).closest('.js-discussion-note-form'); + + imageDiff.hideCommentIndicator(e); + return this.removeDiscussionNoteForm(form); } -- 2.22.0 From cb0d5c7c4cb198734cb857258ee4ed157b08e5a8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 12:31:42 -0500 Subject: [PATCH 020/211] [skip ci] hide comment indicator after comment post --- app/assets/javascripts/image_diff/image_diff.js | 5 ++--- app/assets/javascripts/notes.js | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 80be0fdfd0d8..5ade5c12fdac 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -16,9 +16,8 @@ export function showCommentIndicator(event) { } } -export function hideCommentIndicator(event) { - const container = event.target.closest('.diff-viewer'); - const commentIndicator = container.querySelector('.comment-indicator'); +export function hideCommentIndicator(diffViewerEl) { + const commentIndicator = diffViewerEl.querySelector('.comment-indicator'); if (commentIndicator) { commentIndicator.remove(); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 6accfc9016f9..7a47fb6e31aa 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1445,6 +1445,8 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); + imageDiff.hideCommentIndicator($form.closest('.diff-viewer')[0]); + // Reset cached commands list when command is applied if (hasQuickActions) { $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho'); -- 2.22.0 From 8c2360df92342b1c8455f2326c0365b124f38326 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 12:32:41 -0500 Subject: [PATCH 021/211] [skip ci] hide comment indicator when comment post fails --- app/assets/javascripts/notes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 7a47fb6e31aa..0878ab535ec2 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1490,6 +1490,8 @@ export default class Notes { // Submission failed, remove placeholder note and show Flash error message $notesContainer.find(`#${noteUniqueId}`).remove(); + imageDiff.hideCommentIndicator($form.closest('.diff-viewer')[0]); + if (hasQuickActions) { $notesContainer.find(`#${systemNoteUniqueId}`).remove(); } -- 2.22.0 From ef784db1158c3ae2822921860e5692e7f2f0532c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 15:13:33 -0500 Subject: [PATCH 022/211] [skip ci] Fix cursor offset so that selection is based on pointy part of the image --- app/assets/stylesheets/framework/variables.scss | 6 ++++++ app/assets/stylesheets/pages/diff.scss | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index a3da9fd44e88..caad5566eeb7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -696,3 +696,9 @@ Project Templates Icons $rails: #c00; $node: #353535; $java: #70ad51; + +/* +Image Commenting cursor +*/ +$image-comment-cursor-left-offset: 11; +$image-comment-cursor-top-offset: 29; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index e5329a226851..52a8e6bba5af 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -660,10 +660,12 @@ .frame.click-to-comment { position: relative; - cursor: url(icon_image_comment.svg), auto; + cursor: url(icon_image_comment.svg) + $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; .comment-indicator { position: absolute; + padding: 0; .image-comment-dark { // Override styling set by .image .frame @@ -672,6 +674,12 @@ background-position: inherit; background-size: inherit; max-width: inherit; + + // Compensate for cursor offsets + position: absolute; + left: (-1px * $image-comment-cursor-left-offset); + // TODO: Fix pixel calculation due to empty white space in cursor image + top: (-1px * $image-comment-cursor-top-offset); } } } -- 2.22.0 From 10b2fece1464675eaea2afdb5ec7ddb397769dcd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 15:33:15 -0500 Subject: [PATCH 023/211] [skip ci] fix missed hideCommentIndicator parameter change --- app/assets/javascripts/notes.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 0878ab535ec2..e18c06680f97 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1027,13 +1027,12 @@ export default class Notes { } cancelDiscussionForm(e) { - var form; e.preventDefault(); - form = $(e.target).closest('.js-discussion-note-form'); + const $form = $(e.target).closest('.js-discussion-note-form'); - imageDiff.hideCommentIndicator(e); + imageDiff.hideCommentIndicator($form.closest('.diff-viewer')[0]); - return this.removeDiscussionNoteForm(form); + return this.removeDiscussionNoteForm($form); } /** -- 2.22.0 From 17502e94b845a332a1bd7b32387ef02eac5a83ce Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 16:00:50 -0500 Subject: [PATCH 024/211] [skip ci] Use clearer comment image --- app/assets/images/icon_image_comment.svg | 37 +------------------ app/assets/images/icon_image_comment_dark.svg | 37 +------------------ 2 files changed, 2 insertions(+), 72 deletions(-) diff --git a/app/assets/images/icon_image_comment.svg b/app/assets/images/icon_image_comment.svg index 4f4a4608d770..c03a8ef0d090 100644 --- a/app/assets/images/icon_image_comment.svg +++ b/app/assets/images/icon_image_comment.svg @@ -1,36 +1 @@ - -cursor -Created using Figma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cursorCreated using Figma diff --git a/app/assets/images/icon_image_comment_dark.svg b/app/assets/images/icon_image_comment_dark.svg index e2364a2f89f5..4a2d0c0038c1 100644 --- a/app/assets/images/icon_image_comment_dark.svg +++ b/app/assets/images/icon_image_comment_dark.svg @@ -1,36 +1 @@ - -cursor_active -Created using Figma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cursor_activeCreated using Figma -- 2.22.0 From ae130bc18815c3ee1c8a10672ff183daaf795bda Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 22 Sep 2017 19:10:29 -0300 Subject: [PATCH 025/211] Save image diff notes --- .../javascripts/commit/image_diff_helper.js | 2 +- app/models/diff_note.rb | 12 +++++++++++- .../discussions/_diff_with_notes.html.haml | 18 ++++++++++-------- .../projects/diffs/viewers/_image.html.haml | 1 + lib/gitlab/diff/file.rb | 1 + lib/gitlab/diff/formatters/base_formatter.rb | 11 ++++++----- lib/gitlab/diff/formatters/image_formatter.rb | 6 +++--- lib/gitlab/diff/formatters/text_formatter.rb | 6 +++--- lib/gitlab/diff/position.rb | 4 ++-- 9 files changed, 38 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/commit/image_diff_helper.js b/app/assets/javascripts/commit/image_diff_helper.js index c676421a925d..0bda7554daae 100644 --- a/app/assets/javascripts/commit/image_diff_helper.js +++ b/app/assets/javascripts/commit/image_diff_helper.js @@ -52,7 +52,7 @@ export function setPositionDataAttribute(el, options) { positionObject.y_axis = y; positionObject.width = width; positionObject.height = height; - positionObject.component_type = 'image'; + positionObject.position_type = 'image'; el.setAttribute('data-position', JSON.stringify(positionObject)); } diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index e9a60e6ce098..1bb1be9dcad7 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -12,7 +12,7 @@ class DiffNote < Note validates :original_position, presence: true validates :position, presence: true - validates :diff_line, presence: true + validates :diff_line, presence: true, if: :on_text? validates :line_code, presence: true, line_code: true validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } validate :positions_complete @@ -43,6 +43,10 @@ class DiffNote < Note end end + def on_text? + position_type == :text + end + def diff_file @diff_file ||= self.original_position.diff_file(self.project.repository) end @@ -56,9 +60,15 @@ class DiffNote < Note end def original_line_code + return if self.position_type != :text + self.diff_file.line_code(self.diff_line) end + def position_type + position.position_type + end + def active?(diff_refs = nil) return false unless supported? return true if for_commit? diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 4a41be972da8..b48a19745bb5 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -8,11 +8,13 @@ .diff-content.code.js-syntax-highlight %table - - discussions = { discussion.original_line_code => [discussion] } - = render partial: "projects/diffs/line", - collection: discussion.truncated_diff_lines, - as: :line, - locals: { diff_file: diff_file, - discussions: discussions, - discussion_expanded: true, - plain: true } + - if diff_file.text? + - discussions = { discussion.original_line_code => [discussion] } + + = render partial: "projects/diffs/line", + collection: discussion.truncated_diff_lines, + as: :line, + locals: { diff_file: diff_file, + discussions: discussions, + discussion_expanded: true, + plain: true } diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index a071de8e761c..9b2a1fc7966d 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -7,6 +7,7 @@ / For test test purposes - image_point = Gitlab::Diff::ImagePoint.new(300, 300, 0, 0) # Point on top left corner - position_code = diff_file.line_code(image_point) +- notes = DiffNote.where(project_id: @project.id) - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 9818bd564635..54d157358b76 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -66,6 +66,7 @@ module Gitlab def line_code_for_position(pos) line = line_for_position(pos) + line_code(line) if line end diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index 95df8518d162..f477dcb81f10 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -7,6 +7,7 @@ module Gitlab attr_reader :base_sha attr_reader :start_sha attr_reader :head_sha + attr_reader :position_type def initialize(attrs) if diff_file = attrs[:diff_file] @@ -26,6 +27,9 @@ module Gitlab @base_sha = attrs[:base_sha] @start_sha = attrs[:start_sha] @head_sha = attrs[:head_sha] + + # Make sure older positions have text as type + @position_type = attrs[:position_type] || "text" end def key @@ -36,17 +40,14 @@ module Gitlab raise NotImplementedError end - def position_type - raise NotImplementedError - end - def to_h { base_sha: base_sha, start_sha: start_sha, head_sha: head_sha, old_path: old_path, - new_path: new_path + new_path: new_path, + position_type: position_type } end end diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index 62ec27017178..634a06c14b68 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -28,9 +28,9 @@ module Gitlab super.merge(width: width, height: height, x_axis: x_axis, y_axis: y_axis) end - def position_type - :image - end + # def position_type + # :image + # end end end end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index d31b9a48908e..2128ef95e808 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -24,9 +24,9 @@ module Gitlab super.merge(old_line: old_line, new_line: new_line) end - def position_type - :text - end + # def position_type + # :text + # end end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index bf80b82f11b2..cc4bcac92320 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -14,10 +14,10 @@ module Gitlab :base_sha, :start_sha, :head_sha, - :component_type, :to => :formatter + :position_type, :to => :formatter def initialize(attrs = {}) - @formatter = get_formatter_class(attrs[:file_type]).new(attrs) + @formatter = get_formatter_class(attrs[:position_type]).new(attrs) end # `Gitlab::Diff::Position` objects are stored as serialized attributes in -- 2.22.0 From 98b2f7068218b2190989d6d78706b9f0f85581f1 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 22 Sep 2017 19:18:09 -0300 Subject: [PATCH 026/211] Adjustment on frontend --- app/views/projects/diffs/viewers/_image.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index b9459d2de5b1..48d88fb170d1 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -7,7 +7,7 @@ / For test test purposes - image_point = Gitlab::Diff::ImagePoint.new(300, 300, 0, 0) # Point on top left corner - position_code = diff_file.line_code(image_point) -- notes = DiffNote.where(project_id: @project.id) +- note = DiffNote.where(project_id: @project.id).first - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } -- 2.22.0 From 3988a106f30d856f620ae4a74dfdcccb7b83ef96 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 22 Sep 2017 22:30:43 -0500 Subject: [PATCH 027/211] [skip ci] Fix cursor positioning on border of images --- app/assets/images/icon_image_comment.svg | 2 +- app/assets/images/icon_image_comment@2x.svg | 1 + app/assets/images/icon_image_comment_dark.svg | 2 +- .../javascripts/image_diff/image_diff.js | 2 +- .../image_diff/image_diff_helper.js | 28 ++++++++++--------- .../stylesheets/framework/variables.scss | 4 +-- app/assets/stylesheets/pages/diff.scss | 6 +++- .../projects/diffs/viewers/_image.html.haml | 1 + 8 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 app/assets/images/icon_image_comment@2x.svg diff --git a/app/assets/images/icon_image_comment.svg b/app/assets/images/icon_image_comment.svg index c03a8ef0d090..cf6cb9729403 100644 --- a/app/assets/images/icon_image_comment.svg +++ b/app/assets/images/icon_image_comment.svg @@ -1 +1 @@ -cursorCreated using Figma +cursor diff --git a/app/assets/images/icon_image_comment@2x.svg b/app/assets/images/icon_image_comment@2x.svg new file mode 100644 index 000000000000..83be91d37051 --- /dev/null +++ b/app/assets/images/icon_image_comment@2x.svg @@ -0,0 +1 @@ +cursor_2x diff --git a/app/assets/images/icon_image_comment_dark.svg b/app/assets/images/icon_image_comment_dark.svg index 4a2d0c0038c1..becd5a83d374 100644 --- a/app/assets/images/icon_image_comment_dark.svg +++ b/app/assets/images/icon_image_comment_dark.svg @@ -1 +1 @@ -cursor_activeCreated using Figma +cursor_active diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 5ade5c12fdac..79267313bddb 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,7 +1,7 @@ import * as imageDiffHelper from './image_diff_helper'; export function showCommentIndicator(event) { - const container = event.target.parentElement; + const container = event.currentTarget; const commentIndicator = container.querySelector('.comment-indicator'); const selection = imageDiffHelper.getTargetSelection(event); diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 878250b40eb3..08a92e02cb1c 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -1,33 +1,35 @@ export function getTargetSelection(event) { - const target = event.target; - const container = target.parentElement; + const container = event.currentTarget; + const image = container.querySelector('img'); const x = event.offsetX ? (event.offsetX) : event.pageX - container.offsetLeft; const y = event.offsetY ? (event.offsetY) : event.pageY - container.offsetTop; - const width = target.width; - const height = target.height; + const width = image.width; + const height = image.height; - const actualWidth = target.naturalWidth; - const actualHeight = target.naturalHeight; + const actualWidth = image.naturalWidth; + const actualHeight = image.naturalHeight; const widthRatio = actualWidth / width; const heightRatio = actualHeight / height; - // TODO: x, y contains the top left selection of the cursor - // and does not equate to the pointy part of the comment image - // Need to determine if we need to do offset calculations + // Browser will include the frame as a clickable target, + // which would result in potential 1px out of bounds value + // This bound the coordinates to inside the frame + const normalizedX = Math.max(0, x) && Math.min(x, width); + const normalizedY = Math.max(0, y) && Math.min(y, height); return { browser: { - x, - y, + x: normalizedX, + y: normalizedY, width, height, }, actual: { // Round x, y so that we don't need to deal with decimals - x: Math.round(x * widthRatio), - y: Math.round(y * heightRatio), + x: Math.round(normalizedX * widthRatio), + y: Math.round(normalizedY * heightRatio), width: actualWidth, height: actualHeight, }, diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index caad5566eeb7..981090b5f566 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -700,5 +700,5 @@ $java: #70ad51; /* Image Commenting cursor */ -$image-comment-cursor-left-offset: 11; -$image-comment-cursor-top-offset: 29; +$image-comment-cursor-left-offset: 12; +$image-comment-cursor-top-offset: 30; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 52a8e6bba5af..8759730dc7d7 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -663,6 +663,10 @@ cursor: url(icon_image_comment.svg) $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; + // Retina cursor + cursor: -webkit-image-set(url(icon_image_comment.svg) 1x, url(icon_image_comment@2x.svg) 2x) + $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; + .comment-indicator { position: absolute; padding: 0; @@ -679,7 +683,7 @@ position: absolute; left: (-1px * $image-comment-cursor-left-offset); // TODO: Fix pixel calculation due to empty white space in cursor image - top: (-1px * $image-comment-cursor-top-offset); + top: (-1px * $image-comment-cursor-top-offset + 2px); } } } diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 48d88fb170d1..d3a34e324195 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -12,6 +12,7 @@ - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } %span.wrap + -# TODO: Determine if we should conslidate js-add-image-diff-note-button and click-to-comment .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil) } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) %p.image-info= number_to_human_size(blob.size) -- 2.22.0 From c664401ec32122aa0f62e2e9a39f271ec4c49866 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 25 Sep 2017 12:47:53 -0300 Subject: [PATCH 028/211] Retrieve image notes from backend --- app/models/concerns/discussion_on_diff.rb | 1 + app/models/note.rb | 10 +++++++--- app/views/projects/diffs/viewers/_image.html.haml | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb index eee1a36ac6b6..3ac2ab911fae 100644 --- a/app/models/concerns/discussion_on_diff.rb +++ b/app/models/concerns/discussion_on_diff.rb @@ -16,6 +16,7 @@ module DiscussionOnDiff to: :first_note delegate :file_path, + :file_identifier, :blob, :highlighted_diff_lines, :diff_lines, diff --git a/app/models/note.rb b/app/models/note.rb index f44590e2144d..046557c728f3 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -134,14 +134,18 @@ class Note < ActiveRecord::Base Discussion.build(notes) end + # Group diff discussions by line code or Diff::File#file_identifier. + # It is not needed to group by line code when comment is + # on an image. def grouped_diff_discussions(diff_refs = nil) groups = {} diff_notes.fresh.discussions.each do |discussion| - line_code = discussion.line_code_in_diffs(diff_refs) + group_key = discussion.line_code_in_diffs(diff_refs) + group_key ||= discussion.file_identifier - if line_code - discussions = groups[line_code] ||= [] + if group_key + discussions = groups[group_key] ||= [] discussions << discussion end end diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index d3a34e324195..51ecd31bf8bb 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -7,7 +7,7 @@ / For test test purposes - image_point = Gitlab::Diff::ImagePoint.new(300, 300, 0, 0) # Point on top left corner - position_code = diff_file.line_code(image_point) -- note = DiffNote.where(project_id: @project.id).first +- notes = @grouped_diff_discussions[diff_file.file_identifier] - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } -- 2.22.0 From 9dcc6e8a8146cf4ae11107700f5fc6d6dbe01764 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 25 Sep 2017 14:55:44 -0300 Subject: [PATCH 029/211] Fix line code validation --- app/models/diff_note.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 1bb1be9dcad7..b118528c16be 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -13,7 +13,7 @@ class DiffNote < Note validates :original_position, presence: true validates :position, presence: true validates :diff_line, presence: true, if: :on_text? - validates :line_code, presence: true, line_code: true + validates :line_code, presence: true, line_code: true, if: :on_text? validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } validate :positions_complete validate :verify_supported -- 2.22.0 From 5109faa43bf48c4152000b8d727fb2813568605b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 25 Sep 2017 16:39:19 -0300 Subject: [PATCH 030/211] Change notes to discussions --- app/views/discussions/_diff_with_notes.html.haml | 1 - app/views/projects/diffs/viewers/_image.html.haml | 5 ++--- lib/gitlab/diff/file.rb | 2 +- lib/gitlab/diff/formatters/base_formatter.rb | 2 +- lib/gitlab/diff/formatters/image_formatter.rb | 4 ---- 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index b48a19745bb5..27d909688e91 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -10,7 +10,6 @@ %table - if diff_file.text? - discussions = { discussion.original_line_code => [discussion] } - = render partial: "projects/diffs/line", collection: discussion.truncated_diff_lines, as: :line, diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 51ecd31bf8bb..cf10611f557e 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -4,10 +4,9 @@ - blob_raw_path = diff_file_blob_raw_path(diff_file) - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) -/ For test test purposes -- image_point = Gitlab::Diff::ImagePoint.new(300, 300, 0, 0) # Point on top left corner +- image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) # Point on top left corner - position_code = diff_file.line_code(image_point) -- notes = @grouped_diff_discussions[diff_file.file_identifier] +- discussions = @grouped_diff_discussions[diff_file.file_identifier] - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 147af8838994..32f8ebda5b5d 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -27,7 +27,7 @@ module Gitlab @fallback_diff_refs = fallback_diff_refs end - def position(line, file_type=:text) + def position(line, file_type = :text) return unless diff_refs Position.new( diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index f477dcb81f10..fef1c759676e 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -28,7 +28,7 @@ module Gitlab @start_sha = attrs[:start_sha] @head_sha = attrs[:head_sha] - # Make sure older positions have text as type + # Make sure older serialized positions have text as type @position_type = attrs[:position_type] || "text" end diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index 634a06c14b68..1cc653f7d2db 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -27,10 +27,6 @@ module Gitlab def to_h super.merge(width: width, height: height, x_axis: x_axis, y_axis: y_axis) end - - # def position_type - # :image - # end end end end -- 2.22.0 From dafdbdcee175e613a4647089facf15f2d688a846 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 25 Sep 2017 23:27:53 -0500 Subject: [PATCH 031/211] [skip ci] refactor frontend to use namespace event based design --- app/assets/javascripts/diff.js | 7 ++ .../javascripts/image_diff/image_diff.js | 80 +++++++++++++------ app/assets/javascripts/notes.js | 27 +++++-- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 6a0081122032..9a9090ae7bbc 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -3,6 +3,7 @@ import './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; import SingleFileDiff from './single_file_diff'; +import ImageDiff from './image_diff/image_diff'; const UNFOLD_COUNT = 20; let isBound = false; @@ -20,6 +21,12 @@ class Diff { FilesCommentButton.init($diffFile); $diffFile.each((index, file) => new gl.ImageFile(file)); + $diffFile.each((index, file) => { + if (file.querySelector('.diff-viewer .image')) { + const imageDiff = new ImageDiff(file); + imageDiff.bindEvents(); + } + }); if (!isBound) { $(document) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 79267313bddb..204bdec11a40 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,33 +1,65 @@ import * as imageDiffHelper from './image_diff_helper'; -export function showCommentIndicator(event) { - const container = event.currentTarget; - const commentIndicator = container.querySelector('.comment-indicator'); - const selection = imageDiffHelper.getTargetSelection(event); - - if (commentIndicator) { - commentIndicator.style.left = `${selection.browser.x}px`; - commentIndicator.style.top = `${selection.browser.y}px`; - } else { - const button = imageDiffHelper - .addCommentIndicator(container, selection.browser); - - button.addEventListener('click', imageDiffHelper.commentIndicatorOnClick); +export default class ImageDiff { + constructor(el) { + this.el = el; + this.imageFrame = el.querySelector('.diff-viewer .image'); } -} -export function hideCommentIndicator(diffViewerEl) { - const commentIndicator = diffViewerEl.querySelector('.comment-indicator'); + bindEvents() { + this.el.addEventListener('click.imageDiff', this.click); + this.el.addEventListener('blur.imageDiff', this.blur); + this.el.addEventListener('renderBadges.imageDiff', this.renderBadges); + this.el.addEventListener('updateBadges.imageDiff', this.updateBadges); + } - if (commentIndicator) { - commentIndicator.remove(); + unbindEvents() { + this.el.removeEventListener('click.imageDiff', this.click); + this.el.removeEventListener('blur.imageDiff', this.blur); + this.el.removeEventListener('renderBadges.imageDiff', this.renderBadges); + this.el.removeEventListener('updateBadges.imageDiff', this.updateBadges); + } + + static click(event) { + const customEvent = event.detail; + const selection = imageDiffHelper.getTargetSelection(customEvent); + + // showCommentIndicator + const container = customEvent.currentTarget; + const commentIndicator = container.querySelector('.comment-indicator'); + + if (commentIndicator) { + commentIndicator.style.left = `${selection.browser.x}px`; + commentIndicator.style.top = `${selection.browser.y}px`; + } else { + const button = imageDiffHelper + .addCommentIndicator(container, selection.browser); + + button.addEventListener('click', imageDiffHelper.commentIndicatorOnClick); + } + + // setupCoordinatesData + const el = customEvent.currentTarget; + imageDiffHelper.setLineCodeCoordinates(el, selection.actual); + imageDiffHelper.setPositionDataAttribute(el, selection.actual); + } + + // TODO: Rename to something better? + static blur(event) { + const customEvent = event.detail; + const diffViewerEl = customEvent.target.closest('.diff-viewer'); + const commentIndicator = diffViewerEl.querySelector('.comment-indicator'); + + if (commentIndicator) { + commentIndicator.remove(); + } } -} -export function setupCoordinatesData(event) { - const el = event.currentTarget; - const selection = imageDiffHelper.getTargetSelection(event); + static renderBadges() { - imageDiffHelper.setLineCodeCoordinates(el, selection.actual); - imageDiffHelper.setPositionDataAttribute(el, selection.actual); + } + + static updateBadges() { + + } } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 348a4fb29cb5..94c138f0b372 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -24,7 +24,6 @@ import './autosave'; import './dropzone_input'; import TaskList from './task_list'; import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; -import * as imageDiff from './image_diff/image_diff'; window.autosize = autosize; window.Dropzone = Dropzone; @@ -918,9 +917,13 @@ export default class Notes { onAddImageDiffNote(e) { const $link = $(e.currentTarget || e.target); + const $diffFile = $link.closest('.diff-file'); - imageDiff.showCommentIndicator(e); - imageDiff.setupCoordinatesData(e); + const clickEvent = new CustomEvent('click.imageDiff', { + detail: e, + }); + + $diffFile[0].dispatchEvent(clickEvent); // Setup comment form let newForm; @@ -1031,7 +1034,11 @@ export default class Notes { e.preventDefault(); const $form = $(e.target).closest('.js-discussion-note-form'); - imageDiff.hideCommentIndicator($form.closest('.diff-viewer')[0]); + const blurEvent = new CustomEvent('blur.imageDiff', { + detail: e, + }); + + $form.closest('.diff-file')[0].dispatchEvent(blurEvent); return this.removeDiscussionNoteForm($form); } @@ -1445,7 +1452,11 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); - imageDiff.hideCommentIndicator($form.closest('.diff-viewer')[0]); + const blurEvent = new CustomEvent('blur.imageDiff', { + detail: e, + }); + + $form.closest('.diff-file')[0].dispatchEvent(blurEvent); // Reset cached commands list when command is applied if (hasQuickActions) { @@ -1490,7 +1501,11 @@ export default class Notes { // Submission failed, remove placeholder note and show Flash error message $notesContainer.find(`#${noteUniqueId}`).remove(); - imageDiff.hideCommentIndicator($form.closest('.diff-viewer')[0]); + const blurEvent = new CustomEvent('blur.imageDiff', { + detail: e, + }); + + $form.closest('.diff-file')[0].dispatchEvent(blurEvent); if (hasQuickActions) { $notesContainer.find(`#${systemNoteUniqueId}`).remove(); -- 2.22.0 From 9000059f19e22566f6d08208c803e7d3b80ff9e6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 25 Sep 2017 23:32:32 -0500 Subject: [PATCH 032/211] [skip ci] only emit cancel event if it is imagediff --- app/assets/javascripts/notes.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 94c138f0b372..9e8d09893175 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1033,12 +1033,15 @@ export default class Notes { cancelDiscussionForm(e) { e.preventDefault(); const $form = $(e.target).closest('.js-discussion-note-form'); + const diffViewer = $form.closest('.diff-viewer')[0]; - const blurEvent = new CustomEvent('blur.imageDiff', { - detail: e, - }); + if (diffViewer) { + const blurEvent = new CustomEvent('blur.imageDiff', { + detail: e, + }); - $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + } return this.removeDiscussionNoteForm($form); } @@ -1452,11 +1455,15 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); - const blurEvent = new CustomEvent('blur.imageDiff', { - detail: e, - }); + const diffViewer = $form.closest('.diff-viewer')[0]; - $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + if (diffViewer) { + const blurEvent = new CustomEvent('blur.imageDiff', { + detail: e, + }); + + $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + } // Reset cached commands list when command is applied if (hasQuickActions) { -- 2.22.0 From c8e87a62b3e7dbf9131dd15a95fb6065887b20a9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 25 Sep 2017 23:33:08 -0500 Subject: [PATCH 033/211] [skip ci] add initial rendering of discussion notes --- app/assets/javascripts/notes.js | 11 +++++++++-- app/assets/stylesheets/pages/diff.scss | 16 +++++++++++++++- app/controllers/concerns/notes_actions.rb | 1 + .../projects/diffs/viewers/_image.html.haml | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 9e8d09893175..b75b97f83826 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -416,6 +416,11 @@ export default class Notes { this.note_ids.push(noteEntity.id); form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`); row = form.closest('tr'); + + if (row.length === 0) { + row = form; + } + 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? @@ -779,6 +784,7 @@ export default class Notes { // check if this is the last note for this line if ($notes.find('.note').length === 0) { + // TODO: Replace TR check with an actual class name check var notesTr = $notes.closest('tr'); // "Discussions" tab @@ -787,9 +793,10 @@ export default class Notes { $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue'); // The notes tr can contain multiple lists of notes, like on the parallel diff - if (notesTr.find('.discussion-notes').length > 1) { + // notesTr does not exist for image diffs + if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) { $notes.remove(); - } else { + } else if (notesTr.length > 0) { notesTr.remove(); } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 8759730dc7d7..8522d3298b9c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -651,9 +651,23 @@ .note-container { background-color: $gray-light; border-top: 1px solid $white-normal; + + .notes .note { + background-color: $white-light; + } + + .discussion-notes + .discussion-notes::before { + // TODO: Convert to Jagged border + background-color: black; + content: " "; + display: block; + width: 100%; + height: $gl-padding; + } } -.diff-file .note-container .new-note { +.diff-file .note-container .new-note, +.note-container .discussion-notes { margin-left: 100px; border-left: 1px solid $white-normal; } diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 18fd8eb114de..79a98851ebbb 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -121,6 +121,7 @@ module NotesActions def diff_discussion_html(discussion) return unless discussion.diff_discussion? + # TODO: Return different view for image diff if params[:view] == 'parallel' template = "discussions/_parallel_diff_discussion" diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index cf10611f557e..782bd94bec40 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -16,6 +16,7 @@ = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) %p.image-info= number_to_human_size(blob.size) .note-container + = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion - else .image .two-up.view -- 2.22.0 From 855b831c175bf5da02193bc05b51dde11143b503 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 25 Sep 2017 23:43:16 -0500 Subject: [PATCH 034/211] [skip ci] add placeholder avatar badge counter --- app/assets/stylesheets/pages/diff.scss | 12 ++++++++++++ app/views/shared/notes/_note.html.haml | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 8522d3298b9c..6a05ee44c00c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -701,3 +701,15 @@ } } } + +.image-diff-avatar-link { + position: relative; + + .badge { + position: absolute; + top: 25px; + right: 8px; + background-color: $blue-400; + color: $white-light; + } +} diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 4f00a9f2759d..e04af05ef8e0 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -12,8 +12,9 @@ - if note.system = icon_for_system_note(note) - else - %a{ href: user_path(note.author) } + %a.image-diff-avatar-link{ href: user_path(note.author) } = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' + %span.badge 1 .timeline-content .note-header .note-header-info -- 2.22.0 From a8264533e73df033357d614de94f9969f0edf5ce Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 25 Sep 2017 23:50:58 -0500 Subject: [PATCH 035/211] [skip ci] use ImageDiff because of static methods --- app/assets/javascripts/image_diff/image_diff.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 204bdec11a40..2606064b88ab 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -7,17 +7,17 @@ export default class ImageDiff { } bindEvents() { - this.el.addEventListener('click.imageDiff', this.click); - this.el.addEventListener('blur.imageDiff', this.blur); - this.el.addEventListener('renderBadges.imageDiff', this.renderBadges); - this.el.addEventListener('updateBadges.imageDiff', this.updateBadges); + this.el.addEventListener('click.imageDiff', ImageDiff.click); + this.el.addEventListener('blur.imageDiff', ImageDiff.blur); + this.el.addEventListener('renderBadges.imageDiff', ImageDiff.renderBadges); + this.el.addEventListener('updateBadges.imageDiff', ImageDiff.updateBadges); } unbindEvents() { - this.el.removeEventListener('click.imageDiff', this.click); - this.el.removeEventListener('blur.imageDiff', this.blur); - this.el.removeEventListener('renderBadges.imageDiff', this.renderBadges); - this.el.removeEventListener('updateBadges.imageDiff', this.updateBadges); + this.el.removeEventListener('click.imageDiff', ImageDiff.click); + this.el.removeEventListener('blur.imageDiff', ImageDiff.blur); + this.el.removeEventListener('renderBadges.imageDiff', ImageDiff.renderBadges); + this.el.removeEventListener('updateBadges.imageDiff', ImageDiff.updateBadges); } static click(event) { -- 2.22.0 From 858307187ac9a6e10939cfd6b21117b6590d2e3d Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 26 Sep 2017 13:38:24 -0300 Subject: [PATCH 036/211] Fix bagde numbers and render right partial for images --- app/controllers/concerns/notes_actions.rb | 8 ++++---- app/models/diff_discussion.rb | 2 ++ app/models/diff_note.rb | 6 +++++- .../discussions/_diff_discussion.html.haml | 18 ++++++++++++------ app/views/discussions/_notes.html.haml | 2 +- app/views/shared/notes/_note.html.haml | 3 ++- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 79a98851ebbb..d995c1c2c348 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -120,9 +120,6 @@ module NotesActions end def diff_discussion_html(discussion) - return unless discussion.diff_discussion? - # TODO: Return different view for image diff - if params[:view] == 'parallel' template = "discussions/_parallel_diff_discussion" locals = @@ -133,7 +130,10 @@ module NotesActions end else template = "discussions/_diff_discussion" - locals = { discussions: [discussion] } + on_image = discussion.on_image? if discussion.diff_discussion? + + # TODO get discussion bagde count for new discussions + locals = { discussions: [discussion], badge_count: 99, on_image: on_image } end render_to_string( diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb index 07c4846e2ac5..6eba87da1a1b 100644 --- a/app/models/diff_discussion.rb +++ b/app/models/diff_discussion.rb @@ -11,6 +11,8 @@ class DiffDiscussion < Discussion delegate :position, :original_position, :change_position, + :on_text?, + :on_image?, to: :first_note diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index b118528c16be..8846516b1714 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -44,7 +44,11 @@ class DiffNote < Note end def on_text? - position_type == :text + position_type == "text" + end + + def on_image? + position_type == "image" end def diff_file diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index e6d307e5568c..f0f5d62fe9bb 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -1,6 +1,12 @@ -- expanded = local_assigns.fetch(:expanded, true) -%tr.notes_holder{ class: ('hide' unless expanded) } - %td.notes_line{ colspan: 2 } - %td.notes_content - .content{ class: ('hide' unless expanded) } - = render partial: "discussions/notes", collection: discussions, as: :discussion +- if local_assigns[:on_image] + %h1 Should work now + = render partial: "discussions/notes", collection: discussions, as: :discussion +- else + - expanded = local_assigns.fetch(:expanded, true) + %tr.notes_holder{ class: ('hide' unless expanded) } + %td.notes_line{ colspan: 2 } + %td.notes_content + .content{ class: ('hide' unless expanded) } + = render partial: "discussions/notes", collection: discussions, as: :discussion + + diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index db5ab9399482..90e70e6a7ea4 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,6 +1,6 @@ .discussion-notes %ul.notes{ data: { discussion_id: discussion.id } } - = render partial: "shared/notes/note", collection: discussion.notes, as: :note + = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: discussion_counter + 1 } .flash-container diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index e04af05ef8e0..365cca379de4 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -14,7 +14,8 @@ - else %a.image-diff-avatar-link{ href: user_path(note.author) } = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' - %span.badge 1 + %span.badge + = badge_counter if local_assigns[:badge_counter] .timeline-content .note-header .note-header-info -- 2.22.0 From 922b02f2a8b226b0327d3c809cb5ed764b9e7d15 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 26 Sep 2017 16:05:24 -0300 Subject: [PATCH 037/211] Fix note badge --- app/views/discussions/_notes.html.haml | 3 ++- app/views/shared/notes/_note.html.haml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 90e70e6a7ea4..cdac4a67a315 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,6 +1,7 @@ .discussion-notes %ul.notes{ data: { discussion_id: discussion.id } } - = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: discussion_counter + 1 } + - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] + = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } .flash-container diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 365cca379de4..5776e4a06828 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -14,8 +14,9 @@ - else %a.image-diff-avatar-link{ href: user_path(note.author) } = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' - %span.badge - = badge_counter if local_assigns[:badge_counter] + - if note.is_a?(DiffNote) && note.on_image? + %span.badge + = badge_counter if local_assigns[:badge_counter] .timeline-content .note-header .note-header-info -- 2.22.0 From c995310701c2f806f49c77e3c66ac21ebdf67303 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 09:35:49 -0500 Subject: [PATCH 038/211] [skip ci] Use object methods rather than static methods --- .../javascripts/image_diff/image_diff.js | 26 +++++++++---------- app/assets/javascripts/notes.js | 12 ++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 2606064b88ab..56d6593f64fb 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -3,37 +3,39 @@ import * as imageDiffHelper from './image_diff_helper'; export default class ImageDiff { constructor(el) { this.el = el; - this.imageFrame = el.querySelector('.diff-viewer .image'); + this.imageFrame = el.querySelector('.diff-viewer .image .frame'); } bindEvents() { - this.el.addEventListener('click.imageDiff', ImageDiff.click); - this.el.addEventListener('blur.imageDiff', ImageDiff.blur); + this.clickWrapper = this.click.bind(this); + this.blurWrapper = this.blur.bind(this); + + this.el.addEventListener('click.imageDiff', this.clickWrapper); + this.el.addEventListener('blur.imageDiff', this.blurWrapper); this.el.addEventListener('renderBadges.imageDiff', ImageDiff.renderBadges); this.el.addEventListener('updateBadges.imageDiff', ImageDiff.updateBadges); } unbindEvents() { - this.el.removeEventListener('click.imageDiff', ImageDiff.click); - this.el.removeEventListener('blur.imageDiff', ImageDiff.blur); + this.el.removeEventListener('click.imageDiff', this.clickWrapper); + this.el.removeEventListener('blur.imageDiff', this.blurWrapper); this.el.removeEventListener('renderBadges.imageDiff', ImageDiff.renderBadges); this.el.removeEventListener('updateBadges.imageDiff', ImageDiff.updateBadges); } - static click(event) { + click(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); // showCommentIndicator - const container = customEvent.currentTarget; - const commentIndicator = container.querySelector('.comment-indicator'); + const commentIndicator = this.imageFrame.querySelector('.comment-indicator'); if (commentIndicator) { commentIndicator.style.left = `${selection.browser.x}px`; commentIndicator.style.top = `${selection.browser.y}px`; } else { const button = imageDiffHelper - .addCommentIndicator(container, selection.browser); + .addCommentIndicator(this.imageFrame, selection.browser); button.addEventListener('click', imageDiffHelper.commentIndicatorOnClick); } @@ -45,10 +47,8 @@ export default class ImageDiff { } // TODO: Rename to something better? - static blur(event) { - const customEvent = event.detail; - const diffViewerEl = customEvent.target.closest('.diff-viewer'); - const commentIndicator = diffViewerEl.querySelector('.comment-indicator'); + blur() { + const commentIndicator = this.imageFrame.querySelector('.comment-indicator'); if (commentIndicator) { commentIndicator.remove(); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index b75b97f83826..75dc8483a1c8 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1040,14 +1040,14 @@ export default class Notes { cancelDiscussionForm(e) { e.preventDefault(); const $form = $(e.target).closest('.js-discussion-note-form'); - const diffViewer = $form.closest('.diff-viewer')[0]; + const diffFile = $form.closest('.diff-file')[0]; - if (diffViewer) { + if (diffFile) { const blurEvent = new CustomEvent('blur.imageDiff', { detail: e, }); - $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + diffFile.dispatchEvent(blurEvent); } return this.removeDiscussionNoteForm($form); @@ -1462,14 +1462,14 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); - const diffViewer = $form.closest('.diff-viewer')[0]; + const diffFile = $form.closest('.diff-file')[0]; - if (diffViewer) { + if (diffFile) { const blurEvent = new CustomEvent('blur.imageDiff', { detail: e, }); - $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + diffFile.dispatchEvent(blurEvent); } // Reset cached commands list when command is applied -- 2.22.0 From 137fc5c4b615eaa352fb727b6d8e74fcc7e1c291 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 12:42:06 -0500 Subject: [PATCH 039/211] [skip ci] Add discussion badge to image after discussion creation --- .../javascripts/image_diff/image_diff.js | 52 ++++++++++++++++--- .../image_diff/image_diff_helper.js | 34 ++++++++++++ app/assets/javascripts/notes.js | 11 ++++ app/assets/stylesheets/pages/diff.scss | 9 ++++ app/views/discussions/_notes.html.haml | 2 +- .../projects/diffs/viewers/_image.html.haml | 2 +- 6 files changed, 102 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 56d6593f64fb..61b3c6583948 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -4,23 +4,30 @@ export default class ImageDiff { constructor(el) { this.el = el; this.imageFrame = el.querySelector('.diff-viewer .image .frame'); + this.image = this.imageFrame.querySelector('img'); + this.badges = []; } bindEvents() { this.clickWrapper = this.click.bind(this); this.blurWrapper = this.blur.bind(this); + this.renderBadgesWrapper = this.renderBadges.bind(this); + this.addBadgeWrapper = this.addBadge.bind(this); this.el.addEventListener('click.imageDiff', this.clickWrapper); this.el.addEventListener('blur.imageDiff', this.blurWrapper); - this.el.addEventListener('renderBadges.imageDiff', ImageDiff.renderBadges); - this.el.addEventListener('updateBadges.imageDiff', ImageDiff.updateBadges); + this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); + + // Render badges after the image diff is loaded + this.image.addEventListener('load', this.renderBadgesWrapper); } unbindEvents() { this.el.removeEventListener('click.imageDiff', this.clickWrapper); this.el.removeEventListener('blur.imageDiff', this.blurWrapper); - this.el.removeEventListener('renderBadges.imageDiff', ImageDiff.renderBadges); - this.el.removeEventListener('updateBadges.imageDiff', ImageDiff.updateBadges); + this.el.removeEventListener('addBadge.imageDiff', this.addBadgeWrapper); + + this.image.removeEventListener('load', this.renderBadgesWrapper); } click(event) { @@ -55,11 +62,44 @@ export default class ImageDiff { } } - static renderBadges() { + renderBadges() { + // Process existing badges from html + const discussions = this.el.querySelectorAll('.note-container .discussion-notes .notes'); + [].forEach.call(discussions, (discussion) => { + const position = JSON.parse(discussion.dataset.position); + + this.badges.push({ + actual: { + x: position.x_axis, + y: position.y_axis, + width: position.width, + height: position.height, + }, + }); + }); + const browserImage = this.imageFrame.querySelector('img'); + + this.badges.map((badge) => { + const newBadge = badge; + newBadge.browser = imageDiffHelper.createBadgeBrowserFromActual(browserImage, badge.actual); + return newBadge; + }); + + this.badges.forEach((badge, index) => + imageDiffHelper.addCommentBadge(this.imageFrame, badge.browser, index + 1)); } - static updateBadges() { + addBadge(event) { + const actual = event.detail; + const browserImage = this.imageFrame.querySelector('img'); + const badge = { + actual, + browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), + }; + + imageDiffHelper.addCommentBadge(this.imageFrame, badge.browser, this.badges.length + 1); + this.badges.push(badge); } } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 08a92e02cb1c..43b9234f116f 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -88,3 +88,37 @@ export function commentIndicatorOnClick(e) { const textarea = diffViewer.querySelector('.note-container form .note-textarea'); textarea.focus(); } + +export function addCommentBadge(containerEl, coordinate, badgeText) { + const { x, y } = coordinate; + const button = document.createElement('button'); + button.classList.add('btn-transparent', 'badge'); + button.setAttribute('type', 'button'); + button.innerText = badgeText; + + containerEl.appendChild(button); + + // TODO: We should use math to calculate the width so that we don't + // have to do a reflow here but we can leave this here for now + const { width, height } = button.getBoundingClientRect(); + button.style.left = `${x - (width * 0.5)}px`; + button.style.top = `${y - (height * 0.5)}px`; +} + +// TODO: Refactor into separate discussionBadge object +export function createBadgeBrowserFromActual(imageEl, actualProps) { + const { x, y, width, height } = actualProps; + + const browserImageWidth = imageEl.width; + const browserImageHeight = imageEl.height; + + const widthRatio = browserImageWidth / width; + const heightRatio = browserImageHeight / height; + + return { + x: Math.round(x * widthRatio), + y: Math.round(y * heightRatio), + width: browserImageWidth, + height: browserImageHeight, + }; +} diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 75dc8483a1c8..4e5ddf9be202 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1469,7 +1469,18 @@ export default class Notes { detail: e, }); + const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); + const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { + detail: { + x: x_axis, + y: y_axis, + width, + height, + }, + }); + diffFile.dispatchEvent(blurEvent); + diffFile.dispatchEvent(addBadgeEvent); } // Reset cached commands list when command is applied diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 6a05ee44c00c..9e9adf990ab2 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -700,6 +700,15 @@ top: (-1px * $image-comment-cursor-top-offset + 2px); } } + + .badge { + position: absolute; + background-color: $blue-400; + color: $white-light; + border-color: $white-light; + border-width: 1px; + border-style: solid; + } } .image-diff-avatar-link { diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index cdac4a67a315..5d4a4a74a04b 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,5 +1,5 @@ .discussion-notes - %ul.notes{ data: { discussion_id: discussion.id } } + %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 782bd94bec40..91a42289f785 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -13,7 +13,7 @@ %span.wrap -# TODO: Determine if we should conslidate js-add-image-diff-note-button and click-to-comment .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil) } - = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) + = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) %p.image-info= number_to_human_size(blob.size) .note-container = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion -- 2.22.0 From 85619ae236cd6427d58502450372b017cae9b0c8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 13:24:19 -0500 Subject: [PATCH 040/211] [skip ci] Stop comment badge button from propagating --- app/assets/javascripts/image_diff/image_diff_helper.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 43b9234f116f..af085d9e229b 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -103,6 +103,11 @@ export function addCommentBadge(containerEl, coordinate, badgeText) { const { width, height } = button.getBoundingClientRect(); button.style.left = `${x - (width * 0.5)}px`; button.style.top = `${y - (height * 0.5)}px`; + + // TODO: Highlight first note in the discussion when button is clicked + button.addEventListener('click', e => e.stopPropagation()); + + return button; } // TODO: Refactor into separate discussionBadge object -- 2.22.0 From a3301167de57b23ca430e9c559438829802b627e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 14:33:07 -0500 Subject: [PATCH 041/211] [skip ci] Link comment badge to the first discussion url hash --- .../javascripts/image_diff/image_diff.js | 39 +++++++++++++++++-- .../image_diff/image_diff_helper.js | 8 ++-- app/assets/javascripts/notes.js | 28 +++++++------ 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 61b3c6583948..ba2196e3d0b2 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -68,6 +68,8 @@ export default class ImageDiff { [].forEach.call(discussions, (discussion) => { const position = JSON.parse(discussion.dataset.position); + const firstNote = discussion.querySelector('.note'); + this.badges.push({ actual: { x: position.x_axis, @@ -75,6 +77,7 @@ export default class ImageDiff { width: position.width, height: position.height, }, + noteId: firstNote.id, }); }); @@ -87,18 +90,48 @@ export default class ImageDiff { }); this.badges.forEach((badge, index) => - imageDiffHelper.addCommentBadge(this.imageFrame, badge.browser, index + 1)); + imageDiffHelper.addCommentBadge(this.imageFrame, { + coordinate: badge.browser, + badgeText: index + 1, + noteId: badge.noteId, + })); + } + + renderDiscussionBadges() { + const discussions = this.el.querySelectorAll('.note-container .discussion-notes .notes'); + + // TODO: Get feedback on this n^2 operation + [].forEach.call(discussions, (discussion, index) => { + const notes = discussion.querySelectorAll('.note'); + [].forEach.call(notes, (note) => { + const badge = note.querySelector('.image-diff-avatar-link .badge'); + badge.innerText = index + 1; + badge.classList.remove('hidden'); + }); + }); } addBadge(event) { - const actual = event.detail; + const { x, y, width, height, noteId } = event.detail; + const actual = { + x, + y, + width, + height, + }; + const browserImage = this.imageFrame.querySelector('img'); const badge = { actual, browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), + noteId, }; - imageDiffHelper.addCommentBadge(this.imageFrame, badge.browser, this.badges.length + 1); + imageDiffHelper.addCommentBadge(this.imageFrame, { + coordinate: badge.browser, + badgeText: this.badges.length + 1, + noteId, + }); this.badges.push(badge); } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index af085d9e229b..8c96ccb04cb6 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -89,7 +89,7 @@ export function commentIndicatorOnClick(e) { textarea.focus(); } -export function addCommentBadge(containerEl, coordinate, badgeText) { +export function addCommentBadge(containerEl, { coordinate, badgeText, noteId }) { const { x, y } = coordinate; const button = document.createElement('button'); button.classList.add('btn-transparent', 'badge'); @@ -104,8 +104,10 @@ export function addCommentBadge(containerEl, coordinate, badgeText) { button.style.left = `${x - (width * 0.5)}px`; button.style.top = `${y - (height * 0.5)}px`; - // TODO: Highlight first note in the discussion when button is clicked - button.addEventListener('click', e => e.stopPropagation()); + button.addEventListener('click', (e) => { + e.stopPropagation(); + window.location.hash = noteId; + }); return button; } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 4e5ddf9be202..0357f70c1e5f 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -445,6 +445,23 @@ export default class Notes { row.find(contentContainerClass + ' .content').append($notes.closest('.content').children()); } + + // Add badge for image diffs + const $diffFile = form.closest('.diff-file'); + if ($diffFile.length > 0) { + const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); + const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { + detail: { + x: x_axis, + y: y_axis, + width, + height, + noteId: $discussion[0].querySelector('.notes .note').id, + }, + }); + + $diffFile[0].dispatchEvent(addBadgeEvent); + } } // Init discussion on 'Discussion' page if it is merge request page const page = $('body').attr('data-page'); @@ -1469,18 +1486,7 @@ export default class Notes { detail: e, }); - const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); - const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { - detail: { - x: x_axis, - y: y_axis, - width, - height, - }, - }); - diffFile.dispatchEvent(blurEvent); - diffFile.dispatchEvent(addBadgeEvent); } // Reset cached commands list when command is applied -- 2.22.0 From 1e3627c3073b4567d90da360bee490760711dfd8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 14:36:57 -0500 Subject: [PATCH 042/211] [skip ci] Fix comment textarea css offset --- app/assets/stylesheets/pages/diff.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 9e9adf990ab2..797892662220 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -666,7 +666,7 @@ } } -.diff-file .note-container .new-note, +.diff-file .note-container > .new-note, .note-container .discussion-notes { margin-left: 100px; border-left: 1px solid $white-normal; -- 2.22.0 From 171a2e77b9df014e50e26ea5a95030920e5371fb Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 14:45:59 -0500 Subject: [PATCH 043/211] [skip ci] Remove should work now text --- app/views/discussions/_diff_discussion.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index f0f5d62fe9bb..7221ccbf347c 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -1,5 +1,4 @@ - if local_assigns[:on_image] - %h1 Should work now = render partial: "discussions/notes", collection: discussions, as: :discussion - else - expanded = local_assigns.fetch(:expanded, true) -- 2.22.0 From ab106ffc0da2b5339d0876a42d9f9e130b06c39e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 14:47:21 -0500 Subject: [PATCH 044/211] [skip ci] Remove unused renderDiscussionBadges --- app/assets/javascripts/image_diff/image_diff.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index ba2196e3d0b2..afb3c378b44c 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -97,20 +97,6 @@ export default class ImageDiff { })); } - renderDiscussionBadges() { - const discussions = this.el.querySelectorAll('.note-container .discussion-notes .notes'); - - // TODO: Get feedback on this n^2 operation - [].forEach.call(discussions, (discussion, index) => { - const notes = discussion.querySelectorAll('.note'); - [].forEach.call(notes, (note) => { - const badge = note.querySelector('.image-diff-avatar-link .badge'); - badge.innerText = index + 1; - badge.classList.remove('hidden'); - }); - }); - } - addBadge(event) { const { x, y, width, height, noteId } = event.detail; const actual = { -- 2.22.0 From 68de08137aa4e2ca961bd6da267e442ba34078af Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 26 Sep 2017 17:33:13 -0300 Subject: [PATCH 045/211] Hide note badge counter for new discussions and new notes on discussions --- app/controllers/concerns/notes_actions.rb | 4 ++-- app/views/shared/notes/_note.html.haml | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index d995c1c2c348..0d09258f4ba5 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -131,9 +131,9 @@ module NotesActions else template = "discussions/_diff_discussion" on_image = discussion.on_image? if discussion.diff_discussion? + @fresh_discussion = true - # TODO get discussion bagde count for new discussions - locals = { discussions: [discussion], badge_count: 99, on_image: on_image } + locals = { discussions: [discussion], on_image: on_image } end render_to_string( diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 5776e4a06828..41fcd4107340 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -15,8 +15,10 @@ %a.image-diff-avatar-link{ href: user_path(note.author) } = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' - if note.is_a?(DiffNote) && note.on_image? - %span.badge - = badge_counter if local_assigns[:badge_counter] + - counter = badge_counter if local_assigns[:badge_counter] + - badge_class = "hidden" if @fresh_discussion || counter.nil? + %span.badge{ class: badge_class } + = counter .timeline-content .note-header .note-header-info -- 2.22.0 From 517dd3eb912c6ef24941b6757c8954b61f028ced Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 16:06:02 -0500 Subject: [PATCH 046/211] [skip ci] add badge to new discussion comments --- app/assets/javascripts/image_diff/image_diff.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index afb3c378b44c..fdd3ec68bc8f 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -107,6 +107,7 @@ export default class ImageDiff { }; const browserImage = this.imageFrame.querySelector('img'); + const badgeText = this.badges.length + 1; const badge = { actual, browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), @@ -115,10 +116,15 @@ export default class ImageDiff { imageDiffHelper.addCommentBadge(this.imageFrame, { coordinate: badge.browser, - badgeText: this.badges.length + 1, + badgeText, noteId, }); + // Add badge to new comment + const avatarBadge = this.el.querySelector(`#${noteId} .badge`); + avatarBadge.innerText = badgeText; + avatarBadge.classList.remove('hidden'); + this.badges.push(badge); } } -- 2.22.0 From 09fb2a8bb6224852f509de853dc391f64078afba Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 17:00:40 -0500 Subject: [PATCH 047/211] Cleanup diff-notes-collapse css --- app/assets/stylesheets/pages/diff.scss | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 797892662220..a07313ac0a65 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -535,7 +535,6 @@ } .diff-notes-collapse { - position: relative; width: 19px; height: 19px; padding: 0; @@ -543,11 +542,7 @@ z-index: 100; svg { - position: absolute; - left: 50%; - top: 50%; - margin-left: -5.5px; - margin-top: -5.5px; + vertical-align: text-top; } path { -- 2.22.0 From 618e38c4fc4dbf32eea60c2b05a4fe1a8b1fecdf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 17:27:05 -0500 Subject: [PATCH 048/211] [skip ci] add collapse discussion button --- app/assets/stylesheets/pages/diff.scss | 9 +++++++++ app/views/discussions/_notes.html.haml | 3 +++ 2 files changed, 12 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index a07313ac0a65..04bc54d9336b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -717,3 +717,12 @@ color: $white-light; } } + +.note-container .notes { + position: relative; +} + +.note-container .diff-notes-collapse { + position: absolute; + left: -10px; +} diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 5d4a4a74a04b..bf665cfe91f7 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,5 +1,8 @@ .discussion-notes %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } + - if discussion.try(:on_image?) + %button.diff-notes-collapse{ type: 'button' } + = custom_icon('collapse_icon') - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } -- 2.22.0 From e725174f950ba90a139697d7b0b48a2447bf8e48 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 17:27:46 -0500 Subject: [PATCH 049/211] [skip ci] restructure duplicate css --- app/assets/stylesheets/pages/diff.scss | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 04bc54d9336b..f3c8a840e1a7 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -695,26 +695,24 @@ top: (-1px * $image-comment-cursor-top-offset + 2px); } } +} - .badge { - position: absolute; - background-color: $blue-400; - color: $white-light; - border-color: $white-light; - border-width: 1px; - border-style: solid; - } +.frame.click-to-comment .badge, +.image-diff-avatar-link .badge { + position: absolute; + background-color: $blue-400; + color: $white-light; + border-color: $white-light; + border-width: 1px; + border-style: solid; } .image-diff-avatar-link { position: relative; .badge { - position: absolute; top: 25px; right: 8px; - background-color: $blue-400; - color: $white-light; } } -- 2.22.0 From a2fc4881fe718da816edf56084e3451812270216 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 26 Sep 2017 18:17:49 -0500 Subject: [PATCH 050/211] [skip ci] refactor renderBadges --- .../javascripts/image_diff/image_diff.js | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index fdd3ec68bc8f..35953acfcff3 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -64,37 +64,34 @@ export default class ImageDiff { renderBadges() { // Process existing badges from html + const browserImage = this.imageFrame.querySelector('img'); const discussions = this.el.querySelectorAll('.note-container .discussion-notes .notes'); - [].forEach.call(discussions, (discussion) => { - const position = JSON.parse(discussion.dataset.position); + [].forEach.call(discussions, (discussion, index) => { + const position = JSON.parse(discussion.dataset.position); const firstNote = discussion.querySelector('.note'); - this.badges.push({ - actual: { - x: position.x_axis, - y: position.y_axis, - width: position.width, - height: position.height, - }, - noteId: firstNote.id, - }); - }); + const actual = { + x: position.x_axis, + y: position.y_axis, + width: position.width, + height: position.height, + }; - const browserImage = this.imageFrame.querySelector('img'); - - this.badges.map((badge) => { - const newBadge = badge; - newBadge.browser = imageDiffHelper.createBadgeBrowserFromActual(browserImage, badge.actual); - return newBadge; - }); + const badge = { + actual, + browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), + noteId: firstNote.id, + }; - this.badges.forEach((badge, index) => imageDiffHelper.addCommentBadge(this.imageFrame, { coordinate: badge.browser, badgeText: index + 1, noteId: badge.noteId, - })); + }); + + this.badges.push(badge); + }); } addBadge(event) { -- 2.22.0 From f33a0dabb07891e4d89ab0a9c046cdf5c9dc5b7f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 08:24:21 -0500 Subject: [PATCH 051/211] [skip ci] Display badge counter in collapsed state --- app/assets/stylesheets/pages/diff.scss | 24 +++++++++++++++++++++++- app/views/discussions/_notes.html.haml | 8 ++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index f3c8a840e1a7..a77cf15e833f 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -698,7 +698,8 @@ } .frame.click-to-comment .badge, -.image-diff-avatar-link .badge { +.image-diff-avatar-link .badge, +.notes > .badge { position: absolute; background-color: $blue-400; color: $white-light; @@ -724,3 +725,24 @@ position: absolute; left: -10px; } + +.notes > .badge { + display: none; + left: -11px; +} + +.discussion-notes.collapsed { + .diff-notes-collapse, + .note, + .discussion-reply-holder, { + display: none; + } + + .notes > .badge { + display: block; + } +} + +.discussion-notes { + min-height: 20px; +} diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index bf665cfe91f7..bfd96c4c73e1 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,9 +1,13 @@ -.discussion-notes +- collapsed_class = 'collapsed' if discussion.resolved? +- badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] + +.discussion-notes{ class: collapsed_class } %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - if discussion.try(:on_image?) %button.diff-notes-collapse{ type: 'button' } = custom_icon('collapse_icon') - - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] + %button.btn-transparent.badge + = badge_counter = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } .flash-container -- 2.22.0 From b1b7fcc6a0d3e398eae27ac394930d73a1540f80 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 09:41:14 -0500 Subject: [PATCH 052/211] [skip ci] Add missing reference to collapse icon --- app/views/shared/icons/_collapse_icon.svg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/views/shared/icons/_collapse_icon.svg diff --git a/app/views/shared/icons/_collapse_icon.svg b/app/views/shared/icons/_collapse_icon.svg new file mode 100644 index 000000000000..0b9c1429cf60 --- /dev/null +++ b/app/views/shared/icons/_collapse_icon.svg @@ -0,0 +1,2 @@ + + -- 2.22.0 From 5b8c6a6f0da5834ae8da034e436a684770814437 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 27 Sep 2017 12:20:27 -0400 Subject: [PATCH 053/211] Divide discussion notes sections by jagged lines. --- app/assets/stylesheets/pages/diff.scss | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index a77cf15e833f..c5e22dcc78c8 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -651,13 +651,18 @@ background-color: $white-light; } + // double jagged line divider .discussion-notes + .discussion-notes::before { - // TODO: Convert to Jagged border - background-color: black; - content: " "; + content: ''; + position: relative; display: block; width: 100%; - height: $gl-padding; + height: 10px; + background-color: $white-light; + background-image: linear-gradient(45deg,transparent,transparent 73%,#dadada 75%,#fff 80%),linear-gradient(225deg,transparent,transparent 73%,#dadada 75%,#fff 80%),linear-gradient(135deg,transparent,transparent 73%,#dadada 75%,#fff 80%),linear-gradient(-45deg,transparent,transparent 73%,#dadada 75%,#fff 80%); + background-position: 5px 5px,0 5px,0 5px,5px 5px; + background-size: 10px 10px; + background-repeat: repeat; } } -- 2.22.0 From 111df93555937e1ed8f5a7387e9c85b8e6454cb2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 27 Sep 2017 12:54:07 -0400 Subject: [PATCH 054/211] Disable indicator when user can't create a note. --- app/assets/javascripts/diff.js | 10 +++++++++- .../javascripts/image_diff/image_diff.js | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 9a9090ae7bbc..6070671a1162 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -21,10 +21,18 @@ class Diff { FilesCommentButton.init($diffFile); $diffFile.each((index, file) => new gl.ImageFile(file)); + + let canCreateNote = null; + $diffFile.each((index, file) => { + // check permissions only on first file + if (canCreateNote === null) { + canCreateNote = $(file).closest('.files').data('can-create-note') === ''; + } + if (file.querySelector('.diff-viewer .image')) { const imageDiff = new ImageDiff(file); - imageDiff.bindEvents(); + imageDiff.bindEvents(canCreateNote); } }); diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 35953acfcff3..ac61a8fae0e6 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -8,18 +8,23 @@ export default class ImageDiff { this.badges = []; } - bindEvents() { + bindEvents(canCreateNote) { this.clickWrapper = this.click.bind(this); this.blurWrapper = this.blur.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); - this.el.addEventListener('click.imageDiff', this.clickWrapper); - this.el.addEventListener('blur.imageDiff', this.blurWrapper); - this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); - // Render badges after the image diff is loaded this.image.addEventListener('load', this.renderBadgesWrapper); + + if (canCreateNote) { + this.el.addEventListener('click.imageDiff', this.clickWrapper); + this.el.addEventListener('blur.imageDiff', this.blurWrapper); + this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); + + } else { + this.disableCursor(); + } } unbindEvents() { @@ -30,6 +35,10 @@ export default class ImageDiff { this.image.removeEventListener('load', this.renderBadgesWrapper); } + disableCursor() { + this.imageFrame.style.cursor = 'auto'; + } + click(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); -- 2.22.0 From c345850166d44b3f299e4aef65942a2e9262ff2e Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 27 Sep 2017 16:05:54 -0300 Subject: [PATCH 055/211] Code cleanup --- .../javascripts/image_diff/image_diff.js | 1 - .../image_diff/image_diff_helper.js | 12 ------ app/helpers/notes_helper.rb | 10 ++--- app/models/diff_note.rb | 2 +- .../projects/diffs/viewers/_image.html.haml | 5 +-- lib/gitlab/diff/file.rb | 42 +++++++++++-------- lib/gitlab/diff/image_point.rb | 11 +---- lib/gitlab/diff/line.rb | 5 --- lib/gitlab/diff/line_code.rb | 4 +- 9 files changed, 36 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 35953acfcff3..87ed467ae04b 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -49,7 +49,6 @@ export default class ImageDiff { // setupCoordinatesData const el = customEvent.currentTarget; - imageDiffHelper.setLineCodeCoordinates(el, selection.actual); imageDiffHelper.setPositionDataAttribute(el, selection.actual); } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 8c96ccb04cb6..e4972712bc86 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -36,17 +36,6 @@ export function getTargetSelection(event) { }; } -export function setLineCodeCoordinates(el, coordinate) { - const { x, y } = coordinate; - const lineCode = el.dataset.lineCode; - - // TODO: Temporarily remove the trailing numbers that define the x and y coordinates - // Until backend strips this out for us - const lineCodeWithoutCoordinates = lineCode.match(/^(.*?)_/)[0]; - - el.setAttribute('data-line-code', `${lineCodeWithoutCoordinates}${x}_${y}`); -} - export function setPositionDataAttribute(el, options) { const { x, y, width, height } = options; const position = el.dataset.position; @@ -55,7 +44,6 @@ export function setPositionDataAttribute(el, options) { positionObject.y_axis = y; positionObject.width = width; positionObject.height = height; - positionObject.position_type = 'image'; el.setAttribute('data-position', JSON.stringify(positionObject)); } diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 726576a001ad..ce028195e519 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -29,12 +29,12 @@ module NotesHelper @new_diff_note_attrs.slice(:noteable_id, :noteable_type, :commit_id) end - def diff_view_position_data(position_code, position, type) + def diff_view_line_data(line_code, position, line_type) return if @diff_notes_disabled data = { - line_code: position_code, - line_type: type + line_code: line_code, + line_type: line_type } if @use_legacy_diff_notes @@ -47,13 +47,13 @@ module NotesHelper data end - def add_diff_note_button(position_code, position, type=nil) + 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_position_data(position_code, position, type), + data: diff_view_line_data(line_code, position, line_type), title: 'Add a comment to this line' do icon('comment-o') end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 8846516b1714..0ede96f08c61 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -64,7 +64,7 @@ class DiffNote < Note end def original_line_code - return if self.position_type != :text + return if self.position_type != "text" self.diff_file.line_code(self.diff_line) end diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 91a42289f785..138da5e3238f 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -4,15 +4,14 @@ - blob_raw_path = diff_file_blob_raw_path(diff_file) - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) -- image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) # Point on top left corner -- position_code = diff_file.line_code(image_point) +- image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) - discussions = @grouped_diff_discussions[diff_file.file_identifier] - if diff_file.new_file? || diff_file.deleted_file? .image{ data: diff_view_data } %span.wrap -# TODO: Determine if we should conslidate js-add-image-diff-note-button and click-to-comment - .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: diff_view_position_data(position_code, diff_file.position(image_point, :image), nil) } + .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) %p.image-info= number_to_human_size(blob.size) .note-container diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 32f8ebda5b5d..c9581f4b63c7 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -27,28 +27,29 @@ module Gitlab @fallback_diff_refs = fallback_diff_refs end - def position(line, file_type = :text) + def position(position_marker, position_type = "text") return unless diff_refs - Position.new( - file_type: file_type, + data = { + diff_refs: diff_refs, + position_type: position_type, old_path: old_path, - new_path: new_path, - # Move these into separate objects - old_line: line.try(:old_line), - new_line: line.try(:new_line), - x_axis: line.try(:x_axis), - y_axis: line.try(:y_axis), - width: line.try(:width), - height: line.try(:height), - diff_refs: diff_refs - ) + new_path: new_path + } + + if position_type == "text" + data.merge!(text_position_properties(position_marker)) + else + data.merge!(image_position_properties(position_marker)) + end + + Position.new(data) end - def line_code(marker) - return if marker.meta? + def line_code(line) + return if line.meta? - Gitlab::Diff::LineCode.generate(file_path, marker.key_attributes) + Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) end def line_for_line_code(code) @@ -66,7 +67,6 @@ module Gitlab def line_code_for_position(pos) line = line_for_position(pos) - line_code(line) if line end @@ -235,6 +235,14 @@ module Gitlab private + def text_position_properties(line) + { old_line: line.old_line, new_line: line.new_line } + end + + def image_position_properties(image_point) + image_point.to_h + end + def blobs_changed? old_blob && new_blob && old_blob.id != new_blob.id end diff --git a/lib/gitlab/diff/image_point.rb b/lib/gitlab/diff/image_point.rb index c0512289651c..9adf7abc5031 100644 --- a/lib/gitlab/diff/image_point.rb +++ b/lib/gitlab/diff/image_point.rb @@ -10,12 +10,7 @@ module Gitlab @y_axis = y_axis end - # Create superclass method with NotImplemented - def key_attributes - [x_axis, y_axis] - end - - def as_json(opts = nil) + def to_h { width: width, height: height, @@ -23,10 +18,6 @@ module Gitlab y_axis: y_axis } end - # Move to parent class - def meta? - false - end end end end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index c6b1d72a20e3..0603141e4417 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -15,11 +15,6 @@ module Gitlab new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos]) end - # Used to build position code and group discussions - def key_attributes - [old_pos, new_pos] - end - def serialize_keys @serialize_keys ||= %i(text type index old_pos new_pos) end diff --git a/lib/gitlab/diff/line_code.rb b/lib/gitlab/diff/line_code.rb index 4b07228ddcd5..f3578ab3d351 100644 --- a/lib/gitlab/diff/line_code.rb +++ b/lib/gitlab/diff/line_code.rb @@ -1,8 +1,8 @@ module Gitlab module Diff class LineCode - def self.generate(file_path, key_attributes) - "#{Digest::SHA1.hexdigest(file_path)}_#{key_attributes[0]}_#{key_attributes[1]}" + def self.generate(file_path, new_line_position, old_line_position) + "#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}" end end end -- 2.22.0 From 7610cb7e68efc36d1c1a4c559ce9bb6622b2f6c8 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 27 Sep 2017 16:37:06 -0300 Subject: [PATCH 056/211] Clean position serializer --- lib/gitlab/diff/formatters/text_formatter.rb | 12 +++++++++--- lib/gitlab/diff/position.rb | 12 ++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index 2128ef95e808..43770831178e 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -24,9 +24,15 @@ module Gitlab super.merge(old_line: old_line, new_line: new_line) end - # def position_type - # :text - # end + def line_age + if old_line && new_line + nil + elsif new_line + 'new' + else + 'old' + end + end end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index cc4bcac92320..5287bfb1741d 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -8,13 +8,11 @@ module Gitlab attr_accessor :formatter delegate :old_path, - :old_line, - :new_line, :new_path, :base_sha, :start_sha, :head_sha, - :position_type, :to => :formatter + :position_type, to: :formatter def initialize(attrs = {}) @formatter = get_formatter_class(attrs[:position_type]).new(attrs) @@ -56,13 +54,7 @@ module Gitlab end def type - if old_line && new_line - nil - elsif new_line - 'new' - else - 'old' - end + formatter.line_age end def unchanged? -- 2.22.0 From 57e6d5e2312972c5efb5db95b4e767464f59826a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 16:19:08 -0500 Subject: [PATCH 057/211] [skip ci] shift badge numbers when deleted, add new badge number to avatar when new comment in existing discussion --- .../javascripts/image_diff/image_diff.js | 54 ++++++++++++++++++- app/assets/javascripts/notes.js | 42 ++++++++++++--- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 4797ff84c1bb..988c386646d6 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -12,7 +12,9 @@ export default class ImageDiff { this.clickWrapper = this.click.bind(this); this.blurWrapper = this.blur.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); + this.addAvatarBadgeWrapper = this.addAvatarBadge.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); + this.removeBadgeWrapper = this.removeBadge.bind(this); // Render badges after the image diff is loaded this.image.addEventListener('load', this.renderBadgesWrapper); @@ -21,6 +23,8 @@ export default class ImageDiff { this.el.addEventListener('click.imageDiff', this.clickWrapper); this.el.addEventListener('blur.imageDiff', this.blurWrapper); this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); + this.el.addEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); + this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); } else { this.disableCursor(); @@ -31,6 +35,8 @@ export default class ImageDiff { this.el.removeEventListener('click.imageDiff', this.clickWrapper); this.el.removeEventListener('blur.imageDiff', this.blurWrapper); this.el.removeEventListener('addBadge.imageDiff', this.addBadgeWrapper); + this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); + this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); this.image.removeEventListener('load', this.renderBadgesWrapper); } @@ -90,6 +96,7 @@ export default class ImageDiff { actual, browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), noteId: firstNote.id, + discussionId: discussion.dataset.discussionId, }; imageDiffHelper.addCommentBadge(this.imageFrame, { @@ -125,11 +132,54 @@ export default class ImageDiff { noteId, }); + this.addAvatarBadge({ + detail: { + noteId, + badgeNumber: badgeText, + }, + }); + + this.badges.push(badge); + } + + addAvatarBadge(event) { + const { noteId, badgeNumber } = event.detail; + // Add badge to new comment const avatarBadge = this.el.querySelector(`#${noteId} .badge`); - avatarBadge.innerText = badgeText; + avatarBadge.innerText = badgeNumber; avatarBadge.classList.remove('hidden'); + } - this.badges.push(badge); + removeBadge(event) { + const { discussionId, badgeNumber } = event.detail; + + const imageBadges = this.imageFrame.querySelectorAll('.badge'); + + if (this.badges.length !== badgeNumber) { + // Cascade badges count numbers for (avatar badges + image badges) + this.badges.forEach(function(badge, index) { + if (index > badgeNumber - 1) { + const updatedBadgeNumber = index; + imageBadges[index].innerText = updatedBadgeNumber; + + const { discussionId } = badge; + const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); + const discussionBadgeEl = discussionEl.querySelector('.badge'); + discussionBadgeEl.innerText = updatedBadgeNumber; + + const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); + + [].map.call(avatarBadges, (avatarBadge) => { + avatarBadge.innerText = updatedBadgeNumber; + return avatarBadge; + }); + } + }.bind(this)); + } + + const removedBadge = this.badges.splice(badgeNumber - 1, 1); + const selectedImageBadge = imageBadges[badgeNumber - 1]; + selectedImageBadge.remove(); } } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 0357f70c1e5f..d8bf2490df8c 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -456,7 +456,8 @@ export default class Notes { y: y_axis, width, height, - noteId: $discussion[0].querySelector('.notes .note').id, + noteId: $discussion.find('.notes .note').attr('id'), + discussionId: $discussion.find('.notes').data('discussionId'), }, }); @@ -790,6 +791,7 @@ export default class Notes { $note = $(el); $notes = $note.closest('.discussion-notes'); const discussionId = $('.notes', $notes).data('discussion-id'); + const badgeNumber = parseInt($note.find('.image-diff-avatar-link .badge').text(), 10); if (typeof gl.diffNotesCompileComponents !== 'undefined') { if (gl.diffNoteApps[noteElId]) { @@ -812,6 +814,19 @@ export default class Notes { // The notes tr can contain multiple lists of notes, like on the parallel diff // notesTr does not exist for image diffs if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) { + + const $diffFile = $notes.closest('.diff-file'); + if ($diffFile.length > 0) { + const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', { + detail: { + discussionId, + badgeNumber, + }, + }); + + $diffFile[0].dispatchEvent(removeBadgeEvent); + } + $notes.remove(); } else if (notesTr.length > 0) { notesTr.remove(); @@ -1479,14 +1494,13 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); - const diffFile = $form.closest('.diff-file')[0]; - - if (diffFile) { + const $diffFile = $notesContainer.closest('.diff-file'); + if ($diffFile.length > 0) { const blurEvent = new CustomEvent('blur.imageDiff', { detail: e, }); - diffFile.dispatchEvent(blurEvent); + $diffFile[0].dispatchEvent(blurEvent); } // Reset cached commands list when command is applied @@ -1511,7 +1525,23 @@ export default class Notes { } // Show final note element on UI - this.addDiscussionNote($form, note, $notesContainer.length === 0); + const isNewDiffComment = $notesContainer.length === 0 + this.addDiscussionNote($form, note, isNewDiffComment); + + if (!isNewDiffComment) { + // Note's added to existing discussions do not preload with avatar badge counts + if ($diffFile.length > 0) { + const badgeNumber = parseInt($notesContainer.find('.badge').text().trim(), 10); + const addAvatarBadgeEvent = new CustomEvent('addAvatarBadge.imageDiff', { + detail: { + noteId: `note_${note.id}`, + badgeNumber, + }, + }); + + $diffFile[0].dispatchEvent(addAvatarBadgeEvent); + } + } // append flash-container to the Notes list if ($notesContainer.length) { -- 2.22.0 From 01f71c3bf6a50eff8d2e5c413ce6f3498cfc92d7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 16:29:01 -0500 Subject: [PATCH 058/211] [skip ci] update avatar badge when user updates note --- app/assets/javascripts/notes.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index d8bf2490df8c..8958bcb9f839 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1611,6 +1611,8 @@ export default class Notes { const $noteBody = $editingNote.find('.js-task-list-container'); const $noteBodyText = $noteBody.find('.note-text'); const { formData, formContent, formAction } = this.getFormData($form); + const $diffFile = $form.closest('.diff-file'); + const $notesContainer = $form.closest('.notes'); // Cache original comment content const cachedNoteBodyText = $noteBodyText.html(); @@ -1622,10 +1624,23 @@ export default class Notes { /* eslint-disable promise/catch-or-return */ // Make request to update comment on server + ajaxPost(formAction, formData) .then((note) => { // Submission successful! render final note element this.updateNote(note, $editingNote); + + if ($diffFile.length > 0) { + const badgeNumber = parseInt($notesContainer.find('.badge').text().trim(), 10); + const addAvatarBadgeEvent = new CustomEvent('addAvatarBadge.imageDiff', { + detail: { + noteId: `note_${note.id}`, + badgeNumber, + }, + }); + + $diffFile[0].dispatchEvent(addAvatarBadgeEvent); + } }) .fail(() => { // Submission failed, revert back to original note -- 2.22.0 From de5414f26e0d49617a10fee2d193793b003ecc66 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 16:30:51 -0500 Subject: [PATCH 059/211] [skip ci] Prefer getting the diffFile and then checking the length --- app/assets/javascripts/notes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 8958bcb9f839..67d2054fabda 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1072,14 +1072,14 @@ export default class Notes { cancelDiscussionForm(e) { e.preventDefault(); const $form = $(e.target).closest('.js-discussion-note-form'); - const diffFile = $form.closest('.diff-file')[0]; + const $diffFile = $form.closest('.diff-file'); - if (diffFile) { + if ($diffFile.length > 0) { const blurEvent = new CustomEvent('blur.imageDiff', { detail: e, }); - diffFile.dispatchEvent(blurEvent); + $diffFile[0].dispatchEvent(blurEvent); } return this.removeDiscussionNoteForm($form); -- 2.22.0 From 45f8ba8e8a7fb104017d0f607cf7c7d7da732c8d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 27 Sep 2017 17:37:49 -0400 Subject: [PATCH 060/211] Get collapse toggling working. --- .../javascripts/image_diff/image_diff.js | 29 +++++++++++++++---- app/assets/stylesheets/pages/diff.scss | 5 ++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 4797ff84c1bb..eddf05c1a58b 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -5,6 +5,7 @@ export default class ImageDiff { this.el = el; this.imageFrame = el.querySelector('.diff-viewer .image .frame'); this.image = this.imageFrame.querySelector('img'); + this.noteContainer = this.el.querySelector('.note-container'); this.badges = []; } @@ -13,17 +14,37 @@ export default class ImageDiff { this.blurWrapper = this.blur.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); + this.toggleCollapsedWrapper = this.toggleCollapsed.bind(this); // Render badges after the image diff is loaded this.image.addEventListener('load', this.renderBadgesWrapper); + this.noteContainer.addEventListener('click', this.toggleCollapsedWrapper); if (canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); this.el.addEventListener('blur.imageDiff', this.blurWrapper); this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); + } + } - } else { - this.disableCursor(); + toggleCollapsed(e) { + const clickTarget = e.target; + const targetIsButton = clickTarget.classList.contains('diff-notes-collapse'); + const targetIsSvg = clickTarget.parentNode.classList.contains('diff-notes-collapse'); + const targetIsBadge = clickTarget.classList.contains('badge'); + const shouldToggle = targetIsButton || targetIsSvg || targetIsBadge; + + if (shouldToggle) { + if (targetIsButton || targetIsSvg) { + const $button = targetIsButton ? clickTarget : clickTarget.parentNode; + const notesContainer = $button.parentNode.parentNode; + + notesContainer.classList.add('collapsed'); + } else if (targetIsBadge) { + const notesContainer = clickTarget.parentNode.parentNode; + + notesContainer.classList.remove('collapsed'); + } } } @@ -35,10 +56,6 @@ export default class ImageDiff { this.image.removeEventListener('load', this.renderBadgesWrapper); } - disableCursor() { - this.imageFrame.style.cursor = 'auto'; - } - click(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index c5e22dcc78c8..dee830b723e5 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -666,6 +666,10 @@ } } +.files[data-can-create-note] .frame { + cursor: auto; +} + .diff-file .note-container > .new-note, .note-container .discussion-notes { margin-left: 100px; @@ -737,6 +741,7 @@ } .discussion-notes.collapsed { + &+ .discussion-notes::before, .diff-notes-collapse, .note, .discussion-reply-holder, { -- 2.22.0 From c3cfdc6c294e9a76a530b216bad7b4ddafd7f90a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 17:38:54 -0500 Subject: [PATCH 061/211] [skip ci] fix eslint --- .../javascripts/image_diff/image_diff.js | 18 +++++++++--------- app/assets/javascripts/notes.js | 4 +--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 988c386646d6..04d429a52968 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -25,7 +25,6 @@ export default class ImageDiff { this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); this.el.addEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); - } else { this.disableCursor(); } @@ -152,13 +151,13 @@ export default class ImageDiff { } removeBadge(event) { - const { discussionId, badgeNumber } = event.detail; + const { badgeNumber } = event.detail; const imageBadges = this.imageFrame.querySelectorAll('.badge'); if (this.badges.length !== badgeNumber) { // Cascade badges count numbers for (avatar badges + image badges) - this.badges.forEach(function(badge, index) { + this.badges.forEach((badge, index) => { if (index > badgeNumber - 1) { const updatedBadgeNumber = index; imageBadges[index].innerText = updatedBadgeNumber; @@ -170,15 +169,16 @@ export default class ImageDiff { const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); - [].map.call(avatarBadges, (avatarBadge) => { - avatarBadge.innerText = updatedBadgeNumber; - return avatarBadge; - }); + [].map.call(avatarBadges, avatarBadge => + Object.assign(avatarBadge, { + innerText: updatedBadgeNumber, + }), + ); } - }.bind(this)); + }); } - const removedBadge = this.badges.splice(badgeNumber - 1, 1); + this.badges.splice(badgeNumber - 1, 1); const selectedImageBadge = imageBadges[badgeNumber - 1]; selectedImageBadge.remove(); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 67d2054fabda..a020b19eb8a5 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -814,12 +814,10 @@ export default class Notes { // The notes tr can contain multiple lists of notes, like on the parallel diff // notesTr does not exist for image diffs if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) { - const $diffFile = $notes.closest('.diff-file'); if ($diffFile.length > 0) { const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', { detail: { - discussionId, badgeNumber, }, }); @@ -1525,7 +1523,7 @@ export default class Notes { } // Show final note element on UI - const isNewDiffComment = $notesContainer.length === 0 + const isNewDiffComment = $notesContainer.length === 0; this.addDiscussionNote($form, note, isNewDiffComment); if (!isNewDiffComment) { -- 2.22.0 From 2e10bbd1852253f5d3123cbf37d9950201e89b04 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 19:26:36 -0500 Subject: [PATCH 062/211] [skip ci] Create ImageBadges class for improved maintainability --- .../javascripts/image_diff/image_badge.js | 38 ++++++++++++ .../javascripts/image_diff/image_diff.js | 62 +++++++++---------- .../image_diff/image_diff_helper.js | 18 ------ 3 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 app/assets/javascripts/image_diff/image_badge.js diff --git a/app/assets/javascripts/image_diff/image_badge.js b/app/assets/javascripts/image_diff/image_badge.js new file mode 100644 index 000000000000..8608b4b7d3a9 --- /dev/null +++ b/app/assets/javascripts/image_diff/image_badge.js @@ -0,0 +1,38 @@ +const defaultMeta = { + x: 0, + y: 0, + width: 0, + height: 0, +}; + +export default class ImageBadge { + constructor(options) { + const { noteId, discussionId } = options; + + this.actual = options.actual || defaultMeta; + this.browser = options.browser || defaultMeta; + this.noteId = noteId; + this.discussionId = discussionId; + + if (options.imageEl && !options.browser) { + this.browser = this.generateBrowserMeta(options.imageEl); + } + } + + generateBrowserMeta(imageEl) { + const { x, y, width, height } = this.actual; + + const browserImageWidth = imageEl.width; + const browserImageHeight = imageEl.height; + + const widthRatio = browserImageWidth / width; + const heightRatio = browserImageHeight / height; + + return { + x: Math.round(x * widthRatio), + y: Math.round(y * heightRatio), + width: browserImageWidth, + height: browserImageHeight, + }; + } +} diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 04d429a52968..da26a3f22402 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,11 +1,12 @@ import * as imageDiffHelper from './image_diff_helper'; +import ImageBadge from './image_badge'; export default class ImageDiff { constructor(el) { this.el = el; this.imageFrame = el.querySelector('.diff-viewer .image .frame'); this.image = this.imageFrame.querySelector('img'); - this.badges = []; + this.imageBadges = []; } bindEvents(canCreateNote) { @@ -16,7 +17,7 @@ export default class ImageDiff { this.addBadgeWrapper = this.addBadge.bind(this); this.removeBadgeWrapper = this.removeBadge.bind(this); - // Render badges after the image diff is loaded + // Render image badges after the image diff is loaded this.image.addEventListener('load', this.renderBadgesWrapper); if (canCreateNote) { @@ -83,20 +84,17 @@ export default class ImageDiff { [].forEach.call(discussions, (discussion, index) => { const position = JSON.parse(discussion.dataset.position); const firstNote = discussion.querySelector('.note'); - - const actual = { - x: position.x_axis, - y: position.y_axis, - width: position.width, - height: position.height, - }; - - const badge = { - actual, - browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), + const badge = new ImageBadge({ + actual: { + x: position.x_axis, + y: position.y_axis, + width: position.width, + height: position.height, + }, + imageEl: browserImage, noteId: firstNote.id, discussionId: discussion.dataset.discussionId, - }; + }); imageDiffHelper.addCommentBadge(this.imageFrame, { coordinate: badge.browser, @@ -104,26 +102,24 @@ export default class ImageDiff { noteId: badge.noteId, }); - this.badges.push(badge); + this.imageBadges.push(badge); }); } addBadge(event) { - const { x, y, width, height, noteId } = event.detail; - const actual = { - x, - y, - width, - height, - }; - - const browserImage = this.imageFrame.querySelector('img'); - const badgeText = this.badges.length + 1; - const badge = { - actual, - browser: imageDiffHelper.createBadgeBrowserFromActual(browserImage, actual), + const { x, y, width, height, noteId, discussionId } = event.detail; + const badgeText = this.imageBadges.length + 1; + const badge = new ImageBadge({ + actual: { + x, + y, + width, + height, + }, + imageEl: this.imageFrame.querySelector('img'), noteId, - }; + discussionId, + }); imageDiffHelper.addCommentBadge(this.imageFrame, { coordinate: badge.browser, @@ -138,7 +134,7 @@ export default class ImageDiff { }, }); - this.badges.push(badge); + this.imageBadges.push(badge); } addAvatarBadge(event) { @@ -155,9 +151,9 @@ export default class ImageDiff { const imageBadges = this.imageFrame.querySelectorAll('.badge'); - if (this.badges.length !== badgeNumber) { + if (this.imageBadges.length !== badgeNumber) { // Cascade badges count numbers for (avatar badges + image badges) - this.badges.forEach((badge, index) => { + this.imageBadges.forEach((badge, index) => { if (index > badgeNumber - 1) { const updatedBadgeNumber = index; imageBadges[index].innerText = updatedBadgeNumber; @@ -178,7 +174,7 @@ export default class ImageDiff { }); } - this.badges.splice(badgeNumber - 1, 1); + this.imageBadges.splice(badgeNumber - 1, 1); const selectedImageBadge = imageBadges[badgeNumber - 1]; selectedImageBadge.remove(); } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index e4972712bc86..36dcd248d3d9 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -99,21 +99,3 @@ export function addCommentBadge(containerEl, { coordinate, badgeText, noteId }) return button; } - -// TODO: Refactor into separate discussionBadge object -export function createBadgeBrowserFromActual(imageEl, actualProps) { - const { x, y, width, height } = actualProps; - - const browserImageWidth = imageEl.width; - const browserImageHeight = imageEl.height; - - const widthRatio = browserImageWidth / width; - const heightRatio = browserImageHeight / height; - - return { - x: Math.round(x * widthRatio), - y: Math.round(y * heightRatio), - width: browserImageWidth, - height: browserImageHeight, - }; -} -- 2.22.0 From 7f557598bf378ceb5b0c60e19a144c94dfc050b7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 20:14:04 -0500 Subject: [PATCH 063/211] [skip ci] refactor frontend for improved maintainability --- .../javascripts/image_diff/image_diff.js | 121 +++++---------- .../image_diff/image_diff_helper.js | 138 +++++++++++++----- app/assets/javascripts/notes.js | 4 +- 3 files changed, 140 insertions(+), 123 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index da26a3f22402..bcb1705139de 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -4,21 +4,21 @@ import ImageBadge from './image_badge'; export default class ImageDiff { constructor(el) { this.el = el; - this.imageFrame = el.querySelector('.diff-viewer .image .frame'); - this.image = this.imageFrame.querySelector('img'); + this.imageFrameEl = el.querySelector('.diff-viewer .image .frame'); + this.imageEl = this.imageFrameEl.querySelector('img'); this.imageBadges = []; } bindEvents(canCreateNote) { this.clickWrapper = this.click.bind(this); - this.blurWrapper = this.blur.bind(this); + this.blurWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl); this.renderBadgesWrapper = this.renderBadges.bind(this); - this.addAvatarBadgeWrapper = this.addAvatarBadge.bind(this); + this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); this.addBadgeWrapper = this.addBadge.bind(this); this.removeBadgeWrapper = this.removeBadge.bind(this); // Render image badges after the image diff is loaded - this.image.addEventListener('load', this.renderBadgesWrapper); + this.imageEl.addEventListener('load', this.renderBadgesWrapper); if (canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); @@ -38,144 +38,95 @@ export default class ImageDiff { this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); - this.image.removeEventListener('load', this.renderBadgesWrapper); + this.imageEl.removeEventListener('load', this.renderBadgesWrapper); } disableCursor() { - this.imageFrame.style.cursor = 'auto'; + this.imageFrameEl.style.cursor = 'auto'; } click(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); - - // showCommentIndicator - const commentIndicator = this.imageFrame.querySelector('.comment-indicator'); - - if (commentIndicator) { - commentIndicator.style.left = `${selection.browser.x}px`; - commentIndicator.style.top = `${selection.browser.y}px`; - } else { - const button = imageDiffHelper - .addCommentIndicator(this.imageFrame, selection.browser); - - button.addEventListener('click', imageDiffHelper.commentIndicatorOnClick); - } - - // setupCoordinatesData const el = customEvent.currentTarget; + imageDiffHelper.setPositionDataAttribute(el, selection.actual); + imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser); } - // TODO: Rename to something better? - blur() { - const commentIndicator = this.imageFrame.querySelector('.comment-indicator'); + renderBadges() { + const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); - if (commentIndicator) { - commentIndicator.remove(); - } - } + [].forEach.call(discussionsEls, (discussionEl, index) => { + const imageBadge = imageDiffHelper + .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); - renderBadges() { - // Process existing badges from html - const browserImage = this.imageFrame.querySelector('img'); - const discussions = this.el.querySelectorAll('.note-container .discussion-notes .notes'); - - [].forEach.call(discussions, (discussion, index) => { - const position = JSON.parse(discussion.dataset.position); - const firstNote = discussion.querySelector('.note'); - const badge = new ImageBadge({ - actual: { - x: position.x_axis, - y: position.y_axis, - width: position.width, - height: position.height, - }, - imageEl: browserImage, - noteId: firstNote.id, - discussionId: discussion.dataset.discussionId, - }); + this.imageBadges.push(imageBadge); - imageDiffHelper.addCommentBadge(this.imageFrame, { - coordinate: badge.browser, + imageDiffHelper.addCommentBadge(this.imageFrameEl, { + coordinate: imageBadge.browser, badgeText: index + 1, - noteId: badge.noteId, + noteId: imageBadge.noteId, }); - - this.imageBadges.push(badge); }); } addBadge(event) { const { x, y, width, height, noteId, discussionId } = event.detail; const badgeText = this.imageBadges.length + 1; - const badge = new ImageBadge({ + const imageBadge = new ImageBadge({ actual: { x, y, width, height, }, - imageEl: this.imageFrame.querySelector('img'), + imageEl: this.imageFrameEl.querySelector('img'), noteId, discussionId, }); - imageDiffHelper.addCommentBadge(this.imageFrame, { - coordinate: badge.browser, + this.imageBadges.push(imageBadge); + + imageDiffHelper.addCommentBadge(this.imageFrameEl, { + coordinate: imageBadge.browser, badgeText, noteId, }); - this.addAvatarBadge({ + imageDiffHelper.addAvatarBadge(this.el, { detail: { noteId, badgeNumber: badgeText, }, }); - - this.imageBadges.push(badge); - } - - addAvatarBadge(event) { - const { noteId, badgeNumber } = event.detail; - - // Add badge to new comment - const avatarBadge = this.el.querySelector(`#${noteId} .badge`); - avatarBadge.innerText = badgeNumber; - avatarBadge.classList.remove('hidden'); } removeBadge(event) { const { badgeNumber } = event.detail; - - const imageBadges = this.imageFrame.querySelectorAll('.badge'); + const indexToRemove = badgeNumber - 1; + const imageBadgeEls = this.imageFrameEl.querySelectorAll('.badge'); if (this.imageBadges.length !== badgeNumber) { // Cascade badges count numbers for (avatar badges + image badges) this.imageBadges.forEach((badge, index) => { - if (index > badgeNumber - 1) { - const updatedBadgeNumber = index; - imageBadges[index].innerText = updatedBadgeNumber; - + if (index > indexToRemove) { const { discussionId } = badge; + const updatedBadgeNumber = index; const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); const discussionBadgeEl = discussionEl.querySelector('.badge'); - discussionBadgeEl.innerText = updatedBadgeNumber; - const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); + imageBadgeEls[index].innerText = updatedBadgeNumber; + discussionBadgeEl.innerText = updatedBadgeNumber; - [].map.call(avatarBadges, avatarBadge => - Object.assign(avatarBadge, { - innerText: updatedBadgeNumber, - }), - ); + imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); } }); } - this.imageBadges.splice(badgeNumber - 1, 1); - const selectedImageBadge = imageBadges[badgeNumber - 1]; - selectedImageBadge.remove(); + this.imageBadges.splice(indexToRemove, 1); + + const imageBadgeEl = imageBadgeEls[indexToRemove]; + imageBadgeEl.remove(); } } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 36dcd248d3d9..66d99660b5a8 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -1,14 +1,16 @@ +import ImageBadge from './image_badge'; + export function getTargetSelection(event) { - const container = event.currentTarget; - const image = container.querySelector('img'); - const x = event.offsetX ? (event.offsetX) : event.pageX - container.offsetLeft; - const y = event.offsetY ? (event.offsetY) : event.pageY - container.offsetTop; + const containerEl = event.currentTarget; + const imageEl = containerEl.querySelector('img'); + const x = event.offsetX ? (event.offsetX) : event.pageX - containerEl.offsetLeft; + const y = event.offsetY ? (event.offsetY) : event.pageY - containerEl.offsetTop; - const width = image.width; - const height = image.height; + const width = imageEl.width; + const height = imageEl.height; - const actualWidth = image.naturalWidth; - const actualHeight = image.naturalHeight; + const actualWidth = imageEl.naturalWidth; + const actualHeight = imageEl.naturalHeight; const widthRatio = actualWidth / width; const heightRatio = actualHeight / height; @@ -37,6 +39,8 @@ export function getTargetSelection(event) { } export function setPositionDataAttribute(el, options) { + // Update position data attribute so that the + // new comment form can use this data for ajax request const { x, y, width, height } = options; const position = el.dataset.position; const positionObject = JSON.parse(position); @@ -48,54 +52,114 @@ export function setPositionDataAttribute(el, options) { el.setAttribute('data-position', JSON.stringify(positionObject)); } +export function commentIndicatorOnClick(e) { + // Prevent from triggering onAddImageDiffNote in notes.js + e.stopPropagation(); + + const buttonEl = e.currentTarget; + const diffViewerEl = buttonEl.closest('.diff-viewer'); + const textareaEl = diffViewerEl.querySelector('.note-container form .note-textarea'); + textareaEl.focus(); +} + export function addCommentIndicator(containerEl, coordinate) { const { x, y } = coordinate; - const button = document.createElement('button'); - button.classList.add('btn-transparent', 'comment-indicator'); - button.setAttribute('type', 'button'); - button.style.left = `${x}px`; - button.style.top = `${y}px`; + const buttonEl = document.createElement('button'); + buttonEl.classList.add('btn-transparent', 'comment-indicator'); + buttonEl.setAttribute('type', 'button'); + buttonEl.style.left = `${x}px`; + buttonEl.style.top = `${y}px`; - const image = document.createElement('img'); - image.classList.add('image-comment-dark'); - image.src = '/assets/icon_image_comment_dark.svg'; - image.alt = 'comment indicator'; + const imageEl = document.createElement('img'); + imageEl.classList.add('image-comment-dark'); + imageEl.src = '/assets/icon_image_comment_dark.svg'; + imageEl.alt = 'comment indicator'; - button.appendChild(image); - containerEl.appendChild(button); + buttonEl.appendChild(imageEl); + containerEl.appendChild(buttonEl); - return button; + return buttonEl; } -export function commentIndicatorOnClick(e) { - // Prevent from triggering onAddImageDiffNote in notes.js - e.stopPropagation(); +export function removeCommentIndicator(imageFrameEl) { + const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); - const button = e.currentTarget; - const diffViewer = button.closest('.diff-viewer'); - const textarea = diffViewer.querySelector('.note-container form .note-textarea'); - textarea.focus(); + if (commentIndicatorEl) { + commentIndicatorEl.remove(); + } +} + +export function showCommentIndicator(imageFrameEl, coordinate) { + const { x, y } = coordinate; + const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); + + if (commentIndicatorEl) { + commentIndicatorEl.style.left = `${x}px`; + commentIndicatorEl.style.top = `${y}px`; + } else { + const buttonEl = addCommentIndicator(imageFrameEl, coordinate); + buttonEl.addEventListener('click', commentIndicatorOnClick); + } } export function addCommentBadge(containerEl, { coordinate, badgeText, noteId }) { const { x, y } = coordinate; - const button = document.createElement('button'); - button.classList.add('btn-transparent', 'badge'); - button.setAttribute('type', 'button'); - button.innerText = badgeText; + const buttonEl = document.createElement('button'); + buttonEl.classList.add('btn-transparent', 'badge'); + buttonEl.setAttribute('type', 'button'); + buttonEl.innerText = badgeText; - containerEl.appendChild(button); + containerEl.appendChild(buttonEl); // TODO: We should use math to calculate the width so that we don't // have to do a reflow here but we can leave this here for now - const { width, height } = button.getBoundingClientRect(); - button.style.left = `${x - (width * 0.5)}px`; - button.style.top = `${y - (height * 0.5)}px`; - button.addEventListener('click', (e) => { + // Set button center to be the center of the clicked position + const { width, height } = buttonEl.getBoundingClientRect(); + buttonEl.style.left = `${x - (width * 0.5)}px`; + buttonEl.style.top = `${y - (height * 0.5)}px`; + + buttonEl.addEventListener('click', (e) => { e.stopPropagation(); window.location.hash = noteId; }); - return button; + return buttonEl; +} + +export function addAvatarBadge(el, event) { + const { noteId, badgeNumber } = event.detail; + + // Add badge to new comment + const avatarBadgeEl = el.querySelector(`#${noteId} .badge`); + avatarBadgeEl.innerText = badgeNumber; + avatarBadgeEl.classList.remove('hidden'); +} + +export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) { + const position = JSON.parse(discussionEl.dataset.position); + const firstNoteEl = discussionEl.querySelector('.note'); + const badge = new ImageBadge({ + actual: { + x: position.x_axis, + y: position.y_axis, + width: position.width, + height: position.height, + }, + imageEl: imageFrameEl.querySelector('img'), + noteId: firstNoteEl.id, + discussionId: discussionEl.dataset.discussionId, + }); + + return badge; +} + +export function updateAvatarBadgeNumber(discussionEl, newBadgeNumber) { + const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); + + [].map.call(avatarBadges, avatarBadge => + Object.assign(avatarBadge, { + innerText: newBadgeNumber, + }), + ); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a020b19eb8a5..77f1087b7d6c 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1492,7 +1492,7 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); - const $diffFile = $notesContainer.closest('.diff-file'); + let $diffFile = $form.closest('.diff-file'); if ($diffFile.length > 0) { const blurEvent = new CustomEvent('blur.imageDiff', { detail: e, @@ -1528,6 +1528,8 @@ export default class Notes { if (!isNewDiffComment) { // Note's added to existing discussions do not preload with avatar badge counts + // Use $notesContainer to locate .diff-file because $form does not have parent + $diffFile = $notesContainer.closest('.diff-file'); if ($diffFile.length > 0) { const badgeNumber = parseInt($notesContainer.find('.badge').text().trim(), 10); const addAvatarBadgeEvent = new CustomEvent('addAvatarBadge.imageDiff', { -- 2.22.0 From d88be2e204ffa86a66b1b3e4ac09090944a9fd10 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 20:32:30 -0500 Subject: [PATCH 064/211] [skip ci] improve comments --- app/assets/javascripts/image_diff/image_diff.js | 2 ++ app/assets/javascripts/notes.js | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index bcb1705139de..6c460609c812 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -100,6 +100,8 @@ export default class ImageDiff { badgeNumber: badgeText, }, }); + + // TODO: Set toggle discussion badge } removeBadge(event) { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 77f1087b7d6c..05bb47a5c095 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -446,7 +446,7 @@ export default class Notes { row.find(contentContainerClass + ' .content').append($notes.closest('.content').children()); } - // Add badge for image diffs + // Add image badge, avatar badge and toggle discussion badge for image diffs const $diffFile = form.closest('.diff-file'); if ($diffFile.length > 0) { const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); @@ -1624,7 +1624,6 @@ export default class Notes { /* eslint-disable promise/catch-or-return */ // Make request to update comment on server - ajaxPost(formAction, formData) .then((note) => { // Submission successful! render final note element -- 2.22.0 From 60b454fa75ac1bdc7a187a41647ecba001052eb3 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 20:44:03 -0500 Subject: [PATCH 065/211] [skip ci] Add type button --- app/views/discussions/_notes.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index bfd96c4c73e1..494e7aa0c80a 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -6,7 +6,7 @@ - if discussion.try(:on_image?) %button.diff-notes-collapse{ type: 'button' } = custom_icon('collapse_icon') - %button.btn-transparent.badge + %button.btn-transparent.badge{ type: 'button' } = badge_counter = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } -- 2.22.0 From bfeffe350ce379ec40063850646080afdf6a8da3 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 20:44:24 -0500 Subject: [PATCH 066/211] [skip ci] Set toggle discussion badge number when a new comment is created --- app/assets/javascripts/image_diff/image_diff.js | 6 +++--- app/assets/javascripts/image_diff/image_diff_helper.js | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 6c460609c812..d3105b13fc13 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -101,7 +101,8 @@ export default class ImageDiff { }, }); - // TODO: Set toggle discussion badge + const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); + imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); } removeBadge(event) { @@ -116,11 +117,10 @@ export default class ImageDiff { const { discussionId } = badge; const updatedBadgeNumber = index; const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); - const discussionBadgeEl = discussionEl.querySelector('.badge'); imageBadgeEls[index].innerText = updatedBadgeNumber; - discussionBadgeEl.innerText = updatedBadgeNumber; + imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); } }); diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 66d99660b5a8..6f6d78f1f6ff 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -163,3 +163,9 @@ export function updateAvatarBadgeNumber(discussionEl, newBadgeNumber) { }), ); } + +export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { + const discussionBadgeEl = discussionEl.querySelector('.badge'); + + discussionBadgeEl.innerText = newBadgeNumber; +} -- 2.22.0 From c0cac68203ae5ebee3a6c9e27ee34734f6f4de8c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Sep 2017 22:37:24 -0500 Subject: [PATCH 067/211] [skip ci] Add replaced image diff --- app/assets/javascripts/diff.js | 7 +- .../image_diff/replaced_image_diff.js | 145 ++++++++++++++++++ .../projects/diffs/viewers/_image.html.haml | 18 ++- 3 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/image_diff/replaced_image_diff.js diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 6070671a1162..99bcac07adb7 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -4,6 +4,7 @@ import './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; import SingleFileDiff from './single_file_diff'; import ImageDiff from './image_diff/image_diff'; +import ReplacedImageDiff from './image_diff/replaced_image_diff'; const UNFOLD_COUNT = 20; let isBound = false; @@ -30,9 +31,13 @@ class Diff { canCreateNote = $(file).closest('.files').data('can-create-note') === ''; } - if (file.querySelector('.diff-viewer .image')) { + // Single image diff + if (file.querySelector('.diff-viewer .js-single-image')) { const imageDiff = new ImageDiff(file); imageDiff.bindEvents(canCreateNote); + } else if (file.querySelector('.diff-viewer .js-replaced-image')) { + const replacedImageDiff = new ReplacedImageDiff(file); + replacedImageDiff.bindEvents(canCreateNote); } }); diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js new file mode 100644 index 000000000000..5b9e934e9bb1 --- /dev/null +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -0,0 +1,145 @@ +import * as imageDiffHelper from './image_diff_helper'; +import ImageDiff from './image_diff'; + +const viewTypes = { + TWO_UP: 'TWO_UP', + SWIPE: 'SWIPE', + ONION_SKIN: 'ONION_SKIN', +}; + +const defaultViewType = viewTypes.TWO_UP; + +// TODO: Determine whether we can refactor imageDiff and this into one file +export default class ReplacedImageDiff extends ImageDiff { + constructor(el) { + super(el); + + // Insert two up frames separately so we can use regular array methods + // TODO: Determine if we need to load badges and indicators on the replaced file image + const twoUpFramesEls = el.querySelectorAll('.two-up .frame'); + + this.imageFrameEls = { + [viewTypes.TWO_UP]: [twoUpFramesEls[0], twoUpFramesEls[1]], + [viewTypes.SWIPE]: el.querySelector('.swipe .swipe-wrap .frame'), + [viewTypes.ONION_SKIN]: el.querySelectorAll('.onion-skin .frame')[1], + }; + + this.imageEls = { + [viewTypes.TWO_UP]: [ + this.imageFrameEls[viewTypes.TWO_UP][0].querySelector('img'), + this.imageFrameEls[viewTypes.TWO_UP][1].querySelector('img'), + ], + [viewTypes.SWIPE]: this.imageFrameEls[viewTypes.SWIPE].querySelector('img'), + [viewTypes.ONION_SKIN]: this.imageFrameEls[viewTypes.ONION_SKIN].querySelector('img'), + }; + + this.currentView = defaultViewType; + } + + bindEvents(canCreateNote) { + this.clickWrapper = this.click.bind(this); + this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP); + this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE); + this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN); + this.renderBadgesWrapper = this.renderBadges.bind(this); + + const viewModesEl = this.el.querySelector('.view-modes-menu'); + viewModesEl.querySelector('.two-up').addEventListener('click', this.changeToViewTwoUp); + viewModesEl.querySelector('.swipe').addEventListener('click', this.changeToViewSwipe); + viewModesEl.querySelector('.onion-skin').addEventListener('click', this.changeToViewOnionSkin); + + // Render image badges after the image diff is loaded + this.getImageEl(this.currentView).addEventListener('load', this.renderBadgesWrapper); + + if (canCreateNote) { + this.el.addEventListener('click.imageDiff', this.clickWrapper); + } else { + this.disableCursor(); + } + } + + changeView(newView) { + // TODO: add validation for newView to match viewTypes + this.currentView = newView; + + // Clear existing badges on new view + const existingBadges = this.getImageFrameEl().querySelectorAll('.badge'); + [].map.call(existingBadges, badge => badge.remove()); + + // Image_file.js has a fade animation for loading the view + // Need to wait for the images to load in order to re-normalize + // their dimensions + setTimeout(() => { + // Re-normalize badge coordinates based on dimensions of image view + this.imageBadges.forEach(badge => badge.generateBrowserMeta(this.getImageEl())); + + this.renderBadgesWrapper(); + + // TODO: Re-render comment indicator (if any) + }, 300); + } + + click(event) { + const customEvent = event.detail; + const selection = imageDiffHelper.getTargetSelection(customEvent); + const el = customEvent.currentTarget; + + imageDiffHelper.setPositionDataAttribute(el, selection.actual); + imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); + } + + getImageEl() { + let el; + switch (this.currentView) { + case viewTypes.TWO_UP: + el = this.imageEls[viewTypes.TWO_UP][1]; + break; + case viewTypes.SWIPE: + el = this.imageEls[viewTypes.SWIPE]; + break; + case viewTypes.ONION_SKIN: + el = this.imageEls[viewTypes.ONION_SKIN]; + break; + default: + break; + } + + return el; + } + + getImageFrameEl() { + let el; + switch (this.currentView) { + case viewTypes.TWO_UP: + el = this.imageFrameEls[viewTypes.TWO_UP][1]; + break; + case viewTypes.SWIPE: + el = this.imageFrameEls[viewTypes.SWIPE]; + break; + case viewTypes.ONION_SKIN: + el = this.imageFrameEls[viewTypes.ONION_SKIN]; + break; + default: + break; + } + + return el; + } + + renderBadges() { + const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); + + [].forEach.call(discussionsEls, (discussionEl, index) => { + const imageBadge = imageDiffHelper + .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); + + this.imageBadges.push(imageBadge); + + imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { + coordinate: imageBadge.browser, + badgeText: index + 1, + noteId: imageBadge.noteId, + }); + }); + } +} diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 138da5e3238f..22bd630b57d7 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -8,7 +8,7 @@ - discussions = @grouped_diff_discussions[diff_file.file_identifier] - if diff_file.new_file? || diff_file.deleted_file? - .image{ data: diff_view_data } + .image.js-single-image{ data: diff_view_data } %span.wrap -# TODO: Determine if we should conslidate js-add-image-diff-note-button and click-to-comment .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } @@ -17,7 +17,7 @@ .note-container = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion - else - .image + .image.js-replaced-image{ data: diff_view_data } .two-up.view %span.wrap .frame.deleted @@ -31,8 +31,8 @@ %b H: %span.meta-height %span.wrap - .frame.added - = image_tag(blob_raw_path, alt: diff_file.new_path) + .frame.added.js-add-image-diff-note-button.click-to-comment{ data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } + = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) %p.image-info.hide %span.meta-filesize= number_to_human_size(blob.size) | @@ -47,8 +47,8 @@ .frame.deleted = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) .swipe-wrap - .frame.added - = image_tag(blob_raw_path, alt: diff_file.new_path, lazy: false) + .frame.added.js-add-image-diff-note-button.click-to-comment{ data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } + = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) %span.swipe-bar %span.top-handle %span.bottom-handle @@ -57,8 +57,8 @@ .onion-skin-frame .frame.deleted = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - .frame.added - = image_tag(blob_raw_path, alt: diff_file.new_path, lazy: false) + .frame.added.js-add-image-diff-note-button.click-to-comment{ data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } + = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) .controls .transparent .drag-track @@ -71,3 +71,5 @@ %li.two-up{ data: { mode: 'two-up' } } 2-up %li.swipe{ data: { mode: 'swipe' } } Swipe %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin + .note-container + = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion -- 2.22.0 From 0e21f142eb1c7b679be875905630f856b5b3f19a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 28 Sep 2017 09:07:29 -0400 Subject: [PATCH 068/211] Fix syntax error. --- app/assets/javascripts/image_diff/image_diff.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index b2c051833f1e..36d0bca56a98 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -50,6 +50,7 @@ export default class ImageDiff { notesContainer.classList.remove('collapsed'); } + } } unbindEvents() { -- 2.22.0 From f0d2e45e055339d810d32ef8629b73ea6918e808 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 28 Sep 2017 09:09:14 -0400 Subject: [PATCH 069/211] Fix image/imageEl merge error. --- app/assets/javascripts/image_diff/image_diff.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 36d0bca56a98..e597df67c2b2 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -20,7 +20,7 @@ export default class ImageDiff { this.removeBadgeWrapper = this.removeBadge.bind(this); // Render badges after the image diff is loaded - this.image.addEventListener('load', this.renderBadgesWrapper); + this.imageEl.addEventListener('load', this.renderBadgesWrapper); this.noteContainer.addEventListener('click', this.toggleCollapsedWrapper); if (canCreateNote) { -- 2.22.0 From 4dd78862f50b7799636b55a6b6099a83efd19593 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 28 Sep 2017 09:17:26 -0400 Subject: [PATCH 070/211] Use refined colors and variables for jagged border. --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/diff.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 981090b5f566..1445272c65ab 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -314,6 +314,7 @@ $diff-image-info-color: grey; $diff-swipe-border: #999; $diff-view-modes-color: grey; $diff-view-modes-border: #c1c1c1; +$diff-jagged-border-gradient: darken( $white-normal, .8% ); /* * Fonts diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index dee830b723e5..c4288d9bc2bd 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -659,7 +659,7 @@ width: 100%; height: 10px; background-color: $white-light; - background-image: linear-gradient(45deg,transparent,transparent 73%,#dadada 75%,#fff 80%),linear-gradient(225deg,transparent,transparent 73%,#dadada 75%,#fff 80%),linear-gradient(135deg,transparent,transparent 73%,#dadada 75%,#fff 80%),linear-gradient(-45deg,transparent,transparent 73%,#dadada 75%,#fff 80%); + background-image: linear-gradient(45deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%),linear-gradient(225deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%),linear-gradient(135deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%),linear-gradient(-45deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%); background-position: 5px 5px,0 5px,0 5px,5px 5px; background-size: 10px 10px; background-repeat: repeat; -- 2.22.0 From 3a0a257e1941907928f74302f460fac6ffa83ba9 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 28 Sep 2017 09:37:39 -0400 Subject: [PATCH 071/211] Pass canCreateNote to the constructor, remove from loop. --- app/assets/javascripts/diff.js | 15 ++++------ .../javascripts/image_diff/image_diff.js | 30 +++++++++++-------- .../image_diff/replaced_image_diff.js | 4 +-- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 99bcac07adb7..4dd9ba8a428e 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -23,21 +23,16 @@ class Diff { $diffFile.each((index, file) => new gl.ImageFile(file)); - let canCreateNote = null; + const canCreateNote = $($diffFile[0]).closest('.files').data('can-create-note') === ''; $diffFile.each((index, file) => { - // check permissions only on first file - if (canCreateNote === null) { - canCreateNote = $(file).closest('.files').data('can-create-note') === ''; - } - // Single image diff if (file.querySelector('.diff-viewer .js-single-image')) { - const imageDiff = new ImageDiff(file); - imageDiff.bindEvents(canCreateNote); + const imageDiff = new ImageDiff(file, canCreateNote); + imageDiff.bindEvents(); } else if (file.querySelector('.diff-viewer .js-replaced-image')) { - const replacedImageDiff = new ReplacedImageDiff(file); - replacedImageDiff.bindEvents(canCreateNote); + const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote); + replacedImageDiff.bindEvents(); } }); diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index e597df67c2b2..8fec2b66d19c 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -2,15 +2,16 @@ import * as imageDiffHelper from './image_diff_helper'; import ImageBadge from './image_badge'; export default class ImageDiff { - constructor(el) { + constructor(el, canCreateNote = false) { this.el = el; + this.canCreateNote = canCreateNote; this.imageFrameEl = el.querySelector('.diff-viewer .image .frame'); this.imageEl = this.imageFrameEl.querySelector('img'); this.noteContainer = this.el.querySelector('.note-container'); this.imageBadges = []; } - bindEvents(canCreateNote) { + bindEvents() { this.clickWrapper = this.click.bind(this); this.blurWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl); this.renderBadgesWrapper = this.renderBadges.bind(this); @@ -23,7 +24,7 @@ export default class ImageDiff { this.imageEl.addEventListener('load', this.renderBadgesWrapper); this.noteContainer.addEventListener('click', this.toggleCollapsedWrapper); - if (canCreateNote) { + if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); this.el.addEventListener('blur.imageDiff', this.blurWrapper); this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); @@ -32,6 +33,19 @@ export default class ImageDiff { } } + unbindEvents() { + if (this.canCreateNote) { + this.el.removeEventListener('click.imageDiff', this.clickWrapper); + this.el.removeEventListener('blur.imageDiff', this.blurWrapper); + this.el.removeEventListener('addBadge.imageDiff', this.addBadgeWrapper); + this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); + this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); + } + + this.noteContainer.removeEventListener('click', this.toggleCollapsedWrapper); + this.imageEl.removeEventListener('load', this.renderBadgesWrapper); + } + toggleCollapsed(e) { const clickTarget = e.target; const targetIsButton = clickTarget.classList.contains('diff-notes-collapse'); @@ -53,16 +67,6 @@ export default class ImageDiff { } } - unbindEvents() { - this.el.removeEventListener('click.imageDiff', this.clickWrapper); - this.el.removeEventListener('blur.imageDiff', this.blurWrapper); - this.el.removeEventListener('addBadge.imageDiff', this.addBadgeWrapper); - this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); - this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); - - this.imageEl.removeEventListener('load', this.renderBadgesWrapper); - } - click(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 5b9e934e9bb1..6bdd334a7f8d 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -11,8 +11,8 @@ const defaultViewType = viewTypes.TWO_UP; // TODO: Determine whether we can refactor imageDiff and this into one file export default class ReplacedImageDiff extends ImageDiff { - constructor(el) { - super(el); + constructor(el, canCreateNote) { + super(el, canCreateNote); // Insert two up frames separately so we can use regular array methods // TODO: Determine if we need to load badges and indicators on the replaced file image -- 2.22.0 From 0bdb7c5213b2d5f5674c04d8b0186e033905dd16 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 08:50:19 -0500 Subject: [PATCH 072/211] [skip ci] re-normalize indicator when view changes --- .../javascripts/image_diff/image_badge.js | 17 +----- .../image_diff/image_diff_helper.js | 32 +++++++++- .../image_diff/replaced_image_diff.js | 60 ++++++------------- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_badge.js b/app/assets/javascripts/image_diff/image_badge.js index 8608b4b7d3a9..d8f4bd3a47c7 100644 --- a/app/assets/javascripts/image_diff/image_badge.js +++ b/app/assets/javascripts/image_diff/image_badge.js @@ -1,3 +1,5 @@ +import * as imageDiffHelper from './image_diff_helper'; + const defaultMeta = { x: 0, y: 0, @@ -20,19 +22,6 @@ export default class ImageBadge { } generateBrowserMeta(imageEl) { - const { x, y, width, height } = this.actual; - - const browserImageWidth = imageEl.width; - const browserImageHeight = imageEl.height; - - const widthRatio = browserImageWidth / width; - const heightRatio = browserImageHeight / height; - - return { - x: Math.round(x * widthRatio), - y: Math.round(y * heightRatio), - width: browserImageWidth, - height: browserImageHeight, - }; + return imageDiffHelper.generateBrowserMeta(imageEl, this.actual); } } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 6f6d78f1f6ff..c65fd4bb3bbf 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -83,10 +83,22 @@ export function addCommentIndicator(containerEl, coordinate) { export function removeCommentIndicator(imageFrameEl) { const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); + const imageEl = imageFrameEl.querySelector('img'); + const willRemove = commentIndicatorEl; - if (commentIndicatorEl) { + if (willRemove) { commentIndicatorEl.remove(); } + + return { + removed: willRemove, + x: parseInt(commentIndicatorEl.style.left.replace('px', ''), 10), + y: parseInt(commentIndicatorEl.style.top.replace('px', ''), 10), + image: { + width: imageEl.width, + height: imageEl.height, + }, + }; } export function showCommentIndicator(imageFrameEl, coordinate) { @@ -169,3 +181,21 @@ export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { discussionBadgeEl.innerText = newBadgeNumber; } + +// TODO: This transforms the value, doesn't necessarily have to transform into browser meta +export function generateBrowserMeta(imageEl, meta) { + const { x, y, width, height } = meta; + + const browserImageWidth = imageEl.width; + const browserImageHeight = imageEl.height; + + const widthRatio = browserImageWidth / width; + const heightRatio = browserImageHeight / height; + + return { + x: Math.round(x * widthRatio), + y: Math.round(y * heightRatio), + width: browserImageWidth, + height: browserImageHeight, + }; +} diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 6bdd334a7f8d..5fea15d54485 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -14,21 +14,15 @@ export default class ReplacedImageDiff extends ImageDiff { constructor(el, canCreateNote) { super(el, canCreateNote); - // Insert two up frames separately so we can use regular array methods - // TODO: Determine if we need to load badges and indicators on the replaced file image - const twoUpFramesEls = el.querySelectorAll('.two-up .frame'); - this.imageFrameEls = { - [viewTypes.TWO_UP]: [twoUpFramesEls[0], twoUpFramesEls[1]], + [viewTypes.TWO_UP]: el.querySelectorAll('.two-up .frame')[1], [viewTypes.SWIPE]: el.querySelector('.swipe .swipe-wrap .frame'), [viewTypes.ONION_SKIN]: el.querySelectorAll('.onion-skin .frame')[1], }; + // TODO: Refactor into a method to auto generate each of them this.imageEls = { - [viewTypes.TWO_UP]: [ - this.imageFrameEls[viewTypes.TWO_UP][0].querySelector('img'), - this.imageFrameEls[viewTypes.TWO_UP][1].querySelector('img'), - ], + [viewTypes.TWO_UP]: this.imageFrameEls[viewTypes.TWO_UP].querySelector('img'), [viewTypes.SWIPE]: this.imageFrameEls[viewTypes.SWIPE].querySelector('img'), [viewTypes.ONION_SKIN]: this.imageFrameEls[viewTypes.ONION_SKIN].querySelector('img'), }; @@ -59,6 +53,8 @@ export default class ReplacedImageDiff extends ImageDiff { } changeView(newView) { + const indicator = imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + // TODO: add validation for newView to match viewTypes this.currentView = newView; @@ -75,7 +71,17 @@ export default class ReplacedImageDiff extends ImageDiff { this.renderBadgesWrapper(); - // TODO: Re-render comment indicator (if any) + // Re-render indicator in new view + if (indicator.removed) { + // Re-normalize indicator x,y + const normalizedIndicator = imageDiffHelper.generateBrowserMeta(this.getImageEl(), { + x: indicator.x, + y: indicator.y, + width: indicator.image.width, + height: indicator.image.height, + }); + imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); + } }, 300); } @@ -89,41 +95,11 @@ export default class ReplacedImageDiff extends ImageDiff { } getImageEl() { - let el; - switch (this.currentView) { - case viewTypes.TWO_UP: - el = this.imageEls[viewTypes.TWO_UP][1]; - break; - case viewTypes.SWIPE: - el = this.imageEls[viewTypes.SWIPE]; - break; - case viewTypes.ONION_SKIN: - el = this.imageEls[viewTypes.ONION_SKIN]; - break; - default: - break; - } - - return el; + return this.imageEls[this.currentView]; } getImageFrameEl() { - let el; - switch (this.currentView) { - case viewTypes.TWO_UP: - el = this.imageFrameEls[viewTypes.TWO_UP][1]; - break; - case viewTypes.SWIPE: - el = this.imageFrameEls[viewTypes.SWIPE]; - break; - case viewTypes.ONION_SKIN: - el = this.imageFrameEls[viewTypes.ONION_SKIN]; - break; - default: - break; - } - - return el; + return this.imageFrameEls[this.currentView]; } renderBadges() { -- 2.22.0 From 15bc0b2e3c43251696d7a99acf0ce03539bd34dd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 09:18:49 -0500 Subject: [PATCH 073/211] [skip ci] Fix cursor for when not logged in --- app/assets/javascripts/diff.js | 2 +- app/assets/javascripts/image_diff/replaced_image_diff.js | 6 ++---- app/assets/stylesheets/pages/diff.scss | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 4dd9ba8a428e..47d1ef81df09 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -23,7 +23,7 @@ class Diff { $diffFile.each((index, file) => new gl.ImageFile(file)); - const canCreateNote = $($diffFile[0]).closest('.files').data('can-create-note') === ''; + const canCreateNote = $diffFile.first().closest('.files').is('[data-can-create-note]'); $diffFile.each((index, file) => { // Single image diff diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 5fea15d54485..3dd117c5bfae 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -30,7 +30,7 @@ export default class ReplacedImageDiff extends ImageDiff { this.currentView = defaultViewType; } - bindEvents(canCreateNote) { + bindEvents() { this.clickWrapper = this.click.bind(this); this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP); this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE); @@ -45,10 +45,8 @@ export default class ReplacedImageDiff extends ImageDiff { // Render image badges after the image diff is loaded this.getImageEl(this.currentView).addEventListener('load', this.renderBadgesWrapper); - if (canCreateNote) { + if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); - } else { - this.disableCursor(); } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index c4288d9bc2bd..6ac100d4a364 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -666,7 +666,7 @@ } } -.files[data-can-create-note] .frame { +.files:not([data-can-create-note]) .frame { cursor: auto; } -- 2.22.0 From 2fd0b506a4b6d2ff1ae085c43e281a7b8e4b8388 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 28 Sep 2017 10:35:47 -0400 Subject: [PATCH 074/211] Simplify collapsed toggling logic with jquery. --- .../javascripts/image_diff/image_diff.js | 25 +++++-------------- app/views/discussions/_notes.html.haml | 4 +-- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 8fec2b66d19c..c85903612964 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -22,7 +22,9 @@ export default class ImageDiff { // Render badges after the image diff is loaded this.imageEl.addEventListener('load', this.renderBadgesWrapper); - this.noteContainer.addEventListener('click', this.toggleCollapsedWrapper); + + // jquery makes the event delegation here much simpler + $(this.noteContainer).on('click', '.js-diff-notes-toggle', this.toggleCollapsedWrapper); if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); @@ -47,24 +49,9 @@ export default class ImageDiff { } toggleCollapsed(e) { - const clickTarget = e.target; - const targetIsButton = clickTarget.classList.contains('diff-notes-collapse'); - const targetIsSvg = clickTarget.parentNode.classList.contains('diff-notes-collapse'); - const targetIsBadge = clickTarget.classList.contains('badge'); - const shouldToggle = targetIsButton || targetIsSvg || targetIsBadge; - - if (shouldToggle) { - if (targetIsButton || targetIsSvg) { - const $button = targetIsButton ? clickTarget : clickTarget.parentNode; - const notesContainer = $button.parentNode.parentNode; - - notesContainer.classList.add('collapsed'); - } else if (targetIsBadge) { - const notesContainer = clickTarget.parentNode.parentNode; - - notesContainer.classList.remove('collapsed'); - } - } + const $toggleBtn = $(e.currentTarget); + + $toggleBtn.closest('.discussion-notes').toggleClass('collapsed'); } click(event) { diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 494e7aa0c80a..1ed28eb9a95f 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -4,9 +4,9 @@ .discussion-notes{ class: collapsed_class } %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - if discussion.try(:on_image?) - %button.diff-notes-collapse{ type: 'button' } + %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' } = custom_icon('collapse_icon') - %button.btn-transparent.badge{ type: 'button' } + %button.btn-transparent.badge.js-diff-notes-toggle{ type: 'button' } = badge_counter = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } -- 2.22.0 From becb7d2ec916198059966cd360869abf86e0b202 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 10:50:43 -0500 Subject: [PATCH 075/211] [skip ci] initial support for replaced image diff --- .../image_diff/image_diff_helper.js | 22 +++--- .../image_diff/replaced_image_diff.js | 78 ++++++++++++++++++- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index c65fd4bb3bbf..0e701639fac7 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -84,21 +84,25 @@ export function addCommentIndicator(containerEl, coordinate) { export function removeCommentIndicator(imageFrameEl) { const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); const imageEl = imageFrameEl.querySelector('img'); - const willRemove = commentIndicatorEl; + const willRemove = commentIndicatorEl !== null; + let meta = {}; if (willRemove) { + meta = { + x: parseInt(commentIndicatorEl.style.left.replace('px', ''), 10), + y: parseInt(commentIndicatorEl.style.top.replace('px', ''), 10), + image: { + width: imageEl.width, + height: imageEl.height, + }, + }; + commentIndicatorEl.remove(); } - return { + return Object.assign(meta, { removed: willRemove, - x: parseInt(commentIndicatorEl.style.left.replace('px', ''), 10), - y: parseInt(commentIndicatorEl.style.top.replace('px', ''), 10), - image: { - width: imageEl.width, - height: imageEl.height, - }, - }; + }); } export function showCommentIndicator(imageFrameEl, coordinate) { diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 3dd117c5bfae..1752fc32706b 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -1,5 +1,6 @@ import * as imageDiffHelper from './image_diff_helper'; import ImageDiff from './image_diff'; +import ImageBadge from './image_badge'; const viewTypes = { TWO_UP: 'TWO_UP', @@ -32,10 +33,14 @@ export default class ReplacedImageDiff extends ImageDiff { bindEvents() { this.clickWrapper = this.click.bind(this); + this.blurWrapper = this.blur.bind(this); this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP); this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE); this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN); this.renderBadgesWrapper = this.renderBadges.bind(this); + this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); + this.addBadgeWrapper = this.addBadge.bind(this); + this.removeBadgeWrapper = this.removeBadge.bind(this); const viewModesEl = this.el.querySelector('.view-modes-menu'); viewModesEl.querySelector('.two-up').addEventListener('click', this.changeToViewTwoUp); @@ -47,9 +52,17 @@ export default class ReplacedImageDiff extends ImageDiff { if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); + this.el.addEventListener('blur.imageDiff', this.blurWrapper); + this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); + this.el.addEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); + this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); } } + blur() { + return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + } + changeView(newView) { const indicator = imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); @@ -65,8 +78,8 @@ export default class ReplacedImageDiff extends ImageDiff { // their dimensions setTimeout(() => { // Re-normalize badge coordinates based on dimensions of image view - this.imageBadges.forEach(badge => badge.generateBrowserMeta(this.getImageEl())); - + // this.imageBadges.forEach(badge => badge.generateBrowserMeta(this.getImageEl())); + this.imageBadges = []; this.renderBadgesWrapper(); // Re-render indicator in new view @@ -116,4 +129,65 @@ export default class ReplacedImageDiff extends ImageDiff { }); }); } + + addBadge(event) { + const { x, y, width, height, noteId, discussionId } = event.detail; + const badgeText = this.imageBadges.length + 1; + const imageBadge = new ImageBadge({ + actual: { + x, + y, + width, + height, + }, + imageEl: this.getImageFrameEl().querySelector('img'), + noteId, + discussionId, + }); + + this.imageBadges.push(imageBadge); + + imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { + coordinate: imageBadge.browser, + badgeText, + noteId, + }); + + imageDiffHelper.addAvatarBadge(this.el, { + detail: { + noteId, + badgeNumber: badgeText, + }, + }); + + const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); + imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); + } + + removeBadge(event) { + const { badgeNumber } = event.detail; + const indexToRemove = badgeNumber - 1; + const imageBadgeEls = this.getImageFrameEl().querySelectorAll('.badge'); + + if (this.imageBadges.length !== badgeNumber) { + // Cascade badges count numbers for (avatar badges + image badges) + this.imageBadges.forEach((badge, index) => { + if (index > indexToRemove) { + const { discussionId } = badge; + const updatedBadgeNumber = index; + const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); + + imageBadgeEls[index].innerText = updatedBadgeNumber; + + imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); + imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); + } + }); + } + + this.imageBadges.splice(indexToRemove, 1); + + const imageBadgeEl = imageBadgeEls[indexToRemove]; + imageBadgeEl.remove(); + } } -- 2.22.0 From cf3f37ec16f0cf54af3f6860d28d1948d2e644c4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 12:33:59 -0500 Subject: [PATCH 076/211] [skip ci] refactor replaced image diff --- app/assets/javascripts/diff.js | 4 +- .../javascripts/image_diff/image_diff.js | 54 +++--- .../image_diff/image_diff_helper.js | 5 + .../image_diff/replaced_image_diff.js | 182 ++++-------------- .../javascripts/image_diff/view_types.js | 9 + 5 files changed, 84 insertions(+), 170 deletions(-) create mode 100644 app/assets/javascripts/image_diff/view_types.js diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 47d1ef81df09..ac0e64880e4b 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -29,10 +29,10 @@ class Diff { // Single image diff if (file.querySelector('.diff-viewer .js-single-image')) { const imageDiff = new ImageDiff(file, canCreateNote); - imageDiff.bindEvents(); + imageDiff.init(); } else if (file.querySelector('.diff-viewer .js-replaced-image')) { const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote); - replacedImageDiff.bindEvents(); + replacedImageDiff.init(); } }); diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index c85903612964..0922747371e1 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -5,26 +5,38 @@ export default class ImageDiff { constructor(el, canCreateNote = false) { this.el = el; this.canCreateNote = canCreateNote; - this.imageFrameEl = el.querySelector('.diff-viewer .image .frame'); - this.imageEl = this.imageFrameEl.querySelector('img'); - this.noteContainer = this.el.querySelector('.note-container'); + this.$noteContainer = $(this.el.querySelector('.note-container')); this.imageBadges = []; } + init() { + this.imageFrameEl = this.el.querySelector('.diff-viewer .image .frame'); + this.imageEl = this.imageFrameEl.querySelector('img'); + + this.bindEvents(); + } + + getImageEl() { + return this.imageEl; + } + + getImageFrameEl() { + return this.imageFrameEl; + } + bindEvents() { this.clickWrapper = this.click.bind(this); - this.blurWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl); - this.renderBadgesWrapper = this.renderBadges.bind(this); - this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); + this.blurWrapper = this.blur.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); - this.toggleCollapsedWrapper = this.toggleCollapsed.bind(this); + this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); this.removeBadgeWrapper = this.removeBadge.bind(this); + this.renderBadgesWrapper = this.renderBadges.bind(this); // Render badges after the image diff is loaded - this.imageEl.addEventListener('load', this.renderBadgesWrapper); + this.getImageEl().addEventListener('load', this.renderBadgesWrapper); // jquery makes the event delegation here much simpler - $(this.noteContainer).on('click', '.js-diff-notes-toggle', this.toggleCollapsedWrapper); + $noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); @@ -44,14 +56,8 @@ export default class ImageDiff { this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); } - this.noteContainer.removeEventListener('click', this.toggleCollapsedWrapper); this.imageEl.removeEventListener('load', this.renderBadgesWrapper); - } - - toggleCollapsed(e) { - const $toggleBtn = $(e.currentTarget); - - $toggleBtn.closest('.discussion-notes').toggleClass('collapsed'); + $noteContainer.off('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); } click(event) { @@ -60,7 +66,11 @@ export default class ImageDiff { const el = customEvent.currentTarget; imageDiffHelper.setPositionDataAttribute(el, selection.actual); - imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser); + imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); + } + + blur() { + return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); } renderBadges() { @@ -68,11 +78,11 @@ export default class ImageDiff { [].forEach.call(discussionsEls, (discussionEl, index) => { const imageBadge = imageDiffHelper - .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); + .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); this.imageBadges.push(imageBadge); - imageDiffHelper.addCommentBadge(this.imageFrameEl, { + imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { coordinate: imageBadge.browser, badgeText: index + 1, noteId: imageBadge.noteId, @@ -90,14 +100,14 @@ export default class ImageDiff { width, height, }, - imageEl: this.imageFrameEl.querySelector('img'), + imageEl: this.getImageFrameEl().querySelector('img'), noteId, discussionId, }); this.imageBadges.push(imageBadge); - imageDiffHelper.addCommentBadge(this.imageFrameEl, { + imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { coordinate: imageBadge.browser, badgeText, noteId, @@ -117,7 +127,7 @@ export default class ImageDiff { removeBadge(event) { const { badgeNumber } = event.detail; const indexToRemove = badgeNumber - 1; - const imageBadgeEls = this.imageFrameEl.querySelectorAll('.badge'); + const imageBadgeEls = this.getImageFrameEl().querySelectorAll('.badge'); if (this.imageBadges.length !== badgeNumber) { // Cascade badges count numbers for (avatar badges + image badges) diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 0e701639fac7..023177766035 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -203,3 +203,8 @@ export function generateBrowserMeta(imageEl, meta) { height: browserImageHeight, }; } + +export function toggleCollapsed(e) { + const toggleButtonEl = e.currentTarget; + toggleButtonEl.closest('.discussion-notes').classList.toggle('collapsed'); +} diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 1752fc32706b..ed1fce24cff2 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -1,90 +1,75 @@ import * as imageDiffHelper from './image_diff_helper'; +import { viewTypes, isValidViewType } from './view_types'; import ImageDiff from './image_diff'; -import ImageBadge from './image_badge'; -const viewTypes = { - TWO_UP: 'TWO_UP', - SWIPE: 'SWIPE', - ONION_SKIN: 'ONION_SKIN', -}; - -const defaultViewType = viewTypes.TWO_UP; - -// TODO: Determine whether we can refactor imageDiff and this into one file export default class ReplacedImageDiff extends ImageDiff { - constructor(el, canCreateNote) { - super(el, canCreateNote); - + init(defaultViewType = viewTypes.TWO_UP) { this.imageFrameEls = { - [viewTypes.TWO_UP]: el.querySelectorAll('.two-up .frame')[1], - [viewTypes.SWIPE]: el.querySelector('.swipe .swipe-wrap .frame'), - [viewTypes.ONION_SKIN]: el.querySelectorAll('.onion-skin .frame')[1], - }; - - // TODO: Refactor into a method to auto generate each of them - this.imageEls = { - [viewTypes.TWO_UP]: this.imageFrameEls[viewTypes.TWO_UP].querySelector('img'), - [viewTypes.SWIPE]: this.imageFrameEls[viewTypes.SWIPE].querySelector('img'), - [viewTypes.ONION_SKIN]: this.imageFrameEls[viewTypes.ONION_SKIN].querySelector('img'), + [viewTypes.TWO_UP]: this.el.querySelectorAll('.two-up .frame')[1], + [viewTypes.SWIPE]: this.el.querySelector('.swipe .swipe-wrap .frame'), + [viewTypes.ONION_SKIN]: this.el.querySelectorAll('.onion-skin .frame')[1], }; this.currentView = defaultViewType; + this.generateImageEls(); + this.bindEvents(); + } + + generateImageEls() { + this.imageEls = {}; + + const viewTypeNames = Object.getOwnPropertyNames(viewTypes); + viewTypeNames.forEach((viewType) => { + this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img'); + }); } bindEvents() { - this.clickWrapper = this.click.bind(this); - this.blurWrapper = this.blur.bind(this); + super.bindEvents(); + this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP); this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE); this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN); - this.renderBadgesWrapper = this.renderBadges.bind(this); - this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); - this.addBadgeWrapper = this.addBadge.bind(this); - this.removeBadgeWrapper = this.removeBadge.bind(this); const viewModesEl = this.el.querySelector('.view-modes-menu'); viewModesEl.querySelector('.two-up').addEventListener('click', this.changeToViewTwoUp); viewModesEl.querySelector('.swipe').addEventListener('click', this.changeToViewSwipe); viewModesEl.querySelector('.onion-skin').addEventListener('click', this.changeToViewOnionSkin); + } - // Render image badges after the image diff is loaded - this.getImageEl(this.currentView).addEventListener('load', this.renderBadgesWrapper); - - if (this.canCreateNote) { - this.el.addEventListener('click.imageDiff', this.clickWrapper); - this.el.addEventListener('blur.imageDiff', this.blurWrapper); - this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); - this.el.addEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); - this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); - } + getImageEl() { + return this.imageEls[this.currentView]; } - blur() { - return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + getImageFrameEl() { + return this.imageFrameEls[this.currentView]; } changeView(newView) { + if (!isValidViewType(newView)) { + return; + } + const indicator = imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); - // TODO: add validation for newView to match viewTypes this.currentView = newView; // Clear existing badges on new view const existingBadges = this.getImageFrameEl().querySelectorAll('.badge'); [].map.call(existingBadges, badge => badge.remove()); - // Image_file.js has a fade animation for loading the view - // Need to wait for the images to load in order to re-normalize - // their dimensions + // Remove existing references to old view image badges + this.imageBadges = []; + + // Image_file.js has a fade animation of 200ms for loading the view + // Need to wait an additional 250ms for the images to be displayed + // on window in order to re-normalize their dimensions setTimeout(() => { - // Re-normalize badge coordinates based on dimensions of image view - // this.imageBadges.forEach(badge => badge.generateBrowserMeta(this.getImageEl())); - this.imageBadges = []; - this.renderBadgesWrapper(); + // Generate badge coordinates on new view + this.renderBadges(); // Re-render indicator in new view if (indicator.removed) { - // Re-normalize indicator x,y const normalizedIndicator = imageDiffHelper.generateBrowserMeta(this.getImageEl(), { x: indicator.x, y: indicator.y, @@ -93,101 +78,6 @@ export default class ReplacedImageDiff extends ImageDiff { }); imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); } - }, 300); - } - - click(event) { - const customEvent = event.detail; - const selection = imageDiffHelper.getTargetSelection(customEvent); - const el = customEvent.currentTarget; - - imageDiffHelper.setPositionDataAttribute(el, selection.actual); - imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); - } - - getImageEl() { - return this.imageEls[this.currentView]; - } - - getImageFrameEl() { - return this.imageFrameEls[this.currentView]; - } - - renderBadges() { - const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); - - [].forEach.call(discussionsEls, (discussionEl, index) => { - const imageBadge = imageDiffHelper - .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); - - this.imageBadges.push(imageBadge); - - imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { - coordinate: imageBadge.browser, - badgeText: index + 1, - noteId: imageBadge.noteId, - }); - }); - } - - addBadge(event) { - const { x, y, width, height, noteId, discussionId } = event.detail; - const badgeText = this.imageBadges.length + 1; - const imageBadge = new ImageBadge({ - actual: { - x, - y, - width, - height, - }, - imageEl: this.getImageFrameEl().querySelector('img'), - noteId, - discussionId, - }); - - this.imageBadges.push(imageBadge); - - imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { - coordinate: imageBadge.browser, - badgeText, - noteId, - }); - - imageDiffHelper.addAvatarBadge(this.el, { - detail: { - noteId, - badgeNumber: badgeText, - }, - }); - - const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); - imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); - } - - removeBadge(event) { - const { badgeNumber } = event.detail; - const indexToRemove = badgeNumber - 1; - const imageBadgeEls = this.getImageFrameEl().querySelectorAll('.badge'); - - if (this.imageBadges.length !== badgeNumber) { - // Cascade badges count numbers for (avatar badges + image badges) - this.imageBadges.forEach((badge, index) => { - if (index > indexToRemove) { - const { discussionId } = badge; - const updatedBadgeNumber = index; - const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); - - imageBadgeEls[index].innerText = updatedBadgeNumber; - - imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); - imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); - } - }); - } - - this.imageBadges.splice(indexToRemove, 1); - - const imageBadgeEl = imageBadgeEls[indexToRemove]; - imageBadgeEl.remove(); + }, 250); } } diff --git a/app/assets/javascripts/image_diff/view_types.js b/app/assets/javascripts/image_diff/view_types.js new file mode 100644 index 000000000000..2bcfe05420ae --- /dev/null +++ b/app/assets/javascripts/image_diff/view_types.js @@ -0,0 +1,9 @@ +export const viewTypes = { + TWO_UP: 'TWO_UP', + SWIPE: 'SWIPE', + ONION_SKIN: 'ONION_SKIN', +}; + +export function isValidViewType(validate) { + return Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate); +} -- 2.22.0 From eb4d53ba6a19b414f41b33f129281099c21b9694 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 12:38:32 -0500 Subject: [PATCH 077/211] [skip ci] Rename generateBrowserMeta to resizeCoordinatesToImageElement --- app/assets/javascripts/image_diff/image_badge.js | 6 +----- .../javascripts/image_diff/image_diff_helper.js | 15 +++++++-------- .../javascripts/image_diff/replaced_image_diff.js | 2 +- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_badge.js b/app/assets/javascripts/image_diff/image_badge.js index d8f4bd3a47c7..ce4c10976284 100644 --- a/app/assets/javascripts/image_diff/image_badge.js +++ b/app/assets/javascripts/image_diff/image_badge.js @@ -17,11 +17,7 @@ export default class ImageBadge { this.discussionId = discussionId; if (options.imageEl && !options.browser) { - this.browser = this.generateBrowserMeta(options.imageEl); + this.browser = imageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual); } } - - generateBrowserMeta(imageEl) { - return imageDiffHelper.generateBrowserMeta(imageEl, this.actual); - } } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js index 023177766035..3a8ee11ef0c4 100644 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ b/app/assets/javascripts/image_diff/image_diff_helper.js @@ -186,21 +186,20 @@ export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { discussionBadgeEl.innerText = newBadgeNumber; } -// TODO: This transforms the value, doesn't necessarily have to transform into browser meta -export function generateBrowserMeta(imageEl, meta) { +export function resizeCoordinatesToImageElement(imageEl, meta) { const { x, y, width, height } = meta; - const browserImageWidth = imageEl.width; - const browserImageHeight = imageEl.height; + const imageWidth = imageEl.width; + const imageHeight = imageEl.height; - const widthRatio = browserImageWidth / width; - const heightRatio = browserImageHeight / height; + const widthRatio = imageWidth / width; + const heightRatio = imageHeight / height; return { x: Math.round(x * widthRatio), y: Math.round(y * heightRatio), - width: browserImageWidth, - height: browserImageHeight, + width: imageWidth, + height: imageHeight, }; } diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index ed1fce24cff2..f40ca109d840 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -70,7 +70,7 @@ export default class ReplacedImageDiff extends ImageDiff { // Re-render indicator in new view if (indicator.removed) { - const normalizedIndicator = imageDiffHelper.generateBrowserMeta(this.getImageEl(), { + const normalizedIndicator = imageDiffHelper.resizeCoordinatesToImageElement(this.getImageEl(), { x: indicator.x, y: indicator.y, width: indicator.image.width, -- 2.22.0 From eab14a3880daf1471a5b76f321712c86cea5abbe Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 13:19:16 -0500 Subject: [PATCH 078/211] [skip ci] refactor helper into modules --- .../image_diff/helpers/badge_helper.js | 35 +++ .../helpers/comment_indicator_helper.js | 64 ++++++ .../image_diff/helpers/dom_helper.js | 33 +++ .../javascripts/image_diff/helpers/index.js | 24 ++ .../image_diff/helpers/utils_helper.js | 74 +++++++ .../javascripts/image_diff/image_badge.js | 4 +- .../javascripts/image_diff/image_diff.js | 32 +-- .../image_diff/image_diff_helper.js | 209 ------------------ .../image_diff/replaced_image_diff.js | 19 +- 9 files changed, 259 insertions(+), 235 deletions(-) create mode 100644 app/assets/javascripts/image_diff/helpers/badge_helper.js create mode 100644 app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js create mode 100644 app/assets/javascripts/image_diff/helpers/dom_helper.js create mode 100644 app/assets/javascripts/image_diff/helpers/index.js create mode 100644 app/assets/javascripts/image_diff/helpers/utils_helper.js delete mode 100644 app/assets/javascripts/image_diff/image_diff_helper.js diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js new file mode 100644 index 000000000000..487881509477 --- /dev/null +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -0,0 +1,35 @@ +export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { + const { x, y } = coordinate; + const buttonEl = document.createElement('button'); + buttonEl.classList.add('btn-transparent', 'badge', 'js-image-badge'); + buttonEl.setAttribute('type', 'button'); + buttonEl.dataset.noteId = noteId; + buttonEl.innerText = badgeText; + + containerEl.appendChild(buttonEl); + + // TODO: We should use math to calculate the width so that we don't + // have to do a reflow here but we can leave this here for now + + // Set button center to be the center of the clicked position + const { width, height } = buttonEl.getBoundingClientRect(); + buttonEl.style.left = `${x - (width * 0.5)}px`; + buttonEl.style.top = `${y - (height * 0.5)}px`; +} + +export function imageBadgeOnClick(event) { + event.stopPropagation(); + const badge = event.currentTarget; + window.location.hash = badge.dataset.noteId; +} + +// IMAGE BADGE END + +export function addAvatarBadge(el, event) { + const { noteId, badgeNumber } = event.detail; + + // Add badge to new comment + const avatarBadgeEl = el.querySelector(`#${noteId} .badge`); + avatarBadgeEl.innerText = badgeNumber; + avatarBadgeEl.classList.remove('hidden'); +} diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js new file mode 100644 index 000000000000..e11eb1b829c1 --- /dev/null +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -0,0 +1,64 @@ +export function addCommentIndicator(containerEl, coordinate) { + const { x, y } = coordinate; + const buttonEl = document.createElement('button'); + buttonEl.classList.add('btn-transparent', 'comment-indicator'); + buttonEl.setAttribute('type', 'button'); + buttonEl.style.left = `${x}px`; + buttonEl.style.top = `${y}px`; + + const imageEl = document.createElement('img'); + imageEl.classList.add('image-comment-dark'); + imageEl.src = '/assets/icon_image_comment_dark.svg'; + imageEl.alt = 'comment indicator'; + + buttonEl.appendChild(imageEl); + containerEl.appendChild(buttonEl); +} + +export function removeCommentIndicator(imageFrameEl) { + const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); + const imageEl = imageFrameEl.querySelector('img'); + const willRemove = commentIndicatorEl !== null; + let meta = {}; + + if (willRemove) { + meta = { + x: parseInt(commentIndicatorEl.style.left.replace('px', ''), 10), + y: parseInt(commentIndicatorEl.style.top.replace('px', ''), 10), + image: { + width: imageEl.width, + height: imageEl.height, + }, + }; + + commentIndicatorEl.remove(); + } + + return Object.assign(meta, { + removed: willRemove, + }); +} + +export function showCommentIndicator(imageFrameEl, coordinate) { + const { x, y } = coordinate; + const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); + + if (commentIndicatorEl) { + commentIndicatorEl.style.left = `${x}px`; + commentIndicatorEl.style.top = `${y}px`; + } else { + addCommentIndicator(imageFrameEl, coordinate); + } +} + +export function commentIndicatorOnClick(event) { + // Prevent from triggering onAddImageDiffNote in notes.js + event.stopPropagation(); + + const buttonEl = event.currentTarget; + const diffViewerEl = buttonEl.closest('.diff-viewer'); + // TODO: Focus on the new comment form. There is a bug where this just goes to the closest form + // TODO: Also looks like canceling this other comment form will dismiss the comment indicator + const textareaEl = diffViewerEl.querySelector('.note-container form .note-textarea'); + textareaEl.focus(); +} diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js new file mode 100644 index 000000000000..dd5a9e403e5d --- /dev/null +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -0,0 +1,33 @@ +export function setPositionDataAttribute(el, options) { + // Update position data attribute so that the + // new comment form can use this data for ajax request + const { x, y, width, height } = options; + const position = el.dataset.position; + const positionObject = JSON.parse(position); + positionObject.x_axis = x; + positionObject.y_axis = y; + positionObject.width = width; + positionObject.height = height; + + el.setAttribute('data-position', JSON.stringify(positionObject)); +} + +export function updateAvatarBadgeNumber(discussionEl, newBadgeNumber) { + const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); + + [].map.call(avatarBadges, avatarBadge => + Object.assign(avatarBadge, { + innerText: newBadgeNumber, + }), + ); +} + +export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { + const discussionBadgeEl = discussionEl.querySelector('.badge'); + discussionBadgeEl.innerText = newBadgeNumber; +} + +export function toggleCollapsed(event) { + const toggleButtonEl = event.currentTarget; + toggleButtonEl.closest('.discussion-notes').classList.toggle('collapsed'); +} diff --git a/app/assets/javascripts/image_diff/helpers/index.js b/app/assets/javascripts/image_diff/helpers/index.js new file mode 100644 index 000000000000..a8ebbdd6a7cc --- /dev/null +++ b/app/assets/javascripts/image_diff/helpers/index.js @@ -0,0 +1,24 @@ +import * as badgeHelper from './badge_helper'; +import * as commentIndicatorHelper from './comment_indicator_helper'; +import * as domHelper from './dom_helper'; +import * as utilsHelper from './utils_helper'; + +export default { + addCommentIndicator: commentIndicatorHelper.addCommentIndicator, + removeCommentIndicator: commentIndicatorHelper.removeCommentIndicator, + showCommentIndicator: commentIndicatorHelper.showCommentIndicator, + commentIndicatorOnClick: commentIndicatorHelper.commentIndicatorOnClick, + + addImageBadge: badgeHelper.addImageBadge, + imageBadgeOnClick: badgeHelper.imageBadgeOnClick, + addAvatarBadge: badgeHelper.addAvatarBadge, + + setPositionDataAttribute: domHelper.setPositionDataAttribute, + updateAvatarBadgeNumber: domHelper.updateAvatarBadgeNumber, + updateDiscussionBadgeNumber: domHelper.updateDiscussionBadgeNumber, + toggleCollapsed: domHelper.toggleCollapsed, + + resizeCoordinatesToImageElement: utilsHelper.resizeCoordinatesToImageElement, + generateBadgeFromDiscussionDOM: utilsHelper.generateBadgeFromDiscussionDOM, + getTargetSelection: utilsHelper.getTargetSelection, +}; diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js new file mode 100644 index 000000000000..e80abf7f98b9 --- /dev/null +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -0,0 +1,74 @@ +import ImageBadge from '../image_badge'; + +export function resizeCoordinatesToImageElement(imageEl, meta) { + const { x, y, width, height } = meta; + + const imageWidth = imageEl.width; + const imageHeight = imageEl.height; + + const widthRatio = imageWidth / width; + const heightRatio = imageHeight / height; + + return { + x: Math.round(x * widthRatio), + y: Math.round(y * heightRatio), + width: imageWidth, + height: imageHeight, + }; +} + +export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) { + const position = JSON.parse(discussionEl.dataset.position); + const firstNoteEl = discussionEl.querySelector('.note'); + const badge = new ImageBadge({ + actual: { + x: position.x_axis, + y: position.y_axis, + width: position.width, + height: position.height, + }, + imageEl: imageFrameEl.querySelector('img'), + noteId: firstNoteEl.id, + discussionId: discussionEl.dataset.discussionId, + }); + + return badge; +} + +export function getTargetSelection(event) { + const containerEl = event.currentTarget; + const imageEl = containerEl.querySelector('img'); + const x = event.offsetX ? (event.offsetX) : event.pageX - containerEl.offsetLeft; + const y = event.offsetY ? (event.offsetY) : event.pageY - containerEl.offsetTop; + + const width = imageEl.width; + const height = imageEl.height; + + const actualWidth = imageEl.naturalWidth; + const actualHeight = imageEl.naturalHeight; + + const widthRatio = actualWidth / width; + const heightRatio = actualHeight / height; + + // Browser will include the frame as a clickable target, + // which would result in potential 1px out of bounds value + // This bound the coordinates to inside the frame + const normalizedX = Math.max(0, x) && Math.min(x, width); + const normalizedY = Math.max(0, y) && Math.min(y, height); + + return { + browser: { + x: normalizedX, + y: normalizedY, + width, + height, + }, + actual: { + // Round x, y so that we don't need to deal with decimals + x: Math.round(normalizedX * widthRatio), + y: Math.round(normalizedY * heightRatio), + width: actualWidth, + height: actualHeight, + }, + }; +} diff --git a/app/assets/javascripts/image_diff/image_badge.js b/app/assets/javascripts/image_diff/image_badge.js index ce4c10976284..18fecd3fbb51 100644 --- a/app/assets/javascripts/image_diff/image_badge.js +++ b/app/assets/javascripts/image_diff/image_badge.js @@ -1,4 +1,4 @@ -import * as imageDiffHelper from './image_diff_helper'; +import ImageDiffHelper from './helpers/index'; const defaultMeta = { x: 0, @@ -17,7 +17,7 @@ export default class ImageBadge { this.discussionId = discussionId; if (options.imageEl && !options.browser) { - this.browser = imageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual); + this.browser = ImageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual); } } } diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 0922747371e1..e7fca8103b8c 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,4 +1,4 @@ -import * as imageDiffHelper from './image_diff_helper'; +import ImageDiffHelper from './helpers/index'; import ImageBadge from './image_badge'; export default class ImageDiff { @@ -28,7 +28,7 @@ export default class ImageDiff { this.clickWrapper = this.click.bind(this); this.blurWrapper = this.blur.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); - this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); + this.addAvatarBadgeWrapper = ImageDiffHelper.addAvatarBadge.bind(null, this.el); this.removeBadgeWrapper = this.removeBadge.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); @@ -36,7 +36,9 @@ export default class ImageDiff { this.getImageEl().addEventListener('load', this.renderBadgesWrapper); // jquery makes the event delegation here much simpler - $noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); + this.$noteContainer.on('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); + $(this.el).on('click', '.comment-indicator', ImageDiffHelper.commentIndicatorOnClick); + $(this.el).on('click', '.js-image-badge', ImageDiffHelper.imageBadgeOnClick); if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); @@ -57,32 +59,32 @@ export default class ImageDiff { } this.imageEl.removeEventListener('load', this.renderBadgesWrapper); - $noteContainer.off('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); + this.$noteContainer.off('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); } click(event) { const customEvent = event.detail; - const selection = imageDiffHelper.getTargetSelection(customEvent); + const selection = ImageDiffHelper.getTargetSelection(customEvent); const el = customEvent.currentTarget; - imageDiffHelper.setPositionDataAttribute(el, selection.actual); - imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); + ImageDiffHelper.setPositionDataAttribute(el, selection.actual); + ImageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); } blur() { - return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + return ImageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); } renderBadges() { const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); [].forEach.call(discussionsEls, (discussionEl, index) => { - const imageBadge = imageDiffHelper + const imageBadge = ImageDiffHelper .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); this.imageBadges.push(imageBadge); - imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { + ImageDiffHelper.addImageBadge(this.getImageFrameEl(), { coordinate: imageBadge.browser, badgeText: index + 1, noteId: imageBadge.noteId, @@ -107,13 +109,13 @@ export default class ImageDiff { this.imageBadges.push(imageBadge); - imageDiffHelper.addCommentBadge(this.getImageFrameEl(), { + ImageDiffHelper.addImageBadge(this.getImageFrameEl(), { coordinate: imageBadge.browser, badgeText, noteId, }); - imageDiffHelper.addAvatarBadge(this.el, { + ImageDiffHelper.addAvatarBadge(this.el, { detail: { noteId, badgeNumber: badgeText, @@ -121,7 +123,7 @@ export default class ImageDiff { }); const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); - imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); + ImageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); } removeBadge(event) { @@ -139,8 +141,8 @@ export default class ImageDiff { imageBadgeEls[index].innerText = updatedBadgeNumber; - imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); - imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); + ImageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); + ImageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); } }); } diff --git a/app/assets/javascripts/image_diff/image_diff_helper.js b/app/assets/javascripts/image_diff/image_diff_helper.js deleted file mode 100644 index 3a8ee11ef0c4..000000000000 --- a/app/assets/javascripts/image_diff/image_diff_helper.js +++ /dev/null @@ -1,209 +0,0 @@ -import ImageBadge from './image_badge'; - -export function getTargetSelection(event) { - const containerEl = event.currentTarget; - const imageEl = containerEl.querySelector('img'); - const x = event.offsetX ? (event.offsetX) : event.pageX - containerEl.offsetLeft; - const y = event.offsetY ? (event.offsetY) : event.pageY - containerEl.offsetTop; - - const width = imageEl.width; - const height = imageEl.height; - - const actualWidth = imageEl.naturalWidth; - const actualHeight = imageEl.naturalHeight; - - const widthRatio = actualWidth / width; - const heightRatio = actualHeight / height; - - // Browser will include the frame as a clickable target, - // which would result in potential 1px out of bounds value - // This bound the coordinates to inside the frame - const normalizedX = Math.max(0, x) && Math.min(x, width); - const normalizedY = Math.max(0, y) && Math.min(y, height); - - return { - browser: { - x: normalizedX, - y: normalizedY, - width, - height, - }, - actual: { - // Round x, y so that we don't need to deal with decimals - x: Math.round(normalizedX * widthRatio), - y: Math.round(normalizedY * heightRatio), - width: actualWidth, - height: actualHeight, - }, - }; -} - -export function setPositionDataAttribute(el, options) { - // Update position data attribute so that the - // new comment form can use this data for ajax request - const { x, y, width, height } = options; - const position = el.dataset.position; - const positionObject = JSON.parse(position); - positionObject.x_axis = x; - positionObject.y_axis = y; - positionObject.width = width; - positionObject.height = height; - - el.setAttribute('data-position', JSON.stringify(positionObject)); -} - -export function commentIndicatorOnClick(e) { - // Prevent from triggering onAddImageDiffNote in notes.js - e.stopPropagation(); - - const buttonEl = e.currentTarget; - const diffViewerEl = buttonEl.closest('.diff-viewer'); - const textareaEl = diffViewerEl.querySelector('.note-container form .note-textarea'); - textareaEl.focus(); -} - -export function addCommentIndicator(containerEl, coordinate) { - const { x, y } = coordinate; - const buttonEl = document.createElement('button'); - buttonEl.classList.add('btn-transparent', 'comment-indicator'); - buttonEl.setAttribute('type', 'button'); - buttonEl.style.left = `${x}px`; - buttonEl.style.top = `${y}px`; - - const imageEl = document.createElement('img'); - imageEl.classList.add('image-comment-dark'); - imageEl.src = '/assets/icon_image_comment_dark.svg'; - imageEl.alt = 'comment indicator'; - - buttonEl.appendChild(imageEl); - containerEl.appendChild(buttonEl); - - return buttonEl; -} - -export function removeCommentIndicator(imageFrameEl) { - const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); - const imageEl = imageFrameEl.querySelector('img'); - const willRemove = commentIndicatorEl !== null; - let meta = {}; - - if (willRemove) { - meta = { - x: parseInt(commentIndicatorEl.style.left.replace('px', ''), 10), - y: parseInt(commentIndicatorEl.style.top.replace('px', ''), 10), - image: { - width: imageEl.width, - height: imageEl.height, - }, - }; - - commentIndicatorEl.remove(); - } - - return Object.assign(meta, { - removed: willRemove, - }); -} - -export function showCommentIndicator(imageFrameEl, coordinate) { - const { x, y } = coordinate; - const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); - - if (commentIndicatorEl) { - commentIndicatorEl.style.left = `${x}px`; - commentIndicatorEl.style.top = `${y}px`; - } else { - const buttonEl = addCommentIndicator(imageFrameEl, coordinate); - buttonEl.addEventListener('click', commentIndicatorOnClick); - } -} - -export function addCommentBadge(containerEl, { coordinate, badgeText, noteId }) { - const { x, y } = coordinate; - const buttonEl = document.createElement('button'); - buttonEl.classList.add('btn-transparent', 'badge'); - buttonEl.setAttribute('type', 'button'); - buttonEl.innerText = badgeText; - - containerEl.appendChild(buttonEl); - - // TODO: We should use math to calculate the width so that we don't - // have to do a reflow here but we can leave this here for now - - // Set button center to be the center of the clicked position - const { width, height } = buttonEl.getBoundingClientRect(); - buttonEl.style.left = `${x - (width * 0.5)}px`; - buttonEl.style.top = `${y - (height * 0.5)}px`; - - buttonEl.addEventListener('click', (e) => { - e.stopPropagation(); - window.location.hash = noteId; - }); - - return buttonEl; -} - -export function addAvatarBadge(el, event) { - const { noteId, badgeNumber } = event.detail; - - // Add badge to new comment - const avatarBadgeEl = el.querySelector(`#${noteId} .badge`); - avatarBadgeEl.innerText = badgeNumber; - avatarBadgeEl.classList.remove('hidden'); -} - -export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) { - const position = JSON.parse(discussionEl.dataset.position); - const firstNoteEl = discussionEl.querySelector('.note'); - const badge = new ImageBadge({ - actual: { - x: position.x_axis, - y: position.y_axis, - width: position.width, - height: position.height, - }, - imageEl: imageFrameEl.querySelector('img'), - noteId: firstNoteEl.id, - discussionId: discussionEl.dataset.discussionId, - }); - - return badge; -} - -export function updateAvatarBadgeNumber(discussionEl, newBadgeNumber) { - const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); - - [].map.call(avatarBadges, avatarBadge => - Object.assign(avatarBadge, { - innerText: newBadgeNumber, - }), - ); -} - -export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { - const discussionBadgeEl = discussionEl.querySelector('.badge'); - - discussionBadgeEl.innerText = newBadgeNumber; -} - -export function resizeCoordinatesToImageElement(imageEl, meta) { - const { x, y, width, height } = meta; - - const imageWidth = imageEl.width; - const imageHeight = imageEl.height; - - const widthRatio = imageWidth / width; - const heightRatio = imageHeight / height; - - return { - x: Math.round(x * widthRatio), - y: Math.round(y * heightRatio), - width: imageWidth, - height: imageHeight, - }; -} - -export function toggleCollapsed(e) { - const toggleButtonEl = e.currentTarget; - toggleButtonEl.closest('.discussion-notes').classList.toggle('collapsed'); -} diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index f40ca109d840..20949d8fe87c 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -1,4 +1,4 @@ -import * as imageDiffHelper from './image_diff_helper'; +import ImageDiffHelper from './helpers/index'; import { viewTypes, isValidViewType } from './view_types'; import ImageDiff from './image_diff'; @@ -50,7 +50,7 @@ export default class ReplacedImageDiff extends ImageDiff { return; } - const indicator = imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + const indicator = ImageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); this.currentView = newView; @@ -70,13 +70,14 @@ export default class ReplacedImageDiff extends ImageDiff { // Re-render indicator in new view if (indicator.removed) { - const normalizedIndicator = imageDiffHelper.resizeCoordinatesToImageElement(this.getImageEl(), { - x: indicator.x, - y: indicator.y, - width: indicator.image.width, - height: indicator.image.height, - }); - imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); + const normalizedIndicator = ImageDiffHelper + .resizeCoordinatesToImageElement(this.getImageEl(), { + x: indicator.x, + y: indicator.y, + width: indicator.image.width, + height: indicator.image.height, + }); + ImageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); } }, 250); } -- 2.22.0 From 96c685a3533f4b8f0b4ed91a82095a040b757ddf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 13:24:27 -0500 Subject: [PATCH 079/211] [skip ci] remove comment --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 487881509477..851c188ab569 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -23,8 +23,6 @@ export function imageBadgeOnClick(event) { window.location.hash = badge.dataset.noteId; } -// IMAGE BADGE END - export function addAvatarBadge(el, event) { const { noteId, badgeNumber } = event.detail; -- 2.22.0 From 2181a1c3c2f55c3f3477f1ed114f04ff120237b2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 13:34:35 -0500 Subject: [PATCH 080/211] [skip ci] Fix bug whereby the wrong badge was being passed --- app/assets/javascripts/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 05bb47a5c095..441a6b990051 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1531,7 +1531,7 @@ export default class Notes { // Use $notesContainer to locate .diff-file because $form does not have parent $diffFile = $notesContainer.closest('.diff-file'); if ($diffFile.length > 0) { - const badgeNumber = parseInt($notesContainer.find('.badge').text().trim(), 10); + const badgeNumber = parseInt($notesContainer.find('.badge.js-diff-notes-toggle').text().trim(), 10); const addAvatarBadgeEvent = new CustomEvent('addAvatarBadge.imageDiff', { detail: { noteId: `note_${note.id}`, -- 2.22.0 From 3e9dc56d669070f6f98915468e5e63989451e05f Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 28 Sep 2017 16:19:54 -0300 Subject: [PATCH 081/211] Fix some failing specs --- lib/gitlab/diff/position_tracer.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb index b68a16368147..4b7e65e84718 100644 --- a/lib/gitlab/diff/position_tracer.rb +++ b/lib/gitlab/diff/position_tracer.rb @@ -89,7 +89,7 @@ module Gitlab def trace_added_line(ab_position) b_path = ab_position.new_path - b_line = ab_position.new_line + b_line = ab_position.formatter.new_line bd_diff = bd_diffs.diff_file_with_old_path(b_path) @@ -128,7 +128,7 @@ module Gitlab def trace_removed_line(ab_position) a_path = ab_position.old_path - a_line = ab_position.old_line + a_line = ab_position.formatter.old_line ac_diff = ac_diffs.diff_file_with_old_path(a_path) @@ -159,9 +159,9 @@ module Gitlab def trace_unchanged_line(ab_position) a_path = ab_position.old_path - a_line = ab_position.old_line + a_line = ab_position.formatter.old_line b_path = ab_position.new_path - b_line = ab_position.new_line + b_line = ab_position.formatter.new_line ac_diff = ac_diffs.diff_file_with_old_path(a_path) -- 2.22.0 From 0ab793255e8483bcc0e51b3d5eb7ca2af6b9407f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 15:59:17 -0500 Subject: [PATCH 082/211] [skip ci] use querySelector to get specific image rather than using querySelectorAll --- app/assets/javascripts/image_diff/replaced_image_diff.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 20949d8fe87c..025ee66aae08 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -5,9 +5,9 @@ import ImageDiff from './image_diff'; export default class ReplacedImageDiff extends ImageDiff { init(defaultViewType = viewTypes.TWO_UP) { this.imageFrameEls = { - [viewTypes.TWO_UP]: this.el.querySelectorAll('.two-up .frame')[1], + [viewTypes.TWO_UP]: this.el.querySelector('.two-up .frame.added'), [viewTypes.SWIPE]: this.el.querySelector('.swipe .swipe-wrap .frame'), - [viewTypes.ONION_SKIN]: this.el.querySelectorAll('.onion-skin .frame')[1], + [viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .frame.added'), }; this.currentView = defaultViewType; -- 2.22.0 From c6d61332be3798870ce13708492803e0b3d23992 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 15:59:52 -0500 Subject: [PATCH 083/211] [skip ci] show image diff in discussion tab --- app/assets/stylesheets/framework/timeline.scss | 5 ++++- app/assets/stylesheets/pages/diff.scss | 15 ++++++++++++++- .../discussions/_diff_with_notes.html.haml | 17 ++++++++++++----- app/views/discussions/_notes.html.haml | 3 ++- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 3d68a50f91f7..2b69cad86fbd 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -17,9 +17,12 @@ .diff-file { border: 1px solid $border-color; - border-bottom: none; margin: 0; } + + &.text-file .diff-file { + border-bottom: none; + } } .timeline-entry { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 6ac100d4a364..fbfa0b19efc0 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -676,6 +676,14 @@ border-left: 1px solid $white-normal; } +.notes.active { + .diff-file .note-container > .new-note, + .note-container .discussion-notes { + margin-left: inherit; + border-left: inherit; + } +} + .frame.click-to-comment { position: relative; cursor: url(icon_image_comment.svg) @@ -708,7 +716,8 @@ .frame.click-to-comment .badge, .image-diff-avatar-link .badge, -.notes > .badge { +.notes > .badge, +.discussion-body .frame .badge { position: absolute; background-color: $blue-400; color: $white-light; @@ -756,3 +765,7 @@ .discussion-notes { min-height: 20px; } + +.discussion-body .image .frame { + position: relative; +} diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 27d909688e91..3a07468731b7 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -1,15 +1,15 @@ - diff_file = discussion.diff_file - blob = discussion.blob +- discussions = { discussion.original_line_code => [discussion] } -.diff-file.file-holder +.diff-file.file-holder{ class: (diff_file.text? ? 'text-file' : '') } .js-file-title.file-title.file-title-flex-parent .file-header-content = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false - .diff-content.code.js-syntax-highlight - %table - - if diff_file.text? - - discussions = { discussion.original_line_code => [discussion] } + - if diff_file.text? + .diff-content.code.js-syntax-highlight + %table = render partial: "projects/diffs/line", collection: discussion.truncated_diff_lines, as: :line, @@ -17,3 +17,10 @@ discussions: discussions, discussion_expanded: true, plain: true } + - else + - blob_raw_path = diff_file_blob_raw_path(diff_file) + .image + .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') } + = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) + .note-container + = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 1ed28eb9a95f..662d96e4be7a 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,9 +1,10 @@ - collapsed_class = 'collapsed' if discussion.resolved? - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] +- show_toggle = local_assigns.fetch(:show_toggle, true) .discussion-notes{ class: collapsed_class } %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - - if discussion.try(:on_image?) + - if discussion.try(:on_image?) && show_toggle %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' } = custom_icon('collapse_icon') %button.btn-transparent.badge.js-diff-notes-toggle{ type: 'button' } -- 2.22.0 From d88d16b9297ec4ea1948578c91e6c1a30268a2ca Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 17:29:16 -0500 Subject: [PATCH 084/211] [skip ci] Add image diff badge to discussion --- app/assets/javascripts/diff.js | 1 - .../image_diff/discussion_badges.js | 34 +++++++++++++++++++ app/assets/javascripts/merge_request_tabs.js | 4 +++ app/assets/stylesheets/pages/diff.scss | 1 + .../discussions/_diff_with_notes.html.haml | 4 +-- 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/image_diff/discussion_badges.js diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index ac0e64880e4b..81e4290b66c1 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -26,7 +26,6 @@ class Diff { const canCreateNote = $diffFile.first().closest('.files').is('[data-can-create-note]'); $diffFile.each((index, file) => { - // Single image diff if (file.querySelector('.diff-viewer .js-single-image')) { const imageDiff = new ImageDiff(file, canCreateNote); imageDiff.init(); diff --git a/app/assets/javascripts/image_diff/discussion_badges.js b/app/assets/javascripts/image_diff/discussion_badges.js new file mode 100644 index 000000000000..516e0885e8c6 --- /dev/null +++ b/app/assets/javascripts/image_diff/discussion_badges.js @@ -0,0 +1,34 @@ +import ImageDiffHelper from './helpers/index'; +// TODO: Rename all instances of imagediffhelper to start with lower case + +export function create(imageEl) { + const imageFrameEl = imageEl.closest('.frame'); + const { x_axis, y_axis, width, height } = JSON.parse(imageFrameEl.dataset.position); + + const meta = ImageDiffHelper.resizeCoordinatesToImageElement(imageEl, { + x: x_axis, + y: y_axis, + width, + height, + }); + + const diffFile = imageFrameEl.closest('.diff-file'); + const firstNote = diffFile.querySelector('.discussion-notes .note'); + + ImageDiffHelper.addImageBadge(imageFrameEl, { + coordinate: { + x: meta.x, + y: meta.y, + }, + // Discussion badges are empty badge dots + badgeText: ' ', + noteId: firstNote.id, + }); +} + +export function init() { + const imageEls = document.querySelectorAll('.timeline-content .diff-file .image .frame img'); + [].forEach.call(imageEls, imageEl => imageEl.addEventListener('load', create.bind(null, imageEl))); + + $('.timeline-content .diff-file').on('click', '.js-image-badge', ImageDiffHelper.imageBadgeOnClick); +} diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 8ae127776e87..b5ef2a2280ee 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -13,6 +13,8 @@ import { isMetaClick, } from './lib/utils/common_utils'; +import * as discussionBadges from './image_diff/discussion_badges'; + /* eslint-disable max-len */ // MergeRequestTabs // @@ -154,6 +156,8 @@ import { } this.resetViewContainer(); this.destroyPipelinesView(); + + discussionBadges.init(); } if (this.setUrl) { this.setCurrentAction(action); diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index fbfa0b19efc0..28a6d5defbbe 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -724,6 +724,7 @@ border-color: $white-light; border-width: 1px; border-style: solid; + min-height: $gl-padding; } .image-diff-avatar-link { diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 3a07468731b7..505232bd42f0 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -20,7 +20,7 @@ - else - blob_raw_path = diff_file_blob_raw_path(diff_file) .image - .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') } - = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) + .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } + = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) .note-container = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false } -- 2.22.0 From 472943d7979010a03f41dfd9c7c427a7af8b9346 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 17:45:31 -0500 Subject: [PATCH 085/211] [skip ci] increase min height of discussion notes --- app/assets/stylesheets/pages/diff.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 28a6d5defbbe..46b020d6c999 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -764,7 +764,7 @@ } .discussion-notes { - min-height: 20px; + min-height: 30px; } .discussion-body .image .frame { -- 2.22.0 From 5bcfe54ba61a0af9cb222df32c3ac24af49d694e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 23:40:23 -0500 Subject: [PATCH 086/211] [skip ci] Fix jump to next discussion --- .../diff_notes/components/jump_to_discussion.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 497c23f014f7..e77910a83d4b 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -171,7 +171,14 @@ const JumpToDiscussion = Vue.extend({ // When jumping between unresolved discussions on the diffs tab, we show them. $target.closest(".content").show(); - $target = $target.closest("tr.notes_holder"); + const $notesHolder = $target.closest("tr.notes_holder"); + + // Image diff discussions does not use notes_holder + // so we should keep original $target value in those cases + if ($notesHolder.length > 0) { + $target = $notesHolder; + } + $target.show(); // If we are on the diffs tab, we don't scroll to the discussion itself, but to -- 2.22.0 From 5166a9427cf5fec6f1819b1e9043a70f8fedd7d3 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 28 Sep 2017 23:58:04 -0500 Subject: [PATCH 087/211] [skip ci] fix note highlighting for image diff note --- app/assets/stylesheets/pages/diff.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 46b020d6c999..f9a51c20f793 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -647,8 +647,8 @@ background-color: $gray-light; border-top: 1px solid $white-normal; - .notes .note { - background-color: $white-light; + .timeline-entry:not(.target) { + background: $white-light; } // double jagged line divider -- 2.22.0 From 865469f84c9cdd61359977126da2f7612e363045 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 00:33:20 -0500 Subject: [PATCH 088/211] [skip ci] Initialize imageFile and imageDiff when collapsed diff is first opened --- app/assets/javascripts/diff.js | 14 ++------------ app/assets/javascripts/image_diff/helpers/index.js | 1 + .../javascripts/image_diff/helpers/utils_helper.js | 12 ++++++++++++ app/assets/javascripts/single_file_diff.js | 9 ++++++++- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 81e4290b66c1..36849123f633 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -3,8 +3,7 @@ import './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; import SingleFileDiff from './single_file_diff'; -import ImageDiff from './image_diff/image_diff'; -import ReplacedImageDiff from './image_diff/replaced_image_diff'; +import imageDiffHelper from './image_diff/helpers/index'; const UNFOLD_COUNT = 20; let isBound = false; @@ -24,16 +23,7 @@ class Diff { $diffFile.each((index, file) => new gl.ImageFile(file)); const canCreateNote = $diffFile.first().closest('.files').is('[data-can-create-note]'); - - $diffFile.each((index, file) => { - if (file.querySelector('.diff-viewer .js-single-image')) { - const imageDiff = new ImageDiff(file, canCreateNote); - imageDiff.init(); - } else if (file.querySelector('.diff-viewer .js-replaced-image')) { - const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote); - replacedImageDiff.init(); - } - }); + $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote)); if (!isBound) { $(document) diff --git a/app/assets/javascripts/image_diff/helpers/index.js b/app/assets/javascripts/image_diff/helpers/index.js index a8ebbdd6a7cc..7c4852ba3fe3 100644 --- a/app/assets/javascripts/image_diff/helpers/index.js +++ b/app/assets/javascripts/image_diff/helpers/index.js @@ -21,4 +21,5 @@ export default { resizeCoordinatesToImageElement: utilsHelper.resizeCoordinatesToImageElement, generateBadgeFromDiscussionDOM: utilsHelper.generateBadgeFromDiscussionDOM, getTargetSelection: utilsHelper.getTargetSelection, + initImageDiff: utilsHelper.initImageDiff, }; diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index e80abf7f98b9..9aabc00ba4af 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -1,4 +1,6 @@ import ImageBadge from '../image_badge'; +import ImageDiff from '../image_diff'; +import ReplacedImageDiff from '../replaced_image_diff'; export function resizeCoordinatesToImageElement(imageEl, meta) { const { x, y, width, height } = meta; @@ -72,3 +74,13 @@ export function getTargetSelection(event) { }, }; } + +export function initImageDiff(file, canCreateNote) { + if (file.querySelector('.diff-viewer .js-single-image')) { + const imageDiff = new ImageDiff(file, canCreateNote); + imageDiff.init(); + } else if (file.querySelector('.diff-viewer .js-replaced-image')) { + const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote); + replacedImageDiff.init(); + } +} diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 4505a79a2df4..2ec16a044126 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -1,6 +1,7 @@ /* 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'; +import imageDiffHelper from './image_diff/helpers/index'; const WRAPPER = '
'; const LOADING_HTML = ''; @@ -74,9 +75,15 @@ export default class SingleFileDiff { gl.diffNotesCompileComponents(); } - FilesCommentButton.init($(_this.file)); + const $file = $(_this.file); + FilesCommentButton.init($file); + + const canCreateNote = $file.closest('.files').is('[data-can-create-note]'); + imageDiffHelper.initImageDiff($file[0], canCreateNote); if (cb) cb(); + + return new gl.ImageFile($file); }; })(this)); } -- 2.22.0 From 9d4a110fb2a280b18d54b729bfa38952ac6c9c6e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 08:39:14 -0500 Subject: [PATCH 089/211] [skip ci] Fix scss_lints --- app/assets/stylesheets/pages/diff.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index f9a51c20f793..9b56ca3db844 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -659,7 +659,10 @@ width: 100%; height: 10px; background-color: $white-light; - background-image: linear-gradient(45deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%),linear-gradient(225deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%),linear-gradient(135deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%),linear-gradient(-45deg,transparent,transparent 73%,$diff-jagged-border-gradient 75%,$white-light 80%); + background-image: linear-gradient(45deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%), + linear-gradient(225deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%), + linear-gradient(135deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%), + linear-gradient(-45deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%); background-position: 5px 5px,0 5px,0 5px,5px 5px; background-size: 10px 10px; background-repeat: repeat; -- 2.22.0 From efa70925905ec2ec2497ecf7599bc16059b3116e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 08:40:13 -0500 Subject: [PATCH 090/211] [skip ci] Add jagged border display even when discussion is collapsed --- app/assets/stylesheets/pages/diff.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 9b56ca3db844..9993e6ae326b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -754,7 +754,6 @@ } .discussion-notes.collapsed { - &+ .discussion-notes::before, .diff-notes-collapse, .note, .discussion-reply-holder, { @@ -768,6 +767,11 @@ .discussion-notes { min-height: 30px; + + &:first-child { + // First child does not have the jagged borders + min-height: 20px; + } } .discussion-body .image .frame { -- 2.22.0 From e6c2b3633d340075f41b934e85fefd7d77514b22 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 08:40:38 -0500 Subject: [PATCH 091/211] [skip ci] Add white background to collapsed image diff discussions --- app/assets/stylesheets/pages/diff.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 9993e6ae326b..57595fd1abdc 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -754,6 +754,8 @@ } .discussion-notes.collapsed { + background-color: $white-light; + .diff-notes-collapse, .note, .discussion-reply-holder, { -- 2.22.0 From 81bba43bc15a4fad6b0e3305d9bb386288c4b31b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 09:46:20 -0500 Subject: [PATCH 092/211] [skip ci] Use -normal for jagged border color --- app/assets/stylesheets/framework/variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 1445272c65ab..a69020bf44b5 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -314,7 +314,7 @@ $diff-image-info-color: grey; $diff-swipe-border: #999; $diff-view-modes-color: grey; $diff-view-modes-border: #c1c1c1; -$diff-jagged-border-gradient: darken( $white-normal, .8% ); +$diff-jagged-border-gradient: $white-normal; /* * Fonts -- 2.22.0 From a069ee6dcfec02d325fe6550c603086d15fddb24 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 10:32:04 -0500 Subject: [PATCH 093/211] [skip ci] Use comment icon for image comment badge in discussions tab --- .../image_diff/discussion_badges.js | 4 +- .../image_diff/helpers/badge_helper.js | 38 +++++++++++++++---- .../javascripts/image_diff/helpers/index.js | 1 + app/assets/stylesheets/framework/buttons.scss | 22 +++++++++++ app/assets/stylesheets/pages/diff.scss | 8 +++- app/assets/stylesheets/pages/notes.scss | 19 +--------- 6 files changed, 61 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/image_diff/discussion_badges.js b/app/assets/javascripts/image_diff/discussion_badges.js index 516e0885e8c6..3e94c26d21fa 100644 --- a/app/assets/javascripts/image_diff/discussion_badges.js +++ b/app/assets/javascripts/image_diff/discussion_badges.js @@ -15,13 +15,11 @@ export function create(imageEl) { const diffFile = imageFrameEl.closest('.diff-file'); const firstNote = diffFile.querySelector('.discussion-notes .note'); - ImageDiffHelper.addImageBadge(imageFrameEl, { + ImageDiffHelper.addImageCommentBadge(imageFrameEl, { coordinate: { x: meta.x, y: meta.y, }, - // Discussion badges are empty badge dots - badgeText: ' ', noteId: firstNote.id, }); } diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 851c188ab569..17143eefe3e9 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -1,20 +1,42 @@ -export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { - const { x, y } = coordinate; +export function createImageBadge(noteId, classNames = []) { const buttonEl = document.createElement('button'); - buttonEl.classList.add('btn-transparent', 'badge', 'js-image-badge'); + const classList = classNames.concat(['btn-transparent', 'js-image-badge']); + buttonEl.classList.add(...classList); buttonEl.setAttribute('type', 'button'); buttonEl.dataset.noteId = noteId; - buttonEl.innerText = badgeText; - containerEl.appendChild(buttonEl); + return buttonEl; +} +export function centerButtonToCoordinate(buttonEl, coordinate) { // TODO: We should use math to calculate the width so that we don't - // have to do a reflow here but we can leave this here for now + // have to do a reflow after adding to the DOM + // but we can leave this here for now // Set button center to be the center of the clicked position + const { x, y } = coordinate; const { width, height } = buttonEl.getBoundingClientRect(); - buttonEl.style.left = `${x - (width * 0.5)}px`; - buttonEl.style.top = `${y - (height * 0.5)}px`; + buttonEl.style.left = `${x - (width * 0.5)}px`; // eslint-disable-line no-param-reassign + buttonEl.style.top = `${y - (height * 0.5)}px`; // eslint-disable-line no-param-reassign +} + +export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { + const buttonEl = createImageBadge(noteId, ['badge']); + buttonEl.innerText = badgeText; + + containerEl.appendChild(buttonEl); + centerButtonToCoordinate(buttonEl, coordinate); +} + +export function addImageCommentBadge(containerEl, { coordinate, noteId }) { + const buttonEl = createImageBadge(noteId, ['image-comment-badge', 'inverted']); + const iconEl = document.createElement('i'); + iconEl.classList.add('fa', 'fa-comment-o'); + iconEl.setAttribute('aria-label', 'comment'); + + buttonEl.appendChild(iconEl); + containerEl.appendChild(buttonEl); + centerButtonToCoordinate(buttonEl, coordinate); } export function imageBadgeOnClick(event) { diff --git a/app/assets/javascripts/image_diff/helpers/index.js b/app/assets/javascripts/image_diff/helpers/index.js index 7c4852ba3fe3..ba7f1a4d4257 100644 --- a/app/assets/javascripts/image_diff/helpers/index.js +++ b/app/assets/javascripts/image_diff/helpers/index.js @@ -10,6 +10,7 @@ export default { commentIndicatorOnClick: commentIndicatorHelper.commentIndicatorOnClick, addImageBadge: badgeHelper.addImageBadge, + addImageCommentBadge: badgeHelper.addImageCommentBadge, imageBadgeOnClick: badgeHelper.imageBadgeOnClick, addAvatarBadge: badgeHelper.addAvatarBadge, diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index d178bc17462e..04e61c724df3 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,3 +1,25 @@ +@mixin btn-comment { + border-radius: 50%; + background: $white-light; + padding: 1px 5px; + font-size: 12px; + color: $blue-500; + width: 23px; + height: 23px; + border: 1px solid $blue-500; + + &:hover, + &.inverted { + background: $blue-500; + border-color: $blue-600; + color: $white-light; + } + + &:active { + outline: 0; + } +} + @mixin btn-default { border-radius: 3px; font-size: $gl-font-size; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 57595fd1abdc..dac643abf7df 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -719,8 +719,7 @@ .frame.click-to-comment .badge, .image-diff-avatar-link .badge, -.notes > .badge, -.discussion-body .frame .badge { +.notes > .badge { position: absolute; background-color: $blue-400; color: $white-light; @@ -730,6 +729,11 @@ min-height: $gl-padding; } +.discussion-body .frame .image-comment-badge { + @include btn-comment; + position: absolute; +} + .image-diff-avatar-link { position: relative; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 46d31e41ada3..819ac448de42 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -650,29 +650,12 @@ ul.notes { } .add-diff-note { + @include btn-comment; opacity: 0; margin-top: -2px; - border-radius: 50%; - background: $white-light; - padding: 1px 5px; - font-size: 12px; - color: $blue-500; margin-left: -55px; position: absolute; z-index: 10; - width: 23px; - height: 23px; - border: 1px solid $blue-500; - - &:hover { - background: $blue-500; - border-color: $blue-600; - color: $white-light; - } - - &:active { - outline: 0; - } } .discussion-body, -- 2.22.0 From 6cbd2ff34416166f082767ac30a57ecf77e61384 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 29 Sep 2017 11:34:43 -0400 Subject: [PATCH 094/211] Round single-digit number badges. --- app/assets/stylesheets/pages/diff.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index f9a51c20f793..b4a3a770e12d 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -725,6 +725,8 @@ border-width: 1px; border-style: solid; min-height: $gl-padding; + padding: 5px 8px; + border-radius: 12px; } .image-diff-avatar-link { -- 2.22.0 From 9d68089726767982a1ec17e8034a88717e8881bb Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 29 Sep 2017 11:40:14 -0400 Subject: [PATCH 095/211] Remove focus styles on badges. --- app/assets/stylesheets/pages/diff.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 46b699261bc8..b5f7d092a614 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -729,6 +729,10 @@ min-height: $gl-padding; padding: 5px 8px; border-radius: 12px; + + &:focus { + outline: none; + } } .discussion-body .frame .image-comment-badge { -- 2.22.0 From 318a006d8bd7f1e280abdfb85f33cfca2780b826 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 10:54:09 -0500 Subject: [PATCH 096/211] [skip ci] Add image comment badge to avatar on discussion tab --- app/assets/stylesheets/pages/diff.scss | 5 +++-- app/views/discussions/_diff_with_notes.html.haml | 2 +- app/views/discussions/_notes.html.haml | 3 ++- app/views/shared/notes/_note.html.haml | 11 +++++++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b5f7d092a614..e7c7b3a82c9e 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -735,7 +735,7 @@ } } -.discussion-body .frame .image-comment-badge { +.image-comment-badge { @include btn-comment; position: absolute; } @@ -743,7 +743,8 @@ .image-diff-avatar-link { position: relative; - .badge { + .badge, + .image-comment-badge { top: 25px; right: 8px; } diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 505232bd42f0..e3f87b37da9a 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -23,4 +23,4 @@ .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) .note-container - = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false } + = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 662d96e4be7a..371ee8a7e505 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,6 +1,7 @@ - collapsed_class = 'collapsed' if discussion.resolved? - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] - show_toggle = local_assigns.fetch(:show_toggle, true) +- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) .discussion-notes{ class: collapsed_class } %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } @@ -9,7 +10,7 @@ = custom_icon('collapse_icon') %button.btn-transparent.badge.js-diff-notes-toggle{ type: 'button' } = badge_counter - = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter } + = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter, show_image_comment_badge: show_image_comment_badge } .flash-container diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 41fcd4107340..7be51156a654 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -1,7 +1,9 @@ - return unless note.author - return if note.cross_reference_not_visible_for?(current_user) +- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) - note_editable = note_editable?(note) + %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: { author_id: note.author.id, @@ -17,8 +19,13 @@ - if note.is_a?(DiffNote) && note.on_image? - counter = badge_counter if local_assigns[:badge_counter] - badge_class = "hidden" if @fresh_discussion || counter.nil? - %span.badge{ class: badge_class } - = counter + - if show_image_comment_badge && note_counter == 0 + -# Only show this for the first comment in the discussion + %span.image-comment-badge.inverted + = icon('comment-o') + - else + %span.badge{ class: badge_class } + = counter .timeline-content .note-header .note-header-info -- 2.22.0 From f340cb1b42ae71acb1cd255dcc70e46db9eb1454 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 29 Sep 2017 11:58:21 -0400 Subject: [PATCH 097/211] Prevent undefined prop access in error handler. --- app/assets/javascripts/notes.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 441a6b990051..0cc45603eda5 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1566,7 +1566,11 @@ export default class Notes { detail: e, }); - $form.closest('.diff-file')[0].dispatchEvent(blurEvent); + const closestDiffFile = $form.closest('.diff-file'); + + if (closestDiffFile.length) { + closestDiffFile[0].dispatchEvent(blurEvent); + } if (hasQuickActions) { $notesContainer.find(`#${systemNoteUniqueId}`).remove(); -- 2.22.0 From bc44fa56b82fa11774ddc8c81775a03442fbe50b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 29 Sep 2017 12:02:38 -0400 Subject: [PATCH 098/211] Prevent n+1 errors with tmp workaround. --- lib/gitlab/gitaly_client.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index cbd9ff406de1..a6b2c0cec5dc 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -144,6 +144,8 @@ module Gitlab # Ensures that Gitaly is not being abuse through n+1 misuse etc def self.enforce_gitaly_request_limits(call_site) + # FIXME: THIS NEEDS TO BE REMOVED. It's a workaround until we get rid of the n+1 issue + return # Only count limits in request-response environments (not sidekiq for example) return unless RequestStore.active? -- 2.22.0 From 01a1e5ee0fa48fcbc8c19036c0772e5db97b72ef Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 29 Sep 2017 12:11:57 -0400 Subject: [PATCH 099/211] Remove unneccessary comment. --- app/views/projects/diffs/viewers/_image.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 22bd630b57d7..fcb5d2f6a20f 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -10,7 +10,6 @@ - if diff_file.new_file? || diff_file.deleted_file? .image.js-single-image{ data: diff_view_data } %span.wrap - -# TODO: Determine if we should conslidate js-add-image-diff-note-button and click-to-comment .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) %p.image-info= number_to_human_size(blob.size) -- 2.22.0 From 28cea17887893bfeb721a304db3ff54fbda1dc4b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 29 Sep 2017 16:26:46 -0300 Subject: [PATCH 100/211] Update position equals method --- lib/gitlab/diff/formatters/base_formatter.rb | 12 ++++++++---- lib/gitlab/diff/formatters/image_formatter.rb | 5 +++++ lib/gitlab/diff/formatters/text_formatter.rb | 5 +++++ lib/gitlab/diff/position.rb | 3 +-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index fef1c759676e..6f7b6aaa669c 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -36,10 +36,6 @@ module Gitlab [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || "")] end - def complete? - raise NotImplementedError - end - def to_h { base_sha: base_sha, @@ -50,6 +46,14 @@ module Gitlab position_type: position_type } end + + def ==(other) + raise NotImplementedError + end + + def complete? + raise NotImplementedError + end end end end diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index 1cc653f7d2db..ff8052a1e215 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -27,6 +27,11 @@ module Gitlab def to_h super.merge(width: width, height: height, x_axis: x_axis, y_axis: y_axis) end + + def ==(other) + x_axis == other.x_axis && + y_axis == other.y_axis + end end end end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index 43770831178e..955a24ae6033 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -33,6 +33,11 @@ module Gitlab 'old' end end + + def ==(other) + new_line == other.new_line && + old_line == other.old_line + end end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index 3400e0c71890..ec1f33729f5b 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -42,8 +42,7 @@ module Gitlab other.diff_refs == diff_refs && other.old_path == old_path && other.new_path == new_path && - other.old_line == old_line && - other.new_line == new_line + other.formatter == formatter end def inspect -- 2.22.0 From bc1a21702c95a85e1a7be2dff5991bc7823dd308 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 11:10:39 -0500 Subject: [PATCH 101/211] [skip ci] Increase min height of discussion notes to accomodate height increase in badge --- app/assets/stylesheets/pages/diff.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index e7c7b3a82c9e..271d205e5897 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -779,11 +779,11 @@ } .discussion-notes { - min-height: 30px; + min-height: 35px; &:first-child { // First child does not have the jagged borders - min-height: 20px; + min-height: 25px; } } -- 2.22.0 From 242ad12639037f2e2f4afa73ae016457bb2edba8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 14:48:06 -0500 Subject: [PATCH 102/211] [skip ci] Fix issue where resolved image diff discussions in the discussion tab would not appear when expanded --- app/views/discussions/_diff_with_notes.html.haml | 2 +- app/views/discussions/_notes.html.haml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index e3f87b37da9a..942bd050b708 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -23,4 +23,4 @@ .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) .note-container - = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true } + = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 371ee8a7e505..be2ff2bde71a 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,4 +1,5 @@ -- collapsed_class = 'collapsed' if discussion.resolved? +- disable_collapse = local_assigns.fetch(:disable_collapse, false) +- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] - show_toggle = local_assigns.fetch(:show_toggle, true) - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) -- 2.22.0 From 96cf4716861320b5fddbbb4ed8295b4311d0fa28 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 29 Sep 2017 17:47:13 -0300 Subject: [PATCH 103/211] Fix image discussion polling --- lib/gitlab/diff/formatters/image_formatter.rb | 1 + lib/gitlab/diff/formatters/text_formatter.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index ff8052a1e215..fcb651fea63d 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -29,6 +29,7 @@ module Gitlab end def ==(other) + self.class == other.class && x_axis == other.x_axis && y_axis == other.y_axis end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index 955a24ae6033..aacc213b1471 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -35,6 +35,7 @@ module Gitlab end def ==(other) + self.class == other.class && new_line == other.new_line && old_line == other.old_line end -- 2.22.0 From 6ab51d74848a5bd229b36aa47884d8edc5c2c22b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 29 Sep 2017 18:33:29 -0300 Subject: [PATCH 104/211] Add on image flag when notes are polled --- app/controllers/concerns/notes_actions.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 0d09258f4ba5..6d7bf7442035 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -96,7 +96,8 @@ module NotesActions id: note.id, discussion_id: note.discussion_id(noteable), html: note_html(note), - note: note.note + note: note.note, + on_image: note.try(:on_image?) ) discussion = note.to_discussion(noteable) -- 2.22.0 From bc5e9d5cf2d218c70d2196896079a115860e449b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 21:58:49 -0500 Subject: [PATCH 105/211] [skip ci] Center image diff discussion badge alignment --- app/assets/stylesheets/pages/diff.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b040718b56c1..0d9fc079c422 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -535,14 +535,15 @@ } .diff-notes-collapse { - width: 19px; - height: 19px; + width: 24px; + height: 24px; + border-radius: 50%; padding: 0; transition: transform .1s ease-out; z-index: 100; svg { - vertical-align: text-top; + vertical-align: middle; } path { @@ -746,12 +747,12 @@ .note-container .diff-notes-collapse { position: absolute; - left: -10px; + left: -13px; } .notes > .badge { display: none; - left: -11px; + left: -13px; } .discussion-notes.collapsed { -- 2.22.0 From a1c2cad3617eb5240a4b986de030e2f8d05bf2e2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 29 Sep 2017 22:25:35 -0500 Subject: [PATCH 106/211] [skip ci] add image diff commenting to commit diff page --- app/assets/javascripts/image_diff/image_diff.js | 9 +++++++-- app/assets/javascripts/lib/utils/image_utility.js | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/image_utility.js diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index e7fca8103b8c..47bd01a6fad1 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,5 +1,6 @@ import ImageDiffHelper from './helpers/index'; import ImageBadge from './image_badge'; +import { isImageLoaded } from '../lib/utils/image_utility'; export default class ImageDiff { constructor(el, canCreateNote = false) { @@ -32,8 +33,12 @@ export default class ImageDiff { this.removeBadgeWrapper = this.removeBadge.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); - // Render badges after the image diff is loaded - this.getImageEl().addEventListener('load', this.renderBadgesWrapper); + // Render badges + if (isImageLoaded(this.getImageEl())) { + this.renderBadgesWrapper(); + } else { + this.getImageEl().addEventListener('load', this.renderBadgesWrapper); + } // jquery makes the event delegation here much simpler this.$noteContainer.on('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); diff --git a/app/assets/javascripts/lib/utils/image_utility.js b/app/assets/javascripts/lib/utils/image_utility.js new file mode 100644 index 000000000000..f906e595cc77 --- /dev/null +++ b/app/assets/javascripts/lib/utils/image_utility.js @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export const isImageLoaded = element => element.complete && element.naturalHeight !== 0; -- 2.22.0 From 54f48fcd533f046fc81c2c4d791066e81e86d512 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 21:38:30 -0500 Subject: [PATCH 107/211] [skip ci] Add unbind events to replaced imaeg diff --- .../javascripts/image_diff/image_diff.js | 8 ++++--- .../image_diff/replaced_image_diff.js | 22 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 47bd01a6fad1..a2b34323be10 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -55,6 +55,11 @@ export default class ImageDiff { } unbindEvents() { + this.imageEl.removeEventListener('load', this.renderBadgesWrapper); + this.$noteContainer.off('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); + $(this.el).off('click', '.comment-indicator', ImageDiffHelper.commentIndicatorOnClick); + $(this.el).off('click', '.js-image-badge', ImageDiffHelper.imageBadgeOnClick); + if (this.canCreateNote) { this.el.removeEventListener('click.imageDiff', this.clickWrapper); this.el.removeEventListener('blur.imageDiff', this.blurWrapper); @@ -62,9 +67,6 @@ export default class ImageDiff { this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); } - - this.imageEl.removeEventListener('load', this.renderBadgesWrapper); - this.$noteContainer.off('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); } click(event) { diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 025ee66aae08..b53b596b2a26 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -10,6 +10,13 @@ export default class ReplacedImageDiff extends ImageDiff { [viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .frame.added'), }; + const viewModesEl = this.el.querySelector('.view-modes-menu'); + this.viewModesEls = { + [viewTypes.TWO_UP]: viewModesEl.querySelector('.two-up'), + [viewTypes.SWIPE]: viewModesEl.querySelector('.swipe'), + [viewTypes.ONION_SKIN]: viewModesEl.querySelector('.onion-skin'), + }; + this.currentView = defaultViewType; this.generateImageEls(); this.bindEvents(); @@ -31,10 +38,17 @@ export default class ReplacedImageDiff extends ImageDiff { this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE); this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN); - const viewModesEl = this.el.querySelector('.view-modes-menu'); - viewModesEl.querySelector('.two-up').addEventListener('click', this.changeToViewTwoUp); - viewModesEl.querySelector('.swipe').addEventListener('click', this.changeToViewSwipe); - viewModesEl.querySelector('.onion-skin').addEventListener('click', this.changeToViewOnionSkin); + this.viewModesEls[viewTypes.TWO_UP].addEventListener('click', this.changeToViewTwoUp); + this.viewModesEls[viewTypes.SWIPE].addEventListener('click', this.changeToViewSwipe); + this.viewModesEls[viewTypes.ONION_SKIN].addEventListener('click', this.changeToViewOnionSkin); + } + + unbindEvents() { + super.unbindEvents(); + + this.viewModesEls[viewTypes.TWO_UP].removeEventListener('click', this.changeToViewTwoUp); + this.viewModesEls[viewTypes.SWIPE].removeEventListener('click', this.changeToViewSwipe); + this.viewModesEls[viewTypes.ONION_SKIN].removeEventListener('click', this.changeToViewOnionSkin); } getImageEl() { -- 2.22.0 From f467ed739bfd26eaf4d7013ee47f7ceb7239a2a2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 21:49:51 -0500 Subject: [PATCH 108/211] [skip ci] ImageDiffHelper => imageDiffHelper --- .../image_diff/discussion_badges.js | 9 ++--- .../javascripts/image_diff/image_badge.js | 4 +- .../javascripts/image_diff/image_diff.js | 38 +++++++++---------- .../image_diff/replaced_image_diff.js | 8 ++-- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/image_diff/discussion_badges.js b/app/assets/javascripts/image_diff/discussion_badges.js index 3e94c26d21fa..a5111e1be0b3 100644 --- a/app/assets/javascripts/image_diff/discussion_badges.js +++ b/app/assets/javascripts/image_diff/discussion_badges.js @@ -1,11 +1,10 @@ -import ImageDiffHelper from './helpers/index'; -// TODO: Rename all instances of imagediffhelper to start with lower case +import imageDiffHelper from './helpers/index'; export function create(imageEl) { const imageFrameEl = imageEl.closest('.frame'); const { x_axis, y_axis, width, height } = JSON.parse(imageFrameEl.dataset.position); - const meta = ImageDiffHelper.resizeCoordinatesToImageElement(imageEl, { + const meta = imageDiffHelper.resizeCoordinatesToImageElement(imageEl, { x: x_axis, y: y_axis, width, @@ -15,7 +14,7 @@ export function create(imageEl) { const diffFile = imageFrameEl.closest('.diff-file'); const firstNote = diffFile.querySelector('.discussion-notes .note'); - ImageDiffHelper.addImageCommentBadge(imageFrameEl, { + imageDiffHelper.addImageCommentBadge(imageFrameEl, { coordinate: { x: meta.x, y: meta.y, @@ -28,5 +27,5 @@ export function init() { const imageEls = document.querySelectorAll('.timeline-content .diff-file .image .frame img'); [].forEach.call(imageEls, imageEl => imageEl.addEventListener('load', create.bind(null, imageEl))); - $('.timeline-content .diff-file').on('click', '.js-image-badge', ImageDiffHelper.imageBadgeOnClick); + $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); } diff --git a/app/assets/javascripts/image_diff/image_badge.js b/app/assets/javascripts/image_diff/image_badge.js index 18fecd3fbb51..51a8cda98d73 100644 --- a/app/assets/javascripts/image_diff/image_badge.js +++ b/app/assets/javascripts/image_diff/image_badge.js @@ -1,4 +1,4 @@ -import ImageDiffHelper from './helpers/index'; +import imageDiffHelper from './helpers/index'; const defaultMeta = { x: 0, @@ -17,7 +17,7 @@ export default class ImageBadge { this.discussionId = discussionId; if (options.imageEl && !options.browser) { - this.browser = ImageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual); + this.browser = imageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual); } } } diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index a2b34323be10..5c5948f07a09 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -1,4 +1,4 @@ -import ImageDiffHelper from './helpers/index'; +import imageDiffHelper from './helpers/index'; import ImageBadge from './image_badge'; import { isImageLoaded } from '../lib/utils/image_utility'; @@ -29,7 +29,7 @@ export default class ImageDiff { this.clickWrapper = this.click.bind(this); this.blurWrapper = this.blur.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); - this.addAvatarBadgeWrapper = ImageDiffHelper.addAvatarBadge.bind(null, this.el); + this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); this.removeBadgeWrapper = this.removeBadge.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); @@ -41,9 +41,9 @@ export default class ImageDiff { } // jquery makes the event delegation here much simpler - this.$noteContainer.on('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); - $(this.el).on('click', '.comment-indicator', ImageDiffHelper.commentIndicatorOnClick); - $(this.el).on('click', '.js-image-badge', ImageDiffHelper.imageBadgeOnClick); + this.$noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); + $(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); + $(this.el).on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); @@ -56,9 +56,9 @@ export default class ImageDiff { unbindEvents() { this.imageEl.removeEventListener('load', this.renderBadgesWrapper); - this.$noteContainer.off('click', '.js-diff-notes-toggle', ImageDiffHelper.toggleCollapsed); - $(this.el).off('click', '.comment-indicator', ImageDiffHelper.commentIndicatorOnClick); - $(this.el).off('click', '.js-image-badge', ImageDiffHelper.imageBadgeOnClick); + this.$noteContainer.off('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); + $(this.el).off('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); + $(this.el).off('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); if (this.canCreateNote) { this.el.removeEventListener('click.imageDiff', this.clickWrapper); @@ -71,27 +71,27 @@ export default class ImageDiff { click(event) { const customEvent = event.detail; - const selection = ImageDiffHelper.getTargetSelection(customEvent); + const selection = imageDiffHelper.getTargetSelection(customEvent); const el = customEvent.currentTarget; - ImageDiffHelper.setPositionDataAttribute(el, selection.actual); - ImageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); + imageDiffHelper.setPositionDataAttribute(el, selection.actual); + imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); } blur() { - return ImageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); } renderBadges() { const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); [].forEach.call(discussionsEls, (discussionEl, index) => { - const imageBadge = ImageDiffHelper + const imageBadge = imageDiffHelper .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); this.imageBadges.push(imageBadge); - ImageDiffHelper.addImageBadge(this.getImageFrameEl(), { + imageDiffHelper.addImageBadge(this.getImageFrameEl(), { coordinate: imageBadge.browser, badgeText: index + 1, noteId: imageBadge.noteId, @@ -116,13 +116,13 @@ export default class ImageDiff { this.imageBadges.push(imageBadge); - ImageDiffHelper.addImageBadge(this.getImageFrameEl(), { + imageDiffHelper.addImageBadge(this.getImageFrameEl(), { coordinate: imageBadge.browser, badgeText, noteId, }); - ImageDiffHelper.addAvatarBadge(this.el, { + imageDiffHelper.addAvatarBadge(this.el, { detail: { noteId, badgeNumber: badgeText, @@ -130,7 +130,7 @@ export default class ImageDiff { }); const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); - ImageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); + imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); } removeBadge(event) { @@ -148,8 +148,8 @@ export default class ImageDiff { imageBadgeEls[index].innerText = updatedBadgeNumber; - ImageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); - ImageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); + imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); + imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); } }); } diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index b53b596b2a26..8f7c00655309 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -1,4 +1,4 @@ -import ImageDiffHelper from './helpers/index'; +import imageDiffHelper from './helpers/index'; import { viewTypes, isValidViewType } from './view_types'; import ImageDiff from './image_diff'; @@ -64,7 +64,7 @@ export default class ReplacedImageDiff extends ImageDiff { return; } - const indicator = ImageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + const indicator = imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); this.currentView = newView; @@ -84,14 +84,14 @@ export default class ReplacedImageDiff extends ImageDiff { // Re-render indicator in new view if (indicator.removed) { - const normalizedIndicator = ImageDiffHelper + const normalizedIndicator = imageDiffHelper .resizeCoordinatesToImageElement(this.getImageEl(), { x: indicator.x, y: indicator.y, width: indicator.image.width, height: indicator.image.height, }); - ImageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); + imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); } }, 250); } -- 2.22.0 From 5008a4b5e5e3120454acd1a83802dcf03712bae4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:01:52 -0500 Subject: [PATCH 109/211] [skip ci] rearrange variables so that they are only inside the scope where it's needed --- app/views/shared/notes/_note.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 7be51156a654..73494f8464b4 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -17,13 +17,13 @@ %a.image-diff-avatar-link{ href: user_path(note.author) } = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' - if note.is_a?(DiffNote) && note.on_image? - - counter = badge_counter if local_assigns[:badge_counter] - - badge_class = "hidden" if @fresh_discussion || counter.nil? - if show_image_comment_badge && note_counter == 0 -# Only show this for the first comment in the discussion %span.image-comment-badge.inverted = icon('comment-o') - else + - counter = badge_counter if local_assigns[:badge_counter] + - badge_class = "hidden" if @fresh_discussion || counter.nil? %span.badge{ class: badge_class } = counter .timeline-content -- 2.22.0 From 85950793104346aa3462fbd9578c888a5648b9fe Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:08:56 -0500 Subject: [PATCH 110/211] [skip ci] Add comment for text diff discussions --- app/views/discussions/_diff_discussion.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index 7221ccbf347c..1506465b9e4a 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -1,6 +1,7 @@ - if local_assigns[:on_image] = render partial: "discussions/notes", collection: discussions, as: :discussion - else + -# Text diff discussions - expanded = local_assigns.fetch(:expanded, true) %tr.notes_holder{ class: ('hide' unless expanded) } %td.notes_line{ colspan: 2 } -- 2.22.0 From fd3c8a0928ca0f584935d0086ceefd81dddfb84b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:10:08 -0500 Subject: [PATCH 111/211] [skip ci] Use explicit if statement --- app/views/discussions/_diff_with_notes.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 942bd050b708..d05f42cbe1fb 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -2,7 +2,7 @@ - blob = discussion.blob - discussions = { discussion.original_line_code => [discussion] } -.diff-file.file-holder{ class: (diff_file.text? ? 'text-file' : '') } +.diff-file.file-holder{ class: 'text-file' if diff_file.text? } .js-file-title.file-title.file-title-flex-parent .file-header-content = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false -- 2.22.0 From 83a85c10af2af174244e2f21f58fc52e619d2e8b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:11:39 -0500 Subject: [PATCH 112/211] [skip ci] Add comment for first note --- app/views/discussions/_notes.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index be2ff2bde71a..23d6c1d2fcc8 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -5,6 +5,8 @@ - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) .discussion-notes{ class: collapsed_class } + -# Save the first note position data so that we have a reference and can go + -# to the first note position when we click on a badge diff discussion %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - if discussion.try(:on_image?) && show_toggle %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' } -- 2.22.0 From 4474cdcfd4eee6287b235320cf710462967e6435 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:15:46 -0500 Subject: [PATCH 113/211] [skip ci] Add comment as to why we inherit the css for this class --- app/assets/stylesheets/pages/diff.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 0d9fc079c422..647f9e014543 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -673,6 +673,8 @@ .notes.active { .diff-file .note-container > .new-note, .note-container .discussion-notes { + // Override our margin and border (set for diff tab) + // when user is on the discussion tab for MR margin-left: inherit; border-left: inherit; } -- 2.22.0 From a926dbc058c03f48bdbdfa8d9817942ba3952d39 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:18:13 -0500 Subject: [PATCH 114/211] [skip ci] create variable in haml file --- app/views/discussions/_diff_with_notes.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index d05f42cbe1fb..4d17d366aa48 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -1,8 +1,9 @@ - diff_file = discussion.diff_file - blob = discussion.blob - discussions = { discussion.original_line_code => [discussion] } +- diff_file_class = 'text-file' if diff_file.text? -.diff-file.file-holder{ class: 'text-file' if diff_file.text? } +.diff-file.file-holder{ class: diff_file_class } .js-file-title.file-title.file-title-flex-parent .file-header-content = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false -- 2.22.0 From f508633cb0ee763a5795c1efa20dd5b4db5a6290 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 1 Oct 2017 22:39:49 -0500 Subject: [PATCH 115/211] [skip ci] Fix bug whereby clicking on the image for a new discussion would open up an existing discussion form --- app/assets/javascripts/notes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 0cc45603eda5..3a089ef7ef4b 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -965,12 +965,13 @@ export default class Notes { // Setup comment form let newForm; const $noteContainer = $link.closest('.diff-viewer').find('.note-container'); + const $form = $noteContainer.find('> .discussion-form'); - if ($noteContainer.find('form').length === 0) { + if ($form.length === 0) { newForm = this.cleanForm(this.formClone.clone()); newForm.appendTo($noteContainer); } else { - newForm = $noteContainer.find('form'); + newForm = $form; } this.setupDiscussionNoteForm($link, newForm); -- 2.22.0 From 714f554a97d337e2e02c908d59ca147d0abcccd4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 09:57:19 -0500 Subject: [PATCH 116/211] [skip ci] Fix bug where sometimes image file wouldn't initialize the two-up view --- app/assets/javascripts/commit/image_file.js | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 4763985c802e..c9a39ce4d6a8 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,4 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */ +import { isImageLoaded } from '../lib/utils/image_utility'; + (function() { gl.ImageFile = (function() { var prepareFrames; @@ -17,16 +19,28 @@ // Load two-up view after images are loaded // so that we can display the correct width and height information - const images = $('.two-up.view img', _this.file); - let loadedCount = 0; + const $images = $('.two-up.view img', _this.file); + const deleted = $images[0]; + const added = $images[1]; - images.on('load', () => { - loadedCount += 1; + const deletedLoaded = isImageLoaded(deleted) ? 1 : 0; + const addedLoaded = isImageLoaded(added) ? 1 : 0; - if (loadedCount === images.length) { - _this.initView('two-up'); - } - }); + const leftToLoad = $images.length - deletedLoaded - addedLoaded; + + if (leftToLoad === 0) { + _this.initView('two-up'); + } else { + let loadedCount = 0; + + $images.on('load', () => { + loadedCount += 1; + + if (loadedCount === leftToLoad) { + _this.initView('two-up'); + } + }); + } }); }; })(this)); -- 2.22.0 From 00325a4f9121d2f2fed86148d913666ab6277edb Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 09:59:43 -0500 Subject: [PATCH 117/211] [skip ci] Load replaced image diff view in discussion tab --- .../image_diff/discussion_badges.js | 37 ++++------ .../image_diff/helpers/utils_helper.js | 10 +-- .../javascripts/image_diff/image_diff.js | 25 +++++-- app/assets/stylesheets/pages/diff.scss | 3 +- .../discussions/_diff_with_notes.html.haml | 67 +++++++++++++++++-- .../projects/diffs/viewers/_image.html.haml | 2 +- 6 files changed, 104 insertions(+), 40 deletions(-) diff --git a/app/assets/javascripts/image_diff/discussion_badges.js b/app/assets/javascripts/image_diff/discussion_badges.js index a5111e1be0b3..554ad67eeb78 100644 --- a/app/assets/javascripts/image_diff/discussion_badges.js +++ b/app/assets/javascripts/image_diff/discussion_badges.js @@ -1,31 +1,20 @@ +// TODO Rename this file to explain what it does better import imageDiffHelper from './helpers/index'; -export function create(imageEl) { - const imageFrameEl = imageEl.closest('.frame'); - const { x_axis, y_axis, width, height } = JSON.parse(imageFrameEl.dataset.position); - - const meta = imageDiffHelper.resizeCoordinatesToImageElement(imageEl, { - x: x_axis, - y: y_axis, - width, - height, - }); - - const diffFile = imageFrameEl.closest('.diff-file'); - const firstNote = diffFile.querySelector('.discussion-notes .note'); +export function init() { + const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); + [].forEach.call(diffFileEls, (diffFileEl) => { + // ImageFile needs to be invoked before initImageDiff so that badges + // can mount to the correct location + new gl.ImageFile(diffFileEl); // eslint-disable-line no-new - imageDiffHelper.addImageCommentBadge(imageFrameEl, { - coordinate: { - x: meta.x, - y: meta.y, - }, - noteId: firstNote.id, + // Always pass can-create-note as false because user cannot place new badge markers + // on discussion tab + const canCreateNote = false; + const renderCommentBadge = true; + imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge); }); -} - -export function init() { - const imageEls = document.querySelectorAll('.timeline-content .diff-file .image .frame img'); - [].forEach.call(imageEls, imageEl => imageEl.addEventListener('load', create.bind(null, imageEl))); + // TODO: Related to image diff.js line 50 $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); } diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index 9aabc00ba4af..c1fdd9bc72f8 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -75,12 +75,12 @@ export function getTargetSelection(event) { }; } -export function initImageDiff(file, canCreateNote) { - if (file.querySelector('.diff-viewer .js-single-image')) { - const imageDiff = new ImageDiff(file, canCreateNote); +export function initImageDiff(file, canCreateNote, renderCommentBadge) { + if (file.querySelector('.diff-file .js-single-image')) { + const imageDiff = new ImageDiff(file, canCreateNote, renderCommentBadge); imageDiff.init(); - } else if (file.querySelector('.diff-viewer .js-replaced-image')) { - const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote); + } else if (file.querySelector('.diff-file .js-replaced-image')) { + const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote, renderCommentBadge); replacedImageDiff.init(); } } diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 5c5948f07a09..1ce6eab9a6a5 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -3,15 +3,17 @@ import ImageBadge from './image_badge'; import { isImageLoaded } from '../lib/utils/image_utility'; export default class ImageDiff { - constructor(el, canCreateNote = false) { + // TODO: Refactor options into options object + constructor(el, canCreateNote = false, renderCommentBadge = false) { this.el = el; this.canCreateNote = canCreateNote; + this.renderCommentBadge = renderCommentBadge; this.$noteContainer = $(this.el.querySelector('.note-container')); this.imageBadges = []; } init() { - this.imageFrameEl = this.el.querySelector('.diff-viewer .image .frame'); + this.imageFrameEl = this.el.querySelector('.diff-file .image .frame'); this.imageEl = this.imageFrameEl.querySelector('img'); this.bindEvents(); @@ -43,6 +45,10 @@ export default class ImageDiff { // jquery makes the event delegation here much simpler this.$noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); $(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); + + // TODO: Investigate why jQuery event delegation + // isn't properly adjusting the view to the location hash + // This works properly when it does not use jquery event delegation $(this.el).on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); if (this.canCreateNote) { @@ -91,11 +97,20 @@ export default class ImageDiff { this.imageBadges.push(imageBadge); - imageDiffHelper.addImageBadge(this.getImageFrameEl(), { + const options = { coordinate: imageBadge.browser, - badgeText: index + 1, noteId: imageBadge.noteId, - }); + }; + + if (this.renderCommentBadge) { + imageDiffHelper.addImageCommentBadge(this.getImageFrameEl(), options); + } else { + const numberBadgeOptions = Object.assign(options, { + badgeText: index + 1, + }); + + imageDiffHelper.addImageBadge(this.getImageFrameEl(), numberBadgeOptions); + } }); } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 647f9e014543..1c3ebccb38e9 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -285,6 +285,7 @@ .drag-track { display: block; position: absolute; + top: 0; left: 12px; height: 10px; width: 276px; @@ -710,7 +711,7 @@ } } -.frame.click-to-comment .badge, +.frame .badge, .image-diff-avatar-link .badge, .notes > .badge { position: absolute; diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 4d17d366aa48..671bea23c57f 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -1,7 +1,7 @@ - diff_file = discussion.diff_file - blob = discussion.blob - discussions = { discussion.original_line_code => [discussion] } -- diff_file_class = 'text-file' if diff_file.text? +- diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file' .diff-file.file-holder{ class: diff_file_class } .js-file-title.file-title.file-title-flex-parent @@ -19,9 +19,68 @@ discussion_expanded: true, plain: true } - else + - blob = diff_file.blob + - old_blob = diff_file.old_blob - blob_raw_path = diff_file_blob_raw_path(diff_file) - .image - .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } - = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false) + - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) + - if diff_file.new_file? || diff_file.deleted_file? + .image.js-single-image + .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } + = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) + - else + .image.js-replaced-image + .two-up.view + %span.wrap + .frame.deleted + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) + %p.image-info.hide + %span.meta-filesize= number_to_human_size(old_blob.size) + | + %b W: + %span.meta-width + | + %b H: + %span.meta-height + %span.wrap + .frame.added{ data: { position: discussion.position.to_json } } + = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) + %p.image-info.hide + %span.meta-filesize= number_to_human_size(blob.size) + | + %b W: + %span.meta-width + | + %b H: + %span.meta-height + + .swipe.view.hide + .swipe-frame + .frame.deleted + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) + .swipe-wrap + .frame.added{ data: { position: discussion.position.to_json } } + = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) + %span.swipe-bar + %span.top-handle + %span.bottom-handle + + .onion-skin.view.hide + .onion-skin-frame + .frame.deleted + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) + .frame.added{ data: { position: discussion.position.to_json } } + = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) + .controls + .transparent + .drag-track + .dragger{ :style => "left: 0px;" } + .opaque + + .view-modes.hide + %ul.view-modes-menu + %li.two-up{ data: { mode: 'two-up' } } 2-up + %li.swipe{ data: { mode: 'swipe' } } Swipe + %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin + .note-container = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index fcb5d2f6a20f..36b6acff515c 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -20,7 +20,7 @@ .two-up.view %span.wrap .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path) + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) %p.image-info.hide %span.meta-filesize= number_to_human_size(old_blob.size) | -- 2.22.0 From a66e1c2070e21295ae9308db02320350b732086a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 10:02:09 -0500 Subject: [PATCH 118/211] [skip ci] Add todo --- app/views/discussions/_diff_with_notes.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 671bea23c57f..7c05bc884fcf 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -28,6 +28,7 @@ .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) - else + -# TODO: Create shared partial with `app/views/projects/diffs/viewers/_image.html.haml` .image.js-replaced-image .two-up.view %span.wrap -- 2.22.0 From e77f5df3f683a2d7400c16d12424cbe07ca7836f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 10:21:40 -0500 Subject: [PATCH 119/211] [skip ci] Fix bug whereby comment box for discussion would still stay visible even after the collapse button is selected --- .../javascripts/image_diff/helpers/dom_helper.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index dd5a9e403e5d..bde9289cff2c 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -29,5 +29,16 @@ export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { export function toggleCollapsed(event) { const toggleButtonEl = event.currentTarget; - toggleButtonEl.closest('.discussion-notes').classList.toggle('collapsed'); + const discussionNotesEl = toggleButtonEl.closest('.discussion-notes'); + const formEl = discussionNotesEl.querySelector('.discussion-form'); + + discussionNotesEl.classList.toggle('collapsed'); + const isCollapsed = discussionNotesEl.classList.contains('collapsed'); + + // Override the inline display style set in notes.js + if (formEl && isCollapsed) { + formEl.style.display = 'none'; + } else if (formEl && !isCollapsed) { + formEl.style.display = 'block'; + } } -- 2.22.0 From a14a215942f8869700f2ffb879bb74af69777ab2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 11:26:44 -0500 Subject: [PATCH 120/211] [skip ci] Initialize ImageFile in image diff only --- app/assets/javascripts/commit.js | 12 ------------ app/assets/javascripts/commit/file.js | 14 -------------- app/assets/javascripts/diff.js | 2 -- app/assets/javascripts/dispatcher.js | 2 -- .../image_diff/discussion_badges.js | 18 +++++++----------- .../image_diff/helpers/utils_helper.js | 4 ++++ app/assets/javascripts/main.js | 3 --- app/assets/javascripts/single_file_diff.js | 2 -- 8 files changed, 11 insertions(+), 46 deletions(-) delete mode 100644 app/assets/javascripts/commit.js delete mode 100644 app/assets/javascripts/commit/file.js diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js deleted file mode 100644 index 5f637524e300..000000000000 --- a/app/assets/javascripts/commit.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife */ -/* global CommitFile */ - -window.Commit = (function() { - function Commit() { - $('.files .diff-file').each(function() { - return new CommitFile(this); - }); - } - - return Commit; -})(); diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js deleted file mode 100644 index ee087c978dd8..000000000000 --- a/app/assets/javascripts/commit/file.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new */ -/* global ImageFile */ - -(function() { - this.CommitFile = (function() { - function CommitFile(file) { - if ($('.image', file).length) { - new gl.ImageFile(file); - } - } - - return CommitFile; - })(); -}).call(window); diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 36849123f633..abddd005b3d9 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -20,8 +20,6 @@ class Diff { FilesCommentButton.init($diffFile); - $diffFile.each((index, file) => new gl.ImageFile(file)); - const canCreateNote = $diffFile.first().closest('.files').is('[data-can-create-note]'); $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote)); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index bbaa4e4d91ef..9359af64abb8 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -7,7 +7,6 @@ /* global IssuableForm */ /* global LabelsSelect */ /* global MilestoneSelect */ -/* global Commit */ /* global CommitsList */ /* global NewBranchForm */ /* global NotificationsForm */ @@ -316,7 +315,6 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; new gl.Activities(); break; case 'projects:commit:show': - new Commit(); new gl.Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/image_diff/discussion_badges.js b/app/assets/javascripts/image_diff/discussion_badges.js index 554ad67eeb78..2c890f36c0ca 100644 --- a/app/assets/javascripts/image_diff/discussion_badges.js +++ b/app/assets/javascripts/image_diff/discussion_badges.js @@ -2,18 +2,14 @@ import imageDiffHelper from './helpers/index'; export function init() { - const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); - [].forEach.call(diffFileEls, (diffFileEl) => { - // ImageFile needs to be invoked before initImageDiff so that badges - // can mount to the correct location - new gl.ImageFile(diffFileEl); // eslint-disable-line no-new + // Always pass can-create-note as false because user cannot place new badge markers + // on discussion tab + const canCreateNote = false; + const renderCommentBadge = true; - // Always pass can-create-note as false because user cannot place new badge markers - // on discussion tab - const canCreateNote = false; - const renderCommentBadge = true; - imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge); - }); + const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); + [].forEach.call(diffFileEls, diffFileEl => + imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge)); // TODO: Related to image diff.js line 50 $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index c1fdd9bc72f8..4a8647b087d4 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -76,6 +76,10 @@ export function getTargetSelection(event) { } export function initImageDiff(file, canCreateNote, renderCommentBadge) { + // ImageFile needs to be invoked before initImageDiff so that badges + // can mount to the correct location + new gl.ImageFile(file); // eslint-disable-line no-new + if (file.querySelector('.diff-file .js-single-image')) { const imageDiff = new ImageDiff(file, canCreateNote, renderCommentBadge); imageDiff.init(); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 24abc5c5c9e7..b253fbf4ea87 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -35,8 +35,6 @@ import './shortcuts_network'; import './templates/issuable_template_selector'; import './templates/issuable_template_selectors'; -// commit -import './commit/file'; import './commit/image_file'; // lib/utils @@ -71,7 +69,6 @@ import './build'; import './build_artifacts'; import './build_variables'; import './ci_lint_editor'; -import './commit'; import './commits'; import './compare'; import './compare_autocomplete'; diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 2ec16a044126..3f811c59cb93 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -82,8 +82,6 @@ export default class SingleFileDiff { imageDiffHelper.initImageDiff($file[0], canCreateNote); if (cb) cb(); - - return new gl.ImageFile($file); }; })(this)); } -- 2.22.0 From 7db7a76f6215e741e5486cbd9ab79da0a120807c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 11:53:59 -0500 Subject: [PATCH 121/211] [skip ci] Add jagged border before comment form --- app/assets/stylesheets/pages/diff.scss | 3 +- app/assets/stylesheets/pages/note_form.scss | 5 ++- app/views/shared/notes/_form.html.haml | 35 +++++++++++---------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1c3ebccb38e9..d81bbbea8ec2 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -644,7 +644,8 @@ } // double jagged line divider - .discussion-notes + .discussion-notes::before { + .discussion-notes + .discussion-notes::before, + .discussion-notes + .discussion-form::before { content: ''; position: relative; display: block; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index be4db5976894..b73f88bc298c 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -137,10 +137,13 @@ } .discussion-form { - padding: $gl-padding-top $gl-padding $gl-padding; background-color: $white-light; } +.discussion-form-container { + padding: $gl-padding-top $gl-padding $gl-padding; +} + .discussion-notes .disabled-comment { padding: 6px 0; } diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index 725bf9165923..71c0d740bc80 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -24,20 +24,21 @@ -# DiffNote = f.hidden_field :position - = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do - = render 'projects/zen', f: f, - attr: :note, - classes: 'note-textarea js-note-text', - placeholder: "Write a comment or drag your files here...", - supports_quick_actions: supports_quick_actions, - supports_autocomplete: supports_autocomplete - = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions - .error-alert - - .note-form-actions.clearfix - = render partial: 'shared/notes/comment_button' - - = yield(:note_actions) - - %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } - Discard draft + .discussion-form-container + = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do + = render 'projects/zen', f: f, + attr: :note, + classes: 'note-textarea js-note-text', + placeholder: "Write a comment or drag your files here...", + supports_quick_actions: supports_quick_actions, + supports_autocomplete: supports_autocomplete + = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions + .error-alert + + .note-form-actions.clearfix + = render partial: 'shared/notes/comment_button' + + = yield(:note_actions) + + %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } + Discard draft -- 2.22.0 From 9732e6b75ff9aff502de44a536a63764036a4f8a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 12:04:51 -0500 Subject: [PATCH 122/211] [skip ci] Fix bug where cancelling a discussion comment form would dismiss the comment indicator --- app/assets/javascripts/notes.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 3a089ef7ef4b..3dafc1154ec1 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1071,14 +1071,20 @@ export default class Notes { cancelDiscussionForm(e) { e.preventDefault(); const $form = $(e.target).closest('.js-discussion-note-form'); - const $diffFile = $form.closest('.diff-file'); + const $discussionNote = $(e.target).closest('.discussion-notes'); - if ($diffFile.length > 0) { - const blurEvent = new CustomEvent('blur.imageDiff', { - detail: e, - }); + if ($discussionNote.length === 0) { + // Only send blur event when the discussion form + // is not part of a discussion note + const $diffFile = $form.closest('.diff-file'); - $diffFile[0].dispatchEvent(blurEvent); + if ($diffFile.length > 0) { + const blurEvent = new CustomEvent('blur.imageDiff', { + detail: e, + }); + + $diffFile[0].dispatchEvent(blurEvent); + } } return this.removeDiscussionNoteForm($form); -- 2.22.0 From 3561293c15fbbab3afd54ab57880f7a5a3c61e0b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 12:16:20 -0500 Subject: [PATCH 123/211] [skip ci] Rename discussion_badges to init_discussion_tab --- .../{discussion_badges.js => init_discussion_tab.js} | 3 +-- app/assets/javascripts/merge_request_tabs.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) rename app/assets/javascripts/image_diff/{discussion_badges.js => init_discussion_tab.js} (88%) diff --git a/app/assets/javascripts/image_diff/discussion_badges.js b/app/assets/javascripts/image_diff/init_discussion_tab.js similarity index 88% rename from app/assets/javascripts/image_diff/discussion_badges.js rename to app/assets/javascripts/image_diff/init_discussion_tab.js index 2c890f36c0ca..7fb15e5e3a5e 100644 --- a/app/assets/javascripts/image_diff/discussion_badges.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -1,7 +1,6 @@ -// TODO Rename this file to explain what it does better import imageDiffHelper from './helpers/index'; -export function init() { +export default () => { // Always pass can-create-note as false because user cannot place new badge markers // on discussion tab const canCreateNote = false; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 012ce76ab933..c042b22d1fd8 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -13,7 +13,7 @@ import { isMetaClick, } from './lib/utils/common_utils'; -import * as discussionBadges from './image_diff/discussion_badges'; +import initDiscussionTab from './image_diff/init_discussion_tab'; /* eslint-disable max-len */ // MergeRequestTabs @@ -157,7 +157,7 @@ import * as discussionBadges from './image_diff/discussion_badges'; this.resetViewContainer(); this.destroyPipelinesView(); - discussionBadges.init(); + initDiscussionTab(); } if (this.setUrl) { this.setCurrentAction(action); -- 2.22.0 From 39a66b454c3f5addb0ef0cc5fdbca3a308484ff6 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 2 Oct 2017 13:16:42 -0400 Subject: [PATCH 124/211] Get avatar badge numbers rendering on async notes quick and dirty. --- app/assets/javascripts/notes.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 3a089ef7ef4b..1b005d4ef787 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -24,6 +24,7 @@ import './autosave'; import './dropzone_input'; import TaskList from './task_list'; import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; +import imageDiffHelper from './image_diff/helpers/index'; window.autosize = autosize; window.Dropzone = Dropzone; @@ -476,7 +477,17 @@ export default class Notes { if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) { gl.diffNotesCompileComponents(); + this.renderDiscussionAvatar(diffAvatarContainer, noteEntity); + + if (noteEntity.on_image) { + const noteEl = $(`.note-row-${noteEntity.id}:visible`); + + // get badge ID from previous sibling + const badgeId = noteEl.prev().find('.badge:not(".hidden")').text().trim(); + + imageDiffHelper.addAvatarBadge(noteEl.parents('.discussion-notes').get(0), { detail: { badgeNumber: badgeId, noteId: `note_${noteEntity.id}` } }); + } } gl.utils.localTimeAgo($('.js-timeago'), false); -- 2.22.0 From a2725eb8c8e9870a5f71a22fd91abb31b0e6e5dd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 2 Oct 2017 13:21:01 -0400 Subject: [PATCH 125/211] Add TODO to clean avatar badge async. --- app/assets/javascripts/notes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 86fc54c8645b..d00eed31506b 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -480,6 +480,7 @@ export default class Notes { this.renderDiscussionAvatar(diffAvatarContainer, noteEntity); + // TODO: Clean this up -- feels very hacky if (noteEntity.on_image) { const noteEl = $(`.note-row-${noteEntity.id}:visible`); -- 2.22.0 From 2b61cf8c1ff6ecb2a0e76fdb333ecba4aedb28da Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 12:26:31 -0500 Subject: [PATCH 126/211] [skip ci] Remove completed todo comments --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 4 ---- .../image_diff/helpers/comment_indicator_helper.js | 2 -- app/assets/javascripts/notes.js | 1 - 3 files changed, 7 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 17143eefe3e9..e810e79ed5b9 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -9,10 +9,6 @@ export function createImageBadge(noteId, classNames = []) { } export function centerButtonToCoordinate(buttonEl, coordinate) { - // TODO: We should use math to calculate the width so that we don't - // have to do a reflow after adding to the DOM - // but we can leave this here for now - // Set button center to be the center of the clicked position const { x, y } = coordinate; const { width, height } = buttonEl.getBoundingClientRect(); diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index e11eb1b829c1..0b5de1f5f9c6 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -57,8 +57,6 @@ export function commentIndicatorOnClick(event) { const buttonEl = event.currentTarget; const diffViewerEl = buttonEl.closest('.diff-viewer'); - // TODO: Focus on the new comment form. There is a bug where this just goes to the closest form - // TODO: Also looks like canceling this other comment form will dismiss the comment indicator const textareaEl = diffViewerEl.querySelector('.note-container form .note-textarea'); textareaEl.focus(); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index d00eed31506b..bfb766045935 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -815,7 +815,6 @@ export default class Notes { // check if this is the last note for this line if ($notes.find('.note').length === 0) { - // TODO: Replace TR check with an actual class name check var notesTr = $notes.closest('tr'); // "Discussions" tab -- 2.22.0 From 52bd03bcc23432a3e4258af079871b5ba18dc36b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 2 Oct 2017 13:31:42 -0400 Subject: [PATCH 127/211] Center diff notes collapse. --- app/assets/stylesheets/pages/diff.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index d81bbbea8ec2..ae4c7b128210 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -751,7 +751,7 @@ .note-container .diff-notes-collapse { position: absolute; - left: -13px; + left: -12px; } .notes > .badge { -- 2.22.0 From 902c8ad24c9750d3e40a185337c5c726b709fcba Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 12:32:34 -0500 Subject: [PATCH 128/211] [skip ci] Use options object for ImageDiff --- .../javascripts/image_diff/helpers/utils_helper.js | 9 +++++++-- app/assets/javascripts/image_diff/image_diff.js | 6 ++++-- app/assets/javascripts/image_diff/init_discussion_tab.js | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index 4a8647b087d4..1ca8fcaf5be1 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -76,15 +76,20 @@ export function getTargetSelection(event) { } export function initImageDiff(file, canCreateNote, renderCommentBadge) { + const options = { + canCreateNote, + renderCommentBadge, + }; + // ImageFile needs to be invoked before initImageDiff so that badges // can mount to the correct location new gl.ImageFile(file); // eslint-disable-line no-new if (file.querySelector('.diff-file .js-single-image')) { - const imageDiff = new ImageDiff(file, canCreateNote, renderCommentBadge); + const imageDiff = new ImageDiff(file, options); imageDiff.init(); } else if (file.querySelector('.diff-file .js-replaced-image')) { - const replacedImageDiff = new ReplacedImageDiff(file, canCreateNote, renderCommentBadge); + const replacedImageDiff = new ReplacedImageDiff(file, options); replacedImageDiff.init(); } } diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 1ce6eab9a6a5..524ed4985675 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -3,8 +3,10 @@ import ImageBadge from './image_badge'; import { isImageLoaded } from '../lib/utils/image_utility'; export default class ImageDiff { - // TODO: Refactor options into options object - constructor(el, canCreateNote = false, renderCommentBadge = false) { + constructor(el, { + canCreateNote = false, + renderCommentBadge = false, + }) { this.el = el; this.canCreateNote = canCreateNote; this.renderCommentBadge = renderCommentBadge; diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js index 7fb15e5e3a5e..132c669fb304 100644 --- a/app/assets/javascripts/image_diff/init_discussion_tab.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -12,4 +12,4 @@ export default () => { // TODO: Related to image diff.js line 50 $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); -} +}; -- 2.22.0 From 0b7adbc30778ddb52332533170d115b4e26ef1f8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 12:50:49 -0500 Subject: [PATCH 129/211] [skip ci] Fix bug whereby linked notes weren't being highlighted on discussion tab --- app/assets/stylesheets/framework/timeline.scss | 1 + app/assets/stylesheets/pages/diff.scss | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 2b69cad86fbd..f718ec4bcad5 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -29,6 +29,7 @@ border-color: $white-normal; color: $gl-text-color; border-bottom: 1px solid $border-white-light; + background: $white-light; .timeline-entry-inner { position: relative; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index ae4c7b128210..93e12bd8258a 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -639,10 +639,6 @@ background-color: $gray-light; border-top: 1px solid $white-normal; - .timeline-entry:not(.target) { - background: $white-light; - } - // double jagged line divider .discussion-notes + .discussion-notes::before, .discussion-notes + .discussion-form::before { -- 2.22.0 From 71624c62d61b5cced292126c84e1f9ab8ebdcbf8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 13:14:54 -0500 Subject: [PATCH 130/211] [skip ci] Add white border for comment badge --- app/assets/stylesheets/pages/diff.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 93e12bd8258a..2c8ea6c04135 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -729,6 +729,10 @@ .image-comment-badge { @include btn-comment; position: absolute; + + &.inverted { + border-color: $white-light; + } } .image-diff-avatar-link { -- 2.22.0 From 1f11d5f339b34b74aa7dd89dd5a74a4e905d32bd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 13:56:15 -0500 Subject: [PATCH 131/211] [skip ci] Fix issue where badge numbers wouldn't show up for new discussions --- app/assets/javascripts/notes.js | 34 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index bfb766045935..1b00fea582c0 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -446,24 +446,6 @@ export default class Notes { row.find(contentContainerClass + ' .content').append($notes.closest('.content').children()); } - - // Add image badge, avatar badge and toggle discussion badge for image diffs - const $diffFile = form.closest('.diff-file'); - if ($diffFile.length > 0) { - const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); - const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { - detail: { - x: x_axis, - y: y_axis, - width, - height, - noteId: $discussion.find('.notes .note').attr('id'), - discussionId: $discussion.find('.notes').data('discussionId'), - }, - }); - - $diffFile[0].dispatchEvent(addBadgeEvent); - } } // Init discussion on 'Discussion' page if it is merge request page const page = $('body').attr('data-page'); @@ -1559,6 +1541,22 @@ export default class Notes { $diffFile[0].dispatchEvent(addAvatarBadgeEvent); } + } else { + if ($diffFile.length > 0) { + const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); + const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { + detail: { + x: x_axis, + y: y_axis, + width, + height, + noteId: `note_${note.id}`, + discussionId: note.discussion_id, + }, + }); + + $diffFile[0].dispatchEvent(addBadgeEvent); + } } // append flash-container to the Notes list -- 2.22.0 From e41b38ec6327ecc45ba6d95acc5cf181dced0c94 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 14:00:30 -0500 Subject: [PATCH 132/211] [skip ci] disable image badge click for image diff --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 1 + app/assets/javascripts/image_diff/image_diff.js | 6 ------ app/assets/javascripts/image_diff/init_discussion_tab.js | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index e810e79ed5b9..a6045fc23b26 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -19,6 +19,7 @@ export function centerButtonToCoordinate(buttonEl, coordinate) { export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { const buttonEl = createImageBadge(noteId, ['badge']); buttonEl.innerText = badgeText; + buttonEl.setAttribute('disabled', true); containerEl.appendChild(buttonEl); centerButtonToCoordinate(buttonEl, coordinate); diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 524ed4985675..290032631ac5 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -48,11 +48,6 @@ export default class ImageDiff { this.$noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); $(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); - // TODO: Investigate why jQuery event delegation - // isn't properly adjusting the view to the location hash - // This works properly when it does not use jquery event delegation - $(this.el).on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); - if (this.canCreateNote) { this.el.addEventListener('click.imageDiff', this.clickWrapper); this.el.addEventListener('blur.imageDiff', this.blurWrapper); @@ -66,7 +61,6 @@ export default class ImageDiff { this.imageEl.removeEventListener('load', this.renderBadgesWrapper); this.$noteContainer.off('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); $(this.el).off('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); - $(this.el).off('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); if (this.canCreateNote) { this.el.removeEventListener('click.imageDiff', this.clickWrapper); diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js index 132c669fb304..e35819a767d2 100644 --- a/app/assets/javascripts/image_diff/init_discussion_tab.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -10,6 +10,5 @@ export default () => { [].forEach.call(diffFileEls, diffFileEl => imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge)); - // TODO: Related to image diff.js line 50 $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); }; -- 2.22.0 From ac854ca2218907347b402fbafc8a682066dbc219 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 14:02:28 -0500 Subject: [PATCH 133/211] [skip ci] Remove changes to line-code --- app/assets/javascripts/notes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 1b00fea582c0..0dce414efc9e 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -900,7 +900,7 @@ export default class Notes { form.find('#note_type').val(dataHolder.data('noteType')); // LegacyDiffNote - form.find('#note_line_code').val(dataHolder.attr('data-line-code')); + form.find('#note_line_code').val(dataHolder.data('lineCode')); // DiffNote form.find('#note_position').val(dataHolder.attr('data-position')); @@ -1542,6 +1542,7 @@ export default class Notes { $diffFile[0].dispatchEvent(addAvatarBadgeEvent); } } else { + // Add image badge, avatar badge and toggle discussion badge for new image diffs if ($diffFile.length > 0) { const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { -- 2.22.0 From 0a773cb54362ac9e28bc109ae5b66b86a36e2a78 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 15:16:36 -0500 Subject: [PATCH 134/211] [skip ci] Refactor image diff haml into partials --- .../discussions/_diff_with_notes.html.haml | 65 ++---------------- .../diffs/_image_diff_frame.html.haml | 5 ++ .../diffs/_replaced_image_diff.html.haml | 61 +++++++++++++++++ .../diffs/_single_image_diff.html.haml | 16 +++++ .../projects/diffs/viewers/_image.html.haml | 68 ++----------------- 5 files changed, 92 insertions(+), 123 deletions(-) create mode 100644 app/views/projects/diffs/_image_diff_frame.html.haml create mode 100644 app/views/projects/diffs/_replaced_image_diff.html.haml create mode 100644 app/views/projects/diffs/_single_image_diff.html.haml diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 7c05bc884fcf..b2b5055a3554 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -19,69 +19,12 @@ discussion_expanded: true, plain: true } - else - - blob = diff_file.blob - - old_blob = diff_file.old_blob - - blob_raw_path = diff_file_blob_raw_path(diff_file) - - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) + + - locals = { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } - if diff_file.new_file? || diff_file.deleted_file? - .image.js-single-image - .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: discussion.position.to_json } } - = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) + = render partial: "projects/diffs/single_image_diff", locals: locals - else - -# TODO: Create shared partial with `app/views/projects/diffs/viewers/_image.html.haml` - .image.js-replaced-image - .two-up.view - %span.wrap - .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - %p.image-info.hide - %span.meta-filesize= number_to_human_size(old_blob.size) - | - %b W: - %span.meta-width - | - %b H: - %span.meta-height - %span.wrap - .frame.added{ data: { position: discussion.position.to_json } } - = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) - %p.image-info.hide - %span.meta-filesize= number_to_human_size(blob.size) - | - %b W: - %span.meta-width - | - %b H: - %span.meta-height - - .swipe.view.hide - .swipe-frame - .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - .swipe-wrap - .frame.added{ data: { position: discussion.position.to_json } } - = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) - %span.swipe-bar - %span.top-handle - %span.bottom-handle - - .onion-skin.view.hide - .onion-skin-frame - .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - .frame.added{ data: { position: discussion.position.to_json } } - = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) - .controls - .transparent - .drag-track - .dragger{ :style => "left: 0px;" } - .opaque - - .view-modes.hide - %ul.view-modes-menu - %li.two-up{ data: { mode: 'two-up' } } 2-up - %li.swipe{ data: { mode: 'swipe' } } Swipe - %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin + = render partial: "projects/diffs/replaced_image_diff", locals: locals .note-container = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } diff --git a/app/views/projects/diffs/_image_diff_frame.html.haml b/app/views/projects/diffs/_image_diff_frame.html.haml new file mode 100644 index 000000000000..dae73e104606 --- /dev/null +++ b/app/views/projects/diffs/_image_diff_frame.html.haml @@ -0,0 +1,5 @@ +- class_name = local_assigns.fetch(:class_name, '') +- note_type = local_assigns.fetch(:note_type, '') + +.frame{ class: class_name, data: { position: position, note_type: note_type } } + = image_tag(image_path, alt: alt, draggable: false, lazy: false) diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml new file mode 100644 index 000000000000..bfe79042b3b5 --- /dev/null +++ b/app/views/projects/diffs/_replaced_image_diff.html.haml @@ -0,0 +1,61 @@ +- blob = diff_file.blob +- old_blob = diff_file.old_blob +- blob_raw_path = diff_file_blob_raw_path(diff_file) +- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) +- click_to_comment = local_assigns.fetch(:click_to_comment, true) +- diff_view_data = local_assigns.fetch(:diff_view_data, '') +- class_name = '' + +- if click_to_comment + - class_name = 'js-add-image-diff-note-button click-to-comment' + +.image.js-replaced-image{ data: diff_view_data } + .two-up.view + %span.wrap + .frame.deleted + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) + %p.image-info.hide + %span.meta-filesize= number_to_human_size(old_blob.size) + | + %b W: + %span.meta-width + | + %b H: + %span.meta-height + %span.wrap + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } + %p.image-info.hide + %span.meta-filesize= number_to_human_size(blob.size) + | + %b W: + %span.meta-width + | + %b H: + %span.meta-height + + .swipe.view.hide + .swipe-frame + .frame.deleted + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) + .swipe-wrap + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } + %span.swipe-bar + %span.top-handle + %span.bottom-handle + + .onion-skin.view.hide + .onion-skin-frame + .frame.deleted + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } + .controls + .transparent + .drag-track + .dragger{ :style => "left: 0px;" } + .opaque + +.view-modes.hide + %ul.view-modes-menu + %li.two-up{ data: { mode: 'two-up' } } 2-up + %li.swipe{ data: { mode: 'swipe' } } Swipe + %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml new file mode 100644 index 000000000000..827e1ffd0f8a --- /dev/null +++ b/app/views/projects/diffs/_single_image_diff.html.haml @@ -0,0 +1,16 @@ +- blob = diff_file.blob +- old_blob = diff_file.old_blob +- blob_raw_path = diff_file_blob_raw_path(diff_file) +- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) +- click_to_comment = local_assigns.fetch(:click_to_comment, true) +- diff_view_data = local_assigns.fetch(:diff_view_data, '') +- class_name = '' + +- if click_to_comment + - class_name = 'js-add-image-diff-note-button click-to-comment' + +.image.js-single-image{ data: diff_view_data } + %span.wrap + - single_class_name = diff_file.deleted_file? ? 'deleted' : 'added' + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path } + %p.image-info= number_to_human_size(blob.size) diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 36b6acff515c..17c031da672d 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -7,68 +7,12 @@ - image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) - discussions = @grouped_diff_discussions[diff_file.file_identifier] +- locals = { diff_file: diff_file, position: diff_file.position(image_point, "image").to_json, click_to_comment: true, diff_view_data: diff_view_data } + - if diff_file.new_file? || diff_file.deleted_file? - .image.js-single-image{ data: diff_view_data } - %span.wrap - .frame.js-add-image-diff-note-button.click-to-comment{ class: (diff_file.deleted_file? ? 'deleted' : 'added'), data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } - = image_tag(blob_raw_path, alt: diff_file.file_path, draggable: false, lazy: false) - %p.image-info= number_to_human_size(blob.size) - .note-container - = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion + = render partial: "projects/diffs/single_image_diff", locals: locals - else - .image.js-replaced-image{ data: diff_view_data } - .two-up.view - %span.wrap - .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - %p.image-info.hide - %span.meta-filesize= number_to_human_size(old_blob.size) - | - %b W: - %span.meta-width - | - %b H: - %span.meta-height - %span.wrap - .frame.added.js-add-image-diff-note-button.click-to-comment{ data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } - = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) - %p.image-info.hide - %span.meta-filesize= number_to_human_size(blob.size) - | - %b W: - %span.meta-width - | - %b H: - %span.meta-height - - .swipe.view.hide - .swipe-frame - .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - .swipe-wrap - .frame.added.js-add-image-diff-note-button.click-to-comment{ data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } - = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) - %span.swipe-bar - %span.top-handle - %span.bottom-handle - - .onion-skin.view.hide - .onion-skin-frame - .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - .frame.added.js-add-image-diff-note-button.click-to-comment{ data: { position: diff_file.position(image_point, "image").to_json, note_type: DiffNote.name } } - = image_tag(blob_raw_path, alt: diff_file.new_path, draggable: false, lazy: false) - .controls - .transparent - .drag-track - .dragger{ :style => "left: 0px;" } - .opaque - + = render partial: "projects/diffs/replaced_image_diff", locals: locals - .view-modes.hide - %ul.view-modes-menu - %li.two-up{ data: { mode: 'two-up' } } 2-up - %li.swipe{ data: { mode: 'swipe' } } Swipe - %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin - .note-container - = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion +.note-container + = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion -- 2.22.0 From f1de044e4462d9f870205a51ef0c679e5a9d496a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 15:23:26 -0500 Subject: [PATCH 135/211] [skip ci] Remove unused variables --- app/views/projects/diffs/viewers/_image.html.haml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 17c031da672d..e26e08a7ba7a 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -1,9 +1,4 @@ - diff_file = viewer.diff_file -- blob = diff_file.blob -- old_blob = diff_file.old_blob -- blob_raw_path = diff_file_blob_raw_path(diff_file) -- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) - - image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) - discussions = @grouped_diff_discussions[diff_file.file_identifier] -- 2.22.0 From 1aa11db9d30b0c425bbeb285596329ba49283671 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 15:33:29 -0500 Subject: [PATCH 136/211] [skip ci] cleanup scss --- app/assets/stylesheets/pages/diff.scss | 52 +++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 2c8ea6c04135..b372957330d6 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -656,10 +656,15 @@ background-size: 10px 10px; background-repeat: repeat; } -} -.files:not([data-can-create-note]) .frame { - cursor: auto; + .notes { + position: relative; + } + + .diff-notes-collapse { + position: absolute; + left: -12px; + } } .diff-file .note-container > .new-note, @@ -678,6 +683,10 @@ } } +.files:not([data-can-create-note]) .frame { + cursor: auto; +} + .frame.click-to-comment { position: relative; cursor: url(icon_image_comment.svg) @@ -745,34 +754,11 @@ } } -.note-container .notes { - position: relative; -} - -.note-container .diff-notes-collapse { - position: absolute; - left: -12px; -} - .notes > .badge { display: none; left: -13px; } -.discussion-notes.collapsed { - background-color: $white-light; - - .diff-notes-collapse, - .note, - .discussion-reply-holder, { - display: none; - } - - .notes > .badge { - display: block; - } -} - .discussion-notes { min-height: 35px; @@ -780,6 +766,20 @@ // First child does not have the jagged borders min-height: 25px; } + + &.collapsed { + background-color: $white-light; + + .diff-notes-collapse, + .note, + .discussion-reply-holder, { + display: none; + } + + .notes > .badge { + display: block; + } + } } .discussion-body .image .frame { -- 2.22.0 From 3cdf523edc9cd6471506409be6689daf127d06e8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 15:53:44 -0500 Subject: [PATCH 137/211] [skip ci] Fix pixel calculation offset for cursor image --- app/assets/stylesheets/framework/variables.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b41963fe551d..ab3908422fc9 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -704,4 +704,4 @@ $java: #70ad51; Image Commenting cursor */ $image-comment-cursor-left-offset: 12; -$image-comment-cursor-top-offset: 30; +$image-comment-cursor-top-offset: 28; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b372957330d6..886831e3f2a3 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -711,8 +711,7 @@ // Compensate for cursor offsets position: absolute; left: (-1px * $image-comment-cursor-left-offset); - // TODO: Fix pixel calculation due to empty white space in cursor image - top: (-1px * $image-comment-cursor-top-offset + 2px); + top: (-1px * $image-comment-cursor-top-offset); } } } -- 2.22.0 From 97a2f773d7d6e9a95d487877d62858be8cfdfef7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 16:12:17 -0500 Subject: [PATCH 138/211] [skip ci] Add changelog --- changelogs/unreleased/issue_35873.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/issue_35873.yml diff --git a/changelogs/unreleased/issue_35873.yml b/changelogs/unreleased/issue_35873.yml new file mode 100644 index 000000000000..65064b97e56b --- /dev/null +++ b/changelogs/unreleased/issue_35873.yml @@ -0,0 +1,5 @@ +--- +title: Commenting on image diffs +merge_request: 14061 +author: +type: added -- 2.22.0 From cf32a1a05890a1165bce7b574cc16c7df0a67798 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 16:36:37 -0500 Subject: [PATCH 139/211] [skip ci] remove todo --- app/views/shared/icons/_collapse_icon.svg | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/shared/icons/_collapse_icon.svg b/app/views/shared/icons/_collapse_icon.svg index 0b9c1429cf60..bd4b393cfaad 100644 --- a/app/views/shared/icons/_collapse_icon.svg +++ b/app/views/shared/icons/_collapse_icon.svg @@ -1,2 +1 @@ - -- 2.22.0 From ebe3a5e7e60af1551159ccc5eb2f1cf6f7524962 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 2 Oct 2017 16:47:09 -0500 Subject: [PATCH 140/211] [skip ci] cleanup avatarbadge for polling updates --- app/assets/javascripts/notes.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 0dce414efc9e..388f3416c4cc 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -462,14 +462,17 @@ export default class Notes { this.renderDiscussionAvatar(diffAvatarContainer, noteEntity); - // TODO: Clean this up -- feels very hacky if (noteEntity.on_image) { - const noteEl = $(`.note-row-${noteEntity.id}:visible`); - - // get badge ID from previous sibling - const badgeId = noteEl.prev().find('.badge:not(".hidden")').text().trim(); - - imageDiffHelper.addAvatarBadge(noteEl.parents('.discussion-notes').get(0), { detail: { badgeNumber: badgeId, noteId: `note_${noteEntity.id}` } }); + const $noteEl = $(`.note-row-${noteEntity.id}:visible`); + const $notesContainer = $noteEl.closest('.notes'); + const badgeNumber = parseInt($notesContainer.find('.badge.js-diff-notes-toggle').text().trim(), 10); + + imageDiffHelper.addAvatarBadge($notesContainer[0], { + detail: { + badgeNumber, + noteId: `note_${noteEntity.id}` + } + }); } } -- 2.22.0 From 442a1e81eb270a42d49355e65035d2345565e201 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 2 Oct 2017 21:08:12 -0300 Subject: [PATCH 141/211] Fix specs --- app/controllers/concerns/notes_actions.rb | 2 + app/models/legacy_diff_discussion.rb | 8 ++++ app/models/note.rb | 8 +++- .../projects/diffs/viewers/_image.html.haml | 4 +- lib/gitlab/diff/formatters/image_formatter.rb | 4 +- lib/gitlab/diff/formatters/text_formatter.rb | 4 +- lib/gitlab/diff/position.rb | 6 ++- spec/lib/gitlab/diff/position_spec.rb | 47 ++++++++++--------- spec/lib/gitlab/diff/position_tracer_spec.rb | 40 ++++++++++------ spec/models/diff_note_spec.rb | 4 +- .../update_diff_position_service_spec.rb | 6 +-- 11 files changed, 82 insertions(+), 51 deletions(-) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 6d7bf7442035..f5693e688c56 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -121,6 +121,8 @@ module NotesActions end def diff_discussion_html(discussion) + return unless discussion.diff_discussion? + if params[:view] == 'parallel' template = "discussions/_parallel_diff_discussion" locals = diff --git a/app/models/legacy_diff_discussion.rb b/app/models/legacy_diff_discussion.rb index 3c1d34db5fa1..80fc6304fd4c 100644 --- a/app/models/legacy_diff_discussion.rb +++ b/app/models/legacy_diff_discussion.rb @@ -17,6 +17,14 @@ class LegacyDiffDiscussion < Discussion true end + def on_image? + false + end + + def on_text? + true + end + def active?(*args) return @active if @active.present? diff --git a/app/models/note.rb b/app/models/note.rb index 046557c728f3..3562105c1d68 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -141,8 +141,12 @@ class Note < ActiveRecord::Base groups = {} diff_notes.fresh.discussions.each do |discussion| - group_key = discussion.line_code_in_diffs(diff_refs) - group_key ||= discussion.file_identifier + group_key = + if discussion.on_image? + discussion.file_identifier + else + discussion.line_code_in_diffs(diff_refs) + end if group_key discussions = groups[group_key] ||= [] diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index e26e08a7ba7a..8e992721715e 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -1,6 +1,6 @@ - diff_file = viewer.diff_file - image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) -- discussions = @grouped_diff_discussions[diff_file.file_identifier] +- discussions = @grouped_diff_discussions[diff_file.file_identifier] if @grouped_diff_discussions - locals = { diff_file: diff_file, position: diff_file.position(image_point, "image").to_json, click_to_comment: true, diff_view_data: diff_view_data } @@ -10,4 +10,4 @@ = render partial: "projects/diffs/replaced_image_diff", locals: locals .note-container - = render partial: "discussions/notes", collection: @grouped_diff_discussions[diff_file.file_identifier], as: :discussion + = render partial: "discussions/notes", collection: discussions, as: :discussion diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index fcb651fea63d..43d6c1dbfc1d 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -30,8 +30,8 @@ module Gitlab def ==(other) self.class == other.class && - x_axis == other.x_axis && - y_axis == other.y_axis + x_axis == other.x_axis && + y_axis == other.y_axis end end end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index aacc213b1471..89ad866ca324 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -36,8 +36,8 @@ module Gitlab def ==(other) self.class == other.class && - new_line == other.new_line && - old_line == other.old_line + new_line == other.new_line && + old_line == other.old_line end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index ec1f33729f5b..5e6ba2b30d3d 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -45,8 +45,12 @@ module Gitlab other.formatter == formatter end + def to_h + formatter.to_h + end + def inspect - %(#<#{self.class}:#{object_id} #{formatter.to_h}>) + %(#<#{self.class}:#{object_id} #{to_h}>) end def complete? diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 7798736a4dc4..d78362cbec51 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -33,14 +33,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.new_line).to eq(subject.formatter.new_line) expect(diff_line.text).to eq("+ Created with Sketch.") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 0) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -76,14 +76,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.new_line).to eq(subject.formatter.new_line) expect(diff_line.text).to eq("+ vars = {") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 15) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 15) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -116,15 +116,15 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.unchanged?).to be true - expect(diff_line.old_line).to eq(subject.old_line) - expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.old_line).to eq(subject.formatter.old_line) + expect(diff_line.new_line).to eq(subject.formatter.new_line) expect(diff_line.text).to eq(" unless File.directory?(path)") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, subject.formatter.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -157,14 +157,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.old_line).to eq(subject.formatter.old_line) expect(diff_line.text).to eq("- options = { chdir: path }") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.formatter.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -201,14 +201,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.new_line).to eq(subject.formatter.new_line) expect(diff_line.text).to eq("+ new CommitFile(@)") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 5) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 5) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -241,15 +241,15 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.unchanged?).to be true - expect(diff_line.old_line).to eq(subject.old_line) - expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.old_line).to eq(subject.formatter.old_line) + expect(diff_line.new_line).to eq(subject.formatter.new_line) expect(diff_line.text).to eq(" $('.files .diff-file').each ->") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, subject.formatter.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -282,14 +282,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.old_line).to eq(subject.formatter.old_line) expect(diff_line.text).to eq("- new CommitFile(this)") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.formatter.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -325,14 +325,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.old_line).to eq(subject.formatter.old_line) expect(diff_line.text).to eq("-Copyright (c) 2014 gitlabhq") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.formatter.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -367,14 +367,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.new_line).to eq(subject.formatter.new_line) expect(diff_line.text).to eq("+testme") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 0) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -415,14 +415,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.old_line).to eq(subject.formatter.old_line) expect(diff_line.text).to eq("- puts 'bar'") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.formatter.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -476,7 +476,8 @@ describe Gitlab::Diff::Position do new_line: 14, base_sha: nil, head_sha: nil, - start_sha: nil + start_sha: nil, + position_type: "text" } end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 4fa30d8df8b0..87c78cfe147f 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -71,6 +71,10 @@ describe Gitlab::Diff::PositionTracer do Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) end + def text_position_attrs + [:old_line, :new_line] + end + def position(attrs = {}) attrs.reverse_merge!( diff_refs: old_diff_refs @@ -91,7 +95,11 @@ describe Gitlab::Diff::PositionTracer do expect(new_position.diff_refs).to eq(new_diff_refs) attrs.each do |attr, value| - expect(new_position.send(attr)).to eq(value) + if text_position_attrs.include?(attr) + expect(new_position.formatter.send(attr)).to eq(value) + else + expect(new_position.send(attr)).to eq(value) + end end end end @@ -110,7 +118,11 @@ describe Gitlab::Diff::PositionTracer do expect(change_position.diff_refs).to eq(change_diff_refs) attrs.each do |attr, value| - expect(change_position.send(attr)).to eq(value) + if text_position_attrs.include?(attr) + expect(change_position.formatter.send(attr)).to eq(value) + else + expect(change_position.send(attr)).to eq(value) + end end end end @@ -365,7 +377,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -389,7 +401,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -492,7 +504,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -517,7 +529,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -649,8 +661,8 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( old_path: file_name, new_path: new_file_name, - old_line: old_position.new_line, - new_line: old_position.new_line + old_line: old_position.formatter.new_line, + new_line: old_position.formatter.new_line ) end end @@ -1000,7 +1012,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -1025,7 +1037,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -1132,7 +1144,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -1157,7 +1169,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -1239,7 +1251,7 @@ describe Gitlab::Diff::PositionTracer do old_path: old_position.old_path, new_path: old_position.new_path, old_line: nil, - new_line: old_position.new_line + new_line: old_position.formatter.new_line ) end end @@ -1350,7 +1362,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( old_path: old_position.old_path, new_path: old_position.new_path, - old_line: old_position.old_line, + old_line: old_position.formatter.old_line, new_line: nil ) end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 4aa9ec789a3a..dd94b78ec00f 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -98,14 +98,14 @@ describe DiffNote do diff_line = subject.diff_line expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(position.new_line) + expect(diff_line.new_line).to eq(position.formatter.new_line) expect(diff_line.text).to eq("+ vars = {") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(position.file_path, position.new_line, 15) + line_code = Gitlab::Diff::LineCode.generate(position.file_path, position.formatter.new_line, 15) expect(subject.line_code).to eq(line_code) end diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb index 82b156f5ebeb..2b84206318fd 100644 --- a/spec/services/discussions/update_diff_position_service_spec.rb +++ b/spec/services/discussions/update_diff_position_service_spec.rb @@ -164,8 +164,8 @@ describe Discussions::UpdateDiffPositionService do change_position = discussion.change_position expect(change_position.start_sha).to eq(old_diff_refs.head_sha) expect(change_position.head_sha).to eq(new_diff_refs.head_sha) - expect(change_position.old_line).to eq(9) - expect(change_position.new_line).to be_nil + expect(change_position.formatter.old_line).to eq(9) + expect(change_position.formatter.new_line).to be_nil end it 'creates a system discussion' do @@ -184,7 +184,7 @@ describe Discussions::UpdateDiffPositionService do expect(discussion.original_position).to eq(old_position) expect(discussion.position).not_to eq(old_position) - expect(discussion.position.new_line).to eq(22) + expect(discussion.position.formatter.new_line).to eq(22) end context 'when the resolve_outdated_diff_discussions setting is set' do -- 2.22.0 From 546c34a902513702dc2f824f07cd4dc5f252ec59 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 07:14:59 -0500 Subject: [PATCH 142/211] [skip ci] Breakout classList with multiple parameters because of IE11 --- .../javascripts/image_diff/helpers/badge_helper.js | 5 +++-- .../image_diff/helpers/comment_indicator_helper.js | 3 ++- .../javascripts/image_diff/helpers/dom_helper.js | 12 ++++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index a6045fc23b26..e4dccb6833ed 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -1,7 +1,7 @@ export function createImageBadge(noteId, classNames = []) { const buttonEl = document.createElement('button'); const classList = classNames.concat(['btn-transparent', 'js-image-badge']); - buttonEl.classList.add(...classList); + classList.forEach(className => buttonEl.classList.add(className)); buttonEl.setAttribute('type', 'button'); buttonEl.dataset.noteId = noteId; @@ -28,7 +28,8 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) { const buttonEl = createImageBadge(noteId, ['image-comment-badge', 'inverted']); const iconEl = document.createElement('i'); - iconEl.classList.add('fa', 'fa-comment-o'); + iconEl.classList.add('fa'); + iconEl.classList.add('fa-comment-o'); iconEl.setAttribute('aria-label', 'comment'); buttonEl.appendChild(iconEl); diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index 0b5de1f5f9c6..8307b75fe493 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -1,7 +1,8 @@ export function addCommentIndicator(containerEl, coordinate) { const { x, y } = coordinate; const buttonEl = document.createElement('button'); - buttonEl.classList.add('btn-transparent', 'comment-indicator'); + buttonEl.classList.add('btn-transparent'); + buttonEl.classList.add('comment-indicator'); buttonEl.setAttribute('type', 'button'); buttonEl.style.left = `${x}px`; buttonEl.style.top = `${y}px`; diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index bde9289cff2c..48efbd439b79 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -31,14 +31,18 @@ export function toggleCollapsed(event) { const toggleButtonEl = event.currentTarget; const discussionNotesEl = toggleButtonEl.closest('.discussion-notes'); const formEl = discussionNotesEl.querySelector('.discussion-form'); - - discussionNotesEl.classList.toggle('collapsed'); const isCollapsed = discussionNotesEl.classList.contains('collapsed'); + if (isCollapsed) { + discussionNotesEl.classList.remove('collapsed'); + } else { + discussionNotesEl.classList.add('collapsed'); + } + // Override the inline display style set in notes.js - if (formEl && isCollapsed) { + if (formEl && !isCollapsed) { formEl.style.display = 'none'; - } else if (formEl && !isCollapsed) { + } else if (formEl && isCollapsed) { formEl.style.display = 'block'; } } -- 2.22.0 From 2a88025d80c118c082f653fff59a4c1dd7577b78 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 07:39:12 -0500 Subject: [PATCH 143/211] [skip ci] replace %b with %strong --- app/views/projects/diffs/_replaced_image_diff.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml index bfe79042b3b5..a2f1d84c798f 100644 --- a/app/views/projects/diffs/_replaced_image_diff.html.haml +++ b/app/views/projects/diffs/_replaced_image_diff.html.haml @@ -17,20 +17,20 @@ %p.image-info.hide %span.meta-filesize= number_to_human_size(old_blob.size) | - %b W: + %strong W: %span.meta-width | - %b H: + %strong H: %span.meta-height %span.wrap = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } %p.image-info.hide %span.meta-filesize= number_to_human_size(blob.size) | - %b W: + %strong W: %span.meta-width | - %b H: + %strong H: %span.meta-height .swipe.view.hide -- 2.22.0 From 2a79bf1f7e59cd5a9e76509266aa506938fc56bf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 07:42:12 -0500 Subject: [PATCH 144/211] [skip ci] Rename to gradient-color --- app/assets/stylesheets/framework/variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index ab3908422fc9..995e10ea08ac 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -316,7 +316,7 @@ $diff-image-info-color: grey; $diff-swipe-border: #999; $diff-view-modes-color: grey; $diff-view-modes-border: #c1c1c1; -$diff-jagged-border-gradient: $white-normal; +$diff-jagged-border-gradient-color: $white-normal; /* * Fonts -- 2.22.0 From 10ce93a7ad723044842b26d6197a79c25a59b777 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 07:43:21 -0500 Subject: [PATCH 145/211] [skip ci] Improve comment grammer --- app/assets/javascripts/image_diff/init_discussion_tab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js index e35819a767d2..c6fb118d1a29 100644 --- a/app/assets/javascripts/image_diff/init_discussion_tab.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -1,8 +1,8 @@ import imageDiffHelper from './helpers/index'; export default () => { - // Always pass can-create-note as false because user cannot place new badge markers - // on discussion tab + // Always pass can-create-note as false because a user + // cannot place new badge markers on discussion tab const canCreateNote = false; const renderCommentBadge = true; -- 2.22.0 From 7d8bc989d66b5317d8eaeb485b3f66f82161cb6f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 07:44:59 -0500 Subject: [PATCH 146/211] [skip ci] Fix diff-jagged-border-gradient-color reference --- app/assets/stylesheets/pages/diff.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 886831e3f2a3..07f93e602fdb 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -648,10 +648,10 @@ width: 100%; height: 10px; background-color: $white-light; - background-image: linear-gradient(45deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%), - linear-gradient(225deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%), - linear-gradient(135deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%), - linear-gradient(-45deg, transparent, transparent 73%, $diff-jagged-border-gradient 75%, $white-light 80%); + background-image: linear-gradient(45deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%), + linear-gradient(225deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%), + linear-gradient(135deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%), + linear-gradient(-45deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%); background-position: 5px 5px,0 5px,0 5px,5px 5px; background-size: 10px 10px; background-repeat: repeat; -- 2.22.0 From 01121ca2883919a3418a1aa87dc80ef233040c58 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 08:02:09 -0500 Subject: [PATCH 147/211] [skip ci] Use files.first instead --- app/assets/javascripts/diff.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index abddd005b3d9..b74af0d53472 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -20,7 +20,7 @@ class Diff { FilesCommentButton.init($diffFile); - const canCreateNote = $diffFile.first().closest('.files').is('[data-can-create-note]'); + const canCreateNote = $('.files').first().is('[data-can-create-note]'); $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote)); if (!isBound) { -- 2.22.0 From dd5018b58346a6d1a27e1973b88c50d408d94d95 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 08:26:21 -0500 Subject: [PATCH 148/211] [skip ci] Use full jquery to get the noteContainer --- app/assets/javascripts/image_diff/image_diff.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 290032631ac5..3611cbbdbb5a 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -10,7 +10,7 @@ export default class ImageDiff { this.el = el; this.canCreateNote = canCreateNote; this.renderCommentBadge = renderCommentBadge; - this.$noteContainer = $(this.el.querySelector('.note-container')); + this.$noteContainer = $('.note-container', this.el); this.imageBadges = []; } -- 2.22.0 From c651a6e6145e524cead68e0b3083a111528409de Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 08:27:30 -0500 Subject: [PATCH 149/211] [skip ci] Use renderBadges instead of renderBadgesWrapper --- app/assets/javascripts/image_diff/image_diff.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 3611cbbdbb5a..d091665b4443 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -39,7 +39,7 @@ export default class ImageDiff { // Render badges if (isImageLoaded(this.getImageEl())) { - this.renderBadgesWrapper(); + this.renderBadges(); } else { this.getImageEl().addEventListener('load', this.renderBadgesWrapper); } -- 2.22.0 From ef7a88090af3d9eab1f63bceaf88033b341895ce Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 08:45:15 -0500 Subject: [PATCH 150/211] [skip ci] Use spread syntax instead of forEach.call --- app/assets/javascripts/image_diff/image_diff.js | 2 +- app/assets/javascripts/image_diff/init_discussion_tab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index d091665b4443..20573a6251b5 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -87,7 +87,7 @@ export default class ImageDiff { renderBadges() { const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); - [].forEach.call(discussionsEls, (discussionEl, index) => { + [...discussionsEls].forEach((discussionEl, index) => { const imageBadge = imageDiffHelper .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js index c6fb118d1a29..5ad87394d6eb 100644 --- a/app/assets/javascripts/image_diff/init_discussion_tab.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -7,7 +7,7 @@ export default () => { const renderCommentBadge = true; const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); - [].forEach.call(diffFileEls, diffFileEl => + [...diffFileEls].forEach(diffFileEl => imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge)); $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); -- 2.22.0 From 5b933eba09ac97656a5bd5f4c5959b8d1be2c525 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 08:56:17 -0500 Subject: [PATCH 151/211] [skip ci] Add js-image-frame as a direct reference --- app/assets/javascripts/image_diff/image_diff.js | 2 +- app/assets/javascripts/image_diff/replaced_image_diff.js | 6 +++--- app/views/projects/diffs/_replaced_image_diff.html.haml | 6 +++--- app/views/projects/diffs/_single_image_diff.html.haml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 20573a6251b5..a961d27cae74 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -15,7 +15,7 @@ export default class ImageDiff { } init() { - this.imageFrameEl = this.el.querySelector('.diff-file .image .frame'); + this.imageFrameEl = this.el.querySelector('.diff-file .js-image-frame'); this.imageEl = this.imageFrameEl.querySelector('img'); this.bindEvents(); diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 8f7c00655309..343e03c1f4d8 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -5,9 +5,9 @@ import ImageDiff from './image_diff'; export default class ReplacedImageDiff extends ImageDiff { init(defaultViewType = viewTypes.TWO_UP) { this.imageFrameEls = { - [viewTypes.TWO_UP]: this.el.querySelector('.two-up .frame.added'), - [viewTypes.SWIPE]: this.el.querySelector('.swipe .swipe-wrap .frame'), - [viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .frame.added'), + [viewTypes.TWO_UP]: this.el.querySelector('.two-up .js-image-frame'), + [viewTypes.SWIPE]: this.el.querySelector('.swipe .js-image-frame'), + [viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .js-image-frame'), }; const viewModesEl = this.el.querySelector('.view-modes-menu'); diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml index a2f1d84c798f..93ec130d6b6f 100644 --- a/app/views/projects/diffs/_replaced_image_diff.html.haml +++ b/app/views/projects/diffs/_replaced_image_diff.html.haml @@ -23,7 +23,7 @@ %strong H: %span.meta-height %span.wrap - = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } %p.image-info.hide %span.meta-filesize= number_to_human_size(blob.size) | @@ -38,7 +38,7 @@ .frame.deleted = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) .swipe-wrap - = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } %span.swipe-bar %span.top-handle %span.bottom-handle @@ -47,7 +47,7 @@ .onion-skin-frame .frame.deleted = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) - = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } .controls .transparent .drag-track diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml index 827e1ffd0f8a..496dc027204c 100644 --- a/app/views/projects/diffs/_single_image_diff.html.haml +++ b/app/views/projects/diffs/_single_image_diff.html.haml @@ -12,5 +12,5 @@ .image.js-single-image{ data: diff_view_data } %span.wrap - single_class_name = diff_file.deleted_file? ? 'deleted' : 'added' - = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path } + = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path } %p.image-info= number_to_human_size(blob.size) -- 2.22.0 From 186d163ee717ac8c3123385eed9180c7e866e9aa Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 09:03:10 -0500 Subject: [PATCH 152/211] [skip ci] Rename btn-comment mixin to btn-comment-icon --- app/assets/stylesheets/framework/buttons.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 2 +- app/assets/stylesheets/pages/notes.scss | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 04e61c724df3..b659e3375e18 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,4 +1,4 @@ -@mixin btn-comment { +@mixin btn-comment-icon { border-radius: 50%; background: $white-light; padding: 1px 5px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 07f93e602fdb..dabeeb0a9b45 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -735,7 +735,7 @@ } .image-comment-badge { - @include btn-comment; + @include btn-comment-icon; position: absolute; &.inverted { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 819ac448de42..bc377ee3ff12 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -650,7 +650,7 @@ ul.notes { } .add-diff-note { - @include btn-comment; + @include btn-comment-icon; opacity: 0; margin-top: -2px; margin-left: -55px; -- 2.22.0 From dced6d59a32766544d0b6b2805089f0551e48703 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 09:07:09 -0500 Subject: [PATCH 153/211] [skip ci] use shorthand for border styling --- app/assets/stylesheets/pages/diff.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index dabeeb0a9b45..ead98f82826d 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -722,9 +722,7 @@ position: absolute; background-color: $blue-400; color: $white-light; - border-color: $white-light; - border-width: 1px; - border-style: solid; + border: $white-light 1px solid; min-height: $gl-padding; padding: 5px 8px; border-radius: 12px; -- 2.22.0 From 7dec4f1fa66b93d5348069322da1b0f6a6ff9566 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 09:09:50 -0500 Subject: [PATCH 154/211] [skip ci] Use more general selector --- .../javascripts/image_diff/helpers/comment_indicator_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index 8307b75fe493..7af02eb31a38 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -58,6 +58,6 @@ export function commentIndicatorOnClick(event) { const buttonEl = event.currentTarget; const diffViewerEl = buttonEl.closest('.diff-viewer'); - const textareaEl = diffViewerEl.querySelector('.note-container form .note-textarea'); + const textareaEl = diffViewerEl.querySelector('.note-container .note-textarea'); textareaEl.focus(); } -- 2.22.0 From 3082f308f9f50c663303281c493b4f798a15d50d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 09:12:08 -0500 Subject: [PATCH 155/211] [skip ci] replace span with div for image diff haml --- app/views/projects/diffs/_replaced_image_diff.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml index 93ec130d6b6f..8fc232b464e1 100644 --- a/app/views/projects/diffs/_replaced_image_diff.html.haml +++ b/app/views/projects/diffs/_replaced_image_diff.html.haml @@ -11,7 +11,7 @@ .image.js-replaced-image{ data: diff_view_data } .two-up.view - %span.wrap + .wrap .frame.deleted = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) %p.image-info.hide @@ -22,7 +22,7 @@ | %strong H: %span.meta-height - %span.wrap + .wrap = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } %p.image-info.hide %span.meta-filesize= number_to_human_size(blob.size) -- 2.22.0 From b1a30848947c2c06d480245a1c2ee96d649c14bf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 09:12:23 -0500 Subject: [PATCH 156/211] [skip ci] replace span with div for image diff haml --- app/views/projects/diffs/_single_image_diff.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml index 496dc027204c..6b0c6bbe48fb 100644 --- a/app/views/projects/diffs/_single_image_diff.html.haml +++ b/app/views/projects/diffs/_single_image_diff.html.haml @@ -10,7 +10,7 @@ - class_name = 'js-add-image-diff-note-button click-to-comment' .image.js-single-image{ data: diff_view_data } - %span.wrap + .wrap - single_class_name = diff_file.deleted_file? ? 'deleted' : 'added' = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path } %p.image-info= number_to_human_size(blob.size) -- 2.22.0 From eb86eedab2277feb8e70dfe0d883a9b5e9ee29ac Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 09:39:18 -0500 Subject: [PATCH 157/211] [skip ci] Use object.assign to save bytes --- .../javascripts/image_diff/helpers/dom_helper.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index 48efbd439b79..5fd3ca243b59 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -3,11 +3,12 @@ export function setPositionDataAttribute(el, options) { // new comment form can use this data for ajax request const { x, y, width, height } = options; const position = el.dataset.position; - const positionObject = JSON.parse(position); - positionObject.x_axis = x; - positionObject.y_axis = y; - positionObject.width = width; - positionObject.height = height; + const positionObject = Object.assign(JSON.parse(position), { + x_axis: x, + y_axis: y, + width, + height, + }); el.setAttribute('data-position', JSON.stringify(positionObject)); } -- 2.22.0 From 51657a7bdcd1fa2d8693fd703011c4b8ef4514ab Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 10:13:49 -0500 Subject: [PATCH 158/211] [skip ci] Rename methods to be more verbose --- app/assets/javascripts/image_diff/image_diff.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index a961d27cae74..f27f1f2bc21c 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -30,8 +30,8 @@ export default class ImageDiff { } bindEvents() { - this.clickWrapper = this.click.bind(this); - this.blurWrapper = this.blur.bind(this); + this.imageClickedWrapper = this.imageClicked.bind(this); + this.imageBlurredWrapper = this.imageBlurred.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); this.removeBadgeWrapper = this.removeBadge.bind(this); @@ -49,8 +49,8 @@ export default class ImageDiff { $(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); if (this.canCreateNote) { - this.el.addEventListener('click.imageDiff', this.clickWrapper); - this.el.addEventListener('blur.imageDiff', this.blurWrapper); + this.el.addEventListener('click.imageDiff', this.imageClickedWrapper); + this.el.addEventListener('blur.imageDiff', this.imageBlurredWrapper); this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); this.el.addEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); @@ -63,15 +63,15 @@ export default class ImageDiff { $(this.el).off('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); if (this.canCreateNote) { - this.el.removeEventListener('click.imageDiff', this.clickWrapper); - this.el.removeEventListener('blur.imageDiff', this.blurWrapper); + this.el.removeEventListener('click.imageDiff', this.imageClickedWrapper); + this.el.removeEventListener('blur.imageDiff', this.imageBlurredWrapper); this.el.removeEventListener('addBadge.imageDiff', this.addBadgeWrapper); this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); } } - click(event) { + imageClicked(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); const el = customEvent.currentTarget; @@ -80,7 +80,7 @@ export default class ImageDiff { imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); } - blur() { + imageBlurred() { return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); } -- 2.22.0 From 1f585854a498e747f8a6ae3967573252da68fdbe Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 3 Oct 2017 12:45:14 -0300 Subject: [PATCH 159/211] Remove gitaly workaround --- lib/gitlab/gitaly_client.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 6bc16b94e821..955d2307f88b 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -144,8 +144,6 @@ module Gitlab # Ensures that Gitaly is not being abuse through n+1 misuse etc def self.enforce_gitaly_request_limits(call_site) - # FIXME: THIS NEEDS TO BE REMOVED. It's a workaround until we get rid of the n+1 issue - return # Only count limits in request-response environments (not sidekiq for example) return unless RequestStore.active? -- 2.22.0 From 6d21ec3206e4a66ff618f206ccf4e4a17db4c90a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 10:57:40 -0500 Subject: [PATCH 160/211] [skip ci] Explicitly use noteEntity.on_image --- app/assets/javascripts/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 388f3416c4cc..7732cbbace1b 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -418,7 +418,7 @@ export default class Notes { form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`); row = form.closest('tr'); - if (row.length === 0) { + if (noteEntity.on_image) { row = form; } -- 2.22.0 From 0d546c6b89329fccb187e4bc7e2513329cbbb0ef Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Tue, 3 Oct 2017 18:28:13 +0200 Subject: [PATCH 161/211] Fixed color of jagged border to be more visual --- app/assets/stylesheets/framework/variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 995e10ea08ac..94d9c3fda83c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -316,7 +316,7 @@ $diff-image-info-color: grey; $diff-swipe-border: #999; $diff-view-modes-color: grey; $diff-view-modes-border: #c1c1c1; -$diff-jagged-border-gradient-color: $white-normal; +$diff-jagged-border-gradient-color: darken($white-normal, 8%); /* * Fonts -- 2.22.0 From 714fac71eefb8752a7906fe4e09915f8280af170 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 11:38:43 -0500 Subject: [PATCH 162/211] [skip ci] Fix bug whereby image discussions wouldn't work on side by side view --- app/assets/javascripts/notes.js | 2 +- app/controllers/concerns/notes_actions.rb | 5 +++-- app/views/discussions/_diff_discussion.html.haml | 2 -- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 7732cbbace1b..dbf707fa379c 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -433,7 +433,7 @@ export default class Notes { if (noteEntity.diff_discussion_html) { var $discussion = $(noteEntity.diff_discussion_html).renderGFM(); - if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) { + if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) { // insert the note and the reply button after the temp row row.after($discussion); } else { diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index f5693e688c56..a4eb2255fb7f 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -123,7 +123,9 @@ module NotesActions def diff_discussion_html(discussion) return unless discussion.diff_discussion? - if params[:view] == 'parallel' + on_image = discussion.on_image? if discussion.diff_discussion? + + if params[:view] == 'parallel' && !on_image template = "discussions/_parallel_diff_discussion" locals = if params[:line_type] == 'old' @@ -133,7 +135,6 @@ module NotesActions end else template = "discussions/_diff_discussion" - on_image = discussion.on_image? if discussion.diff_discussion? @fresh_discussion = true locals = { discussions: [discussion], on_image: on_image } diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index 1506465b9e4a..52279d0a8700 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -8,5 +8,3 @@ %td.notes_content .content{ class: ('hide' unless expanded) } = render partial: "discussions/notes", collection: discussions, as: :discussion - - -- 2.22.0 From 84c1b3697d1ff2f770b6e0b758c4152b06edf18c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 11:42:33 -0500 Subject: [PATCH 163/211] [skip ci] Temporarily removing image badge click for this iteration --- app/assets/javascripts/image_diff/init_discussion_tab.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js index 5ad87394d6eb..2f16c6ef115c 100644 --- a/app/assets/javascripts/image_diff/init_discussion_tab.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -9,6 +9,4 @@ export default () => { const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); [...diffFileEls].forEach(diffFileEl => imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge)); - - $('.timeline-content .diff-file').on('click', '.js-image-badge', imageDiffHelper.imageBadgeOnClick); }; -- 2.22.0 From a967bd524a5cbde39cf764ec135fe8ab736d0f4a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 11:56:23 -0500 Subject: [PATCH 164/211] [skip ci] Use getters for imageFrameEl and imageEl --- .../javascripts/image_diff/image_diff.js | 28 +++++++------------ .../image_diff/replaced_image_diff.js | 12 ++++---- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index f27f1f2bc21c..1c3743d54d2a 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -21,14 +21,6 @@ export default class ImageDiff { this.bindEvents(); } - getImageEl() { - return this.imageEl; - } - - getImageFrameEl() { - return this.imageFrameEl; - } - bindEvents() { this.imageClickedWrapper = this.imageClicked.bind(this); this.imageBlurredWrapper = this.imageBlurred.bind(this); @@ -38,10 +30,10 @@ export default class ImageDiff { this.renderBadgesWrapper = this.renderBadges.bind(this); // Render badges - if (isImageLoaded(this.getImageEl())) { + if (isImageLoaded(this.imageEl)) { this.renderBadges(); } else { - this.getImageEl().addEventListener('load', this.renderBadgesWrapper); + this.imageEl.addEventListener('load', this.renderBadgesWrapper); } // jquery makes the event delegation here much simpler @@ -77,11 +69,11 @@ export default class ImageDiff { const el = customEvent.currentTarget; imageDiffHelper.setPositionDataAttribute(el, selection.actual); - imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), selection.browser); + imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser); } imageBlurred() { - return imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + return imageDiffHelper.removeCommentIndicator(this.imageFrameEl); } renderBadges() { @@ -89,7 +81,7 @@ export default class ImageDiff { [...discussionsEls].forEach((discussionEl, index) => { const imageBadge = imageDiffHelper - .generateBadgeFromDiscussionDOM(this.getImageFrameEl(), discussionEl); + .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); this.imageBadges.push(imageBadge); @@ -99,13 +91,13 @@ export default class ImageDiff { }; if (this.renderCommentBadge) { - imageDiffHelper.addImageCommentBadge(this.getImageFrameEl(), options); + imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options); } else { const numberBadgeOptions = Object.assign(options, { badgeText: index + 1, }); - imageDiffHelper.addImageBadge(this.getImageFrameEl(), numberBadgeOptions); + imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions); } }); } @@ -120,14 +112,14 @@ export default class ImageDiff { width, height, }, - imageEl: this.getImageFrameEl().querySelector('img'), + imageEl: this.imageFrameEl.querySelector('img'), noteId, discussionId, }); this.imageBadges.push(imageBadge); - imageDiffHelper.addImageBadge(this.getImageFrameEl(), { + imageDiffHelper.addImageBadge(this.imageFrameEl, { coordinate: imageBadge.browser, badgeText, noteId, @@ -147,7 +139,7 @@ export default class ImageDiff { removeBadge(event) { const { badgeNumber } = event.detail; const indexToRemove = badgeNumber - 1; - const imageBadgeEls = this.getImageFrameEl().querySelectorAll('.badge'); + const imageBadgeEls = this.imageFrameEl.querySelectorAll('.badge'); if (this.imageBadges.length !== badgeNumber) { // Cascade badges count numbers for (avatar badges + image badges) diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 343e03c1f4d8..13847114b066 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -51,11 +51,11 @@ export default class ReplacedImageDiff extends ImageDiff { this.viewModesEls[viewTypes.ONION_SKIN].removeEventListener('click', this.changeToViewOnionSkin); } - getImageEl() { + get imageEl() { return this.imageEls[this.currentView]; } - getImageFrameEl() { + get imageFrameEl() { return this.imageFrameEls[this.currentView]; } @@ -64,12 +64,12 @@ export default class ReplacedImageDiff extends ImageDiff { return; } - const indicator = imageDiffHelper.removeCommentIndicator(this.getImageFrameEl()); + const indicator = imageDiffHelper.removeCommentIndicator(this.imageFrameEl); this.currentView = newView; // Clear existing badges on new view - const existingBadges = this.getImageFrameEl().querySelectorAll('.badge'); + const existingBadges = this.imageFrameEl.querySelectorAll('.badge'); [].map.call(existingBadges, badge => badge.remove()); // Remove existing references to old view image badges @@ -85,13 +85,13 @@ export default class ReplacedImageDiff extends ImageDiff { // Re-render indicator in new view if (indicator.removed) { const normalizedIndicator = imageDiffHelper - .resizeCoordinatesToImageElement(this.getImageEl(), { + .resizeCoordinatesToImageElement(this.imageEl, { x: indicator.x, y: indicator.y, width: indicator.image.width, height: indicator.image.height, }); - imageDiffHelper.showCommentIndicator(this.getImageFrameEl(), normalizedIndicator); + imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator); } }, 250); } -- 2.22.0 From 8620a02b81f3fd4710d2c83565c51b62d87ce434 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 12:08:34 -0500 Subject: [PATCH 165/211] [skip ci] Use waitForImages --- app/assets/javascripts/commit/image_file.js | 23 +++------------------ 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index c9a39ce4d6a8..e7adf8814b8b 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,5 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */ -import { isImageLoaded } from '../lib/utils/image_utility'; +import 'vendor/jquery.waitforimages'; (function() { gl.ImageFile = (function() { @@ -20,27 +20,10 @@ import { isImageLoaded } from '../lib/utils/image_utility'; // Load two-up view after images are loaded // so that we can display the correct width and height information const $images = $('.two-up.view img', _this.file); - const deleted = $images[0]; - const added = $images[1]; - const deletedLoaded = isImageLoaded(deleted) ? 1 : 0; - const addedLoaded = isImageLoaded(added) ? 1 : 0; - - const leftToLoad = $images.length - deletedLoaded - addedLoaded; - - if (leftToLoad === 0) { + $images.waitForImages(function() { _this.initView('two-up'); - } else { - let loadedCount = 0; - - $images.on('load', () => { - loadedCount += 1; - - if (loadedCount === leftToLoad) { - _this.initView('two-up'); - } - }); - } + }); }); }; })(this)); -- 2.22.0 From 48221cd1307ced190c5ac6498fe495c1e81f3bc2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 3 Oct 2017 13:46:27 -0400 Subject: [PATCH 166/211] Commit spec samples. WIP. --- .../merge_requests/image_diff_notes.rb | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 spec/features/merge_requests/image_diff_notes.rb diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb new file mode 100644 index 000000000000..d5e9ed0d529b --- /dev/null +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -0,0 +1,219 @@ +require 'spec_helper' + +feature 'Diff note avatars', js: true do + include NoteInteractionHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + let(:path) { "files/images/6049019_460s.jpg" } + let(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x_axis: 1, + y_axis: 1, + position_type: "image", + diff_refs: merge_request.diff_refs + ) + end + + let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } + + before do + project.team << [user, :master] + sign_in user + + page.driver.set_cookie('sidebar_collapsed', 'true') + end + + context 'diff tab' do + # eventually split out for parallel and inline views + describe 'creating a new diff note' do + before do + visit diffs_project_merge_request_path(project, merge_request) + + find('.js-add-image-diff-note-button').click + + find('.note-textarea').native.send_keys('image diff test comment') + + click_button 'Comment' + + wait_for_requests + end + + it 'shows indicator badge on image diff', focus: true do + indicator = find('.js-image-badge') + + expect(indicator).to have_content('1') + end + + it 'shows the avatar badge on the new note' do + badge = find('.image-diff-avatar-link .badge') + + expect(badge).to have_content('1') + end + + it 'allows collapsing the discussion notes' do + find('.js-diff-notes-toggle').click + + expect(page).not_to have_content('image diff test comment') + end + + it 'allows expanding discussion notes' do + find('.js-diff-notes-toggle').click + find('.js-diff-notes-toggle').click + + expect(page).to have_content('image diff test comment') + end + end + end + + context 'discussion tab' do + before do + visit project_merge_request_path(project, merge_request) + end + + it 'shows the image diff' do + frame = find('.js-image-frame') + end + end + + context 'commit view' do + before do + visit project_commit_path(project, merge_request.commits.first.id) + end + + it 'does not render avatar after commenting' do + first('.diff-line-num').trigger('mouseover') + find('.js-add-diff-note-button').click + + page.within('.js-discussion-note-form') do + find('.note-textarea').native.send_keys('test comment') + + click_button 'Comment' + + wait_for_requests + end + + visit project_merge_request_path(project, merge_request) + + expect(page).to have_content('test comment') + expect(page).not_to have_selector('.js-avatar-container') + expect(page).not_to have_selector('.diff-comment-avatar-holders') + end + end + + %w(inline parallel).each do |view| + context "#{view} view" do + before do + visit diffs_project_merge_request_path(project, merge_request, view: view) + + wait_for_requests + end + + it 'shows note avatar' do + page.within find("[id='#{position.line_code(project.repository)}']") do + find('.diff-notes-collapse').click + + expect(page).to have_selector('img.js-diff-comment-avatar', count: 1) + end + end + + it 'shows comment on note avatar' do + page.within find("[id='#{position.line_code(project.repository)}']") do + find('.diff-notes-collapse').click + + expect(first('img.js-diff-comment-avatar')["data-original-title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}") + end + end + + it 'toggles comments when clicking avatar' do + page.within find("[id='#{position.line_code(project.repository)}']") do + find('.diff-notes-collapse').click + end + + expect(page).to have_selector('.notes_holder', visible: false) + + page.within find("[id='#{position.line_code(project.repository)}']") do + first('img.js-diff-comment-avatar').click + end + + expect(page).to have_selector('.notes_holder') + end + + it 'removes avatar when note is deleted' do + open_more_actions_dropdown(note) + + page.within find(".note-row-#{note.id}") do + find('.js-note-delete').click + end + + wait_for_requests + + page.within find("[id='#{position.line_code(project.repository)}']") do + expect(page).not_to have_selector('img.js-diff-comment-avatar') + end + end + + it 'adds avatar when commenting' do + click_button 'Reply...' + + page.within '.js-discussion-note-form' do + find('.js-note-text').native.send_keys('Test') + + click_button 'Comment' + + wait_for_requests + end + + page.within find("[id='#{position.line_code(project.repository)}']") do + find('.diff-notes-collapse').trigger('click') + + expect(page).to have_selector('img.js-diff-comment-avatar', count: 2) + end + end + + it 'adds multiple comments' do + 3.times do + click_button 'Reply...' + + page.within '.js-discussion-note-form' do + find('.js-note-text').native.send_keys('Test') + + find('.js-comment-button').trigger('click') + + wait_for_requests + end + end + + page.within find("[id='#{position.line_code(project.repository)}']") do + find('.diff-notes-collapse').trigger('click') + + expect(page).to have_selector('img.js-diff-comment-avatar', count: 3) + expect(find('.diff-comments-more-count')).to have_content '+1' + end + end + + context 'multiple comments' do + before do + create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note) + + visit diffs_project_merge_request_path(project, merge_request, view: view) + + wait_for_requests + end + + it 'shows extra comment count' do + page.within find("[id='#{position.line_code(project.repository)}']") do + find('.diff-notes-collapse').click + + expect(find('.diff-comments-more-count')).to have_content '+1' + end + end + end + end + end +end -- 2.22.0 From 36a53053c8ebfc82052bee6e57617329140c13d2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 3 Oct 2017 14:10:02 -0400 Subject: [PATCH 167/211] Stub out feature spec. --- .../merge_requests/image_diff_notes.rb | 192 ++++-------------- 1 file changed, 44 insertions(+), 148 deletions(-) diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index d5e9ed0d529b..95abf9125528 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -29,191 +29,87 @@ feature 'Diff note avatars', js: true do page.driver.set_cookie('sidebar_collapsed', 'true') end - context 'diff tab' do - # eventually split out for parallel and inline views - describe 'creating a new diff note' do - before do - visit diffs_project_merge_request_path(project, merge_request) - - find('.js-add-image-diff-note-button').click - - find('.note-textarea').native.send_keys('image diff test comment') - - click_button 'Comment' - - wait_for_requests - end - - it 'shows indicator badge on image diff', focus: true do - indicator = find('.js-image-badge') - - expect(indicator).to have_content('1') - end - - it 'shows the avatar badge on the new note' do - badge = find('.image-diff-avatar-link .badge') - - expect(badge).to have_content('1') - end - - it 'allows collapsing the discussion notes' do - find('.js-diff-notes-toggle').click - - expect(page).not_to have_content('image diff test comment') - end - - it 'allows expanding discussion notes' do - find('.js-diff-notes-toggle').click - find('.js-diff-notes-toggle').click - - expect(page).to have_content('image diff test comment') - end - end - end - - context 'discussion tab' do - before do - visit project_merge_request_path(project, merge_request) - end - - it 'shows the image diff' do - frame = find('.js-image-frame') - end - end - context 'commit view' do before do visit project_commit_path(project, merge_request.commits.first.id) end - it 'does not render avatar after commenting' do - first('.diff-line-num').trigger('mouseover') - find('.js-add-diff-note-button').click - - page.within('.js-discussion-note-form') do - find('.note-textarea').native.send_keys('test comment') - - click_button 'Comment' - - wait_for_requests - end - - visit project_merge_request_path(project, merge_request) - - expect(page).to have_content('test comment') - expect(page).not_to have_selector('.js-avatar-container') - expect(page).not_to have_selector('.diff-comment-avatar-holders') - end + # same behavior as diff note end %w(inline parallel).each do |view| context "#{view} view" do - before do - visit diffs_project_merge_request_path(project, merge_request, view: view) - - wait_for_requests - end - - it 'shows note avatar' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click - expect(page).to have_selector('img.js-diff-comment-avatar', count: 1) + describe 'creating a new diff note' do + before do + visit diffs_project_merge_request_path(project, merge_request, view: view) + create_image_diff_note end - end - it 'shows comment on note avatar' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + it 'shows indicator badge on image diff', focus: true do + indicator = find('.js-image-badge') - expect(first('img.js-diff-comment-avatar')["data-original-title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}") - end - end - - it 'toggles comments when clicking avatar' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + expect(indicator).to have_content('1') end - expect(page).to have_selector('.notes_holder', visible: false) + it 'shows the avatar badge on the new note' do + badge = find('.image-diff-avatar-link .badge') - page.within find("[id='#{position.line_code(project.repository)}']") do - first('img.js-diff-comment-avatar').click + expect(badge).to have_content('1') end - expect(page).to have_selector('.notes_holder') - end - - it 'removes avatar when note is deleted' do - open_more_actions_dropdown(note) + it 'allows collapsing the discussion notes' do + find('.js-diff-notes-toggle').click - page.within find(".note-row-#{note.id}") do - find('.js-note-delete').click + expect(page).not_to have_content('image diff test comment') end - wait_for_requests + it 'allows expanding discussion notes' do + find('.js-diff-notes-toggle').click + find('.js-diff-notes-toggle').click - page.within find("[id='#{position.line_code(project.repository)}']") do - expect(page).not_to have_selector('img.js-diff-comment-avatar') + expect(page).to have_content('image diff test comment') end end - it 'adds avatar when commenting' do - click_button 'Reply...' - - page.within '.js-discussion-note-form' do - find('.js-note-text').native.send_keys('Test') - - click_button 'Comment' - - wait_for_requests + describe 'render diff notes' do + before do + # mock a couple separate comments on the image diff end - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').trigger('click') - - expect(page).to have_selector('img.js-diff-comment-avatar', count: 2) + it 'render diff indicators within the image frame' do end - end - - it 'adds multiple comments' do - 3.times do - click_button 'Reply...' - - page.within '.js-discussion-note-form' do - find('.js-note-text').native.send_keys('Test') - - find('.js-comment-button').trigger('click') - wait_for_requests - end + it 'shows the diff notes' do end - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').trigger('click') - - expect(page).to have_selector('img.js-diff-comment-avatar', count: 3) - expect(find('.diff-comments-more-count')).to have_content '+1' + it 'shows the diff notes with correct avatar badge numbers' do end end + end + end - context 'multiple comments' do - before do - create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note) - - visit diffs_project_merge_request_path(project, merge_request, view: view) + context 'discussion tab' do + before do + visit project_merge_request_path(project, merge_request) + end - wait_for_requests - end + it 'shows the image diff frame' do + frame = find('.js-image-frame') + end - it 'shows extra comment count' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + it 'shows the indicator on the frame' do + end - expect(find('.diff-comments-more-count')).to have_content '+1' - end - end - end + it 'shows the note with a generic comment icon' do end end + +end + +def create_image_diff_note + find('.js-add-image-diff-note-button').click + find('.note-textarea').native.send_keys('image diff test comment') + click_button 'Comment' + wait_for_requests end -- 2.22.0 From 4efed4b417a1cc6c9c57e1cb0661dc93487f52c6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 13:50:23 -0500 Subject: [PATCH 168/211] [skip ci] Add collapse icon to svg sprite --- app/assets/stylesheets/pages/diff.scss | 8 +++++++- app/views/discussions/_notes.html.haml | 2 +- app/views/shared/icons/_collapse_icon.svg | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 app/views/shared/icons/_collapse_icon.svg diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index ead98f82826d..883940153413 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -543,11 +543,17 @@ transition: transform .1s ease-out; z-index: 100; + .collapse-icon { + height: 50%; + width: 100%; + } + svg { vertical-align: middle; } - path { + .collapse-icon, + .path { fill: $white-light; } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 23d6c1d2fcc8..838bde9b0f9c 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -10,7 +10,7 @@ %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - if discussion.try(:on_image?) && show_toggle %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' } - = custom_icon('collapse_icon') + = sprite_icon('collapse', css_class: 'collapse-icon') %button.btn-transparent.badge.js-diff-notes-toggle{ type: 'button' } = badge_counter = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter, show_image_comment_badge: show_image_comment_badge } diff --git a/app/views/shared/icons/_collapse_icon.svg b/app/views/shared/icons/_collapse_icon.svg deleted file mode 100644 index bd4b393cfaad..000000000000 --- a/app/views/shared/icons/_collapse_icon.svg +++ /dev/null @@ -1 +0,0 @@ - -- 2.22.0 From 6d9930c9a8c06ca22a989ac7d31ca46269f7858f Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 3 Oct 2017 15:58:29 -0300 Subject: [PATCH 169/211] add branch with images to test repo --- spec/factories/merge_requests.rb | 5 +++++ spec/features/merge_requests/image_diff_notes.rb | 4 ++-- spec/support/test_env.rb | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index cbec716d6ea5..2c732aaf4edc 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -22,6 +22,11 @@ FactoryGirl.define do trait :with_diffs do end + trait :with_image_diffs do + source_branch "add_images_and_changes" + target_branch "master" + end + trait :without_diffs do source_branch "improve/awesome" target_branch "master" diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index d5e9ed0d529b..6ce348550582 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -5,8 +5,8 @@ feature 'Diff note avatars', js: true do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } - let(:path) { "files/images/6049019_460s.jpg" } + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user, title: "Added images and changes") } + let(:path) { "files/images/ee_repo_logo.png" } let(:position) do Gitlab::Diff::Position.new( old_path: path, diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index b4e8b5ea67ba..4c5f87fabd5b 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -45,7 +45,8 @@ module TestEnv 'v1.1.0' => 'b83d6e3', 'add-ipython-files' => '93ee732', 'add-pdf-file' => 'e774ebd', - 'add-pdf-text-binary' => '79faa7b' + 'add-pdf-text-binary' => '79faa7b', + 'add_images_and_changes' => '010d106' }.freeze # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily -- 2.22.0 From 7ee2fe2032b27fd9334789b0298380ac5f5ad310 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 14:42:52 -0500 Subject: [PATCH 170/211] [skip ci] Fix collapse icon path style --- app/assets/stylesheets/pages/diff.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 883940153413..e287db0deab6 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -553,7 +553,7 @@ } .collapse-icon, - .path { + path { fill: $white-light; } -- 2.22.0 From c55d7c79c12335a7f6090df8e579ce1b831ed790 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 3 Oct 2017 16:45:56 -0300 Subject: [PATCH 171/211] Simplify position type --- app/models/diff_note.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 0ede96f08c61..d88a92dc0275 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -44,11 +44,11 @@ class DiffNote < Note end def on_text? - position_type == "text" + position.position_type == "text" end def on_image? - position_type == "image" + position.position_type == "image" end def diff_file @@ -64,15 +64,11 @@ class DiffNote < Note end def original_line_code - return if self.position_type != "text" + return unless on_text? self.diff_file.line_code(self.diff_line) end - def position_type - position.position_type - end - def active?(diff_refs = nil) return false unless supported? return true if for_commit? -- 2.22.0 From 785400289e1c0c409a836cb80f49fb5bafac9d7c Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 3 Oct 2017 16:00:34 -0400 Subject: [PATCH 172/211] Further fill out feature spec. --- .../merge_requests/image_diff_notes.rb | 76 ++++++++++++++++--- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index 300b40af8f6a..b2735ceb5cac 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -30,43 +30,95 @@ feature 'Diff note avatars', js: true do end context 'commit view' do - before do - visit project_commit_path(project, merge_request.commits.first.id) + describe 'creating a new diff note' do + before do + visit project_commit_path(project, '2f63565e7aac07bcdadb654e253078b727143ec4') + create_image_diff_note + end + + it 'shows indicator badge on image diff' do + indicator = find('.js-image-badge') + + expect(indicator).to have_content('1') + end + + it 'shows the avatar badge on the new note' do + badge = find('.image-diff-avatar-link .badge') + + expect(badge).to have_content('1') + end + + it 'allows collapsing the discussion notes' do + find('.js-diff-notes-toggle').click + + expect(page).not_to have_content('image diff test comment') + end + + it 'allows expanding discussion notes' do + find('.js-diff-notes-toggle').click + find('.js-diff-notes-toggle').click + + expect(page).to have_content('image diff test comment') + end end - # same behavior as diff note + describe 'render diff notes' do + commit_id = '2f63565e7aac07bcdadb654e253078b727143ec4' + + let!(:note2) { create(:note_on_commit, commit_id: commit_id, project: project, note: 'my note 2') } + let!(:note3) { create(:note_on_commit, commit_id: commit_id, project: project, note: 'my note 3') } + + before do + visit project_commit_path(project, commit_id) + end + + it 'render diff indicators within the image diff frame' do + expect(page).to have_css('.js-image-badge', count: 2) + end + + it 'shows the diff notes' do + expect(page).to have_css('.diff-content .note', count: 2) + end + + it 'shows the diff notes with correct avatar badge numbers' do + first_note_avatar = find('.image-diff-avatar-link', match: :first) + second_note_avatar = find('.image-diff-avatar-link', match: :second) + + expect(first_note_avatar).to have_content("1") + expect(second_note_avatar).to have_content("2") + end + end end %w(inline parallel).each do |view| context "#{view} view" do - - describe 'creating a new diff note' do + describe 'creating a new diff note', focus: true do before do visit diffs_project_merge_request_path(project, merge_request, view: view) create_image_diff_note end - it 'shows indicator badge on image diff', focus: true do - indicator = find('.js-image-badge') + it 'shows indicator badge on image diff'do + indicator = find('.js-image-badge', match: :first) expect(indicator).to have_content('1') end it 'shows the avatar badge on the new note' do - badge = find('.image-diff-avatar-link .badge') + badge = find('.image-diff-avatar-link .badge', match: :first) expect(badge).to have_content('1') end it 'allows collapsing the discussion notes' do - find('.js-diff-notes-toggle').click + find('.js-diff-notes-toggle', match: :first).click expect(page).not_to have_content('image diff test comment') end it 'allows expanding discussion notes' do - find('.js-diff-notes-toggle').click - find('.js-diff-notes-toggle').click + find('.js-diff-notes-toggle', match: :first).click + find('.js-diff-notes-toggle', match: :first).click expect(page).to have_content('image diff test comment') end @@ -109,7 +161,7 @@ end def create_image_diff_note find('.js-add-image-diff-note-button').click - find('.note-textarea').native.send_keys('image diff test comment') + find('.diff-content .note-textarea').native.send_keys('image diff test comment') click_button 'Comment' wait_for_requests end -- 2.22.0 From 4f3c487146d1579b93d5d13a2f1c3eb5b0e512a3 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 3 Oct 2017 18:16:54 -0300 Subject: [PATCH 173/211] Backend improvements --- app/controllers/concerns/notes_actions.rb | 2 +- app/models/discussion.rb | 4 ++++ .../projects/diffs/viewers/_image.html.haml | 2 +- lib/gitlab/diff/file.rb | 6 +++--- lib/gitlab/diff/formatters/base_formatter.rb | 7 ++++--- lib/gitlab/diff/formatters/image_formatter.rb | 6 +++++- lib/gitlab/diff/formatters/text_formatter.rb | 6 +++++- lib/gitlab/diff/position.rb | 16 +++++++++++----- 8 files changed, 34 insertions(+), 15 deletions(-) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index a4eb2255fb7f..68490bf47c48 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -123,7 +123,7 @@ module NotesActions def diff_discussion_html(discussion) return unless discussion.diff_discussion? - on_image = discussion.on_image? if discussion.diff_discussion? + on_image = discussion.on_image? if params[:view] == 'parallel' && !on_image template = "discussions/_parallel_diff_discussion" diff --git a/app/models/discussion.rb b/app/models/discussion.rb index b80da7b246a8..437df923d2d8 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -66,6 +66,10 @@ class Discussion @context_noteable = context_noteable end + def on_image? + false + end + def ==(other) other.class == self.class && other.context_noteable == self.context_noteable && diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 8e992721715e..cf0783e26f33 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -2,7 +2,7 @@ - image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) - discussions = @grouped_diff_discussions[diff_file.file_identifier] if @grouped_diff_discussions -- locals = { diff_file: diff_file, position: diff_file.position(image_point, "image").to_json, click_to_comment: true, diff_view_data: diff_view_data } +- locals = { diff_file: diff_file, position: diff_file.position(image_point, position_type: :image).to_json, click_to_comment: true, diff_view_data: diff_view_data } - if diff_file.new_file? || diff_file.deleted_file? = render partial: "projects/diffs/single_image_diff", locals: locals diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index c9581f4b63c7..599c3c5deab7 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -27,17 +27,17 @@ module Gitlab @fallback_diff_refs = fallback_diff_refs end - def position(position_marker, position_type = "text") + def position(position_marker, position_type: :text) return unless diff_refs data = { diff_refs: diff_refs, - position_type: position_type, + position_type: position_type.to_s, old_path: old_path, new_path: new_path } - if position_type == "text" + if position_type == :text data.merge!(text_position_properties(position_marker)) else data.merge!(image_position_properties(position_marker)) diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index 6f7b6aaa669c..5e923b9e6025 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -27,9 +27,6 @@ module Gitlab @base_sha = attrs[:base_sha] @start_sha = attrs[:start_sha] @head_sha = attrs[:head_sha] - - # Make sure older serialized positions have text as type - @position_type = attrs[:position_type] || "text" end def key @@ -47,6 +44,10 @@ module Gitlab } end + def position_type + raise NotImplementedError + end + def ==(other) raise NotImplementedError end diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index 43d6c1dbfc1d..f3e5887555ec 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -28,8 +28,12 @@ module Gitlab super.merge(width: width, height: height, x_axis: x_axis, y_axis: y_axis) end + def position_type + "image" + end + def ==(other) - self.class == other.class && + other.is_a?(self.class) && x_axis == other.x_axis && y_axis == other.y_axis end diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index 89ad866ca324..01c7e9f51ab4 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -34,8 +34,12 @@ module Gitlab end end + def position_type + "text" + end + def ==(other) - self.class == other.class && + other.is_a?(self.class) && new_line == other.new_line && old_line == other.old_line end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index 5e6ba2b30d3d..ccc8095e80a2 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -1,10 +1,8 @@ -# Defines a specific location, identified by paths and line numbers, +# Defines a specific location, identified by paths line numbers and image coordinates, # within a specific diff, identified by start, head and base commit ids. module Gitlab module Diff class Position - FORMATTER_CLASS_SUFFIX = "_formatter".freeze - attr_accessor :formatter delegate :old_path, @@ -14,6 +12,10 @@ module Gitlab :head_sha, :position_type, to: :formatter + # A position can belong to a text line or to an image coordinate + # it depends of the position_type argument. + # Text position will have: new_line and old_line + # Image position will have: width, height, x_axis, y_axis def initialize(attrs = {}) @formatter = get_formatter_class(attrs[:position_type]).new(attrs) end @@ -124,9 +126,13 @@ module Gitlab def get_formatter_class(type) type ||= "text" - class_name = (type.to_s + FORMATTER_CLASS_SUFFIX).classify - Gitlab::Diff::Formatters.const_get(class_name) + case type + when 'image' + Gitlab::Diff::Formatters::ImageFormatter + else + Gitlab::Diff::Formatters::TextFormatter + end end end end -- 2.22.0 From 6c44c76519cd454c2055acf19d42d05a9092ce3d Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 3 Oct 2017 18:45:17 -0300 Subject: [PATCH 174/211] add position backend specs --- spec/lib/gitlab/diff/position_spec.rb | 59 +++++++++++++++++++-------- spec/models/diff_note_spec.rb | 3 ++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index d78362cbec51..0db96645319f 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -468,27 +468,54 @@ describe Gitlab::Diff::Position do end describe "#to_json" do - let(:hash) do - { - old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 14, - base_sha: nil, - head_sha: nil, - start_sha: nil, - position_type: "text" - } + shared_examples "diff position json" do + it "returns the position as JSON" do + expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + end + + it "works when nested under another hash" do + expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + end end - let(:diff_position) { described_class.new(hash) } + context "for text positon" do + let(:hash) do + { + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + base_sha: nil, + head_sha: nil, + start_sha: nil, + position_type: "text" + } + end + + let(:diff_position) { described_class.new(hash) } - it "returns the position as JSON" do - expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + it_behaves_like "diff position json" end - it "works when nested under another hash" do - expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + context "for image positon" do + let(:hash) do + { + old_path: "files/any.img", + new_path: "files/any.img", + base_sha: nil, + head_sha: nil, + start_sha: nil, + width: 100, + height: 100, + x_axis: 1, + y_axis: 100 + position_type: "image", + } + end + + let(:diff_position) { described_class.new(hash) } + + it_behaves_like "diff position json" end end end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index dd94b78ec00f..119e81b0a21c 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -255,4 +255,7 @@ describe DiffNote do end end end + + describe "image diff notes" do + end end -- 2.22.0 From 9b643375a4bdecdf38deca05b346ba7150a4b12c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 18:54:17 -0500 Subject: [PATCH 175/211] [skip ci] disable image comment badge --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index e4dccb6833ed..de883265adc0 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -27,6 +27,8 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) { const buttonEl = createImageBadge(noteId, ['image-comment-badge', 'inverted']); + buttonEl.setAttribute('disabled', true); + const iconEl = document.createElement('i'); iconEl.classList.add('fa'); iconEl.classList.add('fa-comment-o'); -- 2.22.0 From 2b70f8eacd9818ec4aebe8dada335f3bd825a4c8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:02:00 -0500 Subject: [PATCH 176/211] [skip ci] Fix NaN bug --- app/assets/javascripts/notes.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index dbf707fa379c..672f98b2654d 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -465,14 +465,19 @@ export default class Notes { if (noteEntity.on_image) { const $noteEl = $(`.note-row-${noteEntity.id}:visible`); const $notesContainer = $noteEl.closest('.notes'); - const badgeNumber = parseInt($notesContainer.find('.badge.js-diff-notes-toggle').text().trim(), 10); + const $badge = $notesContainer.find('.badge.js-diff-notes-toggle'); + const badgeNumber = parseInt($badge.text().trim(), 10); - imageDiffHelper.addAvatarBadge($notesContainer[0], { - detail: { - badgeNumber, - noteId: `note_${noteEntity.id}` - } - }); + // Only add avatar badge if the badge exists + // as there are no avatar badges for discussion tab + if ($badge.length > 0) { + imageDiffHelper.addAvatarBadge($notesContainer[0], { + detail: { + badgeNumber, + noteId: `note_${noteEntity.id}` + } + }); + } } } -- 2.22.0 From a473e89358700e188b99c96c1f5c33b566b0fedc Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:12:54 -0500 Subject: [PATCH 177/211] [skip ci] Create partial variable instead of locals --- app/views/discussions/_diff_with_notes.html.haml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index b2b5055a3554..492da673a369 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -19,12 +19,9 @@ discussion_expanded: true, plain: true } - else + - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replace_image_diff' - - locals = { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } - - if diff_file.new_file? || diff_file.deleted_file? - = render partial: "projects/diffs/single_image_diff", locals: locals - - else - = render partial: "projects/diffs/replaced_image_diff", locals: locals + = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } .note-container = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } -- 2.22.0 From 3a536090c8049504886e0466ba0d9793ff0178a8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:18:08 -0500 Subject: [PATCH 178/211] [skip ci] Remove unused unbindEvents --- app/assets/javascripts/image_diff/image_diff.js | 14 -------------- .../javascripts/image_diff/replaced_image_diff.js | 8 -------- 2 files changed, 22 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 1c3743d54d2a..97ba089f0af3 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -49,20 +49,6 @@ export default class ImageDiff { } } - unbindEvents() { - this.imageEl.removeEventListener('load', this.renderBadgesWrapper); - this.$noteContainer.off('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed); - $(this.el).off('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick); - - if (this.canCreateNote) { - this.el.removeEventListener('click.imageDiff', this.imageClickedWrapper); - this.el.removeEventListener('blur.imageDiff', this.imageBlurredWrapper); - this.el.removeEventListener('addBadge.imageDiff', this.addBadgeWrapper); - this.el.removeEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); - this.el.removeEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); - } - } - imageClicked(event) { const customEvent = event.detail; const selection = imageDiffHelper.getTargetSelection(customEvent); diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 13847114b066..8690cfad5c6d 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -43,14 +43,6 @@ export default class ReplacedImageDiff extends ImageDiff { this.viewModesEls[viewTypes.ONION_SKIN].addEventListener('click', this.changeToViewOnionSkin); } - unbindEvents() { - super.unbindEvents(); - - this.viewModesEls[viewTypes.TWO_UP].removeEventListener('click', this.changeToViewTwoUp); - this.viewModesEls[viewTypes.SWIPE].removeEventListener('click', this.changeToViewSwipe); - this.viewModesEls[viewTypes.ONION_SKIN].removeEventListener('click', this.changeToViewOnionSkin); - } - get imageEl() { return this.imageEls[this.currentView]; } -- 2.22.0 From b7ecd129ffbe63033b7078a6a481b6533f82f47f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:22:19 -0500 Subject: [PATCH 179/211] [skip ci] fix partial name --- app/views/discussions/_diff_with_notes.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 492da673a369..636d06cab533 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -19,7 +19,7 @@ discussion_expanded: true, plain: true } - else - - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replace_image_diff' + - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff' = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } -- 2.22.0 From 546bc0077bd19936b485294114d4bf2d5872e73c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:23:44 -0500 Subject: [PATCH 180/211] [skip ci] Use spread syntax for map() --- app/assets/javascripts/image_diff/replaced_image_diff.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 8690cfad5c6d..5c7e4d5e5a60 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -62,7 +62,7 @@ export default class ReplacedImageDiff extends ImageDiff { // Clear existing badges on new view const existingBadges = this.imageFrameEl.querySelectorAll('.badge'); - [].map.call(existingBadges, badge => badge.remove()); + [...existingBadges].map(badge => badge.remove()); // Remove existing references to old view image badges this.imageBadges = []; -- 2.22.0 From 175543b4216d78e3e5fb8a3e6d1ad2b0a8924ebe Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:27:17 -0500 Subject: [PATCH 181/211] [skip ci] remove explicit px replacement and rely on parseInt to remove string --- .../image_diff/helpers/comment_indicator_helper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index 7af02eb31a38..7f1003658723 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -24,8 +24,8 @@ export function removeCommentIndicator(imageFrameEl) { if (willRemove) { meta = { - x: parseInt(commentIndicatorEl.style.left.replace('px', ''), 10), - y: parseInt(commentIndicatorEl.style.top.replace('px', ''), 10), + x: parseInt(commentIndicatorEl.style.left, 10), + y: parseInt(commentIndicatorEl.style.top, 10), image: { width: imageEl.width, height: imageEl.height, -- 2.22.0 From 6de11a0e574a76d362e1f2595b632267360d0fcd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 19:30:22 -0500 Subject: [PATCH 182/211] [skip ci] use shorthand to check for null --- .../javascripts/image_diff/helpers/comment_indicator_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index 7f1003658723..17b0f88e6d8e 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -19,7 +19,7 @@ export function addCommentIndicator(containerEl, coordinate) { export function removeCommentIndicator(imageFrameEl) { const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator'); const imageEl = imageFrameEl.querySelector('img'); - const willRemove = commentIndicatorEl !== null; + const willRemove = !!commentIndicatorEl; let meta = {}; if (willRemove) { -- 2.22.0 From d158da7c06dd75c96cda75950dd9a61e8adc58c0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 20:03:49 -0500 Subject: [PATCH 183/211] [skip ci] Use hasAttribute to check canCreateNote because it is faster --- app/assets/javascripts/diff.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index b74af0d53472..062ea6f5007f 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -20,7 +20,8 @@ class Diff { FilesCommentButton.init($diffFile); - const canCreateNote = $('.files').first().is('[data-can-create-note]'); + const firstFile = $('.files').first().get(0); + const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note'); $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote)); if (!isBound) { -- 2.22.0 From 04cb35736d0dcb2876f0022216e1a15e2431d794 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 20:09:51 -0500 Subject: [PATCH 184/211] [skip ci] Fix no-param-reassign --- .../javascripts/image_diff/helpers/badge_helper.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index de883265adc0..4b51a4e7005f 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -9,11 +9,13 @@ export function createImageBadge(noteId, classNames = []) { } export function centerButtonToCoordinate(buttonEl, coordinate) { - // Set button center to be the center of the clicked position const { x, y } = coordinate; - const { width, height } = buttonEl.getBoundingClientRect(); - buttonEl.style.left = `${x - (width * 0.5)}px`; // eslint-disable-line no-param-reassign - buttonEl.style.top = `${y - (height * 0.5)}px`; // eslint-disable-line no-param-reassign + const updatedButtonEl = buttonEl; + + const { width, height } = updatedButtonEl.getBoundingClientRect(); + // Set button center to be the center of the clicked position + updatedButtonEl.style.left = `${x - (width * 0.5)}px`; + updatedButtonEl.style.top = `${y - (height * 0.5)}px`; } export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { -- 2.22.0 From e9b58d076c7e8c53e8f2ce5fc204cf0503bf622b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 20:22:35 -0500 Subject: [PATCH 185/211] [skip ci] remove x,y click coordinate calculation when we can use offset --- app/assets/javascripts/image_diff/helpers/utils_helper.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index 1ca8fcaf5be1..d0cd42df0736 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -40,8 +40,9 @@ export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) { export function getTargetSelection(event) { const containerEl = event.currentTarget; const imageEl = containerEl.querySelector('img'); - const x = event.offsetX ? (event.offsetX) : event.pageX - containerEl.offsetLeft; - const y = event.offsetY ? (event.offsetY) : event.pageY - containerEl.offsetTop; + + const x = event.offsetX; + const y = event.offsetY; const width = imageEl.width; const height = imageEl.height; -- 2.22.0 From b1eb10635d3a8c8146ebbe91cf7a41c328b285c6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 3 Oct 2017 20:32:38 -0500 Subject: [PATCH 186/211] [skip ci] Pass discussion id as part of the element id --- app/assets/javascripts/image_diff/image_diff.js | 4 ++-- app/views/discussions/_notes.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 97ba089f0af3..42b6676abf72 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -118,7 +118,7 @@ export default class ImageDiff { }, }); - const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); + const discussionEl = this.el.querySelector(`#discussion_${discussionId}`); imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText); } @@ -133,7 +133,7 @@ export default class ImageDiff { if (index > indexToRemove) { const { discussionId } = badge; const updatedBadgeNumber = index; - const discussionEl = this.el.querySelector(`.notes[data-discussion-id="${discussionId}"]`); + const discussionEl = this.el.querySelector(`#discussion_${discussionId}`); imageBadgeEls[index].innerText = updatedBadgeNumber; diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 838bde9b0f9c..9efcfef690f3 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -7,7 +7,7 @@ .discussion-notes{ class: collapsed_class } -# Save the first note position data so that we have a reference and can go -# to the first note position when we click on a badge diff discussion - %ul.notes{ data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } + %ul.notes{ id: "discussion_#{discussion.id}", data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } } - if discussion.try(:on_image?) && show_toggle %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' } = sprite_icon('collapse', css_class: 'collapse-icon') -- 2.22.0 From b638029978541758e53449a5138539d1340a445a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 4 Oct 2017 10:32:32 -0400 Subject: [PATCH 187/211] Check in most recent feature spec. --- .../merge_requests/image_diff_notes.rb | 91 +++++++++++-------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index b2735ceb5cac..cdc48ea3e2c2 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -5,22 +5,6 @@ feature 'Diff note avatars', js: true do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } - let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user, title: "Added images and changes") } - let(:path) { "files/images/ee_repo_logo.png" } - let(:position) do - Gitlab::Diff::Position.new( - old_path: path, - new_path: path, - width: 100, - height: 100, - x_axis: 1, - y_axis: 1, - position_type: "image", - diff_refs: merge_request.diff_refs - ) - end - - let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } before do project.team << [user, :master] @@ -29,10 +13,12 @@ feature 'Diff note avatars', js: true do page.driver.set_cookie('sidebar_collapsed', 'true') end - context 'commit view' do - describe 'creating a new diff note' do + context 'commit view', focus: true do + commit_id = '2f63565e7aac07bcdadb654e253078b727143ec4' + + describe 'create a new diff note' do before do - visit project_commit_path(project, '2f63565e7aac07bcdadb654e253078b727143ec4') + visit project_commit_path(project, commit_id) create_image_diff_note end @@ -63,10 +49,24 @@ feature 'Diff note avatars', js: true do end describe 'render diff notes' do - commit_id = '2f63565e7aac07bcdadb654e253078b727143ec4' + let(:path) { "files/images/6049019_460s.jpg" } + + # move into MR context + let(:note2_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x_axis: 1, + y_axis: 1, + position_type: "image", + diff_refs: commit_id + ) + end - let!(:note2) { create(:note_on_commit, commit_id: commit_id, project: project, note: 'my note 2') } - let!(:note3) { create(:note_on_commit, commit_id: commit_id, project: project, note: 'my note 3') } + # Need to scope these notes to the diff + let!(:note2) { create(:note_on_commit, commit_id: commit_id, project: project, position: :note2_position, note: 'my note 2') } before do visit project_commit_path(project, commit_id) @@ -82,17 +82,33 @@ feature 'Diff note avatars', js: true do it 'shows the diff notes with correct avatar badge numbers' do first_note_avatar = find('.image-diff-avatar-link', match: :first) - second_note_avatar = find('.image-diff-avatar-link', match: :second) expect(first_note_avatar).to have_content("1") - expect(second_note_avatar).to have_content("2") end end end %w(inline parallel).each do |view| context "#{view} view" do - describe 'creating a new diff note', focus: true do + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user, title: "Added images and changes") } + let(:path) { "files/images/ee_repo_logo.png" } + + let(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x_axis: 1, + y_axis: 1, + position_type: "image", + diff_refs: merge_request.diff_refs + ) + end + + let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } + + describe 'creating a new diff note' do before do visit diffs_project_merge_request_path(project, merge_request, view: view) create_image_diff_note @@ -138,25 +154,24 @@ feature 'Diff note avatars', js: true do it 'shows the diff notes with correct avatar badge numbers' do end end - end - end - context 'discussion tab' do - before do - visit project_merge_request_path(project, merge_request) - end + context 'discussion tab' do + before do + visit project_merge_request_path(project, merge_request) + end - it 'shows the image diff frame' do - frame = find('.js-image-frame') - end + it 'shows the image diff frame' do + frame = find('.js-image-frame') + end - it 'shows the indicator on the frame' do - end + it 'shows the indicator on the frame' do + end - it 'shows the note with a generic comment icon' do + it 'shows the note with a generic comment icon' do + end + end end end - end def create_image_diff_note -- 2.22.0 From 929e213165612020b590ea35e38de777cb6bbca7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 10:48:40 -0500 Subject: [PATCH 188/211] [skip ci] use val() instead of [0].value --- app/assets/javascripts/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 672f98b2654d..b07ef868ea82 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1552,7 +1552,7 @@ export default class Notes { } else { // Add image badge, avatar badge and toggle discussion badge for new image diffs if ($diffFile.length > 0) { - const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position')[0].value); + const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position').val()); const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { detail: { x: x_axis, -- 2.22.0 From fe84dd55cbb3e7056183ad58845a5a2c16207c38 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 10:51:39 -0500 Subject: [PATCH 189/211] [skip ci] Remove unused imageBadgeOnClick --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 6 ------ app/assets/javascripts/image_diff/helpers/index.js | 1 - 2 files changed, 7 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 4b51a4e7005f..25eb5b725101 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -41,12 +41,6 @@ export function addImageCommentBadge(containerEl, { coordinate, noteId }) { centerButtonToCoordinate(buttonEl, coordinate); } -export function imageBadgeOnClick(event) { - event.stopPropagation(); - const badge = event.currentTarget; - window.location.hash = badge.dataset.noteId; -} - export function addAvatarBadge(el, event) { const { noteId, badgeNumber } = event.detail; diff --git a/app/assets/javascripts/image_diff/helpers/index.js b/app/assets/javascripts/image_diff/helpers/index.js index ba7f1a4d4257..752c3a98bf25 100644 --- a/app/assets/javascripts/image_diff/helpers/index.js +++ b/app/assets/javascripts/image_diff/helpers/index.js @@ -11,7 +11,6 @@ export default { addImageBadge: badgeHelper.addImageBadge, addImageCommentBadge: badgeHelper.addImageCommentBadge, - imageBadgeOnClick: badgeHelper.imageBadgeOnClick, addAvatarBadge: badgeHelper.addAvatarBadge, setPositionDataAttribute: domHelper.setPositionDataAttribute, -- 2.22.0 From dc6b856bca4da5cf0e58cde52d6d068e6123b198 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 11:08:10 -0500 Subject: [PATCH 190/211] [skip ci] set className instead of classList --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 25eb5b725101..c1d1d6f21510 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -32,8 +32,7 @@ export function addImageCommentBadge(containerEl, { coordinate, noteId }) { buttonEl.setAttribute('disabled', true); const iconEl = document.createElement('i'); - iconEl.classList.add('fa'); - iconEl.classList.add('fa-comment-o'); + iconEl.className = 'fa fa-comment-o'; iconEl.setAttribute('aria-label', 'comment'); buttonEl.appendChild(iconEl); -- 2.22.0 From 290833c03902df3ab86b742fcd43443cbc7233e4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 11:12:25 -0500 Subject: [PATCH 191/211] [skip ci] keep button disable DRY --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index c1d1d6f21510..06b3f92f26c5 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -3,6 +3,7 @@ export function createImageBadge(noteId, classNames = []) { const classList = classNames.concat(['btn-transparent', 'js-image-badge']); classList.forEach(className => buttonEl.classList.add(className)); buttonEl.setAttribute('type', 'button'); + buttonEl.setAttribute('disabled', true); buttonEl.dataset.noteId = noteId; return buttonEl; @@ -21,7 +22,6 @@ export function centerButtonToCoordinate(buttonEl, coordinate) { export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { const buttonEl = createImageBadge(noteId, ['badge']); buttonEl.innerText = badgeText; - buttonEl.setAttribute('disabled', true); containerEl.appendChild(buttonEl); centerButtonToCoordinate(buttonEl, coordinate); @@ -29,8 +29,6 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) { const buttonEl = createImageBadge(noteId, ['image-comment-badge', 'inverted']); - buttonEl.setAttribute('disabled', true); - const iconEl = document.createElement('i'); iconEl.className = 'fa fa-comment-o'; iconEl.setAttribute('aria-label', 'comment'); -- 2.22.0 From 465b4ba04dfce7094092d91ae72762357fa541ce Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 11:25:06 -0500 Subject: [PATCH 192/211] [skip ci] Use css transform to center image --- .../image_diff/helpers/badge_helper.js | 20 +++++-------------- app/assets/stylesheets/pages/diff.scss | 1 + 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 06b3f92f26c5..cac0c08327d9 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -1,41 +1,31 @@ -export function createImageBadge(noteId, classNames = []) { +export function createImageBadge(noteId, coordinate, classNames = []) { const buttonEl = document.createElement('button'); const classList = classNames.concat(['btn-transparent', 'js-image-badge']); classList.forEach(className => buttonEl.classList.add(className)); buttonEl.setAttribute('type', 'button'); buttonEl.setAttribute('disabled', true); buttonEl.dataset.noteId = noteId; + buttonEl.style.left = `${x}px`; + buttonEl.style.top = `${y}px`; return buttonEl; } -export function centerButtonToCoordinate(buttonEl, coordinate) { - const { x, y } = coordinate; - const updatedButtonEl = buttonEl; - - const { width, height } = updatedButtonEl.getBoundingClientRect(); - // Set button center to be the center of the clicked position - updatedButtonEl.style.left = `${x - (width * 0.5)}px`; - updatedButtonEl.style.top = `${y - (height * 0.5)}px`; -} - export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { - const buttonEl = createImageBadge(noteId, ['badge']); + const buttonEl = createImageBadge(noteId, coordinate, ['badge']); buttonEl.innerText = badgeText; containerEl.appendChild(buttonEl); - centerButtonToCoordinate(buttonEl, coordinate); } export function addImageCommentBadge(containerEl, { coordinate, noteId }) { - const buttonEl = createImageBadge(noteId, ['image-comment-badge', 'inverted']); + const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']); const iconEl = document.createElement('i'); iconEl.className = 'fa fa-comment-o'; iconEl.setAttribute('aria-label', 'comment'); buttonEl.appendChild(iconEl); containerEl.appendChild(buttonEl); - centerButtonToCoordinate(buttonEl, coordinate); } export function addAvatarBadge(el, event) { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index e287db0deab6..d5f5131a6992 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -732,6 +732,7 @@ min-height: $gl-padding; padding: 5px 8px; border-radius: 12px; + transform: translate3d(-50%, -50%, 0); &:focus { outline: none; -- 2.22.0 From 3761c888d9c499715835f2e104be0b2ff29d0cf2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 11:28:26 -0500 Subject: [PATCH 193/211] [skip ci] extract {x,y} from coordinate --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 2 +- .../javascripts/image_diff/helpers/comment_indicator_helper.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index cac0c08327d9..9797328ed8d3 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -1,4 +1,4 @@ -export function createImageBadge(noteId, coordinate, classNames = []) { +export function createImageBadge(noteId, { x, y }, classNames = []) { const buttonEl = document.createElement('button'); const classList = classNames.concat(['btn-transparent', 'js-image-badge']); classList.forEach(className => buttonEl.classList.add(className)); diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index 17b0f88e6d8e..09717e4bfd6f 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -1,5 +1,4 @@ -export function addCommentIndicator(containerEl, coordinate) { - const { x, y } = coordinate; +export function addCommentIndicator(containerEl, { x, y }) { const buttonEl = document.createElement('button'); buttonEl.classList.add('btn-transparent'); buttonEl.classList.add('comment-indicator'); -- 2.22.0 From 1cdbd98fb2309cdca5050ed97629db9bbdce9a78 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 11:41:39 -0500 Subject: [PATCH 194/211] [skip ci] center badge for frames only --- app/assets/stylesheets/pages/diff.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index d5f5131a6992..6880d3fba914 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -732,13 +732,17 @@ min-height: $gl-padding; padding: 5px 8px; border-radius: 12px; - transform: translate3d(-50%, -50%, 0); &:focus { outline: none; } } +.frame .badge { + // Center align badges on the frame + transform: translate3d(-50%, -50%, 0); +} + .image-comment-badge { @include btn-comment-icon; position: absolute; -- 2.22.0 From f824434ab3ffb819e0acc94f8620ae946061088f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 13:54:54 -0500 Subject: [PATCH 195/211] [skip ci] Use gitlab-svg to add image-comment-dark --- app/assets/images/icon_image_comment_dark.svg | 1 - .../helpers/comment_indicator_helper.js | 6 +----- .../stylesheets/framework/variables.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 20 ++++++++----------- 4 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 app/assets/images/icon_image_comment_dark.svg diff --git a/app/assets/images/icon_image_comment_dark.svg b/app/assets/images/icon_image_comment_dark.svg deleted file mode 100644 index becd5a83d374..000000000000 --- a/app/assets/images/icon_image_comment_dark.svg +++ /dev/null @@ -1 +0,0 @@ -cursor_active diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index 09717e4bfd6f..befea8a878f1 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -6,12 +6,8 @@ export function addCommentIndicator(containerEl, { x, y }) { buttonEl.style.left = `${x}px`; buttonEl.style.top = `${y}px`; - const imageEl = document.createElement('img'); - imageEl.classList.add('image-comment-dark'); - imageEl.src = '/assets/icon_image_comment_dark.svg'; - imageEl.alt = 'comment indicator'; + buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark'); - buttonEl.appendChild(imageEl); containerEl.appendChild(buttonEl); } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 94d9c3fda83c..f654bba5280c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -704,4 +704,4 @@ $java: #70ad51; Image Commenting cursor */ $image-comment-cursor-left-offset: 12; -$image-comment-cursor-top-offset: 28; +$image-comment-cursor-top-offset: 30; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 6880d3fba914..bd2fb9de3be5 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -705,19 +705,15 @@ .comment-indicator { position: absolute; padding: 0; + width: (2px * $image-comment-cursor-left-offset); + height: (1px * $image-comment-cursor-top-offset); + // center the indicator to match the top left click region + margin-top: (-1px * $image-comment-cursor-top-offset) + 2; + margin-left: (-1px * $image-comment-cursor-left-offset) + 1; - .image-comment-dark { - // Override styling set by .image .frame - background-image: inherit; - border: none; - background-position: inherit; - background-size: inherit; - max-width: inherit; - - // Compensate for cursor offsets - position: absolute; - left: (-1px * $image-comment-cursor-left-offset); - top: (-1px * $image-comment-cursor-top-offset); + svg { + width: 100%; + height: 100%; } } } -- 2.22.0 From a20eb9ed109459d760e85854128df5e4d0ddc972 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 14:06:42 -0500 Subject: [PATCH 196/211] [skip ci] Fix map() --- app/assets/javascripts/image_diff/helpers/dom_helper.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index 5fd3ca243b59..c694b8a0dc84 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -14,13 +14,15 @@ export function setPositionDataAttribute(el, options) { } export function updateAvatarBadgeNumber(discussionEl, newBadgeNumber) { - const avatarBadges = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); + let avatarBadgeEls = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); - [].map.call(avatarBadges, avatarBadge => - Object.assign(avatarBadge, { + avatarBadgeEls = [...avatarBadgeEls].map(avatarBadgeEl => + Object.assign(avatarBadgeEl, { innerText: newBadgeNumber, }), ); + + return avatarBadgeEls; } export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { -- 2.22.0 From bce3f9a880428cdb485d7ffe10288b9bf2a1c60c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 14:25:53 -0500 Subject: [PATCH 197/211] [skip ci] Remove unused btn-transparent --- app/assets/javascripts/image_diff/helpers/badge_helper.js | 2 +- app/assets/stylesheets/pages/diff.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 9797328ed8d3..6a6a668308da 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -1,6 +1,6 @@ export function createImageBadge(noteId, { x, y }, classNames = []) { const buttonEl = document.createElement('button'); - const classList = classNames.concat(['btn-transparent', 'js-image-badge']); + const classList = classNames.concat(['js-image-badge']); classList.forEach(className => buttonEl.classList.add(className)); buttonEl.setAttribute('type', 'button'); buttonEl.setAttribute('disabled', true); diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index bd2fb9de3be5..8b0fb4aee98d 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -715,6 +715,10 @@ width: 100%; height: 100%; } + + &:focus { + outline: none; + } } } -- 2.22.0 From 33112651cf47713eeba07cbdb2bc9cd45ae95d01 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 4 Oct 2017 15:19:34 -0500 Subject: [PATCH 198/211] [skip ci] Only render avatar badge for the first author of each discussion --- .../image_diff/helpers/dom_helper.js | 13 ++--- .../javascripts/image_diff/helpers/index.js | 2 +- .../javascripts/image_diff/image_diff.js | 4 +- app/assets/javascripts/notes.js | 51 ++----------------- app/views/shared/notes/_note.html.haml | 3 +- 5 files changed, 10 insertions(+), 63 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index c694b8a0dc84..547ce745b2a8 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -13,16 +13,9 @@ export function setPositionDataAttribute(el, options) { el.setAttribute('data-position', JSON.stringify(positionObject)); } -export function updateAvatarBadgeNumber(discussionEl, newBadgeNumber) { - let avatarBadgeEls = discussionEl.querySelectorAll('.image-diff-avatar-link .badge'); - - avatarBadgeEls = [...avatarBadgeEls].map(avatarBadgeEl => - Object.assign(avatarBadgeEl, { - innerText: newBadgeNumber, - }), - ); - - return avatarBadgeEls; +export function updateDiscussionAvatarBadgeNumber(discussionEl, newBadgeNumber) { + const avatarBadgeEl = discussionEl.querySelector('.image-diff-avatar-link .badge'); + avatarBadgeEl.innerText = newBadgeNumber; } export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) { diff --git a/app/assets/javascripts/image_diff/helpers/index.js b/app/assets/javascripts/image_diff/helpers/index.js index 752c3a98bf25..4a1006310036 100644 --- a/app/assets/javascripts/image_diff/helpers/index.js +++ b/app/assets/javascripts/image_diff/helpers/index.js @@ -14,7 +14,7 @@ export default { addAvatarBadge: badgeHelper.addAvatarBadge, setPositionDataAttribute: domHelper.setPositionDataAttribute, - updateAvatarBadgeNumber: domHelper.updateAvatarBadgeNumber, + updateDiscussionAvatarBadgeNumber: domHelper.updateDiscussionAvatarBadgeNumber, updateDiscussionBadgeNumber: domHelper.updateDiscussionBadgeNumber, toggleCollapsed: domHelper.toggleCollapsed, diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 42b6676abf72..64af534eeb43 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -25,7 +25,6 @@ export default class ImageDiff { this.imageClickedWrapper = this.imageClicked.bind(this); this.imageBlurredWrapper = this.imageBlurred.bind(this); this.addBadgeWrapper = this.addBadge.bind(this); - this.addAvatarBadgeWrapper = imageDiffHelper.addAvatarBadge.bind(null, this.el); this.removeBadgeWrapper = this.removeBadge.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); @@ -44,7 +43,6 @@ export default class ImageDiff { this.el.addEventListener('click.imageDiff', this.imageClickedWrapper); this.el.addEventListener('blur.imageDiff', this.imageBlurredWrapper); this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper); - this.el.addEventListener('addAvatarBadge.imageDiff', this.addAvatarBadgeWrapper); this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper); } } @@ -138,7 +136,7 @@ export default class ImageDiff { imageBadgeEls[index].innerText = updatedBadgeNumber; imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber); - imageDiffHelper.updateAvatarBadgeNumber(discussionEl, updatedBadgeNumber); + imageDiffHelper.updateDiscussionAvatarBadgeNumber(discussionEl, updatedBadgeNumber); } }); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index b07ef868ea82..3935407f6616 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -461,24 +461,6 @@ export default class Notes { gl.diffNotesCompileComponents(); this.renderDiscussionAvatar(diffAvatarContainer, noteEntity); - - if (noteEntity.on_image) { - const $noteEl = $(`.note-row-${noteEntity.id}:visible`); - const $notesContainer = $noteEl.closest('.notes'); - const $badge = $notesContainer.find('.badge.js-diff-notes-toggle'); - const badgeNumber = parseInt($badge.text().trim(), 10); - - // Only add avatar badge if the badge exists - // as there are no avatar badges for discussion tab - if ($badge.length > 0) { - imageDiffHelper.addAvatarBadge($notesContainer[0], { - detail: { - badgeNumber, - noteId: `note_${noteEntity.id}` - } - }); - } - } } gl.utils.localTimeAgo($('.js-timeago'), false); @@ -793,7 +775,6 @@ export default class Notes { $note = $(el); $notes = $note.closest('.discussion-notes'); const discussionId = $('.notes', $notes).data('discussion-id'); - const badgeNumber = parseInt($note.find('.image-diff-avatar-link .badge').text(), 10); if (typeof gl.diffNotesCompileComponents !== 'undefined') { if (gl.diffNoteApps[noteElId]) { @@ -819,7 +800,8 @@ export default class Notes { if ($diffFile.length > 0) { const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', { detail: { - badgeNumber, + // badgeNumber's start with 1 and index starts with 0 + badgeNumber: $notes.index() + 1, }, }); @@ -1534,22 +1516,7 @@ export default class Notes { const isNewDiffComment = $notesContainer.length === 0; this.addDiscussionNote($form, note, isNewDiffComment); - if (!isNewDiffComment) { - // Note's added to existing discussions do not preload with avatar badge counts - // Use $notesContainer to locate .diff-file because $form does not have parent - $diffFile = $notesContainer.closest('.diff-file'); - if ($diffFile.length > 0) { - const badgeNumber = parseInt($notesContainer.find('.badge.js-diff-notes-toggle').text().trim(), 10); - const addAvatarBadgeEvent = new CustomEvent('addAvatarBadge.imageDiff', { - detail: { - noteId: `note_${note.id}`, - badgeNumber, - }, - }); - - $diffFile[0].dispatchEvent(addAvatarBadgeEvent); - } - } else { + if (isNewDiffComment) { // Add image badge, avatar badge and toggle discussion badge for new image diffs if ($diffFile.length > 0) { const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position').val()); @@ -1657,18 +1624,6 @@ export default class Notes { .then((note) => { // Submission successful! render final note element this.updateNote(note, $editingNote); - - if ($diffFile.length > 0) { - const badgeNumber = parseInt($notesContainer.find('.badge').text().trim(), 10); - const addAvatarBadgeEvent = new CustomEvent('addAvatarBadge.imageDiff', { - detail: { - noteId: `note_${note.id}`, - badgeNumber, - }, - }); - - $diffFile[0].dispatchEvent(addAvatarBadgeEvent); - } }) .fail(() => { // Submission failed, revert back to original note diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 73494f8464b4..b6085fd3af03 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -3,6 +3,7 @@ - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) - note_editable = note_editable?(note) +- note_counter = local_assigns.fetch(:note_counter, 0) %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], @@ -21,7 +22,7 @@ -# Only show this for the first comment in the discussion %span.image-comment-badge.inverted = icon('comment-o') - - else + - elsif note_counter == 0 - counter = badge_counter if local_assigns[:badge_counter] - badge_class = "hidden" if @fresh_discussion || counter.nil? %span.badge{ class: badge_class } -- 2.22.0 From 19c7e0ee867ed14f2d95f26e3bbf59c7e83916eb Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 4 Oct 2017 19:35:13 -0300 Subject: [PATCH 199/211] Add more backend specs --- app/models/concerns/discussion_on_diff.rb | 5 +- app/models/note.rb | 4 +- .../projects/diffs/viewers/_image.html.haml | 2 +- .../diff/formatters/image_formatter_spec.rb | 20 ++++++++ .../diff/formatters/text_formatter_spec.rb | 42 ++++++++++++++++ spec/lib/gitlab/diff/position_spec.rb | 31 ++++++++++-- spec/models/diff_note_spec.rb | 33 +++++++++++- spec/models/note_spec.rb | 50 +++++++++++++++++++ .../shared_examples/position_formatters.rb | 43 ++++++++++++++++ 9 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 spec/lib/gitlab/diff/formatters/image_formatter_spec.rb create mode 100644 spec/lib/gitlab/diff/formatters/text_formatter_spec.rb create mode 100644 spec/support/shared_examples/position_formatters.rb diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb index 3ac2ab911fae..f5cbb3becada 100644 --- a/app/models/concerns/discussion_on_diff.rb +++ b/app/models/concerns/discussion_on_diff.rb @@ -16,7 +16,6 @@ module DiscussionOnDiff to: :first_note delegate :file_path, - :file_identifier, :blob, :highlighted_diff_lines, :diff_lines, @@ -29,6 +28,10 @@ module DiscussionOnDiff true end + def file_new_path + first_note.position.new_path + end + # Returns an array of at most 16 highlighted lines above a diff note def truncated_diff_lines(highlight: true) lines = highlight ? highlighted_diff_lines : diff_lines diff --git a/app/models/note.rb b/app/models/note.rb index 3562105c1d68..ceded9f2aefb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -134,7 +134,7 @@ class Note < ActiveRecord::Base Discussion.build(notes) end - # Group diff discussions by line code or Diff::File#file_identifier. + # Group diff discussions by line code or file path. # It is not needed to group by line code when comment is # on an image. def grouped_diff_discussions(diff_refs = nil) @@ -143,7 +143,7 @@ class Note < ActiveRecord::Base diff_notes.fresh.discussions.each do |discussion| group_key = if discussion.on_image? - discussion.file_identifier + discussion.file_new_path else discussion.line_code_in_diffs(diff_refs) end diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index cf0783e26f33..f190073c2fcd 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -1,6 +1,6 @@ - diff_file = viewer.diff_file - image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil) -- discussions = @grouped_diff_discussions[diff_file.file_identifier] if @grouped_diff_discussions +- discussions = @grouped_diff_discussions[diff_file.new_path] if @grouped_diff_discussions - locals = { diff_file: diff_file, position: diff_file.position(image_point, position_type: :image).to_json, click_to_comment: true, diff_view_data: diff_view_data } diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb new file mode 100644 index 000000000000..9f2a2512f860 --- /dev/null +++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Diff::Formatters::ImageFormatter do + it_behaves_like "position formatter" do + let(:base_attrs) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_image.png', + new_path: 'new_image.png', + position_type: 'image' + } + end + + let(:attrs) do + base_attrs.merge(width: 100, height: 100, x_axis: 1, y_axis: 2) + end + end +end diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb new file mode 100644 index 000000000000..44f731d501c1 --- /dev/null +++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Gitlab::Diff::Formatters::TextFormatter do + let!(:base) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_path.txt', + new_path: 'new_path.txt' + } + end + + let!(:complete) do + base.merge(old_line: 1, new_line: 2) + end + + it_behaves_like "position formatter" do + let(:base_attrs) { base } + + let(:attrs) { complete } + end + + # Especific text formatter examples + let!(:formatter) { described_class.new(attrs) } + + describe '#line_age' do + subject { formatter.line_age } + + context ' when there is only new_line' do + let(:attrs) { base.merge(new_line: 1) } + + it { is_expected.to eq('new') } + end + + context ' when there is only old_line' do + let(:attrs) { base.merge(old_line: 1) } + + it { is_expected.to eq('old') } + end + end +end diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 0db96645319f..e05c3718adcc 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Diff::Position do let(:project) { create(:project, :repository) } - describe "position for an added file" do + describe "position for an added text file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } subject do @@ -47,6 +47,31 @@ describe Gitlab::Diff::Position do end end + describe "position for an added image file" do + let(:commit) { project.commit("33f3729a45c02fc67d00adb1b8bca394b0e761d9") } + + subject do + described_class.new( + old_path: "files/images/6049019_460s.jpg", + new_path: "files/images/6049019_460s.jpg", + width: 100, + height: 100, + x_axis: 1, + y_axis: 100, + diff_refs: commit.diff_refs, + position_type: "image" + ) + end + + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.new_file?).to be true + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + describe "position for a changed file" do let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } @@ -508,8 +533,8 @@ describe Gitlab::Diff::Position do width: 100, height: 100, x_axis: 1, - y_axis: 100 - position_type: "image", + y_axis: 100, + position_type: "image" } end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 119e81b0a21c..e50e5dde38e0 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe DiffNote do include RepoHelpers - let(:merge_request) { create(:merge_request) } + let!(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } let(:commit) { project.commit(sample_commit.id) } @@ -257,5 +257,36 @@ describe DiffNote do end describe "image diff notes" do + let(:path) { "files/images/any_image.png" } + + let!(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 10, + height: 10, + x_axis: 1, + y_axis: 1, + diff_refs: merge_request.diff_refs, + position_type: "image" + ) + end + + describe "validations" do + subject { build(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + + it { is_expected.not_to validate_presence_of(:line_code) } + + it "does not validate diff line" do + diff_line = subject.diff_line + + expect(diff_line).to be nil + expect(subject).to be_valid + end + end + + it "returns true for on_image?" do + expect(subject.on_image?).to be_truthy + end end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index b214074fdcef..76cb67c718ca 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -314,6 +314,56 @@ describe Note do expect(subject[active_diff_note1.line_code].first.id).to eq(active_diff_note1.discussion_id) expect(subject[active_diff_note3.line_code].first.id).to eq(active_diff_note3.discussion_id) end + + context 'with image discussions' do + let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images and changes") } + let(:image_path) { "files/images/ee_repo_logo.png" } + let(:text_path) { "bar/branch-test.txt" } + let!(:image_note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position) } + let!(:text_note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: text_position) } + + let(:image_position) do + Gitlab::Diff::Position.new( + old_path: image_path, + new_path: image_path, + width: 100, + height: 100, + x_axis: 1, + y_axis: 1, + position_type: "image", + diff_refs: merge_request2.diff_refs + ) + end + + let(:text_position) do + Gitlab::Diff::Position.new( + old_path: text_path, + new_path: text_path, + old_line: nil, + new_line: 2, + position_type: "text", + diff_refs: merge_request2.diff_refs + ) + end + + it "groups image discussions by file identifier" do + diff_discussion = DiffDiscussion.new([image_note]) + + discussions = merge_request2.notes.grouped_diff_discussions + + expect(discussions.size).to eq(2) + expect(discussions[image_note.diff_file.new_path]).to include(diff_discussion) + end + + it "groups text discussions by line code" do + diff_discussion = DiffDiscussion.new([text_note]) + + discussions = merge_request2.notes.grouped_diff_discussions + + expect(discussions.size).to eq(2) + expect(discussions[text_note.line_code]).to include(diff_discussion) + end + end end context 'diff discussions for older diff refs' do diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb new file mode 100644 index 000000000000..ffc9456dbc7c --- /dev/null +++ b/spec/support/shared_examples/position_formatters.rb @@ -0,0 +1,43 @@ +shared_examples_for "position formatter" do + let(:formatter) { described_class.new(attrs) } + + describe '#key' do + let(:key) { [123, 456, 789, Digest::SHA1.hexdigest(formatter.old_path), Digest::SHA1.hexdigest(formatter.new_path), 1, 2] } + + subject { formatter.key } + + it { is_expected.to eq(key) } + end + + describe '#complete?' do + subject { formatter.complete? } + + context 'when there are missing key attributes' do + it { is_expected.to be_truthy } + end + + context 'when old_line and new_line are nil' do + let(:attrs) { base_attrs } + + it { is_expected.to be_falsy } + end + end + + describe '#to_h' do + let(:formatter_hash) do + attrs.merge(position_type: base_attrs[:position_type] || 'text' ) + end + + subject { formatter.to_h } + + it { is_expected.to eq(formatter_hash) } + end + + describe '#==' do + subject { formatter } + + let(:other_formatter) { described_class.new(attrs) } + + it { is_expected.to eq(other_formatter) } + end +end -- 2.22.0 From 56b708b6bad0aef9a941e16f514b2b8a37c8b770 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Oct 2017 13:01:37 -0300 Subject: [PATCH 200/211] fix feature spec --- spec/features/merge_requests/diffs_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index e9068f722d58..f4ba660ca39e 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -42,8 +42,12 @@ feature 'Diffs URL', js: true do visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}" end - it 'shows expanded note' do - expect(page).to have_selector(fragment, visible: true) + it 'shows collapsed note' do + wait_for_requests + + expect(page).to have_selector('.discussion-notes.collapsed') do |note_container| + expect(note_container).to have_selector(fragment, visible: false) + end end end end -- 2.22.0 From 5ff26df01bf99a36b4712c52effa596d0205a5a3 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Oct 2017 15:26:29 -0300 Subject: [PATCH 201/211] fix feature spec --- spec/features/merge_requests/user_posts_diff_notes_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb index 2fb6d0b965f0..18e4b274edea 100644 --- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb @@ -194,6 +194,7 @@ feature 'Merge requests > User posts diff notes', :js do before do merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) visit diffs_project_merge_request_path(project, merge_request, view: 'inline') + wait_for_requests end context 'with a new line' do -- 2.22.0 From 28f8f8fcb37a93fa87e2a896aacc2873cd7790f0 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Oct 2017 21:21:27 -0300 Subject: [PATCH 202/211] Add feature specs --- .../merge_requests/image_diff_notes.rb | 124 ++++++++++-------- 1 file changed, 69 insertions(+), 55 deletions(-) diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index cdc48ea3e2c2..f0adf86ea625 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -1,19 +1,23 @@ require 'spec_helper' -feature 'Diff note avatars', js: true do +feature 'image diff notes', js: true do include NoteInteractionHelpers - let(:user) { create(:user) } - let(:project) { create(:project, :public, :repository) } + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } before do project.team << [user, :master] sign_in user page.driver.set_cookie('sidebar_collapsed', 'true') + + # Stub helper to return any blob file as image from public app folder. + # This is necessary to run this specs since we don't display repo images in capybara. + allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png') end - context 'commit view', focus: true do + context 'create commit diff notes', focus: true do commit_id = '2f63565e7aac07bcdadb654e253078b727143ec4' describe 'create a new diff note' do @@ -34,42 +38,53 @@ feature 'Diff note avatars', js: true do expect(badge).to have_content('1') end - it 'allows collapsing the discussion notes' do - find('.js-diff-notes-toggle').click + it 'allows collapsing/expanding the discussion notes' do + find('.js-diff-notes-toggle', :first).click expect(page).not_to have_content('image diff test comment') - end - it 'allows expanding discussion notes' do - find('.js-diff-notes-toggle').click find('.js-diff-notes-toggle').click expect(page).to have_content('image diff test comment') end end - describe 'render diff notes' do - let(:path) { "files/images/6049019_460s.jpg" } + describe 'render commit diff notes' do + let(:path) { "files/images/6049019_460s.jpg" } + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + + let(:note1_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x_axis: 10, + y_axis: 10, + position_type: "image", + diff_refs: commit.diff_refs + ) + end - # move into MR context let(:note2_position) do Gitlab::Diff::Position.new( old_path: path, new_path: path, width: 100, height: 100, - x_axis: 1, - y_axis: 1, + x_axis: 20, + y_axis: 20, position_type: "image", - diff_refs: commit_id + diff_refs: commit.diff_refs ) end - # Need to scope these notes to the diff - let!(:note2) { create(:note_on_commit, commit_id: commit_id, project: project, position: :note2_position, note: 'my note 2') } + let!(:note1) { create(:diff_note_on_commit, commit_id: commit.id, project: project, position: note1_position, note: 'my note 1') } + let!(:note2) { create(:diff_note_on_commit, commit_id: commit.id, project: project, position: note2_position, note: 'my note 2') } before do - visit project_commit_path(project, commit_id) + visit project_commit_path(project, commit.id) + wait_for_requests end it 'render diff indicators within the image diff frame' do @@ -81,16 +96,15 @@ feature 'Diff note avatars', js: true do end it 'shows the diff notes with correct avatar badge numbers' do - first_note_avatar = find('.image-diff-avatar-link', match: :first) - - expect(first_note_avatar).to have_content("1") + expect(page).to have_css('.image-diff-avatar-link', text: 1) + expect(page).to have_css('.image-diff-avatar-link', text: 2) end end end %w(inline parallel).each do |view| context "#{view} view" do - let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user, title: "Added images and changes") } + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } let(:path) { "files/images/ee_repo_logo.png" } let(:position) do @@ -126,56 +140,56 @@ feature 'Diff note avatars', js: true do expect(badge).to have_content('1') end - it 'allows collapsing the discussion notes' do - find('.js-diff-notes-toggle', match: :first).click + it 'allows expanding/collapsing the discussion notes' do + page.all('.js-diff-notes-toggle')[0].trigger('click') + page.all('.js-diff-notes-toggle')[1].trigger('click') expect(page).not_to have_content('image diff test comment') - end - it 'allows expanding discussion notes' do - find('.js-diff-notes-toggle', match: :first).click - find('.js-diff-notes-toggle', match: :first).click + page.all('.js-diff-notes-toggle')[0].trigger('click') + page.all('.js-diff-notes-toggle')[1].trigger('click') expect(page).to have_content('image diff test comment') end end + end + end - describe 'render diff notes' do - before do - # mock a couple separate comments on the image diff - end - - it 'render diff indicators within the image frame' do - end - - it 'shows the diff notes' do - end - - it 'shows the diff notes with correct avatar badge numbers' do - end - end + describe 'discussion tab polling', :js do + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } + let(:path) { "files/images/ee_repo_logo.png" } + + let(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x_axis: 50, + y_axis: 50, + position_type: "image", + diff_refs: merge_request.diff_refs + ) + end - context 'discussion tab' do - before do - visit project_merge_request_path(project, merge_request) - end + before do + visit project_merge_request_path(project, merge_request) + end - it 'shows the image diff frame' do - frame = find('.js-image-frame') - end + it 'render diff indicators within the image frame' do + diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) - it 'shows the indicator on the frame' do - end + wait_for_requests - it 'shows the note with a generic comment icon' do - end - end + expect(page).to have_selector('.image-comment-badge') + expect(page).to have_content(diff_note.note) end end end def create_image_diff_note - find('.js-add-image-diff-note-button').click + find('.js-add-image-diff-note-button', match: :first).click + page.all('.js-add-image-diff-note-button')[0].trigger('click') find('.diff-content .note-textarea').native.send_keys('image diff test comment') click_button 'Comment' wait_for_requests -- 2.22.0 From 2fe7964e82f8ccfc83af561002f425d0d4438b73 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 6 Oct 2017 12:52:20 -0300 Subject: [PATCH 203/211] Code tweaks --- lib/gitlab/diff/position.rb | 2 + lib/gitlab/diff/position_tracer.rb | 8 ++-- .../merge_requests/image_diff_notes.rb | 4 +- .../diff/formatters/text_formatter_spec.rb | 2 +- spec/lib/gitlab/diff/position_spec.rb | 44 +++++++++---------- spec/lib/gitlab/diff/position_tracer_spec.rb | 24 +++++----- 6 files changed, 43 insertions(+), 41 deletions(-) diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index ccc8095e80a2..ab11018ed3a9 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -10,6 +10,8 @@ module Gitlab :base_sha, :start_sha, :head_sha, + :old_line, + :new_line, :position_type, to: :formatter # A position can belong to a text line or to an image coordinate diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb index 4b7e65e84718..b68a16368147 100644 --- a/lib/gitlab/diff/position_tracer.rb +++ b/lib/gitlab/diff/position_tracer.rb @@ -89,7 +89,7 @@ module Gitlab def trace_added_line(ab_position) b_path = ab_position.new_path - b_line = ab_position.formatter.new_line + b_line = ab_position.new_line bd_diff = bd_diffs.diff_file_with_old_path(b_path) @@ -128,7 +128,7 @@ module Gitlab def trace_removed_line(ab_position) a_path = ab_position.old_path - a_line = ab_position.formatter.old_line + a_line = ab_position.old_line ac_diff = ac_diffs.diff_file_with_old_path(a_path) @@ -159,9 +159,9 @@ module Gitlab def trace_unchanged_line(ab_position) a_path = ab_position.old_path - a_line = ab_position.formatter.old_line + a_line = ab_position.old_line b_path = ab_position.new_path - b_line = ab_position.formatter.new_line + b_line = ab_position.new_line ac_diff = ac_diffs.diff_file_with_old_path(a_path) diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index f0adf86ea625..2c9e9b18be51 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -17,7 +17,7 @@ feature 'image diff notes', js: true do allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png') end - context 'create commit diff notes', focus: true do + context 'create commit diff notes' do commit_id = '2f63565e7aac07bcdadb654e253078b727143ec4' describe 'create a new diff note' do @@ -128,7 +128,7 @@ feature 'image diff notes', js: true do create_image_diff_note end - it 'shows indicator badge on image diff'do + it 'shows indicator badge on image diff' do indicator = find('.js-image-badge', match: :first) expect(indicator).to have_content('1') diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb index 44f731d501c1..897dc917f6af 100644 --- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb +++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Diff::Formatters::TextFormatter do let(:attrs) { complete } end - # Especific text formatter examples + # Specific text formatter examples let!(:formatter) { described_class.new(attrs) } describe '#line_age' do diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index e05c3718adcc..66727a9dabe3 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -33,14 +33,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.formatter.new_line) + expect(diff_line.new_line).to eq(subject.new_line) expect(diff_line.text).to eq("+ Created with Sketch.") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 0) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -101,14 +101,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.formatter.new_line) + expect(diff_line.new_line).to eq(subject.new_line) expect(diff_line.text).to eq("+ vars = {") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 15) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 15) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -141,15 +141,15 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.unchanged?).to be true - expect(diff_line.old_line).to eq(subject.formatter.old_line) - expect(diff_line.new_line).to eq(subject.formatter.new_line) + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.new_line).to eq(subject.new_line) expect(diff_line.text).to eq(" unless File.directory?(path)") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, subject.formatter.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -182,14 +182,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.formatter.old_line) + expect(diff_line.old_line).to eq(subject.old_line) expect(diff_line.text).to eq("- options = { chdir: path }") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.formatter.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -226,14 +226,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.formatter.new_line) + expect(diff_line.new_line).to eq(subject.new_line) expect(diff_line.text).to eq("+ new CommitFile(@)") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 5) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 5) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -266,15 +266,15 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.unchanged?).to be true - expect(diff_line.old_line).to eq(subject.formatter.old_line) - expect(diff_line.new_line).to eq(subject.formatter.new_line) + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.new_line).to eq(subject.new_line) expect(diff_line.text).to eq(" $('.files .diff-file').each ->") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, subject.formatter.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -307,14 +307,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.formatter.old_line) + expect(diff_line.old_line).to eq(subject.old_line) expect(diff_line.text).to eq("- new CommitFile(this)") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.formatter.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -350,14 +350,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.formatter.old_line) + expect(diff_line.old_line).to eq(subject.old_line) expect(diff_line.text).to eq("-Copyright (c) 2014 gitlabhq") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.formatter.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -392,14 +392,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(subject.formatter.new_line) + expect(diff_line.new_line).to eq(subject.new_line) expect(diff_line.text).to eq("+testme") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.formatter.new_line, 0) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -440,14 +440,14 @@ describe Gitlab::Diff::Position do diff_line = subject.diff_line(project.repository) expect(diff_line.removed?).to be true - expect(diff_line.old_line).to eq(subject.formatter.old_line) + expect(diff_line.old_line).to eq(subject.old_line) expect(diff_line.text).to eq("- puts 'bar'") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.formatter.old_line) + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 87c78cfe147f..e51387054431 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -377,7 +377,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -401,7 +401,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -504,7 +504,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -529,7 +529,7 @@ describe Gitlab::Diff::PositionTracer do it "returns the new position" do expect_new_position( new_path: old_position.new_path, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -661,8 +661,8 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( old_path: file_name, new_path: new_file_name, - old_line: old_position.formatter.new_line, - new_line: old_position.formatter.new_line + old_line: old_position.new_line, + new_line: old_position.new_line ) end end @@ -1012,7 +1012,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -1037,7 +1037,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -1144,7 +1144,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -1169,7 +1169,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( new_path: old_position.new_path, old_line: nil, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -1251,7 +1251,7 @@ describe Gitlab::Diff::PositionTracer do old_path: old_position.old_path, new_path: old_position.new_path, old_line: nil, - new_line: old_position.formatter.new_line + new_line: old_position.new_line ) end end @@ -1362,7 +1362,7 @@ describe Gitlab::Diff::PositionTracer do expect_new_position( old_path: old_position.old_path, new_path: old_position.new_path, - old_line: old_position.formatter.old_line, + old_line: old_position.old_line, new_line: nil ) end -- 2.22.0 From 8915a6cf4c51bdb668398270c716fb1bc90916d2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Oct 2017 17:09:39 +0000 Subject: [PATCH 204/211] Add javascript specs for image commenting --- .../helpers/comment_indicator_helper.js | 2 +- .../image_diff/helpers/dom_helper.js | 2 +- .../image_diff/helpers/utils_helper.js | 22 +- .../javascripts/image_diff/image_diff.js | 50 ++- .../image_diff/replaced_image_diff.js | 34 +- .../javascripts/image_diff/view_types.js | 2 +- .../javascripts/lib/utils/image_utility.js | 4 +- app/assets/javascripts/notes.js | 2 +- app/assets/stylesheets/pages/diff.scss | 3 +- .../image_diff/helpers/badge_helper_spec.js | 132 +++++++ .../helpers/comment_indicator_helper_spec.js | 139 +++++++ .../image_diff/helpers/dom_helper_spec.js | 118 ++++++ .../image_diff/helpers/utils_helper_spec.js | 208 ++++++++++ .../image_diff/image_badge_spec.js | 84 ++++ .../javascripts/image_diff/image_diff_spec.js | 361 ++++++++++++++++++ .../image_diff/init_discussion_tab_spec.js | 37 ++ spec/javascripts/image_diff/mock_data.js | 35 ++ .../image_diff/replaced_image_diff_spec.js | 312 +++++++++++++++ .../javascripts/image_diff/view_types_spec.js | 24 ++ .../lib/utils/image_utility_spec.js | 32 ++ 20 files changed, 1544 insertions(+), 59 deletions(-) create mode 100644 spec/javascripts/image_diff/helpers/badge_helper_spec.js create mode 100644 spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js create mode 100644 spec/javascripts/image_diff/helpers/dom_helper_spec.js create mode 100644 spec/javascripts/image_diff/helpers/utils_helper_spec.js create mode 100644 spec/javascripts/image_diff/image_badge_spec.js create mode 100644 spec/javascripts/image_diff/image_diff_spec.js create mode 100644 spec/javascripts/image_diff/init_discussion_tab_spec.js create mode 100644 spec/javascripts/image_diff/mock_data.js create mode 100644 spec/javascripts/image_diff/replaced_image_diff_spec.js create mode 100644 spec/javascripts/image_diff/view_types_spec.js create mode 100644 spec/javascripts/lib/utils/image_utility_spec.js diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index befea8a878f1..05000c73052d 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -30,7 +30,7 @@ export function removeCommentIndicator(imageFrameEl) { commentIndicatorEl.remove(); } - return Object.assign(meta, { + return Object.assign({}, meta, { removed: willRemove, }); } diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index 547ce745b2a8..0923e27a7b83 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -3,7 +3,7 @@ export function setPositionDataAttribute(el, options) { // new comment form can use this data for ajax request const { x, y, width, height } = options; const position = el.dataset.position; - const positionObject = Object.assign(JSON.parse(position), { + const positionObject = Object.assign({}, JSON.parse(position), { x_axis: x, y_axis: y, width, diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index d0cd42df0736..c9b747f71308 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -1,6 +1,7 @@ import ImageBadge from '../image_badge'; import ImageDiff from '../image_diff'; import ReplacedImageDiff from '../replaced_image_diff'; +import '../../commit/image_file'; export function resizeCoordinatesToImageElement(imageEl, meta) { const { x, y, width, height } = meta; @@ -76,21 +77,24 @@ export function getTargetSelection(event) { }; } -export function initImageDiff(file, canCreateNote, renderCommentBadge) { +export function initImageDiff(fileEl, canCreateNote, renderCommentBadge) { const options = { canCreateNote, renderCommentBadge, }; + let diff; // ImageFile needs to be invoked before initImageDiff so that badges // can mount to the correct location - new gl.ImageFile(file); // eslint-disable-line no-new - - if (file.querySelector('.diff-file .js-single-image')) { - const imageDiff = new ImageDiff(file, options); - imageDiff.init(); - } else if (file.querySelector('.diff-file .js-replaced-image')) { - const replacedImageDiff = new ReplacedImageDiff(file, options); - replacedImageDiff.init(); + new gl.ImageFile(fileEl); // eslint-disable-line no-new + + if (fileEl.querySelector('.diff-file .js-single-image')) { + diff = new ImageDiff(fileEl, options); + diff.init(); + } else if (fileEl.querySelector('.diff-file .js-replaced-image')) { + diff = new ReplacedImageDiff(fileEl, options); + diff.init(); } + + return diff; } diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index 64af534eeb43..f3af92cf2b06 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -3,13 +3,10 @@ import ImageBadge from './image_badge'; import { isImageLoaded } from '../lib/utils/image_utility'; export default class ImageDiff { - constructor(el, { - canCreateNote = false, - renderCommentBadge = false, - }) { + constructor(el, options) { this.el = el; - this.canCreateNote = canCreateNote; - this.renderCommentBadge = renderCommentBadge; + this.canCreateNote = !!(options && options.canCreateNote); + this.renderCommentBadge = !!(options && options.renderCommentBadge); this.$noteContainer = $('.note-container', this.el); this.imageBadges = []; } @@ -23,7 +20,7 @@ export default class ImageDiff { bindEvents() { this.imageClickedWrapper = this.imageClicked.bind(this); - this.imageBlurredWrapper = this.imageBlurred.bind(this); + this.imageBlurredWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl); this.addBadgeWrapper = this.addBadge.bind(this); this.removeBadgeWrapper = this.removeBadge.bind(this); this.renderBadgesWrapper = this.renderBadges.bind(this); @@ -56,34 +53,31 @@ export default class ImageDiff { imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser); } - imageBlurred() { - return imageDiffHelper.removeCommentIndicator(this.imageFrameEl); - } - renderBadges() { const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes'); + [...discussionsEls].forEach(this.renderBadge.bind(this)); + } - [...discussionsEls].forEach((discussionEl, index) => { - const imageBadge = imageDiffHelper - .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); + renderBadge(discussionEl, index) { + const imageBadge = imageDiffHelper + .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); - this.imageBadges.push(imageBadge); + this.imageBadges.push(imageBadge); - const options = { - coordinate: imageBadge.browser, - noteId: imageBadge.noteId, - }; + const options = { + coordinate: imageBadge.browser, + noteId: imageBadge.noteId, + }; - if (this.renderCommentBadge) { - imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options); - } else { - const numberBadgeOptions = Object.assign(options, { - badgeText: index + 1, - }); + if (this.renderCommentBadge) { + imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options); + } else { + const numberBadgeOptions = Object.assign({}, options, { + badgeText: index + 1, + }); - imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions); - } - }); + imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions); + } } addBadge(event) { diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 5c7e4d5e5a60..4abd13fb4729 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -70,21 +70,23 @@ export default class ReplacedImageDiff extends ImageDiff { // Image_file.js has a fade animation of 200ms for loading the view // Need to wait an additional 250ms for the images to be displayed // on window in order to re-normalize their dimensions - setTimeout(() => { - // Generate badge coordinates on new view - this.renderBadges(); - - // Re-render indicator in new view - if (indicator.removed) { - const normalizedIndicator = imageDiffHelper - .resizeCoordinatesToImageElement(this.imageEl, { - x: indicator.x, - y: indicator.y, - width: indicator.image.width, - height: indicator.image.height, - }); - imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator); - } - }, 250); + setTimeout(this.renderNewView.bind(this, indicator), 250); + } + + renderNewView(indicator) { + // Generate badge coordinates on new view + this.renderBadges(); + + // Re-render indicator in new view + if (indicator.removed) { + const normalizedIndicator = imageDiffHelper + .resizeCoordinatesToImageElement(this.imageEl, { + x: indicator.x, + y: indicator.y, + width: indicator.image.width, + height: indicator.image.height, + }); + imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator); + } } } diff --git a/app/assets/javascripts/image_diff/view_types.js b/app/assets/javascripts/image_diff/view_types.js index 2bcfe05420ae..ab0a595571ff 100644 --- a/app/assets/javascripts/image_diff/view_types.js +++ b/app/assets/javascripts/image_diff/view_types.js @@ -5,5 +5,5 @@ export const viewTypes = { }; export function isValidViewType(validate) { - return Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate); + return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate); } diff --git a/app/assets/javascripts/lib/utils/image_utility.js b/app/assets/javascripts/lib/utils/image_utility.js index f906e595cc77..2977ec821cbb 100644 --- a/app/assets/javascripts/lib/utils/image_utility.js +++ b/app/assets/javascripts/lib/utils/image_utility.js @@ -1,3 +1,5 @@ /* eslint-disable import/prefer-default-export */ -export const isImageLoaded = element => element.complete && element.naturalHeight !== 0; +export function isImageLoaded(element) { + return element.complete && element.naturalHeight !== 0; +} diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 3935407f6616..5898ce65c5b0 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1482,7 +1482,7 @@ export default class Notes { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); - let $diffFile = $form.closest('.diff-file'); + const $diffFile = $form.closest('.diff-file'); if ($diffFile.length > 0) { const blurEvent = new CustomEvent('blur.imageDiff', { detail: e, diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 45995fb21c77..ffb5fc944759 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -750,7 +750,8 @@ } } -.frame .badge { +.frame .badge, +.frame .image-comment-badge { // Center align badges on the frame transform: translate3d(-50%, -50%, 0); } diff --git a/spec/javascripts/image_diff/helpers/badge_helper_spec.js b/spec/javascripts/image_diff/helpers/badge_helper_spec.js new file mode 100644 index 000000000000..fb9c7e590318 --- /dev/null +++ b/spec/javascripts/image_diff/helpers/badge_helper_spec.js @@ -0,0 +1,132 @@ +import * as badgeHelper from '~/image_diff/helpers/badge_helper'; +import * as mockData from '../mock_data'; + +describe('badge helper', () => { + const { coordinate, noteId, badgeText, badgeNumber } = mockData; + let containerEl; + let buttonEl; + + beforeEach(() => { + containerEl = document.createElement('div'); + }); + + describe('createImageBadge', () => { + beforeEach(() => { + buttonEl = badgeHelper.createImageBadge(noteId, coordinate); + }); + + it('should create button', () => { + expect(buttonEl.tagName).toEqual('BUTTON'); + expect(buttonEl.getAttribute('type')).toEqual('button'); + }); + + it('should set disabled attribute', () => { + expect(buttonEl.hasAttribute('disabled')).toEqual(true); + }); + + it('should set noteId', () => { + expect(buttonEl.dataset.noteId).toEqual(noteId); + }); + + it('should set coordinate', () => { + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + + describe('classNames', () => { + it('should set .js-image-badge by default', () => { + expect(buttonEl.className).toEqual('js-image-badge'); + }); + + it('should add additional class names if parameter is passed', () => { + const classNames = ['first-class', 'second-class']; + buttonEl = badgeHelper.createImageBadge(noteId, coordinate, classNames); + + expect(buttonEl.className).toEqual(classNames.concat('js-image-badge').join(' ')); + }); + }); + }); + + describe('addImageBadge', () => { + beforeEach(() => { + badgeHelper.addImageBadge(containerEl, { + coordinate, + badgeText, + noteId, + }); + buttonEl = containerEl.querySelector('button'); + }); + + it('should appends button to container', () => { + expect(buttonEl).toBeDefined(); + }); + + it('should set the badge text', () => { + expect(buttonEl.innerText).toEqual(badgeText); + }); + + it('should set the button coordinates', () => { + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + + it('should set the button noteId', () => { + expect(buttonEl.dataset.noteId).toEqual(noteId); + }); + }); + + describe('addImageCommentBadge', () => { + beforeEach(() => { + badgeHelper.addImageCommentBadge(containerEl, { + coordinate, + noteId, + }); + buttonEl = containerEl.querySelector('button'); + }); + + it('should append icon button to container', () => { + expect(buttonEl).toBeDefined(); + }); + + it('should create icon comment button', () => { + const iconEl = buttonEl.querySelector('i'); + expect(iconEl).toBeDefined(); + expect(iconEl.classList.contains('fa')).toEqual(true); + expect(iconEl.classList.contains('fa-comment-o')).toEqual(true); + }); + + it('should have .image-comment-badge.inverted in button class', () => { + expect(buttonEl.classList.contains('image-comment-badge')).toEqual(true); + expect(buttonEl.classList.contains('inverted')).toEqual(true); + }); + }); + + describe('addAvatarBadge', () => { + let avatarBadgeEl; + + beforeEach(() => { + containerEl.innerHTML = ` +
+ +
+ `; + + badgeHelper.addAvatarBadge(containerEl, { + detail: { + noteId, + badgeNumber, + }, + }); + avatarBadgeEl = containerEl.querySelector(`#${noteId} .badge`); + }); + + it('should update badge number', () => { + expect(avatarBadgeEl.innerText).toEqual(badgeNumber.toString()); + }); + + it('should remove hidden class', () => { + expect(avatarBadgeEl.classList.contains('hidden')).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js new file mode 100644 index 000000000000..a284b981d2a9 --- /dev/null +++ b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js @@ -0,0 +1,139 @@ +import * as commentIndicatorHelper from '~/image_diff/helpers/comment_indicator_helper'; +import * as mockData from '../mock_data'; + +describe('commentIndicatorHelper', () => { + const { coordinate } = mockData; + let containerEl; + + beforeEach(() => { + containerEl = document.createElement('div'); + }); + + describe('addCommentIndicator', () => { + let buttonEl; + + beforeEach(() => { + commentIndicatorHelper.addCommentIndicator(containerEl, coordinate); + buttonEl = containerEl.querySelector('button'); + }); + + it('should append button to container', () => { + expect(buttonEl).toBeDefined(); + }); + + describe('button', () => { + it('should set coordinate', () => { + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + + it('should contain image-comment-dark svg', () => { + const svgEl = buttonEl.querySelector('svg'); + expect(svgEl).toBeDefined(); + + const svgLink = svgEl.querySelector('use').getAttribute('xlink:href'); + expect(svgLink.indexOf('image-comment-dark') !== -1).toEqual(true); + }); + }); + }); + + describe('removeCommentIndicator', () => { + it('should return removed false if there is no comment-indicator', () => { + const result = commentIndicatorHelper.removeCommentIndicator(containerEl); + expect(result.removed).toEqual(false); + }); + + describe('has comment indicator', () => { + let result; + + beforeEach(() => { + containerEl.innerHTML = ` +
+ +
+ `; + result = commentIndicatorHelper.removeCommentIndicator(containerEl); + }); + + it('should remove comment indicator', () => { + expect(containerEl.querySelector('.comment-indicator')).toBeNull(); + }); + + it('should return removed true', () => { + expect(result.removed).toEqual(true); + }); + + it('should return indicator meta', () => { + expect(result.x).toEqual(coordinate.x); + expect(result.y).toEqual(coordinate.y); + expect(result.image).toBeDefined(); + expect(result.image.width).toBeDefined(); + expect(result.image.height).toBeDefined(); + }); + }); + }); + + describe('showCommentIndicator', () => { + describe('commentIndicator exists', () => { + beforeEach(() => { + containerEl.innerHTML = ` + + `; + commentIndicatorHelper.showCommentIndicator(containerEl, coordinate); + }); + + it('should set commentIndicator coordinates', () => { + const commentIndicatorEl = containerEl.querySelector('.comment-indicator'); + expect(commentIndicatorEl.style.left).toEqual(`${coordinate.x}px`); + expect(commentIndicatorEl.style.top).toEqual(`${coordinate.y}px`); + }); + }); + + describe('commentIndicator does not exist', () => { + beforeEach(() => { + commentIndicatorHelper.showCommentIndicator(containerEl, coordinate); + }); + + it('should addCommentIndicator', () => { + const buttonEl = containerEl.querySelector('.comment-indicator'); + expect(buttonEl).toBeDefined(); + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + }); + }); + + describe('commentIndicatorOnClick', () => { + let event; + let textAreaEl; + + beforeEach(() => { + containerEl.innerHTML = ` +
+ +
+ +
+
+ `; + textAreaEl = containerEl.querySelector('textarea'); + + event = { + stopPropagation: () => {}, + currentTarget: containerEl.querySelector('button'), + }; + + spyOn(event, 'stopPropagation'); + spyOn(textAreaEl, 'focus'); + commentIndicatorHelper.commentIndicatorOnClick(event); + }); + + it('should stopPropagation', () => { + expect(event.stopPropagation).toHaveBeenCalled(); + }); + + it('should focus textAreaEl', () => { + expect(textAreaEl.focus).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/image_diff/helpers/dom_helper_spec.js b/spec/javascripts/image_diff/helpers/dom_helper_spec.js new file mode 100644 index 000000000000..f49248dd0eb6 --- /dev/null +++ b/spec/javascripts/image_diff/helpers/dom_helper_spec.js @@ -0,0 +1,118 @@ +import * as domHelper from '~/image_diff/helpers/dom_helper'; +import * as mockData from '../mock_data'; + +describe('domHelper', () => { + const { imageMeta, badgeNumber } = mockData; + + describe('setPositionDataAttribute', () => { + let containerEl; + let attributeAfterCall; + const position = { + myProperty: 'myProperty', + }; + + beforeEach(() => { + containerEl = document.createElement('div'); + containerEl.dataset.position = JSON.stringify(position); + domHelper.setPositionDataAttribute(containerEl, imageMeta); + attributeAfterCall = JSON.parse(containerEl.dataset.position); + }); + + it('should set x, y, width, height', () => { + expect(attributeAfterCall.x_axis).toEqual(imageMeta.x); + expect(attributeAfterCall.y_axis).toEqual(imageMeta.y); + expect(attributeAfterCall.width).toEqual(imageMeta.width); + expect(attributeAfterCall.height).toEqual(imageMeta.height); + }); + + it('should not override other properties', () => { + expect(attributeAfterCall.myProperty).toEqual('myProperty'); + }); + }); + + describe('updateDiscussionAvatarBadgeNumber', () => { + let discussionEl; + + beforeEach(() => { + discussionEl = document.createElement('div'); + discussionEl.innerHTML = ` + +
+
+ `; + domHelper.updateDiscussionAvatarBadgeNumber(discussionEl, badgeNumber); + }); + + it('should update avatar badge number', () => { + expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString()); + }); + }); + + describe('updateDiscussionBadgeNumber', () => { + let discussionEl; + + beforeEach(() => { + discussionEl = document.createElement('div'); + discussionEl.innerHTML = ` +
+ `; + domHelper.updateDiscussionBadgeNumber(discussionEl, badgeNumber); + }); + + it('should update discussion badge number', () => { + expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString()); + }); + }); + + describe('toggleCollapsed', () => { + let element; + let discussionNotesEl; + + beforeEach(() => { + element = document.createElement('div'); + element.innerHTML = ` +
+ +
+
+ `; + discussionNotesEl = element.querySelector('.discussion-notes'); + }); + + describe('not collapsed', () => { + beforeEach(() => { + domHelper.toggleCollapsed({ + currentTarget: element.querySelector('button'), + }); + }); + + it('should add collapsed class', () => { + expect(discussionNotesEl.classList.contains('collapsed')).toEqual(true); + }); + + it('should force formEl to display none', () => { + const formEl = element.querySelector('.discussion-form'); + expect(formEl.style.display).toEqual('none'); + }); + }); + + describe('collapsed', () => { + beforeEach(() => { + discussionNotesEl.classList.add('collapsed'); + + domHelper.toggleCollapsed({ + currentTarget: element.querySelector('button'), + }); + }); + + it('should remove collapsed class', () => { + expect(discussionNotesEl.classList.contains('collapsed')).toEqual(false); + }); + + it('should force formEl to display block', () => { + const formEl = element.querySelector('.discussion-form'); + expect(formEl.style.display).toEqual('block'); + }); + }); + }); +}); diff --git a/spec/javascripts/image_diff/helpers/utils_helper_spec.js b/spec/javascripts/image_diff/helpers/utils_helper_spec.js new file mode 100644 index 000000000000..8de467f3c940 --- /dev/null +++ b/spec/javascripts/image_diff/helpers/utils_helper_spec.js @@ -0,0 +1,208 @@ +import * as utilsHelper from '~/image_diff/helpers/utils_helper'; +import ImageDiff from '~/image_diff/image_diff'; +import ReplacedImageDiff from '~/image_diff/replaced_image_diff'; +import ImageBadge from '~/image_diff/image_badge'; +import * as mockData from '../mock_data'; + +describe('utilsHelper', () => { + const { + noteId, + discussionId, + image, + imageProperties, + imageMeta, + imagePositionAttr, + } = mockData; + + describe('resizeCoordinatesToImageElement', () => { + let result; + + beforeEach(() => { + result = utilsHelper.resizeCoordinatesToImageElement(image, imageMeta); + }); + + it('should return x based on widthRatio', () => { + expect(result.x).toEqual(imageMeta.x * 0.5); + }); + + it('should return y based on heightRatio', () => { + expect(result.y).toEqual(imageMeta.y * 0.5); + }); + + it('should return image width', () => { + expect(result.width).toEqual(image.width); + }); + + it('should return image height', () => { + expect(result.height).toEqual(image.height); + }); + }); + + describe('generateBadgeFromDiscussionDOM', () => { + let discussionEl; + let result; + + beforeEach(() => { + const imageFrameEl = document.createElement('div'); + imageFrameEl.innerHTML = ` + + `; + discussionEl = document.createElement('div'); + discussionEl.dataset.discussionId = discussionId; + discussionEl.innerHTML = ` +
+ `; + discussionEl.dataset.position = JSON.stringify(imagePositionAttr); + result = utilsHelper.generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl); + }); + + it('should return actual image properties', () => { + const { actual } = result; + expect(actual.x).toEqual(imagePositionAttr.x_axis); + expect(actual.y).toEqual(imagePositionAttr.y_axis); + expect(actual.width).toEqual(imagePositionAttr.width); + expect(actual.height).toEqual(imagePositionAttr.height); + }); + + it('should return browser image properties', () => { + const { browser } = result; + expect(browser.x).toBeDefined(); + expect(browser.y).toBeDefined(); + expect(browser.width).toBeDefined(); + expect(browser.height).toBeDefined(); + }); + + it('should return instance of ImageBadge', () => { + expect(result instanceof ImageBadge).toEqual(true); + }); + + it('should return noteId', () => { + expect(result.noteId).toEqual(noteId); + }); + + it('should return discussionId', () => { + expect(result.discussionId).toEqual(discussionId); + }); + }); + + describe('getTargetSelection', () => { + let containerEl; + + beforeEach(() => { + containerEl = { + querySelector: () => imageProperties, + }; + }); + + function generateEvent(offsetX, offsetY) { + return { + currentTarget: containerEl, + offsetX, + offsetY, + }; + } + + it('should return browser properties', () => { + const event = generateEvent(25, 25); + const result = utilsHelper.getTargetSelection(event); + + const { browser } = result; + expect(browser.x).toEqual(event.offsetX); + expect(browser.y).toEqual(event.offsetY); + expect(browser.width).toEqual(imageProperties.width); + expect(browser.height).toEqual(imageProperties.height); + }); + + it('should return resized actual image properties', () => { + const event = generateEvent(50, 50); + const result = utilsHelper.getTargetSelection(event); + + const { actual } = result; + expect(actual.x).toEqual(100); + expect(actual.y).toEqual(100); + expect(actual.width).toEqual(imageProperties.naturalWidth); + expect(actual.height).toEqual(imageProperties.naturalHeight); + }); + + describe('normalize coordinates', () => { + it('should return x = 0 if x < 0', () => { + const event = generateEvent(-5, 50); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.x).toEqual(0); + }); + + it('should return x = width if x > width', () => { + const event = generateEvent(1000, 50); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.x).toEqual(imageProperties.width); + }); + + it('should return y = 0 if y < 0', () => { + const event = generateEvent(50, -10); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.y).toEqual(0); + }); + + it('should return y = height if y > height', () => { + const event = generateEvent(50, 1000); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.y).toEqual(imageProperties.height); + }); + }); + }); + + describe('initImageDiff', () => { + let glCache; + let fileEl; + + beforeEach(() => { + window.gl = window.gl || (window.gl = {}); + glCache = window.gl; + window.gl.ImageFile = () => {}; + fileEl = document.createElement('div'); + fileEl.innerHTML = ` +
+ `; + + spyOn(ImageDiff.prototype, 'init').and.callFake(() => {}); + spyOn(ReplacedImageDiff.prototype, 'init').and.callFake(() => {}); + }); + + afterEach(() => { + window.gl = glCache; + }); + + it('should initialize gl.ImageFile', () => { + spyOn(window.gl, 'ImageFile'); + + utilsHelper.initImageDiff(fileEl, false, false); + expect(gl.ImageFile).toHaveBeenCalled(); + }); + + it('should initialize ImageDiff if js-single-image', () => { + const diffFileEl = fileEl.querySelector('.diff-file'); + diffFileEl.innerHTML = ` +
+
+ `; + + const imageDiff = utilsHelper.initImageDiff(fileEl, true, false); + expect(ImageDiff.prototype.init).toHaveBeenCalled(); + expect(imageDiff.canCreateNote).toEqual(true); + expect(imageDiff.renderCommentBadge).toEqual(false); + }); + + it('should initialize ReplacedImageDiff if js-replaced-image', () => { + const diffFileEl = fileEl.querySelector('.diff-file'); + diffFileEl.innerHTML = ` +
+
+ `; + + const replacedImageDiff = utilsHelper.initImageDiff(fileEl, false, true); + expect(ReplacedImageDiff.prototype.init).toHaveBeenCalled(); + expect(replacedImageDiff.canCreateNote).toEqual(false); + expect(replacedImageDiff.renderCommentBadge).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/image_diff/image_badge_spec.js b/spec/javascripts/image_diff/image_badge_spec.js new file mode 100644 index 000000000000..87f98fc0926d --- /dev/null +++ b/spec/javascripts/image_diff/image_badge_spec.js @@ -0,0 +1,84 @@ +import ImageBadge from '~/image_diff/image_badge'; +import imageDiffHelper from '~/image_diff/helpers/index'; +import * as mockData from './mock_data'; + +describe('ImageBadge', () => { + const { noteId, discussionId, imageMeta } = mockData; + const options = { + noteId, + discussionId, + }; + + it('should save actual property', () => { + const imageBadge = new ImageBadge(Object.assign({}, options, { + actual: imageMeta, + })); + + const { actual } = imageBadge; + expect(actual.x).toEqual(imageMeta.x); + expect(actual.y).toEqual(imageMeta.y); + expect(actual.width).toEqual(imageMeta.width); + expect(actual.height).toEqual(imageMeta.height); + }); + + it('should save browser property', () => { + const imageBadge = new ImageBadge(Object.assign({}, options, { + browser: imageMeta, + })); + + const { browser } = imageBadge; + expect(browser.x).toEqual(imageMeta.x); + expect(browser.y).toEqual(imageMeta.y); + expect(browser.width).toEqual(imageMeta.width); + expect(browser.height).toEqual(imageMeta.height); + }); + + it('should save noteId', () => { + const imageBadge = new ImageBadge(options); + expect(imageBadge.noteId).toEqual(noteId); + }); + + it('should save discussionId', () => { + const imageBadge = new ImageBadge(options); + expect(imageBadge.discussionId).toEqual(discussionId); + }); + + describe('default values', () => { + let imageBadge; + + beforeEach(() => { + imageBadge = new ImageBadge(options); + }); + + it('should return defaultimageMeta if actual property is not provided', () => { + const { actual } = imageBadge; + expect(actual.x).toEqual(0); + expect(actual.y).toEqual(0); + expect(actual.width).toEqual(0); + expect(actual.height).toEqual(0); + }); + + it('should return defaultimageMeta if browser property is not provided', () => { + const { browser } = imageBadge; + expect(browser.x).toEqual(0); + expect(browser.y).toEqual(0); + expect(browser.width).toEqual(0); + expect(browser.height).toEqual(0); + }); + }); + + describe('imageEl property is provided and not browser property', () => { + beforeEach(() => { + spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(true); + }); + + it('should generate browser property', () => { + const imageBadge = new ImageBadge(Object.assign({}, options, { + imageEl: document.createElement('img'), + })); + + expect(imageDiffHelper.resizeCoordinatesToImageElement).toHaveBeenCalled(); + expect(imageBadge.browser).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/image_diff/image_diff_spec.js b/spec/javascripts/image_diff/image_diff_spec.js new file mode 100644 index 000000000000..346282328c76 --- /dev/null +++ b/spec/javascripts/image_diff/image_diff_spec.js @@ -0,0 +1,361 @@ +import ImageDiff from '~/image_diff/image_diff'; +import * as imageUtility from '~/lib/utils/image_utility'; +import imageDiffHelper from '~/image_diff/helpers/index'; +import * as mockData from './mock_data'; + +describe('ImageDiff', () => { + let element; + let imageDiff; + + beforeEach(() => { + setFixtures(` +
+
+
+ +
+
1
+
2
+
3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `); + element = document.getElementById('element'); + }); + + describe('constructor', () => { + beforeEach(() => { + imageDiff = new ImageDiff(element, { + canCreateNote: true, + renderCommentBadge: true, + }); + }); + + it('should set el', () => { + expect(imageDiff.el).toEqual(element); + }); + + it('should set canCreateNote', () => { + expect(imageDiff.canCreateNote).toEqual(true); + }); + + it('should set renderCommentBadge', () => { + expect(imageDiff.renderCommentBadge).toEqual(true); + }); + + it('should set $noteContainer', () => { + expect(imageDiff.$noteContainer[0]).toEqual(element.querySelector('.note-container')); + }); + + describe('default', () => { + beforeEach(() => { + imageDiff = new ImageDiff(element); + }); + + it('should set canCreateNote as false', () => { + expect(imageDiff.canCreateNote).toEqual(false); + }); + + it('should set renderCommentBadge as false', () => { + expect(imageDiff.renderCommentBadge).toEqual(false); + }); + }); + }); + + describe('init', () => { + beforeEach(() => { + spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.init(); + }); + + it('should set imageFrameEl', () => { + expect(imageDiff.imageFrameEl).toEqual(element.querySelector('.diff-file .js-image-frame')); + }); + + it('should set imageEl', () => { + expect(imageDiff.imageEl).toEqual(element.querySelector('.diff-file .js-image-frame img')); + }); + + it('should call bindEvents', () => { + expect(imageDiff.bindEvents).toHaveBeenCalled(); + }); + }); + + describe('bindEvents', () => { + let imageEl; + + beforeEach(() => { + spyOn(imageDiffHelper, 'toggleCollapsed').and.callFake(() => {}); + spyOn(imageDiffHelper, 'commentIndicatorOnClick').and.callFake(() => {}); + spyOn(imageDiffHelper, 'removeCommentIndicator').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'imageClicked').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'addBadge').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'removeBadge').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'renderBadges').and.callFake(() => {}); + imageEl = element.querySelector('.diff-file .js-image-frame img'); + }); + + describe('default', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should register click event delegation to js-diff-notes-toggle', () => { + element.querySelector('.js-diff-notes-toggle').click(); + expect(imageDiffHelper.toggleCollapsed).toHaveBeenCalled(); + }); + + it('should register click event delegation to comment-indicator', () => { + element.querySelector('.comment-indicator').click(); + expect(imageDiffHelper.commentIndicatorOnClick).toHaveBeenCalled(); + }); + }); + + describe('image loaded', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(true); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + }); + + it('should renderBadges', () => {}); + }); + + describe('image not loaded', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should registers load eventListener', () => { + const loadEvent = new Event('load'); + imageEl.dispatchEvent(loadEvent); + expect(imageDiff.renderBadges).toHaveBeenCalled(); + }); + }); + + describe('canCreateNote', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element, { + canCreateNote: true, + }); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should register click.imageDiff event', () => { + const event = new CustomEvent('click.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.imageClicked).toHaveBeenCalled(); + }); + + it('should register blur.imageDiff event', () => { + const event = new CustomEvent('blur.imageDiff'); + element.dispatchEvent(event); + expect(imageDiffHelper.removeCommentIndicator).toHaveBeenCalled(); + }); + + it('should register addBadge.imageDiff event', () => { + const event = new CustomEvent('addBadge.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.addBadge).toHaveBeenCalled(); + }); + + it('should register removeBadge.imageDiff event', () => { + const event = new CustomEvent('removeBadge.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.removeBadge).toHaveBeenCalled(); + }); + }); + + describe('canCreateNote is false', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should not register click.imageDiff event', () => { + const event = new CustomEvent('click.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.imageClicked).not.toHaveBeenCalled(); + }); + }); + }); + + describe('imageClicked', () => { + beforeEach(() => { + spyOn(imageDiffHelper, 'getTargetSelection').and.returnValue({ + actual: {}, + browser: {}, + }); + spyOn(imageDiffHelper, 'setPositionDataAttribute').and.callFake(() => {}); + spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.imageClicked({ + detail: { + currentTarget: {}, + }, + }); + }); + + it('should call getTargetSelection', () => { + expect(imageDiffHelper.getTargetSelection).toHaveBeenCalled(); + }); + + it('should call setPositionDataAttribute', () => { + expect(imageDiffHelper.setPositionDataAttribute).toHaveBeenCalled(); + }); + + it('should call showCommentIndicator', () => { + expect(imageDiffHelper.showCommentIndicator).toHaveBeenCalled(); + }); + }); + + describe('renderBadges', () => { + beforeEach(() => { + spyOn(ImageDiff.prototype, 'renderBadge').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.renderBadges(); + }); + + it('should call renderBadge for each discussionEl', () => { + const discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes'); + expect(imageDiff.renderBadge.calls.count()).toEqual(discussionEls.length); + }); + }); + + describe('renderBadge', () => { + let discussionEls; + + beforeEach(() => { + spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'addImageCommentBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'generateBadgeFromDiscussionDOM').and.returnValue({ + browser: {}, + noteId: 'noteId', + }); + discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes'); + imageDiff = new ImageDiff(element); + imageDiff.renderBadge(discussionEls[0], 0); + }); + + it('should populate imageBadges', () => { + expect(imageDiff.imageBadges.length).toEqual(1); + }); + + describe('renderCommentBadge', () => { + beforeEach(() => { + imageDiff.renderCommentBadge = true; + imageDiff.renderBadge(discussionEls[0], 0); + }); + + it('should call addImageCommentBadge', () => { + expect(imageDiffHelper.addImageCommentBadge).toHaveBeenCalled(); + }); + }); + + describe('renderCommentBadge is false', () => { + it('should call addImageBadge', () => { + expect(imageDiffHelper.addImageBadge).toHaveBeenCalled(); + }); + }); + }); + + describe('addBadge', () => { + beforeEach(() => { + spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'addAvatarBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame'); + imageDiff.addBadge({ + detail: { + x: 0, + y: 1, + width: 25, + height: 50, + noteId: 'noteId', + discussionId: 'discussionId', + }, + }); + }); + + it('should add imageBadge to imageBadges', () => { + expect(imageDiff.imageBadges.length).toEqual(1); + }); + + it('should call addImageBadge', () => { + expect(imageDiffHelper.addImageBadge).toHaveBeenCalled(); + }); + + it('should call addAvatarBadge', () => { + expect(imageDiffHelper.addAvatarBadge).toHaveBeenCalled(); + }); + + it('should call updateDiscussionBadgeNumber', () => { + expect(imageDiffHelper.updateDiscussionBadgeNumber).toHaveBeenCalled(); + }); + }); + + describe('removeBadge', () => { + beforeEach(() => { + const { imageMeta } = mockData; + + spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {}); + spyOn(imageDiffHelper, 'updateDiscussionAvatarBadgeNumber').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.imageBadges = [imageMeta, imageMeta, imageMeta]; + imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame'); + imageDiff.removeBadge({ + detail: { + badgeNumber: 2, + }, + }); + }); + + describe('cascade badge count', () => { + it('should update next imageBadgeEl value', () => { + const imageBadgeEls = imageDiff.imageFrameEl.querySelectorAll('.badge'); + expect(imageBadgeEls[0].innerText).toEqual('1'); + expect(imageBadgeEls[1].innerText).toEqual('2'); + expect(imageBadgeEls.length).toEqual(2); + }); + + it('should call updateDiscussionBadgeNumber', () => { + expect(imageDiffHelper.updateDiscussionBadgeNumber).toHaveBeenCalled(); + }); + + it('should call updateDiscussionAvatarBadgeNumber', () => { + expect(imageDiffHelper.updateDiscussionAvatarBadgeNumber).toHaveBeenCalled(); + }); + }); + + it('should remove badge from imageBadges', () => { + expect(imageDiff.imageBadges.length).toEqual(2); + }); + + it('should remove imageBadgeEl', () => { + expect(imageDiff.imageFrameEl.querySelector('#badge-2')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/image_diff/init_discussion_tab_spec.js b/spec/javascripts/image_diff/init_discussion_tab_spec.js new file mode 100644 index 000000000000..7c447d6f70d0 --- /dev/null +++ b/spec/javascripts/image_diff/init_discussion_tab_spec.js @@ -0,0 +1,37 @@ +import initDiscussionTab from '~/image_diff/init_discussion_tab'; +import imageDiffHelper from '~/image_diff/helpers/index'; + +describe('initDiscussionTab', () => { + beforeEach(() => { + setFixtures(` +
+
+
+
+ `); + }); + + it('should pass canCreateNote as false to initImageDiff', (done) => { + spyOn(imageDiffHelper, 'initImageDiff').and.callFake((diffFileEl, canCreateNote) => { + expect(canCreateNote).toEqual(false); + done(); + }); + + initDiscussionTab(); + }); + + it('should pass renderCommentBadge as true to initImageDiff', (done) => { + spyOn(imageDiffHelper, 'initImageDiff').and.callFake((diffFileEl, canCreateNote, renderCommentBadge) => { + expect(renderCommentBadge).toEqual(true); + done(); + }); + + initDiscussionTab(); + }); + + it('should call initImageDiff for each diffFileEls', () => { + spyOn(imageDiffHelper, 'initImageDiff').and.callFake(() => {}); + initDiscussionTab(); + expect(imageDiffHelper.initImageDiff.calls.count()).toEqual(2); + }); +}); diff --git a/spec/javascripts/image_diff/mock_data.js b/spec/javascripts/image_diff/mock_data.js new file mode 100644 index 000000000000..6b5220470537 --- /dev/null +++ b/spec/javascripts/image_diff/mock_data.js @@ -0,0 +1,35 @@ +export const noteId = 'noteId'; +export const discussionId = 'discussionId'; +export const badgeText = 'badgeText'; +export const badgeNumber = 5; + +export const coordinate = { + x: 100, + y: 100, +}; + +export const image = { + width: 100, + height: 100, +}; + +export const imageProperties = { + width: image.width, + height: image.height, + naturalWidth: image.width * 2, + naturalHeight: image.height * 2, +}; + +export const imageMeta = { + x: coordinate.x, + y: coordinate.y, + width: imageProperties.naturalWidth, + height: imageProperties.naturalHeight, +}; + +export const imagePositionAttr = { + x_axis: coordinate.x, + y_axis: coordinate.y, + width: image.width, + height: image.height, +}; diff --git a/spec/javascripts/image_diff/replaced_image_diff_spec.js b/spec/javascripts/image_diff/replaced_image_diff_spec.js new file mode 100644 index 000000000000..5f8cd7c531a0 --- /dev/null +++ b/spec/javascripts/image_diff/replaced_image_diff_spec.js @@ -0,0 +1,312 @@ +import ReplacedImageDiff from '~/image_diff/replaced_image_diff'; +import ImageDiff from '~/image_diff/image_diff'; +import { viewTypes } from '~/image_diff/view_types'; +import imageDiffHelper from '~/image_diff/helpers/index'; + +describe('ReplacedImageDiff', () => { + let element; + let replacedImageDiff; + + beforeEach(() => { + setFixtures(` +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
2-up
+
Swipe
+
Onion skin
+
+
+ `); + element = document.getElementById('element'); + }); + + function setupImageFrameEls() { + replacedImageDiff.imageFrameEls = []; + replacedImageDiff.imageFrameEls[viewTypes.TWO_UP] = element.querySelector('.two-up .js-image-frame'); + replacedImageDiff.imageFrameEls[viewTypes.SWIPE] = element.querySelector('.swipe .js-image-frame'); + replacedImageDiff.imageFrameEls[viewTypes.ONION_SKIN] = element.querySelector('.onion-skin .js-image-frame'); + } + + function setupViewModesEls() { + replacedImageDiff.viewModesEls = []; + replacedImageDiff.viewModesEls[viewTypes.TWO_UP] = element.querySelector('.view-modes-menu .two-up'); + replacedImageDiff.viewModesEls[viewTypes.SWIPE] = element.querySelector('.view-modes-menu .swipe'); + replacedImageDiff.viewModesEls[viewTypes.ONION_SKIN] = element.querySelector('.view-modes-menu .onion-skin'); + } + + function setupImageEls() { + replacedImageDiff.imageEls = []; + replacedImageDiff.imageEls[viewTypes.TWO_UP] = element.querySelector('.two-up img'); + replacedImageDiff.imageEls[viewTypes.SWIPE] = element.querySelector('.swipe img'); + replacedImageDiff.imageEls[viewTypes.ONION_SKIN] = element.querySelector('.onion-skin img'); + } + + it('should extend ImageDiff', () => { + replacedImageDiff = new ReplacedImageDiff(element); + expect(replacedImageDiff instanceof ImageDiff).toEqual(true); + }); + + describe('init', () => { + beforeEach(() => { + spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(ReplacedImageDiff.prototype, 'generateImageEls').and.callFake(() => {}); + + replacedImageDiff = new ReplacedImageDiff(element); + replacedImageDiff.init(); + }); + + it('should set imageFrameEls', () => { + const { imageFrameEls } = replacedImageDiff; + expect(imageFrameEls).toBeDefined(); + expect(imageFrameEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.two-up .js-image-frame')); + expect(imageFrameEls[viewTypes.SWIPE]).toEqual(element.querySelector('.swipe .js-image-frame')); + expect(imageFrameEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.onion-skin .js-image-frame')); + }); + + it('should set viewModesEls', () => { + const { viewModesEls } = replacedImageDiff; + expect(viewModesEls).toBeDefined(); + expect(viewModesEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.view-modes-menu .two-up')); + expect(viewModesEls[viewTypes.SWIPE]).toEqual(element.querySelector('.view-modes-menu .swipe')); + expect(viewModesEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.view-modes-menu .onion-skin')); + }); + + it('should generateImageEls', () => { + expect(ReplacedImageDiff.prototype.generateImageEls).toHaveBeenCalled(); + }); + + it('should bindEvents', () => { + expect(ReplacedImageDiff.prototype.bindEvents).toHaveBeenCalled(); + }); + + describe('currentView', () => { + it('should set currentView', () => { + replacedImageDiff.init(viewTypes.ONION_SKIN); + expect(replacedImageDiff.currentView).toEqual(viewTypes.ONION_SKIN); + }); + + it('should default to viewTypes.TWO_UP', () => { + expect(replacedImageDiff.currentView).toEqual(viewTypes.TWO_UP); + }); + }); + }); + + describe('generateImageEls', () => { + beforeEach(() => { + spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + + replacedImageDiff = new ReplacedImageDiff(element, { + canCreateNote: false, + renderCommentBadge: false, + }); + + setupImageFrameEls(); + }); + + it('should set imageEls', () => { + replacedImageDiff.generateImageEls(); + const { imageEls } = replacedImageDiff; + expect(imageEls).toBeDefined(); + expect(imageEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.two-up img')); + expect(imageEls[viewTypes.SWIPE]).toEqual(element.querySelector('.swipe img')); + expect(imageEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.onion-skin img')); + }); + }); + + describe('bindEvents', () => { + beforeEach(() => { + spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + replacedImageDiff = new ReplacedImageDiff(element); + + setupViewModesEls(); + }); + + it('should call super.bindEvents', () => { + replacedImageDiff.bindEvents(); + expect(ImageDiff.prototype.bindEvents).toHaveBeenCalled(); + }); + + it('should register click eventlistener to 2-up view mode', (done) => { + spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => { + expect(viewMode).toEqual(viewTypes.TWO_UP); + done(); + }); + + replacedImageDiff.bindEvents(); + replacedImageDiff.viewModesEls[viewTypes.TWO_UP].click(); + }); + + it('should register click eventlistener to swipe view mode', (done) => { + spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => { + expect(viewMode).toEqual(viewTypes.SWIPE); + done(); + }); + + replacedImageDiff.bindEvents(); + replacedImageDiff.viewModesEls[viewTypes.SWIPE].click(); + }); + + it('should register click eventlistener to onion skin view mode', (done) => { + spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => { + expect(viewMode).toEqual(viewTypes.SWIPE); + done(); + }); + + replacedImageDiff.bindEvents(); + replacedImageDiff.viewModesEls[viewTypes.SWIPE].click(); + }); + }); + + describe('getters', () => { + describe('imageEl', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + replacedImageDiff.currentView = viewTypes.TWO_UP; + setupImageEls(); + }); + + it('should return imageEl based on currentView', () => { + expect(replacedImageDiff.imageEl).toEqual(element.querySelector('.two-up img')); + + replacedImageDiff.currentView = viewTypes.SWIPE; + expect(replacedImageDiff.imageEl).toEqual(element.querySelector('.swipe img')); + }); + }); + + describe('imageFrameEl', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + replacedImageDiff.currentView = viewTypes.TWO_UP; + setupImageFrameEls(); + }); + + it('should return imageFrameEl based on currentView', () => { + expect(replacedImageDiff.imageFrameEl).toEqual(element.querySelector('.two-up .js-image-frame')); + + replacedImageDiff.currentView = viewTypes.ONION_SKIN; + expect(replacedImageDiff.imageFrameEl).toEqual(element.querySelector('.onion-skin .js-image-frame')); + }); + }); + }); + + describe('changeView', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + spyOn(imageDiffHelper, 'removeCommentIndicator').and.returnValue({ + removed: false, + }); + setupImageFrameEls(); + }); + + describe('invalid viewType', () => { + beforeEach(() => { + replacedImageDiff.changeView('some-view-name'); + }); + + it('should not call removeCommentIndicator', () => { + expect(imageDiffHelper.removeCommentIndicator).not.toHaveBeenCalled(); + }); + }); + + describe('valid viewType', () => { + beforeEach(() => { + jasmine.clock().install(); + spyOn(ReplacedImageDiff.prototype, 'renderNewView').and.callFake(() => {}); + replacedImageDiff.changeView(viewTypes.ONION_SKIN); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('should call removeCommentIndicator', () => { + expect(imageDiffHelper.removeCommentIndicator).toHaveBeenCalled(); + }); + + it('should update currentView to newView', () => { + expect(replacedImageDiff.currentView).toEqual(viewTypes.ONION_SKIN); + }); + + it('should clear imageBadges', () => { + expect(replacedImageDiff.imageBadges.length).toEqual(0); + }); + + it('should call renderNewView', () => { + jasmine.clock().tick(251); + expect(replacedImageDiff.renderNewView).toHaveBeenCalled(); + }); + }); + }); + + describe('renderNewView', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + }); + + it('should call renderBadges', () => { + spyOn(ReplacedImageDiff.prototype, 'renderBadges').and.callFake(() => {}); + + replacedImageDiff.renderNewView({ + removed: false, + }); + + expect(replacedImageDiff.renderBadges).toHaveBeenCalled(); + }); + + describe('removeIndicator', () => { + const indicator = { + removed: true, + x: 0, + y: 1, + image: { + width: 50, + height: 100, + }, + }; + + beforeEach(() => { + setupImageEls(); + setupImageFrameEls(); + }); + + it('should pass showCommentIndicator normalized indicator values', (done) => { + spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {}); + spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.callFake((imageEl, meta) => { + expect(meta.x).toEqual(indicator.x); + expect(meta.y).toEqual(indicator.y); + expect(meta.width).toEqual(indicator.image.width); + expect(meta.height).toEqual(indicator.image.height); + done(); + }); + replacedImageDiff.renderNewView(indicator); + }); + + it('should call showCommentIndicator', (done) => { + const normalized = { + normalized: true, + }; + spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(normalized); + spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake((imageFrameEl, normalizedIndicator) => { + expect(normalizedIndicator).toEqual(normalized); + done(); + }); + replacedImageDiff.renderNewView(indicator); + }); + }); + }); +}); diff --git a/spec/javascripts/image_diff/view_types_spec.js b/spec/javascripts/image_diff/view_types_spec.js new file mode 100644 index 000000000000..e9639f464974 --- /dev/null +++ b/spec/javascripts/image_diff/view_types_spec.js @@ -0,0 +1,24 @@ +import { viewTypes, isValidViewType } from '~/image_diff/view_types'; + +describe('viewTypes', () => { + describe('isValidViewType', () => { + it('should return true for TWO_UP', () => { + expect(isValidViewType(viewTypes.TWO_UP)).toEqual(true); + }); + + it('should return true for SWIPE', () => { + expect(isValidViewType(viewTypes.SWIPE)).toEqual(true); + }); + + it('should return true for ONION_SKIN', () => { + expect(isValidViewType(viewTypes.ONION_SKIN)).toEqual(true); + }); + + it('should return false for non view types', () => { + expect(isValidViewType('some-view-type')).toEqual(false); + expect(isValidViewType(null)).toEqual(false); + expect(isValidViewType(undefined)).toEqual(false); + expect(isValidViewType('')).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/image_utility_spec.js b/spec/javascripts/lib/utils/image_utility_spec.js new file mode 100644 index 000000000000..75addfcc8334 --- /dev/null +++ b/spec/javascripts/lib/utils/image_utility_spec.js @@ -0,0 +1,32 @@ +import * as imageUtility from '~/lib/utils/image_utility'; + +describe('imageUtility', () => { + describe('isImageLoaded', () => { + it('should return false when image.complete is false', () => { + const element = { + complete: false, + naturalHeight: 100, + }; + + expect(imageUtility.isImageLoaded(element)).toEqual(false); + }); + + it('should return false when naturalHeight = 0', () => { + const element = { + complete: true, + naturalHeight: 0, + }; + + expect(imageUtility.isImageLoaded(element)).toEqual(false); + }); + + it('should return true when image.complete and naturalHeight != 0', () => { + const element = { + complete: true, + naturalHeight: 100, + }; + + expect(imageUtility.isImageLoaded(element)).toEqual(true); + }); + }); +}); -- 2.22.0 From b940f595c906faac554224b9a31fe3083ad4586b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 6 Oct 2017 15:58:11 -0300 Subject: [PATCH 205/211] Fix feature specs --- spec/features/merge_requests/user_posts_diff_notes_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb index a99fa208e171..1c7dc688c1a9 100644 --- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb @@ -196,7 +196,6 @@ feature 'Merge requests > User posts diff notes', :js do before do merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) visit diffs_project_merge_request_path(project, merge_request, view: 'inline') - wait_for_requests end context 'with a new line' do @@ -228,7 +227,6 @@ feature 'Merge requests > User posts diff notes', :js do write_comment_on_line(line_holder, diff_side) click_button 'Comment' - wait_for_requests assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset) end @@ -260,6 +258,8 @@ feature 'Merge requests > User posts diff notes', :js do def assert_comment_persistence(line_holder, asset_form_reset:) notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) + sleep 3 + expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) expect(notes_holder_saved).to have_content test_note_comment -- 2.22.0 From 7e52d3cfeaa6771da898d19753d8fa07dfe6d68e Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 6 Oct 2017 16:40:02 -0300 Subject: [PATCH 206/211] Fix feature spec --- spec/support/features/discussion_comments_shared_example.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 81cb94ab8c44..cc8002fde0d3 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -46,6 +46,7 @@ shared_examples 'discussion comments' do |resource_name| find("#{form_selector} .note-textarea").send_keys('a') find(toggle_selector).click + sleep 1 end it 'has a "Comment" item (selected by default) and "Start discussion" item' do -- 2.22.0 From afce83611ac2bad73521523067209446aeae26fa Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Oct 2017 16:22:02 -0500 Subject: [PATCH 207/211] Add extra check as legacy MRs dont have notePosition --- app/assets/javascripts/notes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 5898ce65c5b0..a6390f4ae8f0 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1518,8 +1518,9 @@ export default class Notes { if (isNewDiffComment) { // Add image badge, avatar badge and toggle discussion badge for new image diffs - if ($diffFile.length > 0) { - const { x_axis, y_axis, width, height } = JSON.parse($form.find('#note_position').val()); + const notePosition = $form.find('#note_position').val(); + if ($diffFile.length > 0 && notePosition.length > 0) { + const { x_axis, y_axis, width, height } = JSON.parse(notePosition); const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { detail: { x: x_axis, -- 2.22.0 From bd41a59259fd1da3d9af11dcae1c772fba573963 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 6 Oct 2017 18:22:24 -0300 Subject: [PATCH 208/211] [SKIP CI] Undo feature spec changes --- spec/features/merge_requests/user_posts_diff_notes_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb index 1c7dc688c1a9..7a773fb2baac 100644 --- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb @@ -228,6 +228,8 @@ feature 'Merge requests > User posts diff notes', :js do click_button 'Comment' + wait_for_requests + assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset) end @@ -258,8 +260,6 @@ feature 'Merge requests > User posts diff notes', :js do def assert_comment_persistence(line_holder, asset_form_reset:) notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) - sleep 3 - expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) expect(notes_holder_saved).to have_content test_note_comment -- 2.22.0 From bdb4f052ade8f9196cdac8c61c6ff1bcbdc1be8c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 6 Oct 2017 23:46:48 +0000 Subject: [PATCH 209/211] Change x_axis,y_axis to x,y --- .../image_diff/helpers/dom_helper.js | 4 ++-- .../image_diff/helpers/utils_helper.js | 7 +------ app/assets/javascripts/notes.js | 6 +++--- lib/gitlab/diff/formatters/image_formatter.rb | 18 +++++++++--------- lib/gitlab/diff/image_point.rb | 12 ++++++------ lib/gitlab/diff/position.rb | 2 +- .../merge_requests/image_diff_notes.rb | 16 ++++++++-------- .../image_diff/helpers/dom_helper_spec.js | 4 ++-- .../image_diff/helpers/utils_helper_spec.js | 11 +++++------ spec/javascripts/image_diff/mock_data.js | 7 ------- .../diff/formatters/image_formatter_spec.rb | 2 +- spec/lib/gitlab/diff/position_spec.rb | 8 ++++---- spec/models/diff_note_spec.rb | 4 ++-- spec/models/note_spec.rb | 4 ++-- 14 files changed, 46 insertions(+), 59 deletions(-) diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js index 0923e27a7b83..12d56714b34d 100644 --- a/app/assets/javascripts/image_diff/helpers/dom_helper.js +++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js @@ -4,8 +4,8 @@ export function setPositionDataAttribute(el, options) { const { x, y, width, height } = options; const position = el.dataset.position; const positionObject = Object.assign({}, JSON.parse(position), { - x_axis: x, - y_axis: y, + x, + y, width, height, }); diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js index c9b747f71308..96fc735e6290 100644 --- a/app/assets/javascripts/image_diff/helpers/utils_helper.js +++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js @@ -24,12 +24,7 @@ export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) { const position = JSON.parse(discussionEl.dataset.position); const firstNoteEl = discussionEl.querySelector('.note'); const badge = new ImageBadge({ - actual: { - x: position.x_axis, - y: position.y_axis, - width: position.width, - height: position.height, - }, + actual: position, imageEl: imageFrameEl.querySelector('img'), noteId: firstNoteEl.id, discussionId: discussionEl.dataset.discussionId, diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a6390f4ae8f0..24de21f2ce28 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1520,11 +1520,11 @@ export default class Notes { // Add image badge, avatar badge and toggle discussion badge for new image diffs const notePosition = $form.find('#note_position').val(); if ($diffFile.length > 0 && notePosition.length > 0) { - const { x_axis, y_axis, width, height } = JSON.parse(notePosition); + const { x, y, width, height } = JSON.parse(notePosition); const addBadgeEvent = new CustomEvent('addBadge.imageDiff', { detail: { - x: x_axis, - y: y_axis, + x, + y, width, height, noteId: `note_${note.id}`, diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index f3e5887555ec..ccd0d3099722 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -4,12 +4,12 @@ module Gitlab class ImageFormatter < BaseFormatter attr_reader :width attr_reader :height - attr_reader :x_axis - attr_reader :y_axis + attr_reader :x + attr_reader :y def initialize(attrs) - @x_axis = attrs[:x_axis] - @y_axis = attrs[:y_axis] + @x = attrs[:x] + @y = attrs[:y] @width = attrs[:width] @height = attrs[:height] @@ -17,15 +17,15 @@ module Gitlab end def key - @key ||= super.push(x_axis, y_axis) + @key ||= super.push(x, y) end def complete? - x_axis && y_axis && width && height + x && y && width && height end def to_h - super.merge(width: width, height: height, x_axis: x_axis, y_axis: y_axis) + super.merge(width: width, height: height, x: x, y: y) end def position_type @@ -34,8 +34,8 @@ module Gitlab def ==(other) other.is_a?(self.class) && - x_axis == other.x_axis && - y_axis == other.y_axis + x == other.x && + y == other.y end end end diff --git a/lib/gitlab/diff/image_point.rb b/lib/gitlab/diff/image_point.rb index 9adf7abc5031..65332dfd239e 100644 --- a/lib/gitlab/diff/image_point.rb +++ b/lib/gitlab/diff/image_point.rb @@ -1,21 +1,21 @@ module Gitlab module Diff class ImagePoint - attr_reader :width, :height, :x_axis, :y_axis + attr_reader :width, :height, :x, :y - def initialize(width, height, x_axis, y_axis) + def initialize(width, height, x, y) @width = width @height = height - @x_axis = x_axis - @y_axis = y_axis + @x = x + @y = y end def to_h { width: width, height: height, - x_axis: x_axis, - y_axis: y_axis + x: x, + y: y } end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index ab11018ed3a9..bd0a9502a5e3 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -17,7 +17,7 @@ module Gitlab # A position can belong to a text line or to an image coordinate # it depends of the position_type argument. # Text position will have: new_line and old_line - # Image position will have: width, height, x_axis, y_axis + # Image position will have: width, height, x, y def initialize(attrs = {}) @formatter = get_formatter_class(attrs[:position_type]).new(attrs) end diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb index 2c9e9b18be51..e8ca8a98d70b 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -59,8 +59,8 @@ feature 'image diff notes', js: true do new_path: path, width: 100, height: 100, - x_axis: 10, - y_axis: 10, + x: 10, + y: 10, position_type: "image", diff_refs: commit.diff_refs ) @@ -72,8 +72,8 @@ feature 'image diff notes', js: true do new_path: path, width: 100, height: 100, - x_axis: 20, - y_axis: 20, + x: 20, + y: 20, position_type: "image", diff_refs: commit.diff_refs ) @@ -113,8 +113,8 @@ feature 'image diff notes', js: true do new_path: path, width: 100, height: 100, - x_axis: 1, - y_axis: 1, + x: 1, + y: 1, position_type: "image", diff_refs: merge_request.diff_refs ) @@ -165,8 +165,8 @@ feature 'image diff notes', js: true do new_path: path, width: 100, height: 100, - x_axis: 50, - y_axis: 50, + x: 50, + y: 50, position_type: "image", diff_refs: merge_request.diff_refs ) diff --git a/spec/javascripts/image_diff/helpers/dom_helper_spec.js b/spec/javascripts/image_diff/helpers/dom_helper_spec.js index f49248dd0eb6..8dde924e8aec 100644 --- a/spec/javascripts/image_diff/helpers/dom_helper_spec.js +++ b/spec/javascripts/image_diff/helpers/dom_helper_spec.js @@ -19,8 +19,8 @@ describe('domHelper', () => { }); it('should set x, y, width, height', () => { - expect(attributeAfterCall.x_axis).toEqual(imageMeta.x); - expect(attributeAfterCall.y_axis).toEqual(imageMeta.y); + expect(attributeAfterCall.x).toEqual(imageMeta.x); + expect(attributeAfterCall.y).toEqual(imageMeta.y); expect(attributeAfterCall.width).toEqual(imageMeta.width); expect(attributeAfterCall.height).toEqual(imageMeta.height); }); diff --git a/spec/javascripts/image_diff/helpers/utils_helper_spec.js b/spec/javascripts/image_diff/helpers/utils_helper_spec.js index 8de467f3c940..56d77a05c4cb 100644 --- a/spec/javascripts/image_diff/helpers/utils_helper_spec.js +++ b/spec/javascripts/image_diff/helpers/utils_helper_spec.js @@ -11,7 +11,6 @@ describe('utilsHelper', () => { image, imageProperties, imageMeta, - imagePositionAttr, } = mockData; describe('resizeCoordinatesToImageElement', () => { @@ -52,16 +51,16 @@ describe('utilsHelper', () => { discussionEl.innerHTML = `
`; - discussionEl.dataset.position = JSON.stringify(imagePositionAttr); + discussionEl.dataset.position = JSON.stringify(imageMeta); result = utilsHelper.generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl); }); it('should return actual image properties', () => { const { actual } = result; - expect(actual.x).toEqual(imagePositionAttr.x_axis); - expect(actual.y).toEqual(imagePositionAttr.y_axis); - expect(actual.width).toEqual(imagePositionAttr.width); - expect(actual.height).toEqual(imagePositionAttr.height); + expect(actual.x).toEqual(imageMeta.x); + expect(actual.y).toEqual(imageMeta.y); + expect(actual.width).toEqual(imageMeta.width); + expect(actual.height).toEqual(imageMeta.height); }); it('should return browser image properties', () => { diff --git a/spec/javascripts/image_diff/mock_data.js b/spec/javascripts/image_diff/mock_data.js index 6b5220470537..a0d1732dd0a0 100644 --- a/spec/javascripts/image_diff/mock_data.js +++ b/spec/javascripts/image_diff/mock_data.js @@ -26,10 +26,3 @@ export const imageMeta = { width: imageProperties.naturalWidth, height: imageProperties.naturalHeight, }; - -export const imagePositionAttr = { - x_axis: coordinate.x, - y_axis: coordinate.y, - width: image.width, - height: image.height, -}; diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb index 9f2a2512f860..2f99febe04e6 100644 --- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb +++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Diff::Formatters::ImageFormatter do end let(:attrs) do - base_attrs.merge(width: 100, height: 100, x_axis: 1, y_axis: 2) + base_attrs.merge(width: 100, height: 100, x: 1, y: 2) end end end diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 66727a9dabe3..9bf54fdecc47 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -56,8 +56,8 @@ describe Gitlab::Diff::Position do new_path: "files/images/6049019_460s.jpg", width: 100, height: 100, - x_axis: 1, - y_axis: 100, + x: 1, + y: 100, diff_refs: commit.diff_refs, position_type: "image" ) @@ -532,8 +532,8 @@ describe Gitlab::Diff::Position do start_sha: nil, width: 100, height: 100, - x_axis: 1, - y_axis: 100, + x: 1, + y: 100, position_type: "image" } end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index e50e5dde38e0..eb0a3e9e0d3c 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -265,8 +265,8 @@ describe DiffNote do new_path: path, width: 10, height: 10, - x_axis: 1, - y_axis: 1, + x: 1, + y: 1, diff_refs: merge_request.diff_refs, position_type: "image" ) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 76cb67c718ca..1ecb50586c73 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -328,8 +328,8 @@ describe Note do new_path: image_path, width: 100, height: 100, - x_axis: 1, - y_axis: 1, + x: 1, + y: 1, position_type: "image", diff_refs: merge_request2.diff_refs ) -- 2.22.0 From 1878d790d30b814e48cbad29bb655638e2e34bf6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Oct 2017 18:53:35 -0500 Subject: [PATCH 210/211] Remove sleep --- spec/support/features/discussion_comments_shared_example.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index cc8002fde0d3..81cb94ab8c44 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -46,7 +46,6 @@ shared_examples 'discussion comments' do |resource_name| find("#{form_selector} .note-textarea").send_keys('a') find(toggle_selector).click - sleep 1 end it 'has a "Comment" item (selected by default) and "Start discussion" item' do -- 2.22.0 From 82237c688faf2633d4a1a349d9462f0be6754a12 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Oct 2017 18:54:04 -0500 Subject: [PATCH 211/211] Fix rspec failure --- spec/support/features/discussion_comments_shared_example.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 81cb94ab8c44..9f05cabf7aec 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -71,7 +71,7 @@ shared_examples 'discussion comments' do |resource_name| expect(page).not_to have_selector menu_selector find(toggle_selector).click - find('body').click + find('body').trigger 'click' expect(page).not_to have_selector menu_selector end -- 2.22.0