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

+4 -4
View File
@@ -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<HTMLDialogElement>(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());