@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
package/dist/lib/sse.js DELETED
@@ -1,218 +0,0 @@
1
- /**
2
- * Datastar response utilities for v1.0.0-RC.7
3
- *
4
- * Provides both SSE (multi-event) and plain HTTP (single-event) response helpers.
5
- *
6
- * **Non-SSE helpers** (preferred for single operations):
7
- * - `dsRedirect(url)` — redirect via text/html
8
- * - `dsToast(message, type)` — toast notification via text/html
9
- * - `dsSignals(signals)` — signal patch via application/json
10
- *
11
- * **SSE** (for multiple operations in one response):
12
- * - `sse(c, handler)` — streaming SSE with full stream API
13
- *
14
- * Datastar auto-detects response type by Content-Type:
15
- * - `text/html` → dispatches as `datastar-patch-elements`
16
- * - `application/json` → dispatches as `datastar-patch-signals`
17
- *
18
- * @see https://data-star.dev/
19
- */ // ---------------------------------------------------------------------------
20
- // Shared internal helpers (used by both SSE and non-SSE response builders)
21
- // ---------------------------------------------------------------------------
22
- /** Build the redirect script tag for Datastar patch-elements */ function buildRedirectScript(url) {
23
- const escapedUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
24
- return `<script data-effect="el.remove()">window.location.href='${escapedUrl}'</script>`;
25
- }
26
- /** Build a toast notification HTML element */ function buildToastHtml(message, type) {
27
- const cls = type === "error" ? "toast-error" : "toast-success";
28
- const icon = type === "error" ? '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>';
29
- const closeBtn = `<button class="toast-close" data-on:click="el.closest('.toast').classList.add('toast-out'); el.closest('.toast').addEventListener('animationend', () => el.closest('.toast').remove())"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>`;
30
- const escapedMessage = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31
- return `<div class="toast ${cls}" data-init="setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)">${icon}<span>${escapedMessage}</span>${closeBtn}</div>`;
32
- }
33
- // ---------------------------------------------------------------------------
34
- // SSE helpers
35
- // ---------------------------------------------------------------------------
36
- /**
37
- * Format a single SSE event string
38
- *
39
- * @param eventType - The Datastar event type (e.g. "datastar-patch-elements")
40
- * @param dataLines - Array of "key value" data lines
41
- * @returns Formatted SSE event string
42
- */ function formatEvent(eventType, dataLines) {
43
- let event = `event: ${eventType}\n`;
44
- for (const line of dataLines){
45
- event += `data: ${line}\n`;
46
- }
47
- event += "\n";
48
- return event;
49
- }
50
- /**
51
- * Create an SSE response for Datastar
52
- *
53
- * @param c - Hono context
54
- * @param handler - Async function that writes to the SSE stream
55
- * @param options - Optional response options (e.g. headers for cookie forwarding)
56
- * @returns Response with SSE content-type
57
- *
58
- * @example
59
- * ```ts
60
- * app.post("/api/upload", (c) => {
61
- * return sse(c, async (stream) => {
62
- * await stream.patchSignals({ uploading: false });
63
- * await stream.patchElements('<div id="new-item">...</div>', {
64
- * mode: 'append',
65
- * selector: '#items'
66
- * });
67
- * });
68
- * });
69
- *
70
- * // With cookie forwarding (for auth)
71
- * app.post("/signin", (c) => {
72
- * return sse(c, async (stream) => {
73
- * await stream.redirect('/dash');
74
- * }, { headers: { 'Set-Cookie': cookieValue } });
75
- * });
76
- * ```
77
- */ export function sse(c, handler, options) {
78
- const encoder = new TextEncoder();
79
- const body = new ReadableStream({
80
- async start (controller) {
81
- const stream = {
82
- patchSignals (signals, opts) {
83
- const dataLines = [
84
- `signals ${JSON.stringify(signals)}`
85
- ];
86
- if (opts?.onlyIfMissing) {
87
- dataLines.push("onlyIfMissing true");
88
- }
89
- controller.enqueue(encoder.encode(formatEvent("datastar-patch-signals", dataLines)));
90
- },
91
- patchElements (html, opts) {
92
- const dataLines = [];
93
- // Each line of HTML gets its own "elements <line>" data line
94
- for (const line of html.split("\n")){
95
- dataLines.push(`elements ${line}`);
96
- }
97
- if (opts?.mode) {
98
- dataLines.push(`mode ${opts.mode}`);
99
- }
100
- if (opts?.selector) {
101
- dataLines.push(`selector ${opts.selector}`);
102
- }
103
- if (opts?.useViewTransition) {
104
- dataLines.push("useViewTransition true");
105
- }
106
- controller.enqueue(encoder.encode(formatEvent("datastar-patch-elements", dataLines)));
107
- },
108
- redirect (url) {
109
- const dataLines = [
110
- `elements ${buildRedirectScript(url)}`,
111
- "mode append",
112
- "selector body"
113
- ];
114
- controller.enqueue(encoder.encode(formatEvent("datastar-patch-elements", dataLines)));
115
- },
116
- remove (selector) {
117
- controller.enqueue(encoder.encode(formatEvent("datastar-patch-elements", [
118
- "elements ",
119
- `mode remove`,
120
- `selector ${selector}`
121
- ])));
122
- },
123
- toast (message, type = "success") {
124
- const dataLines = [
125
- `elements ${buildToastHtml(message, type)}`,
126
- "mode append",
127
- "selector #toast-container"
128
- ];
129
- controller.enqueue(encoder.encode(formatEvent("datastar-patch-elements", dataLines)));
130
- }
131
- };
132
- await handler(stream);
133
- controller.close();
134
- }
135
- });
136
- const headers = {
137
- "Content-Type": "text/event-stream",
138
- "Cache-Control": "no-cache",
139
- Connection: "keep-alive",
140
- ...options?.headers
141
- };
142
- return new Response(body, {
143
- headers
144
- });
145
- }
146
- // ---------------------------------------------------------------------------
147
- // Non-SSE Datastar helpers (for single-operation responses)
148
- // ---------------------------------------------------------------------------
149
- /**
150
- * Datastar redirect via text/html
151
- *
152
- * Returns a plain HTML response that Datastar dispatches as `datastar-patch-elements`.
153
- * Use instead of `sse()` when the only action is a redirect.
154
- *
155
- * @param url - The URL to redirect to
156
- * @param options - Optional extra headers (accepts any `HeadersInit`)
157
- * @returns Response with text/html content-type
158
- *
159
- * @example
160
- * ```ts
161
- * return dsRedirect("/dash/posts");
162
- *
163
- * // With cookie forwarding (for auth)
164
- * return dsRedirect("/dash", { headers: authResponse.headers });
165
- * ```
166
- */ export function dsRedirect(url, options) {
167
- const headers = options?.headers ? new Headers(options.headers) : new Headers();
168
- headers.set("Content-Type", "text/html");
169
- headers.set("Datastar-Mode", "append");
170
- headers.set("Datastar-Selector", "body");
171
- return new Response(buildRedirectScript(url), {
172
- headers
173
- });
174
- }
175
- /**
176
- * Datastar toast notification via text/html
177
- *
178
- * Returns a plain HTML response that Datastar dispatches as `datastar-patch-elements`.
179
- * Use instead of `sse()` when the only action is showing a toast.
180
- *
181
- * @param message - The message to display
182
- * @param type - Toast type: "success" (default) or "error"
183
- * @returns Response with text/html content-type
184
- *
185
- * @example
186
- * ```ts
187
- * return dsToast("Settings saved successfully.");
188
- * return dsToast("Something went wrong.", "error");
189
- * ```
190
- */ export function dsToast(message, type = "success") {
191
- return new Response(buildToastHtml(message, type), {
192
- headers: {
193
- "Content-Type": "text/html",
194
- "Datastar-Mode": "append",
195
- "Datastar-Selector": "#toast-container"
196
- }
197
- });
198
- }
199
- /**
200
- * Datastar signal patch via application/json
201
- *
202
- * Returns a JSON response that Datastar dispatches as `datastar-patch-signals`.
203
- * Use instead of `sse()` when the only action is updating signals.
204
- *
205
- * @param signals - Object containing signal values to update
206
- * @returns Response with application/json content-type
207
- *
208
- * @example
209
- * ```ts
210
- * return dsSignals({ _uploadError: "File too large" });
211
- * ```
212
- */ export function dsSignals(signals) {
213
- return new Response(JSON.stringify(signals), {
214
- headers: {
215
- "Content-Type": "application/json"
216
- }
217
- });
218
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * Storage Driver Abstraction
3
- *
4
- * Provides a common interface for file storage with R2 and S3-compatible backends.
5
- */ /**
6
- * Creates an R2 storage driver that delegates to a Cloudflare R2 bucket binding.
7
- *
8
- * @param r2 - The R2 bucket binding from the Cloudflare Workers environment
9
- * @returns A StorageDriver backed by R2
10
- */ export function createR2Driver(r2) {
11
- return {
12
- async put (key, body, opts) {
13
- await r2.put(key, body, {
14
- httpMetadata: opts?.contentType ? {
15
- contentType: opts.contentType
16
- } : undefined
17
- });
18
- },
19
- async get (key) {
20
- const object = await r2.get(key);
21
- if (!object) return null;
22
- return {
23
- body: object.body,
24
- contentType: object.httpMetadata?.contentType ?? undefined
25
- };
26
- },
27
- async delete (key) {
28
- await r2.delete(key);
29
- }
30
- };
31
- }
32
- /**
33
- * Creates an S3-compatible storage driver using the AWS SDK.
34
- *
35
- * Supports any S3-compatible service: AWS S3, Backblaze B2, MinIO, etc.
36
- * Uses path-style addressing for non-AWS endpoints.
37
- *
38
- * @param config - S3 connection configuration
39
- * @returns A StorageDriver backed by S3
40
- */ export function createS3Driver(config) {
41
- // Lazy-load the AWS SDK to avoid bundling it when using R2
42
- let clientPromise = null;
43
- function getClient() {
44
- if (!clientPromise) {
45
- clientPromise = import("@aws-sdk/client-s3").then((sdk)=>{
46
- const forcePathStyle = !config.endpoint.includes("amazonaws.com");
47
- const client = new sdk.S3Client({
48
- endpoint: config.endpoint,
49
- region: config.region,
50
- credentials: {
51
- accessKeyId: config.accessKeyId,
52
- secretAccessKey: config.secretAccessKey
53
- },
54
- forcePathStyle
55
- });
56
- return {
57
- send: (cmd)=>client.send(cmd),
58
- S3Client: sdk.S3Client,
59
- PutObjectCommand: sdk.PutObjectCommand,
60
- GetObjectCommand: sdk.GetObjectCommand,
61
- DeleteObjectCommand: sdk.DeleteObjectCommand,
62
- bucket: config.bucket
63
- };
64
- });
65
- }
66
- return clientPromise;
67
- }
68
- return {
69
- async put (key, body, opts) {
70
- const s3 = await getClient();
71
- // Buffer the stream to Uint8Array for the S3 SDK
72
- let bodyBytes;
73
- if (body instanceof Uint8Array) {
74
- bodyBytes = body;
75
- } else {
76
- const reader = body.getReader();
77
- const chunks = [];
78
- for(;;){
79
- const { done, value } = await reader.read();
80
- if (done) break;
81
- chunks.push(value);
82
- }
83
- let totalLength = 0;
84
- for (const chunk of chunks)totalLength += chunk.length;
85
- bodyBytes = new Uint8Array(totalLength);
86
- let offset = 0;
87
- for (const chunk of chunks){
88
- bodyBytes.set(chunk, offset);
89
- offset += chunk.length;
90
- }
91
- }
92
- const command = new s3.PutObjectCommand({
93
- Bucket: s3.bucket,
94
- Key: key,
95
- Body: bodyBytes,
96
- ContentType: opts?.contentType
97
- });
98
- await s3.send(command);
99
- },
100
- async get (key) {
101
- const s3 = await getClient();
102
- try {
103
- const command = new s3.GetObjectCommand({
104
- Bucket: s3.bucket,
105
- Key: key
106
- });
107
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- AWS SDK response type
108
- const response = await s3.send(command);
109
- if (!response.Body) return null;
110
- return {
111
- body: response.Body.transformToWebStream(),
112
- contentType: response.ContentType ?? undefined
113
- };
114
- } catch (err) {
115
- // NoSuchKey → return null instead of throwing
116
- if (err instanceof Error && (err.name === "NoSuchKey" || err.name === "NotFound")) {
117
- return null;
118
- }
119
- throw err;
120
- }
121
- },
122
- async delete (key) {
123
- const s3 = await getClient();
124
- const command = new s3.DeleteObjectCommand({
125
- Bucket: s3.bucket,
126
- Key: key
127
- });
128
- await s3.send(command);
129
- }
130
- };
131
- }
132
- /**
133
- * Creates the appropriate storage driver based on environment configuration.
134
- *
135
- * Returns `null` if no storage is configured (no R2 binding and no S3 config).
136
- *
137
- * @param env - The Cloudflare Workers environment bindings
138
- * @returns A StorageDriver instance or null
139
- *
140
- * @example
141
- * ```ts
142
- * const storage = createStorageDriver(c.env);
143
- * if (storage) {
144
- * await storage.put("media/file.jpg", stream, { contentType: "image/jpeg" });
145
- * }
146
- * ```
147
- */ export function createStorageDriver(env) {
148
- const driver = env.STORAGE_DRIVER || "r2";
149
- if (driver === "s3") {
150
- if (!env.S3_ENDPOINT || !env.S3_BUCKET || !env.S3_ACCESS_KEY_ID || !env.S3_SECRET_ACCESS_KEY) {
151
- return null;
152
- }
153
- return createS3Driver({
154
- endpoint: env.S3_ENDPOINT,
155
- bucket: env.S3_BUCKET,
156
- accessKeyId: env.S3_ACCESS_KEY_ID,
157
- secretAccessKey: env.S3_SECRET_ACCESS_KEY,
158
- region: env.S3_REGION || "auto"
159
- });
160
- }
161
- // Default: R2
162
- if (!env.R2) return null;
163
- return createR2Driver(env.R2);
164
- }
package/dist/lib/theme.js DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Theme Resolution Helpers
3
- *
4
- * Resolves the active color theme and builds CSS for injection into `<head>`.
5
- */ import { BUILTIN_COLOR_THEMES } from "../ui/color-themes.js";
6
- /**
7
- * Get the list of available color themes.
8
- *
9
- * Returns `config.colorThemes` if provided, otherwise the built-in list.
10
- *
11
- * @param config - The Jant configuration
12
- * @returns Array of available color themes
13
- *
14
- * @example
15
- * ```typescript
16
- * const themes = getAvailableThemes(c.var.config);
17
- * ```
18
- */ export function getAvailableThemes(config) {
19
- return config.colorThemes ?? BUILTIN_COLOR_THEMES;
20
- }
21
- /**
22
- * Build a `<style>` CSS string from a color theme and optional cssVariables overlay.
23
- *
24
- * Priority (lowest → highest):
25
- * BaseCoat defaults → selected theme → cssVariables
26
- *
27
- * @param theme - The active color theme (undefined = no theme overrides)
28
- * @param cssVariables - Extra CSS variable overrides from `createApp({ cssVariables })`
29
- * @returns CSS string to inject in `<head>`, or empty string if nothing to inject
30
- *
31
- * Uses `:root:root` and `:root.dark` selectors for higher specificity than
32
- * BaseCoat defaults (`:root` and `.dark`). This ensures theme overrides win
33
- * regardless of source order — important because Vite dev mode injects CSS
34
- * as `<style>` tags after the theme `<style>`.
35
- *
36
- * @example
37
- * ```typescript
38
- * const css = buildThemeStyle(blueTheme, { "--radius": "0.5rem" });
39
- * // => ":root:root { --primary: oklch(...); ... }\n:root.dark { ... }"
40
- * ```
41
- */ export function buildThemeStyle(theme, cssVariables) {
42
- const lightVars = {
43
- ...theme?.light ?? {},
44
- ...cssVariables ?? {}
45
- };
46
- const darkVars = {
47
- ...theme?.dark ?? {},
48
- ...cssVariables ?? {}
49
- };
50
- const hasLight = Object.keys(lightVars).length > 0;
51
- const hasDark = Object.keys(darkVars).length > 0;
52
- if (!hasLight && !hasDark) return "";
53
- const parts = [];
54
- if (hasLight) {
55
- const declarations = Object.entries(lightVars).map(([k, v])=>` ${k}: ${v};`).join("\n");
56
- // :root:root has specificity (0,0,2) > BaseCoat's :root (0,0,1)
57
- parts.push(`:root:root {\n${declarations}\n}`);
58
- }
59
- if (hasDark) {
60
- const declarations = Object.entries(darkVars).map(([k, v])=>` ${k}: ${v};`).join("\n");
61
- // :root.dark has specificity (0,1,1) > BaseCoat's .dark (0,1,0)
62
- parts.push(`:root.dark {\n${declarations}\n}`);
63
- }
64
- return parts.join("\n");
65
- }
package/dist/lib/time.js DELETED
@@ -1,159 +0,0 @@
1
- /**
2
- * Time Utilities
3
- */ /**
4
- * Gets the current Unix timestamp in seconds.
5
- *
6
- * Returns the number of seconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
7
- * This is the standard time format used throughout the application for consistency
8
- * and database storage.
9
- *
10
- * @returns Current Unix timestamp in seconds (not milliseconds)
11
- *
12
- * @example
13
- * ```ts
14
- * const timestamp = now();
15
- * // Returns: 1706745600 (example value for Feb 1, 2024)
16
- * ```
17
- */ export function now() {
18
- return Math.floor(Date.now() / 1000);
19
- }
20
- /**
21
- * One month in seconds
22
- */ const ONE_MONTH = 30 * 24 * 60 * 60;
23
- /**
24
- * Checks if a Unix timestamp is within the last 30 days.
25
- *
26
- * Compares the given timestamp to the current time to determine if it falls within
27
- * the last month (defined as 30 days). Useful for highlighting recent posts or
28
- * filtering time-sensitive content.
29
- *
30
- * @param timestamp - Unix timestamp in seconds to check
31
- * @returns `true` if the timestamp is within the last 30 days, `false` otherwise
32
- *
33
- * @example
34
- * ```ts
35
- * const recentPost = 1706745600; // Recent timestamp
36
- * if (isWithinMonth(recentPost)) {
37
- * // Show "new" badge
38
- * }
39
- * ```
40
- */ export function isWithinMonth(timestamp) {
41
- return now() - timestamp < ONE_MONTH;
42
- }
43
- /**
44
- * Converts a Unix timestamp to an ISO 8601 date-time string.
45
- *
46
- * Formats a Unix timestamp (in seconds) as an ISO 8601 string suitable for HTML
47
- * `datetime` attributes and API responses. The output includes full date, time,
48
- * and timezone information in UTC.
49
- *
50
- * @param timestamp - Unix timestamp in seconds to convert
51
- * @returns ISO 8601 formatted string (e.g., "2024-02-01T12:00:00.000Z")
52
- *
53
- * @example
54
- * ```ts
55
- * const isoDate = toISOString(1706745600);
56
- * // Returns: "2024-02-01T00:00:00.000Z"
57
- * ```
58
- */ export function toISOString(timestamp) {
59
- return new Date(timestamp * 1000).toISOString();
60
- }
61
- /**
62
- * Formats a Unix timestamp as a human-readable date string.
63
- *
64
- * Converts a Unix timestamp (in seconds) to a localized date string in the format
65
- * "MMM DD, YYYY" (e.g., "Jan 15, 2024"). Always uses UTC timezone to ensure
66
- * consistent display regardless of server or client location.
67
- *
68
- * @param timestamp - Unix timestamp in seconds to format
69
- * @returns Formatted date string in "MMM DD, YYYY" format
70
- *
71
- * @example
72
- * ```ts
73
- * const readable = formatDate(1706745600);
74
- * // Returns: "Feb 1, 2024"
75
- * ```
76
- */ export function formatDate(timestamp) {
77
- return new Date(timestamp * 1000).toLocaleDateString("en-US", {
78
- year: "numeric",
79
- month: "short",
80
- day: "numeric",
81
- timeZone: "UTC"
82
- });
83
- }
84
- /**
85
- * Formats a Unix timestamp as a year-month string for grouping.
86
- *
87
- * Converts a Unix timestamp (in seconds) to a "YYYY-MM" format string, useful for
88
- * grouping posts by month in archives or creating month-based URLs. Always uses
89
- * UTC timezone for consistency.
90
- *
91
- * @param timestamp - Unix timestamp in seconds to format
92
- * @returns Year-month string in "YYYY-MM" format
93
- *
94
- * @example
95
- * ```ts
96
- * const yearMonth = formatYearMonth(1706745600);
97
- * // Returns: "2024-02"
98
- * ```
99
- */ /**
100
- * Formats a Unix timestamp as a 24-hour time string (HH:MM).
101
- *
102
- * Converts a Unix timestamp (in seconds) to a zero-padded time string in
103
- * 24-hour format. Always uses UTC timezone for consistency.
104
- *
105
- * @param timestamp - Unix timestamp in seconds to format
106
- * @returns Formatted time string in "HH:MM" format
107
- *
108
- * @example
109
- * ```ts
110
- * const time = formatTime(1706745600);
111
- * // Returns: "00:00"
112
- * ```
113
- */ export function formatTime(timestamp) {
114
- const date = new Date(timestamp * 1000);
115
- const hours = String(date.getUTCHours()).padStart(2, "0");
116
- const minutes = String(date.getUTCMinutes()).padStart(2, "0");
117
- return `${hours}:${minutes}`;
118
- }
119
- /**
120
- * Formats a Unix timestamp as a short relative time string.
121
- *
122
- * Returns compact labels like "1m", "5h", "3d" for recent timestamps,
123
- * and falls back to "MMM D" (e.g. "Feb 1") for anything older than 7 days.
124
- *
125
- * @param timestamp - Unix timestamp in seconds
126
- * @returns Short relative time string
127
- *
128
- * @example
129
- * ```ts
130
- * // Assuming current time is Feb 16, 2026
131
- * formatRelativeTime(now() - 30); // "1m" (30 seconds → rounds up)
132
- * formatRelativeTime(now() - 3600); // "1h"
133
- * formatRelativeTime(now() - 86400); // "1d"
134
- * formatRelativeTime(now() - 604800); // "7d"
135
- * formatRelativeTime(now() - 864000); // "Feb 6"
136
- * ```
137
- */ export function formatRelativeTime(timestamp) {
138
- const seconds = now() - timestamp;
139
- if (seconds < 60) return "1m";
140
- const minutes = Math.floor(seconds / 60);
141
- if (minutes < 60) return `${minutes}m`;
142
- const hours = Math.floor(seconds / 3600);
143
- if (hours < 24) return `${hours}h`;
144
- const days = Math.floor(seconds / 86400);
145
- if (days <= 7) return `${days}d`;
146
- // Older than 7 days: show "MMM D" (e.g. "Feb 1")
147
- const date = new Date(timestamp * 1000);
148
- return date.toLocaleDateString("en-US", {
149
- month: "short",
150
- day: "numeric",
151
- timeZone: "UTC"
152
- });
153
- }
154
- export function formatYearMonth(timestamp) {
155
- const date = new Date(timestamp * 1000);
156
- const year = date.getUTCFullYear();
157
- const month = String(date.getUTCMonth() + 1).padStart(2, "0");
158
- return `${year}-${month}`;
159
- }