@jant/core 0.3.26 → 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 (314) 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 +112 -173
  9. package/src/auth.ts +4 -1
  10. package/src/client.ts +13 -0
  11. package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
  12. package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
  13. package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
  14. package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
  15. package/src/db/schema.ts +24 -4
  16. package/src/i18n/locales/en.po +810 -385
  17. package/src/i18n/locales/en.ts +1 -1
  18. package/src/i18n/locales/zh-Hans.po +733 -522
  19. package/src/i18n/locales/zh-Hans.ts +1 -1
  20. package/src/i18n/locales/zh-Hant.po +733 -522
  21. package/src/i18n/locales/zh-Hant.ts +1 -1
  22. package/src/i18n/middleware.ts +7 -11
  23. package/src/index.ts +1 -1
  24. package/src/lib/__tests__/icons.test.ts +178 -0
  25. package/src/lib/__tests__/resolve-config.test.ts +184 -0
  26. package/src/lib/__tests__/schemas.test.ts +12 -6
  27. package/src/lib/__tests__/theme.test.ts +62 -0
  28. package/src/lib/__tests__/timezones.test.ts +1 -1
  29. package/src/lib/__tests__/url.test.ts +12 -0
  30. package/src/lib/__tests__/view.test.ts +1 -5
  31. package/src/lib/avatar-upload.ts +18 -10
  32. package/src/lib/collection-form-bridge.ts +52 -0
  33. package/src/lib/collections-reorder.ts +28 -0
  34. package/src/lib/compose-bridge.ts +251 -0
  35. package/src/lib/errors.ts +116 -0
  36. package/src/lib/excerpt.ts +1 -1
  37. package/src/lib/favicon.ts +3 -5
  38. package/src/lib/html.ts +22 -0
  39. package/src/lib/icon-catalog.ts +181 -0
  40. package/src/lib/icons.ts +202 -0
  41. package/src/lib/navigation.ts +18 -33
  42. package/src/lib/pagination.ts +3 -2
  43. package/src/lib/post-form-bridge.ts +136 -0
  44. package/src/lib/render.tsx +11 -4
  45. package/src/lib/resolve-config.ts +157 -0
  46. package/src/lib/schemas.ts +76 -12
  47. package/src/lib/settings-bridge.ts +139 -0
  48. package/src/lib/storage.ts +37 -16
  49. package/src/lib/theme.ts +5 -7
  50. package/src/lib/timeline.ts +4 -8
  51. package/src/lib/toast.ts +134 -0
  52. package/src/lib/upload.ts +71 -0
  53. package/src/lib/url.ts +9 -1
  54. package/src/lib/version.ts +16 -0
  55. package/src/lib/view.ts +9 -10
  56. package/src/middleware/__tests__/auth.test.ts +6 -28
  57. package/src/middleware/__tests__/onboarding.test.ts +1 -1
  58. package/src/middleware/auth.ts +6 -12
  59. package/src/middleware/config.ts +51 -0
  60. package/src/middleware/error-handler.ts +56 -0
  61. package/src/middleware/onboarding.ts +1 -1
  62. package/src/preset.css +6 -0
  63. package/src/routes/__tests__/compose.test.ts +104 -17
  64. package/src/routes/api/__tests__/collections.test.ts +93 -2
  65. package/src/routes/api/__tests__/posts.test.ts +2 -1
  66. package/src/routes/api/__tests__/settings.test.ts +1 -1
  67. package/src/routes/api/collections.ts +64 -68
  68. package/src/routes/api/nav-items.ts +21 -59
  69. package/src/routes/api/pages.ts +18 -46
  70. package/src/routes/api/posts.ts +64 -86
  71. package/src/routes/api/search.ts +6 -4
  72. package/src/routes/api/settings.ts +8 -24
  73. package/src/routes/api/upload.ts +55 -53
  74. package/src/routes/auth/__tests__/setup.test.ts +118 -0
  75. package/src/routes/auth/reset.tsx +17 -66
  76. package/src/routes/auth/setup.tsx +67 -11
  77. package/src/routes/auth/signin.tsx +44 -8
  78. package/src/routes/compose.tsx +194 -0
  79. package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
  80. package/src/routes/dash/__tests__/pages.test.ts +2 -2
  81. package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
  82. package/src/routes/dash/appearance.tsx +173 -0
  83. package/src/routes/dash/collections.tsx +80 -14
  84. package/src/routes/dash/index.tsx +12 -14
  85. package/src/routes/dash/media.tsx +46 -49
  86. package/src/routes/dash/pages.tsx +85 -37
  87. package/src/routes/dash/posts.tsx +60 -23
  88. package/src/routes/dash/redirects.tsx +43 -33
  89. package/src/routes/dash/settings.tsx +234 -214
  90. package/src/routes/feed/__tests__/rss.test.ts +7 -3
  91. package/src/routes/feed/rss.ts +11 -16
  92. package/src/routes/feed/sitemap.ts +15 -9
  93. package/src/routes/pages/__tests__/collections.test.ts +9 -8
  94. package/src/routes/pages/archive.tsx +2 -2
  95. package/src/routes/pages/collection.tsx +76 -9
  96. package/src/routes/pages/collections.tsx +3 -1
  97. package/src/routes/pages/featured.tsx +2 -2
  98. package/src/routes/pages/home.tsx +3 -3
  99. package/src/routes/pages/latest.tsx +2 -2
  100. package/src/routes/pages/page.tsx +2 -2
  101. package/src/routes/pages/post.tsx +2 -2
  102. package/src/routes/pages/search.tsx +2 -2
  103. package/src/services/__tests__/collection.test.ts +324 -34
  104. package/src/services/__tests__/media.test.ts +1 -1
  105. package/src/services/__tests__/page.test.ts +116 -1
  106. package/src/services/auth.ts +88 -0
  107. package/src/services/collection.ts +169 -30
  108. package/src/services/index.ts +8 -3
  109. package/src/services/media.ts +39 -12
  110. package/src/services/navigation.ts +17 -5
  111. package/src/services/page.ts +24 -4
  112. package/src/services/post.ts +87 -19
  113. package/src/services/search.ts +0 -1
  114. package/src/services/settings.ts +21 -13
  115. package/src/style.css +3 -0
  116. package/src/styles/components.css +42 -1
  117. package/src/styles/tokens.css +4 -0
  118. package/src/styles/ui.css +902 -73
  119. package/src/types/app-context.ts +25 -0
  120. package/src/types/bindings.ts +1 -0
  121. package/src/types/config.ts +60 -23
  122. package/src/types/entities.ts +12 -2
  123. package/src/types/lingui-react-macro.d.ts +3 -3
  124. package/src/types/operations.ts +2 -4
  125. package/src/types/views.ts +1 -3
  126. package/src/ui/__tests__/font-themes.test.ts +27 -8
  127. package/src/ui/color-themes.ts +1 -1
  128. package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
  129. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
  130. package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
  131. package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
  132. package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
  133. package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
  134. package/src/ui/components/collection-types.ts +45 -0
  135. package/src/ui/components/compose-types.ts +75 -0
  136. package/src/ui/components/jant-collection-form.ts +512 -0
  137. package/src/ui/components/jant-compose-dialog.ts +494 -0
  138. package/src/ui/components/jant-compose-editor.ts +799 -0
  139. package/src/ui/components/jant-post-form.ts +290 -0
  140. package/src/ui/components/jant-settings-avatar.ts +231 -0
  141. package/src/ui/components/jant-settings-general.ts +436 -0
  142. package/src/ui/components/post-form-template.ts +260 -0
  143. package/src/ui/components/post-form-types.ts +87 -0
  144. package/src/ui/components/settings-types.ts +62 -0
  145. package/src/ui/compose/ComposeDialog.tsx +141 -385
  146. package/src/ui/compose/ComposePrompt.tsx +3 -3
  147. package/src/ui/dash/PostList.tsx +55 -61
  148. package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
  149. package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
  150. package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
  151. package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
  152. package/src/ui/dash/collections/CollectionForm.tsx +130 -117
  153. package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
  154. package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
  155. package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
  156. package/src/ui/dash/index.ts +1 -1
  157. package/src/ui/dash/posts/PostForm.tsx +248 -0
  158. package/src/ui/dash/settings/AccountContent.tsx +69 -80
  159. package/src/ui/dash/settings/GeneralContent.tsx +159 -478
  160. package/src/ui/dash/settings/SettingsNav.tsx +4 -4
  161. package/src/ui/font-themes.ts +115 -32
  162. package/src/ui/layouts/BaseLayout.tsx +49 -19
  163. package/src/ui/layouts/DashLayout.tsx +14 -9
  164. package/src/ui/layouts/SiteLayout.tsx +38 -23
  165. package/src/ui/pages/CollectionPage.tsx +12 -2
  166. package/src/ui/pages/CollectionsPage.tsx +27 -27
  167. package/src/ui/pages/HomePage.tsx +15 -6
  168. package/src/ui/pages/SearchPage.tsx +1 -2
  169. package/src/ui/shared/CollectionsSidebar.tsx +59 -0
  170. package/src/ui/shared/Pagination.tsx +2 -2
  171. package/dist/app.js +0 -265
  172. package/dist/auth.js +0 -36
  173. package/dist/client.js +0 -13
  174. package/dist/db/index.js +0 -10
  175. package/dist/db/schema.js +0 -224
  176. package/dist/i18n/Trans.js +0 -24
  177. package/dist/i18n/context.js +0 -58
  178. package/dist/i18n/detect.js +0 -26
  179. package/dist/i18n/i18n.js +0 -49
  180. package/dist/i18n/index.js +0 -44
  181. package/dist/i18n/locales/en.js +0 -1
  182. package/dist/i18n/locales/zh-Hans.js +0 -1
  183. package/dist/i18n/locales/zh-Hant.js +0 -1
  184. package/dist/i18n/locales.js +0 -13
  185. package/dist/i18n/middleware.js +0 -30
  186. package/dist/lib/avatar-upload.js +0 -134
  187. package/dist/lib/config.js +0 -143
  188. package/dist/lib/constants.js +0 -50
  189. package/dist/lib/excerpt.js +0 -76
  190. package/dist/lib/favicon.js +0 -102
  191. package/dist/lib/feed.js +0 -123
  192. package/dist/lib/image-processor.js +0 -187
  193. package/dist/lib/image.js +0 -97
  194. package/dist/lib/index.js +0 -7
  195. package/dist/lib/markdown.js +0 -83
  196. package/dist/lib/media-helpers.js +0 -49
  197. package/dist/lib/media-upload.js +0 -104
  198. package/dist/lib/nav-reorder.js +0 -27
  199. package/dist/lib/navigation.js +0 -79
  200. package/dist/lib/pagination.js +0 -44
  201. package/dist/lib/render.js +0 -53
  202. package/dist/lib/schemas.js +0 -174
  203. package/dist/lib/sqid.js +0 -72
  204. package/dist/lib/sse.js +0 -218
  205. package/dist/lib/storage.js +0 -164
  206. package/dist/lib/theme.js +0 -65
  207. package/dist/lib/time.js +0 -159
  208. package/dist/lib/timeline.js +0 -95
  209. package/dist/lib/timezones.js +0 -388
  210. package/dist/lib/url.js +0 -89
  211. package/dist/lib/view.js +0 -217
  212. package/dist/middleware/auth.js +0 -52
  213. package/dist/middleware/onboarding.js +0 -41
  214. package/dist/routes/api/collections.js +0 -124
  215. package/dist/routes/api/nav-items.js +0 -104
  216. package/dist/routes/api/pages.js +0 -91
  217. package/dist/routes/api/posts.js +0 -218
  218. package/dist/routes/api/search.js +0 -48
  219. package/dist/routes/api/settings.js +0 -68
  220. package/dist/routes/api/upload.js +0 -246
  221. package/dist/routes/auth/reset.js +0 -221
  222. package/dist/routes/auth/setup.js +0 -194
  223. package/dist/routes/auth/signin.js +0 -176
  224. package/dist/routes/compose.js +0 -48
  225. package/dist/routes/dash/collections.js +0 -115
  226. package/dist/routes/dash/index.js +0 -118
  227. package/dist/routes/dash/media.js +0 -106
  228. package/dist/routes/dash/pages.js +0 -294
  229. package/dist/routes/dash/posts.js +0 -244
  230. package/dist/routes/dash/redirects.js +0 -257
  231. package/dist/routes/dash/settings.js +0 -379
  232. package/dist/routes/feed/rss.js +0 -62
  233. package/dist/routes/feed/sitemap.js +0 -49
  234. package/dist/routes/pages/archive.js +0 -62
  235. package/dist/routes/pages/collection.js +0 -34
  236. package/dist/routes/pages/collections.js +0 -28
  237. package/dist/routes/pages/featured.js +0 -36
  238. package/dist/routes/pages/home.js +0 -64
  239. package/dist/routes/pages/latest.js +0 -45
  240. package/dist/routes/pages/page.js +0 -68
  241. package/dist/routes/pages/post.js +0 -44
  242. package/dist/routes/pages/search.js +0 -54
  243. package/dist/services/collection.js +0 -109
  244. package/dist/services/index.js +0 -24
  245. package/dist/services/media.js +0 -117
  246. package/dist/services/navigation.js +0 -91
  247. package/dist/services/page.js +0 -84
  248. package/dist/services/post.js +0 -229
  249. package/dist/services/redirect.js +0 -48
  250. package/dist/services/search.js +0 -67
  251. package/dist/services/settings.js +0 -68
  252. package/dist/types/bindings.js +0 -3
  253. package/dist/types/config.js +0 -147
  254. package/dist/types/constants.js +0 -27
  255. package/dist/types/entities.js +0 -3
  256. package/dist/types/lingui-react-macro.d.js +0 -9
  257. package/dist/types/operations.js +0 -3
  258. package/dist/types/props.js +0 -3
  259. package/dist/types/sortablejs.d.js +0 -5
  260. package/dist/types/views.js +0 -5
  261. package/dist/types.js +0 -11
  262. package/dist/ui/color-themes.js +0 -268
  263. package/dist/ui/compose/ComposeDialog.js +0 -467
  264. package/dist/ui/compose/ComposePrompt.js +0 -55
  265. package/dist/ui/dash/ActionButtons.js +0 -46
  266. package/dist/ui/dash/CrudPageHeader.js +0 -22
  267. package/dist/ui/dash/DangerZone.js +0 -36
  268. package/dist/ui/dash/FormatBadge.js +0 -27
  269. package/dist/ui/dash/ListItemRow.js +0 -21
  270. package/dist/ui/dash/PageForm.js +0 -195
  271. package/dist/ui/dash/PostForm.js +0 -395
  272. package/dist/ui/dash/PostList.js +0 -83
  273. package/dist/ui/dash/StatusBadge.js +0 -46
  274. package/dist/ui/dash/collections/CollectionForm.js +0 -152
  275. package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
  276. package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
  277. package/dist/ui/dash/index.js +0 -10
  278. package/dist/ui/dash/media/MediaListContent.js +0 -166
  279. package/dist/ui/dash/media/ViewMediaContent.js +0 -212
  280. package/dist/ui/dash/pages/LinkFormContent.js +0 -130
  281. package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
  282. package/dist/ui/dash/settings/AccountContent.js +0 -209
  283. package/dist/ui/dash/settings/AppearanceContent.js +0 -259
  284. package/dist/ui/dash/settings/GeneralContent.js +0 -536
  285. package/dist/ui/dash/settings/SettingsNav.js +0 -41
  286. package/dist/ui/feed/LinkCard.js +0 -72
  287. package/dist/ui/feed/NoteCard.js +0 -58
  288. package/dist/ui/feed/QuoteCard.js +0 -63
  289. package/dist/ui/feed/ThreadPreview.js +0 -48
  290. package/dist/ui/feed/TimelineFeed.js +0 -41
  291. package/dist/ui/feed/TimelineItem.js +0 -27
  292. package/dist/ui/font-themes.js +0 -36
  293. package/dist/ui/layouts/BaseLayout.js +0 -153
  294. package/dist/ui/layouts/DashLayout.js +0 -141
  295. package/dist/ui/layouts/SiteLayout.js +0 -169
  296. package/dist/ui/pages/ArchivePage.js +0 -143
  297. package/dist/ui/pages/CollectionPage.js +0 -70
  298. package/dist/ui/pages/CollectionsPage.js +0 -76
  299. package/dist/ui/pages/FeaturedPage.js +0 -24
  300. package/dist/ui/pages/HomePage.js +0 -24
  301. package/dist/ui/pages/PostPage.js +0 -55
  302. package/dist/ui/pages/SearchPage.js +0 -122
  303. package/dist/ui/pages/SinglePage.js +0 -23
  304. package/dist/ui/shared/EmptyState.js +0 -27
  305. package/dist/ui/shared/MediaGallery.js +0 -35
  306. package/dist/ui/shared/Pagination.js +0 -195
  307. package/dist/ui/shared/ThreadView.js +0 -108
  308. package/dist/ui/shared/index.js +0 -5
  309. package/dist/vendor/datastar.js +0 -1606
  310. package/src/lib/__tests__/config.test.ts +0 -192
  311. package/src/lib/config.ts +0 -167
  312. package/src/routes/compose.ts +0 -63
  313. package/src/ui/dash/PostForm.tsx +0 -360
  314. 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
- }