diff options
Diffstat (limited to 'app')
14 files changed, 108 insertions, 49 deletions
diff --git a/app/assets/javascripts/code_navigation/components/app.vue b/app/assets/javascripts/code_navigation/components/app.vue index 0e0160b9832..d738c914125 100644 --- a/app/assets/javascripts/code_navigation/components/app.vue +++ b/app/assets/javascripts/code_navigation/components/app.vue @@ -1,6 +1,7 @@ <script> import { mapActions, mapState } from 'vuex'; import Popover from './popover.vue'; +import eventHub from '../../notes/event_hub'; export default { components: { @@ -10,24 +11,27 @@ export default { ...mapState(['currentDefinition', 'currentDefinitionPosition', 'definitionPathPrefix']), }, mounted() { - this.blobViewer = document.querySelector('.blob-viewer'); + this.body = document.body; + + eventHub.$on('showBlobInteractionZones', this.showBlobInteractionZones); this.addGlobalEventListeners(); this.fetchData(); }, beforeDestroy() { + eventHub.$off('showBlobInteractionZones', this.showBlobInteractionZones); this.removeGlobalEventListeners(); }, methods: { - ...mapActions(['fetchData', 'showDefinition']), + ...mapActions(['fetchData', 'showDefinition', 'showBlobInteractionZones']), addGlobalEventListeners() { - if (this.blobViewer) { - this.blobViewer.addEventListener('click', this.showDefinition); + if (this.body) { + this.body.addEventListener('click', this.showDefinition); } }, removeGlobalEventListeners() { - if (this.blobViewer) { - this.blobViewer.removeEventListener('click', this.showDefinition); + if (this.body) { + this.body.removeEventListener('click', this.showDefinition); } }, }, diff --git a/app/assets/javascripts/code_navigation/index.js b/app/assets/javascripts/code_navigation/index.js index 2222c986dfe..362c26ae065 100644 --- a/app/assets/javascripts/code_navigation/index.js +++ b/app/assets/javascripts/code_navigation/index.js @@ -5,10 +5,10 @@ import App from './components/app.vue'; Vue.use(Vuex); -export default () => { +export default initialData => { const el = document.getElementById('js-code-navigation'); - store.dispatch('setInitialData', el.dataset); + store.dispatch('setInitialData', initialData); return new Vue({ el, diff --git a/app/assets/javascripts/code_navigation/store/actions.js b/app/assets/javascripts/code_navigation/store/actions.js index 9b607023f39..6ecede32944 100644 --- a/app/assets/javascripts/code_navigation/store/actions.js +++ b/app/assets/javascripts/code_navigation/store/actions.js @@ -12,20 +12,25 @@ export default { fetchData({ commit, dispatch, state }) { commit(types.REQUEST_DATA); - axios - .get(state.codeNavUrl) - .then(({ data }) => { - const normalizedData = data.reduce((acc, d) => { - if (d.hover) { - acc[`${d.start_line}:${d.start_char}`] = d; - addInteractionClass(d); - } - return acc; - }, {}); - - commit(types.REQUEST_DATA_SUCCESS, normalizedData); - }) - .catch(() => dispatch('requestDataError')); + state.blobs.forEach(({ path, codeNavigationPath }) => { + axios + .get(codeNavigationPath) + .then(({ data }) => { + const normalizedData = data.reduce((acc, d) => { + if (d.hover) { + acc[`${d.start_line}:${d.start_char}`] = d; + addInteractionClass(path, d); + } + return acc; + }, {}); + + commit(types.REQUEST_DATA_SUCCESS, { path, normalizedData }); + }) + .catch(() => dispatch('requestDataError')); + }); + }, + showBlobInteractionZones({ state }, path) { + Object.values(state.data[path]).forEach(d => addInteractionClass(path, d)); }, showDefinition({ commit, state }, { target: el }) { let definition; @@ -39,15 +44,28 @@ export default { getCurrentHoverElement().classList.remove('hll'); } - if (el.classList.contains('js-code-navigation') && !isCurrentElementPopoverOpen) { + const blobEl = el.closest('[data-path]'); + + if (!blobEl) { + commit(types.SET_CURRENT_DEFINITION, { definition, position }); + + return; + } + + const data = state.data[blobEl.dataset.path]; + + if (!data) return; + + if (el.closest('.js-code-navigation') && !isCurrentElementPopoverOpen) { const { lineIndex, charIndex } = el.dataset; + const { x, y } = el.getBoundingClientRect(); position = { - x: el.offsetLeft, - y: el.offsetTop, + x: x || 0, + y: y + window.scrollY || 0, height: el.offsetHeight, }; - definition = state.data[`${lineIndex}:${charIndex}`]; + definition = data[`${lineIndex}:${charIndex}`]; el.classList.add('hll'); diff --git a/app/assets/javascripts/code_navigation/store/mutations.js b/app/assets/javascripts/code_navigation/store/mutations.js index febb7afe2f8..84b1c264418 100644 --- a/app/assets/javascripts/code_navigation/store/mutations.js +++ b/app/assets/javascripts/code_navigation/store/mutations.js @@ -1,16 +1,16 @@ import * as types from './mutation_types'; export default { - [types.SET_INITIAL_DATA](state, { codeNavUrl, definitionPathPrefix }) { - state.codeNavUrl = codeNavUrl; + [types.SET_INITIAL_DATA](state, { blobs, definitionPathPrefix }) { + state.blobs = blobs; state.definitionPathPrefix = definitionPathPrefix; }, [types.REQUEST_DATA](state) { state.loading = true; }, - [types.REQUEST_DATA_SUCCESS](state, data) { + [types.REQUEST_DATA_SUCCESS](state, { path, normalizedData }) { state.loading = false; - state.data = data; + state.data = { ...state.data, [path]: normalizedData }; }, [types.REQUEST_DATA_ERROR](state) { state.loading = false; diff --git a/app/assets/javascripts/code_navigation/store/state.js b/app/assets/javascripts/code_navigation/store/state.js index a7b3b289db4..ffe44ec5381 100644 --- a/app/assets/javascripts/code_navigation/store/state.js +++ b/app/assets/javascripts/code_navigation/store/state.js @@ -1,7 +1,5 @@ export default () => ({ - projectPath: null, - commitId: null, - blobPath: null, + blobs: [], loading: false, data: null, currentDefinition: null, diff --git a/app/assets/javascripts/code_navigation/utils/index.js b/app/assets/javascripts/code_navigation/utils/index.js index 2dee0de6501..4d118852a94 100644 --- a/app/assets/javascripts/code_navigation/utils/index.js +++ b/app/assets/javascripts/code_navigation/utils/index.js @@ -3,18 +3,25 @@ export const cachedData = new Map(); export const getCurrentHoverElement = () => cachedData.get('current'); export const setCurrentHoverElement = el => cachedData.set('current', el); -export const addInteractionClass = d => { - let charCount = 0; - const line = document.getElementById(`LC${d.start_line + 1}`); - const el = [...line.childNodes].find(({ textContent }) => { - if (charCount === d.start_char) return true; - charCount += textContent.length; - return false; - }); +export const addInteractionClass = (path, d) => { + const lineNumber = d.start_line + 1; + const lines = document + .querySelector(`[data-path="${path}"]`) + .querySelectorAll(`.blob-content #LC${lineNumber}, .line_content:not(.old) #LC${lineNumber}`); + if (!lines?.length) return; + + lines.forEach(line => { + let charCount = 0; + const el = [...line.childNodes].find(({ textContent }) => { + if (charCount === d.start_char) return true; + charCount += textContent.length; + return false; + }); - if (el) { - el.setAttribute('data-char-index', d.start_char); - el.setAttribute('data-line-index', d.start_line); - el.classList.add('cursor-pointer', 'code-navigation', 'js-code-navigation'); - } + if (el) { + el.setAttribute('data-char-index', d.start_char); + el.setAttribute('data-line-index', d.start_line); + el.classList.add('cursor-pointer', 'code-navigation', 'js-code-navigation'); + } + }); }; diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index a5ffa84e3fb..5656bfc4707 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -12,6 +12,7 @@ import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_ import NoteForm from '../../notes/components/note_form.vue'; import ImageDiffOverlay from './image_diff_overlay.vue'; import DiffDiscussions from './diff_discussions.vue'; +import eventHub from '../../notes/event_hub'; import { IMAGE_DIFF_POSITION_TYPE } from '../constants'; import { getDiffMode } from '../store/utils'; import { diffViewerModes } from '~/ide/constants'; @@ -77,6 +78,13 @@ export default { return this.getUserData; }, }, + updated() { + if (window.gon?.features?.codeNavigation) { + this.$nextTick(() => { + eventHub.$emit('showBlobInteractionZones', this.diffFile.new_path); + }); + } + }, methods: { ...mapActions('diffs', ['saveDiffDiscussion', 'closeDiffFileCommentForm']), handleSaveNote(note) { diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 8babc05f1ce..82ca3749ac1 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -145,6 +145,7 @@ export default { :class="{ 'is-active': currentDiffFileId === file.file_hash, }" + :data-path="file.new_path" class="diff-file file-holder" > <diff-file-header diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 18bbdf402ee..93c242e32ac 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -129,6 +129,18 @@ export const fetchDiffFilesBatch = ({ commit, state }) => { if (!pagination.next_page) { commit(types.SET_RETRIEVING_BATCHES, false); + if (gon.features?.codeNavigation) { + // eslint-disable-next-line promise/catch-or-return,promise/no-nesting + import('~/code_navigation').then(m => + m.default({ + blobs: state.diffFiles.map(f => ({ + path: f.new_path, + codeNavigationPath: f.code_navigation_path, + })), + definitionPathPrefix: state.definitionPathPrefix, + }), + ); + } } return pagination.next_page; diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index 9ff68a88f95..557aea0c5de 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -33,8 +33,16 @@ document.addEventListener('DOMContentLoaded', () => { GpgBadges.fetch(); if (gon.features?.codeNavigation) { + const el = document.getElementById('js-code-navigation'); + const { codeNavigationPath, blobPath, definitionPathPrefix } = el.dataset; + // eslint-disable-next-line promise/catch-or-return - import('~/code_navigation').then(m => m.default()); + import('~/code_navigation').then(m => + m.default({ + blobs: [{ path: blobPath, codeNavigationPath }], + definitionPathPrefix, + }), + ); } if (gon.features?.suggestPipeline) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a81fd0f7dc9..26de200a1c1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -23,6 +23,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:deploy_from_footer, @project, default_enabled: true) push_frontend_feature_flag(:single_mr_diff_view, @project, default_enabled: true) push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline) + push_frontend_feature_flag(:code_navigation, @project) end before_action do diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 8e8a6b847df..a0f644717ad 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -10,7 +10,7 @@ #blob-content-holder.blob-content-holder - if @code_navigation_path - #js-code-navigation{ data: { code_nav_url: @code_navigation_path, definition_path_prefix: project_blob_path(@project, @ref) } } + #js-code-navigation{ data: { code_navigation_path: @code_navigation_path, blob_path: blob.path, definition_path_prefix: project_blob_path(@project, @ref) } } %article.file-holder = render 'projects/blob/header', blob: blob = render 'projects/blob/content', blob: blob diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 4304a18558e..7e146a36d84 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -51,6 +51,8 @@ .tab-content#diff-notes-app #js-diff-file-finder + - if native_code_navigation_enabled?(@project) + #js-code-navigation = render "projects/merge_requests/tabs/pane", id: "notes", class: "notes voting_notes" do .row %section.col-md-12 diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index d7e57fc0d01..18f51f0c0c8 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -10,7 +10,7 @@ %a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } = link_icon = i - .blob-content{ data: { blob_id: blob.id } } + .blob-content{ data: { blob_id: blob.id, path: blob.path } } %pre.code.highlight %code = blob.present.highlight |