@jant/core 0.3.22 → 0.3.23
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 +22 -3
- package/dist/index.js +3 -4
- package/dist/lib/render.js +1 -1
- package/dist/lib/view.js +1 -1
- package/dist/routes/api/timeline.js +3 -3
- package/dist/routes/pages/archive.js +1 -1
- package/dist/routes/pages/collection.js +1 -1
- package/dist/routes/pages/home.js +1 -1
- package/dist/routes/pages/page.js +1 -1
- package/dist/routes/pages/post.js +1 -1
- package/dist/routes/pages/search.js +1 -1
- 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/minimal/MinimalSiteLayout.js +83 -0
- package/dist/themes/minimal/index.js +65 -0
- package/dist/{theme → themes/minimal}/pages/ArchivePage.js +7 -8
- package/dist/{theme → themes/minimal}/pages/CollectionPage.js +7 -5
- package/dist/{theme → themes/minimal}/pages/HomePage.js +2 -3
- package/dist/{theme → themes/minimal}/pages/PostPage.js +5 -6
- package/dist/{theme → themes/minimal}/pages/SearchPage.js +11 -10
- package/dist/{theme → themes/minimal}/pages/SinglePage.js +3 -4
- package/dist/themes/minimal/timeline/ArticleCard.js +36 -0
- package/dist/themes/minimal/timeline/ImageCard.js +67 -0
- package/dist/{theme/components → themes/minimal}/timeline/LinkCard.js +14 -26
- package/dist/{theme/components → themes/minimal}/timeline/NoteCard.js +7 -7
- package/dist/{theme/components → themes/minimal}/timeline/QuoteCard.js +6 -6
- package/dist/{theme/components → themes/minimal}/timeline/ThreadPreview.js +13 -18
- package/dist/themes/minimal/timeline/TimelineFeed.js +48 -0
- package/dist/{theme/components → themes/minimal}/timeline/TimelineItem.js +1 -2
- package/package.json +1 -1
- package/src/app.tsx +26 -3
- package/src/i18n/locales/en.po +47 -47
- package/src/i18n/locales/zh-Hans.po +47 -47
- package/src/i18n/locales/zh-Hant.po +47 -47
- package/src/index.ts +4 -5
- package/src/lib/__tests__/view.test.ts +18 -16
- package/src/lib/render.tsx +1 -1
- package/src/lib/view.ts +1 -1
- package/src/routes/api/timeline.tsx +3 -3
- package/src/routes/pages/archive.tsx +1 -1
- package/src/routes/pages/collection.tsx +1 -1
- package/src/routes/pages/home.tsx +1 -1
- package/src/routes/pages/page.tsx +1 -1
- package/src/routes/pages/post.tsx +1 -1
- package/src/routes/pages/search.tsx +1 -1
- package/src/styles/components.css +0 -54
- 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/minimal/MinimalSiteLayout.tsx +100 -0
- package/src/themes/minimal/index.ts +83 -0
- package/src/{theme → themes/minimal}/pages/ArchivePage.tsx +8 -11
- package/src/{theme → themes/minimal}/pages/CollectionPage.tsx +6 -6
- package/src/{theme → themes/minimal}/pages/HomePage.tsx +3 -4
- package/src/{theme → themes/minimal}/pages/PostPage.tsx +6 -7
- package/src/{theme → themes/minimal}/pages/SearchPage.tsx +11 -17
- package/src/{theme → themes/minimal}/pages/SinglePage.tsx +4 -5
- package/src/themes/minimal/timeline/ArticleCard.tsx +37 -0
- package/src/themes/minimal/timeline/ImageCard.tsx +63 -0
- package/src/themes/minimal/timeline/LinkCard.tsx +48 -0
- package/src/{theme/components → themes/minimal}/timeline/NoteCard.tsx +10 -9
- package/src/{theme/components → themes/minimal}/timeline/QuoteCard.tsx +9 -8
- package/src/{theme/components → themes/minimal}/timeline/ThreadPreview.tsx +14 -16
- package/src/{theme/components → themes/minimal}/timeline/TimelineFeed.tsx +14 -13
- package/src/{theme/components → themes/minimal}/timeline/TimelineItem.tsx +1 -4
- package/dist/theme/components/timeline/ArticleCard.js +0 -46
- package/dist/theme/components/timeline/ImageCard.js +0 -83
- 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/index.js +0 -11
- package/src/theme/components/timeline/ArticleCard.tsx +0 -45
- package/src/theme/components/timeline/ImageCard.tsx +0 -70
- package/src/theme/components/timeline/LinkCard.tsx +0 -59
- package/src/theme/components/timeline/index.ts +0 -8
- package/src/theme/layouts/SiteLayout.tsx +0 -132
- package/src/theme/pages/index.ts +0 -13
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Theme - Site Layout
|
|
3
|
+
*
|
|
4
|
+
* Single-column, centered layout with horizontal nav.
|
|
5
|
+
* Inspired by Tufte CSS and Manton.org.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
9
|
+
import type { NavLinkView, SiteLayoutProps } from "../../types.js";
|
|
10
|
+
|
|
11
|
+
function NavLinks({ links }: { links: NavLinkView[] }) {
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
{links.map((link) => (
|
|
15
|
+
<a
|
|
16
|
+
key={link.id}
|
|
17
|
+
href={link.url}
|
|
18
|
+
class={`text-sm ${
|
|
19
|
+
link.isActive
|
|
20
|
+
? "text-foreground font-medium"
|
|
21
|
+
: "text-muted-foreground hover:text-foreground"
|
|
22
|
+
}`}
|
|
23
|
+
{...(link.isExternal
|
|
24
|
+
? { target: "_blank", rel: "noopener noreferrer" }
|
|
25
|
+
: {})}
|
|
26
|
+
>
|
|
27
|
+
{link.label}
|
|
28
|
+
{link.isExternal && (
|
|
29
|
+
<span class="ml-0.5 text-xs opacity-50">↗</span>
|
|
30
|
+
)}
|
|
31
|
+
</a>
|
|
32
|
+
))}
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
38
|
+
siteName,
|
|
39
|
+
links,
|
|
40
|
+
children,
|
|
41
|
+
}) => {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
class="max-w-2xl mx-auto px-4 py-8"
|
|
45
|
+
data-signals={JSON.stringify({ _menuOpen: false })}
|
|
46
|
+
>
|
|
47
|
+
{/* Header */}
|
|
48
|
+
<header class="mb-12">
|
|
49
|
+
<div class="flex items-center justify-between">
|
|
50
|
+
<a href="/" class="text-xl font-semibold">
|
|
51
|
+
{siteName}
|
|
52
|
+
</a>
|
|
53
|
+
|
|
54
|
+
{/* Mobile menu toggle */}
|
|
55
|
+
{links.length > 0 && (
|
|
56
|
+
<button
|
|
57
|
+
data-on:click="$_menuOpen = !$_menuOpen"
|
|
58
|
+
class="p-2 -mr-2 text-muted-foreground hover:text-foreground sm:hidden"
|
|
59
|
+
aria-label="Toggle 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
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{/* Desktop nav (inline) */}
|
|
79
|
+
{links.length > 0 && (
|
|
80
|
+
<nav class="hidden sm:flex flex-wrap gap-x-4 gap-y-1 mt-3">
|
|
81
|
+
<NavLinks links={links} />
|
|
82
|
+
</nav>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{/* Mobile nav (collapsible) */}
|
|
86
|
+
{links.length > 0 && (
|
|
87
|
+
<nav
|
|
88
|
+
class="sm:hidden flex flex-col gap-1 mt-3 overflow-hidden"
|
|
89
|
+
data-show="$_menuOpen"
|
|
90
|
+
>
|
|
91
|
+
<NavLinks links={links} />
|
|
92
|
+
</nav>
|
|
93
|
+
)}
|
|
94
|
+
</header>
|
|
95
|
+
|
|
96
|
+
{/* Main content */}
|
|
97
|
+
<main>{children}</main>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Theme
|
|
3
|
+
*
|
|
4
|
+
* A content-first, borderless theme inspired by Tufte CSS and Manton.org.
|
|
5
|
+
* Single-column layout with serif-friendly typography and generous whitespace.
|
|
6
|
+
*
|
|
7
|
+
* This is the default theme for Jant.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { JantTheme, ThemeComponents } from "../../types.js";
|
|
11
|
+
import type { ColorTheme } from "../../theme/color-themes.js";
|
|
12
|
+
|
|
13
|
+
// Layout
|
|
14
|
+
import { SiteLayout } from "./MinimalSiteLayout.js";
|
|
15
|
+
|
|
16
|
+
// Pages
|
|
17
|
+
import { HomePage } from "./pages/HomePage.js";
|
|
18
|
+
import { PostPage } from "./pages/PostPage.js";
|
|
19
|
+
import { SinglePage } from "./pages/SinglePage.js";
|
|
20
|
+
import { ArchivePage } from "./pages/ArchivePage.js";
|
|
21
|
+
import { SearchPage } from "./pages/SearchPage.js";
|
|
22
|
+
import { CollectionPage } from "./pages/CollectionPage.js";
|
|
23
|
+
|
|
24
|
+
// Timeline
|
|
25
|
+
import { NoteCard } from "./timeline/NoteCard.js";
|
|
26
|
+
import { ArticleCard } from "./timeline/ArticleCard.js";
|
|
27
|
+
import { LinkCard } from "./timeline/LinkCard.js";
|
|
28
|
+
import { QuoteCard } from "./timeline/QuoteCard.js";
|
|
29
|
+
import { ImageCard } from "./timeline/ImageCard.js";
|
|
30
|
+
import { ThreadPreview } from "./timeline/ThreadPreview.js";
|
|
31
|
+
import { TimelineFeed } from "./timeline/TimelineFeed.js";
|
|
32
|
+
|
|
33
|
+
export interface ThemeOptions {
|
|
34
|
+
/** Override individual components */
|
|
35
|
+
components?: Partial<ThemeComponents>;
|
|
36
|
+
/** CSS variable overrides */
|
|
37
|
+
cssVariables?: Record<string, string>;
|
|
38
|
+
/** Custom color themes */
|
|
39
|
+
colorThemes?: ColorTheme[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create the minimal theme configuration.
|
|
44
|
+
*
|
|
45
|
+
* @param options - Optional overrides for components, CSS variables, or color themes
|
|
46
|
+
* @returns A JantTheme configuration object
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { createApp } from "@jant/core";
|
|
51
|
+
* import { minimalTheme } from "@jant/core";
|
|
52
|
+
*
|
|
53
|
+
* export default createApp({
|
|
54
|
+
* theme: minimalTheme(), // re-exported as minimalTheme from @jant/core
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function theme(options?: ThemeOptions): JantTheme {
|
|
59
|
+
return {
|
|
60
|
+
name: "minimal",
|
|
61
|
+
components: {
|
|
62
|
+
SiteLayout,
|
|
63
|
+
HomePage,
|
|
64
|
+
PostPage,
|
|
65
|
+
SinglePage,
|
|
66
|
+
ArchivePage,
|
|
67
|
+
SearchPage,
|
|
68
|
+
CollectionPage,
|
|
69
|
+
NoteCard,
|
|
70
|
+
ArticleCard,
|
|
71
|
+
LinkCard,
|
|
72
|
+
QuoteCard,
|
|
73
|
+
ImageCard,
|
|
74
|
+
ThreadPreview,
|
|
75
|
+
TimelineFeed,
|
|
76
|
+
...options?.components,
|
|
77
|
+
},
|
|
78
|
+
cssVariables: {
|
|
79
|
+
...options?.cssVariables,
|
|
80
|
+
},
|
|
81
|
+
colorThemes: options?.colorThemes,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal Theme - Archive Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.ArchivePage.
|
|
4
|
+
* Date-first list with type filter and cursor pagination.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { FC } from "hono/jsx";
|
|
9
8
|
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import type { ArchivePageProps } from "
|
|
11
|
-
import { POST_TYPES } from "
|
|
12
|
-
import { Pagination as DefaultPagination } from "
|
|
9
|
+
import type { ArchivePageProps } from "../../../types.js";
|
|
10
|
+
import { POST_TYPES } from "../../../types.js";
|
|
11
|
+
import { Pagination as DefaultPagination } from "../../../theme/components/Pagination.js";
|
|
13
12
|
|
|
14
13
|
function getTypeLabel(type: string): string {
|
|
15
14
|
const { t } = useLingui();
|
|
@@ -83,11 +82,10 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
83
82
|
<header class="mb-8">
|
|
84
83
|
<h1 class="text-2xl font-semibold">{title}</h1>
|
|
85
84
|
|
|
86
|
-
{/* Type filter */}
|
|
87
85
|
<nav class="flex flex-wrap gap-2 mt-4">
|
|
88
86
|
<a
|
|
89
87
|
href="/archive"
|
|
90
|
-
class={`
|
|
88
|
+
class={`text-sm ${!type ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground"}`}
|
|
91
89
|
>
|
|
92
90
|
{t({
|
|
93
91
|
message: "All",
|
|
@@ -98,7 +96,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
98
96
|
<a
|
|
99
97
|
key={typeKey}
|
|
100
98
|
href={`/archive?type=${typeKey}`}
|
|
101
|
-
class={`
|
|
99
|
+
class={`text-sm ${type === typeKey ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground"}`}
|
|
102
100
|
>
|
|
103
101
|
{getTypeLabelPlural(typeKey)}
|
|
104
102
|
</a>
|
|
@@ -136,7 +134,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
136
134
|
`Post #${post.id}`}
|
|
137
135
|
</a>
|
|
138
136
|
{!type && (
|
|
139
|
-
<span class="ml-2
|
|
137
|
+
<span class="ml-2 text-xs text-muted-foreground">
|
|
140
138
|
{getTypeLabel(post.type)}
|
|
141
139
|
</span>
|
|
142
140
|
)}
|
|
@@ -149,7 +147,6 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
149
147
|
)}
|
|
150
148
|
</main>
|
|
151
149
|
|
|
152
|
-
{/* Pagination */}
|
|
153
150
|
<PaginationComponent
|
|
154
151
|
baseUrl={type ? `/archive?type=${type}` : "/archive"}
|
|
155
152
|
hasMore={hasMore}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal Theme - Collection Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.CollectionPage.
|
|
4
|
+
* Simple list of posts in a collection.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { FC } from "hono/jsx";
|
|
9
8
|
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import type { CollectionPageProps } from "
|
|
9
|
+
import type { CollectionPageProps } from "../../../types.js";
|
|
11
10
|
|
|
12
11
|
export const CollectionPage: FC<CollectionPageProps> = ({
|
|
13
12
|
collection,
|
|
@@ -24,7 +23,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
24
23
|
)}
|
|
25
24
|
</header>
|
|
26
25
|
|
|
27
|
-
<main class="flex flex-col
|
|
26
|
+
<main class="flex flex-col">
|
|
28
27
|
{posts.length === 0 ? (
|
|
29
28
|
<p class="text-muted-foreground">
|
|
30
29
|
{t({
|
|
@@ -33,8 +32,9 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
33
32
|
})}
|
|
34
33
|
</p>
|
|
35
34
|
) : (
|
|
36
|
-
posts.map((post) => (
|
|
35
|
+
posts.map((post, i) => (
|
|
37
36
|
<article key={post.id} class="h-entry">
|
|
37
|
+
{i > 0 && <hr class="my-6 border-border" />}
|
|
38
38
|
{post.title && (
|
|
39
39
|
<h2 class="p-name text-lg font-medium mb-2">
|
|
40
40
|
<a href={post.permalink} class="u-url hover:underline">
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal Theme - Home Page
|
|
3
3
|
*
|
|
4
4
|
* Renders the timeline feed with thread previews.
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.HomePage.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { FC } from "hono/jsx";
|
|
9
8
|
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import type { HomePageProps } from "
|
|
11
|
-
import { TimelineFeed as DefaultTimelineFeed } from "../
|
|
9
|
+
import type { HomePageProps } from "../../../types.js";
|
|
10
|
+
import { TimelineFeed as DefaultTimelineFeed } from "../timeline/TimelineFeed.js";
|
|
12
11
|
|
|
13
12
|
export const HomePage: FC<HomePageProps> = ({
|
|
14
13
|
items,
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal Theme - Post Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.PostPage.
|
|
4
|
+
* Clean article layout for a single post.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { FC } from "hono/jsx";
|
|
9
8
|
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import type { PostPageProps } from "
|
|
11
|
-
import { MediaGallery as DefaultMediaGallery } from "
|
|
9
|
+
import type { PostPageProps } from "../../../types.js";
|
|
10
|
+
import { MediaGallery as DefaultMediaGallery } from "../../../theme/components/MediaGallery.js";
|
|
12
11
|
|
|
13
12
|
export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
|
|
14
13
|
const { t } = useLingui();
|
|
@@ -28,11 +27,11 @@ export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
|
|
|
28
27
|
|
|
29
28
|
{post.media.length > 0 && <Gallery attachments={post.media} />}
|
|
30
29
|
|
|
31
|
-
<footer class="mt-
|
|
30
|
+
<footer class="mt-8 pt-4 border-t border-border text-sm text-muted-foreground">
|
|
32
31
|
<time class="dt-published" datetime={post.publishedAt}>
|
|
33
32
|
{post.publishedAtFormatted}
|
|
34
33
|
</time>
|
|
35
|
-
<a href={post.permalink} class="u-url ml-4">
|
|
34
|
+
<a href={post.permalink} class="u-url ml-4 hover:underline">
|
|
36
35
|
{t({
|
|
37
36
|
message: "Permalink",
|
|
38
37
|
comment: "@context: Link to permanent URL of post",
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal Theme - Search Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.SearchPage.
|
|
4
|
+
* Minimal search form + results with page-based pagination.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { FC } from "hono/jsx";
|
|
9
8
|
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import type { SearchPageProps } from "
|
|
11
|
-
import { PagePagination as DefaultPagePagination } from "
|
|
9
|
+
import type { SearchPageProps } from "../../../types.js";
|
|
10
|
+
import { PagePagination as DefaultPagePagination } from "../../../theme/components/Pagination.js";
|
|
12
11
|
|
|
13
12
|
export const SearchPage: FC<SearchPageProps> = ({
|
|
14
13
|
query,
|
|
@@ -30,7 +29,6 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
30
29
|
<div>
|
|
31
30
|
<h1 class="text-2xl font-semibold mb-6">{searchTitle}</h1>
|
|
32
31
|
|
|
33
|
-
{/* Search form */}
|
|
34
32
|
<form method="get" action="/search" class="mb-8">
|
|
35
33
|
<div class="flex gap-2">
|
|
36
34
|
<input
|
|
@@ -53,14 +51,12 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
53
51
|
</div>
|
|
54
52
|
</form>
|
|
55
53
|
|
|
56
|
-
{/* Error */}
|
|
57
54
|
{error && (
|
|
58
55
|
<div class="alert-destructive mb-6">
|
|
59
56
|
<h2>{error}</h2>
|
|
60
57
|
</div>
|
|
61
58
|
)}
|
|
62
59
|
|
|
63
|
-
{/* Results */}
|
|
64
60
|
{query && !error && (
|
|
65
61
|
<div>
|
|
66
62
|
<p class="text-sm text-muted-foreground mb-4">
|
|
@@ -85,12 +81,9 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
85
81
|
<>
|
|
86
82
|
<div class="flex flex-col gap-4">
|
|
87
83
|
{results.map((result) => (
|
|
88
|
-
<article
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
>
|
|
92
|
-
<a href={result.post.permalink} class="block">
|
|
93
|
-
<h2 class="font-medium hover:underline">
|
|
84
|
+
<article key={result.post.id} class="py-3">
|
|
85
|
+
<a href={result.post.permalink} class="block group">
|
|
86
|
+
<h2 class="font-medium group-hover:underline">
|
|
94
87
|
{result.post.title ||
|
|
95
88
|
result.post.content?.slice(0, 60) ||
|
|
96
89
|
`Post #${result.post.id}`}
|
|
@@ -98,13 +91,14 @@ export const SearchPage: FC<SearchPageProps> = ({
|
|
|
98
91
|
|
|
99
92
|
{result.snippet && (
|
|
100
93
|
<p
|
|
101
|
-
class="text-sm text-muted-foreground mt-
|
|
94
|
+
class="text-sm text-muted-foreground mt-1 line-clamp-2"
|
|
102
95
|
dangerouslySetInnerHTML={{ __html: result.snippet }}
|
|
103
96
|
/>
|
|
104
97
|
)}
|
|
105
98
|
|
|
106
|
-
<footer class="flex items-center gap-2 mt-
|
|
107
|
-
<span
|
|
99
|
+
<footer class="flex items-center gap-2 mt-1 text-xs text-muted-foreground">
|
|
100
|
+
<span>{result.post.type}</span>
|
|
101
|
+
<span>·</span>
|
|
108
102
|
<time datetime={result.post.publishedAt}>
|
|
109
103
|
{result.post.publishedAtFormatted}
|
|
110
104
|
</time>
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal Theme - Single Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Theme authors can replace this entirely via ThemeComponents.SinglePage.
|
|
4
|
+
* Simple page content layout for type="page" posts.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { FC } from "hono/jsx";
|
|
9
|
-
import type { SinglePageProps } from "
|
|
8
|
+
import type { SinglePageProps } from "../../../types.js";
|
|
10
9
|
|
|
11
10
|
export const SinglePage: FC<SinglePageProps> = ({ page }) => {
|
|
12
11
|
return (
|
|
13
12
|
<article class="h-entry">
|
|
14
13
|
{page.title && (
|
|
15
|
-
<h1 class="p-name text-
|
|
14
|
+
<h1 class="p-name text-2xl font-semibold mb-6">{page.title}</h1>
|
|
16
15
|
)}
|
|
17
16
|
|
|
18
17
|
<div
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Theme - Article Card
|
|
3
|
+
*
|
|
4
|
+
* Title + excerpt, borderless, 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 class={`h-entry${compact ? " text-sm" : ""}`}>
|
|
13
|
+
{post.title && (
|
|
14
|
+
<h2 class={`p-name font-semibold ${compact ? "text-sm" : "text-lg"}`}>
|
|
15
|
+
<a href={post.permalink} class="u-url hover:underline">
|
|
16
|
+
{post.title}
|
|
17
|
+
</a>
|
|
18
|
+
</h2>
|
|
19
|
+
)}
|
|
20
|
+
{!compact && post.excerpt && (
|
|
21
|
+
<p class="e-content text-muted-foreground mt-1 line-clamp-3">
|
|
22
|
+
{post.excerpt}
|
|
23
|
+
</p>
|
|
24
|
+
)}
|
|
25
|
+
<footer class="mt-2">
|
|
26
|
+
<a
|
|
27
|
+
href={post.permalink}
|
|
28
|
+
class="u-url text-xs text-muted-foreground hover:text-foreground"
|
|
29
|
+
>
|
|
30
|
+
<time class="dt-published" datetime={post.publishedAt}>
|
|
31
|
+
{post.publishedAtFormatted}
|
|
32
|
+
</time>
|
|
33
|
+
</a>
|
|
34
|
+
</footer>
|
|
35
|
+
</article>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Theme - Image Card
|
|
3
|
+
*
|
|
4
|
+
* Inline images with no card frame for type="image" posts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FC } from "hono/jsx";
|
|
8
|
+
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
+
import { MediaGallery } from "../../../theme/components/MediaGallery.js";
|
|
10
|
+
|
|
11
|
+
export const ImageCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
12
|
+
if (compact) {
|
|
13
|
+
return (
|
|
14
|
+
<article class="h-entry text-sm">
|
|
15
|
+
{post.title && (
|
|
16
|
+
<h2 class="p-name font-medium text-sm">
|
|
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">
|
|
29
|
+
<a
|
|
30
|
+
href={post.permalink}
|
|
31
|
+
class="u-url text-xs text-muted-foreground hover:text-foreground"
|
|
32
|
+
>
|
|
33
|
+
<time class="dt-published" datetime={post.publishedAt}>
|
|
34
|
+
{post.publishedAtFormatted}
|
|
35
|
+
</time>
|
|
36
|
+
</a>
|
|
37
|
+
</footer>
|
|
38
|
+
</article>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<article class="h-entry">
|
|
44
|
+
{post.contentHtml && (
|
|
45
|
+
<div
|
|
46
|
+
class="e-content prose prose-sm"
|
|
47
|
+
dangerouslySetInnerHTML={{ __html: post.contentHtml }}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
{post.media.length > 0 && <MediaGallery attachments={post.media} />}
|
|
51
|
+
<footer class="mt-2">
|
|
52
|
+
<a
|
|
53
|
+
href={post.permalink}
|
|
54
|
+
class="u-url text-xs text-muted-foreground hover:text-foreground"
|
|
55
|
+
>
|
|
56
|
+
<time class="dt-published" datetime={post.publishedAt}>
|
|
57
|
+
{post.publishedAtFormatted}
|
|
58
|
+
</time>
|
|
59
|
+
</a>
|
|
60
|
+
</footer>
|
|
61
|
+
</article>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Theme - Link Card
|
|
3
|
+
*
|
|
4
|
+
* Subtle external link indicator for type="link" posts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FC } from "hono/jsx";
|
|
8
|
+
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
+
|
|
10
|
+
export const LinkCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
11
|
+
return (
|
|
12
|
+
<article class={`h-entry${compact ? " text-sm" : ""}`}>
|
|
13
|
+
{post.title && (
|
|
14
|
+
<h2 class={`p-name font-semibold ${compact ? "text-sm" : "text-base"}`}>
|
|
15
|
+
<a
|
|
16
|
+
href={post.sourceUrl || post.permalink}
|
|
17
|
+
class="u-url hover:underline"
|
|
18
|
+
target={post.sourceUrl ? "_blank" : undefined}
|
|
19
|
+
rel={post.sourceUrl ? "noopener noreferrer" : undefined}
|
|
20
|
+
>
|
|
21
|
+
{post.title}
|
|
22
|
+
</a>
|
|
23
|
+
</h2>
|
|
24
|
+
)}
|
|
25
|
+
{post.sourceDomain && (
|
|
26
|
+
<div class="text-xs text-muted-foreground mt-0.5">
|
|
27
|
+
↗ {post.sourceDomain}
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
{!compact && post.contentHtml && (
|
|
31
|
+
<div
|
|
32
|
+
class="e-content prose prose-sm text-muted-foreground mt-1"
|
|
33
|
+
dangerouslySetInnerHTML={{ __html: post.contentHtml }}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
<footer class="mt-2">
|
|
37
|
+
<a
|
|
38
|
+
href={post.permalink}
|
|
39
|
+
class="text-xs text-muted-foreground hover:text-foreground"
|
|
40
|
+
>
|
|
41
|
+
<time class="dt-published" datetime={post.publishedAt}>
|
|
42
|
+
{post.publishedAtFormatted}
|
|
43
|
+
</time>
|
|
44
|
+
</a>
|
|
45
|
+
</footer>
|
|
46
|
+
</article>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Note Card
|
|
2
|
+
* Minimal Theme - Note Card
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Borderless, content-first card for type="note" posts.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
8
8
|
import type { TimelineCardProps } from "../../../types.js";
|
|
9
|
-
import { MediaGallery } from "
|
|
9
|
+
import { MediaGallery } from "../../../theme/components/MediaGallery.js";
|
|
10
10
|
|
|
11
11
|
export const NoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
12
12
|
return (
|
|
13
|
-
<article
|
|
14
|
-
class={`h-entry timeline-card${compact ? " timeline-card-compact" : ""}`}
|
|
15
|
-
>
|
|
13
|
+
<article class={`h-entry${compact ? " text-sm" : ""}`}>
|
|
16
14
|
{post.contentHtml && (
|
|
17
15
|
<div
|
|
18
|
-
class={`e-content prose ${compact ? "prose-sm" : "
|
|
16
|
+
class={`e-content prose ${compact ? "prose-sm" : ""}`}
|
|
19
17
|
dangerouslySetInnerHTML={{ __html: post.contentHtml }}
|
|
20
18
|
/>
|
|
21
19
|
)}
|
|
22
20
|
{!compact && post.media.length > 0 && (
|
|
23
21
|
<MediaGallery attachments={post.media} />
|
|
24
22
|
)}
|
|
25
|
-
<footer class="mt-2
|
|
26
|
-
<a
|
|
23
|
+
<footer class="mt-2">
|
|
24
|
+
<a
|
|
25
|
+
href={post.permalink}
|
|
26
|
+
class="u-url text-xs text-muted-foreground hover:text-foreground"
|
|
27
|
+
>
|
|
27
28
|
<time class="dt-published" datetime={post.publishedAt}>
|
|
28
29
|
{post.publishedAtFormatted}
|
|
29
30
|
</time>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Quote Card
|
|
2
|
+
* Minimal Theme - Quote Card
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Subtle blockquote with left border for type="quote" posts.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { FC } from "hono/jsx";
|
|
@@ -9,12 +9,10 @@ import type { TimelineCardProps } from "../../../types.js";
|
|
|
9
9
|
|
|
10
10
|
export const QuoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
11
11
|
return (
|
|
12
|
-
<article
|
|
13
|
-
class={`h-entry timeline-card timeline-card-quote${compact ? " timeline-card-compact" : ""}`}
|
|
14
|
-
>
|
|
12
|
+
<article class={`h-entry${compact ? " text-sm" : ""}`}>
|
|
15
13
|
{post.contentHtml && (
|
|
16
14
|
<blockquote
|
|
17
|
-
class={`e-content italic ${compact ? "text-sm" : "
|
|
15
|
+
class={`e-content border-l-2 border-muted-foreground/30 pl-4 italic ${compact ? "text-sm" : ""} leading-relaxed`}
|
|
18
16
|
>
|
|
19
17
|
<div dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
|
|
20
18
|
</blockquote>
|
|
@@ -36,8 +34,11 @@ export const QuoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
|
36
34
|
)}
|
|
37
35
|
</div>
|
|
38
36
|
)}
|
|
39
|
-
<footer class="mt-2
|
|
40
|
-
<a
|
|
37
|
+
<footer class="mt-2">
|
|
38
|
+
<a
|
|
39
|
+
href={post.permalink}
|
|
40
|
+
class="u-url text-xs text-muted-foreground hover:text-foreground"
|
|
41
|
+
>
|
|
41
42
|
<time class="dt-published" datetime={post.publishedAt}>
|
|
42
43
|
{post.publishedAtFormatted}
|
|
43
44
|
</time>
|