@jant/core 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +101 -571
- package/dist/client.js +1 -0
- package/dist/db/schema.js +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -9
- package/dist/lib/avatar-upload.js +134 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/constants.js +10 -9
- package/dist/lib/favicon.js +102 -0
- package/dist/lib/image.js +13 -17
- package/dist/lib/media-helpers.js +2 -2
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +48 -3
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +16 -11
- package/dist/lib/schemas.js +34 -3
- package/dist/lib/theme.js +4 -4
- package/dist/lib/timeline.js +24 -48
- package/dist/lib/timezones.js +388 -0
- package/dist/lib/view.js +3 -3
- package/dist/routes/api/collections.js +124 -0
- package/dist/routes/api/nav-items.js +104 -0
- package/dist/routes/api/pages.js +91 -0
- package/dist/routes/api/posts.js +3 -3
- package/dist/routes/api/search.js +2 -2
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +3 -3
- package/dist/routes/auth/reset.js +221 -0
- package/dist/routes/auth/setup.js +194 -0
- package/dist/routes/auth/signin.js +176 -0
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -416
- package/dist/routes/dash/index.js +1 -1
- package/dist/routes/dash/media.js +13 -393
- package/dist/routes/dash/pages.js +112 -86
- package/dist/routes/dash/posts.js +3 -5
- package/dist/routes/dash/redirects.js +20 -14
- package/dist/routes/dash/settings.js +213 -518
- package/dist/routes/feed/rss.js +4 -3
- package/dist/routes/feed/sitemap.js +5 -3
- package/dist/routes/pages/archive.js +3 -6
- package/dist/routes/pages/collection.js +3 -6
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +36 -0
- package/dist/routes/pages/home.js +33 -49
- package/dist/routes/pages/latest.js +45 -0
- package/dist/routes/pages/page.js +29 -32
- package/dist/routes/pages/post.js +3 -6
- package/dist/routes/pages/search.js +3 -6
- package/dist/services/page.js +5 -1
- package/dist/services/post.js +45 -31
- package/dist/services/search.js +1 -1
- package/dist/types/bindings.js +3 -0
- package/dist/types/config.js +147 -0
- package/dist/types/constants.js +27 -0
- package/dist/types/entities.js +3 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/props.js +3 -0
- package/dist/types/views.js +5 -0
- package/dist/types.js +8 -111
- package/dist/{theme → ui}/color-themes.js +33 -33
- package/dist/ui/compose/ComposeDialog.js +467 -0
- package/dist/ui/compose/ComposePrompt.js +55 -0
- package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
- package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
- package/dist/{theme/components → ui/dash}/PostList.js +6 -6
- package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
- package/dist/ui/dash/collections/CollectionForm.js +152 -0
- package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
- package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/dash/media/MediaListContent.js +166 -0
- package/dist/ui/dash/media/ViewMediaContent.js +212 -0
- package/dist/ui/dash/pages/LinkFormContent.js +130 -0
- package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
- package/dist/ui/dash/settings/AccountContent.js +209 -0
- package/dist/ui/dash/settings/AppearanceContent.js +259 -0
- package/dist/ui/dash/settings/GeneralContent.js +536 -0
- package/dist/ui/dash/settings/SettingsNav.js +41 -0
- package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
- package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
- package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
- package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
- package/dist/ui/feed/TimelineFeed.js +41 -0
- package/dist/ui/feed/TimelineItem.js +27 -0
- package/dist/ui/font-themes.js +36 -0
- package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +169 -0
- package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
- package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
- package/dist/ui/pages/CollectionsPage.js +76 -0
- package/dist/ui/pages/FeaturedPage.js +24 -0
- package/dist/ui/pages/HomePage.js +24 -0
- package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
- package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
- package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
- package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
- package/dist/ui/shared/index.js +5 -0
- package/package.json +1 -9
- package/src/__tests__/helpers/db.ts +3 -0
- package/src/app.tsx +131 -561
- package/src/client.ts +1 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +1 -1
- package/src/i18n/locales/en.po +477 -261
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +477 -261
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +477 -261
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +7 -36
- package/src/lib/__tests__/config.test.ts +192 -0
- package/src/lib/__tests__/favicon.test.ts +151 -0
- package/src/lib/__tests__/image.test.ts +2 -6
- package/src/lib/__tests__/schemas.test.ts +60 -19
- package/src/lib/__tests__/timeline.test.ts +45 -81
- package/src/lib/__tests__/timezones.test.ts +61 -0
- package/src/lib/__tests__/view.test.ts +15 -9
- package/src/lib/avatar-upload.ts +165 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/constants.ts +19 -10
- package/src/lib/favicon.ts +115 -0
- package/src/lib/image.ts +13 -21
- package/src/lib/media-helpers.ts +2 -2
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +73 -4
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +22 -15
- package/src/lib/schemas.ts +47 -6
- package/src/lib/theme.ts +5 -5
- package/src/lib/timeline.ts +28 -57
- package/src/lib/timezones.ts +325 -0
- package/src/lib/view.ts +3 -3
- package/src/preset.css +2 -1
- package/src/routes/__tests__/compose.test.ts +199 -0
- package/src/routes/api/__tests__/collections.test.ts +249 -0
- package/src/routes/api/__tests__/nav-items.test.ts +222 -0
- package/src/routes/api/__tests__/pages.test.ts +218 -0
- package/src/routes/api/__tests__/settings.test.ts +132 -0
- package/src/routes/api/collections.ts +143 -0
- package/src/routes/api/nav-items.ts +115 -0
- package/src/routes/api/pages.ts +101 -0
- package/src/routes/api/posts.ts +3 -3
- package/src/routes/api/search.ts +2 -2
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +2 -3
- package/src/routes/auth/reset.tsx +239 -0
- package/src/routes/auth/setup.tsx +189 -0
- package/src/routes/auth/signin.tsx +163 -0
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
- package/src/routes/dash/collections.tsx +18 -367
- package/src/routes/dash/index.tsx +1 -1
- package/src/routes/dash/media.tsx +13 -415
- package/src/routes/dash/pages.tsx +131 -98
- package/src/routes/dash/posts.tsx +3 -7
- package/src/routes/dash/redirects.tsx +22 -16
- package/src/routes/dash/settings.tsx +265 -478
- package/src/routes/feed/__tests__/rss.test.ts +141 -0
- package/src/routes/feed/rss.ts +5 -3
- package/src/routes/feed/sitemap.ts +5 -3
- package/src/routes/pages/__tests__/collections.test.ts +94 -0
- package/src/routes/pages/__tests__/featured.test.ts +94 -0
- package/src/routes/pages/archive.tsx +2 -6
- package/src/routes/pages/collection.tsx +2 -6
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +44 -0
- package/src/routes/pages/home.tsx +30 -53
- package/src/routes/pages/latest.tsx +59 -0
- package/src/routes/pages/page.tsx +28 -30
- package/src/routes/pages/post.tsx +2 -5
- package/src/routes/pages/search.tsx +2 -6
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post.test.ts +114 -15
- package/src/services/page.ts +13 -1
- package/src/services/post.ts +58 -40
- package/src/services/search.ts +2 -2
- package/src/styles/components.css +0 -65
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +475 -0
- package/src/types/bindings.ts +30 -0
- package/src/types/config.ts +183 -0
- package/src/types/constants.ts +26 -0
- package/src/types/entities.ts +109 -0
- package/src/types/operations.ts +88 -0
- package/src/types/props.ts +115 -0
- package/src/types/views.ts +172 -0
- package/src/types.ts +8 -774
- package/src/ui/__tests__/font-themes.test.ts +34 -0
- package/src/{theme → ui}/color-themes.ts +34 -34
- package/src/ui/compose/ComposeDialog.tsx +414 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
- package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
- package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
- package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
- package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
- package/src/ui/dash/collections/CollectionForm.tsx +153 -0
- package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/dash/media/MediaListContent.tsx +201 -0
- package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
- package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
- package/src/ui/dash/settings/AccountContent.tsx +176 -0
- package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
- package/src/ui/dash/settings/GeneralContent.tsx +533 -0
- package/src/ui/dash/settings/SettingsNav.tsx +56 -0
- package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
- package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
- package/src/ui/feed/TimelineFeed.tsx +49 -0
- package/src/ui/feed/TimelineItem.tsx +45 -0
- package/src/ui/font-themes.ts +54 -0
- package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +164 -0
- package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
- package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
- package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
- package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
- package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
- package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
- package/src/ui/shared/__tests__/pagination.test.ts +46 -0
- package/src/ui/shared/index.ts +12 -0
- package/bin/jant.js +0 -185
- package/dist/lib/theme-components.js +0 -46
- package/dist/routes/dash/navigation.js +0 -289
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
- package/dist/themes/threads/index.js +0 -81
- package/dist/themes/threads/pages/HomePage.js +0 -25
- package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
- package/dist/themes/threads/timeline/TimelineItem.js +0 -36
- package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
- package/dist/themes/threads/timeline/groupByDate.js +0 -22
- package/dist/themes/threads/timeline/timelineMore.js +0 -107
- package/src/lib/__tests__/theme-components.test.ts +0 -105
- package/src/lib/theme-components.ts +0 -65
- package/src/routes/dash/navigation.tsx +0 -317
- package/src/theme/components/index.ts +0 -23
- package/src/theme/index.ts +0 -22
- package/src/theme/layouts/index.ts +0 -7
- package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
- package/src/themes/threads/index.ts +0 -100
- package/src/themes/threads/style.css +0 -336
- package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
- package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
- package/src/themes/threads/timeline/groupByDate.ts +0 -30
- package/src/themes/threads/timeline/timelineMore.tsx +0 -130
- /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
- /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
- /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
- /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
- /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
- /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
- /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Threads Theme - Timeline Feed
|
|
3
|
-
*
|
|
4
|
-
* Date-grouped posts separated by thin dividers.
|
|
5
|
-
* A centered date header appears above each group.
|
|
6
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
-
import { TimelineItem } from "./TimelineItem.js";
|
|
8
|
-
import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
|
|
9
|
-
import { TimelineLoadMore as DefaultTimelineLoadMore } from "./TimelineLoadMore.js";
|
|
10
|
-
import { groupByDate } from "./groupByDate.js";
|
|
11
|
-
export const TimelineFeed = ({ items, hasMore, nextCursor, theme })=>{
|
|
12
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
13
|
-
const ResolvedLoadMore = theme?.TimelineLoadMore ?? DefaultTimelineLoadMore;
|
|
14
|
-
const groups = groupByDate(items);
|
|
15
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
16
|
-
children: [
|
|
17
|
-
/*#__PURE__*/ _jsx("div", {
|
|
18
|
-
id: "timeline-feed",
|
|
19
|
-
children: groups.map((group)=>/*#__PURE__*/ _jsxs("div", {
|
|
20
|
-
class: "threads-card",
|
|
21
|
-
children: [
|
|
22
|
-
/*#__PURE__*/ _jsx("div", {
|
|
23
|
-
class: "threads-date-header",
|
|
24
|
-
children: /*#__PURE__*/ _jsx("span", {
|
|
25
|
-
children: group.label
|
|
26
|
-
})
|
|
27
|
-
}),
|
|
28
|
-
/*#__PURE__*/ _jsx("div", {
|
|
29
|
-
id: `date-items-${group.dateKey}`,
|
|
30
|
-
class: "flex flex-col",
|
|
31
|
-
children: group.items.map((item, i)=>/*#__PURE__*/ _jsxs("div", {
|
|
32
|
-
children: [
|
|
33
|
-
i > 0 && /*#__PURE__*/ _jsx("hr", {
|
|
34
|
-
class: "border-border my-5"
|
|
35
|
-
}),
|
|
36
|
-
item.threadPreview ? /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
|
|
37
|
-
rootPost: item.post,
|
|
38
|
-
previewReplies: item.threadPreview.replies,
|
|
39
|
-
totalReplyCount: item.threadPreview.totalReplyCount,
|
|
40
|
-
theme: theme
|
|
41
|
-
}) : /*#__PURE__*/ _jsx(TimelineItem, {
|
|
42
|
-
item: item,
|
|
43
|
-
theme: theme
|
|
44
|
-
})
|
|
45
|
-
]
|
|
46
|
-
}, item.post.id))
|
|
47
|
-
})
|
|
48
|
-
]
|
|
49
|
-
}, group.dateKey))
|
|
50
|
-
}),
|
|
51
|
-
hasMore && nextCursor && /*#__PURE__*/ _jsx(ResolvedLoadMore, {
|
|
52
|
-
nextCursor: nextCursor,
|
|
53
|
-
lastDate: groups.at(-1)?.dateKey,
|
|
54
|
-
theme: theme
|
|
55
|
-
})
|
|
56
|
-
]
|
|
57
|
-
});
|
|
58
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Threads Theme - Timeline Item
|
|
3
|
-
*
|
|
4
|
-
* Dispatches to the correct card component based on post format.
|
|
5
|
-
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
6
|
-
import { NoteCard } from "./NoteCard.js";
|
|
7
|
-
import { LinkCard } from "./LinkCard.js";
|
|
8
|
-
import { QuoteCard } from "./QuoteCard.js";
|
|
9
|
-
const CARD_MAP = {
|
|
10
|
-
note: NoteCard,
|
|
11
|
-
link: LinkCard,
|
|
12
|
-
quote: QuoteCard
|
|
13
|
-
};
|
|
14
|
-
const THEME_KEY_MAP = {
|
|
15
|
-
note: "NoteCard",
|
|
16
|
-
link: "LinkCard",
|
|
17
|
-
quote: "QuoteCard"
|
|
18
|
-
};
|
|
19
|
-
export const TimelineItem = ({ item, compact, cardOverride, theme })=>{
|
|
20
|
-
const themeKey = THEME_KEY_MAP[item.post.format];
|
|
21
|
-
const themeCard = theme?.[themeKey];
|
|
22
|
-
const Card = cardOverride ?? themeCard ?? CARD_MAP[item.post.format];
|
|
23
|
-
return /*#__PURE__*/ _jsx(Card, {
|
|
24
|
-
post: item.post,
|
|
25
|
-
compact: compact
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
export const TimelineItemFromPost = ({ post, compact, cardOverride, theme })=>{
|
|
29
|
-
const themeKey = THEME_KEY_MAP[post.format];
|
|
30
|
-
const themeCard = theme?.[themeKey];
|
|
31
|
-
const Card = cardOverride ?? themeCard ?? CARD_MAP[post.format];
|
|
32
|
-
return /*#__PURE__*/ _jsx(Card, {
|
|
33
|
-
post: post,
|
|
34
|
-
compact: compact
|
|
35
|
-
});
|
|
36
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Threads Theme - Timeline Load More
|
|
3
|
-
*
|
|
4
|
-
* Auto-loads more posts when scrolled into view.
|
|
5
|
-
* Passes `lastDate` to the server so it can merge date groups across pages.
|
|
6
|
-
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
7
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
-
export const TimelineLoadMore = ({ nextCursor, lastDate })=>{
|
|
9
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
|
-
const url = lastDate ? `/?cursor=${nextCursor}&lastDate=${lastDate}` : `/?cursor=${nextCursor}`;
|
|
11
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
12
|
-
id: "load-more-container",
|
|
13
|
-
class: "py-6 text-center",
|
|
14
|
-
"data-on-intersect__once": `@get('${url}')`,
|
|
15
|
-
children: /*#__PURE__*/ _jsx("span", {
|
|
16
|
-
class: "text-sm text-muted-foreground",
|
|
17
|
-
children: $__i18n._({
|
|
18
|
-
id: "Z3FXyt",
|
|
19
|
-
message: "Loading..."
|
|
20
|
-
})
|
|
21
|
-
})
|
|
22
|
-
});
|
|
23
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Groups timeline items by their publication date (YYYY-MM-DD).
|
|
3
|
-
*
|
|
4
|
-
* Shared between TimelineFeed (initial render) and timelineMore (SSE patches)
|
|
5
|
-
* so that both produce identical date group structure.
|
|
6
|
-
*/ export function groupByDate(items) {
|
|
7
|
-
const groups = [];
|
|
8
|
-
let current = null;
|
|
9
|
-
for (const item of items){
|
|
10
|
-
const dateKey = item.post.publishedAt.slice(0, 10);
|
|
11
|
-
if (!current || current.dateKey !== dateKey) {
|
|
12
|
-
current = {
|
|
13
|
-
dateKey,
|
|
14
|
-
label: item.post.publishedAtFormatted,
|
|
15
|
-
items: []
|
|
16
|
-
};
|
|
17
|
-
groups.push(current);
|
|
18
|
-
}
|
|
19
|
-
current.items.push(item);
|
|
20
|
-
}
|
|
21
|
-
return groups;
|
|
22
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Threads Theme - Timeline Load-More SSE Renderer
|
|
3
|
-
*
|
|
4
|
-
* Produces SSE DOM patches for incremental timeline loading.
|
|
5
|
-
* Uses date-grouped layout with threads-specific HTML (threads-card, threads-date-header)
|
|
6
|
-
* matching TimelineFeed's initial render exactly.
|
|
7
|
-
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
8
|
-
import { groupByDate } from "./groupByDate.js";
|
|
9
|
-
import { TimelineItem } from "./TimelineItem.js";
|
|
10
|
-
import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
|
|
11
|
-
import { TimelineLoadMore as DefaultTimelineLoadMore } from "./TimelineLoadMore.js";
|
|
12
|
-
function renderItem(item, props) {
|
|
13
|
-
const theme = props.theme;
|
|
14
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
15
|
-
if (item.threadPreview) {
|
|
16
|
-
return /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
|
|
17
|
-
rootPost: item.post,
|
|
18
|
-
previewReplies: item.threadPreview.replies,
|
|
19
|
-
totalReplyCount: item.threadPreview.totalReplyCount,
|
|
20
|
-
theme: theme
|
|
21
|
-
}).toString();
|
|
22
|
-
}
|
|
23
|
-
return /*#__PURE__*/ _jsx(TimelineItem, {
|
|
24
|
-
item: item,
|
|
25
|
-
theme: theme
|
|
26
|
-
}).toString();
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Renders SSE patches for the threads theme's load-more response.
|
|
30
|
-
*
|
|
31
|
-
* @param props - Timeline more props with items, pagination, and theme
|
|
32
|
-
* @returns Array of DOM patch instructions for the SSE stream
|
|
33
|
-
*/ export function timelineMore(props) {
|
|
34
|
-
const { items, lastDate, hasMore, nextCursor, theme } = props;
|
|
35
|
-
const patches = [];
|
|
36
|
-
const groups = groupByDate(items);
|
|
37
|
-
if (groups.length === 0) return patches;
|
|
38
|
-
const firstGroup = groups[0];
|
|
39
|
-
const isContinuation = lastDate === firstGroup.dateKey;
|
|
40
|
-
// Continuation items: append into the existing date group's container
|
|
41
|
-
if (isContinuation) {
|
|
42
|
-
const continuationHtml = firstGroup.items.map((item)=>{
|
|
43
|
-
const content = renderItem(item, props);
|
|
44
|
-
return `<div><hr class="border-border my-5"/>${content}</div>`;
|
|
45
|
-
}).join("");
|
|
46
|
-
if (continuationHtml) {
|
|
47
|
-
patches.push({
|
|
48
|
-
selector: `#date-items-${lastDate}`,
|
|
49
|
-
content: continuationHtml,
|
|
50
|
-
mode: "append"
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// New date groups: append to #timeline-feed
|
|
55
|
-
const newGroups = isContinuation ? groups.slice(1) : groups;
|
|
56
|
-
if (newGroups.length > 0) {
|
|
57
|
-
const newGroupsHtml = newGroups.map((group)=>{
|
|
58
|
-
const itemsHtml = group.items.map((item, i)=>{
|
|
59
|
-
const content = renderItem(item, props);
|
|
60
|
-
return i > 0 ? `<div><hr class="border-border my-5"/>${content}</div>` : `<div>${content}</div>`;
|
|
61
|
-
}).join("");
|
|
62
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
63
|
-
class: "threads-card",
|
|
64
|
-
children: [
|
|
65
|
-
/*#__PURE__*/ _jsx("div", {
|
|
66
|
-
class: "threads-date-header",
|
|
67
|
-
children: /*#__PURE__*/ _jsx("span", {
|
|
68
|
-
children: group.label
|
|
69
|
-
})
|
|
70
|
-
}),
|
|
71
|
-
/*#__PURE__*/ _jsx("div", {
|
|
72
|
-
id: `date-items-${group.dateKey}`,
|
|
73
|
-
class: "flex flex-col",
|
|
74
|
-
dangerouslySetInnerHTML: {
|
|
75
|
-
__html: itemsHtml
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
]
|
|
79
|
-
}).toString();
|
|
80
|
-
}).join("");
|
|
81
|
-
patches.push({
|
|
82
|
-
selector: "#timeline-feed",
|
|
83
|
-
content: newGroupsHtml,
|
|
84
|
-
mode: "append"
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
// Load-more button
|
|
88
|
-
const ResolvedLoadMore = theme?.TimelineLoadMore ?? DefaultTimelineLoadMore;
|
|
89
|
-
const lastGroupDate = groups.at(-1)?.dateKey;
|
|
90
|
-
if (hasMore && nextCursor) {
|
|
91
|
-
patches.push({
|
|
92
|
-
selector: "#load-more-container",
|
|
93
|
-
content: /*#__PURE__*/ _jsx(ResolvedLoadMore, {
|
|
94
|
-
nextCursor: nextCursor,
|
|
95
|
-
lastDate: lastGroupDate,
|
|
96
|
-
theme: theme
|
|
97
|
-
}).toString()
|
|
98
|
-
});
|
|
99
|
-
} else {
|
|
100
|
-
patches.push({
|
|
101
|
-
selector: "#load-more-container",
|
|
102
|
-
content: "",
|
|
103
|
-
mode: "remove"
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return patches;
|
|
107
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolveCardComponent, resolveComponent } from "../theme-components.js";
|
|
3
|
-
import type {
|
|
4
|
-
ThemeComponents,
|
|
5
|
-
TimelineCardProps,
|
|
6
|
-
ThreadPreviewProps,
|
|
7
|
-
TimelineFeedProps,
|
|
8
|
-
Format,
|
|
9
|
-
HomePageProps,
|
|
10
|
-
} from "../../types.js";
|
|
11
|
-
import type { FC } from "hono/jsx";
|
|
12
|
-
|
|
13
|
-
// Create simple mock components for testing (avoids importing .tsx files with i18n)
|
|
14
|
-
const MockNoteCard: FC<TimelineCardProps> = () => null;
|
|
15
|
-
const MockLinkCard: FC<TimelineCardProps> = () => null;
|
|
16
|
-
const MockQuoteCard: FC<TimelineCardProps> = () => null;
|
|
17
|
-
const MockThreadPreview: FC<ThreadPreviewProps> = () => null;
|
|
18
|
-
const MockTimelineFeed: FC<TimelineFeedProps> = () => null;
|
|
19
|
-
const MockHomePage: FC<HomePageProps> = () => null;
|
|
20
|
-
|
|
21
|
-
const DEFAULT_CARD_MAP: Record<Format, FC<TimelineCardProps>> = {
|
|
22
|
-
note: MockNoteCard,
|
|
23
|
-
link: MockLinkCard,
|
|
24
|
-
quote: MockQuoteCard,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
describe("theme-components", () => {
|
|
28
|
-
describe("resolveCardComponent", () => {
|
|
29
|
-
it("returns default NoteCard for note type", () => {
|
|
30
|
-
expect(resolveCardComponent("note", DEFAULT_CARD_MAP)).toBe(MockNoteCard);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("returns default LinkCard for link type", () => {
|
|
34
|
-
expect(resolveCardComponent("link", DEFAULT_CARD_MAP)).toBe(MockLinkCard);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("returns default QuoteCard for quote type", () => {
|
|
38
|
-
expect(resolveCardComponent("quote", DEFAULT_CARD_MAP)).toBe(
|
|
39
|
-
MockQuoteCard,
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("returns theme override when provided", () => {
|
|
44
|
-
const CustomNote: FC<TimelineCardProps> = () => null;
|
|
45
|
-
const overrides: ThemeComponents = { NoteCard: CustomNote };
|
|
46
|
-
expect(resolveCardComponent("note", DEFAULT_CARD_MAP, overrides)).toBe(
|
|
47
|
-
CustomNote,
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("returns default when theme has no override for type", () => {
|
|
52
|
-
const overrides: ThemeComponents = {};
|
|
53
|
-
expect(resolveCardComponent("link", DEFAULT_CARD_MAP, overrides)).toBe(
|
|
54
|
-
MockLinkCard,
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("resolveComponent", () => {
|
|
60
|
-
it("returns default ThreadPreview when no override", () => {
|
|
61
|
-
expect(resolveComponent("ThreadPreview", MockThreadPreview)).toBe(
|
|
62
|
-
MockThreadPreview,
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("returns theme override for ThreadPreview when provided", () => {
|
|
67
|
-
const Custom: FC<ThreadPreviewProps> = () => null;
|
|
68
|
-
expect(
|
|
69
|
-
resolveComponent("ThreadPreview", MockThreadPreview, {
|
|
70
|
-
ThreadPreview: Custom,
|
|
71
|
-
}),
|
|
72
|
-
).toBe(Custom);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("returns default TimelineFeed when no override", () => {
|
|
76
|
-
expect(resolveComponent("TimelineFeed", MockTimelineFeed)).toBe(
|
|
77
|
-
MockTimelineFeed,
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("returns theme override for TimelineFeed when provided", () => {
|
|
82
|
-
const Custom: FC<TimelineFeedProps> = () => null;
|
|
83
|
-
expect(
|
|
84
|
-
resolveComponent("TimelineFeed", MockTimelineFeed, {
|
|
85
|
-
TimelineFeed: Custom,
|
|
86
|
-
}),
|
|
87
|
-
).toBe(Custom);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("returns default HomePage when no override", () => {
|
|
91
|
-
expect(resolveComponent("HomePage", MockHomePage)).toBe(MockHomePage);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("returns theme override for HomePage when provided", () => {
|
|
95
|
-
const Custom: FC<HomePageProps> = () => null;
|
|
96
|
-
expect(
|
|
97
|
-
resolveComponent("HomePage", MockHomePage, { HomePage: Custom }),
|
|
98
|
-
).toBe(Custom);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("returns default when theme has empty overrides", () => {
|
|
102
|
-
expect(resolveComponent("HomePage", MockHomePage, {})).toBe(MockHomePage);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Component Resolution
|
|
3
|
-
*
|
|
4
|
-
* Resolves theme-overridable components, falling back to defaults.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { Format, ThemeComponents, TimelineCardProps } from "../types.js";
|
|
9
|
-
|
|
10
|
-
const THEME_KEY_MAP: Record<Format, keyof ThemeComponents> = {
|
|
11
|
-
note: "NoteCard",
|
|
12
|
-
link: "LinkCard",
|
|
13
|
-
quote: "QuoteCard",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Generic component resolver.
|
|
18
|
-
*
|
|
19
|
-
* Looks up a component by key in `ThemeComponents` and falls back to the
|
|
20
|
-
* provided default component.
|
|
21
|
-
*
|
|
22
|
-
* @param key - ThemeComponents key to look up
|
|
23
|
-
* @param defaultComponent - Fallback component
|
|
24
|
-
* @param themeComponents - Optional theme component overrides
|
|
25
|
-
* @returns The resolved component
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const Gallery = resolveComponent("MediaGallery", DefaultMediaGallery, theme);
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export function resolveComponent<K extends keyof ThemeComponents>(
|
|
33
|
-
key: K,
|
|
34
|
-
defaultComponent: NonNullable<ThemeComponents[K]>,
|
|
35
|
-
themeComponents?: ThemeComponents,
|
|
36
|
-
): NonNullable<ThemeComponents[K]> {
|
|
37
|
-
return (themeComponents?.[key] ?? defaultComponent) as NonNullable<
|
|
38
|
-
ThemeComponents[K]
|
|
39
|
-
>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Resolves the card component for a given post format.
|
|
44
|
-
*
|
|
45
|
-
* Checks theme overrides first, then falls back to the provided default card component.
|
|
46
|
-
*
|
|
47
|
-
* @param format - The post format to resolve a card for
|
|
48
|
-
* @param defaults - Map of format to default card component
|
|
49
|
-
* @param themeComponents - Optional theme component overrides
|
|
50
|
-
* @returns The resolved card component
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* const Card = resolveCardComponent("note", DEFAULT_CARD_MAP, c.var.config.theme?.components);
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export function resolveCardComponent(
|
|
58
|
-
format: Format,
|
|
59
|
-
defaults: Record<Format, FC<TimelineCardProps>>,
|
|
60
|
-
themeComponents?: ThemeComponents,
|
|
61
|
-
): FC<TimelineCardProps> {
|
|
62
|
-
const key = THEME_KEY_MAP[format];
|
|
63
|
-
const override = themeComponents?.[key] as FC<TimelineCardProps> | undefined;
|
|
64
|
-
return override ?? defaults[format];
|
|
65
|
-
}
|