@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
|
@@ -27,10 +27,15 @@ import type {
|
|
|
27
27
|
import { FORMATS, MEDIA_KINDS } from "../../types.js";
|
|
28
28
|
import { ArchivePage } from "../../ui/pages/ArchivePage.js";
|
|
29
29
|
import { defaultFeedRenderer } from "../../lib/feed.js";
|
|
30
|
+
import {
|
|
31
|
+
getFeedEntryUpdatedAt,
|
|
32
|
+
getRssPublishedBefore,
|
|
33
|
+
RSS_FEED_CACHE_CONTROL,
|
|
34
|
+
} from "../../lib/feed-policy.js";
|
|
30
35
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
31
36
|
import { buildPageTitle } from "../../lib/page-title.js";
|
|
32
37
|
import { renderPublicPage } from "../../lib/render.js";
|
|
33
|
-
import { formatYearMonth
|
|
38
|
+
import { formatYearMonth } from "../../lib/time.js";
|
|
34
39
|
import { toAbsoluteSiteUrl, toPublicPath } from "../../lib/url.js";
|
|
35
40
|
import {
|
|
36
41
|
createMediaContext,
|
|
@@ -207,6 +212,8 @@ function buildArchivePostFilters(
|
|
|
207
212
|
hasMedia: params.hasMedia,
|
|
208
213
|
hasTitle: params.hasTitle,
|
|
209
214
|
hasReplies: params.hasReplies,
|
|
215
|
+
sortBy: "published",
|
|
216
|
+
ignorePinnedSort: true,
|
|
210
217
|
};
|
|
211
218
|
}
|
|
212
219
|
|
|
@@ -456,7 +463,9 @@ export async function renderArchivePage(
|
|
|
456
463
|
isAuthenticated={navData.isAuthenticated}
|
|
457
464
|
sitePathPrefix={navData.sitePathPrefix}
|
|
458
465
|
timeZone={appConfig.timeZone}
|
|
459
|
-
feedHref={
|
|
466
|
+
feedHref={
|
|
467
|
+
appConfig.rssFeedsEnabled ? `/archive/feed${feedQuery}` : undefined
|
|
468
|
+
}
|
|
460
469
|
/>
|
|
461
470
|
),
|
|
462
471
|
});
|
|
@@ -615,6 +624,12 @@ async function buildArchiveFeedData(
|
|
|
615
624
|
const collection = params.collectionSlug
|
|
616
625
|
? await services.collections.getBySlug(params.collectionSlug)
|
|
617
626
|
: undefined;
|
|
627
|
+
const rssPublishedBefore = getRssPublishedBefore(
|
|
628
|
+
appConfig.rssPublishDelaySeconds,
|
|
629
|
+
);
|
|
630
|
+
const yearPublishedBefore = params.validYear
|
|
631
|
+
? Date.UTC(params.validYear + 1, 0, 1) / 1000
|
|
632
|
+
: undefined;
|
|
618
633
|
|
|
619
634
|
// Feed mirrors the unauthenticated archive page: published + non-private,
|
|
620
635
|
// including Hidden-from-Latest. /archive is the canonical "all posts" view,
|
|
@@ -634,9 +649,12 @@ async function buildArchiveFeedData(
|
|
|
634
649
|
...(params.validYear
|
|
635
650
|
? {
|
|
636
651
|
publishedAfter: Date.UTC(params.validYear, 0, 1) / 1000,
|
|
637
|
-
publishedBefore: Date.UTC(params.validYear + 1, 0, 1) / 1000,
|
|
638
652
|
}
|
|
639
653
|
: {}),
|
|
654
|
+
publishedBefore:
|
|
655
|
+
yearPublishedBefore === undefined
|
|
656
|
+
? rssPublishedBefore
|
|
657
|
+
: Math.min(yearPublishedBefore, rssPublishedBefore),
|
|
640
658
|
limit: appConfig.rssFeedLimit,
|
|
641
659
|
};
|
|
642
660
|
|
|
@@ -648,7 +666,9 @@ async function buildArchiveFeedData(
|
|
|
648
666
|
// Batch load media, aliases, and thread replies
|
|
649
667
|
const postIds = posts.map((p) => p.id);
|
|
650
668
|
const [threadMap, rawMediaMap, aliasesMap] = await Promise.all([
|
|
651
|
-
services.posts.getPublishedThreads(rootIds
|
|
669
|
+
services.posts.getPublishedThreads(rootIds, {
|
|
670
|
+
publishedBefore: rssPublishedBefore,
|
|
671
|
+
}),
|
|
652
672
|
services.media.getByPostIds(postIds),
|
|
653
673
|
services.paths.getPostAliases(postIds),
|
|
654
674
|
]);
|
|
@@ -702,9 +722,9 @@ async function buildArchiveFeedData(
|
|
|
702
722
|
aliasMap,
|
|
703
723
|
).map((postView, index) => {
|
|
704
724
|
const post = posts[index] as (typeof posts)[number];
|
|
725
|
+
const thread = threadMap.get(post.id);
|
|
705
726
|
|
|
706
727
|
// Build thread replies
|
|
707
|
-
const thread = threadMap.get(post.id);
|
|
708
728
|
const replies =
|
|
709
729
|
thread && thread.length > 1
|
|
710
730
|
? toPostViews(
|
|
@@ -720,7 +740,7 @@ async function buildArchiveFeedData(
|
|
|
720
740
|
|
|
721
741
|
return {
|
|
722
742
|
...postView,
|
|
723
|
-
feedUpdatedAt:
|
|
743
|
+
feedUpdatedAt: getFeedEntryUpdatedAt(post, thread),
|
|
724
744
|
threadReplies: replies,
|
|
725
745
|
};
|
|
726
746
|
});
|
|
@@ -748,7 +768,7 @@ archiveRoutes.get("/feed", async (c) => {
|
|
|
748
768
|
return new Response(defaultFeedRenderer(feedData), {
|
|
749
769
|
headers: {
|
|
750
770
|
"Content-Type": "application/atom+xml; charset=utf-8",
|
|
751
|
-
"Cache-Control":
|
|
771
|
+
"Cache-Control": RSS_FEED_CACHE_CONTROL,
|
|
752
772
|
},
|
|
753
773
|
});
|
|
754
774
|
});
|
|
@@ -19,9 +19,13 @@ import {
|
|
|
19
19
|
} from "../../lib/collection-sort.js";
|
|
20
20
|
import { assembleCollectionTimeline } from "../../lib/timeline.js";
|
|
21
21
|
import { defaultFeedRenderer } from "../../lib/feed.js";
|
|
22
|
+
import {
|
|
23
|
+
getFeedEntryUpdatedAt,
|
|
24
|
+
getRssPublishedBefore,
|
|
25
|
+
RSS_FEED_CACHE_CONTROL,
|
|
26
|
+
} from "../../lib/feed-policy.js";
|
|
22
27
|
import { toPlainText as markdownToPlainText } from "../../lib/markdown.js";
|
|
23
28
|
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
24
|
-
import { toISOString } from "../../lib/time.js";
|
|
25
29
|
import { createMediaContext, toPostViews } from "../../lib/view.js";
|
|
26
30
|
import { toAbsoluteSiteUrl, toPublicPath } from "../../lib/url.js";
|
|
27
31
|
import {
|
|
@@ -147,16 +151,17 @@ export async function renderCollectionPage(
|
|
|
147
151
|
);
|
|
148
152
|
const isAggregate = selection.collections.length > 1;
|
|
149
153
|
|
|
150
|
-
const
|
|
151
|
-
|
|
154
|
+
const ratedThreadCount =
|
|
155
|
+
await c.var.services.posts.countCollectionThreadRootsUpToForCollections(
|
|
152
156
|
collectionIds,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
{
|
|
158
|
+
status: "published",
|
|
159
|
+
excludePrivate: !navData.isAuthenticated,
|
|
160
|
+
hasRating: true,
|
|
161
|
+
},
|
|
162
|
+
2,
|
|
163
|
+
);
|
|
164
|
+
const showRatingSort = supportsCollectionRatingSort(ratedThreadCount);
|
|
160
165
|
const requestedDefaultSort = isAggregate
|
|
161
166
|
? "newest"
|
|
162
167
|
: primaryCollection.sortOrder;
|
|
@@ -219,7 +224,17 @@ export async function renderCollectionPage(
|
|
|
219
224
|
defaultSort={defaultSort}
|
|
220
225
|
showRatingSort={showRatingSort}
|
|
221
226
|
isAuthenticated={navData.isAuthenticated}
|
|
227
|
+
isInNavigation={navData.links.some(
|
|
228
|
+
(item) =>
|
|
229
|
+
item.type === "collection" &&
|
|
230
|
+
item.collectionId === primaryCollection.id,
|
|
231
|
+
)}
|
|
222
232
|
sitePathPrefix={navData.sitePathPrefix}
|
|
233
|
+
feedHref={
|
|
234
|
+
c.var.appConfig.rssFeedsEnabled
|
|
235
|
+
? `${canonicalPagePath}/feed`
|
|
236
|
+
: undefined
|
|
237
|
+
}
|
|
223
238
|
/>
|
|
224
239
|
),
|
|
225
240
|
});
|
|
@@ -254,6 +269,9 @@ export async function renderCollectionFeed(
|
|
|
254
269
|
const siteUrl = appConfig.siteUrl;
|
|
255
270
|
const siteLanguage = appConfig.siteLanguage;
|
|
256
271
|
const feedLimit = appConfig.rssFeedLimit;
|
|
272
|
+
const publishedBefore = getRssPublishedBefore(
|
|
273
|
+
appConfig.rssPublishDelaySeconds,
|
|
274
|
+
);
|
|
257
275
|
const primaryCollection = selection.collections[0];
|
|
258
276
|
if (!primaryCollection) return null;
|
|
259
277
|
|
|
@@ -264,6 +282,7 @@ export async function renderCollectionFeed(
|
|
|
264
282
|
status: "published",
|
|
265
283
|
excludePrivate: true,
|
|
266
284
|
ignoreCollectionPinnedSort: true,
|
|
285
|
+
publishedBefore,
|
|
267
286
|
limit: feedLimit,
|
|
268
287
|
},
|
|
269
288
|
);
|
|
@@ -274,7 +293,7 @@ export async function renderCollectionFeed(
|
|
|
274
293
|
|
|
275
294
|
const postIds = posts.map((post) => post.id);
|
|
276
295
|
const [threadMap, rawMediaMap, aliasesMap] = await Promise.all([
|
|
277
|
-
c.var.services.posts.getPublishedThreads(rootIds),
|
|
296
|
+
c.var.services.posts.getPublishedThreads(rootIds, { publishedBefore }),
|
|
278
297
|
c.var.services.media.getByPostIds(postIds),
|
|
279
298
|
c.var.services.paths.getPostAliases(postIds),
|
|
280
299
|
]);
|
|
@@ -345,15 +364,9 @@ export async function renderCollectionFeed(
|
|
|
345
364
|
)
|
|
346
365
|
: undefined;
|
|
347
366
|
|
|
348
|
-
// feedUpdatedAt = max(lastActivityAt, collectedAt)
|
|
349
|
-
const lastActivity = toISOString(post.lastActivityAt);
|
|
350
|
-
const collectedIso = collectedAt ? toISOString(collectedAt) : null;
|
|
351
|
-
const feedUpdatedAt =
|
|
352
|
-
collectedIso && collectedIso > lastActivity ? collectedIso : lastActivity;
|
|
353
|
-
|
|
354
367
|
return {
|
|
355
368
|
...postView,
|
|
356
|
-
feedUpdatedAt,
|
|
369
|
+
feedUpdatedAt: getFeedEntryUpdatedAt(post, thread, [collectedAt]),
|
|
357
370
|
threadReplies: replies,
|
|
358
371
|
};
|
|
359
372
|
});
|
|
@@ -382,7 +395,7 @@ export async function renderCollectionFeed(
|
|
|
382
395
|
return new Response(xml, {
|
|
383
396
|
headers: {
|
|
384
397
|
"Content-Type": "application/atom+xml; charset=utf-8",
|
|
385
|
-
"Cache-Control":
|
|
398
|
+
"Cache-Control": RSS_FEED_CACHE_CONTROL,
|
|
386
399
|
},
|
|
387
400
|
});
|
|
388
401
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collections Listing Page Route
|
|
3
3
|
*
|
|
4
|
-
* Lists all collections with their
|
|
4
|
+
* Lists all collections with their Thread counts.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
@@ -70,6 +70,11 @@ collectionsPageRoutes.get("/", async (c) => {
|
|
|
70
70
|
<CollectionsPage
|
|
71
71
|
items={directoryData.items}
|
|
72
72
|
isAuthenticated={navData.isAuthenticated ?? false}
|
|
73
|
+
navigationCollectionIds={navData.links.flatMap((item) =>
|
|
74
|
+
item.type === "collection" && item.collectionId
|
|
75
|
+
? [item.collectionId]
|
|
76
|
+
: [],
|
|
77
|
+
)}
|
|
73
78
|
sitePathPrefix={navData.sitePathPrefix}
|
|
74
79
|
siteOrigin={c.var.appConfig.siteOrigin}
|
|
75
80
|
/>
|
|
@@ -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,6 +224,37 @@ 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,
|
|
@@ -225,7 +262,9 @@ async function renderPost(
|
|
|
225
262
|
);
|
|
226
263
|
|
|
227
264
|
return renderPublicPage(c, {
|
|
228
|
-
title:
|
|
265
|
+
title: previewTitle
|
|
266
|
+
? buildPageTitle(previewTitle, meta.title, navData.siteName)
|
|
267
|
+
: buildPageTitle(meta.title, navData.siteName),
|
|
229
268
|
description: meta.description,
|
|
230
269
|
...(post.status === "published"
|
|
231
270
|
? {
|
|
@@ -247,18 +286,59 @@ async function renderPost(
|
|
|
247
286
|
}
|
|
248
287
|
: {}),
|
|
249
288
|
navData,
|
|
289
|
+
...(options.isPreview
|
|
290
|
+
? {
|
|
291
|
+
pageChrome: <DraftPreviewBar />,
|
|
292
|
+
showComposeDialog: false,
|
|
293
|
+
noindex: true,
|
|
294
|
+
}
|
|
295
|
+
: {}),
|
|
250
296
|
content: (
|
|
251
|
-
<PostPage
|
|
297
|
+
<PostPage
|
|
298
|
+
post={postView}
|
|
299
|
+
threadPosts={threadPostViews}
|
|
300
|
+
isPreview={options.isPreview}
|
|
301
|
+
/>
|
|
252
302
|
),
|
|
253
303
|
});
|
|
254
304
|
}
|
|
255
305
|
|
|
306
|
+
pageRoutes.get("/preview/:slug", requireAuth(), async (c) => {
|
|
307
|
+
const post = await c.var.services.posts.getBySlug(c.req.param("slug"));
|
|
308
|
+
if (!post) return c.notFound();
|
|
309
|
+
|
|
310
|
+
if (post.status === "published") {
|
|
311
|
+
return c.redirect(
|
|
312
|
+
toPublicPath(`/${post.slug}`, c.var.appConfig.sitePathPrefix),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
c.header("Cache-Control", "private, no-store");
|
|
317
|
+
c.header("X-Robots-Tag", "noindex, nofollow");
|
|
318
|
+
return renderPost(c, post, { allowDraft: true, isPreview: true });
|
|
319
|
+
});
|
|
320
|
+
|
|
256
321
|
// Catch-all for path-registry backed post URLs, aliases, and redirects
|
|
257
322
|
pageRoutes.get("/*", async (c) => {
|
|
258
323
|
const fullPath = c.req.path.slice(1); // Remove leading /
|
|
259
324
|
if (!fullPath) return c.notFound();
|
|
260
325
|
const sitePathPrefix = c.var.appConfig.sitePathPrefix;
|
|
261
326
|
|
|
327
|
+
// Explicit application routes are registered before this catch-all. Any
|
|
328
|
+
// unmatched path under a reserved prefix is therefore an application 404,
|
|
329
|
+
// never a legacy alias or redirect from path_registry.
|
|
330
|
+
if (isReservedPath(fullPath)) return c.notFound();
|
|
331
|
+
|
|
332
|
+
// Stored redirects outrank the normal post/collection/deep-link resolvers,
|
|
333
|
+
// but can never shadow an explicit or reserved application route.
|
|
334
|
+
const resolved = await c.var.services.paths.resolve(fullPath);
|
|
335
|
+
if (resolved?.kind === "redirect" && resolved.redirectToPath) {
|
|
336
|
+
return c.redirect(
|
|
337
|
+
toPublicHref(`/${resolved.redirectToPath}`, sitePathPrefix),
|
|
338
|
+
resolved.redirectType ?? 301,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
262
342
|
if (fullPath.endsWith("/feed")) {
|
|
263
343
|
const collectionPath = fullPath.slice(0, -"/feed".length);
|
|
264
344
|
if (!collectionPath) return c.notFound();
|
|
@@ -360,16 +440,8 @@ pageRoutes.get("/*", async (c) => {
|
|
|
360
440
|
}
|
|
361
441
|
}
|
|
362
442
|
|
|
363
|
-
const resolved = await c.var.services.paths.resolve(fullPath);
|
|
364
443
|
if (!resolved) return c.notFound();
|
|
365
444
|
|
|
366
|
-
if (resolved.kind === "redirect" && resolved.redirectToPath) {
|
|
367
|
-
return c.redirect(
|
|
368
|
-
toPublicHref(`/${resolved.redirectToPath}`, sitePathPrefix),
|
|
369
|
-
resolved.redirectType ?? 301,
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
445
|
if (resolved.kind === "archive" && resolved.archiveQuery) {
|
|
374
446
|
const overrides = Object.fromEntries(
|
|
375
447
|
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);
|