@jant/core 0.3.25 → 0.3.27

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 (133) hide show
  1. package/dist/app.js +70 -563
  2. package/dist/auth.js +3 -0
  3. package/dist/client.js +1 -0
  4. package/dist/i18n/locales/en.js +1 -1
  5. package/dist/i18n/locales/zh-Hans.js +1 -1
  6. package/dist/i18n/locales/zh-Hant.js +1 -1
  7. package/dist/lib/avatar-upload.js +134 -0
  8. package/dist/lib/config.js +39 -0
  9. package/dist/lib/constants.js +10 -10
  10. package/dist/lib/favicon.js +102 -0
  11. package/dist/lib/image.js +13 -17
  12. package/dist/lib/media-helpers.js +2 -2
  13. package/dist/lib/navigation.js +23 -3
  14. package/dist/lib/render.js +10 -1
  15. package/dist/lib/schemas.js +31 -0
  16. package/dist/lib/timezones.js +388 -0
  17. package/dist/lib/view.js +1 -1
  18. package/dist/routes/api/posts.js +1 -1
  19. package/dist/routes/api/upload.js +3 -3
  20. package/dist/routes/auth/reset.js +221 -0
  21. package/dist/routes/auth/setup.js +194 -0
  22. package/dist/routes/auth/signin.js +176 -0
  23. package/dist/routes/dash/collections.js +23 -415
  24. package/dist/routes/dash/media.js +12 -392
  25. package/dist/routes/dash/pages.js +7 -330
  26. package/dist/routes/dash/redirects.js +18 -12
  27. package/dist/routes/dash/settings.js +198 -577
  28. package/dist/routes/feed/rss.js +2 -1
  29. package/dist/routes/feed/sitemap.js +4 -2
  30. package/dist/routes/pages/featured.js +5 -1
  31. package/dist/routes/pages/home.js +26 -1
  32. package/dist/routes/pages/latest.js +45 -0
  33. package/dist/services/post.js +30 -50
  34. package/dist/types/bindings.js +3 -0
  35. package/dist/types/config.js +147 -0
  36. package/dist/types/constants.js +27 -0
  37. package/dist/types/entities.js +3 -0
  38. package/dist/types/operations.js +3 -0
  39. package/dist/types/props.js +3 -0
  40. package/dist/types/views.js +5 -0
  41. package/dist/types.js +8 -111
  42. package/dist/ui/color-themes.js +33 -33
  43. package/dist/ui/compose/ComposeDialog.js +36 -21
  44. package/dist/ui/dash/PageForm.js +21 -15
  45. package/dist/ui/dash/PostForm.js +22 -16
  46. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  47. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  48. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  49. package/dist/ui/dash/media/MediaListContent.js +166 -0
  50. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  51. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  52. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  53. package/dist/ui/dash/settings/AccountContent.js +209 -0
  54. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  55. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  56. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  57. package/dist/ui/font-themes.js +36 -0
  58. package/dist/ui/layouts/BaseLayout.js +24 -2
  59. package/dist/ui/layouts/SiteLayout.js +47 -19
  60. package/package.json +1 -1
  61. package/src/app.tsx +95 -553
  62. package/src/auth.ts +4 -1
  63. package/src/client.ts +1 -0
  64. package/src/i18n/locales/en.po +240 -175
  65. package/src/i18n/locales/en.ts +1 -1
  66. package/src/i18n/locales/zh-Hans.po +240 -175
  67. package/src/i18n/locales/zh-Hans.ts +1 -1
  68. package/src/i18n/locales/zh-Hant.po +240 -175
  69. package/src/i18n/locales/zh-Hant.ts +1 -1
  70. package/src/lib/__tests__/config.test.ts +192 -0
  71. package/src/lib/__tests__/favicon.test.ts +151 -0
  72. package/src/lib/__tests__/image.test.ts +2 -6
  73. package/src/lib/__tests__/timezones.test.ts +61 -0
  74. package/src/lib/__tests__/view.test.ts +2 -2
  75. package/src/lib/avatar-upload.ts +165 -0
  76. package/src/lib/config.ts +47 -0
  77. package/src/lib/constants.ts +19 -11
  78. package/src/lib/favicon.ts +115 -0
  79. package/src/lib/image.ts +13 -21
  80. package/src/lib/media-helpers.ts +2 -2
  81. package/src/lib/navigation.ts +33 -2
  82. package/src/lib/render.tsx +15 -1
  83. package/src/lib/schemas.ts +39 -0
  84. package/src/lib/timezones.ts +325 -0
  85. package/src/lib/view.ts +1 -1
  86. package/src/routes/api/posts.ts +1 -1
  87. package/src/routes/api/upload.ts +2 -3
  88. package/src/routes/auth/reset.tsx +239 -0
  89. package/src/routes/auth/setup.tsx +189 -0
  90. package/src/routes/auth/signin.tsx +163 -0
  91. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  92. package/src/routes/dash/collections.tsx +17 -366
  93. package/src/routes/dash/media.tsx +12 -414
  94. package/src/routes/dash/pages.tsx +8 -348
  95. package/src/routes/dash/redirects.tsx +20 -14
  96. package/src/routes/dash/settings.tsx +243 -534
  97. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  98. package/src/routes/feed/rss.ts +3 -1
  99. package/src/routes/feed/sitemap.ts +4 -2
  100. package/src/routes/pages/featured.tsx +7 -1
  101. package/src/routes/pages/home.tsx +25 -2
  102. package/src/routes/pages/latest.tsx +59 -0
  103. package/src/services/post.ts +34 -66
  104. package/src/styles/components.css +0 -65
  105. package/src/styles/tokens.css +1 -1
  106. package/src/styles/ui.css +24 -40
  107. package/src/types/bindings.ts +30 -0
  108. package/src/types/config.ts +183 -0
  109. package/src/types/constants.ts +26 -0
  110. package/src/types/entities.ts +109 -0
  111. package/src/types/operations.ts +88 -0
  112. package/src/types/props.ts +115 -0
  113. package/src/types/views.ts +172 -0
  114. package/src/types.ts +8 -644
  115. package/src/ui/__tests__/font-themes.test.ts +34 -0
  116. package/src/ui/color-themes.ts +34 -34
  117. package/src/ui/compose/ComposeDialog.tsx +40 -21
  118. package/src/ui/dash/PageForm.tsx +25 -19
  119. package/src/ui/dash/PostForm.tsx +26 -20
  120. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  121. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  122. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  123. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  124. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  125. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  126. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  127. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  128. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  129. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  130. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  131. package/src/ui/font-themes.ts +54 -0
  132. package/src/ui/layouts/BaseLayout.tsx +17 -0
  133. package/src/ui/layouts/SiteLayout.tsx +45 -31
@@ -1,376 +1,23 @@
1
- import { getSiteName } from "../../lib/config.js";
2
1
  /**
3
2
  * Dashboard Collections Routes
4
3
  */
5
4
 
6
5
  import { Hono } from "hono";
7
- import { useLingui } from "@lingui/react/macro";
8
- import type { Bindings, Collection, Post } from "../../types.js";
6
+ import type { Bindings } from "../../types.js";
9
7
  import type { AppVariables } from "../../app.js";
10
8
  import { DashLayout } from "../../ui/layouts/DashLayout.js";
11
- import {
12
- EmptyState,
13
- ListItemRow,
14
- ActionButtons,
15
- CrudPageHeader,
16
- DangerZone,
17
- } from "../../ui/dash/index.js";
18
- import * as sqid from "../../lib/sqid.js";
9
+ import { DangerZone } from "../../ui/dash/index.js";
19
10
  import { dsRedirect } from "../../lib/sse.js";
11
+ import { getSiteName } from "../../lib/config.js";
12
+ import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
13
+ import { CollectionsListContent } from "../../ui/dash/collections/CollectionsListContent.js";
14
+ import { CollectionForm } from "../../ui/dash/collections/CollectionForm.js";
15
+ import { ViewCollectionContent } from "../../ui/dash/collections/ViewCollectionContent.js";
20
16
 
21
17
  type Env = { Bindings: Bindings; Variables: AppVariables };
22
18
 
23
19
  export const collectionsRoutes = new Hono<Env>();
24
20
 
25
- function CollectionsListContent({
26
- collections,
27
- }: {
28
- collections: Collection[];
29
- }) {
30
- const { t } = useLingui();
31
-
32
- return (
33
- <>
34
- <CrudPageHeader
35
- title={t({
36
- message: "Collections",
37
- comment: "@context: Dashboard heading",
38
- })}
39
- ctaLabel={t({
40
- message: "New Collection",
41
- comment: "@context: Button to create new collection",
42
- })}
43
- ctaHref="/dash/collections/new"
44
- />
45
-
46
- {collections.length === 0 ? (
47
- <EmptyState
48
- message={t({
49
- message: "No collections yet.",
50
- comment: "@context: Empty state message",
51
- })}
52
- ctaText={t({
53
- message: "New Collection",
54
- comment: "@context: Button to create new collection",
55
- })}
56
- ctaHref="/dash/collections/new"
57
- />
58
- ) : (
59
- <div class="flex flex-col divide-y">
60
- {collections.map((col) => (
61
- <ListItemRow
62
- key={col.id}
63
- actions={
64
- <ActionButtons
65
- editHref={`/dash/collections/${col.id}/edit`}
66
- editLabel={t({
67
- message: "Edit",
68
- comment: "@context: Button to edit collection",
69
- })}
70
- viewHref={`/c/${col.slug}`}
71
- viewLabel={t({
72
- message: "View",
73
- comment: "@context: Button to view collection",
74
- })}
75
- />
76
- }
77
- >
78
- <a
79
- href={`/dash/collections/${col.id}`}
80
- class="font-medium hover:underline"
81
- >
82
- {col.title}
83
- </a>
84
- <p class="text-sm text-muted-foreground">/{col.slug}</p>
85
- {col.description && (
86
- <p class="text-sm text-muted-foreground mt-1">
87
- {col.description}
88
- </p>
89
- )}
90
- </ListItemRow>
91
- ))}
92
- </div>
93
- )}
94
- </>
95
- );
96
- }
97
-
98
- function NewCollectionContent() {
99
- const { t } = useLingui();
100
- return (
101
- <>
102
- <h1 class="text-2xl font-semibold mb-6">
103
- {t({ message: "New Collection", comment: "@context: Page heading" })}
104
- </h1>
105
-
106
- <form
107
- data-signals="{title: '', slug: '', description: ''}"
108
- data-on:submit__prevent="@post('/dash/collections')"
109
- data-indicator="_loading"
110
- class="flex flex-col gap-4 max-w-lg"
111
- >
112
- <div class="field">
113
- <label class="label">
114
- {t({
115
- message: "Title",
116
- comment: "@context: Collection form field",
117
- })}
118
- </label>
119
- <input
120
- type="text"
121
- data-bind="title"
122
- class="input"
123
- required
124
- placeholder={t({
125
- message: "My Collection",
126
- comment: "@context: Collection title placeholder",
127
- })}
128
- />
129
- </div>
130
-
131
- <div class="field">
132
- <label class="label">
133
- {t({ message: "Slug", comment: "@context: Collection form field" })}
134
- </label>
135
- <input
136
- type="text"
137
- data-bind="slug"
138
- class="input"
139
- required
140
- placeholder="my-collection"
141
- pattern="[a-z0-9-]+"
142
- />
143
- <p class="text-xs text-muted-foreground mt-1">
144
- {t({
145
- message: "URL-safe identifier (lowercase, numbers, hyphens)",
146
- comment: "@context: Collection path help text",
147
- })}
148
- </p>
149
- </div>
150
-
151
- <div class="field">
152
- <label class="label">
153
- {t({
154
- message: "Description (optional)",
155
- comment: "@context: Collection form field",
156
- })}
157
- </label>
158
- <textarea
159
- data-bind="description"
160
- class="textarea"
161
- rows={3}
162
- placeholder={t({
163
- message: "What's this collection about?",
164
- comment: "@context: Collection description placeholder",
165
- })}
166
- />
167
- </div>
168
-
169
- <div class="flex gap-2">
170
- <button type="submit" class="btn" data-attr-disabled="$_loading">
171
- <span data-show="!$_loading">
172
- {t({
173
- message: "Create Collection",
174
- comment: "@context: Button to save new collection",
175
- })}
176
- </span>
177
- <span data-show="$_loading">
178
- {t({
179
- message: "Processing...",
180
- comment:
181
- "@context: Loading text shown on submit button while request is in progress",
182
- })}
183
- </span>
184
- </button>
185
- <a href="/dash/collections" class="btn-outline">
186
- {t({
187
- message: "Cancel",
188
- comment: "@context: Button to cancel form",
189
- })}
190
- </a>
191
- </div>
192
- </form>
193
- </>
194
- );
195
- }
196
-
197
- function ViewCollectionContent({
198
- collection,
199
- posts,
200
- }: {
201
- collection: Collection;
202
- posts: Post[];
203
- }) {
204
- const { t } = useLingui();
205
- const postsHeader = t({
206
- message: "Posts in Collection ({count})",
207
- comment: "@context: Collection posts section heading",
208
- values: { count: String(posts.length) },
209
- });
210
-
211
- return (
212
- <>
213
- <div class="flex items-center justify-between mb-6">
214
- <div>
215
- <h1 class="text-2xl font-semibold">{collection.title}</h1>
216
- <p class="text-sm text-muted-foreground">/{collection.slug}</p>
217
- </div>
218
- <ActionButtons
219
- editHref={`/dash/collections/${collection.id}/edit`}
220
- editLabel={t({
221
- message: "Edit",
222
- comment: "@context: Button to edit collection",
223
- })}
224
- viewHref={`/c/${collection.slug}`}
225
- viewLabel={t({
226
- message: "View",
227
- comment: "@context: Button to view collection",
228
- })}
229
- />
230
- </div>
231
-
232
- {collection.description && (
233
- <p class="text-muted-foreground mb-6">{collection.description}</p>
234
- )}
235
-
236
- <div class="card">
237
- <header>
238
- <h2>{postsHeader}</h2>
239
- </header>
240
- <section>
241
- {posts.length === 0 ? (
242
- <p class="text-muted-foreground">
243
- {t({
244
- message: "No posts in this collection.",
245
- comment: "@context: Empty state message",
246
- })}
247
- </p>
248
- ) : (
249
- <div class="flex flex-col divide-y">
250
- {posts.map((post) => (
251
- <div key={post.id} class="py-3 flex items-center gap-4">
252
- <div class="flex-1 min-w-0">
253
- <a
254
- href={`/dash/posts/${sqid.encode(post.id)}`}
255
- class="font-medium hover:underline"
256
- >
257
- {post.title ||
258
- post.body?.slice(0, 50) ||
259
- `Post #${post.id}`}
260
- </a>
261
- </div>
262
- </div>
263
- ))}
264
- </div>
265
- )}
266
- </section>
267
- </div>
268
-
269
- <div class="mt-6">
270
- <a href="/dash/collections" class="text-sm hover:underline">
271
- {t({
272
- message: "\u2190 Back to Collections",
273
- comment: "@context: Navigation link",
274
- })}
275
- </a>
276
- </div>
277
- </>
278
- );
279
- }
280
-
281
- function EditCollectionContent({ collection }: { collection: Collection }) {
282
- const { t } = useLingui();
283
-
284
- const signals = JSON.stringify({
285
- title: collection.title,
286
- slug: collection.slug ?? "",
287
- description: collection.description ?? "",
288
- }).replace(/</g, "\\u003c");
289
-
290
- return (
291
- <>
292
- <h1 class="text-2xl font-semibold mb-6">
293
- {t({ message: "Edit Collection", comment: "@context: Page heading" })}
294
- </h1>
295
-
296
- <form
297
- data-signals={signals}
298
- data-on:submit__prevent={`@post('/dash/collections/${collection.id}')`}
299
- data-indicator="_loading"
300
- class="flex flex-col gap-4 max-w-lg"
301
- >
302
- <div class="field">
303
- <label class="label">
304
- {t({
305
- message: "Title",
306
- comment: "@context: Collection form field",
307
- })}
308
- </label>
309
- <input type="text" data-bind="title" class="input" required />
310
- </div>
311
-
312
- <div class="field">
313
- <label class="label">
314
- {t({ message: "Slug", comment: "@context: Collection form field" })}
315
- </label>
316
- <input
317
- type="text"
318
- data-bind="slug"
319
- class="input"
320
- required
321
- pattern="[a-z0-9-]+"
322
- />
323
- </div>
324
-
325
- <div class="field">
326
- <label class="label">
327
- {t({
328
- message: "Description (optional)",
329
- comment: "@context: Collection form field",
330
- })}
331
- </label>
332
- <textarea data-bind="description" class="textarea" rows={3}>
333
- {collection.description ?? ""}
334
- </textarea>
335
- </div>
336
-
337
- <div class="flex gap-2">
338
- <button type="submit" class="btn" data-attr-disabled="$_loading">
339
- <span data-show="!$_loading">
340
- {t({
341
- message: "Update Collection",
342
- comment: "@context: Button to save collection changes",
343
- })}
344
- </span>
345
- <span data-show="$_loading">
346
- {t({
347
- message: "Processing...",
348
- comment:
349
- "@context: Loading text shown on submit button while request is in progress",
350
- })}
351
- </span>
352
- </button>
353
- <a href={`/dash/collections/${collection.id}`} class="btn-outline">
354
- {t({
355
- message: "Cancel",
356
- comment: "@context: Button to cancel form",
357
- })}
358
- </a>
359
- </div>
360
- </form>
361
-
362
- <DangerZone
363
- actionLabel={t({
364
- message: "Delete Collection",
365
- comment: "@context: Button to delete collection",
366
- })}
367
- formAction={`/dash/collections/${collection.id}/delete`}
368
- confirmMessage="Are you sure you want to delete this collection?"
369
- />
370
- </>
371
- );
372
- }
373
-
374
21
  // List collections
375
22
  collectionsRoutes.get("/", async (c) => {
376
23
  const siteName = await getSiteName(c);
@@ -399,7 +46,7 @@ collectionsRoutes.get("/new", async (c) => {
399
46
  siteName={siteName}
400
47
  currentPath="/dash/collections"
401
48
  >
402
- <NewCollectionContent />
49
+ <CollectionForm />
403
50
  </DashLayout>,
404
51
  );
405
52
  });
@@ -429,10 +76,9 @@ collectionsRoutes.get("/:id", async (c) => {
429
76
  const collection = await c.var.services.collections.getById(id);
430
77
  if (!collection) return c.notFound();
431
78
 
432
- // Fetch posts in this collection via post service
433
- const posts = await c.var.services.posts.list({
434
- collectionId: id,
435
- });
79
+ const rawPosts = await c.var.services.posts.list({ collectionId: id });
80
+ const ctx = createMediaContext(c);
81
+ const posts = toPostViewsFromPosts(rawPosts, ctx);
436
82
  const siteName = await getSiteName(c);
437
83
 
438
84
  return c.html(
@@ -464,7 +110,12 @@ collectionsRoutes.get("/:id/edit", async (c) => {
464
110
  siteName={siteName}
465
111
  currentPath="/dash/collections"
466
112
  >
467
- <EditCollectionContent collection={collection} />
113
+ <CollectionForm collection={collection} isEdit />
114
+ <DangerZone
115
+ actionLabel="Delete Collection"
116
+ formAction={`/dash/collections/${collection.id}/delete`}
117
+ confirmMessage="Are you sure you want to delete this collection?"
118
+ />
468
119
  </DashLayout>,
469
120
  );
470
121
  });