@jant/core 0.3.23 → 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 +4 -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 +3 -3
- 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 +61 -72
- 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/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/themes/{minimal → threads}/pages/ArchivePage.js +32 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/themes/{minimal → threads}/pages/HomePage.js +3 -3
- package/dist/themes/{minimal → threads}/pages/PostPage.js +12 -9
- package/dist/themes/{minimal → threads}/pages/SearchPage.js +13 -14
- package/dist/themes/{minimal → threads}/pages/SinglePage.js +4 -4
- package/dist/themes/threads/timeline/LinkCard.js +68 -0
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/themes/{minimal → threads}/timeline/ThreadPreview.js +17 -13
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/themes/{minimal → threads}/timeline/TimelineItem.js +8 -16
- 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 +4 -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 +28 -12
- 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 +199 -51
- 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 +80 -82
- 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/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/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/{minimal → threads}/index.ts +30 -13
- package/src/themes/{minimal → threads}/pages/ArchivePage.tsx +53 -53
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/themes/{minimal → threads}/pages/HomePage.tsx +3 -3
- package/src/themes/{minimal → threads}/pages/PostPage.tsx +12 -8
- package/src/themes/{minimal → threads}/pages/SearchPage.tsx +15 -13
- package/src/themes/{minimal → threads}/pages/SinglePage.tsx +4 -4
- package/src/themes/threads/style.css +336 -0
- package/src/themes/threads/timeline/LinkCard.tsx +67 -0
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/themes/{minimal → threads}/timeline/ThreadPreview.tsx +15 -13
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/themes/{minimal → threads}/timeline/TimelineItem.tsx +9 -17
- 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/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/QuoteCard.js +0 -48
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/src/routes/api/timeline.tsx +0 -159
- package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
|
@@ -1,106 +1,96 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Threads Theme - Archive Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Posts grouped by year-month with format filter and cursor pagination.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
8
8
|
import { useLingui } from "@lingui/react/macro";
|
|
9
9
|
import type { ArchivePageProps } from "../../../types.js";
|
|
10
|
-
import {
|
|
11
|
-
import { Pagination as DefaultPagination } from "../../../theme/
|
|
10
|
+
import { FORMATS } from "../../../types.js";
|
|
11
|
+
import { Pagination as DefaultPagination } from "../../../theme/index.js";
|
|
12
12
|
|
|
13
|
-
function
|
|
13
|
+
function getFormatLabel(format: string): string {
|
|
14
14
|
const { t } = useLingui();
|
|
15
15
|
const labels: Record<string, string> = {
|
|
16
|
-
note: t({ message: "Note", comment: "@context: Post
|
|
17
|
-
|
|
18
|
-
message: "Article",
|
|
19
|
-
comment: "@context: Post type label - article",
|
|
20
|
-
}),
|
|
21
|
-
link: t({ message: "Link", comment: "@context: Post type label - link" }),
|
|
16
|
+
note: t({ message: "Note", comment: "@context: Post format label - note" }),
|
|
17
|
+
link: t({ message: "Link", comment: "@context: Post format label - link" }),
|
|
22
18
|
quote: t({
|
|
23
19
|
message: "Quote",
|
|
24
|
-
comment: "@context: Post
|
|
25
|
-
}),
|
|
26
|
-
image: t({
|
|
27
|
-
message: "Image",
|
|
28
|
-
comment: "@context: Post type label - image",
|
|
20
|
+
comment: "@context: Post format label - quote",
|
|
29
21
|
}),
|
|
30
|
-
page: t({ message: "Page", comment: "@context: Post type label - page" }),
|
|
31
22
|
};
|
|
32
|
-
return labels[
|
|
23
|
+
return labels[format] ?? format;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
|
-
function
|
|
26
|
+
function getFormatLabelPlural(format: string): string {
|
|
36
27
|
const { t } = useLingui();
|
|
37
28
|
const labels: Record<string, string> = {
|
|
38
29
|
note: t({
|
|
39
30
|
message: "Notes",
|
|
40
|
-
comment: "@context: Post
|
|
41
|
-
}),
|
|
42
|
-
article: t({
|
|
43
|
-
message: "Articles",
|
|
44
|
-
comment: "@context: Post type label plural - articles",
|
|
31
|
+
comment: "@context: Post format label plural - notes",
|
|
45
32
|
}),
|
|
46
33
|
link: t({
|
|
47
34
|
message: "Links",
|
|
48
|
-
comment: "@context: Post
|
|
35
|
+
comment: "@context: Post format label plural - links",
|
|
49
36
|
}),
|
|
50
37
|
quote: t({
|
|
51
38
|
message: "Quotes",
|
|
52
|
-
comment: "@context: Post
|
|
53
|
-
}),
|
|
54
|
-
image: t({
|
|
55
|
-
message: "Images",
|
|
56
|
-
comment: "@context: Post type label plural - images",
|
|
57
|
-
}),
|
|
58
|
-
page: t({
|
|
59
|
-
message: "Pages",
|
|
60
|
-
comment: "@context: Post type label plural - pages",
|
|
39
|
+
comment: "@context: Post format label plural - quotes",
|
|
61
40
|
}),
|
|
62
41
|
};
|
|
63
|
-
return labels[
|
|
42
|
+
return labels[format] ?? `${format}s`;
|
|
64
43
|
}
|
|
65
44
|
|
|
66
45
|
export const ArchivePage: FC<ArchivePageProps> = ({
|
|
67
46
|
groups,
|
|
68
47
|
hasMore,
|
|
69
48
|
nextCursor,
|
|
70
|
-
|
|
49
|
+
format,
|
|
50
|
+
featured,
|
|
71
51
|
theme,
|
|
72
52
|
}) => {
|
|
73
53
|
const { t } = useLingui();
|
|
74
|
-
const title =
|
|
75
|
-
?
|
|
54
|
+
const title = format
|
|
55
|
+
? getFormatLabelPlural(format)
|
|
76
56
|
: t({ message: "Archive", comment: "@context: Archive page title" });
|
|
77
57
|
|
|
78
58
|
const PaginationComponent = theme?.Pagination ?? DefaultPagination;
|
|
79
59
|
|
|
80
60
|
return (
|
|
81
|
-
<div>
|
|
61
|
+
<div class="py-6">
|
|
82
62
|
<header class="mb-8">
|
|
83
63
|
<h1 class="text-2xl font-semibold">{title}</h1>
|
|
84
64
|
|
|
65
|
+
{/* Format filter */}
|
|
85
66
|
<nav class="flex flex-wrap gap-2 mt-4">
|
|
86
67
|
<a
|
|
87
68
|
href="/archive"
|
|
88
|
-
class={`
|
|
69
|
+
class={`badge ${!format && !featured ? "badge-primary" : "badge-outline"}`}
|
|
89
70
|
>
|
|
90
71
|
{t({
|
|
91
72
|
message: "All",
|
|
92
|
-
comment: "@context: Archive filter - all
|
|
73
|
+
comment: "@context: Archive filter - all formats",
|
|
93
74
|
})}
|
|
94
75
|
</a>
|
|
95
|
-
{
|
|
76
|
+
{FORMATS.map((formatKey) => (
|
|
96
77
|
<a
|
|
97
|
-
key={
|
|
98
|
-
href={`/archive?
|
|
99
|
-
class={`
|
|
78
|
+
key={formatKey}
|
|
79
|
+
href={`/archive?format=${formatKey}`}
|
|
80
|
+
class={`badge ${format === formatKey ? "badge-primary" : "badge-outline"}`}
|
|
100
81
|
>
|
|
101
|
-
{
|
|
82
|
+
{getFormatLabelPlural(formatKey)}
|
|
102
83
|
</a>
|
|
103
84
|
))}
|
|
85
|
+
<a
|
|
86
|
+
href="/archive?featured=true"
|
|
87
|
+
class={`badge ${featured ? "badge-primary" : "badge-outline"}`}
|
|
88
|
+
>
|
|
89
|
+
{t({
|
|
90
|
+
message: "Featured",
|
|
91
|
+
comment: "@context: Archive filter - featured posts",
|
|
92
|
+
})}
|
|
93
|
+
</a>
|
|
104
94
|
</nav>
|
|
105
95
|
</header>
|
|
106
96
|
|
|
@@ -118,9 +108,12 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
118
108
|
<h2 class="text-lg font-medium mb-4 text-muted-foreground">
|
|
119
109
|
{group.label}
|
|
120
110
|
</h2>
|
|
121
|
-
<div class="
|
|
111
|
+
<div class="divide-y divide-border">
|
|
122
112
|
{group.posts.map((post) => (
|
|
123
|
-
<article
|
|
113
|
+
<article
|
|
114
|
+
key={post.id}
|
|
115
|
+
class="flex items-baseline gap-4 py-2.5"
|
|
116
|
+
>
|
|
124
117
|
<time
|
|
125
118
|
class="text-sm text-muted-foreground w-12 shrink-0"
|
|
126
119
|
datetime={post.publishedAt}
|
|
@@ -130,12 +123,12 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
130
123
|
<div class="flex-1 min-w-0">
|
|
131
124
|
<a href={post.permalink} class="hover:underline">
|
|
132
125
|
{post.title ||
|
|
133
|
-
post.
|
|
126
|
+
post.excerpt?.slice(0, 80) ||
|
|
134
127
|
`Post #${post.id}`}
|
|
135
128
|
</a>
|
|
136
|
-
{!
|
|
137
|
-
<span class="ml-2
|
|
138
|
-
{
|
|
129
|
+
{!format && (
|
|
130
|
+
<span class="ml-2 badge-outline text-xs">
|
|
131
|
+
{getFormatLabel(post.format)}
|
|
139
132
|
</span>
|
|
140
133
|
)}
|
|
141
134
|
</div>
|
|
@@ -147,8 +140,15 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
147
140
|
)}
|
|
148
141
|
</main>
|
|
149
142
|
|
|
143
|
+
{/* Pagination */}
|
|
150
144
|
<PaginationComponent
|
|
151
|
-
baseUrl={
|
|
145
|
+
baseUrl={
|
|
146
|
+
format
|
|
147
|
+
? `/archive?format=${format}`
|
|
148
|
+
: featured
|
|
149
|
+
? "/archive?featured=true"
|
|
150
|
+
: "/archive"
|
|
151
|
+
}
|
|
152
152
|
hasMore={hasMore}
|
|
153
153
|
nextCursor={nextCursor}
|
|
154
154
|
/>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threads Theme - Collection Page
|
|
3
|
+
*
|
|
4
|
+
* Collection header with divider-separated post list.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FC } from "hono/jsx";
|
|
8
|
+
import { useLingui } from "@lingui/react/macro";
|
|
9
|
+
import type { CollectionPageProps } from "../../../types.js";
|
|
10
|
+
|
|
11
|
+
export const CollectionPage: FC<CollectionPageProps> = ({
|
|
12
|
+
collection,
|
|
13
|
+
posts,
|
|
14
|
+
}) => {
|
|
15
|
+
const { t } = useLingui();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div class="py-6">
|
|
19
|
+
<header class="mb-8">
|
|
20
|
+
<h1 class="text-2xl font-semibold">{collection.title}</h1>
|
|
21
|
+
{collection.description && (
|
|
22
|
+
<p class="text-muted-foreground mt-2">{collection.description}</p>
|
|
23
|
+
)}
|
|
24
|
+
</header>
|
|
25
|
+
|
|
26
|
+
<main>
|
|
27
|
+
{posts.length === 0 ? (
|
|
28
|
+
<p class="text-muted-foreground">
|
|
29
|
+
{t({
|
|
30
|
+
message: "No posts in this collection.",
|
|
31
|
+
comment: "@context: Empty state message",
|
|
32
|
+
})}
|
|
33
|
+
</p>
|
|
34
|
+
) : (
|
|
35
|
+
<div class="divide-y divide-border">
|
|
36
|
+
{posts.map((post) => (
|
|
37
|
+
<article key={post.id} class="h-entry py-4">
|
|
38
|
+
{post.title && (
|
|
39
|
+
<h2 class="p-name text-lg font-medium mb-2">
|
|
40
|
+
<a href={post.permalink} class="u-url hover:underline">
|
|
41
|
+
{post.title}
|
|
42
|
+
</a>
|
|
43
|
+
</h2>
|
|
44
|
+
)}
|
|
45
|
+
<div
|
|
46
|
+
class="e-content prose prose-sm"
|
|
47
|
+
dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
|
|
48
|
+
/>
|
|
49
|
+
<footer class="mt-2 text-sm text-muted-foreground">
|
|
50
|
+
<time class="dt-published" datetime={post.publishedAt}>
|
|
51
|
+
{post.publishedAtFormatted}
|
|
52
|
+
</time>
|
|
53
|
+
</footer>
|
|
54
|
+
</article>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</main>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Threads Theme - Home Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Clean feed of posts separated by dividers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
@@ -22,7 +22,7 @@ export const HomePage: FC<HomePageProps> = ({
|
|
|
22
22
|
return (
|
|
23
23
|
<>
|
|
24
24
|
{items.length === 0 ? (
|
|
25
|
-
<p class="text-muted-foreground">
|
|
25
|
+
<p class="py-12 text-center text-muted-foreground">
|
|
26
26
|
{t({
|
|
27
27
|
message: "No posts yet.",
|
|
28
28
|
comment: "@context: Empty state message on home page",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Threads Theme - Post Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Single post view — clean, no card border, with divider footer.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
8
8
|
import { useLingui } from "@lingui/react/macro";
|
|
9
9
|
import type { PostPageProps } from "../../../types.js";
|
|
10
|
-
import { MediaGallery as DefaultMediaGallery } from "../../../theme/
|
|
10
|
+
import { MediaGallery as DefaultMediaGallery } from "../../../theme/index.js";
|
|
11
11
|
|
|
12
12
|
export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
|
|
13
13
|
const { t } = useLingui();
|
|
@@ -15,23 +15,27 @@ export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
|
|
|
15
15
|
const Gallery = theme?.MediaGallery ?? DefaultMediaGallery;
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<article class="h-entry">
|
|
18
|
+
<article class="h-entry py-6">
|
|
19
19
|
{post.title && (
|
|
20
20
|
<h1 class="p-name text-2xl font-semibold mb-4">{post.title}</h1>
|
|
21
21
|
)}
|
|
22
22
|
|
|
23
23
|
<div
|
|
24
24
|
class="e-content prose"
|
|
25
|
-
dangerouslySetInnerHTML={{ __html: post.
|
|
25
|
+
dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
|
|
26
26
|
/>
|
|
27
27
|
|
|
28
|
-
{post.media.length > 0 &&
|
|
28
|
+
{post.media.length > 0 && (
|
|
29
|
+
<div class="threads-media mt-4">
|
|
30
|
+
<Gallery attachments={post.media} />
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
29
33
|
|
|
30
|
-
<footer class="mt-
|
|
34
|
+
<footer class="mt-6 pt-4 border-t text-sm text-muted-foreground">
|
|
31
35
|
<time class="dt-published" datetime={post.publishedAt}>
|
|
32
36
|
{post.publishedAtFormatted}
|
|
33
37
|
</time>
|
|
34
|
-
<a href={post.permalink} class="u-url ml-4
|
|
38
|
+
<a href={post.permalink} class="u-url ml-4">
|
|
35
39
|
{t({
|
|
36
40
|
message: "Permalink",
|
|
37
41
|
comment: "@context: Link to permanent URL of post",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Threads Theme - Search Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Search form and results — divider-separated instead of bordered cards.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
8
8
|
import { useLingui } from "@lingui/react/macro";
|
|
9
9
|
import type { SearchPageProps } from "../../../types.js";
|
|
10
|
-
import { PagePagination as DefaultPagePagination } from "../../../theme/
|
|
10
|
+
import { PagePagination as DefaultPagePagination } from "../../../theme/index.js";
|
|
11
11
|
|
|
12
12
|
export const SearchPage: FC<SearchPageProps> = ({
|
|
13
13
|
query,
|
|
@@ -26,9 +26,10 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
26
26
|
const PaginationComponent = theme?.PagePagination ?? DefaultPagePagination;
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
|
-
<div>
|
|
29
|
+
<div class="py-6">
|
|
30
30
|
<h1 class="text-2xl font-semibold mb-6">{searchTitle}</h1>
|
|
31
31
|
|
|
32
|
+
{/* Search form */}
|
|
32
33
|
<form method="get" action="/search" class="mb-8">
|
|
33
34
|
<div class="flex gap-2">
|
|
34
35
|
<input
|
|
@@ -51,12 +52,14 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
51
52
|
</div>
|
|
52
53
|
</form>
|
|
53
54
|
|
|
55
|
+
{/* Error */}
|
|
54
56
|
{error && (
|
|
55
57
|
<div class="alert-destructive mb-6">
|
|
56
58
|
<h2>{error}</h2>
|
|
57
59
|
</div>
|
|
58
60
|
)}
|
|
59
61
|
|
|
62
|
+
{/* Results */}
|
|
60
63
|
{query && !error && (
|
|
61
64
|
<div>
|
|
62
65
|
<p class="text-sm text-muted-foreground mb-4">
|
|
@@ -79,26 +82,25 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
79
82
|
|
|
80
83
|
{results.length > 0 && (
|
|
81
84
|
<>
|
|
82
|
-
<div class="
|
|
85
|
+
<div class="divide-y divide-border">
|
|
83
86
|
{results.map((result) => (
|
|
84
|
-
<article key={result.post.id} class="py-
|
|
85
|
-
<a href={result.post.permalink} class="block
|
|
86
|
-
<h2 class="font-medium
|
|
87
|
+
<article key={result.post.id} class="py-4">
|
|
88
|
+
<a href={result.post.permalink} class="block">
|
|
89
|
+
<h2 class="font-medium hover:underline">
|
|
87
90
|
{result.post.title ||
|
|
88
|
-
result.post.
|
|
91
|
+
result.post.excerpt?.slice(0, 60) ||
|
|
89
92
|
`Post #${result.post.id}`}
|
|
90
93
|
</h2>
|
|
91
94
|
|
|
92
95
|
{result.snippet && (
|
|
93
96
|
<p
|
|
94
|
-
class="text-sm text-muted-foreground mt-
|
|
97
|
+
class="text-sm text-muted-foreground mt-2 line-clamp-2"
|
|
95
98
|
dangerouslySetInnerHTML={{ __html: result.snippet }}
|
|
96
99
|
/>
|
|
97
100
|
)}
|
|
98
101
|
|
|
99
|
-
<footer class="flex items-center gap-2 mt-
|
|
100
|
-
<span>{result.post.
|
|
101
|
-
<span>·</span>
|
|
102
|
+
<footer class="flex items-center gap-2 mt-2 text-xs text-muted-foreground">
|
|
103
|
+
<span class="badge-outline">{result.post.format}</span>
|
|
102
104
|
<time datetime={result.post.publishedAt}>
|
|
103
105
|
{result.post.publishedAtFormatted}
|
|
104
106
|
</time>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Threads Theme - Single Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Custom page (type "page") view — clean centered content.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
@@ -9,14 +9,14 @@ import type { SinglePageProps } from "../../../types.js";
|
|
|
9
9
|
|
|
10
10
|
export const SinglePage: FC<SinglePageProps> = ({ page }) => {
|
|
11
11
|
return (
|
|
12
|
-
<article class="h-entry">
|
|
12
|
+
<article class="h-entry py-6">
|
|
13
13
|
{page.title && (
|
|
14
14
|
<h1 class="p-name text-2xl font-semibold mb-6">{page.title}</h1>
|
|
15
15
|
)}
|
|
16
16
|
|
|
17
17
|
<div
|
|
18
18
|
class="e-content prose"
|
|
19
|
-
dangerouslySetInnerHTML={{ __html: page.
|
|
19
|
+
dangerouslySetInnerHTML={{ __html: page.bodyHtml || "" }}
|
|
20
20
|
/>
|
|
21
21
|
</article>
|
|
22
22
|
);
|