1
1
mirror of https://github.com/go-gitea/gitea synced 2025-01-23 16:14:29 +00:00

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
This commit is contained in:
wxiaoguang 2025-01-04 10:56:07 +08:00 committed by GitHub
parent 188e0ee8e4
commit 2b064b8637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 71 additions and 139 deletions

View File

@ -336,8 +336,13 @@ a.label,
border-color: var(--color-secondary); border-color: var(--color-secondary);
} }
.ui.dropdown .menu > .header {
text-transform: none; /* reset fomantic's "uppercase" */
}
.ui.dropdown .menu > .header:not(.ui) { .ui.dropdown .menu > .header:not(.ui) {
color: var(--color-text); color: var(--color-text);
font-size: 0.95em; /* reset fomantic's small font-size */
} }
.ui.dropdown .menu > .item { .ui.dropdown .menu > .item {
@ -691,10 +696,6 @@ input:-webkit-autofill:active,
box-shadow: 0 6px 18px var(--color-shadow) !important; box-shadow: 0 6px 18px var(--color-shadow) !important;
} }
.ui.dropdown .menu > .header {
font-size: 0.8em;
}
.ui .text.left { .ui .text.left {
text-align: left !important; text-align: left !important;
} }

View File

@ -1,17 +0,0 @@
import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.ts';
test('singleAnchorRegex', () => {
expect(singleAnchorRegex.test('#L0')).toEqual(false);
expect(singleAnchorRegex.test('#L1')).toEqual(true);
expect(singleAnchorRegex.test('#L01')).toEqual(false);
expect(singleAnchorRegex.test('#n0')).toEqual(false);
expect(singleAnchorRegex.test('#n1')).toEqual(true);
expect(singleAnchorRegex.test('#n01')).toEqual(false);
});
test('rangeAnchorRegex', () => {
expect(rangeAnchorRegex.test('#L0-L10')).toEqual(false);
expect(rangeAnchorRegex.test('#L1-L10')).toEqual(true);
expect(rangeAnchorRegex.test('#L01-L10')).toEqual(false);
expect(rangeAnchorRegex.test('#L1-L01')).toEqual(false);
});

View File

@ -1,12 +1,8 @@
import $ from 'jquery';
import {svg} from '../svg.ts'; import {svg} from '../svg.ts';
import {invertFileFolding} from './file-fold.ts';
import {createTippy} from '../modules/tippy.ts'; import {createTippy} from '../modules/tippy.ts';
import {clippie} from 'clippie'; import {clippie} from 'clippie';
import {toAbsoluteUrl} from '../utils.ts'; import {toAbsoluteUrl} from '../utils.ts';
import {addDelegatedEventListener} from '../utils/dom.ts';
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
function changeHash(hash: string) { function changeHash(hash: string) {
if (window.history.pushState) { if (window.history.pushState) {
@ -16,20 +12,11 @@ function changeHash(hash: string) {
} }
} }
function isBlame() { // it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
return Boolean(document.querySelector('div.blame')); function selectRange(range: string): Element {
} for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active');
const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`);
function getLineEls() {
return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
}
function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
for (const el of $linesEls) {
el.closest('tr').classList.remove('active');
}
// add hashchange to permalink
const refInNewIssue = document.querySelector('a.ref-in-new-issue'); const refInNewIssue = document.querySelector('a.ref-in-new-issue');
const copyPermalink = document.querySelector('a.copy-line-permalink'); const copyPermalink = document.querySelector('a.copy-line-permalink');
const viewGitBlame = document.querySelector('a.view_git_blame'); const viewGitBlame = document.querySelector('a.view_git_blame');
@ -59,37 +46,30 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
copyPermalink.setAttribute('data-url', link); copyPermalink.setAttribute('data-url', link);
}; };
if ($selectionStartEls) { const rangeFields = range ? range.split('-') : [];
let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1)); const start = rangeFields[0] ?? '';
let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1)); if (!start) return null;
let c; const stop = rangeFields[1] || start;
if (a !== b) {
if (a > b) {
c = a;
a = b;
b = c;
}
const classes = [];
for (let i = a; i <= b; i++) {
classes.push(`[rel=L${i}]`);
}
$linesEls.filter(classes.join(',')).each(function () {
this.closest('tr').classList.add('active');
});
changeHash(`#L${a}-L${b}`);
updateIssueHref(`L${a}-L${b}`); // format is i.e. 'L14-L26'
updateViewGitBlameFragment(`L${a}-L${b}`); let startLineNum = parseInt(start.substring(1));
updateCopyPermalinkUrl(`L${a}-L${b}`); let stopLineNum = parseInt(stop.substring(1));
return; if (startLineNum > stopLineNum) {
const tmp = startLineNum;
startLineNum = stopLineNum;
stopLineNum = tmp;
range = `${stop}-${start}`;
} }
}
$selectionEndEl[0].closest('tr').classList.add('active');
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
updateIssueHref($selectionEndEl[0].getAttribute('rel')); const first = elLineNums[startLineNum - 1] ?? null;
updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel')); for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) {
updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel')); elLineNums[i].closest('tr').classList.add('active');
}
changeHash(`#${range}`);
updateIssueHref(range);
updateViewGitBlameFragment(range);
updateCopyPermalinkUrl(range);
return first;
} }
function showLineButton() { function showLineButton() {
@ -103,6 +83,8 @@ function showLineButton() {
// find active row and add button // find active row and add button
const tr = document.querySelector('.code-view tr.active'); const tr = document.querySelector('.code-view tr.active');
if (!tr) return;
const td = tr.querySelector('td.lines-num'); const td = tr.querySelector('td.lines-num');
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.classList.add('code-line-button', 'ui', 'basic', 'button'); btn.classList.add('code-line-button', 'ui', 'basic', 'button');
@ -128,62 +110,36 @@ function showLineButton() {
} }
export function initRepoCodeView() { export function initRepoCodeView() {
if ($('.code-view .lines-num').length > 0) { if (!document.querySelector('.code-view .lines-num')) return;
$(document).on('click', '.lines-num span', function (e) {
const linesEls = getLineEls();
const selectedEls = Array.from(linesEls).filter((el) => {
return el.matches(`[rel=${this.getAttribute('id')}]`);
});
let from; let selRangeStart: string;
if (e.shiftKey) { addDelegatedEventListener(document, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
from = Array.from(linesEls).filter((el) => { if (!selRangeStart || !e.shiftKey) {
return el.closest('tr').classList.contains('active'); selRangeStart = el.getAttribute('id');
}); selectRange(selRangeStart);
} else {
const selRangeStop = el.getAttribute('id');
selectRange(`${selRangeStart}-${selRangeStop}`);
} }
selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
showLineButton(); showLineButton();
}); });
$(window).on('hashchange', () => { const onHashChange = () => {
let m = rangeAnchorRegex.exec(window.location.hash); if (!window.location.hash) return;
const $linesEls = $(getLineEls()); const range = window.location.hash.substring(1);
let $first; const first = selectRange(range);
if (m) { if (first) {
$first = $linesEls.filter(`[rel=${m[1]}]`); // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if ($first.length) { if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
selectRange($linesEls, $first, $linesEls.filter(`[rel=${m[2]}]`)); first.scrollIntoView({block: 'start'});
// show code view menu marker (don't show in blame page)
if (!isBlame()) {
showLineButton(); showLineButton();
} }
};
onHashChange();
window.addEventListener('hashchange', onHashChange);
$('html, body').scrollTop($first.offset().top - 200); addDelegatedEventListener(document, 'click', '.copy-line-permalink', (el) => {
return; clippie(toAbsoluteUrl(el.getAttribute('data-url')));
}
}
m = singleAnchorRegex.exec(window.location.hash);
if (m) {
$first = $linesEls.filter(`[rel=L${m[2]}]`);
if ($first.length) {
selectRange($linesEls, $first);
// show code view menu marker (don't show in blame page)
if (!isBlame()) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
}
}
}).trigger('hashchange');
}
$(document).on('click', '.fold-file', ({currentTarget}) => {
invertFileFolding(currentTarget.closest('.file-content'), currentTarget);
});
$(document).on('click', '.copy-line-permalink', async ({currentTarget}) => {
await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url')));
}); });
} }

View File

@ -19,6 +19,7 @@ import {
import {POST, GET} from '../modules/fetch.ts'; import {POST, GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts';
import {createTippy} from '../modules/tippy.ts'; import {createTippy} from '../modules/tippy.ts';
import {invertFileFolding} from './file-fold.ts';
const {pageData, i18n} = window.config; const {pageData, i18n} = window.config;
@ -244,4 +245,8 @@ export function initRepoDiffView() {
initRepoDiffFileViewToggle(); initRepoDiffFileViewToggle();
initViewedCheckboxListenerFor(); initViewedCheckboxListenerFor();
initExpandAndCollapseFilesButton(); initExpandAndCollapseFilesButton();
addDelegatedEventListener(document, 'click', '.fold-file', (el) => {
invertFileFolding(el.closest('.file-content'), el);
});
} }

View File

@ -373,10 +373,6 @@ export async function handleReply(el) {
export function initRepoPullRequestReview() { export function initRepoPullRequestReview() {
if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) { if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if (window.history.scrollRestoration !== 'manual') {
window.history.scrollRestoration = 'manual';
}
const commentDiv = document.querySelector(window.location.hash); const commentDiv = document.querySelector(window.location.hash);
if (commentDiv) { if (commentDiv) {
// get the name of the parent id // get the name of the parent id
@ -384,14 +380,6 @@ export function initRepoPullRequestReview() {
if (groupID && groupID.startsWith('code-comments-')) { if (groupID && groupID.startsWith('code-comments-')) {
const id = groupID.slice(14); const id = groupID.slice(14);
const ancestorDiffBox = commentDiv.closest('.diff-file-box'); const ancestorDiffBox = commentDiv.closest('.diff-file-box');
// on pages like conversation, there is no diff header
const diffHeader = ancestorDiffBox?.querySelector('.diff-file-header');
// offset is for scrolling
let offset = 30;
if (diffHeader) {
offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight();
}
hideElem(`#show-outdated-${id}`); hideElem(`#show-outdated-${id}`);
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`); showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
@ -399,12 +387,11 @@ export function initRepoPullRequestReview() {
if (ancestorDiffBox?.getAttribute('data-folded') === 'true') { if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false); setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
} }
window.scrollTo({
top: $(commentDiv).offset().top - offset,
behavior: 'instant',
});
} }
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
// wait for a while because some elements (eg: image, editor, etc.) may change the viewport's height.
setTimeout(() => commentDiv.scrollIntoView({block: 'start'}), 100);
} }
} }