@jant/core 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +101 -571
- package/dist/client.js +1 -0
- package/dist/db/schema.js +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -9
- package/dist/lib/avatar-upload.js +134 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/constants.js +10 -9
- package/dist/lib/favicon.js +102 -0
- package/dist/lib/image.js +13 -17
- package/dist/lib/media-helpers.js +2 -2
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +48 -3
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +16 -11
- package/dist/lib/schemas.js +34 -3
- package/dist/lib/theme.js +4 -4
- package/dist/lib/timeline.js +24 -48
- package/dist/lib/timezones.js +388 -0
- package/dist/lib/view.js +3 -3
- package/dist/routes/api/collections.js +124 -0
- package/dist/routes/api/nav-items.js +104 -0
- package/dist/routes/api/pages.js +91 -0
- package/dist/routes/api/posts.js +3 -3
- package/dist/routes/api/search.js +2 -2
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +3 -3
- package/dist/routes/auth/reset.js +221 -0
- package/dist/routes/auth/setup.js +194 -0
- package/dist/routes/auth/signin.js +176 -0
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -416
- package/dist/routes/dash/index.js +1 -1
- package/dist/routes/dash/media.js +13 -393
- package/dist/routes/dash/pages.js +112 -86
- package/dist/routes/dash/posts.js +3 -5
- package/dist/routes/dash/redirects.js +20 -14
- package/dist/routes/dash/settings.js +213 -518
- package/dist/routes/feed/rss.js +4 -3
- package/dist/routes/feed/sitemap.js +5 -3
- package/dist/routes/pages/archive.js +3 -6
- package/dist/routes/pages/collection.js +3 -6
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +36 -0
- package/dist/routes/pages/home.js +33 -49
- package/dist/routes/pages/latest.js +45 -0
- package/dist/routes/pages/page.js +29 -32
- package/dist/routes/pages/post.js +3 -6
- package/dist/routes/pages/search.js +3 -6
- package/dist/services/page.js +5 -1
- package/dist/services/post.js +45 -31
- package/dist/services/search.js +1 -1
- package/dist/types/bindings.js +3 -0
- package/dist/types/config.js +147 -0
- package/dist/types/constants.js +27 -0
- package/dist/types/entities.js +3 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/props.js +3 -0
- package/dist/types/views.js +5 -0
- package/dist/types.js +8 -111
- package/dist/{theme → ui}/color-themes.js +33 -33
- package/dist/ui/compose/ComposeDialog.js +467 -0
- package/dist/ui/compose/ComposePrompt.js +55 -0
- package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
- package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
- package/dist/{theme/components → ui/dash}/PostList.js +6 -6
- package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
- package/dist/ui/dash/collections/CollectionForm.js +152 -0
- package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
- package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/dash/media/MediaListContent.js +166 -0
- package/dist/ui/dash/media/ViewMediaContent.js +212 -0
- package/dist/ui/dash/pages/LinkFormContent.js +130 -0
- package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
- package/dist/ui/dash/settings/AccountContent.js +209 -0
- package/dist/ui/dash/settings/AppearanceContent.js +259 -0
- package/dist/ui/dash/settings/GeneralContent.js +536 -0
- package/dist/ui/dash/settings/SettingsNav.js +41 -0
- package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
- package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
- package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
- package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
- package/dist/ui/feed/TimelineFeed.js +41 -0
- package/dist/ui/feed/TimelineItem.js +27 -0
- package/dist/ui/font-themes.js +36 -0
- package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +169 -0
- package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
- package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
- package/dist/ui/pages/CollectionsPage.js +76 -0
- package/dist/ui/pages/FeaturedPage.js +24 -0
- package/dist/ui/pages/HomePage.js +24 -0
- package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
- package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
- package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
- package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
- package/dist/ui/shared/index.js +5 -0
- package/package.json +1 -9
- package/src/__tests__/helpers/db.ts +3 -0
- package/src/app.tsx +131 -561
- package/src/client.ts +1 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +1 -1
- package/src/i18n/locales/en.po +477 -261
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +477 -261
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +477 -261
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +7 -36
- package/src/lib/__tests__/config.test.ts +192 -0
- package/src/lib/__tests__/favicon.test.ts +151 -0
- package/src/lib/__tests__/image.test.ts +2 -6
- package/src/lib/__tests__/schemas.test.ts +60 -19
- package/src/lib/__tests__/timeline.test.ts +45 -81
- package/src/lib/__tests__/timezones.test.ts +61 -0
- package/src/lib/__tests__/view.test.ts +15 -9
- package/src/lib/avatar-upload.ts +165 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/constants.ts +19 -10
- package/src/lib/favicon.ts +115 -0
- package/src/lib/image.ts +13 -21
- package/src/lib/media-helpers.ts +2 -2
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +73 -4
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +22 -15
- package/src/lib/schemas.ts +47 -6
- package/src/lib/theme.ts +5 -5
- package/src/lib/timeline.ts +28 -57
- package/src/lib/timezones.ts +325 -0
- package/src/lib/view.ts +3 -3
- package/src/preset.css +2 -1
- package/src/routes/__tests__/compose.test.ts +199 -0
- package/src/routes/api/__tests__/collections.test.ts +249 -0
- package/src/routes/api/__tests__/nav-items.test.ts +222 -0
- package/src/routes/api/__tests__/pages.test.ts +218 -0
- package/src/routes/api/__tests__/settings.test.ts +132 -0
- package/src/routes/api/collections.ts +143 -0
- package/src/routes/api/nav-items.ts +115 -0
- package/src/routes/api/pages.ts +101 -0
- package/src/routes/api/posts.ts +3 -3
- package/src/routes/api/search.ts +2 -2
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +2 -3
- package/src/routes/auth/reset.tsx +239 -0
- package/src/routes/auth/setup.tsx +189 -0
- package/src/routes/auth/signin.tsx +163 -0
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
- package/src/routes/dash/collections.tsx +18 -367
- package/src/routes/dash/index.tsx +1 -1
- package/src/routes/dash/media.tsx +13 -415
- package/src/routes/dash/pages.tsx +131 -98
- package/src/routes/dash/posts.tsx +3 -7
- package/src/routes/dash/redirects.tsx +22 -16
- package/src/routes/dash/settings.tsx +265 -478
- package/src/routes/feed/__tests__/rss.test.ts +141 -0
- package/src/routes/feed/rss.ts +5 -3
- package/src/routes/feed/sitemap.ts +5 -3
- package/src/routes/pages/__tests__/collections.test.ts +94 -0
- package/src/routes/pages/__tests__/featured.test.ts +94 -0
- package/src/routes/pages/archive.tsx +2 -6
- package/src/routes/pages/collection.tsx +2 -6
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +44 -0
- package/src/routes/pages/home.tsx +30 -53
- package/src/routes/pages/latest.tsx +59 -0
- package/src/routes/pages/page.tsx +28 -30
- package/src/routes/pages/post.tsx +2 -5
- package/src/routes/pages/search.tsx +2 -6
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post.test.ts +114 -15
- package/src/services/page.ts +13 -1
- package/src/services/post.ts +58 -40
- package/src/services/search.ts +2 -2
- package/src/styles/components.css +0 -65
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +475 -0
- package/src/types/bindings.ts +30 -0
- package/src/types/config.ts +183 -0
- package/src/types/constants.ts +26 -0
- package/src/types/entities.ts +109 -0
- package/src/types/operations.ts +88 -0
- package/src/types/props.ts +115 -0
- package/src/types/views.ts +172 -0
- package/src/types.ts +8 -774
- package/src/ui/__tests__/font-themes.test.ts +34 -0
- package/src/{theme → ui}/color-themes.ts +34 -34
- package/src/ui/compose/ComposeDialog.tsx +414 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
- package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
- package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
- package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
- package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
- package/src/ui/dash/collections/CollectionForm.tsx +153 -0
- package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/dash/media/MediaListContent.tsx +201 -0
- package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
- package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
- package/src/ui/dash/settings/AccountContent.tsx +176 -0
- package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
- package/src/ui/dash/settings/GeneralContent.tsx +533 -0
- package/src/ui/dash/settings/SettingsNav.tsx +56 -0
- package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
- package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
- package/src/ui/feed/TimelineFeed.tsx +49 -0
- package/src/ui/feed/TimelineItem.tsx +45 -0
- package/src/ui/font-themes.ts +54 -0
- package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +164 -0
- package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
- package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
- package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
- package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
- package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
- package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
- package/src/ui/shared/__tests__/pagination.test.ts +46 -0
- package/src/ui/shared/index.ts +12 -0
- package/bin/jant.js +0 -185
- package/dist/lib/theme-components.js +0 -46
- package/dist/routes/dash/navigation.js +0 -289
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
- package/dist/themes/threads/index.js +0 -81
- package/dist/themes/threads/pages/HomePage.js +0 -25
- package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
- package/dist/themes/threads/timeline/TimelineItem.js +0 -36
- package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
- package/dist/themes/threads/timeline/groupByDate.js +0 -22
- package/dist/themes/threads/timeline/timelineMore.js +0 -107
- package/src/lib/__tests__/theme-components.test.ts +0 -105
- package/src/lib/theme-components.ts +0 -65
- package/src/routes/dash/navigation.tsx +0 -317
- package/src/theme/components/index.ts +0 -23
- package/src/theme/index.ts +0 -22
- package/src/theme/layouts/index.ts +0 -7
- package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
- package/src/themes/threads/index.ts +0 -100
- package/src/themes/threads/style.css +0 -336
- package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
- package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
- package/src/themes/threads/timeline/groupByDate.ts +0 -30
- package/src/themes/threads/timeline/timelineMore.tsx +0 -130
- /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
- /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
- /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
- /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
- /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
- /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
- /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
package/dist/lib/navigation.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* Navigation Helper
|
|
3
3
|
*
|
|
4
4
|
* Provides shared data fetching for public page navigation.
|
|
5
|
-
*/ import { getSiteName } from "./config.js";
|
|
5
|
+
*/ import { getSiteName, getHomeDefaultView, getSiteFooter } from "./config.js";
|
|
6
6
|
import { toNavItemViews } from "./view.js";
|
|
7
|
+
import { getMediaUrl, getPublicUrlForProvider } from "./image.js";
|
|
8
|
+
import { render as renderMarkdown } from "./markdown.js";
|
|
7
9
|
/**
|
|
8
10
|
* Fetch navigation data for public pages.
|
|
9
11
|
*
|
|
10
12
|
* Returns NavItemView[] with pre-computed isActive/isExternal state.
|
|
13
|
+
* Also checks authentication status and loads collections for authenticated users.
|
|
11
14
|
*
|
|
12
15
|
* @param c - Hono context
|
|
13
16
|
* @returns Navigation data for SiteLayout
|
|
@@ -24,11 +27,53 @@ import { toNavItemViews } from "./view.js";
|
|
|
24
27
|
*/ export async function getNavigationData(c) {
|
|
25
28
|
const items = await c.var.services.navItems.list();
|
|
26
29
|
const currentPath = new URL(c.req.url).pathname;
|
|
27
|
-
const siteName = await
|
|
30
|
+
const [siteName, homeDefaultView, siteFooter] = await Promise.all([
|
|
31
|
+
getSiteName(c),
|
|
32
|
+
getHomeDefaultView(c),
|
|
33
|
+
getSiteFooter(c)
|
|
34
|
+
]);
|
|
35
|
+
// Only include description if explicitly set (DB or env), not the default
|
|
36
|
+
const dbDescription = await c.var.services.settings.get("SITE_DESCRIPTION");
|
|
37
|
+
const envDescription = c.env.SITE_DESCRIPTION;
|
|
38
|
+
const siteDescription = dbDescription || (typeof envDescription === "string" ? envDescription : "");
|
|
39
|
+
// Resolve avatar URL from storage key
|
|
40
|
+
const avatarKey = await c.var.services.settings.get("SITE_AVATAR");
|
|
41
|
+
const showHeaderAvatar = await c.var.services.settings.get("SHOW_HEADER_AVATAR") === "true";
|
|
42
|
+
let siteAvatarUrl;
|
|
43
|
+
if (avatarKey) {
|
|
44
|
+
const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
|
|
45
|
+
siteAvatarUrl = getMediaUrl(avatarKey, publicUrl);
|
|
46
|
+
}
|
|
47
|
+
// Render footer markdown
|
|
48
|
+
const siteFooterHtml = siteFooter ? renderMarkdown(siteFooter) : undefined;
|
|
28
49
|
const links = toNavItemViews(items, currentPath);
|
|
50
|
+
// Check auth status for compose button
|
|
51
|
+
let isAuthenticated = false;
|
|
52
|
+
let collections = [];
|
|
53
|
+
if (c.var.auth) {
|
|
54
|
+
try {
|
|
55
|
+
const session = await c.var.auth.api.getSession({
|
|
56
|
+
headers: c.req.raw.headers
|
|
57
|
+
});
|
|
58
|
+
isAuthenticated = !!session?.user;
|
|
59
|
+
} catch {
|
|
60
|
+
// Not authenticated
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Only load collections when authenticated (for compose dialog)
|
|
64
|
+
if (isAuthenticated) {
|
|
65
|
+
collections = await c.var.services.collections.list();
|
|
66
|
+
}
|
|
29
67
|
return {
|
|
30
68
|
links,
|
|
31
69
|
currentPath,
|
|
32
|
-
siteName
|
|
70
|
+
siteName,
|
|
71
|
+
siteDescription,
|
|
72
|
+
isAuthenticated,
|
|
73
|
+
collections,
|
|
74
|
+
homeDefaultView,
|
|
75
|
+
siteAvatarUrl,
|
|
76
|
+
showHeaderAvatar: showHeaderAvatar && !!siteAvatarUrl,
|
|
77
|
+
siteFooterHtml
|
|
33
78
|
};
|
|
34
79
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination Utilities
|
|
3
|
+
*
|
|
4
|
+
* Pure utility functions for page-based pagination.
|
|
5
|
+
*/ /**
|
|
6
|
+
* Computes which page numbers to display in a numbered pagination control.
|
|
7
|
+
* Always includes: first page, last page, current page, and 1 page on each side of current.
|
|
8
|
+
* Gaps between non-consecutive pages are represented by 0 (ellipsis marker).
|
|
9
|
+
*
|
|
10
|
+
* @param currentPage - The current active page (1-indexed)
|
|
11
|
+
* @param totalPages - Total number of pages
|
|
12
|
+
* @returns Array of page numbers, with 0 representing ellipsis gaps
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* getPageNumbers(1, 5) // [1, 2, 3, 4, 5]
|
|
17
|
+
* getPageNumbers(1, 20) // [1, 2, 0, 20]
|
|
18
|
+
* getPageNumbers(10, 20) // [1, 0, 9, 10, 11, 0, 20]
|
|
19
|
+
* ```
|
|
20
|
+
*/ export function getPageNumbers(currentPage, totalPages) {
|
|
21
|
+
if (totalPages <= 7) {
|
|
22
|
+
return Array.from({
|
|
23
|
+
length: totalPages
|
|
24
|
+
}, (_, i)=>i + 1);
|
|
25
|
+
}
|
|
26
|
+
const pages = new Set();
|
|
27
|
+
pages.add(1);
|
|
28
|
+
pages.add(totalPages);
|
|
29
|
+
pages.add(currentPage);
|
|
30
|
+
if (currentPage > 1) pages.add(currentPage - 1);
|
|
31
|
+
if (currentPage < totalPages) pages.add(currentPage + 1);
|
|
32
|
+
const sorted = [
|
|
33
|
+
...pages
|
|
34
|
+
].sort((a, b)=>a - b);
|
|
35
|
+
// Insert 0 for gaps
|
|
36
|
+
const result = [];
|
|
37
|
+
for(let i = 0; i < sorted.length; i++){
|
|
38
|
+
if (i > 0 && sorted[i] - sorted[i - 1] > 1) {
|
|
39
|
+
result.push(0); // ellipsis marker
|
|
40
|
+
}
|
|
41
|
+
result.push(sorted[i]);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
package/dist/lib/render.js
CHANGED
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides a single entry point for rendering public pages with the
|
|
5
5
|
* correct layout stack: BaseLayout > SiteLayout > content.
|
|
6
|
-
*
|
|
7
|
-
* BaseLayout is always the built-in implementation (handles Vite assets,
|
|
8
|
-
* I18nProvider, toast). SiteLayout is resolved from theme components.
|
|
9
6
|
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
10
|
-
import { BaseLayout } from "../
|
|
11
|
-
import {
|
|
7
|
+
import { BaseLayout } from "../ui/layouts/BaseLayout.js";
|
|
8
|
+
import { SiteLayout } from "../ui/layouts/SiteLayout.js";
|
|
12
9
|
/**
|
|
13
10
|
* Render a public page with the standard layout stack.
|
|
14
11
|
*
|
|
15
|
-
* Always uses the built-in BaseLayout, resolves SiteLayout from theme config.
|
|
16
|
-
*
|
|
17
12
|
* @param c - Hono context
|
|
18
13
|
* @param options - Page rendering options
|
|
19
14
|
* @returns Hono HTML response
|
|
@@ -29,18 +24,28 @@ import { ThreadsSiteLayout as DefaultSiteLayout } from "../themes/threads/Thread
|
|
|
29
24
|
* ```
|
|
30
25
|
*/ export function renderPublicPage(c, options) {
|
|
31
26
|
const { title, description, navData, content } = options;
|
|
32
|
-
const components = c.var.config?.theme?.components;
|
|
33
|
-
const Layout = components?.SiteLayout ?? DefaultSiteLayout;
|
|
34
27
|
const layoutProps = {
|
|
35
28
|
siteName: navData.siteName,
|
|
29
|
+
siteDescription: navData.siteDescription,
|
|
36
30
|
links: navData.links,
|
|
37
|
-
currentPath: navData.currentPath
|
|
31
|
+
currentPath: navData.currentPath,
|
|
32
|
+
isAuthenticated: navData.isAuthenticated,
|
|
33
|
+
collections: navData.collections,
|
|
34
|
+
homeDefaultView: navData.homeDefaultView,
|
|
35
|
+
siteAvatarUrl: navData.siteAvatarUrl,
|
|
36
|
+
showHeaderAvatar: navData.showHeaderAvatar,
|
|
37
|
+
siteFooterHtml: navData.siteFooterHtml
|
|
38
38
|
};
|
|
39
|
+
// Read favicon and noindex from context (set by theme middleware)
|
|
40
|
+
const faviconUrl = c.get("faviconUrl");
|
|
41
|
+
const noindex = c.get("noindex");
|
|
39
42
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
40
43
|
title: title,
|
|
41
44
|
description: description,
|
|
42
45
|
c: c,
|
|
43
|
-
|
|
46
|
+
faviconUrl: faviconUrl,
|
|
47
|
+
noindex: noindex,
|
|
48
|
+
children: /*#__PURE__*/ _jsx(SiteLayout, {
|
|
44
49
|
...layoutProps,
|
|
45
50
|
children: content
|
|
46
51
|
})
|
package/dist/lib/schemas.js
CHANGED
|
@@ -31,12 +31,12 @@ import { FORMATS, STATUSES, SORT_ORDERS, NAV_ITEM_TYPES, MAX_MEDIA_ATTACHMENTS }
|
|
|
31
31
|
]);
|
|
32
32
|
/**
|
|
33
33
|
* Rating schema (1-5 integer)
|
|
34
|
-
*/ export const RatingSchema = z.coerce.number().int().min(
|
|
34
|
+
*/ export const RatingSchema = z.coerce.number().int().min(0).max(5).optional().or(z.literal("").transform(()=>undefined)).transform((v)=>v === 0 ? undefined : v);
|
|
35
35
|
/**
|
|
36
36
|
* API request body schema for creating a post
|
|
37
37
|
*/ export const CreatePostSchema = z.object({
|
|
38
38
|
format: FormatSchema,
|
|
39
|
-
|
|
39
|
+
path: z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/).optional().or(z.literal("").transform(()=>undefined)),
|
|
40
40
|
title: z.string().optional(),
|
|
41
41
|
body: z.string().optional(),
|
|
42
42
|
status: StatusSchema.optional(),
|
|
@@ -51,7 +51,7 @@ import { FORMATS, STATUSES, SORT_ORDERS, NAV_ITEM_TYPES, MAX_MEDIA_ATTACHMENTS }
|
|
|
51
51
|
url: z.url().optional().or(z.literal("")),
|
|
52
52
|
quoteText: z.string().optional(),
|
|
53
53
|
rating: RatingSchema,
|
|
54
|
-
collectionId: z.coerce.number().int().
|
|
54
|
+
collectionId: z.coerce.number().int().min(0).optional().or(z.literal("").transform(()=>undefined)).transform((v)=>v === 0 ? undefined : v),
|
|
55
55
|
replyToId: z.string().optional(),
|
|
56
56
|
publishedAt: z.number().int().positive().optional(),
|
|
57
57
|
mediaIds: z.array(z.string()).max(MAX_MEDIA_ATTACHMENTS).optional()
|
|
@@ -99,6 +99,37 @@ import { FORMATS, STATUSES, SORT_ORDERS, NAV_ITEM_TYPES, MAX_MEDIA_ATTACHMENTS }
|
|
|
99
99
|
/**
|
|
100
100
|
* API request body schema for updating a collection
|
|
101
101
|
*/ export const UpdateCollectionSchema = CreateCollectionSchema.partial();
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Auth Schemas
|
|
104
|
+
// =============================================================================
|
|
105
|
+
/**
|
|
106
|
+
* Setup form validation schema
|
|
107
|
+
*/ export const SetupSchema = z.object({
|
|
108
|
+
name: z.string().min(1, "Name is required"),
|
|
109
|
+
email: z.string().email("Invalid email address"),
|
|
110
|
+
password: z.string().min(8, "Password must be at least 8 characters")
|
|
111
|
+
});
|
|
112
|
+
/**
|
|
113
|
+
* Sign-in form validation schema
|
|
114
|
+
*/ export const SigninSchema = z.object({
|
|
115
|
+
email: z.string().email("Invalid email address"),
|
|
116
|
+
password: z.string().min(1, "Password is required")
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Password reset form validation schema
|
|
120
|
+
*/ export const ResetPasswordSchema = z.object({
|
|
121
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
122
|
+
confirmPassword: z.string().min(1),
|
|
123
|
+
token: z.string().min(1)
|
|
124
|
+
}).refine((d)=>d.password === d.confirmPassword, {
|
|
125
|
+
message: "Passwords do not match",
|
|
126
|
+
path: [
|
|
127
|
+
"confirmPassword"
|
|
128
|
+
]
|
|
129
|
+
});
|
|
130
|
+
// =============================================================================
|
|
131
|
+
// Form Data Helpers
|
|
132
|
+
// =============================================================================
|
|
102
133
|
/**
|
|
103
134
|
* Form data helper: safely parse a FormData value with a schema
|
|
104
135
|
*
|
package/dist/lib/theme.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Theme Resolution Helpers
|
|
3
3
|
*
|
|
4
4
|
* Resolves the active color theme and builds CSS for injection into `<head>`.
|
|
5
|
-
*/ import { BUILTIN_COLOR_THEMES } from "../
|
|
5
|
+
*/ import { BUILTIN_COLOR_THEMES } from "../ui/color-themes.js";
|
|
6
6
|
/**
|
|
7
7
|
* Get the list of available color themes.
|
|
8
8
|
*
|
|
9
|
-
* Returns `config.
|
|
9
|
+
* Returns `config.colorThemes` if provided, otherwise the built-in list.
|
|
10
10
|
*
|
|
11
11
|
* @param config - The Jant configuration
|
|
12
12
|
* @returns Array of available color themes
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* const themes = getAvailableThemes(c.var.config);
|
|
17
17
|
* ```
|
|
18
18
|
*/ export function getAvailableThemes(config) {
|
|
19
|
-
return config.
|
|
19
|
+
return config.colorThemes ?? BUILTIN_COLOR_THEMES;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Build a `<style>` CSS string from a color theme and optional cssVariables overlay.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* BaseCoat defaults → selected theme → cssVariables
|
|
26
26
|
*
|
|
27
27
|
* @param theme - The active color theme (undefined = no theme overrides)
|
|
28
|
-
* @param cssVariables - Extra CSS variable overrides from `createApp({
|
|
28
|
+
* @param cssVariables - Extra CSS variable overrides from `createApp({ cssVariables })`
|
|
29
29
|
* @returns CSS string to inject in `<head>`, or empty string if nothing to inject
|
|
30
30
|
*
|
|
31
31
|
* Uses `:root:root` and `:root.dark` selectors for higher specificity than
|
package/dist/lib/timeline.js
CHANGED
|
@@ -2,44 +2,51 @@
|
|
|
2
2
|
* Timeline Data Assembly
|
|
3
3
|
*
|
|
4
4
|
* Shared helper for assembling timeline items with media and thread previews.
|
|
5
|
-
* Used by
|
|
5
|
+
* Used by page rendering with page-based pagination.
|
|
6
6
|
*/ import { buildMediaMap } from "./media-helpers.js";
|
|
7
7
|
import { createMediaContext, toPostView, toPostViews } from "./view.js";
|
|
8
8
|
const DEFAULT_PAGE_SIZE = 20;
|
|
9
9
|
/**
|
|
10
10
|
* Assembles a page of timeline items with media attachments and thread previews.
|
|
11
11
|
*
|
|
12
|
-
* Fetches posts, batch-loads media, identifies
|
|
13
|
-
* render-ready `TimelineItemView[]` with
|
|
12
|
+
* Fetches posts using offset-based pagination, batch-loads media, identifies
|
|
13
|
+
* threads, and returns render-ready `TimelineItemView[]` with page info.
|
|
14
14
|
*
|
|
15
15
|
* @param c - Hono context (provides services + env)
|
|
16
|
-
* @param options - Optional
|
|
16
|
+
* @param options - Optional page number (1-indexed, defaults to 1)
|
|
17
17
|
* @returns Assembled timeline items with pagination info
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
|
-
* const { items,
|
|
22
|
-
* const { items,
|
|
21
|
+
* const { items, currentPage, totalPages } = await assembleTimeline(c);
|
|
22
|
+
* const { items, currentPage, totalPages } = await assembleTimeline(c, { page: 2 });
|
|
23
23
|
* ```
|
|
24
24
|
*/ export async function assembleTimeline(c, options) {
|
|
25
25
|
const pageSize = parseInt(c.env.PAGE_SIZE ?? String(DEFAULT_PAGE_SIZE), 10) || DEFAULT_PAGE_SIZE;
|
|
26
|
-
|
|
26
|
+
const page = Math.max(1, options?.page ?? 1);
|
|
27
|
+
const offset = (page - 1) * pageSize;
|
|
28
|
+
// Get total count for pagination
|
|
29
|
+
const totalCount = await c.var.services.posts.count({
|
|
30
|
+
status: "published",
|
|
31
|
+
excludeReplies: true
|
|
32
|
+
});
|
|
33
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
|
|
34
|
+
// Fetch posts for the current page
|
|
27
35
|
const posts = await c.var.services.posts.list({
|
|
28
36
|
status: "published",
|
|
29
37
|
excludeReplies: true,
|
|
30
|
-
limit: pageSize
|
|
31
|
-
|
|
38
|
+
limit: pageSize,
|
|
39
|
+
offset
|
|
32
40
|
});
|
|
33
|
-
|
|
34
|
-
const displayPosts = hasMore ? posts.slice(0, pageSize) : posts;
|
|
35
|
-
if (displayPosts.length === 0) {
|
|
41
|
+
if (posts.length === 0) {
|
|
36
42
|
return {
|
|
37
43
|
items: [],
|
|
38
|
-
|
|
44
|
+
currentPage: page,
|
|
45
|
+
totalPages
|
|
39
46
|
};
|
|
40
47
|
}
|
|
41
48
|
// Batch load media attachments
|
|
42
|
-
const postIds =
|
|
49
|
+
const postIds = posts.map((p)=>p.id);
|
|
43
50
|
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
44
51
|
const mediaCtx = createMediaContext(c);
|
|
45
52
|
const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
|
|
@@ -57,7 +64,7 @@ const DEFAULT_PAGE_SIZE = 20;
|
|
|
57
64
|
}
|
|
58
65
|
const previewMediaMap = previewReplyIds.length > 0 ? buildMediaMap(await c.var.services.media.getByPostIds(previewReplyIds), mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl) : new Map();
|
|
59
66
|
// Assemble timeline items with View Models
|
|
60
|
-
const items =
|
|
67
|
+
const items = posts.map((post)=>{
|
|
61
68
|
const postView = toPostView({
|
|
62
69
|
...post,
|
|
63
70
|
mediaAttachments: mediaMap.get(post.id) ?? []
|
|
@@ -80,40 +87,9 @@ const DEFAULT_PAGE_SIZE = 20;
|
|
|
80
87
|
post: postView
|
|
81
88
|
};
|
|
82
89
|
});
|
|
83
|
-
// Determine next cursor
|
|
84
|
-
const lastPost = displayPosts[displayPosts.length - 1];
|
|
85
|
-
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
86
90
|
return {
|
|
87
91
|
items,
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
currentPage: page,
|
|
93
|
+
totalPages
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Groups timeline items by their publication date (YYYY-MM-DD).
|
|
94
|
-
*
|
|
95
|
-
* @param items - Timeline items to group
|
|
96
|
-
* @returns Array of date groups, each containing items published on the same day
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```ts
|
|
100
|
-
* const groups = groupByDate(items);
|
|
101
|
-
* // [{ dateKey: "2024-02-01", label: "Feb 1, 2024", items: [...] }, ...]
|
|
102
|
-
* ```
|
|
103
|
-
*/ export function groupByDate(items) {
|
|
104
|
-
const groups = [];
|
|
105
|
-
let current = null;
|
|
106
|
-
for (const item of items){
|
|
107
|
-
const dateKey = item.post.publishedAt.slice(0, 10);
|
|
108
|
-
if (!current || current.dateKey !== dateKey) {
|
|
109
|
-
current = {
|
|
110
|
-
dateKey,
|
|
111
|
-
label: item.post.publishedAtFormatted,
|
|
112
|
-
items: []
|
|
113
|
-
};
|
|
114
|
-
groups.push(current);
|
|
115
|
-
}
|
|
116
|
-
current.items.push(item);
|
|
117
|
-
}
|
|
118
|
-
return groups;
|
|
119
|
-
}
|