@jant/core 0.6.14 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/import-site.js +81 -13
- package/bin/commands/migrate.js +55 -0
- package/bin/commands/posts/rebuild-html.js +184 -0
- package/bin/commands/site/snapshot/import.js +14 -10
- package/bin/lib/d1-query.js +23 -5
- package/bin/lib/migration-runner.js +29 -7
- package/bin/lib/node-database.js +32 -0
- package/bin/lib/site-snapshot.js +347 -46
- package/bin/lib/sql-export.js +1 -1
- package/bin/lib/thread-collection-migration.js +326 -0
- package/dist/app-BxCOR3Uc.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-B_eGKN5p.css +2 -0
- package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
- package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
- package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +5 -5
- package/package.json +11 -10
- package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
- package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
- package/src/__tests__/export-hugo-build.test.ts +63 -4
- package/src/__tests__/export-import-roundtrip.test.ts +6 -4
- package/src/__tests__/export-service.test.ts +142 -9
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +2 -0
- package/src/__tests__/import-site-command.test.ts +65 -0
- package/src/__tests__/vite-config.test.ts +45 -0
- package/src/app.tsx +16 -3
- package/src/client/__tests__/collection-created-notice.test.ts +36 -0
- package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
- package/src/client/__tests__/collection-page-actions.test.ts +71 -2
- package/src/client/__tests__/compose-bridge.test.ts +64 -0
- package/src/client/__tests__/feed-video-player.test.ts +16 -1
- package/src/client/__tests__/footnote-rail.test.ts +472 -0
- package/src/client/__tests__/hydrate-partial.test.ts +3 -0
- package/src/client/__tests__/toast.test.ts +18 -1
- package/src/client/collection-created-notice.ts +66 -0
- package/src/client/collection-form-bridge.ts +17 -0
- package/src/client/collection-navigation.ts +44 -0
- package/src/client/collection-page-actions.ts +57 -1
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
- package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
- package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
- package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
- package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-manager-types.ts +9 -3
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +2 -1
- package/src/client/components/jant-compose-dialog.ts +85 -21
- package/src/client/components/jant-compose-editor.ts +13 -7
- package/src/client/components/jant-compose-fullscreen.ts +24 -4
- package/src/client/components/jant-config-editor.ts +722 -0
- package/src/client/components/jant-media-lightbox.ts +100 -18
- package/src/client/components/jant-nav-manager.ts +1283 -54
- package/src/client/components/jant-post-form.ts +3 -3
- package/src/client/components/jant-post-menu.ts +86 -74
- package/src/client/components/nav-manager-types.ts +51 -2
- package/src/client/components/post-form-types.ts +1 -1
- package/src/client/compose-bridge.ts +4 -1
- package/src/client/feed-video-player.ts +62 -2
- package/src/client/footnote-rail.ts +478 -0
- package/src/client/hydrate-partial.ts +2 -0
- package/src/client/media-lightbox-events.ts +63 -0
- package/src/client/site-header-fragment.ts +38 -0
- package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
- package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
- package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
- package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
- package/src/client/tiptap/bubble-menu.ts +88 -10
- package/src/client/tiptap/continuous-lists.ts +102 -0
- package/src/client/tiptap/create-editor.ts +3 -0
- package/src/client/tiptap/extensions.ts +14 -5
- package/src/client/tiptap/markdown-clipboard.ts +608 -11
- package/src/client/tiptap/slash-commands.ts +8 -1
- package/src/client/tiptap/structural-keymap.ts +211 -0
- package/src/client/tiptap/tab-indent.ts +8 -22
- package/src/client/tiptap/table-control-labels.ts +30 -0
- package/src/client/tiptap/table-controls.ts +537 -0
- package/src/client/tiptap/table-size-picker.ts +254 -0
- package/src/client/toast.ts +8 -6
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/d1-query.test.ts +43 -1
- package/src/db/__tests__/migration-runner.test.ts +63 -0
- package/src/db/__tests__/migrations.test.ts +469 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/migrations/0027_old_blue_blade.sql +39 -0
- package/src/db/migrations/0028_superb_post.sql +2 -0
- package/src/db/migrations/0029_boring_magma.sql +1 -0
- package/src/db/migrations/0030_typical_vivisector.sql +50 -0
- package/src/db/migrations/meta/0027_snapshot.json +2511 -0
- package/src/db/migrations/meta/0028_snapshot.json +2511 -0
- package/src/db/migrations/meta/0029_snapshot.json +2519 -0
- package/src/db/migrations/meta/0030_snapshot.json +2541 -0
- package/src/db/migrations/meta/_journal.json +28 -0
- package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
- package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
- package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
- package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
- package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
- package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
- package/src/db/migrations/pg/meta/_journal.json +28 -0
- package/src/db/pg/schema.ts +28 -20
- package/src/db/rehearsal-fixtures/demo-current.json +39 -4
- package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
- package/src/db/schema.ts +28 -20
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +133 -30
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +128 -25
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +128 -25
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +513 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +515 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +515 -12
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/api-settings.test.ts +299 -0
- package/src/lib/__tests__/constants.test.ts +5 -0
- package/src/lib/__tests__/feed-policy.test.ts +25 -0
- package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
- package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
- package/src/lib/__tests__/markdown.test.ts +8 -6
- package/src/lib/__tests__/navigation.test.ts +59 -2
- package/src/lib/__tests__/post-body-html.test.ts +176 -0
- package/src/lib/__tests__/post-display.test.ts +64 -0
- package/src/lib/__tests__/resolve-config.test.ts +113 -1
- package/src/lib/__tests__/summary.test.ts +82 -0
- package/src/lib/__tests__/timeline.test.ts +109 -70
- package/src/lib/__tests__/timezones.test.ts +31 -3
- package/src/lib/__tests__/tiptap-render.test.ts +268 -9
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
- package/src/lib/__tests__/view.test.ts +53 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
- package/src/lib/api-settings.ts +239 -12
- package/src/lib/collection-sort.ts +7 -5
- package/src/lib/constants.ts +2 -0
- package/src/lib/feed-path.ts +25 -0
- package/src/lib/feed-policy.ts +64 -0
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +10 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/markdown-manager.ts +188 -5
- package/src/lib/markdown.ts +10 -3
- package/src/lib/navigation.ts +16 -3
- package/src/lib/post-body-html.ts +131 -0
- package/src/lib/post-display.ts +7 -1
- package/src/lib/render.tsx +9 -2
- package/src/lib/resolve-config.ts +100 -16
- package/src/lib/schemas.ts +133 -1
- package/src/lib/settings-paths.ts +5 -0
- package/src/lib/site-skill.ts +78 -0
- package/src/lib/summary.ts +16 -5
- package/src/lib/timeline.ts +98 -122
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +355 -81
- package/src/lib/view.ts +30 -20
- package/src/lib/worker-response-cache.ts +11 -18
- package/src/middleware/__tests__/public-content-access.test.ts +91 -0
- package/src/middleware/config.ts +2 -2
- package/src/middleware/public-content-access.ts +87 -0
- package/src/node/__tests__/cli-export.test.ts +14 -0
- package/src/node/__tests__/cli-migrate.test.ts +6 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
- package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
- package/src/node/index.ts +2 -1
- package/src/preset.css +16 -2
- package/src/routes/__tests__/compose.test.ts +51 -0
- package/src/routes/__tests__/site-skill.test.ts +112 -0
- package/src/routes/api/__tests__/collections.test.ts +91 -19
- package/src/routes/api/__tests__/mcp.test.ts +65 -1
- package/src/routes/api/__tests__/nav-items.test.ts +91 -0
- package/src/routes/api/__tests__/public-access.test.ts +81 -0
- package/src/routes/api/__tests__/settings.test.ts +217 -0
- package/src/routes/api/collections.ts +22 -26
- package/src/routes/api/export.ts +2 -0
- package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
- package/src/routes/api/internal/post-body-html.ts +43 -0
- package/src/routes/api/internal/sites.ts +24 -0
- package/src/routes/api/nav-items.ts +24 -1
- package/src/routes/api/posts.ts +2 -2
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +208 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +41 -24
- package/src/routes/api/search.ts +3 -0
- package/src/routes/api/settings.ts +33 -0
- package/src/routes/auth/__tests__/setup.test.ts +7 -4
- package/src/routes/compose.tsx +2 -1
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
- package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
- package/src/routes/pages/__tests__/collections.test.ts +9 -9
- package/src/routes/pages/__tests__/preview.test.ts +174 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +27 -7
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +83 -11
- package/src/routes/site-skill.ts +18 -0
- package/src/services/__tests__/auth.test.ts +7 -5
- package/src/services/__tests__/collection.test.ts +159 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +109 -30
- package/src/services/__tests__/post.test.ts +567 -5
- package/src/services/__tests__/search.test.ts +48 -1
- package/src/services/__tests__/settings.test.ts +8 -0
- package/src/services/__tests__/site-admin.test.ts +121 -1
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +3 -2
- package/src/services/collection.ts +226 -167
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +46 -25
- package/src/services/export-theme/layouts/_default/list.html +18 -4
- package/src/services/export-theme/layouts/_default/rss.xml +8 -12
- package/src/services/export-theme/layouts/featured/list.html +5 -4
- package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
- package/src/services/export-theme/styles/main.css +30 -2
- package/src/services/export.ts +91 -80
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/post.ts +874 -271
- package/src/services/search.ts +8 -2
- package/src/services/settings.ts +2 -10
- package/src/services/site-admin.ts +63 -43
- package/src/style-cjk-jp.css +1 -1
- package/src/style-cjk-kr.css +1 -1
- package/src/style-cjk-tc.css +1 -1
- package/src/style-cjk.css +1 -1
- package/src/styles/components.css +540 -0
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +14 -9
- package/src/styles/ui.css +829 -29
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +243 -12
- package/src/types/constants.ts +22 -1
- package/src/types/entities.ts +9 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +8 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +16 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +92 -2
- package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
- package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
- package/src/ui/dash/settings/GeneralContent.tsx +15 -5
- package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
- package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
- package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
- package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
- package/src/ui/feed/LinkCard.tsx +2 -12
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/QuoteCard.tsx +2 -12
- package/src/ui/feed/ThreadPreview.tsx +26 -34
- package/src/ui/feed/TimelineFeed.tsx +2 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
- package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
- package/src/ui/feed/thread-preview-state.ts +5 -7
- package/src/ui/font-themes.ts +74 -26
- package/src/ui/layouts/BaseLayout.tsx +22 -8
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
- package/src/ui/pages/CollectionPage.tsx +101 -22
- package/src/ui/pages/CollectionsPage.tsx +3 -1
- package/src/ui/pages/PostPage.tsx +39 -12
- package/src/ui/pages/SearchPage.tsx +6 -14
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
- package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
- package/src/ui/shared/CollectionDirectory.tsx +13 -9
- package/src/ui/shared/CollectionsManager.tsx +17 -7
- package/src/ui/shared/DraftPreviewBar.tsx +33 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +3 -1
- package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
- package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
- package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
- package/src/ui/shared/collection-management-labels.ts +33 -2
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +46 -0
- package/dist/app-DZDlmh7C.js +0 -6
- package/dist/client/_assets/client-Cevfpm8H.css +0 -2
- package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
- package/src/client/tiptap/wrapping-input-rules.ts +0 -102
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Tufte-style footnote rail for wide public post views.
|
|
3
|
+
*
|
|
4
|
+
* Published HTML always remains a natural trailing endnote list. This module
|
|
5
|
+
* only positions that existing list in the margin when the post's detail or
|
|
6
|
+
* timeline container is wide enough and every note can be laid out safely. It
|
|
7
|
+
* never clones or moves footnote content, so narrow, no-JS, print, reader, and
|
|
8
|
+
* failure states retain the canonical endnote document order.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const FOOTNOTE_BODY_SELECTOR = [
|
|
12
|
+
'[data-post-view] [data-page="post"] [data-post-body].post-detail-body',
|
|
13
|
+
"[data-timeline-item] article[data-post] > [data-post-body].prose",
|
|
14
|
+
].join(", ");
|
|
15
|
+
const FOOTNOTE_CONTAINER_SELECTOR = "[data-post-view], [data-timeline-item]";
|
|
16
|
+
const RAIL_ENABLED_PROPERTY = "--jant-footnote-rail-enabled";
|
|
17
|
+
const DEFAULT_NOTE_GAP_PX = 12;
|
|
18
|
+
const MAX_EXTRA_RAIL_HEIGHT_PX = 160;
|
|
19
|
+
const MAX_EXTRA_RAIL_HEIGHT_RATIO = 0.5;
|
|
20
|
+
const LAYOUT_EPSILON_PX = 0.5;
|
|
21
|
+
|
|
22
|
+
interface FootnoteRailItem {
|
|
23
|
+
anchorTop: number;
|
|
24
|
+
blockSize: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FootnoteRailLayout {
|
|
28
|
+
offsets: number[];
|
|
29
|
+
blockSize: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FootnotePair {
|
|
33
|
+
reference: HTMLAnchorElement;
|
|
34
|
+
note: globalThis.HTMLLIElement;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface FootnoteStructure {
|
|
38
|
+
section: HTMLElement;
|
|
39
|
+
list: globalThis.HTMLOListElement;
|
|
40
|
+
pairs: FootnotePair[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface RailController extends FootnoteStructure {
|
|
44
|
+
body: HTMLElement;
|
|
45
|
+
container: HTMLElement;
|
|
46
|
+
observer: globalThis.ResizeObserver;
|
|
47
|
+
abortController: AbortController;
|
|
48
|
+
rafId: number;
|
|
49
|
+
containerInlineSize: number | null;
|
|
50
|
+
correctedInitialHash: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const controllers = new Map<HTMLElement, RailController>();
|
|
54
|
+
let printActive = false;
|
|
55
|
+
|
|
56
|
+
function finiteOr(value: number, fallback: number): number {
|
|
57
|
+
return Number.isFinite(value) ? value : fallback;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Place notes at their references while greedily preventing collisions.
|
|
62
|
+
*
|
|
63
|
+
* @param items - Reference offsets and measured note block sizes
|
|
64
|
+
* @param gap - Minimum gap between adjacent notes in pixels
|
|
65
|
+
* @returns Per-note offsets and the total rail block size
|
|
66
|
+
*/
|
|
67
|
+
function computeFootnoteRailLayout(
|
|
68
|
+
items: FootnoteRailItem[],
|
|
69
|
+
gap: number,
|
|
70
|
+
): FootnoteRailLayout {
|
|
71
|
+
const safeGap = Math.max(0, finiteOr(gap, DEFAULT_NOTE_GAP_PX));
|
|
72
|
+
const offsets: number[] = [];
|
|
73
|
+
let previousBottom = 0;
|
|
74
|
+
|
|
75
|
+
for (const [index, item] of items.entries()) {
|
|
76
|
+
const anchorTop = Math.max(0, finiteOr(item.anchorTop, 0));
|
|
77
|
+
const blockSize = Math.max(0, finiteOr(item.blockSize, 0));
|
|
78
|
+
const offset =
|
|
79
|
+
index === 0 ? anchorTop : Math.max(anchorTop, previousBottom + safeGap);
|
|
80
|
+
|
|
81
|
+
offsets.push(offset);
|
|
82
|
+
previousBottom = offset + blockSize;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { offsets, blockSize: previousBottom };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function decodeFragment(href: string): string | null {
|
|
89
|
+
if (!href.startsWith("#") || href.length === 1) return null;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
return decodeURIComponent(href.slice(1));
|
|
93
|
+
} catch {
|
|
94
|
+
return href.slice(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function hasRole(element: globalThis.Element, role: string): boolean {
|
|
99
|
+
return (element.getAttribute("role") ?? "").split(/\s+/).includes(role);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function findFootnoteStructure(body: HTMLElement): FootnoteStructure | null {
|
|
103
|
+
const sections = Array.from(body.children).filter(
|
|
104
|
+
(child): child is HTMLElement =>
|
|
105
|
+
child instanceof HTMLElement &&
|
|
106
|
+
child.classList.contains("footnote-endnotes") &&
|
|
107
|
+
hasRole(child, "doc-endnotes"),
|
|
108
|
+
);
|
|
109
|
+
if (sections.length !== 1) return null;
|
|
110
|
+
|
|
111
|
+
const section = sections[0];
|
|
112
|
+
if (!section) return null;
|
|
113
|
+
|
|
114
|
+
const lists = Array.from(section.children).filter(
|
|
115
|
+
(child): child is globalThis.HTMLOListElement =>
|
|
116
|
+
child instanceof globalThis.HTMLOListElement &&
|
|
117
|
+
child.classList.contains("footnote-list"),
|
|
118
|
+
);
|
|
119
|
+
if (lists.length !== 1) return null;
|
|
120
|
+
|
|
121
|
+
const list = lists[0];
|
|
122
|
+
if (!list) return null;
|
|
123
|
+
|
|
124
|
+
const notes = Array.from(list.children).filter(
|
|
125
|
+
(child): child is globalThis.HTMLLIElement =>
|
|
126
|
+
child instanceof globalThis.HTMLLIElement &&
|
|
127
|
+
child.classList.contains("footnote"),
|
|
128
|
+
);
|
|
129
|
+
if (notes.length === 0 || notes.length !== list.children.length) return null;
|
|
130
|
+
|
|
131
|
+
const notesById = new Map<string, globalThis.HTMLLIElement>();
|
|
132
|
+
for (const note of notes) {
|
|
133
|
+
if (!note.id || notesById.has(note.id)) return null;
|
|
134
|
+
if (document.getElementById(note.id) !== note) return null;
|
|
135
|
+
notesById.set(note.id, note);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const firstReferencesByTarget = new Map<string, HTMLAnchorElement>();
|
|
139
|
+
for (const reference of body.querySelectorAll<HTMLAnchorElement>(
|
|
140
|
+
'a[role~="doc-noteref"][href^="#"]',
|
|
141
|
+
)) {
|
|
142
|
+
const targetId = decodeFragment(reference.getAttribute("href") ?? "");
|
|
143
|
+
if (
|
|
144
|
+
targetId &&
|
|
145
|
+
notesById.has(targetId) &&
|
|
146
|
+
!firstReferencesByTarget.has(targetId)
|
|
147
|
+
) {
|
|
148
|
+
firstReferencesByTarget.set(targetId, reference);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const pairs: FootnotePair[] = [];
|
|
153
|
+
for (const note of notes) {
|
|
154
|
+
const reference = firstReferencesByTarget.get(note.id);
|
|
155
|
+
if (!reference) return null;
|
|
156
|
+
pairs.push({ reference, note });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { section, list, pairs };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getObservedInlineSize(entry: globalThis.ResizeObserverEntry): number {
|
|
163
|
+
const box = Array.isArray(entry.contentBoxSize)
|
|
164
|
+
? entry.contentBoxSize[0]
|
|
165
|
+
: entry.contentBoxSize;
|
|
166
|
+
return finiteOr(box?.inlineSize ?? entry.contentRect.width, 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatPixels(value: number): string {
|
|
170
|
+
return `${Math.round(value * 1000) / 1000}px`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function clearRuntimeLayout(controller: RailController): void {
|
|
174
|
+
controller.body.classList.remove(
|
|
175
|
+
"footnote-rail-measuring",
|
|
176
|
+
"footnote-rail-ready",
|
|
177
|
+
);
|
|
178
|
+
controller.body.style.removeProperty("min-block-size");
|
|
179
|
+
controller.list.style.removeProperty("--footnote-rail-block-size");
|
|
180
|
+
|
|
181
|
+
for (const { note } of controller.pairs) {
|
|
182
|
+
note.style.removeProperty("--footnote-rail-y");
|
|
183
|
+
note.style.removeProperty("--footnote-rail-number");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function setRuntimeNumbers(controller: RailController): void {
|
|
188
|
+
let number = controller.list.start;
|
|
189
|
+
|
|
190
|
+
for (const { note } of controller.pairs) {
|
|
191
|
+
if (note.hasAttribute("value")) number = note.value;
|
|
192
|
+
note.style.setProperty("--footnote-rail-number", `"${number}"`);
|
|
193
|
+
number += 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getRailGap(list: globalThis.HTMLOListElement): number {
|
|
198
|
+
const gap = Number.parseFloat(getComputedStyle(list).rowGap);
|
|
199
|
+
return Number.isFinite(gap) ? gap : DEFAULT_NOTE_GAP_PX;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function railCanGrowTo(contentSize: number, railSize: number): boolean {
|
|
203
|
+
const extra = Math.max(0, railSize - contentSize);
|
|
204
|
+
const allowedExtra = Math.max(
|
|
205
|
+
MAX_EXTRA_RAIL_HEIGHT_PX,
|
|
206
|
+
contentSize * MAX_EXTRA_RAIL_HEIGHT_RATIO,
|
|
207
|
+
);
|
|
208
|
+
return extra <= allowedExtra + LAYOUT_EPSILON_PX;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function correctInitialHash(controller: RailController): void {
|
|
212
|
+
if (controller.correctedInitialHash) return;
|
|
213
|
+
controller.correctedInitialHash = true;
|
|
214
|
+
|
|
215
|
+
const targetId = decodeFragment(globalThis.location.hash);
|
|
216
|
+
if (!targetId) return;
|
|
217
|
+
|
|
218
|
+
const target = controller.pairs.find(
|
|
219
|
+
({ note }) => note.id === targetId,
|
|
220
|
+
)?.note;
|
|
221
|
+
if (!target) return;
|
|
222
|
+
|
|
223
|
+
requestAnimationFrame(() => {
|
|
224
|
+
if (!controller.body.classList.contains("footnote-rail-ready")) return;
|
|
225
|
+
target.scrollIntoView({ behavior: "auto", block: "nearest" });
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isRailEnabled(body: HTMLElement): boolean {
|
|
230
|
+
return (
|
|
231
|
+
!printActive &&
|
|
232
|
+
getComputedStyle(body).getPropertyValue(RAIL_ENABLED_PROPERTY).trim() ===
|
|
233
|
+
"1"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function layoutController(controller: RailController): void {
|
|
238
|
+
if (!controller.body.isConnected) {
|
|
239
|
+
destroyController(controller);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!isRailEnabled(controller.body)) {
|
|
244
|
+
clearRuntimeLayout(controller);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
clearRuntimeLayout(controller);
|
|
249
|
+
controller.body.classList.add("footnote-rail-measuring");
|
|
250
|
+
setRuntimeNumbers(controller);
|
|
251
|
+
|
|
252
|
+
const bodyRect = controller.body.getBoundingClientRect();
|
|
253
|
+
const contentBlockSize = Math.max(0, bodyRect.height);
|
|
254
|
+
const items = controller.pairs.map(({ reference, note }) => ({
|
|
255
|
+
anchorTop: reference.getBoundingClientRect().top - bodyRect.top,
|
|
256
|
+
blockSize: note.getBoundingClientRect().height,
|
|
257
|
+
}));
|
|
258
|
+
|
|
259
|
+
if (items.some(({ blockSize }) => blockSize <= LAYOUT_EPSILON_PX)) {
|
|
260
|
+
clearRuntimeLayout(controller);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const layout = computeFootnoteRailLayout(items, getRailGap(controller.list));
|
|
265
|
+
if (!railCanGrowTo(contentBlockSize, layout.blockSize)) {
|
|
266
|
+
clearRuntimeLayout(controller);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const [index, { note }] of controller.pairs.entries()) {
|
|
271
|
+
note.style.setProperty(
|
|
272
|
+
"--footnote-rail-y",
|
|
273
|
+
formatPixels(layout.offsets[index] ?? 0),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
controller.list.style.setProperty(
|
|
277
|
+
"--footnote-rail-block-size",
|
|
278
|
+
formatPixels(layout.blockSize),
|
|
279
|
+
);
|
|
280
|
+
controller.body.style.setProperty(
|
|
281
|
+
"min-block-size",
|
|
282
|
+
formatPixels(Math.max(contentBlockSize, layout.blockSize)),
|
|
283
|
+
);
|
|
284
|
+
controller.body.classList.remove("footnote-rail-measuring");
|
|
285
|
+
controller.body.classList.add("footnote-rail-ready");
|
|
286
|
+
correctInitialHash(controller);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function scheduleController(controller: RailController): void {
|
|
290
|
+
if (controller.rafId !== 0) return;
|
|
291
|
+
|
|
292
|
+
controller.rafId = requestAnimationFrame(() => {
|
|
293
|
+
controller.rafId = 0;
|
|
294
|
+
layoutController(controller);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function destroyController(controller: RailController): void {
|
|
299
|
+
if (controller.rafId !== 0) cancelAnimationFrame(controller.rafId);
|
|
300
|
+
controller.observer.disconnect();
|
|
301
|
+
controller.abortController.abort();
|
|
302
|
+
clearRuntimeLayout(controller);
|
|
303
|
+
controllers.delete(controller.body);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function structuresMatch(
|
|
307
|
+
controller: RailController,
|
|
308
|
+
structure: FootnoteStructure,
|
|
309
|
+
): boolean {
|
|
310
|
+
return (
|
|
311
|
+
controller.section === structure.section &&
|
|
312
|
+
controller.list === structure.list &&
|
|
313
|
+
controller.pairs.length === structure.pairs.length &&
|
|
314
|
+
controller.pairs.every(
|
|
315
|
+
(pair, index) =>
|
|
316
|
+
pair.note === structure.pairs[index]?.note &&
|
|
317
|
+
pair.reference === structure.pairs[index]?.reference,
|
|
318
|
+
)
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function createController(
|
|
323
|
+
body: HTMLElement,
|
|
324
|
+
structure: FootnoteStructure,
|
|
325
|
+
): RailController | null {
|
|
326
|
+
if (!("ResizeObserver" in globalThis)) return null;
|
|
327
|
+
|
|
328
|
+
const container = body.closest<HTMLElement>(FOOTNOTE_CONTAINER_SELECTOR);
|
|
329
|
+
if (!container) return null;
|
|
330
|
+
|
|
331
|
+
const abortController = new AbortController();
|
|
332
|
+
const observer = new globalThis.ResizeObserver((entries) => {
|
|
333
|
+
const controller = controllers.get(body);
|
|
334
|
+
if (!controller) return;
|
|
335
|
+
let needsLayout = false;
|
|
336
|
+
|
|
337
|
+
for (const entry of entries) {
|
|
338
|
+
if (entry.target === container) {
|
|
339
|
+
const inlineSize = getObservedInlineSize(entry);
|
|
340
|
+
if (
|
|
341
|
+
controller.containerInlineSize === null ||
|
|
342
|
+
Math.abs(controller.containerInlineSize - inlineSize) >
|
|
343
|
+
LAYOUT_EPSILON_PX
|
|
344
|
+
) {
|
|
345
|
+
controller.containerInlineSize = inlineSize;
|
|
346
|
+
needsLayout = true;
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
needsLayout = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (needsLayout) scheduleController(controller);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const controller: RailController = {
|
|
357
|
+
body,
|
|
358
|
+
container,
|
|
359
|
+
...structure,
|
|
360
|
+
observer,
|
|
361
|
+
abortController,
|
|
362
|
+
rafId: 0,
|
|
363
|
+
containerInlineSize: null,
|
|
364
|
+
correctedInitialHash: false,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
observer.observe(container);
|
|
368
|
+
for (const child of body.children) {
|
|
369
|
+
if (child !== structure.section) observer.observe(child);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const image of body.querySelectorAll("img")) {
|
|
373
|
+
if (image.complete) continue;
|
|
374
|
+
image.addEventListener("load", () => scheduleController(controller), {
|
|
375
|
+
once: true,
|
|
376
|
+
signal: abortController.signal,
|
|
377
|
+
});
|
|
378
|
+
image.addEventListener("error", () => scheduleController(controller), {
|
|
379
|
+
once: true,
|
|
380
|
+
signal: abortController.signal,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
controllers.set(body, controller);
|
|
385
|
+
scheduleController(controller);
|
|
386
|
+
return controller;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function collectFootnoteBodies(
|
|
390
|
+
root: globalThis.Document | globalThis.Element,
|
|
391
|
+
): HTMLElement[] {
|
|
392
|
+
const bodies: HTMLElement[] = [];
|
|
393
|
+
if (
|
|
394
|
+
root instanceof globalThis.Element &&
|
|
395
|
+
root.matches(FOOTNOTE_BODY_SELECTOR)
|
|
396
|
+
) {
|
|
397
|
+
bodies.push(root as HTMLElement);
|
|
398
|
+
}
|
|
399
|
+
bodies.push(...root.querySelectorAll<HTMLElement>(FOOTNOTE_BODY_SELECTOR));
|
|
400
|
+
return bodies;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Initialize or refresh progressive footnote rails inside a document fragment.
|
|
405
|
+
*
|
|
406
|
+
* @param root - Document or swapped fragment to scan
|
|
407
|
+
* @returns Nothing
|
|
408
|
+
*/
|
|
409
|
+
export function initFootnoteRails(
|
|
410
|
+
root: globalThis.Document | globalThis.Element = document,
|
|
411
|
+
): void {
|
|
412
|
+
for (const controller of controllers.values()) {
|
|
413
|
+
if (!controller.body.isConnected) destroyController(controller);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const body of collectFootnoteBodies(root)) {
|
|
417
|
+
const structure = findFootnoteStructure(body);
|
|
418
|
+
const existing = controllers.get(body);
|
|
419
|
+
|
|
420
|
+
if (!structure) {
|
|
421
|
+
if (existing) destroyController(existing);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (existing && structuresMatch(existing, structure)) {
|
|
426
|
+
scheduleController(existing);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (existing) destroyController(existing);
|
|
431
|
+
createController(body, structure);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function scheduleAllControllers(): void {
|
|
436
|
+
for (const controller of controllers.values()) scheduleController(controller);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
globalThis.addEventListener("beforeprint", () => {
|
|
440
|
+
printActive = true;
|
|
441
|
+
for (const controller of controllers.values()) clearRuntimeLayout(controller);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
globalThis.addEventListener("afterprint", () => {
|
|
445
|
+
printActive = false;
|
|
446
|
+
scheduleAllControllers();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (document.fonts) {
|
|
450
|
+
void document.fonts.ready.then(scheduleAllControllers);
|
|
451
|
+
document.fonts.addEventListener("loadingdone", scheduleAllControllers);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (document.readyState === "loading") {
|
|
455
|
+
document.addEventListener(
|
|
456
|
+
"DOMContentLoaded",
|
|
457
|
+
() => initFootnoteRails(document),
|
|
458
|
+
{ once: true },
|
|
459
|
+
);
|
|
460
|
+
} else {
|
|
461
|
+
initFootnoteRails(document);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export const __testOnly = {
|
|
465
|
+
computeFootnoteRailLayout,
|
|
466
|
+
decodeFragment,
|
|
467
|
+
findFootnoteStructure,
|
|
468
|
+
railCanGrowTo,
|
|
469
|
+
resetControllers(): void {
|
|
470
|
+
for (const controller of [...controllers.values()]) {
|
|
471
|
+
destroyController(controller);
|
|
472
|
+
}
|
|
473
|
+
printActive = false;
|
|
474
|
+
},
|
|
475
|
+
getControllerCount(): number {
|
|
476
|
+
return controllers.size;
|
|
477
|
+
},
|
|
478
|
+
};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { setupThreadContexts } from "./thread-context.js";
|
|
16
16
|
import { initFeedVideoPlayer } from "./feed-video-player.js";
|
|
17
17
|
import { initPrecomputedWaveforms } from "./audio-player.js";
|
|
18
|
+
import { initFootnoteRails } from "./footnote-rail.js";
|
|
18
19
|
|
|
19
20
|
export function hydratePartial(
|
|
20
21
|
root: globalThis.Document | globalThis.Element,
|
|
@@ -22,4 +23,5 @@ export function hydratePartial(
|
|
|
22
23
|
setupThreadContexts(root);
|
|
23
24
|
initFeedVideoPlayer(root);
|
|
24
25
|
initPrecomputedWaveforms(root);
|
|
26
|
+
initFootnoteRails(root);
|
|
25
27
|
}
|
|
@@ -1 +1,64 @@
|
|
|
1
1
|
export const MEDIA_LIGHTBOX_TOGGLE_EVENT = "jant:media-lightbox-toggle";
|
|
2
|
+
|
|
3
|
+
export const MEDIA_VIDEO_PLAYBACK_INTENT_EVENT =
|
|
4
|
+
"jant:media-video-playback-intent";
|
|
5
|
+
|
|
6
|
+
export interface MediaVideoPlaybackIntentDetail {
|
|
7
|
+
mediaId: string;
|
|
8
|
+
paused: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const manuallyPausedMediaIds = new Set<string>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check whether the user explicitly paused a media item on this page.
|
|
15
|
+
*
|
|
16
|
+
* @param mediaId - Stable media ID to inspect
|
|
17
|
+
* @returns `true` when the media should remain paused
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* isMediaVideoPlaybackPaused("med_123");
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function isMediaVideoPlaybackPaused(
|
|
25
|
+
mediaId: string | undefined,
|
|
26
|
+
): boolean {
|
|
27
|
+
const normalizedMediaId = mediaId?.trim();
|
|
28
|
+
return !!normalizedMediaId && manuallyPausedMediaIds.has(normalizedMediaId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Store and publish the user's explicit playback intent for a media item.
|
|
33
|
+
*
|
|
34
|
+
* @param mediaId - Stable media ID to update
|
|
35
|
+
* @param paused - Whether the media should remain paused
|
|
36
|
+
* @returns Nothing
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* setMediaVideoPlaybackPaused("med_123", true);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function setMediaVideoPlaybackPaused(
|
|
44
|
+
mediaId: string | undefined,
|
|
45
|
+
paused: boolean,
|
|
46
|
+
): void {
|
|
47
|
+
const normalizedMediaId = mediaId?.trim();
|
|
48
|
+
if (!normalizedMediaId) return;
|
|
49
|
+
|
|
50
|
+
if (paused) {
|
|
51
|
+
manuallyPausedMediaIds.add(normalizedMediaId);
|
|
52
|
+
} else {
|
|
53
|
+
manuallyPausedMediaIds.delete(normalizedMediaId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
document.dispatchEvent(
|
|
57
|
+
new CustomEvent<MediaVideoPlaybackIntentDetail>(
|
|
58
|
+
MEDIA_VIDEO_PLAYBACK_INTENT_EVENT,
|
|
59
|
+
{
|
|
60
|
+
detail: { mediaId: normalizedMediaId, paused },
|
|
61
|
+
},
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace the live site header with a server-rendered navigation fragment.
|
|
3
|
+
*
|
|
4
|
+
* @param headerHtml - Trusted fragment returned by Jant's navigation API
|
|
5
|
+
* @returns Nothing
|
|
6
|
+
* @example
|
|
7
|
+
* applySiteHeaderHtml(response.headerHtml);
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { destroySiteHeaderNav, initSiteHeaderNav } from "./site-header-nav.js";
|
|
11
|
+
|
|
12
|
+
export function applySiteHeaderHtml(headerHtml: string | undefined): void {
|
|
13
|
+
if (!headerHtml) return;
|
|
14
|
+
|
|
15
|
+
const template = document.createElement("template");
|
|
16
|
+
template.innerHTML = headerHtml.trim();
|
|
17
|
+
|
|
18
|
+
const selectors = [
|
|
19
|
+
'[data-site-header-fragment="header"]',
|
|
20
|
+
'[data-site-header-fragment="drawer-backdrop"]',
|
|
21
|
+
'[data-site-header-fragment="drawer"]',
|
|
22
|
+
];
|
|
23
|
+
let replaced = false;
|
|
24
|
+
|
|
25
|
+
destroySiteHeaderNav(document);
|
|
26
|
+
for (const selector of selectors) {
|
|
27
|
+
const current = document.querySelector<HTMLElement>(selector);
|
|
28
|
+
const next = template.content.querySelector<HTMLElement>(selector);
|
|
29
|
+
if (!current || !next) continue;
|
|
30
|
+
|
|
31
|
+
current.replaceWith(next);
|
|
32
|
+
replaced = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (replaced) {
|
|
36
|
+
initSiteHeaderNav(document);
|
|
37
|
+
}
|
|
38
|
+
}
|