@jant/core 0.3.27 → 0.3.28
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/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +21 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
package/src/routes/feed/rss.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Context } from "hono";
|
|
7
7
|
import type { Bindings, FeedData } from "../../types.js";
|
|
8
|
-
import type { AppVariables } from "../../app.js";
|
|
8
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
9
9
|
import { defaultRssRenderer, defaultAtomRenderer } from "../../lib/feed.js";
|
|
10
|
-
import { getSiteLanguage } from "../../lib/config.js";
|
|
11
10
|
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
11
|
+
|
|
12
12
|
import { createMediaContext, toPostViews } from "../../lib/view.js";
|
|
13
13
|
|
|
14
14
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
@@ -19,13 +19,12 @@ export const rssRoutes = new Hono<Env>();
|
|
|
19
19
|
* Build FeedData from the Hono context.
|
|
20
20
|
*/
|
|
21
21
|
async function buildFeedData(c: Context<Env>): Promise<FeedData> {
|
|
22
|
-
const
|
|
23
|
-
const siteName =
|
|
24
|
-
const siteDescription =
|
|
25
|
-
const siteUrl =
|
|
26
|
-
const siteLanguage =
|
|
27
|
-
|
|
28
|
-
const feedLimit = parseInt(c.env.RSS_FEED_LIMIT ?? "50", 10) || 50;
|
|
22
|
+
const { appConfig } = c.var;
|
|
23
|
+
const siteName = appConfig.siteName;
|
|
24
|
+
const siteDescription = appConfig.siteDescription;
|
|
25
|
+
const siteUrl = appConfig.siteUrl;
|
|
26
|
+
const siteLanguage = appConfig.siteLanguage;
|
|
27
|
+
const feedLimit = appConfig.rssFeedLimit;
|
|
29
28
|
|
|
30
29
|
const posts = await c.var.services.posts.list({
|
|
31
30
|
status: "published",
|
|
@@ -36,7 +35,7 @@ async function buildFeedData(c: Context<Env>): Promise<FeedData> {
|
|
|
36
35
|
// Batch load media for enclosures
|
|
37
36
|
const postIds = posts.map((p) => p.id);
|
|
38
37
|
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
39
|
-
const mediaCtx = createMediaContext(
|
|
38
|
+
const mediaCtx = createMediaContext(appConfig);
|
|
40
39
|
const mediaMap = buildMediaMap(
|
|
41
40
|
rawMediaMap,
|
|
42
41
|
mediaCtx.r2PublicUrl,
|
|
@@ -65,9 +64,7 @@ async function buildFeedData(c: Context<Env>): Promise<FeedData> {
|
|
|
65
64
|
// RSS 2.0 Feed - main feed at /feed
|
|
66
65
|
rssRoutes.get("/", async (c) => {
|
|
67
66
|
const feedData = await buildFeedData(c);
|
|
68
|
-
|
|
69
|
-
const renderer = c.var.config.feed?.rss ?? defaultRssRenderer;
|
|
70
|
-
const xml = renderer(feedData);
|
|
67
|
+
const xml = defaultRssRenderer(feedData);
|
|
71
68
|
|
|
72
69
|
return new Response(xml, {
|
|
73
70
|
headers: {
|
|
@@ -79,9 +76,7 @@ rssRoutes.get("/", async (c) => {
|
|
|
79
76
|
// Atom Feed
|
|
80
77
|
rssRoutes.get("/atom.xml", async (c) => {
|
|
81
78
|
const feedData = await buildFeedData(c);
|
|
82
|
-
|
|
83
|
-
const renderer = c.var.config.feed?.atom ?? defaultAtomRenderer;
|
|
84
|
-
const xml = renderer(feedData);
|
|
79
|
+
const xml = defaultAtomRenderer(feedData);
|
|
85
80
|
|
|
86
81
|
return new Response(xml, {
|
|
87
82
|
headers: {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Bindings } from "../../types.js";
|
|
7
|
-
import type { AppVariables } from "../../app.js";
|
|
7
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
8
8
|
import { defaultSitemapRenderer } from "../../lib/feed.js";
|
|
9
9
|
import {
|
|
10
10
|
createMediaContext,
|
|
@@ -18,7 +18,8 @@ export const sitemapRoutes = new Hono<Env>();
|
|
|
18
18
|
|
|
19
19
|
// XML Sitemap
|
|
20
20
|
sitemapRoutes.get("/sitemap.xml", async (c) => {
|
|
21
|
-
const
|
|
21
|
+
const { appConfig } = c.var;
|
|
22
|
+
const siteUrl = appConfig.siteUrl;
|
|
22
23
|
|
|
23
24
|
const posts = await c.var.services.posts.list({
|
|
24
25
|
status: "published",
|
|
@@ -27,16 +28,20 @@ sitemapRoutes.get("/sitemap.xml", async (c) => {
|
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
// Fetch published pages
|
|
30
|
-
const
|
|
31
|
-
|
|
31
|
+
const publishedPages = await c.var.services.pages.list({
|
|
32
|
+
status: "published",
|
|
33
|
+
});
|
|
32
34
|
|
|
33
35
|
// Transform to View Models
|
|
34
|
-
const mediaCtx = createMediaContext(
|
|
36
|
+
const mediaCtx = createMediaContext(appConfig);
|
|
35
37
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
36
38
|
const pageViews = publishedPages.map(toPageView);
|
|
37
39
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
+
const xml = defaultSitemapRenderer({
|
|
41
|
+
siteUrl,
|
|
42
|
+
posts: postViews,
|
|
43
|
+
pages: pageViews,
|
|
44
|
+
});
|
|
40
45
|
|
|
41
46
|
return new Response(xml, {
|
|
42
47
|
headers: {
|
|
@@ -47,8 +52,9 @@ sitemapRoutes.get("/sitemap.xml", async (c) => {
|
|
|
47
52
|
|
|
48
53
|
// robots.txt
|
|
49
54
|
sitemapRoutes.get("/robots.txt", async (c) => {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
55
|
+
const { appConfig } = c.var;
|
|
56
|
+
const siteUrl = appConfig.siteUrl;
|
|
57
|
+
const noindex = appConfig.noindex;
|
|
52
58
|
|
|
53
59
|
const directive = noindex ? "Disallow: /" : "Allow: /";
|
|
54
60
|
const robots = `User-agent: *
|
|
@@ -34,17 +34,17 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
34
34
|
title: "Travel",
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
// Add posts to recipes collection
|
|
38
|
-
await postService.create({
|
|
37
|
+
// Add posts to recipes collection via junction table
|
|
38
|
+
const p1 = await postService.create({
|
|
39
39
|
format: "note",
|
|
40
40
|
body: "Recipe 1",
|
|
41
|
-
collectionId: recipes.id,
|
|
42
41
|
});
|
|
43
|
-
await postService.create({
|
|
42
|
+
const p2 = await postService.create({
|
|
44
43
|
format: "note",
|
|
45
44
|
body: "Recipe 2",
|
|
46
|
-
collectionId: recipes.id,
|
|
47
45
|
});
|
|
46
|
+
await collectionService.addPost(recipes.id, p1.id);
|
|
47
|
+
await collectionService.addPost(recipes.id, p2.id);
|
|
48
48
|
|
|
49
49
|
// Simulate route handler logic
|
|
50
50
|
const [allCollections, postCounts] = await Promise.all([
|
|
@@ -78,14 +78,15 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
78
78
|
const post = await postService.create({
|
|
79
79
|
format: "note",
|
|
80
80
|
body: "Will be deleted",
|
|
81
|
-
collectionId: col.id,
|
|
82
81
|
});
|
|
83
|
-
await postService.create({
|
|
82
|
+
const post2 = await postService.create({
|
|
84
83
|
format: "note",
|
|
85
84
|
body: "Will remain",
|
|
86
|
-
collectionId: col.id,
|
|
87
85
|
});
|
|
88
86
|
|
|
87
|
+
await collectionService.addPost(col.id, post.id);
|
|
88
|
+
await collectionService.addPost(col.id, post2.id);
|
|
89
|
+
|
|
89
90
|
await postService.delete(post.id);
|
|
90
91
|
|
|
91
92
|
const postCounts = await collectionService.getPostCounts();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
import type { Bindings, Format } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
9
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
10
10
|
import { FORMATS } from "../../types.js";
|
|
11
11
|
import { ArchivePage } from "../../ui/pages/ArchivePage.js";
|
|
12
12
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
@@ -66,7 +66,7 @@ archiveRoutes.get("/", async (c) => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Transform to View Models
|
|
69
|
-
const mediaCtx = createMediaContext(c);
|
|
69
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
70
70
|
const groups = toArchiveGroups(grouped, mediaCtx);
|
|
71
71
|
|
|
72
72
|
return renderPublicPage(c, {
|
|
@@ -4,11 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Bindings } from "../../types.js";
|
|
7
|
-
import type { AppVariables } from "../../app.js";
|
|
7
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
8
8
|
import { CollectionPage } from "../../ui/pages/CollectionPage.js";
|
|
9
9
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
10
|
import { renderPublicPage } from "../../lib/render.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
createMediaContext,
|
|
13
|
+
toPostViewsFromPosts,
|
|
14
|
+
toPostViews,
|
|
15
|
+
} from "../../lib/view.js";
|
|
16
|
+
import { defaultRssRenderer } from "../../lib/feed.js";
|
|
17
|
+
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
18
|
+
import { CollectionsSidebar } from "../../ui/shared/CollectionsSidebar.js";
|
|
12
19
|
|
|
13
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
14
21
|
|
|
@@ -20,23 +27,29 @@ collectionRoutes.get("/:slug", async (c) => {
|
|
|
20
27
|
const collection = await c.var.services.collections.getBySlug(slug);
|
|
21
28
|
if (!collection) return c.notFound();
|
|
22
29
|
|
|
23
|
-
// Fetch posts in
|
|
24
|
-
const posts = await
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
// Fetch posts and all collections in parallel
|
|
31
|
+
const [posts, allCollections] = await Promise.all([
|
|
32
|
+
c.var.services.posts.list({
|
|
33
|
+
collectionId: collection.id,
|
|
34
|
+
status: "published",
|
|
35
|
+
excludeReplies: true,
|
|
36
|
+
}),
|
|
37
|
+
c.var.services.collections.list(),
|
|
38
|
+
]);
|
|
29
39
|
|
|
30
40
|
const navData = await getNavigationData(c);
|
|
31
41
|
|
|
32
42
|
// Transform to View Models
|
|
33
|
-
const mediaCtx = createMediaContext(c);
|
|
43
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
34
44
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
35
45
|
|
|
36
46
|
return renderPublicPage(c, {
|
|
37
47
|
title: `${collection.title} - ${navData.siteName}`,
|
|
38
48
|
description: collection.description ?? undefined,
|
|
39
49
|
navData,
|
|
50
|
+
sidebar: (
|
|
51
|
+
<CollectionsSidebar collections={allCollections} activeSlug={slug} />
|
|
52
|
+
),
|
|
40
53
|
content: (
|
|
41
54
|
<CollectionPage
|
|
42
55
|
collection={collection}
|
|
@@ -46,3 +59,57 @@ collectionRoutes.get("/:slug", async (c) => {
|
|
|
46
59
|
),
|
|
47
60
|
});
|
|
48
61
|
});
|
|
62
|
+
|
|
63
|
+
// Collection RSS feed
|
|
64
|
+
collectionRoutes.get("/:slug/feed", async (c) => {
|
|
65
|
+
const slug = c.req.param("slug");
|
|
66
|
+
|
|
67
|
+
const collection = await c.var.services.collections.getBySlug(slug);
|
|
68
|
+
if (!collection) return c.notFound();
|
|
69
|
+
|
|
70
|
+
const { appConfig } = c.var;
|
|
71
|
+
const siteName = appConfig.siteName;
|
|
72
|
+
const siteUrl = appConfig.siteUrl;
|
|
73
|
+
const siteLanguage = appConfig.siteLanguage;
|
|
74
|
+
const feedLimit = appConfig.rssFeedLimit;
|
|
75
|
+
|
|
76
|
+
const posts = await c.var.services.posts.list({
|
|
77
|
+
collectionId: collection.id,
|
|
78
|
+
status: "published",
|
|
79
|
+
excludeReplies: true,
|
|
80
|
+
limit: feedLimit,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Batch load media for enclosures
|
|
84
|
+
const postIds = posts.map((p) => p.id);
|
|
85
|
+
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
86
|
+
const mediaCtx = createMediaContext(appConfig);
|
|
87
|
+
const mediaMap = buildMediaMap(
|
|
88
|
+
rawMediaMap,
|
|
89
|
+
mediaCtx.r2PublicUrl,
|
|
90
|
+
mediaCtx.imageTransformUrl,
|
|
91
|
+
mediaCtx.s3PublicUrl,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const postViews = toPostViews(
|
|
95
|
+
posts.map((p) => ({
|
|
96
|
+
...p,
|
|
97
|
+
mediaAttachments: mediaMap.get(p.id) ?? [],
|
|
98
|
+
})),
|
|
99
|
+
mediaCtx,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const xml = defaultRssRenderer({
|
|
103
|
+
siteName: `${collection.title} - ${siteName}`,
|
|
104
|
+
siteDescription: collection.description ?? "",
|
|
105
|
+
siteUrl,
|
|
106
|
+
siteLanguage,
|
|
107
|
+
posts: postViews,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return new Response(xml, {
|
|
111
|
+
headers: {
|
|
112
|
+
"Content-Type": "application/rss+xml; charset=utf-8",
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
import type { Bindings } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
9
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
10
10
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
11
11
|
import { renderPublicPage } from "../../lib/render.js";
|
|
12
12
|
import { CollectionsPage } from "../../ui/pages/CollectionsPage.js";
|
|
13
|
+
import { CollectionsSidebar } from "../../ui/shared/CollectionsSidebar.js";
|
|
13
14
|
|
|
14
15
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
15
16
|
|
|
@@ -31,6 +32,7 @@ collectionsPageRoutes.get("/", async (c) => {
|
|
|
31
32
|
return renderPublicPage(c, {
|
|
32
33
|
title: `Collections - ${navData.siteName}`,
|
|
33
34
|
navData,
|
|
35
|
+
sidebar: <CollectionsSidebar collections={allCollections} />,
|
|
34
36
|
content: <CollectionsPage collections={collections} />,
|
|
35
37
|
});
|
|
36
38
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
import type { Bindings } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
9
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
10
10
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
11
11
|
import { renderPublicPage } from "../../lib/render.js";
|
|
12
12
|
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
@@ -30,7 +30,7 @@ featuredRoutes.get("/", async (c) => {
|
|
|
30
30
|
excludeReplies: true,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
const mediaCtx = createMediaContext(c);
|
|
33
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
34
34
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
35
35
|
|
|
36
36
|
// Convert to timeline items (simple — no thread previews)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { Hono } from "hono";
|
|
12
12
|
import type { Bindings } from "../../types.js";
|
|
13
|
-
import type { AppVariables } from "../../app.js";
|
|
13
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
14
14
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
15
15
|
import { renderPublicPage } from "../../lib/render.js";
|
|
16
16
|
import { assembleTimeline } from "../../lib/timeline.js";
|
|
@@ -32,7 +32,7 @@ homeRoutes.get("/", async (c) => {
|
|
|
32
32
|
status: "published",
|
|
33
33
|
excludeReplies: true,
|
|
34
34
|
});
|
|
35
|
-
const mediaCtx = createMediaContext(c);
|
|
35
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
36
36
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
37
37
|
const items = postViews.map((post) => ({ post }));
|
|
38
38
|
|
|
@@ -57,7 +57,7 @@ homeRoutes.get("/", async (c) => {
|
|
|
57
57
|
status: "published",
|
|
58
58
|
excludeReplies: true,
|
|
59
59
|
});
|
|
60
|
-
const mediaCtx = createMediaContext(c);
|
|
60
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
61
61
|
const pinnedItems = toPostViewsFromPosts(pinnedPosts, mediaCtx);
|
|
62
62
|
|
|
63
63
|
return renderPublicPage(c, {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { Hono } from "hono";
|
|
11
11
|
import type { Bindings } from "../../types.js";
|
|
12
|
-
import type { AppVariables } from "../../app.js";
|
|
12
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
13
13
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
14
14
|
import { renderPublicPage } from "../../lib/render.js";
|
|
15
15
|
import { assembleTimeline } from "../../lib/timeline.js";
|
|
@@ -41,7 +41,7 @@ latestRoutes.get("/", async (c) => {
|
|
|
41
41
|
status: "published",
|
|
42
42
|
excludeReplies: true,
|
|
43
43
|
});
|
|
44
|
-
const mediaCtx = createMediaContext(c);
|
|
44
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
45
45
|
const pinnedItems = toPostViewsFromPosts(pinnedPosts, mediaCtx);
|
|
46
46
|
|
|
47
47
|
return renderPublicPage(c, {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { Hono } from "hono";
|
|
10
10
|
import type { Bindings } from "../../types.js";
|
|
11
|
-
import type { AppVariables } from "../../app.js";
|
|
11
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
12
12
|
import { SinglePage } from "../../ui/pages/SinglePage.js";
|
|
13
13
|
import { PostPage } from "../../ui/pages/PostPage.js";
|
|
14
14
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
@@ -58,7 +58,7 @@ pageRoutes.get("/*", async (c) => {
|
|
|
58
58
|
|
|
59
59
|
// Load media attachments
|
|
60
60
|
const rawMediaMap = await c.var.services.media.getByPostIds([post.id]);
|
|
61
|
-
const mediaCtx = createMediaContext(c);
|
|
61
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
62
62
|
const mediaMap = buildMediaMap(
|
|
63
63
|
rawMediaMap,
|
|
64
64
|
mediaCtx.r2PublicUrl,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Bindings } from "../../types.js";
|
|
7
|
-
import type { AppVariables } from "../../app.js";
|
|
7
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
8
8
|
import { PostPage } from "../../ui/pages/PostPage.js";
|
|
9
9
|
import * as sqid from "../../lib/sqid.js";
|
|
10
10
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
@@ -33,7 +33,7 @@ postRoutes.get("/:id", async (c) => {
|
|
|
33
33
|
|
|
34
34
|
// Batch load media attachments
|
|
35
35
|
const rawMediaMap = await c.var.services.media.getByPostIds([post.id]);
|
|
36
|
-
const mediaCtx = createMediaContext(c);
|
|
36
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
37
37
|
const mediaMap = buildMediaMap(
|
|
38
38
|
rawMediaMap,
|
|
39
39
|
mediaCtx.r2PublicUrl,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Bindings, SearchResult } from "../../types.js";
|
|
7
|
-
import type { AppVariables } from "../../app.js";
|
|
7
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
8
8
|
import { SearchPage } from "../../ui/pages/SearchPage.js";
|
|
9
9
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
10
|
import { renderPublicPage } from "../../lib/render.js";
|
|
@@ -49,7 +49,7 @@ searchRoutes.get("/", async (c) => {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// Transform to View Models
|
|
52
|
-
const mediaCtx = createMediaContext(c);
|
|
52
|
+
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
53
53
|
const resultViews = toSearchResultViews(results, mediaCtx);
|
|
54
54
|
|
|
55
55
|
return renderPublicPage(c, {
|