@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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Quote Card
|
|
3
3
|
*
|
|
4
|
-
* Left-border accent blockquote
|
|
4
|
+
* Left-border accent blockquote with full date in footer.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Fields:
|
|
7
7
|
* - quoteText: the quoted text
|
|
8
8
|
* - title: attribution (who said it)
|
|
9
9
|
* - url: source link
|
|
@@ -11,13 +11,17 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { FC } from "hono/jsx";
|
|
14
|
-
import type { TimelineCardProps } from "
|
|
14
|
+
import type { TimelineCardProps } from "../../types.js";
|
|
15
15
|
|
|
16
16
|
export const QuoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
17
17
|
return (
|
|
18
|
-
<article
|
|
18
|
+
<article
|
|
19
|
+
class={`h-entry${compact ? " feed-compact" : ""}`}
|
|
20
|
+
data-post
|
|
21
|
+
data-format="quote"
|
|
22
|
+
>
|
|
19
23
|
{post.quoteText && (
|
|
20
|
-
<blockquote class="
|
|
24
|
+
<blockquote class="feed-quote">
|
|
21
25
|
<div
|
|
22
26
|
class={`e-content ${compact ? "text-sm" : "text-base"} leading-relaxed`}
|
|
23
27
|
>
|
|
@@ -45,16 +49,17 @@ export const QuoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
|
45
49
|
{!compact && post.bodyHtml && (
|
|
46
50
|
<div
|
|
47
51
|
class="mt-3 prose text-muted-foreground"
|
|
52
|
+
data-post-body
|
|
48
53
|
dangerouslySetInnerHTML={{ __html: post.bodyHtml }}
|
|
49
54
|
/>
|
|
50
55
|
)}
|
|
51
|
-
<footer class="mt-2">
|
|
56
|
+
<footer class="mt-2" data-post-meta>
|
|
52
57
|
<a
|
|
53
58
|
href={post.permalink}
|
|
54
59
|
class="u-url text-xs text-muted-foreground hover:underline"
|
|
55
60
|
>
|
|
56
61
|
<time class="dt-published" datetime={post.publishedAt}>
|
|
57
|
-
{post.
|
|
62
|
+
{post.publishedAtFormatted}
|
|
58
63
|
</time>
|
|
59
64
|
</a>
|
|
60
65
|
</footer>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Thread Preview
|
|
3
3
|
*
|
|
4
4
|
* Root post + vertical line connector + compact replies underneath.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
8
8
|
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { ThreadPreviewProps } from "
|
|
9
|
+
import type { ThreadPreviewProps } from "../../types.js";
|
|
10
10
|
import { TimelineItem } from "./TimelineItem.js";
|
|
11
11
|
import { TimelineItemFromPost } from "./TimelineItem.js";
|
|
12
12
|
|
|
@@ -14,23 +14,22 @@ export const ThreadPreview: FC<ThreadPreviewProps> = ({
|
|
|
14
14
|
rootPost,
|
|
15
15
|
previewReplies,
|
|
16
16
|
totalReplyCount,
|
|
17
|
-
theme,
|
|
18
17
|
}) => {
|
|
19
18
|
const { t } = useLingui();
|
|
20
19
|
const remainingCount = totalReplyCount - previewReplies.length;
|
|
21
20
|
|
|
22
21
|
return (
|
|
23
22
|
<div>
|
|
24
|
-
<TimelineItem item={{ post: rootPost }}
|
|
23
|
+
<TimelineItem item={{ post: rootPost }} />
|
|
25
24
|
{previewReplies.length > 0 && (
|
|
26
|
-
<div class="
|
|
25
|
+
<div class="feed-replies">
|
|
27
26
|
{previewReplies.map((reply) => (
|
|
28
|
-
<div key={reply.id} class="
|
|
29
|
-
<TimelineItemFromPost post={reply} compact
|
|
27
|
+
<div key={reply.id} class="feed-reply">
|
|
28
|
+
<TimelineItemFromPost post={reply} compact />
|
|
30
29
|
</div>
|
|
31
30
|
))}
|
|
32
31
|
{remainingCount > 0 && (
|
|
33
|
-
<div class="
|
|
32
|
+
<div class="feed-reply">
|
|
34
33
|
<a
|
|
35
34
|
href={rootPost.permalink}
|
|
36
35
|
class="text-sm text-muted-foreground hover:text-foreground hover:underline"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline Feed
|
|
3
|
+
*
|
|
4
|
+
* Flat list of posts separated by simple dividers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FC } from "hono/jsx";
|
|
8
|
+
import type { TimelineFeedProps } from "../../types.js";
|
|
9
|
+
import { TimelineItem } from "./TimelineItem.js";
|
|
10
|
+
import { ThreadPreview } from "./ThreadPreview.js";
|
|
11
|
+
import { PagePagination } from "../shared/Pagination.js";
|
|
12
|
+
|
|
13
|
+
export const TimelineFeed: FC<TimelineFeedProps> = ({
|
|
14
|
+
items,
|
|
15
|
+
currentPage,
|
|
16
|
+
totalPages,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<div data-feed>
|
|
20
|
+
<div id="timeline-feed">
|
|
21
|
+
<div id="timeline-items" class="flex flex-col">
|
|
22
|
+
{items.map((item, i) => (
|
|
23
|
+
<div key={item.post.id}>
|
|
24
|
+
{i > 0 && <hr class="feed-divider" />}
|
|
25
|
+
{item.threadPreview ? (
|
|
26
|
+
<ThreadPreview
|
|
27
|
+
rootPost={item.post}
|
|
28
|
+
previewReplies={item.threadPreview.replies}
|
|
29
|
+
totalReplyCount={item.threadPreview.totalReplyCount}
|
|
30
|
+
/>
|
|
31
|
+
) : (
|
|
32
|
+
<TimelineItem item={item} />
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
{currentPage !== undefined &&
|
|
39
|
+
totalPages !== undefined &&
|
|
40
|
+
totalPages > 1 && (
|
|
41
|
+
<PagePagination
|
|
42
|
+
baseUrl="/"
|
|
43
|
+
currentPage={currentPage}
|
|
44
|
+
totalPages={totalPages}
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline Item
|
|
3
|
+
*
|
|
4
|
+
* Dispatches to the correct card component based on post format.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FC } from "hono/jsx";
|
|
8
|
+
import type {
|
|
9
|
+
TimelineItemView,
|
|
10
|
+
TimelineCardProps,
|
|
11
|
+
PostView,
|
|
12
|
+
Format,
|
|
13
|
+
} from "../../types.js";
|
|
14
|
+
import { NoteCard } from "./NoteCard.js";
|
|
15
|
+
import { LinkCard } from "./LinkCard.js";
|
|
16
|
+
import { QuoteCard } from "./QuoteCard.js";
|
|
17
|
+
|
|
18
|
+
const CARD_MAP: Record<Format, FC<TimelineCardProps>> = {
|
|
19
|
+
note: NoteCard,
|
|
20
|
+
link: LinkCard,
|
|
21
|
+
quote: QuoteCard,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
interface TimelineItemProps {
|
|
25
|
+
item: TimelineItemView;
|
|
26
|
+
compact?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TimelineItemFromPostProps {
|
|
30
|
+
post: PostView;
|
|
31
|
+
compact?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const TimelineItem: FC<TimelineItemProps> = ({ item, compact }) => {
|
|
35
|
+
const Card = CARD_MAP[item.post.format];
|
|
36
|
+
return <Card post={item.post} compact={compact} />;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const TimelineItemFromPost: FC<TimelineItemFromPostProps> = ({
|
|
40
|
+
post,
|
|
41
|
+
compact,
|
|
42
|
+
}) => {
|
|
43
|
+
const Card = CARD_MAP[post.format];
|
|
44
|
+
return <Card post={post} compact={compact} />;
|
|
45
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Font Themes
|
|
3
|
+
*
|
|
4
|
+
* System-font-only presets — no external font loading required.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A font theme definition with display metadata.
|
|
9
|
+
*/
|
|
10
|
+
export interface FontTheme {
|
|
11
|
+
/** Stored in DB settings, e.g. "serif" */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Display name, e.g. "Serif" */
|
|
14
|
+
name: string;
|
|
15
|
+
/** CSS font-family stack */
|
|
16
|
+
fontFamily: string;
|
|
17
|
+
/** Short description for the picker UI */
|
|
18
|
+
description: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const BUILTIN_FONT_THEMES: FontTheme[] = [
|
|
22
|
+
{
|
|
23
|
+
id: "default",
|
|
24
|
+
name: "System Default",
|
|
25
|
+
// 现代系统字体栈:先英文,后 Mac/iOS 中文,再 Win 中文
|
|
26
|
+
fontFamily:
|
|
27
|
+
'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans CN", sans-serif',
|
|
28
|
+
description: "与你的操作系统保持一致,最稳定的阅读体验",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "serif",
|
|
32
|
+
name: "Classic Serif",
|
|
33
|
+
// Charter 是 Apple 系统自带的极品衬线体
|
|
34
|
+
fontFamily:
|
|
35
|
+
'Charter, "Bitstream Charter", "Sitka Text", Georgia, "Songti SC", "Source Han Serif CN", "STSong", "SimSun", serif',
|
|
36
|
+
description: "传统的衬线体,适合深度长文阅读",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "humanist",
|
|
40
|
+
name: "Humanist",
|
|
41
|
+
// Optima 具有书法韵味,Candara 是 Windows 上的优质人文体
|
|
42
|
+
fontFamily:
|
|
43
|
+
'Optima, Candara, "Noto Sans", "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
|
|
44
|
+
description: "温润如玉的字体风格,兼具现代感与书法美感",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "mono",
|
|
48
|
+
name: "Monospace",
|
|
49
|
+
// 优先使用 JetBrains Mono 或 SF Mono
|
|
50
|
+
fontFamily:
|
|
51
|
+
'"JetBrains Mono", "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", "PingFang SC", "Microsoft YaHei", monospace',
|
|
52
|
+
description: "等宽字体,适合技术内容或代码展示",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
@@ -23,6 +23,8 @@ export interface BaseLayoutProps {
|
|
|
23
23
|
lang?: string;
|
|
24
24
|
c?: Context;
|
|
25
25
|
toast?: ToastProps;
|
|
26
|
+
faviconUrl?: string;
|
|
27
|
+
noindex?: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
@@ -31,17 +33,31 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
31
33
|
lang,
|
|
32
34
|
c,
|
|
33
35
|
toast,
|
|
36
|
+
faviconUrl,
|
|
37
|
+
noindex,
|
|
34
38
|
children,
|
|
35
39
|
}) => {
|
|
36
40
|
// Read lang from Hono context if available, otherwise use prop or default
|
|
37
41
|
const resolvedLang = lang ?? (c ? c.get("lang") : "en");
|
|
38
42
|
|
|
43
|
+
// Read faviconUrl from context when not provided as prop (fixes dashboard favicon)
|
|
44
|
+
const resolvedFaviconUrl = faviconUrl ?? (c ? c.get("faviconUrl") : undefined);
|
|
45
|
+
|
|
46
|
+
// Read noindex from context when not provided as prop
|
|
47
|
+
const resolvedNoindex = noindex ?? (c ? c.get("noindex") : undefined);
|
|
48
|
+
|
|
39
49
|
// Automatically wrap with I18nProvider if Context is provided
|
|
40
50
|
const content = c ? <I18nProvider c={c}>{children}</I18nProvider> : children;
|
|
41
51
|
|
|
42
52
|
// Read theme style from Hono context if available
|
|
43
53
|
const themeStyle = c ? c.get("themeStyle") : undefined;
|
|
44
54
|
|
|
55
|
+
// Read custom CSS from Hono context if available
|
|
56
|
+
const customCSS = c ? c.get("customCSS") : undefined;
|
|
57
|
+
|
|
58
|
+
// Check authentication status for data attribute
|
|
59
|
+
const isAuthenticated = c ? c.get("isAuthenticated") : false;
|
|
60
|
+
|
|
45
61
|
return (
|
|
46
62
|
<html lang={resolvedLang}>
|
|
47
63
|
<head>
|
|
@@ -49,12 +65,23 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
49
65
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
50
66
|
<title>{title}</title>
|
|
51
67
|
{description && <meta name="description" content={description} />}
|
|
68
|
+
{resolvedNoindex && <meta name="robots" content="noindex, nofollow" />}
|
|
69
|
+
{resolvedFaviconUrl && (
|
|
70
|
+
<>
|
|
71
|
+
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32" />
|
|
72
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
|
|
73
|
+
</>
|
|
74
|
+
)}
|
|
52
75
|
<ViteClient />
|
|
53
76
|
<Link href="/src/style.css" rel="stylesheet" />
|
|
54
77
|
{themeStyle && <style>{themeStyle}</style>}
|
|
78
|
+
{customCSS && <style>{customCSS}</style>}
|
|
55
79
|
<Script src="/src/client.ts" />
|
|
56
80
|
</head>
|
|
57
|
-
<body
|
|
81
|
+
<body
|
|
82
|
+
class="bg-background text-foreground antialiased"
|
|
83
|
+
{...(isAuthenticated ? { "data-authenticated": true } : {})}
|
|
84
|
+
>
|
|
58
85
|
{content}
|
|
59
86
|
<div id="toast-container" class="toast-container">
|
|
60
87
|
{toast && (
|
|
@@ -126,16 +126,6 @@ function DashLayoutContent({
|
|
|
126
126
|
comment: "@context: Dashboard navigation - URL redirects",
|
|
127
127
|
})}
|
|
128
128
|
</a>
|
|
129
|
-
<a
|
|
130
|
-
href="/dash/navigation"
|
|
131
|
-
class={navClass("/dash/navigation", /^\/dash\/navigation/)}
|
|
132
|
-
>
|
|
133
|
-
{t({
|
|
134
|
-
message: "Navigation",
|
|
135
|
-
comment:
|
|
136
|
-
"@context: Dashboard navigation - navigation links management",
|
|
137
|
-
})}
|
|
138
|
-
</a>
|
|
139
129
|
<a
|
|
140
130
|
href="/dash/settings"
|
|
141
131
|
class={navClass("/dash/settings", /^\/dash\/settings/)}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site Layout
|
|
3
|
+
*
|
|
4
|
+
* Vertical header: site name on top, custom nav links below, description under nav.
|
|
5
|
+
* Content area with browse filter tabs and compose prompt/dialog for authenticated users.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
9
|
+
import { useLingui } from "@lingui/react/macro";
|
|
10
|
+
import type { NavItemView, SiteLayoutProps } from "../../types.js";
|
|
11
|
+
import { ComposeDialog } from "../compose/ComposeDialog.js";
|
|
12
|
+
import { ComposePrompt } from "../compose/ComposePrompt.js";
|
|
13
|
+
|
|
14
|
+
function HeaderLink({ link }: { link: NavItemView }) {
|
|
15
|
+
return (
|
|
16
|
+
<a
|
|
17
|
+
href={link.url}
|
|
18
|
+
class={`site-header-link ${link.isActive ? "site-header-link-active" : ""}`}
|
|
19
|
+
{...(link.isExternal
|
|
20
|
+
? { target: "_blank", rel: "noopener noreferrer" }
|
|
21
|
+
: {})}
|
|
22
|
+
>
|
|
23
|
+
{link.label}
|
|
24
|
+
</a>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
29
|
+
siteName,
|
|
30
|
+
siteDescription,
|
|
31
|
+
links,
|
|
32
|
+
currentPath,
|
|
33
|
+
isAuthenticated,
|
|
34
|
+
collections,
|
|
35
|
+
homeDefaultView,
|
|
36
|
+
siteAvatarUrl,
|
|
37
|
+
showHeaderAvatar,
|
|
38
|
+
siteFooterHtml,
|
|
39
|
+
children,
|
|
40
|
+
}) => {
|
|
41
|
+
const { t } = useLingui();
|
|
42
|
+
|
|
43
|
+
const latestHref = homeDefaultView === "featured" ? "/latest" : "/";
|
|
44
|
+
const featuredHref = homeDefaultView === "featured" ? "/" : "/featured";
|
|
45
|
+
|
|
46
|
+
const latestLink = {
|
|
47
|
+
href: latestHref,
|
|
48
|
+
label: t({
|
|
49
|
+
message: "Latest",
|
|
50
|
+
comment: "@context: Browse filter for latest posts",
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
const featuredLink = {
|
|
54
|
+
href: featuredHref,
|
|
55
|
+
label: t({
|
|
56
|
+
message: "Featured",
|
|
57
|
+
comment: "@context: Browse filter for featured posts",
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Default view tab comes first
|
|
62
|
+
const browseLinks =
|
|
63
|
+
homeDefaultView === "featured"
|
|
64
|
+
? [featuredLink, latestLink]
|
|
65
|
+
: [latestLink, featuredLink];
|
|
66
|
+
|
|
67
|
+
const searchLabel = t({
|
|
68
|
+
message: "Search",
|
|
69
|
+
comment: "@context: Search icon link in browse nav",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const isHomePage =
|
|
73
|
+
currentPath === "/" ||
|
|
74
|
+
currentPath === "/featured" ||
|
|
75
|
+
currentPath === "/latest";
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div class="site-page">
|
|
79
|
+
<header class="site-header">
|
|
80
|
+
<div class="site-header-inner">
|
|
81
|
+
<div class="site-header-top site-header-top-bordered">
|
|
82
|
+
<a href="/" class="site-logo">
|
|
83
|
+
{showHeaderAvatar && siteAvatarUrl && (
|
|
84
|
+
<img src={siteAvatarUrl} class="site-logo-avatar" alt="" />
|
|
85
|
+
)}
|
|
86
|
+
{siteName}
|
|
87
|
+
</a>
|
|
88
|
+
<div class="site-header-right">
|
|
89
|
+
{links.length > 0 && (
|
|
90
|
+
<nav class="site-header-nav">
|
|
91
|
+
{links.map((link) => (
|
|
92
|
+
<HeaderLink key={link.id} link={link} />
|
|
93
|
+
))}
|
|
94
|
+
</nav>
|
|
95
|
+
)}
|
|
96
|
+
<a
|
|
97
|
+
href="/search"
|
|
98
|
+
class={`site-header-search ${currentPath === "/search" ? "site-header-search-active" : ""}`}
|
|
99
|
+
aria-label={searchLabel}
|
|
100
|
+
title={searchLabel}
|
|
101
|
+
>
|
|
102
|
+
<svg
|
|
103
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
104
|
+
width="16"
|
|
105
|
+
height="16"
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
fill="none"
|
|
108
|
+
stroke="currentColor"
|
|
109
|
+
stroke-width="2"
|
|
110
|
+
stroke-linecap="round"
|
|
111
|
+
stroke-linejoin="round"
|
|
112
|
+
>
|
|
113
|
+
<circle cx="11" cy="11" r="8" />
|
|
114
|
+
<path d="m21 21-4.35-4.35" />
|
|
115
|
+
</svg>
|
|
116
|
+
</a>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
{isHomePage && siteDescription && (
|
|
120
|
+
<p class="site-description">{siteDescription}</p>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</header>
|
|
124
|
+
|
|
125
|
+
<main class="site-main">
|
|
126
|
+
<div class="site-container">
|
|
127
|
+
<div class="site-content">
|
|
128
|
+
{isHomePage && (
|
|
129
|
+
<nav class="site-browse-nav">
|
|
130
|
+
{browseLinks.map((link, i) => (
|
|
131
|
+
<>
|
|
132
|
+
{i > 0 && <span class="site-browse-sep">/</span>}
|
|
133
|
+
<a
|
|
134
|
+
key={link.href}
|
|
135
|
+
href={link.href}
|
|
136
|
+
class={`site-browse-link ${currentPath === link.href ? "site-browse-link-active" : ""}`}
|
|
137
|
+
>
|
|
138
|
+
{link.label}
|
|
139
|
+
</a>
|
|
140
|
+
</>
|
|
141
|
+
))}
|
|
142
|
+
</nav>
|
|
143
|
+
)}
|
|
144
|
+
{isHomePage && isAuthenticated && <ComposePrompt />}
|
|
145
|
+
{children}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</main>
|
|
149
|
+
|
|
150
|
+
{siteFooterHtml && (
|
|
151
|
+
<footer class="site-footer" data-footer>
|
|
152
|
+
<div class="site-container">
|
|
153
|
+
<div
|
|
154
|
+
class="prose"
|
|
155
|
+
dangerouslySetInnerHTML={{ __html: siteFooterHtml }}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
</footer>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{isAuthenticated && <ComposeDialog collections={collections} />}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 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
|
-
import type { ArchivePageProps } from "
|
|
10
|
-
import { FORMATS } from "
|
|
11
|
-
import { Pagination
|
|
9
|
+
import type { ArchivePageProps } from "../../types.js";
|
|
10
|
+
import { FORMATS } from "../../types.js";
|
|
11
|
+
import { Pagination } from "../shared/Pagination.js";
|
|
12
12
|
|
|
13
13
|
function getFormatLabel(format: string): string {
|
|
14
14
|
const { t } = useLingui();
|
|
@@ -39,7 +39,7 @@ function getFormatLabelPlural(format: string): string {
|
|
|
39
39
|
comment: "@context: Post format label plural - quotes",
|
|
40
40
|
}),
|
|
41
41
|
};
|
|
42
|
-
return labels[format] ??
|
|
42
|
+
return labels[format] ?? format + "s";
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export const ArchivePage: FC<ArchivePageProps> = ({
|
|
@@ -48,17 +48,14 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
48
48
|
nextCursor,
|
|
49
49
|
format,
|
|
50
50
|
featured,
|
|
51
|
-
theme,
|
|
52
51
|
}) => {
|
|
53
52
|
const { t } = useLingui();
|
|
54
53
|
const title = format
|
|
55
54
|
? getFormatLabelPlural(format)
|
|
56
55
|
: t({ message: "Archive", comment: "@context: Archive page title" });
|
|
57
56
|
|
|
58
|
-
const PaginationComponent = theme?.Pagination ?? DefaultPagination;
|
|
59
|
-
|
|
60
57
|
return (
|
|
61
|
-
<div class="py-6">
|
|
58
|
+
<div class="py-6" data-page="archive">
|
|
62
59
|
<header class="mb-8">
|
|
63
60
|
<h1 class="text-2xl font-semibold">{title}</h1>
|
|
64
61
|
|
|
@@ -66,7 +63,10 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
66
63
|
<nav class="flex flex-wrap gap-2 mt-4">
|
|
67
64
|
<a
|
|
68
65
|
href="/archive"
|
|
69
|
-
class={
|
|
66
|
+
class={
|
|
67
|
+
"badge " +
|
|
68
|
+
(!format && !featured ? "badge-primary" : "badge-outline")
|
|
69
|
+
}
|
|
70
70
|
>
|
|
71
71
|
{t({
|
|
72
72
|
message: "All",
|
|
@@ -76,15 +76,18 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
76
76
|
{FORMATS.map((formatKey) => (
|
|
77
77
|
<a
|
|
78
78
|
key={formatKey}
|
|
79
|
-
href={
|
|
80
|
-
class={
|
|
79
|
+
href={"/archive?format=" + formatKey}
|
|
80
|
+
class={
|
|
81
|
+
"badge " +
|
|
82
|
+
(format === formatKey ? "badge-primary" : "badge-outline")
|
|
83
|
+
}
|
|
81
84
|
>
|
|
82
85
|
{getFormatLabelPlural(formatKey)}
|
|
83
86
|
</a>
|
|
84
87
|
))}
|
|
85
88
|
<a
|
|
86
89
|
href="/archive?featured=true"
|
|
87
|
-
class={
|
|
90
|
+
class={"badge " + (featured ? "badge-primary" : "badge-outline")}
|
|
88
91
|
>
|
|
89
92
|
{t({
|
|
90
93
|
message: "Featured",
|
|
@@ -104,7 +107,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
104
107
|
</p>
|
|
105
108
|
) : (
|
|
106
109
|
groups.map((group) => (
|
|
107
|
-
<section key={
|
|
110
|
+
<section key={group.year + "-" + group.month} class="mb-8">
|
|
108
111
|
<h2 class="text-lg font-medium mb-4 text-muted-foreground">
|
|
109
112
|
{group.label}
|
|
110
113
|
</h2>
|
|
@@ -113,6 +116,8 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
113
116
|
<article
|
|
114
117
|
key={post.id}
|
|
115
118
|
class="flex items-baseline gap-4 py-2.5"
|
|
119
|
+
data-post
|
|
120
|
+
data-format={post.format}
|
|
116
121
|
>
|
|
117
122
|
<time
|
|
118
123
|
class="text-sm text-muted-foreground w-12 shrink-0"
|
|
@@ -124,7 +129,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
124
129
|
<a href={post.permalink} class="hover:underline">
|
|
125
130
|
{post.title ||
|
|
126
131
|
post.excerpt?.slice(0, 80) ||
|
|
127
|
-
|
|
132
|
+
"Post #" + post.id}
|
|
128
133
|
</a>
|
|
129
134
|
{!format && (
|
|
130
135
|
<span class="ml-2 badge-outline text-xs">
|
|
@@ -141,10 +146,10 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
141
146
|
</main>
|
|
142
147
|
|
|
143
148
|
{/* Pagination */}
|
|
144
|
-
<
|
|
149
|
+
<Pagination
|
|
145
150
|
baseUrl={
|
|
146
151
|
format
|
|
147
|
-
?
|
|
152
|
+
? "/archive?format=" + format
|
|
148
153
|
: featured
|
|
149
154
|
? "/archive?featured=true"
|
|
150
155
|
: "/archive"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Collection Page
|
|
3
3
|
*
|
|
4
4
|
* Collection header with divider-separated post list.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
8
8
|
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { CollectionPageProps } from "
|
|
9
|
+
import type { CollectionPageProps } from "../../types.js";
|
|
10
10
|
|
|
11
11
|
export const CollectionPage: FC<CollectionPageProps> = ({
|
|
12
12
|
collection,
|
|
@@ -15,7 +15,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
15
15
|
const { t } = useLingui();
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<div class="py-6">
|
|
18
|
+
<div class="py-6" data-page="collection">
|
|
19
19
|
<header class="mb-8">
|
|
20
20
|
<h1 class="text-2xl font-semibold">{collection.title}</h1>
|
|
21
21
|
{collection.description && (
|
|
@@ -34,7 +34,12 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
34
34
|
) : (
|
|
35
35
|
<div class="divide-y divide-border">
|
|
36
36
|
{posts.map((post) => (
|
|
37
|
-
<article
|
|
37
|
+
<article
|
|
38
|
+
key={post.id}
|
|
39
|
+
class="h-entry py-4"
|
|
40
|
+
data-post
|
|
41
|
+
data-format={post.format}
|
|
42
|
+
>
|
|
38
43
|
{post.title && (
|
|
39
44
|
<h2 class="p-name text-lg font-medium mb-2">
|
|
40
45
|
<a href={post.permalink} class="u-url hover:underline">
|
|
@@ -44,9 +49,13 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
44
49
|
)}
|
|
45
50
|
<div
|
|
46
51
|
class="e-content prose prose-sm"
|
|
52
|
+
data-post-body
|
|
47
53
|
dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
|
|
48
54
|
/>
|
|
49
|
-
<footer
|
|
55
|
+
<footer
|
|
56
|
+
class="mt-2 text-sm text-muted-foreground"
|
|
57
|
+
data-post-meta
|
|
58
|
+
>
|
|
50
59
|
<time class="dt-published" datetime={post.publishedAt}>
|
|
51
60
|
{post.publishedAtFormatted}
|
|
52
61
|
</time>
|