@jant/core 0.3.27 → 0.3.28

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 (313) hide show
  1. package/dist/client/client.css +1 -0
  2. package/dist/client/client.js +31561 -0
  3. package/dist/index.js +15209 -15
  4. package/package.json +21 -15
  5. package/src/__tests__/helpers/app.ts +19 -3
  6. package/src/__tests__/helpers/db.ts +44 -0
  7. package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
  8. package/src/app.tsx +111 -174
  9. package/src/client.ts +13 -0
  10. package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
  11. package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
  12. package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
  13. package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
  14. package/src/db/schema.ts +24 -4
  15. package/src/i18n/locales/en.po +810 -385
  16. package/src/i18n/locales/en.ts +1 -1
  17. package/src/i18n/locales/zh-Hans.po +733 -522
  18. package/src/i18n/locales/zh-Hans.ts +1 -1
  19. package/src/i18n/locales/zh-Hant.po +733 -522
  20. package/src/i18n/locales/zh-Hant.ts +1 -1
  21. package/src/i18n/middleware.ts +7 -11
  22. package/src/index.ts +1 -1
  23. package/src/lib/__tests__/icons.test.ts +178 -0
  24. package/src/lib/__tests__/resolve-config.test.ts +184 -0
  25. package/src/lib/__tests__/schemas.test.ts +12 -6
  26. package/src/lib/__tests__/theme.test.ts +62 -0
  27. package/src/lib/__tests__/timezones.test.ts +1 -1
  28. package/src/lib/__tests__/url.test.ts +12 -0
  29. package/src/lib/__tests__/view.test.ts +1 -5
  30. package/src/lib/avatar-upload.ts +18 -10
  31. package/src/lib/collection-form-bridge.ts +52 -0
  32. package/src/lib/collections-reorder.ts +28 -0
  33. package/src/lib/compose-bridge.ts +251 -0
  34. package/src/lib/errors.ts +116 -0
  35. package/src/lib/excerpt.ts +1 -1
  36. package/src/lib/favicon.ts +3 -5
  37. package/src/lib/html.ts +22 -0
  38. package/src/lib/icon-catalog.ts +181 -0
  39. package/src/lib/icons.ts +202 -0
  40. package/src/lib/navigation.ts +18 -33
  41. package/src/lib/pagination.ts +3 -2
  42. package/src/lib/post-form-bridge.ts +136 -0
  43. package/src/lib/render.tsx +11 -4
  44. package/src/lib/resolve-config.ts +157 -0
  45. package/src/lib/schemas.ts +76 -12
  46. package/src/lib/settings-bridge.ts +139 -0
  47. package/src/lib/storage.ts +37 -16
  48. package/src/lib/theme.ts +5 -7
  49. package/src/lib/timeline.ts +4 -8
  50. package/src/lib/toast.ts +134 -0
  51. package/src/lib/upload.ts +71 -0
  52. package/src/lib/url.ts +9 -1
  53. package/src/lib/version.ts +16 -0
  54. package/src/lib/view.ts +9 -10
  55. package/src/middleware/__tests__/auth.test.ts +6 -28
  56. package/src/middleware/__tests__/onboarding.test.ts +1 -1
  57. package/src/middleware/auth.ts +6 -12
  58. package/src/middleware/config.ts +51 -0
  59. package/src/middleware/error-handler.ts +56 -0
  60. package/src/middleware/onboarding.ts +1 -1
  61. package/src/preset.css +6 -0
  62. package/src/routes/__tests__/compose.test.ts +104 -17
  63. package/src/routes/api/__tests__/collections.test.ts +93 -2
  64. package/src/routes/api/__tests__/posts.test.ts +2 -1
  65. package/src/routes/api/__tests__/settings.test.ts +1 -1
  66. package/src/routes/api/collections.ts +64 -68
  67. package/src/routes/api/nav-items.ts +21 -59
  68. package/src/routes/api/pages.ts +18 -46
  69. package/src/routes/api/posts.ts +64 -86
  70. package/src/routes/api/search.ts +6 -4
  71. package/src/routes/api/settings.ts +8 -24
  72. package/src/routes/api/upload.ts +55 -53
  73. package/src/routes/auth/__tests__/setup.test.ts +118 -0
  74. package/src/routes/auth/reset.tsx +17 -66
  75. package/src/routes/auth/setup.tsx +67 -11
  76. package/src/routes/auth/signin.tsx +44 -8
  77. package/src/routes/compose.tsx +194 -0
  78. package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
  79. package/src/routes/dash/__tests__/pages.test.ts +2 -2
  80. package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
  81. package/src/routes/dash/appearance.tsx +173 -0
  82. package/src/routes/dash/collections.tsx +80 -14
  83. package/src/routes/dash/index.tsx +12 -14
  84. package/src/routes/dash/media.tsx +46 -49
  85. package/src/routes/dash/pages.tsx +85 -37
  86. package/src/routes/dash/posts.tsx +60 -23
  87. package/src/routes/dash/redirects.tsx +43 -33
  88. package/src/routes/dash/settings.tsx +234 -214
  89. package/src/routes/feed/__tests__/rss.test.ts +7 -3
  90. package/src/routes/feed/rss.ts +11 -16
  91. package/src/routes/feed/sitemap.ts +15 -9
  92. package/src/routes/pages/__tests__/collections.test.ts +9 -8
  93. package/src/routes/pages/archive.tsx +2 -2
  94. package/src/routes/pages/collection.tsx +76 -9
  95. package/src/routes/pages/collections.tsx +3 -1
  96. package/src/routes/pages/featured.tsx +2 -2
  97. package/src/routes/pages/home.tsx +3 -3
  98. package/src/routes/pages/latest.tsx +2 -2
  99. package/src/routes/pages/page.tsx +2 -2
  100. package/src/routes/pages/post.tsx +2 -2
  101. package/src/routes/pages/search.tsx +2 -2
  102. package/src/services/__tests__/collection.test.ts +324 -34
  103. package/src/services/__tests__/media.test.ts +1 -1
  104. package/src/services/__tests__/page.test.ts +116 -1
  105. package/src/services/auth.ts +88 -0
  106. package/src/services/collection.ts +169 -30
  107. package/src/services/index.ts +8 -3
  108. package/src/services/media.ts +39 -12
  109. package/src/services/navigation.ts +17 -5
  110. package/src/services/page.ts +24 -4
  111. package/src/services/post.ts +87 -19
  112. package/src/services/search.ts +0 -1
  113. package/src/services/settings.ts +21 -13
  114. package/src/style.css +3 -0
  115. package/src/styles/components.css +42 -1
  116. package/src/styles/tokens.css +4 -0
  117. package/src/styles/ui.css +902 -73
  118. package/src/types/app-context.ts +25 -0
  119. package/src/types/bindings.ts +1 -0
  120. package/src/types/config.ts +60 -23
  121. package/src/types/entities.ts +12 -2
  122. package/src/types/lingui-react-macro.d.ts +3 -3
  123. package/src/types/operations.ts +2 -4
  124. package/src/types/views.ts +1 -3
  125. package/src/ui/__tests__/font-themes.test.ts +27 -8
  126. package/src/ui/color-themes.ts +1 -1
  127. package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
  128. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
  129. package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
  130. package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
  131. package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
  132. package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
  133. package/src/ui/components/collection-types.ts +45 -0
  134. package/src/ui/components/compose-types.ts +75 -0
  135. package/src/ui/components/jant-collection-form.ts +512 -0
  136. package/src/ui/components/jant-compose-dialog.ts +494 -0
  137. package/src/ui/components/jant-compose-editor.ts +799 -0
  138. package/src/ui/components/jant-post-form.ts +290 -0
  139. package/src/ui/components/jant-settings-avatar.ts +231 -0
  140. package/src/ui/components/jant-settings-general.ts +436 -0
  141. package/src/ui/components/post-form-template.ts +260 -0
  142. package/src/ui/components/post-form-types.ts +87 -0
  143. package/src/ui/components/settings-types.ts +62 -0
  144. package/src/ui/compose/ComposeDialog.tsx +141 -385
  145. package/src/ui/compose/ComposePrompt.tsx +3 -3
  146. package/src/ui/dash/PostList.tsx +55 -61
  147. package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
  148. package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
  149. package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
  150. package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
  151. package/src/ui/dash/collections/CollectionForm.tsx +130 -117
  152. package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
  153. package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
  154. package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
  155. package/src/ui/dash/index.ts +1 -1
  156. package/src/ui/dash/posts/PostForm.tsx +248 -0
  157. package/src/ui/dash/settings/AccountContent.tsx +69 -80
  158. package/src/ui/dash/settings/GeneralContent.tsx +159 -478
  159. package/src/ui/dash/settings/SettingsNav.tsx +4 -4
  160. package/src/ui/font-themes.ts +115 -32
  161. package/src/ui/layouts/BaseLayout.tsx +49 -19
  162. package/src/ui/layouts/DashLayout.tsx +14 -9
  163. package/src/ui/layouts/SiteLayout.tsx +38 -23
  164. package/src/ui/pages/CollectionPage.tsx +12 -2
  165. package/src/ui/pages/CollectionsPage.tsx +27 -27
  166. package/src/ui/pages/HomePage.tsx +15 -6
  167. package/src/ui/pages/SearchPage.tsx +1 -2
  168. package/src/ui/shared/CollectionsSidebar.tsx +59 -0
  169. package/src/ui/shared/Pagination.tsx +2 -2
  170. package/dist/app.js +0 -267
  171. package/dist/auth.js +0 -39
  172. package/dist/client.js +0 -13
  173. package/dist/db/index.js +0 -10
  174. package/dist/db/schema.js +0 -224
  175. package/dist/i18n/Trans.js +0 -24
  176. package/dist/i18n/context.js +0 -58
  177. package/dist/i18n/detect.js +0 -26
  178. package/dist/i18n/i18n.js +0 -49
  179. package/dist/i18n/index.js +0 -44
  180. package/dist/i18n/locales/en.js +0 -1
  181. package/dist/i18n/locales/zh-Hans.js +0 -1
  182. package/dist/i18n/locales/zh-Hant.js +0 -1
  183. package/dist/i18n/locales.js +0 -13
  184. package/dist/i18n/middleware.js +0 -30
  185. package/dist/lib/avatar-upload.js +0 -134
  186. package/dist/lib/config.js +0 -143
  187. package/dist/lib/constants.js +0 -50
  188. package/dist/lib/excerpt.js +0 -76
  189. package/dist/lib/favicon.js +0 -102
  190. package/dist/lib/feed.js +0 -123
  191. package/dist/lib/image-processor.js +0 -187
  192. package/dist/lib/image.js +0 -97
  193. package/dist/lib/index.js +0 -7
  194. package/dist/lib/markdown.js +0 -83
  195. package/dist/lib/media-helpers.js +0 -49
  196. package/dist/lib/media-upload.js +0 -104
  197. package/dist/lib/nav-reorder.js +0 -27
  198. package/dist/lib/navigation.js +0 -79
  199. package/dist/lib/pagination.js +0 -44
  200. package/dist/lib/render.js +0 -53
  201. package/dist/lib/schemas.js +0 -174
  202. package/dist/lib/sqid.js +0 -72
  203. package/dist/lib/sse.js +0 -218
  204. package/dist/lib/storage.js +0 -164
  205. package/dist/lib/theme.js +0 -65
  206. package/dist/lib/time.js +0 -159
  207. package/dist/lib/timeline.js +0 -95
  208. package/dist/lib/timezones.js +0 -388
  209. package/dist/lib/url.js +0 -89
  210. package/dist/lib/view.js +0 -217
  211. package/dist/middleware/auth.js +0 -52
  212. package/dist/middleware/onboarding.js +0 -41
  213. package/dist/routes/api/collections.js +0 -124
  214. package/dist/routes/api/nav-items.js +0 -104
  215. package/dist/routes/api/pages.js +0 -91
  216. package/dist/routes/api/posts.js +0 -218
  217. package/dist/routes/api/search.js +0 -48
  218. package/dist/routes/api/settings.js +0 -68
  219. package/dist/routes/api/upload.js +0 -246
  220. package/dist/routes/auth/reset.js +0 -221
  221. package/dist/routes/auth/setup.js +0 -194
  222. package/dist/routes/auth/signin.js +0 -176
  223. package/dist/routes/compose.js +0 -48
  224. package/dist/routes/dash/collections.js +0 -115
  225. package/dist/routes/dash/index.js +0 -118
  226. package/dist/routes/dash/media.js +0 -106
  227. package/dist/routes/dash/pages.js +0 -294
  228. package/dist/routes/dash/posts.js +0 -244
  229. package/dist/routes/dash/redirects.js +0 -257
  230. package/dist/routes/dash/settings.js +0 -379
  231. package/dist/routes/feed/rss.js +0 -62
  232. package/dist/routes/feed/sitemap.js +0 -49
  233. package/dist/routes/pages/archive.js +0 -62
  234. package/dist/routes/pages/collection.js +0 -34
  235. package/dist/routes/pages/collections.js +0 -28
  236. package/dist/routes/pages/featured.js +0 -36
  237. package/dist/routes/pages/home.js +0 -64
  238. package/dist/routes/pages/latest.js +0 -45
  239. package/dist/routes/pages/page.js +0 -68
  240. package/dist/routes/pages/post.js +0 -44
  241. package/dist/routes/pages/search.js +0 -54
  242. package/dist/services/collection.js +0 -109
  243. package/dist/services/index.js +0 -24
  244. package/dist/services/media.js +0 -117
  245. package/dist/services/navigation.js +0 -91
  246. package/dist/services/page.js +0 -84
  247. package/dist/services/post.js +0 -229
  248. package/dist/services/redirect.js +0 -48
  249. package/dist/services/search.js +0 -67
  250. package/dist/services/settings.js +0 -68
  251. package/dist/types/bindings.js +0 -3
  252. package/dist/types/config.js +0 -147
  253. package/dist/types/constants.js +0 -27
  254. package/dist/types/entities.js +0 -3
  255. package/dist/types/lingui-react-macro.d.js +0 -9
  256. package/dist/types/operations.js +0 -3
  257. package/dist/types/props.js +0 -3
  258. package/dist/types/sortablejs.d.js +0 -5
  259. package/dist/types/views.js +0 -5
  260. package/dist/types.js +0 -11
  261. package/dist/ui/color-themes.js +0 -268
  262. package/dist/ui/compose/ComposeDialog.js +0 -467
  263. package/dist/ui/compose/ComposePrompt.js +0 -55
  264. package/dist/ui/dash/ActionButtons.js +0 -46
  265. package/dist/ui/dash/CrudPageHeader.js +0 -22
  266. package/dist/ui/dash/DangerZone.js +0 -36
  267. package/dist/ui/dash/FormatBadge.js +0 -27
  268. package/dist/ui/dash/ListItemRow.js +0 -21
  269. package/dist/ui/dash/PageForm.js +0 -195
  270. package/dist/ui/dash/PostForm.js +0 -395
  271. package/dist/ui/dash/PostList.js +0 -83
  272. package/dist/ui/dash/StatusBadge.js +0 -46
  273. package/dist/ui/dash/collections/CollectionForm.js +0 -152
  274. package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
  275. package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
  276. package/dist/ui/dash/index.js +0 -10
  277. package/dist/ui/dash/media/MediaListContent.js +0 -166
  278. package/dist/ui/dash/media/ViewMediaContent.js +0 -212
  279. package/dist/ui/dash/pages/LinkFormContent.js +0 -130
  280. package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
  281. package/dist/ui/dash/settings/AccountContent.js +0 -209
  282. package/dist/ui/dash/settings/AppearanceContent.js +0 -259
  283. package/dist/ui/dash/settings/GeneralContent.js +0 -536
  284. package/dist/ui/dash/settings/SettingsNav.js +0 -41
  285. package/dist/ui/feed/LinkCard.js +0 -72
  286. package/dist/ui/feed/NoteCard.js +0 -58
  287. package/dist/ui/feed/QuoteCard.js +0 -63
  288. package/dist/ui/feed/ThreadPreview.js +0 -48
  289. package/dist/ui/feed/TimelineFeed.js +0 -41
  290. package/dist/ui/feed/TimelineItem.js +0 -27
  291. package/dist/ui/font-themes.js +0 -36
  292. package/dist/ui/layouts/BaseLayout.js +0 -153
  293. package/dist/ui/layouts/DashLayout.js +0 -141
  294. package/dist/ui/layouts/SiteLayout.js +0 -169
  295. package/dist/ui/pages/ArchivePage.js +0 -143
  296. package/dist/ui/pages/CollectionPage.js +0 -70
  297. package/dist/ui/pages/CollectionsPage.js +0 -76
  298. package/dist/ui/pages/FeaturedPage.js +0 -24
  299. package/dist/ui/pages/HomePage.js +0 -24
  300. package/dist/ui/pages/PostPage.js +0 -55
  301. package/dist/ui/pages/SearchPage.js +0 -122
  302. package/dist/ui/pages/SinglePage.js +0 -23
  303. package/dist/ui/shared/EmptyState.js +0 -27
  304. package/dist/ui/shared/MediaGallery.js +0 -35
  305. package/dist/ui/shared/Pagination.js +0 -195
  306. package/dist/ui/shared/ThreadView.js +0 -108
  307. package/dist/ui/shared/index.js +0 -5
  308. package/dist/vendor/datastar.js +0 -1606
  309. package/src/lib/__tests__/config.test.ts +0 -192
  310. package/src/lib/config.ts +0 -167
  311. package/src/routes/compose.ts +0 -63
  312. package/src/ui/dash/PostForm.tsx +0 -360
  313. package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
@@ -1,104 +0,0 @@
1
- /**
2
- * Client-side Media Upload Handler
3
- *
4
- * Handles file upload flow:
5
- * 1. User selects file via [data-media-upload] input
6
- * 2. Creates placeholder in grid with spinner
7
- * 3. Processes image via ImageProcessor (resize/convert to WebP)
8
- * 4. Sets processed file on hidden Datastar form via DataTransfer API
9
- * 5. Triggers form.requestSubmit() — Datastar handles upload + SSE response
10
- */ import { ImageProcessor } from "./image-processor.js";
11
- /**
12
- * Format file size for display
13
- */ function formatFileSize(bytes) {
14
- if (bytes < 1024) return `${bytes} B`;
15
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
16
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
17
- }
18
- /**
19
- * Ensure the media grid exists, removing empty state if needed
20
- */ function ensureGridExists() {
21
- let grid = document.getElementById("media-grid");
22
- if (grid) return grid;
23
- document.getElementById("empty-state")?.remove();
24
- grid = document.createElement("div");
25
- grid.id = "media-grid";
26
- grid.className = "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4";
27
- document.getElementById("media-content")?.appendChild(grid);
28
- return grid;
29
- }
30
- /**
31
- * Create a placeholder card with spinner in the media grid
32
- */ function createPlaceholder(fileName, fileSize, statusText) {
33
- const placeholder = document.createElement("div");
34
- placeholder.id = "upload-placeholder";
35
- placeholder.className = "group relative";
36
- placeholder.innerHTML = `
37
- <div class="aspect-square bg-muted rounded-lg overflow-hidden border flex items-center justify-center">
38
- <div class="text-center px-2">
39
- <svg class="animate-spin h-6 w-6 text-muted-foreground mx-auto mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
40
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
41
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
42
- </svg>
43
- <span id="upload-status" class="text-xs text-muted-foreground">${statusText}</span>
44
- </div>
45
- </div>
46
- <div class="mt-2 text-xs truncate" title="${fileName}">${fileName}</div>
47
- <div class="text-xs text-muted-foreground">${formatFileSize(fileSize)}</div>
48
- `;
49
- return placeholder;
50
- }
51
- /**
52
- * Replace placeholder content with an error message
53
- */ function showPlaceholderError(placeholder, fileName, errorMessage) {
54
- placeholder.innerHTML = `
55
- <div class="aspect-square bg-destructive/10 rounded-lg overflow-hidden border border-destructive flex items-center justify-center">
56
- <div class="text-center px-2">
57
- <span class="text-xs text-destructive">${errorMessage}</span>
58
- </div>
59
- </div>
60
- <div class="mt-2 text-xs truncate text-destructive">${fileName}</div>
61
- <button type="button" class="text-xs text-muted-foreground hover:underline" onclick="this.closest('.group').remove()">Remove</button>
62
- `;
63
- }
64
- /**
65
- * Handle the upload flow for a selected file
66
- */ async function handleUpload(input, file) {
67
- const processingText = input.dataset.textProcessing || "Processing...";
68
- const uploadingText = input.dataset.textUploading || "Uploading...";
69
- const errorText = input.dataset.textError || "Upload failed. Please try again.";
70
- const grid = ensureGridExists();
71
- const placeholder = createPlaceholder(file.name, file.size, processingText);
72
- grid.prepend(placeholder);
73
- try {
74
- // Process image client-side (resize, convert to WebP)
75
- const processed = await ImageProcessor.processToFile(file);
76
- // Update status
77
- const statusEl = document.getElementById("upload-status");
78
- if (statusEl) statusEl.textContent = uploadingText;
79
- // Set processed file on hidden form input via DataTransfer API
80
- const formInput = document.getElementById("upload-file-input");
81
- const form = document.getElementById("upload-form");
82
- if (!formInput || !form) throw new Error("Upload form not found");
83
- const dt = new DataTransfer();
84
- dt.items.add(processed);
85
- formInput.files = dt.files;
86
- // Trigger Datastar-intercepted form submission
87
- form.requestSubmit();
88
- } catch (err) {
89
- const message = err instanceof Error ? err.message : errorText;
90
- showPlaceholderError(placeholder, file.name, message);
91
- }
92
- // Reset file input so the same file can be re-selected
93
- input.value = "";
94
- }
95
- /**
96
- * Initialize media upload via event delegation
97
- */ function initMediaUpload() {
98
- document.addEventListener("change", (e)=>{
99
- const input = e.target.closest("[data-media-upload]");
100
- if (!input?.files?.[0]) return;
101
- handleUpload(input, input.files[0]);
102
- });
103
- }
104
- initMediaUpload();
@@ -1,27 +0,0 @@
1
- /**
2
- * Navigation Link Reorder
3
- *
4
- * Initializes SortableJS on the navigation links list in the dashboard.
5
- * Auto-detects the list element and only activates when present.
6
- */ import Sortable from "sortablejs";
7
- const list = document.getElementById("nav-links-list");
8
- if (list) {
9
- Sortable.create(list, {
10
- animation: 150,
11
- handle: "[data-id]",
12
- onEnd () {
13
- const ids = [
14
- ...list.querySelectorAll("[data-id]")
15
- ].map((el)=>Number(el.dataset.id));
16
- fetch("/dash/pages/reorder", {
17
- method: "POST",
18
- headers: {
19
- "Content-Type": "application/json"
20
- },
21
- body: JSON.stringify({
22
- ids
23
- })
24
- });
25
- }
26
- });
27
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * Navigation Helper
3
- *
4
- * Provides shared data fetching for public page navigation.
5
- */ import { getSiteName, getHomeDefaultView, getSiteFooter } from "./config.js";
6
- import { toNavItemViews } from "./view.js";
7
- import { getMediaUrl, getPublicUrlForProvider } from "./image.js";
8
- import { render as renderMarkdown } from "./markdown.js";
9
- /**
10
- * Fetch navigation data for public pages.
11
- *
12
- * Returns NavItemView[] with pre-computed isActive/isExternal state.
13
- * Also checks authentication status and loads collections for authenticated users.
14
- *
15
- * @param c - Hono context
16
- * @returns Navigation data for SiteLayout
17
- *
18
- * @example
19
- * ```typescript
20
- * const navData = await getNavigationData(c);
21
- * return renderPublicPage(c, {
22
- * title: "My Page",
23
- * navData,
24
- * content: <MyContent />,
25
- * });
26
- * ```
27
- */ export async function getNavigationData(c) {
28
- const items = await c.var.services.navItems.list();
29
- const currentPath = new URL(c.req.url).pathname;
30
- const [siteName, homeDefaultView, siteFooter] = await Promise.all([
31
- getSiteName(c),
32
- getHomeDefaultView(c),
33
- getSiteFooter(c)
34
- ]);
35
- // Only include description if explicitly set (DB or env), not the default
36
- const dbDescription = await c.var.services.settings.get("SITE_DESCRIPTION");
37
- const envDescription = c.env.SITE_DESCRIPTION;
38
- const siteDescription = dbDescription || (typeof envDescription === "string" ? envDescription : "");
39
- // Resolve avatar URL from storage key
40
- const avatarKey = await c.var.services.settings.get("SITE_AVATAR");
41
- const showHeaderAvatar = await c.var.services.settings.get("SHOW_HEADER_AVATAR") === "true";
42
- let siteAvatarUrl;
43
- if (avatarKey) {
44
- const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
45
- siteAvatarUrl = getMediaUrl(avatarKey, publicUrl);
46
- }
47
- // Render footer markdown
48
- const siteFooterHtml = siteFooter ? renderMarkdown(siteFooter) : undefined;
49
- const links = toNavItemViews(items, currentPath);
50
- // Check auth status for compose button
51
- let isAuthenticated = false;
52
- let collections = [];
53
- if (c.var.auth) {
54
- try {
55
- const session = await c.var.auth.api.getSession({
56
- headers: c.req.raw.headers
57
- });
58
- isAuthenticated = !!session?.user;
59
- } catch {
60
- // Not authenticated
61
- }
62
- }
63
- // Only load collections when authenticated (for compose dialog)
64
- if (isAuthenticated) {
65
- collections = await c.var.services.collections.list();
66
- }
67
- return {
68
- links,
69
- currentPath,
70
- siteName,
71
- siteDescription,
72
- isAuthenticated,
73
- collections,
74
- homeDefaultView,
75
- siteAvatarUrl,
76
- showHeaderAvatar: showHeaderAvatar && !!siteAvatarUrl,
77
- siteFooterHtml
78
- };
79
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * Pagination Utilities
3
- *
4
- * Pure utility functions for page-based pagination.
5
- */ /**
6
- * Computes which page numbers to display in a numbered pagination control.
7
- * Always includes: first page, last page, current page, and 1 page on each side of current.
8
- * Gaps between non-consecutive pages are represented by 0 (ellipsis marker).
9
- *
10
- * @param currentPage - The current active page (1-indexed)
11
- * @param totalPages - Total number of pages
12
- * @returns Array of page numbers, with 0 representing ellipsis gaps
13
- *
14
- * @example
15
- * ```ts
16
- * getPageNumbers(1, 5) // [1, 2, 3, 4, 5]
17
- * getPageNumbers(1, 20) // [1, 2, 0, 20]
18
- * getPageNumbers(10, 20) // [1, 0, 9, 10, 11, 0, 20]
19
- * ```
20
- */ export function getPageNumbers(currentPage, totalPages) {
21
- if (totalPages <= 7) {
22
- return Array.from({
23
- length: totalPages
24
- }, (_, i)=>i + 1);
25
- }
26
- const pages = new Set();
27
- pages.add(1);
28
- pages.add(totalPages);
29
- pages.add(currentPage);
30
- if (currentPage > 1) pages.add(currentPage - 1);
31
- if (currentPage < totalPages) pages.add(currentPage + 1);
32
- const sorted = [
33
- ...pages
34
- ].sort((a, b)=>a - b);
35
- // Insert 0 for gaps
36
- const result = [];
37
- for(let i = 0; i < sorted.length; i++){
38
- if (i > 0 && sorted[i] - sorted[i - 1] > 1) {
39
- result.push(0); // ellipsis marker
40
- }
41
- result.push(sorted[i]);
42
- }
43
- return result;
44
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * Public Page Rendering Helper
3
- *
4
- * Provides a single entry point for rendering public pages with the
5
- * correct layout stack: BaseLayout > SiteLayout > content.
6
- */ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
7
- import { BaseLayout } from "../ui/layouts/BaseLayout.js";
8
- import { SiteLayout } from "../ui/layouts/SiteLayout.js";
9
- /**
10
- * Render a public page with the standard layout stack.
11
- *
12
- * @param c - Hono context
13
- * @param options - Page rendering options
14
- * @returns Hono HTML response
15
- *
16
- * @example
17
- * ```typescript
18
- * const navData = await getNavigationData(c);
19
- * return renderPublicPage(c, {
20
- * title: "My Page",
21
- * navData,
22
- * content: <MyPageComponent />,
23
- * });
24
- * ```
25
- */ export function renderPublicPage(c, options) {
26
- const { title, description, navData, content } = options;
27
- const layoutProps = {
28
- siteName: navData.siteName,
29
- siteDescription: navData.siteDescription,
30
- links: navData.links,
31
- currentPath: navData.currentPath,
32
- isAuthenticated: navData.isAuthenticated,
33
- collections: navData.collections,
34
- homeDefaultView: navData.homeDefaultView,
35
- siteAvatarUrl: navData.siteAvatarUrl,
36
- showHeaderAvatar: navData.showHeaderAvatar,
37
- siteFooterHtml: navData.siteFooterHtml
38
- };
39
- // Read favicon and noindex from context (set by theme middleware)
40
- const faviconUrl = c.get("faviconUrl");
41
- const noindex = c.get("noindex");
42
- return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
43
- title: title,
44
- description: description,
45
- c: c,
46
- faviconUrl: faviconUrl,
47
- noindex: noindex,
48
- children: /*#__PURE__*/ _jsx(SiteLayout, {
49
- ...layoutProps,
50
- children: content
51
- })
52
- }));
53
- }
@@ -1,174 +0,0 @@
1
- /**
2
- * Shared Zod schemas for validation (v2)
3
- *
4
- * These schemas ensure type-safe validation of user input
5
- * from forms, API requests, and other external sources.
6
- *
7
- * IMPORTANT: Types are defined in types.ts as the single source of truth.
8
- * This file only defines Zod validation schemas based on those types.
9
- */ import { z } from "zod";
10
- import { FORMATS, STATUSES, SORT_ORDERS, NAV_ITEM_TYPES, MAX_MEDIA_ATTACHMENTS } from "../types.js";
11
- /**
12
- * Post format enum schema
13
- * Based on FORMATS from types.ts
14
- */ export const FormatSchema = z.enum(FORMATS);
15
- /**
16
- * Post status enum schema
17
- * Based on STATUSES from types.ts
18
- */ export const StatusSchema = z.enum(STATUSES);
19
- /**
20
- * Collection sort order enum schema
21
- */ export const SortOrderSchema = z.enum(SORT_ORDERS);
22
- /**
23
- * Navigation item type enum schema
24
- */ export const NavItemTypeSchema = z.enum(NAV_ITEM_TYPES);
25
- /**
26
- * Redirect type enum schema
27
- * Form input validation for redirect type (stored as number in DB)
28
- */ export const RedirectTypeSchema = z.enum([
29
- "301",
30
- "302"
31
- ]);
32
- /**
33
- * Rating schema (1-5 integer)
34
- */ export const RatingSchema = z.coerce.number().int().min(0).max(5).optional().or(z.literal("").transform(()=>undefined)).transform((v)=>v === 0 ? undefined : v);
35
- /**
36
- * API request body schema for creating a post
37
- */ export const CreatePostSchema = z.object({
38
- format: FormatSchema,
39
- path: z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/).optional().or(z.literal("").transform(()=>undefined)),
40
- title: z.string().optional(),
41
- body: z.string().optional(),
42
- status: StatusSchema.optional(),
43
- featured: z.union([
44
- z.boolean(),
45
- z.literal("on").transform(()=>true)
46
- ]).optional(),
47
- pinned: z.union([
48
- z.boolean(),
49
- z.literal("on").transform(()=>true)
50
- ]).optional(),
51
- url: z.url().optional().or(z.literal("")),
52
- quoteText: z.string().optional(),
53
- rating: RatingSchema,
54
- collectionId: z.coerce.number().int().min(0).optional().or(z.literal("").transform(()=>undefined)).transform((v)=>v === 0 ? undefined : v),
55
- replyToId: z.string().optional(),
56
- publishedAt: z.number().int().positive().optional(),
57
- mediaIds: z.array(z.string()).max(MAX_MEDIA_ATTACHMENTS).optional()
58
- });
59
- /**
60
- * API request body schema for updating a post
61
- */ export const UpdatePostSchema = CreatePostSchema.partial();
62
- /**
63
- * API request body schema for creating a page
64
- */ export const CreatePageSchema = z.object({
65
- slug: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/),
66
- title: z.string().optional(),
67
- body: z.string().optional(),
68
- status: StatusSchema.optional()
69
- });
70
- /**
71
- * API request body schema for updating a page
72
- */ export const UpdatePageSchema = CreatePageSchema.partial();
73
- /**
74
- * API request body schema for creating a navigation item
75
- */ export const CreateNavItemSchema = z.object({
76
- type: NavItemTypeSchema,
77
- label: z.string().min(1),
78
- url: z.string().min(1),
79
- pageId: z.coerce.number().int().positive().optional(),
80
- position: z.coerce.number().int().min(0).optional()
81
- });
82
- /**
83
- * API request body schema for updating a navigation item
84
- */ export const UpdateNavItemSchema = CreateNavItemSchema.partial();
85
- /**
86
- * API request body schema for creating a collection
87
- */ export const CreateCollectionSchema = z.object({
88
- slug: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/),
89
- title: z.string().min(1),
90
- description: z.string().optional(),
91
- icon: z.string().optional(),
92
- sortOrder: SortOrderSchema.optional(),
93
- position: z.coerce.number().int().min(0).optional(),
94
- showDivider: z.union([
95
- z.boolean(),
96
- z.literal("on").transform(()=>true)
97
- ]).optional()
98
- });
99
- /**
100
- * API request body schema for updating a collection
101
- */ export const UpdateCollectionSchema = CreateCollectionSchema.partial();
102
- // =============================================================================
103
- // Auth Schemas
104
- // =============================================================================
105
- /**
106
- * Setup form validation schema
107
- */ export const SetupSchema = z.object({
108
- name: z.string().min(1, "Name is required"),
109
- email: z.string().email("Invalid email address"),
110
- password: z.string().min(8, "Password must be at least 8 characters")
111
- });
112
- /**
113
- * Sign-in form validation schema
114
- */ export const SigninSchema = z.object({
115
- email: z.string().email("Invalid email address"),
116
- password: z.string().min(1, "Password is required")
117
- });
118
- /**
119
- * Password reset form validation schema
120
- */ export const ResetPasswordSchema = z.object({
121
- password: z.string().min(8, "Password must be at least 8 characters"),
122
- confirmPassword: z.string().min(1),
123
- token: z.string().min(1)
124
- }).refine((d)=>d.password === d.confirmPassword, {
125
- message: "Passwords do not match",
126
- path: [
127
- "confirmPassword"
128
- ]
129
- });
130
- // =============================================================================
131
- // Form Data Helpers
132
- // =============================================================================
133
- /**
134
- * Form data helper: safely parse a FormData value with a schema
135
- *
136
- * @example
137
- * ```ts
138
- * const format = parseFormData(formData, "format", FormatSchema);
139
- * // format is Format, throws if invalid
140
- * ```
141
- */ export function parseFormData(formData, key, schema) {
142
- const value = formData.get(key);
143
- if (value === null) {
144
- throw new Error(`Missing required field: ${key}`);
145
- }
146
- return schema.parse(value);
147
- }
148
- /**
149
- * Form data helper: safely parse optional FormData value with a schema
150
- *
151
- * @example
152
- * ```ts
153
- * const slug = parseFormDataOptional(formData, "slug", z.string());
154
- * // slug is string | undefined
155
- * ```
156
- */ export function parseFormDataOptional(formData, key, schema) {
157
- const value = formData.get(key);
158
- if (value === null || value === "") {
159
- return undefined;
160
- }
161
- return schema.parse(value);
162
- }
163
- /**
164
- * Validates media attachment count for a post.
165
- * All formats allow 0-20 media attachments.
166
- *
167
- * @param mediaIds - Array of media IDs to attach
168
- * @returns null if valid, error string if invalid
169
- */ export function validateMediaCount(mediaIds) {
170
- if (mediaIds.length > MAX_MEDIA_ATTACHMENTS) {
171
- return `Posts allow at most ${MAX_MEDIA_ATTACHMENTS} media attachments`;
172
- }
173
- return null;
174
- }
package/dist/lib/sqid.js DELETED
@@ -1,72 +0,0 @@
1
- /**
2
- * Sqids - Short unique IDs for URLs
3
- *
4
- * Encodes numeric IDs to short strings like "jR3k"
5
- */ import Sqids from "sqids";
6
- const sqids = new Sqids({
7
- minLength: 4
8
- });
9
- /**
10
- * Encodes a numeric database ID to a short, URL-friendly string.
11
- *
12
- * Uses the Sqids library to generate short unique identifiers with a minimum length of 4 characters.
13
- * These are used in URLs (e.g., `/p/jR3k`) to obscure sequential integer IDs while maintaining
14
- * uniqueness and reversibility.
15
- *
16
- * @param id - The numeric database ID to encode
17
- * @returns A short string representation of the ID (minimum 4 characters)
18
- *
19
- * @example
20
- * ```ts
21
- * const sqid = encode(123);
22
- * // Returns: "jR3k" (or similar short string)
23
- * ```
24
- */ export function encode(id) {
25
- return sqids.encode([
26
- id
27
- ]);
28
- }
29
- /**
30
- * Decodes a sqid string back to the original numeric database ID.
31
- *
32
- * Attempts to decode a sqid string generated by the `encode()` function. Returns the original
33
- * numeric ID if valid, or `null` if the string is not a valid sqid. This is used to extract
34
- * database IDs from URL parameters.
35
- *
36
- * @param str - The sqid string to decode
37
- * @returns The original numeric ID if valid, or `null` if decoding fails
38
- *
39
- * @example
40
- * ```ts
41
- * const id = decode("jR3k");
42
- * // Returns: 123
43
- *
44
- * const invalid = decode("invalid");
45
- * // Returns: null
46
- * ```
47
- */ export function decode(str) {
48
- try {
49
- const ids = sqids.decode(str);
50
- return ids[0] ?? null;
51
- } catch {
52
- return null;
53
- }
54
- }
55
- /**
56
- * Checks if a string is a valid sqid that can be decoded.
57
- *
58
- * Validates whether a string can be successfully decoded to a numeric ID.
59
- * Useful for route validation and input sanitization.
60
- *
61
- * @param str - The string to validate
62
- * @returns `true` if the string is a valid sqid, `false` otherwise
63
- *
64
- * @example
65
- * ```ts
66
- * if (isValidSqid("jR3k")) {
67
- * // Process the valid sqid
68
- * }
69
- * ```
70
- */ export function isValidSqid(str) {
71
- return decode(str) !== null;
72
- }