@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
|
@@ -19,6 +19,7 @@ import { toPublicPath } from "../../lib/url.js";
|
|
|
19
19
|
import { TimelineFeed } from "../feed/TimelineFeed.js";
|
|
20
20
|
import { getCollectionMutationLabels } from "../shared/collection-management-labels.js";
|
|
21
21
|
import { getIconSvg } from "../../lib/icons.js";
|
|
22
|
+
import { NAVIGATION_SETTINGS_PATH } from "../../lib/settings-paths.js";
|
|
22
23
|
|
|
23
24
|
const escapeJson = (data: unknown) =>
|
|
24
25
|
JSON.stringify(data).replace(/</g, "\\u003c");
|
|
@@ -35,7 +36,9 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
35
36
|
defaultSort,
|
|
36
37
|
showRatingSort,
|
|
37
38
|
isAuthenticated,
|
|
39
|
+
isInNavigation = false,
|
|
38
40
|
sitePathPrefix = "",
|
|
41
|
+
feedHref,
|
|
39
42
|
}) => {
|
|
40
43
|
const primaryCollection = collections[0];
|
|
41
44
|
if (!primaryCollection) return null;
|
|
@@ -58,6 +61,10 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
58
61
|
)}`,
|
|
59
62
|
sitePathPrefix,
|
|
60
63
|
);
|
|
64
|
+
const navigationSettingsUrl = toPublicPath(
|
|
65
|
+
NAVIGATION_SETTINGS_PATH,
|
|
66
|
+
sitePathPrefix,
|
|
67
|
+
);
|
|
61
68
|
const sortUiId = isAggregate
|
|
62
69
|
? collections.map((collection) => collection.slug).join("-")
|
|
63
70
|
: primaryCollection.id;
|
|
@@ -226,7 +233,9 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
226
233
|
<div
|
|
227
234
|
class="collection-page-description prose"
|
|
228
235
|
dangerouslySetInnerHTML={{
|
|
229
|
-
__html: renderMarkdown(primaryCollection.description
|
|
236
|
+
__html: renderMarkdown(primaryCollection.description, {
|
|
237
|
+
namespace: primaryCollection.id,
|
|
238
|
+
}),
|
|
230
239
|
}}
|
|
231
240
|
/>
|
|
232
241
|
) : null}
|
|
@@ -298,25 +307,30 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
298
307
|
"@context: Plural thread count label on collection detail page",
|
|
299
308
|
}),
|
|
300
309
|
)}
|
|
301
|
-
{pageLabel ? <span> / {pageLabel}</span> : null}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
310
|
+
{pageLabel ? <span> / {pageLabel}</span> : null}
|
|
311
|
+
{feedHref ? (
|
|
312
|
+
<>
|
|
313
|
+
{" "}
|
|
314
|
+
<a
|
|
315
|
+
href={toPublicPath(feedHref, sitePathPrefix)}
|
|
316
|
+
class="feed-link"
|
|
317
|
+
title={i18n._(
|
|
318
|
+
msg({
|
|
319
|
+
message: "RSS feed",
|
|
320
|
+
comment:
|
|
321
|
+
"@context: Tooltip for the RSS feed link on a collection page",
|
|
322
|
+
}),
|
|
323
|
+
)}
|
|
324
|
+
rel="noopener noreferrer"
|
|
325
|
+
>
|
|
326
|
+
<span
|
|
327
|
+
dangerouslySetInnerHTML={{
|
|
328
|
+
__html: getIconSvg("rss") ?? "",
|
|
329
|
+
}}
|
|
330
|
+
/>
|
|
331
|
+
</a>
|
|
332
|
+
</>
|
|
333
|
+
) : null}
|
|
320
334
|
</p>
|
|
321
335
|
<span class="collection-page-meta-divider" aria-hidden="true">
|
|
322
336
|
·
|
|
@@ -450,6 +464,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
450
464
|
data-collection-page-actions
|
|
451
465
|
data-collection-id={primaryCollection.id}
|
|
452
466
|
data-collection-page-labels={escapeJson(mutationLabels)}
|
|
467
|
+
data-collection-in-navigation={String(isInNavigation)}
|
|
453
468
|
data-collection-page-redirect-url={toPublicPath(
|
|
454
469
|
getCollectionsDirectoryPath(),
|
|
455
470
|
sitePathPrefix,
|
|
@@ -510,6 +525,70 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
510
525
|
{mutationLabels.edit}
|
|
511
526
|
</span>
|
|
512
527
|
</a>
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
class="collections-page-menu-item"
|
|
531
|
+
role="menuitem"
|
|
532
|
+
data-collection-page-action="add-to-navigation"
|
|
533
|
+
hidden={isInNavigation}
|
|
534
|
+
>
|
|
535
|
+
<span
|
|
536
|
+
class="collections-page-menu-item-icon"
|
|
537
|
+
aria-hidden="true"
|
|
538
|
+
>
|
|
539
|
+
<svg
|
|
540
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
541
|
+
width="16"
|
|
542
|
+
height="16"
|
|
543
|
+
viewBox="0 0 24 24"
|
|
544
|
+
fill="none"
|
|
545
|
+
stroke="currentColor"
|
|
546
|
+
stroke-width="2"
|
|
547
|
+
stroke-linecap="round"
|
|
548
|
+
stroke-linejoin="round"
|
|
549
|
+
>
|
|
550
|
+
<path d="M12 5v14" />
|
|
551
|
+
<path d="M5 12h14" />
|
|
552
|
+
</svg>
|
|
553
|
+
</span>
|
|
554
|
+
<span class="collections-page-menu-item-label">
|
|
555
|
+
{mutationLabels.addToNavigation}
|
|
556
|
+
</span>
|
|
557
|
+
</button>
|
|
558
|
+
<a
|
|
559
|
+
href={navigationSettingsUrl}
|
|
560
|
+
class="collections-page-menu-item"
|
|
561
|
+
role="menuitem"
|
|
562
|
+
data-collection-page-edit-navigation
|
|
563
|
+
hidden={!isInNavigation}
|
|
564
|
+
>
|
|
565
|
+
<span
|
|
566
|
+
class="collections-page-menu-item-icon"
|
|
567
|
+
aria-hidden="true"
|
|
568
|
+
>
|
|
569
|
+
<svg
|
|
570
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
571
|
+
width="16"
|
|
572
|
+
height="16"
|
|
573
|
+
viewBox="0 0 24 24"
|
|
574
|
+
fill="none"
|
|
575
|
+
stroke="currentColor"
|
|
576
|
+
stroke-width="2"
|
|
577
|
+
stroke-linecap="round"
|
|
578
|
+
stroke-linejoin="round"
|
|
579
|
+
>
|
|
580
|
+
<path d="M8 6h13" />
|
|
581
|
+
<path d="M8 12h13" />
|
|
582
|
+
<path d="M8 18h13" />
|
|
583
|
+
<path d="M3 6h.01" />
|
|
584
|
+
<path d="M3 12h.01" />
|
|
585
|
+
<path d="M3 18h.01" />
|
|
586
|
+
</svg>
|
|
587
|
+
</span>
|
|
588
|
+
<span class="collections-page-menu-item-label">
|
|
589
|
+
{mutationLabels.editNavigation}
|
|
590
|
+
</span>
|
|
591
|
+
</a>
|
|
513
592
|
<button
|
|
514
593
|
type="button"
|
|
515
594
|
class="collections-page-menu-item collections-page-menu-item-danger"
|
|
@@ -556,7 +635,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
556
635
|
? i18n._(
|
|
557
636
|
msg({
|
|
558
637
|
message:
|
|
559
|
-
"Nothing here yet. Add
|
|
638
|
+
"Nothing here yet. Add threads to one of these collections to fill this view.",
|
|
560
639
|
comment:
|
|
561
640
|
"@context: Empty state message on an aggregate collection page",
|
|
562
641
|
}),
|
|
@@ -564,7 +643,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
564
643
|
: i18n._(
|
|
565
644
|
msg({
|
|
566
645
|
message:
|
|
567
|
-
"This collection is empty. Add
|
|
646
|
+
"This collection is empty. Add threads from the editor.",
|
|
568
647
|
comment: "@context: Empty state message",
|
|
569
648
|
}),
|
|
570
649
|
)}
|
|
@@ -14,13 +14,14 @@ import { CollectionsManager } from "../shared/CollectionsManager.js";
|
|
|
14
14
|
export const CollectionsPage: FC<CollectionsPageProps> = ({
|
|
15
15
|
items,
|
|
16
16
|
isAuthenticated,
|
|
17
|
+
navigationCollectionIds = [],
|
|
17
18
|
sitePathPrefix = "",
|
|
18
19
|
siteOrigin = "",
|
|
19
20
|
}) => {
|
|
20
21
|
const { i18n } = useLingui();
|
|
21
22
|
const emptyMessage = i18n._(
|
|
22
23
|
msg({
|
|
23
|
-
message: "No collections yet. Start one to organize
|
|
24
|
+
message: "No collections yet. Start one to organize threads by topic.",
|
|
24
25
|
comment: "@context: Empty state message on collections page",
|
|
25
26
|
}),
|
|
26
27
|
);
|
|
@@ -30,6 +31,7 @@ export const CollectionsPage: FC<CollectionsPageProps> = ({
|
|
|
30
31
|
<div class="py-6" data-page="collections">
|
|
31
32
|
<CollectionsManager
|
|
32
33
|
items={items}
|
|
34
|
+
navigationCollectionIds={navigationCollectionIds}
|
|
33
35
|
sitePathPrefix={sitePathPrefix}
|
|
34
36
|
siteOrigin={siteOrigin}
|
|
35
37
|
/>
|
|
@@ -12,10 +12,22 @@
|
|
|
12
12
|
import { msg } from "@lingui/core/macro";
|
|
13
13
|
import type { FC } from "hono/jsx";
|
|
14
14
|
import { useLingui } from "../../i18n/context.js";
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
PostPageProps,
|
|
17
|
+
PostView,
|
|
18
|
+
TimelineCardDisplayOptions,
|
|
19
|
+
} from "../../types.js";
|
|
16
20
|
import { TimelineItemFromPost } from "../feed/TimelineItem.js";
|
|
17
21
|
|
|
18
|
-
const
|
|
22
|
+
const PREVIEW_DISPLAY: TimelineCardDisplayOptions = {
|
|
23
|
+
footer: { hideActions: true, hideReply: true, hideTimestamp: false },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const renderThreadItem = (
|
|
27
|
+
tp: PostView,
|
|
28
|
+
currentId: string,
|
|
29
|
+
isPreview: boolean,
|
|
30
|
+
) => {
|
|
19
31
|
const isCurrent = tp.id === currentId;
|
|
20
32
|
return (
|
|
21
33
|
<div
|
|
@@ -27,16 +39,19 @@ const renderThreadItem = (tp: PostView, currentId: string) => {
|
|
|
27
39
|
<TimelineItemFromPost
|
|
28
40
|
post={tp}
|
|
29
41
|
mode="detail"
|
|
30
|
-
display={
|
|
42
|
+
display={
|
|
43
|
+
isPreview ? PREVIEW_DISPLAY : { footer: { hideTimestamp: false } }
|
|
44
|
+
}
|
|
31
45
|
/>
|
|
32
46
|
</div>
|
|
33
47
|
);
|
|
34
48
|
};
|
|
35
49
|
|
|
36
|
-
const ThreadDetail: FC<{
|
|
37
|
-
post
|
|
38
|
-
threadPosts
|
|
39
|
-
|
|
50
|
+
const ThreadDetail: FC<{
|
|
51
|
+
post: PostView;
|
|
52
|
+
threadPosts: PostView[];
|
|
53
|
+
isPreview: boolean;
|
|
54
|
+
}> = ({ post, threadPosts, isPreview }) => {
|
|
40
55
|
const { i18n } = useLingui();
|
|
41
56
|
const showMoreLabel = i18n._(
|
|
42
57
|
msg({
|
|
@@ -66,7 +81,7 @@ const ThreadDetail: FC<{ post: PostView; threadPosts: PostView[] }> = ({
|
|
|
66
81
|
data-thread-context
|
|
67
82
|
data-collapsed=""
|
|
68
83
|
>
|
|
69
|
-
{ancestors.map((tp) => renderThreadItem(tp, post.id))}
|
|
84
|
+
{ancestors.map((tp) => renderThreadItem(tp, post.id, isPreview))}
|
|
70
85
|
</div>
|
|
71
86
|
<button
|
|
72
87
|
type="button"
|
|
@@ -94,12 +109,16 @@ const ThreadDetail: FC<{ post: PostView; threadPosts: PostView[] }> = ({
|
|
|
94
109
|
</button>
|
|
95
110
|
</>
|
|
96
111
|
)}
|
|
97
|
-
{currentAndAfter.map((tp) => renderThreadItem(tp, post.id))}
|
|
112
|
+
{currentAndAfter.map((tp) => renderThreadItem(tp, post.id, isPreview))}
|
|
98
113
|
</div>
|
|
99
114
|
);
|
|
100
115
|
};
|
|
101
116
|
|
|
102
|
-
export const PostPage: FC<PostPageProps> = ({
|
|
117
|
+
export const PostPage: FC<PostPageProps> = ({
|
|
118
|
+
post,
|
|
119
|
+
threadPosts,
|
|
120
|
+
isPreview = false,
|
|
121
|
+
}) => {
|
|
103
122
|
return (
|
|
104
123
|
<div
|
|
105
124
|
data-post-view
|
|
@@ -107,9 +126,17 @@ export const PostPage: FC<PostPageProps> = ({ post, threadPosts }) => {
|
|
|
107
126
|
data-thread-root-id={post.threadRootId ?? post.id}
|
|
108
127
|
>
|
|
109
128
|
{threadPosts && threadPosts.length > 1 ? (
|
|
110
|
-
<ThreadDetail
|
|
129
|
+
<ThreadDetail
|
|
130
|
+
post={post}
|
|
131
|
+
threadPosts={threadPosts}
|
|
132
|
+
isPreview={isPreview}
|
|
133
|
+
/>
|
|
111
134
|
) : (
|
|
112
|
-
<TimelineItemFromPost
|
|
135
|
+
<TimelineItemFromPost
|
|
136
|
+
post={post}
|
|
137
|
+
mode="detail"
|
|
138
|
+
display={isPreview ? PREVIEW_DISPLAY : undefined}
|
|
139
|
+
/>
|
|
113
140
|
)}
|
|
114
141
|
{/* Public integration slot — code injection (giscus, Webmentions, etc.) appends here. */}
|
|
115
142
|
<div data-post-end />
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
CompactCollectionTags,
|
|
16
16
|
PostMenuTriggerButton,
|
|
17
17
|
} from "../shared/PostFooter.js";
|
|
18
|
+
import { getPostArticleAttributes } from "../shared/post-article-attributes.js";
|
|
18
19
|
|
|
19
20
|
// External link icon (shared by LinkCard)
|
|
20
21
|
const ExternalLinkIcon = () => (
|
|
@@ -46,20 +47,11 @@ const SearchResultCard: FC<{
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
+
const isChildPost = !!post.replyToId || !!post.threadRootId;
|
|
51
|
+
const visibleCollections = isChildPost ? [] : post.collections;
|
|
52
|
+
const hasCollections = visibleCollections.length > 0;
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
const postAttrs = {
|
|
53
|
-
"data-post": "",
|
|
54
|
-
"data-format": post.format,
|
|
55
|
-
"data-post-id": post.id,
|
|
56
|
-
"data-post-slug": post.slug,
|
|
57
|
-
"data-thread-root-id": post.threadRootId ?? post.id,
|
|
58
|
-
...(post.pinned ? { "data-post-pinned": "" } : {}),
|
|
59
|
-
...(post.featured ? { "data-post-featured": "" } : {}),
|
|
60
|
-
"data-post-visibility": post.visibility,
|
|
61
|
-
...(post.threadRootId ? { "data-post-reply": "" } : {}),
|
|
62
|
-
};
|
|
54
|
+
const postAttrs = getPostArticleAttributes(post);
|
|
63
55
|
|
|
64
56
|
const footer = (
|
|
65
57
|
<footer class="post-menu-footer mt-2">
|
|
@@ -69,7 +61,7 @@ const SearchResultCard: FC<{
|
|
|
69
61
|
<time datetime={post.publishedAt}>{post.publishedAtFormatted}</time>
|
|
70
62
|
</a>
|
|
71
63
|
<CompactCollectionTags
|
|
72
|
-
collections={
|
|
64
|
+
collections={visibleCollections}
|
|
73
65
|
showSeparator={hasCollections}
|
|
74
66
|
/>
|
|
75
67
|
</div>
|
|
@@ -106,4 +106,74 @@ describe("ArchivePage", () => {
|
|
|
106
106
|
expect(html).toContain("archive-chip-active");
|
|
107
107
|
expect(html).toContain("Single posts");
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it("uses a recognizable quote mark for the quote format filter", () => {
|
|
111
|
+
const html = renderArchivePage({ filters: { format: "quote" } });
|
|
112
|
+
|
|
113
|
+
expect(html).toContain('class="lucide lucide-quote"');
|
|
114
|
+
expect(html).not.toContain('class="lucide lucide-text-quote"');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders the feed button only when a feed href is provided", () => {
|
|
118
|
+
expect(renderArchivePage()).not.toContain('class="feed-link"');
|
|
119
|
+
expect(renderArchivePage({ feedHref: "/archive/feed" })).toContain(
|
|
120
|
+
'class="feed-link"',
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("offers the updated sort and marks published as the default", () => {
|
|
125
|
+
const html = renderArchivePage();
|
|
126
|
+
|
|
127
|
+
expect(html).toContain('aria-label="Sort order"');
|
|
128
|
+
expect(html).toContain("sort=updated");
|
|
129
|
+
expect(html).toContain(
|
|
130
|
+
'<a href="/archive" class="archive-view-btn archive-view-btn-active" role="radio" aria-checked="true" aria-label="Sort by when each thread was published"',
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("marks the updated sort active and keeps a way back", () => {
|
|
135
|
+
const html = renderArchivePage({ filters: { sort: "updated" } });
|
|
136
|
+
|
|
137
|
+
expect(html).toContain(
|
|
138
|
+
'<a href="/archive?sort=updated" class="archive-view-btn archive-view-btn-active" role="radio" aria-checked="true" aria-label="Sort by when each thread was last added to"',
|
|
139
|
+
);
|
|
140
|
+
expect(html).toContain('<a href="/archive" class="archive-view-btn"');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Icon-only controls need a hover tooltip too — aria-label only reaches
|
|
144
|
+
// assistive tech, and a bare clock/history pair is unreadable without one.
|
|
145
|
+
it("gives every toolbar toggle a tooltip matching its accessible name", () => {
|
|
146
|
+
const html = renderArchivePage();
|
|
147
|
+
|
|
148
|
+
for (const label of [
|
|
149
|
+
"Sort by when each thread was published",
|
|
150
|
+
"Sort by when each thread was last added to",
|
|
151
|
+
"Show as a grid of tiles",
|
|
152
|
+
"Show as a list with full posts",
|
|
153
|
+
]) {
|
|
154
|
+
expect(html).toContain(`aria-label="${label}" title="${label}"`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("labels month headers as update months in the updated sort", () => {
|
|
159
|
+
expect(renderArchivePage()).toContain(
|
|
160
|
+
'<span class="archive-month-header-label">March 2026</span>',
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const updated = renderArchivePage({ filters: { sort: "updated" } });
|
|
164
|
+
expect(updated).toContain(
|
|
165
|
+
'<span class="archive-month-header-label">Updated March 2026</span>',
|
|
166
|
+
);
|
|
167
|
+
expect(updated).toContain("newest changes first");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("keeps the sort selection in filter and view links", () => {
|
|
171
|
+
const html = renderArchivePage({
|
|
172
|
+
filters: { sort: "updated" },
|
|
173
|
+
availableCollections: [{ slug: "tech", title: "Tech" }],
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(html).toContain("collection=tech&sort=updated");
|
|
177
|
+
expect(html).toContain("view=list&sort=updated");
|
|
178
|
+
});
|
|
109
179
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import { renderToString } from "hono/jsx/dom/server";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { I18nProvider } from "../../../i18n/context.js";
|
|
5
|
+
import { createI18n } from "../../../i18n/i18n.js";
|
|
6
|
+
import type { PostView } from "../../../types.js";
|
|
7
|
+
import { PostPage } from "../PostPage.js";
|
|
8
|
+
|
|
9
|
+
function createPostView(overrides: Partial<PostView> = {}): PostView {
|
|
10
|
+
return {
|
|
11
|
+
id: "pst_root",
|
|
12
|
+
permalink: "/root",
|
|
13
|
+
slug: "root",
|
|
14
|
+
bodyHtml: "<p>Post body</p>",
|
|
15
|
+
format: "note",
|
|
16
|
+
status: "published",
|
|
17
|
+
visibility: "public",
|
|
18
|
+
pinned: false,
|
|
19
|
+
featured: false,
|
|
20
|
+
publishedAt: "2026-07-18T00:00:00.000Z",
|
|
21
|
+
publishedAtFormatted: "Jul 18, 2026",
|
|
22
|
+
publishedAtTime: "08:00",
|
|
23
|
+
publishedAtRelative: "now",
|
|
24
|
+
updatedAt: "2026-07-18T00:00:00.000Z",
|
|
25
|
+
media: [],
|
|
26
|
+
collections: [],
|
|
27
|
+
isLastInThread: false,
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderPostPage(post: PostView, threadPosts: PostView[]): string {
|
|
33
|
+
const i18n = createI18n("en");
|
|
34
|
+
const c = {
|
|
35
|
+
get(key: string) {
|
|
36
|
+
if (key === "i18n") return i18n;
|
|
37
|
+
return undefined;
|
|
38
|
+
},
|
|
39
|
+
} as unknown as Context;
|
|
40
|
+
|
|
41
|
+
I18nProvider({ c, children: "" });
|
|
42
|
+
return renderToString(PostPage({ post, threadPosts }));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getArticleTag(html: string, postId: string): string {
|
|
46
|
+
const article = (html.match(/<article\b[^>]*>/g) ?? []).find((tag) =>
|
|
47
|
+
tag.includes(`data-post-id="${postId}"`),
|
|
48
|
+
);
|
|
49
|
+
if (!article) throw new Error(`Missing article for ${postId}`);
|
|
50
|
+
return article;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("PostPage", () => {
|
|
54
|
+
it("preserves Root-versus-Child action context in detail mode", () => {
|
|
55
|
+
const root = createPostView();
|
|
56
|
+
const child = createPostView({
|
|
57
|
+
id: "pst_child",
|
|
58
|
+
permalink: "/child",
|
|
59
|
+
slug: "child",
|
|
60
|
+
format: "link",
|
|
61
|
+
title: "Child link",
|
|
62
|
+
url: "https://example.com/child",
|
|
63
|
+
replyToId: root.id,
|
|
64
|
+
threadRootId: root.id,
|
|
65
|
+
});
|
|
66
|
+
const grandchild = createPostView({
|
|
67
|
+
id: "pst_grandchild",
|
|
68
|
+
permalink: "/grandchild",
|
|
69
|
+
slug: "grandchild",
|
|
70
|
+
format: "quote",
|
|
71
|
+
quoteText: "Child quote",
|
|
72
|
+
replyToId: child.id,
|
|
73
|
+
threadRootId: root.id,
|
|
74
|
+
isLastInThread: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const html = renderPostPage(child, [root, child, grandchild]);
|
|
78
|
+
const rootArticle = getArticleTag(html, root.id);
|
|
79
|
+
const childArticle = getArticleTag(html, child.id);
|
|
80
|
+
const grandchildArticle = getArticleTag(html, grandchild.id);
|
|
81
|
+
|
|
82
|
+
expect(rootArticle).not.toContain("data-post-reply");
|
|
83
|
+
expect(childArticle).toContain('data-post-reply=""');
|
|
84
|
+
expect(childArticle).toContain(`data-thread-root-id="${root.id}"`);
|
|
85
|
+
expect(grandchildArticle).toContain('data-post-reply=""');
|
|
86
|
+
expect(grandchildArticle).toContain(`data-thread-root-id="${root.id}"`);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -124,7 +124,7 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
|
|
|
124
124
|
i18n._(
|
|
125
125
|
msg({
|
|
126
126
|
message:
|
|
127
|
-
"No collections yet. Start one to organize
|
|
127
|
+
"No collections yet. Start one to organize threads by topic.",
|
|
128
128
|
comment: "@context: Empty state message on collections page",
|
|
129
129
|
}),
|
|
130
130
|
)}
|
|
@@ -231,7 +231,9 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
|
|
|
231
231
|
<div
|
|
232
232
|
class="collection-directory-description prose"
|
|
233
233
|
dangerouslySetInnerHTML={{
|
|
234
|
-
__html: renderMarkdown(item.description
|
|
234
|
+
__html: renderMarkdown(item.description, {
|
|
235
|
+
namespace: item.id,
|
|
236
|
+
}),
|
|
235
237
|
}}
|
|
236
238
|
/>
|
|
237
239
|
) : (
|
|
@@ -279,24 +281,26 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
|
|
|
279
281
|
<div
|
|
280
282
|
class="collection-directory-description prose"
|
|
281
283
|
dangerouslySetInnerHTML={{
|
|
282
|
-
__html: renderMarkdown(collection.description
|
|
284
|
+
__html: renderMarkdown(collection.description, {
|
|
285
|
+
namespace: collection.id,
|
|
286
|
+
}),
|
|
283
287
|
}}
|
|
284
288
|
/>
|
|
285
289
|
)}
|
|
286
290
|
<p class="collection-directory-summary">
|
|
287
291
|
<span class="collection-directory-meta">
|
|
288
|
-
{collection.
|
|
289
|
-
{collection.
|
|
292
|
+
{collection.threadCount}{" "}
|
|
293
|
+
{collection.threadCount === 1
|
|
290
294
|
? i18n._(
|
|
291
295
|
msg({
|
|
292
|
-
message: "
|
|
293
|
-
comment: "@context: Singular
|
|
296
|
+
message: "thread",
|
|
297
|
+
comment: "@context: Singular thread count label",
|
|
294
298
|
}),
|
|
295
299
|
)
|
|
296
300
|
: i18n._(
|
|
297
301
|
msg({
|
|
298
|
-
message: "
|
|
299
|
-
comment: "@context: Plural
|
|
302
|
+
message: "threads",
|
|
303
|
+
comment: "@context: Plural thread count label",
|
|
300
304
|
}),
|
|
301
305
|
)}
|
|
302
306
|
</span>
|
|
@@ -15,12 +15,14 @@ const escapeJson = (data: unknown) =>
|
|
|
15
15
|
|
|
16
16
|
export interface CollectionsManagerProps {
|
|
17
17
|
items: CollectionDirectoryItem[];
|
|
18
|
+
navigationCollectionIds?: string[];
|
|
18
19
|
sitePathPrefix?: string;
|
|
19
20
|
siteOrigin?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
23
24
|
items,
|
|
25
|
+
navigationCollectionIds = [],
|
|
24
26
|
sitePathPrefix = "",
|
|
25
27
|
siteOrigin = "",
|
|
26
28
|
}) => {
|
|
@@ -102,21 +104,21 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
|
102
104
|
comment: "@context: Tooltip for divider delete button",
|
|
103
105
|
}),
|
|
104
106
|
),
|
|
105
|
-
|
|
107
|
+
threadSingular: i18n._(
|
|
106
108
|
msg({
|
|
107
|
-
message: "
|
|
108
|
-
comment: "@context: Singular
|
|
109
|
+
message: "thread",
|
|
110
|
+
comment: "@context: Singular thread count label",
|
|
109
111
|
}),
|
|
110
112
|
),
|
|
111
|
-
|
|
113
|
+
threadPlural: i18n._(
|
|
112
114
|
msg({
|
|
113
|
-
message: "
|
|
114
|
-
comment: "@context: Plural
|
|
115
|
+
message: "threads",
|
|
116
|
+
comment: "@context: Plural thread count label",
|
|
115
117
|
}),
|
|
116
118
|
),
|
|
117
119
|
emptyState: i18n._(
|
|
118
120
|
msg({
|
|
119
|
-
message: "No collections yet. Start one to organize
|
|
121
|
+
message: "No collections yet. Start one to organize threads by topic.",
|
|
120
122
|
comment: "@context: Empty state message on collections page",
|
|
121
123
|
}),
|
|
122
124
|
),
|
|
@@ -126,6 +128,13 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
|
126
128
|
comment: "@context: Toast after reordering collections",
|
|
127
129
|
}),
|
|
128
130
|
),
|
|
131
|
+
notNow: i18n._(
|
|
132
|
+
msg({
|
|
133
|
+
message: "Not now",
|
|
134
|
+
comment:
|
|
135
|
+
"@context: Action to dismiss the post-create Collection navigation prompt",
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
129
138
|
...mutationLabels,
|
|
130
139
|
};
|
|
131
140
|
|
|
@@ -312,6 +321,7 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
|
312
321
|
<jant-collections-manager
|
|
313
322
|
items={escapeJson(items)}
|
|
314
323
|
labels={escapeJson(labels)}
|
|
324
|
+
navigation-collection-ids={escapeJson(navigationCollectionIds)}
|
|
315
325
|
>
|
|
316
326
|
<CollectionDirectory
|
|
317
327
|
items={items}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { msg } from "@lingui/core/macro";
|
|
2
|
+
import type { FC } from "hono/jsx";
|
|
3
|
+
import { useLingui } from "../../i18n/context.js";
|
|
4
|
+
|
|
5
|
+
interface DraftPreviewBarProps {
|
|
6
|
+
editHref: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Persistent page chrome that distinguishes an authenticated draft preview. */
|
|
10
|
+
export const DraftPreviewBar: FC<DraftPreviewBarProps> = ({ editHref }) => {
|
|
11
|
+
const { i18n } = useLingui();
|
|
12
|
+
const label = i18n._(
|
|
13
|
+
msg({
|
|
14
|
+
message: "Draft preview",
|
|
15
|
+
comment: "@context: Status label above a draft preview page",
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<aside class="draft-preview-bar" aria-label={label} data-preview-status>
|
|
21
|
+
<div class="draft-preview-bar-inner">
|
|
22
|
+
<div class="draft-preview-bar-copy">
|
|
23
|
+
<span class="draft-preview-bar-label">
|
|
24
|
+
<span class="draft-preview-bar-dot" aria-hidden="true" />
|
|
25
|
+
{label}
|
|
26
|
+
</span>
|
|
27
|
+
<span class="draft-preview-bar-description">
|
|
28
|
+
{i18n._(
|
|
29
|
+
msg({
|
|
30
|
+
message: "This post isn’t published.",
|
|
31
|
+
comment:
|
|
32
|
+
"@context: Explanation in the draft preview status bar",
|
|
33
|
+
}),
|
|
34
|
+
)}
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
<a class="draft-preview-bar-action" href={editHref}>
|
|
38
|
+
{i18n._(
|
|
39
|
+
msg({
|
|
40
|
+
message: "Edit draft",
|
|
41
|
+
comment:
|
|
42
|
+
"@context: Action in the draft preview status bar that opens the editor",
|
|
43
|
+
}),
|
|
44
|
+
)}
|
|
45
|
+
</a>
|
|
46
|
+
</div>
|
|
47
|
+
</aside>
|
|
48
|
+
);
|
|
49
|
+
};
|