@jant/core 0.3.35 → 0.3.36

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.
Files changed (156) hide show
  1. package/dist/client/assets/module-RjUF93sV.js +716 -0
  2. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  3. package/dist/client/assets/url-8Dj-5CLW.js +1 -0
  4. package/dist/client/client.css +1 -1
  5. package/dist/client/client.js +3109 -2294
  6. package/dist/index.js +3026 -2778
  7. package/package.json +13 -4
  8. package/src/__tests__/helpers/app.ts +1 -1
  9. package/src/__tests__/helpers/db.ts +6 -0
  10. package/src/app.tsx +1 -5
  11. package/src/{lib → client}/avatar-upload.ts +1 -1
  12. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  13. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  14. package/src/{ui → client}/components/__tests__/jant-compose-dialog.test.ts +46 -14
  15. package/src/{ui → client}/components/__tests__/jant-compose-editor.test.ts +64 -24
  16. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +24 -14
  17. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  18. package/src/client/components/collection-sidebar-types.ts +45 -0
  19. package/src/{ui → client}/components/collection-types.ts +3 -4
  20. package/src/{ui → client}/components/compose-types.ts +3 -1
  21. package/src/{ui → client}/components/jant-collection-form.ts +301 -182
  22. package/src/client/components/jant-collection-sidebar.ts +801 -0
  23. package/src/{ui → client}/components/jant-compose-dialog.ts +231 -1
  24. package/src/client/components/jant-compose-editor.ts +1249 -0
  25. package/src/client/components/jant-compose-fullscreen.ts +338 -0
  26. package/src/client/components/jant-media-lightbox.ts +257 -0
  27. package/src/{ui → client}/components/jant-nav-manager.ts +143 -84
  28. package/src/{ui → client}/components/jant-post-form.ts +57 -8
  29. package/src/{ui → client}/components/jant-settings-general.ts +2 -2
  30. package/src/{ui → client}/components/nav-manager-types.ts +3 -0
  31. package/src/{ui → client}/components/post-form-template.ts +35 -31
  32. package/src/{ui → client}/components/post-form-types.ts +7 -3
  33. package/src/{lib → client}/compose-bridge.ts +9 -7
  34. package/src/client/lazy-slugify.ts +51 -0
  35. package/src/{lib → client}/media-upload.ts +16 -3
  36. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  37. package/src/client/page-slug-bridge.ts +42 -0
  38. package/src/{lib → client}/post-form-bridge.ts +2 -2
  39. package/src/{lib → client}/settings-bridge.ts +3 -3
  40. package/src/client/tiptap/bubble-menu.ts +205 -0
  41. package/src/client/tiptap/create-editor.ts +40 -0
  42. package/src/client/tiptap/exitable-marks.ts +73 -0
  43. package/src/client/tiptap/extensions.ts +60 -0
  44. package/src/client/tiptap/image-node.ts +488 -0
  45. package/src/client/tiptap/link-toolbar.ts +371 -0
  46. package/src/client/tiptap/more-break.ts +50 -0
  47. package/src/client/tiptap/paste-image.ts +140 -0
  48. package/src/client/tiptap/slash-commands.ts +328 -0
  49. package/src/{types → client/types}/sortablejs.d.ts +1 -1
  50. package/src/client.ts +24 -17
  51. package/src/db/migrations/0012_add_tiptap_columns.sql +2 -0
  52. package/src/db/migrations/0013_replace_featured_with_visibility.sql +8 -0
  53. package/src/db/schema.ts +6 -1
  54. package/src/i18n/locales/en.po +641 -215
  55. package/src/i18n/locales/en.ts +1 -1
  56. package/src/i18n/locales/zh-Hans.po +642 -204
  57. package/src/i18n/locales/zh-Hans.ts +1 -1
  58. package/src/i18n/locales/zh-Hant.po +642 -204
  59. package/src/i18n/locales/zh-Hant.ts +1 -1
  60. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  61. package/src/lib/__tests__/schemas.test.ts +9 -6
  62. package/src/lib/__tests__/url.test.ts +2 -2
  63. package/src/lib/__tests__/view.test.ts +9 -9
  64. package/src/lib/emoji-catalog.ts +146 -0
  65. package/src/lib/feed.ts +1 -1
  66. package/src/lib/media-helpers.ts +10 -9
  67. package/src/lib/render.tsx +4 -3
  68. package/src/lib/resolve-config.ts +8 -1
  69. package/src/lib/schemas.ts +2 -3
  70. package/src/lib/summary.ts +92 -0
  71. package/src/lib/timeline.ts +2 -0
  72. package/src/lib/tiptap-render.ts +196 -0
  73. package/src/lib/upload.ts +97 -9
  74. package/src/lib/url.ts +7 -23
  75. package/src/lib/view.ts +33 -19
  76. package/src/middleware/error-handler.ts +3 -3
  77. package/src/preset.css +38 -0
  78. package/src/routes/api/collections.ts +20 -3
  79. package/src/routes/api/posts.ts +48 -33
  80. package/src/routes/api/upload.ts +7 -5
  81. package/src/routes/auth/reset.tsx +5 -4
  82. package/src/routes/auth/setup.tsx +26 -11
  83. package/src/routes/auth/signin.tsx +10 -7
  84. package/src/routes/compose.tsx +20 -11
  85. package/src/routes/dash/__tests__/settings-avatar.test.ts +43 -8
  86. package/src/routes/dash/index.tsx +7 -1
  87. package/src/routes/dash/media.tsx +3 -0
  88. package/src/routes/dash/pages.tsx +8 -2
  89. package/src/routes/dash/posts.tsx +6 -2
  90. package/src/routes/dash/redirects.tsx +15 -9
  91. package/src/routes/dash/settings.tsx +336 -32
  92. package/src/routes/feed/__tests__/rss.test.ts +7 -7
  93. package/src/routes/feed/rss.ts +8 -6
  94. package/src/routes/pages/__tests__/featured.test.ts +6 -7
  95. package/src/routes/pages/archive.tsx +11 -7
  96. package/src/routes/pages/collection.tsx +32 -15
  97. package/src/routes/pages/collections.tsx +11 -2
  98. package/src/routes/pages/featured.tsx +1 -1
  99. package/src/routes/pages/home.tsx +1 -1
  100. package/src/services/__tests__/post.test.ts +124 -33
  101. package/src/services/__tests__/settings.test.ts +3 -3
  102. package/src/services/page.ts +16 -3
  103. package/src/services/post.ts +96 -37
  104. package/src/services/search.ts +4 -2
  105. package/src/services/settings.ts +6 -2
  106. package/src/styles/components.css +240 -60
  107. package/src/styles/tokens.css +10 -0
  108. package/src/styles/ui.css +1157 -81
  109. package/src/types/bindings.ts +5 -0
  110. package/src/types/config.ts +23 -1
  111. package/src/types/constants.ts +3 -0
  112. package/src/types/entities.ts +9 -2
  113. package/src/types/operations.ts +9 -3
  114. package/src/types/props.ts +3 -3
  115. package/src/types/views.ts +3 -2
  116. package/src/ui/compose/ComposeDialog.tsx +24 -7
  117. package/src/ui/dash/PageForm.tsx +2 -0
  118. package/src/ui/dash/PostList.tsx +5 -5
  119. package/src/ui/dash/StatusBadge.tsx +13 -5
  120. package/src/ui/dash/appearance/AdvancedContent.tsx +52 -61
  121. package/src/ui/dash/appearance/ColorThemeContent.tsx +30 -35
  122. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  123. package/src/ui/dash/appearance/NavigationContent.tsx +107 -96
  124. package/src/ui/dash/media/MediaListContent.tsx +9 -4
  125. package/src/ui/dash/media/ViewMediaContent.tsx +2 -2
  126. package/src/ui/dash/pages/PagesContent.tsx +2 -1
  127. package/src/ui/dash/posts/PostForm.tsx +19 -7
  128. package/src/ui/dash/settings/AccountContent.tsx +133 -138
  129. package/src/ui/dash/settings/AvatarContent.tsx +70 -0
  130. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  131. package/src/ui/dash/settings/SettingsRootContent.tsx +236 -0
  132. package/src/ui/layouts/DashLayout.tsx +157 -75
  133. package/src/ui/layouts/SiteLayout.tsx +13 -13
  134. package/src/ui/pages/ArchivePage.tsx +10 -7
  135. package/src/ui/pages/CollectionPage.tsx +6 -35
  136. package/src/ui/pages/CollectionsPage.tsx +2 -1
  137. package/src/ui/pages/FeaturedPage.tsx +2 -1
  138. package/src/ui/pages/HomePage.tsx +1 -1
  139. package/src/ui/pages/SearchPage.tsx +1 -1
  140. package/src/ui/shared/CollectionsSidebar.tsx +228 -3
  141. package/src/ui/shared/MediaGallery.tsx +179 -41
  142. package/src/lib/collections-reorder.ts +0 -28
  143. package/src/routes/dash/appearance.tsx +0 -240
  144. package/src/routes/dash/collections.tsx +0 -211
  145. package/src/ui/components/jant-compose-editor.ts +0 -814
  146. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  147. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  148. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  149. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  150. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  151. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  152. /package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +0 -0
  153. /package/src/{ui → client}/components/jant-settings-avatar.ts +0 -0
  154. /package/src/{ui → client}/components/settings-types.ts +0 -0
  155. /package/src/{lib → client}/image-processor.ts +0 -0
  156. /package/src/{lib → client}/toast.ts +0 -0
@@ -1,240 +0,0 @@
1
- /**
2
- * Dashboard Appearance Routes
3
- *
4
- * Sub-pages: Navigation (default), Color Theme, Font Theme, Advanced (Custom CSS)
5
- */
6
-
7
- import { Hono } from "hono";
8
- import { msg } from "@lingui/core/macro";
9
- import type { Bindings } from "../../types.js";
10
- import type { AppVariables } from "../../types/app-context.js";
11
- import { DashLayout } from "../../ui/layouts/DashLayout.js";
12
- import { dsRedirect, dsToast } from "../../lib/sse.js";
13
- import { getI18n } from "../../i18n/index.js";
14
- import { SETTINGS_KEYS } from "../../lib/constants.js";
15
- import { getAvailableThemes } from "../../lib/theme.js";
16
- import { BUILTIN_FONT_THEMES } from "../../ui/font-themes.js";
17
- import { ColorThemeContent } from "../../ui/dash/appearance/ColorThemeContent.js";
18
- import { FontThemeContent } from "../../ui/dash/appearance/FontThemeContent.js";
19
- import { NavigationContent } from "../../ui/dash/appearance/NavigationContent.js";
20
- import { AdvancedContent } from "../../ui/dash/appearance/AdvancedContent.js";
21
-
22
- type Env = { Bindings: Bindings; Variables: AppVariables };
23
-
24
- export const appearanceRoutes = new Hono<Env>();
25
-
26
- // ===========================================================================
27
- // Navigation (default tab)
28
- // ===========================================================================
29
-
30
- appearanceRoutes.get("/", async (c) => {
31
- const [navItems, availablePages] = await Promise.all([
32
- c.var.services.navItems.list(),
33
- c.var.services.pages.listNotInNav(),
34
- ]);
35
- const siteName = c.var.appConfig.siteName;
36
- const headerNavMaxVisible = c.var.appConfig.headerNavMaxVisible;
37
- const homeDefaultView = c.var.appConfig.homeDefaultView;
38
-
39
- return c.html(
40
- <DashLayout
41
- c={c}
42
- title="Appearance"
43
- siteName={siteName}
44
- currentPath="/dash/appearance"
45
- >
46
- <NavigationContent
47
- navItems={navItems}
48
- availablePages={availablePages}
49
- headerNavMaxVisible={headerNavMaxVisible}
50
- homeDefaultView={homeDefaultView}
51
- siteName={siteName}
52
- />
53
- </DashLayout>,
54
- );
55
- });
56
-
57
- // ===========================================================================
58
- // Nav max visible links
59
- // ===========================================================================
60
-
61
- appearanceRoutes.post("/nav-max-visible", async (c) => {
62
- const body = await c.req.json<{ value: number }>();
63
- const { settings } = c.var.services;
64
-
65
- const navMax = Math.max(0, Math.min(5, body.value ?? 3));
66
- if (navMax !== 3) {
67
- await settings.set("HEADER_NAV_MAX_VISIBLE", String(navMax));
68
- } else {
69
- await settings.remove("HEADER_NAV_MAX_VISIBLE");
70
- }
71
-
72
- return c.json({ ok: true });
73
- });
74
-
75
- // ===========================================================================
76
- // Home default view
77
- // ===========================================================================
78
-
79
- appearanceRoutes.post("/home-default-view", async (c) => {
80
- const body = await c.req.json<{ value: string }>();
81
- const { settings } = c.var.services;
82
-
83
- if (body.value === "featured") {
84
- await settings.set("HOME_DEFAULT_VIEW", "featured");
85
- } else {
86
- await settings.remove("HOME_DEFAULT_VIEW");
87
- }
88
-
89
- return c.json({ ok: true });
90
- });
91
-
92
- // ===========================================================================
93
- // Color Theme
94
- // ===========================================================================
95
-
96
- appearanceRoutes.get("/color", async (c) => {
97
- const siteName = c.var.appConfig.siteName;
98
- const defaultThemeId = c.var.appConfig.fallbacks.defaultTheme;
99
- const currentThemeId =
100
- c.var.allSettings[SETTINGS_KEYS.THEME] ?? defaultThemeId;
101
- const themes = getAvailableThemes();
102
- const saved = c.req.query("saved") !== undefined;
103
-
104
- return c.html(
105
- <DashLayout
106
- c={c}
107
- title="Appearance"
108
- siteName={siteName}
109
- currentPath="/dash/appearance"
110
- toast={saved ? { message: "Theme saved successfully." } : undefined}
111
- >
112
- <ColorThemeContent themes={themes} currentThemeId={currentThemeId} />
113
- </DashLayout>,
114
- );
115
- });
116
-
117
- appearanceRoutes.post("/color", async (c) => {
118
- const i18n = getI18n(c);
119
- const body = await c.req.json<{ theme: string }>();
120
- const { settings } = c.var.services;
121
- const themes = getAvailableThemes();
122
-
123
- const validTheme = themes.find((t) => t.id === body.theme);
124
- if (!validTheme) {
125
- return dsToast(
126
- i18n._(
127
- msg({
128
- message: "Invalid theme selected.",
129
- comment: "@context: Error toast when selected theme is not valid",
130
- }),
131
- ),
132
- "error",
133
- );
134
- }
135
-
136
- const defaultThemeId = c.var.appConfig.fallbacks.defaultTheme;
137
- if (validTheme.id === defaultThemeId) {
138
- await settings.remove(SETTINGS_KEYS.THEME);
139
- } else {
140
- await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
141
- }
142
-
143
- return dsRedirect("/dash/appearance/color?saved");
144
- });
145
-
146
- // ===========================================================================
147
- // Font Theme
148
- // ===========================================================================
149
-
150
- appearanceRoutes.get("/fonts", async (c) => {
151
- const siteName = c.var.appConfig.siteName;
152
- const currentFontThemeId = c.var.allSettings["FONT_THEME"] ?? "default";
153
- const saved = c.req.query("saved") !== undefined;
154
-
155
- return c.html(
156
- <DashLayout
157
- c={c}
158
- title="Appearance"
159
- siteName={siteName}
160
- currentPath="/dash/appearance"
161
- toast={saved ? { message: "Font theme saved successfully." } : undefined}
162
- >
163
- <FontThemeContent
164
- fontThemes={BUILTIN_FONT_THEMES}
165
- currentFontThemeId={currentFontThemeId}
166
- />
167
- </DashLayout>,
168
- );
169
- });
170
-
171
- appearanceRoutes.post("/font-theme", async (c) => {
172
- const i18n = getI18n(c);
173
- const body = await c.req.json<{ fontTheme: string }>();
174
- const { settings } = c.var.services;
175
-
176
- const validFont = BUILTIN_FONT_THEMES.find((f) => f.id === body.fontTheme);
177
- if (!validFont) {
178
- return dsToast(
179
- i18n._(
180
- msg({
181
- message: "Invalid font theme selected.",
182
- comment:
183
- "@context: Error toast when selected font theme is not valid",
184
- }),
185
- ),
186
- "error",
187
- );
188
- }
189
-
190
- if (validFont.id === "default") {
191
- await settings.remove("FONT_THEME");
192
- } else {
193
- await settings.set("FONT_THEME", validFont.id);
194
- }
195
-
196
- return dsRedirect("/dash/appearance/fonts?saved");
197
- });
198
-
199
- // ===========================================================================
200
- // Advanced (Custom CSS)
201
- // ===========================================================================
202
-
203
- appearanceRoutes.get("/advanced", async (c) => {
204
- const siteName = c.var.appConfig.siteName;
205
- const customCSS = c.var.allSettings[SETTINGS_KEYS.CUSTOM_CSS] ?? "";
206
-
207
- return c.html(
208
- <DashLayout
209
- c={c}
210
- title="Appearance"
211
- siteName={siteName}
212
- currentPath="/dash/appearance"
213
- >
214
- <AdvancedContent customCSS={customCSS} />
215
- </DashLayout>,
216
- );
217
- });
218
-
219
- appearanceRoutes.post("/custom-css", async (c) => {
220
- const i18n = getI18n(c);
221
- const body = await c.req.json<{ customCSS: string }>();
222
- const { settings } = c.var.services;
223
-
224
- const css = body.customCSS?.trim() ?? "";
225
-
226
- if (css) {
227
- await settings.set(SETTINGS_KEYS.CUSTOM_CSS, css);
228
- } else {
229
- await settings.remove(SETTINGS_KEYS.CUSTOM_CSS);
230
- }
231
-
232
- return dsToast(
233
- i18n._(
234
- msg({
235
- message: "Custom CSS saved successfully.",
236
- comment: "@context: Toast after saving custom CSS",
237
- }),
238
- ),
239
- );
240
- });
@@ -1,211 +0,0 @@
1
- /**
2
- * Dashboard Collections Routes
3
- */
4
-
5
- import { Hono } from "hono";
6
- import type { Bindings } from "../../types.js";
7
- import type { AppVariables } from "../../types/app-context.js";
8
- import { DashLayout } from "../../ui/layouts/DashLayout.js";
9
- import { DangerZone } from "../../ui/dash/index.js";
10
- import { dsRedirect } from "../../lib/sse.js";
11
- import {
12
- CreateCollectionSchema,
13
- UpdateCollectionSchema,
14
- parseValidated,
15
- } from "../../lib/schemas.js";
16
- import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
17
- import { slugify } from "../../lib/url.js";
18
- import { CollectionsListContent } from "../../ui/dash/collections/CollectionsListContent.js";
19
- import { CollectionForm } from "../../ui/dash/collections/CollectionForm.js";
20
- import { ViewCollectionContent } from "../../ui/dash/collections/ViewCollectionContent.js";
21
- import { IconPickerGrid } from "../../ui/dash/collections/IconPickerGrid.js";
22
-
23
- type Env = { Bindings: Bindings; Variables: AppVariables };
24
-
25
- export const collectionsRoutes = new Hono<Env>();
26
-
27
- // List collections
28
- collectionsRoutes.get("/", async (c) => {
29
- const siteName = c.var.appConfig.siteName;
30
- const [collections, dividers, postCounts] = await Promise.all([
31
- c.var.services.collections.list(),
32
- c.var.services.collections.listDividers(),
33
- c.var.services.collections.getPostCounts(),
34
- ]);
35
-
36
- return c.html(
37
- <DashLayout
38
- c={c}
39
- title="Collections"
40
- siteName={siteName}
41
- currentPath="/dash/collections"
42
- >
43
- <CollectionsListContent
44
- collections={collections}
45
- dividers={dividers}
46
- postCounts={postCounts}
47
- />
48
- </DashLayout>,
49
- );
50
- });
51
-
52
- // New collection form
53
- collectionsRoutes.get("/new", async (c) => {
54
- const siteName = c.var.appConfig.siteName;
55
-
56
- return c.html(
57
- <DashLayout
58
- c={c}
59
- title="New Collection"
60
- siteName={siteName}
61
- currentPath="/dash/collections"
62
- >
63
- <CollectionForm />
64
- </DashLayout>,
65
- );
66
- });
67
-
68
- // Create collection
69
- collectionsRoutes.post("/", async (c) => {
70
- const wantsJson = c.req.header("Accept")?.includes("application/json");
71
- const raw = await c.req.json();
72
- const body = parseValidated(CreateCollectionSchema, {
73
- ...raw,
74
- slug: raw.slug || slugify(raw.title ?? ""),
75
- });
76
-
77
- const collection = await c.var.services.collections.create({
78
- title: body.title,
79
- slug: body.slug,
80
- description: body.description || undefined,
81
- icon: body.icon || undefined,
82
- sortOrder: body.sortOrder || undefined,
83
- });
84
-
85
- const redirectUrl = `/dash/collections/${collection.id}`;
86
- if (wantsJson) {
87
- return c.json({ status: "redirect" as const, url: redirectUrl });
88
- }
89
-
90
- return dsRedirect(redirectUrl);
91
- });
92
-
93
- // Reorder collections (accepts prefixed items)
94
- collectionsRoutes.post("/reorder", async (c) => {
95
- const body = await c.req.json<{ items?: string[]; ids?: number[] }>();
96
-
97
- if (body.items) {
98
- await c.var.services.collections.reorderAll(body.items);
99
- } else if (body.ids) {
100
- // Backward compat: plain numeric IDs
101
- await c.var.services.collections.reorder(body.ids);
102
- }
103
-
104
- return c.json({ success: true });
105
- });
106
-
107
- // Create divider
108
- collectionsRoutes.post("/dividers", async (c) => {
109
- await c.var.services.collections.createDivider();
110
- return dsRedirect("/dash/collections");
111
- });
112
-
113
- // Delete divider
114
- collectionsRoutes.post("/dividers/:id/delete", async (c) => {
115
- const id = parseInt(c.req.param("id"), 10);
116
- if (!isNaN(id)) {
117
- await c.var.services.collections.deleteDivider(id);
118
- }
119
- return dsRedirect("/dash/collections");
120
- });
121
-
122
- // Icon picker grid (HTML fragment)
123
- collectionsRoutes.get("/icons", (c) => {
124
- return c.html(<IconPickerGrid />);
125
- });
126
-
127
- // View single collection
128
- collectionsRoutes.get("/:id", async (c) => {
129
- const id = parseInt(c.req.param("id"), 10);
130
- if (isNaN(id)) return c.notFound();
131
-
132
- const collection = await c.var.services.collections.getById(id);
133
- if (!collection) return c.notFound();
134
-
135
- const rawPosts = await c.var.services.posts.list({ collectionId: id });
136
- const ctx = createMediaContext(c.var.appConfig);
137
- const posts = toPostViewsFromPosts(rawPosts, ctx);
138
- const siteName = c.var.appConfig.siteName;
139
-
140
- return c.html(
141
- <DashLayout
142
- c={c}
143
- title={collection.title}
144
- siteName={siteName}
145
- currentPath="/dash/collections"
146
- >
147
- <ViewCollectionContent collection={collection} posts={posts} />
148
- </DashLayout>,
149
- );
150
- });
151
-
152
- // Edit collection form
153
- collectionsRoutes.get("/:id/edit", async (c) => {
154
- const id = parseInt(c.req.param("id"), 10);
155
- if (isNaN(id)) return c.notFound();
156
-
157
- const collection = await c.var.services.collections.getById(id);
158
- if (!collection) return c.notFound();
159
-
160
- const siteName = c.var.appConfig.siteName;
161
-
162
- return c.html(
163
- <DashLayout
164
- c={c}
165
- title={`Edit: ${collection.title}`}
166
- siteName={siteName}
167
- currentPath="/dash/collections"
168
- >
169
- <CollectionForm collection={collection} isEdit />
170
- <DangerZone
171
- actionLabel="Delete Collection"
172
- formAction={`/dash/collections/${collection.id}/delete`}
173
- confirmMessage="Are you sure you want to delete this collection?"
174
- />
175
- </DashLayout>,
176
- );
177
- });
178
-
179
- // Update collection
180
- collectionsRoutes.post("/:id", async (c) => {
181
- const id = parseInt(c.req.param("id"), 10);
182
- if (isNaN(id)) return c.notFound();
183
-
184
- const wantsJson = c.req.header("Accept")?.includes("application/json");
185
- const body = parseValidated(UpdateCollectionSchema, await c.req.json());
186
-
187
- await c.var.services.collections.update(id, {
188
- title: body.title,
189
- slug: body.slug,
190
- description: body.description || null,
191
- icon: body.icon || null,
192
- sortOrder: body.sortOrder || undefined,
193
- });
194
-
195
- const redirectUrl = `/dash/collections/${id}`;
196
- if (wantsJson) {
197
- return c.json({ status: "redirect" as const, url: redirectUrl });
198
- }
199
-
200
- return dsRedirect(redirectUrl);
201
- });
202
-
203
- // Delete collection
204
- collectionsRoutes.post("/:id/delete", async (c) => {
205
- const id = parseInt(c.req.param("id"), 10);
206
- if (isNaN(id)) return c.notFound();
207
-
208
- await c.var.services.collections.delete(id);
209
-
210
- return dsRedirect("/dash/collections");
211
- });