@jant/core 0.3.26 → 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 (314) 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 +112 -173
  9. package/src/auth.ts +4 -1
  10. package/src/client.ts +13 -0
  11. package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
  12. package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
  13. package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
  14. package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
  15. package/src/db/schema.ts +24 -4
  16. package/src/i18n/locales/en.po +810 -385
  17. package/src/i18n/locales/en.ts +1 -1
  18. package/src/i18n/locales/zh-Hans.po +733 -522
  19. package/src/i18n/locales/zh-Hans.ts +1 -1
  20. package/src/i18n/locales/zh-Hant.po +733 -522
  21. package/src/i18n/locales/zh-Hant.ts +1 -1
  22. package/src/i18n/middleware.ts +7 -11
  23. package/src/index.ts +1 -1
  24. package/src/lib/__tests__/icons.test.ts +178 -0
  25. package/src/lib/__tests__/resolve-config.test.ts +184 -0
  26. package/src/lib/__tests__/schemas.test.ts +12 -6
  27. package/src/lib/__tests__/theme.test.ts +62 -0
  28. package/src/lib/__tests__/timezones.test.ts +1 -1
  29. package/src/lib/__tests__/url.test.ts +12 -0
  30. package/src/lib/__tests__/view.test.ts +1 -5
  31. package/src/lib/avatar-upload.ts +18 -10
  32. package/src/lib/collection-form-bridge.ts +52 -0
  33. package/src/lib/collections-reorder.ts +28 -0
  34. package/src/lib/compose-bridge.ts +251 -0
  35. package/src/lib/errors.ts +116 -0
  36. package/src/lib/excerpt.ts +1 -1
  37. package/src/lib/favicon.ts +3 -5
  38. package/src/lib/html.ts +22 -0
  39. package/src/lib/icon-catalog.ts +181 -0
  40. package/src/lib/icons.ts +202 -0
  41. package/src/lib/navigation.ts +18 -33
  42. package/src/lib/pagination.ts +3 -2
  43. package/src/lib/post-form-bridge.ts +136 -0
  44. package/src/lib/render.tsx +11 -4
  45. package/src/lib/resolve-config.ts +157 -0
  46. package/src/lib/schemas.ts +76 -12
  47. package/src/lib/settings-bridge.ts +139 -0
  48. package/src/lib/storage.ts +37 -16
  49. package/src/lib/theme.ts +5 -7
  50. package/src/lib/timeline.ts +4 -8
  51. package/src/lib/toast.ts +134 -0
  52. package/src/lib/upload.ts +71 -0
  53. package/src/lib/url.ts +9 -1
  54. package/src/lib/version.ts +16 -0
  55. package/src/lib/view.ts +9 -10
  56. package/src/middleware/__tests__/auth.test.ts +6 -28
  57. package/src/middleware/__tests__/onboarding.test.ts +1 -1
  58. package/src/middleware/auth.ts +6 -12
  59. package/src/middleware/config.ts +51 -0
  60. package/src/middleware/error-handler.ts +56 -0
  61. package/src/middleware/onboarding.ts +1 -1
  62. package/src/preset.css +6 -0
  63. package/src/routes/__tests__/compose.test.ts +104 -17
  64. package/src/routes/api/__tests__/collections.test.ts +93 -2
  65. package/src/routes/api/__tests__/posts.test.ts +2 -1
  66. package/src/routes/api/__tests__/settings.test.ts +1 -1
  67. package/src/routes/api/collections.ts +64 -68
  68. package/src/routes/api/nav-items.ts +21 -59
  69. package/src/routes/api/pages.ts +18 -46
  70. package/src/routes/api/posts.ts +64 -86
  71. package/src/routes/api/search.ts +6 -4
  72. package/src/routes/api/settings.ts +8 -24
  73. package/src/routes/api/upload.ts +55 -53
  74. package/src/routes/auth/__tests__/setup.test.ts +118 -0
  75. package/src/routes/auth/reset.tsx +17 -66
  76. package/src/routes/auth/setup.tsx +67 -11
  77. package/src/routes/auth/signin.tsx +44 -8
  78. package/src/routes/compose.tsx +194 -0
  79. package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
  80. package/src/routes/dash/__tests__/pages.test.ts +2 -2
  81. package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
  82. package/src/routes/dash/appearance.tsx +173 -0
  83. package/src/routes/dash/collections.tsx +80 -14
  84. package/src/routes/dash/index.tsx +12 -14
  85. package/src/routes/dash/media.tsx +46 -49
  86. package/src/routes/dash/pages.tsx +85 -37
  87. package/src/routes/dash/posts.tsx +60 -23
  88. package/src/routes/dash/redirects.tsx +43 -33
  89. package/src/routes/dash/settings.tsx +234 -214
  90. package/src/routes/feed/__tests__/rss.test.ts +7 -3
  91. package/src/routes/feed/rss.ts +11 -16
  92. package/src/routes/feed/sitemap.ts +15 -9
  93. package/src/routes/pages/__tests__/collections.test.ts +9 -8
  94. package/src/routes/pages/archive.tsx +2 -2
  95. package/src/routes/pages/collection.tsx +76 -9
  96. package/src/routes/pages/collections.tsx +3 -1
  97. package/src/routes/pages/featured.tsx +2 -2
  98. package/src/routes/pages/home.tsx +3 -3
  99. package/src/routes/pages/latest.tsx +2 -2
  100. package/src/routes/pages/page.tsx +2 -2
  101. package/src/routes/pages/post.tsx +2 -2
  102. package/src/routes/pages/search.tsx +2 -2
  103. package/src/services/__tests__/collection.test.ts +324 -34
  104. package/src/services/__tests__/media.test.ts +1 -1
  105. package/src/services/__tests__/page.test.ts +116 -1
  106. package/src/services/auth.ts +88 -0
  107. package/src/services/collection.ts +169 -30
  108. package/src/services/index.ts +8 -3
  109. package/src/services/media.ts +39 -12
  110. package/src/services/navigation.ts +17 -5
  111. package/src/services/page.ts +24 -4
  112. package/src/services/post.ts +87 -19
  113. package/src/services/search.ts +0 -1
  114. package/src/services/settings.ts +21 -13
  115. package/src/style.css +3 -0
  116. package/src/styles/components.css +42 -1
  117. package/src/styles/tokens.css +4 -0
  118. package/src/styles/ui.css +902 -73
  119. package/src/types/app-context.ts +25 -0
  120. package/src/types/bindings.ts +1 -0
  121. package/src/types/config.ts +60 -23
  122. package/src/types/entities.ts +12 -2
  123. package/src/types/lingui-react-macro.d.ts +3 -3
  124. package/src/types/operations.ts +2 -4
  125. package/src/types/views.ts +1 -3
  126. package/src/ui/__tests__/font-themes.test.ts +27 -8
  127. package/src/ui/color-themes.ts +1 -1
  128. package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
  129. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
  130. package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
  131. package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
  132. package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
  133. package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
  134. package/src/ui/components/collection-types.ts +45 -0
  135. package/src/ui/components/compose-types.ts +75 -0
  136. package/src/ui/components/jant-collection-form.ts +512 -0
  137. package/src/ui/components/jant-compose-dialog.ts +494 -0
  138. package/src/ui/components/jant-compose-editor.ts +799 -0
  139. package/src/ui/components/jant-post-form.ts +290 -0
  140. package/src/ui/components/jant-settings-avatar.ts +231 -0
  141. package/src/ui/components/jant-settings-general.ts +436 -0
  142. package/src/ui/components/post-form-template.ts +260 -0
  143. package/src/ui/components/post-form-types.ts +87 -0
  144. package/src/ui/components/settings-types.ts +62 -0
  145. package/src/ui/compose/ComposeDialog.tsx +141 -385
  146. package/src/ui/compose/ComposePrompt.tsx +3 -3
  147. package/src/ui/dash/PostList.tsx +55 -61
  148. package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
  149. package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
  150. package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
  151. package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
  152. package/src/ui/dash/collections/CollectionForm.tsx +130 -117
  153. package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
  154. package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
  155. package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
  156. package/src/ui/dash/index.ts +1 -1
  157. package/src/ui/dash/posts/PostForm.tsx +248 -0
  158. package/src/ui/dash/settings/AccountContent.tsx +69 -80
  159. package/src/ui/dash/settings/GeneralContent.tsx +159 -478
  160. package/src/ui/dash/settings/SettingsNav.tsx +4 -4
  161. package/src/ui/font-themes.ts +115 -32
  162. package/src/ui/layouts/BaseLayout.tsx +49 -19
  163. package/src/ui/layouts/DashLayout.tsx +14 -9
  164. package/src/ui/layouts/SiteLayout.tsx +38 -23
  165. package/src/ui/pages/CollectionPage.tsx +12 -2
  166. package/src/ui/pages/CollectionsPage.tsx +27 -27
  167. package/src/ui/pages/HomePage.tsx +15 -6
  168. package/src/ui/pages/SearchPage.tsx +1 -2
  169. package/src/ui/shared/CollectionsSidebar.tsx +59 -0
  170. package/src/ui/shared/Pagination.tsx +2 -2
  171. package/dist/app.js +0 -265
  172. package/dist/auth.js +0 -36
  173. package/dist/client.js +0 -13
  174. package/dist/db/index.js +0 -10
  175. package/dist/db/schema.js +0 -224
  176. package/dist/i18n/Trans.js +0 -24
  177. package/dist/i18n/context.js +0 -58
  178. package/dist/i18n/detect.js +0 -26
  179. package/dist/i18n/i18n.js +0 -49
  180. package/dist/i18n/index.js +0 -44
  181. package/dist/i18n/locales/en.js +0 -1
  182. package/dist/i18n/locales/zh-Hans.js +0 -1
  183. package/dist/i18n/locales/zh-Hant.js +0 -1
  184. package/dist/i18n/locales.js +0 -13
  185. package/dist/i18n/middleware.js +0 -30
  186. package/dist/lib/avatar-upload.js +0 -134
  187. package/dist/lib/config.js +0 -143
  188. package/dist/lib/constants.js +0 -50
  189. package/dist/lib/excerpt.js +0 -76
  190. package/dist/lib/favicon.js +0 -102
  191. package/dist/lib/feed.js +0 -123
  192. package/dist/lib/image-processor.js +0 -187
  193. package/dist/lib/image.js +0 -97
  194. package/dist/lib/index.js +0 -7
  195. package/dist/lib/markdown.js +0 -83
  196. package/dist/lib/media-helpers.js +0 -49
  197. package/dist/lib/media-upload.js +0 -104
  198. package/dist/lib/nav-reorder.js +0 -27
  199. package/dist/lib/navigation.js +0 -79
  200. package/dist/lib/pagination.js +0 -44
  201. package/dist/lib/render.js +0 -53
  202. package/dist/lib/schemas.js +0 -174
  203. package/dist/lib/sqid.js +0 -72
  204. package/dist/lib/sse.js +0 -218
  205. package/dist/lib/storage.js +0 -164
  206. package/dist/lib/theme.js +0 -65
  207. package/dist/lib/time.js +0 -159
  208. package/dist/lib/timeline.js +0 -95
  209. package/dist/lib/timezones.js +0 -388
  210. package/dist/lib/url.js +0 -89
  211. package/dist/lib/view.js +0 -217
  212. package/dist/middleware/auth.js +0 -52
  213. package/dist/middleware/onboarding.js +0 -41
  214. package/dist/routes/api/collections.js +0 -124
  215. package/dist/routes/api/nav-items.js +0 -104
  216. package/dist/routes/api/pages.js +0 -91
  217. package/dist/routes/api/posts.js +0 -218
  218. package/dist/routes/api/search.js +0 -48
  219. package/dist/routes/api/settings.js +0 -68
  220. package/dist/routes/api/upload.js +0 -246
  221. package/dist/routes/auth/reset.js +0 -221
  222. package/dist/routes/auth/setup.js +0 -194
  223. package/dist/routes/auth/signin.js +0 -176
  224. package/dist/routes/compose.js +0 -48
  225. package/dist/routes/dash/collections.js +0 -115
  226. package/dist/routes/dash/index.js +0 -118
  227. package/dist/routes/dash/media.js +0 -106
  228. package/dist/routes/dash/pages.js +0 -294
  229. package/dist/routes/dash/posts.js +0 -244
  230. package/dist/routes/dash/redirects.js +0 -257
  231. package/dist/routes/dash/settings.js +0 -379
  232. package/dist/routes/feed/rss.js +0 -62
  233. package/dist/routes/feed/sitemap.js +0 -49
  234. package/dist/routes/pages/archive.js +0 -62
  235. package/dist/routes/pages/collection.js +0 -34
  236. package/dist/routes/pages/collections.js +0 -28
  237. package/dist/routes/pages/featured.js +0 -36
  238. package/dist/routes/pages/home.js +0 -64
  239. package/dist/routes/pages/latest.js +0 -45
  240. package/dist/routes/pages/page.js +0 -68
  241. package/dist/routes/pages/post.js +0 -44
  242. package/dist/routes/pages/search.js +0 -54
  243. package/dist/services/collection.js +0 -109
  244. package/dist/services/index.js +0 -24
  245. package/dist/services/media.js +0 -117
  246. package/dist/services/navigation.js +0 -91
  247. package/dist/services/page.js +0 -84
  248. package/dist/services/post.js +0 -229
  249. package/dist/services/redirect.js +0 -48
  250. package/dist/services/search.js +0 -67
  251. package/dist/services/settings.js +0 -68
  252. package/dist/types/bindings.js +0 -3
  253. package/dist/types/config.js +0 -147
  254. package/dist/types/constants.js +0 -27
  255. package/dist/types/entities.js +0 -3
  256. package/dist/types/lingui-react-macro.d.js +0 -9
  257. package/dist/types/operations.js +0 -3
  258. package/dist/types/props.js +0 -3
  259. package/dist/types/sortablejs.d.js +0 -5
  260. package/dist/types/views.js +0 -5
  261. package/dist/types.js +0 -11
  262. package/dist/ui/color-themes.js +0 -268
  263. package/dist/ui/compose/ComposeDialog.js +0 -467
  264. package/dist/ui/compose/ComposePrompt.js +0 -55
  265. package/dist/ui/dash/ActionButtons.js +0 -46
  266. package/dist/ui/dash/CrudPageHeader.js +0 -22
  267. package/dist/ui/dash/DangerZone.js +0 -36
  268. package/dist/ui/dash/FormatBadge.js +0 -27
  269. package/dist/ui/dash/ListItemRow.js +0 -21
  270. package/dist/ui/dash/PageForm.js +0 -195
  271. package/dist/ui/dash/PostForm.js +0 -395
  272. package/dist/ui/dash/PostList.js +0 -83
  273. package/dist/ui/dash/StatusBadge.js +0 -46
  274. package/dist/ui/dash/collections/CollectionForm.js +0 -152
  275. package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
  276. package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
  277. package/dist/ui/dash/index.js +0 -10
  278. package/dist/ui/dash/media/MediaListContent.js +0 -166
  279. package/dist/ui/dash/media/ViewMediaContent.js +0 -212
  280. package/dist/ui/dash/pages/LinkFormContent.js +0 -130
  281. package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
  282. package/dist/ui/dash/settings/AccountContent.js +0 -209
  283. package/dist/ui/dash/settings/AppearanceContent.js +0 -259
  284. package/dist/ui/dash/settings/GeneralContent.js +0 -536
  285. package/dist/ui/dash/settings/SettingsNav.js +0 -41
  286. package/dist/ui/feed/LinkCard.js +0 -72
  287. package/dist/ui/feed/NoteCard.js +0 -58
  288. package/dist/ui/feed/QuoteCard.js +0 -63
  289. package/dist/ui/feed/ThreadPreview.js +0 -48
  290. package/dist/ui/feed/TimelineFeed.js +0 -41
  291. package/dist/ui/feed/TimelineItem.js +0 -27
  292. package/dist/ui/font-themes.js +0 -36
  293. package/dist/ui/layouts/BaseLayout.js +0 -153
  294. package/dist/ui/layouts/DashLayout.js +0 -141
  295. package/dist/ui/layouts/SiteLayout.js +0 -169
  296. package/dist/ui/pages/ArchivePage.js +0 -143
  297. package/dist/ui/pages/CollectionPage.js +0 -70
  298. package/dist/ui/pages/CollectionsPage.js +0 -76
  299. package/dist/ui/pages/FeaturedPage.js +0 -24
  300. package/dist/ui/pages/HomePage.js +0 -24
  301. package/dist/ui/pages/PostPage.js +0 -55
  302. package/dist/ui/pages/SearchPage.js +0 -122
  303. package/dist/ui/pages/SinglePage.js +0 -23
  304. package/dist/ui/shared/EmptyState.js +0 -27
  305. package/dist/ui/shared/MediaGallery.js +0 -35
  306. package/dist/ui/shared/Pagination.js +0 -195
  307. package/dist/ui/shared/ThreadView.js +0 -108
  308. package/dist/ui/shared/index.js +0 -5
  309. package/dist/vendor/datastar.js +0 -1606
  310. package/src/lib/__tests__/config.test.ts +0 -192
  311. package/src/lib/config.ts +0 -167
  312. package/src/routes/compose.ts +0 -63
  313. package/src/ui/dash/PostForm.tsx +0 -360
  314. package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
package/dist/app.js DELETED
@@ -1,265 +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 auth = createAuth(session, {
93
- secret: c.env.AUTH_SECRET,
94
- baseURL
95
- });
96
- c.set("auth", auth);
97
- }
98
- await next();
99
- });
100
- // Onboarding gate — redirect to /setup if not yet initialized
101
- app.use("*", requireOnboarding());
102
- // Theme middleware - resolve active color theme, font theme, custom CSS, and auth state
103
- app.use("*", async (c, next)=>{
104
- const [themeId, fontThemeId, customCSS, noindexValue, avatarKey] = await Promise.all([
105
- c.var.services.settings.get(SETTINGS_KEYS.THEME),
106
- c.var.services.settings.get("FONT_THEME"),
107
- c.var.services.settings.get(SETTINGS_KEYS.CUSTOM_CSS),
108
- c.var.services.settings.get("NOINDEX"),
109
- c.var.services.settings.get("SITE_AVATAR")
110
- ]);
111
- const themes = getAvailableThemes(resolvedConfig);
112
- const activeTheme = themeId ? themes.find((t)=>t.id === themeId) : undefined;
113
- // Build font override CSS variables
114
- const fontTheme = fontThemeId ? BUILTIN_FONT_THEMES.find((f)=>f.id === fontThemeId) : undefined;
115
- const fontOverrides = {};
116
- if (fontTheme) {
117
- fontOverrides["--font-body"] = fontTheme.fontFamily;
118
- }
119
- const themeStyle = buildThemeStyle(activeTheme, {
120
- ...resolvedConfig.cssVariables,
121
- ...fontOverrides
122
- });
123
- c.set("themeStyle", themeStyle);
124
- c.set("customCSS", customCSS ?? "");
125
- // Noindex
126
- c.set("noindex", noindexValue === "true");
127
- // Resolve favicon from avatar storage key
128
- if (avatarKey) {
129
- const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
130
- c.set("faviconUrl", getMediaUrl(avatarKey, publicUrl));
131
- }
132
- // Check auth state for data-authenticated attribute on <body>
133
- let isAuthenticated = false;
134
- if (c.var.auth) {
135
- try {
136
- const session = await c.var.auth.api.getSession({
137
- headers: c.req.raw.headers
138
- });
139
- isAuthenticated = !!session;
140
- } catch {
141
- // Not authenticated
142
- }
143
- }
144
- c.set("isAuthenticated", isAuthenticated);
145
- await next();
146
- });
147
- // i18n middleware
148
- app.use("*", i18nMiddleware());
149
- // Trailing slash redirect (redirect /foo/ to /foo)
150
- app.use("*", async (c, next)=>{
151
- const url = new URL(c.req.url);
152
- if (url.pathname !== "/" && url.pathname.endsWith("/")) {
153
- const newUrl = url.pathname.slice(0, -1) + url.search;
154
- return c.redirect(newUrl, 301);
155
- }
156
- await next();
157
- });
158
- // Redirect middleware
159
- app.use("*", async (c, next)=>{
160
- const path = new URL(c.req.url).pathname;
161
- // Skip redirect check for API routes and static assets
162
- if (path.startsWith("/api/") || path.startsWith("/assets/")) {
163
- return next();
164
- }
165
- const redirect = await c.var.services.redirects.getByPath(path);
166
- if (redirect) {
167
- return c.redirect(redirect.toPath, redirect.type);
168
- }
169
- await next();
170
- });
171
- // Health check
172
- app.get("/health", (c)=>c.json({
173
- status: "ok",
174
- auth: c.env.AUTH_SECRET ? "configured" : "missing",
175
- authSecretLength: c.env.AUTH_SECRET?.length ?? 0
176
- }));
177
- // Favicon routes - serve from DB settings (small files, avoids R2 round-trip)
178
- app.get("/favicon.ico", async (c)=>{
179
- const data = await c.var.services.settings.get("SITE_FAVICON_ICO");
180
- if (!data) return c.notFound();
181
- return new Response(base64ToUint8Array(data), {
182
- headers: {
183
- "Content-Type": "image/x-icon",
184
- "Cache-Control": "public, max-age=86400"
185
- }
186
- });
187
- });
188
- app.get("/apple-touch-icon.png", async (c)=>{
189
- const data = await c.var.services.settings.get("SITE_FAVICON_APPLE_TOUCH");
190
- if (!data) return c.notFound();
191
- return new Response(base64ToUint8Array(data), {
192
- headers: {
193
- "Content-Type": "image/png",
194
- "Cache-Control": "public, max-age=86400"
195
- }
196
- });
197
- });
198
- // better-auth handler
199
- app.all("/api/auth/*", async (c)=>{
200
- if (!c.var.auth) {
201
- return c.json({
202
- error: "Auth not configured. Set AUTH_SECRET."
203
- }, 500);
204
- }
205
- return c.var.auth.handler(c.req.raw);
206
- });
207
- // API Routes
208
- app.route("/api/posts", postsApiRoutes);
209
- app.route("/api/pages", pagesApiRoutes);
210
- app.route("/api/nav-items", navItemsApiRoutes);
211
- app.route("/api/collections", collectionsApiRoutes);
212
- app.route("/api/settings", settingsApiRoutes);
213
- // Auth routes
214
- app.route("/", setupRoutes);
215
- app.route("/", signinRoutes);
216
- app.route("/", resetRoutes);
217
- // Dashboard routes (protected)
218
- app.use("/dash/*", requireAuth());
219
- app.route("/dash", dashIndexRoutes);
220
- app.route("/dash/posts", dashPostsRoutes);
221
- app.route("/dash/pages", dashPagesRoutes);
222
- app.route("/dash/media", dashMediaRoutes);
223
- app.route("/dash/settings", dashSettingsRoutes);
224
- app.route("/dash/redirects", dashRedirectsRoutes);
225
- app.route("/dash/collections", dashCollectionsRoutes);
226
- // API routes
227
- app.route("/api/upload", uploadApiRoutes);
228
- app.route("/api/search", searchApiRoutes);
229
- // Media files from storage (path matches storage key: media/YYYY/MM/uuid.ext)
230
- app.get("/media/*", async (c)=>{
231
- const storage = c.var.storage;
232
- if (!storage) {
233
- return c.notFound();
234
- }
235
- // The storage key is the full path without the leading "/"
236
- const storageKey = c.req.path.slice(1);
237
- const object = await storage.get(storageKey);
238
- if (!object) {
239
- return c.notFound();
240
- }
241
- const headers = new Headers();
242
- headers.set("Content-Type", object.contentType || "application/octet-stream");
243
- headers.set("Cache-Control", "public, max-age=31536000, immutable");
244
- return new Response(object.body, {
245
- headers
246
- });
247
- });
248
- // Compose route (auth enforced in route middleware)
249
- app.route("/compose", composeRoutes);
250
- // Feed routes
251
- app.route("/feed", rssRoutes);
252
- app.route("/", sitemapRoutes);
253
- // Frontend routes
254
- app.route("/search", searchRoutes);
255
- app.route("/archive", archiveRoutes);
256
- app.route("/featured", featuredRoutes);
257
- app.route("/latest", latestRoutes);
258
- app.route("/collections", collectionsPageRoutes);
259
- app.route("/c", collectionRoutes);
260
- app.route("/p", postRoutes);
261
- app.route("/", homeRoutes);
262
- // Custom page catch-all (must be last)
263
- app.route("/", pageRoutes);
264
- return app;
265
- }
package/dist/auth.js DELETED
@@ -1,36 +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
- emailAndPassword: {
24
- enabled: true,
25
- autoSignIn: true,
26
- minPasswordLength: 8
27
- },
28
- session: {
29
- expiresIn: 3600 * 24 * 366,
30
- cookieCache: {
31
- enabled: true,
32
- maxAge: 60 * 5
33
- }
34
- }
35
- });
36
- }
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
- }