@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings API Routes
|
|
3
|
+
*/ import { Hono } from "hono";
|
|
4
|
+
import { requireAuthApi } from "../../middleware/auth.js";
|
|
5
|
+
import { CONFIG_FIELDS } from "../../types.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
export const settingsApiRoutes = new Hono();
|
|
8
|
+
/** Config keys that can be modified via the settings API */ const editableKeys = Object.entries(CONFIG_FIELDS).filter(([, field])=>!field.envOnly).map(([key])=>key);
|
|
9
|
+
const UpdateSettingsSchema = z.record(z.string(), z.string());
|
|
10
|
+
// Get all settings (requires auth)
|
|
11
|
+
settingsApiRoutes.get("/", requireAuthApi(), async (c)=>{
|
|
12
|
+
const allSettings = await c.var.services.settings.getAll();
|
|
13
|
+
// Include default values for editable keys not yet stored in DB
|
|
14
|
+
const result = {};
|
|
15
|
+
for (const key of editableKeys){
|
|
16
|
+
result[key] = allSettings[key] ?? CONFIG_FIELDS[key].defaultValue;
|
|
17
|
+
}
|
|
18
|
+
return c.json({
|
|
19
|
+
settings: result
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
// Update settings (requires auth)
|
|
23
|
+
settingsApiRoutes.put("/", requireAuthApi(), async (c)=>{
|
|
24
|
+
const rawBody = await c.req.json();
|
|
25
|
+
const parseResult = UpdateSettingsSchema.safeParse(rawBody);
|
|
26
|
+
if (!parseResult.success) {
|
|
27
|
+
return c.json({
|
|
28
|
+
error: "Validation failed",
|
|
29
|
+
details: parseResult.error.flatten()
|
|
30
|
+
}, 400);
|
|
31
|
+
}
|
|
32
|
+
const updates = parseResult.data;
|
|
33
|
+
// Filter to only editable keys
|
|
34
|
+
const filteredUpdates = {};
|
|
35
|
+
const rejectedKeys = [];
|
|
36
|
+
for (const [key, value] of Object.entries(updates)){
|
|
37
|
+
if (editableKeys.includes(key)) {
|
|
38
|
+
filteredUpdates[key] = value;
|
|
39
|
+
} else {
|
|
40
|
+
rejectedKeys.push(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (rejectedKeys.length > 0 && Object.keys(filteredUpdates).length === 0) {
|
|
44
|
+
return c.json({
|
|
45
|
+
error: "None of the provided keys are editable",
|
|
46
|
+
rejectedKeys
|
|
47
|
+
}, 400);
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(filteredUpdates).length > 0) {
|
|
50
|
+
// Settings service expects SettingsKey, but our ConfigKeys that are
|
|
51
|
+
// editable (SITE_NAME, SITE_DESCRIPTION, SITE_LANGUAGE) are valid SettingsKeys
|
|
52
|
+
for (const [key, value] of Object.entries(filteredUpdates)){
|
|
53
|
+
await c.var.services.settings.set(key, value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Return updated state
|
|
57
|
+
const allSettings = await c.var.services.settings.getAll();
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const key of editableKeys){
|
|
60
|
+
result[key] = allSettings[key] ?? CONFIG_FIELDS[key].defaultValue;
|
|
61
|
+
}
|
|
62
|
+
return c.json({
|
|
63
|
+
settings: result,
|
|
64
|
+
...rejectedKeys.length > 0 && {
|
|
65
|
+
rejectedKeys
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -8,7 +8,7 @@ import { html } from "hono/html";
|
|
|
8
8
|
import { uuidv7 } from "uuidv7";
|
|
9
9
|
import { requireAuthApi } from "../../middleware/auth.js";
|
|
10
10
|
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
11
|
-
import { sse
|
|
11
|
+
import { sse } from "../../lib/sse.js";
|
|
12
12
|
export const uploadApiRoutes = new Hono();
|
|
13
13
|
// Require auth for all upload routes
|
|
14
14
|
uploadApiRoutes.use("*", requireAuthApi());
|
|
@@ -85,14 +85,20 @@ function formatSize(bytes) {
|
|
|
85
85
|
const accept = c.req.header("accept") || "";
|
|
86
86
|
return accept.includes("text/event-stream");
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Return an SSE error response that removes the upload placeholder and shows a toast
|
|
90
|
+
*/ function sseUploadError(c, message) {
|
|
91
|
+
return sse(c, async (stream)=>{
|
|
92
|
+
await stream.remove("#upload-placeholder");
|
|
93
|
+
await stream.toast(message, "error");
|
|
94
|
+
});
|
|
95
|
+
}
|
|
88
96
|
// Upload a file
|
|
89
97
|
uploadApiRoutes.post("/", async (c)=>{
|
|
90
98
|
const storage = c.var.storage;
|
|
91
99
|
if (!storage) {
|
|
92
100
|
if (wantsSSE(c)) {
|
|
93
|
-
return
|
|
94
|
-
_uploadError: "Storage not configured"
|
|
95
|
-
});
|
|
101
|
+
return sseUploadError(c, "Storage not configured");
|
|
96
102
|
}
|
|
97
103
|
return c.json({
|
|
98
104
|
error: "Storage not configured"
|
|
@@ -102,9 +108,7 @@ uploadApiRoutes.post("/", async (c)=>{
|
|
|
102
108
|
const file = formData.get("file");
|
|
103
109
|
if (!file) {
|
|
104
110
|
if (wantsSSE(c)) {
|
|
105
|
-
return
|
|
106
|
-
_uploadError: "No file provided"
|
|
107
|
-
});
|
|
111
|
+
return sseUploadError(c, "No file provided");
|
|
108
112
|
}
|
|
109
113
|
return c.json({
|
|
110
114
|
error: "No file provided"
|
|
@@ -120,9 +124,7 @@ uploadApiRoutes.post("/", async (c)=>{
|
|
|
120
124
|
];
|
|
121
125
|
if (!allowedTypes.includes(file.type)) {
|
|
122
126
|
if (wantsSSE(c)) {
|
|
123
|
-
return
|
|
124
|
-
_uploadError: "File type not allowed"
|
|
125
|
-
});
|
|
127
|
+
return sseUploadError(c, "File type not allowed");
|
|
126
128
|
}
|
|
127
129
|
return c.json({
|
|
128
130
|
error: "File type not allowed"
|
|
@@ -132,9 +134,7 @@ uploadApiRoutes.post("/", async (c)=>{
|
|
|
132
134
|
const maxSize = 10 * 1024 * 1024;
|
|
133
135
|
if (file.size > maxSize) {
|
|
134
136
|
if (wantsSSE(c)) {
|
|
135
|
-
return
|
|
136
|
-
_uploadError: "File too large (max 10MB)"
|
|
137
|
-
});
|
|
137
|
+
return sseUploadError(c, "File too large (max 10MB)");
|
|
138
138
|
}
|
|
139
139
|
return c.json({
|
|
140
140
|
error: "File too large (max 10MB)"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose Route
|
|
3
|
+
*
|
|
4
|
+
* Handles post creation from the public-site compose dialog.
|
|
5
|
+
* Returns dsRedirect to the new post's permalink (Datastar form pattern).
|
|
6
|
+
*/ import { Hono } from "hono";
|
|
7
|
+
import { requireAuth } from "../middleware/auth.js";
|
|
8
|
+
import { CreatePostSchema, validateMediaCount } from "../lib/schemas.js";
|
|
9
|
+
import * as sqid from "../lib/sqid.js";
|
|
10
|
+
import { dsRedirect, dsToast } from "../lib/sse.js";
|
|
11
|
+
export const composeRoutes = new Hono();
|
|
12
|
+
// All compose routes require authentication
|
|
13
|
+
composeRoutes.use("*", requireAuth());
|
|
14
|
+
composeRoutes.post("/", async (c)=>{
|
|
15
|
+
const raw = await c.req.json();
|
|
16
|
+
const result = CreatePostSchema.safeParse(raw);
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
const firstError = result.error.issues[0]?.message ?? "Invalid input";
|
|
19
|
+
return dsToast(firstError, "error");
|
|
20
|
+
}
|
|
21
|
+
const data = result.data;
|
|
22
|
+
// Validate media count
|
|
23
|
+
if (data.mediaIds) {
|
|
24
|
+
const mediaError = validateMediaCount(data.mediaIds);
|
|
25
|
+
if (mediaError) {
|
|
26
|
+
return dsToast(mediaError, "error");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const post = await c.var.services.posts.create({
|
|
30
|
+
format: data.format,
|
|
31
|
+
title: data.title || undefined,
|
|
32
|
+
body: data.body || undefined,
|
|
33
|
+
status: data.status ?? "published",
|
|
34
|
+
featured: data.featured,
|
|
35
|
+
pinned: data.pinned,
|
|
36
|
+
url: data.url || undefined,
|
|
37
|
+
quoteText: data.quoteText || undefined,
|
|
38
|
+
rating: data.rating || undefined,
|
|
39
|
+
collectionId: data.collectionId || undefined
|
|
40
|
+
});
|
|
41
|
+
// Attach media if provided
|
|
42
|
+
if (data.mediaIds && data.mediaIds.length > 0) {
|
|
43
|
+
await c.var.services.media.attachToPost(post.id, data.mediaIds);
|
|
44
|
+
}
|
|
45
|
+
// Redirect to the new post's permalink
|
|
46
|
+
const permalink = post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`;
|
|
47
|
+
return dsRedirect(permalink);
|
|
48
|
+
});
|
|
@@ -4,8 +4,8 @@ import { getSiteName } from "../../lib/config.js";
|
|
|
4
4
|
* Dashboard Collections Routes
|
|
5
5
|
*/ import { Hono } from "hono";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { DashLayout } from "../../
|
|
8
|
-
import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader, DangerZone } from "../../
|
|
7
|
+
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
8
|
+
import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader, DangerZone } from "../../ui/dash/index.js";
|
|
9
9
|
import * as sqid from "../../lib/sqid.js";
|
|
10
10
|
import { dsRedirect } from "../../lib/sse.js";
|
|
11
11
|
export const collectionsRoutes = new Hono();
|
|
@@ -43,7 +43,7 @@ function CollectionsListContent({ collections }) {
|
|
|
43
43
|
id: "ePK91l",
|
|
44
44
|
message: "Edit"
|
|
45
45
|
}),
|
|
46
|
-
viewHref: `/c/${col.
|
|
46
|
+
viewHref: `/c/${col.slug}`,
|
|
47
47
|
viewLabel: $__i18n._({
|
|
48
48
|
id: "jpctdh",
|
|
49
49
|
message: "View"
|
|
@@ -59,7 +59,7 @@ function CollectionsListContent({ collections }) {
|
|
|
59
59
|
class: "text-sm text-muted-foreground",
|
|
60
60
|
children: [
|
|
61
61
|
"/",
|
|
62
|
-
col.
|
|
62
|
+
col.slug
|
|
63
63
|
]
|
|
64
64
|
}),
|
|
65
65
|
col.description && /*#__PURE__*/ _jsx("p", {
|
|
@@ -84,7 +84,7 @@ function NewCollectionContent() {
|
|
|
84
84
|
})
|
|
85
85
|
}),
|
|
86
86
|
/*#__PURE__*/ _jsxs("form", {
|
|
87
|
-
"data-signals": "{title: '',
|
|
87
|
+
"data-signals": "{title: '', slug: '', description: ''}",
|
|
88
88
|
"data-on:submit__prevent": "@post('/dash/collections')",
|
|
89
89
|
"data-indicator": "_loading",
|
|
90
90
|
class: "flex flex-col gap-4 max-w-lg",
|
|
@@ -123,7 +123,7 @@ function NewCollectionContent() {
|
|
|
123
123
|
}),
|
|
124
124
|
/*#__PURE__*/ _jsx("input", {
|
|
125
125
|
type: "text",
|
|
126
|
-
"data-bind": "
|
|
126
|
+
"data-bind": "slug",
|
|
127
127
|
class: "input",
|
|
128
128
|
required: true,
|
|
129
129
|
placeholder: "my-collection",
|
|
@@ -219,7 +219,7 @@ function ViewCollectionContent({ collection, posts }) {
|
|
|
219
219
|
class: "text-sm text-muted-foreground",
|
|
220
220
|
children: [
|
|
221
221
|
"/",
|
|
222
|
-
collection.
|
|
222
|
+
collection.slug
|
|
223
223
|
]
|
|
224
224
|
})
|
|
225
225
|
]
|
|
@@ -230,7 +230,7 @@ function ViewCollectionContent({ collection, posts }) {
|
|
|
230
230
|
id: "ePK91l",
|
|
231
231
|
message: "Edit"
|
|
232
232
|
}),
|
|
233
|
-
viewHref: `/c/${collection.
|
|
233
|
+
viewHref: `/c/${collection.slug}`,
|
|
234
234
|
viewLabel: $__i18n._({
|
|
235
235
|
id: "jpctdh",
|
|
236
236
|
message: "View"
|
|
@@ -259,27 +259,16 @@ function ViewCollectionContent({ collection, posts }) {
|
|
|
259
259
|
})
|
|
260
260
|
}) : /*#__PURE__*/ _jsx("div", {
|
|
261
261
|
class: "flex flex-col divide-y",
|
|
262
|
-
children: posts.map((post)=>/*#__PURE__*/
|
|
262
|
+
children: posts.map((post)=>/*#__PURE__*/ _jsx("div", {
|
|
263
263
|
class: "py-3 flex items-center gap-4",
|
|
264
|
-
children:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
children: post.title || post.content?.slice(0, 50) || `Post #${post.id}`
|
|
271
|
-
})
|
|
272
|
-
}),
|
|
273
|
-
/*#__PURE__*/ _jsx("button", {
|
|
274
|
-
type: "button",
|
|
275
|
-
class: "btn-sm-ghost text-destructive",
|
|
276
|
-
"data-on:click__prevent": `@post('/dash/collections/${collection.id}/remove-post', {payload: {postId: ${post.id}}})`,
|
|
277
|
-
children: $__i18n._({
|
|
278
|
-
id: "t/YqKh",
|
|
279
|
-
message: "Remove"
|
|
280
|
-
})
|
|
264
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
265
|
+
class: "flex-1 min-w-0",
|
|
266
|
+
children: /*#__PURE__*/ _jsx("a", {
|
|
267
|
+
href: `/dash/posts/${sqid.encode(post.id)}`,
|
|
268
|
+
class: "font-medium hover:underline",
|
|
269
|
+
children: post.title || post.body?.slice(0, 50) || `Post #${post.id}`
|
|
281
270
|
})
|
|
282
|
-
|
|
271
|
+
})
|
|
283
272
|
}, post.id))
|
|
284
273
|
})
|
|
285
274
|
})
|
|
@@ -303,7 +292,7 @@ function EditCollectionContent({ collection }) {
|
|
|
303
292
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
304
293
|
const signals = JSON.stringify({
|
|
305
294
|
title: collection.title,
|
|
306
|
-
|
|
295
|
+
slug: collection.slug ?? "",
|
|
307
296
|
description: collection.description ?? ""
|
|
308
297
|
}).replace(/</g, "\\u003c");
|
|
309
298
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
@@ -351,7 +340,7 @@ function EditCollectionContent({ collection }) {
|
|
|
351
340
|
}),
|
|
352
341
|
/*#__PURE__*/ _jsx("input", {
|
|
353
342
|
type: "text",
|
|
354
|
-
"data-bind": "
|
|
343
|
+
"data-bind": "slug",
|
|
355
344
|
class: "input",
|
|
356
345
|
required: true,
|
|
357
346
|
pattern: "[a-z0-9-]+"
|
|
@@ -453,7 +442,7 @@ collectionsRoutes.post("/", async (c)=>{
|
|
|
453
442
|
const body = await c.req.json();
|
|
454
443
|
const collection = await c.var.services.collections.create({
|
|
455
444
|
title: body.title,
|
|
456
|
-
|
|
445
|
+
slug: body.slug,
|
|
457
446
|
description: body.description || undefined
|
|
458
447
|
});
|
|
459
448
|
return dsRedirect(`/dash/collections/${collection.id}`);
|
|
@@ -464,7 +453,10 @@ collectionsRoutes.get("/:id", async (c)=>{
|
|
|
464
453
|
if (isNaN(id)) return c.notFound();
|
|
465
454
|
const collection = await c.var.services.collections.getById(id);
|
|
466
455
|
if (!collection) return c.notFound();
|
|
467
|
-
|
|
456
|
+
// Fetch posts in this collection via post service
|
|
457
|
+
const posts = await c.var.services.posts.list({
|
|
458
|
+
collectionId: id
|
|
459
|
+
});
|
|
468
460
|
const siteName = await getSiteName(c);
|
|
469
461
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
470
462
|
c: c,
|
|
@@ -501,7 +493,7 @@ collectionsRoutes.post("/:id", async (c)=>{
|
|
|
501
493
|
const body = await c.req.json();
|
|
502
494
|
await c.var.services.collections.update(id, {
|
|
503
495
|
title: body.title,
|
|
504
|
-
|
|
496
|
+
slug: body.slug,
|
|
505
497
|
description: body.description || undefined
|
|
506
498
|
});
|
|
507
499
|
return dsRedirect(`/dash/collections/${id}`);
|
|
@@ -513,13 +505,3 @@ collectionsRoutes.post("/:id/delete", async (c)=>{
|
|
|
513
505
|
await c.var.services.collections.delete(id);
|
|
514
506
|
return dsRedirect("/dash/collections");
|
|
515
507
|
});
|
|
516
|
-
// Remove post from collection
|
|
517
|
-
collectionsRoutes.post("/:id/remove-post", async (c)=>{
|
|
518
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
519
|
-
if (isNaN(id)) return c.notFound();
|
|
520
|
-
const body = await c.req.json();
|
|
521
|
-
if (body.postId) {
|
|
522
|
-
await c.var.services.collections.removePost(id, body.postId);
|
|
523
|
-
}
|
|
524
|
-
return dsRedirect(`/dash/collections/${id}`);
|
|
525
|
-
});
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
|
6
6
|
*/ import { Hono } from "hono";
|
|
7
7
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
8
|
import { Trans as Trans_ } from "@jant/core/i18n";
|
|
9
|
-
import { DashLayout } from "../../
|
|
9
|
+
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
10
10
|
import { getSiteName } from "../../lib/config.js";
|
|
11
11
|
export const dashIndexRoutes = new Hono();
|
|
12
12
|
/**
|
|
@@ -103,8 +103,8 @@ dashIndexRoutes.get("/", async (c)=>{
|
|
|
103
103
|
const allPosts = await c.var.services.posts.list({
|
|
104
104
|
limit: 1000
|
|
105
105
|
});
|
|
106
|
-
const publishedPosts = allPosts.filter((p)=>p.
|
|
107
|
-
const draftPosts = allPosts.filter((p)=>p.
|
|
106
|
+
const publishedPosts = allPosts.filter((p)=>p.status !== "draft");
|
|
107
|
+
const draftPosts = allPosts.filter((p)=>p.status === "draft");
|
|
108
108
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
109
109
|
c: c,
|
|
110
110
|
title: "Dashboard",
|
|
@@ -7,8 +7,8 @@ import { getSiteName } from "../../lib/config.js";
|
|
|
7
7
|
* Uses SSE for real-time UI updates without page reloads.
|
|
8
8
|
*/ import { Hono } from "hono";
|
|
9
9
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
10
|
-
import { DashLayout } from "../../
|
|
11
|
-
import { EmptyState, DangerZone } from "../../
|
|
10
|
+
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
11
|
+
import { EmptyState, DangerZone } from "../../ui/dash/index.js";
|
|
12
12
|
import * as time from "../../lib/time.js";
|
|
13
13
|
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
14
14
|
import { dsRedirect } from "../../lib/sse.js";
|