debian-mirror-gitlab/app/assets/javascripts/notes/mixins/discussion_navigation.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

237 lines
6.6 KiB
JavaScript
Raw Normal View History

2020-03-13 15:44:24 +05:30
import { mapGetters, mapActions, mapState } from 'vuex';
2022-10-11 01:57:18 +05:30
import { scrollToElementWithContext, scrollToElement, contentTop } from '~/lib/utils/common_utils';
2022-06-21 17:19:12 +05:30
import { updateHistory } from '~/lib/utils/url_utility';
2020-05-24 23:13:21 +05:30
import eventHub from '../event_hub';
2018-11-18 11:00:15 +05:30
2020-04-08 14:13:33 +05:30
/**
* @param {string} selector
* @returns {boolean}
*/
2022-10-11 01:57:18 +05:30
function scrollTo(selector, { withoutContext = false, offset = 0 } = {}) {
2020-04-08 14:13:33 +05:30
const el = document.querySelector(selector);
2021-02-22 17:27:13 +05:30
const scrollFunction = withoutContext ? scrollToElement : scrollToElementWithContext;
2020-04-08 14:13:33 +05:30
if (el) {
2021-12-11 22:18:48 +05:30
scrollFunction(el, {
2022-04-04 11:22:00 +05:30
behavior: 'auto',
2022-10-11 01:57:18 +05:30
offset,
2021-12-11 22:18:48 +05:30
});
2020-04-08 14:13:33 +05:30
return true;
}
return false;
}
2021-12-11 22:18:48 +05:30
function updateUrlWithNoteId(noteId) {
const newHistoryEntry = {
state: null,
title: window.title,
url: `#note_${noteId}`,
replace: true,
};
2022-04-04 11:22:00 +05:30
if (noteId) {
2021-12-11 22:18:48 +05:30
// Temporarily mask the ID to avoid the browser default
// scrolling taking over which is broken with virtual
// scrolling enabled.
const note = document.querySelector(`#note_${noteId}`);
note?.setAttribute('id', `masked::${note.id}`);
// Update the hash now that the ID "doesn't exist" in the page
updateHistory(newHistoryEntry);
// Unmask the note's ID
note?.setAttribute('id', `note_${noteId}`);
}
}
2020-04-08 14:13:33 +05:30
/**
* @param {object} self Component instance with mixin applied
* @param {string} id Discussion id we are jumping to
*/
2021-12-11 22:18:48 +05:30
function diffsJump({ expandDiscussion }, id, firstNoteId) {
2020-04-08 14:13:33 +05:30
const selector = `ul.notes[data-discussion-id="${id}"]`;
2021-12-11 22:18:48 +05:30
eventHub.$once('scrollToDiscussion', () => {
scrollTo(selector);
// Wait for the discussion scroll before updating to the more specific ID
setTimeout(() => updateUrlWithNoteId(firstNoteId), 0);
});
2020-04-08 14:13:33 +05:30
expandDiscussion({ discussionId: id });
}
/**
* @param {object} self Component instance with mixin applied
* @param {string} id Discussion id we are jumping to
* @returns {boolean}
*/
function discussionJump({ expandDiscussion }, id) {
const selector = `div.discussion[data-discussion-id="${id}"]`;
expandDiscussion({ discussionId: id });
2022-10-11 01:57:18 +05:30
return scrollTo(selector, {
withoutContext: true,
offset: window.gon?.features?.movedMrSidebar ? -28 : 0,
});
2020-04-08 14:13:33 +05:30
}
/**
* @param {object} self Component instance with mixin applied
* @param {string} id Discussion id we are jumping to
*/
function switchToDiscussionsTabAndJumpTo(self, id) {
window.mrTabs.eventHub.$once('MergeRequestTabChange', () => {
setTimeout(() => discussionJump(self, id), 0);
});
window.mrTabs.tabShown('show');
}
/**
* @param {object} self Component instance with mixin applied
* @param {object} discussion Discussion we are jumping to
*/
function jumpToDiscussion(self, discussion) {
2021-12-11 22:18:48 +05:30
const { id, diff_discussion: isDiffDiscussion, notes } = discussion;
const firstNoteId = notes?.[0]?.id;
2020-04-08 14:13:33 +05:30
if (id) {
const activeTab = window.mrTabs.currentAction;
if (activeTab === 'diffs' && isDiffDiscussion) {
2021-12-11 22:18:48 +05:30
diffsJump(self, id, firstNoteId);
2020-04-08 14:13:33 +05:30
} else {
switchToDiscussionsTabAndJumpTo(self, id);
}
}
}
/**
* @param {object} self Component instance with mixin applied
* @param {function} fn Which function used to get the target discussion's id
*/
2022-10-11 01:57:18 +05:30
function handleDiscussionJump(self, fn) {
2020-04-08 14:13:33 +05:30
const isDiffView = window.mrTabs.currentAction === 'diffs';
2022-10-11 01:57:18 +05:30
const targetId = fn(self.currentDiscussionId, isDiffView);
2020-04-08 14:13:33 +05:30
const discussion = self.getDiscussion(targetId);
2020-10-24 23:57:45 +05:30
const discussionFilePath = discussion?.diff_file?.file_path;
2020-07-28 23:09:34 +05:30
2022-04-04 11:22:00 +05:30
window.location.hash = '';
2021-12-11 22:18:48 +05:30
2020-07-28 23:09:34 +05:30
if (discussionFilePath) {
2021-12-11 22:18:48 +05:30
self.scrollToFile({
path: discussionFilePath,
});
2020-07-28 23:09:34 +05:30
}
self.$nextTick(() => {
jumpToDiscussion(self, discussion);
self.setCurrentDiscussionId(targetId);
});
2020-04-08 14:13:33 +05:30
}
2022-10-11 01:57:18 +05:30
function getAllDiscussionElements() {
return Array.from(
document.querySelectorAll('[data-discussion-id]:not([data-discussion-resolved])'),
);
}
function hasReachedPageEnd() {
return document.body.scrollHeight <= Math.ceil(window.scrollY + window.innerHeight);
}
function findNextClosestVisibleDiscussion(discussionElements) {
const offsetHeight = contentTop();
let isActive;
const index = discussionElements.findIndex((element) => {
const { y } = element.getBoundingClientRect();
const visibleHorizontalOffset = Math.ceil(y) - offsetHeight;
// handle rect rounding errors
isActive = visibleHorizontalOffset < 2;
return visibleHorizontalOffset >= 0;
});
return [discussionElements[index], index, isActive];
}
function getNextDiscussion() {
const discussionElements = getAllDiscussionElements();
const firstDiscussion = discussionElements[0];
if (hasReachedPageEnd()) {
return firstDiscussion;
}
const [nextClosestDiscussion, index, isActive] = findNextClosestVisibleDiscussion(
discussionElements,
);
if (nextClosestDiscussion && !isActive) {
return nextClosestDiscussion;
}
const nextDiscussion = discussionElements[index + 1];
if (!nextClosestDiscussion || !nextDiscussion) {
return firstDiscussion;
}
return nextDiscussion;
}
function getPreviousDiscussion() {
const discussionElements = getAllDiscussionElements();
const lastDiscussion = discussionElements[discussionElements.length - 1];
const [, index] = findNextClosestVisibleDiscussion(discussionElements);
const previousDiscussion = discussionElements[index - 1];
if (previousDiscussion) {
return previousDiscussion;
}
return lastDiscussion;
}
function handleJumpForBothPages(getDiscussion, ctx, fn, scrollOptions) {
if (window.mrTabs.currentAction !== 'show') {
handleDiscussionJump(ctx, fn);
} else {
const discussion = getDiscussion();
const id = discussion.dataset.discussionId;
ctx.expandDiscussion({ discussionId: id });
scrollToElement(discussion, scrollOptions);
}
}
2018-11-18 11:00:15 +05:30
export default {
2020-03-13 15:44:24 +05:30
computed: {
...mapGetters([
'nextUnresolvedDiscussionId',
'previousUnresolvedDiscussionId',
'getDiscussion',
]),
...mapState({
2021-03-08 18:12:59 +05:30
currentDiscussionId: (state) => state.notes.currentDiscussionId,
2020-03-13 15:44:24 +05:30
}),
},
2018-11-18 11:00:15 +05:30
methods: {
2020-03-13 15:44:24 +05:30
...mapActions(['expandDiscussion', 'setCurrentDiscussionId']),
2020-07-28 23:09:34 +05:30
...mapActions('diffs', ['scrollToFile']),
2020-03-13 15:44:24 +05:30
2022-10-11 01:57:18 +05:30
jumpToNextDiscussion(scrollOptions) {
handleJumpForBothPages(
getNextDiscussion,
this,
this.nextUnresolvedDiscussionId,
scrollOptions,
);
2020-03-13 15:44:24 +05:30
},
2022-10-11 01:57:18 +05:30
jumpToPreviousDiscussion(scrollOptions) {
handleJumpForBothPages(
getPreviousDiscussion,
this,
this.previousUnresolvedDiscussionId,
scrollOptions,
);
2020-03-13 15:44:24 +05:30
},
2020-10-24 23:57:45 +05:30
jumpToFirstUnresolvedDiscussion() {
this.setCurrentDiscussionId(null)
.then(() => {
this.jumpToNextDiscussion();
})
.catch(() => {});
},
2018-11-18 11:00:15 +05:30
},
};