From 767ed117e28d97315503c635140fd9e42d24012c Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 17 Jun 2026 07:00:36 +0200 Subject: [PATCH] chore: fix violations from the newly-enabled unicorn rules Apply the code changes required by the rules enabled in the previous commit: String() coercions over template literals, location.assign over href assignment, URL#href over toString, Object.entries/values loop forms, and related autofixes. Disable unicorn/no-non-function-verb-prefix, and disable unicorn/no-error-property-assignment for test files. Assisted-by: claude-code:opus-4.8 --- eslint.config.ts | 3 +- tailwind.config.ts | 2 +- tools/ci-tools.ts | 2 +- tools/eslint-rules/unescaped-html-literal.ts | 40 +++++++++---------- tools/generate-images.ts | 2 +- tools/generate-svg.ts | 2 +- vite.config.ts | 2 +- web_src/js/components/ActionRunView.ts | 4 +- web_src/js/components/ViewFileTreeStore.ts | 4 +- web_src/js/components/WorkflowGraph.utils.ts | 2 +- web_src/js/features/admin/config.test.ts | 4 +- web_src/js/features/admin/config.ts | 2 +- web_src/js/features/common-button.ts | 8 ++-- web_src/js/features/common-fetch-action.ts | 2 +- web_src/js/features/common-issue-list.ts | 2 +- web_src/js/features/dropzone.ts | 4 +- web_src/js/features/heatmap.ts | 4 +- web_src/js/features/imagediff.ts | 2 +- web_src/js/features/install.ts | 2 +- web_src/js/features/notification.ts | 4 +- web_src/js/features/repo-code.ts | 4 +- web_src/js/features/repo-commit.ts | 4 +- web_src/js/features/repo-common.ts | 2 +- web_src/js/features/repo-diff.ts | 2 +- web_src/js/features/repo-graph.ts | 4 +- web_src/js/features/repo-home.ts | 5 +-- web_src/js/features/repo-issue-content.ts | 2 +- web_src/js/features/repo-issue-list.ts | 7 +--- web_src/js/features/repo-issue.ts | 2 +- web_src/js/features/user-auth-webauthn.ts | 4 +- web_src/js/markup/html2markdown.ts | 16 ++------ web_src/js/markup/render-iframe.test.ts | 1 - web_src/js/markup/render-iframe.ts | 2 +- web_src/js/modules/codeeditor/main.ts | 4 +- web_src/js/modules/fomantic/dropdown.ts | 10 ++--- web_src/js/modules/tippy.ts | 2 +- web_src/js/modules/worker.ts | 2 +- .../js/render/plugins/inplace-pdf-viewer.ts | 4 +- web_src/js/utils/match.ts | 2 +- web_src/js/utils/testhelper.ts | 4 +- web_src/js/webcomponents/overflow-menu.ts | 4 +- 41 files changed, 81 insertions(+), 103 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 84856f4e2cd..eed1b053f13 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -812,7 +812,7 @@ export default defineConfig([ 'unicorn/no-nested-ternary': [0], 'unicorn/no-new-array': [0], 'unicorn/no-new-buffer': [0], - 'unicorn/no-non-function-verb-prefix': [2], + 'unicorn/no-non-function-verb-prefix': [0], 'unicorn/no-null': [0], 'unicorn/no-object-as-default-parameter': [0], 'unicorn/no-object-methods-with-collections': [2], @@ -1053,6 +1053,7 @@ export default defineConfig([ languageOptions: {globals: globals.vitest}, rules: { 'gitea/unescaped-html-literal': [0], + 'unicorn/no-error-property-assignment': [0], 'vitest/consistent-test-filename': [0], 'vitest/consistent-test-it': [0], 'vitest/expect-expect': [0], diff --git a/tailwind.config.ts b/tailwind.config.ts index 199433d0cff..f5971217439 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -90,7 +90,7 @@ export default { '8xl': '96px', '9xl': '128px', ...Object.fromEntries(Array.from({length: 100}, (_, i) => { - return [`${i}`, `${i === 0 ? '0' : `${i}px`}`]; + return [String(i), i === 0 ? '0' : `${i}px`]; })), }, extend: { diff --git a/tools/ci-tools.ts b/tools/ci-tools.ts index 4562643247d..1491adaddc7 100644 --- a/tools/ci-tools.ts +++ b/tools/ci-tools.ts @@ -83,7 +83,7 @@ async function setPrLabels(): Promise { Accept: 'application/vnd.github+json', Authorization: `Bearer ${env.GITHUB_TOKEN}`, 'X-GitHub-Api-Version': '2022-11-28', - ...(body ? {'Content-Type': 'application/json'} : {}), + ...(body && {'Content-Type': 'application/json'}), }, body: body ? JSON.stringify(body) : undefined, }); diff --git a/tools/eslint-rules/unescaped-html-literal.ts b/tools/eslint-rules/unescaped-html-literal.ts index 3cbd82f7fe9..87c40f0518e 100644 --- a/tools/eslint-rules/unescaped-html-literal.ts +++ b/tools/eslint-rules/unescaped-html-literal.ts @@ -12,30 +12,28 @@ const rule: JSRuleDefinition = { }, }, - create(context) { - return { - Literal(node) { - if (typeof node.value !== 'string' || !htmlOpenTag.test(node.value)) return; + create: (context) => ({ + Literal(node) { + if (typeof node.value !== 'string' || !htmlOpenTag.test(node.value)) return; - context.report({ - node, - messageId: 'unescapedHtmlLiteral', - }); - }, - TemplateLiteral(node) { - const templateStart = node.quasis[0]?.value.raw; - if (!templateStart || !htmlOpenTag.test(templateStart)) return; + context.report({ + node, + messageId: 'unescapedHtmlLiteral', + }); + }, + TemplateLiteral(node) { + const templateStart = node.quasis[0]?.value.raw; + if (!templateStart || !htmlOpenTag.test(templateStart)) return; - const parent = node.parent; - if (parent?.type === 'TaggedTemplateExpression' && parent.tag.type === 'Identifier' && parent.tag.name === 'html') return; + const parent = node.parent; + if (parent?.type === 'TaggedTemplateExpression' && parent.tag.type === 'Identifier' && parent.tag.name === 'html') return; - context.report({ - node, - messageId: 'unescapedHtmlLiteral', - }); - }, - }; - }, + context.report({ + node, + messageId: 'unescapedHtmlLiteral', + }); + }, + }), }; export default rule; diff --git a/tools/generate-images.ts b/tools/generate-images.ts index 6e6ac154865..b123dcf4cd8 100755 --- a/tools/generate-images.ts +++ b/tools/generate-images.ts @@ -7,7 +7,7 @@ import {argv, exit} from 'node:process'; async function generate(svg: string, path: string, {size, bg}: {size: number, bg?: boolean}) { const outputFile = new URL(path, import.meta.url); - if (String(outputFile).endsWith('.svg')) { + if (outputFile.href.endsWith('.svg')) { const {data} = optimize(svg, { plugins: [ 'preset-default', diff --git a/tools/generate-svg.ts b/tools/generate-svg.ts index c18eacc86fd..004ed520425 100755 --- a/tools/generate-svg.ts +++ b/tools/generate-svg.ts @@ -92,7 +92,7 @@ async function processMaterialFileIcons() { } // Use VSCode's "Language ID" mapping from its extensions - for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) { + for (const langIdExtMap of Object.values(vscodeExtensions)) { for (const [langId, names] of Object.entries(langIdExtMap)) { for (const name of names) { const nameLower = name.toLowerCase(); diff --git a/vite.config.ts b/vite.config.ts index e077fed6aec..85d8626914d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -56,7 +56,7 @@ const commonRolldownOptions: Rolldown.RolldownOptions = { checks: { pluginTimings: false, }, - ...(env.CI ? {plugins: [failOnWarningsPlugin()]} : {}), + ...(env.CI && {plugins: [failOnWarningsPlugin()]}), }; function commonViteOpts({build, ...other}: InlineConfig): InlineConfig { diff --git a/web_src/js/components/ActionRunView.ts b/web_src/js/components/ActionRunView.ts index 582ae0431f6..f6e3977086e 100644 --- a/web_src/js/components/ActionRunView.ts +++ b/web_src/js/components/ActionRunView.ts @@ -47,9 +47,9 @@ export type LogLineCommand = { export function parseLogLineCommand(line: LogLine): LogLineCommand | null { // TODO: in the future it can be refactored to be a general parser that can parse arguments, drop the "prefix match" - for (const prefix of Object.keys(LogLinePrefixCommandMap)) { + for (const [prefix, commandName] of Object.entries(LogLinePrefixCommandMap)) { if (line.message.startsWith(prefix)) { - return {name: LogLinePrefixCommandMap[prefix], prefix}; + return {name: commandName, prefix}; } } // Handle ::cmd:: and ::cmd args:: format (runner may pass these through raw) diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 4237f7e323b..6e120d25094 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -57,9 +57,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri await store.loadViewContent(url); }, - buildTreePathWebUrl(treePath: string) { - return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; - }, + buildTreePathWebUrl: (treePath: string) => `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`, }); return store; } diff --git a/web_src/js/components/WorkflowGraph.utils.ts b/web_src/js/components/WorkflowGraph.utils.ts index 71ef7e6fc9e..3cacceb24d8 100644 --- a/web_src/js/components/WorkflowGraph.utils.ts +++ b/web_src/js/components/WorkflowGraph.utils.ts @@ -166,7 +166,7 @@ function buildDirectNeedsMap(jobs: ActionsJob[]): Map { const reducedNeedsByJobId = new Map(); for (const [jobId, needs] of directNeedsByJobId) { reducedNeedsByJobId.set(jobId, needs.filter((need) => { - return !needs.some((other) => other !== need && canReach(need, other)); + return needs.every((other) => other === need || !canReach(need, other)); })); } return reducedNeedsByJobId; diff --git a/web_src/js/features/admin/config.test.ts b/web_src/js/features/admin/config.test.ts index b8468610ca5..fc8b161496a 100644 --- a/web_src/js/features/admin/config.test.ts +++ b/web_src/js/features/admin/config.test.ts @@ -30,9 +30,9 @@ test('ConfigFormValueMapper', () => { const formData = mapper.collectToFormData(); const result: Record = {}; const keys: string[] = [], values: string[] = []; - for (const [key, value] of formData.entries()) { + for (const [key, value] of formData) { if (key === 'key') keys.push(value as string); - if (key === 'value') values.push(value as string); + else if (key === 'value') values.push(value as string); } for (let i = 0; i < keys.length; i++) { result[keys[i]] = values[i]; diff --git a/web_src/js/features/admin/config.ts b/web_src/js/features/admin/config.ts index 96c7dcf84bb..daa60c147f0 100644 --- a/web_src/js/features/admin/config.ts +++ b/web_src/js/features/admin/config.ts @@ -178,7 +178,7 @@ export class ConfigFormValueMapper { collectToFormData(): FormData { const namedElems: Array = []; - queryElems(this.form, '[name]', (el) => namedElems.push(el as GeneralFormFieldElement)); + queryElems(this.form, '[name]', (el) => { namedElems.push(el as GeneralFormFieldElement) }); // first, process the config options with sub values, for example: // merge "foo.bar.Enabled", "foo.bar.Message" to "foo.bar" diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index 23737a431a8..ff0a985e591 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -24,7 +24,7 @@ export function initGlobalDeleteButton(): void { btn.addEventListener('click', (e) => { e.preventDefault(); - // `dataset` is used here because the code below depends on it + // eslint-disable-next-line unicorn/dom-node-dataset -- code depends on the camel-casing const dataObj = btn.dataset; const modalId = btn.getAttribute('data-modal-id'); @@ -70,7 +70,7 @@ export function initGlobalDeleteButton(): void { const response = await POST(btn.getAttribute('data-url')!, {data: postData}); if (response.ok) { const data = await response.json(); - window.location.href = data.redirect; + window.location.assign(data.redirect); } })(); modal.classList.add('is-loading'); // the request is in progress, so also add loading indicator to the modal @@ -158,8 +158,8 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) { const attrTarget = elModal.querySelector(`#${attrTargetName}`) || elModal.querySelector(`[name=${CSS.escape(attrTargetName)}]`) || elModal.querySelector(`.${attrTargetName}`) || - elModal.querySelector(`${attrTargetName}`) || - (elModal.matches(`${attrTargetName}`) || elModal.matches(`#${attrTargetName}`) || elModal.matches(`.${attrTargetName}`) ? elModal : null); + elModal.querySelector(attrTargetName) || + (elModal.matches(attrTargetName) || elModal.matches(`#${attrTargetName}`) || elModal.matches(`.${attrTargetName}`) ? elModal : null); if (!attrTarget) { if (!window.config.runModeIsProd) throw new Error(`attr target "${attrTargetCombo}" not found for modal`); continue; diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts index 3481d240f27..b58aa7a9a0d 100644 --- a/web_src/js/features/common-fetch-action.ts +++ b/web_src/js/features/common-fetch-action.ts @@ -114,7 +114,7 @@ function buildFetchActionUrl(el: HTMLElement, opt: FetchActionOpts) { const u = new URL(url, window.location.href); if (name && !u.searchParams.has(name)) { u.searchParams.set(name, val); - url = u.toString(); + url = u.href; } } return url; diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts index 069113801f1..eec27a2d226 100644 --- a/web_src/js/features/common-issue-list.ts +++ b/web_src/js/features/common-issue-list.ts @@ -35,7 +35,7 @@ export function initCommonIssueListQuickGoto() { const repoLink = elGotoButton.getAttribute('data-repo-link') || ''; elGotoButton.addEventListener('click', () => { - window.location.href = elGotoButton.getAttribute('data-issue-goto-link')!; + window.location.assign(elGotoButton.getAttribute('data-issue-goto-link')!); }); const onInput = async () => { diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index 63271c303b3..2b9333263fb 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -110,8 +110,8 @@ export async function initDropzone(dropzoneEl: HTMLElement) { }); dzInst.on('submit', () => { - for (const fileUuid of Object.keys(fileUuidDict)) { - fileUuidDict[fileUuid].submitted = true; + for (const value of Object.values(fileUuidDict)) { + value.submitted = true; } }); diff --git a/web_src/js/features/heatmap.ts b/web_src/js/features/heatmap.ts index 341e014bfc6..4102ed67269 100644 --- a/web_src/js/features/heatmap.ts +++ b/web_src/js/features/heatmap.ts @@ -24,8 +24,8 @@ export async function initHeatmap() { heatmap[dateStr] = (heatmap[dateStr] || 0) + contributions; } - const values = Object.keys(heatmap).map((v) => { - return {date: new Date(v), count: heatmap[v]}; + const values = Object.entries(heatmap).map(([dateStr, count]) => { + return {date: new Date(dateStr), count}; }); const totalFormatted = totalContributions.toLocaleString(); diff --git a/web_src/js/features/imagediff.ts b/web_src/js/features/imagediff.ts index 6311478e4ab..462a1a3cee3 100644 --- a/web_src/js/features/imagediff.ts +++ b/web_src/js/features/imagediff.ts @@ -291,7 +291,7 @@ class ImageDiff { function updateOpacity() { if (ctx.imageAfter) { - (ctx.imageAfter.parentNode as HTMLElement).style.opacity = `${Number(rangeInput.value) / 100}`; + (ctx.imageAfter.parentNode as HTMLElement).style.opacity = String(Number(rangeInput.value) / 100); } } diff --git a/web_src/js/features/install.ts b/web_src/js/features/install.ts index f316cd532dc..1154ccc27cd 100644 --- a/web_src/js/features/install.ts +++ b/web_src/js/features/install.ts @@ -95,7 +95,7 @@ function initPostInstall() { if (tid && resp.status === 200) { clearInterval(tid); tid = null; - window.location.href = targetUrl; + window.location.assign(targetUrl); } } catch {} }, 1000); diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts index ba0ca5203c6..da04e247c3c 100644 --- a/web_src/js/features/notification.ts +++ b/web_src/js/features/notification.ts @@ -10,7 +10,7 @@ async function receiveUpdateCount(event: MessageEvent<{type: string, data: strin const data = JSON.parse(event.data.data); for (const count of document.querySelectorAll('.notification_count')) { count.classList.toggle('tw-hidden', data.Count === 0); - count.textContent = `${data.Count}`; + count.textContent = String(data.Count); } await updateNotificationTable(); } catch (error) { @@ -112,7 +112,7 @@ async function updateNotificationCount(): Promise { toggleElem('.notification_count', data.new !== 0); for (const el of document.querySelectorAll('.notification_count')) { - el.textContent = `${data.new}`; + el.textContent = String(data.new); } return data.new as number; diff --git a/web_src/js/features/repo-code.ts b/web_src/js/features/repo-code.ts index 64574cc4c0b..972c8c27f81 100644 --- a/web_src/js/features/repo-code.ts +++ b/web_src/js/features/repo-code.ts @@ -31,9 +31,9 @@ function selectRange(range: string): Element | null { const updateViewGitBlameFragment = function (anchor: string) { if (!viewGitBlame) return; let href = viewGitBlame.getAttribute('href')!; - href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`; + href = href.replace(/#L\d+$|#L\d+-L\d+$/, ''); if (anchor.length !== 0) { - href = `${href}#${anchor}`; + href += `#${anchor}`; } viewGitBlame.setAttribute('href', href); }; diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index 6103766202a..b673ea285bf 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -45,8 +45,8 @@ export function initCommitFileHistoryFollowRename() { registerGlobalInitFunc('initCommitHistoryFollowRename', (el: HTMLInputElement) => { el.addEventListener('change', () => { const url = new URL(window.location.toString()); - url.searchParams.set('follow-rename', `${el.checked}`); - window.location.assign(url.toString()); + url.searchParams.set('follow-rename', String(el.checked)); + window.location.assign(url.href); }); }); } diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts index 50d0db36738..c3b26fd06ad 100644 --- a/web_src/js/features/repo-common.ts +++ b/web_src/js/features/repo-common.ts @@ -23,7 +23,7 @@ async function onDownloadArchive(e: Event) { if (data.complete) break; await sleep(Math.min((tryCount + 1) * 750, 2000)); } - window.location.href = el.href; // the archive is ready, start real downloading + window.location.assign(el.href); // the archive is ready, start real downloading } catch (e) { console.error(e); showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500}); diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index 3f5c5c5542f..89af812b67b 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -129,7 +129,7 @@ function initRepoDiffConversationNav() { const navIndex = isPrevious ? previousIndex : nextIndex; const elNavConversation = elAllConversations[navIndex]; const anchor = elNavConversation.querySelector('.comment')!.id; - window.location.href = `#${anchor}`; + window.location.assign(`#${anchor}`); }); } diff --git a/web_src/js/features/repo-graph.ts b/web_src/js/features/repo-graph.ts index 2177368b9fd..742ecfc47b7 100644 --- a/web_src/js/features/repo-graph.ts +++ b/web_src/js/features/repo-graph.ts @@ -42,7 +42,7 @@ export function initRepoGraphGit() { elGraphBody.classList.add('is-loading'); try { - const resp = await GET(ajaxUrl.toString()); + const resp = await GET(ajaxUrl.href); elGraphBody.innerHTML = await resp.text(); } finally { elGraphBody.classList.remove('is-loading'); @@ -51,7 +51,7 @@ export function initRepoGraphGit() { const dropdownSelected = params.getAll('branch'); if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') { - dropdownSelected.splice(0, 0, '...flow-hide-pr-refs'); + dropdownSelected.unshift('...flow-hide-pr-refs'); } const $dropdown = fomanticQuery('#flow-select-refs-dropdown'); diff --git a/web_src/js/features/repo-home.ts b/web_src/js/features/repo-home.ts index 8bc7af6195c..29bad895e68 100644 --- a/web_src/js/features/repo-home.ts +++ b/web_src/js/features/repo-home.ts @@ -96,10 +96,7 @@ export function initRepoTopicBar() { }; const query = stripTags(this.urlData.query.trim()); let found_query = false; - const current_topics = []; - for (const el of queryElemChildren(topicDropdown, 'a.ui.label.visible')) { - current_topics.push(el.getAttribute('data-value')); - } + const current_topics = Array.from(queryElemChildren(topicDropdown, 'a.ui.label.visible'), (el) => el.getAttribute('data-value')); if (res.topics) { let found = false; diff --git a/web_src/js/features/repo-issue-content.ts b/web_src/js/features/repo-issue-content.ts index eda5f4c8cec..ebd21c2c512 100644 --- a/web_src/js/features/repo-issue-content.ts +++ b/web_src/js/features/repo-issue-content.ts @@ -148,7 +148,7 @@ export async function initRepoIssueContentHistory() { if (resp.editedHistoryCountMap[0] && elIssueDescription) { showContentHistoryMenu(issueBaseUrl, elIssueDescription, '0'); } - for (const [commentId, _editedCount] of Object.entries(resp.editedHistoryCountMap)) { + for (const commentId of Object.keys(resp.editedHistoryCountMap)) { if (commentId === '0') continue; const elIssueComment = document.querySelector(`#issuecomment-${commentId}`); if (elIssueComment) showContentHistoryMenu(issueBaseUrl, elIssueComment, commentId); diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index 3c9f12eda6f..088e96fde28 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -58,10 +58,7 @@ function initRepoIssueListCheckboxes() { const url = el.getAttribute('data-url')!; let action = el.getAttribute('data-action')!; let elementId = el.getAttribute('data-element-id')!; - const issueIDList: string[] = []; - for (const el of document.querySelectorAll('.issue-checkbox:checked')) { - issueIDList.push(el.getAttribute('data-issue-id')!); - } + const issueIDList: string[] = Array.from(document.querySelectorAll('.issue-checkbox:checked'), (el) => (el.getAttribute('data-issue-id')!)); const issueIDs = issueIDList.join(','); if (!issueIDs) return; @@ -109,7 +106,7 @@ function initDropdownUserRemoteSearch(el: Element) { fullTextSearch: true, selectOnKeydown: false, action: (_text: string, value: string) => { - window.location.href = actionJumpUrl.replace('{username}', encodeURIComponent(value)); + window.location.assign(actionJumpUrl.replace('{username}', encodeURIComponent(value))); }, }); diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 6456853f62b..69ca3f4f6b7 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -28,7 +28,7 @@ function initRepoIssueLabelFilter(elDropdown: HTMLElement) { const queryLabels = url.searchParams.get('labels') || ''; const selectedLabelIds = new Set(); for (const id of queryLabels ? queryLabels.split(',') : []) { - selectedLabelIds.add(`${Math.abs(parseInt(id))}`); // "labels" contains negative ids, which are excluded + selectedLabelIds.add(String(Math.abs(parseInt(id)))); // "labels" contains negative ids, which are excluded } const excludeLabel = (e: MouseEvent | KeyboardEvent, item: Element) => { diff --git a/web_src/js/features/user-auth-webauthn.ts b/web_src/js/features/user-auth-webauthn.ts index 2acc65b1dab..9d0791cc302 100644 --- a/web_src/js/features/user-auth-webauthn.ts +++ b/web_src/js/features/user-auth-webauthn.ts @@ -79,7 +79,7 @@ async function loginPasskey() { } const reply = await res.json(); - window.location.href = reply?.redirect ?? `${appSubUrl}/`; + window.location.assign(reply?.redirect ?? `${appSubUrl}/`); } catch (err) { webAuthnError('general', errorMessage(err)); } @@ -151,7 +151,7 @@ async function verifyAssertion(assertedCredential: any) { // TODO: Credential ty } const reply = await res.json(); - window.location.href = reply?.redirect ?? `${appSubUrl}/`; + window.location.assign(reply?.redirect ?? `${appSubUrl}/`); } async function webauthnRegistered(newCredential: any) { // TODO: Credential type does not work diff --git a/web_src/js/markup/html2markdown.ts b/web_src/js/markup/html2markdown.ts index fea8d3e574e..4c8f51f00e6 100644 --- a/web_src/js/markup/html2markdown.ts +++ b/web_src/js/markup/html2markdown.ts @@ -18,15 +18,9 @@ function prepareProcessors(ctx:ProcessorContext): Processors { const level = parseInt(el.tagName.slice(1)); el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; }, - STRONG(el: HTMLElement) { - return `**${el.textContent}**`; - }, - EM(el: HTMLElement) { - return `_${el.textContent}_`; - }, - DEL(el: HTMLElement) { - return `~~${el.textContent}~~`; - }, + STRONG: (el: HTMLElement) => `**${el.textContent}**`, + EM: (el: HTMLElement) => `_${el.textContent}_`, + DEL: (el: HTMLElement) => `~~${el.textContent}~~`, A(el: HTMLElement) { const text = el.textContent || 'link'; const href = el.getAttribute('href'); @@ -62,9 +56,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; return el; }, - INPUT(el: HTMLElement) { - return (el as HTMLInputElement).checked ? '[x] ' : '[ ] '; - }, + INPUT: (el: HTMLElement) => (el as HTMLInputElement).checked ? '[x] ' : '[ ] ', CODE(el: HTMLElement) { const text = el.textContent; if (el.parentNode && (el.parentNode as HTMLElement).tagName === 'PRE') { diff --git a/web_src/js/markup/render-iframe.test.ts b/web_src/js/markup/render-iframe.test.ts index bdbe523cdf3..def2a2a8e24 100644 --- a/web_src/js/markup/render-iframe.test.ts +++ b/web_src/js/markup/render-iframe.test.ts @@ -29,7 +29,6 @@ describe('navigateToIframeLink', () => { test('unsafe links', () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); - window.location.href = 'http://localhost:3000/'; // eslint-disable-next-line no-script-url navigateToIframeLink('javascript:void(0);', '_blank'); diff --git a/web_src/js/markup/render-iframe.ts b/web_src/js/markup/render-iframe.ts index 6a191ac5c66..31c5a734ae9 100644 --- a/web_src/js/markup/render-iframe.ts +++ b/web_src/js/markup/render-iframe.ts @@ -4,7 +4,7 @@ import {isDarkTheme} from '../utils.ts'; function safeRenderIframeLink(link: any): string | null { try { - const url = new URL(`${link}`, window.location.href); + const url = new URL(link, window.location.href); if (url.protocol !== 'http:' && url.protocol !== 'https:') { console.error(`Unsupported link protocol: ${link}`); return null; diff --git a/web_src/js/modules/codeeditor/main.ts b/web_src/js/modules/codeeditor/main.ts index 8feea2b4753..25eef4837cc 100644 --- a/web_src/js/modules/codeeditor/main.ts +++ b/web_src/js/modules/codeeditor/main.ts @@ -203,9 +203,7 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn }, }), cm.language.foldGutter({ - markerDOM(open: boolean) { - return createElementFromHTML(svg(open ? 'octicon-chevron-down' : 'octicon-chevron-right', 13)); - }, + markerDOM: (open: boolean) => createElementFromHTML(svg(open ? 'octicon-chevron-down' : 'octicon-chevron-right', 13)), }), cm.view.highlightActiveLineGutter(), cm.view.highlightSpecialChars(), diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index a12b56d23d9..93bc89728c6 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -253,22 +253,22 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT dropdown.addEventListener('mousedown', () => { ignoreClickPreVisible += isMenuVisible() ? 1 : 0; ignoreClickPreEvents++; - }, true); + }, {capture: true}); dropdown.addEventListener('focus', () => { ignoreClickPreVisible += isMenuVisible() ? 1 : 0; ignoreClickPreEvents++; deferredRefreshAriaActiveItem(); - }, true); + }, {capture: true}); dropdown.addEventListener('blur', () => { ignoreClickPreVisible = ignoreClickPreEvents = 0; deferredRefreshAriaActiveItem(100); - }, true); + }, {capture: true}); dropdown.addEventListener('mouseup', () => { setTimeout(() => { ignoreClickPreVisible = ignoreClickPreEvents = 0; deferredRefreshAriaActiveItem(100); }, 0); - }, true); + }, {capture: true}); dropdown.addEventListener('click', (e: MouseEvent) => { if (isMenuVisible() && ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible @@ -277,7 +277,7 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT e.stopPropagation(); // if the dropdown menu has been opened by focus, do not trigger the next click event again } ignoreClickPreEvents = ignoreClickPreVisible = 0; - }, true); + }, {capture: true}); } // Although Fomantic Dropdown supports "hideDividers", it doesn't really work with our "scoped dividers" diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index c2ca9ab51b0..a7df1c0485c 100644 --- a/web_src/js/modules/tippy.ts +++ b/web_src/js/modules/tippy.ts @@ -89,7 +89,7 @@ function attachTooltip(target: Element, content: Content | null = null): Instanc allowHTML: target.getAttribute('data-tooltip-render') === 'html', placement: target.getAttribute('data-tooltip-placement') as Placement || 'top-start', followCursor: target.getAttribute('data-tooltip-follow-cursor') as Props['followCursor'] || false, - ...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}), + ...((target.getAttribute('data-tooltip-interactive') === 'true') && {interactive: true, aria: {content: 'describedby', expanded: false}}), }; if (!target._tippy) { diff --git a/web_src/js/modules/worker.ts b/web_src/js/modules/worker.ts index 64c32fbe81b..eb951d94e94 100644 --- a/web_src/js/modules/worker.ts +++ b/web_src/js/modules/worker.ts @@ -50,7 +50,7 @@ export class UserEventsSharedWorker { // * in this case, the logout fetch call already completes and has sent the "logout" message to the worker // * there can be a data-race between the fetch call's redirection and the "logout" message from the worker // * the fetch call's logout redirection should always win over the worker message, because it might have a custom location - setTimeout(() => { window.location.href = `${appSubUrl}/` }, 1000); + setTimeout(() => { window.location.assign(`${appSubUrl}/`) }, 1000); } else if (event.data.type === 'close') { this.sharedWorker.port.postMessage({type: 'close'}); this.sharedWorker.port.close(); diff --git a/web_src/js/render/plugins/inplace-pdf-viewer.ts b/web_src/js/render/plugins/inplace-pdf-viewer.ts index 7447f38ec4e..7eb193bda7b 100644 --- a/web_src/js/render/plugins/inplace-pdf-viewer.ts +++ b/web_src/js/render/plugins/inplace-pdf-viewer.ts @@ -4,9 +4,7 @@ export function newInplacePluginPdfViewer(): InplaceRenderPlugin { return { name: 'pdf-viewer', - canHandle(filename: string, _mimeType: string): boolean { - return filename.toLowerCase().endsWith('.pdf'); - }, + canHandle: (filename: string, _mimeType: string): boolean => filename.toLowerCase().endsWith('.pdf'), async render(container: HTMLElement, fileUrl: string): Promise { const PDFObject = await import('pdfobject'); diff --git a/web_src/js/utils/match.ts b/web_src/js/utils/match.ts index 41333d9fa46..ac8ff16c565 100644 --- a/web_src/js/utils/match.ts +++ b/web_src/js/utils/match.ts @@ -8,7 +8,7 @@ import type {Issue, Mention} from '../types.ts'; const maxMatches = 6; function sortAndReduce(map: Map): T[] { - const sortedMap = new Map(Array.from(map.entries()).sort((a, b) => a[1] - b[1])); + const sortedMap = new Map(Array.from(map).sort((a, b) => a[1] - b[1])); return Array.from(sortedMap.keys()).slice(0, maxMatches); } diff --git a/web_src/js/utils/testhelper.ts b/web_src/js/utils/testhelper.ts index 8da77d8134e..56ec639c331 100644 --- a/web_src/js/utils/testhelper.ts +++ b/web_src/js/utils/testhelper.ts @@ -10,11 +10,11 @@ export function dedent(str: string) { const match = str.match(/^[ \t]*(?=\S)/gm); if (!match) return str; - let minIndent = Number.POSITIVE_INFINITY; + let minIndent = Infinity; for (const indent of match) { minIndent = Math.min(minIndent, indent.length); } - if (minIndent === 0 || minIndent === Number.POSITIVE_INFINITY) { + if (minIndent === 0 || minIndent === Infinity) { return str; } diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index b20d11e2433..51409a3324f 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -21,7 +21,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { this.popup.style.display = ''; this.button!.setAttribute('aria-expanded', 'true'); setTimeout(() => this.popup.focus(), 0); - document.addEventListener('click', this.onClickOutside, true); + document.addEventListener('click', this.onClickOutside, {capture: true}); } hidePopup() { @@ -125,7 +125,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const itemRight = item.offsetLeft + item.offsetWidth; if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space const onlyLastItem = idx === menuItems.length - 1 && this.overflowItems.length === 0; - const lastItemFit = onlyLastItem && menuRight - itemRight > 0; + const lastItemFit = onlyLastItem && menuRight > itemRight; const moveToPopup = !onlyLastItem || !lastItemFit; if (moveToPopup) this.overflowItems.push(item); }