From f65e01226a1135fe159dcde2ae5d8af5630d7ead Mon Sep 17 00:00:00 2001 From: Timur Moziev Date: Sat, 3 Oct 2026 17:27:17 +0300 Subject: [PATCH] enhance(webhook): add select all and deselect all buttons for custom events (#35980) Adds `Select All` and `Deselect All` buttons to the custom events section of the webhook form. This is useful when you need all events but one. - The buttons toggle every event checkbox and keep the unsaved-changes prompt working - Fix the "Trigger On" radio spacing by removing a leftover Fomantic checkbox margin Co-authored-by: silverwind Co-authored-by: wxiaoguang --- templates/repo/settings/webhook/history.tmpl | 18 +++--- templates/repo/settings/webhook/settings.tmpl | 12 ++-- templates/webhook/new.tmpl | 2 +- tests/e2e/user-settings.test.ts | 12 +++- web_src/css/modules/checkbox.css | 8 --- web_src/css/modules/form.css | 7 --- web_src/css/repo.css | 10 ---- web_src/js/features/comp/WebHookEditor.ts | 56 ++++++++++--------- 8 files changed, 59 insertions(+), 66 deletions(-) diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl index 122eb76f2fc..e83e1b48d95 100644 --- a/templates/repo/settings/webhook/history.tmpl +++ b/templates/repo/settings/webhook/history.tmpl @@ -2,7 +2,7 @@

{{ctx.Locale.Tr "repo.settings.recent_deliveries"}} {{if .Permission.IsAdmin}} - + {{DateUtils.TimeSince .Delivered}} -
+ -
+
{{if .RequestInfo}}
{{ctx.Locale.Tr "repo.settings.webhook.headers"}}
Request URL: {{.RequestInfo.URL}}
@@ -68,13 +68,13 @@
 								-
 							{{end}}
 						
-
+
{{if .ResponseInfo}}
{{ctx.Locale.Tr "repo.settings.webhook.headers"}}
{{range $key, $val := .ResponseInfo.Headers}}{{$key}}: {{$val}}
-{{end}}
+{{else}}(no response header){{end}}
{{ctx.Locale.Tr "repo.settings.webhook.body"}}
-
{{.ResponseInfo.Body}}
+
{{or .ResponseInfo.Body "(no response body)"}}
{{else}} - {{end}} diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl index a518e86ce86..524e2c53ae8 100644 --- a/templates/repo/settings/webhook/settings.tmpl +++ b/templates/repo/settings/webhook/settings.tmpl @@ -66,26 +66,30 @@

{{ctx.Locale.Tr "repo.settings.event_desc"}}

-
+
-
+
-
+
-
+
+
+ + +
diff --git a/templates/webhook/new.tmpl b/templates/webhook/new.tmpl index 2cded883655..aef457636c3 100644 --- a/templates/webhook/new.tmpl +++ b/templates/webhook/new.tmpl @@ -9,7 +9,7 @@ {{template "repo/settings/webhook/link_menu" ctx.RootData}}

-
+
{{template "repo/settings/webhook/gitea" ctx.RootData}} {{template "repo/settings/webhook/gogs" ctx.RootData}} {{template "repo/settings/webhook/slack" ctx.RootData}} diff --git a/tests/e2e/user-settings.test.ts b/tests/e2e/user-settings.test.ts index 2a2d964abae..7fe35d127ad 100644 --- a/tests/e2e/user-settings.test.ts +++ b/tests/e2e/user-settings.test.ts @@ -1,5 +1,5 @@ import {test, expect} from '@playwright/test'; -import {loginUser, apiCreateUser, randomString} from './utils.ts'; +import {login, loginUser, apiCreateUser, randomString} from './utils.ts'; test('update profile biography', async ({page, request}) => { const username = `e2e-settings-${randomString(8)}`; @@ -14,3 +14,13 @@ test('update profile biography', async ({page, request}) => { await page.getByRole('button', {name: 'Update Profile'}).click(); await expect(page.getByLabel('Biography')).toHaveValue(''); }); + +test('select and deselect all webhook events', async ({page}) => { + await login(page); + await page.goto('/user/settings/hooks/gitea/new'); + await page.getByRole('radio', {name: 'Custom Events'}).check(); + await page.getByRole('button', {name: 'Select All', exact: true}).click(); + await expect(page.getByRole('checkbox', {checked: false})).toHaveCount(0); + await page.getByRole('button', {name: 'Deselect All', exact: true}).click(); + await expect(page.getByRole('checkbox', {checked: true})).toHaveAccessibleName('Active'); +}); diff --git a/web_src/css/modules/checkbox.css b/web_src/css/modules/checkbox.css index b986301f3b4..45be1948e75 100644 --- a/web_src/css/modules/checkbox.css +++ b/web_src/css/modules/checkbox.css @@ -88,10 +88,6 @@ input[type="checkbox"]:indeterminate::before { .ui.checkbox label { position: relative; display: block; -} - -.ui.checkbox label, -.ui.radio.checkbox label { margin-left: 20px; } @@ -107,10 +103,6 @@ input[type="checkbox"]:indeterminate::before { pointer-events: none; } -.ui.radio.checkbox { - min-height: var(--checkbox-size); -} - /* "switch" styled checkbox */ .ui.toggle.checkbox { diff --git a/web_src/css/modules/form.css b/web_src/css/modules/form.css index cc11ac9b318..57db0aa6350 100644 --- a/web_src/css/modules/form.css +++ b/web_src/css/modules/form.css @@ -114,10 +114,6 @@ textarea:focus, vertical-align: top; } -.ui.ui.form .field .fields .field:not(:only-child) .ui.checkbox { - margin-top: 0.6em; -} - .ui.form .field > .selection.dropdown { min-width: 14em; /* matches the default min width */ width: 100%; @@ -255,9 +251,6 @@ textarea:focus, padding-left: 0.5em; padding-right: 0.5em; } -.ui.form .fields > .field:first-child { - border-left: none; -} @media only screen and (max-width: 767.98px) { .ui.form .fields { diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 6c8f94aafcc..911b2fe28b3 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -952,16 +952,6 @@ font-style: italic; } -.repository.settings.webhook .events .column { - padding-bottom: 0; -} - -.repository.settings.webhook .events .help { - font-size: 13px; - margin-left: 26px; - padding-top: 0; -} - .repository .repository-summary { box-shadow: none; } diff --git a/web_src/js/features/comp/WebHookEditor.ts b/web_src/js/features/comp/WebHookEditor.ts index a6944ff185d..b76945ba8ee 100644 --- a/web_src/js/features/comp/WebHookEditor.ts +++ b/web_src/js/features/comp/WebHookEditor.ts @@ -1,42 +1,46 @@ +import {registerGlobalEventFunc, registerGlobalInitFunc} from '../../modules/observer.ts'; import {POST} from '../../modules/fetch.ts'; -import {hideElem, showElem, toggleElem} from '../../utils/dom.ts'; +import {queryElems, toggleElem} from '../../utils/dom.ts'; -export function initCompWebHookEditor() { - if (!document.querySelectorAll('.new.webhook').length) { - return; - } +function initCompWebHookEditorForm(el: HTMLElement) { + const elCustomEvents = el.querySelector('.js-webhook-custom-events')!; - for (const input of document.querySelectorAll('.events.checkbox input')) { - input.addEventListener('change', function () { - if (this.checked) { - showElem('.events.fields'); - } + // Trigger On: use custom events or not + queryElems(el, 'input[name=events]', (input) => input.addEventListener('change', () => { + if (!input.checked) return; + toggleElem(elCustomEvents, input.value === 'choose_events'); + })); + + // Select All / Deselect All custom events + queryElems(el, 'button[data-events-select-all]', (btn) => btn.addEventListener('click', () => { + const checked = btn.getAttribute('data-events-select-all') === 'true'; + queryElems(elCustomEvents, 'input[type=checkbox]', (input) => { + input.checked = checked; + input.dispatchEvent(new Event('change', {bubbles: true})); // for the areYouSure dirty tracking }); - } - - for (const input of document.querySelectorAll('.non-events.checkbox input')) { - input.addEventListener('change', function () { - if (this.checked) { - hideElem('.events.fields'); - } - }); - } + })); // some webhooks (like Gitea) allow to set the request method (GET/POST), and it would toggle the "Content Type" field - const httpMethodInput = document.querySelector('#http_method'); + const httpMethodInput = el.querySelector('#http_method'); if (httpMethodInput) { - const updateContentType = function () { + const updateContentType = () => { const visible = httpMethodInput.value === 'POST'; - toggleElem(document.querySelector('#content_type')!.closest('.field')!, visible); + toggleElem(el.querySelector('#content_type')!.closest('.field')!, visible); }; updateContentType(); httpMethodInput.addEventListener('change', updateContentType); } +} - // Test delivery - document.querySelector('#test-delivery')?.addEventListener('click', async function () { - this.classList.add('is-loading', 'disabled'); - await POST(this.getAttribute('data-link')!); +function initCompWebHookEditorHistory() { + registerGlobalEventFunc('click', 'onWebhookTestDeliveryClick', async (el) => { + el.classList.add('is-loading', 'disabled'); + await POST(el.getAttribute('data-link')!); setTimeout(() => window.location.reload(), 5000); }); } + +export function initCompWebHookEditor() { + registerGlobalInitFunc('initCompWebHookEditorForm', initCompWebHookEditorForm); + initCompWebHookEditorHistory(); +}