summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/code_navigation/components/app.vue16
-rw-r--r--app/assets/javascripts/code_navigation/index.js4
-rw-r--r--app/assets/javascripts/code_navigation/store/actions.js54
-rw-r--r--app/assets/javascripts/code_navigation/store/mutations.js8
-rw-r--r--app/assets/javascripts/code_navigation/store/state.js4
-rw-r--r--app/assets/javascripts/code_navigation/utils/index.js33
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue8
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue1
-rw-r--r--app/assets/javascripts/diffs/store/actions.js12
-rw-r--r--app/assets/javascripts/pages/projects/blob/show/index.js10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml2
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