From 35eb7d00303f7c4d6a6c719b1a287fe41846c0bd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 8 May 2025 14:37:20 +0800 Subject: [PATCH 1/2] fix --- templates/admin/auth/edit.tmpl | 2 +- templates/admin/user/edit.tmpl | 6 +-- templates/org/settings/options.tmpl | 2 +- templates/repo/settings/collaboration.tmpl | 2 +- templates/repo/settings/options.tmpl | 2 +- templates/user/settings/profile.tmpl | 2 +- web_src/js/features/common-button.ts | 11 +++--- web_src/js/features/common-issue-list.ts | 6 +-- web_src/js/features/repo-issue-list.ts | 6 +-- web_src/js/utils/dom.ts | 43 +++++++++------------- 10 files changed, 38 insertions(+), 44 deletions(-) diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 15683307ed2ce..91b84e13b6a19 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -15,7 +15,7 @@
- +
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index c04d332660677..879b5cb550d30 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -9,7 +9,7 @@ {{.CsrfTokenHtml}}
- +
@@ -55,7 +55,7 @@
- +
@@ -63,7 +63,7 @@
- +
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 76315f3eac48d..f4583bbe366b6 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -12,7 +12,7 @@
{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}
{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}} - +
diff --git a/templates/repo/settings/collaboration.tmpl b/templates/repo/settings/collaboration.tmpl index 44613982586db..7064b4c7ba324 100644 --- a/templates/repo/settings/collaboration.tmpl +++ b/templates/repo/settings/collaboration.tmpl @@ -90,7 +90,7 @@
{{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 4d61604612389..fc42056e0a2bb 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -10,7 +10,7 @@
- +
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 03c3c18f28c1c..d8e5e27b896a6 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -12,7 +12,7 @@ {{ctx.Locale.Tr "settings.change_username_prompt"}} {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}} - + {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}

{{ctx.Locale.Tr "settings.password_username_disabled"}}

{{end}} diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index 003bfbce5da55..320229bfe83af 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -1,5 +1,5 @@ import {POST} from '../modules/fetch.ts'; -import {addDelegatedEventListener, hideElem, showElem, toggleElem} from '../utils/dom.ts'; +import {addDelegatedEventListener, hideElem, isElemVisible, showElem, toggleElem} from '../utils/dom.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {camelize} from 'vue'; @@ -79,10 +79,11 @@ function onShowPanelClick(el: HTMLElement, e: MouseEvent) { // if it has "toggle" class, it toggles the panel e.preventDefault(); const sel = el.getAttribute('data-panel'); - if (el.classList.contains('toggle')) { - toggleElem(sel); - } else { - showElem(sel); + const elems = el.classList.contains('toggle') ? toggleElem(sel) : showElem(sel); + for (const elem of elems) { + if (isElemVisible(elem as HTMLElement)) { + elem.querySelector('[autofocus]')?.focus(); + } } } diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts index e2073647940ab..037529bd1048d 100644 --- a/web_src/js/features/common-issue-list.ts +++ b/web_src/js/features/common-issue-list.ts @@ -1,4 +1,4 @@ -import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts'; +import {isElemVisible, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts'; import {GET} from '../modules/fetch.ts'; const {appSubUrl} = window.config; @@ -28,7 +28,7 @@ export function parseIssueListQuickGotoLink(repoLink: string, searchText: string } export function initCommonIssueListQuickGoto() { - const goto = document.querySelector('#issue-list-quick-goto'); + const goto = document.querySelector('#issue-list-quick-goto'); if (!goto) return; const form = goto.closest('form'); @@ -37,7 +37,7 @@ export function initCommonIssueListQuickGoto() { form.addEventListener('submit', (e) => { // if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly - let doQuickGoto = !isElemHidden(goto); + let doQuickGoto = isElemVisible(goto); const submitter = submitEventSubmitter(e); if (submitter !== form && submitter !== input && submitter !== goto) doQuickGoto = false; if (!doQuickGoto) return; diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index 8cd4483357586..3ea5fb70c0b7d 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -1,5 +1,5 @@ import {updateIssuesMeta} from './repo-common.ts'; -import {toggleElem, isElemHidden, queryElems} from '../utils/dom.ts'; +import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts'; import {htmlEscape} from 'escape-goat'; import {confirmModal} from './comp/ConfirmModal.ts'; import {showErrorToast} from '../modules/toast.ts'; @@ -33,8 +33,8 @@ function initRepoIssueListCheckboxes() { toggleElem('#issue-filters', !anyChecked); toggleElem('#issue-actions', anyChecked); // there are two panels but only one select-all checkbox, so move the checkbox to the visible panel - const panels = document.querySelectorAll('#issue-filters, #issue-actions'); - const visiblePanel = Array.from(panels).find((el) => !isElemHidden(el)); + const panels = document.querySelectorAll('#issue-filters, #issue-actions'); + const visiblePanel = Array.from(panels).find((el) => isElemVisible(el)); const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left'); toolbarLeft.prepend(issueSelectAll); }; diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 4386d386327bb..91e9af11cfe4e 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -9,24 +9,24 @@ type ElementsCallback = (el: T) => Promisable; type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable; export type DOMEvent = E & { target: Partial; }; -function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) { +function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]): ArrayLikeIterable { if (typeof el === 'string' || el instanceof String) { el = document.querySelectorAll(el as string); } if (el instanceof Node) { func(el, ...args); + return [el]; } else if (el.length !== undefined) { // this works for: NodeList, HTMLCollection, Array, jQuery - for (const e of (el as ArrayLikeIterable)) { - func(e, ...args); - } - } else { - throw new Error('invalid argument to be shown/hidden'); + const elems = el as ArrayLikeIterable; + for (const elem of elems) func(elem, ...args); + return elems; } + throw new Error('invalid argument to be shown/hidden'); } -export function toggleClass(el: ElementArg, className: string, force?: boolean) { - elementsCall(el, (e: Element) => { +export function toggleClass(el: ElementArg, className: string, force?: boolean): ArrayLikeIterable { + return elementsCall(el, (e: Element) => { if (force === true) { e.classList.add(className); } else if (force === false) { @@ -43,23 +43,16 @@ export function toggleClass(el: ElementArg, className: string, force?: boolean) * @param el ElementArg * @param force force=true to show or force=false to hide, undefined to toggle */ -export function toggleElem(el: ElementArg, force?: boolean) { - toggleClass(el, 'tw-hidden', force === undefined ? force : !force); -} - -export function showElem(el: ElementArg) { - toggleElem(el, true); +export function toggleElem(el: ElementArg, force?: boolean): ArrayLikeIterable { + return toggleClass(el, 'tw-hidden', force === undefined ? force : !force); } -export function hideElem(el: ElementArg) { - toggleElem(el, false); +export function showElem(el: ElementArg): ArrayLikeIterable { + return toggleElem(el, true); } -export function isElemHidden(el: ElementArg) { - const res: boolean[] = []; - elementsCall(el, (e) => res.push(e.classList.contains('tw-hidden'))); - if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`); - return res[0]; +export function hideElem(el: ElementArg): ArrayLikeIterable { + return toggleElem(el, false); } function applyElemsCallback(elems: ArrayLikeIterable, fn?: ElementsCallback): ArrayLikeIterable { @@ -279,10 +272,10 @@ export function initSubmitEventPolyfill() { * Check if an element is visible, equivalent to jQuery's `:visible` pseudo. * Note: This function doesn't account for all possible visibility scenarios. */ -export function isElemVisible(element: HTMLElement): boolean { - if (!element) return false; - // checking element.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout - return Boolean((element.offsetWidth || element.offsetHeight || element.getClientRects().length) && element.style.display !== 'none'); +export function isElemVisible(el: HTMLElement): boolean { + if (!el) return false; + // checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout + return !el.classList.contains('tw-hidden') && Boolean((el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none'); } // replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this From 26d4c59df3f04072c0c56542f41f2675ce3b86de Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 8 May 2025 19:32:59 +0800 Subject: [PATCH 2/2] improve tests and comments --- web_src/js/utils/dom.test.ts | 6 +++++- web_src/js/utils/dom.ts | 6 ++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web_src/js/utils/dom.test.ts b/web_src/js/utils/dom.test.ts index 6a3af9155653f..057ea9808c853 100644 --- a/web_src/js/utils/dom.test.ts +++ b/web_src/js/utils/dom.test.ts @@ -25,10 +25,14 @@ test('createElementFromAttrs', () => { }); test('querySingleVisibleElem', () => { - let el = createElementFromHTML('
foo
'); + let el = createElementFromHTML('
'); + expect(querySingleVisibleElem(el, 'span')).toBeNull(); + el = createElementFromHTML('
foo
'); expect(querySingleVisibleElem(el, 'span').textContent).toEqual('foo'); el = createElementFromHTML('
foobar
'); expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar'); + el = createElementFromHTML('
foobar
'); + expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar'); el = createElementFromHTML('
foobar
'); expect(() => querySingleVisibleElem(el, 'span')).toThrowError('Expected exactly one visible element'); }); diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 91e9af11cfe4e..83a0d9c8df2b2 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -268,11 +268,9 @@ export function initSubmitEventPolyfill() { document.body.addEventListener('focus', submitEventPolyfillListener); } -/** - * Check if an element is visible, equivalent to jQuery's `:visible` pseudo. - * Note: This function doesn't account for all possible visibility scenarios. - */ export function isElemVisible(el: HTMLElement): boolean { + // Check if an element is visible, equivalent to jQuery's `:visible` pseudo. + // This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem" if (!el) return false; // checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout return !el.classList.contains('tw-hidden') && Boolean((el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none');