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:
parent
188e0ee8e4
commit
2b064b8637
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
});
|
|
@ -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')));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user