@jant/core 0.6.14 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/import-site.js +81 -13
- package/bin/commands/migrate.js +55 -0
- package/bin/commands/posts/rebuild-html.js +184 -0
- package/bin/commands/site/snapshot/import.js +14 -10
- package/bin/lib/d1-query.js +23 -5
- package/bin/lib/migration-runner.js +29 -7
- package/bin/lib/node-database.js +32 -0
- package/bin/lib/site-snapshot.js +347 -46
- package/bin/lib/sql-export.js +1 -1
- package/bin/lib/thread-collection-migration.js +326 -0
- package/dist/app-BxCOR3Uc.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-B_eGKN5p.css +2 -0
- package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
- package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
- package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +5 -5
- package/package.json +11 -10
- package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
- package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
- package/src/__tests__/export-hugo-build.test.ts +63 -4
- package/src/__tests__/export-import-roundtrip.test.ts +6 -4
- package/src/__tests__/export-service.test.ts +142 -9
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +2 -0
- package/src/__tests__/import-site-command.test.ts +65 -0
- package/src/__tests__/vite-config.test.ts +45 -0
- package/src/app.tsx +16 -3
- package/src/client/__tests__/collection-created-notice.test.ts +36 -0
- package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
- package/src/client/__tests__/collection-page-actions.test.ts +71 -2
- package/src/client/__tests__/compose-bridge.test.ts +64 -0
- package/src/client/__tests__/feed-video-player.test.ts +16 -1
- package/src/client/__tests__/footnote-rail.test.ts +472 -0
- package/src/client/__tests__/hydrate-partial.test.ts +3 -0
- package/src/client/__tests__/toast.test.ts +18 -1
- package/src/client/collection-created-notice.ts +66 -0
- package/src/client/collection-form-bridge.ts +17 -0
- package/src/client/collection-navigation.ts +44 -0
- package/src/client/collection-page-actions.ts +57 -1
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
- package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
- package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
- package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
- package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-manager-types.ts +9 -3
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +2 -1
- package/src/client/components/jant-compose-dialog.ts +85 -21
- package/src/client/components/jant-compose-editor.ts +13 -7
- package/src/client/components/jant-compose-fullscreen.ts +24 -4
- package/src/client/components/jant-config-editor.ts +722 -0
- package/src/client/components/jant-media-lightbox.ts +100 -18
- package/src/client/components/jant-nav-manager.ts +1283 -54
- package/src/client/components/jant-post-form.ts +3 -3
- package/src/client/components/jant-post-menu.ts +86 -74
- package/src/client/components/nav-manager-types.ts +51 -2
- package/src/client/components/post-form-types.ts +1 -1
- package/src/client/compose-bridge.ts +4 -1
- package/src/client/feed-video-player.ts +62 -2
- package/src/client/footnote-rail.ts +478 -0
- package/src/client/hydrate-partial.ts +2 -0
- package/src/client/media-lightbox-events.ts +63 -0
- package/src/client/site-header-fragment.ts +38 -0
- package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
- package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
- package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
- package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
- package/src/client/tiptap/bubble-menu.ts +88 -10
- package/src/client/tiptap/continuous-lists.ts +102 -0
- package/src/client/tiptap/create-editor.ts +3 -0
- package/src/client/tiptap/extensions.ts +14 -5
- package/src/client/tiptap/markdown-clipboard.ts +608 -11
- package/src/client/tiptap/slash-commands.ts +8 -1
- package/src/client/tiptap/structural-keymap.ts +211 -0
- package/src/client/tiptap/tab-indent.ts +8 -22
- package/src/client/tiptap/table-control-labels.ts +30 -0
- package/src/client/tiptap/table-controls.ts +537 -0
- package/src/client/tiptap/table-size-picker.ts +254 -0
- package/src/client/toast.ts +8 -6
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/d1-query.test.ts +43 -1
- package/src/db/__tests__/migration-runner.test.ts +63 -0
- package/src/db/__tests__/migrations.test.ts +469 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/migrations/0027_old_blue_blade.sql +39 -0
- package/src/db/migrations/0028_superb_post.sql +2 -0
- package/src/db/migrations/0029_boring_magma.sql +1 -0
- package/src/db/migrations/0030_typical_vivisector.sql +50 -0
- package/src/db/migrations/meta/0027_snapshot.json +2511 -0
- package/src/db/migrations/meta/0028_snapshot.json +2511 -0
- package/src/db/migrations/meta/0029_snapshot.json +2519 -0
- package/src/db/migrations/meta/0030_snapshot.json +2541 -0
- package/src/db/migrations/meta/_journal.json +28 -0
- package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
- package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
- package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
- package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
- package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
- package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
- package/src/db/migrations/pg/meta/_journal.json +28 -0
- package/src/db/pg/schema.ts +28 -20
- package/src/db/rehearsal-fixtures/demo-current.json +39 -4
- package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
- package/src/db/schema.ts +28 -20
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +133 -30
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +128 -25
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +128 -25
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +513 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +515 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +515 -12
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/api-settings.test.ts +299 -0
- package/src/lib/__tests__/constants.test.ts +5 -0
- package/src/lib/__tests__/feed-policy.test.ts +25 -0
- package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
- package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
- package/src/lib/__tests__/markdown.test.ts +8 -6
- package/src/lib/__tests__/navigation.test.ts +59 -2
- package/src/lib/__tests__/post-body-html.test.ts +176 -0
- package/src/lib/__tests__/post-display.test.ts +64 -0
- package/src/lib/__tests__/resolve-config.test.ts +113 -1
- package/src/lib/__tests__/summary.test.ts +82 -0
- package/src/lib/__tests__/timeline.test.ts +109 -70
- package/src/lib/__tests__/timezones.test.ts +31 -3
- package/src/lib/__tests__/tiptap-render.test.ts +268 -9
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
- package/src/lib/__tests__/view.test.ts +53 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
- package/src/lib/api-settings.ts +239 -12
- package/src/lib/collection-sort.ts +7 -5
- package/src/lib/constants.ts +2 -0
- package/src/lib/feed-path.ts +25 -0
- package/src/lib/feed-policy.ts +64 -0
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +10 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/markdown-manager.ts +188 -5
- package/src/lib/markdown.ts +10 -3
- package/src/lib/navigation.ts +16 -3
- package/src/lib/post-body-html.ts +131 -0
- package/src/lib/post-display.ts +7 -1
- package/src/lib/render.tsx +9 -2
- package/src/lib/resolve-config.ts +100 -16
- package/src/lib/schemas.ts +133 -1
- package/src/lib/settings-paths.ts +5 -0
- package/src/lib/site-skill.ts +78 -0
- package/src/lib/summary.ts +16 -5
- package/src/lib/timeline.ts +98 -122
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +355 -81
- package/src/lib/view.ts +30 -20
- package/src/lib/worker-response-cache.ts +11 -18
- package/src/middleware/__tests__/public-content-access.test.ts +91 -0
- package/src/middleware/config.ts +2 -2
- package/src/middleware/public-content-access.ts +87 -0
- package/src/node/__tests__/cli-export.test.ts +14 -0
- package/src/node/__tests__/cli-migrate.test.ts +6 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
- package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
- package/src/node/index.ts +2 -1
- package/src/preset.css +16 -2
- package/src/routes/__tests__/compose.test.ts +51 -0
- package/src/routes/__tests__/site-skill.test.ts +112 -0
- package/src/routes/api/__tests__/collections.test.ts +91 -19
- package/src/routes/api/__tests__/mcp.test.ts +65 -1
- package/src/routes/api/__tests__/nav-items.test.ts +91 -0
- package/src/routes/api/__tests__/public-access.test.ts +81 -0
- package/src/routes/api/__tests__/settings.test.ts +217 -0
- package/src/routes/api/collections.ts +22 -26
- package/src/routes/api/export.ts +2 -0
- package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
- package/src/routes/api/internal/post-body-html.ts +43 -0
- package/src/routes/api/internal/sites.ts +24 -0
- package/src/routes/api/nav-items.ts +24 -1
- package/src/routes/api/posts.ts +2 -2
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +208 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +41 -24
- package/src/routes/api/search.ts +3 -0
- package/src/routes/api/settings.ts +33 -0
- package/src/routes/auth/__tests__/setup.test.ts +7 -4
- package/src/routes/compose.tsx +2 -1
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
- package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
- package/src/routes/pages/__tests__/collections.test.ts +9 -9
- package/src/routes/pages/__tests__/preview.test.ts +174 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +27 -7
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +83 -11
- package/src/routes/site-skill.ts +18 -0
- package/src/services/__tests__/auth.test.ts +7 -5
- package/src/services/__tests__/collection.test.ts +159 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +109 -30
- package/src/services/__tests__/post.test.ts +567 -5
- package/src/services/__tests__/search.test.ts +48 -1
- package/src/services/__tests__/settings.test.ts +8 -0
- package/src/services/__tests__/site-admin.test.ts +121 -1
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +3 -2
- package/src/services/collection.ts +226 -167
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +46 -25
- package/src/services/export-theme/layouts/_default/list.html +18 -4
- package/src/services/export-theme/layouts/_default/rss.xml +8 -12
- package/src/services/export-theme/layouts/featured/list.html +5 -4
- package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
- package/src/services/export-theme/styles/main.css +30 -2
- package/src/services/export.ts +91 -80
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/post.ts +874 -271
- package/src/services/search.ts +8 -2
- package/src/services/settings.ts +2 -10
- package/src/services/site-admin.ts +63 -43
- package/src/style-cjk-jp.css +1 -1
- package/src/style-cjk-kr.css +1 -1
- package/src/style-cjk-tc.css +1 -1
- package/src/style-cjk.css +1 -1
- package/src/styles/components.css +540 -0
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +14 -9
- package/src/styles/ui.css +829 -29
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +243 -12
- package/src/types/constants.ts +22 -1
- package/src/types/entities.ts +9 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +8 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +16 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +92 -2
- package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
- package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
- package/src/ui/dash/settings/GeneralContent.tsx +15 -5
- package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
- package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
- package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
- package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
- package/src/ui/feed/LinkCard.tsx +2 -12
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/QuoteCard.tsx +2 -12
- package/src/ui/feed/ThreadPreview.tsx +26 -34
- package/src/ui/feed/TimelineFeed.tsx +2 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
- package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
- package/src/ui/feed/thread-preview-state.ts +5 -7
- package/src/ui/font-themes.ts +74 -26
- package/src/ui/layouts/BaseLayout.tsx +22 -8
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
- package/src/ui/pages/CollectionPage.tsx +101 -22
- package/src/ui/pages/CollectionsPage.tsx +3 -1
- package/src/ui/pages/PostPage.tsx +39 -12
- package/src/ui/pages/SearchPage.tsx +6 -14
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
- package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
- package/src/ui/shared/CollectionDirectory.tsx +13 -9
- package/src/ui/shared/CollectionsManager.tsx +17 -7
- package/src/ui/shared/DraftPreviewBar.tsx +33 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +3 -1
- package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
- package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
- package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
- package/src/ui/shared/collection-management-labels.ts +33 -2
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +46 -0
- package/dist/app-DZDlmh7C.js +0 -6
- package/dist/client/_assets/client-Cevfpm8H.css +0 -2
- package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
- package/src/client/tiptap/wrapping-input-rules.ts +0 -102
package/src/lib/timeline.ts
CHANGED
|
@@ -28,7 +28,17 @@ export interface TimelineResult {
|
|
|
28
28
|
totalCount: number;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
interface CuratedThreadSource {
|
|
32
|
+
posts: Array<{ post: Post; position: number }>;
|
|
33
|
+
highlightedPostIds: ReadonlySet<string>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CuratedThreadDisplayOptions {
|
|
37
|
+
collectionPinnedThreadIds?: ReadonlySet<string>;
|
|
38
|
+
/** Visually focus the newest Post when the Thread itself is the selection. */
|
|
39
|
+
highlightLatestPost?: boolean;
|
|
40
|
+
showContextRatings: boolean;
|
|
41
|
+
}
|
|
32
42
|
|
|
33
43
|
async function buildTimelineItems(
|
|
34
44
|
c: Context<Env>,
|
|
@@ -57,12 +67,13 @@ async function buildTimelineItems(
|
|
|
57
67
|
mediaCtx.sitePathPrefix,
|
|
58
68
|
);
|
|
59
69
|
|
|
60
|
-
// Batch load media for
|
|
70
|
+
// Batch load media for the bounded leading/trailing thread context.
|
|
61
71
|
const contextPostIds = new Set<string>();
|
|
62
72
|
for (const ctx of threadContexts.values()) {
|
|
63
73
|
contextPostIds.add(ctx.latestReply.id);
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
for (const reply of [...ctx.leadingReplies, ...ctx.trailingReplies]) {
|
|
75
|
+
contextPostIds.add(reply.id);
|
|
76
|
+
}
|
|
66
77
|
}
|
|
67
78
|
const [contextMediaMap, contextCollectionsMap, contextAliasesMap] =
|
|
68
79
|
contextPostIds.size > 0
|
|
@@ -108,19 +119,21 @@ async function buildTimelineItems(
|
|
|
108
119
|
// Thread root is not the last post — hide reply button on it
|
|
109
120
|
postView.isLastInThread = false;
|
|
110
121
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
const toContextPostView = (reply: Post) =>
|
|
123
|
+
toPostView(
|
|
124
|
+
{
|
|
125
|
+
...reply,
|
|
126
|
+
mediaAttachments: contextMediaMap.get(reply.id) ?? [],
|
|
127
|
+
},
|
|
128
|
+
mediaCtx,
|
|
129
|
+
contextCollectionsMap.get(reply.id),
|
|
130
|
+
false,
|
|
131
|
+
firstContextAlias(reply.id),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const leadingReplyViews = threadCtx.leadingReplies.map(toContextPostView);
|
|
135
|
+
const trailingReplyViews =
|
|
136
|
+
threadCtx.trailingReplies.map(toContextPostView);
|
|
124
137
|
|
|
125
138
|
const latestReplyView = toPostView(
|
|
126
139
|
{
|
|
@@ -133,25 +146,11 @@ async function buildTimelineItems(
|
|
|
133
146
|
firstContextAlias(threadCtx.latestReply.id),
|
|
134
147
|
);
|
|
135
148
|
|
|
136
|
-
const penultimateReplyView = threadCtx.penultimateReply
|
|
137
|
-
? toPostView(
|
|
138
|
-
{
|
|
139
|
-
...threadCtx.penultimateReply,
|
|
140
|
-
mediaAttachments:
|
|
141
|
-
contextMediaMap.get(threadCtx.penultimateReply.id) ?? [],
|
|
142
|
-
},
|
|
143
|
-
mediaCtx,
|
|
144
|
-
contextCollectionsMap.get(threadCtx.penultimateReply.id),
|
|
145
|
-
false, // penultimateReply is not the last post
|
|
146
|
-
firstContextAlias(threadCtx.penultimateReply.id),
|
|
147
|
-
)
|
|
148
|
-
: undefined;
|
|
149
|
-
|
|
150
149
|
return {
|
|
151
150
|
post: postView,
|
|
152
151
|
threadPreview: {
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
leadingReplies: leadingReplyViews,
|
|
153
|
+
trailingReplies: trailingReplyViews,
|
|
155
154
|
latestReply: latestReplyView,
|
|
156
155
|
totalReplyCount: threadCtx.totalReplyCount,
|
|
157
156
|
},
|
|
@@ -182,13 +181,12 @@ export async function assembleTimelineItems(
|
|
|
182
181
|
async function buildCuratedThreadItems(
|
|
183
182
|
c: Context<Env>,
|
|
184
183
|
rootIds: string[],
|
|
185
|
-
threadsByRootId: Map<string,
|
|
186
|
-
|
|
187
|
-
collectionPinnedPostIds?: Set<string>,
|
|
184
|
+
threadsByRootId: Map<string, CuratedThreadSource>,
|
|
185
|
+
display: CuratedThreadDisplayOptions,
|
|
188
186
|
): Promise<TimelineItemView[]> {
|
|
189
187
|
const orderedThreads = rootIds
|
|
190
|
-
.map((rootId) => threadsByRootId.get(rootId)
|
|
191
|
-
.filter((thread) => thread
|
|
188
|
+
.map((rootId) => threadsByRootId.get(rootId))
|
|
189
|
+
.filter((thread): thread is CuratedThreadSource => Boolean(thread));
|
|
192
190
|
|
|
193
191
|
if (orderedThreads.length === 0) {
|
|
194
192
|
return [];
|
|
@@ -196,7 +194,7 @@ async function buildCuratedThreadItems(
|
|
|
196
194
|
|
|
197
195
|
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
198
196
|
const postIds = orderedThreads.flatMap((thread) =>
|
|
199
|
-
thread.map((post) => post.id),
|
|
197
|
+
thread.posts.map(({ post }) => post.id),
|
|
200
198
|
);
|
|
201
199
|
const [rawMediaMap, collectionsMap, curatedAliasesMap] = await Promise.all([
|
|
202
200
|
c.var.services.media.getByPostIds(postIds),
|
|
@@ -213,14 +211,15 @@ async function buildCuratedThreadItems(
|
|
|
213
211
|
);
|
|
214
212
|
|
|
215
213
|
return orderedThreads.reduce<TimelineItemView[]>((items, thread) => {
|
|
216
|
-
const
|
|
217
|
-
if (!
|
|
214
|
+
const rootEntry = thread.posts[0];
|
|
215
|
+
if (!rootEntry || rootEntry.position !== 0) {
|
|
218
216
|
return items;
|
|
219
217
|
}
|
|
220
218
|
|
|
221
|
-
const lastPostId = thread[thread.length - 1]?.id;
|
|
222
|
-
const
|
|
223
|
-
|
|
219
|
+
const lastPostId = thread.posts[thread.posts.length - 1]?.post.id;
|
|
220
|
+
const renderedPosts = thread.posts.map(({ post, position }) => ({
|
|
221
|
+
position,
|
|
222
|
+
view: toPostView(
|
|
224
223
|
{
|
|
225
224
|
...post,
|
|
226
225
|
mediaAttachments: mediaMap.get(post.id) ?? [],
|
|
@@ -229,54 +228,34 @@ async function buildCuratedThreadItems(
|
|
|
229
228
|
collectionsMap.get(post.id),
|
|
230
229
|
post.id === lastPostId,
|
|
231
230
|
curatedAliasesMap.get(post.id)?.[0],
|
|
232
|
-
|
|
231
|
+
post.id === post.threadId &&
|
|
232
|
+
display.collectionPinnedThreadIds?.has(post.threadId),
|
|
233
233
|
),
|
|
234
|
-
);
|
|
235
|
-
const rootView =
|
|
236
|
-
const selectedIds = selectedPostIdsByThread.get(root.id);
|
|
234
|
+
}));
|
|
235
|
+
const rootView = renderedPosts[0]?.view;
|
|
237
236
|
|
|
238
|
-
if (!rootView
|
|
237
|
+
if (!rootView) {
|
|
239
238
|
return items;
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
const
|
|
243
|
-
(indices, post, index) => {
|
|
244
|
-
if (selectedIds.has(post.id)) {
|
|
245
|
-
indices.push(index);
|
|
246
|
-
}
|
|
247
|
-
return indices;
|
|
248
|
-
},
|
|
249
|
-
[],
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
if (selectedIndices.length === 0) {
|
|
253
|
-
return items;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const selectedIndexSet = new Set(selectedIndices);
|
|
257
|
-
const visibleIndices = [
|
|
258
|
-
...new Set([0, postViews.length - 1, ...selectedIndices]),
|
|
259
|
-
].sort((left, right) => left - right);
|
|
260
|
-
const segments = visibleIndices.reduce<
|
|
241
|
+
const segments = renderedPosts.reduce<
|
|
261
242
|
NonNullable<TimelineItemView["curatedThread"]>["segments"]
|
|
262
|
-
>((items,
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const previousIndex =
|
|
269
|
-
segmentIndex === 0 ? undefined : visibleIndices[segmentIndex - 1];
|
|
243
|
+
>((items, renderedPost, segmentIndex) => {
|
|
244
|
+
const previousPosition =
|
|
245
|
+
segmentIndex === 0
|
|
246
|
+
? undefined
|
|
247
|
+
: renderedPosts[segmentIndex - 1]?.position;
|
|
270
248
|
|
|
271
249
|
items.push({
|
|
272
|
-
post:
|
|
250
|
+
post: renderedPost.view,
|
|
273
251
|
hiddenBeforeCount:
|
|
274
|
-
|
|
275
|
-
?
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
252
|
+
previousPosition === undefined
|
|
253
|
+
? renderedPost.position
|
|
254
|
+
: renderedPost.position - previousPosition - 1,
|
|
255
|
+
highlighted:
|
|
256
|
+
thread.highlightedPostIds.has(renderedPost.view.id) ||
|
|
257
|
+
(display.highlightLatestPost === true &&
|
|
258
|
+
renderedPost.view.id === lastPostId),
|
|
280
259
|
});
|
|
281
260
|
|
|
282
261
|
return items;
|
|
@@ -286,10 +265,10 @@ async function buildCuratedThreadItems(
|
|
|
286
265
|
return items;
|
|
287
266
|
}
|
|
288
267
|
|
|
289
|
-
const
|
|
268
|
+
const isStandaloneRoot =
|
|
290
269
|
segments.length === 1 && segments[0]?.post.id === rootView.id;
|
|
291
270
|
|
|
292
|
-
if (
|
|
271
|
+
if (isStandaloneRoot) {
|
|
293
272
|
items.push({ post: rootView });
|
|
294
273
|
return items;
|
|
295
274
|
}
|
|
@@ -298,6 +277,7 @@ async function buildCuratedThreadItems(
|
|
|
298
277
|
post: rootView,
|
|
299
278
|
curatedThread: {
|
|
300
279
|
rootPost: rootView,
|
|
280
|
+
showContextRatings: display.showContextRatings,
|
|
301
281
|
segments,
|
|
302
282
|
},
|
|
303
283
|
});
|
|
@@ -429,36 +409,31 @@ export async function assembleFeaturedTimeline(
|
|
|
429
409
|
return { items: [], currentPage: page, totalPages, totalCount };
|
|
430
410
|
}
|
|
431
411
|
|
|
432
|
-
const
|
|
433
|
-
await c.var.services.posts.
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
selectedPostIdsByThread.set(threadId, new Set(selectedIds));
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const items = await buildCuratedThreadItems(
|
|
447
|
-
c,
|
|
448
|
-
rootIds,
|
|
449
|
-
threadsByRootId,
|
|
450
|
-
selectedPostIdsByThread,
|
|
412
|
+
const featuredThreads =
|
|
413
|
+
await c.var.services.posts.getFeaturedThreadTimelineData(rootIds);
|
|
414
|
+
const threadsByRootId = new Map<string, CuratedThreadSource>(
|
|
415
|
+
[...featuredThreads].map(([threadId, thread]) => [
|
|
416
|
+
threadId,
|
|
417
|
+
{
|
|
418
|
+
posts: thread.posts,
|
|
419
|
+
highlightedPostIds: new Set(thread.featuredPostIds),
|
|
420
|
+
},
|
|
421
|
+
]),
|
|
451
422
|
);
|
|
452
423
|
|
|
424
|
+
const items = await buildCuratedThreadItems(c, rootIds, threadsByRootId, {
|
|
425
|
+
showContextRatings: false,
|
|
426
|
+
});
|
|
427
|
+
|
|
453
428
|
return { items, currentPage: page, totalPages, totalCount };
|
|
454
429
|
}
|
|
455
430
|
|
|
456
431
|
/**
|
|
457
432
|
* Assembles a paginated collection timeline grouped by thread root.
|
|
458
433
|
*
|
|
459
|
-
* Threads are ordered by collection
|
|
460
|
-
*
|
|
461
|
-
*
|
|
434
|
+
* Threads are ordered by collection activity/rating semantics. Every published
|
|
435
|
+
* post in each matching Thread is rendered; Collection membership never selects
|
|
436
|
+
* or hides individual posts within the Thread.
|
|
462
437
|
*
|
|
463
438
|
* @param c - Hono context (provides services + appConfig)
|
|
464
439
|
* @param options - Collection IDs, optional page number, auth state, and sort
|
|
@@ -504,27 +479,28 @@ export async function assembleCollectionTimeline(
|
|
|
504
479
|
return { items: [], currentPage: page, totalPages, totalCount };
|
|
505
480
|
}
|
|
506
481
|
|
|
507
|
-
const [threadsByRootId,
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
),
|
|
514
|
-
c.var.services.collections.getPinnedPostIds(options.collectionIds),
|
|
515
|
-
]);
|
|
516
|
-
const selectedPostIdsByThread: CuratedThreadSelectionMap = new Map(
|
|
517
|
-
[...collectedPostIdsByThread.entries()].map(([threadId, postIds]) => [
|
|
482
|
+
const [threadsByRootId, pinnedThreadIds] = await Promise.all([
|
|
483
|
+
c.var.services.posts.getPublishedThreads(rootIds),
|
|
484
|
+
c.var.services.collections.getPinnedThreadIds(options.collectionIds),
|
|
485
|
+
]);
|
|
486
|
+
const curatedThreadsByRootId = new Map<string, CuratedThreadSource>(
|
|
487
|
+
[...threadsByRootId].map(([threadId, thread]) => [
|
|
518
488
|
threadId,
|
|
519
|
-
|
|
489
|
+
{
|
|
490
|
+
posts: thread.map((post, position) => ({ post, position })),
|
|
491
|
+
highlightedPostIds: new Set<string>(),
|
|
492
|
+
},
|
|
520
493
|
]),
|
|
521
494
|
);
|
|
522
495
|
const items = await buildCuratedThreadItems(
|
|
523
496
|
c,
|
|
524
497
|
rootIds,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
498
|
+
curatedThreadsByRootId,
|
|
499
|
+
{
|
|
500
|
+
collectionPinnedThreadIds: pinnedThreadIds,
|
|
501
|
+
highlightLatestPost: true,
|
|
502
|
+
showContextRatings: true,
|
|
503
|
+
},
|
|
528
504
|
);
|
|
529
505
|
|
|
530
506
|
return { items, currentPage: page, totalPages, totalCount };
|
package/src/lib/timezones.ts
CHANGED
|
@@ -336,21 +336,28 @@ export const TIMEZONES: TimezoneEntry[] = [
|
|
|
336
336
|
},
|
|
337
337
|
];
|
|
338
338
|
|
|
339
|
-
function
|
|
340
|
-
return TIMEZONES.find(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
339
|
+
function findLegacyTimezoneEntry(value: string): TimezoneEntry | undefined {
|
|
340
|
+
return TIMEZONES.find((tz) => tz.legacyValues?.includes(value));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function resolveRuntimeTimeZone(value: string): string | null {
|
|
344
|
+
try {
|
|
345
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: value })
|
|
346
|
+
.resolvedOptions()
|
|
347
|
+
.timeZone.trim();
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
346
351
|
}
|
|
347
352
|
|
|
348
353
|
/**
|
|
349
|
-
* Normalizes a timezone value from settings, env, or browser detection
|
|
350
|
-
*
|
|
354
|
+
* Normalizes a timezone value from settings, env, or browser detection. Any
|
|
355
|
+
* IANA name or fixed-offset identifier supported by the runtime is preserved;
|
|
356
|
+
* historical display labels are mapped to their curated replacement. Unknown
|
|
357
|
+
* values fall back to UTC.
|
|
351
358
|
*
|
|
352
359
|
* @param value - Timezone value to normalize
|
|
353
|
-
* @returns
|
|
360
|
+
* @returns Runtime-normalized timezone identifier or UTC
|
|
354
361
|
*
|
|
355
362
|
* @example
|
|
356
363
|
* ```ts
|
|
@@ -365,12 +372,17 @@ export function normalizeTimeZone(value: string | null | undefined): string {
|
|
|
365
372
|
return "UTC";
|
|
366
373
|
}
|
|
367
374
|
|
|
368
|
-
|
|
375
|
+
const runtimeTimeZone = resolveRuntimeTimeZone(trimmed);
|
|
376
|
+
if (runtimeTimeZone) {
|
|
377
|
+
return runtimeTimeZone;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return findLegacyTimezoneEntry(trimmed)?.value ?? "UTC";
|
|
369
381
|
}
|
|
370
382
|
|
|
371
383
|
/**
|
|
372
|
-
* Returns whether the
|
|
373
|
-
*
|
|
384
|
+
* Returns whether the runtime recognizes a timezone value or the app can map
|
|
385
|
+
* it from a historical display label.
|
|
374
386
|
*
|
|
375
387
|
* @param value - Timezone value to validate
|
|
376
388
|
* @returns `true` when the value can be normalized safely
|
|
@@ -381,7 +393,35 @@ export function isSupportedTimeZone(value: string | null | undefined): boolean {
|
|
|
381
393
|
return false;
|
|
382
394
|
}
|
|
383
395
|
|
|
384
|
-
return
|
|
396
|
+
return (
|
|
397
|
+
resolveRuntimeTimeZone(trimmed) !== null ||
|
|
398
|
+
findLegacyTimezoneEntry(trimmed) !== undefined
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Returns the curated settings options plus a valid current value that is not
|
|
404
|
+
* already represented. This keeps the UI compact without losing an IANA zone
|
|
405
|
+
* or fixed offset detected outside the curated list.
|
|
406
|
+
*
|
|
407
|
+
* @param currentValue - Current stored timezone value
|
|
408
|
+
* @returns Timezone options that can represent the current setting
|
|
409
|
+
*/
|
|
410
|
+
export function getTimeZoneOptions(currentValue: string): TimezoneEntry[] {
|
|
411
|
+
const normalized = normalizeTimeZone(currentValue);
|
|
412
|
+
if (TIMEZONES.some((timezone) => timezone.value === normalized)) {
|
|
413
|
+
return TIMEZONES;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return [
|
|
417
|
+
...TIMEZONES,
|
|
418
|
+
{
|
|
419
|
+
value: normalized,
|
|
420
|
+
label: normalized,
|
|
421
|
+
offset: "",
|
|
422
|
+
iana: [normalized],
|
|
423
|
+
},
|
|
424
|
+
];
|
|
385
425
|
}
|
|
386
426
|
|
|
387
427
|
/**
|