@jant/core 0.6.14 → 0.6.16
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 +88 -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-C75Zc8dD.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C7_4oab9.js} +7774 -6425
- package/dist/client/.vite/manifest.json +8 -8
- package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
- package/dist/client/_assets/{client-BvID5ucz.js → client-Bq0vre8Y.js} +59 -38
- package/dist/client/_assets/client-D3VZyG4L.css +2 -0
- package/dist/client/_assets/client-auth-F2rb9WfF.js +5571 -0
- package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
- package/dist/{export-DWn149b7.js → export-Dl5KCiEs.js} +1348 -128
- package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
- package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-Bqg62IvV.js} +3 -3
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-HdK2EgvJ.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/node.js +7 -7
- package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
- 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__/mise-config.test.ts +22 -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 +316 -2
- package/src/client/__tests__/compose-shortcuts.test.ts +20 -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 +82 -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__/compose-format-convert.test.ts +0 -19
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +2268 -206
- package/src/client/components/__tests__/jant-compose-editor.test.ts +147 -59
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +114 -64
- 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-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +55 -3
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +14 -7
- package/src/client/components/jant-compose-dialog.ts +1542 -694
- package/src/client/components/jant-compose-editor.ts +237 -99
- package/src/client/components/jant-compose-fullscreen.ts +27 -9
- 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 +177 -95
- 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 +147 -123
- package/src/client/compose-launch.ts +13 -0
- package/src/client/compose-shortcuts.ts +5 -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/post-refresh.ts +135 -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__/link-input-rules.test.ts +121 -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/link-input-rules.ts +4 -4
- 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 +133 -39
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/backfill-thread-activity.test.ts +234 -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-activity.test.ts +113 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -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/0031_many_ego.sql +3 -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/0031_snapshot.json +2562 -0
- package/src/db/migrations/meta/_journal.json +35 -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/0029_rare_hairball.sql +3 -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/0029_snapshot.json +3289 -0
- package/src/db/migrations/pg/meta/_journal.json +35 -0
- package/src/db/pg/schema.ts +46 -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 +48 -20
- package/src/db/thread-activity.ts +101 -0
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +222 -51
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +216 -45
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +216 -45
- 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__/feed.test.ts +168 -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 +24 -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__/slug.test.ts +18 -0
- 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__/translit.test.ts +87 -0
- package/src/lib/__tests__/url.test.ts +53 -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/feed.ts +113 -11
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +15 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +193 -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 +47 -8
- 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 +137 -131
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +359 -83
- package/src/lib/translit.ts +288 -0
- package/src/lib/url.ts +123 -12
- package/src/lib/view.ts +58 -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__/palette.test.ts +44 -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 +14 -10
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +246 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +48 -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 +8 -3
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +384 -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__/featured.test.ts +6 -2
- package/src/routes/pages/__tests__/preview.test.ts +176 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +90 -22
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +86 -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 +190 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +249 -39
- package/src/services/__tests__/post.test.ts +844 -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 +224 -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 +94 -85
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/path.ts +4 -1
- package/src/services/post.ts +1142 -342
- package/src/services/search.ts +17 -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 -14
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +76 -10
- package/src/styles/ui.css +2227 -1347
- 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 +14 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +20 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +22 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +109 -19
- 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/LinkPreview.tsx +2 -7
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/PostStatusBadges.tsx +42 -3
- 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/ArchivePage.tsx +131 -24
- 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 +70 -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 +49 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +38 -13
- 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/custom-icons.ts +5 -0
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +52 -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
package/src/lib/tiptap-render.ts
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import type { JSONContent } from "@tiptap/core";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
getFootnoteLabelKey,
|
|
11
11
|
normalizeFootnoteArtifacts,
|
|
12
12
|
normalizeFootnoteLabel,
|
|
13
13
|
} from "./footnotes.js";
|
|
14
14
|
import { renderEmbedFromAttrs } from "./embed-render.js";
|
|
15
15
|
import { escapeHtml } from "./html.js";
|
|
16
16
|
import { renderPublishedImageFigure } from "./rich-image.js";
|
|
17
|
-
import {
|
|
17
|
+
import { sanitizeRichTextHref } from "./url.js";
|
|
18
18
|
|
|
19
19
|
interface TiptapMark {
|
|
20
20
|
type: string;
|
|
@@ -30,6 +30,7 @@ interface TiptapNode {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
interface RenderContext {
|
|
33
|
+
footnotes: FootnotePlan;
|
|
33
34
|
renderChildren(content?: TiptapNode[]): string;
|
|
34
35
|
renderNode(node: TiptapNode): string;
|
|
35
36
|
renderText(text: string, marks?: TiptapMark[]): string;
|
|
@@ -38,6 +39,40 @@ interface RenderContext {
|
|
|
38
39
|
type MarkRenderer = (html: string, mark: TiptapMark) => string;
|
|
39
40
|
type NodeRenderer = (node: TiptapNode, context: RenderContext) => string;
|
|
40
41
|
|
|
42
|
+
export interface TiptapRenderOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Stable namespace for fragment IDs when multiple rendered documents can
|
|
45
|
+
* share a page. Persisted post HTML passes the immutable post TypeID.
|
|
46
|
+
*/
|
|
47
|
+
namespace?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type TiptapRenderResult =
|
|
51
|
+
| { ok: true; html: string }
|
|
52
|
+
| { ok: false; error: string };
|
|
53
|
+
|
|
54
|
+
interface FootnoteReferencePlan {
|
|
55
|
+
definitionId: string;
|
|
56
|
+
referenceId: string;
|
|
57
|
+
number: number;
|
|
58
|
+
occurrence: number;
|
|
59
|
+
label: string;
|
|
60
|
+
hasDefinition: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface FootnoteGroup {
|
|
64
|
+
definitionId: string;
|
|
65
|
+
number: number;
|
|
66
|
+
label: string;
|
|
67
|
+
definition: TiptapNode | null;
|
|
68
|
+
references: FootnoteReferencePlan[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface FootnotePlan {
|
|
72
|
+
groups: FootnoteGroup[];
|
|
73
|
+
referencesByNode: WeakMap<TiptapNode, FootnoteReferencePlan>;
|
|
74
|
+
}
|
|
75
|
+
|
|
41
76
|
function getStringAttr(
|
|
42
77
|
attrs: Record<string, unknown> | undefined,
|
|
43
78
|
name: string,
|
|
@@ -80,47 +115,206 @@ function renderTableCell(
|
|
|
80
115
|
return `<${tagName}${colspanAttr}${rowspanAttr}>${context.renderChildren(node.content)}</${tagName}>`;
|
|
81
116
|
}
|
|
82
117
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
function
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
|
|
118
|
+
const FOOTNOTE_NAMESPACE_HASH_OFFSET = 0xcbf29ce484222325n;
|
|
119
|
+
const FOOTNOTE_NAMESPACE_HASH_PRIME = 0x100000001b3n;
|
|
120
|
+
const FOOTNOTE_NAMESPACE_HASH_MASK = 0xffffffffffffffffn;
|
|
121
|
+
|
|
122
|
+
function createFootnoteIdScope(namespace: string | undefined): string {
|
|
123
|
+
const normalized = (namespace ?? "")
|
|
124
|
+
.trim()
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
127
|
+
.replace(/^-+|-+$/g, "");
|
|
128
|
+
if (!normalized) return "";
|
|
129
|
+
|
|
130
|
+
// Fragment IDs are public implementation details, not content IDs. Hash the
|
|
131
|
+
// immutable entity namespace so HTML stays readable without embedding a full
|
|
132
|
+
// 30-character TypeID. A 64-bit scope keeps collision risk negligible even
|
|
133
|
+
// across a page containing thousands of independently rendered documents.
|
|
134
|
+
let hash = FOOTNOTE_NAMESPACE_HASH_OFFSET;
|
|
135
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
136
|
+
hash ^= BigInt(normalized.charCodeAt(index));
|
|
137
|
+
hash =
|
|
138
|
+
(hash * FOOTNOTE_NAMESPACE_HASH_PRIME) & FOOTNOTE_NAMESPACE_HASH_MASK;
|
|
95
139
|
}
|
|
96
|
-
|
|
140
|
+
|
|
141
|
+
return hash.toString(36).padStart(13, "0");
|
|
97
142
|
}
|
|
98
143
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
144
|
+
function collectFootnoteReferences(
|
|
145
|
+
node: TiptapNode,
|
|
146
|
+
visit: (reference: TiptapNode) => void,
|
|
147
|
+
): void {
|
|
148
|
+
if (node.type === "footnoteReference") {
|
|
149
|
+
visit(node);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const child of node.content ?? []) {
|
|
154
|
+
collectFootnoteReferences(child, visit);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createFootnotePlan(
|
|
159
|
+
doc: TiptapNode,
|
|
160
|
+
options: TiptapRenderOptions,
|
|
161
|
+
): FootnotePlan {
|
|
162
|
+
const definitions = new Map<string, TiptapNode>();
|
|
163
|
+
const bodyNodes = (doc.content ?? []).filter(
|
|
164
|
+
(node) => node.type !== "footnoteDefinition",
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
for (const node of doc.content ?? []) {
|
|
168
|
+
if (node.type !== "footnoteDefinition") continue;
|
|
169
|
+
const key = getFootnoteLabelKey(getStringAttr(node.attrs, "label"));
|
|
170
|
+
if (key && !definitions.has(key)) definitions.set(key, node);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const namespace = createFootnoteIdScope(options.namespace);
|
|
174
|
+
const idScope = namespace ? `${namespace}-` : "";
|
|
175
|
+
const groupsByKey = new Map<string, FootnoteGroup>();
|
|
176
|
+
const groups: FootnoteGroup[] = [];
|
|
177
|
+
const referencesByNode = new WeakMap<TiptapNode, FootnoteReferencePlan>();
|
|
178
|
+
|
|
179
|
+
for (const owner of bodyNodes) {
|
|
180
|
+
collectFootnoteReferences(owner, (reference) => {
|
|
181
|
+
const normalizedLabel = normalizeFootnoteLabel(
|
|
182
|
+
getStringAttr(reference.attrs, "label"),
|
|
183
|
+
);
|
|
184
|
+
const label = normalizedLabel || "footnote";
|
|
185
|
+
const key = getFootnoteLabelKey(label);
|
|
186
|
+
let group = groupsByKey.get(key);
|
|
187
|
+
|
|
188
|
+
if (!group) {
|
|
189
|
+
const number = groupsByKey.size + 1;
|
|
190
|
+
group = {
|
|
191
|
+
definitionId: `fn-${idScope}${number}`,
|
|
192
|
+
number,
|
|
193
|
+
label,
|
|
194
|
+
definition: definitions.get(key) ?? null,
|
|
195
|
+
references: [],
|
|
196
|
+
};
|
|
197
|
+
groupsByKey.set(key, group);
|
|
198
|
+
groups.push(group);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const occurrence = group.references.length + 1;
|
|
202
|
+
const referencePlan: FootnoteReferencePlan = {
|
|
203
|
+
definitionId: group.definitionId,
|
|
204
|
+
referenceId: `fnref-${idScope}${group.number}-${occurrence}`,
|
|
205
|
+
number: group.number,
|
|
206
|
+
occurrence,
|
|
207
|
+
label: group.label,
|
|
208
|
+
hasDefinition: group.definition !== null,
|
|
209
|
+
};
|
|
210
|
+
group.references.push(referencePlan);
|
|
211
|
+
referencesByNode.set(reference, referencePlan);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
104
214
|
|
|
105
|
-
|
|
215
|
+
return { groups, referencesByNode };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function renderFootnoteReference(
|
|
106
219
|
node: TiptapNode,
|
|
107
220
|
context: RenderContext,
|
|
108
221
|
): string {
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// silently dropping `span.sidenote`. See docs/internal/markdown-contract.md.
|
|
222
|
+
const plan = context.footnotes.referencesByNode.get(node);
|
|
223
|
+
const fallbackLabel =
|
|
224
|
+
normalizeFootnoteLabel(getStringAttr(node.attrs, "label")) || "footnote";
|
|
225
|
+
const label = plan?.label ?? fallbackLabel;
|
|
226
|
+
const number = plan?.number ?? label;
|
|
227
|
+
|
|
228
|
+
if (!plan?.hasDefinition) {
|
|
229
|
+
return `<sup class="footnote-ref">${escapeHtml(String(number))}</sup>`;
|
|
230
|
+
}
|
|
231
|
+
|
|
120
232
|
return (
|
|
121
|
-
`<
|
|
122
|
-
`<
|
|
123
|
-
|
|
233
|
+
`<sup class="footnote-ref">` +
|
|
234
|
+
`<a id="${escapeHtml(plan.referenceId)}" href="#${escapeHtml(plan.definitionId)}" role="doc-noteref">` +
|
|
235
|
+
`${plan.number}</a></sup>`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function renderFootnoteGroup(
|
|
240
|
+
group: FootnoteGroup,
|
|
241
|
+
context: RenderContext,
|
|
242
|
+
includeValue: boolean,
|
|
243
|
+
): string {
|
|
244
|
+
if (!group.definition) return "";
|
|
245
|
+
|
|
246
|
+
const definitionContent = group.definition.content ?? [];
|
|
247
|
+
const backlinks = group.references
|
|
248
|
+
.map((reference) => {
|
|
249
|
+
const occurrence =
|
|
250
|
+
group.references.length > 1 ? String(reference.occurrence) : "";
|
|
251
|
+
return (
|
|
252
|
+
`<a href="#${escapeHtml(reference.referenceId)}" class="footnote-backref" ` +
|
|
253
|
+
`role="doc-backlink">↩︎${occurrence}</a>`
|
|
254
|
+
);
|
|
255
|
+
})
|
|
256
|
+
.join(" ");
|
|
257
|
+
const backlinksHtml = `<span class="footnote-backlinks">${backlinks}</span>`;
|
|
258
|
+
const lastBlock = definitionContent.at(-1);
|
|
259
|
+
let bodyHtml: string;
|
|
260
|
+
|
|
261
|
+
if (lastBlock?.type === "paragraph") {
|
|
262
|
+
const precedingBlocks = definitionContent.slice(0, -1);
|
|
263
|
+
const lastParagraphHtml = context.renderChildren(lastBlock.content);
|
|
264
|
+
const separator = lastParagraphHtml ? " " : "";
|
|
265
|
+
bodyHtml =
|
|
266
|
+
context.renderChildren(precedingBlocks) +
|
|
267
|
+
`<p>${lastParagraphHtml}${separator}${backlinksHtml}</p>`;
|
|
268
|
+
} else {
|
|
269
|
+
bodyHtml =
|
|
270
|
+
context.renderChildren(definitionContent) +
|
|
271
|
+
`<p class="footnote-backlinks">${backlinks}</p>`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const valueAttr = includeValue ? ` value="${group.number}"` : "";
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
`<li id="${escapeHtml(group.definitionId)}" class="footnote"${valueAttr}>` +
|
|
278
|
+
bodyHtml +
|
|
279
|
+
`</li>`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function renderFootnoteEndnotes(context: RenderContext): string {
|
|
284
|
+
const groups = context.footnotes.groups.filter(
|
|
285
|
+
(group) => group.definition !== null,
|
|
286
|
+
);
|
|
287
|
+
if (groups.length === 0) return "";
|
|
288
|
+
|
|
289
|
+
const firstNumber = groups[0]?.number ?? 1;
|
|
290
|
+
let expectedNumber = firstNumber;
|
|
291
|
+
const items = groups
|
|
292
|
+
.map((group) => {
|
|
293
|
+
const includeValue = group.number !== expectedNumber;
|
|
294
|
+
expectedNumber = group.number + 1;
|
|
295
|
+
return renderFootnoteGroup(group, context, includeValue);
|
|
296
|
+
})
|
|
297
|
+
.join("");
|
|
298
|
+
const startAttr = firstNumber === 1 ? "" : ` start="${firstNumber}"`;
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
`<section class="footnote-endnotes" role="doc-endnotes">` +
|
|
302
|
+
`<ol class="footnote-list"${startAttr}>${items}</ol>` +
|
|
303
|
+
`</section>`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function renderDocumentContent(
|
|
308
|
+
node: TiptapNode,
|
|
309
|
+
context: RenderContext,
|
|
310
|
+
): string {
|
|
311
|
+
const bodyNodes = (node.content ?? []).filter(
|
|
312
|
+
(child) => child.type !== "footnoteDefinition",
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
bodyNodes.map((bodyNode) => context.renderNode(bodyNode)).join("") +
|
|
317
|
+
renderFootnoteEndnotes(context)
|
|
124
318
|
);
|
|
125
319
|
}
|
|
126
320
|
|
|
@@ -130,7 +324,9 @@ const MARK_RENDERERS: Record<string, MarkRenderer> = {
|
|
|
130
324
|
strike: (html) => `<s>${html}</s>`,
|
|
131
325
|
code: (html) => `<code>${html}</code>`,
|
|
132
326
|
link: (html, mark) => {
|
|
133
|
-
const href = escapeHtml(
|
|
327
|
+
const href = escapeHtml(
|
|
328
|
+
sanitizeRichTextHref(getStringAttr(mark.attrs, "href")),
|
|
329
|
+
);
|
|
134
330
|
const target = getStringAttr(mark.attrs, "target");
|
|
135
331
|
const targetAttr = target ? ` target="${escapeHtml(target)}"` : "";
|
|
136
332
|
const relAttr = target ? ' rel="noopener noreferrer"' : "";
|
|
@@ -140,30 +336,7 @@ const MARK_RENDERERS: Record<string, MarkRenderer> = {
|
|
|
140
336
|
};
|
|
141
337
|
|
|
142
338
|
const NODE_RENDERERS: Record<string, NodeRenderer> = {
|
|
143
|
-
doc: (node, context) =>
|
|
144
|
-
const content = node.content ?? [];
|
|
145
|
-
|
|
146
|
-
// Build definition lookup so footnoteReference can inline sidenotes
|
|
147
|
-
const definitionMap = new Map<string, TiptapNode>();
|
|
148
|
-
for (const child of content) {
|
|
149
|
-
if (child.type === "footnoteDefinition") {
|
|
150
|
-
const label = normalizeFootnoteLabel(
|
|
151
|
-
getStringAttr(child.attrs, "label"),
|
|
152
|
-
);
|
|
153
|
-
if (label) definitionMap.set(label, child);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
activeDefinitionMap = definitionMap.size > 0 ? definitionMap : null;
|
|
158
|
-
try {
|
|
159
|
-
const bodyNodes = content.filter(
|
|
160
|
-
(child) => child.type !== "footnoteDefinition",
|
|
161
|
-
);
|
|
162
|
-
return context.renderChildren(bodyNodes);
|
|
163
|
-
} finally {
|
|
164
|
-
activeDefinitionMap = null;
|
|
165
|
-
}
|
|
166
|
-
},
|
|
339
|
+
doc: (node, context) => renderDocumentContent(node, context),
|
|
167
340
|
paragraph: (node, context) =>
|
|
168
341
|
`<p>${context.renderChildren(node.content)}</p>`,
|
|
169
342
|
heading: (node, context) => {
|
|
@@ -213,7 +386,7 @@ const NODE_RENDERERS: Record<string, NodeRenderer> = {
|
|
|
213
386
|
return `<div class="tiptap-html-block">${html}</div>`;
|
|
214
387
|
},
|
|
215
388
|
moreBreak: () => "<!--more-->",
|
|
216
|
-
footnoteReference: (node, context) =>
|
|
389
|
+
footnoteReference: (node, context) => renderFootnoteReference(node, context),
|
|
217
390
|
footnoteDefinition: () => "",
|
|
218
391
|
};
|
|
219
392
|
|
|
@@ -230,40 +403,144 @@ function renderText(text: string, marks: TiptapMark[] = []): string {
|
|
|
230
403
|
return html;
|
|
231
404
|
}
|
|
232
405
|
|
|
233
|
-
function renderChildren(content: TiptapNode[] = []): string {
|
|
234
|
-
return content.map(renderNode).join("");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
406
|
function renderUnknownNode(node: TiptapNode, context: RenderContext): string {
|
|
238
407
|
return node.content ? context.renderChildren(node.content) : "";
|
|
239
408
|
}
|
|
240
409
|
|
|
241
|
-
function
|
|
242
|
-
const
|
|
243
|
-
|
|
410
|
+
function createRenderContext(footnotes: FootnotePlan): RenderContext {
|
|
411
|
+
const context: RenderContext = {
|
|
412
|
+
footnotes,
|
|
413
|
+
renderChildren(content: TiptapNode[] = []) {
|
|
414
|
+
return content.map((node) => context.renderNode(node)).join("");
|
|
415
|
+
},
|
|
416
|
+
renderNode(node) {
|
|
417
|
+
const renderNodeType = NODE_RENDERERS[node.type] ?? renderUnknownNode;
|
|
418
|
+
return renderNodeType(node, context);
|
|
419
|
+
},
|
|
420
|
+
renderText,
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
return context;
|
|
244
424
|
}
|
|
245
425
|
|
|
246
|
-
const RENDER_CONTEXT: RenderContext = {
|
|
247
|
-
renderChildren,
|
|
248
|
-
renderNode,
|
|
249
|
-
renderText,
|
|
250
|
-
};
|
|
251
|
-
|
|
252
426
|
/**
|
|
253
427
|
* Renders a parsed TipTap document to HTML.
|
|
254
428
|
*
|
|
255
429
|
* @param doc - Parsed TipTap document
|
|
430
|
+
* @param options - Rendering options, including a stable footnote ID namespace
|
|
256
431
|
* @returns HTML string
|
|
257
432
|
*/
|
|
258
|
-
export function renderTiptapDocument(
|
|
433
|
+
export function renderTiptapDocument(
|
|
434
|
+
doc: JSONContent,
|
|
435
|
+
options: TiptapRenderOptions = {},
|
|
436
|
+
): string {
|
|
259
437
|
if (doc.type !== "doc") return "";
|
|
260
|
-
|
|
438
|
+
const normalized = normalizeFootnoteArtifacts(doc) as TiptapNode;
|
|
439
|
+
const context = createRenderContext(createFootnotePlan(normalized, options));
|
|
440
|
+
return context.renderNode(normalized);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export interface TiptapBoundaryRenderResult {
|
|
444
|
+
beforeHtml: string;
|
|
445
|
+
afterHtml: string;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Render a document as two byte-compatible segments around a top-level source
|
|
450
|
+
* boundary.
|
|
451
|
+
*
|
|
452
|
+
* The full document is planned before either segment is rendered, so a
|
|
453
|
+
* footnote before the boundary still includes backlinks to repeated references
|
|
454
|
+
* after it. Joining the two strings is therefore identical to a normal render.
|
|
455
|
+
*
|
|
456
|
+
* @param doc - Parsed TipTap document
|
|
457
|
+
* @param boundaryIndex - Index in the original `doc.content` array
|
|
458
|
+
* @param options - Rendering options, including a stable footnote ID namespace
|
|
459
|
+
* @returns The rendered segments, or null for an invalid root or boundary
|
|
460
|
+
* @example
|
|
461
|
+
* ```ts
|
|
462
|
+
* renderTiptapDocumentAroundBoundary(
|
|
463
|
+
* { type: "doc", content: [{ type: "paragraph" }] },
|
|
464
|
+
* 1,
|
|
465
|
+
* );
|
|
466
|
+
* // { beforeHtml: "<p></p>", afterHtml: "" }
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
export function renderTiptapDocumentAroundBoundary(
|
|
470
|
+
doc: JSONContent,
|
|
471
|
+
boundaryIndex: number,
|
|
472
|
+
options: TiptapRenderOptions = {},
|
|
473
|
+
): TiptapBoundaryRenderResult | null {
|
|
474
|
+
const originalContent = doc.content ?? [];
|
|
475
|
+
if (
|
|
476
|
+
doc.type !== "doc" ||
|
|
477
|
+
!Array.isArray(doc.content) ||
|
|
478
|
+
!Number.isInteger(boundaryIndex) ||
|
|
479
|
+
boundaryIndex < 0 ||
|
|
480
|
+
boundaryIndex > originalContent.length
|
|
481
|
+
) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const normalized = normalizeFootnoteArtifacts(doc) as TiptapNode;
|
|
486
|
+
const context = createRenderContext(createFootnotePlan(normalized, options));
|
|
487
|
+
const normalizedBoundaryIndex = Math.min(
|
|
488
|
+
boundaryIndex,
|
|
489
|
+
normalized.content?.length ?? 0,
|
|
490
|
+
);
|
|
491
|
+
let beforeBodyHtml = "";
|
|
492
|
+
let afterBodyHtml = "";
|
|
493
|
+
|
|
494
|
+
for (const [index, node] of (normalized.content ?? []).entries()) {
|
|
495
|
+
if (node.type === "footnoteDefinition") continue;
|
|
496
|
+
const html = context.renderNode(node);
|
|
497
|
+
if (index < normalizedBoundaryIndex) {
|
|
498
|
+
beforeBodyHtml += html;
|
|
499
|
+
} else {
|
|
500
|
+
afterBodyHtml += html;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
beforeHtml: beforeBodyHtml,
|
|
506
|
+
afterHtml: afterBodyHtml + renderFootnoteEndnotes(context),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Strictly parses and renders a TipTap JSON document.
|
|
512
|
+
*
|
|
513
|
+
* Unlike `renderTiptapJson`, this distinguishes invalid canonical JSON from a
|
|
514
|
+
* valid empty document. Projection rebuilds use it so malformed historical
|
|
515
|
+
* content is reported and never marked as current.
|
|
516
|
+
*
|
|
517
|
+
* @param json - TipTap JSON string
|
|
518
|
+
* @param options - Rendering options
|
|
519
|
+
* @returns A discriminated render result
|
|
520
|
+
*/
|
|
521
|
+
export function tryRenderTiptapJson(
|
|
522
|
+
json: string,
|
|
523
|
+
options: TiptapRenderOptions = {},
|
|
524
|
+
): TiptapRenderResult {
|
|
525
|
+
try {
|
|
526
|
+
const doc = JSON.parse(json) as JSONContent;
|
|
527
|
+
if (doc.type !== "doc") {
|
|
528
|
+
return { ok: false, error: "TipTap body root must be a doc node." };
|
|
529
|
+
}
|
|
530
|
+
return { ok: true, html: renderTiptapDocument(doc, options) };
|
|
531
|
+
} catch (error) {
|
|
532
|
+
return {
|
|
533
|
+
ok: false,
|
|
534
|
+
error: error instanceof Error ? error.message : "Invalid TipTap JSON.",
|
|
535
|
+
};
|
|
536
|
+
}
|
|
261
537
|
}
|
|
262
538
|
|
|
263
539
|
/**
|
|
264
540
|
* Renders a Tiptap JSON document to an HTML string.
|
|
265
541
|
*
|
|
266
542
|
* @param json - Tiptap JSON string or parsed document object
|
|
543
|
+
* @param options - Rendering options, including a stable footnote ID namespace
|
|
267
544
|
* @returns HTML string
|
|
268
545
|
*
|
|
269
546
|
* @example
|
|
@@ -272,13 +549,12 @@ export function renderTiptapDocument(doc: JSONContent): string {
|
|
|
272
549
|
* // "<p>Hello</p>"
|
|
273
550
|
* ```
|
|
274
551
|
*/
|
|
275
|
-
export function renderTiptapJson(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
552
|
+
export function renderTiptapJson(
|
|
553
|
+
json: string,
|
|
554
|
+
options: TiptapRenderOptions = {},
|
|
555
|
+
): string {
|
|
556
|
+
const result = tryRenderTiptapJson(json, options);
|
|
557
|
+
return result.ok ? result.html : "";
|
|
282
558
|
}
|
|
283
559
|
|
|
284
560
|
/**
|