@jant/core 0.1.2 → 0.2.0

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 (288) hide show
  1. package/dist/app.d.ts +34 -0
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +474 -0
  4. package/dist/assets/datastar.min.js +1775 -0
  5. package/dist/auth.d.ts +23 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +34 -0
  8. package/{src/client.ts → dist/client.d.ts} +1 -1
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +4 -0
  11. package/dist/db/index.d.ts +10 -0
  12. package/dist/db/index.d.ts.map +1 -0
  13. package/dist/db/index.js +10 -0
  14. package/dist/db/schema.d.ts +1507 -0
  15. package/dist/db/schema.d.ts.map +1 -0
  16. package/dist/db/schema.js +183 -0
  17. package/{src/i18n/Trans.tsx → dist/i18n/Trans.d.ts} +4 -10
  18. package/dist/i18n/Trans.d.ts.map +1 -0
  19. package/dist/i18n/Trans.js +24 -0
  20. package/dist/i18n/context.d.ts +69 -0
  21. package/dist/i18n/context.d.ts.map +1 -0
  22. package/dist/i18n/context.js +61 -0
  23. package/dist/i18n/detect.d.ts +31 -0
  24. package/dist/i18n/detect.d.ts.map +1 -0
  25. package/dist/i18n/detect.js +77 -0
  26. package/{src/i18n/i18n.ts → dist/i18n/i18n.d.ts} +5 -25
  27. package/dist/i18n/i18n.d.ts.map +1 -0
  28. package/dist/i18n/i18n.js +55 -0
  29. package/dist/i18n/index.d.ts +41 -0
  30. package/dist/i18n/index.d.ts.map +1 -0
  31. package/{src/i18n/index.ts → dist/i18n/index.js} +3 -24
  32. package/dist/i18n/locales/en.d.ts +3 -0
  33. package/dist/i18n/locales/en.d.ts.map +1 -0
  34. package/dist/i18n/locales/en.js +1 -0
  35. package/dist/i18n/locales/zh-Hans.d.ts +3 -0
  36. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -0
  37. package/dist/i18n/locales/zh-Hans.js +1 -0
  38. package/dist/i18n/locales/zh-Hant.d.ts +3 -0
  39. package/dist/i18n/locales/zh-Hant.d.ts.map +1 -0
  40. package/dist/i18n/locales/zh-Hant.js +1 -0
  41. package/dist/i18n/locales.d.ts +11 -0
  42. package/dist/i18n/locales.d.ts.map +1 -0
  43. package/dist/i18n/locales.js +13 -0
  44. package/dist/i18n/middleware.d.ts +24 -0
  45. package/dist/i18n/middleware.d.ts.map +1 -0
  46. package/dist/i18n/middleware.js +41 -0
  47. package/dist/index.d.ts +16 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/{src/index.ts → dist/index.js} +1 -28
  50. package/dist/lib/assets.d.ts +19 -0
  51. package/dist/lib/assets.d.ts.map +1 -0
  52. package/dist/lib/assets.js +33 -0
  53. package/dist/lib/constants.d.ts +36 -0
  54. package/dist/lib/constants.d.ts.map +1 -0
  55. package/dist/lib/constants.js +50 -0
  56. package/{src/lib/image.ts → dist/lib/image.d.ts} +13 -47
  57. package/dist/lib/image.d.ts.map +1 -0
  58. package/dist/lib/image.js +77 -0
  59. package/{src/lib/index.ts → dist/lib/index.d.ts} +1 -1
  60. package/dist/lib/index.d.ts.map +1 -0
  61. package/dist/lib/index.js +7 -0
  62. package/dist/lib/markdown.d.ts +60 -0
  63. package/dist/lib/markdown.d.ts.map +1 -0
  64. package/{src/lib/markdown.ts → dist/lib/markdown.js} +16 -26
  65. package/dist/lib/schemas.d.ts +113 -0
  66. package/dist/lib/schemas.d.ts.map +1 -0
  67. package/dist/lib/schemas.js +71 -0
  68. package/dist/lib/sqid.d.ts +60 -0
  69. package/dist/lib/sqid.d.ts.map +1 -0
  70. package/{src/lib/sqid.ts → dist/lib/sqid.js} +15 -22
  71. package/dist/lib/sse.d.ts +95 -0
  72. package/dist/lib/sse.d.ts.map +1 -0
  73. package/dist/lib/sse.js +81 -0
  74. package/dist/lib/time.d.ts +90 -0
  75. package/dist/lib/time.d.ts.map +1 -0
  76. package/{src/lib/time.ts → dist/lib/time.js} +20 -33
  77. package/{src/lib/url.ts → dist/lib/url.d.ts} +5 -30
  78. package/dist/lib/url.d.ts.map +1 -0
  79. package/dist/lib/url.js +89 -0
  80. package/dist/middleware/auth.d.ts +24 -0
  81. package/dist/middleware/auth.d.ts.map +1 -0
  82. package/dist/middleware/auth.js +52 -0
  83. package/dist/routes/api/posts.d.ts +13 -0
  84. package/dist/routes/api/posts.d.ts.map +1 -0
  85. package/dist/routes/api/posts.js +124 -0
  86. package/dist/routes/api/search.d.ts +13 -0
  87. package/dist/routes/api/search.d.ts.map +1 -0
  88. package/dist/routes/api/search.js +49 -0
  89. package/dist/routes/api/upload.d.ts +16 -0
  90. package/dist/routes/api/upload.d.ts.map +1 -0
  91. package/dist/routes/api/upload.js +227 -0
  92. package/dist/routes/dash/collections.d.ts +13 -0
  93. package/dist/routes/dash/collections.d.ts.map +1 -0
  94. package/dist/routes/dash/collections.js +512 -0
  95. package/dist/routes/dash/index.d.ts +15 -0
  96. package/dist/routes/dash/index.d.ts.map +1 -0
  97. package/dist/routes/dash/index.js +117 -0
  98. package/dist/routes/dash/media.d.ts +16 -0
  99. package/dist/routes/dash/media.d.ts.map +1 -0
  100. package/dist/routes/dash/media.js +589 -0
  101. package/dist/routes/dash/pages.d.ts +15 -0
  102. package/dist/routes/dash/pages.d.ts.map +1 -0
  103. package/dist/routes/dash/pages.js +290 -0
  104. package/dist/routes/dash/posts.d.ts +13 -0
  105. package/dist/routes/dash/posts.d.ts.map +1 -0
  106. package/dist/routes/dash/posts.js +226 -0
  107. package/dist/routes/dash/redirects.d.ts +13 -0
  108. package/dist/routes/dash/redirects.d.ts.map +1 -0
  109. package/dist/routes/dash/redirects.js +237 -0
  110. package/dist/routes/dash/settings.d.ts +13 -0
  111. package/dist/routes/dash/settings.d.ts.map +1 -0
  112. package/dist/routes/dash/settings.js +154 -0
  113. package/dist/routes/feed/rss.d.ts +13 -0
  114. package/dist/routes/feed/rss.d.ts.map +1 -0
  115. package/dist/routes/feed/rss.js +95 -0
  116. package/dist/routes/feed/sitemap.d.ts +13 -0
  117. package/dist/routes/feed/sitemap.d.ts.map +1 -0
  118. package/dist/routes/feed/sitemap.js +59 -0
  119. package/dist/routes/pages/archive.d.ts +15 -0
  120. package/dist/routes/pages/archive.d.ts.map +1 -0
  121. package/dist/routes/pages/archive.js +255 -0
  122. package/dist/routes/pages/collection.d.ts +13 -0
  123. package/dist/routes/pages/collection.d.ts.map +1 -0
  124. package/dist/routes/pages/collection.js +93 -0
  125. package/dist/routes/pages/home.d.ts +13 -0
  126. package/dist/routes/pages/home.d.ts.map +1 -0
  127. package/dist/routes/pages/home.js +122 -0
  128. package/dist/routes/pages/page.d.ts +15 -0
  129. package/dist/routes/pages/page.d.ts.map +1 -0
  130. package/dist/routes/pages/page.js +69 -0
  131. package/dist/routes/pages/post.d.ts +13 -0
  132. package/dist/routes/pages/post.d.ts.map +1 -0
  133. package/dist/routes/pages/post.js +90 -0
  134. package/dist/routes/pages/search.d.ts +13 -0
  135. package/dist/routes/pages/search.d.ts.map +1 -0
  136. package/dist/routes/pages/search.js +180 -0
  137. package/dist/services/collection.d.ts +31 -0
  138. package/dist/services/collection.d.ts.map +1 -0
  139. package/dist/services/collection.js +108 -0
  140. package/dist/services/index.d.ts +28 -0
  141. package/dist/services/index.d.ts.map +1 -0
  142. package/dist/services/index.js +20 -0
  143. package/dist/services/media.d.ts +27 -0
  144. package/dist/services/media.d.ts.map +1 -0
  145. package/dist/services/media.js +62 -0
  146. package/dist/services/post.d.ts +31 -0
  147. package/dist/services/post.d.ts.map +1 -0
  148. package/dist/services/post.js +191 -0
  149. package/dist/services/redirect.d.ts +15 -0
  150. package/dist/services/redirect.d.ts.map +1 -0
  151. package/dist/services/redirect.js +48 -0
  152. package/dist/services/search.d.ts +26 -0
  153. package/dist/services/search.d.ts.map +1 -0
  154. package/dist/services/search.js +61 -0
  155. package/dist/services/settings.d.ts +17 -0
  156. package/dist/services/settings.d.ts.map +1 -0
  157. package/dist/services/settings.js +65 -0
  158. package/dist/theme/components/ActionButtons.d.ts +43 -0
  159. package/dist/theme/components/ActionButtons.d.ts.map +1 -0
  160. package/dist/theme/components/ActionButtons.js +50 -0
  161. package/dist/theme/components/CrudPageHeader.d.ts +23 -0
  162. package/dist/theme/components/CrudPageHeader.d.ts.map +1 -0
  163. package/dist/theme/components/CrudPageHeader.js +22 -0
  164. package/dist/theme/components/DangerZone.d.ts +36 -0
  165. package/dist/theme/components/DangerZone.d.ts.map +1 -0
  166. package/dist/theme/components/DangerZone.js +39 -0
  167. package/dist/theme/components/EmptyState.d.ts +27 -0
  168. package/dist/theme/components/EmptyState.d.ts.map +1 -0
  169. package/dist/theme/components/EmptyState.js +27 -0
  170. package/dist/theme/components/ListItemRow.d.ts +15 -0
  171. package/dist/theme/components/ListItemRow.d.ts.map +1 -0
  172. package/dist/theme/components/ListItemRow.js +21 -0
  173. package/dist/theme/components/PageForm.d.ts +14 -0
  174. package/dist/theme/components/PageForm.d.ts.map +1 -0
  175. package/dist/theme/components/PageForm.js +173 -0
  176. package/dist/theme/components/Pagination.d.ts +46 -0
  177. package/dist/theme/components/Pagination.d.ts.map +1 -0
  178. package/dist/theme/components/Pagination.js +159 -0
  179. package/dist/theme/components/PostForm.d.ts +12 -0
  180. package/dist/theme/components/PostForm.d.ts.map +1 -0
  181. package/dist/theme/components/PostForm.js +230 -0
  182. package/dist/theme/components/PostList.d.ts +10 -0
  183. package/dist/theme/components/PostList.d.ts.map +1 -0
  184. package/dist/theme/components/PostList.js +73 -0
  185. package/dist/theme/components/ThreadView.d.ts +15 -0
  186. package/dist/theme/components/ThreadView.d.ts.map +1 -0
  187. package/dist/theme/components/ThreadView.js +111 -0
  188. package/dist/theme/components/TypeBadge.d.ts +12 -0
  189. package/dist/theme/components/TypeBadge.d.ts.map +1 -0
  190. package/dist/theme/components/TypeBadge.js +39 -0
  191. package/dist/theme/components/VisibilityBadge.d.ts +12 -0
  192. package/dist/theme/components/VisibilityBadge.d.ts.map +1 -0
  193. package/dist/theme/components/VisibilityBadge.js +37 -0
  194. package/{src/theme/components/index.ts → dist/theme/components/index.d.ts} +1 -0
  195. package/dist/theme/components/index.d.ts.map +1 -0
  196. package/dist/theme/components/index.js +12 -0
  197. package/dist/theme/index.d.ts +21 -0
  198. package/dist/theme/index.d.ts.map +1 -0
  199. package/{src/theme/index.ts → dist/theme/index.js} +1 -4
  200. package/dist/theme/layouts/BaseLayout.d.ts +16 -0
  201. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -0
  202. package/dist/theme/layouts/BaseLayout.js +58 -0
  203. package/dist/theme/layouts/DashLayout.d.ts +15 -0
  204. package/dist/theme/layouts/DashLayout.d.ts.map +1 -0
  205. package/dist/theme/layouts/DashLayout.js +139 -0
  206. package/{src/theme/layouts/index.ts → dist/theme/layouts/index.d.ts} +1 -0
  207. package/dist/theme/layouts/index.d.ts.map +1 -0
  208. package/dist/theme/layouts/index.js +2 -0
  209. package/dist/theme/styles/main.css +2 -0
  210. package/dist/types.d.ts +179 -0
  211. package/dist/types.d.ts.map +1 -0
  212. package/dist/types.js +19 -0
  213. package/package.json +26 -26
  214. package/drizzle.config.ts +0 -10
  215. package/lingui.config.ts +0 -16
  216. package/src/app.tsx +0 -377
  217. package/src/assets/datastar.min.js +0 -8
  218. package/src/auth.ts +0 -38
  219. package/src/db/index.ts +0 -14
  220. package/src/db/migrations/0000_solid_moon_knight.sql +0 -118
  221. package/src/db/migrations/0001_add_search_fts.sql +0 -40
  222. package/src/db/migrations/0002_collection_path.sql +0 -2
  223. package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
  224. package/src/db/migrations/0004_media_uuid.sql +0 -35
  225. package/src/db/migrations/meta/0000_snapshot.json +0 -784
  226. package/src/db/migrations/meta/_journal.json +0 -41
  227. package/src/db/schema.ts +0 -159
  228. package/src/i18n/EXAMPLES.md +0 -235
  229. package/src/i18n/README.md +0 -296
  230. package/src/i18n/context.tsx +0 -101
  231. package/src/i18n/detect.ts +0 -100
  232. package/src/i18n/locales/en.po +0 -875
  233. package/src/i18n/locales/en.ts +0 -1
  234. package/src/i18n/locales/zh-Hans.po +0 -875
  235. package/src/i18n/locales/zh-Hans.ts +0 -1
  236. package/src/i18n/locales/zh-Hant.po +0 -875
  237. package/src/i18n/locales/zh-Hant.ts +0 -1
  238. package/src/i18n/locales.ts +0 -14
  239. package/src/i18n/middleware.ts +0 -59
  240. package/src/lib/assets.ts +0 -47
  241. package/src/lib/constants.ts +0 -67
  242. package/src/lib/schemas.ts +0 -92
  243. package/src/lib/sse.ts +0 -152
  244. package/src/middleware/auth.ts +0 -59
  245. package/src/routes/api/posts.ts +0 -127
  246. package/src/routes/api/search.ts +0 -53
  247. package/src/routes/api/upload.ts +0 -240
  248. package/src/routes/dash/collections.tsx +0 -341
  249. package/src/routes/dash/index.tsx +0 -89
  250. package/src/routes/dash/media.tsx +0 -551
  251. package/src/routes/dash/pages.tsx +0 -245
  252. package/src/routes/dash/posts.tsx +0 -202
  253. package/src/routes/dash/redirects.tsx +0 -155
  254. package/src/routes/dash/settings.tsx +0 -93
  255. package/src/routes/feed/rss.ts +0 -119
  256. package/src/routes/feed/sitemap.ts +0 -75
  257. package/src/routes/pages/archive.tsx +0 -223
  258. package/src/routes/pages/collection.tsx +0 -79
  259. package/src/routes/pages/home.tsx +0 -93
  260. package/src/routes/pages/page.tsx +0 -64
  261. package/src/routes/pages/post.tsx +0 -81
  262. package/src/routes/pages/search.tsx +0 -162
  263. package/src/services/collection.ts +0 -180
  264. package/src/services/index.ts +0 -40
  265. package/src/services/media.ts +0 -97
  266. package/src/services/post.ts +0 -279
  267. package/src/services/redirect.ts +0 -74
  268. package/src/services/search.ts +0 -117
  269. package/src/services/settings.ts +0 -76
  270. package/src/theme/components/ActionButtons.tsx +0 -98
  271. package/src/theme/components/CrudPageHeader.tsx +0 -48
  272. package/src/theme/components/DangerZone.tsx +0 -77
  273. package/src/theme/components/EmptyState.tsx +0 -56
  274. package/src/theme/components/ListItemRow.tsx +0 -24
  275. package/src/theme/components/PageForm.tsx +0 -114
  276. package/src/theme/components/Pagination.tsx +0 -196
  277. package/src/theme/components/PostForm.tsx +0 -122
  278. package/src/theme/components/PostList.tsx +0 -68
  279. package/src/theme/components/ThreadView.tsx +0 -118
  280. package/src/theme/components/TypeBadge.tsx +0 -28
  281. package/src/theme/components/VisibilityBadge.tsx +0 -33
  282. package/src/theme/layouts/BaseLayout.tsx +0 -49
  283. package/src/theme/layouts/DashLayout.tsx +0 -108
  284. package/src/theme/styles/main.css +0 -52
  285. package/src/types.ts +0 -222
  286. package/tsconfig.json +0 -16
  287. package/vite.config.ts +0 -82
  288. package/wrangler.toml +0 -21
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Time Utilities
3
+ */
4
+ /**
5
+ * Gets the current Unix timestamp in seconds.
6
+ *
7
+ * Returns the number of seconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
8
+ * This is the standard time format used throughout the application for consistency
9
+ * and database storage.
10
+ *
11
+ * @returns Current Unix timestamp in seconds (not milliseconds)
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const timestamp = now();
16
+ * // Returns: 1706745600 (example value for Feb 1, 2024)
17
+ * ```
18
+ */
19
+ export declare function now(): number;
20
+ /**
21
+ * Checks if a Unix timestamp is within the last 30 days.
22
+ *
23
+ * Compares the given timestamp to the current time to determine if it falls within
24
+ * the last month (defined as 30 days). Useful for highlighting recent posts or
25
+ * filtering time-sensitive content.
26
+ *
27
+ * @param timestamp - Unix timestamp in seconds to check
28
+ * @returns `true` if the timestamp is within the last 30 days, `false` otherwise
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const recentPost = 1706745600; // Recent timestamp
33
+ * if (isWithinMonth(recentPost)) {
34
+ * // Show "new" badge
35
+ * }
36
+ * ```
37
+ */
38
+ export declare function isWithinMonth(timestamp: number): boolean;
39
+ /**
40
+ * Converts a Unix timestamp to an ISO 8601 date-time string.
41
+ *
42
+ * Formats a Unix timestamp (in seconds) as an ISO 8601 string suitable for HTML
43
+ * `datetime` attributes and API responses. The output includes full date, time,
44
+ * and timezone information in UTC.
45
+ *
46
+ * @param timestamp - Unix timestamp in seconds to convert
47
+ * @returns ISO 8601 formatted string (e.g., "2024-02-01T12:00:00.000Z")
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * const isoDate = toISOString(1706745600);
52
+ * // Returns: "2024-02-01T00:00:00.000Z"
53
+ * ```
54
+ */
55
+ export declare function toISOString(timestamp: number): string;
56
+ /**
57
+ * Formats a Unix timestamp as a human-readable date string.
58
+ *
59
+ * Converts a Unix timestamp (in seconds) to a localized date string in the format
60
+ * "MMM DD, YYYY" (e.g., "Jan 15, 2024"). Always uses UTC timezone to ensure
61
+ * consistent display regardless of server or client location.
62
+ *
63
+ * @param timestamp - Unix timestamp in seconds to format
64
+ * @returns Formatted date string in "MMM DD, YYYY" format
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const readable = formatDate(1706745600);
69
+ * // Returns: "Feb 1, 2024"
70
+ * ```
71
+ */
72
+ export declare function formatDate(timestamp: number): string;
73
+ /**
74
+ * Formats a Unix timestamp as a year-month string for grouping.
75
+ *
76
+ * Converts a Unix timestamp (in seconds) to a "YYYY-MM" format string, useful for
77
+ * grouping posts by month in archives or creating month-based URLs. Always uses
78
+ * UTC timezone for consistency.
79
+ *
80
+ * @param timestamp - Unix timestamp in seconds to format
81
+ * @returns Year-month string in "YYYY-MM" format
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const yearMonth = formatYearMonth(1706745600);
86
+ * // Returns: "2024-02"
87
+ * ```
88
+ */
89
+ export declare function formatYearMonth(timestamp: number): string;
90
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/lib/time.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKzD"}
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Time Utilities
3
- */
4
-
5
- /**
3
+ */ /**
6
4
  * Gets the current Unix timestamp in seconds.
7
5
  *
8
6
  * Returns the number of seconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
@@ -16,16 +14,12 @@
16
14
  * const timestamp = now();
17
15
  * // Returns: 1706745600 (example value for Feb 1, 2024)
18
16
  * ```
19
- */
20
- export function now(): number {
21
- return Math.floor(Date.now() / 1000);
17
+ */ export function now() {
18
+ return Math.floor(Date.now() / 1000);
22
19
  }
23
-
24
20
  /**
25
21
  * One month in seconds
26
- */
27
- const ONE_MONTH = 30 * 24 * 60 * 60;
28
-
22
+ */ const ONE_MONTH = 30 * 24 * 60 * 60;
29
23
  /**
30
24
  * Checks if a Unix timestamp is within the last 30 days.
31
25
  *
@@ -43,11 +37,9 @@ const ONE_MONTH = 30 * 24 * 60 * 60;
43
37
  * // Show "new" badge
44
38
  * }
45
39
  * ```
46
- */
47
- export function isWithinMonth(timestamp: number): boolean {
48
- return now() - timestamp < ONE_MONTH;
40
+ */ export function isWithinMonth(timestamp) {
41
+ return now() - timestamp < ONE_MONTH;
49
42
  }
50
-
51
43
  /**
52
44
  * Converts a Unix timestamp to an ISO 8601 date-time string.
53
45
  *
@@ -63,11 +55,9 @@ export function isWithinMonth(timestamp: number): boolean {
63
55
  * const isoDate = toISOString(1706745600);
64
56
  * // Returns: "2024-02-01T00:00:00.000Z"
65
57
  * ```
66
- */
67
- export function toISOString(timestamp: number): string {
68
- return new Date(timestamp * 1000).toISOString();
58
+ */ export function toISOString(timestamp) {
59
+ return new Date(timestamp * 1000).toISOString();
69
60
  }
70
-
71
61
  /**
72
62
  * Formats a Unix timestamp as a human-readable date string.
73
63
  *
@@ -83,16 +73,14 @@ export function toISOString(timestamp: number): string {
83
73
  * const readable = formatDate(1706745600);
84
74
  * // Returns: "Feb 1, 2024"
85
75
  * ```
86
- */
87
- export function formatDate(timestamp: number): string {
88
- return new Date(timestamp * 1000).toLocaleDateString("en-US", {
89
- year: "numeric",
90
- month: "short",
91
- day: "numeric",
92
- timeZone: "UTC",
93
- });
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
+ });
94
83
  }
95
-
96
84
  /**
97
85
  * Formats a Unix timestamp as a year-month string for grouping.
98
86
  *
@@ -108,10 +96,9 @@ export function formatDate(timestamp: number): string {
108
96
  * const yearMonth = formatYearMonth(1706745600);
109
97
  * // Returns: "2024-02"
110
98
  * ```
111
- */
112
- export function formatYearMonth(timestamp: number): string {
113
- const date = new Date(timestamp * 1000);
114
- const year = date.getUTCFullYear();
115
- const month = String(date.getUTCMonth() + 1).padStart(2, "0");
116
- return `${year}-${month}`;
99
+ */ export function formatYearMonth(timestamp) {
100
+ const date = new Date(timestamp * 1000);
101
+ const year = date.getUTCFullYear();
102
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
103
+ return `${year}-${month}`;
117
104
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * URL Utilities
3
3
  */
4
-
5
4
  /**
6
5
  * Extracts the hostname (domain) from a URL string.
7
6
  *
@@ -20,15 +19,7 @@
20
19
  * // Returns: null
21
20
  * ```
22
21
  */
23
- export function extractDomain(url: string): string | null {
24
- try {
25
- const parsed = new URL(url);
26
- return parsed.hostname;
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
22
+ export declare function extractDomain(url: string): string | null;
32
23
  /**
33
24
  * Normalizes a path by removing slashes and converting to lowercase.
34
25
  *
@@ -45,14 +36,7 @@ export function extractDomain(url: string): string | null {
45
36
  * // Returns: "about/contact"
46
37
  * ```
47
38
  */
48
- export function normalizePath(path: string): string {
49
- return path
50
- .trim()
51
- .toLowerCase()
52
- .replace(/^\/+|\/+$/g, "")
53
- .replace(/\/+/g, "/");
54
- }
55
-
39
+ export declare function normalizePath(path: string): string;
56
40
  /**
57
41
  * Checks if a string is a full URL with HTTP or HTTPS protocol.
58
42
  *
@@ -70,10 +54,7 @@ export function normalizePath(path: string): string {
70
54
  * isFullUrl("example.com"); // Returns: false
71
55
  * ```
72
56
  */
73
- export function isFullUrl(str: string): boolean {
74
- return str.startsWith("http://") || str.startsWith("https://");
75
- }
76
-
57
+ export declare function isFullUrl(str: string): boolean;
77
58
  /**
78
59
  * Converts text to a URL-friendly slug.
79
60
  *
@@ -97,11 +78,5 @@ export function isFullUrl(str: string): boolean {
97
78
  * // Returns: "multiple-spaces"
98
79
  * ```
99
80
  */
100
- export function slugify(text: string): string {
101
- return text
102
- .toLowerCase()
103
- .trim()
104
- .replace(/[^\w\s-]/g, "")
105
- .replace(/[\s_-]+/g, "-")
106
- .replace(/^-+|-+$/g, "");
107
- }
81
+ export declare function slugify(text: string): string;
82
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/lib/url.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOxD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO5C"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * URL Utilities
3
+ */ /**
4
+ * Extracts the hostname (domain) from a URL string.
5
+ *
6
+ * Parses a full URL and returns just the hostname portion (e.g., "example.com" from
7
+ * "https://example.com/path"). Returns `null` if the URL is malformed or cannot be parsed.
8
+ *
9
+ * @param url - The full URL string to extract the domain from
10
+ * @returns The hostname/domain if valid, or `null` if parsing fails
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const domain = extractDomain("https://www.example.com/path");
15
+ * // Returns: "www.example.com"
16
+ *
17
+ * const invalid = extractDomain("not-a-url");
18
+ * // Returns: null
19
+ * ```
20
+ */ export function extractDomain(url) {
21
+ try {
22
+ const parsed = new URL(url);
23
+ return parsed.hostname;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Normalizes a path by removing slashes and converting to lowercase.
30
+ *
31
+ * Trims whitespace, converts to lowercase, removes leading and trailing slashes,
32
+ * and collapses multiple consecutive slashes into single slashes. Used to create
33
+ * consistent path representations for routing and storage.
34
+ *
35
+ * @param path - The path string to normalize
36
+ * @returns The normalized path string
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const normalized = normalizePath(" /About/Contact// ");
41
+ * // Returns: "about/contact"
42
+ * ```
43
+ */ export function normalizePath(path) {
44
+ return path.trim().toLowerCase().replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
45
+ }
46
+ /**
47
+ * Checks if a string is a full URL with HTTP or HTTPS protocol.
48
+ *
49
+ * Validates whether a string starts with "http://" or "https://", indicating it's
50
+ * a full URL rather than a relative path. Useful for distinguishing between internal
51
+ * paths and external URLs.
52
+ *
53
+ * @param str - The string to check
54
+ * @returns `true` if the string starts with http:// or https://, `false` otherwise
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * isFullUrl("https://example.com"); // Returns: true
59
+ * isFullUrl("/about"); // Returns: false
60
+ * isFullUrl("example.com"); // Returns: false
61
+ * ```
62
+ */ export function isFullUrl(str) {
63
+ return str.startsWith("http://") || str.startsWith("https://");
64
+ }
65
+ /**
66
+ * Converts text to a URL-friendly slug.
67
+ *
68
+ * Transforms text into a lowercase, hyphen-separated slug by:
69
+ * - Converting to lowercase
70
+ * - Removing special characters (keeping only word characters, spaces, and hyphens)
71
+ * - Replacing whitespace and underscores with hyphens
72
+ * - Removing leading and trailing hyphens
73
+ *
74
+ * Used for generating clean URLs from titles and names.
75
+ *
76
+ * @param text - The text to convert to a slug
77
+ * @returns The slugified string
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const slug = slugify("Hello World! This is a Test.");
82
+ * // Returns: "hello-world-this-is-a-test"
83
+ *
84
+ * const slug = slugify(" Multiple Spaces ");
85
+ * // Returns: "multiple-spaces"
86
+ * ```
87
+ */ export function slugify(text) {
88
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
89
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Authentication Middleware
3
+ *
4
+ * Protects routes by requiring authentication
5
+ */
6
+ import type { MiddlewareHandler } from "hono";
7
+ import type { Bindings } from "../types.js";
8
+ import type { AppVariables } from "../app.js";
9
+ type Env = {
10
+ Bindings: Bindings;
11
+ Variables: AppVariables;
12
+ };
13
+ /**
14
+ * Middleware that requires authentication.
15
+ * Redirects to signin page if not authenticated.
16
+ */
17
+ export declare function requireAuth(redirectTo?: string): MiddlewareHandler<Env>;
18
+ /**
19
+ * Middleware for API routes that requires authentication.
20
+ * Returns 401 if not authenticated.
21
+ */
22
+ export declare function requireAuthApi(): MiddlewareHandler<Env>;
23
+ export {};
24
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,UAAU,SAAY,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAkB1E;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAkBvD"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Authentication Middleware
3
+ *
4
+ * Protects routes by requiring authentication
5
+ */ /**
6
+ * Middleware that requires authentication.
7
+ * Redirects to signin page if not authenticated.
8
+ */ export function requireAuth(redirectTo = "/signin") {
9
+ return async (c, next)=>{
10
+ if (!c.var.auth) {
11
+ return c.redirect(redirectTo);
12
+ }
13
+ try {
14
+ const session = await c.var.auth.api.getSession({
15
+ headers: c.req.raw.headers
16
+ });
17
+ if (!session?.user) {
18
+ return c.redirect(redirectTo);
19
+ }
20
+ await next();
21
+ } catch {
22
+ return c.redirect(redirectTo);
23
+ }
24
+ };
25
+ }
26
+ /**
27
+ * Middleware for API routes that requires authentication.
28
+ * Returns 401 if not authenticated.
29
+ */ export function requireAuthApi() {
30
+ return async (c, next)=>{
31
+ if (!c.var.auth) {
32
+ return c.json({
33
+ error: "Authentication not configured"
34
+ }, 500);
35
+ }
36
+ try {
37
+ const session = await c.var.auth.api.getSession({
38
+ headers: c.req.raw.headers
39
+ });
40
+ if (!session?.user) {
41
+ return c.json({
42
+ error: "Unauthorized"
43
+ }, 401);
44
+ }
45
+ await next();
46
+ } catch {
47
+ return c.json({
48
+ error: "Unauthorized"
49
+ }, 401);
50
+ }
51
+ };
52
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Posts API Routes
3
+ */
4
+ import { Hono } from "hono";
5
+ import type { Bindings } from "../../types.js";
6
+ import type { AppVariables } from "../../app.js";
7
+ type Env = {
8
+ Bindings: Bindings;
9
+ Variables: AppVariables;
10
+ };
11
+ export declare const postsApiRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
12
+ export {};
13
+ //# sourceMappingURL=posts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../../src/routes/api/posts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAwB,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,cAAc,kDAAkB,CAAC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Posts API Routes
3
+ */ import { Hono } from "hono";
4
+ import * as sqid from "../../lib/sqid.js";
5
+ import { CreatePostSchema, UpdatePostSchema } from "../../lib/schemas.js";
6
+ import { requireAuthApi } from "../../middleware/auth.js";
7
+ export const postsApiRoutes = new Hono();
8
+ // List posts
9
+ postsApiRoutes.get("/", async (c)=>{
10
+ const type = c.req.query("type");
11
+ const visibility = c.req.query("visibility");
12
+ const cursor = c.req.query("cursor");
13
+ const limit = parseInt(c.req.query("limit") ?? "100", 10);
14
+ const posts = await c.var.services.posts.list({
15
+ type,
16
+ visibility: visibility ? [
17
+ visibility
18
+ ] : [
19
+ "featured",
20
+ "quiet"
21
+ ],
22
+ cursor: cursor ? sqid.decode(cursor) ?? undefined : undefined,
23
+ limit
24
+ });
25
+ return c.json({
26
+ posts: posts.map((p)=>({
27
+ ...p,
28
+ sqid: sqid.encode(p.id)
29
+ })),
30
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Array length check guarantees element exists
31
+ nextCursor: posts.length === limit ? sqid.encode(posts[posts.length - 1].id) : null
32
+ });
33
+ });
34
+ // Get single post
35
+ postsApiRoutes.get("/:id", async (c)=>{
36
+ const id = sqid.decode(c.req.param("id"));
37
+ if (!id) return c.json({
38
+ error: "Invalid ID"
39
+ }, 400);
40
+ const post = await c.var.services.posts.getById(id);
41
+ if (!post) return c.json({
42
+ error: "Not found"
43
+ }, 404);
44
+ return c.json({
45
+ ...post,
46
+ sqid: sqid.encode(post.id)
47
+ });
48
+ });
49
+ // Create post (requires auth)
50
+ postsApiRoutes.post("/", requireAuthApi(), async (c)=>{
51
+ const rawBody = await c.req.json();
52
+ // Validate request body
53
+ const parseResult = CreatePostSchema.safeParse(rawBody);
54
+ if (!parseResult.success) {
55
+ return c.json({
56
+ error: "Validation failed",
57
+ details: parseResult.error.flatten()
58
+ }, 400);
59
+ }
60
+ const body = parseResult.data;
61
+ const post = await c.var.services.posts.create({
62
+ type: body.type,
63
+ title: body.title,
64
+ content: body.content,
65
+ visibility: body.visibility,
66
+ sourceUrl: body.sourceUrl || undefined,
67
+ sourceName: body.sourceName,
68
+ path: body.path || undefined,
69
+ replyToId: body.replyToId ? sqid.decode(body.replyToId) ?? undefined : undefined,
70
+ publishedAt: body.publishedAt
71
+ });
72
+ return c.json({
73
+ ...post,
74
+ sqid: sqid.encode(post.id)
75
+ }, 201);
76
+ });
77
+ // Update post (requires auth)
78
+ postsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
79
+ const id = sqid.decode(c.req.param("id"));
80
+ if (!id) return c.json({
81
+ error: "Invalid ID"
82
+ }, 400);
83
+ const rawBody = await c.req.json();
84
+ // Validate request body
85
+ const parseResult = UpdatePostSchema.safeParse(rawBody);
86
+ if (!parseResult.success) {
87
+ return c.json({
88
+ error: "Validation failed",
89
+ details: parseResult.error.flatten()
90
+ }, 400);
91
+ }
92
+ const body = parseResult.data;
93
+ const post = await c.var.services.posts.update(id, {
94
+ type: body.type,
95
+ title: body.title,
96
+ content: body.content,
97
+ visibility: body.visibility,
98
+ sourceUrl: body.sourceUrl,
99
+ sourceName: body.sourceName,
100
+ path: body.path,
101
+ publishedAt: body.publishedAt
102
+ });
103
+ if (!post) return c.json({
104
+ error: "Not found"
105
+ }, 404);
106
+ return c.json({
107
+ ...post,
108
+ sqid: sqid.encode(post.id)
109
+ });
110
+ });
111
+ // Delete post (requires auth)
112
+ postsApiRoutes.delete("/:id", requireAuthApi(), async (c)=>{
113
+ const id = sqid.decode(c.req.param("id"));
114
+ if (!id) return c.json({
115
+ error: "Invalid ID"
116
+ }, 400);
117
+ const success = await c.var.services.posts.delete(id);
118
+ if (!success) return c.json({
119
+ error: "Not found"
120
+ }, 404);
121
+ return c.json({
122
+ success: true
123
+ });
124
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Search API Routes
3
+ */
4
+ import { Hono } from "hono";
5
+ import type { Bindings } from "../../types.js";
6
+ import type { AppVariables } from "../../app.js";
7
+ type Env = {
8
+ Bindings: Bindings;
9
+ Variables: AppVariables;
10
+ };
11
+ export declare const searchApiRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
12
+ export {};
13
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/routes/api/search.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,eAAe,kDAAkB,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Search API Routes
3
+ */ import { Hono } from "hono";
4
+ import * as sqid from "../../lib/sqid.js";
5
+ export const searchApiRoutes = new Hono();
6
+ // Search posts
7
+ searchApiRoutes.get("/", async (c)=>{
8
+ const query = c.req.query("q");
9
+ if (!query || query.trim().length === 0) {
10
+ return c.json({
11
+ error: "Query parameter 'q' is required"
12
+ }, 400);
13
+ }
14
+ if (query.length > 200) {
15
+ return c.json({
16
+ error: "Query too long"
17
+ }, 400);
18
+ }
19
+ const limitParam = c.req.query("limit");
20
+ const limit = limitParam ? Math.min(parseInt(limitParam, 10) || 20, 50) : 20;
21
+ try {
22
+ const results = await c.var.services.search.search(query, {
23
+ limit,
24
+ visibility: [
25
+ "featured",
26
+ "quiet"
27
+ ]
28
+ });
29
+ return c.json({
30
+ query,
31
+ results: results.map((r)=>({
32
+ id: sqid.encode(r.post.id),
33
+ type: r.post.type,
34
+ title: r.post.title,
35
+ path: r.post.path,
36
+ snippet: r.snippet,
37
+ publishedAt: r.post.publishedAt,
38
+ url: `/p/${sqid.encode(r.post.id)}`
39
+ })),
40
+ count: results.length
41
+ });
42
+ } catch (err) {
43
+ // eslint-disable-next-line no-console -- Error logging is intentional
44
+ console.error("Search error:", err);
45
+ return c.json({
46
+ error: "Search failed"
47
+ }, 500);
48
+ }
49
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Upload API Routes
3
+ *
4
+ * Handles file uploads to R2 storage.
5
+ * Supports both JSON and SSE (Datastar) responses.
6
+ */
7
+ import { Hono } from "hono";
8
+ import type { Bindings } from "../../types.js";
9
+ import type { AppVariables } from "../../app.js";
10
+ type Env = {
11
+ Bindings: Bindings;
12
+ Variables: AppVariables;
13
+ };
14
+ export declare const uploadApiRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
15
+ export {};
16
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/routes/api/upload.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,eAAe,kDAAkB,CAAC"}