@jant/core 0.3.22 → 0.3.24
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 +23 -5
- package/dist/db/schema.js +72 -47
- 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 +5 -6
- package/dist/lib/constants.js +1 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/navigation.js +4 -5
- package/dist/lib/render.js +1 -1
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme-components.js +8 -11
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +119 -0
- package/dist/lib/view.js +62 -73
- package/dist/routes/api/posts.js +29 -35
- package/dist/routes/api/search.js +5 -6
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/dash/collections.js +22 -40
- package/dist/routes/dash/index.js +2 -2
- package/dist/routes/dash/navigation.js +25 -24
- package/dist/routes/dash/pages.js +42 -57
- package/dist/routes/dash/posts.js +27 -35
- package/dist/routes/feed/rss.js +2 -4
- package/dist/routes/feed/sitemap.js +10 -7
- package/dist/routes/pages/archive.js +12 -11
- package/dist/routes/pages/collection.js +11 -5
- package/dist/routes/pages/home.js +53 -61
- package/dist/routes/pages/page.js +60 -29
- package/dist/routes/pages/post.js +5 -12
- package/dist/routes/pages/search.js +3 -4
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +80 -0
- package/dist/services/post.js +68 -69
- package/dist/services/search.js +24 -18
- package/dist/theme/components/MediaGallery.js +19 -91
- package/dist/theme/components/PageForm.js +15 -15
- package/dist/theme/components/PostForm.js +136 -129
- package/dist/theme/components/PostList.js +13 -8
- package/dist/theme/components/ThreadView.js +3 -3
- package/dist/theme/components/TypeBadge.js +3 -14
- package/dist/theme/components/VisibilityBadge.js +33 -23
- package/dist/theme/components/index.js +0 -2
- package/dist/theme/index.js +10 -16
- package/dist/theme/layouts/index.js +0 -1
- package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/{theme → themes/threads}/pages/ArchivePage.js +31 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/{theme → themes/threads}/pages/HomePage.js +4 -5
- package/dist/{theme → themes/threads}/pages/PostPage.js +10 -8
- package/dist/{theme → themes/threads}/pages/SearchPage.js +8 -8
- package/dist/{theme → themes/threads}/pages/SinglePage.js +5 -6
- package/dist/{theme/components → themes/threads}/timeline/LinkCard.js +20 -11
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/{theme/components → themes/threads}/timeline/ThreadPreview.js +5 -6
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/{theme/components → themes/threads}/timeline/TimelineItem.js +8 -17
- package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
- package/dist/themes/threads/timeline/groupByDate.js +22 -0
- package/dist/themes/threads/timeline/timelineMore.js +107 -0
- package/dist/types.js +24 -40
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +51 -74
- package/src/app.tsx +27 -6
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +216 -164
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +216 -164
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +216 -164
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +30 -15
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +166 -105
- package/src/lib/__tests__/theme-components.test.ts +4 -25
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
- package/src/lib/__tests__/view.test.ts +217 -67
- package/src/lib/constants.ts +1 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/navigation.ts +6 -7
- package/src/lib/render.tsx +1 -1
- package/src/lib/schemas.ts +118 -52
- package/src/lib/theme-components.ts +10 -13
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +170 -0
- package/src/lib/view.ts +81 -83
- package/src/preset.css +45 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/posts.ts +30 -30
- package/src/routes/api/search.ts +4 -4
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/dash/collections.tsx +18 -40
- package/src/routes/dash/index.tsx +2 -2
- package/src/routes/dash/navigation.tsx +27 -26
- package/src/routes/dash/pages.tsx +45 -60
- package/src/routes/dash/posts.tsx +44 -52
- package/src/routes/feed/rss.ts +2 -1
- package/src/routes/feed/sitemap.ts +14 -4
- package/src/routes/pages/archive.tsx +14 -10
- package/src/routes/pages/collection.tsx +17 -6
- package/src/routes/pages/home.tsx +56 -81
- package/src/routes/pages/page.tsx +64 -27
- package/src/routes/pages/post.tsx +5 -14
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +342 -206
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +124 -0
- package/src/services/post.ts +93 -103
- package/src/services/search.ts +38 -27
- package/src/styles/components.css +0 -54
- package/src/theme/components/MediaGallery.tsx +27 -96
- package/src/theme/components/PageForm.tsx +21 -21
- package/src/theme/components/PostForm.tsx +122 -118
- package/src/theme/components/PostList.tsx +58 -49
- package/src/theme/components/ThreadView.tsx +6 -3
- package/src/theme/components/TypeBadge.tsx +9 -17
- package/src/theme/components/VisibilityBadge.tsx +40 -23
- package/src/theme/components/index.ts +0 -13
- package/src/theme/index.ts +10 -16
- package/src/theme/layouts/index.ts +0 -1
- package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/threads/index.ts +100 -0
- package/src/{theme → themes/threads}/pages/ArchivePage.tsx +52 -55
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/{theme → themes/threads}/pages/HomePage.tsx +5 -6
- package/src/{theme → themes/threads}/pages/PostPage.tsx +11 -8
- package/src/{theme → themes/threads}/pages/SearchPage.tsx +9 -13
- package/src/themes/threads/pages/SinglePage.tsx +23 -0
- package/src/themes/threads/style.css +336 -0
- package/src/{theme/components → themes/threads}/timeline/LinkCard.tsx +21 -13
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/{theme/components → themes/threads}/timeline/ThreadPreview.tsx +6 -6
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/{theme/components → themes/threads}/timeline/TimelineItem.tsx +9 -20
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
- package/src/themes/threads/timeline/groupByDate.ts +30 -0
- package/src/themes/threads/timeline/timelineMore.tsx +130 -0
- package/src/types.ts +242 -98
- package/dist/routes/api/timeline.js +0 -120
- package/dist/theme/components/timeline/ArticleCard.js +0 -46
- package/dist/theme/components/timeline/ImageCard.js +0 -83
- package/dist/theme/components/timeline/NoteCard.js +0 -34
- package/dist/theme/components/timeline/QuoteCard.js +0 -48
- package/dist/theme/components/timeline/TimelineFeed.js +0 -46
- package/dist/theme/components/timeline/index.js +0 -8
- package/dist/theme/layouts/SiteLayout.js +0 -131
- package/dist/theme/pages/CollectionPage.js +0 -63
- package/dist/theme/pages/index.js +0 -11
- package/src/routes/api/timeline.tsx +0 -159
- package/src/theme/components/timeline/ArticleCard.tsx +0 -45
- package/src/theme/components/timeline/ImageCard.tsx +0 -70
- package/src/theme/components/timeline/NoteCard.tsx +0 -34
- package/src/theme/components/timeline/QuoteCard.tsx +0 -48
- package/src/theme/components/timeline/TimelineFeed.tsx +0 -56
- package/src/theme/components/timeline/index.ts +0 -8
- package/src/theme/layouts/SiteLayout.tsx +0 -132
- package/src/theme/pages/CollectionPage.tsx +0 -60
- package/src/theme/pages/SinglePage.tsx +0 -24
- package/src/theme/pages/index.ts +0 -13
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Article Card Component
|
|
3
|
-
*
|
|
4
|
-
* Prominent title + excerpt for type="article" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
export const ArticleCard = ({ post, compact })=>{
|
|
7
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
8
|
-
class: `h-entry timeline-card${compact ? " timeline-card-compact" : ""}`,
|
|
9
|
-
children: [
|
|
10
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
11
|
-
class: `p-name font-semibold ${compact ? "text-sm" : "text-lg"} mb-1`,
|
|
12
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
13
|
-
href: post.permalink,
|
|
14
|
-
class: "u-url hover:underline",
|
|
15
|
-
children: post.title
|
|
16
|
-
})
|
|
17
|
-
}),
|
|
18
|
-
!compact && post.excerpt && /*#__PURE__*/ _jsx("p", {
|
|
19
|
-
class: "e-content text-sm text-muted-foreground line-clamp-3",
|
|
20
|
-
children: post.excerpt
|
|
21
|
-
}),
|
|
22
|
-
/*#__PURE__*/ _jsxs("footer", {
|
|
23
|
-
class: "mt-2 text-xs text-muted-foreground",
|
|
24
|
-
children: [
|
|
25
|
-
/*#__PURE__*/ _jsx("a", {
|
|
26
|
-
href: post.permalink,
|
|
27
|
-
class: "u-url hover:underline",
|
|
28
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
29
|
-
class: "dt-published",
|
|
30
|
-
datetime: post.publishedAt,
|
|
31
|
-
children: post.publishedAtFormatted
|
|
32
|
-
})
|
|
33
|
-
}),
|
|
34
|
-
!compact && /*#__PURE__*/ _jsx("span", {
|
|
35
|
-
class: "ml-2",
|
|
36
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
37
|
-
href: post.permalink,
|
|
38
|
-
class: "hover:underline",
|
|
39
|
-
children: "Read more →"
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
]
|
|
43
|
-
})
|
|
44
|
-
]
|
|
45
|
-
});
|
|
46
|
-
};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Card Component
|
|
3
|
-
*
|
|
4
|
-
* Image-first layout for type="image" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { MediaGallery } from "../MediaGallery.js";
|
|
7
|
-
export const ImageCard = ({ post, compact })=>{
|
|
8
|
-
if (compact) {
|
|
9
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
10
|
-
class: "h-entry timeline-card timeline-card-compact",
|
|
11
|
-
children: [
|
|
12
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
13
|
-
class: "p-name text-sm font-medium mb-1",
|
|
14
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
15
|
-
href: post.permalink,
|
|
16
|
-
class: "u-url hover:underline",
|
|
17
|
-
children: post.title
|
|
18
|
-
})
|
|
19
|
-
}),
|
|
20
|
-
post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
21
|
-
class: "e-content prose prose-sm text-muted-foreground",
|
|
22
|
-
dangerouslySetInnerHTML: {
|
|
23
|
-
__html: post.contentHtml
|
|
24
|
-
}
|
|
25
|
-
}),
|
|
26
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
27
|
-
class: "mt-1 text-xs text-muted-foreground",
|
|
28
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
29
|
-
href: post.permalink,
|
|
30
|
-
class: "u-url hover:underline",
|
|
31
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
32
|
-
class: "dt-published",
|
|
33
|
-
datetime: post.publishedAt,
|
|
34
|
-
children: post.publishedAtFormatted
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
]
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
42
|
-
class: "h-entry timeline-card timeline-card-image",
|
|
43
|
-
children: [
|
|
44
|
-
post.media.length > 0 && /*#__PURE__*/ _jsx("div", {
|
|
45
|
-
class: "timeline-card-image-gallery",
|
|
46
|
-
children: /*#__PURE__*/ _jsx(MediaGallery, {
|
|
47
|
-
attachments: post.media
|
|
48
|
-
})
|
|
49
|
-
}),
|
|
50
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
51
|
-
class: "p-4",
|
|
52
|
-
children: [
|
|
53
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
54
|
-
class: "p-name font-medium mb-1",
|
|
55
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
56
|
-
href: post.permalink,
|
|
57
|
-
class: "u-url hover:underline",
|
|
58
|
-
children: post.title
|
|
59
|
-
})
|
|
60
|
-
}),
|
|
61
|
-
post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
62
|
-
class: "e-content prose prose-sm",
|
|
63
|
-
dangerouslySetInnerHTML: {
|
|
64
|
-
__html: post.contentHtml
|
|
65
|
-
}
|
|
66
|
-
}),
|
|
67
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
68
|
-
class: "mt-2 text-xs text-muted-foreground",
|
|
69
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
70
|
-
href: post.permalink,
|
|
71
|
-
class: "u-url hover:underline",
|
|
72
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
73
|
-
class: "dt-published",
|
|
74
|
-
datetime: post.publishedAt,
|
|
75
|
-
children: post.publishedAtFormatted
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
]
|
|
80
|
-
})
|
|
81
|
-
]
|
|
82
|
-
});
|
|
83
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Note Card Component
|
|
3
|
-
*
|
|
4
|
-
* Text-first, minimal card for type="note" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { MediaGallery } from "../MediaGallery.js";
|
|
7
|
-
export const NoteCard = ({ post, compact })=>{
|
|
8
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
9
|
-
class: `h-entry timeline-card${compact ? " timeline-card-compact" : ""}`,
|
|
10
|
-
children: [
|
|
11
|
-
post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
12
|
-
class: `e-content prose ${compact ? "prose-sm" : "prose-sm"}`,
|
|
13
|
-
dangerouslySetInnerHTML: {
|
|
14
|
-
__html: post.contentHtml
|
|
15
|
-
}
|
|
16
|
-
}),
|
|
17
|
-
!compact && post.media.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
18
|
-
attachments: post.media
|
|
19
|
-
}),
|
|
20
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
21
|
-
class: "mt-2 text-xs text-muted-foreground",
|
|
22
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
23
|
-
href: post.permalink,
|
|
24
|
-
class: "u-url hover:underline",
|
|
25
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
26
|
-
class: "dt-published",
|
|
27
|
-
datetime: post.publishedAt,
|
|
28
|
-
children: post.publishedAtFormatted
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
]
|
|
33
|
-
});
|
|
34
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quote Card Component
|
|
3
|
-
*
|
|
4
|
-
* Blockquote + attribution for type="quote" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
export const QuoteCard = ({ post, compact })=>{
|
|
7
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
8
|
-
class: `h-entry timeline-card timeline-card-quote${compact ? " timeline-card-compact" : ""}`,
|
|
9
|
-
children: [
|
|
10
|
-
post.contentHtml && /*#__PURE__*/ _jsx("blockquote", {
|
|
11
|
-
class: `e-content italic ${compact ? "text-sm" : "text-base"} leading-relaxed`,
|
|
12
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
13
|
-
dangerouslySetInnerHTML: {
|
|
14
|
-
__html: post.contentHtml
|
|
15
|
-
}
|
|
16
|
-
})
|
|
17
|
-
}),
|
|
18
|
-
!compact && (post.sourceName || post.sourceUrl) && /*#__PURE__*/ _jsxs("div", {
|
|
19
|
-
class: "mt-2 text-sm text-muted-foreground",
|
|
20
|
-
children: [
|
|
21
|
-
"—",
|
|
22
|
-
" ",
|
|
23
|
-
post.sourceUrl ? /*#__PURE__*/ _jsx("a", {
|
|
24
|
-
href: post.sourceUrl,
|
|
25
|
-
class: "hover:underline",
|
|
26
|
-
target: "_blank",
|
|
27
|
-
rel: "noopener noreferrer",
|
|
28
|
-
children: post.sourceName || post.sourceDomain || "Source"
|
|
29
|
-
}) : /*#__PURE__*/ _jsx("span", {
|
|
30
|
-
children: post.sourceName
|
|
31
|
-
})
|
|
32
|
-
]
|
|
33
|
-
}),
|
|
34
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
35
|
-
class: "mt-2 text-xs text-muted-foreground",
|
|
36
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
37
|
-
href: post.permalink,
|
|
38
|
-
class: "u-url hover:underline",
|
|
39
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
40
|
-
class: "dt-published",
|
|
41
|
-
datetime: post.publishedAt,
|
|
42
|
-
children: post.publishedAtFormatted
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
]
|
|
47
|
-
});
|
|
48
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeline Feed Component
|
|
3
|
-
*
|
|
4
|
-
* Main feed wrapper with load-more button.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { TimelineItem } from "./TimelineItem.js";
|
|
8
|
-
import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
|
|
9
|
-
export const TimelineFeed = ({ items, hasMore, nextCursor, theme })=>{
|
|
10
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
12
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
13
|
-
children: [
|
|
14
|
-
/*#__PURE__*/ _jsx("div", {
|
|
15
|
-
id: "timeline-feed",
|
|
16
|
-
class: "flex flex-col gap-4",
|
|
17
|
-
children: items.map((item)=>{
|
|
18
|
-
if (item.threadPreview) {
|
|
19
|
-
return /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
|
|
20
|
-
rootPost: item.post,
|
|
21
|
-
previewReplies: item.threadPreview.replies,
|
|
22
|
-
totalReplyCount: item.threadPreview.totalReplyCount,
|
|
23
|
-
theme: theme
|
|
24
|
-
}, item.post.id);
|
|
25
|
-
}
|
|
26
|
-
return /*#__PURE__*/ _jsx(TimelineItem, {
|
|
27
|
-
item: item,
|
|
28
|
-
theme: theme
|
|
29
|
-
}, item.post.id);
|
|
30
|
-
})
|
|
31
|
-
}),
|
|
32
|
-
hasMore && nextCursor && /*#__PURE__*/ _jsx("div", {
|
|
33
|
-
id: "load-more-container",
|
|
34
|
-
class: "mt-6 text-center",
|
|
35
|
-
children: /*#__PURE__*/ _jsx("button", {
|
|
36
|
-
class: "btn btn-outline",
|
|
37
|
-
"data-on:click": `@get('/api/timeline?cursor=${nextCursor}')`,
|
|
38
|
-
children: $__i18n._({
|
|
39
|
-
id: "yQ2kGp",
|
|
40
|
-
message: "Load more"
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
]
|
|
45
|
-
});
|
|
46
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { NoteCard } from "./NoteCard.js";
|
|
2
|
-
export { ArticleCard } from "./ArticleCard.js";
|
|
3
|
-
export { LinkCard } from "./LinkCard.js";
|
|
4
|
-
export { QuoteCard } from "./QuoteCard.js";
|
|
5
|
-
export { ImageCard } from "./ImageCard.js";
|
|
6
|
-
export { ThreadPreview } from "./ThreadPreview.js";
|
|
7
|
-
export { TimelineItem, TimelineItemFromPost } from "./TimelineItem.js";
|
|
8
|
-
export { TimelineFeed } from "./TimelineFeed.js";
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Site Layout
|
|
3
|
-
*
|
|
4
|
-
* Two-column layout for public pages with sidebar navigation.
|
|
5
|
-
* On mobile, uses a slide-out drawer menu.
|
|
6
|
-
*/ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
7
|
-
/**
|
|
8
|
-
* Render navigation links with dot indicator for active state.
|
|
9
|
-
*/ function NavLinks({ links }) {
|
|
10
|
-
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
11
|
-
children: links.map((link)=>/*#__PURE__*/ _jsxs("a", {
|
|
12
|
-
href: link.url,
|
|
13
|
-
class: `text-sm flex items-center gap-2 py-0.5 ${link.isActive ? "text-primary font-medium" : "text-muted-foreground hover:text-foreground"}`,
|
|
14
|
-
...link.isExternal ? {
|
|
15
|
-
target: "_blank",
|
|
16
|
-
rel: "noopener noreferrer"
|
|
17
|
-
} : {},
|
|
18
|
-
children: [
|
|
19
|
-
/*#__PURE__*/ _jsx("span", {
|
|
20
|
-
class: `size-1.5 rounded-full shrink-0 ${link.isActive ? "bg-primary" : "bg-transparent"}`
|
|
21
|
-
}),
|
|
22
|
-
link.label,
|
|
23
|
-
link.isExternal && /*#__PURE__*/ _jsx("span", {
|
|
24
|
-
class: "ml-1 text-xs opacity-50",
|
|
25
|
-
children: "↗"
|
|
26
|
-
})
|
|
27
|
-
]
|
|
28
|
-
}, link.id))
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
export const SiteLayout = ({ siteName, links, children })=>{
|
|
32
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
33
|
-
class: "container py-8 md:flex md:gap-12",
|
|
34
|
-
"data-signals": JSON.stringify({
|
|
35
|
-
_drawerOpen: false
|
|
36
|
-
}),
|
|
37
|
-
children: [
|
|
38
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
39
|
-
class: "flex items-center justify-between mb-6 md:hidden",
|
|
40
|
-
children: [
|
|
41
|
-
/*#__PURE__*/ _jsx("a", {
|
|
42
|
-
href: "/",
|
|
43
|
-
class: "text-xl font-semibold",
|
|
44
|
-
children: siteName
|
|
45
|
-
}),
|
|
46
|
-
/*#__PURE__*/ _jsx("button", {
|
|
47
|
-
"data-on:click": "$_drawerOpen = true",
|
|
48
|
-
class: "p-2 -mr-2 text-muted-foreground hover:text-foreground",
|
|
49
|
-
"aria-label": "Open menu",
|
|
50
|
-
children: /*#__PURE__*/ _jsx("svg", {
|
|
51
|
-
class: "size-5",
|
|
52
|
-
fill: "none",
|
|
53
|
-
viewBox: "0 0 24 24",
|
|
54
|
-
"stroke-width": "1.5",
|
|
55
|
-
stroke: "currentColor",
|
|
56
|
-
children: /*#__PURE__*/ _jsx("path", {
|
|
57
|
-
"stroke-linecap": "round",
|
|
58
|
-
"stroke-linejoin": "round",
|
|
59
|
-
d: "M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
]
|
|
64
|
-
}),
|
|
65
|
-
/*#__PURE__*/ _jsx("div", {
|
|
66
|
-
class: "fixed inset-0 bg-black/50 z-40 opacity-0 pointer-events-none transition-opacity duration-300 ease-in-out md:hidden",
|
|
67
|
-
"data-class": "{'opacity-100 pointer-events-auto': $_drawerOpen, 'opacity-0 pointer-events-none': !$_drawerOpen}",
|
|
68
|
-
"data-on:click": "$_drawerOpen = false"
|
|
69
|
-
}),
|
|
70
|
-
/*#__PURE__*/ _jsxs("aside", {
|
|
71
|
-
class: "fixed inset-y-0 left-0 w-64 bg-background z-50 p-6 overflow-y-auto shadow-lg -translate-x-full transition-transform duration-300 ease-in-out md:hidden",
|
|
72
|
-
"data-class": "{'translate-x-0': $_drawerOpen, '-translate-x-full': !$_drawerOpen}",
|
|
73
|
-
children: [
|
|
74
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
75
|
-
class: "flex items-center justify-between mb-8",
|
|
76
|
-
children: [
|
|
77
|
-
/*#__PURE__*/ _jsx("a", {
|
|
78
|
-
href: "/",
|
|
79
|
-
class: "text-xl font-semibold",
|
|
80
|
-
children: siteName
|
|
81
|
-
}),
|
|
82
|
-
/*#__PURE__*/ _jsx("button", {
|
|
83
|
-
"data-on:click": "$_drawerOpen = false",
|
|
84
|
-
class: "p-2 -mr-2 text-muted-foreground hover:text-foreground",
|
|
85
|
-
"aria-label": "Close menu",
|
|
86
|
-
children: /*#__PURE__*/ _jsx("svg", {
|
|
87
|
-
class: "size-5",
|
|
88
|
-
fill: "none",
|
|
89
|
-
viewBox: "0 0 24 24",
|
|
90
|
-
"stroke-width": "1.5",
|
|
91
|
-
stroke: "currentColor",
|
|
92
|
-
children: /*#__PURE__*/ _jsx("path", {
|
|
93
|
-
"stroke-linecap": "round",
|
|
94
|
-
"stroke-linejoin": "round",
|
|
95
|
-
d: "M6 18L18 6M6 6l12 12"
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
]
|
|
100
|
-
}),
|
|
101
|
-
/*#__PURE__*/ _jsx("nav", {
|
|
102
|
-
class: "flex flex-col gap-0.5",
|
|
103
|
-
children: /*#__PURE__*/ _jsx(NavLinks, {
|
|
104
|
-
links: links
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
]
|
|
108
|
-
}),
|
|
109
|
-
/*#__PURE__*/ _jsxs("aside", {
|
|
110
|
-
class: "hidden md:block md:w-48 md:shrink-0 md:sticky md:top-8 md:self-start",
|
|
111
|
-
children: [
|
|
112
|
-
/*#__PURE__*/ _jsx("a", {
|
|
113
|
-
href: "/",
|
|
114
|
-
class: "text-xl font-semibold block mb-20",
|
|
115
|
-
children: siteName
|
|
116
|
-
}),
|
|
117
|
-
/*#__PURE__*/ _jsx("nav", {
|
|
118
|
-
class: "flex flex-col gap-0.5",
|
|
119
|
-
children: /*#__PURE__*/ _jsx(NavLinks, {
|
|
120
|
-
links: links
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
]
|
|
124
|
-
}),
|
|
125
|
-
/*#__PURE__*/ _jsx("main", {
|
|
126
|
-
class: "flex-1 min-w-0",
|
|
127
|
-
children: children
|
|
128
|
-
})
|
|
129
|
-
]
|
|
130
|
-
});
|
|
131
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Collection Page Component
|
|
3
|
-
*
|
|
4
|
-
* Renders a collection with its posts.
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.CollectionPage.
|
|
6
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
-
export const CollectionPage = ({ collection, posts })=>{
|
|
9
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
11
|
-
children: [
|
|
12
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
13
|
-
class: "mb-8",
|
|
14
|
-
children: [
|
|
15
|
-
/*#__PURE__*/ _jsx("h1", {
|
|
16
|
-
class: "text-2xl font-semibold",
|
|
17
|
-
children: collection.title
|
|
18
|
-
}),
|
|
19
|
-
collection.description && /*#__PURE__*/ _jsx("p", {
|
|
20
|
-
class: "text-muted-foreground mt-2",
|
|
21
|
-
children: collection.description
|
|
22
|
-
})
|
|
23
|
-
]
|
|
24
|
-
}),
|
|
25
|
-
/*#__PURE__*/ _jsx("main", {
|
|
26
|
-
class: "flex flex-col gap-6",
|
|
27
|
-
children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
28
|
-
class: "text-muted-foreground",
|
|
29
|
-
children: $__i18n._({
|
|
30
|
-
id: "J4FNfC",
|
|
31
|
-
message: "No posts in this collection."
|
|
32
|
-
})
|
|
33
|
-
}) : posts.map((post)=>/*#__PURE__*/ _jsxs("article", {
|
|
34
|
-
class: "h-entry",
|
|
35
|
-
children: [
|
|
36
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
37
|
-
class: "p-name text-lg font-medium mb-2",
|
|
38
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
39
|
-
href: post.permalink,
|
|
40
|
-
class: "u-url hover:underline",
|
|
41
|
-
children: post.title
|
|
42
|
-
})
|
|
43
|
-
}),
|
|
44
|
-
/*#__PURE__*/ _jsx("div", {
|
|
45
|
-
class: "e-content prose prose-sm",
|
|
46
|
-
dangerouslySetInnerHTML: {
|
|
47
|
-
__html: post.contentHtml || ""
|
|
48
|
-
}
|
|
49
|
-
}),
|
|
50
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
51
|
-
class: "mt-2 text-sm text-muted-foreground",
|
|
52
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
53
|
-
class: "dt-published",
|
|
54
|
-
datetime: post.publishedAt,
|
|
55
|
-
children: post.publishedAtFormatted
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
]
|
|
59
|
-
}, post.id))
|
|
60
|
-
})
|
|
61
|
-
]
|
|
62
|
-
});
|
|
63
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Page Components
|
|
3
|
-
*
|
|
4
|
-
* These are the built-in page components that render each public page.
|
|
5
|
-
* Theme authors can import these to wrap/extend them.
|
|
6
|
-
*/ export { HomePage } from "./HomePage.js";
|
|
7
|
-
export { PostPage } from "./PostPage.js";
|
|
8
|
-
export { SinglePage } from "./SinglePage.js";
|
|
9
|
-
export { ArchivePage } from "./ArchivePage.js";
|
|
10
|
-
export { SearchPage } from "./SearchPage.js";
|
|
11
|
-
export { CollectionPage } from "./CollectionPage.js";
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeline API Routes
|
|
3
|
-
*
|
|
4
|
-
* Provides load-more functionality for the timeline feed via SSE.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Hono } from "hono";
|
|
8
|
-
import type { Bindings, TimelineItemView } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
10
|
-
import { sse } from "../../lib/sse.js";
|
|
11
|
-
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
12
|
-
import { TimelineItem } from "../../theme/components/timeline/TimelineItem.js";
|
|
13
|
-
import { ThreadPreview as DefaultThreadPreview } from "../../theme/components/timeline/ThreadPreview.js";
|
|
14
|
-
import { createMediaContext, toPostView, toPostViews } from "../../lib/view.js";
|
|
15
|
-
|
|
16
|
-
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
17
|
-
|
|
18
|
-
const PAGE_SIZE = 20;
|
|
19
|
-
|
|
20
|
-
export const timelineApiRoutes = new Hono<Env>();
|
|
21
|
-
|
|
22
|
-
timelineApiRoutes.get("/", async (c) => {
|
|
23
|
-
const cursorParam = c.req.query("cursor");
|
|
24
|
-
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
25
|
-
|
|
26
|
-
if (!cursor || isNaN(cursor)) {
|
|
27
|
-
return c.json({ error: "cursor parameter required" }, 400);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Fetch one extra to determine if there are more
|
|
31
|
-
const posts = await c.var.services.posts.list({
|
|
32
|
-
visibility: ["featured", "quiet"],
|
|
33
|
-
excludeReplies: true,
|
|
34
|
-
excludeTypes: ["page"],
|
|
35
|
-
limit: PAGE_SIZE + 1,
|
|
36
|
-
cursor,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const hasMore = posts.length > PAGE_SIZE;
|
|
40
|
-
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
41
|
-
|
|
42
|
-
if (displayPosts.length === 0) {
|
|
43
|
-
return sse(c, async (stream) => {
|
|
44
|
-
stream.remove("#load-more-container");
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Build media map
|
|
49
|
-
const postIds = displayPosts.map((p) => p.id);
|
|
50
|
-
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
51
|
-
const mediaCtx = createMediaContext(c);
|
|
52
|
-
const mediaMap = buildMediaMap(
|
|
53
|
-
rawMediaMap,
|
|
54
|
-
mediaCtx.r2PublicUrl,
|
|
55
|
-
mediaCtx.imageTransformUrl,
|
|
56
|
-
mediaCtx.s3PublicUrl,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// Get reply counts to identify thread roots
|
|
60
|
-
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
61
|
-
const threadRootIds = postIds.filter((id) => (replyCounts.get(id) ?? 0) > 0);
|
|
62
|
-
|
|
63
|
-
// Get thread previews
|
|
64
|
-
const threadPreviews = await c.var.services.posts.getThreadPreviews(
|
|
65
|
-
threadRootIds,
|
|
66
|
-
3,
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
// Load media for preview replies
|
|
70
|
-
const previewReplyIds: number[] = [];
|
|
71
|
-
for (const replies of threadPreviews.values()) {
|
|
72
|
-
for (const reply of replies) {
|
|
73
|
-
previewReplyIds.push(reply.id);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
const previewMediaMap =
|
|
77
|
-
previewReplyIds.length > 0
|
|
78
|
-
? buildMediaMap(
|
|
79
|
-
await c.var.services.media.getByPostIds(previewReplyIds),
|
|
80
|
-
mediaCtx.r2PublicUrl,
|
|
81
|
-
mediaCtx.imageTransformUrl,
|
|
82
|
-
mediaCtx.s3PublicUrl,
|
|
83
|
-
)
|
|
84
|
-
: new Map();
|
|
85
|
-
|
|
86
|
-
// Assemble timeline items with View Models
|
|
87
|
-
const items: TimelineItemView[] = displayPosts.map((post) => {
|
|
88
|
-
const postView = toPostView(
|
|
89
|
-
{ ...post, mediaAttachments: mediaMap.get(post.id) ?? [] },
|
|
90
|
-
mediaCtx,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
94
|
-
const previewReplies = threadPreviews.get(post.id);
|
|
95
|
-
|
|
96
|
-
if (replyCount > 0 && previewReplies) {
|
|
97
|
-
return {
|
|
98
|
-
post: postView,
|
|
99
|
-
threadPreview: {
|
|
100
|
-
replies: toPostViews(
|
|
101
|
-
previewReplies.map((r) => ({
|
|
102
|
-
...r,
|
|
103
|
-
mediaAttachments: previewMediaMap.get(r.id) ?? [],
|
|
104
|
-
})),
|
|
105
|
-
mediaCtx,
|
|
106
|
-
),
|
|
107
|
-
totalReplyCount: replyCount,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { post: postView };
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Resolve theme components for card rendering
|
|
116
|
-
const theme = c.var.config.theme?.components;
|
|
117
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
118
|
-
|
|
119
|
-
// Render items to HTML
|
|
120
|
-
const itemsHtml = items
|
|
121
|
-
.map((item) => {
|
|
122
|
-
if (item.threadPreview) {
|
|
123
|
-
return (
|
|
124
|
-
<ResolvedThreadPreview
|
|
125
|
-
rootPost={item.post}
|
|
126
|
-
previewReplies={item.threadPreview.replies}
|
|
127
|
-
totalReplyCount={item.threadPreview.totalReplyCount}
|
|
128
|
-
theme={theme}
|
|
129
|
-
/>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
return <TimelineItem item={item} theme={theme} />;
|
|
133
|
-
})
|
|
134
|
-
.map((jsx) => jsx.toString())
|
|
135
|
-
.join("");
|
|
136
|
-
|
|
137
|
-
// Determine next cursor
|
|
138
|
-
const lastPost = displayPosts[displayPosts.length - 1];
|
|
139
|
-
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
140
|
-
|
|
141
|
-
// Build load-more button HTML
|
|
142
|
-
const loadMoreHtml = nextCursor
|
|
143
|
-
? `<div id="load-more-container" class="mt-6 text-center"><button class="btn btn-outline" data-on:click="@get('/api/timeline?cursor=${nextCursor}')">Load more</button></div>`
|
|
144
|
-
: "";
|
|
145
|
-
|
|
146
|
-
return sse(c, async (stream) => {
|
|
147
|
-
// Append new items to the feed
|
|
148
|
-
stream.patchElements(itemsHtml, {
|
|
149
|
-
mode: "append",
|
|
150
|
-
selector: "#timeline-feed",
|
|
151
|
-
});
|
|
152
|
-
// Replace or remove the load-more container
|
|
153
|
-
if (loadMoreHtml) {
|
|
154
|
-
stream.patchElements(loadMoreHtml);
|
|
155
|
-
} else {
|
|
156
|
-
stream.remove("#load-more-container");
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
});
|