@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
|
@@ -5,14 +5,19 @@
|
|
|
5
5
|
* Must be registered last.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { msg } from "@lingui/core/macro";
|
|
8
9
|
import { Hono, type Context } from "hono";
|
|
9
10
|
import type { Bindings } from "../../types.js";
|
|
10
11
|
import type { AppVariables } from "../../types/app-context.js";
|
|
12
|
+
import { getI18n } from "../../i18n/index.js";
|
|
13
|
+
import { requireAuth } from "../../middleware/auth.js";
|
|
11
14
|
import { PostPage } from "../../ui/pages/PostPage.js";
|
|
15
|
+
import { DraftPreviewBar } from "../../ui/shared/DraftPreviewBar.js";
|
|
12
16
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
13
17
|
import { renderPublicPage } from "../../lib/render.js";
|
|
14
18
|
import { buildPageTitle } from "../../lib/page-title.js";
|
|
15
19
|
import { buildPostMeta } from "../../lib/post-meta.js";
|
|
20
|
+
import { isReservedPath } from "../../lib/constants.js";
|
|
16
21
|
import {
|
|
17
22
|
assemblePostPageDisplay,
|
|
18
23
|
type PostPageDisplayData,
|
|
@@ -203,7 +208,7 @@ function canRenderDraftAboutEditor(
|
|
|
203
208
|
async function renderPost(
|
|
204
209
|
c: Context<Env>,
|
|
205
210
|
post: Post,
|
|
206
|
-
options: { allowDraft?: boolean } = {},
|
|
211
|
+
options: { allowDraft?: boolean; isPreview?: boolean } = {},
|
|
207
212
|
) {
|
|
208
213
|
// Start navData fetch immediately — it's independent of thread/media queries
|
|
209
214
|
const navDataPromise = getNavigationData(c);
|
|
@@ -211,6 +216,7 @@ async function renderPost(
|
|
|
211
216
|
// Private-post access is validated before renderPost() is called.
|
|
212
217
|
isAuthenticated: true,
|
|
213
218
|
allowDraft: options.allowDraft,
|
|
219
|
+
includeDraftThread: options.isPreview,
|
|
214
220
|
});
|
|
215
221
|
if (!display) {
|
|
216
222
|
return c.notFound();
|
|
@@ -218,14 +224,51 @@ async function renderPost(
|
|
|
218
224
|
|
|
219
225
|
const navData = await navDataPromise;
|
|
220
226
|
const meta = buildPostMeta(post, navData.siteName);
|
|
227
|
+
const postView =
|
|
228
|
+
options.isPreview && display.postView.status === "draft"
|
|
229
|
+
? {
|
|
230
|
+
...display.postView,
|
|
231
|
+
permalink: toPublicPath(
|
|
232
|
+
`/preview/${display.postView.slug}`,
|
|
233
|
+
c.var.appConfig.sitePathPrefix,
|
|
234
|
+
),
|
|
235
|
+
}
|
|
236
|
+
: display.postView;
|
|
237
|
+
const threadPostViews = options.isPreview
|
|
238
|
+
? display.threadPostViews?.map((threadPost) =>
|
|
239
|
+
threadPost.status === "draft"
|
|
240
|
+
? {
|
|
241
|
+
...threadPost,
|
|
242
|
+
permalink: toPublicPath(
|
|
243
|
+
`/preview/${threadPost.slug}`,
|
|
244
|
+
c.var.appConfig.sitePathPrefix,
|
|
245
|
+
),
|
|
246
|
+
}
|
|
247
|
+
: threadPost,
|
|
248
|
+
)
|
|
249
|
+
: display.threadPostViews;
|
|
250
|
+
const previewTitle = options.isPreview
|
|
251
|
+
? getI18n(c)._(
|
|
252
|
+
msg({
|
|
253
|
+
message: "Draft preview",
|
|
254
|
+
comment: "@context: Browser title prefix for a draft preview page",
|
|
255
|
+
}),
|
|
256
|
+
)
|
|
257
|
+
: null;
|
|
221
258
|
const canonicalHref = buildPostCanonicalHref(
|
|
222
259
|
display.postView,
|
|
223
260
|
display.threadPostViews,
|
|
224
261
|
c.var.appConfig.siteUrl,
|
|
225
262
|
);
|
|
263
|
+
const draftEditHref = `${toPublicPath(
|
|
264
|
+
`/preview/${post.slug}`,
|
|
265
|
+
c.var.appConfig.sitePathPrefix,
|
|
266
|
+
)}?edit=1`;
|
|
226
267
|
|
|
227
268
|
return renderPublicPage(c, {
|
|
228
|
-
title:
|
|
269
|
+
title: previewTitle
|
|
270
|
+
? buildPageTitle(previewTitle, meta.title, navData.siteName)
|
|
271
|
+
: buildPageTitle(meta.title, navData.siteName),
|
|
229
272
|
description: meta.description,
|
|
230
273
|
...(post.status === "published"
|
|
231
274
|
? {
|
|
@@ -247,18 +290,58 @@ async function renderPost(
|
|
|
247
290
|
}
|
|
248
291
|
: {}),
|
|
249
292
|
navData,
|
|
293
|
+
...(options.isPreview
|
|
294
|
+
? {
|
|
295
|
+
pageChrome: <DraftPreviewBar editHref={draftEditHref} />,
|
|
296
|
+
noindex: true,
|
|
297
|
+
}
|
|
298
|
+
: {}),
|
|
250
299
|
content: (
|
|
251
|
-
<PostPage
|
|
300
|
+
<PostPage
|
|
301
|
+
post={postView}
|
|
302
|
+
threadPosts={threadPostViews}
|
|
303
|
+
isPreview={options.isPreview}
|
|
304
|
+
/>
|
|
252
305
|
),
|
|
253
306
|
});
|
|
254
307
|
}
|
|
255
308
|
|
|
309
|
+
pageRoutes.get("/preview/:slug", requireAuth(), async (c) => {
|
|
310
|
+
const post = await c.var.services.posts.getBySlug(c.req.param("slug"));
|
|
311
|
+
if (!post) return c.notFound();
|
|
312
|
+
|
|
313
|
+
if (post.status === "published") {
|
|
314
|
+
return c.redirect(
|
|
315
|
+
toPublicPath(`/${post.slug}`, c.var.appConfig.sitePathPrefix),
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
c.header("Cache-Control", "private, no-store");
|
|
320
|
+
c.header("X-Robots-Tag", "noindex, nofollow");
|
|
321
|
+
return renderPost(c, post, { allowDraft: true, isPreview: true });
|
|
322
|
+
});
|
|
323
|
+
|
|
256
324
|
// Catch-all for path-registry backed post URLs, aliases, and redirects
|
|
257
325
|
pageRoutes.get("/*", async (c) => {
|
|
258
326
|
const fullPath = c.req.path.slice(1); // Remove leading /
|
|
259
327
|
if (!fullPath) return c.notFound();
|
|
260
328
|
const sitePathPrefix = c.var.appConfig.sitePathPrefix;
|
|
261
329
|
|
|
330
|
+
// Explicit application routes are registered before this catch-all. Any
|
|
331
|
+
// unmatched path under a reserved prefix is therefore an application 404,
|
|
332
|
+
// never a legacy alias or redirect from path_registry.
|
|
333
|
+
if (isReservedPath(fullPath)) return c.notFound();
|
|
334
|
+
|
|
335
|
+
// Stored redirects outrank the normal post/collection/deep-link resolvers,
|
|
336
|
+
// but can never shadow an explicit or reserved application route.
|
|
337
|
+
const resolved = await c.var.services.paths.resolve(fullPath);
|
|
338
|
+
if (resolved?.kind === "redirect" && resolved.redirectToPath) {
|
|
339
|
+
return c.redirect(
|
|
340
|
+
toPublicHref(`/${resolved.redirectToPath}`, sitePathPrefix),
|
|
341
|
+
resolved.redirectType ?? 301,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
262
345
|
if (fullPath.endsWith("/feed")) {
|
|
263
346
|
const collectionPath = fullPath.slice(0, -"/feed".length);
|
|
264
347
|
if (!collectionPath) return c.notFound();
|
|
@@ -360,16 +443,8 @@ pageRoutes.get("/*", async (c) => {
|
|
|
360
443
|
}
|
|
361
444
|
}
|
|
362
445
|
|
|
363
|
-
const resolved = await c.var.services.paths.resolve(fullPath);
|
|
364
446
|
if (!resolved) return c.notFound();
|
|
365
447
|
|
|
366
|
-
if (resolved.kind === "redirect" && resolved.redirectToPath) {
|
|
367
|
-
return c.redirect(
|
|
368
|
-
toPublicHref(`/${resolved.redirectToPath}`, sitePathPrefix),
|
|
369
|
-
resolved.redirectType ?? 301,
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
448
|
if (resolved.kind === "archive" && resolved.archiveQuery) {
|
|
374
449
|
const overrides = Object.fromEntries(
|
|
375
450
|
new URLSearchParams(resolved.archiveQuery),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { renderSiteSkill } from "../lib/site-skill.js";
|
|
3
|
+
import type { Bindings } from "../types.js";
|
|
4
|
+
import type { AppVariables } from "../types/app-context.js";
|
|
5
|
+
|
|
6
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
7
|
+
|
|
8
|
+
export const siteSkillRoutes = new Hono<Env>();
|
|
9
|
+
|
|
10
|
+
siteSkillRoutes.get("/skill.md", (c) => {
|
|
11
|
+
return new Response(renderSiteSkill(c.var.appConfig.siteUrl), {
|
|
12
|
+
headers: {
|
|
13
|
+
"Cache-Control": "public, max-age=300, s-maxage=3600",
|
|
14
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
15
|
+
"X-Robots-Tag": "noindex",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
media,
|
|
12
12
|
navItems,
|
|
13
13
|
pathRegistry,
|
|
14
|
-
|
|
14
|
+
threadCollections,
|
|
15
15
|
posts,
|
|
16
16
|
session,
|
|
17
17
|
settings as settingsTable,
|
|
@@ -165,9 +165,9 @@ describe("AuthService", () => {
|
|
|
165
165
|
updatedAt: timestamp,
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
await db.insert(
|
|
168
|
+
await db.insert(threadCollections).values({
|
|
169
169
|
siteId: DEFAULT_TEST_SITE_ID,
|
|
170
|
-
|
|
170
|
+
threadId: "pst_01km9authdelete00000000000",
|
|
171
171
|
collectionId: "col_01km9authdelete00000000000",
|
|
172
172
|
createdAt: timestamp,
|
|
173
173
|
});
|
|
@@ -245,7 +245,9 @@ describe("AuthService", () => {
|
|
|
245
245
|
const remainingDirectoryItems = await db
|
|
246
246
|
.select()
|
|
247
247
|
.from(collectionDirectoryItems);
|
|
248
|
-
const
|
|
248
|
+
const remainingThreadCollections = await db
|
|
249
|
+
.select()
|
|
250
|
+
.from(threadCollections);
|
|
249
251
|
const remainingNavItems = await db.select().from(navItems);
|
|
250
252
|
const remainingApiTokens = await db.select().from(apiTokens);
|
|
251
253
|
const existingSites = await db.select().from(sites);
|
|
@@ -262,7 +264,7 @@ describe("AuthService", () => {
|
|
|
262
264
|
expect(remainingCollections).toHaveLength(0);
|
|
263
265
|
expect(remainingPaths).toHaveLength(0);
|
|
264
266
|
expect(remainingDirectoryItems).toHaveLength(0);
|
|
265
|
-
expect(
|
|
267
|
+
expect(remainingThreadCollections).toHaveLength(0);
|
|
266
268
|
expect(remainingNavItems).toHaveLength(0);
|
|
267
269
|
expect(remainingApiTokens).toHaveLength(0);
|
|
268
270
|
expect(existingSites).toHaveLength(0);
|
|
@@ -281,7 +281,7 @@ describe("CollectionService", () => {
|
|
|
281
281
|
format: "note",
|
|
282
282
|
bodyMarkdown: "Book note",
|
|
283
283
|
});
|
|
284
|
-
await collectionService.
|
|
284
|
+
await collectionService.addThread(reading.id, post.id);
|
|
285
285
|
|
|
286
286
|
const directory = await collectionService.listDirectoryData();
|
|
287
287
|
|
|
@@ -297,7 +297,7 @@ describe("CollectionService", () => {
|
|
|
297
297
|
type: "collection",
|
|
298
298
|
collection: expect.objectContaining({
|
|
299
299
|
id: reading.id,
|
|
300
|
-
|
|
300
|
+
threadCount: 1,
|
|
301
301
|
recentActivityAt: post.lastActivityAt,
|
|
302
302
|
}),
|
|
303
303
|
}),
|
|
@@ -309,6 +309,61 @@ describe("CollectionService", () => {
|
|
|
309
309
|
]);
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
// The directory's "recent activity" uses the same definition as every
|
|
313
|
+
// other surface: the Thread gained a post. Editing one is not activity,
|
|
314
|
+
// or fixing a typo would reshuffle the whole collection directory.
|
|
315
|
+
it("does not treat a later Root edit as recent activity", async () => {
|
|
316
|
+
vi.useFakeTimers();
|
|
317
|
+
try {
|
|
318
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
319
|
+
const collection = await collectionService.create({
|
|
320
|
+
slug: "edited-thread",
|
|
321
|
+
title: "Edited Thread",
|
|
322
|
+
});
|
|
323
|
+
const root = await postService.create({
|
|
324
|
+
format: "note",
|
|
325
|
+
bodyMarkdown: "before",
|
|
326
|
+
});
|
|
327
|
+
await collectionService.addThread(collection.id, root.id);
|
|
328
|
+
|
|
329
|
+
vi.setSystemTime(new Date("2024-01-01T00:01:00Z"));
|
|
330
|
+
await postService.update(root.id, { bodyMarkdown: "after" });
|
|
331
|
+
|
|
332
|
+
const directory = await collectionService.listDirectoryData();
|
|
333
|
+
expect(directory.collections[0]?.recentActivityAt).toBe(1704067200);
|
|
334
|
+
} finally {
|
|
335
|
+
vi.useRealTimers();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("treats a new reply as recent activity", async () => {
|
|
340
|
+
vi.useFakeTimers();
|
|
341
|
+
try {
|
|
342
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
|
343
|
+
const collection = await collectionService.create({
|
|
344
|
+
slug: "growing-thread",
|
|
345
|
+
title: "Growing Thread",
|
|
346
|
+
});
|
|
347
|
+
const root = await postService.create({
|
|
348
|
+
format: "note",
|
|
349
|
+
bodyMarkdown: "root",
|
|
350
|
+
});
|
|
351
|
+
await collectionService.addThread(collection.id, root.id);
|
|
352
|
+
|
|
353
|
+
vi.setSystemTime(new Date("2024-01-01T00:01:00Z"));
|
|
354
|
+
await postService.create({
|
|
355
|
+
format: "note",
|
|
356
|
+
bodyMarkdown: "reply",
|
|
357
|
+
replyToId: root.id,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const directory = await collectionService.listDirectoryData();
|
|
361
|
+
expect(directory.collections[0]?.recentActivityAt).toBe(1704067260);
|
|
362
|
+
} finally {
|
|
363
|
+
vi.useRealTimers();
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
312
367
|
it("includes custom links in directory items", async () => {
|
|
313
368
|
const link = await collectionService.createDirectoryItem({
|
|
314
369
|
type: "link",
|
|
@@ -451,7 +506,7 @@ describe("CollectionService", () => {
|
|
|
451
506
|
bodyMarkdown: "test post",
|
|
452
507
|
});
|
|
453
508
|
|
|
454
|
-
await collectionService.
|
|
509
|
+
await collectionService.addThread(collection.id, post.id);
|
|
455
510
|
|
|
456
511
|
// Verify association exists
|
|
457
512
|
const before = await collectionService.getCollectionsByPostId(post.id);
|
|
@@ -736,15 +791,15 @@ describe("CollectionService", () => {
|
|
|
736
791
|
});
|
|
737
792
|
});
|
|
738
793
|
|
|
739
|
-
describe("
|
|
794
|
+
describe("getThreadCounts", () => {
|
|
740
795
|
it("returns empty map when no posts exist", async () => {
|
|
741
796
|
await collectionService.create({ slug: "empty", title: "Empty" });
|
|
742
797
|
|
|
743
|
-
const counts = await collectionService.
|
|
798
|
+
const counts = await collectionService.getThreadCounts();
|
|
744
799
|
expect(counts.size).toBe(0);
|
|
745
800
|
});
|
|
746
801
|
|
|
747
|
-
it("returns correct counts for collections with
|
|
802
|
+
it("returns correct counts for collections with Threads", async () => {
|
|
748
803
|
const col1 = await collectionService.create({
|
|
749
804
|
slug: "col1",
|
|
750
805
|
title: "Col 1",
|
|
@@ -767,16 +822,16 @@ describe("CollectionService", () => {
|
|
|
767
822
|
bodyMarkdown: "post 3",
|
|
768
823
|
});
|
|
769
824
|
|
|
770
|
-
await collectionService.
|
|
771
|
-
await collectionService.
|
|
772
|
-
await collectionService.
|
|
825
|
+
await collectionService.addThread(col1.id, p1.id);
|
|
826
|
+
await collectionService.addThread(col1.id, p2.id);
|
|
827
|
+
await collectionService.addThread(col2.id, p3.id);
|
|
773
828
|
|
|
774
|
-
const counts = await collectionService.
|
|
829
|
+
const counts = await collectionService.getThreadCounts();
|
|
775
830
|
expect(counts.get(col1.id)).toBe(2);
|
|
776
831
|
expect(counts.get(col2.id)).toBe(1);
|
|
777
832
|
});
|
|
778
833
|
|
|
779
|
-
it("does not count
|
|
834
|
+
it("does not count Threads without a collection", async () => {
|
|
780
835
|
const col = await collectionService.create({
|
|
781
836
|
slug: "col",
|
|
782
837
|
title: "Col",
|
|
@@ -791,14 +846,14 @@ describe("CollectionService", () => {
|
|
|
791
846
|
bodyMarkdown: "no collection",
|
|
792
847
|
});
|
|
793
848
|
|
|
794
|
-
await collectionService.
|
|
849
|
+
await collectionService.addThread(col.id, p1.id);
|
|
795
850
|
|
|
796
|
-
const counts = await collectionService.
|
|
851
|
+
const counts = await collectionService.getThreadCounts();
|
|
797
852
|
expect(counts.get(col.id)).toBe(1);
|
|
798
853
|
expect(counts.size).toBe(1);
|
|
799
854
|
});
|
|
800
855
|
|
|
801
|
-
it("does not count
|
|
856
|
+
it("does not count deleted Threads", async () => {
|
|
802
857
|
const col = await collectionService.create({
|
|
803
858
|
slug: "col",
|
|
804
859
|
title: "Col",
|
|
@@ -813,19 +868,18 @@ describe("CollectionService", () => {
|
|
|
813
868
|
bodyMarkdown: "still alive",
|
|
814
869
|
});
|
|
815
870
|
|
|
816
|
-
await collectionService.
|
|
817
|
-
await collectionService.
|
|
871
|
+
await collectionService.addThread(col.id, post.id);
|
|
872
|
+
await collectionService.addThread(col.id, post2.id);
|
|
818
873
|
|
|
819
|
-
// Soft-delete one post
|
|
820
874
|
await postService.delete(post.id);
|
|
821
875
|
|
|
822
|
-
const counts = await collectionService.
|
|
876
|
+
const counts = await collectionService.getThreadCounts();
|
|
823
877
|
expect(counts.get(col.id)).toBe(1);
|
|
824
878
|
});
|
|
825
879
|
});
|
|
826
880
|
|
|
827
|
-
describe("
|
|
828
|
-
it("adds a
|
|
881
|
+
describe("addThread / removeThread", () => {
|
|
882
|
+
it("adds a Thread to a collection", async () => {
|
|
829
883
|
const col = await collectionService.create({
|
|
830
884
|
slug: "test",
|
|
831
885
|
title: "Test",
|
|
@@ -835,7 +889,7 @@ describe("CollectionService", () => {
|
|
|
835
889
|
bodyMarkdown: "test",
|
|
836
890
|
});
|
|
837
891
|
|
|
838
|
-
await collectionService.
|
|
892
|
+
await collectionService.addThread(col.id, post.id);
|
|
839
893
|
|
|
840
894
|
const collections = await collectionService.getCollectionsByPostId(
|
|
841
895
|
post.id,
|
|
@@ -854,14 +908,14 @@ describe("CollectionService", () => {
|
|
|
854
908
|
bodyMarkdown: "test",
|
|
855
909
|
});
|
|
856
910
|
|
|
857
|
-
await collectionService.
|
|
858
|
-
await collectionService.
|
|
911
|
+
await collectionService.addThread(col.id, post.id);
|
|
912
|
+
await collectionService.addThread(col.id, post.id); // duplicate
|
|
859
913
|
|
|
860
|
-
const
|
|
861
|
-
expect(
|
|
914
|
+
const threadIds = await collectionService.getThreadIds(col.id);
|
|
915
|
+
expect(threadIds).toHaveLength(1);
|
|
862
916
|
});
|
|
863
917
|
|
|
864
|
-
it("removes a
|
|
918
|
+
it("removes a Thread from a collection", async () => {
|
|
865
919
|
const col = await collectionService.create({
|
|
866
920
|
slug: "test",
|
|
867
921
|
title: "Test",
|
|
@@ -871,14 +925,43 @@ describe("CollectionService", () => {
|
|
|
871
925
|
bodyMarkdown: "test",
|
|
872
926
|
});
|
|
873
927
|
|
|
874
|
-
await collectionService.
|
|
875
|
-
await collectionService.
|
|
928
|
+
await collectionService.addThread(col.id, post.id);
|
|
929
|
+
await collectionService.removeThread(col.id, post.id);
|
|
876
930
|
|
|
877
931
|
const collections = await collectionService.getCollectionsByPostId(
|
|
878
932
|
post.id,
|
|
879
933
|
);
|
|
880
934
|
expect(collections).toHaveLength(0);
|
|
881
935
|
});
|
|
936
|
+
|
|
937
|
+
it("normalizes Child mutations to the shared Thread root", async () => {
|
|
938
|
+
const collection = await collectionService.create({
|
|
939
|
+
slug: "child-mutation",
|
|
940
|
+
title: "Child Mutation",
|
|
941
|
+
});
|
|
942
|
+
const root = await postService.create({
|
|
943
|
+
format: "note",
|
|
944
|
+
bodyMarkdown: "root",
|
|
945
|
+
});
|
|
946
|
+
const child = await postService.create({
|
|
947
|
+
format: "note",
|
|
948
|
+
bodyMarkdown: "child",
|
|
949
|
+
replyToId: root.id,
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
await collectionService.addThread(collection.id, child.id);
|
|
953
|
+
expect(await collectionService.getThreadIds(collection.id)).toEqual([
|
|
954
|
+
root.id,
|
|
955
|
+
]);
|
|
956
|
+
expect(
|
|
957
|
+
(await collectionService.getCollectionsByPostId(root.id)).map(
|
|
958
|
+
(item) => item.id,
|
|
959
|
+
),
|
|
960
|
+
).toEqual([collection.id]);
|
|
961
|
+
|
|
962
|
+
await collectionService.removeThread(collection.id, child.id);
|
|
963
|
+
expect(await collectionService.getThreadIds(collection.id)).toEqual([]);
|
|
964
|
+
});
|
|
882
965
|
});
|
|
883
966
|
|
|
884
967
|
describe("getCollectionsByPostId", () => {
|
|
@@ -899,8 +982,8 @@ describe("CollectionService", () => {
|
|
|
899
982
|
bodyMarkdown: "test",
|
|
900
983
|
});
|
|
901
984
|
|
|
902
|
-
await collectionService.
|
|
903
|
-
await collectionService.
|
|
985
|
+
await collectionService.addThread(cols[0]?.id ?? "", post.id);
|
|
986
|
+
await collectionService.addThread(cols[1]?.id ?? "", post.id);
|
|
904
987
|
|
|
905
988
|
const collections = await collectionService.getCollectionsByPostId(
|
|
906
989
|
post.id,
|
|
@@ -919,10 +1002,70 @@ describe("CollectionService", () => {
|
|
|
919
1002
|
);
|
|
920
1003
|
expect(collections).toHaveLength(0);
|
|
921
1004
|
});
|
|
1005
|
+
|
|
1006
|
+
it("projects one shared membership set onto the root and every child", async () => {
|
|
1007
|
+
const collection = await collectionService.create({
|
|
1008
|
+
slug: "shared-thread",
|
|
1009
|
+
title: "Shared Thread",
|
|
1010
|
+
});
|
|
1011
|
+
const root = await postService.create({
|
|
1012
|
+
format: "note",
|
|
1013
|
+
bodyMarkdown: "root",
|
|
1014
|
+
});
|
|
1015
|
+
const child = await postService.create({
|
|
1016
|
+
format: "note",
|
|
1017
|
+
bodyMarkdown: "child",
|
|
1018
|
+
replyToId: root.id,
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
await collectionService.addThread(collection.id, root.id);
|
|
1022
|
+
|
|
1023
|
+
const [rootCollections, childCollections, batch] = await Promise.all([
|
|
1024
|
+
collectionService.getCollectionsByPostId(root.id),
|
|
1025
|
+
collectionService.getCollectionsByPostId(child.id),
|
|
1026
|
+
collectionService.getCollectionsByPostIds([root.id, child.id]),
|
|
1027
|
+
]);
|
|
1028
|
+
|
|
1029
|
+
expect(rootCollections.map((item) => item.id)).toEqual([collection.id]);
|
|
1030
|
+
expect(childCollections.map((item) => item.id)).toEqual([collection.id]);
|
|
1031
|
+
expect(batch.get(root.id)?.map((item) => item.id)).toEqual([
|
|
1032
|
+
collection.id,
|
|
1033
|
+
]);
|
|
1034
|
+
expect(batch.get(child.id)?.map((item) => item.id)).toEqual([
|
|
1035
|
+
collection.id,
|
|
1036
|
+
]);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it("keeps shared membership when a Child is deleted and removes it with the Root", async () => {
|
|
1040
|
+
const collection = await collectionService.create({
|
|
1041
|
+
slug: "thread-lifecycle",
|
|
1042
|
+
title: "Thread Lifecycle",
|
|
1043
|
+
});
|
|
1044
|
+
const root = await postService.create({
|
|
1045
|
+
format: "note",
|
|
1046
|
+
bodyMarkdown: "root",
|
|
1047
|
+
});
|
|
1048
|
+
const child = await postService.create({
|
|
1049
|
+
format: "note",
|
|
1050
|
+
bodyMarkdown: "child",
|
|
1051
|
+
replyToId: root.id,
|
|
1052
|
+
});
|
|
1053
|
+
await collectionService.addThread(collection.id, root.id);
|
|
1054
|
+
|
|
1055
|
+
await postService.delete(child.id);
|
|
1056
|
+
expect(
|
|
1057
|
+
(await collectionService.getCollectionsByPostId(root.id)).map(
|
|
1058
|
+
(item) => item.id,
|
|
1059
|
+
),
|
|
1060
|
+
).toEqual([collection.id]);
|
|
1061
|
+
|
|
1062
|
+
await postService.delete(root.id);
|
|
1063
|
+
expect(await collectionService.getThreadIds(collection.id)).toEqual([]);
|
|
1064
|
+
});
|
|
922
1065
|
});
|
|
923
1066
|
|
|
924
|
-
describe("
|
|
925
|
-
it("returns all
|
|
1067
|
+
describe("getThreadIds", () => {
|
|
1068
|
+
it("returns all Thread root IDs in a collection", async () => {
|
|
926
1069
|
const col = await collectionService.create({
|
|
927
1070
|
slug: "test",
|
|
928
1071
|
title: "Test",
|
|
@@ -936,18 +1079,18 @@ describe("CollectionService", () => {
|
|
|
936
1079
|
bodyMarkdown: "two",
|
|
937
1080
|
});
|
|
938
1081
|
|
|
939
|
-
await collectionService.
|
|
940
|
-
await collectionService.
|
|
1082
|
+
await collectionService.addThread(col.id, p1.id);
|
|
1083
|
+
await collectionService.addThread(col.id, p2.id);
|
|
941
1084
|
|
|
942
|
-
const ids = await collectionService.
|
|
1085
|
+
const ids = await collectionService.getThreadIds(col.id);
|
|
943
1086
|
expect(ids).toHaveLength(2);
|
|
944
1087
|
expect(ids).toContain(p1.id);
|
|
945
1088
|
expect(ids).toContain(p2.id);
|
|
946
1089
|
});
|
|
947
1090
|
});
|
|
948
1091
|
|
|
949
|
-
describe("
|
|
950
|
-
it("replaces all collection memberships for a
|
|
1092
|
+
describe("syncThreadCollections", () => {
|
|
1093
|
+
it("replaces all collection memberships for a Thread", async () => {
|
|
951
1094
|
const col1 = await collectionService.create({
|
|
952
1095
|
slug: "col1",
|
|
953
1096
|
title: "Col 1",
|
|
@@ -967,11 +1110,14 @@ describe("CollectionService", () => {
|
|
|
967
1110
|
});
|
|
968
1111
|
|
|
969
1112
|
// Initially in col1 and col2
|
|
970
|
-
await collectionService.
|
|
971
|
-
await collectionService.
|
|
1113
|
+
await collectionService.addThread(col1.id, post.id);
|
|
1114
|
+
await collectionService.addThread(col2.id, post.id);
|
|
972
1115
|
|
|
973
1116
|
// Sync to col2 and col3
|
|
974
|
-
await collectionService.
|
|
1117
|
+
await collectionService.syncThreadCollections(post.id, [
|
|
1118
|
+
col2.id,
|
|
1119
|
+
col3.id,
|
|
1120
|
+
]);
|
|
975
1121
|
|
|
976
1122
|
const collections = await collectionService.getCollectionsByPostId(
|
|
977
1123
|
post.id,
|
|
@@ -993,8 +1139,8 @@ describe("CollectionService", () => {
|
|
|
993
1139
|
bodyMarkdown: "test",
|
|
994
1140
|
});
|
|
995
1141
|
|
|
996
|
-
await collectionService.
|
|
997
|
-
await collectionService.
|
|
1142
|
+
await collectionService.addThread(col.id, post.id);
|
|
1143
|
+
await collectionService.syncThreadCollections(post.id, []);
|
|
998
1144
|
|
|
999
1145
|
const collections = await collectionService.getCollectionsByPostId(
|
|
1000
1146
|
post.id,
|
|
@@ -1018,13 +1164,13 @@ describe("CollectionService", () => {
|
|
|
1018
1164
|
const originalTransaction = dbWithoutTransaction.transaction.bind(db);
|
|
1019
1165
|
dbWithoutTransaction.transaction = async () => {
|
|
1020
1166
|
throw new Error(
|
|
1021
|
-
"sqlite
|
|
1167
|
+
"sqlite syncThreadCollections() should not call transaction()",
|
|
1022
1168
|
);
|
|
1023
1169
|
};
|
|
1024
1170
|
|
|
1025
1171
|
try {
|
|
1026
1172
|
await expect(
|
|
1027
|
-
collectionService.
|
|
1173
|
+
collectionService.syncThreadCollections(post.id, [col.id]),
|
|
1028
1174
|
).resolves.toBeUndefined();
|
|
1029
1175
|
} finally {
|
|
1030
1176
|
dbWithoutTransaction.transaction = originalTransaction;
|