@jant/core 0.3.27 → 0.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/dist/client/client.css +1 -0
  2. package/dist/client/client.js +31561 -0
  3. package/dist/index.js +15209 -15
  4. package/package.json +21 -15
  5. package/src/__tests__/helpers/app.ts +19 -3
  6. package/src/__tests__/helpers/db.ts +44 -0
  7. package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
  8. package/src/app.tsx +111 -174
  9. package/src/client.ts +13 -0
  10. package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
  11. package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
  12. package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
  13. package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
  14. package/src/db/schema.ts +24 -4
  15. package/src/i18n/locales/en.po +810 -385
  16. package/src/i18n/locales/en.ts +1 -1
  17. package/src/i18n/locales/zh-Hans.po +733 -522
  18. package/src/i18n/locales/zh-Hans.ts +1 -1
  19. package/src/i18n/locales/zh-Hant.po +733 -522
  20. package/src/i18n/locales/zh-Hant.ts +1 -1
  21. package/src/i18n/middleware.ts +7 -11
  22. package/src/index.ts +1 -1
  23. package/src/lib/__tests__/icons.test.ts +178 -0
  24. package/src/lib/__tests__/resolve-config.test.ts +184 -0
  25. package/src/lib/__tests__/schemas.test.ts +12 -6
  26. package/src/lib/__tests__/theme.test.ts +62 -0
  27. package/src/lib/__tests__/timezones.test.ts +1 -1
  28. package/src/lib/__tests__/url.test.ts +12 -0
  29. package/src/lib/__tests__/view.test.ts +1 -5
  30. package/src/lib/avatar-upload.ts +18 -10
  31. package/src/lib/collection-form-bridge.ts +52 -0
  32. package/src/lib/collections-reorder.ts +28 -0
  33. package/src/lib/compose-bridge.ts +251 -0
  34. package/src/lib/errors.ts +116 -0
  35. package/src/lib/excerpt.ts +1 -1
  36. package/src/lib/favicon.ts +3 -5
  37. package/src/lib/html.ts +22 -0
  38. package/src/lib/icon-catalog.ts +181 -0
  39. package/src/lib/icons.ts +202 -0
  40. package/src/lib/navigation.ts +18 -33
  41. package/src/lib/pagination.ts +3 -2
  42. package/src/lib/post-form-bridge.ts +136 -0
  43. package/src/lib/render.tsx +11 -4
  44. package/src/lib/resolve-config.ts +157 -0
  45. package/src/lib/schemas.ts +76 -12
  46. package/src/lib/settings-bridge.ts +139 -0
  47. package/src/lib/storage.ts +37 -16
  48. package/src/lib/theme.ts +5 -7
  49. package/src/lib/timeline.ts +4 -8
  50. package/src/lib/toast.ts +134 -0
  51. package/src/lib/upload.ts +71 -0
  52. package/src/lib/url.ts +9 -1
  53. package/src/lib/version.ts +16 -0
  54. package/src/lib/view.ts +9 -10
  55. package/src/middleware/__tests__/auth.test.ts +6 -28
  56. package/src/middleware/__tests__/onboarding.test.ts +1 -1
  57. package/src/middleware/auth.ts +6 -12
  58. package/src/middleware/config.ts +51 -0
  59. package/src/middleware/error-handler.ts +56 -0
  60. package/src/middleware/onboarding.ts +1 -1
  61. package/src/preset.css +6 -0
  62. package/src/routes/__tests__/compose.test.ts +104 -17
  63. package/src/routes/api/__tests__/collections.test.ts +93 -2
  64. package/src/routes/api/__tests__/posts.test.ts +2 -1
  65. package/src/routes/api/__tests__/settings.test.ts +1 -1
  66. package/src/routes/api/collections.ts +64 -68
  67. package/src/routes/api/nav-items.ts +21 -59
  68. package/src/routes/api/pages.ts +18 -46
  69. package/src/routes/api/posts.ts +64 -86
  70. package/src/routes/api/search.ts +6 -4
  71. package/src/routes/api/settings.ts +8 -24
  72. package/src/routes/api/upload.ts +55 -53
  73. package/src/routes/auth/__tests__/setup.test.ts +118 -0
  74. package/src/routes/auth/reset.tsx +17 -66
  75. package/src/routes/auth/setup.tsx +67 -11
  76. package/src/routes/auth/signin.tsx +44 -8
  77. package/src/routes/compose.tsx +194 -0
  78. package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
  79. package/src/routes/dash/__tests__/pages.test.ts +2 -2
  80. package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
  81. package/src/routes/dash/appearance.tsx +173 -0
  82. package/src/routes/dash/collections.tsx +80 -14
  83. package/src/routes/dash/index.tsx +12 -14
  84. package/src/routes/dash/media.tsx +46 -49
  85. package/src/routes/dash/pages.tsx +85 -37
  86. package/src/routes/dash/posts.tsx +60 -23
  87. package/src/routes/dash/redirects.tsx +43 -33
  88. package/src/routes/dash/settings.tsx +234 -214
  89. package/src/routes/feed/__tests__/rss.test.ts +7 -3
  90. package/src/routes/feed/rss.ts +11 -16
  91. package/src/routes/feed/sitemap.ts +15 -9
  92. package/src/routes/pages/__tests__/collections.test.ts +9 -8
  93. package/src/routes/pages/archive.tsx +2 -2
  94. package/src/routes/pages/collection.tsx +76 -9
  95. package/src/routes/pages/collections.tsx +3 -1
  96. package/src/routes/pages/featured.tsx +2 -2
  97. package/src/routes/pages/home.tsx +3 -3
  98. package/src/routes/pages/latest.tsx +2 -2
  99. package/src/routes/pages/page.tsx +2 -2
  100. package/src/routes/pages/post.tsx +2 -2
  101. package/src/routes/pages/search.tsx +2 -2
  102. package/src/services/__tests__/collection.test.ts +324 -34
  103. package/src/services/__tests__/media.test.ts +1 -1
  104. package/src/services/__tests__/page.test.ts +116 -1
  105. package/src/services/auth.ts +88 -0
  106. package/src/services/collection.ts +169 -30
  107. package/src/services/index.ts +8 -3
  108. package/src/services/media.ts +39 -12
  109. package/src/services/navigation.ts +17 -5
  110. package/src/services/page.ts +24 -4
  111. package/src/services/post.ts +87 -19
  112. package/src/services/search.ts +0 -1
  113. package/src/services/settings.ts +21 -13
  114. package/src/style.css +3 -0
  115. package/src/styles/components.css +42 -1
  116. package/src/styles/tokens.css +4 -0
  117. package/src/styles/ui.css +902 -73
  118. package/src/types/app-context.ts +25 -0
  119. package/src/types/bindings.ts +1 -0
  120. package/src/types/config.ts +60 -23
  121. package/src/types/entities.ts +12 -2
  122. package/src/types/lingui-react-macro.d.ts +3 -3
  123. package/src/types/operations.ts +2 -4
  124. package/src/types/views.ts +1 -3
  125. package/src/ui/__tests__/font-themes.test.ts +27 -8
  126. package/src/ui/color-themes.ts +1 -1
  127. package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
  128. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
  129. package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
  130. package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
  131. package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
  132. package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
  133. package/src/ui/components/collection-types.ts +45 -0
  134. package/src/ui/components/compose-types.ts +75 -0
  135. package/src/ui/components/jant-collection-form.ts +512 -0
  136. package/src/ui/components/jant-compose-dialog.ts +494 -0
  137. package/src/ui/components/jant-compose-editor.ts +799 -0
  138. package/src/ui/components/jant-post-form.ts +290 -0
  139. package/src/ui/components/jant-settings-avatar.ts +231 -0
  140. package/src/ui/components/jant-settings-general.ts +436 -0
  141. package/src/ui/components/post-form-template.ts +260 -0
  142. package/src/ui/components/post-form-types.ts +87 -0
  143. package/src/ui/components/settings-types.ts +62 -0
  144. package/src/ui/compose/ComposeDialog.tsx +141 -385
  145. package/src/ui/compose/ComposePrompt.tsx +3 -3
  146. package/src/ui/dash/PostList.tsx +55 -61
  147. package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
  148. package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
  149. package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
  150. package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
  151. package/src/ui/dash/collections/CollectionForm.tsx +130 -117
  152. package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
  153. package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
  154. package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
  155. package/src/ui/dash/index.ts +1 -1
  156. package/src/ui/dash/posts/PostForm.tsx +248 -0
  157. package/src/ui/dash/settings/AccountContent.tsx +69 -80
  158. package/src/ui/dash/settings/GeneralContent.tsx +159 -478
  159. package/src/ui/dash/settings/SettingsNav.tsx +4 -4
  160. package/src/ui/font-themes.ts +115 -32
  161. package/src/ui/layouts/BaseLayout.tsx +49 -19
  162. package/src/ui/layouts/DashLayout.tsx +14 -9
  163. package/src/ui/layouts/SiteLayout.tsx +38 -23
  164. package/src/ui/pages/CollectionPage.tsx +12 -2
  165. package/src/ui/pages/CollectionsPage.tsx +27 -27
  166. package/src/ui/pages/HomePage.tsx +15 -6
  167. package/src/ui/pages/SearchPage.tsx +1 -2
  168. package/src/ui/shared/CollectionsSidebar.tsx +59 -0
  169. package/src/ui/shared/Pagination.tsx +2 -2
  170. package/dist/app.js +0 -267
  171. package/dist/auth.js +0 -39
  172. package/dist/client.js +0 -13
  173. package/dist/db/index.js +0 -10
  174. package/dist/db/schema.js +0 -224
  175. package/dist/i18n/Trans.js +0 -24
  176. package/dist/i18n/context.js +0 -58
  177. package/dist/i18n/detect.js +0 -26
  178. package/dist/i18n/i18n.js +0 -49
  179. package/dist/i18n/index.js +0 -44
  180. package/dist/i18n/locales/en.js +0 -1
  181. package/dist/i18n/locales/zh-Hans.js +0 -1
  182. package/dist/i18n/locales/zh-Hant.js +0 -1
  183. package/dist/i18n/locales.js +0 -13
  184. package/dist/i18n/middleware.js +0 -30
  185. package/dist/lib/avatar-upload.js +0 -134
  186. package/dist/lib/config.js +0 -143
  187. package/dist/lib/constants.js +0 -50
  188. package/dist/lib/excerpt.js +0 -76
  189. package/dist/lib/favicon.js +0 -102
  190. package/dist/lib/feed.js +0 -123
  191. package/dist/lib/image-processor.js +0 -187
  192. package/dist/lib/image.js +0 -97
  193. package/dist/lib/index.js +0 -7
  194. package/dist/lib/markdown.js +0 -83
  195. package/dist/lib/media-helpers.js +0 -49
  196. package/dist/lib/media-upload.js +0 -104
  197. package/dist/lib/nav-reorder.js +0 -27
  198. package/dist/lib/navigation.js +0 -79
  199. package/dist/lib/pagination.js +0 -44
  200. package/dist/lib/render.js +0 -53
  201. package/dist/lib/schemas.js +0 -174
  202. package/dist/lib/sqid.js +0 -72
  203. package/dist/lib/sse.js +0 -218
  204. package/dist/lib/storage.js +0 -164
  205. package/dist/lib/theme.js +0 -65
  206. package/dist/lib/time.js +0 -159
  207. package/dist/lib/timeline.js +0 -95
  208. package/dist/lib/timezones.js +0 -388
  209. package/dist/lib/url.js +0 -89
  210. package/dist/lib/view.js +0 -217
  211. package/dist/middleware/auth.js +0 -52
  212. package/dist/middleware/onboarding.js +0 -41
  213. package/dist/routes/api/collections.js +0 -124
  214. package/dist/routes/api/nav-items.js +0 -104
  215. package/dist/routes/api/pages.js +0 -91
  216. package/dist/routes/api/posts.js +0 -218
  217. package/dist/routes/api/search.js +0 -48
  218. package/dist/routes/api/settings.js +0 -68
  219. package/dist/routes/api/upload.js +0 -246
  220. package/dist/routes/auth/reset.js +0 -221
  221. package/dist/routes/auth/setup.js +0 -194
  222. package/dist/routes/auth/signin.js +0 -176
  223. package/dist/routes/compose.js +0 -48
  224. package/dist/routes/dash/collections.js +0 -115
  225. package/dist/routes/dash/index.js +0 -118
  226. package/dist/routes/dash/media.js +0 -106
  227. package/dist/routes/dash/pages.js +0 -294
  228. package/dist/routes/dash/posts.js +0 -244
  229. package/dist/routes/dash/redirects.js +0 -257
  230. package/dist/routes/dash/settings.js +0 -379
  231. package/dist/routes/feed/rss.js +0 -62
  232. package/dist/routes/feed/sitemap.js +0 -49
  233. package/dist/routes/pages/archive.js +0 -62
  234. package/dist/routes/pages/collection.js +0 -34
  235. package/dist/routes/pages/collections.js +0 -28
  236. package/dist/routes/pages/featured.js +0 -36
  237. package/dist/routes/pages/home.js +0 -64
  238. package/dist/routes/pages/latest.js +0 -45
  239. package/dist/routes/pages/page.js +0 -68
  240. package/dist/routes/pages/post.js +0 -44
  241. package/dist/routes/pages/search.js +0 -54
  242. package/dist/services/collection.js +0 -109
  243. package/dist/services/index.js +0 -24
  244. package/dist/services/media.js +0 -117
  245. package/dist/services/navigation.js +0 -91
  246. package/dist/services/page.js +0 -84
  247. package/dist/services/post.js +0 -229
  248. package/dist/services/redirect.js +0 -48
  249. package/dist/services/search.js +0 -67
  250. package/dist/services/settings.js +0 -68
  251. package/dist/types/bindings.js +0 -3
  252. package/dist/types/config.js +0 -147
  253. package/dist/types/constants.js +0 -27
  254. package/dist/types/entities.js +0 -3
  255. package/dist/types/lingui-react-macro.d.js +0 -9
  256. package/dist/types/operations.js +0 -3
  257. package/dist/types/props.js +0 -3
  258. package/dist/types/sortablejs.d.js +0 -5
  259. package/dist/types/views.js +0 -5
  260. package/dist/types.js +0 -11
  261. package/dist/ui/color-themes.js +0 -268
  262. package/dist/ui/compose/ComposeDialog.js +0 -467
  263. package/dist/ui/compose/ComposePrompt.js +0 -55
  264. package/dist/ui/dash/ActionButtons.js +0 -46
  265. package/dist/ui/dash/CrudPageHeader.js +0 -22
  266. package/dist/ui/dash/DangerZone.js +0 -36
  267. package/dist/ui/dash/FormatBadge.js +0 -27
  268. package/dist/ui/dash/ListItemRow.js +0 -21
  269. package/dist/ui/dash/PageForm.js +0 -195
  270. package/dist/ui/dash/PostForm.js +0 -395
  271. package/dist/ui/dash/PostList.js +0 -83
  272. package/dist/ui/dash/StatusBadge.js +0 -46
  273. package/dist/ui/dash/collections/CollectionForm.js +0 -152
  274. package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
  275. package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
  276. package/dist/ui/dash/index.js +0 -10
  277. package/dist/ui/dash/media/MediaListContent.js +0 -166
  278. package/dist/ui/dash/media/ViewMediaContent.js +0 -212
  279. package/dist/ui/dash/pages/LinkFormContent.js +0 -130
  280. package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
  281. package/dist/ui/dash/settings/AccountContent.js +0 -209
  282. package/dist/ui/dash/settings/AppearanceContent.js +0 -259
  283. package/dist/ui/dash/settings/GeneralContent.js +0 -536
  284. package/dist/ui/dash/settings/SettingsNav.js +0 -41
  285. package/dist/ui/feed/LinkCard.js +0 -72
  286. package/dist/ui/feed/NoteCard.js +0 -58
  287. package/dist/ui/feed/QuoteCard.js +0 -63
  288. package/dist/ui/feed/ThreadPreview.js +0 -48
  289. package/dist/ui/feed/TimelineFeed.js +0 -41
  290. package/dist/ui/feed/TimelineItem.js +0 -27
  291. package/dist/ui/font-themes.js +0 -36
  292. package/dist/ui/layouts/BaseLayout.js +0 -153
  293. package/dist/ui/layouts/DashLayout.js +0 -141
  294. package/dist/ui/layouts/SiteLayout.js +0 -169
  295. package/dist/ui/pages/ArchivePage.js +0 -143
  296. package/dist/ui/pages/CollectionPage.js +0 -70
  297. package/dist/ui/pages/CollectionsPage.js +0 -76
  298. package/dist/ui/pages/FeaturedPage.js +0 -24
  299. package/dist/ui/pages/HomePage.js +0 -24
  300. package/dist/ui/pages/PostPage.js +0 -55
  301. package/dist/ui/pages/SearchPage.js +0 -122
  302. package/dist/ui/pages/SinglePage.js +0 -23
  303. package/dist/ui/shared/EmptyState.js +0 -27
  304. package/dist/ui/shared/MediaGallery.js +0 -35
  305. package/dist/ui/shared/Pagination.js +0 -195
  306. package/dist/ui/shared/ThreadView.js +0 -108
  307. package/dist/ui/shared/index.js +0 -5
  308. package/dist/vendor/datastar.js +0 -1606
  309. package/src/lib/__tests__/config.test.ts +0 -192
  310. package/src/lib/config.ts +0 -167
  311. package/src/routes/compose.ts +0 -63
  312. package/src/ui/dash/PostForm.tsx +0 -360
  313. package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
@@ -1,102 +0,0 @@
1
- /**
2
- * Favicon Utilities
3
- *
4
- * Sizes and ICO encoding for generated favicon variants.
5
- * Favicon data is stored as base64 in the settings table (not R2)
6
- * since the files are tiny and accessed on every page load.
7
- */ /**
8
- * Favicon variant sizes (width x height in pixels)
9
- */ export const FAVICON_SIZES = {
10
- ICO_16: 16,
11
- ICO_32: 32,
12
- APPLE_TOUCH: 180
13
- };
14
- /**
15
- * Encode PNG images into an ICO file.
16
- *
17
- * ICO format (with PNG payloads):
18
- * - Header: 6 bytes (reserved=0, type=1, count=N)
19
- * - Directory: 16 bytes per entry (width, height, colors, reserved, planes, bpp, size, offset)
20
- * - Data: raw PNG bytes for each entry
21
- *
22
- * @param entries - Array of { size, png } where png is an ArrayBuffer of PNG data
23
- * @returns ICO file as a Blob
24
- *
25
- * @example
26
- * ```ts
27
- * const ico = encodeIco([
28
- * { size: 16, png: png16ArrayBuffer },
29
- * { size: 32, png: png32ArrayBuffer },
30
- * ]);
31
- * ```
32
- */ export function encodeIco(entries) {
33
- const headerSize = 6;
34
- const dirEntrySize = 16;
35
- const dirSize = entries.length * dirEntrySize;
36
- let dataOffset = headerSize + dirSize;
37
- // Build header + directory
38
- const header = new ArrayBuffer(headerSize + dirSize);
39
- const view = new DataView(header);
40
- // ICO header
41
- view.setUint16(0, 0, true); // reserved
42
- view.setUint16(2, 1, true); // type = icon
43
- view.setUint16(4, entries.length, true); // count
44
- const pngBuffers = [];
45
- for(let i = 0; i < entries.length; i++){
46
- const entry = entries[i];
47
- const offset = headerSize + i * dirEntrySize;
48
- // Width/height: 0 means 256
49
- view.setUint8(offset + 0, entry.size < 256 ? entry.size : 0);
50
- view.setUint8(offset + 1, entry.size < 256 ? entry.size : 0);
51
- view.setUint8(offset + 2, 0); // color count (0 for >256 colors)
52
- view.setUint8(offset + 3, 0); // reserved
53
- view.setUint16(offset + 4, 1, true); // color planes
54
- view.setUint16(offset + 6, 32, true); // bits per pixel
55
- view.setUint32(offset + 8, entry.png.byteLength, true); // image size
56
- view.setUint32(offset + 12, dataOffset, true); // image offset
57
- dataOffset += entry.png.byteLength;
58
- pngBuffers.push(entry.png);
59
- }
60
- return new Blob([
61
- header,
62
- ...pngBuffers
63
- ], {
64
- type: "image/x-icon"
65
- });
66
- }
67
- /**
68
- * Convert an ArrayBuffer to a base64 string.
69
- *
70
- * @param buffer - The ArrayBuffer to encode
71
- * @returns base64-encoded string
72
- *
73
- * @example
74
- * ```ts
75
- * const b64 = arrayBufferToBase64(await blob.arrayBuffer());
76
- * ```
77
- */ export function arrayBufferToBase64(buffer) {
78
- const bytes = new Uint8Array(buffer);
79
- let binary = "";
80
- for(let i = 0; i < bytes.byteLength; i++){
81
- binary += String.fromCharCode(bytes[i]);
82
- }
83
- return btoa(binary);
84
- }
85
- /**
86
- * Convert a base64 string to a Uint8Array.
87
- *
88
- * @param base64 - The base64 string to decode
89
- * @returns decoded Uint8Array
90
- *
91
- * @example
92
- * ```ts
93
- * const bytes = base64ToUint8Array(storedBase64);
94
- * ```
95
- */ export function base64ToUint8Array(base64) {
96
- const binary = atob(base64);
97
- const bytes = new Uint8Array(binary.length);
98
- for(let i = 0; i < binary.length; i++){
99
- bytes[i] = binary.charCodeAt(i);
100
- }
101
- return bytes;
102
- }
package/dist/lib/feed.js DELETED
@@ -1,123 +0,0 @@
1
- /**
2
- * Default Feed Renderers
3
- *
4
- * RSS 2.0, Atom, and Sitemap XML generators.
5
- * Theme authors can import these to extend/wrap the defaults:
6
- *
7
- * @example
8
- * ```typescript
9
- * import { defaultRssRenderer } from "@jant/core/lib/feed";
10
- * ```
11
- */ /**
12
- * Escape special XML characters.
13
- */ function escapeXml(str) {
14
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
15
- }
16
- /**
17
- * Default RSS 2.0 renderer.
18
- *
19
- * @param data - Feed data with PostView[] (pre-computed URLs)
20
- * @returns RSS 2.0 XML string
21
- */ export function defaultRssRenderer(data) {
22
- const { siteName, siteDescription, siteUrl, siteLanguage, posts } = data;
23
- const items = posts.map((post)=>{
24
- const link = `${siteUrl}${post.permalink}`;
25
- const title = post.title || `Post #${post.id}`;
26
- const pubDate = new Date(post.publishedAt).toUTCString();
27
- // Add enclosure for first media attachment
28
- const firstMedia = post.media[0];
29
- const enclosure = firstMedia ? `\n <enclosure url="${firstMedia.url}" type="${firstMedia.mimeType}"${firstMedia.size ? ` length="${firstMedia.size}"` : ""}/>` : "";
30
- return `
31
- <item>
32
- <title><![CDATA[${escapeXml(title)}]]></title>
33
- <link>${link}</link>
34
- <guid isPermaLink="true">${link}</guid>
35
- <pubDate>${pubDate}</pubDate>
36
- <description><![CDATA[${post.bodyHtml || ""}]]></description>${enclosure}
37
- </item>`;
38
- }).join("");
39
- return `<?xml version="1.0" encoding="UTF-8"?>
40
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
41
- <channel>
42
- <title>${escapeXml(siteName)}</title>
43
- <link>${siteUrl}</link>
44
- <description>${escapeXml(siteDescription)}</description>
45
- <language>${siteLanguage}</language>
46
- <atom:link href="${siteUrl}/feed" rel="self" type="application/rss+xml"/>
47
- ${items}
48
- </channel>
49
- </rss>`;
50
- }
51
- /**
52
- * Default Atom renderer.
53
- *
54
- * @param data - Feed data with PostView[] (pre-computed URLs)
55
- * @returns Atom XML string
56
- */ export function defaultAtomRenderer(data) {
57
- const { siteName, siteDescription, siteUrl, posts } = data;
58
- const entries = posts.map((post)=>{
59
- const link = `${siteUrl}${post.permalink}`;
60
- const title = post.title || `Post #${post.id}`;
61
- return `
62
- <entry>
63
- <title>${escapeXml(title)}</title>
64
- <link href="${link}" rel="alternate"/>
65
- <id>${link}</id>
66
- <published>${post.publishedAt}</published>
67
- <updated>${post.updatedAt}</updated>
68
- <content type="html"><![CDATA[${post.bodyHtml || ""}]]></content>
69
- </entry>`;
70
- }).join("");
71
- const now = new Date().toISOString();
72
- return `<?xml version="1.0" encoding="UTF-8"?>
73
- <feed xmlns="http://www.w3.org/2005/Atom">
74
- <title>${escapeXml(siteName)}</title>
75
- <subtitle>${escapeXml(siteDescription)}</subtitle>
76
- <link href="${siteUrl}" rel="alternate"/>
77
- <link href="${siteUrl}/feed/atom.xml" rel="self"/>
78
- <id>${siteUrl}/</id>
79
- <updated>${now}</updated>
80
- ${entries}
81
- </feed>`;
82
- }
83
- /**
84
- * Default Sitemap renderer.
85
- *
86
- * @param data - Sitemap data with PostView[] and PageView[]
87
- * @returns Sitemap XML string
88
- */ export function defaultSitemapRenderer(data) {
89
- const { siteUrl, posts, pages } = data;
90
- const postUrls = posts.map((post)=>{
91
- const loc = `${siteUrl}${post.permalink}`;
92
- const lastmod = post.updatedAt.split("T")[0];
93
- const priority = post.featured ? "0.8" : "0.6";
94
- return `
95
- <url>
96
- <loc>${loc}</loc>
97
- <lastmod>${lastmod}</lastmod>
98
- <priority>${priority}</priority>
99
- </url>`;
100
- }).join("");
101
- const pageUrls = pages.map((page)=>{
102
- const loc = `${siteUrl}/${page.slug}`;
103
- const lastmod = page.updatedAt.split("T")[0];
104
- return `
105
- <url>
106
- <loc>${loc}</loc>
107
- <lastmod>${lastmod}</lastmod>
108
- <priority>0.7</priority>
109
- </url>`;
110
- }).join("");
111
- const homepageUrl = `
112
- <url>
113
- <loc>${siteUrl}/</loc>
114
- <priority>1.0</priority>
115
- <changefreq>daily</changefreq>
116
- </url>`;
117
- return `<?xml version="1.0" encoding="UTF-8"?>
118
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
119
- ${homepageUrl}
120
- ${postUrls}
121
- ${pageUrls}
122
- </urlset>`;
123
- }
@@ -1,187 +0,0 @@
1
- /**
2
- * Client-side Image Processor
3
- *
4
- * Processes images before upload:
5
- * - Corrects EXIF orientation
6
- * - Resizes to max dimensions
7
- * - Strips all metadata (privacy)
8
- * - Converts to WebP format
9
- */ const DEFAULT_OPTIONS = {
10
- maxWidth: 1920,
11
- maxHeight: 1920,
12
- quality: 0.85,
13
- mimeType: "image/webp"
14
- };
15
- /**
16
- * EXIF Orientation values and their transformations
17
- */ const ORIENTATIONS = {
18
- 1: {
19
- rotate: 0,
20
- flip: false
21
- },
22
- 2: {
23
- rotate: 0,
24
- flip: true
25
- },
26
- 3: {
27
- rotate: 180,
28
- flip: false
29
- },
30
- 4: {
31
- rotate: 180,
32
- flip: true
33
- },
34
- 5: {
35
- rotate: 90,
36
- flip: true
37
- },
38
- 6: {
39
- rotate: 90,
40
- flip: false
41
- },
42
- 7: {
43
- rotate: 270,
44
- flip: true
45
- },
46
- 8: {
47
- rotate: 270,
48
- flip: false
49
- }
50
- };
51
- /**
52
- * Read EXIF orientation from JPEG file
53
- */ function readExifOrientation(buffer) {
54
- const view = new DataView(buffer);
55
- // Check for JPEG SOI marker
56
- if (view.getUint16(0) !== 0xffd8) return 1;
57
- let offset = 2;
58
- const length = view.byteLength;
59
- while(offset < length){
60
- if (view.getUint8(offset) !== 0xff) return 1;
61
- const marker = view.getUint8(offset + 1);
62
- // APP1 marker (EXIF)
63
- if (marker === 0xe1) {
64
- const exifOffset = offset + 4;
65
- // Check for "Exif\0\0"
66
- if (view.getUint32(exifOffset) !== 0x45786966 || view.getUint16(exifOffset + 4) !== 0x0000) {
67
- return 1;
68
- }
69
- const tiffOffset = exifOffset + 6;
70
- const littleEndian = view.getUint16(tiffOffset) === 0x4949;
71
- // Validate TIFF header
72
- if (view.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) return 1;
73
- const ifdOffset = view.getUint32(tiffOffset + 4, littleEndian);
74
- const numEntries = view.getUint16(tiffOffset + ifdOffset, littleEndian);
75
- // Search for orientation tag (0x0112)
76
- for(let i = 0; i < numEntries; i++){
77
- const entryOffset = tiffOffset + ifdOffset + 2 + i * 12;
78
- const tag = view.getUint16(entryOffset, littleEndian);
79
- if (tag === 0x0112) {
80
- return view.getUint16(entryOffset + 8, littleEndian);
81
- }
82
- }
83
- return 1;
84
- }
85
- // Skip to next marker
86
- if (marker === 0xd8 || marker === 0xd9) {
87
- offset += 2;
88
- } else {
89
- offset += 2 + view.getUint16(offset + 2);
90
- }
91
- }
92
- return 1;
93
- }
94
- /**
95
- * Load image from file
96
- */ function loadImage(file) {
97
- return new Promise((resolve, reject)=>{
98
- const img = new Image();
99
- img.onload = ()=>{
100
- URL.revokeObjectURL(img.src);
101
- resolve(img);
102
- };
103
- img.onerror = ()=>reject(new Error("Failed to load image"));
104
- img.src = URL.createObjectURL(file);
105
- });
106
- }
107
- /**
108
- * Calculate output dimensions maintaining aspect ratio
109
- */ function calculateDimensions(width, height, maxWidth, maxHeight) {
110
- if (width <= maxWidth && height <= maxHeight) {
111
- return {
112
- width,
113
- height
114
- };
115
- }
116
- const ratio = Math.min(maxWidth / width, maxHeight / height);
117
- return {
118
- width: Math.round(width * ratio),
119
- height: Math.round(height * ratio)
120
- };
121
- }
122
- /**
123
- * Process image file
124
- */ async function process(file, options = {}) {
125
- const opts = {
126
- ...DEFAULT_OPTIONS,
127
- ...options
128
- };
129
- // Read file buffer for EXIF
130
- const buffer = await file.arrayBuffer();
131
- const orientation = readExifOrientation(buffer);
132
- const transform = ORIENTATIONS[orientation] || ORIENTATIONS[1];
133
- // Load image
134
- const img = await loadImage(file);
135
- // For 90° or 270° rotation, swap dimensions
136
- const isRotated = transform.rotate === 90 || transform.rotate === 270;
137
- const srcWidth = isRotated ? img.height : img.width;
138
- const srcHeight = isRotated ? img.width : img.height;
139
- // Calculate output size
140
- const { width, height } = calculateDimensions(srcWidth, srcHeight, opts.maxWidth, opts.maxHeight);
141
- // Create canvas
142
- const canvas = document.createElement("canvas");
143
- canvas.width = width;
144
- canvas.height = height;
145
- const ctx = canvas.getContext("2d");
146
- if (!ctx) throw new Error("Failed to get canvas context");
147
- // Apply transformations
148
- ctx.save();
149
- ctx.translate(width / 2, height / 2);
150
- if (transform.rotate) {
151
- ctx.rotate(transform.rotate * Math.PI / 180);
152
- }
153
- if (transform.flip) {
154
- ctx.scale(-1, 1);
155
- }
156
- const drawWidth = isRotated ? height : width;
157
- const drawHeight = isRotated ? width : height;
158
- ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
159
- ctx.restore();
160
- // Export as WebP
161
- return new Promise((resolve, reject)=>{
162
- canvas.toBlob((blob)=>{
163
- if (blob) {
164
- resolve(blob);
165
- } else {
166
- reject(new Error("Failed to create blob"));
167
- }
168
- }, opts.mimeType, opts.quality);
169
- });
170
- }
171
- /**
172
- * Process file and create a new File object
173
- */ async function processToFile(file, options = {}) {
174
- const blob = await process(file, options);
175
- // Generate new filename with .webp extension
176
- const originalName = file.name.replace(/\.[^.]+$/, "");
177
- const newName = `${originalName}.webp`;
178
- return new File([
179
- blob
180
- ], newName, {
181
- type: "image/webp"
182
- });
183
- }
184
- export const ImageProcessor = {
185
- process,
186
- processToFile
187
- };
package/dist/lib/image.js DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * Image URL utilities
3
- *
4
- * Provides helpers for generating image URLs with optional transformations.
5
- */ /**
6
- * Options for image transformations
7
- */ /**
8
- * Generates an image URL with optional transformations.
9
- *
10
- * If `transformUrl` is provided and options are specified, returns a transformed image URL.
11
- * Otherwise, returns the original URL unchanged.
12
- *
13
- * Compatible with:
14
- * - Cloudflare Image Transformations (`/cdn-cgi/image/...`)
15
- * - imgproxy
16
- * - Cloudinary
17
- * - Any service with similar URL-based transformation API
18
- *
19
- * @param originalUrl - The original image URL
20
- * @param transformUrl - The base URL for transformations (e.g., `https://example.com/cdn-cgi/image`)
21
- * @param options - Transformation options (width, height, quality, format, fit)
22
- * @returns The transformed URL or original URL if transformations are not configured
23
- *
24
- * @example
25
- * ```ts
26
- * // Without transform URL - returns original
27
- * getImageUrl("/media/abc123", undefined, { width: 200 });
28
- * // Returns: "/media/abc123"
29
- *
30
- * // With transform URL - returns transformed
31
- * getImageUrl("/media/abc123", "https://example.com/cdn-cgi/image", { width: 200, quality: 80 });
32
- * // Returns: "https://example.com/cdn-cgi/image/width=200,quality=80/https://example.com/media/abc123"
33
- * ```
34
- */ export function getImageUrl(originalUrl, transformUrl, options) {
35
- if (!transformUrl || !options || Object.keys(options).length === 0) {
36
- return originalUrl;
37
- }
38
- const params = [];
39
- if (options.width) params.push(`width=${options.width}`);
40
- if (options.height) params.push(`height=${options.height}`);
41
- if (options.quality) params.push(`quality=${options.quality}`);
42
- if (options.format) params.push(`format=${options.format}`);
43
- if (options.fit) params.push(`fit=${options.fit}`);
44
- if (params.length === 0) {
45
- return originalUrl;
46
- }
47
- return `${transformUrl}/${params.join(",")}/${originalUrl}`;
48
- }
49
- /**
50
- * Returns the appropriate public URL base for a given storage provider.
51
- *
52
- * For `"s3"` provider, returns `s3PublicUrl`. For all other providers
53
- * (including `"r2"`), returns `r2PublicUrl`. Falls back to `undefined`
54
- * if the matching URL is not configured.
55
- *
56
- * @param provider - The storage provider identifier (e.g., `"r2"`, `"s3"`)
57
- * @param r2PublicUrl - Optional R2 public URL
58
- * @param s3PublicUrl - Optional S3 public URL
59
- * @returns The public URL base for the provider, or undefined
60
- *
61
- * @example
62
- * ```ts
63
- * getPublicUrlForProvider("r2", "https://r2.example.com", "https://s3.example.com");
64
- * // Returns: "https://r2.example.com"
65
- *
66
- * getPublicUrlForProvider("s3", "https://r2.example.com", "https://s3.example.com");
67
- * // Returns: "https://s3.example.com"
68
- * ```
69
- */ export function getPublicUrlForProvider(provider, r2PublicUrl, s3PublicUrl) {
70
- if (provider === "s3") return s3PublicUrl;
71
- return r2PublicUrl;
72
- }
73
- /**
74
- * Generates a media URL from a storage key.
75
- *
76
- * Both proxy and CDN paths use the same structure — only the domain differs.
77
- * Without a public URL, returns a root-relative path for the local proxy.
78
- * With a public URL, prefixes that domain.
79
- *
80
- * @param storageKey - The storage object key (e.g. `"media/2025/01/uuid.webp"`)
81
- * @param publicUrl - Optional public URL base for direct CDN access
82
- * @returns The public URL for the media file
83
- *
84
- * @example
85
- * ```ts
86
- * // Without public URL - local proxy
87
- * getMediaUrl("media/2025/01/01902a9f-1a2b-7c3d.webp");
88
- * // Returns: "/media/2025/01/01902a9f-1a2b-7c3d.webp"
89
- *
90
- * // With public URL - CDN
91
- * getMediaUrl("media/2025/01/01902a9f-1a2b-7c3d.webp", "https://cdn.example.com");
92
- * // Returns: "https://cdn.example.com/media/2025/01/01902a9f-1a2b-7c3d.webp"
93
- * ```
94
- */ export function getMediaUrl(storageKey, publicUrl) {
95
- const base = publicUrl ? publicUrl.replace(/\/+$/, "") : "";
96
- return `${base}/${storageKey}`;
97
- }
package/dist/lib/index.js DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Utility Functions
3
- */ export * from "./constants.js";
4
- export * as sqid from "./sqid.js";
5
- export * as time from "./time.js";
6
- export * as url from "./url.js";
7
- export * as markdown from "./markdown.js";
@@ -1,83 +0,0 @@
1
- /**
2
- * Markdown Rendering
3
- *
4
- * Uses marked with minimal configuration
5
- */ import { marked } from "marked";
6
- // Configure marked for security and simplicity
7
- marked.setOptions({
8
- gfm: true,
9
- breaks: true
10
- });
11
- /**
12
- * Renders Markdown content to HTML using the marked library.
13
- *
14
- * Configured with GitHub Flavored Markdown (GFM) support and line breaks enabled.
15
- * Uses synchronous parsing for simplicity and consistency in server-side rendering.
16
- *
17
- * @param markdown - The Markdown string to convert to HTML
18
- * @returns The rendered HTML string
19
- *
20
- * @example
21
- * ```ts
22
- * const html = render("# Hello\n\nThis is **bold** text.");
23
- * // Returns: "<h1>Hello</h1>\n<p>This is <strong>bold</strong> text.</p>"
24
- * ```
25
- */ export function render(markdown) {
26
- return marked.parse(markdown, {
27
- async: false
28
- });
29
- }
30
- /**
31
- * Converts Markdown to plain text by stripping all formatting syntax.
32
- *
33
- * Removes Markdown syntax including headers, bold, italic, links, images, code blocks,
34
- * blockquotes, lists, and converts newlines to spaces. Useful for generating text excerpts,
35
- * meta descriptions, or search indexes.
36
- *
37
- * @param markdown - The Markdown string to convert to plain text
38
- * @returns The plain text string with all Markdown syntax removed
39
- *
40
- * @example
41
- * ```ts
42
- * const plain = toPlainText("## Hello\n\nThis is **bold** and [a link](url).");
43
- * // Returns: "Hello This is bold and a link."
44
- * ```
45
- */ export function toPlainText(markdown) {
46
- return markdown.replace(/#{1,6}\s+/g, "") // Remove headers
47
- .replace(/\*\*(.+?)\*\*/g, "$1") // Bold
48
- .replace(/\*(.+?)\*/g, "$1") // Italic
49
- .replace(/\[(.+?)\]\(.+?\)/g, "$1") // Links
50
- .replace(/!\[.*?\]\(.+?\)/g, "") // Images
51
- .replace(/`{1,3}[^`]*`{1,3}/g, "") // Code
52
- .replace(/>\s+/g, "") // Blockquotes
53
- .replace(/[-*+]\s+/g, "") // Lists
54
- .replace(/\n+/g, " ") // Newlines
55
- .trim();
56
- }
57
- /**
58
- * Extracts a title from Markdown content by taking the first sentence or line.
59
- *
60
- * Converts Markdown to plain text first, then takes the first sentence (split by `.!?`)
61
- * or truncates to the specified maximum length. Useful for generating automatic titles
62
- * from post content when no explicit title is provided.
63
- *
64
- * @param markdown - The Markdown string to extract a title from
65
- * @param maxLength - Maximum length of the extracted title (default: 120)
66
- * @returns The extracted title string, with "..." appended if truncated
67
- *
68
- * @example
69
- * ```ts
70
- * const title = extractTitle("This is the first sentence. And another one.", 50);
71
- * // Returns: "This is the first sentence"
72
- *
73
- * const title = extractTitle("A very long sentence that exceeds the maximum length...", 30);
74
- * // Returns: "A very long sentence that ex..."
75
- * ```
76
- */ export function extractTitle(markdown, maxLength = 120) {
77
- const plain = toPlainText(markdown);
78
- const firstLine = plain.split(/[.!?]/)[0] ?? plain;
79
- if (firstLine.length <= maxLength) {
80
- return firstLine;
81
- }
82
- return plain.slice(0, maxLength).trim() + "...";
83
- }
@@ -1,49 +0,0 @@
1
- /**
2
- * Media Helper Utilities
3
- *
4
- * Shared logic for building MediaAttachment maps from raw media data.
5
- */ import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "./image.js";
6
- /**
7
- * Builds a map of post IDs to MediaAttachment arrays from raw media data.
8
- *
9
- * Transforms raw Media objects (with storage keys) into MediaAttachment objects
10
- * (with public URLs and preview URLs) suitable for rendering.
11
- * Automatically resolves the correct public URL based on each media item's
12
- * storage provider (`"r2"` or `"s3"`).
13
- *
14
- * @param rawMediaMap - Map of post IDs to raw Media arrays from the media service
15
- * @param r2PublicUrl - Optional R2 public URL for direct CDN access
16
- * @param imageTransformUrl - Optional image transformation service URL
17
- * @param s3PublicUrl - Optional S3 public URL for direct CDN access
18
- * @returns Map of post IDs to MediaAttachment arrays
19
- *
20
- * @example
21
- * ```ts
22
- * const rawMediaMap = await services.media.getByPostIds(postIds);
23
- * const mediaMap = buildMediaMap(rawMediaMap, c.env.R2_PUBLIC_URL, c.env.IMAGE_TRANSFORM_URL, c.env.S3_PUBLIC_URL);
24
- * ```
25
- */ export function buildMediaMap(rawMediaMap, r2PublicUrl, imageTransformUrl, s3PublicUrl) {
26
- const mediaMap = new Map();
27
- for (const [postId, mediaList] of rawMediaMap){
28
- mediaMap.set(postId, mediaList.map((m)=>{
29
- const publicUrl = getPublicUrlForProvider(m.provider, r2PublicUrl, s3PublicUrl);
30
- return {
31
- id: m.id,
32
- url: getMediaUrl(m.storageKey, publicUrl),
33
- previewUrl: getImageUrl(getMediaUrl(m.storageKey, publicUrl), imageTransformUrl, {
34
- width: 400,
35
- quality: 80,
36
- format: "auto",
37
- fit: "cover"
38
- }),
39
- alt: m.alt,
40
- blurhash: m.blurhash,
41
- width: m.width,
42
- height: m.height,
43
- position: m.position,
44
- mimeType: m.mimeType
45
- };
46
- }));
47
- }
48
- return mediaMap;
49
- }