@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,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Article Card Component
|
|
3
|
-
*
|
|
4
|
-
* Prominent title + excerpt for type="article" posts.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
-
|
|
10
|
-
export const ArticleCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
11
|
-
return (
|
|
12
|
-
<article
|
|
13
|
-
class={`h-entry timeline-card${compact ? " timeline-card-compact" : ""}`}
|
|
14
|
-
>
|
|
15
|
-
{post.title && (
|
|
16
|
-
<h2
|
|
17
|
-
class={`p-name font-semibold ${compact ? "text-sm" : "text-lg"} mb-1`}
|
|
18
|
-
>
|
|
19
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
20
|
-
{post.title}
|
|
21
|
-
</a>
|
|
22
|
-
</h2>
|
|
23
|
-
)}
|
|
24
|
-
{!compact && post.excerpt && (
|
|
25
|
-
<p class="e-content text-sm text-muted-foreground line-clamp-3">
|
|
26
|
-
{post.excerpt}
|
|
27
|
-
</p>
|
|
28
|
-
)}
|
|
29
|
-
<footer class="mt-2 text-xs text-muted-foreground">
|
|
30
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
31
|
-
<time class="dt-published" datetime={post.publishedAt}>
|
|
32
|
-
{post.publishedAtFormatted}
|
|
33
|
-
</time>
|
|
34
|
-
</a>
|
|
35
|
-
{!compact && (
|
|
36
|
-
<span class="ml-2">
|
|
37
|
-
<a href={post.permalink} class="hover:underline">
|
|
38
|
-
Read more →
|
|
39
|
-
</a>
|
|
40
|
-
</span>
|
|
41
|
-
)}
|
|
42
|
-
</footer>
|
|
43
|
-
</article>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Card Component
|
|
3
|
-
*
|
|
4
|
-
* Image-first layout for type="image" posts.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
-
import { MediaGallery } from "../MediaGallery.js";
|
|
10
|
-
|
|
11
|
-
export const ImageCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
12
|
-
if (compact) {
|
|
13
|
-
return (
|
|
14
|
-
<article class="h-entry timeline-card timeline-card-compact">
|
|
15
|
-
{post.title && (
|
|
16
|
-
<h2 class="p-name text-sm font-medium mb-1">
|
|
17
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
18
|
-
{post.title}
|
|
19
|
-
</a>
|
|
20
|
-
</h2>
|
|
21
|
-
)}
|
|
22
|
-
{post.contentHtml && (
|
|
23
|
-
<div
|
|
24
|
-
class="e-content prose prose-sm text-muted-foreground"
|
|
25
|
-
dangerouslySetInnerHTML={{ __html: post.contentHtml }}
|
|
26
|
-
/>
|
|
27
|
-
)}
|
|
28
|
-
<footer class="mt-1 text-xs text-muted-foreground">
|
|
29
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
30
|
-
<time class="dt-published" datetime={post.publishedAt}>
|
|
31
|
-
{post.publishedAtFormatted}
|
|
32
|
-
</time>
|
|
33
|
-
</a>
|
|
34
|
-
</footer>
|
|
35
|
-
</article>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<article class="h-entry timeline-card timeline-card-image">
|
|
41
|
-
{post.media.length > 0 && (
|
|
42
|
-
<div class="timeline-card-image-gallery">
|
|
43
|
-
<MediaGallery attachments={post.media} />
|
|
44
|
-
</div>
|
|
45
|
-
)}
|
|
46
|
-
<div class="p-4">
|
|
47
|
-
{post.title && (
|
|
48
|
-
<h2 class="p-name font-medium mb-1">
|
|
49
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
50
|
-
{post.title}
|
|
51
|
-
</a>
|
|
52
|
-
</h2>
|
|
53
|
-
)}
|
|
54
|
-
{post.contentHtml && (
|
|
55
|
-
<div
|
|
56
|
-
class="e-content prose prose-sm"
|
|
57
|
-
dangerouslySetInnerHTML={{ __html: post.contentHtml }}
|
|
58
|
-
/>
|
|
59
|
-
)}
|
|
60
|
-
<footer class="mt-2 text-xs text-muted-foreground">
|
|
61
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
62
|
-
<time class="dt-published" datetime={post.publishedAt}>
|
|
63
|
-
{post.publishedAtFormatted}
|
|
64
|
-
</time>
|
|
65
|
-
</a>
|
|
66
|
-
</footer>
|
|
67
|
-
</div>
|
|
68
|
-
</article>
|
|
69
|
-
);
|
|
70
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Note Card Component
|
|
3
|
-
*
|
|
4
|
-
* Text-first, minimal card for type="note" posts.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
-
import { MediaGallery } from "../MediaGallery.js";
|
|
10
|
-
|
|
11
|
-
export const NoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
12
|
-
return (
|
|
13
|
-
<article
|
|
14
|
-
class={`h-entry timeline-card${compact ? " timeline-card-compact" : ""}`}
|
|
15
|
-
>
|
|
16
|
-
{post.contentHtml && (
|
|
17
|
-
<div
|
|
18
|
-
class={`e-content prose ${compact ? "prose-sm" : "prose-sm"}`}
|
|
19
|
-
dangerouslySetInnerHTML={{ __html: post.contentHtml }}
|
|
20
|
-
/>
|
|
21
|
-
)}
|
|
22
|
-
{!compact && post.media.length > 0 && (
|
|
23
|
-
<MediaGallery attachments={post.media} />
|
|
24
|
-
)}
|
|
25
|
-
<footer class="mt-2 text-xs text-muted-foreground">
|
|
26
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
27
|
-
<time class="dt-published" datetime={post.publishedAt}>
|
|
28
|
-
{post.publishedAtFormatted}
|
|
29
|
-
</time>
|
|
30
|
-
</a>
|
|
31
|
-
</footer>
|
|
32
|
-
</article>
|
|
33
|
-
);
|
|
34
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quote Card Component
|
|
3
|
-
*
|
|
4
|
-
* Blockquote + attribution for type="quote" posts.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
-
|
|
10
|
-
export const QuoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
11
|
-
return (
|
|
12
|
-
<article
|
|
13
|
-
class={`h-entry timeline-card timeline-card-quote${compact ? " timeline-card-compact" : ""}`}
|
|
14
|
-
>
|
|
15
|
-
{post.contentHtml && (
|
|
16
|
-
<blockquote
|
|
17
|
-
class={`e-content italic ${compact ? "text-sm" : "text-base"} leading-relaxed`}
|
|
18
|
-
>
|
|
19
|
-
<div dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
|
|
20
|
-
</blockquote>
|
|
21
|
-
)}
|
|
22
|
-
{!compact && (post.sourceName || post.sourceUrl) && (
|
|
23
|
-
<div class="mt-2 text-sm text-muted-foreground">
|
|
24
|
-
—{" "}
|
|
25
|
-
{post.sourceUrl ? (
|
|
26
|
-
<a
|
|
27
|
-
href={post.sourceUrl}
|
|
28
|
-
class="hover:underline"
|
|
29
|
-
target="_blank"
|
|
30
|
-
rel="noopener noreferrer"
|
|
31
|
-
>
|
|
32
|
-
{post.sourceName || post.sourceDomain || "Source"}
|
|
33
|
-
</a>
|
|
34
|
-
) : (
|
|
35
|
-
<span>{post.sourceName}</span>
|
|
36
|
-
)}
|
|
37
|
-
</div>
|
|
38
|
-
)}
|
|
39
|
-
<footer class="mt-2 text-xs text-muted-foreground">
|
|
40
|
-
<a href={post.permalink} class="u-url hover:underline">
|
|
41
|
-
<time class="dt-published" datetime={post.publishedAt}>
|
|
42
|
-
{post.publishedAtFormatted}
|
|
43
|
-
</time>
|
|
44
|
-
</a>
|
|
45
|
-
</footer>
|
|
46
|
-
</article>
|
|
47
|
-
);
|
|
48
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeline Feed Component
|
|
3
|
-
*
|
|
4
|
-
* Main feed wrapper with load-more button.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { TimelineFeedProps } from "../../../types.js";
|
|
10
|
-
import { TimelineItem } from "./TimelineItem.js";
|
|
11
|
-
import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
|
|
12
|
-
|
|
13
|
-
export const TimelineFeed: FC<TimelineFeedProps> = ({
|
|
14
|
-
items,
|
|
15
|
-
hasMore,
|
|
16
|
-
nextCursor,
|
|
17
|
-
theme,
|
|
18
|
-
}) => {
|
|
19
|
-
const { t } = useLingui();
|
|
20
|
-
|
|
21
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div>
|
|
25
|
-
<div id="timeline-feed" class="flex flex-col gap-4">
|
|
26
|
-
{items.map((item) => {
|
|
27
|
-
if (item.threadPreview) {
|
|
28
|
-
return (
|
|
29
|
-
<ResolvedThreadPreview
|
|
30
|
-
key={item.post.id}
|
|
31
|
-
rootPost={item.post}
|
|
32
|
-
previewReplies={item.threadPreview.replies}
|
|
33
|
-
totalReplyCount={item.threadPreview.totalReplyCount}
|
|
34
|
-
theme={theme}
|
|
35
|
-
/>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return <TimelineItem key={item.post.id} item={item} theme={theme} />;
|
|
39
|
-
})}
|
|
40
|
-
</div>
|
|
41
|
-
{hasMore && nextCursor && (
|
|
42
|
-
<div id="load-more-container" class="mt-6 text-center">
|
|
43
|
-
<button
|
|
44
|
-
class="btn btn-outline"
|
|
45
|
-
data-on:click={`@get('/api/timeline?cursor=${nextCursor}')`}
|
|
46
|
-
>
|
|
47
|
-
{t({
|
|
48
|
-
message: "Load more",
|
|
49
|
-
comment: "@context: Button to load more posts in timeline",
|
|
50
|
-
})}
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
@@ -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,132 +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
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
9
|
-
import type { NavLinkView, SiteLayoutProps } from "../../types.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Render navigation links with dot indicator for active state.
|
|
13
|
-
*/
|
|
14
|
-
function NavLinks({ links }: { links: NavLinkView[] }) {
|
|
15
|
-
return (
|
|
16
|
-
<>
|
|
17
|
-
{links.map((link) => (
|
|
18
|
-
<a
|
|
19
|
-
key={link.id}
|
|
20
|
-
href={link.url}
|
|
21
|
-
class={`text-sm flex items-center gap-2 py-0.5 ${
|
|
22
|
-
link.isActive
|
|
23
|
-
? "text-primary font-medium"
|
|
24
|
-
: "text-muted-foreground hover:text-foreground"
|
|
25
|
-
}`}
|
|
26
|
-
{...(link.isExternal
|
|
27
|
-
? { target: "_blank", rel: "noopener noreferrer" }
|
|
28
|
-
: {})}
|
|
29
|
-
>
|
|
30
|
-
<span
|
|
31
|
-
class={`size-1.5 rounded-full shrink-0 ${link.isActive ? "bg-primary" : "bg-transparent"}`}
|
|
32
|
-
/>
|
|
33
|
-
{link.label}
|
|
34
|
-
{link.isExternal && <span class="ml-1 text-xs opacity-50">↗</span>}
|
|
35
|
-
</a>
|
|
36
|
-
))}
|
|
37
|
-
</>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
42
|
-
siteName,
|
|
43
|
-
links,
|
|
44
|
-
children,
|
|
45
|
-
}) => {
|
|
46
|
-
return (
|
|
47
|
-
<div
|
|
48
|
-
class="container py-8 md:flex md:gap-12"
|
|
49
|
-
data-signals={JSON.stringify({ _drawerOpen: false })}
|
|
50
|
-
>
|
|
51
|
-
{/* Mobile header with hamburger */}
|
|
52
|
-
<div class="flex items-center justify-between mb-6 md:hidden">
|
|
53
|
-
<a href="/" class="text-xl font-semibold">
|
|
54
|
-
{siteName}
|
|
55
|
-
</a>
|
|
56
|
-
<button
|
|
57
|
-
data-on:click="$_drawerOpen = true"
|
|
58
|
-
class="p-2 -mr-2 text-muted-foreground hover:text-foreground"
|
|
59
|
-
aria-label="Open menu"
|
|
60
|
-
>
|
|
61
|
-
<svg
|
|
62
|
-
class="size-5"
|
|
63
|
-
fill="none"
|
|
64
|
-
viewBox="0 0 24 24"
|
|
65
|
-
stroke-width="1.5"
|
|
66
|
-
stroke="currentColor"
|
|
67
|
-
>
|
|
68
|
-
<path
|
|
69
|
-
stroke-linecap="round"
|
|
70
|
-
stroke-linejoin="round"
|
|
71
|
-
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
|
72
|
-
/>
|
|
73
|
-
</svg>
|
|
74
|
-
</button>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
{/* Mobile drawer backdrop */}
|
|
78
|
-
<div
|
|
79
|
-
class="fixed inset-0 bg-black/50 z-40 opacity-0 pointer-events-none transition-opacity duration-300 ease-in-out md:hidden"
|
|
80
|
-
data-class="{'opacity-100 pointer-events-auto': $_drawerOpen, 'opacity-0 pointer-events-none': !$_drawerOpen}"
|
|
81
|
-
data-on:click="$_drawerOpen = false"
|
|
82
|
-
/>
|
|
83
|
-
|
|
84
|
-
{/* Mobile drawer panel */}
|
|
85
|
-
<aside
|
|
86
|
-
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"
|
|
87
|
-
data-class="{'translate-x-0': $_drawerOpen, '-translate-x-full': !$_drawerOpen}"
|
|
88
|
-
>
|
|
89
|
-
<div class="flex items-center justify-between mb-8">
|
|
90
|
-
<a href="/" class="text-xl font-semibold">
|
|
91
|
-
{siteName}
|
|
92
|
-
</a>
|
|
93
|
-
<button
|
|
94
|
-
data-on:click="$_drawerOpen = false"
|
|
95
|
-
class="p-2 -mr-2 text-muted-foreground hover:text-foreground"
|
|
96
|
-
aria-label="Close menu"
|
|
97
|
-
>
|
|
98
|
-
<svg
|
|
99
|
-
class="size-5"
|
|
100
|
-
fill="none"
|
|
101
|
-
viewBox="0 0 24 24"
|
|
102
|
-
stroke-width="1.5"
|
|
103
|
-
stroke="currentColor"
|
|
104
|
-
>
|
|
105
|
-
<path
|
|
106
|
-
stroke-linecap="round"
|
|
107
|
-
stroke-linejoin="round"
|
|
108
|
-
d="M6 18L18 6M6 6l12 12"
|
|
109
|
-
/>
|
|
110
|
-
</svg>
|
|
111
|
-
</button>
|
|
112
|
-
</div>
|
|
113
|
-
<nav class="flex flex-col gap-0.5">
|
|
114
|
-
<NavLinks links={links} />
|
|
115
|
-
</nav>
|
|
116
|
-
</aside>
|
|
117
|
-
|
|
118
|
-
{/* Desktop sidebar */}
|
|
119
|
-
<aside class="hidden md:block md:w-48 md:shrink-0 md:sticky md:top-8 md:self-start">
|
|
120
|
-
<a href="/" class="text-xl font-semibold block mb-20">
|
|
121
|
-
{siteName}
|
|
122
|
-
</a>
|
|
123
|
-
<nav class="flex flex-col gap-0.5">
|
|
124
|
-
<NavLinks links={links} />
|
|
125
|
-
</nav>
|
|
126
|
-
</aside>
|
|
127
|
-
|
|
128
|
-
{/* Main content */}
|
|
129
|
-
<main class="flex-1 min-w-0">{children}</main>
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
};
|
|
@@ -1,60 +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
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FC } from "hono/jsx";
|
|
9
|
-
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import type { CollectionPageProps } from "../../types.js";
|
|
11
|
-
|
|
12
|
-
export const CollectionPage: FC<CollectionPageProps> = ({
|
|
13
|
-
collection,
|
|
14
|
-
posts,
|
|
15
|
-
}) => {
|
|
16
|
-
const { t } = useLingui();
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div>
|
|
20
|
-
<header class="mb-8">
|
|
21
|
-
<h1 class="text-2xl font-semibold">{collection.title}</h1>
|
|
22
|
-
{collection.description && (
|
|
23
|
-
<p class="text-muted-foreground mt-2">{collection.description}</p>
|
|
24
|
-
)}
|
|
25
|
-
</header>
|
|
26
|
-
|
|
27
|
-
<main class="flex flex-col gap-6">
|
|
28
|
-
{posts.length === 0 ? (
|
|
29
|
-
<p class="text-muted-foreground">
|
|
30
|
-
{t({
|
|
31
|
-
message: "No posts in this collection.",
|
|
32
|
-
comment: "@context: Empty state message",
|
|
33
|
-
})}
|
|
34
|
-
</p>
|
|
35
|
-
) : (
|
|
36
|
-
posts.map((post) => (
|
|
37
|
-
<article key={post.id} class="h-entry">
|
|
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.contentHtml || "" }}
|
|
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
|
-
)}
|
|
57
|
-
</main>
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Single Page Component
|
|
3
|
-
*
|
|
4
|
-
* Renders a custom page (type "page").
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.SinglePage.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FC } from "hono/jsx";
|
|
9
|
-
import type { SinglePageProps } from "../../types.js";
|
|
10
|
-
|
|
11
|
-
export const SinglePage: FC<SinglePageProps> = ({ page }) => {
|
|
12
|
-
return (
|
|
13
|
-
<article class="h-entry">
|
|
14
|
-
{page.title && (
|
|
15
|
-
<h1 class="p-name text-3xl font-semibold mb-6">{page.title}</h1>
|
|
16
|
-
)}
|
|
17
|
-
|
|
18
|
-
<div
|
|
19
|
-
class="e-content prose"
|
|
20
|
-
dangerouslySetInnerHTML={{ __html: page.contentHtml || "" }}
|
|
21
|
-
/>
|
|
22
|
-
</article>
|
|
23
|
-
);
|
|
24
|
-
};
|
package/src/theme/pages/index.ts
DELETED
|
@@ -1,13 +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
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { HomePage } from "./HomePage.js";
|
|
9
|
-
export { PostPage } from "./PostPage.js";
|
|
10
|
-
export { SinglePage } from "./SinglePage.js";
|
|
11
|
-
export { ArchivePage } from "./ArchivePage.js";
|
|
12
|
-
export { SearchPage } from "./SearchPage.js";
|
|
13
|
-
export { CollectionPage } from "./CollectionPage.js";
|