Skip to main content

max / makenotwork

3.9 KB · 88 lines History Blame Raw
1 // MNW frontend core, entry point.
2 //
3 // Loaded once, globally, as a `<script type="module">` in place of the retired
4 // `static/mnw.js`. It installs every delegated listener the old god-file did
5 // (toasts, HTMX glue, the data-action + hx-callback dispatchers, copy-link,
6 // keyboard, restart banner) and bridges the shared primitives onto
7 // `window` so the not-yet-migrated `static/*.js` files keep resolving them.
8 //
9 // As feature files migrate into frontend/src, drop their entries from the
10 // bridge and register their handlers via `dispatch.register()` instead of a
11 // global; the `frontend_globals` seal ratchets the legacy count down.
12
13 import { escapeHtml, escapeAttr } from './dom.ts';
14 import { csrfHeaders, apiError } from './net.ts';
15 import { showToast, initToasts } from './toast.ts';
16 import { safeGet, safeSet } from './storage.ts';
17 import { copyWithFeedback, initCopyLink } from './clipboard.ts';
18 import { resolveHtmxLoadingButton, withLoadingState } from './loading.ts';
19 import { initActionDispatcher, initAfterRequestDispatcher } from './dispatch.ts';
20 import { initCartBadge } from './cart-badge.ts';
21 import { initShowing } from './showing.ts';
22 import { toggleShortcutsHelp, initKeyboard } from './keyboard.ts';
23 import { initRestartBanner } from './restart-banner.ts';
24 import { initHtmxGlue } from './htmx-glue.ts';
25 import { timing } from './timing.ts';
26
27 // Common, lightweight islands are registered globally here (side-effect import
28 // defines the custom element). Heavy or page-specific islands (media player,
29 // uploader) load per-page instead. Custom elements auto-upgrade on connect, so
30 // carousels in HTMX-swapped content wire themselves with no extra glue.
31 import '../islands/carousel.ts';
32 // Image uploader appears in HTMX-swapped wizard steps, so it's registered
33 // globally (auto-upgrades on swap). The heavier upload flows (item audio/version
34 // queue, gallery, bundle) still use the legacy static/upload.js until migrated.
35 import '../islands/uploader/image-uploader.ts';
36 // SyncKit dashboard: the keys-endpoint secret action. Registers a dispatcher
37 // verb (no window global) and is inert on pages without the button.
38 import '../islands/synckit-keys-secret.ts';
39
40 /**
41 * Legacy bridge, the not-yet-migrated `static/*.js` files call these by their
42 * historical global names. `mnw.js` exposed them as classic-script globals
43 * (function declarations / `window.*`); the module re-exposes them so the
44 * cutover is behavior-preserving. Retire each entry as its consumers move into
45 * frontend/src.
46 */
47 function installLegacyBridge(): void {
48 const w = window as unknown as Record<string, unknown>;
49 w.escapeHtml = escapeHtml;
50 w.escapeAttr = escapeAttr;
51 w.csrfHeaders = csrfHeaders;
52 w.showToast = showToast;
53 w.safeStorageGet = safeGet;
54 w.safeStorageSet = safeSet;
55 w.toggleShortcutsHelp = toggleShortcutsHelp;
56 w.copyWithFeedback = copyWithFeedback;
57 // One namespace rather than a name each: the durations and the two helpers
58 // that spend them (`timing.debounce`, `timing.clearStatusLater`) are what the
59 // legacy scripts reach for, and the globals ratchet counts names.
60 w.timing = timing;
61 w.resolveHtmxLoadingButton = resolveHtmxLoadingButton;
62 w.withLoadingState = withLoadingState;
63 // Historical name; wraps apiError so the (response, fallback) signature and
64 // the Promise<string> return match the old global exactly.
65 w.apiErrorMessage = (response: Response | null | undefined, fallback?: string) => apiError(response, fallback);
66 }
67
68 function initCore(): void {
69 installLegacyBridge();
70 initToasts();
71 initHtmxGlue();
72 initActionDispatcher();
73 initAfterRequestDispatcher();
74 initCopyLink();
75 initKeyboard();
76 initCartBadge();
77 initShowing();
78 initRestartBanner();
79 }
80
81 // A module script is deferred, so the DOM is already parsed by the time this
82 // runs; the readyState guard is belt-and-suspenders.
83 if (document.readyState === 'loading') {
84 document.addEventListener('DOMContentLoaded', initCore);
85 } else {
86 initCore();
87 }
88