From dde7c4a7706ded56d706cff331ac8326170abd07 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 1 Apr 2025 03:55:34 +0200 Subject: [PATCH 01/23] chore: add new `no-jquery` rules Were added in version v3.1.0. --- eslint.config.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 17f461a8f4..035c70a338 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -318,6 +318,7 @@ export default tseslint.config( 'no-jquery/no-data': [0], 'no-jquery/no-deferred': [2], 'no-jquery/no-delegate': [2], + 'no-jquery/no-done-fail': [2], 'no-jquery/no-each-collection': [0], 'no-jquery/no-each-util': [0], 'no-jquery/no-each': [0], @@ -331,6 +332,7 @@ export default tseslint.config( 'no-jquery/no-find-collection': [0], 'no-jquery/no-find-util': [2], 'no-jquery/no-find': [0], + 'no-jquery/no-fx': [2], 'no-jquery/no-fx-interval': [2], 'no-jquery/no-global-eval': [2], 'no-jquery/no-global-selector': [0], From 9eb67ba8eded4d64a4a21e641e6c28a1e70f3830 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 1 Apr 2025 03:58:09 +0200 Subject: [PATCH 02/23] chore: enable `no-jquery/no-sizzle` Don't rely on Jquery's CSS selector engine. --- eslint.config.mjs | 2 +- web_src/js/features/comp/ReactionSelector.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 035c70a338..1b130cd2e6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -376,7 +376,7 @@ export default tseslint.config( 'no-jquery/no-selector-prop': [2], 'no-jquery/no-serialize': [2], 'no-jquery/no-size': [2], - 'no-jquery/no-sizzle': [0], + 'no-jquery/no-sizzle': [2], 'no-jquery/no-slide': [2], 'no-jquery/no-sub': [2], 'no-jquery/no-support': [2], diff --git a/web_src/js/features/comp/ReactionSelector.js b/web_src/js/features/comp/ReactionSelector.js index fd4601fb91..357a33f469 100644 --- a/web_src/js/features/comp/ReactionSelector.js +++ b/web_src/js/features/comp/ReactionSelector.js @@ -23,7 +23,7 @@ export function initCompReactionSelector($parent) { $react.remove(); } if (!data.empty) { - const $attachments = $content.find('.segment.bottom:first'); + const $attachments = $content.find('.segment.bottom').first(); $react = $(data.html); if ($attachments.length > 0) { $react.insertBefore($attachments); From c124014115c477e4577d154555b72bb1f12d2640 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 1 Apr 2025 03:59:12 +0200 Subject: [PATCH 03/23] chore: enable `no-jquery/no-each-util` - No longer allow `$.each`. This can easily be done with vanilla javascript. --- eslint.config.mjs | 2 +- web_src/js/features/org-team.js | 5 ++--- web_src/js/features/repo-issue.js | 8 ++++---- web_src/js/features/repo-settings.js | 5 ++--- web_src/js/features/repo-template.js | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 1b130cd2e6..c7d01c79da 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -320,7 +320,7 @@ export default tseslint.config( 'no-jquery/no-delegate': [2], 'no-jquery/no-done-fail': [2], 'no-jquery/no-each-collection': [0], - 'no-jquery/no-each-util': [0], + 'no-jquery/no-each-util': [2], 'no-jquery/no-each': [0], 'no-jquery/no-error-shorthand': [2], 'no-jquery/no-error': [2], diff --git a/web_src/js/features/org-team.js b/web_src/js/features/org-team.js index 9b059b3a46..fe816da865 100644 --- a/web_src/js/features/org-team.js +++ b/web_src/js/features/org-team.js @@ -10,13 +10,12 @@ export function initOrgTeamSearchRepoBox() { url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`, onResponse(response) { const items = []; - $.each(response.data, (_i, item) => { + for (const item of response.data) { items.push({ title: item.repository.full_name.split('/')[1], description: item.repository.full_name, }); - }); - + } return {results: items}; }, }, diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index c28bf08442..77ceb0401c 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -139,7 +139,7 @@ export function initRepoIssueSidebarList() { const filteredResponse = {success: true, results: []}; const currIssueId = $('#new-dependency-drop-list').data('issue-id'); // Parse the response from the api to work with our dropdown - $.each(response, (_i, issue) => { + for (const [_, issue] of Object.entries(response)) { // Don't list current issue in the dependency list. if (issue.id === currIssueId) { return; @@ -149,7 +149,7 @@ export function initRepoIssueSidebarList() { }
${htmlEscape(issue.repository.full_name)}
`, value: issue.id, }); - }); + } return filteredResponse; }, cache: false, @@ -345,12 +345,12 @@ export function initRepoIssueReferenceRepositorySearch() { url: `${appSubUrl}/repo/search?q={query}&limit=20`, onResponse(response) { const filteredResponse = {success: true, results: []}; - $.each(response.data, (_r, repo) => { + for (const repo of response.data) { filteredResponse.results.push({ name: htmlEscape(repo.repository.full_name), value: repo.repository.full_name, }); - }); + } return filteredResponse; }, cache: false, diff --git a/web_src/js/features/repo-settings.js b/web_src/js/features/repo-settings.js index 52c5de2bfa..47ada545de 100644 --- a/web_src/js/features/repo-settings.js +++ b/web_src/js/features/repo-settings.js @@ -55,13 +55,12 @@ export function initRepoSettingSearchTeamBox() { headers: {'X-Csrf-Token': csrfToken}, onResponse(response) { const items = []; - $.each(response.data, (_i, item) => { + for (const item of response.data) { items.push({ title: item.name, description: `${item.permission} access`, // TODO: translate this string }); - }); - + } return {results: items}; }, }, diff --git a/web_src/js/features/repo-template.js b/web_src/js/features/repo-template.js index 5f63e8b3ba..5f01be5ad3 100644 --- a/web_src/js/features/repo-template.js +++ b/web_src/js/features/repo-template.js @@ -32,12 +32,12 @@ export function initRepoTemplateSearch() { value: '', }); // Parse the response from the api to work with our dropdown - $.each(response.data, (_r, repo) => { + for (const repo of response.data) { filteredResponse.results.push({ name: htmlEscape(repo.repository.full_name), value: repo.repository.id, }); - }); + } return filteredResponse; }, cache: false, From 9420d3d0a5caad8968aaaa588a0c2ec84fc4340a Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 1 Apr 2025 04:41:57 +0200 Subject: [PATCH 04/23] chore: enable `no-jquery/no-trigger` - A lot of substitution to `.requestSubmit()`. - Where possible, rewrite some other jquery to vanilla javascript to ease the linter fix. --- eslint.config.mjs | 2 +- .../js/components/RepoBranchTagSelector.vue | 4 ++-- web_src/js/features/admin/common.js | 10 ++++++---- web_src/js/features/common-global.js | 4 ++-- web_src/js/features/comp/LabelEdit.js | 4 ++-- web_src/js/features/repo-code.js | 3 ++- web_src/js/features/repo-common.js | 16 +++++++-------- web_src/js/features/repo-editor.js | 2 +- web_src/js/features/repo-issue.js | 20 +++++++++---------- 9 files changed, 34 insertions(+), 31 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index c7d01c79da..cb11195d4d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -381,7 +381,7 @@ export default tseslint.config( 'no-jquery/no-sub': [2], 'no-jquery/no-support': [2], 'no-jquery/no-text': [0], - 'no-jquery/no-trigger': [0], + 'no-jquery/no-trigger': [2], 'no-jquery/no-trim': [2], 'no-jquery/no-type': [2], 'no-jquery/no-unique': [2], diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue index cd86499ddc..12ff564aa7 100644 --- a/web_src/js/components/RepoBranchTagSelector.vue +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -98,14 +98,14 @@ const sfc = { $(`#${this.branchForm} input[name="refType"]`).val('branch'); } if (this.submitForm) { - $(`#${this.branchForm}`).trigger('submit'); + document.getElementById(this.branchForm).requestSubmit(); } this.menuVisible = false; } }, createNewBranch() { if (!this.showCreateNewBranch) return; - $(this.$refs.newBranchForm).trigger('submit'); + this.$refs.newBranchForm.requestSubmit(); }, focusSearchField() { nextTick(() => { diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index 9934c3dd17..b46f4f8a74 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -175,7 +175,7 @@ export function initAdminCommon() { onUsePagedSearchChange(); } }); - $('#auth_type').trigger('change'); + document.getElementById('auth_type').dispatchEvent(new Event('change')); document.getElementById('security_protocol')?.addEventListener('change', onSecurityProtocolChange); document.getElementById('use_paged_search')?.addEventListener('change', onUsePagedSearchChange); document.getElementById('oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); @@ -200,10 +200,12 @@ export function initAdminCommon() { } if (document.querySelector('.admin.authentication')) { - $('#auth_name').on('input', function () { + const authNameEl = document.getElementById('auth_name'); + authNameEl.addEventListener('input', (el) => { // appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash. - document.getElementById('oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(this.value)}/callback`; - }).trigger('input'); + document.getElementById('oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(el.target.value)}/callback`; + }); + authNameEl.dispatchEvent(new Event('input')); } // Notice diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 7848a14b66..7d553f9692 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -57,7 +57,7 @@ export function initGlobalEnterQuickSubmit() { export function initGlobalButtonClickOnEnter() { $(document).on('keypress', 'div.ui.button,span.ui.button', (e) => { if (e.code === ' ' || e.code === 'Enter') { - $(e.target).trigger('click'); + e.target.click(); e.preventDefault(); } }); @@ -314,7 +314,7 @@ export function initGlobalLinkActions() { closable: false, onApprove: async () => { if ($this.data('type') === 'form') { - $($this.data('form')).trigger('submit'); + document.querySelector($this.data('form')).requestSubmit(); return; } if ($this[0].getAttribute('hx-confirm')) { diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js index 2cc75cc6b0..a97c7fe34c 100644 --- a/web_src/js/features/comp/LabelEdit.js +++ b/web_src/js/features/comp/LabelEdit.js @@ -38,7 +38,7 @@ export function initCompLabelEdit(selector) { form.reportValidity(); return false; } - $('.new-label.form').trigger('submit'); + document.querySelector('.new-label.form').requestSubmit(); }, }).modal('show'); return false; @@ -75,7 +75,7 @@ export function initCompLabelEdit(selector) { form.reportValidity(); return false; } - $('.edit-label.form').trigger('submit'); + document.querySelector('.edit-label.form').requestSubmit(); }, }).modal('show'); return false; diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index 794cc38010..2ff9e611ae 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -184,7 +184,8 @@ export function initRepoCodeView() { $('html, body').scrollTop($first.offset().top - 200); } } - }).trigger('hashchange'); + }); + window.dispatchEvent(new Event('hashchange')); } $(document).on('click', '.fold-file', ({currentTarget}) => { invertFileFolding(currentTarget.closest('.file-content'), currentTarget); diff --git a/web_src/js/features/repo-common.js b/web_src/js/features/repo-common.js index 88aa93d850..02a0499f95 100644 --- a/web_src/js/features/repo-common.js +++ b/web_src/js/features/repo-common.js @@ -33,25 +33,25 @@ export function initRepoArchiveLinks() { } export function initRepoCloneLink() { - const $repoCloneSsh = $('#repo-clone-ssh'); - const $repoCloneHttps = $('#repo-clone-https'); - const $inputLink = $('#repo-clone-url'); + const repoCloneSSH = document.getElementById('repo-clone-ssh'); + const repoCloneHTTPS = document.getElementById('repo-clone-https'); + const inputLink = document.getElementById('repo-clone-url'); - if ((!$repoCloneSsh.length && !$repoCloneHttps.length) || !$inputLink.length) { + if ((!repoCloneSSH && !repoCloneHTTPS) || !inputLink) { return; } - $repoCloneSsh.on('click', () => { + repoCloneSSH.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'ssh'); window.updateCloneStates(); }); - $repoCloneHttps.on('click', () => { + repoCloneHTTPS.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'https'); window.updateCloneStates(); }); - $inputLink.on('focus', () => { - $inputLink.trigger('select'); + inputLink.addEventListener('focus', () => { + inputLink.select(); }); } diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js index 5b59b66f80..d3f5f2f8a9 100644 --- a/web_src/js/features/repo-editor.js +++ b/web_src/js/features/repo-editor.js @@ -185,7 +185,7 @@ export function initRepoEditor() { $('#edit-empty-content-modal') .modal({ onApprove() { - $('.edit.form').trigger('submit'); + document.querySelector('.edit.form').requestSubmit(); }, }) .modal('show'); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 77ceb0401c..889687da3e 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -37,27 +37,27 @@ export function initRepoIssueTimeTracking() { $('.issue-start-time-modal').modal({ duration: 200, onApprove() { - $('#add_time_manual_form').trigger('submit'); + document.getElementById('add_time_manual_form').requestSubmit(); }, }).modal('show'); $('.issue-start-time-modal input').on('keydown', (e) => { if ((e.keyCode || e.key) === 13) { - $('#add_time_manual_form').trigger('submit'); + document.getElementById('add_time_manual_form').requestSubmit(); } }); }); $(document).on('click', '.issue-start-time, .issue-stop-time', () => { - $('#toggle_stopwatch_form').trigger('submit'); + document.getElementById('toggle_stopwatch_form').requestSubmit(); }); $(document).on('click', '.issue-cancel-time', () => { - $('#cancel_stopwatch_form').trigger('submit'); + document.getElementById('cancel_stopwatch_form').requestSubmit(); }); $(document).on('click', 'button.issue-delete-time', function () { const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`; $(sel).modal({ duration: 200, onApprove() { - $(`${sel} form`).trigger('submit'); + document.getElementById(`${sel} form`).requestSubmit(); }, }).modal('show'); }); @@ -247,7 +247,7 @@ export function initRepoIssueDependencyDelete() { onApprove: () => { $('#removeDependencyID').val(id); $('#dependencyType').val(type); - $('#removeDependencyForm').trigger('submit'); + document.getElementById('removeDependencyForm').requestSubmit(); }, }).modal('show'); }); @@ -369,9 +369,9 @@ export function initRepoIssueWipTitle() { $('.title_wip_desc > a').on('click', (e) => { e.preventDefault(); - const $issueTitle = $('#issue_title'); - $issueTitle.trigger('focus'); - const value = $issueTitle.val().trim().toUpperCase(); + const issueTitleEl = document.getElementById('issue_title'); + issueTitleEl.focus(); + const value = issueTitleEl.value.trim().toUpperCase(); const wipPrefixes = $('.title_wip_desc').data('wip-prefixes'); for (const prefix of wipPrefixes) { @@ -380,7 +380,7 @@ export function initRepoIssueWipTitle() { } } - $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`); + issueTitleEl.value = `${wipPrefixes[0]} ${issueTitleEl.value}`; }); } From d9892e57bd65e0955a80344993ff98d471d15f18 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 1 Apr 2025 04:44:44 +0200 Subject: [PATCH 05/23] chore: enable `no-jquery/no-map-collection` No linter errors reported. --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index cb11195d4d..f635a8b504 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -352,7 +352,7 @@ export default tseslint.config( 'no-jquery/no-live': [2], 'no-jquery/no-load-shorthand': [2], 'no-jquery/no-load': [2], - 'no-jquery/no-map-collection': [0], + 'no-jquery/no-map-collection': [2], 'no-jquery/no-map-util': [2], 'no-jquery/no-map': [2], 'no-jquery/no-merge': [2], From da62c2cac646b18142c205b4ba68b917fd0a9880 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Tue, 1 Apr 2025 15:55:56 +0000 Subject: [PATCH 06/23] chore(ui): remove ineffective class small in inline code preview (#7414) Followup to https://codeberg.org/forgejo/forgejo/pulls/2669 Ref https://codeberg.org/forgejo/forgejo/issues/5067 Since Forgejo v9 this class is overridden here by other CSS and is ineffective. But it looks ok and gives good visibility. If it needs to be re-implemed, it can be done via CSS, like: ```css .file-preview-box .header span {font-size:0.8rem;} ``` Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7414 Reviewed-by: Gusted Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-committed-by: 0ko <0ko@noreply.codeberg.org> --- modules/markup/file_preview.go | 2 +- modules/markup/html_test.go | 28 ++++++++++++++-------------- modules/markup/sanitizer.go | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go index 5499eff18c..8b1442ed08 100644 --- a/modules/markup/file_preview.go +++ b/modules/markup/file_preview.go @@ -341,7 +341,7 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node { psubtitle := &html.Node{ Type: html.ElementNode, Data: atom.Span.String(), - Attr: []html.Attribute{{Key: "class", Val: "text small grey"}}, + Attr: []html.Attribute{{Key: "class", Val: "text grey"}}, } psubtitle.AppendChild(&html.Node{ Type: html.RawNode, diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index d503796eb6..9d0c40c9e8 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -721,7 +721,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -755,7 +755,7 @@ func TestRender_FilePreview(t *testing.T) { `gogits/gogs – `+ `path/to/file.go`+ ``+ - ``+ + ``+ `Lines 2 to 3 in gogits/gogs@190d949`+ ``+ ``+ @@ -791,7 +791,7 @@ func TestRender_FilePreview(t *testing.T) { `gogits/gogs – `+ `single-line.txt`+ ``+ - ``+ + ``+ `Line 1 in gogits/gogs@4c1aaf5`+ ``+ ``+ @@ -834,7 +834,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -865,7 +865,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -898,7 +898,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -923,7 +923,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -954,7 +954,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -979,7 +979,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -1004,7 +1004,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -1039,7 +1039,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 1 to 2 in c991312`+ ``+ ``+ @@ -1072,7 +1072,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 1 to 2 in c991312`+ ``+ ``+ @@ -1107,7 +1107,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -1142,7 +1142,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Line 1 in eeb243c`+ ``+ ``+ diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index 7ff11f0844..384dd1fe94 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -122,7 +122,7 @@ func createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui table$")).OnElements("div") policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div") policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span") - policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span") + policy.AllowAttrs("class").Matching(regexp.MustCompile("^text grey$")).OnElements("span") policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview$")).OnElements("table") policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td") policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button") From 6ad625b0c173fef12d28890d1403f9b5030c5d5b Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Tue, 1 Apr 2025 16:14:38 +0000 Subject: [PATCH 07/23] feat(ui): make JS asset load error message translatable (#7388) - The 'Failed to load asset files from [...]' is now an translatable string. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7388 Reviewed-by: Gusted Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-committed-by: 0ko <0ko@noreply.codeberg.org> --- options/locale_next/locale_en-US.json | 3 +- templates/base/footer.tmpl | 2 +- tests/integration/common_navigation_test.go | 41 +++++++++++++++++++++ tests/integration/user_dashboard_test.go | 17 +-------- 4 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 tests/integration/common_navigation_test.go diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index e525598ed5..def49e1a58 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -17,5 +17,6 @@ "themes.names.forgejo-auto": "Forgejo (follow system theme)", "themes.names.forgejo-light": "Forgejo light", "themes.names.forgejo-dark": "Forgejo dark", - "error.not_found.title": "Page not found" + "error.not_found.title": "Page not found", + "alert.asset_load_failed": "Failed to load asset files from {path}. Please make sure the asset files can be accessed." } \ No newline at end of file diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index fed426a469..3c2def228a 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -13,7 +13,7 @@ {{template "base/footer_content" .}} - + {{template "custom/footer" .}} diff --git a/tests/integration/common_navigation_test.go b/tests/integration/common_navigation_test.go new file mode 100644 index 0000000000..93a2c15ece --- /dev/null +++ b/tests/integration/common_navigation_test.go @@ -0,0 +1,41 @@ +// Copyright 2024-2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package integration + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "forgejo.org/models/unittest" + "forgejo.org/modules/translation" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// This test verifies common elements that are visible on all pages but most +// likely to be first seen on `/` +func TestCommonNavigationElements(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + session := loginUser(t, "user1") + locale := translation.NewLocale("en-US") + + response := session.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK) + page := NewHTMLParser(t, response.Body) + + // Navbar + links := page.Find("#navbar .dropdown[data-tooltip-content='Create…'] .menu") + assert.Equal(t, locale.TrString("new_repo.link"), strings.TrimSpace(links.Find("a[href='/repo/create']").Text())) + assert.Equal(t, locale.TrString("new_migrate.link"), strings.TrimSpace(links.Find("a[href='/repo/migrate']").Text())) + assert.Equal(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text())) + + // After footer: index.js + page.AssertElement(t, "script[src^='/assets/js/index.js']", true) + onerror, _ := page.Find("script[src^='/assets/js/index.js']").Attr("onerror") + expected := fmt.Sprintf("alert('%s'.replace('{path}', this.src))", locale.TrString("alert.asset_load_failed")) + assert.Equal(t, expected, onerror) +} diff --git a/tests/integration/user_dashboard_test.go b/tests/integration/user_dashboard_test.go index 9e1e04c29f..b18f96c090 100644 --- a/tests/integration/user_dashboard_test.go +++ b/tests/integration/user_dashboard_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 The Forgejo Authors. All rights reserved. +// Copyright 2024-2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later package integration @@ -14,7 +14,6 @@ import ( unit_model "forgejo.org/models/unit" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" - "forgejo.org/modules/translation" issue_service "forgejo.org/services/issue" files_service "forgejo.org/services/repository/files" "forgejo.org/tests" @@ -24,20 +23,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestUserDashboardActionLinks(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - session := loginUser(t, "user1") - locale := translation.NewLocale("en-US") - - response := session.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK) - page := NewHTMLParser(t, response.Body) - links := page.Find("#navbar .dropdown[data-tooltip-content='Create…'] .menu") - assert.Equal(t, locale.TrString("new_repo.link"), strings.TrimSpace(links.Find("a[href='/repo/create']").Text())) - assert.Equal(t, locale.TrString("new_migrate.link"), strings.TrimSpace(links.Find("a[href='/repo/migrate']").Text())) - assert.Equal(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text())) -} - func TestUserDashboardFeedWelcome(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) From 61885a0eaaeb36d9506c18b0328a73377c06a457 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 1 Apr 2025 18:34:02 +0000 Subject: [PATCH 08/23] Update dependency go to v1.24.2 (forgejo) (#7417) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7417 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a36ab38aa1..33ca017729 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module forgejo.org go 1.24 -toolchain go1.24.1 +toolchain go1.24.2 require ( code.forgejo.org/f3/gof3/v3 v3.10.6 From 18c38ed1f37ece7c5aba755fad616ac1a4a702c5 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 2 Apr 2025 00:44:39 +0000 Subject: [PATCH 09/23] Update module github.com/mattn/go-sqlite3 to v1.14.25 (forgejo) (#7424) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7424 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 33ca017729..f92cd9f3ad 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/lib/pq v1.10.9 github.com/markbates/goth v1.80.0 github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-sqlite3 v1.14.24 + github.com/mattn/go-sqlite3 v1.14.25 github.com/meilisearch/meilisearch-go v0.31.0 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.27 diff --git a/go.sum b/go.sum index 09cda25c31..e88f5c3112 100644 --- a/go.sum +++ b/go.sum @@ -1252,8 +1252,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.25 h1:rszkIulEvxqZ8JfFG4yWEZh5u9qAKeSOdea67p8kk6s= +github.com/mattn/go-sqlite3 v1.14.25/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY= github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g= github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ= From d656978818c8a5b82dd822b631854b168288cc8d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 2 Apr 2025 10:43:36 +0000 Subject: [PATCH 10/23] Update module github.com/go-webauthn/webauthn to v0.12.3 (forgejo) (#7423) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7423 Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f92cd9f3ad..0f551f4c3f 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/go-openapi/spec v0.20.14 github.com/go-sql-driver/mysql v1.9.1 github.com/go-testfixtures/testfixtures/v3 v3.14.0 - github.com/go-webauthn/webauthn v0.12.2 + github.com/go-webauthn/webauthn v0.12.3 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 @@ -177,7 +177,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect @@ -190,7 +190,7 @@ require ( github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.7 // indirect - github.com/go-webauthn/x v0.1.19 // indirect + github.com/go-webauthn/x v0.1.20 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect diff --git a/go.sum b/go.sum index e88f5c3112..889b829e88 100644 --- a/go.sum +++ b/go.sum @@ -895,8 +895,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= +github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI= github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI= @@ -967,10 +967,10 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-testfixtures/testfixtures/v3 v3.14.0 h1:aRt5qyH2XjzFgCC5NizNs6QrzjO7rC4pQZ1oJpPIdo8= github.com/go-testfixtures/testfixtures/v3 v3.14.0/go.mod h1:HHb6Yd8spzm6aFZU6jwBj9qFvVUNNkx5nGbjG4UHeOE= -github.com/go-webauthn/webauthn v0.12.2 h1:yLaNPgBUEXDQtWnOjhsGhMMCEWbXwjg/aNkC8riJQI8= -github.com/go-webauthn/webauthn v0.12.2/go.mod h1:Q8SZPPj4sZ469fNTcQXxRpzJOdb30jQrn/36FX8jilA= -github.com/go-webauthn/x v0.1.19 h1:IUfdHiBRoTdujpBA/14qbrMXQ3LGzYe/PRGWdZcmudg= -github.com/go-webauthn/x v0.1.19/go.mod h1:C5arLuTQ3pVHKPw89v7CDGnqAZSZJj+4Jnr40dsn7tk= +github.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE= +github.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY= +github.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw= +github.com/go-webauthn/x v0.1.20/go.mod h1:n/gAc8ssZJGATM0qThE+W+vfgXiMedsWi3wf/C4lld0= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= From f691f03741a46ccf180ab8f9d38c3e93c2d08037 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 2 Apr 2025 12:54:54 +0000 Subject: [PATCH 11/23] chore(ui): remove fomantic's dimmer module (#7416) - Fomantic's dimmer module is responsible for dimming the page and make some element the primary focus on the page (e.g. modal). This module is only used by Fomantic's modal module. - Remove it and replace the javascript with our own `Dimmer` class that is able to provide Fomantic's modal module with everything it needs. - Replace the CSS with our own bare minimum CSS. - No functionality or visual is affected by this replacement. - E2E test added. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7416 Reviewed-by: Michael Kriese Co-authored-by: Gusted Co-committed-by: Gusted --- tests/e2e/dimmer.test.e2e.ts | 39 ++ web_src/css/base.css | 4 - web_src/css/index.css | 1 + web_src/css/modules/dimmer.css | 20 + web_src/fomantic/build/semantic.css | 357 ------------- web_src/fomantic/build/semantic.js | 754 ---------------------------- web_src/fomantic/semantic.json | 1 - web_src/js/modules/dimmer.ts | 47 ++ web_src/js/modules/fomantic.js | 2 + 9 files changed, 109 insertions(+), 1116 deletions(-) create mode 100644 tests/e2e/dimmer.test.e2e.ts create mode 100644 web_src/css/modules/dimmer.css create mode 100644 web_src/js/modules/dimmer.ts diff --git a/tests/e2e/dimmer.test.e2e.ts b/tests/e2e/dimmer.test.e2e.ts new file mode 100644 index 0000000000..04c6433a5a --- /dev/null +++ b/tests/e2e/dimmer.test.e2e.ts @@ -0,0 +1,39 @@ +// @watch start +// templates/shared/user/** +// web_src/css/modules/dimmer.ts +// web_src/css/modules/dimmer.css +// @watch end + +import {expect} from '@playwright/test'; +import {save_visual, test} from './utils_e2e.ts'; + +test.use({user: 'user2'}); + +test('Dimmed modal', async ({page}) => { + await page.goto('/user1'); + + await expect(page.locator('.block')).toContainText('Block'); + + // Ensure the modal is hidden + await expect(page.locator('#block-user')).toBeHidden(); + + await page.locator('.block').click(); + + // Modal and dimmer should be visible. + await expect(page.locator('#block-user')).toBeVisible(); + await expect(page.locator('.ui.dimmer')).toBeVisible(); + await save_visual(page); + + // After canceling, modal and dimmer should be hidden. + await page.locator('#block-user .cancel').click(); + await expect(page.locator('.ui.dimmer')).toBeHidden(); + await expect(page.locator('#block-user')).toBeHidden(); + await save_visual(page); + + // Open the block modal and make the dimmer visible again. + await page.locator('.block').click(); + await expect(page.locator('#block-user')).toBeVisible(); + await expect(page.locator('.ui.dimmer')).toBeVisible(); + await expect(page.locator('.ui.dimmer')).toHaveCount(1); + await save_visual(page); +}); diff --git a/web_src/css/base.css b/web_src/css/base.css index e3d2345e21..bfe8cd54ae 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -757,10 +757,6 @@ img.ui.avatar, box-shadow: 0 6px 18px var(--color-shadow) !important; } -.ui.dimmer { - background: var(--color-overlay-backdrop); -} - /* Override semantic selector '.ui.menu:not(.vertical) .item > .button' */ /* This fixes the commit graph button on the commits page */ /* modal svg icons, copied from fomantic except width and height */ diff --git a/web_src/css/index.css b/web_src/css/index.css index 12830299cf..eec17eab13 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -16,6 +16,7 @@ @import "./modules/card.css"; @import "./modules/checkbox.css"; @import "./modules/modal.css"; +@import "./modules/dimmer.css"; @import "./modules/switch.css"; @import "./modules/select.css"; diff --git a/web_src/css/modules/dimmer.css b/web_src/css/modules/dimmer.css new file mode 100644 index 0000000000..1d0bf83390 --- /dev/null +++ b/web_src/css/modules/dimmer.css @@ -0,0 +1,20 @@ +.ui.active.dimmer { + display: flex; + opacity: 1; +} + +.ui.dimmer { + align-items: center; + animation-fill-mode: both; + background: var(--color-overlay-backdrop); + display: none; + flex-direction: column; + height: 100%; + position: absolute; + opacity: 0; + justify-content: center; + user-select: none; + width: 100%; + will-change: opacity; + z-index: 1000; +} diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index 49c00c4dad..bd9a5f4594 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -2326,363 +2326,6 @@ /******************************* Site Overrides *******************************/ -/*! - * # Fomantic-UI - Dimmer - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Dimmer -*******************************/ - -.dimmable:not(body) { - position: relative; -} - -.ui.dimmer { - display: none; - position: absolute; - top: 0 !important; - left: 0 !important; - width: 100%; - height: 100%; - text-align: center; - vertical-align: middle; - padding: 1em; - background: rgba(0, 0, 0, 0.85); - opacity: 0; - line-height: 1; - animation-fill-mode: both; - animation-duration: 0.5s; - transition: background-color 0.5s linear; - flex-direction: column; - align-items: center; - justify-content: center; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - will-change: opacity; - z-index: 1000; -} - -/* Dimmer Content */ - -.ui.dimmer > .content { - -webkit-user-select: text; - -moz-user-select: text; - user-select: text; - color: #FFFFFF; -} - -/* Loose Coupling */ - -.ui.segment > .ui.dimmer:not(.page) { - border-radius: inherit; -} - -/* Scrollbars */ - -/******************************* - States -*******************************/ - -/* Animating */ - -.animating.dimmable:not(body), -.dimmed.dimmable:not(body) { - overflow: hidden; -} - -/* Animating / Active / Visible */ - -.dimmed.dimmable > .ui.animating.dimmer, -.dimmed.dimmable > .ui.visible.dimmer, -.ui.active.dimmer { - display: flex; - opacity: 1; -} - -/* Disabled */ - -.ui.disabled.dimmer { - width: 0 !important; - height: 0 !important; -} - -/******************************* - Variations -*******************************/ - -/*-------------- - Legacy - ---------------*/ - -/* Animating / Active / Visible */ - -.dimmed.dimmable > .ui.animating.legacy.dimmer, -.dimmed.dimmable > .ui.visible.legacy.dimmer, -.ui.active.legacy.dimmer { - display: block; -} - -/*-------------- - Alignment - ---------------*/ - -.ui[class*="top aligned"].dimmer { - justify-content: flex-start; -} - -.ui[class*="bottom aligned"].dimmer { - justify-content: flex-end; -} - -/*-------------- - Page - ---------------*/ - -.ui.page.dimmer { - position: fixed; - transform-style: ''; - perspective: 2000px; - transform-origin: center center; -} - -.ui.page.dimmer.modals { - -moz-perspective: none; -} - -body.animating.in.dimmable, -body.dimmed.dimmable { - overflow: hidden; -} - -body.dimmable > .dimmer { - position: fixed; -} - -/*-------------- - Blurring - ---------------*/ - -.blurring.dimmable > :not(.dimmer) { - filter: initial; - transition: 800ms filter ease; -} - -.blurring.dimmed.dimmable > :not(.dimmer):not(.popup) { - filter: blur(5px) grayscale(0.7); -} - -/* Dimmer Color */ - -.blurring.dimmable > .dimmer { - background: rgba(0, 0, 0, 0.6); -} - -.blurring.dimmable > .inverted.dimmer { - background: rgba(255, 255, 255, 0.6); -} - -/*-------------- - Aligned - ---------------*/ - -.ui.dimmer > .top.aligned.content > * { - vertical-align: top; -} - -.ui.dimmer > .bottom.aligned.content > * { - vertical-align: bottom; -} - -/*-------------- - Shades - ---------------*/ - -.medium.medium.medium.medium.medium.dimmer { - background: rgba(0, 0, 0, 0.65); -} - -.light.light.light.light.light.dimmer { - background: rgba(0, 0, 0, 0.45); -} - -.very.light.light.light.light.dimmer { - background: rgba(0, 0, 0, 0.25); -} - -/*-------------- - Simple - ---------------*/ - -/* Displays without javascript */ - -.ui.simple.dimmer { - display: block; - overflow: hidden; - opacity: 0; - width: 0; - height: 0; - z-index: -100; - background: rgba(0, 0, 0, 0); -} - -.dimmed.dimmable > .ui.simple.dimmer { - overflow: visible; - opacity: 1; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.85); - z-index: 1; -} - -.ui.simple.inverted.dimmer { - background: rgba(255, 255, 255, 0); -} - -.dimmed.dimmable > .ui.simple.inverted.dimmer { - background: rgba(255, 255, 255, 0.85); -} - -/*-------------- - Partially - ----------------*/ - -.ui[class*="top dimmer"], -.ui[class*="center dimmer"], -.ui[class*="bottom dimmer"] { - height: auto; -} - -.ui[class*="bottom dimmer"] { - top: auto !important; - bottom: 0; -} - -.ui[class*="center dimmer"] { - top: 50% !important; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); -} - -.ui.segment > .ui.ui[class*="top dimmer"] { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.ui.segment > .ui.ui[class*="center dimmer"] { - border-radius: 0; -} - -.ui.segment > .ui.ui[class*="bottom dimmer"] { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.ui[class*="center dimmer"].transition[class*="fade up"].in { - animation-name: fadeInUpCenter; -} - -.ui[class*="center dimmer"].transition[class*="fade down"].in { - animation-name: fadeInDownCenter; -} - -.ui[class*="center dimmer"].transition[class*="fade up"].out { - animation-name: fadeOutUpCenter; -} - -.ui[class*="center dimmer"].transition[class*="fade down"].out { - animation-name: fadeOutDownCenter; -} - -.ui[class*="center dimmer"].bounce.transition { - animation-name: bounceCenter; -} - -@keyframes fadeInUpCenter { - 0% { - opacity: 0; - transform: translateY(-40%); - -webkit-transform: translateY(calc(-40% - 0.5px)); - } - - 100% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } -} - -@keyframes fadeInDownCenter { - 0% { - opacity: 0; - transform: translateY(-60%); - -webkit-transform: translateY(calc(-60% - 0.5px)); - } - - 100% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } -} - -@keyframes fadeOutUpCenter { - 0% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } - - 100% { - opacity: 0; - transform: translateY(-45%); - -webkit-transform: translateY(calc(-45% - 0.5px)); - } -} - -@keyframes fadeOutDownCenter { - 0% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } - - 100% { - opacity: 0; - transform: translateY(-55%); - -webkit-transform: translateY(calc(-55% - 0.5px)); - } -} - -@keyframes bounceCenter { - 0%, 20%, 50%, 80%, 100% { - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } - - 40% { - transform: translateY(calc(-50% - 30px)); - } - - 60% { - transform: translateY(calc(-50% - 15px)); - } -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - User Overrides -*******************************/ /*! * # Fomantic-UI - Dropdown * http://github.com/fomantic/Fomantic-UI/ diff --git a/web_src/fomantic/build/semantic.js b/web_src/fomantic/build/semantic.js index fb68705c08..393d9d8142 100644 --- a/web_src/fomantic/build/semantic.js +++ b/web_src/fomantic/build/semantic.js @@ -1184,760 +1184,6 @@ $.api.settings = { -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Dimmer - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.dimmer = function(parameters) { - var - $allModules = $(this), - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - returnedValue - ; - - $allModules - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.dimmer.settings, parameters) - : $.extend({}, $.fn.dimmer.settings), - - selector = settings.selector, - namespace = settings.namespace, - className = settings.className, - error = settings.error, - - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - moduleSelector = $allModules.selector || '', - - clickEvent = "click", unstableClickEvent = ('ontouchstart' in document.documentElement) - ? 'touchstart' - : 'click', - - $module = $(this), - $dimmer, - $dimmable, - - element = this, - instance = $module.data(moduleNamespace), - module - ; - - module = { - - preinitialize: function() { - if( module.is.dimmer() ) { - - $dimmable = $module.parent(); - $dimmer = $module; - } - else { - $dimmable = $module; - if( module.has.dimmer() ) { - if(settings.dimmerName) { - $dimmer = $dimmable.find(selector.dimmer).filter('.' + settings.dimmerName); - } - else { - $dimmer = $dimmable.find(selector.dimmer); - } - } - else { - $dimmer = module.create(); - } - } - }, - - initialize: function() { - module.debug('Initializing dimmer', settings); - - module.bind.events(); - module.set.dimmable(); - module.instantiate(); - }, - - instantiate: function() { - module.verbose('Storing instance of module', module); - instance = module; - $module - .data(moduleNamespace, instance) - ; - }, - - destroy: function() { - module.verbose('Destroying previous module', $dimmer); - module.unbind.events(); - module.remove.variation(); - $dimmable - .off(eventNamespace) - ; - }, - - bind: { - events: function() { - if(settings.on == 'hover') { - $dimmable - .on('mouseenter' + eventNamespace, module.show) - .on('mouseleave' + eventNamespace, module.hide) - ; - } - else if(settings.on == 'click') { - $dimmable - .on(clickEvent + eventNamespace, module.toggle) - ; - } - if( module.is.page() ) { - module.debug('Setting as a page dimmer', $dimmable); - module.set.pageDimmer(); - } - - if( module.is.closable() ) { - module.verbose('Adding dimmer close event', $dimmer); - $dimmable - .on(clickEvent + eventNamespace, selector.dimmer, module.event.click) - ; - } - } - }, - - unbind: { - events: function() { - $module - .removeData(moduleNamespace) - ; - $dimmable - .off(eventNamespace) - ; - } - }, - - event: { - click: function(event) { - module.verbose('Determining if event occurred on dimmer', event); - if( $dimmer.find(event.target).length === 0 || $(event.target).is(selector.content) ) { - module.hide(); - event.stopImmediatePropagation(); - } - } - }, - - addContent: function(element) { - var - $content = $(element) - ; - module.debug('Add content to dimmer', $content); - if($content.parent()[0] !== $dimmer[0]) { - $content.detach().appendTo($dimmer); - } - }, - - create: function() { - var - $element = $( settings.template.dimmer(settings) ) - ; - if(settings.dimmerName) { - module.debug('Creating named dimmer', settings.dimmerName); - $element.addClass(settings.dimmerName); - } - $element - .appendTo($dimmable) - ; - return $element; - }, - - show: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.debug('Showing dimmer', $dimmer, settings); - module.set.variation(); - if( (!module.is.dimmed() || module.is.animating()) && module.is.enabled() ) { - module.animate.show(callback); - settings.onShow.call(element); - settings.onChange.call(element); - } - else { - module.debug('Dimmer is already shown or disabled'); - } - }, - - hide: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.dimmed() || module.is.animating() ) { - module.debug('Hiding dimmer', $dimmer); - module.animate.hide(callback); - settings.onHide.call(element); - settings.onChange.call(element); - } - else { - module.debug('Dimmer is not visible'); - } - }, - - toggle: function() { - module.verbose('Toggling dimmer visibility', $dimmer); - if( !module.is.dimmed() ) { - module.show(); - } - else { - if ( module.is.closable() ) { - module.hide(); - } - } - }, - - animate: { - show: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { - if(settings.useFlex) { - module.debug('Using flex dimmer'); - module.remove.legacy(); - } - else { - module.debug('Using legacy non-flex dimmer'); - module.set.legacy(); - } - if(settings.opacity !== 'auto') { - module.set.opacity(); - } - $dimmer - .transition({ - displayType : settings.useFlex - ? 'flex' - : 'block', - animation : settings.transition + ' in', - queue : false, - duration : module.get.duration(), - useFailSafe : true, - onStart : function() { - module.set.dimmed(); - }, - onComplete : function() { - module.set.active(); - callback(); - } - }) - ; - } - else { - module.verbose('Showing dimmer animation with javascript'); - module.set.dimmed(); - if(settings.opacity == 'auto') { - settings.opacity = 0.8; - } - $dimmer - .stop() - .css({ - opacity : 0, - width : '100%', - height : '100%' - }) - .fadeTo(module.get.duration(), settings.opacity, function() { - $dimmer.removeAttr('style'); - module.set.active(); - callback(); - }) - ; - } - }, - hide: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { - module.verbose('Hiding dimmer with css'); - $dimmer - .transition({ - displayType : settings.useFlex - ? 'flex' - : 'block', - animation : settings.transition + ' out', - queue : false, - duration : module.get.duration(), - useFailSafe : true, - onComplete : function() { - module.remove.dimmed(); - module.remove.variation(); - module.remove.active(); - callback(); - } - }) - ; - } - else { - module.verbose('Hiding dimmer with javascript'); - $dimmer - .stop() - .fadeOut(module.get.duration(), function() { - module.remove.dimmed(); - module.remove.active(); - $dimmer.removeAttr('style'); - callback(); - }) - ; - } - } - }, - - get: { - dimmer: function() { - return $dimmer; - }, - duration: function() { - if(typeof settings.duration == 'object') { - if( module.is.active() ) { - return settings.duration.hide; - } - else { - return settings.duration.show; - } - } - return settings.duration; - } - }, - - has: { - dimmer: function() { - if(settings.dimmerName) { - return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0); - } - else { - return ( $module.find(selector.dimmer).length > 0 ); - } - } - }, - - is: { - active: function() { - return $dimmer.hasClass(className.active); - }, - animating: function() { - return ( $dimmer.is(':animated') || $dimmer.hasClass(className.animating) ); - }, - closable: function() { - if(settings.closable == 'auto') { - if(settings.on == 'hover') { - return false; - } - return true; - } - return settings.closable; - }, - dimmer: function() { - return $module.hasClass(className.dimmer); - }, - dimmable: function() { - return $module.hasClass(className.dimmable); - }, - dimmed: function() { - return $dimmable.hasClass(className.dimmed); - }, - disabled: function() { - return $dimmable.hasClass(className.disabled); - }, - enabled: function() { - return !module.is.disabled(); - }, - page: function () { - return $dimmable.is('body'); - }, - pageDimmer: function() { - return $dimmer.hasClass(className.pageDimmer); - } - }, - - can: { - show: function() { - return !$dimmer.hasClass(className.disabled); - } - }, - - set: { - opacity: function(opacity) { - var - color = $dimmer.css('background-color'), - colorArray = color.split(','), - isRGB = (colorArray && colorArray.length >= 3) - ; - opacity = settings.opacity === 0 ? 0 : settings.opacity || opacity; - if(isRGB) { - colorArray[2] = colorArray[2].replace(')',''); - colorArray[3] = opacity + ')'; - color = colorArray.join(','); - } - else { - color = 'rgba(0, 0, 0, ' + opacity + ')'; - } - module.debug('Setting opacity to', opacity); - $dimmer.css('background-color', color); - }, - legacy: function() { - $dimmer.addClass(className.legacy); - }, - active: function() { - $dimmer.addClass(className.active); - }, - dimmable: function() { - $dimmable.addClass(className.dimmable); - }, - dimmed: function() { - $dimmable.addClass(className.dimmed); - }, - pageDimmer: function() { - $dimmer.addClass(className.pageDimmer); - }, - disabled: function() { - $dimmer.addClass(className.disabled); - }, - variation: function(variation) { - variation = variation || settings.variation; - if(variation) { - $dimmer.addClass(variation); - } - } - }, - - remove: { - active: function() { - $dimmer - .removeClass(className.active) - ; - }, - legacy: function() { - $dimmer.removeClass(className.legacy); - }, - dimmed: function() { - $dimmable.removeClass(className.dimmed); - }, - disabled: function() { - $dimmer.removeClass(className.disabled); - }, - variation: function(variation) { - variation = variation || settings.variation; - if(variation) { - $dimmer.removeClass(variation); - } - } - }, - - setting: function(name, value) { - module.debug('Changing setting', name, value); - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - if($.isPlainObject(settings[name])) { - $.extend(true, settings[name], value); - } - else { - settings[name] = value; - } - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if($allModules.length > 1) { - title += ' ' + '(' + $allModules.length + ')'; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - module.error(error.method, query); - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - module.preinitialize(); - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.fn.dimmer.settings = { - - name : 'Dimmer', - namespace : 'dimmer', - - silent : false, - debug : false, - verbose : false, - performance : true, - - // whether should use flex layout - useFlex : true, - - // name to distinguish between multiple dimmers in context - dimmerName : false, - - // whether to add a variation type - variation : false, - - // whether to bind close events - closable : 'auto', - - // whether to use css animations - useCSS : true, - - // css animation to use - transition : 'fade', - - // event to bind to - on : false, - - // overriding opacity value - opacity : 'auto', - - // transition durations - duration : { - show : 500, - hide : 500 - }, -// whether the dynamically created dimmer should have a loader - displayLoader: false, - loaderText : false, - loaderVariation : '', - - onChange : function(){}, - onShow : function(){}, - onHide : function(){}, - - error : { - method : 'The method you called is not defined.' - }, - - className : { - active : 'active', - animating : 'animating', - dimmable : 'dimmable', - dimmed : 'dimmed', - dimmer : 'dimmer', - disabled : 'disabled', - hide : 'hide', - legacy : 'legacy', - pageDimmer : 'page', - show : 'show', - loader : 'ui loader' - }, - - selector: { - dimmer : '> .ui.dimmer', - content : '.ui.dimmer > .content, .ui.dimmer > .content > .center' - }, - - template: { - dimmer: function(settings) { - var d = $('
').addClass('ui dimmer'),l; - if(settings.displayLoader) { - l = $('
') - .addClass(settings.className.loader) - .addClass(settings.loaderVariation); - if(!!settings.loaderText){ - l.text(settings.loaderText); - l.addClass('text'); - } - d.append(l); - } - return d; - } - } - -}; - })( jQuery, window, document ); /*! diff --git a/web_src/fomantic/semantic.json b/web_src/fomantic/semantic.json index 5db57bc8d4..69474b7539 100644 --- a/web_src/fomantic/semantic.json +++ b/web_src/fomantic/semantic.json @@ -23,7 +23,6 @@ "components": [ "api", "button", - "dimmer", "dropdown", "form", "menu", diff --git a/web_src/js/modules/dimmer.ts b/web_src/js/modules/dimmer.ts new file mode 100644 index 0000000000..7f1cbaa7ad --- /dev/null +++ b/web_src/js/modules/dimmer.ts @@ -0,0 +1,47 @@ +import $ from 'jquery'; + +class Dimmer { + dimmerEl: HTMLDivElement; + active: boolean; + + constructor() { + this.dimmerEl = document.querySelector('body > div.ui.dimmer') as HTMLDivElement; + if (!this.dimmerEl) { + this.dimmerEl = document.createElement('div'); + this.dimmerEl.classList.add('ui', 'dimmer', 'transition'); + document.body.append(this.dimmerEl); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dimmer(functionName: string, ...args: any[]) { + if (functionName === 'add content') { + this.dimmerEl.append(args[0][0]); + } else if (functionName === 'get dimmer') { + return $(this.dimmerEl); + } else if (functionName === 'show') { + this.dimmerEl.classList.add('active'); + this.dimmerEl.classList.remove('hidden'); + this.active = true; + } else if (functionName === 'hide') { + this.dimmerEl.classList.remove('active'); + this.dimmerEl.classList.add('hidden'); + this.active = false; + } else if (functionName === 'is active') { + return this.active; + } + } + + // JQuery compatibility functions. + get(_index: number): HTMLElement { + return document.body; + } + removeClass() {} + hasClass() {} +} + +export function initDimmer() { + $.fn.dimmer = (arg: string | object) => { + if (typeof arg === 'object') return new Dimmer(); + }; +} diff --git a/web_src/js/modules/fomantic.js b/web_src/js/modules/fomantic.js index c04bc6e863..c736026c6c 100644 --- a/web_src/js/modules/fomantic.js +++ b/web_src/js/modules/fomantic.js @@ -6,6 +6,7 @@ import {initAriaDropdownPatch} from './fomantic/dropdown.js'; import {initAriaModalPatch} from './fomantic/modal.js'; import {initFomanticTransition} from './fomantic/transition.js'; import {svg} from '../svg.js'; +import {initDimmer} from './dimmer.ts'; export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)'); @@ -31,4 +32,5 @@ export function initGiteaFomantic() { initAriaFormFieldPatch(); initAriaDropdownPatch(); initAriaModalPatch(); + initDimmer(); } From bd9366e7fc834d42fd60dbe5391c268f65452db7 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 2 Apr 2025 14:50:02 +0000 Subject: [PATCH 12/23] fix(i18n): fix several usages of i18n (#7422) - Add new translations, if an existing one couldn't be used. - Use existing translations, if one existed and fit the purpose. - Spotted by the lint-locale-usage tooling by @fogti. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7422 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Gusted Co-committed-by: Gusted --- options/locale/locale_en-US.ini | 2 +- options/locale_next/locale_en-US.json | 5 ++++- routers/web/repo/editor.go | 2 +- templates/admin/auth/list.tmpl | 2 +- templates/admin/emails/list.tmpl | 2 +- templates/admin/notice.tmpl | 2 +- templates/admin/org/list.tmpl | 2 +- templates/admin/packages/list.tmpl | 2 +- templates/admin/repo/list.tmpl | 2 +- templates/admin/user/list.tmpl | 2 +- templates/repo/settings/lfs_file.tmpl | 2 +- templates/user/auth/finalize_openid.tmpl | 2 +- 12 files changed, 15 insertions(+), 12 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ea7191b906..d97f90ec9c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1436,7 +1436,7 @@ editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be ed editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository. editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository. editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository. -editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit changes again to overwrite them. +editor.file_changed_while_editing = The file contents have changed since you opened the file. Click here to see them or Commit changes again to overwrite them. editor.file_already_exists = A file named "%s" already exists in this repository. editor.commit_id_not_matching = The file was changed while you were editing it. Commit to a new branch and then merge. editor.push_out_of_date = The push appears to be out of date. diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index def49e1a58..7f9b81a77b 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -18,5 +18,8 @@ "themes.names.forgejo-light": "Forgejo light", "themes.names.forgejo-dark": "Forgejo dark", "error.not_found.title": "Page not found", - "alert.asset_load_failed": "Failed to load asset files from {path}. Please make sure the asset files can be accessed." + "alert.asset_load_failed": "Failed to load asset files from {path}. Please make sure the asset files can be accessed.", + "settings.adopt": "Adopt", + "install.invalid_lfs_path": "Unable to create the LFS root at the specified path: %[1]s", + "install.lfs_jwt_secret_failed": "Unable to generate a LFS JWT secret: %[1]s" } \ No newline at end of file diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 77f71fd2b1..5114cc9c05 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -585,7 +585,7 @@ func DeleteFilePost(ctx *context.Context) { ctx.Error(http.StatusInternalServerError, err.Error()) } } else if models.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) { - ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form) + ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form) } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) if len(errPushRej.Message) == 0 { diff --git a/templates/admin/auth/list.tmpl b/templates/admin/auth/list.tmpl index 9c283fe3d8..0c7138bd68 100644 --- a/templates/admin/auth/list.tmpl +++ b/templates/admin/auth/list.tmpl @@ -31,7 +31,7 @@ {{svg "octicon-pencil"}} {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 8796794aee..5c30df87af 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -67,7 +67,7 @@ {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 4f8783dd42..08f0a4f204 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -25,7 +25,7 @@ {{svg "octicon-note" 16}} {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} {{if .Notices}} diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index b719d259e0..8c9c198897 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -67,7 +67,7 @@ {{svg "octicon-pencil"}} {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index f22600a449..5f9965e34c 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -75,7 +75,7 @@ {{svg "octicon-trash"}} {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 0422705ea9..7a75ceded7 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -87,7 +87,7 @@ {{svg "octicon-trash"}} {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index f4609edbbf..368e113d24 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -110,7 +110,7 @@ {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/repo/settings/lfs_file.tmpl b/templates/repo/settings/lfs_file.tmpl index 941d09f3e6..3b6b763536 100644 --- a/templates/repo/settings/lfs_file.tmpl +++ b/templates/repo/settings/lfs_file.tmpl @@ -31,7 +31,7 @@ {{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}} {{else if .IsPDFFile}} -
+
{{else}} {{ctx.Locale.Tr "repo.file_view_raw"}} {{end}} diff --git a/templates/user/auth/finalize_openid.tmpl b/templates/user/auth/finalize_openid.tmpl index f84f860b02..372471368f 100644 --- a/templates/user/auth/finalize_openid.tmpl +++ b/templates/user/auth/finalize_openid.tmpl @@ -30,7 +30,7 @@ {{if .ShowRegistrationButton}}
From 15a2338ff24d8388781b6151b3eedf45d912f7e8 Mon Sep 17 00:00:00 2001 From: Ellen Emilia Anna Zscheile Date: Wed, 2 Apr 2025 14:57:45 +0000 Subject: [PATCH 13/23] feat(build): teach lint-locale-usage about trPluralString (#7425) This requires using the more complicated parsing from localestore.go In order to avoid future code drift and code duplication, localestore.go was refactored to call IterateMessagesContent instead of essentially duplicating the code of RecursivelyAddTranslationsFromJSON with small adjustments. locale/utils.go was moved to translation/localeiter/utils.go in order to avoid spreading translation-related routines among completely different places. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7425 Reviewed-by: Gusted Co-authored-by: Ellen Emilia Anna Zscheile Co-committed-by: Ellen Emilia Anna Zscheile --- .deadcode-out | 3 ++ build/lint-locale-usage/lint-locale-usage.go | 17 +++--- build/lint-locale/lint-locale.go | 12 +++-- build/lint-locale/lint-locale_test.go | 12 +++++ modules/translation/i18n/localestore.go | 53 ++++--------------- .../localeiter}/utils.go | 42 +++++++++++---- 6 files changed, 73 insertions(+), 66 deletions(-) rename modules/{locale => translation/localeiter}/utils.go (56%) diff --git a/.deadcode-out b/.deadcode-out index 31184826ae..5b5698c5de 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -192,6 +192,9 @@ forgejo.org/modules/translation MockLocale.HasKey MockLocale.PrettyNumber +forgejo.org/modules/translation/localeiter + IterateMessagesContent + forgejo.org/modules/util OptionalArg diff --git a/build/lint-locale-usage/lint-locale-usage.go b/build/lint-locale-usage/lint-locale-usage.go index edbd086d0e..88375c1c36 100644 --- a/build/lint-locale-usage/lint-locale-usage.go +++ b/build/lint-locale-usage/lint-locale-usage.go @@ -18,8 +18,8 @@ import ( tmplParser "text/template/parse" "forgejo.org/modules/container" - "forgejo.org/modules/locale" fjTemplates "forgejo.org/modules/templates" + "forgejo.org/modules/translation/localeiter" "forgejo.org/modules/util" ) @@ -300,10 +300,6 @@ func main() { } msgids := make(container.Set[string]) - onMsgid := func(trKey, trValue string) error { - msgids[trKey] = struct{}{} - return nil - } localeFile := filepath.Join(filepath.Join("options", "locale"), "locale_en-US.ini") localeContent, err := os.ReadFile(localeFile) @@ -312,7 +308,10 @@ func main() { os.Exit(2) } - if err = locale.IterateMessagesContent(localeContent, onMsgid); err != nil { + if err = localeiter.IterateMessagesContent(localeContent, func(trKey, trValue string) error { + msgids[trKey] = struct{}{} + return nil + }); err != nil { fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error()) os.Exit(2) } @@ -324,7 +323,11 @@ func main() { os.Exit(2) } - if err := locale.IterateMessagesNextContent(localeContent, onMsgid); err != nil { + if err := localeiter.IterateMessagesNextContent(localeContent, func(trKey, pluralForm, trValue string) error { + // ignore plural form + msgids[trKey] = struct{}{} + return nil + }); err != nil { fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error()) os.Exit(2) } diff --git a/build/lint-locale/lint-locale.go b/build/lint-locale/lint-locale.go index 94ce941e62..0d80ffa4b0 100644 --- a/build/lint-locale/lint-locale.go +++ b/build/lint-locale/lint-locale.go @@ -14,7 +14,7 @@ import ( "slices" "strings" - "forgejo.org/modules/locale" + "forgejo.org/modules/translation/localeiter" "github.com/microcosm-cc/bluemonday" "github.com/sergi/go-diff/diffmatchpatch" @@ -100,7 +100,7 @@ func checkValue(trKey, value string) []string { func checkLocaleContent(localeContent []byte) []string { errors := []string{} - if err := locale.IterateMessagesContent(localeContent, func(trKey, trValue string) error { + if err := localeiter.IterateMessagesContent(localeContent, func(trKey, trValue string) error { errors = append(errors, checkValue(trKey, trValue)...) return nil }); err != nil { @@ -113,8 +113,12 @@ func checkLocaleContent(localeContent []byte) []string { func checkLocaleNextContent(localeContent []byte) []string { errors := []string{} - if err := locale.IterateMessagesNextContent(localeContent, func(trKey, trValue string) error { - errors = append(errors, checkValue(trKey, trValue)...) + if err := localeiter.IterateMessagesNextContent(localeContent, func(trKey, pluralForm, trValue string) error { + fullKey := trKey + if pluralForm != "" { + fullKey = trKey + "." + pluralForm + } + errors = append(errors, checkValue(fullKey, trValue)...) return nil }); err != nil { panic(err) diff --git a/build/lint-locale/lint-locale_test.go b/build/lint-locale/lint-locale_test.go index 37407461c7..d481a80e37 100644 --- a/build/lint-locale/lint-locale_test.go +++ b/build/lint-locale/lint-locale_test.go @@ -91,4 +91,16 @@ func TestNextLocalizationPolicy(t *testing.T) { "settings.hidden_comment_types_description": "\"