@jant/core 0.3.35 → 0.3.37
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/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4564 -3013
- package/dist/index.js +12885 -8161
- package/package.json +23 -6
- package/src/__tests__/helpers/app.ts +10 -10
- package/src/__tests__/helpers/db.ts +91 -87
- package/src/app.tsx +157 -31
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/{lib → client}/avatar-upload.ts +4 -3
- package/src/{lib → client}/collection-form-bridge.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
- package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
- package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +43 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/client/components/compose-types.ts +174 -0
- package/src/client/components/jant-collection-form.ts +667 -0
- package/src/client/components/jant-collection-sidebar.ts +805 -0
- package/src/client/components/jant-compose-dialog.ts +2161 -0
- package/src/client/components/jant-compose-editor.ts +1813 -0
- package/src/client/components/jant-compose-fullscreen.ts +283 -0
- package/src/client/components/jant-media-lightbox.ts +259 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
- package/src/{ui → client}/components/jant-post-form.ts +141 -12
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
- package/src/{ui → client}/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/{ui → client}/components/nav-manager-types.ts +6 -18
- package/src/{ui → client}/components/post-form-template.ts +137 -38
- package/src/{ui → client}/components/post-form-types.ts +15 -4
- package/src/client/compose-bridge.ts +583 -0
- package/src/{lib → client}/image-processor.ts +26 -8
- package/src/client/lazy-slugify.ts +51 -0
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/{lib → client}/post-form-bridge.ts +53 -2
- package/src/{lib → client}/settings-bridge.ts +3 -15
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +86 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +65 -0
- package/src/client/tiptap/image-node.ts +482 -0
- package/src/client/tiptap/link-toolbar.ts +371 -0
- package/src/client/tiptap/more-break.ts +50 -0
- package/src/client/tiptap/paste-image.ts +129 -0
- package/src/client/tiptap/slash-commands.ts +438 -0
- package/src/{lib → client}/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +44 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +27 -17
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -140
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +783 -1087
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +867 -812
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +878 -823
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +186 -65
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +140 -65
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +963 -0
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +77 -31
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +22 -12
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +24 -5
- package/src/lib/resolve-config.ts +13 -2
- package/src/lib/schemas.ts +226 -58
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +158 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +76 -34
- package/src/lib/tiptap-render.ts +191 -0
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +263 -14
- package/src/lib/url.ts +37 -22
- package/src/lib/view.ts +236 -55
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/error-handler.ts +3 -3
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +83 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +57 -31
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +81 -62
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +92 -24
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +39 -31
- package/src/routes/auth/signin.tsx +13 -14
- package/src/routes/compose.tsx +27 -63
- package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +475 -99
- package/src/routes/feed/__tests__/rss.test.ts +22 -23
- package/src/routes/feed/rss.ts +6 -2
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +36 -18
- package/src/routes/pages/archive.tsx +177 -37
- package/src/routes/pages/collection.tsx +43 -14
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +27 -3
- package/src/routes/pages/home.tsx +15 -14
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +800 -230
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/__tests__/settings.test.ts +3 -3
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +764 -172
- package/src/services/search.ts +161 -74
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +293 -62
- package/src/styles/tokens.css +93 -5
- package/src/styles/ui.css +4349 -766
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +34 -4
- package/src/types/constants.ts +17 -2
- package/src/types/entities.ts +83 -37
- package/src/types/operations.ts +20 -27
- package/src/types/props.ts +52 -17
- package/src/types/views.ts +48 -24
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +255 -16
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +12 -2
- package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
- package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +87 -146
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +78 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +116 -103
- package/src/ui/pages/ArchivePage.tsx +923 -95
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +182 -38
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +239 -4
- package/src/ui/shared/MediaGallery.tsx +475 -41
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/collections-reorder.ts +0 -28
- package/src/lib/compose-bridge.ts +0 -280
- package/src/lib/media-upload.ts +0 -148
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/routes/dash/index.tsx +0 -103
- package/src/routes/dash/media.tsx +0 -132
- package/src/routes/dash/pages.tsx +0 -239
- package/src/routes/dash/posts.tsx +0 -334
- package/src/routes/dash/redirects.tsx +0 -257
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -203
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/types/sortablejs.d.ts +0 -29
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
- package/src/ui/components/compose-types.ts +0 -75
- package/src/ui/components/jant-collection-form.ts +0 -512
- package/src/ui/components/jant-compose-dialog.ts +0 -495
- package/src/ui/components/jant-compose-editor.ts +0 -814
- package/src/ui/dash/PageForm.tsx +0 -185
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
- package/src/ui/dash/collections/CollectionForm.tsx +0 -166
- package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
- package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
- package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
- package/src/ui/dash/media/MediaListContent.tsx +0 -201
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -74
- package/src/ui/dash/posts/PostForm.tsx +0 -248
- package/src/ui/dash/settings/SettingsNav.tsx +0 -52
- package/src/ui/layouts/DashLayout.tsx +0 -165
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
- /package/src/{ui → client}/components/settings-types.ts +0 -0
|
@@ -4,9 +4,9 @@ import type { Bindings } from "../../../types.js";
|
|
|
4
4
|
import type { AppVariables } from "../../../types/app-context.js";
|
|
5
5
|
import { createTestDatabase } from "../../../__tests__/helpers/db.js";
|
|
6
6
|
import { createPostService } from "../../../services/post.js";
|
|
7
|
+
import { createPathService } from "../../../services/path.js";
|
|
7
8
|
import { createSettingsService } from "../../../services/settings.js";
|
|
8
9
|
import { createMediaService } from "../../../services/media.js";
|
|
9
|
-
import { createPathRegistryService } from "../../../services/path-registry.js";
|
|
10
10
|
import { resolveConfig } from "../../../lib/resolve-config.js";
|
|
11
11
|
import { rssRoutes } from "../rss.js";
|
|
12
12
|
|
|
@@ -14,12 +14,11 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
|
14
14
|
|
|
15
15
|
function createFeedTestApp(envOverrides: Partial<Bindings> = {}) {
|
|
16
16
|
const { db } = createTestDatabase();
|
|
17
|
+
const pathService = createPathService(db as never);
|
|
17
18
|
|
|
18
19
|
const services = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
createPathRegistryService(db as never),
|
|
22
|
-
),
|
|
20
|
+
paths: pathService,
|
|
21
|
+
posts: createPostService(db as never, { slugIdLength: 5 }, pathService),
|
|
23
22
|
settings: createSettingsService(db as never),
|
|
24
23
|
media: createMediaService(db as never),
|
|
25
24
|
};
|
|
@@ -28,7 +27,7 @@ function createFeedTestApp(envOverrides: Partial<Bindings> = {}) {
|
|
|
28
27
|
|
|
29
28
|
app.use("*", async (c, next) => {
|
|
30
29
|
const env = {
|
|
31
|
-
SITE_URL: "http://localhost:
|
|
30
|
+
SITE_URL: "http://localhost:9020",
|
|
32
31
|
...envOverrides,
|
|
33
32
|
} as Bindings;
|
|
34
33
|
c.env = env;
|
|
@@ -54,13 +53,13 @@ describe("RSS Feed Routes", () => {
|
|
|
54
53
|
await services.posts.create({
|
|
55
54
|
format: "note",
|
|
56
55
|
title: "Regular Post",
|
|
57
|
-
|
|
56
|
+
bodyMarkdown: "Not featured",
|
|
58
57
|
status: "published",
|
|
59
58
|
});
|
|
60
59
|
await services.posts.create({
|
|
61
60
|
format: "note",
|
|
62
61
|
title: "Featured Post",
|
|
63
|
-
|
|
62
|
+
bodyMarkdown: "This is featured",
|
|
64
63
|
status: "published",
|
|
65
64
|
featured: true,
|
|
66
65
|
});
|
|
@@ -79,7 +78,7 @@ describe("RSS Feed Routes", () => {
|
|
|
79
78
|
await services.posts.create({
|
|
80
79
|
format: "note",
|
|
81
80
|
title: "Regular Post",
|
|
82
|
-
|
|
81
|
+
bodyMarkdown: "Not featured",
|
|
83
82
|
status: "published",
|
|
84
83
|
});
|
|
85
84
|
|
|
@@ -107,13 +106,13 @@ describe("RSS Feed Routes", () => {
|
|
|
107
106
|
await services.posts.create({
|
|
108
107
|
format: "note",
|
|
109
108
|
title: "Regular Post",
|
|
110
|
-
|
|
109
|
+
bodyMarkdown: "Not featured",
|
|
111
110
|
status: "published",
|
|
112
111
|
});
|
|
113
112
|
await services.posts.create({
|
|
114
113
|
format: "note",
|
|
115
114
|
title: "Featured Post",
|
|
116
|
-
|
|
115
|
+
bodyMarkdown: "This is featured",
|
|
117
116
|
status: "published",
|
|
118
117
|
featured: true,
|
|
119
118
|
});
|
|
@@ -137,20 +136,20 @@ describe("RSS Feed Routes", () => {
|
|
|
137
136
|
await services.posts.create({
|
|
138
137
|
format: "note",
|
|
139
138
|
title: "Regular Post",
|
|
140
|
-
|
|
139
|
+
bodyMarkdown: "Not featured",
|
|
141
140
|
status: "published",
|
|
142
141
|
});
|
|
143
142
|
await services.posts.create({
|
|
144
143
|
format: "note",
|
|
145
144
|
title: "Featured Post",
|
|
146
|
-
|
|
145
|
+
bodyMarkdown: "This is featured",
|
|
147
146
|
status: "published",
|
|
148
147
|
featured: true,
|
|
149
148
|
});
|
|
150
149
|
await services.posts.create({
|
|
151
150
|
format: "note",
|
|
152
151
|
title: "Draft Post",
|
|
153
|
-
|
|
152
|
+
bodyMarkdown: "Draft",
|
|
154
153
|
status: "draft",
|
|
155
154
|
});
|
|
156
155
|
|
|
@@ -169,7 +168,7 @@ describe("RSS Feed Routes", () => {
|
|
|
169
168
|
await services.posts.create({
|
|
170
169
|
format: "note",
|
|
171
170
|
title: "My Note",
|
|
172
|
-
|
|
171
|
+
bodyMarkdown: "A note",
|
|
173
172
|
status: "published",
|
|
174
173
|
});
|
|
175
174
|
await services.posts.create({
|
|
@@ -200,7 +199,7 @@ describe("RSS Feed Routes", () => {
|
|
|
200
199
|
await services.posts.create({
|
|
201
200
|
format: "note",
|
|
202
201
|
title: "My Note",
|
|
203
|
-
|
|
202
|
+
bodyMarkdown: "A note",
|
|
204
203
|
status: "published",
|
|
205
204
|
});
|
|
206
205
|
await services.posts.create({
|
|
@@ -236,13 +235,13 @@ describe("RSS Feed Routes", () => {
|
|
|
236
235
|
await services.posts.create({
|
|
237
236
|
format: "note",
|
|
238
237
|
title: "Regular Post",
|
|
239
|
-
|
|
238
|
+
bodyMarkdown: "Not featured",
|
|
240
239
|
status: "published",
|
|
241
240
|
});
|
|
242
241
|
await services.posts.create({
|
|
243
242
|
format: "note",
|
|
244
243
|
title: "Featured Post",
|
|
245
|
-
|
|
244
|
+
bodyMarkdown: "This is featured",
|
|
246
245
|
status: "published",
|
|
247
246
|
featured: true,
|
|
248
247
|
});
|
|
@@ -264,7 +263,7 @@ describe("RSS Feed Routes", () => {
|
|
|
264
263
|
await services.posts.create({
|
|
265
264
|
format: "note",
|
|
266
265
|
title: "My Note",
|
|
267
|
-
|
|
266
|
+
bodyMarkdown: "A note",
|
|
268
267
|
status: "published",
|
|
269
268
|
});
|
|
270
269
|
await services.posts.create({
|
|
@@ -292,7 +291,7 @@ describe("RSS Feed Routes", () => {
|
|
|
292
291
|
await services.posts.create({
|
|
293
292
|
format: "note",
|
|
294
293
|
title: `Post ${i}`,
|
|
295
|
-
|
|
294
|
+
bodyMarkdown: `Body ${i}`,
|
|
296
295
|
status: "published",
|
|
297
296
|
featured: true,
|
|
298
297
|
});
|
|
@@ -318,7 +317,7 @@ describe("RSS Feed Routes", () => {
|
|
|
318
317
|
await services.posts.create({
|
|
319
318
|
format: "note",
|
|
320
319
|
title: `Post ${i}`,
|
|
321
|
-
|
|
320
|
+
bodyMarkdown: `Body ${i}`,
|
|
322
321
|
status: "published",
|
|
323
322
|
});
|
|
324
323
|
}
|
|
@@ -346,7 +345,7 @@ describe("RSS Feed Routes", () => {
|
|
|
346
345
|
await services.posts.create({
|
|
347
346
|
format: "note",
|
|
348
347
|
title: `Post ${i}`,
|
|
349
|
-
|
|
348
|
+
bodyMarkdown: `Body ${i}`,
|
|
350
349
|
status: "published",
|
|
351
350
|
});
|
|
352
351
|
}
|
|
@@ -369,7 +368,7 @@ describe("RSS Feed Routes", () => {
|
|
|
369
368
|
await services.posts.create({
|
|
370
369
|
format: "note",
|
|
371
370
|
title: `Post ${i}`,
|
|
372
|
-
|
|
371
|
+
bodyMarkdown: `Body ${i}`,
|
|
373
372
|
status: "published",
|
|
374
373
|
featured: true,
|
|
375
374
|
});
|
package/src/routes/feed/rss.ts
CHANGED
|
@@ -23,6 +23,8 @@ export const rssRoutes = new Hono<Env>();
|
|
|
23
23
|
|
|
24
24
|
interface FeedOptions {
|
|
25
25
|
featured?: boolean;
|
|
26
|
+
excludeUnlisted?: boolean;
|
|
27
|
+
excludePrivate?: boolean;
|
|
26
28
|
format?: Format;
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -48,6 +50,8 @@ async function buildFeedData(
|
|
|
48
50
|
status: "published",
|
|
49
51
|
excludeReplies: true,
|
|
50
52
|
featured: opts?.featured,
|
|
53
|
+
excludeUnlisted: opts?.excludeUnlisted,
|
|
54
|
+
excludePrivate: opts?.excludePrivate ?? true,
|
|
51
55
|
format: opts?.format,
|
|
52
56
|
limit: feedLimit,
|
|
53
57
|
});
|
|
@@ -124,7 +128,7 @@ rssRoutes.get("/atom.xml", async (c) => {
|
|
|
124
128
|
// RSS 2.0 — /feed/all
|
|
125
129
|
rssRoutes.get("/all", async (c) => {
|
|
126
130
|
const format = parseFormatQuery(c);
|
|
127
|
-
const feedData = await buildFeedData(c, { format });
|
|
131
|
+
const feedData = await buildFeedData(c, { excludeUnlisted: true, format });
|
|
128
132
|
const xml = defaultRssRenderer(feedData);
|
|
129
133
|
|
|
130
134
|
return new Response(xml, {
|
|
@@ -137,7 +141,7 @@ rssRoutes.get("/all", async (c) => {
|
|
|
137
141
|
// Atom — /feed/all/atom.xml
|
|
138
142
|
rssRoutes.get("/all/atom.xml", async (c) => {
|
|
139
143
|
const format = parseFormatQuery(c);
|
|
140
|
-
const feedData = await buildFeedData(c, { format });
|
|
144
|
+
const feedData = await buildFeedData(c, { excludeUnlisted: true, format });
|
|
141
145
|
const xml = defaultAtomRenderer(feedData);
|
|
142
146
|
|
|
143
147
|
return new Response(xml, {
|
|
@@ -6,11 +6,7 @@ import { Hono } from "hono";
|
|
|
6
6
|
import type { Bindings } from "../../types.js";
|
|
7
7
|
import type { AppVariables } from "../../types/app-context.js";
|
|
8
8
|
import { defaultSitemapRenderer } from "../../lib/feed.js";
|
|
9
|
-
import {
|
|
10
|
-
createMediaContext,
|
|
11
|
-
toPostViewsFromPosts,
|
|
12
|
-
toPageView,
|
|
13
|
-
} from "../../lib/view.js";
|
|
9
|
+
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
14
10
|
|
|
15
11
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
16
12
|
|
|
@@ -24,23 +20,17 @@ sitemapRoutes.get("/sitemap.xml", async (c) => {
|
|
|
24
20
|
const posts = await c.var.services.posts.list({
|
|
25
21
|
status: "published",
|
|
26
22
|
excludeReplies: true,
|
|
23
|
+
excludePrivate: true,
|
|
27
24
|
limit: 1000,
|
|
28
25
|
});
|
|
29
26
|
|
|
30
|
-
// Fetch published pages
|
|
31
|
-
const publishedPages = await c.var.services.pages.list({
|
|
32
|
-
status: "published",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
27
|
// Transform to View Models
|
|
36
28
|
const mediaCtx = createMediaContext(appConfig);
|
|
37
29
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
38
|
-
const pageViews = publishedPages.map(toPageView);
|
|
39
30
|
|
|
40
31
|
const xml = defaultSitemapRenderer({
|
|
41
32
|
siteUrl,
|
|
42
33
|
posts: postViews,
|
|
43
|
-
pages: pageViews,
|
|
44
34
|
});
|
|
45
35
|
|
|
46
36
|
return new Response(xml, {
|
|
@@ -10,7 +10,6 @@ import { describe, it, expect, beforeEach } from "vitest";
|
|
|
10
10
|
import { createTestDatabase } from "../../../__tests__/helpers/db.js";
|
|
11
11
|
import { createCollectionService } from "../../../services/collection.js";
|
|
12
12
|
import { createPostService } from "../../../services/post.js";
|
|
13
|
-
import { createPathRegistryService } from "../../../services/path-registry.js";
|
|
14
13
|
import type { Database } from "../../../db/index.js";
|
|
15
14
|
|
|
16
15
|
describe("Collections Listing Page - Data Logic", () => {
|
|
@@ -22,7 +21,7 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
22
21
|
const testDb = createTestDatabase();
|
|
23
22
|
db = testDb.db as unknown as Database;
|
|
24
23
|
collectionService = createCollectionService(db);
|
|
25
|
-
postService = createPostService(db,
|
|
24
|
+
postService = createPostService(db, { slugIdLength: 5 });
|
|
26
25
|
});
|
|
27
26
|
|
|
28
27
|
it("returns collections with post counts", async () => {
|
|
@@ -38,11 +37,11 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
38
37
|
// Add posts to recipes collection via junction table
|
|
39
38
|
const p1 = await postService.create({
|
|
40
39
|
format: "note",
|
|
41
|
-
|
|
40
|
+
bodyMarkdown: "Recipe 1",
|
|
42
41
|
});
|
|
43
42
|
const p2 = await postService.create({
|
|
44
43
|
format: "note",
|
|
45
|
-
|
|
44
|
+
bodyMarkdown: "Recipe 2",
|
|
46
45
|
});
|
|
47
46
|
await collectionService.addPost(recipes.id, p1.id);
|
|
48
47
|
await collectionService.addPost(recipes.id, p2.id);
|
|
@@ -78,11 +77,11 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
78
77
|
|
|
79
78
|
const post = await postService.create({
|
|
80
79
|
format: "note",
|
|
81
|
-
|
|
80
|
+
bodyMarkdown: "Will be deleted",
|
|
82
81
|
});
|
|
83
82
|
const post2 = await postService.create({
|
|
84
83
|
format: "note",
|
|
85
|
-
|
|
84
|
+
bodyMarkdown: "Will remain",
|
|
86
85
|
});
|
|
87
86
|
|
|
88
87
|
await collectionService.addPost(col.id, post.id);
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import { describe, it, expect, beforeEach } from "vitest";
|
|
10
10
|
import { createTestDatabase } from "../../../__tests__/helpers/db.js";
|
|
11
11
|
import { createPostService } from "../../../services/post.js";
|
|
12
|
-
import { createPathRegistryService } from "../../../services/path-registry.js";
|
|
13
12
|
import type { Database } from "../../../db/index.js";
|
|
14
13
|
|
|
15
14
|
describe("Featured Page - Data Logic", () => {
|
|
@@ -19,25 +18,24 @@ describe("Featured Page - Data Logic", () => {
|
|
|
19
18
|
beforeEach(() => {
|
|
20
19
|
const testDb = createTestDatabase();
|
|
21
20
|
db = testDb.db as unknown as Database;
|
|
22
|
-
postService = createPostService(db,
|
|
21
|
+
postService = createPostService(db, { slugIdLength: 5 });
|
|
23
22
|
});
|
|
24
23
|
|
|
25
24
|
it("returns only featured published posts", async () => {
|
|
26
25
|
await postService.create({
|
|
27
26
|
format: "note",
|
|
28
|
-
|
|
27
|
+
bodyMarkdown: "Featured post",
|
|
29
28
|
featured: true,
|
|
30
29
|
status: "published",
|
|
31
30
|
});
|
|
32
31
|
await postService.create({
|
|
33
32
|
format: "note",
|
|
34
|
-
|
|
35
|
-
featured: false,
|
|
33
|
+
bodyMarkdown: "Normal post",
|
|
36
34
|
status: "published",
|
|
37
35
|
});
|
|
38
36
|
await postService.create({
|
|
39
37
|
format: "note",
|
|
40
|
-
|
|
38
|
+
bodyMarkdown: "Draft featured",
|
|
41
39
|
featured: true,
|
|
42
40
|
status: "draft",
|
|
43
41
|
});
|
|
@@ -45,51 +43,71 @@ describe("Featured Page - Data Logic", () => {
|
|
|
45
43
|
const posts = await postService.list({
|
|
46
44
|
featured: true,
|
|
47
45
|
status: "published",
|
|
48
|
-
excludeReplies: true,
|
|
49
46
|
});
|
|
50
47
|
|
|
51
48
|
expect(posts).toHaveLength(1);
|
|
52
|
-
expect(posts[0]?.
|
|
49
|
+
expect(posts[0]?.bodyText).toBe("Featured post");
|
|
53
50
|
});
|
|
54
51
|
|
|
55
52
|
it("returns empty list when no featured posts exist", async () => {
|
|
56
53
|
await postService.create({
|
|
57
54
|
format: "note",
|
|
58
|
-
|
|
55
|
+
bodyMarkdown: "Normal post",
|
|
59
56
|
status: "published",
|
|
60
57
|
});
|
|
61
58
|
|
|
62
59
|
const posts = await postService.list({
|
|
63
60
|
featured: true,
|
|
64
61
|
status: "published",
|
|
65
|
-
excludeReplies: true,
|
|
66
62
|
});
|
|
67
63
|
|
|
68
64
|
expect(posts).toHaveLength(0);
|
|
69
65
|
});
|
|
70
66
|
|
|
71
|
-
it("
|
|
67
|
+
it("includes featured reply posts", async () => {
|
|
72
68
|
const root = await postService.create({
|
|
73
69
|
format: "note",
|
|
74
|
-
|
|
75
|
-
featured: true,
|
|
70
|
+
bodyMarkdown: "Root post",
|
|
76
71
|
status: "published",
|
|
77
72
|
});
|
|
78
73
|
|
|
79
|
-
//
|
|
80
|
-
await postService.create({
|
|
74
|
+
// Create a reply and feature it independently
|
|
75
|
+
const reply = await postService.create({
|
|
81
76
|
format: "note",
|
|
82
|
-
|
|
77
|
+
bodyMarkdown: "Reply to root",
|
|
83
78
|
replyToId: root.id,
|
|
84
79
|
});
|
|
80
|
+
await postService.update(reply.id, { featured: true });
|
|
85
81
|
|
|
86
82
|
const posts = await postService.list({
|
|
87
83
|
featured: true,
|
|
88
84
|
status: "published",
|
|
89
|
-
excludeReplies: true,
|
|
90
85
|
});
|
|
91
86
|
|
|
92
87
|
expect(posts).toHaveLength(1);
|
|
93
|
-
expect(posts[0]?.
|
|
88
|
+
expect(posts[0]?.bodyText).toBe("Reply to root");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("featured root and featured reply both appear", async () => {
|
|
92
|
+
const root = await postService.create({
|
|
93
|
+
format: "note",
|
|
94
|
+
bodyMarkdown: "Featured root",
|
|
95
|
+
featured: true,
|
|
96
|
+
status: "published",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const reply = await postService.create({
|
|
100
|
+
format: "note",
|
|
101
|
+
bodyMarkdown: "Featured reply",
|
|
102
|
+
replyToId: root.id,
|
|
103
|
+
});
|
|
104
|
+
await postService.update(reply.id, { featured: true });
|
|
105
|
+
|
|
106
|
+
const posts = await postService.list({
|
|
107
|
+
featured: true,
|
|
108
|
+
status: "published",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(posts).toHaveLength(2);
|
|
94
112
|
});
|
|
95
113
|
});
|
|
@@ -1,73 +1,211 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Archive Page Route
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Tumblr-style archive grid with rich filtering:
|
|
5
|
+
* year, collection, format, media types, title presence.
|
|
6
|
+
* Page-based pagination with media-enriched post tiles.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { Hono } from "hono";
|
|
8
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
Bindings,
|
|
12
|
+
Format,
|
|
13
|
+
MediaKind,
|
|
14
|
+
PostWithMedia,
|
|
15
|
+
} from "../../types.js";
|
|
9
16
|
import type { AppVariables } from "../../types/app-context.js";
|
|
10
|
-
import {
|
|
17
|
+
import type {
|
|
18
|
+
ArchiveFilters,
|
|
19
|
+
ArchiveView,
|
|
20
|
+
ArchiveVisibility,
|
|
21
|
+
} from "../../types/props.js";
|
|
22
|
+
import { FORMATS, MEDIA_KINDS } from "../../types.js";
|
|
11
23
|
import { ArchivePage } from "../../ui/pages/ArchivePage.js";
|
|
12
24
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
13
25
|
import { renderPublicPage } from "../../lib/render.js";
|
|
14
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
createMediaContext,
|
|
28
|
+
toArchiveGroupsWithMedia,
|
|
29
|
+
} from "../../lib/view.js";
|
|
30
|
+
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
31
|
+
import type { PostFilters } from "../../services/post.js";
|
|
15
32
|
|
|
16
33
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
17
34
|
|
|
18
|
-
const PAGE_SIZE =
|
|
35
|
+
const PAGE_SIZE = 60;
|
|
19
36
|
|
|
20
37
|
export const archiveRoutes = new Hono<Env>();
|
|
21
38
|
|
|
22
|
-
// Archive page - all posts
|
|
23
39
|
archiveRoutes.get("/", async (c) => {
|
|
40
|
+
const { services, appConfig } = c.var;
|
|
41
|
+
|
|
42
|
+
// --- Parse query params ---------------------------------------------------
|
|
43
|
+
|
|
24
44
|
const formatParam = c.req.query("format") as Format | undefined;
|
|
25
45
|
const format =
|
|
26
46
|
formatParam && FORMATS.includes(formatParam) ? formatParam : undefined;
|
|
27
|
-
const featuredParam = c.req.query("featured");
|
|
28
|
-
const featured = featuredParam === "true" ? true : undefined;
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
48
|
+
const yearParam = c.req.query("year");
|
|
49
|
+
const year = yearParam ? parseInt(yearParam, 10) : undefined;
|
|
50
|
+
const validYear = year && !isNaN(year) && year > 1970 ? year : undefined;
|
|
51
|
+
|
|
52
|
+
const collectionSlug = c.req.query("collection") || undefined;
|
|
53
|
+
|
|
54
|
+
const mediaParam = c.req.query("media") || undefined;
|
|
55
|
+
const mediaKinds = mediaParam
|
|
56
|
+
? (mediaParam
|
|
57
|
+
.split(",")
|
|
58
|
+
.filter((m): m is MediaKind =>
|
|
59
|
+
(MEDIA_KINDS as readonly string[]).includes(m),
|
|
60
|
+
) as MediaKind[])
|
|
61
|
+
: undefined;
|
|
62
|
+
|
|
63
|
+
const hasMediaParam = c.req.query("hasMedia");
|
|
64
|
+
const hasMedia =
|
|
65
|
+
hasMediaParam === "1" ? true : hasMediaParam === "0" ? false : undefined;
|
|
66
|
+
|
|
67
|
+
const hasTitleParam = c.req.query("hasTitle");
|
|
68
|
+
const hasTitle =
|
|
69
|
+
hasTitleParam === "1" ? true : hasTitleParam === "0" ? false : undefined;
|
|
70
|
+
|
|
71
|
+
const VALID_VISIBILITIES = ["public", "unlisted", "private", "featured"];
|
|
72
|
+
const visibilityParam = c.req.query("visibility");
|
|
73
|
+
const visibilityAll = visibilityParam === "all";
|
|
74
|
+
const visibility =
|
|
75
|
+
visibilityParam && VALID_VISIBILITIES.includes(visibilityParam)
|
|
76
|
+
? (visibilityParam as ArchiveVisibility)
|
|
77
|
+
: undefined;
|
|
78
|
+
|
|
79
|
+
const viewParam = c.req.query("view") as ArchiveView | undefined;
|
|
80
|
+
const view =
|
|
81
|
+
viewParam && (viewParam === "grid" || viewParam === "list")
|
|
82
|
+
? viewParam
|
|
83
|
+
: undefined;
|
|
84
|
+
|
|
85
|
+
const pageParam = c.req.query("page");
|
|
86
|
+
const currentPage = Math.max(1, parseInt(pageParam || "1", 10) || 1);
|
|
87
|
+
|
|
88
|
+
// --- Resolve collection slug to ID ----------------------------------------
|
|
89
|
+
|
|
90
|
+
const collection = collectionSlug
|
|
91
|
+
? await services.collections.getBySlug(collectionSlug)
|
|
92
|
+
: undefined;
|
|
93
|
+
const collectionId = collection?.id;
|
|
94
|
+
|
|
95
|
+
// --- Build timestamp range for year filter --------------------------------
|
|
96
|
+
|
|
97
|
+
let publishedAfter: number | undefined;
|
|
98
|
+
let publishedBefore: number | undefined;
|
|
99
|
+
if (validYear) {
|
|
100
|
+
publishedAfter = Date.UTC(validYear, 0, 1) / 1000;
|
|
101
|
+
publishedBefore = Date.UTC(validYear + 1, 0, 1) / 1000;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Build filters --------------------------------------------------------
|
|
33
105
|
|
|
34
106
|
const navData = await getNavigationData(c);
|
|
35
107
|
|
|
36
|
-
//
|
|
37
|
-
|
|
108
|
+
// --- Map visibility filter to service-level filters -------------------------
|
|
109
|
+
// Visibility filter is only meaningful when authenticated — unauthenticated
|
|
110
|
+
// users cannot see unlisted or private posts regardless of the query param.
|
|
111
|
+
|
|
112
|
+
// Default to "public" when authenticated unless explicitly set to "all"
|
|
113
|
+
const effectiveVisibility = navData.isAuthenticated
|
|
114
|
+
? visibilityAll
|
|
115
|
+
? undefined
|
|
116
|
+
: (visibility ?? "public")
|
|
117
|
+
: undefined;
|
|
118
|
+
|
|
119
|
+
const filters: PostFilters = {
|
|
38
120
|
format,
|
|
39
121
|
status: "published",
|
|
40
|
-
featured,
|
|
41
122
|
excludeReplies: true,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
123
|
+
excludePrivate: !navData.isAuthenticated,
|
|
124
|
+
excludeUnlisted: !navData.isAuthenticated,
|
|
125
|
+
...(effectiveVisibility === "featured"
|
|
126
|
+
? { featured: true }
|
|
127
|
+
: effectiveVisibility
|
|
128
|
+
? { visibility: effectiveVisibility }
|
|
129
|
+
: {}),
|
|
130
|
+
collectionId,
|
|
131
|
+
publishedAfter,
|
|
132
|
+
publishedBefore,
|
|
133
|
+
mediaKinds: mediaKinds && mediaKinds.length > 0 ? mediaKinds : undefined,
|
|
134
|
+
hasMedia,
|
|
135
|
+
hasTitle,
|
|
136
|
+
};
|
|
45
137
|
|
|
46
|
-
|
|
47
|
-
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
138
|
+
// --- Parallel data fetches ------------------------------------------------
|
|
48
139
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
140
|
+
const [totalCount, posts, availableYears, allCollections] = await Promise.all(
|
|
141
|
+
[
|
|
142
|
+
services.posts.count(filters),
|
|
143
|
+
services.posts.list({
|
|
144
|
+
...filters,
|
|
145
|
+
limit: PAGE_SIZE,
|
|
146
|
+
offset: (currentPage - 1) * PAGE_SIZE,
|
|
147
|
+
}),
|
|
148
|
+
services.posts.getDistinctYears({
|
|
149
|
+
status: "published",
|
|
150
|
+
excludeReplies: true,
|
|
151
|
+
}),
|
|
152
|
+
services.collections.list(),
|
|
153
|
+
],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
55
157
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
158
|
+
// --- Batch-load media for posts -------------------------------------------
|
|
159
|
+
|
|
160
|
+
const postIds = posts.map((p) => p.id);
|
|
161
|
+
const rawMediaMap = await services.media.getByPostIds(postIds);
|
|
162
|
+
const mediaCtx = createMediaContext(appConfig);
|
|
163
|
+
const mediaMap = buildMediaMap(
|
|
164
|
+
rawMediaMap,
|
|
165
|
+
mediaCtx.r2PublicUrl,
|
|
166
|
+
mediaCtx.imageTransformUrl,
|
|
167
|
+
mediaCtx.s3PublicUrl,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// --- Group posts by year-month with media ---------------------------------
|
|
171
|
+
|
|
172
|
+
const grouped = new Map<string, PostWithMedia[]>();
|
|
173
|
+
for (const post of posts) {
|
|
174
|
+
const publishedAt = post.publishedAt ?? post.updatedAt;
|
|
175
|
+
const date = new Date(publishedAt * 1000);
|
|
60
176
|
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
61
177
|
if (!grouped.has(key)) {
|
|
62
178
|
grouped.set(key, []);
|
|
63
179
|
}
|
|
64
180
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Map.set() above guarantees key exists
|
|
65
|
-
grouped.get(key)!.push(
|
|
181
|
+
grouped.get(key)!.push({
|
|
182
|
+
...post,
|
|
183
|
+
mediaAttachments: mediaMap.get(post.id) ?? [],
|
|
184
|
+
});
|
|
66
185
|
}
|
|
67
186
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
187
|
+
const groups = toArchiveGroupsWithMedia(grouped, mediaCtx);
|
|
188
|
+
|
|
189
|
+
// --- Build active filter state for UI -------------------------------------
|
|
190
|
+
|
|
191
|
+
const archiveFilters: ArchiveFilters = {
|
|
192
|
+
year: validYear,
|
|
193
|
+
collectionSlug,
|
|
194
|
+
collectionTitle: collection?.title,
|
|
195
|
+
collectionIcon: collection?.icon,
|
|
196
|
+
format,
|
|
197
|
+
mediaKinds: mediaKinds && mediaKinds.length > 0 ? mediaKinds : undefined,
|
|
198
|
+
hasMedia,
|
|
199
|
+
hasTitle,
|
|
200
|
+
visibility: effectiveVisibility,
|
|
201
|
+
view,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const availableCollectionsList = allCollections.map((col) => ({
|
|
205
|
+
slug: col.slug,
|
|
206
|
+
title: col.title,
|
|
207
|
+
icon: col.icon,
|
|
208
|
+
}));
|
|
71
209
|
|
|
72
210
|
return renderPublicPage(c, {
|
|
73
211
|
title: `Archive - ${navData.siteName}`,
|
|
@@ -75,10 +213,12 @@ archiveRoutes.get("/", async (c) => {
|
|
|
75
213
|
content: (
|
|
76
214
|
<ArchivePage
|
|
77
215
|
groups={groups}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
216
|
+
currentPage={currentPage}
|
|
217
|
+
totalPages={totalPages}
|
|
218
|
+
filters={archiveFilters}
|
|
219
|
+
availableYears={availableYears}
|
|
220
|
+
availableCollections={availableCollectionsList}
|
|
221
|
+
isAuthenticated={navData.isAuthenticated}
|
|
82
222
|
/>
|
|
83
223
|
),
|
|
84
224
|
});
|