@jant/core 0.3.24 → 0.3.26
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/app.js +101 -571
- package/dist/client.js +1 -0
- package/dist/db/schema.js +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -9
- package/dist/lib/avatar-upload.js +134 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/constants.js +10 -9
- package/dist/lib/favicon.js +102 -0
- package/dist/lib/image.js +13 -17
- package/dist/lib/media-helpers.js +2 -2
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +48 -3
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +16 -11
- package/dist/lib/schemas.js +34 -3
- package/dist/lib/theme.js +4 -4
- package/dist/lib/timeline.js +24 -48
- package/dist/lib/timezones.js +388 -0
- package/dist/lib/view.js +3 -3
- package/dist/routes/api/collections.js +124 -0
- package/dist/routes/api/nav-items.js +104 -0
- package/dist/routes/api/pages.js +91 -0
- package/dist/routes/api/posts.js +3 -3
- package/dist/routes/api/search.js +2 -2
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +3 -3
- package/dist/routes/auth/reset.js +221 -0
- package/dist/routes/auth/setup.js +194 -0
- package/dist/routes/auth/signin.js +176 -0
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -416
- package/dist/routes/dash/index.js +1 -1
- package/dist/routes/dash/media.js +13 -393
- package/dist/routes/dash/pages.js +112 -86
- package/dist/routes/dash/posts.js +3 -5
- package/dist/routes/dash/redirects.js +20 -14
- package/dist/routes/dash/settings.js +213 -518
- package/dist/routes/feed/rss.js +4 -3
- package/dist/routes/feed/sitemap.js +5 -3
- package/dist/routes/pages/archive.js +3 -6
- package/dist/routes/pages/collection.js +3 -6
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +36 -0
- package/dist/routes/pages/home.js +33 -49
- package/dist/routes/pages/latest.js +45 -0
- package/dist/routes/pages/page.js +29 -32
- package/dist/routes/pages/post.js +3 -6
- package/dist/routes/pages/search.js +3 -6
- package/dist/services/page.js +5 -1
- package/dist/services/post.js +45 -31
- package/dist/services/search.js +1 -1
- package/dist/types/bindings.js +3 -0
- package/dist/types/config.js +147 -0
- package/dist/types/constants.js +27 -0
- package/dist/types/entities.js +3 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/props.js +3 -0
- package/dist/types/views.js +5 -0
- package/dist/types.js +8 -111
- package/dist/{theme → ui}/color-themes.js +33 -33
- package/dist/ui/compose/ComposeDialog.js +467 -0
- package/dist/ui/compose/ComposePrompt.js +55 -0
- package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
- package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
- package/dist/{theme/components → ui/dash}/PostList.js +6 -6
- package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
- package/dist/ui/dash/collections/CollectionForm.js +152 -0
- package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
- package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/dash/media/MediaListContent.js +166 -0
- package/dist/ui/dash/media/ViewMediaContent.js +212 -0
- package/dist/ui/dash/pages/LinkFormContent.js +130 -0
- package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
- package/dist/ui/dash/settings/AccountContent.js +209 -0
- package/dist/ui/dash/settings/AppearanceContent.js +259 -0
- package/dist/ui/dash/settings/GeneralContent.js +536 -0
- package/dist/ui/dash/settings/SettingsNav.js +41 -0
- package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
- package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
- package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
- package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
- package/dist/ui/feed/TimelineFeed.js +41 -0
- package/dist/ui/feed/TimelineItem.js +27 -0
- package/dist/ui/font-themes.js +36 -0
- package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +169 -0
- package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
- package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
- package/dist/ui/pages/CollectionsPage.js +76 -0
- package/dist/ui/pages/FeaturedPage.js +24 -0
- package/dist/ui/pages/HomePage.js +24 -0
- package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
- package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
- package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
- package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
- package/dist/ui/shared/index.js +5 -0
- package/package.json +1 -9
- package/src/__tests__/helpers/db.ts +3 -0
- package/src/app.tsx +131 -561
- package/src/client.ts +1 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +1 -1
- package/src/i18n/locales/en.po +477 -261
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +477 -261
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +477 -261
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +7 -36
- package/src/lib/__tests__/config.test.ts +192 -0
- package/src/lib/__tests__/favicon.test.ts +151 -0
- package/src/lib/__tests__/image.test.ts +2 -6
- package/src/lib/__tests__/schemas.test.ts +60 -19
- package/src/lib/__tests__/timeline.test.ts +45 -81
- package/src/lib/__tests__/timezones.test.ts +61 -0
- package/src/lib/__tests__/view.test.ts +15 -9
- package/src/lib/avatar-upload.ts +165 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/constants.ts +19 -10
- package/src/lib/favicon.ts +115 -0
- package/src/lib/image.ts +13 -21
- package/src/lib/media-helpers.ts +2 -2
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +73 -4
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +22 -15
- package/src/lib/schemas.ts +47 -6
- package/src/lib/theme.ts +5 -5
- package/src/lib/timeline.ts +28 -57
- package/src/lib/timezones.ts +325 -0
- package/src/lib/view.ts +3 -3
- package/src/preset.css +2 -1
- package/src/routes/__tests__/compose.test.ts +199 -0
- package/src/routes/api/__tests__/collections.test.ts +249 -0
- package/src/routes/api/__tests__/nav-items.test.ts +222 -0
- package/src/routes/api/__tests__/pages.test.ts +218 -0
- package/src/routes/api/__tests__/settings.test.ts +132 -0
- package/src/routes/api/collections.ts +143 -0
- package/src/routes/api/nav-items.ts +115 -0
- package/src/routes/api/pages.ts +101 -0
- package/src/routes/api/posts.ts +3 -3
- package/src/routes/api/search.ts +2 -2
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +2 -3
- package/src/routes/auth/reset.tsx +239 -0
- package/src/routes/auth/setup.tsx +189 -0
- package/src/routes/auth/signin.tsx +163 -0
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
- package/src/routes/dash/collections.tsx +18 -367
- package/src/routes/dash/index.tsx +1 -1
- package/src/routes/dash/media.tsx +13 -415
- package/src/routes/dash/pages.tsx +131 -98
- package/src/routes/dash/posts.tsx +3 -7
- package/src/routes/dash/redirects.tsx +22 -16
- package/src/routes/dash/settings.tsx +265 -478
- package/src/routes/feed/__tests__/rss.test.ts +141 -0
- package/src/routes/feed/rss.ts +5 -3
- package/src/routes/feed/sitemap.ts +5 -3
- package/src/routes/pages/__tests__/collections.test.ts +94 -0
- package/src/routes/pages/__tests__/featured.test.ts +94 -0
- package/src/routes/pages/archive.tsx +2 -6
- package/src/routes/pages/collection.tsx +2 -6
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +44 -0
- package/src/routes/pages/home.tsx +30 -53
- package/src/routes/pages/latest.tsx +59 -0
- package/src/routes/pages/page.tsx +28 -30
- package/src/routes/pages/post.tsx +2 -5
- package/src/routes/pages/search.tsx +2 -6
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post.test.ts +114 -15
- package/src/services/page.ts +13 -1
- package/src/services/post.ts +58 -40
- package/src/services/search.ts +2 -2
- package/src/styles/components.css +0 -65
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +475 -0
- package/src/types/bindings.ts +30 -0
- package/src/types/config.ts +183 -0
- package/src/types/constants.ts +26 -0
- package/src/types/entities.ts +109 -0
- package/src/types/operations.ts +88 -0
- package/src/types/props.ts +115 -0
- package/src/types/views.ts +172 -0
- package/src/types.ts +8 -774
- package/src/ui/__tests__/font-themes.test.ts +34 -0
- package/src/{theme → ui}/color-themes.ts +34 -34
- package/src/ui/compose/ComposeDialog.tsx +414 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
- package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
- package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
- package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
- package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
- package/src/ui/dash/collections/CollectionForm.tsx +153 -0
- package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/dash/media/MediaListContent.tsx +201 -0
- package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
- package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
- package/src/ui/dash/settings/AccountContent.tsx +176 -0
- package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
- package/src/ui/dash/settings/GeneralContent.tsx +533 -0
- package/src/ui/dash/settings/SettingsNav.tsx +56 -0
- package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
- package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
- package/src/ui/feed/TimelineFeed.tsx +49 -0
- package/src/ui/feed/TimelineItem.tsx +45 -0
- package/src/ui/font-themes.ts +54 -0
- package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +164 -0
- package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
- package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
- package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
- package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
- package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
- package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
- package/src/ui/shared/__tests__/pagination.test.ts +46 -0
- package/src/ui/shared/index.ts +12 -0
- package/bin/jant.js +0 -185
- package/dist/lib/theme-components.js +0 -46
- package/dist/routes/dash/navigation.js +0 -289
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
- package/dist/themes/threads/index.js +0 -81
- package/dist/themes/threads/pages/HomePage.js +0 -25
- package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
- package/dist/themes/threads/timeline/TimelineItem.js +0 -36
- package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
- package/dist/themes/threads/timeline/groupByDate.js +0 -22
- package/dist/themes/threads/timeline/timelineMore.js +0 -107
- package/src/lib/__tests__/theme-components.test.ts +0 -105
- package/src/lib/theme-components.ts +0 -65
- package/src/routes/dash/navigation.tsx +0 -317
- package/src/theme/components/index.ts +0 -23
- package/src/theme/index.ts +0 -22
- package/src/theme/layouts/index.ts +0 -7
- package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
- package/src/themes/threads/index.ts +0 -100
- package/src/themes/threads/style.css +0 -336
- package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
- package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
- package/src/themes/threads/timeline/groupByDate.ts +0 -30
- package/src/themes/threads/timeline/timelineMore.tsx +0 -130
- /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
- /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
- /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
- /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
- /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
- /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
- /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
package/dist/routes/feed/rss.js
CHANGED
|
@@ -14,10 +14,11 @@ export const rssRoutes = new Hono();
|
|
|
14
14
|
const siteDescription = all["SITE_DESCRIPTION"] ?? "";
|
|
15
15
|
const siteUrl = c.env.SITE_URL;
|
|
16
16
|
const siteLanguage = await getSiteLanguage(c);
|
|
17
|
+
const feedLimit = parseInt(c.env.RSS_FEED_LIMIT ?? "50", 10) || 50;
|
|
17
18
|
const posts = await c.var.services.posts.list({
|
|
18
19
|
status: "published",
|
|
19
20
|
excludeReplies: true,
|
|
20
|
-
limit:
|
|
21
|
+
limit: feedLimit
|
|
21
22
|
});
|
|
22
23
|
// Batch load media for enclosures
|
|
23
24
|
const postIds = posts.map((p)=>p.id);
|
|
@@ -40,7 +41,7 @@ export const rssRoutes = new Hono();
|
|
|
40
41
|
// RSS 2.0 Feed - main feed at /feed
|
|
41
42
|
rssRoutes.get("/", async (c)=>{
|
|
42
43
|
const feedData = await buildFeedData(c);
|
|
43
|
-
const renderer = c.var.config.
|
|
44
|
+
const renderer = c.var.config.feed?.rss ?? defaultRssRenderer;
|
|
44
45
|
const xml = renderer(feedData);
|
|
45
46
|
return new Response(xml, {
|
|
46
47
|
headers: {
|
|
@@ -51,7 +52,7 @@ rssRoutes.get("/", async (c)=>{
|
|
|
51
52
|
// Atom Feed
|
|
52
53
|
rssRoutes.get("/atom.xml", async (c)=>{
|
|
53
54
|
const feedData = await buildFeedData(c);
|
|
54
|
-
const renderer = c.var.config.
|
|
55
|
+
const renderer = c.var.config.feed?.atom ?? defaultAtomRenderer;
|
|
55
56
|
const xml = renderer(feedData);
|
|
56
57
|
return new Response(xml, {
|
|
57
58
|
headers: {
|
|
@@ -19,7 +19,7 @@ sitemapRoutes.get("/sitemap.xml", async (c)=>{
|
|
|
19
19
|
const mediaCtx = createMediaContext(c);
|
|
20
20
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
21
21
|
const pageViews = publishedPages.map(toPageView);
|
|
22
|
-
const renderer = c.var.config.
|
|
22
|
+
const renderer = c.var.config.feed?.sitemap ?? defaultSitemapRenderer;
|
|
23
23
|
const xml = renderer({
|
|
24
24
|
siteUrl,
|
|
25
25
|
posts: postViews,
|
|
@@ -32,10 +32,12 @@ sitemapRoutes.get("/sitemap.xml", async (c)=>{
|
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
// robots.txt
|
|
35
|
-
sitemapRoutes.get("/robots.txt", (c)=>{
|
|
35
|
+
sitemapRoutes.get("/robots.txt", async (c)=>{
|
|
36
36
|
const siteUrl = c.env.SITE_URL;
|
|
37
|
+
const noindex = await c.var.services.settings.get("NOINDEX") === "true";
|
|
38
|
+
const directive = noindex ? "Disallow: /" : "Allow: /";
|
|
37
39
|
const robots = `User-agent: *
|
|
38
|
-
|
|
40
|
+
${directive}
|
|
39
41
|
|
|
40
42
|
Sitemap: ${siteUrl}/sitemap.xml
|
|
41
43
|
`;
|
|
@@ -5,7 +5,7 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
5
5
|
* Shows all posts, optionally filtered by format or featured status
|
|
6
6
|
*/ import { Hono } from "hono";
|
|
7
7
|
import { FORMATS } from "../../types.js";
|
|
8
|
-
import { ArchivePage
|
|
8
|
+
import { ArchivePage } from "../../ui/pages/ArchivePage.js";
|
|
9
9
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
10
|
import { renderPublicPage } from "../../lib/render.js";
|
|
11
11
|
import { createMediaContext, toArchiveGroups } from "../../lib/view.js";
|
|
@@ -48,18 +48,15 @@ archiveRoutes.get("/", async (c)=>{
|
|
|
48
48
|
// Transform to View Models
|
|
49
49
|
const mediaCtx = createMediaContext(c);
|
|
50
50
|
const groups = toArchiveGroups(grouped, mediaCtx);
|
|
51
|
-
const components = c.var.config.theme?.components;
|
|
52
|
-
const Page = components?.ArchivePage ?? DefaultArchivePage;
|
|
53
51
|
return renderPublicPage(c, {
|
|
54
52
|
title: `Archive - ${navData.siteName}`,
|
|
55
53
|
navData,
|
|
56
|
-
content: /*#__PURE__*/ _jsx(
|
|
54
|
+
content: /*#__PURE__*/ _jsx(ArchivePage, {
|
|
57
55
|
groups: groups,
|
|
58
56
|
hasMore: hasMore,
|
|
59
57
|
nextCursor: nextCursor,
|
|
60
58
|
format: format,
|
|
61
|
-
featured: featured
|
|
62
|
-
theme: components
|
|
59
|
+
featured: featured
|
|
63
60
|
})
|
|
64
61
|
});
|
|
65
62
|
});
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* Collection Page Route
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
|
-
import { CollectionPage
|
|
5
|
+
import { CollectionPage } from "../../ui/pages/CollectionPage.js";
|
|
6
6
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
7
7
|
import { renderPublicPage } from "../../lib/render.js";
|
|
8
8
|
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
@@ -21,17 +21,14 @@ collectionRoutes.get("/:slug", async (c)=>{
|
|
|
21
21
|
// Transform to View Models
|
|
22
22
|
const mediaCtx = createMediaContext(c);
|
|
23
23
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
24
|
-
const components = c.var.config.theme?.components;
|
|
25
|
-
const Page = components?.CollectionPage ?? DefaultCollectionPage;
|
|
26
24
|
return renderPublicPage(c, {
|
|
27
25
|
title: `${collection.title} - ${navData.siteName}`,
|
|
28
26
|
description: collection.description ?? undefined,
|
|
29
27
|
navData,
|
|
30
|
-
content: /*#__PURE__*/ _jsx(
|
|
28
|
+
content: /*#__PURE__*/ _jsx(CollectionPage, {
|
|
31
29
|
collection: collection,
|
|
32
30
|
posts: postViews,
|
|
33
|
-
hasMore: false
|
|
34
|
-
theme: components
|
|
31
|
+
hasMore: false
|
|
35
32
|
})
|
|
36
33
|
});
|
|
37
34
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Collections Listing Page Route
|
|
4
|
+
*
|
|
5
|
+
* Lists all collections with their post counts.
|
|
6
|
+
*/ import { Hono } from "hono";
|
|
7
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
8
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
9
|
+
import { CollectionsPage } from "../../ui/pages/CollectionsPage.js";
|
|
10
|
+
export const collectionsPageRoutes = new Hono();
|
|
11
|
+
collectionsPageRoutes.get("/", async (c)=>{
|
|
12
|
+
const [allCollections, postCounts] = await Promise.all([
|
|
13
|
+
c.var.services.collections.list(),
|
|
14
|
+
c.var.services.collections.getPostCounts()
|
|
15
|
+
]);
|
|
16
|
+
const collections = allCollections.map((col)=>({
|
|
17
|
+
...col,
|
|
18
|
+
postCount: postCounts.get(col.id) ?? 0
|
|
19
|
+
}));
|
|
20
|
+
const navData = await getNavigationData(c);
|
|
21
|
+
return renderPublicPage(c, {
|
|
22
|
+
title: `Collections - ${navData.siteName}`,
|
|
23
|
+
navData,
|
|
24
|
+
content: /*#__PURE__*/ _jsx(CollectionsPage, {
|
|
25
|
+
collections: collections
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Featured Page Route
|
|
4
|
+
*
|
|
5
|
+
* Shows featured posts as a timeline feed.
|
|
6
|
+
*/ import { Hono } from "hono";
|
|
7
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
8
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
9
|
+
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
10
|
+
import { FeaturedPage } from "../../ui/pages/FeaturedPage.js";
|
|
11
|
+
export const featuredRoutes = new Hono();
|
|
12
|
+
featuredRoutes.get("/", async (c)=>{
|
|
13
|
+
const navData = await getNavigationData(c);
|
|
14
|
+
// When homepage already shows featured, redirect to avoid duplicate content
|
|
15
|
+
if (navData.homeDefaultView === "featured") {
|
|
16
|
+
return c.redirect("/", 302);
|
|
17
|
+
}
|
|
18
|
+
const posts = await c.var.services.posts.list({
|
|
19
|
+
featured: true,
|
|
20
|
+
status: "published",
|
|
21
|
+
excludeReplies: true
|
|
22
|
+
});
|
|
23
|
+
const mediaCtx = createMediaContext(c);
|
|
24
|
+
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
25
|
+
// Convert to timeline items (simple — no thread previews)
|
|
26
|
+
const items = postViews.map((post)=>({
|
|
27
|
+
post
|
|
28
|
+
}));
|
|
29
|
+
return renderPublicPage(c, {
|
|
30
|
+
title: `Featured - ${navData.siteName}`,
|
|
31
|
+
navData,
|
|
32
|
+
content: /*#__PURE__*/ _jsx(FeaturedPage, {
|
|
33
|
+
items: items
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -3,59 +3,46 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
3
3
|
* Home Page Route
|
|
4
4
|
*
|
|
5
5
|
* Timeline feed with per-type card components and thread previews.
|
|
6
|
-
*
|
|
6
|
+
* Uses page-based pagination.
|
|
7
|
+
*
|
|
8
|
+
* When HOME_DEFAULT_VIEW is "featured", the homepage shows featured posts
|
|
9
|
+
* instead of latest. The /latest route always shows latest posts explicitly.
|
|
7
10
|
*/ import { Hono } from "hono";
|
|
8
11
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
9
12
|
import { renderPublicPage } from "../../lib/render.js";
|
|
10
13
|
import { assembleTimeline } from "../../lib/timeline.js";
|
|
11
|
-
import { sse } from "../../lib/sse.js";
|
|
12
14
|
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
13
|
-
import { HomePage
|
|
15
|
+
import { HomePage } from "../../ui/pages/HomePage.js";
|
|
16
|
+
import { FeaturedPage } from "../../ui/pages/FeaturedPage.js";
|
|
14
17
|
export const homeRoutes = new Hono();
|
|
15
18
|
homeRoutes.get("/", async (c)=>{
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (cursor && !isNaN(cursor)) {
|
|
24
|
-
if (items.length === 0) {
|
|
25
|
-
return sse(c, async (stream)=>{
|
|
26
|
-
stream.remove("#load-more-container");
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
const themeConfig = c.var.config.theme;
|
|
30
|
-
const renderMore = themeConfig?.timelineMore;
|
|
31
|
-
if (!renderMore) {
|
|
32
|
-
// Should never happen — default theme always provides timelineMore
|
|
33
|
-
return sse(c, async (stream)=>{
|
|
34
|
-
stream.remove("#load-more-container");
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
const patches = renderMore({
|
|
38
|
-
items,
|
|
39
|
-
lastDate: lastDate ?? undefined,
|
|
40
|
-
hasMore,
|
|
41
|
-
nextCursor,
|
|
42
|
-
theme: themeConfig?.components
|
|
19
|
+
const navData = await getNavigationData(c);
|
|
20
|
+
if (navData.homeDefaultView === "featured") {
|
|
21
|
+
// Show featured posts on homepage
|
|
22
|
+
const posts = await c.var.services.posts.list({
|
|
23
|
+
featured: true,
|
|
24
|
+
status: "published",
|
|
25
|
+
excludeReplies: true
|
|
43
26
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
27
|
+
const mediaCtx = createMediaContext(c);
|
|
28
|
+
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
29
|
+
const items = postViews.map((post)=>({
|
|
30
|
+
post
|
|
31
|
+
}));
|
|
32
|
+
return renderPublicPage(c, {
|
|
33
|
+
title: navData.siteName,
|
|
34
|
+
navData,
|
|
35
|
+
content: /*#__PURE__*/ _jsx(FeaturedPage, {
|
|
36
|
+
items: items
|
|
37
|
+
})
|
|
55
38
|
});
|
|
56
39
|
}
|
|
57
|
-
//
|
|
58
|
-
const
|
|
40
|
+
// Default: show latest posts
|
|
41
|
+
const pageParam = c.req.query("page");
|
|
42
|
+
const page = pageParam ? Math.max(1, parseInt(pageParam, 10) || 1) : 1;
|
|
43
|
+
const { items, currentPage, totalPages } = await assembleTimeline(c, {
|
|
44
|
+
page
|
|
45
|
+
});
|
|
59
46
|
// Fetch pinned posts
|
|
60
47
|
const pinnedPosts = await c.var.services.posts.list({
|
|
61
48
|
pinned: true,
|
|
@@ -64,17 +51,14 @@ homeRoutes.get("/", async (c)=>{
|
|
|
64
51
|
});
|
|
65
52
|
const mediaCtx = createMediaContext(c);
|
|
66
53
|
const pinnedItems = toPostViewsFromPosts(pinnedPosts, mediaCtx);
|
|
67
|
-
const components = c.var.config.theme?.components;
|
|
68
|
-
const Page = components?.HomePage ?? DefaultHomePage;
|
|
69
54
|
return renderPublicPage(c, {
|
|
70
55
|
title: navData.siteName,
|
|
71
56
|
navData,
|
|
72
|
-
content: /*#__PURE__*/ _jsx(
|
|
57
|
+
content: /*#__PURE__*/ _jsx(HomePage, {
|
|
73
58
|
items: items,
|
|
74
59
|
pinnedItems: pinnedItems,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
theme: components
|
|
60
|
+
currentPage: currentPage,
|
|
61
|
+
totalPages: totalPages
|
|
78
62
|
})
|
|
79
63
|
});
|
|
80
64
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Latest Page Route
|
|
4
|
+
*
|
|
5
|
+
* Explicit /latest URL that always shows the latest posts timeline.
|
|
6
|
+
* When HOME_DEFAULT_VIEW is "latest" (default), this redirects to /
|
|
7
|
+
* to avoid duplicate content. When it's "featured", this serves as
|
|
8
|
+
* the explicit latest view.
|
|
9
|
+
*/ import { Hono } from "hono";
|
|
10
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
11
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
12
|
+
import { assembleTimeline } from "../../lib/timeline.js";
|
|
13
|
+
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
14
|
+
import { HomePage } from "../../ui/pages/HomePage.js";
|
|
15
|
+
export const latestRoutes = new Hono();
|
|
16
|
+
latestRoutes.get("/", async (c)=>{
|
|
17
|
+
const navData = await getNavigationData(c);
|
|
18
|
+
// When homepage already shows latest, redirect to avoid duplicate content
|
|
19
|
+
if (navData.homeDefaultView !== "featured") {
|
|
20
|
+
return c.redirect("/", 302);
|
|
21
|
+
}
|
|
22
|
+
const pageParam = c.req.query("page");
|
|
23
|
+
const page = pageParam ? Math.max(1, parseInt(pageParam, 10) || 1) : 1;
|
|
24
|
+
const { items, currentPage, totalPages } = await assembleTimeline(c, {
|
|
25
|
+
page
|
|
26
|
+
});
|
|
27
|
+
// Fetch pinned posts
|
|
28
|
+
const pinnedPosts = await c.var.services.posts.list({
|
|
29
|
+
pinned: true,
|
|
30
|
+
status: "published",
|
|
31
|
+
excludeReplies: true
|
|
32
|
+
});
|
|
33
|
+
const mediaCtx = createMediaContext(c);
|
|
34
|
+
const pinnedItems = toPostViewsFromPosts(pinnedPosts, mediaCtx);
|
|
35
|
+
return renderPublicPage(c, {
|
|
36
|
+
title: `Latest - ${navData.siteName}`,
|
|
37
|
+
navData,
|
|
38
|
+
content: /*#__PURE__*/ _jsx(HomePage, {
|
|
39
|
+
items: items,
|
|
40
|
+
pinnedItems: pinnedItems,
|
|
41
|
+
currentPage: currentPage,
|
|
42
|
+
totalPages: totalPages
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -2,44 +2,44 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* Custom Page Route
|
|
4
4
|
*
|
|
5
|
-
* Serves pages from the pages table and posts with custom
|
|
5
|
+
* Serves pages from the pages table and posts with custom paths.
|
|
6
6
|
* This is a catch-all route mounted at "/" - must be registered last.
|
|
7
|
+
* Supports multi-level paths (e.g. /2024/my-post) for posts.
|
|
7
8
|
*/ import { Hono } from "hono";
|
|
8
|
-
import { SinglePage
|
|
9
|
-
import { PostPage
|
|
9
|
+
import { SinglePage } from "../../ui/pages/SinglePage.js";
|
|
10
|
+
import { PostPage } from "../../ui/pages/PostPage.js";
|
|
10
11
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
11
12
|
import { renderPublicPage } from "../../lib/render.js";
|
|
12
13
|
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
13
14
|
import { createMediaContext, toPageView, toPostView } from "../../lib/view.js";
|
|
14
15
|
export const pageRoutes = new Hono();
|
|
15
|
-
// Catch-all for custom page
|
|
16
|
-
pageRoutes.get("
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
// Catch-all for custom page slugs and post paths (including multi-level)
|
|
17
|
+
pageRoutes.get("/*", async (c)=>{
|
|
18
|
+
const fullPath = c.req.path.slice(1); // Remove leading /
|
|
19
|
+
if (!fullPath) return c.notFound();
|
|
20
|
+
const isMultiSegment = fullPath.includes("/");
|
|
21
|
+
// Pages only have single-level slugs; skip page lookup for multi-segment paths
|
|
22
|
+
if (!isMultiSegment) {
|
|
23
|
+
const page = await c.var.services.pages.getBySlug(fullPath);
|
|
24
|
+
if (page) {
|
|
25
|
+
if (page.status === "draft") {
|
|
26
|
+
return c.notFound();
|
|
27
|
+
}
|
|
28
|
+
const navData = await getNavigationData(c);
|
|
29
|
+
const pageView = toPageView(page);
|
|
30
|
+
return renderPublicPage(c, {
|
|
31
|
+
title: `${page.title || fullPath} - ${navData.siteName}`,
|
|
32
|
+
description: page.body?.slice(0, 160),
|
|
33
|
+
navData,
|
|
34
|
+
content: /*#__PURE__*/ _jsx(SinglePage, {
|
|
35
|
+
page: pageView
|
|
36
|
+
})
|
|
37
|
+
});
|
|
24
38
|
}
|
|
25
|
-
const navData = await getNavigationData(c);
|
|
26
|
-
const pageView = toPageView(page);
|
|
27
|
-
const components = c.var.config.theme?.components;
|
|
28
|
-
const Page = components?.SinglePage ?? DefaultSinglePage;
|
|
29
|
-
return renderPublicPage(c, {
|
|
30
|
-
title: `${page.title || slug} - ${navData.siteName}`,
|
|
31
|
-
description: page.body?.slice(0, 160),
|
|
32
|
-
navData,
|
|
33
|
-
content: /*#__PURE__*/ _jsx(Page, {
|
|
34
|
-
page: pageView,
|
|
35
|
-
theme: components
|
|
36
|
-
})
|
|
37
|
-
});
|
|
38
39
|
}
|
|
39
|
-
//
|
|
40
|
-
const post = await c.var.services.posts.
|
|
40
|
+
// Posts support multi-level paths
|
|
41
|
+
const post = await c.var.services.posts.getByPath(fullPath);
|
|
41
42
|
if (post) {
|
|
42
|
-
// Don't show draft posts
|
|
43
43
|
if (post.status === "draft") {
|
|
44
44
|
return c.notFound();
|
|
45
45
|
}
|
|
@@ -55,15 +55,12 @@ pageRoutes.get("/:slug", async (c)=>{
|
|
|
55
55
|
}, mediaCtx);
|
|
56
56
|
const navData = await getNavigationData(c);
|
|
57
57
|
const title = post.title || navData.siteName;
|
|
58
|
-
const components = c.var.config.theme?.components;
|
|
59
|
-
const PostPage = components?.PostPage ?? DefaultPostPage;
|
|
60
58
|
return renderPublicPage(c, {
|
|
61
59
|
title,
|
|
62
60
|
description: post.body?.slice(0, 160),
|
|
63
61
|
navData,
|
|
64
62
|
content: /*#__PURE__*/ _jsx(PostPage, {
|
|
65
|
-
post: postView
|
|
66
|
-
theme: components
|
|
63
|
+
post: postView
|
|
67
64
|
})
|
|
68
65
|
});
|
|
69
66
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* Single Post Page Route
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
|
-
import { PostPage
|
|
5
|
+
import { PostPage } from "../../ui/pages/PostPage.js";
|
|
6
6
|
import * as sqid from "../../lib/sqid.js";
|
|
7
7
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
8
8
|
import { renderPublicPage } from "../../lib/render.js";
|
|
@@ -33,15 +33,12 @@ postRoutes.get("/:id", async (c)=>{
|
|
|
33
33
|
}, mediaCtx);
|
|
34
34
|
const navData = await getNavigationData(c);
|
|
35
35
|
const title = post.title || navData.siteName;
|
|
36
|
-
const components = c.var.config.theme?.components;
|
|
37
|
-
const Page = components?.PostPage ?? DefaultPostPage;
|
|
38
36
|
return renderPublicPage(c, {
|
|
39
37
|
title,
|
|
40
38
|
description: post.body?.slice(0, 160),
|
|
41
39
|
navData,
|
|
42
|
-
content: /*#__PURE__*/ _jsx(
|
|
43
|
-
post: postView
|
|
44
|
-
theme: components
|
|
40
|
+
content: /*#__PURE__*/ _jsx(PostPage, {
|
|
41
|
+
post: postView
|
|
45
42
|
})
|
|
46
43
|
});
|
|
47
44
|
});
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* Search Page Route
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
|
-
import { SearchPage
|
|
5
|
+
import { SearchPage } from "../../ui/pages/SearchPage.js";
|
|
6
6
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
7
7
|
import { renderPublicPage } from "../../lib/render.js";
|
|
8
8
|
import { createMediaContext, toSearchResultViews } from "../../lib/view.js";
|
|
@@ -40,18 +40,15 @@ searchRoutes.get("/", async (c)=>{
|
|
|
40
40
|
// Transform to View Models
|
|
41
41
|
const mediaCtx = createMediaContext(c);
|
|
42
42
|
const resultViews = toSearchResultViews(results, mediaCtx);
|
|
43
|
-
const components = c.var.config.theme?.components;
|
|
44
|
-
const Page = components?.SearchPage ?? DefaultSearchPage;
|
|
45
43
|
return renderPublicPage(c, {
|
|
46
44
|
title: query ? `Search: ${query} - ${navData.siteName}` : `Search - ${navData.siteName}`,
|
|
47
45
|
navData,
|
|
48
|
-
content: /*#__PURE__*/ _jsx(
|
|
46
|
+
content: /*#__PURE__*/ _jsx(SearchPage, {
|
|
49
47
|
query: query,
|
|
50
48
|
results: resultViews,
|
|
51
49
|
error: error,
|
|
52
50
|
hasMore: hasMore,
|
|
53
|
-
page: page
|
|
54
|
-
theme: components
|
|
51
|
+
page: page
|
|
55
52
|
})
|
|
56
53
|
});
|
|
57
54
|
});
|
package/dist/services/page.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Page Service
|
|
3
3
|
*
|
|
4
4
|
* CRUD operations for standalone pages (about, now, etc.)
|
|
5
|
-
*/ import { eq, desc } from "drizzle-orm";
|
|
5
|
+
*/ import { eq, desc, sql } from "drizzle-orm";
|
|
6
6
|
import { pages, navItems } from "../db/schema.js";
|
|
7
7
|
import { now } from "../lib/time.js";
|
|
8
8
|
import { render as renderMarkdown } from "../lib/markdown.js";
|
|
@@ -32,6 +32,10 @@ export function createPageService(db) {
|
|
|
32
32
|
const rows = await db.select().from(pages).orderBy(desc(pages.createdAt));
|
|
33
33
|
return rows.map(toPage);
|
|
34
34
|
},
|
|
35
|
+
async listNotInNav () {
|
|
36
|
+
const rows = await db.select().from(pages).where(sql`${pages.id} NOT IN (SELECT ${navItems.pageId} FROM ${navItems} WHERE ${navItems.pageId} IS NOT NULL)`).orderBy(desc(pages.createdAt));
|
|
37
|
+
return rows.map(toPage);
|
|
38
|
+
},
|
|
35
39
|
async create (data) {
|
|
36
40
|
const timestamp = now();
|
|
37
41
|
const bodyHtml = data.body ? renderMarkdown(data.body) : null;
|
package/dist/services/post.js
CHANGED
|
@@ -9,6 +9,34 @@ import { posts } from "../db/schema.js";
|
|
|
9
9
|
import { now } from "../lib/time.js";
|
|
10
10
|
import { render as renderMarkdown } from "../lib/markdown.js";
|
|
11
11
|
export function createPostService(db) {
|
|
12
|
+
/** Build WHERE conditions from filters (shared by list and count) */ function buildFilterConditions(filters) {
|
|
13
|
+
const conditions = [];
|
|
14
|
+
if (filters.status) {
|
|
15
|
+
conditions.push(eq(posts.status, filters.status));
|
|
16
|
+
}
|
|
17
|
+
if (filters.featured !== undefined) {
|
|
18
|
+
conditions.push(eq(posts.featured, filters.featured ? 1 : 0));
|
|
19
|
+
}
|
|
20
|
+
if (filters.pinned !== undefined) {
|
|
21
|
+
conditions.push(eq(posts.pinned, filters.pinned ? 1 : 0));
|
|
22
|
+
}
|
|
23
|
+
if (filters.format) {
|
|
24
|
+
conditions.push(eq(posts.format, filters.format));
|
|
25
|
+
}
|
|
26
|
+
if (filters.collectionId !== undefined) {
|
|
27
|
+
conditions.push(eq(posts.collectionId, filters.collectionId));
|
|
28
|
+
}
|
|
29
|
+
if (filters.threadId) {
|
|
30
|
+
conditions.push(eq(posts.threadId, filters.threadId));
|
|
31
|
+
}
|
|
32
|
+
if (filters.excludeReplies) {
|
|
33
|
+
conditions.push(isNull(posts.threadId));
|
|
34
|
+
}
|
|
35
|
+
if (!filters.includeDeleted) {
|
|
36
|
+
conditions.push(isNull(posts.deletedAt));
|
|
37
|
+
}
|
|
38
|
+
return conditions;
|
|
39
|
+
}
|
|
12
40
|
function toPost(row) {
|
|
13
41
|
return {
|
|
14
42
|
id: row.id,
|
|
@@ -16,7 +44,7 @@ export function createPostService(db) {
|
|
|
16
44
|
status: row.status,
|
|
17
45
|
featured: row.featured,
|
|
18
46
|
pinned: row.pinned,
|
|
19
|
-
|
|
47
|
+
path: row.path,
|
|
20
48
|
title: row.title,
|
|
21
49
|
url: row.url,
|
|
22
50
|
body: row.body,
|
|
@@ -37,43 +65,29 @@ export function createPostService(db) {
|
|
|
37
65
|
const result = await db.select().from(posts).where(and(eq(posts.id, id), isNull(posts.deletedAt))).limit(1);
|
|
38
66
|
return result[0] ? toPost(result[0]) : null;
|
|
39
67
|
},
|
|
40
|
-
async
|
|
41
|
-
const result = await db.select().from(posts).where(and(eq(posts.
|
|
68
|
+
async getByPath (path) {
|
|
69
|
+
const result = await db.select().from(posts).where(and(eq(posts.path, path), isNull(posts.deletedAt))).limit(1);
|
|
42
70
|
return result[0] ? toPost(result[0]) : null;
|
|
43
71
|
},
|
|
44
72
|
async list (filters = {}) {
|
|
45
|
-
const conditions =
|
|
46
|
-
if (filters.status) {
|
|
47
|
-
conditions.push(eq(posts.status, filters.status));
|
|
48
|
-
}
|
|
49
|
-
if (filters.featured !== undefined) {
|
|
50
|
-
conditions.push(eq(posts.featured, filters.featured ? 1 : 0));
|
|
51
|
-
}
|
|
52
|
-
if (filters.pinned !== undefined) {
|
|
53
|
-
conditions.push(eq(posts.pinned, filters.pinned ? 1 : 0));
|
|
54
|
-
}
|
|
55
|
-
if (filters.format) {
|
|
56
|
-
conditions.push(eq(posts.format, filters.format));
|
|
57
|
-
}
|
|
58
|
-
if (filters.collectionId !== undefined) {
|
|
59
|
-
conditions.push(eq(posts.collectionId, filters.collectionId));
|
|
60
|
-
}
|
|
61
|
-
if (filters.threadId) {
|
|
62
|
-
conditions.push(eq(posts.threadId, filters.threadId));
|
|
63
|
-
}
|
|
64
|
-
if (filters.excludeReplies) {
|
|
65
|
-
conditions.push(isNull(posts.threadId));
|
|
66
|
-
}
|
|
67
|
-
if (!filters.includeDeleted) {
|
|
68
|
-
conditions.push(isNull(posts.deletedAt));
|
|
69
|
-
}
|
|
73
|
+
const conditions = buildFilterConditions(filters);
|
|
70
74
|
if (filters.cursor) {
|
|
71
75
|
conditions.push(sql`${posts.id} < ${filters.cursor}`);
|
|
72
76
|
}
|
|
73
|
-
|
|
77
|
+
let query = db.select().from(posts).where(conditions.length > 0 ? and(...conditions) : undefined).orderBy(desc(posts.publishedAt), desc(posts.id)).limit(filters.limit ?? 100);
|
|
78
|
+
if (filters.offset !== undefined) {
|
|
79
|
+
query = query.offset(filters.offset);
|
|
80
|
+
}
|
|
74
81
|
const rows = await query;
|
|
75
82
|
return rows.map(toPost);
|
|
76
83
|
},
|
|
84
|
+
async count (filters = {}) {
|
|
85
|
+
const conditions = buildFilterConditions(filters);
|
|
86
|
+
const result = await db.select({
|
|
87
|
+
count: sql`count(*)`.as("count")
|
|
88
|
+
}).from(posts).where(conditions.length > 0 ? and(...conditions) : undefined);
|
|
89
|
+
return result[0]?.count ?? 0;
|
|
90
|
+
},
|
|
77
91
|
async create (data) {
|
|
78
92
|
const timestamp = now();
|
|
79
93
|
const bodyHtml = data.body ? renderMarkdown(data.body) : null;
|
|
@@ -98,7 +112,7 @@ export function createPostService(db) {
|
|
|
98
112
|
status,
|
|
99
113
|
featured: featured ? 1 : 0,
|
|
100
114
|
pinned: data.pinned ? 1 : 0,
|
|
101
|
-
|
|
115
|
+
path: data.path ?? null,
|
|
102
116
|
title: data.title ?? null,
|
|
103
117
|
url: data.url ?? null,
|
|
104
118
|
body: data.body ?? null,
|
|
@@ -123,7 +137,7 @@ export function createPostService(db) {
|
|
|
123
137
|
updatedAt: timestamp
|
|
124
138
|
};
|
|
125
139
|
if (data.format !== undefined) updates.format = data.format;
|
|
126
|
-
if (data.
|
|
140
|
+
if (data.path !== undefined) updates.path = data.path;
|
|
127
141
|
if (data.title !== undefined) updates.title = data.title;
|
|
128
142
|
if (data.url !== undefined) updates.url = data.url;
|
|
129
143
|
if (data.quoteText !== undefined) updates.quoteText = data.quoteText;
|
package/dist/services/search.js
CHANGED