refactor: replace Fomantic modal with native dialog (#39846)

Replace Fomantic's hard-forked jQuery modal with native `<dialog>`
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 `<body>` 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 `<dialog class="modal-dialog">`, 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 <wxiaoguang@gmail.com>
This commit is contained in:
silverwindandwxiaoguang authored and GitHub committed 2026-10-20 12:32:27 +02:00
1 parent 7610336d74
commit e645afbb3f
95 files changed
+607 -1719

No files matched your search

@@ -1,15 +1,15 @@
{{template "devtest/devtest-header"}}
<div class="page-content devtest ui container">
<div class="modal-buttons flex-text-block tw-flex-wrap"></div>
<div id="test-modal-form-1" class="ui mini modal">
<dialog id="test-modal-form-1" class="modal-dialog">
<div class="header">Form dialog (layout 1)</div>
<form class="content" method="post" action="?mock_response_delay=1">
<div class="ui input tw-w-full"><input name="user_input"></div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form>
</div>
</dialog>
<div id="test-modal-form-2" class="ui mini modal">
<dialog id="test-modal-form-2" class="modal-dialog">
<div class="header">Form dialog (layout 2)</div>
<form method="post" action="?mock_response_delay=1">
<div class="content">
@@ -17,9 +17,9 @@
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</div>
</form>
</div>
</dialog>
<div id="test-modal-form-3" class="ui mini modal">
<dialog id="test-modal-form-3" class="modal-dialog">
<div class="header">Form dialog (layout 3)</div>
<form method="post" action="?mock_response_delay=1">
<div class="content">
@@ -27,9 +27,9 @@
</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form>
</div>
</dialog>
<div id="test-modal-form-4" class="ui mini modal">
<dialog id="test-modal-form-4" class="modal-dialog">
<div class="header">Form dialog (layout 4)</div>
<div class="content">
<div class="ui input tw-w-full"><input name="user_input"></div>
@@ -37,9 +37,9 @@
<form method="post" action="?mock_response_delay=1">
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form>
</div>
</dialog>
<div id="test-modal-form-5" class="ui mini modal">
<dialog id="test-modal-form-5" class="modal-dialog">
<div class="header">Form dialog (layout 5)</div>
<div class="content">
<form method="post" action="?mock_response_delay=1">
@@ -47,27 +47,27 @@
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form>
</div>
</div>
</dialog>
<div class="ui g-modal-confirm modal" id="test-modal-default">
<dialog class="modal-dialog" id="test-modal-default">
<div class="header">{{svg "octicon-file"}} Default dialog <span>title</span></div>
<div class="content">
very long aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
{{template "base/modal_actions_confirm"}}
</div>
</dialog>
<div class="ui g-modal-confirm modal" id="test-modal-confirm">
<dialog class="modal-dialog" id="test-modal-confirm">
<div class="header">Confirm dialog</div>
<div class="content">hello, this is the modal dialog content</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</div>
</dialog>
<div class="ui g-modal-confirm modal" id="test-modal-danger">
<dialog class="modal-dialog" id="test-modal-danger">
<div class="header">dangerous action dialog <button type="button" class="close-modal"></button></div>
<div class="content">hello, this is the modal dialog content, this is a dangerous operation</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" "I know and must do this is dangerous operation")}}
</div>
</dialog>
<div class="divider"></div>
@@ -79,7 +79,7 @@
data-modal-a.text-content="fill with attr"
>test-modal-fill-values</button>
<div id="test-modal-fill-values" class="ui mini modal" data-skip-button>
<dialog id="test-modal-fill-values" class="modal-dialog" data-skip-button>
<div class="header">Modal dialog (fill values)</div>
<form class="content">
<div id="fill-by-id"></div>
@@ -89,6 +89,6 @@
<a href="#">link</a>
{{template "base/modal_actions_confirm" dict}}
</form>
</div>
</dialog>
</div>
{{template "devtest/devtest-footer"}}