1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
import { mapGetters, mapActions, mapState } from 'vuex';
import { scrollToElement, contentTop } from '~/lib/utils/common_utils';
function isOverviewPage() {
return window.mrTabs?.currentAction === 'show';
}
function getAllDiscussionElements() {
const containerEl = isOverviewPage() ? '.tab-pane.notes' : '.diffs';
return Array.from(
document.querySelectorAll(
`${containerEl} div[data-discussion-id][data-discussion-resolvable]: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();
if (!isOverviewPage() && !discussion) {
window.mrTabs?.eventHub.$once('NotesAppReady', () => {
handleJumpForBothPages(getDiscussion, ctx, fn, scrollOptions);
});
window.mrTabs?.setCurrentAction('show');
window.mrTabs?.tabShown('show', undefined, false);
return;
}
if (discussion) {
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(() => {});
},
},
};
|