import { mapGetters, mapActions, mapState } from 'vuex'; import { scrollToElement, contentTop } from '~/lib/utils/common_utils'; function getAllDiscussionElements() { const containerEl = window.mrTabs?.currentAction === 'diffs' ? '.diffs' : '.tab-pane.notes'; return Array.from( document.querySelectorAll( `${containerEl} div[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) { const discussion = getDiscussion(); const id = discussion.dataset.discussionId; ctx.expandDiscussion({ discussionId: id }); scrollToElement(discussion, scrollOptions); } export default { computed: { ...mapGetters([ 'nextUnresolvedDiscussionId', 'previousUnresolvedDiscussionId', 'getDiscussion', ]), ...mapState({ currentDiscussionId: (state) => state.notes.currentDiscussionId, }), }, methods: { ...mapActions(['expandDiscussion', 'setCurrentDiscussionId']), ...mapActions('diffs', ['scrollToFile', 'disableVirtualScroller']), async jumpToNextDiscussion(scrollOptions) { await this.disableVirtualScroller(); handleJumpForBothPages( getNextDiscussion, this, this.nextUnresolvedDiscussionId, scrollOptions, ); }, async jumpToPreviousDiscussion(scrollOptions) { await this.disableVirtualScroller(); handleJumpForBothPages( getPreviousDiscussion, this, this.previousUnresolvedDiscussionId, scrollOptions, ); }, jumpToFirstUnresolvedDiscussion() { this.setCurrentDiscussionId(null) .then(() => { this.jumpToNextDiscussion(); }) .catch(() => {}); }, }, };