@jant/core 0.3.24 → 0.3.25
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 +50 -25
- 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/constants.js +1 -0
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +26 -1
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +7 -11
- package/dist/lib/schemas.js +3 -3
- package/dist/lib/theme.js +4 -4
- package/dist/lib/timeline.js +24 -48
- package/dist/lib/view.js +2 -2
- 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 +2 -2
- package/dist/routes/api/search.js +2 -2
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +2 -2
- package/dist/routes/dash/index.js +1 -1
- package/dist/routes/dash/media.js +2 -2
- package/dist/routes/dash/pages.js +411 -62
- package/dist/routes/dash/posts.js +3 -5
- package/dist/routes/dash/redirects.js +2 -2
- package/dist/routes/dash/settings.js +79 -5
- package/dist/routes/feed/rss.js +2 -2
- package/dist/routes/feed/sitemap.js +1 -1
- 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 +32 -0
- package/dist/routes/pages/home.js +9 -50
- 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 +40 -6
- package/dist/services/search.js +1 -1
- package/dist/ui/compose/ComposeDialog.js +452 -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}/PostForm.js +0 -27
- package/dist/{theme/components → ui/dash}/PostList.js +6 -6
- package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- 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/{theme → ui}/layouts/BaseLayout.js +10 -0
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +141 -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 +57 -27
- 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 +332 -181
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +332 -181
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +332 -181
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +7 -36
- package/src/lib/__tests__/schemas.test.ts +60 -19
- package/src/lib/__tests__/timeline.test.ts +45 -81
- package/src/lib/__tests__/view.test.ts +13 -7
- package/src/lib/constants.ts +1 -0
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +40 -2
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +7 -14
- package/src/lib/schemas.ts +8 -6
- package/src/lib/theme.ts +5 -5
- package/src/lib/timeline.ts +28 -57
- package/src/lib/view.ts +2 -2
- 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 +2 -2
- package/src/routes/api/search.ts +2 -2
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/collections.tsx +2 -2
- package/src/routes/dash/index.tsx +1 -1
- package/src/routes/dash/media.tsx +2 -2
- package/src/routes/dash/pages.tsx +443 -70
- package/src/routes/dash/posts.tsx +3 -7
- package/src/routes/dash/redirects.tsx +2 -2
- package/src/routes/dash/settings.tsx +83 -5
- package/src/routes/feed/rss.ts +2 -2
- package/src/routes/feed/sitemap.ts +1 -1
- 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 +38 -0
- package/src/routes/pages/home.tsx +9 -55
- 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 +57 -7
- package/src/services/search.ts +2 -2
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +491 -0
- package/src/types.ts +29 -159
- package/src/ui/compose/ComposeDialog.tsx +395 -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}/PostForm.tsx +0 -25
- 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/index.ts +10 -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/{theme → ui}/layouts/BaseLayout.tsx +11 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +150 -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 → ui}/color-themes.js +0 -0
- /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/dash}/PageForm.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme → ui}/color-themes.ts +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/dash}/PageForm.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Featured Page
|
|
3
|
+
*
|
|
4
|
+
* Shows featured posts as a timeline feed.
|
|
5
|
+
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
6
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
+
import { TimelineFeed } from "../feed/TimelineFeed.js";
|
|
8
|
+
export const FeaturedPage = ({ items })=>{
|
|
9
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
11
|
+
"data-page": "featured",
|
|
12
|
+
children: /*#__PURE__*/ _jsx("main", {
|
|
13
|
+
children: items.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
14
|
+
class: "text-muted-foreground",
|
|
15
|
+
children: $__i18n._({
|
|
16
|
+
id: "0yIy82",
|
|
17
|
+
message: "No featured posts yet."
|
|
18
|
+
})
|
|
19
|
+
}) : /*#__PURE__*/ _jsx(TimelineFeed, {
|
|
20
|
+
items: items
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home Page
|
|
3
|
+
*
|
|
4
|
+
* Timeline feed with per-type card components and thread previews.
|
|
5
|
+
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
6
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
+
import { TimelineFeed } from "../feed/TimelineFeed.js";
|
|
8
|
+
export const HomePage = ({ items, currentPage, totalPages })=>{
|
|
9
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
11
|
+
"data-page": "home",
|
|
12
|
+
children: items.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
13
|
+
class: "py-12 text-center text-muted-foreground",
|
|
14
|
+
children: $__i18n._({
|
|
15
|
+
id: "ODiSoW",
|
|
16
|
+
message: "No posts yet."
|
|
17
|
+
})
|
|
18
|
+
}) : /*#__PURE__*/ _jsx(TimelineFeed, {
|
|
19
|
+
items: items,
|
|
20
|
+
currentPage: currentPage,
|
|
21
|
+
totalPages: totalPages
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Single Post Page
|
|
3
3
|
*
|
|
4
4
|
* Single post view — clean, no card border, with divider footer.
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { MediaGallery
|
|
8
|
-
export const PostPage = ({ post
|
|
7
|
+
import { MediaGallery } from "../shared/MediaGallery.js";
|
|
8
|
+
export const PostPage = ({ post })=>{
|
|
9
9
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
|
-
const Gallery = theme?.MediaGallery ?? DefaultMediaGallery;
|
|
11
10
|
return /*#__PURE__*/ _jsxs("article", {
|
|
12
11
|
class: "h-entry py-6",
|
|
12
|
+
"data-page": "post",
|
|
13
|
+
"data-post": true,
|
|
14
|
+
"data-format": post.format,
|
|
13
15
|
children: [
|
|
14
16
|
post.title && /*#__PURE__*/ _jsx("h1", {
|
|
15
17
|
class: "p-name text-2xl font-semibold mb-4",
|
|
16
18
|
children: post.title
|
|
17
19
|
}),
|
|
18
|
-
/*#__PURE__*/ _jsx("div", {
|
|
20
|
+
post.bodyHtml && /*#__PURE__*/ _jsx("div", {
|
|
19
21
|
class: "e-content prose",
|
|
22
|
+
"data-post-body": true,
|
|
20
23
|
dangerouslySetInnerHTML: {
|
|
21
|
-
__html: post.bodyHtml
|
|
24
|
+
__html: post.bodyHtml
|
|
22
25
|
}
|
|
23
26
|
}),
|
|
24
27
|
post.media.length > 0 && /*#__PURE__*/ _jsx("div", {
|
|
25
|
-
class: "
|
|
26
|
-
|
|
28
|
+
class: "mt-4",
|
|
29
|
+
"data-post-media": true,
|
|
30
|
+
children: /*#__PURE__*/ _jsx(MediaGallery, {
|
|
27
31
|
attachments: post.media
|
|
28
32
|
})
|
|
29
33
|
}),
|
|
30
34
|
/*#__PURE__*/ _jsxs("footer", {
|
|
31
35
|
class: "mt-6 pt-4 border-t text-sm text-muted-foreground",
|
|
36
|
+
"data-post-meta": true,
|
|
32
37
|
children: [
|
|
33
38
|
/*#__PURE__*/ _jsx("time", {
|
|
34
39
|
class: "dt-published",
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Search Page
|
|
3
3
|
*
|
|
4
4
|
* Search form and results — divider-separated instead of bordered cards.
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { PagePagination
|
|
8
|
-
export const SearchPage = ({ query, results, error, hasMore, page
|
|
7
|
+
import { PagePagination } from "../shared/Pagination.js";
|
|
8
|
+
export const SearchPage = ({ query, results, error, hasMore, page })=>{
|
|
9
9
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
10
|
const searchTitle = $__i18n._({
|
|
11
11
|
id: "A1taO8",
|
|
12
12
|
message: "Search"
|
|
13
13
|
});
|
|
14
|
-
const PaginationComponent = theme?.PagePagination ?? DefaultPagePagination;
|
|
15
14
|
return /*#__PURE__*/ _jsxs("div", {
|
|
16
15
|
class: "py-6",
|
|
16
|
+
"data-page": "search",
|
|
17
17
|
children: [
|
|
18
18
|
/*#__PURE__*/ _jsx("h1", {
|
|
19
19
|
class: "text-2xl font-semibold mb-6",
|
|
@@ -75,13 +75,15 @@ export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
|
|
|
75
75
|
class: "divide-y divide-border",
|
|
76
76
|
children: results.map((result)=>/*#__PURE__*/ _jsx("article", {
|
|
77
77
|
class: "py-4",
|
|
78
|
+
"data-post": true,
|
|
79
|
+
"data-format": result.post.format,
|
|
78
80
|
children: /*#__PURE__*/ _jsxs("a", {
|
|
79
81
|
href: result.post.permalink,
|
|
80
82
|
class: "block",
|
|
81
83
|
children: [
|
|
82
84
|
/*#__PURE__*/ _jsx("h2", {
|
|
83
85
|
class: "font-medium hover:underline",
|
|
84
|
-
children: result.post.title || result.post.excerpt?.slice(0, 60) ||
|
|
86
|
+
children: result.post.title || result.post.excerpt?.slice(0, 60) || "Post #" + result.post.id
|
|
85
87
|
}),
|
|
86
88
|
result.snippet && /*#__PURE__*/ _jsx("p", {
|
|
87
89
|
class: "text-sm text-muted-foreground mt-2 line-clamp-2",
|
|
@@ -106,8 +108,8 @@ export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
|
|
|
106
108
|
})
|
|
107
109
|
}, result.post.id))
|
|
108
110
|
}),
|
|
109
|
-
/*#__PURE__*/ _jsx(
|
|
110
|
-
baseUrl:
|
|
111
|
+
/*#__PURE__*/ _jsx(PagePagination, {
|
|
112
|
+
baseUrl: "/search?q=" + encodeURIComponent(query),
|
|
111
113
|
currentPage: page,
|
|
112
114
|
hasMore: hasMore
|
|
113
115
|
})
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Single Page (Custom Page)
|
|
3
3
|
*
|
|
4
|
-
* Custom page
|
|
4
|
+
* Custom page view — clean centered content.
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
export const SinglePage = ({ page })=>{
|
|
7
7
|
return /*#__PURE__*/ _jsxs("article", {
|
|
8
8
|
class: "h-entry py-6",
|
|
9
|
+
"data-page": "single-page",
|
|
9
10
|
children: [
|
|
10
11
|
page.title && /*#__PURE__*/ _jsx("h1", {
|
|
11
12
|
class: "p-name text-2xl font-semibold mb-6",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Media Gallery Component
|
|
3
3
|
*
|
|
4
4
|
* Renders media attachments in a horizontal scrollable row,
|
|
5
|
-
* similar to
|
|
5
|
+
* similar to an image carousel.
|
|
6
6
|
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
7
7
|
export const MediaGallery = ({ attachments })=>{
|
|
8
8
|
const images = attachments.filter((a)=>a.mimeType.startsWith("image/"));
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Cursor-based pagination for post lists
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
+
import { getPageNumbers } from "../../lib/pagination.js";
|
|
7
8
|
export const Pagination = ({ baseUrl, hasMore, nextCursor, prevCursor, cursorParam = "cursor" })=>{
|
|
8
9
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
9
10
|
const hasPrev = prevCursor !== undefined;
|
|
@@ -82,10 +83,10 @@ export const LoadMore = ({ href, hasMore, text })=>{
|
|
|
82
83
|
})
|
|
83
84
|
});
|
|
84
85
|
};
|
|
85
|
-
export const PagePagination = ({ baseUrl, currentPage, hasMore, pageParam = "page" })=>{
|
|
86
|
+
export const PagePagination = ({ baseUrl, currentPage, hasMore, totalPages, pageParam = "page" })=>{
|
|
86
87
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
87
88
|
const hasPrev = currentPage > 1;
|
|
88
|
-
const hasNext = hasMore;
|
|
89
|
+
const hasNext = totalPages ? currentPage < totalPages : hasMore ?? false;
|
|
89
90
|
if (!hasPrev && !hasNext) {
|
|
90
91
|
return null;
|
|
91
92
|
}
|
|
@@ -107,6 +108,44 @@ export const PagePagination = ({ baseUrl, currentPage, hasMore, pageParam = "pag
|
|
|
107
108
|
id: "hXzOVo",
|
|
108
109
|
message: "Next"
|
|
109
110
|
});
|
|
111
|
+
// Numbered pagination when totalPages is known
|
|
112
|
+
if (totalPages && totalPages > 1) {
|
|
113
|
+
const pageNumbers = getPageNumbers(currentPage, totalPages);
|
|
114
|
+
return /*#__PURE__*/ _jsxs("nav", {
|
|
115
|
+
class: "flex items-center justify-center gap-4 py-6 text-sm",
|
|
116
|
+
"aria-label": "Pagination",
|
|
117
|
+
children: [
|
|
118
|
+
hasPrev ? /*#__PURE__*/ _jsx("a", {
|
|
119
|
+
href: buildUrl(currentPage - 1),
|
|
120
|
+
class: "underline text-muted-foreground hover:text-foreground",
|
|
121
|
+
children: prevText
|
|
122
|
+
}) : /*#__PURE__*/ _jsx("span", {
|
|
123
|
+
class: "text-muted-foreground/50",
|
|
124
|
+
children: prevText
|
|
125
|
+
}),
|
|
126
|
+
pageNumbers.map((page, i)=>page === 0 ? /*#__PURE__*/ _jsx("span", {
|
|
127
|
+
class: "text-muted-foreground",
|
|
128
|
+
children: "..."
|
|
129
|
+
}, `ellipsis-${i}`) : page === currentPage ? /*#__PURE__*/ _jsx("span", {
|
|
130
|
+
"aria-current": "page",
|
|
131
|
+
children: page
|
|
132
|
+
}, page) : /*#__PURE__*/ _jsx("a", {
|
|
133
|
+
href: buildUrl(page),
|
|
134
|
+
class: "underline text-muted-foreground hover:text-foreground",
|
|
135
|
+
children: page
|
|
136
|
+
}, page)),
|
|
137
|
+
hasNext ? /*#__PURE__*/ _jsx("a", {
|
|
138
|
+
href: buildUrl(currentPage + 1),
|
|
139
|
+
class: "underline text-muted-foreground hover:text-foreground",
|
|
140
|
+
children: nextText
|
|
141
|
+
}) : /*#__PURE__*/ _jsx("span", {
|
|
142
|
+
class: "text-muted-foreground/50",
|
|
143
|
+
children: nextText
|
|
144
|
+
})
|
|
145
|
+
]
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Simple prev/next fallback when totalPages is unknown
|
|
110
149
|
const pageText = $__i18n._({
|
|
111
150
|
id: "tiq7kl",
|
|
112
151
|
message: "Page {page}"
|
|
@@ -15,7 +15,7 @@ const ThreadPost = ({ post, isCurrent, isRoot })=>{
|
|
|
15
15
|
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
16
16
|
class: "p-name text-lg font-medium mb-2",
|
|
17
17
|
children: /*#__PURE__*/ _jsx("a", {
|
|
18
|
-
href: `${post.
|
|
18
|
+
href: `${post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`}`,
|
|
19
19
|
class: "u-url hover:underline",
|
|
20
20
|
children: post.title
|
|
21
21
|
})
|
|
@@ -42,7 +42,7 @@ const ThreadPost = ({ post, isCurrent, isRoot })=>{
|
|
|
42
42
|
})
|
|
43
43
|
}),
|
|
44
44
|
!isCurrent && /*#__PURE__*/ _jsx("a", {
|
|
45
|
-
href: `${post.
|
|
45
|
+
href: `${post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`}`,
|
|
46
46
|
class: "text-xs hover:underline",
|
|
47
47
|
children: $__i18n._({
|
|
48
48
|
id: "D9Oea+",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { EmptyState } from "./EmptyState.js";
|
|
2
|
+
export { MediaGallery } from "./MediaGallery.js";
|
|
3
|
+
export { Pagination, LoadMore, PagePagination } from "./Pagination.js";
|
|
4
|
+
export { getPageNumbers } from "../../lib/pagination.js";
|
|
5
|
+
export { ThreadView } from "./ThreadView.js";
|
package/package.json
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jant/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "A modern, open-source microblogging platform built on Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"jant": "./bin/jant.js"
|
|
8
|
-
},
|
|
9
6
|
"exports": {
|
|
10
7
|
".": {
|
|
11
8
|
"types": "./src/index.ts",
|
|
12
9
|
"default": "./dist/index.js"
|
|
13
10
|
},
|
|
14
|
-
"./theme": {
|
|
15
|
-
"types": "./src/theme/index.ts",
|
|
16
|
-
"default": "./dist/theme/index.js"
|
|
17
|
-
},
|
|
18
11
|
"./i18n": {
|
|
19
12
|
"types": "./src/i18n/index.ts",
|
|
20
13
|
"default": "./dist/i18n/index.js"
|
|
@@ -23,7 +16,6 @@
|
|
|
23
16
|
"./client": "./dist/client.js"
|
|
24
17
|
},
|
|
25
18
|
"files": [
|
|
26
|
-
"bin",
|
|
27
19
|
"dist",
|
|
28
20
|
"src"
|
|
29
21
|
],
|
|
@@ -86,6 +86,9 @@ export function createTestDatabase(options?: { fts?: boolean }) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// Apply 0006: rename slug to path on posts
|
|
90
|
+
applyMigration(sqlite, "0006_rename_slug_to_path.sql");
|
|
91
|
+
|
|
89
92
|
const db = drizzle(sqlite, { schema });
|
|
90
93
|
|
|
91
94
|
return { db, sqlite };
|
package/src/app.tsx
CHANGED
|
@@ -11,7 +11,6 @@ import { i18nMiddleware } from "./i18n/index.js";
|
|
|
11
11
|
import { useLingui } from "@lingui/react/macro";
|
|
12
12
|
import type { Bindings, JantConfig } from "./types.js";
|
|
13
13
|
import { SETTINGS_KEYS } from "./lib/constants.js";
|
|
14
|
-
import { theme as threadsTheme } from "./themes/threads/index.js";
|
|
15
14
|
import { hashPassword } from "better-auth/crypto";
|
|
16
15
|
|
|
17
16
|
// Routes - Pages
|
|
@@ -21,6 +20,8 @@ import { pageRoutes } from "./routes/pages/page.js";
|
|
|
21
20
|
import { collectionRoutes } from "./routes/pages/collection.js";
|
|
22
21
|
import { archiveRoutes } from "./routes/pages/archive.js";
|
|
23
22
|
import { searchRoutes } from "./routes/pages/search.js";
|
|
23
|
+
import { featuredRoutes } from "./routes/pages/featured.js";
|
|
24
|
+
import { collectionsPageRoutes } from "./routes/pages/collections.js";
|
|
24
25
|
|
|
25
26
|
// Routes - Dashboard
|
|
26
27
|
import { dashIndexRoutes } from "./routes/dash/index.js";
|
|
@@ -30,12 +31,18 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
|
|
|
30
31
|
import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
|
|
31
32
|
import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
|
|
32
33
|
import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
|
|
33
|
-
import { navigationRoutes as dashNavigationRoutes } from "./routes/dash/navigation.js";
|
|
34
34
|
|
|
35
35
|
// Routes - API
|
|
36
36
|
import { postsApiRoutes } from "./routes/api/posts.js";
|
|
37
|
+
import { pagesApiRoutes } from "./routes/api/pages.js";
|
|
38
|
+
import { navItemsApiRoutes } from "./routes/api/nav-items.js";
|
|
39
|
+
import { collectionsApiRoutes } from "./routes/api/collections.js";
|
|
40
|
+
import { settingsApiRoutes } from "./routes/api/settings.js";
|
|
37
41
|
import { uploadApiRoutes } from "./routes/api/upload.js";
|
|
38
42
|
import { searchApiRoutes } from "./routes/api/search.js";
|
|
43
|
+
// Routes - Compose
|
|
44
|
+
import { composeRoutes } from "./routes/compose.js";
|
|
45
|
+
|
|
39
46
|
// Routes - Feed
|
|
40
47
|
import { rssRoutes } from "./routes/feed/rss.js";
|
|
41
48
|
import { sitemapRoutes } from "./routes/feed/sitemap.js";
|
|
@@ -45,7 +52,7 @@ import { requireAuth } from "./middleware/auth.js";
|
|
|
45
52
|
import { requireOnboarding } from "./middleware/onboarding.js";
|
|
46
53
|
|
|
47
54
|
// Layouts for auth pages
|
|
48
|
-
import { BaseLayout } from "./
|
|
55
|
+
import { BaseLayout } from "./ui/layouts/BaseLayout.js";
|
|
49
56
|
import { dsRedirect, dsToast } from "./lib/sse.js";
|
|
50
57
|
import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
|
|
51
58
|
import { createStorageDriver, type StorageDriver } from "./lib/storage.js";
|
|
@@ -56,6 +63,8 @@ export interface AppVariables {
|
|
|
56
63
|
auth: Auth;
|
|
57
64
|
config: JantConfig;
|
|
58
65
|
themeStyle: string;
|
|
66
|
+
customCSS: string;
|
|
67
|
+
isAuthenticated: boolean;
|
|
59
68
|
storage: StorageDriver | null;
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -76,30 +85,12 @@ export type App = Hono<{ Bindings: Bindings; Variables: AppVariables }>;
|
|
|
76
85
|
* import { createApp } from "@jant/core";
|
|
77
86
|
*
|
|
78
87
|
* export default createApp({
|
|
79
|
-
*
|
|
88
|
+
* cssVariables: { "--card-radius": "0" },
|
|
80
89
|
* });
|
|
81
90
|
* ```
|
|
82
91
|
*/
|
|
83
92
|
export function createApp(config: JantConfig = {}): App {
|
|
84
|
-
|
|
85
|
-
const defaultTheme = threadsTheme();
|
|
86
|
-
const resolvedConfig: JantConfig = {
|
|
87
|
-
...config,
|
|
88
|
-
theme: {
|
|
89
|
-
name: config.theme?.name ?? defaultTheme.name,
|
|
90
|
-
components: {
|
|
91
|
-
...defaultTheme.components,
|
|
92
|
-
...config.theme?.components,
|
|
93
|
-
},
|
|
94
|
-
timelineMore: config.theme?.timelineMore ?? defaultTheme.timelineMore,
|
|
95
|
-
cssVariables: {
|
|
96
|
-
...defaultTheme.cssVariables,
|
|
97
|
-
...config.theme?.cssVariables,
|
|
98
|
-
},
|
|
99
|
-
colorThemes: config.theme?.colorThemes ?? defaultTheme.colorThemes,
|
|
100
|
-
feed: config.theme?.feed,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
93
|
+
const resolvedConfig: JantConfig = { ...config };
|
|
103
94
|
|
|
104
95
|
const app = new Hono<{ Bindings: Bindings; Variables: AppVariables }>();
|
|
105
96
|
|
|
@@ -133,18 +124,37 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
133
124
|
// Onboarding gate — redirect to /setup if not yet initialized
|
|
134
125
|
app.use("*", requireOnboarding());
|
|
135
126
|
|
|
136
|
-
// Theme middleware - resolve active color theme and
|
|
127
|
+
// Theme middleware - resolve active color theme, custom CSS, and auth state
|
|
137
128
|
app.use("*", async (c, next) => {
|
|
138
|
-
const themeId = await
|
|
129
|
+
const [themeId, customCSS] = await Promise.all([
|
|
130
|
+
c.var.services.settings.get(SETTINGS_KEYS.THEME),
|
|
131
|
+
c.var.services.settings.get(SETTINGS_KEYS.CUSTOM_CSS),
|
|
132
|
+
]);
|
|
139
133
|
const themes = getAvailableThemes(resolvedConfig);
|
|
140
134
|
const activeTheme = themeId
|
|
141
135
|
? themes.find((t) => t.id === themeId)
|
|
142
136
|
: undefined;
|
|
143
137
|
const themeStyle = buildThemeStyle(
|
|
144
138
|
activeTheme,
|
|
145
|
-
resolvedConfig.
|
|
139
|
+
resolvedConfig.cssVariables,
|
|
146
140
|
);
|
|
147
141
|
c.set("themeStyle", themeStyle);
|
|
142
|
+
c.set("customCSS", customCSS ?? "");
|
|
143
|
+
|
|
144
|
+
// Check auth state for data-authenticated attribute on <body>
|
|
145
|
+
let isAuthenticated = false;
|
|
146
|
+
if (c.var.auth) {
|
|
147
|
+
try {
|
|
148
|
+
const session = await c.var.auth.api.getSession({
|
|
149
|
+
headers: c.req.raw.headers,
|
|
150
|
+
});
|
|
151
|
+
isAuthenticated = !!session;
|
|
152
|
+
} catch {
|
|
153
|
+
// Not authenticated
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
c.set("isAuthenticated", isAuthenticated);
|
|
157
|
+
|
|
148
158
|
await next();
|
|
149
159
|
});
|
|
150
160
|
|
|
@@ -196,6 +206,10 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
196
206
|
|
|
197
207
|
// API Routes
|
|
198
208
|
app.route("/api/posts", postsApiRoutes);
|
|
209
|
+
app.route("/api/pages", pagesApiRoutes);
|
|
210
|
+
app.route("/api/nav-items", navItemsApiRoutes);
|
|
211
|
+
app.route("/api/collections", collectionsApiRoutes);
|
|
212
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
199
213
|
|
|
200
214
|
// Setup page component
|
|
201
215
|
const SetupContent: FC = () => {
|
|
@@ -338,6 +352,18 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
338
352
|
|
|
339
353
|
await c.var.services.settings.completeOnboarding();
|
|
340
354
|
|
|
355
|
+
// Seed default navigation items
|
|
356
|
+
await c.var.services.navItems.create({
|
|
357
|
+
type: "link",
|
|
358
|
+
label: "Featured",
|
|
359
|
+
url: "/featured",
|
|
360
|
+
});
|
|
361
|
+
await c.var.services.navItems.create({
|
|
362
|
+
type: "link",
|
|
363
|
+
label: "Collections",
|
|
364
|
+
url: "/collections",
|
|
365
|
+
});
|
|
366
|
+
|
|
341
367
|
return dsRedirect("/signin?setup");
|
|
342
368
|
} catch (err) {
|
|
343
369
|
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
@@ -718,7 +744,6 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
718
744
|
app.route("/dash/settings", dashSettingsRoutes);
|
|
719
745
|
app.route("/dash/redirects", dashRedirectsRoutes);
|
|
720
746
|
app.route("/dash/collections", dashCollectionsRoutes);
|
|
721
|
-
app.route("/dash/navigation", dashNavigationRoutes);
|
|
722
747
|
// API routes
|
|
723
748
|
app.route("/api/upload", uploadApiRoutes);
|
|
724
749
|
app.route("/api/search", searchApiRoutes);
|
|
@@ -751,6 +776,9 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
751
776
|
return new Response(object.body, { headers });
|
|
752
777
|
});
|
|
753
778
|
|
|
779
|
+
// Compose route (auth enforced in route middleware)
|
|
780
|
+
app.route("/compose", composeRoutes);
|
|
781
|
+
|
|
754
782
|
// Feed routes
|
|
755
783
|
app.route("/feed", rssRoutes);
|
|
756
784
|
app.route("/", sitemapRoutes);
|
|
@@ -758,6 +786,8 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
758
786
|
// Frontend routes
|
|
759
787
|
app.route("/search", searchRoutes);
|
|
760
788
|
app.route("/archive", archiveRoutes);
|
|
789
|
+
app.route("/featured", featuredRoutes);
|
|
790
|
+
app.route("/collections", collectionsPageRoutes);
|
|
761
791
|
app.route("/c", collectionRoutes);
|
|
762
792
|
app.route("/p", postRoutes);
|
|
763
793
|
app.route("/", homeRoutes);
|
package/src/db/schema.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const posts = sqliteTable("posts", {
|
|
|
22
22
|
.default("published"),
|
|
23
23
|
featured: integer("featured").notNull().default(0),
|
|
24
24
|
pinned: integer("pinned").notNull().default(0),
|
|
25
|
-
|
|
25
|
+
path: text("path").unique(),
|
|
26
26
|
title: text("title"),
|
|
27
27
|
url: text("url"),
|
|
28
28
|
body: text("body"),
|