mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-10-05 19:30:58 +00:00
chore(ui): replace repo-code jQuery use, convert file to typescript (#9337)
This PR moves repo-code.js to repo-code.ts (with appropriate changes for the JS -> TS conversion), adds e2e tests for file folding and file line permalink copying to fully cover the features implemented in repo-code, then removes the jQuery usage in the file in favor of vanilla JS. * chore(ui): replace jQuery uses in repo-code.ts * chore(ui): add copy line permalink test * chore(ui): add file folding test * chore(ui): convert repo-code to ts This commit additionally removes the use of `document.selection` for IE8 support, as we no longer offer support for the browser. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9337 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Jordan Atwood <nightfirecat@nightfirec.at> Co-committed-by: Jordan Atwood <nightfirecat@nightfirec.at>
This commit is contained in:
parent
c08bdaacdb
commit
01419d9c36
4 changed files with 84 additions and 45 deletions
|
@ -127,3 +127,30 @@ test('Unicode escape highlight', async ({page}) => {
|
|||
await expect(page.locator('.tippy-box .view_git_blame[href$="/a-file#L1"]')).toBeVisible();
|
||||
await expect(page.locator('.tippy-box .copy-line-permalink[data-url$="/a-file#L1"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('File folding', async ({page}) => {
|
||||
const filePath = '/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d';
|
||||
|
||||
const response = await page.goto(filePath);
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
const foldFileButton = page.locator('.fold-file');
|
||||
const diffFileBody = page.locator('.diff-file-body');
|
||||
await foldFileButton.click();
|
||||
await expect(diffFileBody).toBeHidden();
|
||||
await foldFileButton.click();
|
||||
await expect(diffFileBody).toBeVisible();
|
||||
});
|
||||
|
||||
test('Copy line permalink', async ({page}, workerInfo) => {
|
||||
test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'Apple clipboard API addon - starting at just $499!');
|
||||
|
||||
const response = await page.goto('/user2/repo1/src/branch/master/README.md?display=source#L1');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
await page.locator('.code-line-button').click();
|
||||
// eslint-disable-next-line playwright/no-force-option
|
||||
await page.locator('.tippy-box .copy-line-permalink').click({force: true});
|
||||
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
||||
expect(clipboardText).toContain('README.md?display=source#L1');
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.js';
|
||||
import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.ts';
|
||||
import {test, expect} from '@playwright/test';
|
||||
|
||||
test('singleAnchorRegex', () => {
|
||||
expect(singleAnchorRegex.test('#L0')).toEqual(false);
|
|
@ -1,4 +1,3 @@
|
|||
import $ from 'jquery';
|
||||
import {svg} from '../svg.js';
|
||||
import {invertFileFolding} from './file-fold.js';
|
||||
import {createTippy} from '../modules/tippy.js';
|
||||
|
@ -20,12 +19,12 @@ function isBlame() {
|
|||
return Boolean(document.querySelector('div.blame'));
|
||||
}
|
||||
|
||||
function getLineEls() {
|
||||
return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
|
||||
function getLineEls(): Element[] {
|
||||
return Array.from(document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`));
|
||||
}
|
||||
|
||||
function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
||||
for (const el of $linesEls) {
|
||||
function selectRange(linesEls: Element[], selectionEndEl: Element, selectionStartEl?: Element) {
|
||||
for (const el of linesEls) {
|
||||
el.closest('tr').classList.remove('active');
|
||||
}
|
||||
|
||||
|
@ -59,9 +58,9 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
|||
copyPermalink.setAttribute('data-url', link);
|
||||
};
|
||||
|
||||
if ($selectionStartEls) {
|
||||
let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1));
|
||||
let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1));
|
||||
if (selectionStartEl) {
|
||||
let a = parseInt(selectionEndEl.getAttribute('rel').slice(1));
|
||||
let b = parseInt(selectionStartEl.getAttribute('rel').slice(1));
|
||||
let c;
|
||||
if (a !== b) {
|
||||
if (a > b) {
|
||||
|
@ -73,9 +72,9 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
|||
for (let i = a; i <= b; i++) {
|
||||
classes.push(`[rel=L${i}]`);
|
||||
}
|
||||
$linesEls.filter(classes.join(',')).each(function () {
|
||||
this.closest('tr').classList.add('active');
|
||||
});
|
||||
for (const selectedLine of linesEls.filter((line) => line.matches(classes.join(',')))) {
|
||||
selectedLine.closest('tr').classList.add('active');
|
||||
}
|
||||
changeHash(`#L${a}-L${b}`);
|
||||
|
||||
updateIssueHref(`L${a}-L${b}`);
|
||||
|
@ -84,12 +83,12 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
$selectionEndEl[0].closest('tr').classList.add('active');
|
||||
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
|
||||
selectionEndEl.closest('tr').classList.add('active');
|
||||
changeHash(`#${selectionEndEl.getAttribute('rel')}`);
|
||||
|
||||
updateIssueHref($selectionEndEl[0].getAttribute('rel'));
|
||||
updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel'));
|
||||
updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel'));
|
||||
updateIssueHref(selectionEndEl.getAttribute('rel'));
|
||||
updateViewGitBlameFragment(selectionEndEl.getAttribute('rel'));
|
||||
updateCopyPermalinkUrl(selectionEndEl.getAttribute('rel'));
|
||||
}
|
||||
|
||||
function showLineButton() {
|
||||
|
@ -127,70 +126,82 @@ function showLineButton() {
|
|||
}
|
||||
|
||||
export function initRepoCodeView() {
|
||||
if ($('.code-view .lines-num').length > 0) {
|
||||
$(document).on('click', '.lines-num span', function (e) {
|
||||
if (document.querySelector('.code-view .lines-num')) {
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target as Element;
|
||||
if (!target.matches('.lines-num span')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linesEls = getLineEls();
|
||||
const selectedEls = Array.from(linesEls).filter((el) => {
|
||||
return el.matches(`[rel=${this.getAttribute('id')}]`);
|
||||
const selectedEl = linesEls.find((el) => {
|
||||
return el.matches(`[rel=${target.id}]`);
|
||||
});
|
||||
|
||||
let from;
|
||||
if (e.shiftKey) {
|
||||
from = Array.from(linesEls).filter((el) => {
|
||||
from = linesEls.find((el) => {
|
||||
return el.closest('tr').classList.contains('active');
|
||||
});
|
||||
}
|
||||
selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
|
||||
selectRange(linesEls, selectedEl, from);
|
||||
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges();
|
||||
} else {
|
||||
document.selection.empty();
|
||||
}
|
||||
|
||||
showLineButton();
|
||||
});
|
||||
|
||||
$(window).on('hashchange', () => {
|
||||
window.addEventListener('hashchange', () => {
|
||||
let m = window.location.hash.match(rangeAnchorRegex);
|
||||
const $linesEls = $(getLineEls());
|
||||
let $first;
|
||||
const linesEls = getLineEls();
|
||||
let first;
|
||||
if (m) {
|
||||
$first = $linesEls.filter(`[rel=L${m[1]}]`);
|
||||
if ($first.length) {
|
||||
const $last = $linesEls.filter(`[rel=L${m[2]}]`);
|
||||
selectRange($linesEls, $first, $last.length ? $last : $linesEls.last());
|
||||
first = linesEls.find((el) => el.matches(`[rel=L${m[1]}]`));
|
||||
if (first) {
|
||||
const last = linesEls.findLast((el) => el.matches(`[rel=L${m[2]}]`));
|
||||
selectRange(linesEls, first, last ?? linesEls.at(-1));
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if (!isBlame()) {
|
||||
showLineButton();
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
window.scrollBy({top: first.getBoundingClientRect().top - 200});
|
||||
return;
|
||||
}
|
||||
}
|
||||
m = window.location.hash.match(singleAnchorRegex);
|
||||
if (m) {
|
||||
$first = $linesEls.filter(`[rel=L${m[1]}]`);
|
||||
if ($first.length) {
|
||||
selectRange($linesEls, $first);
|
||||
first = linesEls.find((el) => el.matches(`[rel=L${m[1]}]`));
|
||||
if (first) {
|
||||
selectRange(linesEls, first);
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if (!isBlame()) {
|
||||
showLineButton();
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
window.scrollBy({top: first.getBoundingClientRect().top - 200});
|
||||
}
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(new Event('hashchange'));
|
||||
}
|
||||
$(document).on('click', '.fold-file', ({currentTarget}) => {
|
||||
invertFileFolding(currentTarget.closest('.file-content'), currentTarget);
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target as Element;
|
||||
const foldFileButton = target.closest('.fold-file');
|
||||
if (!foldFileButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
invertFileFolding(foldFileButton.closest('.file-content'), foldFileButton);
|
||||
});
|
||||
$(document).on('click', '.copy-line-permalink', async ({currentTarget}) => {
|
||||
await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url')));
|
||||
document.addEventListener('click', async (e) => {
|
||||
const target = e.target as Element;
|
||||
if (!target.matches('.copy-line-permalink')) {
|
||||
return;
|
||||
}
|
||||
|
||||
await clippie(toAbsoluteUrl(target.getAttribute('data-url')));
|
||||
});
|
||||
}
|
|
@ -48,7 +48,7 @@ import {initRepoTopicBar} from './features/repo-home.js';
|
|||
import {initAdminEmails} from './features/admin/emails.js';
|
||||
import {initAdminCommon} from './features/admin/common.js';
|
||||
import {initRepoTemplateSearch} from './features/repo-template.js';
|
||||
import {initRepoCodeView} from './features/repo-code.js';
|
||||
import {initRepoCodeView} from './features/repo-code.ts';
|
||||
import {initSshKeyFormParser} from './features/sshkey-helper.js';
|
||||
import {initRepoArchiveLinks} from './features/repo-common.js';
|
||||
import {initRepoMigrationStatusChecker} from './features/repo-migrate.js';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue