@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
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
- }