@jant/core 0.3.23 → 0.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +4 -5
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -3
- package/dist/lib/constants.js +1 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/navigation.js +4 -5
- package/dist/lib/render.js +1 -1
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme-components.js +8 -11
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +119 -0
- package/dist/lib/view.js +61 -72
- package/dist/routes/api/posts.js +29 -35
- package/dist/routes/api/search.js +5 -6
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/dash/collections.js +22 -40
- package/dist/routes/dash/index.js +2 -2
- package/dist/routes/dash/navigation.js +25 -24
- package/dist/routes/dash/pages.js +42 -57
- package/dist/routes/dash/posts.js +27 -35
- package/dist/routes/feed/rss.js +2 -4
- package/dist/routes/feed/sitemap.js +10 -7
- package/dist/routes/pages/archive.js +12 -11
- package/dist/routes/pages/collection.js +11 -5
- package/dist/routes/pages/home.js +53 -61
- package/dist/routes/pages/page.js +60 -29
- package/dist/routes/pages/post.js +5 -12
- package/dist/routes/pages/search.js +3 -4
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +80 -0
- package/dist/services/post.js +68 -69
- package/dist/services/search.js +24 -18
- package/dist/theme/components/MediaGallery.js +19 -91
- package/dist/theme/components/PageForm.js +15 -15
- package/dist/theme/components/PostForm.js +136 -129
- package/dist/theme/components/PostList.js +13 -8
- package/dist/theme/components/ThreadView.js +3 -3
- package/dist/theme/components/TypeBadge.js +3 -14
- package/dist/theme/components/VisibilityBadge.js +33 -23
- package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/themes/{minimal → threads}/pages/ArchivePage.js +32 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/themes/{minimal → threads}/pages/HomePage.js +3 -3
- package/dist/themes/{minimal → threads}/pages/PostPage.js +12 -9
- package/dist/themes/{minimal → threads}/pages/SearchPage.js +13 -14
- package/dist/themes/{minimal → threads}/pages/SinglePage.js +4 -4
- package/dist/themes/threads/timeline/LinkCard.js +68 -0
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/themes/{minimal → threads}/timeline/ThreadPreview.js +17 -13
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/themes/{minimal → threads}/timeline/TimelineItem.js +8 -16
- package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
- package/dist/themes/threads/timeline/groupByDate.js +22 -0
- package/dist/themes/threads/timeline/timelineMore.js +107 -0
- package/dist/types.js +24 -40
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +51 -74
- package/src/app.tsx +4 -6
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +216 -164
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +216 -164
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +216 -164
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +28 -12
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +166 -105
- package/src/lib/__tests__/theme-components.test.ts +4 -25
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
- package/src/lib/__tests__/view.test.ts +199 -51
- package/src/lib/constants.ts +1 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/navigation.ts +6 -7
- package/src/lib/render.tsx +1 -1
- package/src/lib/schemas.ts +118 -52
- package/src/lib/theme-components.ts +10 -13
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +170 -0
- package/src/lib/view.ts +80 -82
- package/src/preset.css +45 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/posts.ts +30 -30
- package/src/routes/api/search.ts +4 -4
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/dash/collections.tsx +18 -40
- package/src/routes/dash/index.tsx +2 -2
- package/src/routes/dash/navigation.tsx +27 -26
- package/src/routes/dash/pages.tsx +45 -60
- package/src/routes/dash/posts.tsx +44 -52
- package/src/routes/feed/rss.ts +2 -1
- package/src/routes/feed/sitemap.ts +14 -4
- package/src/routes/pages/archive.tsx +14 -10
- package/src/routes/pages/collection.tsx +17 -6
- package/src/routes/pages/home.tsx +56 -81
- package/src/routes/pages/page.tsx +64 -27
- package/src/routes/pages/post.tsx +5 -14
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +342 -206
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +124 -0
- package/src/services/post.ts +93 -103
- package/src/services/search.ts +38 -27
- package/src/theme/components/MediaGallery.tsx +27 -96
- package/src/theme/components/PageForm.tsx +21 -21
- package/src/theme/components/PostForm.tsx +122 -118
- package/src/theme/components/PostList.tsx +58 -49
- package/src/theme/components/ThreadView.tsx +6 -3
- package/src/theme/components/TypeBadge.tsx +9 -17
- package/src/theme/components/VisibilityBadge.tsx +40 -23
- package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/{minimal → threads}/index.ts +30 -13
- package/src/themes/{minimal → threads}/pages/ArchivePage.tsx +53 -53
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/themes/{minimal → threads}/pages/HomePage.tsx +3 -3
- package/src/themes/{minimal → threads}/pages/PostPage.tsx +12 -8
- package/src/themes/{minimal → threads}/pages/SearchPage.tsx +15 -13
- package/src/themes/{minimal → threads}/pages/SinglePage.tsx +4 -4
- package/src/themes/threads/style.css +336 -0
- package/src/themes/threads/timeline/LinkCard.tsx +67 -0
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/themes/{minimal → threads}/timeline/ThreadPreview.tsx +15 -13
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/themes/{minimal → threads}/timeline/TimelineItem.tsx +9 -17
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
- package/src/themes/threads/timeline/groupByDate.ts +30 -0
- package/src/themes/threads/timeline/timelineMore.tsx +130 -0
- package/src/types.ts +242 -98
- package/dist/routes/api/timeline.js +0 -120
- package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/QuoteCard.js +0 -48
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/src/routes/api/timeline.tsx +0 -159
- package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Site Layout
|
|
3
|
-
*
|
|
4
|
-
* Single-column, centered layout with horizontal nav.
|
|
5
|
-
* Inspired by Tufte CSS and Manton.org.
|
|
6
|
-
*/ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
7
|
-
function NavLinks({ links }) {
|
|
8
|
-
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
9
|
-
children: links.map((link)=>/*#__PURE__*/ _jsxs("a", {
|
|
10
|
-
href: link.url,
|
|
11
|
-
class: `text-sm ${link.isActive ? "text-foreground font-medium" : "text-muted-foreground hover:text-foreground"}`,
|
|
12
|
-
...link.isExternal ? {
|
|
13
|
-
target: "_blank",
|
|
14
|
-
rel: "noopener noreferrer"
|
|
15
|
-
} : {},
|
|
16
|
-
children: [
|
|
17
|
-
link.label,
|
|
18
|
-
link.isExternal && /*#__PURE__*/ _jsx("span", {
|
|
19
|
-
class: "ml-0.5 text-xs opacity-50",
|
|
20
|
-
children: "↗"
|
|
21
|
-
})
|
|
22
|
-
]
|
|
23
|
-
}, link.id))
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
export const SiteLayout = ({ siteName, links, children })=>{
|
|
27
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
28
|
-
class: "max-w-2xl mx-auto px-4 py-8",
|
|
29
|
-
"data-signals": JSON.stringify({
|
|
30
|
-
_menuOpen: false
|
|
31
|
-
}),
|
|
32
|
-
children: [
|
|
33
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
34
|
-
class: "mb-12",
|
|
35
|
-
children: [
|
|
36
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
37
|
-
class: "flex items-center justify-between",
|
|
38
|
-
children: [
|
|
39
|
-
/*#__PURE__*/ _jsx("a", {
|
|
40
|
-
href: "/",
|
|
41
|
-
class: "text-xl font-semibold",
|
|
42
|
-
children: siteName
|
|
43
|
-
}),
|
|
44
|
-
links.length > 0 && /*#__PURE__*/ _jsx("button", {
|
|
45
|
-
"data-on:click": "$_menuOpen = !$_menuOpen",
|
|
46
|
-
class: "p-2 -mr-2 text-muted-foreground hover:text-foreground sm:hidden",
|
|
47
|
-
"aria-label": "Toggle menu",
|
|
48
|
-
children: /*#__PURE__*/ _jsx("svg", {
|
|
49
|
-
class: "size-5",
|
|
50
|
-
fill: "none",
|
|
51
|
-
viewBox: "0 0 24 24",
|
|
52
|
-
"stroke-width": "1.5",
|
|
53
|
-
stroke: "currentColor",
|
|
54
|
-
children: /*#__PURE__*/ _jsx("path", {
|
|
55
|
-
"stroke-linecap": "round",
|
|
56
|
-
"stroke-linejoin": "round",
|
|
57
|
-
d: "M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
]
|
|
62
|
-
}),
|
|
63
|
-
links.length > 0 && /*#__PURE__*/ _jsx("nav", {
|
|
64
|
-
class: "hidden sm:flex flex-wrap gap-x-4 gap-y-1 mt-3",
|
|
65
|
-
children: /*#__PURE__*/ _jsx(NavLinks, {
|
|
66
|
-
links: links
|
|
67
|
-
})
|
|
68
|
-
}),
|
|
69
|
-
links.length > 0 && /*#__PURE__*/ _jsx("nav", {
|
|
70
|
-
class: "sm:hidden flex flex-col gap-1 mt-3 overflow-hidden",
|
|
71
|
-
"data-show": "$_menuOpen",
|
|
72
|
-
children: /*#__PURE__*/ _jsx(NavLinks, {
|
|
73
|
-
links: links
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
]
|
|
77
|
-
}),
|
|
78
|
-
/*#__PURE__*/ _jsx("main", {
|
|
79
|
-
children: children
|
|
80
|
-
})
|
|
81
|
-
]
|
|
82
|
-
});
|
|
83
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
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
|
-
*/ // Layout
|
|
9
|
-
import { SiteLayout } from "./MinimalSiteLayout.js";
|
|
10
|
-
// Pages
|
|
11
|
-
import { HomePage } from "./pages/HomePage.js";
|
|
12
|
-
import { PostPage } from "./pages/PostPage.js";
|
|
13
|
-
import { SinglePage } from "./pages/SinglePage.js";
|
|
14
|
-
import { ArchivePage } from "./pages/ArchivePage.js";
|
|
15
|
-
import { SearchPage } from "./pages/SearchPage.js";
|
|
16
|
-
import { CollectionPage } from "./pages/CollectionPage.js";
|
|
17
|
-
// Timeline
|
|
18
|
-
import { NoteCard } from "./timeline/NoteCard.js";
|
|
19
|
-
import { ArticleCard } from "./timeline/ArticleCard.js";
|
|
20
|
-
import { LinkCard } from "./timeline/LinkCard.js";
|
|
21
|
-
import { QuoteCard } from "./timeline/QuoteCard.js";
|
|
22
|
-
import { ImageCard } from "./timeline/ImageCard.js";
|
|
23
|
-
import { ThreadPreview } from "./timeline/ThreadPreview.js";
|
|
24
|
-
import { TimelineFeed } from "./timeline/TimelineFeed.js";
|
|
25
|
-
/**
|
|
26
|
-
* Create the minimal theme configuration.
|
|
27
|
-
*
|
|
28
|
-
* @param options - Optional overrides for components, CSS variables, or color themes
|
|
29
|
-
* @returns A JantTheme configuration object
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```typescript
|
|
33
|
-
* import { createApp } from "@jant/core";
|
|
34
|
-
* import { minimalTheme } from "@jant/core";
|
|
35
|
-
*
|
|
36
|
-
* export default createApp({
|
|
37
|
-
* theme: minimalTheme(), // re-exported as minimalTheme from @jant/core
|
|
38
|
-
* });
|
|
39
|
-
* ```
|
|
40
|
-
*/ export function theme(options) {
|
|
41
|
-
return {
|
|
42
|
-
name: "minimal",
|
|
43
|
-
components: {
|
|
44
|
-
SiteLayout,
|
|
45
|
-
HomePage,
|
|
46
|
-
PostPage,
|
|
47
|
-
SinglePage,
|
|
48
|
-
ArchivePage,
|
|
49
|
-
SearchPage,
|
|
50
|
-
CollectionPage,
|
|
51
|
-
NoteCard,
|
|
52
|
-
ArticleCard,
|
|
53
|
-
LinkCard,
|
|
54
|
-
QuoteCard,
|
|
55
|
-
ImageCard,
|
|
56
|
-
ThreadPreview,
|
|
57
|
-
TimelineFeed,
|
|
58
|
-
...options?.components
|
|
59
|
-
},
|
|
60
|
-
cssVariables: {
|
|
61
|
-
...options?.cssVariables
|
|
62
|
-
},
|
|
63
|
-
colorThemes: options?.colorThemes
|
|
64
|
-
};
|
|
65
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Collection Page
|
|
3
|
-
*
|
|
4
|
-
* Simple list of posts in a collection.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
export const CollectionPage = ({ collection, posts })=>{
|
|
8
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
9
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
10
|
-
children: [
|
|
11
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
12
|
-
class: "mb-8",
|
|
13
|
-
children: [
|
|
14
|
-
/*#__PURE__*/ _jsx("h1", {
|
|
15
|
-
class: "text-2xl font-semibold",
|
|
16
|
-
children: collection.title
|
|
17
|
-
}),
|
|
18
|
-
collection.description && /*#__PURE__*/ _jsx("p", {
|
|
19
|
-
class: "text-muted-foreground mt-2",
|
|
20
|
-
children: collection.description
|
|
21
|
-
})
|
|
22
|
-
]
|
|
23
|
-
}),
|
|
24
|
-
/*#__PURE__*/ _jsx("main", {
|
|
25
|
-
class: "flex flex-col",
|
|
26
|
-
children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
27
|
-
class: "text-muted-foreground",
|
|
28
|
-
children: $__i18n._({
|
|
29
|
-
id: "J4FNfC",
|
|
30
|
-
message: "No posts in this collection."
|
|
31
|
-
})
|
|
32
|
-
}) : posts.map((post, i)=>/*#__PURE__*/ _jsxs("article", {
|
|
33
|
-
class: "h-entry",
|
|
34
|
-
children: [
|
|
35
|
-
i > 0 && /*#__PURE__*/ _jsx("hr", {
|
|
36
|
-
class: "my-6 border-border"
|
|
37
|
-
}),
|
|
38
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
39
|
-
class: "p-name text-lg font-medium mb-2",
|
|
40
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
41
|
-
href: post.permalink,
|
|
42
|
-
class: "u-url hover:underline",
|
|
43
|
-
children: post.title
|
|
44
|
-
})
|
|
45
|
-
}),
|
|
46
|
-
/*#__PURE__*/ _jsx("div", {
|
|
47
|
-
class: "e-content prose prose-sm",
|
|
48
|
-
dangerouslySetInnerHTML: {
|
|
49
|
-
__html: post.contentHtml || ""
|
|
50
|
-
}
|
|
51
|
-
}),
|
|
52
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
53
|
-
class: "mt-2 text-sm text-muted-foreground",
|
|
54
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
55
|
-
class: "dt-published",
|
|
56
|
-
datetime: post.publishedAt,
|
|
57
|
-
children: post.publishedAtFormatted
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
]
|
|
61
|
-
}, post.id))
|
|
62
|
-
})
|
|
63
|
-
]
|
|
64
|
-
});
|
|
65
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Article Card
|
|
3
|
-
*
|
|
4
|
-
* Title + excerpt, borderless, for type="article" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
export const ArticleCard = ({ post, compact })=>{
|
|
7
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
8
|
-
class: `h-entry${compact ? " text-sm" : ""}`,
|
|
9
|
-
children: [
|
|
10
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
11
|
-
class: `p-name font-semibold ${compact ? "text-sm" : "text-lg"}`,
|
|
12
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
13
|
-
href: post.permalink,
|
|
14
|
-
class: "u-url hover:underline",
|
|
15
|
-
children: post.title
|
|
16
|
-
})
|
|
17
|
-
}),
|
|
18
|
-
!compact && post.excerpt && /*#__PURE__*/ _jsx("p", {
|
|
19
|
-
class: "e-content text-muted-foreground mt-1 line-clamp-3",
|
|
20
|
-
children: post.excerpt
|
|
21
|
-
}),
|
|
22
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
23
|
-
class: "mt-2",
|
|
24
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
25
|
-
href: post.permalink,
|
|
26
|
-
class: "u-url text-xs text-muted-foreground hover:text-foreground",
|
|
27
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
28
|
-
class: "dt-published",
|
|
29
|
-
datetime: post.publishedAt,
|
|
30
|
-
children: post.publishedAtFormatted
|
|
31
|
-
})
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
]
|
|
35
|
-
});
|
|
36
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Image Card
|
|
3
|
-
*
|
|
4
|
-
* Inline images with no card frame for type="image" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { MediaGallery } from "../../../theme/components/MediaGallery.js";
|
|
7
|
-
export const ImageCard = ({ post, compact })=>{
|
|
8
|
-
if (compact) {
|
|
9
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
10
|
-
class: "h-entry text-sm",
|
|
11
|
-
children: [
|
|
12
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
13
|
-
class: "p-name font-medium text-sm",
|
|
14
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
15
|
-
href: post.permalink,
|
|
16
|
-
class: "u-url hover:underline",
|
|
17
|
-
children: post.title
|
|
18
|
-
})
|
|
19
|
-
}),
|
|
20
|
-
post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
21
|
-
class: "e-content prose prose-sm text-muted-foreground",
|
|
22
|
-
dangerouslySetInnerHTML: {
|
|
23
|
-
__html: post.contentHtml
|
|
24
|
-
}
|
|
25
|
-
}),
|
|
26
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
27
|
-
class: "mt-1",
|
|
28
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
29
|
-
href: post.permalink,
|
|
30
|
-
class: "u-url text-xs text-muted-foreground hover:text-foreground",
|
|
31
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
32
|
-
class: "dt-published",
|
|
33
|
-
datetime: post.publishedAt,
|
|
34
|
-
children: post.publishedAtFormatted
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
]
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
42
|
-
class: "h-entry",
|
|
43
|
-
children: [
|
|
44
|
-
post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
45
|
-
class: "e-content prose prose-sm",
|
|
46
|
-
dangerouslySetInnerHTML: {
|
|
47
|
-
__html: post.contentHtml
|
|
48
|
-
}
|
|
49
|
-
}),
|
|
50
|
-
post.media.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
51
|
-
attachments: post.media
|
|
52
|
-
}),
|
|
53
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
54
|
-
class: "mt-2",
|
|
55
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
56
|
-
href: post.permalink,
|
|
57
|
-
class: "u-url text-xs text-muted-foreground hover:text-foreground",
|
|
58
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
59
|
-
class: "dt-published",
|
|
60
|
-
datetime: post.publishedAt,
|
|
61
|
-
children: post.publishedAtFormatted
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
]
|
|
66
|
-
});
|
|
67
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Link Card
|
|
3
|
-
*
|
|
4
|
-
* Subtle external link indicator for type="link" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
export const LinkCard = ({ post, compact })=>{
|
|
7
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
8
|
-
class: `h-entry${compact ? " text-sm" : ""}`,
|
|
9
|
-
children: [
|
|
10
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
11
|
-
class: `p-name font-semibold ${compact ? "text-sm" : "text-base"}`,
|
|
12
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
13
|
-
href: post.sourceUrl || post.permalink,
|
|
14
|
-
class: "u-url hover:underline",
|
|
15
|
-
target: post.sourceUrl ? "_blank" : undefined,
|
|
16
|
-
rel: post.sourceUrl ? "noopener noreferrer" : undefined,
|
|
17
|
-
children: post.title
|
|
18
|
-
})
|
|
19
|
-
}),
|
|
20
|
-
post.sourceDomain && /*#__PURE__*/ _jsxs("div", {
|
|
21
|
-
class: "text-xs text-muted-foreground mt-0.5",
|
|
22
|
-
children: [
|
|
23
|
-
"↗ ",
|
|
24
|
-
post.sourceDomain
|
|
25
|
-
]
|
|
26
|
-
}),
|
|
27
|
-
!compact && post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
28
|
-
class: "e-content prose prose-sm text-muted-foreground mt-1",
|
|
29
|
-
dangerouslySetInnerHTML: {
|
|
30
|
-
__html: post.contentHtml
|
|
31
|
-
}
|
|
32
|
-
}),
|
|
33
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
34
|
-
class: "mt-2",
|
|
35
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
36
|
-
href: post.permalink,
|
|
37
|
-
class: "text-xs text-muted-foreground hover:text-foreground",
|
|
38
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
39
|
-
class: "dt-published",
|
|
40
|
-
datetime: post.publishedAt,
|
|
41
|
-
children: post.publishedAtFormatted
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
]
|
|
46
|
-
});
|
|
47
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Note Card
|
|
3
|
-
*
|
|
4
|
-
* Borderless, content-first card for type="note" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { MediaGallery } from "../../../theme/components/MediaGallery.js";
|
|
7
|
-
export const NoteCard = ({ post, compact })=>{
|
|
8
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
9
|
-
class: `h-entry${compact ? " text-sm" : ""}`,
|
|
10
|
-
children: [
|
|
11
|
-
post.contentHtml && /*#__PURE__*/ _jsx("div", {
|
|
12
|
-
class: `e-content prose ${compact ? "prose-sm" : ""}`,
|
|
13
|
-
dangerouslySetInnerHTML: {
|
|
14
|
-
__html: post.contentHtml
|
|
15
|
-
}
|
|
16
|
-
}),
|
|
17
|
-
!compact && post.media.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
18
|
-
attachments: post.media
|
|
19
|
-
}),
|
|
20
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
21
|
-
class: "mt-2",
|
|
22
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
23
|
-
href: post.permalink,
|
|
24
|
-
class: "u-url text-xs text-muted-foreground hover:text-foreground",
|
|
25
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
26
|
-
class: "dt-published",
|
|
27
|
-
datetime: post.publishedAt,
|
|
28
|
-
children: post.publishedAtFormatted
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
]
|
|
33
|
-
});
|
|
34
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Quote Card
|
|
3
|
-
*
|
|
4
|
-
* Subtle blockquote with left border for type="quote" posts.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
export const QuoteCard = ({ post, compact })=>{
|
|
7
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
8
|
-
class: `h-entry${compact ? " text-sm" : ""}`,
|
|
9
|
-
children: [
|
|
10
|
-
post.contentHtml && /*#__PURE__*/ _jsx("blockquote", {
|
|
11
|
-
class: `e-content border-l-2 border-muted-foreground/30 pl-4 italic ${compact ? "text-sm" : ""} leading-relaxed`,
|
|
12
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
13
|
-
dangerouslySetInnerHTML: {
|
|
14
|
-
__html: post.contentHtml
|
|
15
|
-
}
|
|
16
|
-
})
|
|
17
|
-
}),
|
|
18
|
-
!compact && (post.sourceName || post.sourceUrl) && /*#__PURE__*/ _jsxs("div", {
|
|
19
|
-
class: "mt-2 text-sm text-muted-foreground",
|
|
20
|
-
children: [
|
|
21
|
-
"—",
|
|
22
|
-
" ",
|
|
23
|
-
post.sourceUrl ? /*#__PURE__*/ _jsx("a", {
|
|
24
|
-
href: post.sourceUrl,
|
|
25
|
-
class: "hover:underline",
|
|
26
|
-
target: "_blank",
|
|
27
|
-
rel: "noopener noreferrer",
|
|
28
|
-
children: post.sourceName || post.sourceDomain || "Source"
|
|
29
|
-
}) : /*#__PURE__*/ _jsx("span", {
|
|
30
|
-
children: post.sourceName
|
|
31
|
-
})
|
|
32
|
-
]
|
|
33
|
-
}),
|
|
34
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
35
|
-
class: "mt-2",
|
|
36
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
37
|
-
href: post.permalink,
|
|
38
|
-
class: "u-url text-xs text-muted-foreground hover:text-foreground",
|
|
39
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
40
|
-
class: "dt-published",
|
|
41
|
-
datetime: post.publishedAt,
|
|
42
|
-
children: post.publishedAtFormatted
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
]
|
|
47
|
-
});
|
|
48
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Theme - Timeline Feed
|
|
3
|
-
*
|
|
4
|
-
* Divider-separated stream of posts with load-more button.
|
|
5
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { TimelineItem } from "./TimelineItem.js";
|
|
8
|
-
import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
|
|
9
|
-
export const TimelineFeed = ({ items, hasMore, nextCursor, theme })=>{
|
|
10
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
12
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
13
|
-
children: [
|
|
14
|
-
/*#__PURE__*/ _jsx("div", {
|
|
15
|
-
id: "timeline-feed",
|
|
16
|
-
class: "flex flex-col",
|
|
17
|
-
children: items.map((item, i)=>/*#__PURE__*/ _jsxs("div", {
|
|
18
|
-
children: [
|
|
19
|
-
i > 0 && /*#__PURE__*/ _jsx("hr", {
|
|
20
|
-
class: "my-6 border-border"
|
|
21
|
-
}),
|
|
22
|
-
item.threadPreview ? /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
|
|
23
|
-
rootPost: item.post,
|
|
24
|
-
previewReplies: item.threadPreview.replies,
|
|
25
|
-
totalReplyCount: item.threadPreview.totalReplyCount,
|
|
26
|
-
theme: theme
|
|
27
|
-
}) : /*#__PURE__*/ _jsx(TimelineItem, {
|
|
28
|
-
item: item,
|
|
29
|
-
theme: theme
|
|
30
|
-
})
|
|
31
|
-
]
|
|
32
|
-
}, item.post.id))
|
|
33
|
-
}),
|
|
34
|
-
hasMore && nextCursor && /*#__PURE__*/ _jsx("div", {
|
|
35
|
-
id: "load-more-container",
|
|
36
|
-
class: "mt-8 text-center",
|
|
37
|
-
children: /*#__PURE__*/ _jsx("button", {
|
|
38
|
-
class: "text-sm text-muted-foreground hover:text-foreground hover:underline",
|
|
39
|
-
"data-on:click": `@get('/api/timeline?cursor=${nextCursor}')`,
|
|
40
|
-
children: $__i18n._({
|
|
41
|
-
id: "yQ2kGp",
|
|
42
|
-
message: "Load more"
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
]
|
|
47
|
-
});
|
|
48
|
-
};
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeline API Routes
|
|
3
|
-
*
|
|
4
|
-
* Provides load-more functionality for the timeline feed via SSE.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Hono } from "hono";
|
|
8
|
-
import type { Bindings, TimelineItemView } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
10
|
-
import { sse } from "../../lib/sse.js";
|
|
11
|
-
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
12
|
-
import { TimelineItem } from "../../themes/minimal/timeline/TimelineItem.js";
|
|
13
|
-
import { ThreadPreview as DefaultThreadPreview } from "../../themes/minimal/timeline/ThreadPreview.js";
|
|
14
|
-
import { createMediaContext, toPostView, toPostViews } from "../../lib/view.js";
|
|
15
|
-
|
|
16
|
-
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
17
|
-
|
|
18
|
-
const PAGE_SIZE = 20;
|
|
19
|
-
|
|
20
|
-
export const timelineApiRoutes = new Hono<Env>();
|
|
21
|
-
|
|
22
|
-
timelineApiRoutes.get("/", async (c) => {
|
|
23
|
-
const cursorParam = c.req.query("cursor");
|
|
24
|
-
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
25
|
-
|
|
26
|
-
if (!cursor || isNaN(cursor)) {
|
|
27
|
-
return c.json({ error: "cursor parameter required" }, 400);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Fetch one extra to determine if there are more
|
|
31
|
-
const posts = await c.var.services.posts.list({
|
|
32
|
-
visibility: ["featured", "quiet"],
|
|
33
|
-
excludeReplies: true,
|
|
34
|
-
excludeTypes: ["page"],
|
|
35
|
-
limit: PAGE_SIZE + 1,
|
|
36
|
-
cursor,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const hasMore = posts.length > PAGE_SIZE;
|
|
40
|
-
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
41
|
-
|
|
42
|
-
if (displayPosts.length === 0) {
|
|
43
|
-
return sse(c, async (stream) => {
|
|
44
|
-
stream.remove("#load-more-container");
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Build media map
|
|
49
|
-
const postIds = displayPosts.map((p) => p.id);
|
|
50
|
-
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
51
|
-
const mediaCtx = createMediaContext(c);
|
|
52
|
-
const mediaMap = buildMediaMap(
|
|
53
|
-
rawMediaMap,
|
|
54
|
-
mediaCtx.r2PublicUrl,
|
|
55
|
-
mediaCtx.imageTransformUrl,
|
|
56
|
-
mediaCtx.s3PublicUrl,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// Get reply counts to identify thread roots
|
|
60
|
-
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
61
|
-
const threadRootIds = postIds.filter((id) => (replyCounts.get(id) ?? 0) > 0);
|
|
62
|
-
|
|
63
|
-
// Get thread previews
|
|
64
|
-
const threadPreviews = await c.var.services.posts.getThreadPreviews(
|
|
65
|
-
threadRootIds,
|
|
66
|
-
3,
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
// Load media for preview replies
|
|
70
|
-
const previewReplyIds: number[] = [];
|
|
71
|
-
for (const replies of threadPreviews.values()) {
|
|
72
|
-
for (const reply of replies) {
|
|
73
|
-
previewReplyIds.push(reply.id);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
const previewMediaMap =
|
|
77
|
-
previewReplyIds.length > 0
|
|
78
|
-
? buildMediaMap(
|
|
79
|
-
await c.var.services.media.getByPostIds(previewReplyIds),
|
|
80
|
-
mediaCtx.r2PublicUrl,
|
|
81
|
-
mediaCtx.imageTransformUrl,
|
|
82
|
-
mediaCtx.s3PublicUrl,
|
|
83
|
-
)
|
|
84
|
-
: new Map();
|
|
85
|
-
|
|
86
|
-
// Assemble timeline items with View Models
|
|
87
|
-
const items: TimelineItemView[] = displayPosts.map((post) => {
|
|
88
|
-
const postView = toPostView(
|
|
89
|
-
{ ...post, mediaAttachments: mediaMap.get(post.id) ?? [] },
|
|
90
|
-
mediaCtx,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
94
|
-
const previewReplies = threadPreviews.get(post.id);
|
|
95
|
-
|
|
96
|
-
if (replyCount > 0 && previewReplies) {
|
|
97
|
-
return {
|
|
98
|
-
post: postView,
|
|
99
|
-
threadPreview: {
|
|
100
|
-
replies: toPostViews(
|
|
101
|
-
previewReplies.map((r) => ({
|
|
102
|
-
...r,
|
|
103
|
-
mediaAttachments: previewMediaMap.get(r.id) ?? [],
|
|
104
|
-
})),
|
|
105
|
-
mediaCtx,
|
|
106
|
-
),
|
|
107
|
-
totalReplyCount: replyCount,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { post: postView };
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Resolve theme components for card rendering
|
|
116
|
-
const theme = c.var.config.theme?.components;
|
|
117
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
118
|
-
|
|
119
|
-
// Render items to HTML
|
|
120
|
-
const itemsHtml = items
|
|
121
|
-
.map((item) => {
|
|
122
|
-
if (item.threadPreview) {
|
|
123
|
-
return (
|
|
124
|
-
<ResolvedThreadPreview
|
|
125
|
-
rootPost={item.post}
|
|
126
|
-
previewReplies={item.threadPreview.replies}
|
|
127
|
-
totalReplyCount={item.threadPreview.totalReplyCount}
|
|
128
|
-
theme={theme}
|
|
129
|
-
/>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
return <TimelineItem item={item} theme={theme} />;
|
|
133
|
-
})
|
|
134
|
-
.map((jsx) => jsx.toString())
|
|
135
|
-
.join("");
|
|
136
|
-
|
|
137
|
-
// Determine next cursor
|
|
138
|
-
const lastPost = displayPosts[displayPosts.length - 1];
|
|
139
|
-
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
140
|
-
|
|
141
|
-
// Build load-more button HTML
|
|
142
|
-
const loadMoreHtml = nextCursor
|
|
143
|
-
? `<div id="load-more-container" class="mt-8 text-center"><button class="text-sm text-muted-foreground hover:text-foreground hover:underline" data-on:click="@get('/api/timeline?cursor=${nextCursor}')">Load more</button></div>`
|
|
144
|
-
: "";
|
|
145
|
-
|
|
146
|
-
return sse(c, async (stream) => {
|
|
147
|
-
// Append new items to the feed
|
|
148
|
-
stream.patchElements(itemsHtml, {
|
|
149
|
-
mode: "append",
|
|
150
|
-
selector: "#timeline-feed",
|
|
151
|
-
});
|
|
152
|
-
// Replace or remove the load-more container
|
|
153
|
-
if (loadMoreHtml) {
|
|
154
|
-
stream.patchElements(loadMoreHtml);
|
|
155
|
-
} else {
|
|
156
|
-
stream.remove("#load-more-container");
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
});
|