fix(ui): restore Fomantic jQuery plugins after Bootstrap clobbers them (#719)
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 14s
Generic: Project CI / Tests (pull_request) Successful in 32s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 53s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled

Bootstrap 5 still registers jQuery plugins (defineJQueryPlugin) when jQuery is present,
overwriting $.fn.{dropdown,modal,tab,collapse,tooltip,...} — the same names Fomantic-UI
owns. That made $.fn.dropdown.settings undefined, so Gitea's Fomantic init threw
"Cannot set properties of undefined (setting 'fullTextSearch')" and aborted the whole
client init pipeline (dropdowns/modals/tabs broke; the FAB never rendered).

Add vendor/bootstrap/noconflict.ts (imported right after the bundle) that calls each
colliding plugin's .noConflict() to hand the name back to Fomantic. Bootstrap's data-bs-*
API stays functional (it's bound to document events, not the jQuery interface), so the
mobile collapse drawers still work.

Verified via dev deploy: the drop-in built green but the login page surfaced the uncaught
fullTextSearch error + missing FAB; this restores Fomantic.

Authored-by: Moko Consulting
Claude-Session: https://claude.ai/code/session_01D5Zxu4xRRGoh9etzgShP4P
This commit is contained in:
2026-07-21 14:50:56 -05:00
parent e0e5f2f9a7
commit f5bf2321de
2 changed files with 37 additions and 0 deletions
+4
View File
@@ -2,6 +2,10 @@ import '../fomantic/build/fomantic.js';
import '../css/index.css';
// MokoGIT: vendored Bootstrap 5.3.3 JS bundle (incl. Popper; registers the data-bs-* API).
import './vendor/bootstrap/bootstrap.bundle.min.js';
// Hand Fomantic's jQuery plugin names back after Bootstrap's defineJQueryPlugin clobbers
// them (dropdown/modal/tab/…); keeps Bootstrap's data-bs-* API working. MUST stay right
// after the bundle import above.
import './vendor/bootstrap/noconflict.ts';
import {initDashboardRepoList} from './features/dashboard.ts';
import {initGlobalCopyToClipboardListener} from './features/clipboard.ts';
+33
View File
@@ -0,0 +1,33 @@
// MokoGIT: Bootstrap 5 / Fomantic-UI jQuery-plugin no-conflict shim.
//
// Bootstrap 5 dropped its own jQuery dependency but still registers jQuery plugins
// (via `defineJQueryPlugin`) when it detects jQuery on the page. Those plugin names
// — dropdown, modal, tab, collapse, tooltip, popover, toast, button, … — collide with
// Fomantic-UI, which Gitea drives through the very same `$.fn.*` names. The clobber
// makes `$.fn.dropdown.settings` undefined, so Gitea's Fomantic init throws
// (`Cannot set properties of undefined (setting 'fullTextSearch')`) and aborts the
// whole client init pipeline.
//
// Bootstrap keeps the previous implementation on each plugin's `.noConflict()`. Calling
// it hands the name back to Fomantic. We do NOT lose Bootstrap behavior: the mobile
// rewrite drives Bootstrap purely through the `data-bs-*` data-API (collapse drawers,
// table-responsive is CSS-only), which is bound to document-level event handlers at
// bundle load and does not depend on the jQuery interface.
//
// This module MUST be imported immediately AFTER the Bootstrap bundle in index.ts so it
// runs after `defineJQueryPlugin` has clobbered the names.
import $ from 'jquery';
const collidingPlugins = [
'alert', 'button', 'carousel', 'collapse', 'dropdown', 'modal',
'offcanvas', 'popover', 'scrollspy', 'tab', 'toast', 'tooltip',
];
for (const name of collidingPlugins) {
const plugin = ($.fn as Record<string, any>)[name];
if (plugin && typeof plugin.noConflict === 'function') {
// Restores the pre-Bootstrap implementation (Fomantic's, or undefined if Fomantic
// never defined it — harmless, since nothing uses the jQuery interface for it).
plugin.noConflict();
}
}