@jant/core 0.3.35 → 0.3.37

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 (307) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/module-RjUF93sV.js +716 -0
  6. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  7. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  8. package/dist/client/client.css +1 -1
  9. package/dist/client/client.js +4564 -3013
  10. package/dist/index.js +12885 -8161
  11. package/package.json +23 -6
  12. package/src/__tests__/helpers/app.ts +10 -10
  13. package/src/__tests__/helpers/db.ts +91 -87
  14. package/src/app.tsx +157 -31
  15. package/src/auth.ts +20 -2
  16. package/src/client/archive-nav.js +187 -0
  17. package/src/client/audio-player.ts +478 -0
  18. package/src/client/audio-processor.ts +84 -0
  19. package/src/{lib → client}/avatar-upload.ts +4 -3
  20. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  21. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  22. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
  23. package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
  24. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
  25. package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
  26. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  27. package/src/client/components/collection-sidebar-types.ts +43 -0
  28. package/src/{ui → client}/components/collection-types.ts +3 -4
  29. package/src/client/components/compose-types.ts +174 -0
  30. package/src/client/components/jant-collection-form.ts +667 -0
  31. package/src/client/components/jant-collection-sidebar.ts +805 -0
  32. package/src/client/components/jant-compose-dialog.ts +2161 -0
  33. package/src/client/components/jant-compose-editor.ts +1813 -0
  34. package/src/client/components/jant-compose-fullscreen.ts +283 -0
  35. package/src/client/components/jant-media-lightbox.ts +259 -0
  36. package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
  37. package/src/{ui → client}/components/jant-post-form.ts +141 -12
  38. package/src/client/components/jant-post-menu.ts +1019 -0
  39. package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
  40. package/src/{ui → client}/components/jant-settings-general.ts +38 -4
  41. package/src/client/components/jant-text-preview.ts +232 -0
  42. package/src/{ui → client}/components/nav-manager-types.ts +6 -18
  43. package/src/{ui → client}/components/post-form-template.ts +137 -38
  44. package/src/{ui → client}/components/post-form-types.ts +15 -4
  45. package/src/client/compose-bridge.ts +583 -0
  46. package/src/{lib → client}/image-processor.ts +26 -8
  47. package/src/client/lazy-slugify.ts +51 -0
  48. package/src/client/media-metadata.ts +247 -0
  49. package/src/client/multipart-upload.ts +160 -0
  50. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  51. package/src/{lib → client}/post-form-bridge.ts +53 -2
  52. package/src/{lib → client}/settings-bridge.ts +3 -15
  53. package/src/client/thread-context.ts +140 -0
  54. package/src/client/tiptap/bubble-menu.ts +205 -0
  55. package/src/client/tiptap/create-editor.ts +86 -0
  56. package/src/client/tiptap/exitable-marks.ts +73 -0
  57. package/src/client/tiptap/extensions.ts +65 -0
  58. package/src/client/tiptap/image-node.ts +482 -0
  59. package/src/client/tiptap/link-toolbar.ts +371 -0
  60. package/src/client/tiptap/more-break.ts +50 -0
  61. package/src/client/tiptap/paste-image.ts +129 -0
  62. package/src/client/tiptap/slash-commands.ts +438 -0
  63. package/src/{lib → client}/toast.ts +101 -3
  64. package/src/client/types/sortablejs.d.ts +44 -0
  65. package/src/client/upload-with-metadata.ts +54 -0
  66. package/src/client/video-processor.ts +207 -0
  67. package/src/client.ts +27 -17
  68. package/src/db/__tests__/migrations.test.ts +118 -0
  69. package/src/db/index.ts +52 -0
  70. package/src/db/migrations/0000_baseline.sql +269 -0
  71. package/src/db/migrations/0001_fts_setup.sql +31 -0
  72. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  73. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  74. package/src/db/migrations/meta/_journal.json +4 -39
  75. package/src/db/schema.ts +409 -140
  76. package/src/i18n/__tests__/detect.test.ts +115 -0
  77. package/src/i18n/context.tsx +2 -2
  78. package/src/i18n/detect.ts +85 -1
  79. package/src/i18n/i18n.ts +1 -1
  80. package/src/i18n/index.ts +2 -1
  81. package/src/i18n/locales/en.po +783 -1087
  82. package/src/i18n/locales/en.ts +1 -1
  83. package/src/i18n/locales/zh-Hans.po +867 -812
  84. package/src/i18n/locales/zh-Hans.ts +1 -1
  85. package/src/i18n/locales/zh-Hant.po +878 -823
  86. package/src/i18n/locales/zh-Hant.ts +1 -1
  87. package/src/i18n/middleware.ts +6 -0
  88. package/src/index.ts +5 -7
  89. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  90. package/src/lib/__tests__/constants.test.ts +0 -1
  91. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  92. package/src/lib/__tests__/nanoid.test.ts +26 -0
  93. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  94. package/src/lib/__tests__/schemas.test.ts +186 -65
  95. package/src/lib/__tests__/slug.test.ts +126 -0
  96. package/src/lib/__tests__/sse.test.ts +6 -6
  97. package/src/lib/__tests__/summary.test.ts +264 -0
  98. package/src/lib/__tests__/theme.test.ts +1 -1
  99. package/src/lib/__tests__/timeline.test.ts +33 -30
  100. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  101. package/src/lib/__tests__/url.test.ts +2 -2
  102. package/src/lib/__tests__/view.test.ts +140 -65
  103. package/src/lib/blurhash-placeholder.ts +102 -0
  104. package/src/lib/constants.ts +3 -1
  105. package/src/lib/emoji-catalog.ts +963 -0
  106. package/src/lib/errors.ts +11 -8
  107. package/src/lib/feed.ts +77 -31
  108. package/src/lib/html.ts +2 -1
  109. package/src/lib/icon-catalog.ts +5033 -1
  110. package/src/lib/icons.ts +3 -2
  111. package/src/lib/index.ts +0 -1
  112. package/src/lib/markdown-to-tiptap.ts +286 -0
  113. package/src/lib/media-helpers.ts +22 -12
  114. package/src/lib/nanoid.ts +29 -0
  115. package/src/lib/navigation.ts +1 -1
  116. package/src/lib/render.tsx +24 -5
  117. package/src/lib/resolve-config.ts +13 -2
  118. package/src/lib/schemas.ts +226 -58
  119. package/src/lib/search-snippet.ts +34 -0
  120. package/src/lib/slug.ts +96 -0
  121. package/src/lib/sse.ts +6 -6
  122. package/src/lib/storage.ts +115 -7
  123. package/src/lib/summary.ts +158 -0
  124. package/src/lib/theme.ts +11 -8
  125. package/src/lib/timeline.ts +76 -34
  126. package/src/lib/tiptap-render.ts +191 -0
  127. package/src/lib/tiptap-to-markdown.ts +305 -0
  128. package/src/lib/upload.ts +263 -14
  129. package/src/lib/url.ts +37 -22
  130. package/src/lib/view.ts +236 -55
  131. package/src/middleware/__tests__/auth.test.ts +191 -11
  132. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  133. package/src/middleware/auth.ts +63 -9
  134. package/src/middleware/error-handler.ts +3 -3
  135. package/src/middleware/onboarding.ts +1 -1
  136. package/src/middleware/secure-headers.ts +40 -0
  137. package/src/preset.css +83 -2
  138. package/src/routes/__tests__/compose.test.ts +17 -24
  139. package/src/routes/api/__tests__/collections.test.ts +109 -61
  140. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  141. package/src/routes/api/__tests__/posts.test.ts +132 -68
  142. package/src/routes/api/__tests__/search.test.ts +15 -2
  143. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  144. package/src/routes/api/collections.ts +57 -31
  145. package/src/routes/api/custom-urls.ts +80 -0
  146. package/src/routes/api/export.ts +31 -0
  147. package/src/routes/api/nav-items.ts +23 -19
  148. package/src/routes/api/posts.ts +81 -62
  149. package/src/routes/api/search.ts +3 -4
  150. package/src/routes/api/upload-multipart.ts +245 -0
  151. package/src/routes/api/upload.ts +92 -24
  152. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  153. package/src/routes/auth/reset.tsx +5 -4
  154. package/src/routes/auth/setup.tsx +39 -31
  155. package/src/routes/auth/signin.tsx +13 -14
  156. package/src/routes/compose.tsx +27 -63
  157. package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
  158. package/src/routes/dash/custom-urls.tsx +414 -0
  159. package/src/routes/dash/settings.tsx +475 -99
  160. package/src/routes/feed/__tests__/rss.test.ts +22 -23
  161. package/src/routes/feed/rss.ts +6 -2
  162. package/src/routes/feed/sitemap.ts +2 -12
  163. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  164. package/src/routes/pages/__tests__/featured.test.ts +36 -18
  165. package/src/routes/pages/archive.tsx +177 -37
  166. package/src/routes/pages/collection.tsx +43 -14
  167. package/src/routes/pages/collections.tsx +11 -2
  168. package/src/routes/pages/featured.tsx +27 -3
  169. package/src/routes/pages/home.tsx +15 -14
  170. package/src/routes/pages/latest.tsx +1 -11
  171. package/src/routes/pages/new.tsx +39 -0
  172. package/src/routes/pages/page.tsx +95 -49
  173. package/src/routes/pages/search.tsx +1 -1
  174. package/src/services/__tests__/api-token.test.ts +135 -0
  175. package/src/services/__tests__/collection.test.ts +275 -227
  176. package/src/services/__tests__/custom-url.test.ts +213 -0
  177. package/src/services/__tests__/media.test.ts +162 -22
  178. package/src/services/__tests__/navigation.test.ts +109 -68
  179. package/src/services/__tests__/post-timeline.test.ts +205 -32
  180. package/src/services/__tests__/post.test.ts +800 -230
  181. package/src/services/__tests__/search.test.ts +67 -10
  182. package/src/services/__tests__/settings.test.ts +3 -3
  183. package/src/services/api-token.ts +166 -0
  184. package/src/services/auth.ts +17 -2
  185. package/src/services/collection.ts +397 -131
  186. package/src/services/custom-url.ts +188 -0
  187. package/src/services/export.ts +802 -0
  188. package/src/services/index.ts +26 -19
  189. package/src/services/media.ts +100 -22
  190. package/src/services/navigation.ts +158 -47
  191. package/src/services/path.ts +339 -0
  192. package/src/services/post.ts +764 -172
  193. package/src/services/search.ts +161 -74
  194. package/src/services/settings.ts +6 -2
  195. package/src/styles/components.css +293 -62
  196. package/src/styles/tokens.css +93 -5
  197. package/src/styles/ui.css +4349 -766
  198. package/src/types/bindings.ts +8 -0
  199. package/src/types/config.ts +34 -4
  200. package/src/types/constants.ts +17 -2
  201. package/src/types/entities.ts +83 -37
  202. package/src/types/operations.ts +20 -27
  203. package/src/types/props.ts +52 -17
  204. package/src/types/views.ts +48 -24
  205. package/src/ui/color-themes.ts +133 -23
  206. package/src/ui/compose/ComposeDialog.tsx +255 -16
  207. package/src/ui/compose/ComposePrompt.tsx +1 -1
  208. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  209. package/src/ui/dash/ListItemRow.tsx +1 -1
  210. package/src/ui/dash/StatusBadge.tsx +12 -2
  211. package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
  212. package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
  213. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  214. package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
  215. package/src/ui/dash/index.ts +0 -3
  216. package/src/ui/dash/settings/AccountContent.tsx +87 -146
  217. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  218. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  219. package/src/ui/dash/settings/AvatarContent.tsx +78 -0
  220. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  221. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  222. package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
  223. package/src/ui/feed/LinkCard.tsx +89 -40
  224. package/src/ui/feed/NoteCard.tsx +39 -25
  225. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  226. package/src/ui/feed/QuoteCard.tsx +38 -23
  227. package/src/ui/feed/ThreadPreview.tsx +90 -26
  228. package/src/ui/feed/TimelineFeed.tsx +3 -2
  229. package/src/ui/feed/TimelineItem.tsx +15 -6
  230. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  231. package/src/ui/feed/thread-preview-state.ts +61 -0
  232. package/src/ui/font-themes.ts +2 -2
  233. package/src/ui/layouts/BaseLayout.tsx +2 -2
  234. package/src/ui/layouts/SiteLayout.tsx +116 -103
  235. package/src/ui/pages/ArchivePage.tsx +923 -95
  236. package/src/ui/pages/CollectionPage.tsx +6 -35
  237. package/src/ui/pages/CollectionsPage.tsx +2 -1
  238. package/src/ui/pages/ComposePage.tsx +54 -0
  239. package/src/ui/pages/FeaturedPage.tsx +2 -1
  240. package/src/ui/pages/HomePage.tsx +1 -1
  241. package/src/ui/pages/PostPage.tsx +30 -45
  242. package/src/ui/pages/SearchPage.tsx +182 -38
  243. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  244. package/src/ui/shared/CollectionsSidebar.tsx +239 -4
  245. package/src/ui/shared/MediaGallery.tsx +475 -41
  246. package/src/ui/shared/PostFooter.tsx +204 -0
  247. package/src/ui/shared/StarRating.tsx +27 -0
  248. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  249. package/src/ui/shared/index.ts +0 -1
  250. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  251. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  252. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  253. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  254. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  255. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  256. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  257. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  258. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  259. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  260. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  261. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  262. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  263. package/src/lib/__tests__/sqid.test.ts +0 -65
  264. package/src/lib/collections-reorder.ts +0 -28
  265. package/src/lib/compose-bridge.ts +0 -280
  266. package/src/lib/media-upload.ts +0 -148
  267. package/src/lib/sqid.ts +0 -79
  268. package/src/routes/api/__tests__/pages.test.ts +0 -218
  269. package/src/routes/api/pages.ts +0 -73
  270. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  271. package/src/routes/dash/appearance.tsx +0 -240
  272. package/src/routes/dash/collections.tsx +0 -211
  273. package/src/routes/dash/index.tsx +0 -103
  274. package/src/routes/dash/media.tsx +0 -132
  275. package/src/routes/dash/pages.tsx +0 -239
  276. package/src/routes/dash/posts.tsx +0 -334
  277. package/src/routes/dash/redirects.tsx +0 -257
  278. package/src/routes/pages/post.tsx +0 -59
  279. package/src/services/__tests__/page.test.ts +0 -298
  280. package/src/services/__tests__/path-registry.test.ts +0 -165
  281. package/src/services/__tests__/redirect.test.ts +0 -159
  282. package/src/services/page.ts +0 -203
  283. package/src/services/path-registry.ts +0 -160
  284. package/src/services/redirect.ts +0 -97
  285. package/src/types/sortablejs.d.ts +0 -29
  286. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
  287. package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
  288. package/src/ui/components/compose-types.ts +0 -75
  289. package/src/ui/components/jant-collection-form.ts +0 -512
  290. package/src/ui/components/jant-compose-dialog.ts +0 -495
  291. package/src/ui/components/jant-compose-editor.ts +0 -814
  292. package/src/ui/dash/PageForm.tsx +0 -185
  293. package/src/ui/dash/PostList.tsx +0 -95
  294. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  295. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  296. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  297. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  298. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  299. package/src/ui/dash/media/MediaListContent.tsx +0 -201
  300. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  301. package/src/ui/dash/pages/PagesContent.tsx +0 -74
  302. package/src/ui/dash/posts/PostForm.tsx +0 -248
  303. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  304. package/src/ui/layouts/DashLayout.tsx +0 -165
  305. package/src/ui/pages/SinglePage.tsx +0 -23
  306. package/src/ui/shared/ThreadView.tsx +0 -136
  307. /package/src/{ui → client}/components/settings-types.ts +0 -0
@@ -1,185 +0,0 @@
1
- /**
2
- * Page Creation/Edit Form
3
- *
4
- * For managing standalone pages (about, now, etc.)
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { Page } from "../../types.js";
9
- import { useLingui } from "@lingui/react/macro";
10
-
11
- export interface PageFormProps {
12
- page?: Page;
13
- action: string;
14
- cancelUrl?: string;
15
- }
16
-
17
- export const PageForm: FC<PageFormProps> = ({
18
- page,
19
- action,
20
- cancelUrl = "/dash/pages",
21
- }) => {
22
- const { t } = useLingui();
23
- const isEdit = !!page;
24
-
25
- const signals = JSON.stringify({
26
- title: page?.title ?? "",
27
- slug: page?.slug ?? "",
28
- body: page?.body ?? "",
29
- status: page?.status ?? "published",
30
- }).replace(/</g, "\\u003c");
31
-
32
- return (
33
- <form
34
- data-signals={signals}
35
- data-on:submit__prevent={`@post('${action}')`}
36
- data-indicator="_loading"
37
- class="flex flex-col gap-4"
38
- >
39
- <div id="page-form-message"></div>
40
-
41
- {/* Title */}
42
- <div class="field">
43
- <label class="label">
44
- {t({
45
- message: "Title",
46
- comment: "@context: Page form field label - title",
47
- })}
48
- </label>
49
- <input
50
- type="text"
51
- data-bind="title"
52
- class="input"
53
- placeholder={t({
54
- message: "Page title...",
55
- comment: "@context: Page title placeholder",
56
- })}
57
- required
58
- />
59
- </div>
60
-
61
- {/* Slug */}
62
- <div class="field">
63
- <label class="label">
64
- {t({
65
- message: "Slug",
66
- comment: "@context: Page form field label - URL slug",
67
- })}
68
- </label>
69
- <div class="flex items-center gap-2">
70
- <span class="text-muted-foreground">/</span>
71
- <input
72
- type="text"
73
- data-bind="slug"
74
- class="input flex-1"
75
- placeholder="about"
76
- pattern="[a-z0-9\-]+"
77
- title={t({
78
- message: "Lowercase letters, numbers, and hyphens only",
79
- comment: "@context: Page slug validation message",
80
- })}
81
- required
82
- />
83
- </div>
84
- <p class="text-xs text-muted-foreground mt-1">
85
- {t({
86
- message:
87
- "The URL path for this page. Use lowercase letters, numbers, and hyphens.",
88
- comment: "@context: Page slug helper text",
89
- })}
90
- </p>
91
- </div>
92
-
93
- {/* Body */}
94
- <div class="field">
95
- <label class="label">
96
- {t({
97
- message: "Content",
98
- comment: "@context: Page form field label - content",
99
- })}
100
- </label>
101
- <textarea
102
- data-bind="body"
103
- class="textarea min-h-48"
104
- placeholder={t({
105
- message: "Page content (Markdown supported)...",
106
- comment: "@context: Page content placeholder",
107
- })}
108
- required
109
- >
110
- {page?.body ?? ""}
111
- </textarea>
112
- </div>
113
-
114
- {/* Status */}
115
- <div class="field">
116
- <label class="label">
117
- {t({
118
- message: "Status",
119
- comment: "@context: Page form field label - publish status",
120
- })}
121
- </label>
122
- <select data-bind="status" class="select">
123
- <option
124
- value="published"
125
- selected={page?.status === "published" || !page}
126
- >
127
- {t({
128
- message: "Published",
129
- comment: "@context: Page status option - published",
130
- })}
131
- </option>
132
- <option value="draft" selected={page?.status === "draft"}>
133
- {t({
134
- message: "Draft",
135
- comment: "@context: Page status option - draft",
136
- })}
137
- </option>
138
- </select>
139
- <p class="text-xs text-muted-foreground mt-1">
140
- {t({
141
- message:
142
- "Published pages are accessible via their slug. Drafts are not visible.",
143
- comment: "@context: Page status helper text",
144
- })}
145
- </p>
146
- </div>
147
-
148
- {/* Submit */}
149
- <div class="flex gap-2">
150
- <button type="submit" class="btn" data-attr:disabled="$_loading">
151
- <svg
152
- data-show="$_loading"
153
- style="display:none"
154
- class="animate-spin size-4"
155
- xmlns="http://www.w3.org/2000/svg"
156
- viewBox="0 0 24 24"
157
- fill="none"
158
- stroke="currentColor"
159
- stroke-width="2"
160
- stroke-linecap="round"
161
- stroke-linejoin="round"
162
- role="status"
163
- >
164
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
165
- </svg>
166
- {isEdit
167
- ? t({
168
- message: "Update Page",
169
- comment: "@context: Button to update existing page",
170
- })
171
- : t({
172
- message: "Create Page",
173
- comment: "@context: Button to create new page",
174
- })}
175
- </button>
176
- <a href={cancelUrl} class="btn-outline">
177
- {t({
178
- message: "Cancel",
179
- comment: "@context: Button to cancel and go back",
180
- })}
181
- </a>
182
- </div>
183
- </form>
184
- );
185
- };
@@ -1,95 +0,0 @@
1
- /**
2
- * Post List Component
3
- */
4
-
5
- import type { FC } from "hono/jsx";
6
- import { useLingui } from "@lingui/react/macro";
7
- import type { PostView } from "../../types.js";
8
- import * as sqid from "../../lib/sqid.js";
9
- import { StatusBadge } from "./StatusBadge.js";
10
- import { FormatBadge } from "./FormatBadge.js";
11
- import { EmptyState } from "../shared/EmptyState.js";
12
- import { ListItemRow } from "./ListItemRow.js";
13
- import { ActionButtons } from "./ActionButtons.js";
14
-
15
- export interface PostListProps {
16
- posts: PostView[];
17
- }
18
-
19
- export const PostList: FC<PostListProps> = ({ posts }) => {
20
- const { t } = useLingui();
21
- if (posts.length === 0) {
22
- return (
23
- <EmptyState
24
- message={t({
25
- message: "No posts yet.",
26
- comment: "@context: Empty state message when no posts exist",
27
- })}
28
- ctaText={t({
29
- message: "Create your first post",
30
- comment: "@context: Button in empty state to create first post",
31
- })}
32
- ctaHref="/dash/posts/new"
33
- />
34
- );
35
- }
36
-
37
- return (
38
- <div class="flex flex-col divide-y">
39
- {posts.map((post) => (
40
- <ListItemRow
41
- key={post.id}
42
- actions={
43
- <ActionButtons
44
- editHref={`/dash/posts/${sqid.encode(post.id)}/edit`}
45
- editLabel={t({
46
- message: "Edit",
47
- comment: "@context: Button to edit post",
48
- })}
49
- viewHref={post.permalink}
50
- viewLabel={t({
51
- message: "View",
52
- comment: "@context: Button to view post on public site",
53
- })}
54
- deleteAction={`/dash/posts/${sqid.encode(post.id)}/delete`}
55
- deleteConfirm={t({
56
- message:
57
- "Are you sure you want to delete this post? This cannot be undone.",
58
- comment:
59
- "@context: Confirmation dialog when deleting a post from the list",
60
- })}
61
- />
62
- }
63
- >
64
- <div class="flex items-center gap-2 mb-1">
65
- <FormatBadge type={post.format} />
66
- <StatusBadge
67
- status={post.status}
68
- featured={post.featured}
69
- pinned={post.pinned}
70
- />
71
- <span class="text-xs text-muted-foreground">
72
- {post.publishedAtFormatted}
73
- </span>
74
- </div>
75
- <a
76
- href={`/dash/posts/${sqid.encode(post.id)}`}
77
- class="font-medium hover:underline"
78
- >
79
- {post.title ||
80
- post.body?.slice(0, 60) ||
81
- t({
82
- message: "Untitled",
83
- comment: "@context: Default title for untitled post",
84
- })}
85
- </a>
86
- {post.body && !post.title && (
87
- <p class="text-sm text-muted-foreground mt-1 line-clamp-2">
88
- {post.body.slice(0, 120)}
89
- </p>
90
- )}
91
- </ListItemRow>
92
- ))}
93
- </div>
94
- );
95
- };
@@ -1,60 +0,0 @@
1
- /**
2
- * Appearance sub-navigation tabs
3
- */
4
-
5
- import { useLingui } from "@lingui/react/macro";
6
-
7
- export type AppearanceTab = "navigation" | "color" | "fonts" | "advanced";
8
-
9
- export function AppearanceNav({ currentTab }: { currentTab: AppearanceTab }) {
10
- const { t } = useLingui();
11
-
12
- const tabs: { id: AppearanceTab; label: string; href: string }[] = [
13
- {
14
- id: "navigation",
15
- label: t({
16
- message: "Navigation",
17
- comment: "@context: Appearance sub-navigation tab",
18
- }),
19
- href: "/dash/appearance",
20
- },
21
- {
22
- id: "color",
23
- label: t({
24
- message: "Color Theme",
25
- comment: "@context: Appearance sub-navigation tab",
26
- }),
27
- href: "/dash/appearance/color",
28
- },
29
- {
30
- id: "fonts",
31
- label: t({
32
- message: "Font Theme",
33
- comment: "@context: Appearance sub-navigation tab",
34
- }),
35
- href: "/dash/appearance/fonts",
36
- },
37
- {
38
- id: "advanced",
39
- label: t({
40
- message: "Advanced",
41
- comment: "@context: Appearance sub-navigation tab",
42
- }),
43
- href: "/dash/appearance/advanced",
44
- },
45
- ];
46
-
47
- return (
48
- <nav class="dash-subnav">
49
- {tabs.map((tab) => (
50
- <a
51
- key={tab.id}
52
- href={tab.href}
53
- class={tab.id === currentTab ? "active" : ""}
54
- >
55
- {tab.label}
56
- </a>
57
- ))}
58
- </nav>
59
- );
60
- }
@@ -1,166 +0,0 @@
1
- /**
2
- * Collection Form
3
- *
4
- * Server-rendered shell that provides data/labels to the Lit component
5
- * `<jant-collection-form>`. Includes heading and SSR fallback skeleton.
6
- */
7
-
8
- import { useLingui } from "@lingui/react/macro";
9
- import type { FC } from "hono/jsx";
10
- import type { Collection } from "../../../types.js";
11
-
12
- interface CollectionFormProps {
13
- collection?: Collection;
14
- isEdit?: boolean;
15
- }
16
-
17
- export const CollectionForm: FC<CollectionFormProps> = ({
18
- collection,
19
- isEdit,
20
- }) => {
21
- const { t } = useLingui();
22
-
23
- const heading = isEdit
24
- ? t({ message: "Edit Collection", comment: "@context: Page heading" })
25
- : t({ message: "New Collection", comment: "@context: Page heading" });
26
-
27
- const submitLabel = isEdit
28
- ? t({
29
- message: "Update Collection",
30
- comment: "@context: Button to save collection changes",
31
- })
32
- : t({
33
- message: "Create Collection",
34
- comment: "@context: Button to save new collection",
35
- });
36
-
37
- const labels = JSON.stringify({
38
- titleLabel: t({
39
- message: "Title",
40
- comment: "@context: Collection form field",
41
- }),
42
- titlePlaceholder: t({
43
- message: "My Collection",
44
- comment: "@context: Collection title placeholder",
45
- }),
46
- slugLabel: t({
47
- message: "Slug",
48
- comment: "@context: Collection form field",
49
- }),
50
- slugHelp: t({
51
- message:
52
- "URL-safe identifier (lowercase, numbers, hyphens). For CJK titles, slug will be auto-generated on the server.",
53
- comment: "@context: Collection path help text",
54
- }),
55
- descriptionLabel: t({
56
- message: "Description (optional)",
57
- comment: "@context: Collection form field",
58
- }),
59
- descriptionPlaceholder: t({
60
- message: "What's this collection about?",
61
- comment: "@context: Collection description placeholder",
62
- }),
63
- iconLabel: t({
64
- message: "Icon (optional)",
65
- comment: "@context: Collection form field",
66
- }),
67
- chooseIcon: t({
68
- message: "Choose Icon",
69
- comment: "@context: Button to open icon picker",
70
- }),
71
- removeIcon: t({
72
- message: "Remove",
73
- comment: "@context: Button to remove icon",
74
- }),
75
- dialogTitle: t({
76
- message: "Choose Icon",
77
- comment: "@context: Icon picker dialog title",
78
- }),
79
- dialogClose: t({
80
- message: "Close",
81
- comment: "@context: Button to close icon picker",
82
- }),
83
- searchIconsPlaceholder: t({
84
- message: "Search icons...",
85
- comment: "@context: Icon picker search placeholder",
86
- }),
87
- sortOrderLabel: t({
88
- message: "Sort Order",
89
- comment: "@context: Collection form field",
90
- }),
91
- sortNewest: t({
92
- message: "Newest first",
93
- comment: "@context: Collection sort order option",
94
- }),
95
- sortOldest: t({
96
- message: "Oldest first",
97
- comment: "@context: Collection sort order option",
98
- }),
99
- sortRatingDesc: t({
100
- message: "Highest rated",
101
- comment: "@context: Collection sort order option",
102
- }),
103
- sortRatingAsc: t({
104
- message: "Lowest rated",
105
- comment: "@context: Collection sort order option",
106
- }),
107
- submitLabel,
108
- cancelLabel: t({
109
- message: "Cancel",
110
- comment: "@context: Button to cancel form",
111
- }),
112
- }).replace(/</g, "\\u003c");
113
-
114
- const initial = JSON.stringify({
115
- title: collection?.title ?? "",
116
- slug: collection?.slug ?? "",
117
- description: collection?.description ?? "",
118
- sortOrder: collection?.sortOrder ?? "newest",
119
- icon: collection?.icon ?? "",
120
- }).replace(/</g, "\\u003c");
121
-
122
- const action = isEdit
123
- ? `/dash/collections/${collection?.id}`
124
- : "/dash/collections";
125
-
126
- const cancelHref = isEdit
127
- ? `/dash/collections/${collection?.id}`
128
- : "/dash/collections";
129
-
130
- return (
131
- <>
132
- <h1 class="text-2xl font-semibold mb-6">{heading}</h1>
133
-
134
- <jant-collection-form
135
- labels={labels}
136
- initial={initial}
137
- action={action}
138
- cancel-href={cancelHref}
139
- is-edit={isEdit ? "true" : undefined}
140
- >
141
- <div class="flex flex-col gap-4 max-w-lg">
142
- <div class="field">
143
- <div class="label skel-label"></div>
144
- <div class="input skel-input"></div>
145
- </div>
146
- <div class="field">
147
- <div class="label skel-label"></div>
148
- <div class="input skel-input"></div>
149
- </div>
150
- <div class="field">
151
- <div class="label skel-label"></div>
152
- <div class="textarea skel-textarea"></div>
153
- </div>
154
- <div class="field">
155
- <div class="label skel-label"></div>
156
- <div class="input skel-input"></div>
157
- </div>
158
- <div class="flex gap-2">
159
- <div class="btn skel-input min-w-28"></div>
160
- <div class="btn-outline skel-input min-w-20"></div>
161
- </div>
162
- </div>
163
- </jant-collection-form>
164
- </>
165
- );
166
- };
@@ -1,146 +0,0 @@
1
- /**
2
- * Collections list view with drag-and-drop reordering
3
- */
4
-
5
- import { useLingui } from "@lingui/react/macro";
6
- import type { Collection, CollectionDivider } from "../../../types.js";
7
- import { EmptyState, ActionButtons, CrudPageHeader } from "../index.js";
8
- import { renderCollectionIcon } from "../../../lib/icons.js";
9
-
10
- type ListItem =
11
- | { type: "collection"; data: Collection }
12
- | { type: "divider"; data: CollectionDivider };
13
-
14
- export function CollectionsListContent({
15
- collections,
16
- dividers,
17
- postCounts,
18
- }: {
19
- collections: Collection[];
20
- dividers: CollectionDivider[];
21
- postCounts: Map<number, number>;
22
- }) {
23
- const { t } = useLingui();
24
-
25
- const items: ListItem[] = [
26
- ...collections.map((c) => ({ type: "collection", data: c }) as ListItem),
27
- ...dividers.map((d) => ({ type: "divider", data: d }) as ListItem),
28
- ].sort((a, b) => a.data.position - b.data.position);
29
-
30
- const hasItems = collections.length > 0 || dividers.length > 0;
31
-
32
- return (
33
- <>
34
- <CrudPageHeader
35
- title={t({
36
- message: "Collections",
37
- comment: "@context: Dashboard heading",
38
- })}
39
- >
40
- <div class="flex items-center gap-2">
41
- <form method="post" action="/dash/collections/dividers">
42
- <button type="submit" class="btn-sm-outline">
43
- {t({
44
- message: "New Divider",
45
- comment: "@context: Button to add divider between collections",
46
- })}
47
- </button>
48
- </form>
49
- <a href="/dash/collections/new" class="btn-sm">
50
- {t({
51
- message: "New Collection",
52
- comment: "@context: Button to create new collection",
53
- })}
54
- </a>
55
- </div>
56
- </CrudPageHeader>
57
-
58
- {!hasItems ? (
59
- <EmptyState
60
- message={t({
61
- message: "No collections yet.",
62
- comment: "@context: Empty state message",
63
- })}
64
- ctaText={t({
65
- message: "New Collection",
66
- comment: "@context: Button to create new collection",
67
- })}
68
- ctaHref="/dash/collections/new"
69
- />
70
- ) : (
71
- <div id="collections-list" class="flex flex-col">
72
- {items.map((item) => {
73
- if (item.type === "divider") {
74
- return (
75
- <div
76
- key={`d-${item.data.id}`}
77
- class="py-2 flex items-center gap-4"
78
- >
79
- <div
80
- class="flex-1 min-w-0 flex items-center gap-3 cursor-grab"
81
- data-id={`d-${item.data.id}`}
82
- >
83
- <span class="text-muted-foreground select-none">⠿</span>
84
- <hr class="flex-1 border-border" />
85
- </div>
86
- <form
87
- method="post"
88
- action={`/dash/collections/dividers/${item.data.id}/delete`}
89
- >
90
- <button
91
- type="submit"
92
- class="btn-sm-ghost text-muted-foreground hover:text-destructive"
93
- title={t({
94
- message: "Remove divider",
95
- comment: "@context: Button to delete a divider",
96
- })}
97
- >
98
-
99
- </button>
100
- </form>
101
- </div>
102
- );
103
- }
104
-
105
- const col = item.data;
106
- const count = postCounts.get(col.id) ?? 0;
107
- return (
108
- <div key={`c-${col.id}`} class="py-2 flex items-center gap-4">
109
- <div
110
- class="flex-1 min-w-0 flex items-center gap-3 cursor-grab"
111
- data-id={`c-${col.id}`}
112
- >
113
- <span class="text-muted-foreground select-none">⠿</span>
114
- {col.icon && (
115
- <span
116
- class="flex items-center justify-center w-5 h-5 shrink-0"
117
- dangerouslySetInnerHTML={{
118
- __html: renderCollectionIcon(col.icon, {
119
- size: 18,
120
- }),
121
- }}
122
- />
123
- )}
124
- <a
125
- href={`/dash/collections/${col.id}`}
126
- class="font-medium hover:underline"
127
- >
128
- {col.title}
129
- </a>
130
- <span class="badge-secondary">{count}</span>
131
- </div>
132
- <ActionButtons
133
- editHref={`/dash/collections/${col.id}/edit`}
134
- editLabel={t({
135
- message: "Edit",
136
- comment: "@context: Button to edit collection",
137
- })}
138
- />
139
- </div>
140
- );
141
- })}
142
- </div>
143
- )}
144
- </>
145
- );
146
- }