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

+7 -8
View File
@@ -1,6 +1,6 @@
import {registerGlobalInitFunc} from './observer.ts';
import {createElementFromHTML, hideElem, toggleElem} from '../utils/dom.ts';
import {showFomanticModal} from './fomantic/modal.ts';
import {showModal} from './modal.ts';
import {html, htmlRaw} from '../utils/html.ts';
type ShortcutHandler = (el: HTMLElement) => boolean;
@@ -69,10 +69,10 @@ function getShortcutTextContent(elem: Element): string {
}
function showShortcutHelp() {
let modal = document.querySelector<HTMLElement>('#global-shortcut-help');
let modal = document.querySelector<HTMLDialogElement>('#global-shortcut-help');
if (!modal) {
modal = createElementFromHTML<HTMLElement>(html`
<div id="global-shortcut-help" class="ui small modal" role="dialog" aria-modal="true">
modal = createElementFromHTML<HTMLDialogElement>(html`
<dialog id="global-shortcut-help" class="modal-dialog">
<div class="header">
Keyboard Shortcuts
<button type="button" class="close-modal"></button>
@@ -82,9 +82,8 @@ function showShortcutHelp() {
<tbody class="shortcut-list"></tbody>
</table>
</div>
</div>
</dialog>
`);
document.body.append(modal);
}
const seenShortcuts = new Set<string>();
@@ -112,7 +111,7 @@ function showShortcutHelp() {
}
list.append(createElementFromHTML(html`<tr><td>${htmlRaw(keysHtml)}</td><td>${label}</td></tr>`));
}
showFomanticModal(modal);
showModal(modal);
}
export function registerShortcutHandler(name: string, handler: ShortcutHandler) {
@@ -151,7 +150,7 @@ export function initGlobalShortcut() {
// Don't trigger shortcuts when typing in input fields or contenteditable areas
let shouldSkip = target.matches('input, textarea, select') || target.isContentEditable;
// Skip shortcuts when a modal or dropdown is open (active), to avoid interfering with their own keyboard handling
shouldSkip = shouldSkip || Boolean(document.querySelector('.ui.modal.active') || document.querySelector('.ui.dropdown.active'));
shouldSkip = shouldSkip || Boolean(document.querySelector('.modal-dialog[open], .ui.dropdown.active'));
if (shouldSkip) {
shortcutPresses.length = 0;
return;