@jant/core 0.3.27 → 0.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/dist/client/client.css +1 -0
  2. package/dist/client/client.js +31561 -0
  3. package/dist/index.js +15209 -15
  4. package/package.json +21 -15
  5. package/src/__tests__/helpers/app.ts +19 -3
  6. package/src/__tests__/helpers/db.ts +44 -0
  7. package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
  8. package/src/app.tsx +111 -174
  9. package/src/client.ts +13 -0
  10. package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
  11. package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
  12. package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
  13. package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
  14. package/src/db/schema.ts +24 -4
  15. package/src/i18n/locales/en.po +810 -385
  16. package/src/i18n/locales/en.ts +1 -1
  17. package/src/i18n/locales/zh-Hans.po +733 -522
  18. package/src/i18n/locales/zh-Hans.ts +1 -1
  19. package/src/i18n/locales/zh-Hant.po +733 -522
  20. package/src/i18n/locales/zh-Hant.ts +1 -1
  21. package/src/i18n/middleware.ts +7 -11
  22. package/src/index.ts +1 -1
  23. package/src/lib/__tests__/icons.test.ts +178 -0
  24. package/src/lib/__tests__/resolve-config.test.ts +184 -0
  25. package/src/lib/__tests__/schemas.test.ts +12 -6
  26. package/src/lib/__tests__/theme.test.ts +62 -0
  27. package/src/lib/__tests__/timezones.test.ts +1 -1
  28. package/src/lib/__tests__/url.test.ts +12 -0
  29. package/src/lib/__tests__/view.test.ts +1 -5
  30. package/src/lib/avatar-upload.ts +18 -10
  31. package/src/lib/collection-form-bridge.ts +52 -0
  32. package/src/lib/collections-reorder.ts +28 -0
  33. package/src/lib/compose-bridge.ts +251 -0
  34. package/src/lib/errors.ts +116 -0
  35. package/src/lib/excerpt.ts +1 -1
  36. package/src/lib/favicon.ts +3 -5
  37. package/src/lib/html.ts +22 -0
  38. package/src/lib/icon-catalog.ts +181 -0
  39. package/src/lib/icons.ts +202 -0
  40. package/src/lib/navigation.ts +18 -33
  41. package/src/lib/pagination.ts +3 -2
  42. package/src/lib/post-form-bridge.ts +136 -0
  43. package/src/lib/render.tsx +11 -4
  44. package/src/lib/resolve-config.ts +157 -0
  45. package/src/lib/schemas.ts +76 -12
  46. package/src/lib/settings-bridge.ts +139 -0
  47. package/src/lib/storage.ts +37 -16
  48. package/src/lib/theme.ts +5 -7
  49. package/src/lib/timeline.ts +4 -8
  50. package/src/lib/toast.ts +134 -0
  51. package/src/lib/upload.ts +71 -0
  52. package/src/lib/url.ts +9 -1
  53. package/src/lib/version.ts +16 -0
  54. package/src/lib/view.ts +9 -10
  55. package/src/middleware/__tests__/auth.test.ts +6 -28
  56. package/src/middleware/__tests__/onboarding.test.ts +1 -1
  57. package/src/middleware/auth.ts +6 -12
  58. package/src/middleware/config.ts +51 -0
  59. package/src/middleware/error-handler.ts +56 -0
  60. package/src/middleware/onboarding.ts +1 -1
  61. package/src/preset.css +6 -0
  62. package/src/routes/__tests__/compose.test.ts +104 -17
  63. package/src/routes/api/__tests__/collections.test.ts +93 -2
  64. package/src/routes/api/__tests__/posts.test.ts +2 -1
  65. package/src/routes/api/__tests__/settings.test.ts +1 -1
  66. package/src/routes/api/collections.ts +64 -68
  67. package/src/routes/api/nav-items.ts +21 -59
  68. package/src/routes/api/pages.ts +18 -46
  69. package/src/routes/api/posts.ts +64 -86
  70. package/src/routes/api/search.ts +6 -4
  71. package/src/routes/api/settings.ts +8 -24
  72. package/src/routes/api/upload.ts +55 -53
  73. package/src/routes/auth/__tests__/setup.test.ts +118 -0
  74. package/src/routes/auth/reset.tsx +17 -66
  75. package/src/routes/auth/setup.tsx +67 -11
  76. package/src/routes/auth/signin.tsx +44 -8
  77. package/src/routes/compose.tsx +194 -0
  78. package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
  79. package/src/routes/dash/__tests__/pages.test.ts +2 -2
  80. package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
  81. package/src/routes/dash/appearance.tsx +173 -0
  82. package/src/routes/dash/collections.tsx +80 -14
  83. package/src/routes/dash/index.tsx +12 -14
  84. package/src/routes/dash/media.tsx +46 -49
  85. package/src/routes/dash/pages.tsx +85 -37
  86. package/src/routes/dash/posts.tsx +60 -23
  87. package/src/routes/dash/redirects.tsx +43 -33
  88. package/src/routes/dash/settings.tsx +234 -214
  89. package/src/routes/feed/__tests__/rss.test.ts +7 -3
  90. package/src/routes/feed/rss.ts +11 -16
  91. package/src/routes/feed/sitemap.ts +15 -9
  92. package/src/routes/pages/__tests__/collections.test.ts +9 -8
  93. package/src/routes/pages/archive.tsx +2 -2
  94. package/src/routes/pages/collection.tsx +76 -9
  95. package/src/routes/pages/collections.tsx +3 -1
  96. package/src/routes/pages/featured.tsx +2 -2
  97. package/src/routes/pages/home.tsx +3 -3
  98. package/src/routes/pages/latest.tsx +2 -2
  99. package/src/routes/pages/page.tsx +2 -2
  100. package/src/routes/pages/post.tsx +2 -2
  101. package/src/routes/pages/search.tsx +2 -2
  102. package/src/services/__tests__/collection.test.ts +324 -34
  103. package/src/services/__tests__/media.test.ts +1 -1
  104. package/src/services/__tests__/page.test.ts +116 -1
  105. package/src/services/auth.ts +88 -0
  106. package/src/services/collection.ts +169 -30
  107. package/src/services/index.ts +8 -3
  108. package/src/services/media.ts +39 -12
  109. package/src/services/navigation.ts +17 -5
  110. package/src/services/page.ts +24 -4
  111. package/src/services/post.ts +87 -19
  112. package/src/services/search.ts +0 -1
  113. package/src/services/settings.ts +21 -13
  114. package/src/style.css +3 -0
  115. package/src/styles/components.css +42 -1
  116. package/src/styles/tokens.css +4 -0
  117. package/src/styles/ui.css +902 -73
  118. package/src/types/app-context.ts +25 -0
  119. package/src/types/bindings.ts +1 -0
  120. package/src/types/config.ts +60 -23
  121. package/src/types/entities.ts +12 -2
  122. package/src/types/lingui-react-macro.d.ts +3 -3
  123. package/src/types/operations.ts +2 -4
  124. package/src/types/views.ts +1 -3
  125. package/src/ui/__tests__/font-themes.test.ts +27 -8
  126. package/src/ui/color-themes.ts +1 -1
  127. package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
  128. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
  129. package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
  130. package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
  131. package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
  132. package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
  133. package/src/ui/components/collection-types.ts +45 -0
  134. package/src/ui/components/compose-types.ts +75 -0
  135. package/src/ui/components/jant-collection-form.ts +512 -0
  136. package/src/ui/components/jant-compose-dialog.ts +494 -0
  137. package/src/ui/components/jant-compose-editor.ts +799 -0
  138. package/src/ui/components/jant-post-form.ts +290 -0
  139. package/src/ui/components/jant-settings-avatar.ts +231 -0
  140. package/src/ui/components/jant-settings-general.ts +436 -0
  141. package/src/ui/components/post-form-template.ts +260 -0
  142. package/src/ui/components/post-form-types.ts +87 -0
  143. package/src/ui/components/settings-types.ts +62 -0
  144. package/src/ui/compose/ComposeDialog.tsx +141 -385
  145. package/src/ui/compose/ComposePrompt.tsx +3 -3
  146. package/src/ui/dash/PostList.tsx +55 -61
  147. package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
  148. package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
  149. package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
  150. package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
  151. package/src/ui/dash/collections/CollectionForm.tsx +130 -117
  152. package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
  153. package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
  154. package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
  155. package/src/ui/dash/index.ts +1 -1
  156. package/src/ui/dash/posts/PostForm.tsx +248 -0
  157. package/src/ui/dash/settings/AccountContent.tsx +69 -80
  158. package/src/ui/dash/settings/GeneralContent.tsx +159 -478
  159. package/src/ui/dash/settings/SettingsNav.tsx +4 -4
  160. package/src/ui/font-themes.ts +115 -32
  161. package/src/ui/layouts/BaseLayout.tsx +49 -19
  162. package/src/ui/layouts/DashLayout.tsx +14 -9
  163. package/src/ui/layouts/SiteLayout.tsx +38 -23
  164. package/src/ui/pages/CollectionPage.tsx +12 -2
  165. package/src/ui/pages/CollectionsPage.tsx +27 -27
  166. package/src/ui/pages/HomePage.tsx +15 -6
  167. package/src/ui/pages/SearchPage.tsx +1 -2
  168. package/src/ui/shared/CollectionsSidebar.tsx +59 -0
  169. package/src/ui/shared/Pagination.tsx +2 -2
  170. package/dist/app.js +0 -267
  171. package/dist/auth.js +0 -39
  172. package/dist/client.js +0 -13
  173. package/dist/db/index.js +0 -10
  174. package/dist/db/schema.js +0 -224
  175. package/dist/i18n/Trans.js +0 -24
  176. package/dist/i18n/context.js +0 -58
  177. package/dist/i18n/detect.js +0 -26
  178. package/dist/i18n/i18n.js +0 -49
  179. package/dist/i18n/index.js +0 -44
  180. package/dist/i18n/locales/en.js +0 -1
  181. package/dist/i18n/locales/zh-Hans.js +0 -1
  182. package/dist/i18n/locales/zh-Hant.js +0 -1
  183. package/dist/i18n/locales.js +0 -13
  184. package/dist/i18n/middleware.js +0 -30
  185. package/dist/lib/avatar-upload.js +0 -134
  186. package/dist/lib/config.js +0 -143
  187. package/dist/lib/constants.js +0 -50
  188. package/dist/lib/excerpt.js +0 -76
  189. package/dist/lib/favicon.js +0 -102
  190. package/dist/lib/feed.js +0 -123
  191. package/dist/lib/image-processor.js +0 -187
  192. package/dist/lib/image.js +0 -97
  193. package/dist/lib/index.js +0 -7
  194. package/dist/lib/markdown.js +0 -83
  195. package/dist/lib/media-helpers.js +0 -49
  196. package/dist/lib/media-upload.js +0 -104
  197. package/dist/lib/nav-reorder.js +0 -27
  198. package/dist/lib/navigation.js +0 -79
  199. package/dist/lib/pagination.js +0 -44
  200. package/dist/lib/render.js +0 -53
  201. package/dist/lib/schemas.js +0 -174
  202. package/dist/lib/sqid.js +0 -72
  203. package/dist/lib/sse.js +0 -218
  204. package/dist/lib/storage.js +0 -164
  205. package/dist/lib/theme.js +0 -65
  206. package/dist/lib/time.js +0 -159
  207. package/dist/lib/timeline.js +0 -95
  208. package/dist/lib/timezones.js +0 -388
  209. package/dist/lib/url.js +0 -89
  210. package/dist/lib/view.js +0 -217
  211. package/dist/middleware/auth.js +0 -52
  212. package/dist/middleware/onboarding.js +0 -41
  213. package/dist/routes/api/collections.js +0 -124
  214. package/dist/routes/api/nav-items.js +0 -104
  215. package/dist/routes/api/pages.js +0 -91
  216. package/dist/routes/api/posts.js +0 -218
  217. package/dist/routes/api/search.js +0 -48
  218. package/dist/routes/api/settings.js +0 -68
  219. package/dist/routes/api/upload.js +0 -246
  220. package/dist/routes/auth/reset.js +0 -221
  221. package/dist/routes/auth/setup.js +0 -194
  222. package/dist/routes/auth/signin.js +0 -176
  223. package/dist/routes/compose.js +0 -48
  224. package/dist/routes/dash/collections.js +0 -115
  225. package/dist/routes/dash/index.js +0 -118
  226. package/dist/routes/dash/media.js +0 -106
  227. package/dist/routes/dash/pages.js +0 -294
  228. package/dist/routes/dash/posts.js +0 -244
  229. package/dist/routes/dash/redirects.js +0 -257
  230. package/dist/routes/dash/settings.js +0 -379
  231. package/dist/routes/feed/rss.js +0 -62
  232. package/dist/routes/feed/sitemap.js +0 -49
  233. package/dist/routes/pages/archive.js +0 -62
  234. package/dist/routes/pages/collection.js +0 -34
  235. package/dist/routes/pages/collections.js +0 -28
  236. package/dist/routes/pages/featured.js +0 -36
  237. package/dist/routes/pages/home.js +0 -64
  238. package/dist/routes/pages/latest.js +0 -45
  239. package/dist/routes/pages/page.js +0 -68
  240. package/dist/routes/pages/post.js +0 -44
  241. package/dist/routes/pages/search.js +0 -54
  242. package/dist/services/collection.js +0 -109
  243. package/dist/services/index.js +0 -24
  244. package/dist/services/media.js +0 -117
  245. package/dist/services/navigation.js +0 -91
  246. package/dist/services/page.js +0 -84
  247. package/dist/services/post.js +0 -229
  248. package/dist/services/redirect.js +0 -48
  249. package/dist/services/search.js +0 -67
  250. package/dist/services/settings.js +0 -68
  251. package/dist/types/bindings.js +0 -3
  252. package/dist/types/config.js +0 -147
  253. package/dist/types/constants.js +0 -27
  254. package/dist/types/entities.js +0 -3
  255. package/dist/types/lingui-react-macro.d.js +0 -9
  256. package/dist/types/operations.js +0 -3
  257. package/dist/types/props.js +0 -3
  258. package/dist/types/sortablejs.d.js +0 -5
  259. package/dist/types/views.js +0 -5
  260. package/dist/types.js +0 -11
  261. package/dist/ui/color-themes.js +0 -268
  262. package/dist/ui/compose/ComposeDialog.js +0 -467
  263. package/dist/ui/compose/ComposePrompt.js +0 -55
  264. package/dist/ui/dash/ActionButtons.js +0 -46
  265. package/dist/ui/dash/CrudPageHeader.js +0 -22
  266. package/dist/ui/dash/DangerZone.js +0 -36
  267. package/dist/ui/dash/FormatBadge.js +0 -27
  268. package/dist/ui/dash/ListItemRow.js +0 -21
  269. package/dist/ui/dash/PageForm.js +0 -195
  270. package/dist/ui/dash/PostForm.js +0 -395
  271. package/dist/ui/dash/PostList.js +0 -83
  272. package/dist/ui/dash/StatusBadge.js +0 -46
  273. package/dist/ui/dash/collections/CollectionForm.js +0 -152
  274. package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
  275. package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
  276. package/dist/ui/dash/index.js +0 -10
  277. package/dist/ui/dash/media/MediaListContent.js +0 -166
  278. package/dist/ui/dash/media/ViewMediaContent.js +0 -212
  279. package/dist/ui/dash/pages/LinkFormContent.js +0 -130
  280. package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
  281. package/dist/ui/dash/settings/AccountContent.js +0 -209
  282. package/dist/ui/dash/settings/AppearanceContent.js +0 -259
  283. package/dist/ui/dash/settings/GeneralContent.js +0 -536
  284. package/dist/ui/dash/settings/SettingsNav.js +0 -41
  285. package/dist/ui/feed/LinkCard.js +0 -72
  286. package/dist/ui/feed/NoteCard.js +0 -58
  287. package/dist/ui/feed/QuoteCard.js +0 -63
  288. package/dist/ui/feed/ThreadPreview.js +0 -48
  289. package/dist/ui/feed/TimelineFeed.js +0 -41
  290. package/dist/ui/feed/TimelineItem.js +0 -27
  291. package/dist/ui/font-themes.js +0 -36
  292. package/dist/ui/layouts/BaseLayout.js +0 -153
  293. package/dist/ui/layouts/DashLayout.js +0 -141
  294. package/dist/ui/layouts/SiteLayout.js +0 -169
  295. package/dist/ui/pages/ArchivePage.js +0 -143
  296. package/dist/ui/pages/CollectionPage.js +0 -70
  297. package/dist/ui/pages/CollectionsPage.js +0 -76
  298. package/dist/ui/pages/FeaturedPage.js +0 -24
  299. package/dist/ui/pages/HomePage.js +0 -24
  300. package/dist/ui/pages/PostPage.js +0 -55
  301. package/dist/ui/pages/SearchPage.js +0 -122
  302. package/dist/ui/pages/SinglePage.js +0 -23
  303. package/dist/ui/shared/EmptyState.js +0 -27
  304. package/dist/ui/shared/MediaGallery.js +0 -35
  305. package/dist/ui/shared/Pagination.js +0 -195
  306. package/dist/ui/shared/ThreadView.js +0 -108
  307. package/dist/ui/shared/index.js +0 -5
  308. package/dist/vendor/datastar.js +0 -1606
  309. package/src/lib/__tests__/config.test.ts +0 -192
  310. package/src/lib/config.ts +0 -167
  311. package/src/routes/compose.ts +0 -63
  312. package/src/ui/dash/PostForm.tsx +0 -360
  313. package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
package/dist/app.js DELETED
@@ -1,267 +0,0 @@
1
- /**
2
- * Jant App Factory
3
- */ import { Hono } from "hono";
4
- import { createDatabase } from "./db/index.js";
5
- import { createServices } from "./services/index.js";
6
- import { createAuth } from "./auth.js";
7
- import { i18nMiddleware } from "./i18n/index.js";
8
- import { SETTINGS_KEYS } from "./lib/constants.js";
9
- // Routes - Auth
10
- import { setupRoutes } from "./routes/auth/setup.js";
11
- import { signinRoutes } from "./routes/auth/signin.js";
12
- import { resetRoutes } from "./routes/auth/reset.js";
13
- // Routes - Pages
14
- import { homeRoutes } from "./routes/pages/home.js";
15
- import { postRoutes } from "./routes/pages/post.js";
16
- import { pageRoutes } from "./routes/pages/page.js";
17
- import { collectionRoutes } from "./routes/pages/collection.js";
18
- import { archiveRoutes } from "./routes/pages/archive.js";
19
- import { searchRoutes } from "./routes/pages/search.js";
20
- import { featuredRoutes } from "./routes/pages/featured.js";
21
- import { latestRoutes } from "./routes/pages/latest.js";
22
- import { collectionsPageRoutes } from "./routes/pages/collections.js";
23
- // Routes - Dashboard
24
- import { dashIndexRoutes } from "./routes/dash/index.js";
25
- import { postsRoutes as dashPostsRoutes } from "./routes/dash/posts.js";
26
- import { pagesRoutes as dashPagesRoutes } from "./routes/dash/pages.js";
27
- import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
28
- import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
29
- import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
30
- import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
31
- // Routes - API
32
- import { postsApiRoutes } from "./routes/api/posts.js";
33
- import { pagesApiRoutes } from "./routes/api/pages.js";
34
- import { navItemsApiRoutes } from "./routes/api/nav-items.js";
35
- import { collectionsApiRoutes } from "./routes/api/collections.js";
36
- import { settingsApiRoutes } from "./routes/api/settings.js";
37
- import { uploadApiRoutes } from "./routes/api/upload.js";
38
- import { searchApiRoutes } from "./routes/api/search.js";
39
- // Routes - Compose
40
- import { composeRoutes } from "./routes/compose.js";
41
- // Routes - Feed
42
- import { rssRoutes } from "./routes/feed/rss.js";
43
- import { sitemapRoutes } from "./routes/feed/sitemap.js";
44
- // Middleware
45
- import { requireAuth } from "./middleware/auth.js";
46
- import { requireOnboarding } from "./middleware/onboarding.js";
47
- import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
48
- import { createStorageDriver } from "./lib/storage.js";
49
- import { BUILTIN_FONT_THEMES } from "./ui/font-themes.js";
50
- import { getMediaUrl, getPublicUrlForProvider } from "./lib/image.js";
51
- import { base64ToUint8Array } from "./lib/favicon.js";
52
- /**
53
- * Create a Jant application
54
- *
55
- * @param config - Optional configuration
56
- * @returns Hono app instance
57
- *
58
- * Site settings (name, description, language) should be configured via
59
- * environment variables (SITE_NAME, SITE_DESCRIPTION, SITE_LANGUAGE).
60
- * They can also be set in the dashboard, which stores them in the database.
61
- *
62
- * @example
63
- * ```typescript
64
- * import { createApp } from "@jant/core";
65
- *
66
- * export default createApp({
67
- * cssVariables: { "--card-radius": "0" },
68
- * });
69
- * ```
70
- */ export function createApp(config = {}) {
71
- const resolvedConfig = {
72
- ...config
73
- };
74
- const app = new Hono();
75
- // Initialize services, auth, and config middleware
76
- app.use("*", async (c, next)=>{
77
- // Use withSession() to enable D1 Read Replication
78
- const session = c.env.DB.withSession();
79
- // Note: Drizzle ORM doesn't officially support D1DatabaseSession yet (issue #2226)
80
- // but it works at runtime. We use type assertion as a temporary workaround.
81
- const db = createDatabase(session);
82
- const services = createServices(db, session);
83
- c.set("services", services);
84
- c.set("config", resolvedConfig);
85
- c.set("storage", createStorageDriver(c.env));
86
- if (!c.env.AUTH_SECRET) {
87
- // eslint-disable-next-line no-console -- Startup warning is intentional
88
- console.warn("[Jant] AUTH_SECRET is not set. Authentication is disabled. Set AUTH_SECRET in .dev.vars or wrangler.toml to enable auth.");
89
- }
90
- if (c.env.AUTH_SECRET) {
91
- const baseURL = c.env.SITE_URL || new URL(c.req.url).origin;
92
- const requestUrl = new URL(c.req.url);
93
- const auth = createAuth(session, {
94
- secret: c.env.AUTH_SECRET,
95
- baseURL,
96
- useSecureCookies: requestUrl.protocol === "https:"
97
- });
98
- c.set("auth", auth);
99
- }
100
- await next();
101
- });
102
- // Onboarding gate — redirect to /setup if not yet initialized
103
- app.use("*", requireOnboarding());
104
- // Theme middleware - resolve active color theme, font theme, custom CSS, and auth state
105
- app.use("*", async (c, next)=>{
106
- const [themeId, fontThemeId, customCSS, noindexValue, avatarKey] = await Promise.all([
107
- c.var.services.settings.get(SETTINGS_KEYS.THEME),
108
- c.var.services.settings.get("FONT_THEME"),
109
- c.var.services.settings.get(SETTINGS_KEYS.CUSTOM_CSS),
110
- c.var.services.settings.get("NOINDEX"),
111
- c.var.services.settings.get("SITE_AVATAR")
112
- ]);
113
- const themes = getAvailableThemes(resolvedConfig);
114
- const activeTheme = themeId ? themes.find((t)=>t.id === themeId) : undefined;
115
- // Build font override CSS variables
116
- const fontTheme = fontThemeId ? BUILTIN_FONT_THEMES.find((f)=>f.id === fontThemeId) : undefined;
117
- const fontOverrides = {};
118
- if (fontTheme) {
119
- fontOverrides["--font-body"] = fontTheme.fontFamily;
120
- }
121
- const themeStyle = buildThemeStyle(activeTheme, {
122
- ...resolvedConfig.cssVariables,
123
- ...fontOverrides
124
- });
125
- c.set("themeStyle", themeStyle);
126
- c.set("customCSS", customCSS ?? "");
127
- // Noindex
128
- c.set("noindex", noindexValue === "true");
129
- // Resolve favicon from avatar storage key
130
- if (avatarKey) {
131
- const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
132
- c.set("faviconUrl", getMediaUrl(avatarKey, publicUrl));
133
- }
134
- // Check auth state for data-authenticated attribute on <body>
135
- let isAuthenticated = false;
136
- if (c.var.auth) {
137
- try {
138
- const session = await c.var.auth.api.getSession({
139
- headers: c.req.raw.headers
140
- });
141
- isAuthenticated = !!session;
142
- } catch {
143
- // Not authenticated
144
- }
145
- }
146
- c.set("isAuthenticated", isAuthenticated);
147
- await next();
148
- });
149
- // i18n middleware
150
- app.use("*", i18nMiddleware());
151
- // Trailing slash redirect (redirect /foo/ to /foo)
152
- app.use("*", async (c, next)=>{
153
- const url = new URL(c.req.url);
154
- if (url.pathname !== "/" && url.pathname.endsWith("/")) {
155
- const newUrl = url.pathname.slice(0, -1) + url.search;
156
- return c.redirect(newUrl, 301);
157
- }
158
- await next();
159
- });
160
- // Redirect middleware
161
- app.use("*", async (c, next)=>{
162
- const path = new URL(c.req.url).pathname;
163
- // Skip redirect check for API routes and static assets
164
- if (path.startsWith("/api/") || path.startsWith("/assets/")) {
165
- return next();
166
- }
167
- const redirect = await c.var.services.redirects.getByPath(path);
168
- if (redirect) {
169
- return c.redirect(redirect.toPath, redirect.type);
170
- }
171
- await next();
172
- });
173
- // Health check
174
- app.get("/health", (c)=>c.json({
175
- status: "ok",
176
- auth: c.env.AUTH_SECRET ? "configured" : "missing",
177
- authSecretLength: c.env.AUTH_SECRET?.length ?? 0
178
- }));
179
- // Favicon routes - serve from DB settings (small files, avoids R2 round-trip)
180
- app.get("/favicon.ico", async (c)=>{
181
- const data = await c.var.services.settings.get("SITE_FAVICON_ICO");
182
- if (!data) return c.notFound();
183
- return new Response(base64ToUint8Array(data), {
184
- headers: {
185
- "Content-Type": "image/x-icon",
186
- "Cache-Control": "public, max-age=86400"
187
- }
188
- });
189
- });
190
- app.get("/apple-touch-icon.png", async (c)=>{
191
- const data = await c.var.services.settings.get("SITE_FAVICON_APPLE_TOUCH");
192
- if (!data) return c.notFound();
193
- return new Response(base64ToUint8Array(data), {
194
- headers: {
195
- "Content-Type": "image/png",
196
- "Cache-Control": "public, max-age=86400"
197
- }
198
- });
199
- });
200
- // better-auth handler
201
- app.all("/api/auth/*", async (c)=>{
202
- if (!c.var.auth) {
203
- return c.json({
204
- error: "Auth not configured. Set AUTH_SECRET."
205
- }, 500);
206
- }
207
- return c.var.auth.handler(c.req.raw);
208
- });
209
- // API Routes
210
- app.route("/api/posts", postsApiRoutes);
211
- app.route("/api/pages", pagesApiRoutes);
212
- app.route("/api/nav-items", navItemsApiRoutes);
213
- app.route("/api/collections", collectionsApiRoutes);
214
- app.route("/api/settings", settingsApiRoutes);
215
- // Auth routes
216
- app.route("/", setupRoutes);
217
- app.route("/", signinRoutes);
218
- app.route("/", resetRoutes);
219
- // Dashboard routes (protected)
220
- app.use("/dash/*", requireAuth());
221
- app.route("/dash", dashIndexRoutes);
222
- app.route("/dash/posts", dashPostsRoutes);
223
- app.route("/dash/pages", dashPagesRoutes);
224
- app.route("/dash/media", dashMediaRoutes);
225
- app.route("/dash/settings", dashSettingsRoutes);
226
- app.route("/dash/redirects", dashRedirectsRoutes);
227
- app.route("/dash/collections", dashCollectionsRoutes);
228
- // API routes
229
- app.route("/api/upload", uploadApiRoutes);
230
- app.route("/api/search", searchApiRoutes);
231
- // Media files from storage (path matches storage key: media/YYYY/MM/uuid.ext)
232
- app.get("/media/*", async (c)=>{
233
- const storage = c.var.storage;
234
- if (!storage) {
235
- return c.notFound();
236
- }
237
- // The storage key is the full path without the leading "/"
238
- const storageKey = c.req.path.slice(1);
239
- const object = await storage.get(storageKey);
240
- if (!object) {
241
- return c.notFound();
242
- }
243
- const headers = new Headers();
244
- headers.set("Content-Type", object.contentType || "application/octet-stream");
245
- headers.set("Cache-Control", "public, max-age=31536000, immutable");
246
- return new Response(object.body, {
247
- headers
248
- });
249
- });
250
- // Compose route (auth enforced in route middleware)
251
- app.route("/compose", composeRoutes);
252
- // Feed routes
253
- app.route("/feed", rssRoutes);
254
- app.route("/", sitemapRoutes);
255
- // Frontend routes
256
- app.route("/search", searchRoutes);
257
- app.route("/archive", archiveRoutes);
258
- app.route("/featured", featuredRoutes);
259
- app.route("/latest", latestRoutes);
260
- app.route("/collections", collectionsPageRoutes);
261
- app.route("/c", collectionRoutes);
262
- app.route("/p", postRoutes);
263
- app.route("/", homeRoutes);
264
- // Custom page catch-all (must be last)
265
- app.route("/", pageRoutes);
266
- return app;
267
- }
package/dist/auth.js DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Authentication with better-auth
3
- */ import { betterAuth } from "better-auth";
4
- import { drizzleAdapter } from "better-auth/adapters/drizzle";
5
- import { drizzle } from "drizzle-orm/d1";
6
- import * as schema from "./db/schema.js";
7
- export function createAuth(d1, options) {
8
- const db = drizzle(d1, {
9
- schema
10
- });
11
- return betterAuth({
12
- database: drizzleAdapter(db, {
13
- provider: "sqlite",
14
- schema: {
15
- user: schema.user,
16
- session: schema.session,
17
- account: schema.account,
18
- verification: schema.verification
19
- }
20
- }),
21
- secret: options.secret,
22
- baseURL: options.baseURL,
23
- advanced: {
24
- useSecureCookies: options.useSecureCookies
25
- },
26
- emailAndPassword: {
27
- enabled: true,
28
- autoSignIn: true,
29
- minPasswordLength: 8
30
- },
31
- session: {
32
- expiresIn: 3600 * 24 * 366,
33
- cookieCache: {
34
- enabled: true,
35
- maxAge: 60 * 5
36
- }
37
- }
38
- });
39
- }
package/dist/client.js DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * Client-side JavaScript entry point
3
- *
4
- * Bundles all interactive components:
5
- * - Datastar (reactivity)
6
- * - BaseCoat (dialogs, dropdowns)
7
- * - ImageProcessor (media uploads)
8
- */ import "./vendor/datastar.js";
9
- import "basecoat-css/all";
10
- import "./lib/image-processor.js";
11
- import "./lib/media-upload.js";
12
- import "./lib/avatar-upload.js";
13
- import "./lib/nav-reorder.js";
package/dist/db/index.js DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Database utilities
3
- */ import { drizzle } from "drizzle-orm/d1";
4
- import * as schema from "./schema.js";
5
- export function createDatabase(d1) {
6
- return drizzle(d1, {
7
- schema
8
- });
9
- }
10
- export { schema };
package/dist/db/schema.js DELETED
@@ -1,224 +0,0 @@
1
- /**
2
- * Drizzle Schema
3
- *
4
- * Database schema for Jant v2
5
- */ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
6
- // =============================================================================
7
- // Posts
8
- // =============================================================================
9
- export const posts = sqliteTable("posts", {
10
- id: integer("id").primaryKey({
11
- autoIncrement: true
12
- }),
13
- format: text("format", {
14
- enum: [
15
- "note",
16
- "link",
17
- "quote"
18
- ]
19
- }).notNull(),
20
- status: text("status", {
21
- enum: [
22
- "draft",
23
- "published"
24
- ]
25
- }).notNull().default("published"),
26
- featured: integer("featured").notNull().default(0),
27
- pinned: integer("pinned").notNull().default(0),
28
- path: text("path").unique(),
29
- title: text("title"),
30
- url: text("url"),
31
- body: text("body"),
32
- bodyHtml: text("body_html"),
33
- quoteText: text("quote_text"),
34
- rating: integer("rating"),
35
- collectionId: integer("collection_id").references(()=>collections.id, {
36
- onDelete: "set null"
37
- }),
38
- replyToId: integer("reply_to_id"),
39
- threadId: integer("thread_id"),
40
- deletedAt: integer("deleted_at"),
41
- publishedAt: integer("published_at").notNull(),
42
- createdAt: integer("created_at").notNull(),
43
- updatedAt: integer("updated_at").notNull()
44
- });
45
- // =============================================================================
46
- // Pages
47
- // =============================================================================
48
- export const pages = sqliteTable("pages", {
49
- id: integer("id").primaryKey({
50
- autoIncrement: true
51
- }),
52
- slug: text("slug").notNull().unique(),
53
- title: text("title"),
54
- body: text("body"),
55
- bodyHtml: text("body_html"),
56
- status: text("status", {
57
- enum: [
58
- "draft",
59
- "published"
60
- ]
61
- }).notNull().default("published"),
62
- createdAt: integer("created_at").notNull(),
63
- updatedAt: integer("updated_at").notNull()
64
- });
65
- // =============================================================================
66
- // Media
67
- // =============================================================================
68
- export const media = sqliteTable("media", {
69
- id: text("id").primaryKey(),
70
- postId: integer("post_id").references(()=>posts.id),
71
- filename: text("filename").notNull(),
72
- originalName: text("original_name").notNull(),
73
- mimeType: text("mime_type").notNull(),
74
- size: integer("size").notNull(),
75
- storageKey: text("storage_key").notNull(),
76
- provider: text("provider").notNull().default("r2"),
77
- width: integer("width"),
78
- height: integer("height"),
79
- alt: text("alt"),
80
- position: integer("position").notNull().default(0),
81
- blurhash: text("blurhash"),
82
- createdAt: integer("created_at").notNull()
83
- });
84
- // =============================================================================
85
- // Collections
86
- // =============================================================================
87
- export const collections = sqliteTable("collections", {
88
- id: integer("id").primaryKey({
89
- autoIncrement: true
90
- }),
91
- slug: text("slug").notNull().unique(),
92
- title: text("title").notNull(),
93
- description: text("description"),
94
- icon: text("icon"),
95
- sortOrder: text("sort_order", {
96
- enum: [
97
- "newest",
98
- "oldest",
99
- "rating_desc",
100
- "rating_asc"
101
- ]
102
- }).notNull().default("newest"),
103
- position: integer("position").notNull().default(0),
104
- showDivider: integer("show_divider").notNull().default(0),
105
- createdAt: integer("created_at").notNull(),
106
- updatedAt: integer("updated_at").notNull()
107
- });
108
- // =============================================================================
109
- // Navigation Items
110
- // =============================================================================
111
- export const navItems = sqliteTable("nav_items", {
112
- id: integer("id").primaryKey({
113
- autoIncrement: true
114
- }),
115
- type: text("type", {
116
- enum: [
117
- "page",
118
- "link"
119
- ]
120
- }).notNull().default("link"),
121
- label: text("label").notNull(),
122
- url: text("url").notNull(),
123
- pageId: integer("page_id").references(()=>pages.id, {
124
- onDelete: "cascade"
125
- }),
126
- position: integer("position").notNull().default(0),
127
- createdAt: integer("created_at").notNull(),
128
- updatedAt: integer("updated_at").notNull()
129
- });
130
- // =============================================================================
131
- // Redirects
132
- // =============================================================================
133
- export const redirects = sqliteTable("redirects", {
134
- id: integer("id").primaryKey({
135
- autoIncrement: true
136
- }),
137
- fromPath: text("from_path").notNull().unique(),
138
- toPath: text("to_path").notNull(),
139
- type: integer("type", {
140
- mode: "number"
141
- }).notNull().default(301),
142
- createdAt: integer("created_at").notNull()
143
- });
144
- // =============================================================================
145
- // Settings (Key-Value)
146
- // =============================================================================
147
- export const settings = sqliteTable("settings", {
148
- key: text("key").primaryKey(),
149
- value: text("value").notNull(),
150
- updatedAt: integer("updated_at").notNull()
151
- });
152
- // =============================================================================
153
- // better-auth tables
154
- // Note: Using { mode: "timestamp" } so drizzle auto-converts Date <-> integer
155
- // =============================================================================
156
- export const user = sqliteTable("user", {
157
- id: text("id").primaryKey(),
158
- name: text("name").notNull(),
159
- email: text("email").notNull().unique(),
160
- emailVerified: integer("email_verified", {
161
- mode: "boolean"
162
- }).notNull().default(false),
163
- image: text("image"),
164
- role: text("role").default("admin"),
165
- createdAt: integer("created_at", {
166
- mode: "timestamp"
167
- }).notNull(),
168
- updatedAt: integer("updated_at", {
169
- mode: "timestamp"
170
- }).notNull()
171
- });
172
- export const session = sqliteTable("session", {
173
- id: text("id").primaryKey(),
174
- expiresAt: integer("expires_at", {
175
- mode: "timestamp"
176
- }).notNull(),
177
- token: text("token").notNull().unique(),
178
- createdAt: integer("created_at", {
179
- mode: "timestamp"
180
- }).notNull(),
181
- updatedAt: integer("updated_at", {
182
- mode: "timestamp"
183
- }).notNull(),
184
- ipAddress: text("ip_address"),
185
- userAgent: text("user_agent"),
186
- userId: text("user_id").notNull().references(()=>user.id)
187
- });
188
- export const account = sqliteTable("account", {
189
- id: text("id").primaryKey(),
190
- accountId: text("account_id").notNull(),
191
- providerId: text("provider_id").notNull(),
192
- userId: text("user_id").notNull().references(()=>user.id),
193
- accessToken: text("access_token"),
194
- refreshToken: text("refresh_token"),
195
- idToken: text("id_token"),
196
- accessTokenExpiresAt: integer("access_token_expires_at", {
197
- mode: "timestamp"
198
- }),
199
- refreshTokenExpiresAt: integer("refresh_token_expires_at", {
200
- mode: "timestamp"
201
- }),
202
- scope: text("scope"),
203
- password: text("password"),
204
- createdAt: integer("created_at", {
205
- mode: "timestamp"
206
- }).notNull(),
207
- updatedAt: integer("updated_at", {
208
- mode: "timestamp"
209
- }).notNull()
210
- });
211
- export const verification = sqliteTable("verification", {
212
- id: text("id").primaryKey(),
213
- identifier: text("identifier").notNull(),
214
- value: text("value").notNull(),
215
- expiresAt: integer("expires_at", {
216
- mode: "timestamp"
217
- }).notNull(),
218
- createdAt: integer("created_at", {
219
- mode: "timestamp"
220
- }),
221
- updatedAt: integer("updated_at", {
222
- mode: "timestamp"
223
- })
224
- });
@@ -1,24 +0,0 @@
1
- /**
2
- * Trans Component for Hono JSX
3
- *
4
- * Simple implementation that just renders children directly.
5
- * For complex translations with embedded JSX, use the t() function with placeholders.
6
- */ import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
7
- /**
8
- * Trans component - renders children as-is
9
- * Note: This is a simplified implementation. For translations with embedded JSX,
10
- * it's recommended to use t() with placeholders instead.
11
- *
12
- * @example
13
- * ```tsx
14
- * <Trans comment="@context: Help text">
15
- * Visit the <a href="/docs">documentation</a>
16
- * </Trans>
17
- * ```
18
- */ export const Trans = ({ children })=>{
19
- // In a full implementation, this would extract and translate the content
20
- // For now, we just render children as-is (works for English/default locale)
21
- return /*#__PURE__*/ _jsx(_Fragment, {
22
- children: children
23
- });
24
- };
@@ -1,58 +0,0 @@
1
- /**
2
- * Hono JSX i18n Context System
3
- *
4
- * Mimics React's Context API for Hono JSX to provide i18n without prop drilling
5
- */ import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
6
- import { getI18n as getI18nFromContext } from "./i18n.js";
7
- // Store i18n instance during render
8
- let currentI18n = null;
9
- export const I18nProvider = ({ c, children })=>{
10
- // Set current i18n for this render
11
- // Note: In Hono JSX, rendering is synchronous and single-threaded per request
12
- // so we can safely set global context without cleanup
13
- currentI18n = getI18nFromContext(c);
14
- return /*#__PURE__*/ _jsx(_Fragment, {
15
- children: children
16
- });
17
- };
18
- /**
19
- * useLingui hook - get i18n instance and translation function
20
- * Mimics @lingui/react's useLingui() API
21
- *
22
- * @example
23
- * ```tsx
24
- * import { t } from "@lingui/core/macro";
25
- * import { useLingui } from "../i18n/index.js";
26
- *
27
- * function MyComponent() {
28
- * const { t: _ } = useLingui(); // Use _ to avoid conflict with macro
29
- *
30
- * return (
31
- * <div>
32
- * <h1>{_(t({ message: "Dashboard", comment: "@context: Page title" }))}</h1>
33
- * </div>
34
- * );
35
- * }
36
- * ```
37
- *
38
- * Or use the i18n instance directly:
39
- * ```tsx
40
- * const { i18n } = useLingui();
41
- * i18n._(t({ message: "Dashboard", comment: "@context: Page title" }))
42
- * ```
43
- */ export function useLingui() {
44
- if (!currentI18n) {
45
- throw new Error("useLingui() called outside of I18nProvider. " + "Make sure your component is wrapped in <I18nProvider c={c}>...</I18nProvider>");
46
- }
47
- const translate = (descriptor)=>{
48
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- currentI18n is checked above
49
- return currentI18n._(descriptor);
50
- };
51
- return {
52
- i18n: currentI18n,
53
- // t function - can be used with t macro from @lingui/core/macro
54
- t: translate,
55
- // _ is an alias for t (shorter)
56
- _: translate
57
- };
58
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * Language Detection Utilities
3
- */ import { locales, isLocale } from "./locales.js";
4
- /**
5
- * Get display name for a language code
6
- */ export function getLanguageDisplayName(locale) {
7
- const names = {
8
- en: "English",
9
- "zh-Hans": "简体中文",
10
- "zh-Hant": "繁體中文"
11
- };
12
- return names[locale];
13
- }
14
- /**
15
- * Get all supported languages with display names
16
- */ export function getSupportedLanguages() {
17
- return locales.map((code)=>({
18
- code,
19
- name: getLanguageDisplayName(code)
20
- }));
21
- }
22
- /**
23
- * Check if a language code is valid
24
- */ export function isValidLanguage(lang) {
25
- return isLocale(lang);
26
- }