diff --git a/app/assets/javascripts/commit/components/notes/diff_discussions.vue b/app/assets/javascripts/commit/components/notes/diff_discussions.vue new file mode 100644 index 0000000000000000000000000000000000000000..bf9a7fd30cf058bfdead8291237fac8cd4f1efed --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/diff_discussions.vue @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/discussion_notes.vue b/app/assets/javascripts/commit/components/notes/discussion_notes.vue new file mode 100644 index 0000000000000000000000000000000000000000..33baab481cedcb12ad4ab5d42e2150956ec65c64 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/discussion_notes.vue @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/note_actions.vue b/app/assets/javascripts/commit/components/notes/note_actions.vue new file mode 100644 index 0000000000000000000000000000000000000000..b1453108442ce00a6643f1ba306bd064becbc7c2 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/note_actions.vue @@ -0,0 +1,260 @@ + + + + + + {{ __('Author') }} + + + {{ accessLevel }} + + + {{ __('Contributor') }} + + + + + + + + + + {{ __('Copy link') }} + + + + {{ $options.i18n.reportAbuse }} + + + {{ __('Delete comment') }} + + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/note_body.vue b/app/assets/javascripts/commit/components/notes/note_body.vue new file mode 100644 index 0000000000000000000000000000000000000000..c1a6181ceedbb0721177265b146cf8e937f9b978 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/note_body.vue @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/note_form.vue b/app/assets/javascripts/commit/components/notes/note_form.vue new file mode 100644 index 0000000000000000000000000000000000000000..c9e62da97621296195a9bc9bdc2b9d0e33bbea61 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/note_form.vue @@ -0,0 +1,245 @@ + + + + + + + + {{ content }} + + + + + + $emit('handleSuggestDismissed')" + /> + + + + {{ saveButtonTitle }} + + + {{ __('Cancel') }} + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/note_header.vue b/app/assets/javascripts/commit/components/notes/note_header.vue new file mode 100644 index 0000000000000000000000000000000000000000..0f10395ce1850343a713d2e4022c2653fd58cde9 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/note_header.vue @@ -0,0 +1,205 @@ + + + + + + + + + + @{{ author.username }} + + + + + {{ __('A deleted user') }} + + + + + + + {{ actionText }} + + + + + + + · + + + + + {{ __('Internal note') }} + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/note_signed_out_widget.vue b/app/assets/javascripts/commit/components/notes/note_signed_out_widget.vue new file mode 100644 index 0000000000000000000000000000000000000000..1a0dc8859fe4776ef0f8cb3b547e6cf92ad76e31 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/note_signed_out_widget.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/app/assets/javascripts/commit/components/notes/noteable_discussion.vue b/app/assets/javascripts/commit/components/notes/noteable_discussion.vue new file mode 100644 index 0000000000000000000000000000000000000000..08e11d5748cdc319333754f792fb21c92a4ea643 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/noteable_discussion.vue @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/commit/components/notes/noteable_note.vue b/app/assets/javascripts/commit/components/notes/noteable_note.vue new file mode 100644 index 0000000000000000000000000000000000000000..ad44789a167c5c779703e33ebc0b8732c79ecc21 --- /dev/null +++ b/app/assets/javascripts/commit/components/notes/noteable_note.vue @@ -0,0 +1,346 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/entrypoints/lookbook/rapid_diffs.js b/app/assets/javascripts/entrypoints/lookbook/rapid_diffs.js index ab7f2f0e059b3c2b1d5dc8bb9403185b71d2547f..ff87777aba2e744e9b0f9e944e3aa60b97a5da1e 100644 --- a/app/assets/javascripts/entrypoints/lookbook/rapid_diffs.js +++ b/app/assets/javascripts/entrypoints/lookbook/rapid_diffs.js @@ -1,5 +1,5 @@ import { DiffFile } from '~/rapid_diffs/web_components/diff_file'; -import { VIEWER_ADAPTERS } from '~/rapid_diffs/app/adapters'; +import { VIEWER_ADAPTERS } from '~/rapid_diffs/app/adapter_configs/base'; customElements.define('diff-file', DiffFile); customElements.define( diff --git a/app/assets/javascripts/pages/projects/commit/rapid_diffs/index.js b/app/assets/javascripts/pages/projects/commit/rapid_diffs/index.js index f994c4302e0e04617eaa9cc415895ca71c98795f..c752779a0b760b9fda833d41c8841f34efb01249 100644 --- a/app/assets/javascripts/pages/projects/commit/rapid_diffs/index.js +++ b/app/assets/javascripts/pages/projects/commit/rapid_diffs/index.js @@ -1,9 +1,10 @@ import initCommitActions from '~/projects/commit'; import { initCommitBoxInfo } from '~/projects/commit_box/info'; -import { createRapidDiffsApp } from '~/rapid_diffs'; +import { createRapidDiffsCommitsApp } from '~/rapid_diffs/commits_app'; initCommitBoxInfo(); initCommitActions(); -const app = createRapidDiffsApp(); +const app = createRapidDiffsCommitsApp(); app.init(); +app.fetchDiscussions(); diff --git a/app/assets/javascripts/rapid_diffs/adapters/discussions.js b/app/assets/javascripts/rapid_diffs/adapters/discussions.js new file mode 100644 index 0000000000000000000000000000000000000000..33fd42c6aeabdb888de78bbccc07ba4075ae31f8 --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/adapters/discussions.js @@ -0,0 +1,148 @@ +import Vue from 'vue'; +import { MOUNTED } from '~/rapid_diffs/adapter_events'; +import { useDiscussions } from '~/rapid_diffs/stores/discussions'; +import { pinia } from '~/pinia/instance'; +import DiffDiscussions from '~/commit/components/notes/diff_discussions.vue'; + +function findLineRow(element, oldLine, newLine) { + return element + .querySelector( + `[data-position="${oldLine ? 'old' : 'new'}"] [data-line-number="${oldLine || newLine}"]`, + ) + .closest('tr'); +} + +function mountVueApp(el, id, appData) { + // eslint-disable-next-line no-new + new Vue({ + el, + pinia, + provide() { + return { + noteableType: appData.noteableType, + userPermissions: appData.userPermissions, + endpoints: { + previewMarkdown: appData.previewMarkdownEndpoint, + markdownDocs: appData.markdownDocsPath, + register: appData.registerPath, + signIn: appData.signInPath, + }, + }; + }, + computed: { + // this way we ensure reactivity continues to work without rerendering the whole component + discussion() { + return useDiscussions().getDiscussionById(id); + }, + }, + render(h) { + return h(DiffDiscussions, { + props: { + discussions: [this.discussion], + }, + }); + }, + }); +} + +function addInlineCell(lineRow) { + const discussionRow = lineRow.closest('tbody').insertRow(lineRow.rowIndex); + discussionRow.classList.add('rd-diff-discussions-row'); + const cell = document.createElement('td'); + cell.colSpan = lineRow.querySelectorAll('td').length; + cell.classList.add('notes_holder'); + discussionRow.appendChild(cell); + return cell; +} + +function addParallelCells(lineRow) { + const discussionRow = lineRow.closest('tbody').insertRow(lineRow.rowIndex); + discussionRow.classList.add('rd-diff-discussions-row'); + discussionRow.classList.add('rd-diff-discussions-row-parallel'); + const leftCell = document.createElement('td'); + const rightCell = document.createElement('td'); + const span = lineRow.querySelectorAll('td').length / 2; + leftCell.colSpan = span; + rightCell.colSpan = span; + leftCell.classList.add('notes_holder'); + rightCell.classList.add('notes_holder'); + discussionRow.appendChild(leftCell); + discussionRow.appendChild(rightCell); + return [leftCell, rightCell]; +} + +function mountParallelDiscussion({ element, id, position, appData }) { + if (document.querySelector(`[data-discussion-id="${id}"]`)) return; + const { old_line: oldLine, new_line: newLine } = position; + const lineRow = findLineRow(element, oldLine, newLine); + const discussionRow = lineRow.nextElementSibling; + let cell; + if (!discussionRow || !discussionRow.classList.contains('rd-diff-discussions-row')) { + if (oldLine && newLine) { + cell = addInlineCell(lineRow); + } else if (oldLine) { + [cell] = addParallelCells(lineRow); + } else { + [, cell] = addParallelCells(lineRow); + } + } else if (oldLine) { + cell = discussionRow.querySelector('td'); + } else { + cell = discussionRow.querySelector('td:last-child'); + } + const mountTarget = document.createElement('div'); + cell.appendChild(mountTarget); + mountVueApp(mountTarget, id, appData); +} + +function mountInlineDiscussion({ element, id, position, appData }) { + if (document.querySelector(`[data-discussion-id="${id}"]`)) return; + const { old_line: oldLine, new_line: newLine } = position; + const lineRow = findLineRow(element, oldLine, newLine); + const cell = addInlineCell(lineRow); + const mountTarget = document.createElement('div'); + cell.appendChild(mountTarget); + mountVueApp(mountTarget, id, appData); +} + +function createDiscussionsWatcher(oldPath, newPath, callback) { + const store = useDiscussions(pinia); + return store.$subscribe( + () => { + const matchedDiscussions = store.discussions.filter((discussion) => { + if (!discussion.diff_discussion) return false; + return discussion.position.old_path === oldPath && discussion.position.new_path === newPath; + }); + matchedDiscussions.forEach(callback); + }, + { immediate: true }, + ); +} + +export const parallelDiscussionsAdapter = { + [MOUNTED]() { + const { diffElement: element, appData } = this; + this.sink.stopWatchingNotes = createDiscussionsWatcher( + this.data.oldPath, + this.data.newPath, + ({ id, position }) => { + mountParallelDiscussion({ element, id, position, appData }); + }, + ); + return this.sink.stopWatchingNotes; + }, +}; + +export const inlineDiscussionsAdapter = { + [MOUNTED]() { + const { diffElement: element, appData } = this; + this.sink.stopWatchingNotes = createDiscussionsWatcher( + this.data.oldPath, + this.data.newPath, + ({ id, position }) => { + mountInlineDiscussion({ element, id, position, appData }); + }, + ); + return this.sink.stopWatchingNotes; + }, +}; diff --git a/app/assets/javascripts/rapid_diffs/app/adapters.js b/app/assets/javascripts/rapid_diffs/app/adapter_configs/base.js similarity index 100% rename from app/assets/javascripts/rapid_diffs/app/adapters.js rename to app/assets/javascripts/rapid_diffs/app/adapter_configs/base.js diff --git a/app/assets/javascripts/rapid_diffs/app/adapter_configs/commits.js b/app/assets/javascripts/rapid_diffs/app/adapter_configs/commits.js new file mode 100644 index 0000000000000000000000000000000000000000..594a3a68598cddabd59788f1cfb7267913f0b7c2 --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/app/adapter_configs/commits.js @@ -0,0 +1,11 @@ +import { VIEWER_ADAPTERS } from '~/rapid_diffs/app/adapter_configs/base'; +import { + inlineDiscussionsAdapter, + parallelDiscussionsAdapter, +} from '~/rapid_diffs/adapters/discussions'; + +export const adapters = { + ...VIEWER_ADAPTERS, + text_inline: [...VIEWER_ADAPTERS.text_inline, inlineDiscussionsAdapter], + text_parallel: [...VIEWER_ADAPTERS.text_parallel, parallelDiscussionsAdapter], +}; diff --git a/app/assets/javascripts/rapid_diffs/app/index.js b/app/assets/javascripts/rapid_diffs/app/index.js index 119f52f8f3a39be25e601584cd443f92c315b729..e7cc6846733a9db88245b484e8cc9955d1a76402 100644 --- a/app/assets/javascripts/rapid_diffs/app/index.js +++ b/app/assets/javascripts/rapid_diffs/app/index.js @@ -10,7 +10,7 @@ import { createAlert } from '~/alert'; import { __ } from '~/locale'; import { fixWebComponentsStreamingOnSafari } from '~/rapid_diffs/app/quirks/safari_fix'; import { DIFF_FILE_MOUNTED } from '~/rapid_diffs/dom_events'; -import { VIEWER_ADAPTERS } from '~/rapid_diffs/app/adapters'; +import { VIEWER_ADAPTERS } from '~/rapid_diffs/app/adapter_configs/base'; import { camelizeKeys } from '~/lib/utils/object_utils'; import { disableBrokenContentVisibility } from '~/rapid_diffs/app/quirks/content_visibility_fix'; import { useApp } from '~/rapid_diffs/stores/app'; diff --git a/app/assets/javascripts/rapid_diffs/commits_app.js b/app/assets/javascripts/rapid_diffs/commits_app.js new file mode 100644 index 0000000000000000000000000000000000000000..e0c0267f5f2c61cb1dc848ae3000ab532797dcf7 --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/commits_app.js @@ -0,0 +1,16 @@ +import { RapidDiffsFacade } from '~/rapid_diffs/app'; +import { adapters } from '~/rapid_diffs/app/adapter_configs/commits'; +import { useDiscussions } from '~/rapid_diffs/stores/discussions'; +import { pinia } from '~/pinia/instance'; + +class RapidDiffsCommitsApp extends RapidDiffsFacade { + adapterConfig = adapters; + + fetchDiscussions() { + return useDiscussions(pinia).fetchDiscussions(this.appData.notesEndpoint); + } +} + +export const createRapidDiffsCommitsApp = (options) => { + return new RapidDiffsCommitsApp(options); +}; diff --git a/app/assets/javascripts/rapid_diffs/stores/discussions.js b/app/assets/javascripts/rapid_diffs/stores/discussions.js new file mode 100644 index 0000000000000000000000000000000000000000..d3c3f0f553f4aa3ff764d3a777676ad23a74f75b --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/stores/discussions.js @@ -0,0 +1,85 @@ +import { defineStore } from 'pinia'; +import axios from '~/lib/utils/axios_utils'; +import { isCurrentUser } from '~/lib/utils/common_utils'; + +export const useDiscussions = defineStore('discussions', { + state() { + return { + discussions: [], + }; + }, + actions: { + async fetchDiscussions(url) { + const response = await axios.get(url); + this.discussions = response.data.discussions; + }, + setDiscussionProperty(discussion, key, value) { + const newDiscussions = this.discussions.slice(0); + newDiscussions.splice(this.discussions.indexOf(discussion), 1, { + ...discussion, + [key]: value, + }); + this.discussions = newDiscussions; + }, + setNoteProperty(note, key, value) { + const discussion = this.getDiscussionById(note.discussion_id); + const newNotes = discussion.notes.slice(0); + newNotes.splice(discussion.notes.indexOf(note), 1, { + ...note, + [key]: value, + }); + this.setDiscussionProperty(discussion, 'notes', newNotes); + }, + toggleDiscussionReplies(discussion) { + this.setDiscussionProperty(discussion, 'repliesCollapsed', !discussion.repliesCollapsed); + }, + expandDiscussionReplies(discussion) { + this.setDiscussionProperty(discussion, 'repliesCollapsed', false); + }, + saveNote() { + return new Promise(); + }, + deleteNote() { + return new Promise(); + }, + removeNote() { + return new Promise(); + }, + updateNote() { + return new Promise(); + }, + async toggleNoteAward(note, name) { + await axios.post(note.toggle_award_path, { name }); + const awards = note.award_emoji.slice(0); + const existingAwardIndex = awards.findIndex( + (emoji) => `${emoji.name}` === name && isCurrentUser(emoji.user.id), + ); + if (existingAwardIndex !== -1) { + awards.splice(existingAwardIndex, 1); + } else { + awards.push({ + name, + user: { + id: window.gon?.current_user_id, + name: window.gon?.current_user_fullname, + username: window.gon?.current_username, + }, + }); + } + this.setNoteProperty(note, 'award_emoji', awards); + }, + }, + getters: { + getDiscussionById() { + return (id) => this.discussions.find((discussion) => discussion.id === id); + }, + getLastEditableNote() { + return (discussion) => { + return discussion.notes.findLast((note) => { + return isCurrentUser(note.author.id) && note.current_user?.can_edit; + }); + }; + }, + targetNoteHash() {}, + }, +}); diff --git a/app/assets/stylesheets/components/rapid_diffs/_index.scss b/app/assets/stylesheets/components/rapid_diffs/_index.scss index f3b639269a2d8366830943d2e703c1ed8147914e..034973e8fa827b07f541006ea16454f66a4e7fde 100644 --- a/app/assets/stylesheets/components/rapid_diffs/_index.scss +++ b/app/assets/stylesheets/components/rapid_diffs/_index.scss @@ -4,3 +4,4 @@ @import './no_preview'; @import './empty_state'; @import './image_viewer'; +@import './disucssions'; diff --git a/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss b/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss index fc419df9d4bf90a6161f3246a15a3a166c9e5a85..c72617e7f3c24dc8c4236ea24b8118f3c73177d2 100644 --- a/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss +++ b/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss @@ -39,7 +39,7 @@ // content-visibility: auto; applies paint containment, which means you can not draw outside a diff file // we need to disable this when we show dropdowns and other elements which extend past a diff file -.rd-diff-file[data-virtual]:has([data-options-toggle] button[aria-expanded='true']) { +.rd-diff-file[data-virtual]:has(button[aria-controls*="base-dropdown"][aria-expanded='true']) { --rd-content-visibility-auto: visible; } diff --git a/app/assets/stylesheets/components/rapid_diffs/disucssions.scss b/app/assets/stylesheets/components/rapid_diffs/disucssions.scss new file mode 100644 index 0000000000000000000000000000000000000000..a91fdee3cdde8e7d75f0538bc05fadd1e8040894 --- /dev/null +++ b/app/assets/stylesheets/components/rapid_diffs/disucssions.scss @@ -0,0 +1,15 @@ +@import '../../page_bundles/notes/diff_comments'; + +.rd-diff-discussions-row { + display: block; + background-color: var(--code-empty-background); +} + +.rd-diff-discussions-row-parallel { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.rd-diff-discussions-row td { + display: block; +} diff --git a/app/assets/stylesheets/page_bundles/notes/_diff_comments.scss b/app/assets/stylesheets/page_bundles/notes/_diff_comments.scss index e9e6553acdd561d71f53d2bd497e13aed2cbf82a..426726c4b8832194d941c20e4815cac9be00bc6f 100644 --- a/app/assets/stylesheets/page_bundles/notes/_diff_comments.scss +++ b/app/assets/stylesheets/page_bundles/notes/_diff_comments.scss @@ -76,7 +76,7 @@ } // Vue refactored diff discussion adjustments -.files { +.files, .rd-diff-discussions-row { .diff-discussions { .note-discussion.timeline-entry { padding-left: 0; @@ -207,7 +207,7 @@ } } -.diff-file { +.diff-file, .rd-diff-discussions-row { .diff-grid-left:hover, .diff-grid-right:hover, .is-over { diff --git a/app/components/rapid_diffs/app_component.html.haml b/app/components/rapid_diffs/app_component.html.haml index c7f80b61ae2c344218a42d67ad3a00093d2de350..a5cf2e86fca0d52dfea4bc478602382a06dfae86 100644 --- a/app/components/rapid_diffs/app_component.html.haml +++ b/app/components/rapid_diffs/app_component.html.haml @@ -1,6 +1,6 @@ - if !lazy? - - helpers.add_page_startup_api_call diffs_stats_endpoint - - helpers.add_page_startup_api_call diff_files_endpoint + - prefetch_endpoints.each do |endpoint| + - helpers.add_page_startup_api_call endpoint - if diffs_stream_url - helpers.content_for :startup_js do - javascript_tag nonce: content_security_policy_nonce do diff --git a/app/components/rapid_diffs/app_component.rb b/app/components/rapid_diffs/app_component.rb index 8cd2652c60ebd076b6b2ea41a3d0b436bf28bf6f..c91c1176d5449c537e7942c53724c9f8232f10db 100644 --- a/app/components/rapid_diffs/app_component.rb +++ b/app/components/rapid_diffs/app_component.rb @@ -15,6 +15,8 @@ def initialize(presenter) @presenter = presenter end + protected + def app_data { diffs_stream_url: diffs_stream_url, @@ -30,6 +32,10 @@ def app_data } end + def prefetch_endpoints + [diffs_stats_endpoint, diff_files_endpoint] + end + def update_user_endpoint helpers.expose_path(helpers.api_v4_user_preferences_path) end diff --git a/app/components/rapid_diffs/commit_app_component.rb b/app/components/rapid_diffs/commit_app_component.rb new file mode 100644 index 0000000000000000000000000000000000000000..8b51902b651a06eb3238fcf5a16d3635c2af7390 --- /dev/null +++ b/app/components/rapid_diffs/commit_app_component.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module RapidDiffs + class CommitAppComponent < AppComponent + delegate :user_permissions, :notes_endpoint, :preview_markdown_endpoint, :markdown_docs_path, :register_path, + :sign_in_path, :noteable_type, to: :presenter + + protected + + def app_data + { + **super, + user_permissions: user_permissions, + notes_endpoint: notes_endpoint, + preview_markdown_endpoint: preview_markdown_endpoint, + markdown_docs_path: markdown_docs_path, + register_path: register_path, + sign_in_path: sign_in_path, + noteable_type: noteable_type + } + end + + def prefetch_endpoints + [*super, notes_endpoint] + end + end +end diff --git a/app/components/rapid_diffs/diff_file_component.rb b/app/components/rapid_diffs/diff_file_component.rb index 63e32688911778d7d4dd009e9584905f66acf50d..6735be9229a6d9de43dd9fca5125700bb051be23 100644 --- a/app/components/rapid_diffs/diff_file_component.rb +++ b/app/components/rapid_diffs/diff_file_component.rb @@ -21,7 +21,9 @@ def file_data params = tree_join(@diff_file.content_sha, @diff_file.file_path) { viewer: viewer_component.viewer_name, - diff_lines_path: project_blob_diff_lines_path(project, params) + diff_lines_path: project_blob_diff_lines_path(project, params), + old_path: @diff_file.old_path, + new_path: @diff_file.new_path } end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 43e90e52a83c40a99c49b6550d79b92aa2d45c5f..bf1fd1af740e45d8f5412e5e45dba2a11d29a79a 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -144,6 +144,14 @@ def note_json(note) attrs[:discussion] = discussion_serializer.represent(discussion, context: self) elsif use_note_serializer? attrs.merge!(note_serializer.represent(note)) + elsif params[:return_commit_notes] == 'true' + discussion = note.discussion + prepare_notes_for_rendering(discussion.notes) + + attrs[:discussion] = commit_discussion_serializer.represent( + discussion, + context: self + ) else attrs.merge!( id: note.id, @@ -302,6 +310,15 @@ def discussion_serializer ) end + def commit_discussion_serializer + CommitDiscussionSerializer.new( + project: project, + noteable: noteable, + current_user: current_user, + note_entity: CommitNoteEntity + ) + end + def note_project return unless project diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index c7a95dbd539e6d917717f80b448e1ebf3418a0c5..83cb7b5bd24f9d90ebf9d88516c9145b6adabb9f 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -50,6 +50,14 @@ def diff_for_path render_diff_for_path(@commit.diffs(diff_options)) end + def discussions + define_note_vars + + all_discussions = (@grouped_diff_discussions.values.flatten + @discussions) + + render json: { discussions: discussion_serializer.represent(all_discussions) } + end + def diff_files respond_to do |format| format.html do @@ -152,7 +160,9 @@ def rapid_diffs @rapid_diffs_presenter = RapidDiffs::CommitPresenter.new( @commit, diff_view, - commit_diff_options + commit_diff_options, + nil, + current_user ) show @@ -160,6 +170,15 @@ def rapid_diffs private + def discussion_serializer + CommitDiscussionSerializer.new( + project: project, + noteable: commit, + current_user: current_user, + note_entity: CommitNoteEntity + ) + end + def commit_diff_options opts = diff_options opts[:ignore_whitespace_change] = true if params[:format] == 'diff' diff --git a/app/presenters/rapid_diffs/base_presenter.rb b/app/presenters/rapid_diffs/base_presenter.rb index e41a63fb02bf313b21ef9256c847ed175252f639..96a9addb51a5c3cafb8bf7280048808dae771557 100644 --- a/app/presenters/rapid_diffs/base_presenter.rb +++ b/app/presenters/rapid_diffs/base_presenter.rb @@ -38,6 +38,10 @@ def diff_file_endpoint raise NotImplementedError end + def notes_endpoint + nil + end + def should_sort_metadata_files? false end diff --git a/app/presenters/rapid_diffs/commit_presenter.rb b/app/presenters/rapid_diffs/commit_presenter.rb index ca9c724e5b6a04a4ae956ae316ae23798f51a984..c01e460d44a77cdcad21ddeefe238c8ac7292634 100644 --- a/app/presenters/rapid_diffs/commit_presenter.rb +++ b/app/presenters/rapid_diffs/commit_presenter.rb @@ -6,6 +6,11 @@ class CommitPresenter < BasePresenter presents ::Commit, as: :resource + def initialize(subject, diff_view, diff_options, request_params = nil, current_user = nil) + super(subject, diff_view, diff_options, request_params) + @current_user = current_user + end + def diffs_stats_endpoint diffs_stats_project_commit_path(resource.project, resource.id) end @@ -18,6 +23,10 @@ def diff_file_endpoint diff_file_project_commit_path(resource.project, resource.id) end + def notes_endpoint + discussions_project_commit_path(resource.project, resource.id) + end + override(:reload_stream_url) def reload_stream_url(offset: nil, diff_view: nil) diffs_stream_project_commit_path( @@ -27,5 +36,31 @@ def reload_stream_url(offset: nil, diff_view: nil) view: diff_view ) end + + def user_permissions + { + can_create_note: can?(@current_user, :create_note, resource.project) + } + end + + def preview_markdown_endpoint + project_preview_markdown_path(resource.project) + end + + def markdown_docs_path + help_page_path('user/markdown.md') + end + + def register_path + new_user_registration_path(redirect_to_referer: 'yes') + end + + def sign_in_path + new_user_session_path(redirect_to_referer: 'yes') + end + + def noteable_type + 'Commit' + end end end diff --git a/app/serializers/commit_discussion_entity.rb b/app/serializers/commit_discussion_entity.rb new file mode 100644 index 0000000000000000000000000000000000000000..39fc207d03a87419b969bb55cc282fc778a5af6b --- /dev/null +++ b/app/serializers/commit_discussion_entity.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class CommitDiscussionEntity < DiscussionEntity + unexpose :diff_file + unexpose :truncated_diff_lines +end diff --git a/app/serializers/commit_discussion_serializer.rb b/app/serializers/commit_discussion_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..6d325af08818ad2edd09efba4359be31ac103626 --- /dev/null +++ b/app/serializers/commit_discussion_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class CommitDiscussionSerializer < DiscussionSerializer + entity CommitDiscussionEntity +end diff --git a/app/serializers/commit_note_entity.rb b/app/serializers/commit_note_entity.rb new file mode 100644 index 0000000000000000000000000000000000000000..e3c738743ad7b4feddc5ffd72802ebc05da7226f --- /dev/null +++ b/app/serializers/commit_note_entity.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class CommitNoteEntity < ProjectNoteEntity + expose :position + expose :original_position + unexpose :discussion +end diff --git a/app/views/projects/commit/rapid_diffs.html.haml b/app/views/projects/commit/rapid_diffs.html.haml index 51256b628a37d52b7583fe482eb64b829ede3612..9511c3f053c03ebc3b245c192de4f0f24606866f 100644 --- a/app/views/projects/commit/rapid_diffs.html.haml +++ b/app/views/projects/commit/rapid_diffs.html.haml @@ -9,8 +9,9 @@ -# required for file browser styles, remove when .diff-tree-list is refactored into Rapid Diffs - add_page_specific_style 'page_bundles/merge_requests' - add_page_specific_style 'page_bundles/commit_rapid_diffs' +- add_issuable_stylesheet .container-fluid{ class: [container_class] } = render "commit_box" = render "ci_menu" - = render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter) + = render ::RapidDiffs::CommitAppComponent.new(@rapid_diffs_presenter) diff --git a/config/routes/repository.rb b/config/routes/repository.rb index 1699df23cbe877f40fa9877ec4259de9ea38c4d3..d747ac6ca117b0970be456d24fbe225e64d19c4b 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -121,6 +121,7 @@ get :diff_files_metadata get :diffs_stats get :diff_file + get :discussions end end