@jant/core 0.3.23 → 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 -26
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +5 -11
- package/dist/lib/constants.js +2 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +30 -6
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +7 -11
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme.js +4 -4
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +95 -0
- package/dist/lib/view.js +61 -72
- 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 +27 -33
- package/dist/routes/api/search.js +4 -5
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -42
- package/dist/routes/dash/index.js +3 -3
- package/dist/routes/dash/media.js +2 -2
- package/dist/routes/dash/pages.js +440 -106
- package/dist/routes/dash/posts.js +27 -37
- package/dist/routes/dash/redirects.js +2 -2
- package/dist/routes/dash/settings.js +79 -5
- package/dist/routes/feed/rss.js +4 -6
- package/dist/routes/feed/sitemap.js +11 -8
- package/dist/routes/pages/archive.js +13 -15
- package/dist/routes/pages/collection.js +12 -9
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +32 -0
- package/dist/routes/pages/home.js +19 -68
- package/dist/routes/pages/page.js +57 -29
- package/dist/routes/pages/post.js +7 -17
- package/dist/routes/pages/search.js +5 -9
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +84 -0
- package/dist/services/post.js +102 -69
- package/dist/services/search.js +24 -18
- package/dist/types.js +24 -40
- 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} +3 -15
- package/dist/{theme/components → ui/dash}/PageForm.js +15 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +117 -137
- package/dist/{theme/components → ui/dash}/PostList.js +18 -13
- package/dist/ui/dash/StatusBadge.js +46 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/feed/LinkCard.js +72 -0
- package/dist/ui/feed/NoteCard.js +58 -0
- package/dist/{themes/minimal/timeline → ui/feed}/QuoteCard.js +29 -14
- package/dist/{themes/minimal/timeline → ui/feed}/ThreadPreview.js +20 -18
- 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/minimal → ui}/pages/ArchivePage.js +37 -50
- package/dist/ui/pages/CollectionPage.js +70 -0
- 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/minimal → ui}/pages/PostPage.js +20 -12
- package/dist/{themes/minimal → ui}/pages/SearchPage.js +19 -18
- package/dist/{themes/minimal → ui}/pages/SinglePage.js +5 -4
- package/dist/ui/shared/MediaGallery.js +35 -0
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +3 -3
- package/dist/ui/shared/index.js +5 -0
- package/package.json +2 -9
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +53 -73
- package/src/app.tsx +56 -28
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +443 -240
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +443 -240
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +443 -240
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +29 -42
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +201 -99
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +81 -75
- package/src/lib/__tests__/view.test.ts +204 -50
- package/src/lib/constants.ts +2 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +45 -8
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +7 -14
- package/src/lib/schemas.ts +119 -51
- package/src/lib/theme.ts +5 -5
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +141 -0
- package/src/lib/view.ts +80 -82
- package/src/preset.css +46 -0
- 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__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- 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 +28 -28
- package/src/routes/api/search.ts +3 -3
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/collections.tsx +20 -42
- package/src/routes/dash/index.tsx +3 -3
- package/src/routes/dash/media.tsx +2 -2
- package/src/routes/dash/pages.tsx +480 -122
- package/src/routes/dash/posts.tsx +42 -54
- package/src/routes/dash/redirects.tsx +2 -2
- package/src/routes/dash/settings.tsx +83 -5
- package/src/routes/feed/rss.ts +4 -3
- package/src/routes/feed/sitemap.ts +15 -5
- 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 +15 -15
- package/src/routes/pages/collection.tsx +16 -9
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +38 -0
- package/src/routes/pages/home.tsx +21 -92
- package/src/routes/pages/page.tsx +62 -27
- package/src/routes/pages/post.tsx +6 -18
- package/src/routes/pages/search.tsx +3 -7
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +432 -197
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +136 -0
- package/src/services/post.ts +141 -101
- package/src/services/search.ts +38 -27
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +491 -0
- package/src/types.ts +212 -198
- package/src/ui/compose/ComposeDialog.tsx +395 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/ui/dash/FormatBadge.tsx +28 -0
- package/src/{theme/components → ui/dash}/PageForm.tsx +21 -21
- package/src/{theme/components → ui/dash}/PostForm.tsx +110 -131
- package/src/ui/dash/PostList.tsx +101 -0
- package/src/ui/dash/StatusBadge.tsx +61 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/feed/LinkCard.tsx +72 -0
- package/src/ui/feed/NoteCard.tsx +63 -0
- package/src/ui/feed/QuoteCard.tsx +68 -0
- package/src/ui/feed/ThreadPreview.tsx +48 -0
- 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/ui/pages/ArchivePage.tsx +162 -0
- package/src/ui/pages/CollectionPage.tsx +70 -0
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/ui/pages/HomePage.tsx +37 -0
- package/src/ui/pages/PostPage.tsx +56 -0
- package/src/{themes/minimal → ui}/pages/SearchPage.tsx +24 -20
- package/src/{themes/minimal → ui}/pages/SinglePage.tsx +5 -5
- package/src/ui/shared/MediaGallery.tsx +59 -0
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +6 -3
- 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 -49
- package/dist/routes/api/timeline.js +0 -120
- package/dist/routes/dash/navigation.js +0 -288
- package/dist/theme/components/MediaGallery.js +0 -107
- package/dist/theme/components/VisibilityBadge.js +0 -37
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/pages/HomePage.js +0 -25
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/dist/themes/minimal/timeline/TimelineItem.js +0 -44
- package/src/lib/__tests__/theme-components.test.ts +0 -126
- package/src/lib/theme-components.ts +0 -68
- package/src/routes/api/timeline.tsx +0 -159
- package/src/routes/dash/navigation.tsx +0 -316
- package/src/theme/components/MediaGallery.tsx +0 -128
- package/src/theme/components/PostList.tsx +0 -92
- package/src/theme/components/TypeBadge.tsx +0 -37
- package/src/theme/components/VisibilityBadge.tsx +0 -45
- 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/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/index.ts +0 -83
- package/src/themes/minimal/pages/ArchivePage.tsx +0 -157
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/pages/HomePage.tsx +0 -41
- package/src/themes/minimal/pages/PostPage.tsx +0 -43
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/ThreadPreview.tsx +0 -47
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
- package/src/themes/minimal/timeline/TimelineItem.tsx +0 -75
- /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/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/shared}/EmptyState.tsx +0 -0
package/bin/jant.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Jant CLI
|
|
5
|
-
*
|
|
6
|
-
* Commands:
|
|
7
|
-
* swizzle <component> [--wrap|--eject] - Override a theme component
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
11
|
-
import { resolve } from "path";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// Available components that can be swizzled
|
|
15
|
-
const SWIZZLABLE_COMPONENTS = {
|
|
16
|
-
PostCard: {
|
|
17
|
-
file: "PostCard.tsx",
|
|
18
|
-
props: "PostCardProps",
|
|
19
|
-
},
|
|
20
|
-
PostList: {
|
|
21
|
-
file: "PostList.tsx",
|
|
22
|
-
props: "PostListProps",
|
|
23
|
-
},
|
|
24
|
-
Pagination: {
|
|
25
|
-
file: "Pagination.tsx",
|
|
26
|
-
props: "PaginationProps",
|
|
27
|
-
},
|
|
28
|
-
EmptyState: {
|
|
29
|
-
file: "EmptyState.tsx",
|
|
30
|
-
props: "EmptyStateProps",
|
|
31
|
-
},
|
|
32
|
-
BaseLayout: {
|
|
33
|
-
file: "BaseLayout.tsx",
|
|
34
|
-
props: "BaseLayoutProps",
|
|
35
|
-
isLayout: true,
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
function showHelp() {
|
|
40
|
-
console.log(`
|
|
41
|
-
Jant CLI
|
|
42
|
-
|
|
43
|
-
Usage:
|
|
44
|
-
jant swizzle <component> [options]
|
|
45
|
-
|
|
46
|
-
Commands:
|
|
47
|
-
swizzle <component> Override a theme component
|
|
48
|
-
|
|
49
|
-
Options:
|
|
50
|
-
--wrap Create a wrapper around the original component (default)
|
|
51
|
-
--eject Copy the full component source for complete customization
|
|
52
|
-
--list List available components
|
|
53
|
-
|
|
54
|
-
Examples:
|
|
55
|
-
jant swizzle PostCard # Wrap PostCard component
|
|
56
|
-
jant swizzle PostCard --eject # Copy PostCard source
|
|
57
|
-
jant swizzle --list # List all swizzlable components
|
|
58
|
-
`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function listComponents() {
|
|
62
|
-
console.log("\nAvailable components to swizzle:\n");
|
|
63
|
-
for (const [name, info] of Object.entries(SWIZZLABLE_COMPONENTS)) {
|
|
64
|
-
const type = info.isLayout ? "[Layout]" : "[Component]";
|
|
65
|
-
console.log(` ${name.padEnd(15)} ${type}`);
|
|
66
|
-
}
|
|
67
|
-
console.log("\nUsage: jant swizzle <component> [--wrap|--eject]\n");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function generateWrapperCode(componentName, info) {
|
|
71
|
-
const importPath = info.isLayout
|
|
72
|
-
? "@jant/core/theme/layouts"
|
|
73
|
-
: "@jant/core/theme/components";
|
|
74
|
-
|
|
75
|
-
return `/**
|
|
76
|
-
* Custom ${componentName} component
|
|
77
|
-
*
|
|
78
|
-
* This is a wrapper around the original ${componentName}.
|
|
79
|
-
* You can customize the rendering while keeping the original functionality.
|
|
80
|
-
*/
|
|
81
|
-
|
|
82
|
-
import type { ${info.props} } from "@jant/core";
|
|
83
|
-
import { ${componentName} as Original${componentName} } from "${importPath}";
|
|
84
|
-
|
|
85
|
-
export function ${componentName}(props: ${info.props}) {
|
|
86
|
-
// Add your customizations here
|
|
87
|
-
return (
|
|
88
|
-
<div class="custom-${componentName.toLowerCase()}-wrapper">
|
|
89
|
-
<Original${componentName} {...props} />
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function swizzle(componentName, mode) {
|
|
97
|
-
const info = SWIZZLABLE_COMPONENTS[componentName];
|
|
98
|
-
if (!info) {
|
|
99
|
-
console.error(`Error: Unknown component "${componentName}"`);
|
|
100
|
-
console.log("\nAvailable components:");
|
|
101
|
-
listComponents();
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const targetDir = info.isLayout
|
|
106
|
-
? resolve(process.cwd(), "src/theme/layouts")
|
|
107
|
-
: resolve(process.cwd(), "src/theme/components");
|
|
108
|
-
|
|
109
|
-
const targetFile = resolve(targetDir, info.file);
|
|
110
|
-
|
|
111
|
-
// Check if file already exists
|
|
112
|
-
if (existsSync(targetFile)) {
|
|
113
|
-
console.error(`Error: ${targetFile} already exists`);
|
|
114
|
-
console.log("Remove it first if you want to re-swizzle.");
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Create directory if needed
|
|
119
|
-
mkdirSync(targetDir, { recursive: true });
|
|
120
|
-
|
|
121
|
-
if (mode === "eject") {
|
|
122
|
-
// For eject mode, we'd need to copy the actual source
|
|
123
|
-
// For now, show a message about where to find it
|
|
124
|
-
console.log(`
|
|
125
|
-
To eject ${componentName}, copy the source from:
|
|
126
|
-
node_modules/@jant/core/src/theme/${info.isLayout ? "layouts" : "components"}/${info.file}
|
|
127
|
-
|
|
128
|
-
Then modify it as needed.
|
|
129
|
-
`);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Generate wrapper code
|
|
134
|
-
const code = generateWrapperCode(componentName, info);
|
|
135
|
-
writeFileSync(targetFile, code, "utf-8");
|
|
136
|
-
|
|
137
|
-
console.log(`
|
|
138
|
-
✓ Created ${targetFile}
|
|
139
|
-
|
|
140
|
-
Next steps:
|
|
141
|
-
1. Customize the component in the generated file
|
|
142
|
-
2. Import it in your src/index.ts:
|
|
143
|
-
|
|
144
|
-
import { ${componentName} } from "./theme/${info.isLayout ? "layouts" : "components"}/${componentName}";
|
|
145
|
-
|
|
146
|
-
export default createApp({
|
|
147
|
-
theme: {
|
|
148
|
-
components: {
|
|
149
|
-
${componentName},
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Parse arguments
|
|
157
|
-
const args = process.argv.slice(2);
|
|
158
|
-
|
|
159
|
-
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
160
|
-
showHelp();
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const command = args[0];
|
|
165
|
-
|
|
166
|
-
if (command === "swizzle") {
|
|
167
|
-
if (args.includes("--list")) {
|
|
168
|
-
listComponents();
|
|
169
|
-
process.exit(0);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const componentName = args[1];
|
|
173
|
-
if (!componentName) {
|
|
174
|
-
console.error("Error: Component name required");
|
|
175
|
-
console.log("Usage: jant swizzle <component> [--wrap|--eject]");
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const mode = args.includes("--eject") ? "eject" : "wrap";
|
|
180
|
-
swizzle(componentName, mode);
|
|
181
|
-
} else {
|
|
182
|
-
console.error(`Unknown command: ${command}`);
|
|
183
|
-
showHelp();
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Component Resolution
|
|
3
|
-
*
|
|
4
|
-
* Resolves theme-overridable components, falling back to defaults.
|
|
5
|
-
*/ const THEME_KEY_MAP = {
|
|
6
|
-
note: "NoteCard",
|
|
7
|
-
article: "ArticleCard",
|
|
8
|
-
link: "LinkCard",
|
|
9
|
-
quote: "QuoteCard",
|
|
10
|
-
image: "ImageCard",
|
|
11
|
-
page: "NoteCard"
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Generic component resolver.
|
|
15
|
-
*
|
|
16
|
-
* Looks up a component by key in `ThemeComponents` and falls back to the
|
|
17
|
-
* provided default component.
|
|
18
|
-
*
|
|
19
|
-
* @param key - ThemeComponents key to look up
|
|
20
|
-
* @param defaultComponent - Fallback component
|
|
21
|
-
* @param themeComponents - Optional theme component overrides
|
|
22
|
-
* @returns The resolved component
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* const Gallery = resolveComponent("MediaGallery", DefaultMediaGallery, theme);
|
|
27
|
-
* ```
|
|
28
|
-
*/ export function resolveComponent(key, defaultComponent, themeComponents) {
|
|
29
|
-
return themeComponents?.[key] ?? defaultComponent;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Resolves the card component for a given post type.
|
|
33
|
-
*
|
|
34
|
-
* Checks theme overrides first, then falls back to the provided default card component.
|
|
35
|
-
*
|
|
36
|
-
* @param type - The post type to resolve a card for
|
|
37
|
-
* @param defaults - Map of post type to default card component
|
|
38
|
-
* @param themeComponents - Optional theme component overrides
|
|
39
|
-
* @returns The resolved card component
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```ts
|
|
43
|
-
* const Card = resolveCardComponent("article", DEFAULT_CARD_MAP, c.var.config.theme?.components);
|
|
44
|
-
* ```
|
|
45
|
-
*/ export function resolveCardComponent(type, defaults, themeComponents) {
|
|
46
|
-
const key = THEME_KEY_MAP[type];
|
|
47
|
-
const override = themeComponents?.[key];
|
|
48
|
-
return override ?? defaults[type];
|
|
49
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Timeline API Routes
|
|
4
|
-
*
|
|
5
|
-
* Provides load-more functionality for the timeline feed via SSE.
|
|
6
|
-
*/ import { Hono } from "hono";
|
|
7
|
-
import { sse } from "../../lib/sse.js";
|
|
8
|
-
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
9
|
-
import { TimelineItem } from "../../themes/minimal/timeline/TimelineItem.js";
|
|
10
|
-
import { ThreadPreview as DefaultThreadPreview } from "../../themes/minimal/timeline/ThreadPreview.js";
|
|
11
|
-
import { createMediaContext, toPostView, toPostViews } from "../../lib/view.js";
|
|
12
|
-
const PAGE_SIZE = 20;
|
|
13
|
-
export const timelineApiRoutes = new Hono();
|
|
14
|
-
timelineApiRoutes.get("/", async (c)=>{
|
|
15
|
-
const cursorParam = c.req.query("cursor");
|
|
16
|
-
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
17
|
-
if (!cursor || isNaN(cursor)) {
|
|
18
|
-
return c.json({
|
|
19
|
-
error: "cursor parameter required"
|
|
20
|
-
}, 400);
|
|
21
|
-
}
|
|
22
|
-
// Fetch one extra to determine if there are more
|
|
23
|
-
const posts = await c.var.services.posts.list({
|
|
24
|
-
visibility: [
|
|
25
|
-
"featured",
|
|
26
|
-
"quiet"
|
|
27
|
-
],
|
|
28
|
-
excludeReplies: true,
|
|
29
|
-
excludeTypes: [
|
|
30
|
-
"page"
|
|
31
|
-
],
|
|
32
|
-
limit: PAGE_SIZE + 1,
|
|
33
|
-
cursor
|
|
34
|
-
});
|
|
35
|
-
const hasMore = posts.length > PAGE_SIZE;
|
|
36
|
-
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
37
|
-
if (displayPosts.length === 0) {
|
|
38
|
-
return sse(c, async (stream)=>{
|
|
39
|
-
stream.remove("#load-more-container");
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
// Build media map
|
|
43
|
-
const postIds = displayPosts.map((p)=>p.id);
|
|
44
|
-
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
45
|
-
const mediaCtx = createMediaContext(c);
|
|
46
|
-
const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
|
|
47
|
-
// Get reply counts to identify thread roots
|
|
48
|
-
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
49
|
-
const threadRootIds = postIds.filter((id)=>(replyCounts.get(id) ?? 0) > 0);
|
|
50
|
-
// Get thread previews
|
|
51
|
-
const threadPreviews = await c.var.services.posts.getThreadPreviews(threadRootIds, 3);
|
|
52
|
-
// Load media for preview replies
|
|
53
|
-
const previewReplyIds = [];
|
|
54
|
-
for (const replies of threadPreviews.values()){
|
|
55
|
-
for (const reply of replies){
|
|
56
|
-
previewReplyIds.push(reply.id);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const previewMediaMap = previewReplyIds.length > 0 ? buildMediaMap(await c.var.services.media.getByPostIds(previewReplyIds), mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl) : new Map();
|
|
60
|
-
// Assemble timeline items with View Models
|
|
61
|
-
const items = displayPosts.map((post)=>{
|
|
62
|
-
const postView = toPostView({
|
|
63
|
-
...post,
|
|
64
|
-
mediaAttachments: mediaMap.get(post.id) ?? []
|
|
65
|
-
}, mediaCtx);
|
|
66
|
-
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
67
|
-
const previewReplies = threadPreviews.get(post.id);
|
|
68
|
-
if (replyCount > 0 && previewReplies) {
|
|
69
|
-
return {
|
|
70
|
-
post: postView,
|
|
71
|
-
threadPreview: {
|
|
72
|
-
replies: toPostViews(previewReplies.map((r)=>({
|
|
73
|
-
...r,
|
|
74
|
-
mediaAttachments: previewMediaMap.get(r.id) ?? []
|
|
75
|
-
})), mediaCtx),
|
|
76
|
-
totalReplyCount: replyCount
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
post: postView
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
// Resolve theme components for card rendering
|
|
85
|
-
const theme = c.var.config.theme?.components;
|
|
86
|
-
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
87
|
-
// Render items to HTML
|
|
88
|
-
const itemsHtml = items.map((item)=>{
|
|
89
|
-
if (item.threadPreview) {
|
|
90
|
-
return /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
|
|
91
|
-
rootPost: item.post,
|
|
92
|
-
previewReplies: item.threadPreview.replies,
|
|
93
|
-
totalReplyCount: item.threadPreview.totalReplyCount,
|
|
94
|
-
theme: theme
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return /*#__PURE__*/ _jsx(TimelineItem, {
|
|
98
|
-
item: item,
|
|
99
|
-
theme: theme
|
|
100
|
-
});
|
|
101
|
-
}).map((jsx)=>jsx.toString()).join("");
|
|
102
|
-
// Determine next cursor
|
|
103
|
-
const lastPost = displayPosts[displayPosts.length - 1];
|
|
104
|
-
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
105
|
-
// Build load-more button HTML
|
|
106
|
-
const loadMoreHtml = nextCursor ? `<div id="load-more-container" class="mt-8 text-center"><button class="text-sm text-muted-foreground hover:text-foreground hover:underline" data-on:click="@get('/api/timeline?cursor=${nextCursor}')">Load more</button></div>` : "";
|
|
107
|
-
return sse(c, async (stream)=>{
|
|
108
|
-
// Append new items to the feed
|
|
109
|
-
stream.patchElements(itemsHtml, {
|
|
110
|
-
mode: "append",
|
|
111
|
-
selector: "#timeline-feed"
|
|
112
|
-
});
|
|
113
|
-
// Replace or remove the load-more container
|
|
114
|
-
if (loadMoreHtml) {
|
|
115
|
-
stream.patchElements(loadMoreHtml);
|
|
116
|
-
} else {
|
|
117
|
-
stream.remove("#load-more-container");
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
2
|
-
import { getSiteName } from "../../lib/config.js";
|
|
3
|
-
/**
|
|
4
|
-
* Dashboard Navigation Links Routes
|
|
5
|
-
*/ import { Hono } from "hono";
|
|
6
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { DashLayout } from "../../theme/layouts/index.js";
|
|
8
|
-
import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader } from "../../theme/components/index.js";
|
|
9
|
-
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
10
|
-
export const navigationRoutes = new Hono();
|
|
11
|
-
function NavigationListContent({ links }) {
|
|
12
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
|
-
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
14
|
-
children: [
|
|
15
|
-
/*#__PURE__*/ _jsx(CrudPageHeader, {
|
|
16
|
-
title: $__i18n._({
|
|
17
|
-
id: "UxKoFf",
|
|
18
|
-
message: "Navigation"
|
|
19
|
-
}),
|
|
20
|
-
ctaLabel: $__i18n._({
|
|
21
|
-
id: "aaGV/9",
|
|
22
|
-
message: "New Link"
|
|
23
|
-
}),
|
|
24
|
-
ctaHref: "/dash/navigation/new"
|
|
25
|
-
}),
|
|
26
|
-
links.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
|
|
27
|
-
message: $__i18n._({
|
|
28
|
-
id: "wdGjkd",
|
|
29
|
-
message: "No navigation links configured."
|
|
30
|
-
}),
|
|
31
|
-
ctaText: $__i18n._({
|
|
32
|
-
id: "aaGV/9",
|
|
33
|
-
message: "New Link"
|
|
34
|
-
}),
|
|
35
|
-
ctaHref: "/dash/navigation/new"
|
|
36
|
-
}) : /*#__PURE__*/ _jsx(_Fragment, {
|
|
37
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
38
|
-
id: "nav-links-list",
|
|
39
|
-
class: "flex flex-col divide-y",
|
|
40
|
-
children: links.map((link)=>/*#__PURE__*/ _jsx(ListItemRow, {
|
|
41
|
-
actions: /*#__PURE__*/ _jsx(ActionButtons, {
|
|
42
|
-
editHref: `/dash/navigation/${link.id}/edit`,
|
|
43
|
-
editLabel: $__i18n._({
|
|
44
|
-
id: "ePK91l",
|
|
45
|
-
message: "Edit"
|
|
46
|
-
}),
|
|
47
|
-
deleteAction: `/dash/navigation/${link.id}/delete`,
|
|
48
|
-
deleteLabel: $__i18n._({
|
|
49
|
-
id: "cnGeoo",
|
|
50
|
-
message: "Delete"
|
|
51
|
-
})
|
|
52
|
-
}),
|
|
53
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
54
|
-
class: "flex items-center gap-3 cursor-grab",
|
|
55
|
-
"data-id": link.id,
|
|
56
|
-
children: [
|
|
57
|
-
/*#__PURE__*/ _jsx("span", {
|
|
58
|
-
class: "text-muted-foreground select-none",
|
|
59
|
-
children: "⠿"
|
|
60
|
-
}),
|
|
61
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
62
|
-
class: "flex items-center gap-2",
|
|
63
|
-
children: [
|
|
64
|
-
/*#__PURE__*/ _jsx("span", {
|
|
65
|
-
class: "font-medium",
|
|
66
|
-
children: link.label
|
|
67
|
-
}),
|
|
68
|
-
/*#__PURE__*/ _jsx("code", {
|
|
69
|
-
class: "text-sm text-muted-foreground bg-muted px-1 rounded",
|
|
70
|
-
children: link.url
|
|
71
|
-
})
|
|
72
|
-
]
|
|
73
|
-
})
|
|
74
|
-
]
|
|
75
|
-
})
|
|
76
|
-
}, link.id))
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
]
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
function NavigationFormContent({ link, isEdit }) {
|
|
83
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
84
|
-
const title = isEdit ? $__i18n._({
|
|
85
|
-
id: "gDx5MG",
|
|
86
|
-
message: "Edit Link"
|
|
87
|
-
}) : $__i18n._({
|
|
88
|
-
id: "aaGV/9",
|
|
89
|
-
message: "New Link"
|
|
90
|
-
});
|
|
91
|
-
const signals = JSON.stringify({
|
|
92
|
-
label: link?.label ?? "",
|
|
93
|
-
url: link?.url ?? ""
|
|
94
|
-
}).replace(/</g, "\\u003c");
|
|
95
|
-
const action = isEdit ? `/dash/navigation/${link?.id}` : "/dash/navigation";
|
|
96
|
-
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
97
|
-
children: [
|
|
98
|
-
/*#__PURE__*/ _jsx("h1", {
|
|
99
|
-
class: "text-2xl font-semibold mb-6",
|
|
100
|
-
children: title
|
|
101
|
-
}),
|
|
102
|
-
/*#__PURE__*/ _jsxs("form", {
|
|
103
|
-
"data-signals": signals,
|
|
104
|
-
"data-on:submit__prevent": `@post('${action}')`,
|
|
105
|
-
"data-indicator": "_loading",
|
|
106
|
-
class: "flex flex-col gap-4 max-w-lg",
|
|
107
|
-
children: [
|
|
108
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
109
|
-
class: "field",
|
|
110
|
-
children: [
|
|
111
|
-
/*#__PURE__*/ _jsx("label", {
|
|
112
|
-
class: "label",
|
|
113
|
-
children: $__i18n._({
|
|
114
|
-
id: "87a/t/",
|
|
115
|
-
message: "Label"
|
|
116
|
-
})
|
|
117
|
-
}),
|
|
118
|
-
/*#__PURE__*/ _jsx("input", {
|
|
119
|
-
type: "text",
|
|
120
|
-
"data-bind": "label",
|
|
121
|
-
class: "input",
|
|
122
|
-
placeholder: "Home",
|
|
123
|
-
required: true
|
|
124
|
-
}),
|
|
125
|
-
/*#__PURE__*/ _jsx("p", {
|
|
126
|
-
class: "text-xs text-muted-foreground mt-1",
|
|
127
|
-
children: $__i18n._({
|
|
128
|
-
id: "+bHzpy",
|
|
129
|
-
message: "Display text for the link"
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
]
|
|
133
|
-
}),
|
|
134
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
135
|
-
class: "field",
|
|
136
|
-
children: [
|
|
137
|
-
/*#__PURE__*/ _jsx("label", {
|
|
138
|
-
class: "label",
|
|
139
|
-
children: $__i18n._({
|
|
140
|
-
id: "IagCbF",
|
|
141
|
-
message: "URL"
|
|
142
|
-
})
|
|
143
|
-
}),
|
|
144
|
-
/*#__PURE__*/ _jsx("input", {
|
|
145
|
-
type: "text",
|
|
146
|
-
"data-bind": "url",
|
|
147
|
-
class: "input",
|
|
148
|
-
placeholder: "/archive or https://...",
|
|
149
|
-
required: true
|
|
150
|
-
}),
|
|
151
|
-
/*#__PURE__*/ _jsx("p", {
|
|
152
|
-
class: "text-xs text-muted-foreground mt-1",
|
|
153
|
-
children: $__i18n._({
|
|
154
|
-
id: "QEbNBb",
|
|
155
|
-
message: "Path (e.g. /archive) or full URL (e.g. https://example.com)"
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
]
|
|
159
|
-
}),
|
|
160
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
161
|
-
class: "flex gap-2",
|
|
162
|
-
children: [
|
|
163
|
-
/*#__PURE__*/ _jsxs("button", {
|
|
164
|
-
type: "submit",
|
|
165
|
-
class: "btn",
|
|
166
|
-
"data-attr-disabled": "$_loading",
|
|
167
|
-
children: [
|
|
168
|
-
/*#__PURE__*/ _jsx("span", {
|
|
169
|
-
"data-show": "!$_loading",
|
|
170
|
-
children: isEdit ? $__i18n._({
|
|
171
|
-
id: "IUwGEM",
|
|
172
|
-
message: "Save Changes"
|
|
173
|
-
}) : $__i18n._({
|
|
174
|
-
id: "kd7eBB",
|
|
175
|
-
message: "Create Link"
|
|
176
|
-
})
|
|
177
|
-
}),
|
|
178
|
-
/*#__PURE__*/ _jsx("span", {
|
|
179
|
-
"data-show": "$_loading",
|
|
180
|
-
children: $__i18n._({
|
|
181
|
-
id: "k1ifdL",
|
|
182
|
-
message: "Processing..."
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
]
|
|
186
|
-
}),
|
|
187
|
-
/*#__PURE__*/ _jsx("a", {
|
|
188
|
-
href: "/dash/navigation",
|
|
189
|
-
class: "btn-outline",
|
|
190
|
-
children: $__i18n._({
|
|
191
|
-
id: "dEgA5A",
|
|
192
|
-
message: "Cancel"
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
]
|
|
196
|
-
})
|
|
197
|
-
]
|
|
198
|
-
})
|
|
199
|
-
]
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
// List navigation links
|
|
203
|
-
navigationRoutes.get("/", async (c)=>{
|
|
204
|
-
const siteName = await getSiteName(c);
|
|
205
|
-
const links = await c.var.services.navigationLinks.list();
|
|
206
|
-
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
207
|
-
c: c,
|
|
208
|
-
title: "Navigation",
|
|
209
|
-
siteName: siteName,
|
|
210
|
-
currentPath: "/dash/navigation",
|
|
211
|
-
children: /*#__PURE__*/ _jsx(NavigationListContent, {
|
|
212
|
-
links: links
|
|
213
|
-
})
|
|
214
|
-
}));
|
|
215
|
-
});
|
|
216
|
-
// New link form
|
|
217
|
-
navigationRoutes.get("/new", async (c)=>{
|
|
218
|
-
const siteName = await getSiteName(c);
|
|
219
|
-
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
220
|
-
c: c,
|
|
221
|
-
title: "New Link",
|
|
222
|
-
siteName: siteName,
|
|
223
|
-
currentPath: "/dash/navigation",
|
|
224
|
-
children: /*#__PURE__*/ _jsx(NavigationFormContent, {})
|
|
225
|
-
}));
|
|
226
|
-
});
|
|
227
|
-
// Create link
|
|
228
|
-
navigationRoutes.post("/", async (c)=>{
|
|
229
|
-
const body = await c.req.json();
|
|
230
|
-
if (!body.label || !body.url) {
|
|
231
|
-
return dsToast("Label and URL are required", "error");
|
|
232
|
-
}
|
|
233
|
-
await c.var.services.navigationLinks.create({
|
|
234
|
-
label: body.label,
|
|
235
|
-
url: body.url
|
|
236
|
-
});
|
|
237
|
-
return dsRedirect("/dash/navigation");
|
|
238
|
-
});
|
|
239
|
-
// Reorder links (must be before /:id to avoid "reorder" matching as :id)
|
|
240
|
-
navigationRoutes.post("/reorder", async (c)=>{
|
|
241
|
-
const body = await c.req.json();
|
|
242
|
-
if (!Array.isArray(body.ids)) {
|
|
243
|
-
return dsToast("Invalid request", "error");
|
|
244
|
-
}
|
|
245
|
-
await c.var.services.navigationLinks.reorder(body.ids);
|
|
246
|
-
return dsToast("Order saved");
|
|
247
|
-
});
|
|
248
|
-
// Edit link form
|
|
249
|
-
navigationRoutes.get("/:id/edit", async (c)=>{
|
|
250
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
251
|
-
if (isNaN(id)) return c.notFound();
|
|
252
|
-
const link = await c.var.services.navigationLinks.getById(id);
|
|
253
|
-
if (!link) return c.notFound();
|
|
254
|
-
const siteName = await getSiteName(c);
|
|
255
|
-
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
256
|
-
c: c,
|
|
257
|
-
title: "Edit Link",
|
|
258
|
-
siteName: siteName,
|
|
259
|
-
currentPath: "/dash/navigation",
|
|
260
|
-
children: /*#__PURE__*/ _jsx(NavigationFormContent, {
|
|
261
|
-
link: link,
|
|
262
|
-
isEdit: true
|
|
263
|
-
})
|
|
264
|
-
}));
|
|
265
|
-
});
|
|
266
|
-
// Update link
|
|
267
|
-
navigationRoutes.post("/:id", async (c)=>{
|
|
268
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
269
|
-
if (isNaN(id)) return c.notFound();
|
|
270
|
-
const body = await c.req.json();
|
|
271
|
-
if (!body.label || !body.url) {
|
|
272
|
-
return dsToast("Label and URL are required", "error");
|
|
273
|
-
}
|
|
274
|
-
const updated = await c.var.services.navigationLinks.update(id, {
|
|
275
|
-
label: body.label,
|
|
276
|
-
url: body.url
|
|
277
|
-
});
|
|
278
|
-
if (!updated) return c.notFound();
|
|
279
|
-
return dsRedirect("/dash/navigation");
|
|
280
|
-
});
|
|
281
|
-
// Delete link
|
|
282
|
-
navigationRoutes.post("/:id/delete", async (c)=>{
|
|
283
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
284
|
-
if (!isNaN(id)) {
|
|
285
|
-
await c.var.services.navigationLinks.delete(id);
|
|
286
|
-
}
|
|
287
|
-
return dsRedirect("/dash/navigation");
|
|
288
|
-
});
|