@jant/core 0.3.7 → 0.3.9

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 (258) hide show
  1. package/dist/app.js +11 -4
  2. package/dist/client.js +1 -0
  3. package/dist/db/schema.js +15 -1
  4. package/dist/i18n/locales/en.js +1 -1
  5. package/dist/i18n/locales/zh-Hans.js +1 -1
  6. package/dist/i18n/locales/zh-Hant.js +1 -1
  7. package/dist/lib/image.js +39 -15
  8. package/dist/lib/media-helpers.js +49 -0
  9. package/dist/lib/nav-reorder.js +27 -0
  10. package/dist/lib/navigation.js +35 -0
  11. package/dist/lib/storage.js +164 -0
  12. package/dist/lib/theme-components.js +49 -0
  13. package/dist/routes/api/posts.js +12 -7
  14. package/dist/routes/api/timeline.js +116 -0
  15. package/dist/routes/api/upload.js +35 -24
  16. package/dist/routes/dash/media.js +24 -14
  17. package/dist/routes/dash/navigation.js +274 -0
  18. package/dist/routes/dash/posts.js +4 -1
  19. package/dist/routes/feed/rss.js +3 -2
  20. package/dist/routes/pages/archive.js +14 -27
  21. package/dist/routes/pages/collection.js +10 -19
  22. package/dist/routes/pages/home.js +84 -126
  23. package/dist/routes/pages/page.js +19 -38
  24. package/dist/routes/pages/post.js +47 -56
  25. package/dist/routes/pages/search.js +13 -26
  26. package/dist/services/index.js +3 -1
  27. package/dist/services/media.js +8 -6
  28. package/dist/services/navigation.js +115 -0
  29. package/dist/services/post.js +26 -1
  30. package/dist/theme/components/PostForm.js +4 -3
  31. package/dist/theme/components/PostList.js +5 -0
  32. package/dist/theme/components/index.js +2 -0
  33. package/dist/theme/components/timeline/ArticleCard.js +50 -0
  34. package/dist/theme/components/timeline/ImageCard.js +86 -0
  35. package/dist/theme/components/timeline/LinkCard.js +62 -0
  36. package/dist/theme/components/timeline/NoteCard.js +37 -0
  37. package/dist/theme/components/timeline/QuoteCard.js +51 -0
  38. package/dist/theme/components/timeline/ThreadPreview.js +52 -0
  39. package/dist/theme/components/timeline/TimelineFeed.js +43 -0
  40. package/dist/theme/components/timeline/TimelineItem.js +25 -0
  41. package/dist/theme/components/timeline/index.js +8 -0
  42. package/dist/theme/layouts/DashLayout.js +8 -0
  43. package/dist/theme/layouts/SiteLayout.js +160 -0
  44. package/dist/theme/layouts/index.js +1 -0
  45. package/dist/types/sortablejs.d.js +5 -0
  46. package/dist/types.js +32 -0
  47. package/package.json +4 -2
  48. package/src/__tests__/helpers/app.ts +1 -0
  49. package/src/__tests__/helpers/db.ts +20 -0
  50. package/src/app.tsx +12 -7
  51. package/src/client.ts +1 -0
  52. package/src/db/migrations/0003_add_navigation_links.sql +8 -0
  53. package/src/db/migrations/0004_add_storage_provider.sql +3 -0
  54. package/src/db/migrations/meta/0003_snapshot.json +821 -0
  55. package/src/db/migrations/meta/_journal.json +21 -0
  56. package/src/db/schema.ts +15 -1
  57. package/src/i18n/locales/en.po +148 -80
  58. package/src/i18n/locales/en.ts +1 -1
  59. package/src/i18n/locales/zh-Hans.po +150 -103
  60. package/src/i18n/locales/zh-Hans.ts +1 -1
  61. package/src/i18n/locales/zh-Hant.po +150 -103
  62. package/src/i18n/locales/zh-Hant.ts +1 -1
  63. package/src/index.ts +5 -0
  64. package/src/lib/__tests__/image.test.ts +96 -0
  65. package/src/lib/__tests__/storage.test.ts +162 -0
  66. package/src/lib/__tests__/theme-components.test.ts +107 -0
  67. package/src/lib/image.ts +46 -16
  68. package/src/lib/media-helpers.ts +65 -0
  69. package/src/lib/nav-reorder.ts +26 -0
  70. package/src/lib/navigation.ts +46 -0
  71. package/src/lib/storage.ts +236 -0
  72. package/src/lib/theme-components.ts +76 -0
  73. package/src/routes/api/__tests__/posts.test.ts +8 -8
  74. package/src/routes/api/__tests__/timeline.test.ts +242 -0
  75. package/src/routes/api/posts.ts +20 -6
  76. package/src/routes/api/timeline.tsx +152 -0
  77. package/src/routes/api/upload.ts +52 -25
  78. package/src/routes/dash/media.tsx +40 -8
  79. package/src/routes/dash/navigation.tsx +306 -0
  80. package/src/routes/dash/posts.tsx +5 -0
  81. package/src/routes/feed/rss.ts +3 -2
  82. package/src/routes/pages/archive.tsx +15 -23
  83. package/src/routes/pages/collection.tsx +8 -15
  84. package/src/routes/pages/home.tsx +118 -122
  85. package/src/routes/pages/page.tsx +17 -30
  86. package/src/routes/pages/post.tsx +63 -60
  87. package/src/routes/pages/search.tsx +18 -22
  88. package/src/services/__tests__/media.test.ts +73 -28
  89. package/src/services/__tests__/navigation.test.ts +213 -0
  90. package/src/services/__tests__/post-timeline.test.ts +220 -0
  91. package/src/services/index.ts +7 -0
  92. package/src/services/media.ts +12 -8
  93. package/src/services/navigation.ts +165 -0
  94. package/src/services/post.ts +48 -1
  95. package/src/styles/components.css +59 -0
  96. package/src/theme/components/PostForm.tsx +13 -2
  97. package/src/theme/components/PostList.tsx +7 -0
  98. package/src/theme/components/index.ts +12 -0
  99. package/src/theme/components/timeline/ArticleCard.tsx +57 -0
  100. package/src/theme/components/timeline/ImageCard.tsx +80 -0
  101. package/src/theme/components/timeline/LinkCard.tsx +66 -0
  102. package/src/theme/components/timeline/NoteCard.tsx +41 -0
  103. package/src/theme/components/timeline/QuoteCard.tsx +55 -0
  104. package/src/theme/components/timeline/ThreadPreview.tsx +49 -0
  105. package/src/theme/components/timeline/TimelineFeed.tsx +52 -0
  106. package/src/theme/components/timeline/TimelineItem.tsx +39 -0
  107. package/src/theme/components/timeline/index.ts +8 -0
  108. package/src/theme/layouts/DashLayout.tsx +10 -0
  109. package/src/theme/layouts/SiteLayout.tsx +184 -0
  110. package/src/theme/layouts/index.ts +1 -0
  111. package/src/types/sortablejs.d.ts +23 -0
  112. package/src/types.ts +102 -1
  113. package/dist/app.d.ts +0 -38
  114. package/dist/app.d.ts.map +0 -1
  115. package/dist/auth.d.ts +0 -25
  116. package/dist/auth.d.ts.map +0 -1
  117. package/dist/db/index.d.ts +0 -10
  118. package/dist/db/index.d.ts.map +0 -1
  119. package/dist/db/schema.d.ts +0 -1543
  120. package/dist/db/schema.d.ts.map +0 -1
  121. package/dist/i18n/Trans.d.ts +0 -25
  122. package/dist/i18n/Trans.d.ts.map +0 -1
  123. package/dist/i18n/context.d.ts +0 -69
  124. package/dist/i18n/context.d.ts.map +0 -1
  125. package/dist/i18n/detect.d.ts +0 -20
  126. package/dist/i18n/detect.d.ts.map +0 -1
  127. package/dist/i18n/i18n.d.ts +0 -32
  128. package/dist/i18n/i18n.d.ts.map +0 -1
  129. package/dist/i18n/index.d.ts +0 -41
  130. package/dist/i18n/index.d.ts.map +0 -1
  131. package/dist/i18n/locales/en.d.ts +0 -3
  132. package/dist/i18n/locales/en.d.ts.map +0 -1
  133. package/dist/i18n/locales/zh-Hans.d.ts +0 -3
  134. package/dist/i18n/locales/zh-Hans.d.ts.map +0 -1
  135. package/dist/i18n/locales/zh-Hant.d.ts +0 -3
  136. package/dist/i18n/locales/zh-Hant.d.ts.map +0 -1
  137. package/dist/i18n/locales.d.ts +0 -11
  138. package/dist/i18n/locales.d.ts.map +0 -1
  139. package/dist/i18n/middleware.d.ts +0 -21
  140. package/dist/i18n/middleware.d.ts.map +0 -1
  141. package/dist/index.d.ts +0 -16
  142. package/dist/index.d.ts.map +0 -1
  143. package/dist/lib/config.d.ts +0 -83
  144. package/dist/lib/config.d.ts.map +0 -1
  145. package/dist/lib/constants.d.ts +0 -37
  146. package/dist/lib/constants.d.ts.map +0 -1
  147. package/dist/lib/image.d.ts +0 -73
  148. package/dist/lib/image.d.ts.map +0 -1
  149. package/dist/lib/index.d.ts +0 -9
  150. package/dist/lib/index.d.ts.map +0 -1
  151. package/dist/lib/markdown.d.ts +0 -60
  152. package/dist/lib/markdown.d.ts.map +0 -1
  153. package/dist/lib/schemas.d.ts +0 -130
  154. package/dist/lib/schemas.d.ts.map +0 -1
  155. package/dist/lib/sqid.d.ts +0 -60
  156. package/dist/lib/sqid.d.ts.map +0 -1
  157. package/dist/lib/sse.d.ts +0 -192
  158. package/dist/lib/sse.d.ts.map +0 -1
  159. package/dist/lib/theme.d.ts +0 -44
  160. package/dist/lib/theme.d.ts.map +0 -1
  161. package/dist/lib/time.d.ts +0 -90
  162. package/dist/lib/time.d.ts.map +0 -1
  163. package/dist/lib/url.d.ts +0 -82
  164. package/dist/lib/url.d.ts.map +0 -1
  165. package/dist/middleware/auth.d.ts +0 -24
  166. package/dist/middleware/auth.d.ts.map +0 -1
  167. package/dist/middleware/onboarding.d.ts +0 -26
  168. package/dist/middleware/onboarding.d.ts.map +0 -1
  169. package/dist/routes/api/posts.d.ts +0 -13
  170. package/dist/routes/api/posts.d.ts.map +0 -1
  171. package/dist/routes/api/search.d.ts +0 -13
  172. package/dist/routes/api/search.d.ts.map +0 -1
  173. package/dist/routes/api/upload.d.ts +0 -16
  174. package/dist/routes/api/upload.d.ts.map +0 -1
  175. package/dist/routes/dash/collections.d.ts +0 -13
  176. package/dist/routes/dash/collections.d.ts.map +0 -1
  177. package/dist/routes/dash/index.d.ts +0 -15
  178. package/dist/routes/dash/index.d.ts.map +0 -1
  179. package/dist/routes/dash/media.d.ts +0 -16
  180. package/dist/routes/dash/media.d.ts.map +0 -1
  181. package/dist/routes/dash/pages.d.ts +0 -15
  182. package/dist/routes/dash/pages.d.ts.map +0 -1
  183. package/dist/routes/dash/posts.d.ts +0 -13
  184. package/dist/routes/dash/posts.d.ts.map +0 -1
  185. package/dist/routes/dash/redirects.d.ts +0 -13
  186. package/dist/routes/dash/redirects.d.ts.map +0 -1
  187. package/dist/routes/dash/settings.d.ts +0 -15
  188. package/dist/routes/dash/settings.d.ts.map +0 -1
  189. package/dist/routes/feed/rss.d.ts +0 -13
  190. package/dist/routes/feed/rss.d.ts.map +0 -1
  191. package/dist/routes/feed/sitemap.d.ts +0 -13
  192. package/dist/routes/feed/sitemap.d.ts.map +0 -1
  193. package/dist/routes/pages/archive.d.ts +0 -15
  194. package/dist/routes/pages/archive.d.ts.map +0 -1
  195. package/dist/routes/pages/collection.d.ts +0 -13
  196. package/dist/routes/pages/collection.d.ts.map +0 -1
  197. package/dist/routes/pages/home.d.ts +0 -13
  198. package/dist/routes/pages/home.d.ts.map +0 -1
  199. package/dist/routes/pages/page.d.ts +0 -15
  200. package/dist/routes/pages/page.d.ts.map +0 -1
  201. package/dist/routes/pages/post.d.ts +0 -13
  202. package/dist/routes/pages/post.d.ts.map +0 -1
  203. package/dist/routes/pages/search.d.ts +0 -13
  204. package/dist/routes/pages/search.d.ts.map +0 -1
  205. package/dist/services/collection.d.ts +0 -32
  206. package/dist/services/collection.d.ts.map +0 -1
  207. package/dist/services/index.d.ts +0 -28
  208. package/dist/services/index.d.ts.map +0 -1
  209. package/dist/services/media.d.ts +0 -34
  210. package/dist/services/media.d.ts.map +0 -1
  211. package/dist/services/post.d.ts +0 -31
  212. package/dist/services/post.d.ts.map +0 -1
  213. package/dist/services/redirect.d.ts +0 -15
  214. package/dist/services/redirect.d.ts.map +0 -1
  215. package/dist/services/search.d.ts +0 -26
  216. package/dist/services/search.d.ts.map +0 -1
  217. package/dist/services/settings.d.ts +0 -18
  218. package/dist/services/settings.d.ts.map +0 -1
  219. package/dist/theme/color-themes.d.ts +0 -30
  220. package/dist/theme/color-themes.d.ts.map +0 -1
  221. package/dist/theme/components/ActionButtons.d.ts +0 -43
  222. package/dist/theme/components/ActionButtons.d.ts.map +0 -1
  223. package/dist/theme/components/CrudPageHeader.d.ts +0 -23
  224. package/dist/theme/components/CrudPageHeader.d.ts.map +0 -1
  225. package/dist/theme/components/DangerZone.d.ts +0 -36
  226. package/dist/theme/components/DangerZone.d.ts.map +0 -1
  227. package/dist/theme/components/EmptyState.d.ts +0 -27
  228. package/dist/theme/components/EmptyState.d.ts.map +0 -1
  229. package/dist/theme/components/ListItemRow.d.ts +0 -15
  230. package/dist/theme/components/ListItemRow.d.ts.map +0 -1
  231. package/dist/theme/components/MediaGallery.d.ts +0 -13
  232. package/dist/theme/components/MediaGallery.d.ts.map +0 -1
  233. package/dist/theme/components/PageForm.d.ts +0 -14
  234. package/dist/theme/components/PageForm.d.ts.map +0 -1
  235. package/dist/theme/components/Pagination.d.ts +0 -46
  236. package/dist/theme/components/Pagination.d.ts.map +0 -1
  237. package/dist/theme/components/PostForm.d.ts +0 -16
  238. package/dist/theme/components/PostForm.d.ts.map +0 -1
  239. package/dist/theme/components/PostList.d.ts +0 -10
  240. package/dist/theme/components/PostList.d.ts.map +0 -1
  241. package/dist/theme/components/ThreadView.d.ts +0 -15
  242. package/dist/theme/components/ThreadView.d.ts.map +0 -1
  243. package/dist/theme/components/TypeBadge.d.ts +0 -12
  244. package/dist/theme/components/TypeBadge.d.ts.map +0 -1
  245. package/dist/theme/components/VisibilityBadge.d.ts +0 -12
  246. package/dist/theme/components/VisibilityBadge.d.ts.map +0 -1
  247. package/dist/theme/components/index.d.ts +0 -14
  248. package/dist/theme/components/index.d.ts.map +0 -1
  249. package/dist/theme/index.d.ts +0 -21
  250. package/dist/theme/index.d.ts.map +0 -1
  251. package/dist/theme/layouts/BaseLayout.d.ts +0 -23
  252. package/dist/theme/layouts/BaseLayout.d.ts.map +0 -1
  253. package/dist/theme/layouts/DashLayout.d.ts +0 -17
  254. package/dist/theme/layouts/DashLayout.d.ts.map +0 -1
  255. package/dist/theme/layouts/index.d.ts +0 -3
  256. package/dist/theme/layouts/index.d.ts.map +0 -1
  257. package/dist/types.d.ts +0 -237
  258. package/dist/types.d.ts.map +0 -1
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Site Layout
3
+ *
4
+ * Two-column layout for public pages with sidebar navigation.
5
+ * On mobile, uses a slide-out drawer menu.
6
+ */ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
7
+ /**
8
+ * Determine if a navigation link is active based on the current path.
9
+ *
10
+ * @param linkUrl - The link's URL
11
+ * @param currentPath - The current page path
12
+ * @returns Whether the link should be shown as active
13
+ */ function isLinkActive(linkUrl, currentPath) {
14
+ // External links are never active
15
+ if (linkUrl.startsWith("http://") || linkUrl.startsWith("https://")) {
16
+ return false;
17
+ }
18
+ // Exact match for home
19
+ if (linkUrl === "/") {
20
+ return currentPath === "/";
21
+ }
22
+ // Prefix match for other internal links
23
+ return currentPath === linkUrl || currentPath.startsWith(linkUrl + "/");
24
+ }
25
+ /**
26
+ * Check if a URL is external
27
+ */ function isExternalUrl(url) {
28
+ return url.startsWith("http://") || url.startsWith("https://");
29
+ }
30
+ /**
31
+ * Render navigation links with dot indicator for active state.
32
+ */ function NavLinks({ navigationLinks, currentPath }) {
33
+ return /*#__PURE__*/ _jsx(_Fragment, {
34
+ children: navigationLinks.map((link)=>{
35
+ const active = isLinkActive(link.url, currentPath);
36
+ const external = isExternalUrl(link.url);
37
+ return /*#__PURE__*/ _jsxs("a", {
38
+ href: link.url,
39
+ class: `text-sm flex items-center gap-2 py-0.5 ${active ? "text-primary font-medium" : "text-muted-foreground hover:text-foreground"}`,
40
+ ...external ? {
41
+ target: "_blank",
42
+ rel: "noopener noreferrer"
43
+ } : {},
44
+ children: [
45
+ /*#__PURE__*/ _jsx("span", {
46
+ class: `size-1.5 rounded-full shrink-0 ${active ? "bg-primary" : "bg-transparent"}`
47
+ }),
48
+ link.label,
49
+ external && /*#__PURE__*/ _jsx("span", {
50
+ class: "ml-1 text-xs opacity-50",
51
+ children: "↗"
52
+ })
53
+ ]
54
+ }, link.id);
55
+ })
56
+ });
57
+ }
58
+ export const SiteLayout = ({ siteName, navigationLinks, currentPath, children })=>{
59
+ return /*#__PURE__*/ _jsxs("div", {
60
+ class: "container py-8 md:flex md:gap-12",
61
+ "data-signals": JSON.stringify({
62
+ _drawerOpen: false
63
+ }),
64
+ children: [
65
+ /*#__PURE__*/ _jsxs("div", {
66
+ class: "flex items-center justify-between mb-6 md:hidden",
67
+ children: [
68
+ /*#__PURE__*/ _jsx("a", {
69
+ href: "/",
70
+ class: "text-xl font-semibold",
71
+ children: siteName
72
+ }),
73
+ /*#__PURE__*/ _jsx("button", {
74
+ "data-on:click": "$_drawerOpen = true",
75
+ class: "p-2 -mr-2 text-muted-foreground hover:text-foreground",
76
+ "aria-label": "Open menu",
77
+ children: /*#__PURE__*/ _jsx("svg", {
78
+ class: "size-5",
79
+ fill: "none",
80
+ viewBox: "0 0 24 24",
81
+ "stroke-width": "1.5",
82
+ stroke: "currentColor",
83
+ children: /*#__PURE__*/ _jsx("path", {
84
+ "stroke-linecap": "round",
85
+ "stroke-linejoin": "round",
86
+ d: "M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
87
+ })
88
+ })
89
+ })
90
+ ]
91
+ }),
92
+ /*#__PURE__*/ _jsx("div", {
93
+ class: "fixed inset-0 bg-black/50 z-40 opacity-0 pointer-events-none transition-opacity duration-300 ease-in-out md:hidden",
94
+ "data-class": "{'opacity-100 pointer-events-auto': $_drawerOpen, 'opacity-0 pointer-events-none': !$_drawerOpen}",
95
+ "data-on:click": "$_drawerOpen = false"
96
+ }),
97
+ /*#__PURE__*/ _jsxs("aside", {
98
+ class: "fixed inset-y-0 left-0 w-64 bg-background z-50 p-6 overflow-y-auto shadow-lg -translate-x-full transition-transform duration-300 ease-in-out md:hidden",
99
+ "data-class": "{'translate-x-0': $_drawerOpen, '-translate-x-full': !$_drawerOpen}",
100
+ children: [
101
+ /*#__PURE__*/ _jsxs("div", {
102
+ class: "flex items-center justify-between mb-8",
103
+ children: [
104
+ /*#__PURE__*/ _jsx("a", {
105
+ href: "/",
106
+ class: "text-xl font-semibold",
107
+ children: siteName
108
+ }),
109
+ /*#__PURE__*/ _jsx("button", {
110
+ "data-on:click": "$_drawerOpen = false",
111
+ class: "p-2 -mr-2 text-muted-foreground hover:text-foreground",
112
+ "aria-label": "Close menu",
113
+ children: /*#__PURE__*/ _jsx("svg", {
114
+ class: "size-5",
115
+ fill: "none",
116
+ viewBox: "0 0 24 24",
117
+ "stroke-width": "1.5",
118
+ stroke: "currentColor",
119
+ children: /*#__PURE__*/ _jsx("path", {
120
+ "stroke-linecap": "round",
121
+ "stroke-linejoin": "round",
122
+ d: "M6 18L18 6M6 6l12 12"
123
+ })
124
+ })
125
+ })
126
+ ]
127
+ }),
128
+ /*#__PURE__*/ _jsx("nav", {
129
+ class: "flex flex-col gap-0.5",
130
+ children: /*#__PURE__*/ _jsx(NavLinks, {
131
+ navigationLinks: navigationLinks,
132
+ currentPath: currentPath
133
+ })
134
+ })
135
+ ]
136
+ }),
137
+ /*#__PURE__*/ _jsxs("aside", {
138
+ class: "hidden md:block md:w-48 md:shrink-0 md:sticky md:top-8 md:self-start",
139
+ children: [
140
+ /*#__PURE__*/ _jsx("a", {
141
+ href: "/",
142
+ class: "text-xl font-semibold block mb-20",
143
+ children: siteName
144
+ }),
145
+ /*#__PURE__*/ _jsx("nav", {
146
+ class: "flex flex-col gap-0.5",
147
+ children: /*#__PURE__*/ _jsx(NavLinks, {
148
+ navigationLinks: navigationLinks,
149
+ currentPath: currentPath
150
+ })
151
+ })
152
+ ]
153
+ }),
154
+ /*#__PURE__*/ _jsx("main", {
155
+ class: "flex-1 min-w-0",
156
+ children: children
157
+ })
158
+ ]
159
+ });
160
+ };
@@ -1,2 +1,3 @@
1
1
  export { BaseLayout } from "./BaseLayout.js";
2
2
  export { DashLayout } from "./DashLayout.js";
3
+ export { SiteLayout } from "./SiteLayout.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Minimal type declarations for sortablejs
3
+ *
4
+ * Only covers the API surface used by nav-reorder.ts.
5
+ */
package/dist/types.js CHANGED
@@ -38,6 +38,10 @@ export const MAX_MEDIA_ATTACHMENTS = 20;
38
38
  ],
39
39
  page: null
40
40
  };
41
+ export const STORAGE_DRIVERS = [
42
+ "r2",
43
+ "s3"
44
+ ];
41
45
  export const VISIBILITY_LEVELS = [
42
46
  "featured",
43
47
  "quiet",
@@ -94,5 +98,33 @@ export const VISIBILITY_LEVELS = [
94
98
  DEMO_PASSWORD: {
95
99
  defaultValue: "",
96
100
  envOnly: true
101
+ },
102
+ STORAGE_DRIVER: {
103
+ defaultValue: "r2",
104
+ envOnly: true
105
+ },
106
+ S3_ENDPOINT: {
107
+ defaultValue: "",
108
+ envOnly: true
109
+ },
110
+ S3_BUCKET: {
111
+ defaultValue: "",
112
+ envOnly: true
113
+ },
114
+ S3_ACCESS_KEY_ID: {
115
+ defaultValue: "",
116
+ envOnly: true
117
+ },
118
+ S3_SECRET_ACCESS_KEY: {
119
+ defaultValue: "",
120
+ envOnly: true
121
+ },
122
+ S3_REGION: {
123
+ defaultValue: "auto",
124
+ envOnly: true
125
+ },
126
+ S3_PUBLIC_URL: {
127
+ defaultValue: "",
128
+ envOnly: true
97
129
  }
98
130
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,12 +31,14 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
+ "@aws-sdk/client-s3": "^3.987.0",
34
35
  "@lingui/core": "^5.9.0",
35
36
  "basecoat-css": "^0.3.10",
36
37
  "better-auth": "^1.4.18",
37
38
  "drizzle-orm": "^0.45.1",
38
39
  "hono": "^4.11.7",
39
40
  "marked": "^17.0.1",
41
+ "sortablejs": "^1.15.6",
40
42
  "sqids": "^0.3.0",
41
43
  "uuidv7": "^1.1.0",
42
44
  "vite-ssr-components": "^0.5.2",
@@ -90,7 +92,7 @@
90
92
  "build": "pnpm build:lib",
91
93
  "build:lib": "swc src -d dist --strip-leading-paths --ignore '**/__tests__/**' && pnpm build:types",
92
94
  "build:types": "tsc -p tsconfig.build.json",
93
- "typecheck": "tsc --noEmit && tsc -p tsconfig.client.json",
95
+ "typecheck": "tsc --noEmit",
94
96
  "db:generate": "drizzle-kit generate",
95
97
  "i18n:extract": "lingui extract",
96
98
  "i18n:compile": "lingui compile --typescript",
@@ -58,6 +58,7 @@ export function createTestApp(options: TestAppOptions = {}) {
58
58
 
59
59
  c.set("services", services as AppVariables["services"]);
60
60
  c.set("config", {});
61
+ c.set("storage", null);
61
62
 
62
63
  if (options.authenticated) {
63
64
  // Mock auth that always returns a session
@@ -89,6 +89,26 @@ export function createTestDatabase(options?: { fts?: boolean }) {
89
89
  if (trimmed) sqlite.exec(trimmed);
90
90
  }
91
91
 
92
+ // Apply navigation links migration
93
+ const migration3 = readFileSync(
94
+ resolve(MIGRATIONS_DIR, "0003_add_navigation_links.sql"),
95
+ "utf-8",
96
+ );
97
+ for (const sql of migration3.split("--> statement-breakpoint")) {
98
+ const trimmed = sql.trim();
99
+ if (trimmed) sqlite.exec(trimmed);
100
+ }
101
+
102
+ // Apply storage provider migration
103
+ const migration4 = readFileSync(
104
+ resolve(MIGRATIONS_DIR, "0004_add_storage_provider.sql"),
105
+ "utf-8",
106
+ );
107
+ for (const sql of migration4.split("--> statement-breakpoint")) {
108
+ const trimmed = sql.trim();
109
+ if (trimmed) sqlite.exec(trimmed);
110
+ }
111
+
92
112
  const db = drizzle(sqlite, { schema });
93
113
 
94
114
  return { db, sqlite };
package/src/app.tsx CHANGED
@@ -29,11 +29,13 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
29
29
  import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
30
30
  import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
31
31
  import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
32
+ import { navigationRoutes as dashNavigationRoutes } from "./routes/dash/navigation.js";
32
33
 
33
34
  // Routes - API
34
35
  import { postsApiRoutes } from "./routes/api/posts.js";
35
36
  import { uploadApiRoutes } from "./routes/api/upload.js";
36
37
  import { searchApiRoutes } from "./routes/api/search.js";
38
+ import { timelineApiRoutes } from "./routes/api/timeline.js";
37
39
 
38
40
  // Routes - Feed
39
41
  import { rssRoutes } from "./routes/feed/rss.js";
@@ -47,6 +49,7 @@ import { requireOnboarding } from "./middleware/onboarding.js";
47
49
  import { BaseLayout } from "./theme/layouts/index.js";
48
50
  import { dsRedirect, dsToast } from "./lib/sse.js";
49
51
  import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
52
+ import { createStorageDriver, type StorageDriver } from "./lib/storage.js";
50
53
 
51
54
  // Extend Hono's context variables
52
55
  export interface AppVariables {
@@ -54,6 +57,7 @@ export interface AppVariables {
54
57
  auth: Auth;
55
58
  config: JantConfig;
56
59
  themeStyle: string;
60
+ storage: StorageDriver | null;
57
61
  }
58
62
 
59
63
  export type App = Hono<{ Bindings: Bindings; Variables: AppVariables }>;
@@ -93,6 +97,7 @@ export function createApp(config: JantConfig = {}): App {
93
97
  const services = createServices(db, session as unknown as D1Database);
94
98
  c.set("services", services);
95
99
  c.set("config", config);
100
+ c.set("storage", createStorageDriver(c.env));
96
101
 
97
102
  if (c.env.AUTH_SECRET) {
98
103
  const auth = createAuth(session as unknown as D1Database, {
@@ -168,6 +173,7 @@ export function createApp(config: JantConfig = {}): App {
168
173
 
169
174
  // API Routes
170
175
  app.route("/api/posts", postsApiRoutes);
176
+ app.route("/api/timeline", timelineApiRoutes);
171
177
 
172
178
  // Setup page component
173
179
  const SetupContent: FC = () => {
@@ -660,13 +666,15 @@ export function createApp(config: JantConfig = {}): App {
660
666
  app.route("/dash/settings", dashSettingsRoutes);
661
667
  app.route("/dash/redirects", dashRedirectsRoutes);
662
668
  app.route("/dash/collections", dashCollectionsRoutes);
669
+ app.route("/dash/navigation", dashNavigationRoutes);
663
670
  // API routes
664
671
  app.route("/api/upload", uploadApiRoutes);
665
672
  app.route("/api/search", searchApiRoutes);
666
673
 
667
- // Media files from R2 (UUIDv7-based URLs with extension)
674
+ // Media files from storage (UUIDv7-based URLs with extension)
668
675
  app.get("/media/:idWithExt", async (c) => {
669
- if (!c.env.R2) {
676
+ const storage = c.var.storage;
677
+ if (!storage) {
670
678
  return c.notFound();
671
679
  }
672
680
 
@@ -679,16 +687,13 @@ export function createApp(config: JantConfig = {}): App {
679
687
  return c.notFound();
680
688
  }
681
689
 
682
- const object = await c.env.R2.get(media.r2Key);
690
+ const object = await storage.get(media.storageKey);
683
691
  if (!object) {
684
692
  return c.notFound();
685
693
  }
686
694
 
687
695
  const headers = new Headers();
688
- headers.set(
689
- "Content-Type",
690
- object.httpMetadata?.contentType || media.mimeType,
691
- );
696
+ headers.set("Content-Type", object.contentType || media.mimeType);
692
697
  headers.set("Cache-Control", "public, max-age=31536000, immutable");
693
698
 
694
699
  return new Response(object.body, { headers });
package/src/client.ts CHANGED
@@ -11,3 +11,4 @@ import "./vendor/datastar.js";
11
11
  import "basecoat-css/all";
12
12
  import "./lib/image-processor.js";
13
13
  import "./lib/media-upload.js";
14
+ import "./lib/nav-reorder.js";
@@ -0,0 +1,8 @@
1
+ CREATE TABLE `navigation_links` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
+ `label` text NOT NULL,
4
+ `url` text NOT NULL,
5
+ `position` integer DEFAULT 0 NOT NULL,
6
+ `created_at` integer NOT NULL,
7
+ `updated_at` integer NOT NULL
8
+ );
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `media` ADD COLUMN `provider` text NOT NULL DEFAULT 'r2';
2
+ --> statement-breakpoint
3
+ ALTER TABLE `media` RENAME COLUMN `r2_key` TO `storage_key`;