@jant/core 0.3.25 → 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.
Files changed (131) hide show
  1. package/dist/app.js +67 -562
  2. package/dist/client.js +1 -0
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/lib/avatar-upload.js +134 -0
  7. package/dist/lib/config.js +39 -0
  8. package/dist/lib/constants.js +10 -10
  9. package/dist/lib/favicon.js +102 -0
  10. package/dist/lib/image.js +13 -17
  11. package/dist/lib/media-helpers.js +2 -2
  12. package/dist/lib/navigation.js +23 -3
  13. package/dist/lib/render.js +10 -1
  14. package/dist/lib/schemas.js +31 -0
  15. package/dist/lib/timezones.js +388 -0
  16. package/dist/lib/view.js +1 -1
  17. package/dist/routes/api/posts.js +1 -1
  18. package/dist/routes/api/upload.js +3 -3
  19. package/dist/routes/auth/reset.js +221 -0
  20. package/dist/routes/auth/setup.js +194 -0
  21. package/dist/routes/auth/signin.js +176 -0
  22. package/dist/routes/dash/collections.js +23 -415
  23. package/dist/routes/dash/media.js +12 -392
  24. package/dist/routes/dash/pages.js +7 -330
  25. package/dist/routes/dash/redirects.js +18 -12
  26. package/dist/routes/dash/settings.js +198 -577
  27. package/dist/routes/feed/rss.js +2 -1
  28. package/dist/routes/feed/sitemap.js +4 -2
  29. package/dist/routes/pages/featured.js +5 -1
  30. package/dist/routes/pages/home.js +26 -1
  31. package/dist/routes/pages/latest.js +45 -0
  32. package/dist/services/post.js +30 -50
  33. package/dist/types/bindings.js +3 -0
  34. package/dist/types/config.js +147 -0
  35. package/dist/types/constants.js +27 -0
  36. package/dist/types/entities.js +3 -0
  37. package/dist/types/operations.js +3 -0
  38. package/dist/types/props.js +3 -0
  39. package/dist/types/views.js +5 -0
  40. package/dist/types.js +8 -111
  41. package/dist/ui/color-themes.js +33 -33
  42. package/dist/ui/compose/ComposeDialog.js +36 -21
  43. package/dist/ui/dash/PageForm.js +21 -15
  44. package/dist/ui/dash/PostForm.js +22 -16
  45. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  46. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  47. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  48. package/dist/ui/dash/media/MediaListContent.js +166 -0
  49. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  50. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  51. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  52. package/dist/ui/dash/settings/AccountContent.js +209 -0
  53. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  54. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  55. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  56. package/dist/ui/font-themes.js +36 -0
  57. package/dist/ui/layouts/BaseLayout.js +24 -2
  58. package/dist/ui/layouts/SiteLayout.js +47 -19
  59. package/package.json +1 -1
  60. package/src/app.tsx +93 -553
  61. package/src/client.ts +1 -0
  62. package/src/i18n/locales/en.po +240 -175
  63. package/src/i18n/locales/en.ts +1 -1
  64. package/src/i18n/locales/zh-Hans.po +240 -175
  65. package/src/i18n/locales/zh-Hans.ts +1 -1
  66. package/src/i18n/locales/zh-Hant.po +240 -175
  67. package/src/i18n/locales/zh-Hant.ts +1 -1
  68. package/src/lib/__tests__/config.test.ts +192 -0
  69. package/src/lib/__tests__/favicon.test.ts +151 -0
  70. package/src/lib/__tests__/image.test.ts +2 -6
  71. package/src/lib/__tests__/timezones.test.ts +61 -0
  72. package/src/lib/__tests__/view.test.ts +2 -2
  73. package/src/lib/avatar-upload.ts +165 -0
  74. package/src/lib/config.ts +47 -0
  75. package/src/lib/constants.ts +19 -11
  76. package/src/lib/favicon.ts +115 -0
  77. package/src/lib/image.ts +13 -21
  78. package/src/lib/media-helpers.ts +2 -2
  79. package/src/lib/navigation.ts +33 -2
  80. package/src/lib/render.tsx +15 -1
  81. package/src/lib/schemas.ts +39 -0
  82. package/src/lib/timezones.ts +325 -0
  83. package/src/lib/view.ts +1 -1
  84. package/src/routes/api/posts.ts +1 -1
  85. package/src/routes/api/upload.ts +2 -3
  86. package/src/routes/auth/reset.tsx +239 -0
  87. package/src/routes/auth/setup.tsx +189 -0
  88. package/src/routes/auth/signin.tsx +163 -0
  89. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  90. package/src/routes/dash/collections.tsx +17 -366
  91. package/src/routes/dash/media.tsx +12 -414
  92. package/src/routes/dash/pages.tsx +8 -348
  93. package/src/routes/dash/redirects.tsx +20 -14
  94. package/src/routes/dash/settings.tsx +243 -534
  95. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  96. package/src/routes/feed/rss.ts +3 -1
  97. package/src/routes/feed/sitemap.ts +4 -2
  98. package/src/routes/pages/featured.tsx +7 -1
  99. package/src/routes/pages/home.tsx +25 -2
  100. package/src/routes/pages/latest.tsx +59 -0
  101. package/src/services/post.ts +34 -66
  102. package/src/styles/components.css +0 -65
  103. package/src/styles/tokens.css +1 -1
  104. package/src/styles/ui.css +24 -40
  105. package/src/types/bindings.ts +30 -0
  106. package/src/types/config.ts +183 -0
  107. package/src/types/constants.ts +26 -0
  108. package/src/types/entities.ts +109 -0
  109. package/src/types/operations.ts +88 -0
  110. package/src/types/props.ts +115 -0
  111. package/src/types/views.ts +172 -0
  112. package/src/types.ts +8 -644
  113. package/src/ui/__tests__/font-themes.test.ts +34 -0
  114. package/src/ui/color-themes.ts +34 -34
  115. package/src/ui/compose/ComposeDialog.tsx +40 -21
  116. package/src/ui/dash/PageForm.tsx +25 -19
  117. package/src/ui/dash/PostForm.tsx +26 -20
  118. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  119. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  120. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  121. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  122. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  123. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  124. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  125. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  126. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  127. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  128. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  129. package/src/ui/font-themes.ts +54 -0
  130. package/src/ui/layouts/BaseLayout.tsx +17 -0
  131. package/src/ui/layouts/SiteLayout.tsx +45 -31
@@ -1,229 +1,28 @@
1
- import { getSiteName } from "../../lib/config.js";
2
1
  /**
3
2
  * Dashboard Pages & Navigation Routes
4
3
  *
5
- * Unified management for pages and navigation items (pika.page style).
6
- * Two sections: "Your site navigation" (draggable) and "Other pages".
4
+ * Unified management for pages and navigation items.
7
5
  */
8
6
 
9
7
  import { Hono } from "hono";
10
8
  import { useLingui } from "@lingui/react/macro";
11
- import type { Bindings, Page, NavItem } from "../../types.js";
9
+ import type { Bindings, Page } from "../../types.js";
12
10
  import type { AppVariables } from "../../app.js";
13
11
  import { DashLayout } from "../../ui/layouts/DashLayout.js";
14
- import {
15
- PageForm,
16
- ListItemRow,
17
- ActionButtons,
18
- CrudPageHeader,
19
- DangerZone,
20
- } from "../../ui/dash/index.js";
12
+ import { PageForm, ActionButtons, DangerZone } from "../../ui/dash/index.js";
21
13
  import { dsRedirect, dsToast } from "../../lib/sse.js";
14
+ import { getSiteName } from "../../lib/config.js";
15
+ import { UnifiedPagesContent } from "../../ui/dash/pages/UnifiedPagesContent.js";
16
+ import { LinkFormContent } from "../../ui/dash/pages/LinkFormContent.js";
22
17
 
23
18
  type Env = { Bindings: Bindings; Variables: AppVariables };
24
19
 
25
20
  export const pagesRoutes = new Hono<Env>();
26
21
 
27
22
  // =============================================================================
28
- // Components
23
+ // Inline components (small, tightly coupled to route params)
29
24
  // =============================================================================
30
25
 
31
- function UnifiedPagesContent({
32
- navItems,
33
- otherPages,
34
- }: {
35
- navItems: NavItem[];
36
- otherPages: Page[];
37
- }) {
38
- const { t } = useLingui();
39
-
40
- return (
41
- <>
42
- <CrudPageHeader
43
- title={t({
44
- message: "Pages",
45
- comment: "@context: Pages main heading",
46
- })}
47
- >
48
- <div class="flex gap-2">
49
- <a href="/dash/pages/links/new" class="btn-outline">
50
- {t({
51
- message: "Add Link",
52
- comment: "@context: Button to add a navigation link",
53
- })}
54
- </a>
55
- <a href="/dash/pages/new" class="btn">
56
- {t({
57
- message: "New Page",
58
- comment: "@context: Button to create new page",
59
- })}
60
- </a>
61
- </div>
62
- </CrudPageHeader>
63
-
64
- {/* Navigation section */}
65
- <section class="mb-8">
66
- <h2 class="text-lg font-medium mb-3">
67
- {t({
68
- message: "Your site navigation",
69
- comment: "@context: Section heading for navigation items",
70
- })}
71
- </h2>
72
- {navItems.length === 0 ? (
73
- <p class="text-sm text-muted-foreground py-4">
74
- {t({
75
- message:
76
- "No navigation links yet. Add pages to navigation or create links.",
77
- comment: "@context: Empty state for navigation section",
78
- })}
79
- </p>
80
- ) : (
81
- <div id="nav-links-list" class="flex flex-col divide-y">
82
- {navItems.map((item) => (
83
- <ListItemRow
84
- key={item.id}
85
- actions={
86
- item.type === "page" ? (
87
- <>
88
- <ActionButtons
89
- editHref={
90
- item.pageId
91
- ? `/dash/pages/${item.pageId}/edit`
92
- : undefined
93
- }
94
- editLabel={t({
95
- message: "Edit",
96
- comment: "@context: Button to edit page",
97
- })}
98
- />
99
- <button
100
- type="button"
101
- class="btn-sm-ghost"
102
- data-on:click__prevent={`@post('/dash/pages/${item.pageId}/remove-from-nav')`}
103
- >
104
- {t({
105
- message: "Un-nav",
106
- comment:
107
- "@context: Button to remove page from navigation",
108
- })}
109
- </button>
110
- </>
111
- ) : (
112
- <>
113
- <ActionButtons
114
- editHref={`/dash/pages/links/${item.id}/edit`}
115
- editLabel={t({
116
- message: "Edit",
117
- comment: "@context: Button to edit link",
118
- })}
119
- deleteAction={`/dash/pages/links/${item.id}/delete`}
120
- deleteLabel={t({
121
- message: "Delete",
122
- comment: "@context: Button to delete link",
123
- })}
124
- />
125
- </>
126
- )
127
- }
128
- >
129
- <div
130
- class="flex items-center gap-3 cursor-grab"
131
- data-id={item.id}
132
- >
133
- <span class="text-muted-foreground select-none">⠿</span>
134
- <div class="flex items-center gap-2">
135
- <span class="font-medium">{item.label}</span>
136
- <code class="text-sm text-muted-foreground bg-muted px-1 rounded">
137
- {item.url}
138
- </code>
139
- <span class="badge badge-sm">
140
- {item.type === "page"
141
- ? t({
142
- message: "page",
143
- comment: "@context: Nav item type badge",
144
- })
145
- : t({
146
- message: "link",
147
- comment: "@context: Nav item type badge",
148
- })}
149
- </span>
150
- </div>
151
- </div>
152
- </ListItemRow>
153
- ))}
154
- </div>
155
- )}
156
- </section>
157
-
158
- {/* Other pages section */}
159
- <section>
160
- <h2 class="text-lg font-medium mb-3">
161
- {t({
162
- message: "Other pages",
163
- comment: "@context: Section heading for pages not in navigation",
164
- })}
165
- </h2>
166
- {otherPages.length === 0 ? (
167
- <p class="text-sm text-muted-foreground py-4">
168
- {t({
169
- message: "All pages are in your navigation.",
170
- comment: "@context: Empty state when all pages are in nav",
171
- })}
172
- </p>
173
- ) : (
174
- <div class="flex flex-col divide-y">
175
- {otherPages.map((page) => (
176
- <ListItemRow
177
- key={page.id}
178
- actions={
179
- <>
180
- <button
181
- type="button"
182
- class="btn-sm-outline"
183
- data-on:click__prevent={`@post('/dash/pages/${page.id}/add-to-nav')`}
184
- >
185
- {t({
186
- message: "Add to nav",
187
- comment: "@context: Button to add page to navigation",
188
- })}
189
- </button>
190
- <ActionButtons
191
- editHref={`/dash/pages/${page.id}/edit`}
192
- editLabel={t({
193
- message: "Edit",
194
- comment: "@context: Button to edit page",
195
- })}
196
- viewHref={
197
- page.status !== "draft" ? `/${page.slug}` : undefined
198
- }
199
- viewLabel={t({
200
- message: "View",
201
- comment: "@context: Button to view page on public site",
202
- })}
203
- />
204
- </>
205
- }
206
- >
207
- <a
208
- href={`/dash/pages/${page.id}`}
209
- class="font-medium hover:underline"
210
- >
211
- {page.title ||
212
- t({
213
- message: "Untitled",
214
- comment: "@context: Default title for untitled page",
215
- })}
216
- </a>
217
- <p class="text-sm text-muted-foreground mt-1">/{page.slug}</p>
218
- </ListItemRow>
219
- ))}
220
- </div>
221
- )}
222
- </section>
223
- </>
224
- );
225
- }
226
-
227
26
  function NewPageContent() {
228
27
  const { t } = useLingui();
229
28
  return (
@@ -301,118 +100,10 @@ function EditPageContent({ page }: { page: Page }) {
301
100
  );
302
101
  }
303
102
 
304
- function LinkFormContent({
305
- item,
306
- isEdit,
307
- }: {
308
- item?: NavItem;
309
- isEdit?: boolean;
310
- }) {
311
- const { t } = useLingui();
312
- const title = isEdit
313
- ? t({ message: "Edit Link", comment: "@context: Page heading" })
314
- : t({ message: "New Link", comment: "@context: Page heading" });
315
-
316
- const signals = JSON.stringify({
317
- label: item?.label ?? "",
318
- url: item?.url ?? "",
319
- }).replace(/</g, "\\u003c");
320
-
321
- const action = isEdit ? `/dash/pages/links/${item?.id}` : "/dash/pages/links";
322
-
323
- return (
324
- <>
325
- <h1 class="text-2xl font-semibold mb-6">{title}</h1>
326
-
327
- <form
328
- data-signals={signals}
329
- data-on:submit__prevent={`@post('${action}')`}
330
- data-indicator="_loading"
331
- class="flex flex-col gap-4 max-w-lg"
332
- >
333
- <div class="field">
334
- <label class="label">
335
- {t({
336
- message: "Label",
337
- comment: "@context: Navigation link form field",
338
- })}
339
- </label>
340
- <input
341
- type="text"
342
- data-bind="label"
343
- class="input"
344
- placeholder="Home"
345
- required
346
- />
347
- <p class="text-xs text-muted-foreground mt-1">
348
- {t({
349
- message: "Display text for the link",
350
- comment: "@context: Navigation label help text",
351
- })}
352
- </p>
353
- </div>
354
-
355
- <div class="field">
356
- <label class="label">
357
- {t({
358
- message: "URL",
359
- comment: "@context: Navigation link form field",
360
- })}
361
- </label>
362
- <input
363
- type="text"
364
- data-bind="url"
365
- class="input"
366
- placeholder="/archive or https://..."
367
- required
368
- />
369
- <p class="text-xs text-muted-foreground mt-1">
370
- {t({
371
- message:
372
- "Path (e.g. /archive) or full URL (e.g. https://example.com)",
373
- comment: "@context: Navigation URL help text",
374
- })}
375
- </p>
376
- </div>
377
-
378
- <div class="flex gap-2">
379
- <button type="submit" class="btn" data-attr-disabled="$_loading">
380
- <span data-show="!$_loading">
381
- {isEdit
382
- ? t({
383
- message: "Save Changes",
384
- comment: "@context: Button to save edited navigation link",
385
- })
386
- : t({
387
- message: "Create Link",
388
- comment: "@context: Button to save new navigation link",
389
- })}
390
- </span>
391
- <span data-show="$_loading">
392
- {t({
393
- message: "Processing...",
394
- comment:
395
- "@context: Loading text shown on submit button while request is in progress",
396
- })}
397
- </span>
398
- </button>
399
- <a href="/dash/pages" class="btn-outline">
400
- {t({
401
- message: "Cancel",
402
- comment: "@context: Button to cancel form",
403
- })}
404
- </a>
405
- </div>
406
- </form>
407
- </>
408
- );
409
- }
410
-
411
103
  // =============================================================================
412
- // Page Routes
104
+ // Route handlers
413
105
  // =============================================================================
414
106
 
415
- // List pages (unified view)
416
107
  pagesRoutes.get("/", async (c) => {
417
108
  const [navItems, otherPages] = await Promise.all([
418
109
  c.var.services.navItems.list(),
@@ -432,10 +123,8 @@ pagesRoutes.get("/", async (c) => {
432
123
  );
433
124
  });
434
125
 
435
- // New page form
436
126
  pagesRoutes.get("/new", async (c) => {
437
127
  const siteName = await getSiteName(c);
438
-
439
128
  return c.html(
440
129
  <DashLayout
441
130
  c={c}
@@ -448,10 +137,8 @@ pagesRoutes.get("/new", async (c) => {
448
137
  );
449
138
  });
450
139
 
451
- // New link form
452
140
  pagesRoutes.get("/links/new", async (c) => {
453
141
  const siteName = await getSiteName(c);
454
-
455
142
  return c.html(
456
143
  <DashLayout
457
144
  c={c}
@@ -464,10 +151,8 @@ pagesRoutes.get("/links/new", async (c) => {
464
151
  );
465
152
  });
466
153
 
467
- // Create link
468
154
  pagesRoutes.post("/links", async (c) => {
469
155
  const body = await c.req.json<{ label: string; url: string }>();
470
-
471
156
  if (!body.label || !body.url) {
472
157
  return dsToast("Label and URL are required", "error");
473
158
  }
@@ -477,24 +162,18 @@ pagesRoutes.post("/links", async (c) => {
477
162
  label: body.label,
478
163
  url: body.url,
479
164
  });
480
-
481
165
  return dsRedirect("/dash/pages");
482
166
  });
483
167
 
484
- // Reorder nav items (must be before /:id to avoid matching)
485
168
  pagesRoutes.post("/reorder", async (c) => {
486
169
  const body = await c.req.json<{ ids: number[] }>();
487
-
488
170
  if (!Array.isArray(body.ids)) {
489
171
  return dsToast("Invalid request", "error");
490
172
  }
491
-
492
173
  await c.var.services.navItems.reorder(body.ids);
493
-
494
174
  return dsToast("Order saved");
495
175
  });
496
176
 
497
- // Edit link form
498
177
  pagesRoutes.get("/links/:id/edit", async (c) => {
499
178
  const id = parseInt(c.req.param("id"), 10);
500
179
  if (isNaN(id)) return c.notFound();
@@ -503,7 +182,6 @@ pagesRoutes.get("/links/:id/edit", async (c) => {
503
182
  if (!item) return c.notFound();
504
183
 
505
184
  const siteName = await getSiteName(c);
506
-
507
185
  return c.html(
508
186
  <DashLayout
509
187
  c={c}
@@ -516,13 +194,11 @@ pagesRoutes.get("/links/:id/edit", async (c) => {
516
194
  );
517
195
  });
518
196
 
519
- // Update link
520
197
  pagesRoutes.post("/links/:id", async (c) => {
521
198
  const id = parseInt(c.req.param("id"), 10);
522
199
  if (isNaN(id)) return c.notFound();
523
200
 
524
201
  const body = await c.req.json<{ label: string; url: string }>();
525
-
526
202
  if (!body.label || !body.url) {
527
203
  return dsToast("Label and URL are required", "error");
528
204
  }
@@ -531,23 +207,19 @@ pagesRoutes.post("/links/:id", async (c) => {
531
207
  label: body.label,
532
208
  url: body.url,
533
209
  });
534
-
535
210
  if (!updated) return c.notFound();
536
211
 
537
212
  return dsRedirect("/dash/pages");
538
213
  });
539
214
 
540
- // Delete link
541
215
  pagesRoutes.post("/links/:id/delete", async (c) => {
542
216
  const id = parseInt(c.req.param("id"), 10);
543
217
  if (!isNaN(id)) {
544
218
  await c.var.services.navItems.delete(id);
545
219
  }
546
-
547
220
  return dsRedirect("/dash/pages");
548
221
  });
549
222
 
550
- // Create page
551
223
  pagesRoutes.post("/", async (c) => {
552
224
  const body = await c.req.json<{
553
225
  title: string;
@@ -566,7 +238,6 @@ pagesRoutes.post("/", async (c) => {
566
238
  return dsRedirect(`/dash/pages/${page.id}`);
567
239
  });
568
240
 
569
- // Add page to navigation
570
241
  pagesRoutes.post("/:id/add-to-nav", async (c) => {
571
242
  const id = parseInt(c.req.param("id"), 10);
572
243
  if (isNaN(id)) return c.notFound();
@@ -580,26 +251,21 @@ pagesRoutes.post("/:id/add-to-nav", async (c) => {
580
251
  url: `/${page.slug}`,
581
252
  pageId: page.id,
582
253
  });
583
-
584
254
  return dsRedirect("/dash/pages");
585
255
  });
586
256
 
587
- // Remove page from navigation (keeps the page, deletes the nav item)
588
257
  pagesRoutes.post("/:id/remove-from-nav", async (c) => {
589
258
  const pageId = parseInt(c.req.param("id"), 10);
590
259
  if (isNaN(pageId)) return c.notFound();
591
260
 
592
- // Find nav item by pageId
593
261
  const navItems = await c.var.services.navItems.list();
594
262
  const navItem = navItems.find((item) => item.pageId === pageId);
595
263
  if (navItem) {
596
264
  await c.var.services.navItems.delete(navItem.id);
597
265
  }
598
-
599
266
  return dsRedirect("/dash/pages");
600
267
  });
601
268
 
602
- // View single page
603
269
  pagesRoutes.get("/:id", async (c) => {
604
270
  const id = parseInt(c.req.param("id"), 10);
605
271
  if (isNaN(id)) return c.notFound();
@@ -608,7 +274,6 @@ pagesRoutes.get("/:id", async (c) => {
608
274
  if (!page) return c.notFound();
609
275
 
610
276
  const siteName = await getSiteName(c);
611
-
612
277
  return c.html(
613
278
  <DashLayout
614
279
  c={c}
@@ -621,7 +286,6 @@ pagesRoutes.get("/:id", async (c) => {
621
286
  );
622
287
  });
623
288
 
624
- // Edit page form
625
289
  pagesRoutes.get("/:id/edit", async (c) => {
626
290
  const id = parseInt(c.req.param("id"), 10);
627
291
  if (isNaN(id)) return c.notFound();
@@ -630,7 +294,6 @@ pagesRoutes.get("/:id/edit", async (c) => {
630
294
  if (!page) return c.notFound();
631
295
 
632
296
  const siteName = await getSiteName(c);
633
-
634
297
  return c.html(
635
298
  <DashLayout
636
299
  c={c}
@@ -643,7 +306,6 @@ pagesRoutes.get("/:id/edit", async (c) => {
643
306
  );
644
307
  });
645
308
 
646
- // Update page
647
309
  pagesRoutes.post("/:id", async (c) => {
648
310
  const id = parseInt(c.req.param("id"), 10);
649
311
  if (isNaN(id)) return c.notFound();
@@ -665,12 +327,10 @@ pagesRoutes.post("/:id", async (c) => {
665
327
  return dsRedirect(`/dash/pages/${id}`);
666
328
  });
667
329
 
668
- // Delete page
669
330
  pagesRoutes.post("/:id/delete", async (c) => {
670
331
  const id = parseInt(c.req.param("id"), 10);
671
332
  if (isNaN(id)) return c.notFound();
672
333
 
673
334
  await c.var.services.pages.delete(id);
674
-
675
335
  return dsRedirect("/dash/pages");
676
336
  });
@@ -158,20 +158,26 @@ function NewRedirectContent() {
158
158
  </div>
159
159
 
160
160
  <div class="flex gap-2">
161
- <button type="submit" class="btn" data-attr-disabled="$_loading">
162
- <span data-show="!$_loading">
163
- {t({
164
- message: "Create Redirect",
165
- comment: "@context: Button to save new redirect",
166
- })}
167
- </span>
168
- <span data-show="$_loading">
169
- {t({
170
- message: "Processing...",
171
- comment:
172
- "@context: Loading text shown on submit button while request is in progress",
173
- })}
174
- </span>
161
+ <button type="submit" class="btn" data-attr:disabled="$_loading">
162
+ <svg
163
+ data-show="$_loading"
164
+ style="display:none"
165
+ class="animate-spin size-4"
166
+ xmlns="http://www.w3.org/2000/svg"
167
+ viewBox="0 0 24 24"
168
+ fill="none"
169
+ stroke="currentColor"
170
+ stroke-width="2"
171
+ stroke-linecap="round"
172
+ stroke-linejoin="round"
173
+ role="status"
174
+ >
175
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
176
+ </svg>
177
+ {t({
178
+ message: "Create Redirect",
179
+ comment: "@context: Button to save new redirect",
180
+ })}
175
181
  </button>
176
182
  <a href="/dash/redirects" class="btn-outline">
177
183
  {t({