From e645afbb3fd30ebf6c505e69f26626c39afda8bc Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 20 Oct 2026 12:32:27 +0200 Subject: [PATCH] refactor: replace Fomantic modal with native dialog (#39846) Replace Fomantic's hard-forked jQuery modal with native `` elements, advancing https://github.com/go-gitea/gitea/issues/29849. The UI is visually unchanged. Dialogs open with `show()` over an `inert` page, so tooltips, popups and toasts mounted on `` stay usable above them, unlike with `showModal()` (https://github.com/whatwg/html/issues/9936). - Remove the Fomantic modal and dimmer, add `showModal` and `hideModal` - Declare modals as ``, shown inside a `.modal-backdrop`, instead of `ui modal` with its unused size classes - Remove two modals that could never be opened - Block the page while a modal is open, close the topmost one on Escape and restore focus to its opener - Allow stacked modals, use a confirm modal instead of `window.confirm` for deleting content history - Hide popups and toasts when a modal opens or closes - Keep the release notes modal open when generating notes fails Fixes: https://github.com/go-gitea/gitea/issues/39643 Co-authored-by: wxiaoguang --- templates/admin/badge/edit.tmpl | 4 +- templates/admin/emails/list.tmpl | 4 +- templates/admin/notice.tmpl | 4 +- templates/admin/packages/list.tmpl | 14 +- templates/admin/repo/list.tmpl | 18 +- templates/admin/repo/unadopted.tmpl | 8 +- templates/admin/user/edit.tmpl | 4 +- templates/admin/user/view_orgs.tmpl | 20 +- templates/base/modal_actions_confirm.tmpl | 2 +- .../{fomantic-modal.tmpl => modal.tmpl} | 36 +- .../org/settings/options_dangerzone.tmpl | 12 +- templates/org/team/members.tmpl | 14 +- templates/org/team/new.tmpl | 4 +- templates/org/team/sidebar.tmpl | 4 +- templates/org/team/teams.tmpl | 4 +- templates/package/settings.tmpl | 4 +- templates/package/shared/view.tmpl | 4 +- templates/projects/list.tmpl | 4 +- templates/projects/view.tmpl | 4 +- templates/repo/actions/list.tmpl | 4 +- templates/repo/actions/workflow_dispatch.tmpl | 4 +- templates/repo/branch/list.tmpl | 22 +- templates/repo/cite/cite_modal.tmpl | 4 +- templates/repo/commit_page.tmpl | 12 +- templates/repo/header/fork.tmpl | 4 +- .../repo/issue/labels/label_edit_modal.tmpl | 4 +- templates/repo/issue/milestones.tmpl | 4 +- .../issue/sidebar/issue_dependencies.tmpl | 26 +- .../repo/issue/sidebar/issue_management.tmpl | 10 +- .../repo/issue/sidebar/reviewer_list.tmpl | 4 +- .../issue/sidebar/stopwatch_timetracker.tmpl | 8 +- .../view_content/reference_issue_dialog.tmpl | 4 +- templates/repo/migrate/migrating.tmpl | 8 +- templates/repo/release/list.tmpl | 13 - templates/repo/release/new.tmpl | 4 +- templates/repo/settings/branches.tmpl | 4 +- templates/repo/settings/collaboration.tmpl | 4 +- templates/repo/settings/deploy_keys.tmpl | 8 +- templates/repo/settings/lfs.tmpl | 4 +- templates/repo/settings/options.tmpl | 28 +- .../repo/settings/push_mirror_sync_modal.tmpl | 4 +- templates/repo/tag/list.tmpl | 4 +- templates/repo/watch_options_modal.tmpl | 4 +- templates/repo/wiki/view.tmpl | 4 +- templates/shared/actions/runner_edit.tmpl | 4 +- templates/shared/secrets/add_list.tmpl | 4 +- templates/shared/user/access_tokens.tmpl | 8 +- templates/shared/user/block_user_dialog.tmpl | 6 +- templates/shared/user/blocked_users.tmpl | 4 +- templates/shared/variables/variable_list.tmpl | 4 +- templates/user/settings/account.tmpl | 8 +- .../settings/applications_oauth2_list.tmpl | 4 +- templates/user/settings/grants_oauth2.tmpl | 4 +- templates/user/settings/keys_gpg.tmpl | 4 +- templates/user/settings/keys_principal.tmpl | 4 +- templates/user/settings/keys_ssh.tmpl | 4 +- templates/user/settings/organization.tmpl | 14 +- templates/user/settings/repos.tmpl | 19 +- .../user/settings/security/accountlinks.tmpl | 4 +- templates/user/settings/security/openid.tmpl | 4 +- templates/user/settings/security/twofa.tmpl | 4 +- .../user/settings/security/webauthn.tmpl | 8 +- tests/e2e/issue-comment.test.ts | 24 +- tests/e2e/label.test.ts | 19 + tests/e2e/repo.test.ts | 22 +- tests/e2e/utils.ts | 14 + web_src/css/base.css | 2 +- web_src/css/modules/dimmer.css | 17 +- web_src/css/modules/modal.css | 50 +- web_src/css/modules/transition.css | 13 +- web_src/fomantic/build/components/modal.js | 1209 ----------------- web_src/fomantic/build/fomantic.js | 1 - web_src/js/features/admin/common.ts | 6 +- web_src/js/features/common-button.ts | 8 +- web_src/js/features/common-page.ts | 2 +- web_src/js/features/comp/ConfirmModal.ts | 18 +- web_src/js/features/comp/LabelEdit.ts | 6 +- web_src/js/features/repo-branch.ts | 4 +- web_src/js/features/repo-issue-content.ts | 17 +- web_src/js/features/repo-issue.ts | 6 +- web_src/js/features/repo-projects.ts | 6 +- web_src/js/features/repo-release.ts | 12 +- web_src/js/globals.d.ts | 3 - web_src/js/index.ts | 2 + web_src/js/modules/devtest.ts | 6 +- web_src/js/modules/fetch-action.ts | 4 +- web_src/js/modules/fomantic.ts | 4 - web_src/js/modules/fomantic/dimmer.ts | 31 - web_src/js/modules/fomantic/modal.ts | 86 -- web_src/js/modules/fomantic/transition.ts | 16 +- web_src/js/modules/modal.test.ts | 87 ++ web_src/js/modules/modal.ts | 112 ++ web_src/js/modules/shortcut.test.ts | 18 +- web_src/js/modules/shortcut.ts | 15 +- web_src/js/modules/toast.ts | 8 +- 95 files changed, 607 insertions(+), 1719 deletions(-) rename templates/devtest/{fomantic-modal.tmpl => modal.tmpl} (83%) create mode 100644 tests/e2e/label.test.ts delete mode 100644 web_src/fomantic/build/components/modal.js delete mode 100644 web_src/js/modules/fomantic/dimmer.ts delete mode 100644 web_src/js/modules/fomantic/modal.ts create mode 100644 web_src/js/modules/modal.test.ts create mode 100644 web_src/js/modules/modal.ts diff --git a/templates/admin/badge/edit.tmpl b/templates/admin/badge/edit.tmpl index 0d2e1c16dde..52d394864d9 100644 --- a/templates/admin/badge/edit.tmpl +++ b/templates/admin/badge/edit.tmpl @@ -28,7 +28,7 @@ - + {{template "admin/layout_footer" .}} diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 135b4da3a01..6cd82bd903d 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -76,7 +76,7 @@ {{template "base/paginate" .}} - + {{template "admin/layout_footer" .}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 98ab7310049..df9b79f8a77 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -61,9 +61,9 @@ {{template "base/paginate" .}} - + {{template "admin/layout_footer" .}} diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index 41fba825f86..442b82749f8 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -83,12 +83,14 @@ {{template "base/paginate" .}} - {{template "admin/layout_footer" .}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 981fe48a408..2e3adb2e8a2 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -96,14 +96,16 @@ {{template "base/paginate" .}} - {{template "admin/layout_footer" .}} diff --git a/templates/admin/repo/unadopted.tmpl b/templates/admin/repo/unadopted.tmpl index c3ea0ad98a5..b6e85c7432a 100644 --- a/templates/admin/repo/unadopted.tmpl +++ b/templates/admin/repo/unadopted.tmpl @@ -24,7 +24,7 @@ {{svg "octicon-file-directory-fill"}} {{$dir}}
- + - +
{{end}} diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 9dd9f3ff7c1..6a9883dee41 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -223,7 +223,7 @@ - + {{template "admin/layout_footer" .}} diff --git a/templates/admin/user/view_orgs.tmpl b/templates/admin/user/view_orgs.tmpl index d4554555c9d..468957db15f 100644 --- a/templates/admin/user/view_orgs.tmpl +++ b/templates/admin/user/view_orgs.tmpl @@ -5,18 +5,20 @@ {{ctx.Locale.Tr "settings.organization"}} ({{ctx.Locale.Tr "admin.total" $userOrgsTotal}}) {{if $userOrgs}} - {{end}} diff --git a/templates/base/modal_actions_confirm.tmpl b/templates/base/modal_actions_confirm.tmpl index dcf6fe74f2a..af3dcb86e45 100644 --- a/templates/base/modal_actions_confirm.tmpl +++ b/templates/base/modal_actions_confirm.tmpl @@ -6,7 +6,7 @@ Single danger button (GitHub-like): * ModalButtonDangerText "This action will destroy your data" -The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal internally +The ".ok.button" and ".cancel.button" selectors are also used by "modules/modal.ts" */}}
{{if .ModalButtonDangerText}} diff --git a/templates/devtest/fomantic-modal.tmpl b/templates/devtest/modal.tmpl similarity index 83% rename from templates/devtest/fomantic-modal.tmpl rename to templates/devtest/modal.tmpl index bed6251787e..2ad70ace90e 100644 --- a/templates/devtest/fomantic-modal.tmpl +++ b/templates/devtest/modal.tmpl @@ -1,15 +1,15 @@ {{template "devtest/devtest-header"}}
- + - + - + - {{template "devtest/devtest-footer"}} diff --git a/templates/org/settings/options_dangerzone.tmpl b/templates/org/settings/options_dangerzone.tmpl index e28a10c9282..f70d2fac700 100644 --- a/templates/org/settings/options_dangerzone.tmpl +++ b/templates/org/settings/options_dangerzone.tmpl @@ -35,7 +35,7 @@
- - + - - + - - + diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl index 78af9086e57..6a783ed0d5e 100644 --- a/templates/org/team/members.tmpl +++ b/templates/org/team/members.tmpl @@ -72,13 +72,15 @@ - {{template "base/footer" .}} diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl index b34a12005b0..06b638ac28e 100644 --- a/templates/org/team/new.tmpl +++ b/templates/org/team/new.tmpl @@ -194,7 +194,7 @@ - + {{template "base/footer" .}} diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index 97662e23b1e..8515ec3df12 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -91,7 +91,7 @@ {{end}} - + diff --git a/templates/org/team/teams.tmpl b/templates/org/team/teams.tmpl index 62ee9a51e3c..524317595b7 100644 --- a/templates/org/team/teams.tmpl +++ b/templates/org/team/teams.tmpl @@ -63,7 +63,7 @@ {{template "base/paginate" .}} - + {{template "base/footer" .}} diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index 271fb683118..2cd62686082 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -55,7 +55,7 @@
- - + diff --git a/templates/package/shared/view.tmpl b/templates/package/shared/view.tmpl index 65f3e61fdb4..21f9a3e7b99 100644 --- a/templates/package/shared/view.tmpl +++ b/templates/package/shared/view.tmpl @@ -110,7 +110,7 @@
{{svg "octicon-trash"}} {{ctx.Locale.Tr "packages.settings.delete.version"}} - -
+ {{end}} diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 9fedd05f949..772e53bccb5 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -94,9 +94,9 @@ {{if and $.CanWriteProjects (not $.Repository.IsArchived)}} - + {{end}} diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 2e719984584..bc38c8a4b40 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -133,7 +133,7 @@ {{if $canWriteProject}} - - + {{end}} diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 981bafcff6c..136d3eaf7ad 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -113,7 +113,7 @@ {{if $showCreateWorkflowBadge}} - + {{end}} {{template "base/footer" .}} diff --git a/templates/repo/actions/workflow_dispatch.tmpl b/templates/repo/actions/workflow_dispatch.tmpl index 5fb1012e2ee..be37649ce97 100644 --- a/templates/repo/actions/workflow_dispatch.tmpl +++ b/templates/repo/actions/workflow_dispatch.tmpl @@ -5,7 +5,7 @@ - - - {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} - + - {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} - + {{template "base/footer" .}} diff --git a/templates/repo/cite/cite_modal.tmpl b/templates/repo/cite/cite_modal.tmpl index 4496b33cc70..3b0409cd3ea 100644 --- a/templates/repo/cite/cite_modal.tmpl +++ b/templates/repo/cite/cite_modal.tmpl @@ -1,4 +1,4 @@ - - + diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 06b967537ea..f8ba74799bc 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -49,7 +49,7 @@ data-modal-cherry-pick-header="{{ctx.Locale.Tr "repo.commit.cherry-pick-header" (ShortSha .CommitID)}}" data-modal-cherry-pick-content="{{ctx.Locale.Tr "repo.commit.cherry-pick-content"}}" data-modal-cherry-pick-submit="{{ctx.Locale.Tr "repo.commit.cherry-pick"}}">{{ctx.Locale.Tr "repo.commit.cherry-pick"}} - - - - - - + {{end}} diff --git a/templates/repo/header/fork.tmpl b/templates/repo/header/fork.tmpl index 86480694d3f..3adfb5cb345 100644 --- a/templates/repo/header/fork.tmpl +++ b/templates/repo/header/fork.tmpl @@ -22,7 +22,7 @@ {{CountFmt $.Repository.NumForks}} {{if $.ShowForkModal}} - - + {{end}} diff --git a/templates/repo/issue/labels/label_edit_modal.tmpl b/templates/repo/issue/labels/label_edit_modal.tmpl index 23851b0d45d..269478119a7 100644 --- a/templates/repo/issue/labels/label_edit_modal.tmpl +++ b/templates/repo/issue/labels/label_edit_modal.tmpl @@ -1,4 +1,4 @@ - - + diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 4af4697b44f..e707947eff8 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -92,11 +92,11 @@ {{if or .CanWriteIssues .CanWritePulls}} - + {{end}} {{template "base/footer" .}} diff --git a/templates/repo/issue/sidebar/issue_dependencies.tmpl b/templates/repo/issue/sidebar/issue_dependencies.tmpl index 93aa55b6aa2..323766c8652 100644 --- a/templates/repo/issue/sidebar/issue_dependencies.tmpl +++ b/templates/repo/issue/sidebar/issue_dependencies.tmpl @@ -127,18 +127,20 @@ {{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}} - {{end}} {{end}} diff --git a/templates/repo/issue/sidebar/issue_management.tmpl b/templates/repo/issue/sidebar/issue_management.tmpl index 2f9688800e4..a672c6089d0 100644 --- a/templates/repo/issue/sidebar/issue_management.tmpl +++ b/templates/repo/issue/sidebar/issue_management.tmpl @@ -24,7 +24,7 @@ {{svg "octicon-lock"}} {{ctx.Locale.Tr "repo.issues.lock"}} {{end}} - + - {{if $data.CurrentPullReviewers}} - - + {{end}} diff --git a/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl b/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl index ef2b99e817c..ccb20a50220 100644 --- a/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl +++ b/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl @@ -35,7 +35,7 @@ {{end}} {{/* set time estimate modal */}} - - + {{/* manually add time modal */}} - + {{end}} {{if .WorkingUsers}} diff --git a/templates/repo/issue/view_content/reference_issue_dialog.tmpl b/templates/repo/issue/view_content/reference_issue_dialog.tmpl index bbe80f3f16c..752dfd364e7 100644 --- a/templates/repo/issue/view_content/reference_issue_dialog.tmpl +++ b/templates/repo/issue/view_content/reference_issue_dialog.tmpl @@ -1,4 +1,4 @@ - - + diff --git a/templates/repo/migrate/migrating.tmpl b/templates/repo/migrate/migrating.tmpl index e1a652385db..b768d2263cd 100644 --- a/templates/repo/migrate/migrating.tmpl +++ b/templates/repo/migrate/migrating.tmpl @@ -51,7 +51,7 @@ - - + - {{template "base/modal_actions_confirm" .}} - + {{template "base/footer" .}} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 6ccde83d50b..be2ae5df8ff 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -116,17 +116,4 @@ -{{if (and ($.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) .PageIsTagList)}} - -{{end}} - {{template "base/footer" .}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 0495142fa7c..d1c32304b09 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -145,7 +145,7 @@ - + {{template "base/footer" .}} diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl index f8538fed99c..866fa05dec0 100644 --- a/templates/repo/settings/branches.tmpl +++ b/templates/repo/settings/branches.tmpl @@ -65,10 +65,10 @@ {{end}} - + {{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/collaboration.tmpl b/templates/repo/settings/collaboration.tmpl index ecd57ff9bb8..519f266b614 100644 --- a/templates/repo/settings/collaboration.tmpl +++ b/templates/repo/settings/collaboration.tmpl @@ -118,10 +118,10 @@ {{end}} - + {{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 7b2e6397988..8f1a6a38fc6 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -60,16 +60,16 @@ - + - + {{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/lfs.tmpl b/templates/repo/settings/lfs.tmpl index 88226c0f833..95a5a0e535e 100644 --- a/templates/repo/settings/lfs.tmpl +++ b/templates/repo/settings/lfs.tmpl @@ -37,12 +37,12 @@ {{template "base/paginate" .}} - + {{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 0354725cb57..787a73daf11 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -919,7 +919,7 @@ {{if $.CanManagerDangerZone}} {{if .Repository.IsMirror}} - - + {{end}} {{if .CanConvertFork}} - - + {{end}} - - + - - + {{if not .Repository.IsFork}} - - + {{end}} {{if .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}} - - + {{end}} {{if not .Repository.IsMirror}} - + diff --git a/templates/repo/tag/list.tmpl b/templates/repo/tag/list.tmpl index 9d982a612ae..e6e6807c595 100644 --- a/templates/repo/tag/list.tmpl +++ b/templates/repo/tag/list.tmpl @@ -67,7 +67,7 @@ {{if $.Permission.CanWrite ctx.Consts.RepoUnitTypeCode}} - {{template "base/modal_actions_confirm" .}} - + {{end}} {{template "base/footer" .}} diff --git a/templates/repo/watch_options_modal.tmpl b/templates/repo/watch_options_modal.tmpl index 92deb8897a9..e8c1ee08f93 100644 --- a/templates/repo/watch_options_modal.tmpl +++ b/templates/repo/watch_options_modal.tmpl @@ -1,4 +1,4 @@ - + diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index d9428a114f8..1698e862d32 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -96,10 +96,10 @@ - + {{template "base/footer" .}} diff --git a/templates/shared/actions/runner_edit.tmpl b/templates/shared/actions/runner_edit.tmpl index 4fff302d557..3aac7648eb7 100644 --- a/templates/shared/actions/runner_edit.tmpl +++ b/templates/shared/actions/runner_edit.tmpl @@ -95,7 +95,7 @@ {{template "base/paginate" .}} - + diff --git a/templates/shared/secrets/add_list.tmpl b/templates/shared/secrets/add_list.tmpl index b83b60cd3ca..a1a5508aea6 100644 --- a/templates/shared/secrets/add_list.tmpl +++ b/templates/shared/secrets/add_list.tmpl @@ -66,7 +66,7 @@ {{/* Add secret dialog */}} - + diff --git a/templates/shared/user/access_tokens.tmpl b/templates/shared/user/access_tokens.tmpl index 02f75df370c..a8785e79241 100644 --- a/templates/shared/user/access_tokens.tmpl +++ b/templates/shared/user/access_tokens.tmpl @@ -101,7 +101,7 @@ - + {{if not .IsBot}} - {{template "base/modal_actions_confirm"}} - + {{end}} diff --git a/templates/shared/user/block_user_dialog.tmpl b/templates/shared/user/block_user_dialog.tmpl index 1e8c7b669d2..41b5dbdcdfc 100644 --- a/templates/shared/user/block_user_dialog.tmpl +++ b/templates/shared/user/block_user_dialog.tmpl @@ -1,4 +1,4 @@ - - + diff --git a/templates/shared/user/blocked_users.tmpl b/templates/shared/user/blocked_users.tmpl index 86baa5d5af3..a35420c1815 100644 --- a/templates/shared/user/blocked_users.tmpl +++ b/templates/shared/user/blocked_users.tmpl @@ -52,7 +52,7 @@ {{end}} - - + diff --git a/templates/shared/variables/variable_list.tmpl b/templates/shared/variables/variable_list.tmpl index d6d41aaa014..fc08b2beb01 100644 --- a/templates/shared/variables/variable_list.tmpl +++ b/templates/shared/variables/variable_list.tmpl @@ -64,7 +64,7 @@ {{/** Edit variable dialog */}} - + diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index 9b1f25ec89a..c4adbdc44a9 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -127,7 +127,7 @@ - + {{end}} - + {{template "user/settings/layout_footer" .}} diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index 8d10c3b2cd8..735eab30ac8 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -35,7 +35,7 @@ {{end}} - +
diff --git a/templates/user/settings/grants_oauth2.tmpl b/templates/user/settings/grants_oauth2.tmpl index 3b5c19c674a..44249c7c79f 100644 --- a/templates/user/settings/grants_oauth2.tmpl +++ b/templates/user/settings/grants_oauth2.tmpl @@ -27,7 +27,7 @@ {{end}}
- + diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl index 6b63ffd27c3..76432601ea0 100644 --- a/templates/user/settings/keys_gpg.tmpl +++ b/templates/user/settings/keys_gpg.tmpl @@ -123,7 +123,7 @@ {{end}} {{end}} - + diff --git a/templates/user/settings/keys_principal.tmpl b/templates/user/settings/keys_principal.tmpl index e3e5122d768..f0b09f0e0b3 100644 --- a/templates/user/settings/keys_principal.tmpl +++ b/templates/user/settings/keys_principal.tmpl @@ -55,7 +55,7 @@ - + {{end}} diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl index 7eac18b4b2d..ce0b913e278 100644 --- a/templates/user/settings/keys_ssh.tmpl +++ b/templates/user/settings/keys_ssh.tmpl @@ -103,7 +103,7 @@ {{end}} {{end}} - + diff --git a/templates/user/settings/organization.tmpl b/templates/user/settings/organization.tmpl index 3c223f12555..d7c2fa7f370 100644 --- a/templates/user/settings/organization.tmpl +++ b/templates/user/settings/organization.tmpl @@ -38,14 +38,16 @@ - {{template "user/settings/layout_footer" .}} diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index b5242f40600..290e47bce13 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -35,7 +35,7 @@
{{if $.allowAdopt}} - + {{end}} {{if $.allowDelete}} - + {{end}}
{{end}} @@ -114,15 +114,4 @@ - - {{template "user/settings/layout_footer" .}} diff --git a/templates/user/settings/security/accountlinks.tmpl b/templates/user/settings/security/accountlinks.tmpl index fa6dc53b0e9..6eb10f1b491 100644 --- a/templates/user/settings/security/accountlinks.tmpl +++ b/templates/user/settings/security/accountlinks.tmpl @@ -48,7 +48,7 @@ {{end}} - + {{end}} diff --git a/templates/user/settings/security/openid.tmpl b/templates/user/settings/security/openid.tmpl index 1e0b21cdda7..e594f9c5b01 100644 --- a/templates/user/settings/security/openid.tmpl +++ b/templates/user/settings/security/openid.tmpl @@ -48,7 +48,7 @@ - + diff --git a/templates/user/settings/security/twofa.tmpl b/templates/user/settings/security/twofa.tmpl index 2e063f00509..f9adf369bf8 100644 --- a/templates/user/settings/security/twofa.tmpl +++ b/templates/user/settings/security/twofa.tmpl @@ -22,7 +22,7 @@ {{end}} - + diff --git a/templates/user/settings/security/webauthn.tmpl b/templates/user/settings/security/webauthn.tmpl index 581383a83aa..d270fb2e3cc 100644 --- a/templates/user/settings/security/webauthn.tmpl +++ b/templates/user/settings/security/webauthn.tmpl @@ -35,7 +35,7 @@ - - + diff --git a/tests/e2e/issue-comment.test.ts b/tests/e2e/issue-comment.test.ts index ba28a79b775..be430ecbfb7 100644 --- a/tests/e2e/issue-comment.test.ts +++ b/tests/e2e/issue-comment.test.ts @@ -1,6 +1,6 @@ import {env} from 'node:process'; import {test, expect} from '@playwright/test'; -import {login, apiCreateRepo, apiCreateIssue, randomString} from './utils.ts'; +import {login, apiCreateRepo, apiCreateIssue, apiEditIssue, randomString} from './utils.ts'; test('comment on and close an issue', async ({page, request}) => { const repoName = `e2e-issue-comment-${randomString(8)}`; @@ -48,3 +48,25 @@ test('unsaved issue description prompts before leaving', async ({page, request}) expect((await dialogPromise).type()).toBe('beforeunload'); await expect(page).toHaveURL(/\/issues\/new$/); }); + +test('delete an edit history entry via a confirm over the history modal', async ({page, request}) => { + const repoName = `e2e-issue-history-${randomString(8)}`; + const owner = env.GITEA_TEST_E2E_USER; + await Promise.all([ + (async () => { + await apiCreateRepo(request, {name: repoName, autoInit: false}); + await apiCreateIssue(request, {owner, repo: repoName, title: 'History test', body: 'first'}); + await apiEditIssue(request, owner, repoName, 1, {body: 'second'}); + })(), + login(page), + ]); + await page.goto(`/${owner}/${repoName}/issues/1`); + const historyMenu = page.locator('.content-history-menu'); + await historyMenu.click(); + await historyMenu.getByRole('menuitem').last().click(); + const history = page.getByRole('dialog'); + await history.getByText('Options').click(); + await history.getByRole('menuitem', {name: 'Delete from history'}).click(); + await page.getByRole('dialog', {name: 'Delete from history?'}).getByRole('button', {name: 'Confirm'}).click(); + await expect(page.getByRole('dialog')).toHaveCount(0); +}); diff --git a/tests/e2e/label.test.ts b/tests/e2e/label.test.ts new file mode 100644 index 00000000000..4f5a6890260 --- /dev/null +++ b/tests/e2e/label.test.ts @@ -0,0 +1,19 @@ +import {env} from 'node:process'; +import {test, expect} from '@playwright/test'; +import {login, apiCreateRepo, randomString} from './utils.ts'; + +test('create a label in the modal with a color from the picker popup', async ({page}) => { + const repoName = `e2e-label-${randomString(8)}`; + await Promise.all([login(page), apiCreateRepo(page.request, {name: repoName, autoInit: false})]); + await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/labels`); + await page.getByRole('button', {name: 'New Label'}).click(); + const modal = page.getByRole('dialog'); + await modal.getByRole('button', {name: 'Save'}).click(); + await modal.getByPlaceholder('Label name').fill('e2e-label'); + const colorInput = modal.locator('.color-picker-combo input'); + await colorInput.fill(''); + await page.locator('hex-color-picker').click({position: {x: 10, y: 10}}); + await expect(colorInput).toHaveValue(/^#[0-9a-f]{6}$/); + await modal.getByRole('button', {name: 'Save'}).click(); + await expect(page.locator('.issue-label-list')).toContainText('e2e-label'); +}); diff --git a/tests/e2e/repo.test.ts b/tests/e2e/repo.test.ts index 996fdf40295..9e26bf7d0e1 100644 --- a/tests/e2e/repo.test.ts +++ b/tests/e2e/repo.test.ts @@ -1,6 +1,6 @@ import {env} from 'node:process'; -import {test} from '@playwright/test'; -import {login, randomString} from './utils.ts'; +import {test, expect} from '@playwright/test'; +import {login, apiCreateBranch, apiCreateRepo, randomString} from './utils.ts'; test('create a repository', async ({page}) => { const repoName = `e2e-repo-${randomString(8)}`; @@ -10,3 +10,21 @@ test('create a repository', async ({page}) => { await page.getByRole('button', {name: 'Create Repository'}).click(); await page.waitForURL(new RegExp(`/${env.GITEA_TEST_E2E_USER}/${repoName}$`)); }); + +test('delete a branch via its modal', async ({page, request}) => { + const repoName = `e2e-branch-${randomString(8)}`; + const owner = env.GITEA_TEST_E2E_USER; + await Promise.all([ + (async () => { + await apiCreateRepo(request, {name: repoName}); + await apiCreateBranch(request, owner, repoName, 'feature'); + })(), + login(page), + ]); + await page.goto(`/${owner}/${repoName}/branches`); + await page.getByRole('button', {name: 'Delete Branch "feature"'}).click(); + const modal = page.getByRole('dialog'); + await expect(modal).toContainText('feature'); + await modal.getByRole('button', {name: 'Yes'}).click(); + await expect(page.locator('.flash-message')).toContainText('Branch "feature" has been deleted.'); +}); diff --git a/tests/e2e/utils.ts b/tests/e2e/utils.ts index b742a181624..bf24603295d 100644 --- a/tests/e2e/utils.ts +++ b/tests/e2e/utils.ts @@ -91,6 +91,20 @@ export async function apiCloseIssue(requestContext: APIRequestContext, owner: st }), 'apiCloseIssue'); } +export async function apiEditIssue(requestContext: APIRequestContext, owner: string, repo: string, issueIndex: number, data: Record) { + await apiRetry(() => requestContext.patch(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues/${issueIndex}`, { + headers: apiHeaders(), + data, + }), 'apiEditIssue'); +} + +export async function apiCreateBranch(requestContext: APIRequestContext, owner: string, repo: string, name: string) { + await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/branches`, { + headers: apiHeaders(), + data: {new_branch_name: name}, + }), 'apiCreateBranch'); +} + /** Create a PR via API. Returns the PR index for subsequent operations. */ export async function apiCreatePR(requestContext: APIRequestContext, owner: string, repo: string, head: string, base: string, title: string, {headers}: {headers?: Record} = {}): Promise { const response = await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/pulls`, { diff --git a/web_src/css/base.css b/web_src/css/base.css index 43f49d4b49e..5a4adfeea7c 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -845,7 +845,7 @@ table th[data-sortt-desc] .svg { } /* this is useful to make a left-right (e.g.: title .... operations) layout with default gap, and it wrap for small widths */ -.ui.modal .header.flex-left-right, +.modal-dialog .header.flex-left-right, .flex-left-right { display: flex; flex-wrap: wrap; diff --git a/web_src/css/modules/dimmer.css b/web_src/css/modules/dimmer.css index 7d1ca6171aa..15c3309bea7 100644 --- a/web_src/css/modules/dimmer.css +++ b/web_src/css/modules/dimmer.css @@ -1,27 +1,24 @@ -/* These are the remnants of the fomantic dimmer module */ - -.ui.dimmer { +.modal-backdrop { position: fixed; display: none; inset: 0; background: var(--color-overlay-backdrop); - opacity: 0; - z-index: 1000; + z-index: var(--z-index-modal); overflow-y: auto; justify-content: center; padding: 8px 0; animation-name: fadein; animation-duration: .2s; + overscroll-behavior: contain; user-select: none; } -.ui.active.dimmer { +.modal-backdrop:has(> [open]) { display: flex; - opacity: 1; } -.ui.dimmer > .ui.modal { +.modal-backdrop > .modal-dialog { position: static; - margin-top: auto !important; - margin-bottom: auto !important; + margin-top: auto; + margin-bottom: auto; } diff --git a/web_src/css/modules/modal.css b/web_src/css/modules/modal.css index b89b83b7ed4..52c95801ed8 100644 --- a/web_src/css/modules/modal.css +++ b/web_src/css/modules/modal.css @@ -1,17 +1,17 @@ -/* to keep the changes minimal and easy to revert or fine tune, the legacy classes -like "small" and "mini" are still kept for the HTML elements, these can be cleaned up in the future */ -.ui.modal { - position: absolute; - display: none; - z-index: var(--z-index-modal); +.modal-page-blocked { + overflow: hidden; +} + +.modal-dialog { text-align: left; background: var(--color-body); border: none; + margin: 0; + padding: 0; + color: inherit; box-shadow: 1px 3px 3px 0 var(--color-shadow), 1px 3px 15px 2px var(--color-shadow); - transform-origin: 50% 25%; border-radius: var(--border-radius); user-select: text; - will-change: top, left, margin, transform, opacity; width: fit-content; /* on small screens, the modal should not exceed 90% of the viewport width; @@ -22,17 +22,17 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean min-width: min(800px, max(min(300px, 90vw), 30vw)); } -.ui.modal > :first-child { +.modal-dialog > :first-child { border-top-left-radius: var(--border-radius); border-top-right-radius: var(--border-radius); } -.ui.modal > :last-child { +.modal-dialog > :last-child { border-bottom-left-radius: var(--border-radius); border-bottom-right-radius: var(--border-radius); } -.ui.modal .close-modal { +.modal-dialog .close-modal { cursor: pointer; position: absolute; top: 50%; @@ -47,7 +47,7 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean height: 1.5rem; } -.ui.modal .close-modal::before { +.modal-dialog .close-modal::before { content: ""; display: block; width: 100%; @@ -57,11 +57,11 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean mask-size: cover; } -.ui.modal .close-modal:hover { +.modal-dialog .close-modal:hover { opacity: 1; } -.ui.modal > .header { +.modal-dialog > .header { display: block; position: relative; font-size: 18px; @@ -76,8 +76,8 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean border-top-right-radius: var(--border-radius); } -.ui.modal > .content, -.ui.modal form > .content { +.modal-dialog > .content, +.modal-dialog form > .content { display: block; width: 100%; font-size: 1em; @@ -87,9 +87,9 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean border-radius: 0 0 var(--border-radius) var(--border-radius); } -.ui.modal > .actions, -.ui.modal .content + .actions, -.ui.modal .content + form > .actions { +.modal-dialog > .actions, +.modal-dialog .content + .actions, +.modal-dialog .content + form > .actions { background: var(--color-secondary-bg); padding: 1rem; border-top: 1px solid var(--color-secondary); @@ -97,17 +97,13 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean border-radius: 0 0 var(--border-radius) var(--border-radius); } -.ui.active.modal { - display: block; -} - -.ui.modal .content > form > .actions, -.ui.modal .content > .actions { +.modal-dialog .content > form > .actions, +.modal-dialog .content > .actions { padding-top: 1em; text-align: right; } -.ui.modal .actions > .ui.button { +.modal-dialog .actions > .ui.button { display: inline-flex; gap: var(--gap-inline); align-items: center; @@ -116,7 +112,7 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean margin-right: 0; } -.ui.modal .actions > .ui.button.danger { +.modal-dialog .actions > .ui.button.danger { display: block; width: 100%; margin: 0 auto; diff --git a/web_src/css/modules/transition.css b/web_src/css/modules/transition.css index a0e7db2b4c8..1a216348f02 100644 --- a/web_src/css/modules/transition.css +++ b/web_src/css/modules/transition.css @@ -1,23 +1,12 @@ -/* styles for Fomantic transition (toggle): dimmer, modal, dropdown menu */ +/* styles for Fomantic transition (toggle): dropdown menu */ /* this is the only place using "hidden" and "visible" classes, it can safely work with tailwind without prefix in the future */ -.ui.modal.transition.hidden, .ui.dropdown > .menu.transition.hidden { display: none; visibility: hidden; } -.ui.modal.transition.visible, .ui.dropdown > .menu.transition.visible { display: block; visibility: visible; } - -/* dimmer uses "active" but not "hidden/visible" classes, no special classes for it here */ - -/* only modal and dimmer need animation, dropdown menu doesn't */ -.ui.modal.transition { - animation-name: fadein; - animation-duration: 100ms; - animation-timing-function: ease-in-out; -} diff --git a/web_src/fomantic/build/components/modal.js b/web_src/fomantic/build/components/modal.js deleted file mode 100644 index 21b7a0acb39..00000000000 --- a/web_src/fomantic/build/components/modal.js +++ /dev/null @@ -1,1209 +0,0 @@ -/*! - * # Fomantic-UI - Modal - * 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.modal = function(parameters) { - var - $allModules = $(this), - $window = $(window), - $document = $(document), - $body = $('body'), - - moduleSelector = $allModules.selector || '', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - requestAnimationFrame = window.requestAnimationFrame - || window.mozRequestAnimationFrame - || window.webkitRequestAnimationFrame - || window.msRequestAnimationFrame - || function(callback) { setTimeout(callback, 0); }, - - returnedValue - ; - - $allModules - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.modal.settings, parameters) - : $.extend({}, $.fn.modal.settings), - - selector = settings.selector, - className = settings.className, - namespace = settings.namespace, - error = settings.error, - - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = (typeof settings.context === 'string') ? $(document).find(settings.context) : $(settings.context), // GITEA-PATCH: use "jQuery.find(selector)" instead of "jQuery(selector)" - $close = $module.find(selector.close), - - $allModals, - $otherModals, - $focusedElement, - $dimmable, - $dimmer, - - element = this, - instance = $module.data(moduleNamespace), - - ignoreRepeatedEvents = false, - - initialMouseDownInModal, - initialMouseDownInScrollbar, - initialBodyMargin = '', - tempBodyMargin = '', - - elementEventNamespace, - id, - observer, - module - ; - module = { - - initialize: function() { - module.cache = {}; - module.verbose('Initializing dimmer', $context); - - module.create.id(); - module.create.dimmer(); - - if ( settings.allowMultiple ) { - module.create.innerDimmer(); - } - if (!settings.centered){ - $module.addClass('top aligned'); - } - module.refreshModals(); - - module.bind.events(); - if(settings.observeChanges) { - module.observeChanges(); - } - module.instantiate(); - }, - - instantiate: function() { - module.verbose('Storing instance of modal'); - instance = module; - $module - .data(moduleNamespace, instance) - ; - }, - - create: { - dimmer: function() { - var - defaultSettings = { - debug : settings.debug, - dimmerName : 'modals' - }, - dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) - ; - if($.fn.dimmer === undefined) { - module.error(error.dimmer); - return; - } - module.debug('Creating dimmer'); - $dimmable = $context.dimmer(dimmerSettings); - if(settings.detachable) { - module.verbose('Modal is detachable, moving content into dimmer'); - $dimmable.dimmer('add content', $module); - } - else { - module.set.undetached(); - } - $dimmer = $dimmable.dimmer('get dimmer'); - }, - id: function() { - id = (Math.random().toString(16) + '000000000').substr(2, 8); - elementEventNamespace = '.' + id; - module.verbose('Creating unique id for element', id); - }, - innerDimmer: function() { - if ( $module.find(selector.dimmer).length == 0 ) { - $module.prepend('
'); - } - } - }, - - destroy: function() { - if (observer) { - observer.disconnect(); - } - module.verbose('Destroying previous modal'); - $module - .removeData(moduleNamespace) - .off(eventNamespace) - ; - $window.off(elementEventNamespace); - $dimmer.off(elementEventNamespace); - $close.off(eventNamespace); - $context.dimmer('destroy'); - }, - - observeChanges: function() { - if('MutationObserver' in window) { - observer = new MutationObserver(function(mutations) { - module.debug('DOM tree modified, refreshing'); - module.refresh(); - }); - observer.observe(element, { - childList : true, - subtree : true - }); - module.debug('Setting up mutation observer', observer); - } - }, - - refresh: function() { - module.remove.scrolling(); - module.cacheSizes(); - if(!module.can.useFlex()) { - module.set.modalOffset(); - } - module.set.screenHeight(); - module.set.type(); - }, - - refreshModals: function() { - $otherModals = $module.siblings(selector.modal); - $allModals = $otherModals.add($module); - }, - - attachEvents: function(selector, event) { - var - $toggle = $(selector) - ; - event = $.isFunction(module[event]) - ? module[event] - : module.toggle - ; - if($toggle.length > 0) { - module.debug('Attaching modal events to element', selector, event); - $toggle - .off(eventNamespace) - .on('click' + eventNamespace, event) - ; - } - else { - module.error(error.notFound, selector); - } - }, - - bind: { - events: function() { - module.verbose('Attaching events'); - $module - .on('click' + eventNamespace, selector.close, module.event.close) - .on('click' + eventNamespace, selector.approve, module.event.approve) - .on('click' + eventNamespace, selector.deny, module.event.deny) - ; - $window - .on('resize' + elementEventNamespace, module.event.resize) - ; - }, - scrollLock: function() { - // touch events default to passive, due to changes in chrome to optimize mobile perf - $dimmable.get(0).addEventListener('touchmove', module.event.preventScroll, { passive: false }); - } - }, - - unbind: { - scrollLock: function() { - $dimmable.get(0).removeEventListener('touchmove', module.event.preventScroll, { passive: false }); - } - }, - - get: { - id: function() { - return (Math.random().toString(16) + '000000000').substr(2, 8); - } - }, - - event: { - approve: function() { - if(ignoreRepeatedEvents || settings.onApprove.call(element, $(this)) === false) { - module.verbose('Approve callback returned false cancelling hide'); - return; - } - ignoreRepeatedEvents = true; - module.hide(function() { - ignoreRepeatedEvents = false; - }); - }, - preventScroll: function(event) { - if(event.target.className.indexOf('dimmer') !== -1) { - event.preventDefault(); - } - }, - deny: function() { - if(ignoreRepeatedEvents || settings.onDeny.call(element, $(this)) === false) { - module.verbose('Deny callback returned false cancelling hide'); - return; - } - ignoreRepeatedEvents = true; - module.hide(function() { - ignoreRepeatedEvents = false; - }); - }, - close: function() { - module.hide(); - }, - mousedown: function(event) { - var - $target = $(event.target), - isRtl = module.is.rtl(); - ; - initialMouseDownInModal = ($target.closest(selector.modal).length > 0); - if(initialMouseDownInModal) { - module.verbose('Mouse down event registered inside the modal'); - } - initialMouseDownInScrollbar = module.is.scrolling() && ((!isRtl && $(window).outerWidth() - settings.scrollbarWidth <= event.clientX) || (isRtl && settings.scrollbarWidth >= event.clientX)); - if(initialMouseDownInScrollbar) { - module.verbose('Mouse down event registered inside the scrollbar'); - } - }, - mouseup: function(event) { - if(!settings.closable) { - module.verbose('Dimmer clicked but closable setting is disabled'); - return; - } - if(initialMouseDownInModal) { - module.debug('Dimmer clicked but mouse down was initially registered inside the modal'); - return; - } - if(initialMouseDownInScrollbar){ - module.debug('Dimmer clicked but mouse down was initially registered inside the scrollbar'); - return; - } - var - $target = $(event.target), - isInModal = ($target.closest(selector.modal).length > 0), - isInDOM = $.contains(document.documentElement, event.target) - ; - if(!isInModal && isInDOM && module.is.active() && $module.hasClass(className.front) ) { - module.debug('Dimmer clicked, hiding all modals'); - if(settings.allowMultiple) { - if(!module.hideAll()) { - return; - } - } - else if(!module.hide()){ - return; - } - module.remove.clickaway(); - } - }, - debounce: function(method, delay) { - clearTimeout(module.timer); - module.timer = setTimeout(method, delay); - }, - keyboard: function(event) { - var - keyCode = event.which, - escapeKey = 27 - ; - if(keyCode == escapeKey) { - if(settings.closable) { - module.debug('Escape key pressed hiding modal'); - if ( $module.hasClass(className.front) ) { - module.hide(); - } - } - else { - module.debug('Escape key pressed, but closable is set to false'); - } - event.preventDefault(); - } - }, - resize: function() { - if( $dimmable.dimmer('is active') && ( module.is.animating() || module.is.active() ) ) { - requestAnimationFrame(module.refresh); - } - } - }, - - toggle: function() { - if( module.is.active() || module.is.animating() ) { - module.hide(); - } - else { - module.show(); - } - }, - - show: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.refreshModals(); - module.set.dimmerSettings(); - module.set.dimmerStyles(); - - module.showModal(callback); - }, - - hide: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.refreshModals(); - return module.hideModal(callback); - }, - - showModal: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.animating() || !module.is.active() ) { - module.showDimmer(); - module.cacheSizes(); - module.set.bodyMargin(); - if(module.can.useFlex()) { - module.remove.legacy(); - } - else { - module.set.legacy(); - module.set.modalOffset(); - module.debug('Using non-flex legacy modal positioning.'); - } - module.set.screenHeight(); - module.set.type(); - module.set.clickaway(); - - if( !settings.allowMultiple && module.others.active() ) { - module.hideOthers(module.showModal); - } - else { - ignoreRepeatedEvents = false; - if( settings.allowMultiple ) { - if ( module.others.active() ) { - $otherModals.filter('.' + className.active).find(selector.dimmer).addClass('active'); - } - - if ( settings.detachable ) { - $module.detach().appendTo($dimmer); - } - } - settings.onShow.call(element); - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - module.debug('Showing modal with css animations'); - $module - .transition({ - debug : settings.debug, - animation : settings.transition + ' in', - queue : settings.queue, - duration : settings.duration, - useFailSafe : true, - onComplete : function() { - settings.onVisible.apply(element); - if(settings.keyboardShortcuts) { - module.add.keyboardShortcuts(); - } - module.save.focus(); - module.set.active(); - if(settings.autofocus) { - module.set.autofocus(); - } - callback(); - } - }) - ; - } - else { - module.error(error.noTransition); - } - } - } - else { - module.debug('Modal is already visible'); - } - }, - - hideModal: function(callback, keepDimmed, hideOthersToo) { - var - $previousModal = $otherModals.filter('.' + className.active).last() - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.debug('Hiding modal'); - if(settings.onHide.call(element, $(this)) === false) { - module.verbose('Hide callback returned false cancelling hide'); - ignoreRepeatedEvents = false; - return false; - } - $module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden - if( module.is.animating() || module.is.active() ) { - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - module.remove.active(); - $module - .transition({ - debug : settings.debug, - animation : settings.transition + ' out', - queue : settings.queue, - duration : settings.duration, - useFailSafe : true, - onStart : function() { - if(!module.others.active() && !module.others.animating() && !keepDimmed) { - module.hideDimmer(); - } - if( settings.keyboardShortcuts && !module.others.active() ) { - module.remove.keyboardShortcuts(); - } - }, - onComplete : function() { - module.unbind.scrollLock(); - if ( settings.allowMultiple ) { - $previousModal.addClass(className.front); - $module.removeClass(className.front); - - if ( hideOthersToo ) { - $allModals.find(selector.dimmer).removeClass('active'); - } - else { - $previousModal.find(selector.dimmer).removeClass('active'); - } - } - settings.onHidden.call(element); - module.remove.dimmerStyles(); - module.restore.focus(); - callback(); - } - }) - ; - } - else { - module.error(error.noTransition); - } - } - }, - - showDimmer: function() { - if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) { - module.save.bodyMargin(); - module.debug('Showing dimmer'); - $dimmable.dimmer('show'); - } - else { - module.debug('Dimmer already visible'); - } - }, - - hideDimmer: function() { - if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) { - module.unbind.scrollLock(); - $dimmable.dimmer('hide', function() { - module.restore.bodyMargin(); - module.remove.clickaway(); - module.remove.screenHeight(); - }); - } - else { - module.debug('Dimmer is not visible cannot hide'); - return; - } - }, - - hideAll: function(callback) { - var - $visibleModals = $allModals.filter('.' + className.active + ', .' + className.animating) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( $visibleModals.length > 0 ) { - module.debug('Hiding all visible modals'); - var hideOk = true; -//check in reverse order trying to hide most top displayed modal first - $($visibleModals.get().reverse()).each(function(index,element){ - if(hideOk){ - hideOk = $(element).modal('hide modal', callback, false, true); - } - }); - if(hideOk) { - module.hideDimmer(); - } - return hideOk; - } - }, - - hideOthers: function(callback) { - var - $visibleModals = $otherModals.filter('.' + className.active + ', .' + className.animating) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( $visibleModals.length > 0 ) { - module.debug('Hiding other modals', $otherModals); - $visibleModals - .modal('hide modal', callback, true) - ; - } - }, - - others: { - active: function() { - return ($otherModals.filter('.' + className.active).length > 0); - }, - animating: function() { - return ($otherModals.filter('.' + className.animating).length > 0); - } - }, - - - add: { - keyboardShortcuts: function() { - module.verbose('Adding keyboard shortcuts'); - $document - .on('keyup' + eventNamespace, module.event.keyboard) - ; - } - }, - - save: { - focus: function() { - var - $activeElement = $(document.activeElement), - inCurrentModal = $activeElement.closest($module).length > 0 - ; - if(!inCurrentModal) { - $focusedElement = $(document.activeElement).blur(); - } - }, - bodyMargin: function() { - initialBodyMargin = $body.css('margin-'+(module.can.leftBodyScrollbar() ? 'left':'right')); - var bodyMarginRightPixel = parseInt(initialBodyMargin.replace(/[^\d.]/g, '')), - bodyScrollbarWidth = window.innerWidth - document.documentElement.clientWidth; - tempBodyMargin = bodyMarginRightPixel + bodyScrollbarWidth; - } - }, - - restore: { - focus: function() { - if($focusedElement && $focusedElement.length > 0 && settings.restoreFocus) { - $focusedElement.focus(); - } - }, - bodyMargin: function() { - var position = module.can.leftBodyScrollbar() ? 'left':'right'; - $body.css('margin-'+position, initialBodyMargin); - $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, initialBodyMargin); - } - }, - - remove: { - active: function() { - $module.removeClass(className.active); - }, - legacy: function() { - $module.removeClass(className.legacy); - }, - clickaway: function() { - if (!settings.detachable) { - $module - .off('mousedown' + elementEventNamespace) - ; - } - $dimmer - .off('mousedown' + elementEventNamespace) - ; - $dimmer - .off('mouseup' + elementEventNamespace) - ; - }, - dimmerStyles: function() { - $dimmer.removeClass(className.inverted); - $dimmable.removeClass(className.blurring); - }, - bodyStyle: function() { - if($body.attr('style') === '') { - module.verbose('Removing style attribute'); - $body.removeAttr('style'); - } - }, - screenHeight: function() { - module.debug('Removing page height'); - $body - .css('height', '') - ; - }, - keyboardShortcuts: function() { - module.verbose('Removing keyboard shortcuts'); - $document - .off('keyup' + eventNamespace) - ; - }, - scrolling: function() { - $dimmable.removeClass(className.scrolling); - $module.removeClass(className.scrolling); - } - }, - - cacheSizes: function() { - $module.addClass(className.loading); - var - scrollHeight = $module.prop('scrollHeight'), - modalWidth = $module.outerWidth(), - modalHeight = $module.outerHeight() - ; - if(module.cache.pageHeight === undefined || modalHeight !== 0) { - $.extend(module.cache, { - pageHeight : $(document).outerHeight(), - width : modalWidth, - height : modalHeight + settings.offset, - scrollHeight : scrollHeight + settings.offset, - contextHeight : (settings.context == 'body') - ? $(window).height() - : $dimmable.height(), - }); - module.cache.topOffset = -(module.cache.height / 2); - } - $module.removeClass(className.loading); - module.debug('Caching modal and container sizes', module.cache); - }, - - can: { - leftBodyScrollbar: function(){ - if(module.cache.leftBodyScrollbar === undefined) { - module.cache.leftBodyScrollbar = module.is.rtl() && ((module.is.iframe && !module.is.firefox()) || module.is.safari() || module.is.edge() || module.is.ie()); - } - return module.cache.leftBodyScrollbar; - }, - useFlex: function() { - if (settings.useFlex === 'auto') { - return settings.detachable && !module.is.ie(); - } - if(settings.useFlex && module.is.ie()) { - module.debug('useFlex true is not supported in IE'); - } else if(settings.useFlex && !settings.detachable) { - module.debug('useFlex true in combination with detachable false is not supported'); - } - return settings.useFlex; - }, - fit: function() { - var - contextHeight = module.cache.contextHeight, - verticalCenter = module.cache.contextHeight / 2, - topOffset = module.cache.topOffset, - scrollHeight = module.cache.scrollHeight, - height = module.cache.height, - paddingHeight = settings.padding, - startPosition = (verticalCenter + topOffset) - ; - return (scrollHeight > height) - ? (startPosition + scrollHeight + paddingHeight < contextHeight) - : (height + (paddingHeight * 2) < contextHeight) - ; - } - }, - - is: { - active: function() { - return $module.hasClass(className.active); - }, - ie: function() { - if(module.cache.isIE === undefined) { - var - isIE11 = (!(window.ActiveXObject) && 'ActiveXObject' in window), - isIE = ('ActiveXObject' in window) - ; - module.cache.isIE = (isIE11 || isIE); - } - return module.cache.isIE; - }, - animating: function() { - return $module.transition('is supported') - ? $module.transition('is animating') - : $module.is(':visible') - ; - }, - scrolling: function() { - return $dimmable.hasClass(className.scrolling); - }, - modernBrowser: function() { - // appName for IE11 reports 'Netscape' can no longer use - return !(window.ActiveXObject || 'ActiveXObject' in window); - }, - rtl: function() { - if(module.cache.isRTL === undefined) { - module.cache.isRTL = $body.attr('dir') === 'rtl' || $body.css('direction') === 'rtl'; - } - return module.cache.isRTL; - }, - safari: function() { - if(module.cache.isSafari === undefined) { - module.cache.isSafari = /constructor/i.test(window.HTMLElement) || !!window.ApplePaySession; - } - return module.cache.isSafari; - }, - edge: function(){ - if(module.cache.isEdge === undefined) { - module.cache.isEdge = !!window.setImmediate && !module.is.ie(); - } - return module.cache.isEdge; - }, - firefox: function(){ - if(module.cache.isFirefox === undefined) { - module.cache.isFirefox = !!window.InstallTrigger; - } - return module.cache.isFirefox; - }, - iframe: function() { - return !(self === top); - } - }, - - set: { - autofocus: function() { - var - $inputs = $module.find('[tabindex], :input').filter(':visible').filter(function() { - return $(this).closest('.disabled').length === 0; - }), - $autofocus = $inputs.filter('[autofocus]'), - $input = ($autofocus.length > 0) - ? $autofocus.first() - : $inputs.first() - ; - if($input.length > 0) { - $input.focus(); - } - }, - bodyMargin: function() { - var position = module.can.leftBodyScrollbar() ? 'left':'right'; - if(settings.detachable || module.can.fit()) { - $body.css('margin-'+position, tempBodyMargin + 'px'); - } - $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, tempBodyMargin + 'px'); - }, - clickaway: function() { - if (!settings.detachable) { - $module - .on('mousedown' + elementEventNamespace, module.event.mousedown) - ; - } - $dimmer - .on('mousedown' + elementEventNamespace, module.event.mousedown) - ; - $dimmer - .on('mouseup' + elementEventNamespace, module.event.mouseup) - ; - }, - dimmerSettings: function() { - if($.fn.dimmer === undefined) { - module.error(error.dimmer); - return; - } - var - defaultSettings = { - debug : settings.debug, - dimmerName : 'modals', - closable : 'auto', - useFlex : module.can.useFlex(), - duration : { - show : settings.duration, - hide : settings.duration - } - }, - dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) - ; - if(settings.inverted) { - dimmerSettings.variation = (dimmerSettings.variation !== undefined) - ? dimmerSettings.variation + ' inverted' - : 'inverted' - ; - } - $context.dimmer('setting', dimmerSettings); - }, - dimmerStyles: function() { - if(settings.inverted) { - $dimmer.addClass(className.inverted); - } - else { - $dimmer.removeClass(className.inverted); - } - if(settings.blurring) { - $dimmable.addClass(className.blurring); - } - else { - $dimmable.removeClass(className.blurring); - } - }, - modalOffset: function() { - if (!settings.detachable) { - var canFit = module.can.fit(); - $module - .css({ - top: (!$module.hasClass('aligned') && canFit) - ? $(document).scrollTop() + (module.cache.contextHeight - module.cache.height) / 2 - : !canFit || $module.hasClass('top') - ? $(document).scrollTop() + settings.padding - : $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding), - marginLeft: -(module.cache.width / 2) - }) - ; - } else { - $module - .css({ - marginTop: (!$module.hasClass('aligned') && module.can.fit()) - ? -(module.cache.height / 2) - : settings.padding / 2, - marginLeft: -(module.cache.width / 2) - }) - ; - } - module.verbose('Setting modal offset for legacy mode'); - }, - screenHeight: function() { - if( module.can.fit() ) { - $body.css('height', ''); - } - else if(!$module.hasClass('bottom')) { - module.debug('Modal is taller than page content, resizing page height'); - $body - .css('height', module.cache.height + (settings.padding * 2) ) - ; - } - }, - active: function() { - $module.addClass(className.active + ' ' + className.front); - $otherModals.filter('.' + className.active).removeClass(className.front); - }, - scrolling: function() { - $dimmable.addClass(className.scrolling); - $module.addClass(className.scrolling); - module.unbind.scrollLock(); - }, - legacy: function() { - $module.addClass(className.legacy); - }, - type: function() { - if(module.can.fit()) { - module.verbose('Modal fits on screen'); - if(!module.others.active() && !module.others.animating()) { - module.remove.scrolling(); - module.bind.scrollLock(); - } - } - else if (!$module.hasClass('bottom')){ - module.verbose('Modal cannot fit on screen setting to scrolling'); - module.set.scrolling(); - } else { - module.verbose('Bottom aligned modal not fitting on screen is unsupported for scrolling'); - } - }, - undetached: function() { - $dimmable.addClass(className.undetached); - } - }, - - 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( (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 { - 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; - } - }; - - 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.modal.settings = { - - name : 'Modal', - namespace : 'modal', - - useFlex : 'auto', - offset : 0, - - silent : false, - debug : false, - verbose : false, - performance : true, - - observeChanges : false, - - allowMultiple : false, - detachable : true, - closable : true, - autofocus : true, - restoreFocus : true, - - inverted : false, - blurring : false, - - centered : true, - - dimmerSettings : { - closable : false, - useCSS : true - }, - - // whether to use keyboard shortcuts - keyboardShortcuts: true, - - context : 'body', - - queue : false, - duration : 500, - transition : 'scale', - - // padding with edge of page - padding : 50, - scrollbarWidth: 10, - - // called before show animation - onShow : function(){}, - - // called after show animation - onVisible : function(){}, - - // called before hide animation - onHide : function(){ return true; }, - - // called after hide animation - onHidden : function(){}, - - // called after approve selector match - onApprove : function(){ return true; }, - - // called after deny selector match - onDeny : function(){ return true; }, - - selector : { - close : '.close-modal', - approve : '.actions .positive, .actions .approve, .actions .ok', - deny : '.actions .negative, .actions .deny, .actions .cancel', - modal : '.ui.modal', - dimmer : '> .ui.dimmer', - bodyFixed: '> .ui.fixed.menu, > .ui.right.toast-container, > .ui.right.sidebar' - }, - error : { - dimmer : 'UI Dimmer, a required component is not included in this page', - method : 'The method you called is not defined.', - notFound : 'The element you specified could not be found' - }, - className : { - active : 'active', - animating : 'animating', - blurring : 'blurring', - inverted : 'inverted', - legacy : 'legacy', - loading : 'loading', - scrolling : 'scrolling', - undetached : 'undetached', - front : 'front' - } -}; - - -})( jQuery, window, document ); diff --git a/web_src/fomantic/build/fomantic.js b/web_src/fomantic/build/fomantic.js index 8cc8652482b..484a8a23b4c 100644 --- a/web_src/fomantic/build/fomantic.js +++ b/web_src/fomantic/build/fomantic.js @@ -1,5 +1,4 @@ import './components/api.js'; import './components/dropdown.js'; -import './components/modal.js'; // Hard-forked from Fomantic UI 2.8.7, patches are commented with "GITEA-PATCH" diff --git a/web_src/js/features/admin/common.ts b/web_src/js/features/admin/common.ts index 1b237cda7b9..d58aab58f6e 100644 --- a/web_src/js/features/admin/common.ts +++ b/web_src/js/features/admin/common.ts @@ -1,7 +1,7 @@ import {checkAppUrl} from '../common-page.ts'; import {hideElem, queryElems, showElem, toggleElem} from '../../utils/dom.ts'; import {POST} from '../../modules/fetch.ts'; -import {showFomanticModal} from '../../modules/fomantic/modal.ts'; +import {showModal} from '../../modules/modal.ts'; import {pathEscape} from '../../utils/url.ts'; import {registerGlobalInitFunc} from '../../modules/observer.ts'; @@ -274,7 +274,7 @@ function initAdminNotice() { const pageContent = document.querySelector('.page-content.admin.notice'); if (!pageContent) return; - const detailModal = document.querySelector('#detail-modal')!; + const detailModal = document.querySelector('#detail-modal')!; // Attach view detail modals queryElems(pageContent, '.view-detail', (el) => el.addEventListener('click', (e) => { @@ -282,7 +282,7 @@ function initAdminNotice() { const elNoticeDesc = el.closest('tr')!.querySelector('.notice-description')!; const elModalDesc = detailModal.querySelector('.content pre')!; elModalDesc.textContent = elNoticeDesc.textContent; - showFomanticModal(detailModal); + showModal(detailModal); })); // Select actions diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index 0b2b1f46715..5d8183ba7ea 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -1,5 +1,5 @@ import {addDelegatedEventListener, hideElem, isElemVisible, showElem, toggleElem} from '../utils/dom.ts'; -import {showFomanticModal} from '../modules/fomantic/modal.ts'; +import {showModal} from '../modules/modal.ts'; import {camelize} from 'vue'; import {applyAutoFocus} from './common-page.ts'; @@ -90,7 +90,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) { // to make the template variables get correctly escaped in the URL. e.preventDefault(); const modalSelector = el.getAttribute('data-modal')!; - const elModal = document.querySelector(modalSelector); + const elModal = document.querySelector(modalSelector); if (!elModal) throw new Error('no modal for this action'); const modalAttrPrefix = 'data-modal-'; @@ -123,11 +123,11 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) { } } - showFomanticModal(elModal); + showModal(elModal); } export function initGlobalButtons(): void { - // There are many "cancel button" elements in modal dialogs, Fomantic UI expects they are button-like elements but never submit a form. + // There are many "cancel button" elements in modal dialogs, they are expected to be button-like elements but never submit a form. // However, Gitea misuses the modal dialog and put the cancel buttons inside forms, so we must prevent the form submission. // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") addDelegatedEventListener(document, 'click', 'form button.ui.cancel.button', (_ /* el */, e) => e.preventDefault()); diff --git a/web_src/js/features/common-page.ts b/web_src/js/features/common-page.ts index e4344ef5cfc..268dc6b5c41 100644 --- a/web_src/js/features/common-page.ts +++ b/web_src/js/features/common-page.ts @@ -126,7 +126,7 @@ function autoFocusEnd(el: HTMLInputElement | HTMLTextAreaElement) { export function applyAutoFocus(container: Element) { // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/autofocus // "autofocus" behavior is defined by the standard: when a container (e.g.: dialog) becomes visible, focus the element with "autofocus" attribute - // Fomantic UI already supports it for its modal dialog, we need to cover more cases (e.g.: ".show-panel" button) + // The native modal dialog already supports it, we need to cover more cases (e.g.: ".show-panel" button) // Here is just a simple support, we don't expect more than one element that need "autofocus" appearing in the same container container.querySelector('[autofocus]')?.focus(); // Also, apply our autoFocusEnd behavior diff --git a/web_src/js/features/comp/ConfirmModal.ts b/web_src/js/features/comp/ConfirmModal.ts index 2e4a35f2213..e6d2d6126f8 100644 --- a/web_src/js/features/comp/ConfirmModal.ts +++ b/web_src/js/features/comp/ConfirmModal.ts @@ -1,8 +1,7 @@ import {svg} from '../../svg.ts'; import {html, htmlRaw} from '../../utils/html.ts'; import {createElementFromHTML} from '../../utils/dom.ts'; -import {showFomanticModal} from '../../modules/fomantic/modal.ts'; -import {hideToastsAll} from '../../modules/toast.ts'; +import {showModal} from '../../modules/modal.ts'; const {i18n} = window.config; @@ -12,27 +11,24 @@ type ConfirmModalOptions = { confirmButtonColor?: 'primary' | 'red' | 'green' | 'blue'; }; -export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLElement { +export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLDialogElement { const headerHtml = header ? html`
${header}
` : ''; return createElementFromHTML(html` - + `.trim()); } -export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise { - if (!(modal instanceof HTMLElement)) modal = createConfirmModal(modal); - // hide existing toasts when we need to show a new modal, otherwise the toasts only interfere the UI - // it's fine to do so because the modal is triggered by user's explicit action, so the user should already have read the toast messages - hideToastsAll(); +export function confirmModal(modal: HTMLDialogElement | ConfirmModalOptions): Promise { + if (!(modal instanceof HTMLDialogElement)) modal = createConfirmModal(modal); return new Promise((resolve) => { - showFomanticModal(modal, { + showModal(modal, { onApprove() { resolve(true); }, diff --git a/web_src/js/features/comp/LabelEdit.ts b/web_src/js/features/comp/LabelEdit.ts index e10863e94ff..bc9c47be00d 100644 --- a/web_src/js/features/comp/LabelEdit.ts +++ b/web_src/js/features/comp/LabelEdit.ts @@ -1,5 +1,5 @@ import {queryElems, toggleElem} from '../../utils/dom.ts'; -import {showFomanticModal} from '../../modules/fomantic/modal.ts'; +import {showModal} from '../../modules/modal.ts'; import {submitFormFetchAction} from '../../modules/fetch-action.ts'; import {registerGlobalInitFunc} from '../../modules/observer.ts'; @@ -7,7 +7,7 @@ function nameHasScope(name: string): boolean { return /.*[^/]\/[^/].*/.test(name); } -function initLabelEditModal(elModal: HTMLElement) { +function initLabelEditModal(elModal: HTMLDialogElement) { const elLabelId = elModal.querySelector('input[name="id"]')!; const elNameInput = elModal.querySelector('.label-name-input')!; const elExclusiveField = elModal.querySelector('.label-exclusive-input-field')!; @@ -59,7 +59,7 @@ function initLabelEditModal(elModal: HTMLElement) { form.action = isEdit ? `${curPageLink}/edit` : `${curPageLink}/new`; toggleElem(elIsArchivedField, isEdit); syncModalUi(); - showFomanticModal(elModal, { + showModal(elModal, { onApprove() { if (!form.checkValidity()) { form.reportValidity(); diff --git a/web_src/js/features/repo-branch.ts b/web_src/js/features/repo-branch.ts index 837ed79b3ba..16755c3561f 100644 --- a/web_src/js/features/repo-branch.ts +++ b/web_src/js/features/repo-branch.ts @@ -1,5 +1,5 @@ import {toggleElem} from '../utils/dom.ts'; -import {showFomanticModal} from '../modules/fomantic/modal.ts'; +import {showModal} from '../modules/modal.ts'; import {trString} from '../modules/i18n.ts'; import {registerGlobalEventFunc} from '../modules/observer.ts'; @@ -20,7 +20,7 @@ function initRepoCreateBranchButton() { const fromSpanName = el.getAttribute('data-modal-from-span') || '#modal-create-branch-from-span'; document.querySelector(fromSpanName)!.textContent = el.getAttribute('data-branch-from'); - showFomanticModal(document.querySelector(el.getAttribute('data-modal')!)); + showModal(document.querySelector(el.getAttribute('data-modal')!)!); }); } } diff --git a/web_src/js/features/repo-issue-content.ts b/web_src/js/features/repo-issue-content.ts index 5665da6fe10..ce73509b993 100644 --- a/web_src/js/features/repo-issue-content.ts +++ b/web_src/js/features/repo-issue-content.ts @@ -4,7 +4,8 @@ import {GET, POST} from '../modules/fetch.ts'; import {createElementFromHTML, showElem} from '../utils/dom.ts'; import {parseIssuePageInfo} from '../utils.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; -import {hideFomanticModal, showFomanticModal} from '../modules/fomantic/modal.ts'; +import {hideModal, showModal} from '../modules/modal.ts'; +import {confirmModal} from './comp/ConfirmModal.ts'; import {html, htmlRaw} from '../utils/html.ts'; import type {JQueryElem} from '../types.ts'; @@ -14,8 +15,8 @@ let i18nTextDeleteFromHistory: string; let i18nTextDeleteFromHistoryConfirm: string; function showContentHistoryDetail(issueBaseUrl: string, commentId: string, historyId: string, itemTitleHtml: string) { - const elDetailDialog = createElementFromHTML(html` -