From d617918666392c5296abc1add966862da3059639 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 11:33:56 +0100 Subject: [PATCH 01/16] Add links to npm in package.json file view --- web_src/css/base.css | 6 +++++ web_src/js/features/copycontent.js | 4 +-- web_src/js/features/repo-code.js | 40 ++++++++++++++++++++++++++++++ web_src/js/utils/misc.js | 8 ++++++ 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 web_src/js/utils/misc.js diff --git a/web_src/css/base.css b/web_src/css/base.css index 76ecfc9bf5a72..c8eb06fc2a2b8 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1620,6 +1620,12 @@ a.ui.active.label:hover { width: 100%; } +/* don't change link color in highlighted code */ +.code-view .lines-code a, +.code-view .lines-code a:hover { + color: inherit; +} + .ui.primary.label, .ui.primary.labels .label, .ui.ui.ui.primary.label { diff --git a/web_src/js/features/copycontent.js b/web_src/js/features/copycontent.js index 3d3b2a697ecbf..ef78b65d16442 100644 --- a/web_src/js/features/copycontent.js +++ b/web_src/js/features/copycontent.js @@ -1,6 +1,7 @@ import {clippie} from 'clippie'; import {showTemporaryTooltip} from '../modules/tippy.js'; import {convertImage} from '../utils.js'; +import {getFileViewContent} from '../utils/misc.js'; import {GET} from '../modules/fetch.js'; const {i18n} = window.config; @@ -36,8 +37,7 @@ export function initCopyContent() { btn.classList.remove('is-loading', 'small-loading-icon'); } } else { // text, read from DOM - const lineEls = document.querySelectorAll('.file-view .lines-code'); - content = Array.from(lineEls, (el) => el.textContent).join(''); + content = getFileViewContent(); } // try copy original first, if that fails and it's an image, convert it to png diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index a142313211e8b..f9ac41c78c112 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -4,6 +4,7 @@ import {invertFileFolding} from './file-fold.js'; import {createTippy} from '../modules/tippy.js'; import {clippie} from 'clippie'; import {toAbsoluteUrl} from '../utils.js'; +import {getFileViewContent, getFileViewFileName} from '../utils/misc.js'; export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/; export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/; @@ -120,6 +121,44 @@ function showLineButton() { }); } +function initFilePostProcess() { + const fileName = getFileViewFileName(); + if (fileName === 'package.json') { + let data; + try { + data = JSON.parse(getFileViewContent()); + } catch { + return; + } + + const packages = new Set(); + for (const key of [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', + ]) { + for (const packageName of Object.keys(data?.[key] || {})) { + packages.add(packageName); + } + } + + // match chroma NameTag token to detect JSON object keys + for (const el of document.querySelectorAll('.code-inner .nt')) { + const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); + if (packages.has(jsonKey)) { + const a = document.createElement('a'); + a.textContent = jsonKey; + a.href = `https://www.npmjs.com/package/${jsonKey}`; + a.target = '_blank'; + a.rel = 'noopener noreferrer nofollow'; + el.textContent = ''; + el.append('"', a, '"'); + } + } + } +} + export function initRepoCodeView() { if ($('.code-view .lines-num').length > 0) { $(document).on('click', '.lines-num span', function (e) { @@ -197,4 +236,5 @@ export function initRepoCodeView() { $(document).on('click', '.copy-line-permalink', async ({currentTarget}) => { await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url'))); }); + initFilePostProcess(); } diff --git a/web_src/js/utils/misc.js b/web_src/js/utils/misc.js new file mode 100644 index 0000000000000..c717efcb7005c --- /dev/null +++ b/web_src/js/utils/misc.js @@ -0,0 +1,8 @@ +export function getFileViewFileName() { + return document.querySelector('.repo-path .active')?.textContent?.trim(); +} + +export function getFileViewContent() { + const lineEls = document.querySelectorAll('.file-view .lines-code'); + return Array.from(lineEls, (el) => el.textContent).join(''); +} From 405e737bd6d1e3d414fc8fe0fded3f4c8d184a3e Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 12:10:24 +0100 Subject: [PATCH 02/16] factor out createExternalLink --- web_src/js/features/repo-code.js | 13 ++++++------- web_src/js/utils.js | 10 ++++++++++ web_src/js/utils.test.js | 11 ++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index f9ac41c78c112..c368fb353afbb 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -3,7 +3,7 @@ import {svg} from '../svg.js'; import {invertFileFolding} from './file-fold.js'; import {createTippy} from '../modules/tippy.js'; import {clippie} from 'clippie'; -import {toAbsoluteUrl} from '../utils.js'; +import {toAbsoluteUrl, createExternalLink} from '../utils.js'; import {getFileViewContent, getFileViewFileName} from '../utils/misc.js'; export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/; @@ -147,13 +147,12 @@ function initFilePostProcess() { for (const el of document.querySelectorAll('.code-inner .nt')) { const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); if (packages.has(jsonKey)) { - const a = document.createElement('a'); - a.textContent = jsonKey; - a.href = `https://www.npmjs.com/package/${jsonKey}`; - a.target = '_blank'; - a.rel = 'noopener noreferrer nofollow'; + const link = createExternalLink({ + textContent: jsonKey, + href: `https://www.npmjs.com/package/${jsonKey}`, + }); el.textContent = ''; - el.append('"', a, '"'); + el.append('"', link, '"'); } } } diff --git a/web_src/js/utils.js b/web_src/js/utils.js index c82e42d349045..1317b9a88e2b9 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -139,3 +139,13 @@ export function parseDom(text, contentType) { export function serializeXml(node) { return xmlSerializer.serializeToString(node); } + +export function createExternalLink(props = {}) { + const a = document.createElement('a'); + a.target = '_blank'; + a.rel = 'noopener noreferrer nofollow'; + for (const [key, value] of Object.entries(props)) { + a[key] = value; + } + return a; +} diff --git a/web_src/js/utils.test.js b/web_src/js/utils.test.js index 2754e41c433ec..611314c3b7391 100644 --- a/web_src/js/utils.test.js +++ b/web_src/js/utils.test.js @@ -1,7 +1,6 @@ import { - basename, extname, isObject, stripTags, parseIssueHref, - parseUrl, translateMonth, translateDay, blobToDataURI, - toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, + basename, extname, isObject, stripTags, parseIssueHref, parseUrl, translateMonth, translateDay, + blobToDataURI, toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, createExternalLink, } from './utils.js'; test('basename', () => { @@ -112,3 +111,9 @@ test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => { expect(Array.from(decodeURLEncodedBase64('YQ'))).toEqual(Array.from(uint8array('a'))); expect(Array.from(decodeURLEncodedBase64('YQ=='))).toEqual(Array.from(uint8array('a'))); }); + +test('createExternalLink', () => { + const link = createExternalLink({href: 'https://example.com'}); + expect(link.tagName).toEqual('A'); + expect(link.href).toEqual('https://example.com/'); +}); From fdb81a7fc17f2309cf86fe7c59e77027ba4c4928 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 12:11:36 +0100 Subject: [PATCH 03/16] tweak test --- web_src/js/utils.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_src/js/utils.test.js b/web_src/js/utils.test.js index 611314c3b7391..12866856ef9d9 100644 --- a/web_src/js/utils.test.js +++ b/web_src/js/utils.test.js @@ -113,7 +113,8 @@ test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => { }); test('createExternalLink', () => { - const link = createExternalLink({href: 'https://example.com'}); + const link = createExternalLink({href: 'https://example.com', textContent: 'example'}); expect(link.tagName).toEqual('A'); expect(link.href).toEqual('https://example.com/'); + expect(link.textContent).toEqual('example'); }); From f57ecc7669363390c349c7e97612b800e7e3c2ea Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 12:18:28 +0100 Subject: [PATCH 04/16] rename --- web_src/js/features/repo-code.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index c368fb353afbb..1ca62cf014caa 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -121,7 +121,7 @@ function showLineButton() { }); } -function initFilePostProcess() { +function postProcessFile() { const fileName = getFileViewFileName(); if (fileName === 'package.json') { let data; @@ -235,5 +235,5 @@ export function initRepoCodeView() { $(document).on('click', '.copy-line-permalink', async ({currentTarget}) => { await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url'))); }); - initFilePostProcess(); + postProcessFile(); } From 1a12f1878b159667257e245d70fbd606abee2234 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 12:19:51 +0100 Subject: [PATCH 05/16] extract another function --- web_src/js/features/repo-code.js | 64 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index 1ca62cf014caa..ef9d7aaca6de0 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -121,43 +121,47 @@ function showLineButton() { }); } -function postProcessFile() { - const fileName = getFileViewFileName(); - if (fileName === 'package.json') { - let data; - try { - data = JSON.parse(getFileViewContent()); - } catch { - return; - } +function postProcessPackageJson() { + let data; + try { + data = JSON.parse(getFileViewContent()); + } catch { + return; + } - const packages = new Set(); - for (const key of [ - 'dependencies', - 'devDependencies', - 'optionalDependencies', - 'peerDependencies', - ]) { - for (const packageName of Object.keys(data?.[key] || {})) { - packages.add(packageName); - } + const packages = new Set(); + for (const key of [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', + ]) { + for (const packageName of Object.keys(data?.[key] || {})) { + packages.add(packageName); } + } - // match chroma NameTag token to detect JSON object keys - for (const el of document.querySelectorAll('.code-inner .nt')) { - const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); - if (packages.has(jsonKey)) { - const link = createExternalLink({ - textContent: jsonKey, - href: `https://www.npmjs.com/package/${jsonKey}`, - }); - el.textContent = ''; - el.append('"', link, '"'); - } + // match chroma NameTag token to detect JSON object keys + for (const el of document.querySelectorAll('.code-inner .nt')) { + const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); + if (packages.has(jsonKey)) { + const link = createExternalLink({ + textContent: jsonKey, + href: `https://www.npmjs.com/package/${jsonKey}`, + }); + el.textContent = ''; + el.append('"', link, '"'); } } } +function postProcessFile() { + const fileName = getFileViewFileName(); + if (fileName === 'package.json') { + postProcessPackageJson(); + } +} + export function initRepoCodeView() { if ($('.code-view .lines-num').length > 0) { $(document).on('click', '.lines-num span', function (e) { From c47229537a81fcab5d1caded58cdb487325f2320 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 17:46:14 +0100 Subject: [PATCH 06/16] add new suppressed link helper and use it --- web_src/css/base.css | 19 ++++++++++--------- web_src/js/features/repo-code.js | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index c8eb06fc2a2b8..19c521517ce4b 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -165,24 +165,31 @@ a { text-decoration-skip-ink: all; } -/* muted link = only colored when hovered */ -/* silenced link = never colored */ +/* a.muted = colored on hover, underlined on hover */ +/* a.suppressed = never colored, underlined on hover */ +/* a.silenced = never colored, never underlined */ a.muted, +a.suppressed, a.silenced, .muted-links a { color: inherit; } a:hover, +a.suppressed:hover, a.muted:hover, a.muted:hover [class*="color-text"], .muted-links a:hover { color: var(--color-primary); } -a.silenced:hover { +a.silenced:hover, +a.suppressed:hover { color: inherit; +} + +a.silenced:hover { text-decoration: none; } @@ -1620,12 +1627,6 @@ a.ui.active.label:hover { width: 100%; } -/* don't change link color in highlighted code */ -.code-view .lines-code a, -.code-view .lines-code a:hover { - color: inherit; -} - .ui.primary.label, .ui.primary.labels .label, .ui.ui.ui.primary.label { diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index ef9d7aaca6de0..43f63550bfea9 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -146,6 +146,7 @@ function postProcessPackageJson() { const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); if (packages.has(jsonKey)) { const link = createExternalLink({ + className: 'suppressed', textContent: jsonKey, href: `https://www.npmjs.com/package/${jsonKey}`, }); From d7b4a34eaf28175a7838a843d0cc0d6c4c2dc01a Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 23 Feb 2024 17:48:06 +0100 Subject: [PATCH 07/16] add comment --- web_src/css/base.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/css/base.css b/web_src/css/base.css index 19c521517ce4b..08adc0b7d6bbd 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -165,6 +165,7 @@ a { text-decoration-skip-ink: all; } +/* a = always colored, underlined on hover */ /* a.muted = colored on hover, underlined on hover */ /* a.suppressed = never colored, underlined on hover */ /* a.silenced = never colored, never underlined */ From 33b66c7650d5fd42977e1a13125ffde69d5e5a01 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 Mar 2024 00:13:55 +0100 Subject: [PATCH 08/16] restructure and improve --- web_src/js/features/copycontent.js | 4 +-- web_src/js/features/repo-code.js | 46 +---------------------------- web_src/js/index.js | 3 +- web_src/js/render/code.js | 47 ++++++++++++++++++++++++++++++ web_src/js/utils.test.js | 9 +----- web_src/js/utils/misc.js | 20 +++++++++++-- web_src/js/utils/misc.test.js | 8 +++++ 7 files changed, 78 insertions(+), 59 deletions(-) create mode 100644 web_src/js/render/code.js create mode 100644 web_src/js/utils/misc.test.js diff --git a/web_src/js/features/copycontent.js b/web_src/js/features/copycontent.js index ef78b65d16442..bc2d24133d499 100644 --- a/web_src/js/features/copycontent.js +++ b/web_src/js/features/copycontent.js @@ -1,7 +1,7 @@ import {clippie} from 'clippie'; import {showTemporaryTooltip} from '../modules/tippy.js'; import {convertImage} from '../utils.js'; -import {getFileViewContent} from '../utils/misc.js'; +import {getFileViewFileText} from '../utils/misc.js'; import {GET} from '../modules/fetch.js'; const {i18n} = window.config; @@ -37,7 +37,7 @@ export function initCopyContent() { btn.classList.remove('is-loading', 'small-loading-icon'); } } else { // text, read from DOM - content = getFileViewContent(); + content = getFileViewFileText(); } // try copy original first, if that fails and it's an image, convert it to png diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index e892d6478fd22..c4a81ea16550c 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -3,8 +3,7 @@ import {svg} from '../svg.js'; import {invertFileFolding} from './file-fold.js'; import {createTippy} from '../modules/tippy.js'; import {clippie} from 'clippie'; -import {toAbsoluteUrl, createExternalLink} from '../utils.js'; -import {getFileViewContent, getFileViewFileName} from '../utils/misc.js'; +import {toAbsoluteUrl} from '../utils.js'; export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/; export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/; @@ -121,48 +120,6 @@ function showLineButton() { }); } -function postProcessPackageJson() { - let data; - try { - data = JSON.parse(getFileViewContent()); - } catch { - return; - } - - const packages = new Set(); - for (const key of [ - 'dependencies', - 'devDependencies', - 'optionalDependencies', - 'peerDependencies', - ]) { - for (const packageName of Object.keys(data?.[key] || {})) { - packages.add(packageName); - } - } - - // match chroma NameTag token to detect JSON object keys - for (const el of document.querySelectorAll('.code-inner .nt')) { - const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); - if (packages.has(jsonKey)) { - const link = createExternalLink({ - className: 'suppressed', - textContent: jsonKey, - href: `https://www.npmjs.com/package/${jsonKey}`, - }); - el.textContent = ''; - el.append('"', link, '"'); - } - } -} - -function postProcessFile() { - const fileName = getFileViewFileName(); - if (fileName === 'package.json') { - postProcessPackageJson(); - } -} - export function initRepoCodeView() { if ($('.code-view .lines-num').length > 0) { $(document).on('click', '.lines-num span', function (e) { @@ -232,5 +189,4 @@ export function initRepoCodeView() { $(document).on('click', '.copy-line-permalink', async ({currentTarget}) => { await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url'))); }); - postProcessFile(); } diff --git a/web_src/js/index.js b/web_src/js/index.js index abf0d469d18ed..bac327264634f 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -22,7 +22,6 @@ import {initStopwatch} from './features/stopwatch.js'; import {initFindFileInRepo} from './features/repo-findfile.js'; import {initCommentContent, initMarkupContent} from './markup/content.js'; import {initPdfViewer} from './render/pdf.js'; - import {initUserAuthOauth2} from './features/user-auth.js'; import { initRepoIssueDue, @@ -85,6 +84,7 @@ import {initRepoRecentCommits} from './features/recent-commits.js'; import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; import {initDirAuto} from './modules/dirauto.js'; import {initRepositorySearch} from './features/repo-search.js'; +import {initFileView} from './render/code.js'; // Init Gitea's Fomantic settings initGiteaFomantic(); @@ -186,4 +186,5 @@ onDomReady(() => { initRepoDiffView(); initPdfViewer(); initScopedAccessTokenCategories(); + initFileView(); }); diff --git a/web_src/js/render/code.js b/web_src/js/render/code.js new file mode 100644 index 0000000000000..c744cb8171447 --- /dev/null +++ b/web_src/js/render/code.js @@ -0,0 +1,47 @@ +import {getFileViewFilePath, getFileViewFileText, createExternalLink} from '../utils/misc.js'; +import {basename, isObject} from '../utils.js'; + +export function initFileView() { + if (document.querySelector('.file-view.code-view')) { + const fileName = basename(getFileViewFilePath()); + if (fileName === 'package.json') { + processPackageJson(); + } + } +} + +function processPackageJson() { + let obj; + try { + obj = JSON.parse(getFileViewFileText()); + } catch { + return; + } + if (!isObject(obj)) return; + + const packages = new Set(); + for (const key of [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', + ]) { + for (const packageName of Object.keys(obj?.[key] || {})) { + packages.add(packageName); + } + } + + // match chroma NameTag token to detect JSON object keys + for (const el of document.querySelectorAll('.code-inner .nt')) { + const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); + if (packages.has(jsonKey)) { + const link = createExternalLink({ + className: 'suppressed', + textContent: jsonKey, + href: `https://www.npmjs.com/package/${jsonKey}`, + }); + el.textContent = ''; + el.append('"', link, '"'); + } + } +} diff --git a/web_src/js/utils.test.js b/web_src/js/utils.test.js index 12866856ef9d9..ffaab6b0b6999 100644 --- a/web_src/js/utils.test.js +++ b/web_src/js/utils.test.js @@ -1,6 +1,6 @@ import { basename, extname, isObject, stripTags, parseIssueHref, parseUrl, translateMonth, translateDay, - blobToDataURI, toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, createExternalLink, + blobToDataURI, toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, } from './utils.js'; test('basename', () => { @@ -111,10 +111,3 @@ test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => { expect(Array.from(decodeURLEncodedBase64('YQ'))).toEqual(Array.from(uint8array('a'))); expect(Array.from(decodeURLEncodedBase64('YQ=='))).toEqual(Array.from(uint8array('a'))); }); - -test('createExternalLink', () => { - const link = createExternalLink({href: 'https://example.com', textContent: 'example'}); - expect(link.tagName).toEqual('A'); - expect(link.href).toEqual('https://example.com/'); - expect(link.textContent).toEqual('example'); -}); diff --git a/web_src/js/utils/misc.js b/web_src/js/utils/misc.js index c717efcb7005c..96d9332bd38a3 100644 --- a/web_src/js/utils/misc.js +++ b/web_src/js/utils/misc.js @@ -1,8 +1,22 @@ -export function getFileViewFileName() { - return document.querySelector('.repo-path .active')?.textContent?.trim(); +// returns a file's path from repo root, including a leading slash +export function getFileViewFilePath() { + const pathWithRepo = document.querySelector('.repo-path')?.textContent?.trim(); + return `/${pathWithRepo.split('/').filter((_, i) => i !== 0).join('/')}`; } -export function getFileViewContent() { +// returns a file's text content +export function getFileViewFileText() { const lineEls = document.querySelectorAll('.file-view .lines-code'); return Array.from(lineEls, (el) => el.textContent).join(''); } + +// create a external link with suitable attributes +export function createExternalLink(props = {}) { + const a = document.createElement('a'); + a.target = '_blank'; + a.rel = 'noopener noreferrer nofollow'; + for (const [key, value] of Object.entries(props)) { + a[key] = value; + } + return a; +} diff --git a/web_src/js/utils/misc.test.js b/web_src/js/utils/misc.test.js new file mode 100644 index 0000000000000..ca105d78d3a69 --- /dev/null +++ b/web_src/js/utils/misc.test.js @@ -0,0 +1,8 @@ +import {createExternalLink} from "./misc.js"; + +test('createExternalLink', () => { + const link = createExternalLink({href: 'https://example.com', textContent: 'example'}); + expect(link.tagName).toEqual('A'); + expect(link.href).toEqual('https://example.com/'); + expect(link.textContent).toEqual('example'); +}); From 9b2f65036e30c3ec61aae720226356db4092bc4a Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 Mar 2024 00:15:47 +0100 Subject: [PATCH 09/16] revert change --- web_src/js/utils.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web_src/js/utils.test.js b/web_src/js/utils.test.js index ffaab6b0b6999..2754e41c433ec 100644 --- a/web_src/js/utils.test.js +++ b/web_src/js/utils.test.js @@ -1,6 +1,7 @@ import { - basename, extname, isObject, stripTags, parseIssueHref, parseUrl, translateMonth, translateDay, - blobToDataURI, toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, + basename, extname, isObject, stripTags, parseIssueHref, + parseUrl, translateMonth, translateDay, blobToDataURI, + toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, } from './utils.js'; test('basename', () => { From 8aa5c5e7e4ed27fe8d0f6f7c9b35c30e98d7602d Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 Mar 2024 00:22:58 +0100 Subject: [PATCH 10/16] add more keys --- web_src/js/render/code.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_src/js/render/code.js b/web_src/js/render/code.js index c744cb8171447..48d41e33fde19 100644 --- a/web_src/js/render/code.js +++ b/web_src/js/render/code.js @@ -20,11 +20,16 @@ function processPackageJson() { if (!isObject(obj)) return; const packages = new Set(); + for (const key of [ 'dependencies', + 'dependenciesMeta', 'devDependencies', 'optionalDependencies', + 'overrides', 'peerDependencies', + 'peerDependenciesMeta', + 'resolutions', ]) { for (const packageName of Object.keys(obj?.[key] || {})) { packages.add(packageName); From c2217a4375ddc7e05c75b8a5943e495a8537af56 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 Mar 2024 00:27:01 +0100 Subject: [PATCH 11/16] fix lint --- web_src/js/utils/misc.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/utils/misc.test.js b/web_src/js/utils/misc.test.js index ca105d78d3a69..a7a0cc500457e 100644 --- a/web_src/js/utils/misc.test.js +++ b/web_src/js/utils/misc.test.js @@ -1,4 +1,4 @@ -import {createExternalLink} from "./misc.js"; +import {createExternalLink} from '/misc.js'; test('createExternalLink', () => { const link = createExternalLink({href: 'https://example.com', textContent: 'example'}); From 1f3a1512f8224701833c45aae6978db20699e278 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 Mar 2024 00:27:14 +0100 Subject: [PATCH 12/16] fix lint --- web_src/js/utils/misc.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/utils/misc.test.js b/web_src/js/utils/misc.test.js index a7a0cc500457e..fa547beb62d8a 100644 --- a/web_src/js/utils/misc.test.js +++ b/web_src/js/utils/misc.test.js @@ -1,4 +1,4 @@ -import {createExternalLink} from '/misc.js'; +import {createExternalLink} from './misc.js'; test('createExternalLink', () => { const link = createExternalLink({href: 'https://example.com', textContent: 'example'}); From c28fe8be224bb13048bc7b4f1b77bbb7b5b34679 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Mar 2024 12:39:25 +0100 Subject: [PATCH 13/16] remove unneeded attributes and add comments --- web_src/js/utils/misc.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web_src/js/utils/misc.js b/web_src/js/utils/misc.js index 96d9332bd38a3..7328b4081fefa 100644 --- a/web_src/js/utils/misc.js +++ b/web_src/js/utils/misc.js @@ -11,10 +11,12 @@ export function getFileViewFileText() { } // create a external link with suitable attributes +// - no `noopener` attribute because browser defaults to it with target `_blank` +// - no `noreferrer` attribute because we use `` export function createExternalLink(props = {}) { const a = document.createElement('a'); a.target = '_blank'; - a.rel = 'noopener noreferrer nofollow'; + a.rel = 'nofollow'; for (const [key, value] of Object.entries(props)) { a[key] = value; } From 53ff681adca8fc40bcb44c2e9c906b39e89ee6a3 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Mar 2024 12:46:12 +0100 Subject: [PATCH 14/16] change createExternalLink to createLink --- web_src/js/render/code.js | 5 +++-- web_src/js/utils/misc.js | 20 ++++++++++++++------ web_src/js/utils/misc.test.js | 17 +++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/web_src/js/render/code.js b/web_src/js/render/code.js index 48d41e33fde19..54b56068f747d 100644 --- a/web_src/js/render/code.js +++ b/web_src/js/render/code.js @@ -1,4 +1,4 @@ -import {getFileViewFilePath, getFileViewFileText, createExternalLink} from '../utils/misc.js'; +import {getFileViewFilePath, getFileViewFileText, createLink} from '../utils/misc.js'; import {basename, isObject} from '../utils.js'; export function initFileView() { @@ -40,7 +40,8 @@ function processPackageJson() { for (const el of document.querySelectorAll('.code-inner .nt')) { const jsonKey = el.textContent.replace(/^"(.*)"$/, '$1'); if (packages.has(jsonKey)) { - const link = createExternalLink({ + const link = createLink({ + external: true, className: 'suppressed', textContent: jsonKey, href: `https://www.npmjs.com/package/${jsonKey}`, diff --git a/web_src/js/utils/misc.js b/web_src/js/utils/misc.js index 7328b4081fefa..711043eb1b0b9 100644 --- a/web_src/js/utils/misc.js +++ b/web_src/js/utils/misc.js @@ -10,15 +10,23 @@ export function getFileViewFileText() { return Array.from(lineEls, (el) => el.textContent).join(''); } -// create a external link with suitable attributes -// - no `noopener` attribute because browser defaults to it with target `_blank` -// - no `noreferrer` attribute because we use `` -export function createExternalLink(props = {}) { +// create a link with suitable attributes. `props` is a object of props with these additional options: +// - `external`: whether the link is external and should open in new tab +// remarks: +// - no `noopener` attribute for external because browser defaults to it with target `_blank` +// - no `noreferrer` attribute for external because we use `` +export function createLink(props = {}) { const a = document.createElement('a'); - a.target = '_blank'; - a.rel = 'nofollow'; + + if (props.external) { + delete props.external; + a.target = '_blank'; + a.rel = 'nofollow'; + } + for (const [key, value] of Object.entries(props)) { a[key] = value; } + return a; } diff --git a/web_src/js/utils/misc.test.js b/web_src/js/utils/misc.test.js index fa547beb62d8a..637b2067a91ab 100644 --- a/web_src/js/utils/misc.test.js +++ b/web_src/js/utils/misc.test.js @@ -1,8 +1,13 @@ -import {createExternalLink} from './misc.js'; +import {createLink} from './misc.js'; -test('createExternalLink', () => { - const link = createExternalLink({href: 'https://example.com', textContent: 'example'}); - expect(link.tagName).toEqual('A'); - expect(link.href).toEqual('https://example.com/'); - expect(link.textContent).toEqual('example'); +test('createLink', () => { + const internalLink = createLink({href: 'https://example.com', textContent: 'example'}); + expect(internalLink.tagName).toEqual('A'); + expect(internalLink.href).toEqual('https://example.com/'); + expect(internalLink.textContent).toEqual('example'); + const externalLink = createLink({href: 'https://example.com', textContent: 'example', external: true}); + expect(externalLink.tagName).toEqual('A'); + expect(externalLink.href).toEqual('https://example.com/'); + expect(externalLink.textContent).toEqual('example'); + expect(externalLink.target).toEqual('_blank'); }); From 1dd1343c40247bf6130b6838e08095c00a57d37f Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Mar 2024 12:46:51 +0100 Subject: [PATCH 15/16] format --- web_src/js/utils/misc.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/js/utils/misc.test.js b/web_src/js/utils/misc.test.js index 637b2067a91ab..8c8a4c0cff78f 100644 --- a/web_src/js/utils/misc.test.js +++ b/web_src/js/utils/misc.test.js @@ -5,6 +5,7 @@ test('createLink', () => { expect(internalLink.tagName).toEqual('A'); expect(internalLink.href).toEqual('https://example.com/'); expect(internalLink.textContent).toEqual('example'); + const externalLink = createLink({href: 'https://example.com', textContent: 'example', external: true}); expect(externalLink.tagName).toEqual('A'); expect(externalLink.href).toEqual('https://example.com/'); From 3dedb8595735a123cb5d19cdd884e159d5a19018 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Mar 2024 12:47:36 +0100 Subject: [PATCH 16/16] add check --- web_src/js/utils/misc.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/js/utils/misc.test.js b/web_src/js/utils/misc.test.js index 8c8a4c0cff78f..79b8e0eeef03b 100644 --- a/web_src/js/utils/misc.test.js +++ b/web_src/js/utils/misc.test.js @@ -5,6 +5,7 @@ test('createLink', () => { expect(internalLink.tagName).toEqual('A'); expect(internalLink.href).toEqual('https://example.com/'); expect(internalLink.textContent).toEqual('example'); + expect(internalLink.target).toEqual(''); const externalLink = createLink({href: 'https://example.com', textContent: 'example', external: true}); expect(externalLink.tagName).toEqual('A');