Skip to content

Commit 2b064b8

Browse files
authored
Refactor legacy line-number and scroll code (#33094)
1. remove jquery 2. rewrite the "line number selection", fix various edge cases 3. fix the scroll
1 parent 188e0ee commit 2b064b8

File tree

5 files changed

+73
-141
lines changed

5 files changed

+73
-141
lines changed

web_src/css/base.css

+5-4
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,13 @@ a.label,
336336
border-color: var(--color-secondary);
337337
}
338338

339+
.ui.dropdown .menu > .header {
340+
text-transform: none; /* reset fomantic's "uppercase" */
341+
}
342+
339343
.ui.dropdown .menu > .header:not(.ui) {
340344
color: var(--color-text);
345+
font-size: 0.95em; /* reset fomantic's small font-size */
341346
}
342347

343348
.ui.dropdown .menu > .item {
@@ -691,10 +696,6 @@ input:-webkit-autofill:active,
691696
box-shadow: 0 6px 18px var(--color-shadow) !important;
692697
}
693698

694-
.ui.dropdown .menu > .header {
695-
font-size: 0.8em;
696-
}
697-
698699
.ui .text.left {
699700
text-align: left !important;
700701
}

web_src/js/features/repo-code.test.ts

-17
This file was deleted.

web_src/js/features/repo-code.ts

+59-103
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import $ from 'jquery';
21
import {svg} from '../svg.ts';
3-
import {invertFileFolding} from './file-fold.ts';
42
import {createTippy} from '../modules/tippy.ts';
53
import {clippie} from 'clippie';
64
import {toAbsoluteUrl} from '../utils.ts';
7-
8-
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
9-
export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
5+
import {addDelegatedEventListener} from '../utils/dom.ts';
106

117
function changeHash(hash: string) {
128
if (window.history.pushState) {
@@ -16,20 +12,11 @@ function changeHash(hash: string) {
1612
}
1713
}
1814

19-
function isBlame() {
20-
return Boolean(document.querySelector('div.blame'));
21-
}
15+
// it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
16+
function selectRange(range: string): Element {
17+
for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active');
18+
const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`);
2219

23-
function getLineEls() {
24-
return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
25-
}
26-
27-
function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
28-
for (const el of $linesEls) {
29-
el.closest('tr').classList.remove('active');
30-
}
31-
32-
// add hashchange to permalink
3320
const refInNewIssue = document.querySelector('a.ref-in-new-issue');
3421
const copyPermalink = document.querySelector('a.copy-line-permalink');
3522
const viewGitBlame = document.querySelector('a.view_git_blame');
@@ -59,37 +46,30 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
5946
copyPermalink.setAttribute('data-url', link);
6047
};
6148

62-
if ($selectionStartEls) {
63-
let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1));
64-
let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1));
65-
let c;
66-
if (a !== b) {
67-
if (a > b) {
68-
c = a;
69-
a = b;
70-
b = c;
71-
}
72-
const classes = [];
73-
for (let i = a; i <= b; i++) {
74-
classes.push(`[rel=L${i}]`);
75-
}
76-
$linesEls.filter(classes.join(',')).each(function () {
77-
this.closest('tr').classList.add('active');
78-
});
79-
changeHash(`#L${a}-L${b}`);
80-
81-
updateIssueHref(`L${a}-L${b}`);
82-
updateViewGitBlameFragment(`L${a}-L${b}`);
83-
updateCopyPermalinkUrl(`L${a}-L${b}`);
84-
return;
85-
}
49+
const rangeFields = range ? range.split('-') : [];
50+
const start = rangeFields[0] ?? '';
51+
if (!start) return null;
52+
const stop = rangeFields[1] || start;
53+
54+
// format is i.e. 'L14-L26'
55+
let startLineNum = parseInt(start.substring(1));
56+
let stopLineNum = parseInt(stop.substring(1));
57+
if (startLineNum > stopLineNum) {
58+
const tmp = startLineNum;
59+
startLineNum = stopLineNum;
60+
stopLineNum = tmp;
61+
range = `${stop}-${start}`;
8662
}
87-
$selectionEndEl[0].closest('tr').classList.add('active');
88-
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
8963

90-
updateIssueHref($selectionEndEl[0].getAttribute('rel'));
91-
updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel'));
92-
updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel'));
64+
const first = elLineNums[startLineNum - 1] ?? null;
65+
for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) {
66+
elLineNums[i].closest('tr').classList.add('active');
67+
}
68+
changeHash(`#${range}`);
69+
updateIssueHref(range);
70+
updateViewGitBlameFragment(range);
71+
updateCopyPermalinkUrl(range);
72+
return first;
9373
}
9474

9575
function showLineButton() {
@@ -103,6 +83,8 @@ function showLineButton() {
10383

10484
// find active row and add button
10585
const tr = document.querySelector('.code-view tr.active');
86+
if (!tr) return;
87+
10688
const td = tr.querySelector('td.lines-num');
10789
const btn = document.createElement('button');
10890
btn.classList.add('code-line-button', 'ui', 'basic', 'button');
@@ -128,62 +110,36 @@ function showLineButton() {
128110
}
129111

130112
export function initRepoCodeView() {
131-
if ($('.code-view .lines-num').length > 0) {
132-
$(document).on('click', '.lines-num span', function (e) {
133-
const linesEls = getLineEls();
134-
const selectedEls = Array.from(linesEls).filter((el) => {
135-
return el.matches(`[rel=${this.getAttribute('id')}]`);
136-
});
137-
138-
let from;
139-
if (e.shiftKey) {
140-
from = Array.from(linesEls).filter((el) => {
141-
return el.closest('tr').classList.contains('active');
142-
});
143-
}
144-
selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
145-
window.getSelection().removeAllRanges();
146-
showLineButton();
147-
});
148-
149-
$(window).on('hashchange', () => {
150-
let m = rangeAnchorRegex.exec(window.location.hash);
151-
const $linesEls = $(getLineEls());
152-
let $first;
153-
if (m) {
154-
$first = $linesEls.filter(`[rel=${m[1]}]`);
155-
if ($first.length) {
156-
selectRange($linesEls, $first, $linesEls.filter(`[rel=${m[2]}]`));
157-
158-
// show code view menu marker (don't show in blame page)
159-
if (!isBlame()) {
160-
showLineButton();
161-
}
162-
163-
$('html, body').scrollTop($first.offset().top - 200);
164-
return;
165-
}
166-
}
167-
m = singleAnchorRegex.exec(window.location.hash);
168-
if (m) {
169-
$first = $linesEls.filter(`[rel=L${m[2]}]`);
170-
if ($first.length) {
171-
selectRange($linesEls, $first);
172-
173-
// show code view menu marker (don't show in blame page)
174-
if (!isBlame()) {
175-
showLineButton();
176-
}
177-
178-
$('html, body').scrollTop($first.offset().top - 200);
179-
}
180-
}
181-
}).trigger('hashchange');
182-
}
183-
$(document).on('click', '.fold-file', ({currentTarget}) => {
184-
invertFileFolding(currentTarget.closest('.file-content'), currentTarget);
113+
if (!document.querySelector('.code-view .lines-num')) return;
114+
115+
let selRangeStart: string;
116+
addDelegatedEventListener(document, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
117+
if (!selRangeStart || !e.shiftKey) {
118+
selRangeStart = el.getAttribute('id');
119+
selectRange(selRangeStart);
120+
} else {
121+
const selRangeStop = el.getAttribute('id');
122+
selectRange(`${selRangeStart}-${selRangeStop}`);
123+
}
124+
window.getSelection().removeAllRanges();
125+
showLineButton();
185126
});
186-
$(document).on('click', '.copy-line-permalink', async ({currentTarget}) => {
187-
await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url')));
127+
128+
const onHashChange = () => {
129+
if (!window.location.hash) return;
130+
const range = window.location.hash.substring(1);
131+
const first = selectRange(range);
132+
if (first) {
133+
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
134+
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
135+
first.scrollIntoView({block: 'start'});
136+
showLineButton();
137+
}
138+
};
139+
onHashChange();
140+
window.addEventListener('hashchange', onHashChange);
141+
142+
addDelegatedEventListener(document, 'click', '.copy-line-permalink', (el) => {
143+
clippie(toAbsoluteUrl(el.getAttribute('data-url')));
188144
});
189145
}

web_src/js/features/repo-diff.ts

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import {POST, GET} from '../modules/fetch.ts';
2020
import {fomanticQuery} from '../modules/fomantic/base.ts';
2121
import {createTippy} from '../modules/tippy.ts';
22+
import {invertFileFolding} from './file-fold.ts';
2223

2324
const {pageData, i18n} = window.config;
2425

@@ -244,4 +245,8 @@ export function initRepoDiffView() {
244245
initRepoDiffFileViewToggle();
245246
initViewedCheckboxListenerFor();
246247
initExpandAndCollapseFilesButton();
248+
249+
addDelegatedEventListener(document, 'click', '.fold-file', (el) => {
250+
invertFileFolding(el.closest('.file-content'), el);
251+
});
247252
}

web_src/js/features/repo-issue.ts

+4-17
Original file line numberDiff line numberDiff line change
@@ -373,38 +373,25 @@ export async function handleReply(el) {
373373

374374
export function initRepoPullRequestReview() {
375375
if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
376-
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
377-
if (window.history.scrollRestoration !== 'manual') {
378-
window.history.scrollRestoration = 'manual';
379-
}
380376
const commentDiv = document.querySelector(window.location.hash);
381377
if (commentDiv) {
382378
// get the name of the parent id
383379
const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
384380
if (groupID && groupID.startsWith('code-comments-')) {
385381
const id = groupID.slice(14);
386382
const ancestorDiffBox = commentDiv.closest('.diff-file-box');
387-
// on pages like conversation, there is no diff header
388-
const diffHeader = ancestorDiffBox?.querySelector('.diff-file-header');
389-
390-
// offset is for scrolling
391-
let offset = 30;
392-
if (diffHeader) {
393-
offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight();
394-
}
395383

396384
hideElem(`#show-outdated-${id}`);
397385
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
398386
// if the comment box is folded, expand it
399387
if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
400388
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
401389
}
402-
403-
window.scrollTo({
404-
top: $(commentDiv).offset().top - offset,
405-
behavior: 'instant',
406-
});
407390
}
391+
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
392+
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
393+
// wait for a while because some elements (eg: image, editor, etc.) may change the viewport's height.
394+
setTimeout(() => commentDiv.scrollIntoView({block: 'start'}), 100);
408395
}
409396
}
410397

0 commit comments

Comments
 (0)