@jant/core 0.6.14 → 0.6.15

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 (311) hide show
  1. package/bin/commands/import-site.js +81 -13
  2. package/bin/commands/migrate.js +55 -0
  3. package/bin/commands/posts/rebuild-html.js +184 -0
  4. package/bin/commands/site/snapshot/import.js +14 -10
  5. package/bin/lib/d1-query.js +23 -5
  6. package/bin/lib/migration-runner.js +29 -7
  7. package/bin/lib/node-database.js +32 -0
  8. package/bin/lib/site-snapshot.js +347 -46
  9. package/bin/lib/sql-export.js +1 -1
  10. package/bin/lib/thread-collection-migration.js +326 -0
  11. package/dist/app-BxCOR3Uc.js +6 -0
  12. package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
  13. package/dist/client/.vite/manifest.json +3 -3
  14. package/dist/client/_assets/client-B_eGKN5p.css +2 -0
  15. package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
  16. package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
  17. package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
  18. package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
  19. package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
  20. package/dist/index.js +3 -3
  21. package/dist/node.js +5 -5
  22. package/package.json +11 -10
  23. package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
  24. package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
  25. package/src/__tests__/export-hugo-build.test.ts +63 -4
  26. package/src/__tests__/export-import-roundtrip.test.ts +6 -4
  27. package/src/__tests__/export-service.test.ts +142 -9
  28. package/src/__tests__/helpers/app.ts +3 -0
  29. package/src/__tests__/helpers/export-fixtures.ts +2 -0
  30. package/src/__tests__/import-site-command.test.ts +65 -0
  31. package/src/__tests__/vite-config.test.ts +45 -0
  32. package/src/app.tsx +16 -3
  33. package/src/client/__tests__/collection-created-notice.test.ts +36 -0
  34. package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
  35. package/src/client/__tests__/collection-page-actions.test.ts +71 -2
  36. package/src/client/__tests__/compose-bridge.test.ts +64 -0
  37. package/src/client/__tests__/feed-video-player.test.ts +16 -1
  38. package/src/client/__tests__/footnote-rail.test.ts +472 -0
  39. package/src/client/__tests__/hydrate-partial.test.ts +3 -0
  40. package/src/client/__tests__/toast.test.ts +18 -1
  41. package/src/client/collection-created-notice.ts +66 -0
  42. package/src/client/collection-form-bridge.ts +17 -0
  43. package/src/client/collection-navigation.ts +44 -0
  44. package/src/client/collection-page-actions.ts +57 -1
  45. package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
  46. package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
  47. package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
  48. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
  49. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  50. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  51. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  52. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  53. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  54. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  55. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  56. package/src/client/components/collection-manager-types.ts +9 -3
  57. package/src/client/components/compose-types.ts +8 -0
  58. package/src/client/components/jant-collection-directory.ts +165 -30
  59. package/src/client/components/jant-command-palette.ts +2 -1
  60. package/src/client/components/jant-compose-dialog.ts +85 -21
  61. package/src/client/components/jant-compose-editor.ts +13 -7
  62. package/src/client/components/jant-compose-fullscreen.ts +24 -4
  63. package/src/client/components/jant-config-editor.ts +722 -0
  64. package/src/client/components/jant-media-lightbox.ts +100 -18
  65. package/src/client/components/jant-nav-manager.ts +1283 -54
  66. package/src/client/components/jant-post-form.ts +3 -3
  67. package/src/client/components/jant-post-menu.ts +86 -74
  68. package/src/client/components/nav-manager-types.ts +51 -2
  69. package/src/client/components/post-form-types.ts +1 -1
  70. package/src/client/compose-bridge.ts +4 -1
  71. package/src/client/feed-video-player.ts +62 -2
  72. package/src/client/footnote-rail.ts +478 -0
  73. package/src/client/hydrate-partial.ts +2 -0
  74. package/src/client/media-lightbox-events.ts +63 -0
  75. package/src/client/site-header-fragment.ts +38 -0
  76. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  77. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  78. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  79. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
  80. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  81. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  82. package/src/client/tiptap/bubble-menu.ts +88 -10
  83. package/src/client/tiptap/continuous-lists.ts +102 -0
  84. package/src/client/tiptap/create-editor.ts +3 -0
  85. package/src/client/tiptap/extensions.ts +14 -5
  86. package/src/client/tiptap/markdown-clipboard.ts +608 -11
  87. package/src/client/tiptap/slash-commands.ts +8 -1
  88. package/src/client/tiptap/structural-keymap.ts +211 -0
  89. package/src/client/tiptap/tab-indent.ts +8 -22
  90. package/src/client/tiptap/table-control-labels.ts +30 -0
  91. package/src/client/tiptap/table-controls.ts +537 -0
  92. package/src/client/tiptap/table-size-picker.ts +254 -0
  93. package/src/client/toast.ts +8 -6
  94. package/src/client-auth.ts +1 -0
  95. package/src/client.ts +1 -0
  96. package/src/db/__tests__/d1-query.test.ts +43 -1
  97. package/src/db/__tests__/migration-runner.test.ts +63 -0
  98. package/src/db/__tests__/migrations.test.ts +469 -0
  99. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  100. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  101. package/src/db/migrations/0028_superb_post.sql +2 -0
  102. package/src/db/migrations/0029_boring_magma.sql +1 -0
  103. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  104. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  105. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  106. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  107. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  108. package/src/db/migrations/meta/_journal.json +28 -0
  109. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  110. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  111. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  112. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  113. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  114. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  115. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  116. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  117. package/src/db/migrations/pg/meta/_journal.json +28 -0
  118. package/src/db/pg/schema.ts +28 -20
  119. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  120. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  121. package/src/db/schema.ts +28 -20
  122. package/src/i18n/__tests__/detect.test.ts +8 -2
  123. package/src/i18n/detect.ts +14 -5
  124. package/src/i18n/locales/public/en.po +133 -30
  125. package/src/i18n/locales/public/en.ts +1 -1
  126. package/src/i18n/locales/public/zh-Hans.po +128 -25
  127. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  128. package/src/i18n/locales/public/zh-Hant.po +128 -25
  129. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  130. package/src/i18n/locales/settings/en.po +513 -10
  131. package/src/i18n/locales/settings/en.ts +1 -1
  132. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  133. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  134. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  135. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  136. package/src/lib/__tests__/api-settings.test.ts +299 -0
  137. package/src/lib/__tests__/constants.test.ts +5 -0
  138. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  139. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  140. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  141. package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
  142. package/src/lib/__tests__/markdown.test.ts +8 -6
  143. package/src/lib/__tests__/navigation.test.ts +59 -2
  144. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  145. package/src/lib/__tests__/post-display.test.ts +64 -0
  146. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  147. package/src/lib/__tests__/summary.test.ts +82 -0
  148. package/src/lib/__tests__/timeline.test.ts +109 -70
  149. package/src/lib/__tests__/timezones.test.ts +31 -3
  150. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  151. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  152. package/src/lib/__tests__/view.test.ts +53 -0
  153. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  154. package/src/lib/api-settings.ts +239 -12
  155. package/src/lib/collection-sort.ts +7 -5
  156. package/src/lib/constants.ts +2 -0
  157. package/src/lib/feed-path.ts +25 -0
  158. package/src/lib/feed-policy.ts +64 -0
  159. package/src/lib/footnotes.ts +264 -0
  160. package/src/lib/github-sync-site-config.ts +2 -0
  161. package/src/lib/hugo-markdown.ts +10 -3
  162. package/src/lib/jant-branding.ts +1 -0
  163. package/src/lib/markdown-manager.ts +188 -5
  164. package/src/lib/markdown.ts +10 -3
  165. package/src/lib/navigation.ts +16 -3
  166. package/src/lib/post-body-html.ts +131 -0
  167. package/src/lib/post-display.ts +7 -1
  168. package/src/lib/render.tsx +9 -2
  169. package/src/lib/resolve-config.ts +100 -16
  170. package/src/lib/schemas.ts +133 -1
  171. package/src/lib/settings-paths.ts +5 -0
  172. package/src/lib/site-skill.ts +78 -0
  173. package/src/lib/summary.ts +16 -5
  174. package/src/lib/timeline.ts +98 -122
  175. package/src/lib/timezones.ts +54 -14
  176. package/src/lib/tiptap-render.ts +355 -81
  177. package/src/lib/view.ts +30 -20
  178. package/src/lib/worker-response-cache.ts +11 -18
  179. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  180. package/src/middleware/config.ts +2 -2
  181. package/src/middleware/public-content-access.ts +87 -0
  182. package/src/node/__tests__/cli-export.test.ts +14 -0
  183. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  184. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  185. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  186. package/src/node/index.ts +2 -1
  187. package/src/preset.css +16 -2
  188. package/src/routes/__tests__/compose.test.ts +51 -0
  189. package/src/routes/__tests__/site-skill.test.ts +112 -0
  190. package/src/routes/api/__tests__/collections.test.ts +91 -19
  191. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  192. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  193. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  194. package/src/routes/api/__tests__/settings.test.ts +217 -0
  195. package/src/routes/api/collections.ts +22 -26
  196. package/src/routes/api/export.ts +2 -0
  197. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  198. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  199. package/src/routes/api/internal/post-body-html.ts +43 -0
  200. package/src/routes/api/internal/sites.ts +24 -0
  201. package/src/routes/api/nav-items.ts +24 -1
  202. package/src/routes/api/posts.ts +2 -2
  203. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  204. package/src/routes/api/public/__tests__/posts.test.ts +208 -0
  205. package/src/routes/api/public/archive.ts +4 -0
  206. package/src/routes/api/public/posts.ts +41 -24
  207. package/src/routes/api/search.ts +3 -0
  208. package/src/routes/api/settings.ts +33 -0
  209. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  210. package/src/routes/compose.tsx +2 -1
  211. package/src/routes/dash/settings.tsx +45 -2
  212. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  213. package/src/routes/feed/feed.ts +23 -14
  214. package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
  215. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  216. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  217. package/src/routes/pages/__tests__/preview.test.ts +174 -0
  218. package/src/routes/pages/__tests__/search.test.ts +69 -0
  219. package/src/routes/pages/archive.tsx +27 -7
  220. package/src/routes/pages/collection.tsx +32 -19
  221. package/src/routes/pages/collections.tsx +6 -1
  222. package/src/routes/pages/page.tsx +83 -11
  223. package/src/routes/site-skill.ts +18 -0
  224. package/src/services/__tests__/auth.test.ts +7 -5
  225. package/src/services/__tests__/collection.test.ts +159 -44
  226. package/src/services/__tests__/navigation.test.ts +229 -21
  227. package/src/services/__tests__/post-timeline.test.ts +109 -30
  228. package/src/services/__tests__/post.test.ts +567 -5
  229. package/src/services/__tests__/search.test.ts +48 -1
  230. package/src/services/__tests__/settings.test.ts +8 -0
  231. package/src/services/__tests__/site-admin.test.ts +121 -1
  232. package/src/services/auth.ts +2 -2
  233. package/src/services/bootstrap.ts +3 -2
  234. package/src/services/collection.ts +226 -167
  235. package/src/services/export-theme/assets/client-site.css +1 -1
  236. package/src/services/export-theme/assets/client-site.js +46 -25
  237. package/src/services/export-theme/layouts/_default/list.html +18 -4
  238. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  239. package/src/services/export-theme/layouts/featured/list.html +5 -4
  240. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  241. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  242. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  243. package/src/services/export-theme/styles/main.css +30 -2
  244. package/src/services/export.ts +91 -80
  245. package/src/services/mcp.ts +37 -27
  246. package/src/services/media.ts +1 -1
  247. package/src/services/navigation.ts +185 -46
  248. package/src/services/post.ts +874 -271
  249. package/src/services/search.ts +8 -2
  250. package/src/services/settings.ts +2 -10
  251. package/src/services/site-admin.ts +63 -43
  252. package/src/style-cjk-jp.css +1 -1
  253. package/src/style-cjk-kr.css +1 -1
  254. package/src/style-cjk-tc.css +1 -1
  255. package/src/style-cjk.css +1 -1
  256. package/src/styles/components.css +540 -0
  257. package/src/styles/site-media.css +111 -19
  258. package/src/styles/tokens.css +14 -9
  259. package/src/styles/ui.css +829 -29
  260. package/src/types/bindings.ts +3 -0
  261. package/src/types/config.ts +243 -12
  262. package/src/types/constants.ts +22 -1
  263. package/src/types/entities.ts +9 -8
  264. package/src/types/operations.ts +18 -10
  265. package/src/types/props.ts +8 -2
  266. package/src/types/raw-assets.d.ts +5 -0
  267. package/src/types/views.ts +16 -4
  268. package/src/ui/__tests__/font-themes.test.ts +42 -30
  269. package/src/ui/compose/ComposeDialog.tsx +92 -2
  270. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  271. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  272. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  273. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  274. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  275. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  276. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  277. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  278. package/src/ui/feed/LinkCard.tsx +2 -12
  279. package/src/ui/feed/NoteCard.tsx +10 -20
  280. package/src/ui/feed/QuoteCard.tsx +2 -12
  281. package/src/ui/feed/ThreadPreview.tsx +26 -34
  282. package/src/ui/feed/TimelineFeed.tsx +2 -2
  283. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  284. package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
  285. package/src/ui/feed/thread-preview-state.ts +5 -7
  286. package/src/ui/font-themes.ts +74 -26
  287. package/src/ui/layouts/BaseLayout.tsx +22 -8
  288. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  289. package/src/ui/pages/CollectionPage.tsx +101 -22
  290. package/src/ui/pages/CollectionsPage.tsx +3 -1
  291. package/src/ui/pages/PostPage.tsx +39 -12
  292. package/src/ui/pages/SearchPage.tsx +6 -14
  293. package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
  294. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  295. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  296. package/src/ui/shared/CollectionsManager.tsx +17 -7
  297. package/src/ui/shared/DraftPreviewBar.tsx +33 -0
  298. package/src/ui/shared/HomePageBranding.tsx +2 -2
  299. package/src/ui/shared/MediaGallery.tsx +1 -0
  300. package/src/ui/shared/PostFooter.tsx +3 -1
  301. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  302. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  303. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  304. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  305. package/src/ui/shared/collection-management-labels.ts +33 -2
  306. package/src/ui/shared/navigation-labels.ts +2 -4
  307. package/src/ui/shared/post-article-attributes.ts +46 -0
  308. package/dist/app-DZDlmh7C.js +0 -6
  309. package/dist/client/_assets/client-Cevfpm8H.css +0 -2
  310. package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
  311. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
@@ -40,6 +40,8 @@ export interface Bindings {
40
40
  TIME_ZONE?: EnvBindingValue;
41
41
  SITE_FOOTER?: EnvBindingValue;
42
42
  NOINDEX?: EnvBindingValue;
43
+ PUBLIC_API_ENABLED?: EnvBindingValue;
44
+ RSS_FEEDS_ENABLED?: EnvBindingValue;
43
45
  R2_PUBLIC_URL?: EnvBindingValue;
44
46
  IMAGE_TRANSFORM_URL?: EnvBindingValue;
45
47
  DEMO_EMAIL?: EnvBindingValue;
@@ -83,6 +85,7 @@ export interface Bindings {
83
85
  SLUG_ID_LENGTH?: EnvBindingValue;
84
86
  // RSS feed
85
87
  RSS_FEED_LIMIT?: EnvBindingValue;
88
+ RSS_PUBLISH_DELAY_SECONDS?: EnvBindingValue;
86
89
  // Node runtime database URL. SQLite uses file: URLs; Postgres uses
87
90
  // postgres:/postgresql: URLs.
88
91
  DATABASE_URL?: string;
@@ -4,7 +4,48 @@
4
4
  * Single Source of Truth for all configuration fields.
5
5
  */
6
6
 
7
- import type { FeedKind } from "./constants.js";
7
+ import {
8
+ MAX_SITE_DESCRIPTION_LENGTH,
9
+ MAX_SITE_FOOTER_LENGTH,
10
+ MAX_SITE_NAME_LENGTH,
11
+ type FeedKind,
12
+ } from "./constants.js";
13
+
14
+ export type ConfigEditorOptionsSource = "contentLanguage" | "timeZone";
15
+
16
+ export type ConfigEditorDefinition =
17
+ | {
18
+ type: "boolean";
19
+ }
20
+ | {
21
+ type: "string";
22
+ maxLength?: number;
23
+ }
24
+ | {
25
+ type: "number";
26
+ min?: number;
27
+ max?: number;
28
+ step?: number;
29
+ }
30
+ | {
31
+ type: "enum";
32
+ options: readonly string[];
33
+ optionsSource?: never;
34
+ }
35
+ | {
36
+ type: "enum";
37
+ options?: never;
38
+ optionsSource: ConfigEditorOptionsSource;
39
+ };
40
+
41
+ export interface ConfigEditorLinkDefinition {
42
+ type: "boolean" | "string";
43
+ settingsPath: string;
44
+ display: "value" | "configured";
45
+ fallbackKey?: "DEFAULT_THEME" | "DEFAULT_FONT_THEME";
46
+ fallbackValue?: string;
47
+ resettable?: boolean;
48
+ }
8
49
 
9
50
  /**
10
51
  * Configuration Registry - Single Source of Truth
@@ -20,6 +61,21 @@ interface ConfigField {
20
61
  defaultValue: string;
21
62
  envOnly: boolean;
22
63
  internal?: boolean;
64
+ /** Another runtime field to inherit when this field has no own value. */
65
+ fallbackKey?: "PAGE_SIZE";
66
+ /**
67
+ * Explicitly opts a safe runtime setting into Config Editor.
68
+ *
69
+ * Omission is intentional: environment infrastructure, secrets, and
70
+ * internal state must never become editable merely because they are added
71
+ * to the broader config registry.
72
+ */
73
+ editor?: ConfigEditorDefinition;
74
+ /**
75
+ * Adds a safe dedicated-page setting to the searchable Config Editor index
76
+ * without duplicating its specialized editing workflow.
77
+ */
78
+ configEditorLink?: ConfigEditorLinkDefinition;
23
79
  /**
24
80
  * Environment variable names in resolution order.
25
81
  */
@@ -32,16 +88,31 @@ export const CONFIG_FIELDS = {
32
88
  defaultValue: "Jant",
33
89
  envOnly: false,
34
90
  envKeys: ["SITE_NAME"],
91
+ editor: { type: "string", maxLength: MAX_SITE_NAME_LENGTH },
92
+ configEditorLink: {
93
+ type: "string",
94
+ settingsPath: "/settings/general",
95
+ display: "value",
96
+ resettable: true,
97
+ },
35
98
  },
36
99
  SITE_DESCRIPTION: {
37
100
  defaultValue: "",
38
101
  envOnly: false,
39
102
  envKeys: ["SITE_DESCRIPTION"],
103
+ editor: { type: "string", maxLength: MAX_SITE_DESCRIPTION_LENGTH },
104
+ configEditorLink: {
105
+ type: "string",
106
+ settingsPath: "/settings/general",
107
+ display: "configured",
108
+ resettable: true,
109
+ },
40
110
  },
41
111
  SITE_LANGUAGE: {
42
112
  defaultValue: "en",
43
113
  envOnly: false,
44
114
  envKeys: ["SITE_LANGUAGE"],
115
+ editor: { type: "enum", optionsSource: "contentLanguage" },
45
116
  },
46
117
  // Admin dashboard UI locale. Empty means "follow the content language"
47
118
  // (resolved through the catalog fallback chain, i.e. today's behaviour).
@@ -49,16 +120,25 @@ export const CONFIG_FIELDS = {
49
120
  defaultValue: "",
50
121
  envOnly: false,
51
122
  envKeys: ["DASHBOARD_LANGUAGE"],
123
+ editor: {
124
+ type: "enum",
125
+ options: ["", "en", "zh-Hans", "zh-Hant"],
126
+ },
52
127
  },
53
128
  CJK_SERIF_FONT: {
54
129
  defaultValue: "off",
55
130
  envOnly: false,
56
131
  envKeys: ["CJK_SERIF_FONT"],
132
+ editor: {
133
+ type: "enum",
134
+ options: ["off", "zh-Hans", "zh-Hant", "ja", "ko"],
135
+ },
57
136
  },
58
137
  MAIN_RSS_FEED: {
59
138
  defaultValue: "featured",
60
139
  envOnly: false,
61
140
  envKeys: ["MAIN_RSS_FEED"],
141
+ editor: { type: "enum", options: ["featured", "latest"] },
62
142
  },
63
143
  // Environment-only (deployment/infrastructure config)
64
144
  DEFAULT_THEME: {
@@ -113,18 +193,23 @@ export const CONFIG_FIELDS = {
113
193
  },
114
194
  PAGE_SIZE: {
115
195
  defaultValue: "50",
116
- envOnly: true,
196
+ envOnly: false,
117
197
  envKeys: ["PAGE_SIZE"],
198
+ editor: { type: "number", min: 1, max: 100, step: 1 },
118
199
  },
119
200
  SEARCH_PAGE_SIZE: {
120
201
  defaultValue: "",
121
- envOnly: true,
202
+ envOnly: false,
203
+ fallbackKey: "PAGE_SIZE",
122
204
  envKeys: ["SEARCH_PAGE_SIZE"],
205
+ editor: { type: "number", min: 1, max: 100, step: 1 },
123
206
  },
124
207
  ARCHIVE_PAGE_SIZE: {
125
208
  defaultValue: "",
126
- envOnly: true,
209
+ envOnly: false,
210
+ fallbackKey: "PAGE_SIZE",
127
211
  envKeys: ["ARCHIVE_PAGE_SIZE"],
212
+ editor: { type: "number", min: 1, max: 100, step: 1 },
128
213
  },
129
214
  STORAGE_DRIVER: {
130
215
  defaultValue: "r2",
@@ -173,13 +258,15 @@ export const CONFIG_FIELDS = {
173
258
  },
174
259
  SUMMARY_MAX_PARAGRAPHS: {
175
260
  defaultValue: "5",
176
- envOnly: true,
261
+ envOnly: false,
177
262
  envKeys: ["SUMMARY_MAX_PARAGRAPHS"],
263
+ editor: { type: "number", min: 1, max: 50, step: 1 },
178
264
  },
179
265
  SUMMARY_MAX_CHARS: {
180
266
  defaultValue: "500",
181
- envOnly: true,
267
+ envOnly: false,
182
268
  envKeys: ["SUMMARY_MAX_CHARS"],
269
+ editor: { type: "number", min: 1, max: 1500, step: 1 },
183
270
  },
184
271
  SLUG_ID_LENGTH: {
185
272
  defaultValue: "5",
@@ -188,8 +275,15 @@ export const CONFIG_FIELDS = {
188
275
  },
189
276
  RSS_FEED_LIMIT: {
190
277
  defaultValue: "50",
191
- envOnly: true,
278
+ envOnly: false,
192
279
  envKeys: ["RSS_FEED_LIMIT"],
280
+ editor: { type: "number", min: 1, max: 200, step: 1 },
281
+ },
282
+ RSS_PUBLISH_DELAY_SECONDS: {
283
+ defaultValue: "300",
284
+ envOnly: false,
285
+ envKeys: ["RSS_PUBLISH_DELAY_SECONDS"],
286
+ editor: { type: "number", min: 0, max: 7200, step: 1 },
193
287
  },
194
288
 
195
289
  // Internal settings (DB-only, not configurable via env or settings UI)
@@ -197,31 +291,65 @@ export const CONFIG_FIELDS = {
197
291
  defaultValue: "",
198
292
  envOnly: false,
199
293
  internal: true,
294
+ configEditorLink: {
295
+ type: "string",
296
+ settingsPath: "/settings/color-theme",
297
+ display: "value",
298
+ fallbackKey: "DEFAULT_THEME",
299
+ resettable: true,
300
+ },
200
301
  },
201
302
  CUSTOM_CSS: {
202
303
  defaultValue: "",
203
304
  envOnly: false,
204
305
  internal: true,
306
+ configEditorLink: {
307
+ type: "string",
308
+ settingsPath: "/settings/custom-css",
309
+ display: "configured",
310
+ },
205
311
  },
206
312
  CUSTOM_HEAD_HTML: {
207
313
  defaultValue: "",
208
314
  envOnly: false,
209
315
  internal: true,
316
+ configEditorLink: {
317
+ type: "string",
318
+ settingsPath: "/settings/code-injection",
319
+ display: "configured",
320
+ },
210
321
  },
211
322
  CUSTOM_BODY_END_HTML: {
212
323
  defaultValue: "",
213
324
  envOnly: false,
214
325
  internal: true,
326
+ configEditorLink: {
327
+ type: "string",
328
+ settingsPath: "/settings/code-injection",
329
+ display: "configured",
330
+ },
215
331
  },
216
332
  SITE_AVATAR: {
217
333
  defaultValue: "",
218
334
  envOnly: false,
219
335
  internal: true,
336
+ configEditorLink: {
337
+ type: "string",
338
+ settingsPath: "/settings/avatar",
339
+ display: "configured",
340
+ },
220
341
  },
221
342
  SHOW_HEADER_AVATAR: {
222
343
  defaultValue: "",
223
344
  envOnly: false,
224
345
  internal: true,
346
+ configEditorLink: {
347
+ type: "boolean",
348
+ settingsPath: "/settings/avatar",
349
+ display: "value",
350
+ fallbackValue: "false",
351
+ resettable: true,
352
+ },
225
353
  },
226
354
  SITE_FAVICON_ICO: {
227
355
  defaultValue: "",
@@ -242,31 +370,67 @@ export const CONFIG_FIELDS = {
242
370
  defaultValue: "",
243
371
  envOnly: false,
244
372
  internal: true,
373
+ configEditorLink: {
374
+ type: "string",
375
+ settingsPath: "/settings/font-theme",
376
+ display: "value",
377
+ fallbackKey: "DEFAULT_FONT_THEME",
378
+ resettable: true,
379
+ },
245
380
  },
246
381
  THEME_MODE: {
247
382
  defaultValue: "",
248
383
  envOnly: false,
249
384
  internal: true,
385
+ configEditorLink: {
386
+ type: "string",
387
+ settingsPath: "/settings/color-theme",
388
+ display: "value",
389
+ fallbackValue: "auto",
390
+ resettable: true,
391
+ },
250
392
  },
251
393
  TIME_ZONE: {
252
394
  defaultValue: "UTC",
253
395
  envOnly: false,
254
396
  envKeys: ["TIME_ZONE"],
397
+ editor: { type: "enum", optionsSource: "timeZone" },
255
398
  },
256
399
  SITE_FOOTER: {
257
400
  defaultValue: "",
258
401
  envOnly: false,
259
402
  envKeys: ["SITE_FOOTER"],
403
+ editor: { type: "string", maxLength: MAX_SITE_FOOTER_LENGTH },
404
+ configEditorLink: {
405
+ type: "string",
406
+ settingsPath: "/settings/general",
407
+ display: "configured",
408
+ resettable: true,
409
+ },
260
410
  },
261
411
  SHOW_JANT_BRANDING_ON_HOME: {
262
- defaultValue: "",
412
+ defaultValue: "false",
263
413
  envOnly: false,
264
414
  envKeys: ["SHOW_JANT_BRANDING_ON_HOME"],
415
+ editor: { type: "boolean" },
265
416
  },
266
417
  NOINDEX: {
267
- defaultValue: "",
418
+ defaultValue: "false",
268
419
  envOnly: false,
269
420
  envKeys: ["NOINDEX"],
421
+ editor: { type: "boolean" },
422
+ },
423
+ PUBLIC_API_ENABLED: {
424
+ defaultValue: "true",
425
+ envOnly: false,
426
+ envKeys: ["PUBLIC_API_ENABLED"],
427
+ editor: { type: "boolean" },
428
+ },
429
+ RSS_FEEDS_ENABLED: {
430
+ defaultValue: "true",
431
+ envOnly: false,
432
+ envKeys: ["RSS_FEEDS_ENABLED"],
433
+ editor: { type: "boolean" },
270
434
  },
271
435
  DISCOVERY_COMPOSE_OPEN_SHORTCUT_AT: {
272
436
  defaultValue: "",
@@ -299,11 +463,21 @@ export const CONFIG_FIELDS = {
299
463
  defaultValue: "false",
300
464
  envOnly: false,
301
465
  internal: true,
466
+ configEditorLink: {
467
+ type: "boolean",
468
+ settingsPath: "/settings/github-sync",
469
+ display: "value",
470
+ },
302
471
  },
303
472
  GITHUB_SYNC_REPO: {
304
473
  defaultValue: "",
305
474
  envOnly: false,
306
475
  internal: true,
476
+ configEditorLink: {
477
+ type: "string",
478
+ settingsPath: "/settings/github-sync",
479
+ display: "configured",
480
+ },
307
481
  },
308
482
  GITHUB_SYNC_TOKEN: {
309
483
  defaultValue: "",
@@ -433,6 +607,11 @@ export const CONFIG_FIELDS = {
433
607
  defaultValue: "",
434
608
  envOnly: false,
435
609
  internal: true,
610
+ configEditorLink: {
611
+ type: "string",
612
+ settingsPath: "/settings/telegram",
613
+ display: "configured",
614
+ },
436
615
  },
437
616
  /** Per-site `secret_token` for a bring-your-own bot's webhook. */
438
617
  TELEGRAM_BOT_WEBHOOK_SECRET: {
@@ -443,6 +622,52 @@ export const CONFIG_FIELDS = {
443
622
  } as const satisfies Record<string, ConfigField>;
444
623
 
445
624
  export type ConfigKey = keyof typeof CONFIG_FIELDS;
625
+ export type ConfigEditorKey = {
626
+ [K in ConfigKey]: (typeof CONFIG_FIELDS)[K] extends {
627
+ editor: ConfigEditorDefinition;
628
+ }
629
+ ? K
630
+ : never;
631
+ }[ConfigKey];
632
+
633
+ export type ConfigEditorVisibleKey = {
634
+ [K in ConfigKey]: (typeof CONFIG_FIELDS)[K] extends
635
+ | { editor: ConfigEditorDefinition }
636
+ | { configEditorLink: ConfigEditorLinkDefinition }
637
+ ? K
638
+ : never;
639
+ }[ConfigKey];
640
+
641
+ export type ConfigEditorResettableKey = {
642
+ [K in ConfigKey]: (typeof CONFIG_FIELDS)[K] extends {
643
+ editor: ConfigEditorDefinition;
644
+ }
645
+ ? K
646
+ : (typeof CONFIG_FIELDS)[K] extends {
647
+ configEditorLink: { resettable: true };
648
+ }
649
+ ? K
650
+ : never;
651
+ }[ConfigKey];
652
+
653
+ export interface ConfigEditorFieldState {
654
+ key: ConfigEditorVisibleKey;
655
+ mode: "edit" | "link";
656
+ type: ConfigEditorDefinition["type"];
657
+ value: string;
658
+ fallbackValue: string;
659
+ modified: boolean;
660
+ locked: boolean;
661
+ maxLength?: number;
662
+ min?: number;
663
+ max?: number;
664
+ step?: number;
665
+ options?: readonly string[];
666
+ settingsPath?: string;
667
+ display?: ConfigEditorLinkDefinition["display"];
668
+ resettable?: boolean;
669
+ fallbackKey?: "PAGE_SIZE";
670
+ }
446
671
  export const THEME_MODES = ["auto", "light", "dark"] as const;
447
672
  export type ThemeMode = (typeof THEME_MODES)[number];
448
673
 
@@ -465,7 +690,7 @@ export interface AppConfig {
465
690
  * the translated catalog locales ("en", "zh-Hans", "zh-Hant").
466
691
  */
467
692
  dashboardLanguage: string;
468
- /** CJK serif font locale: "off", "zh-Hans", "zh-Hant", "ja", or "ko" */
693
+ /** Optional CJK font fallback when the content language has no profile. */
469
694
  cjkSerifFont: string;
470
695
  mainRssFeed: FeedKind;
471
696
  /** Canonical IANA timezone identifier used for date/time display. */
@@ -473,6 +698,10 @@ export interface AppConfig {
473
698
  siteFooter: string;
474
699
  showJantBrandingOnHome: boolean;
475
700
  noindex: boolean;
701
+ /** Whether published content can be read from JSON APIs without authentication. */
702
+ publicApiEnabled: boolean;
703
+ /** Whether the site publishes its Atom feed endpoints. */
704
+ rssFeedsEnabled: boolean;
476
705
 
477
706
  // Infrastructure (ENV only)
478
707
  siteUrl: string;
@@ -492,17 +721,19 @@ export interface AppConfig {
492
721
  /** Max upload file size in MB. Defaults to 500. */
493
722
  uploadMaxFileSize: number;
494
723
 
495
- // Summary extraction (ENV only)
724
+ // Summary extraction (DB > ENV > Default)
496
725
  /** Max paragraphs to include in auto-extracted summary. Defaults to 5. */
497
726
  summaryMaxParagraphs: number;
498
727
  /** Max characters to include in auto-extracted summary. Defaults to 500. */
499
728
  summaryMaxChars: number;
500
729
 
501
- // Pagination/Feed (ENV only, parsed to number)
730
+ // Pagination/feed (DB > ENV > Default)
502
731
  pageSize: number;
503
732
  searchPageSize: number;
504
733
  archivePageSize: number;
505
734
  rssFeedLimit: number;
735
+ /** Seconds a published Post waits before it can appear in Atom feeds. */
736
+ rssPublishDelaySeconds: number;
506
737
 
507
738
  // Slug (ENV only)
508
739
  /** Length of random IDs used in auto-generated slugs. Defaults to 5. */
@@ -29,7 +29,7 @@ export const COLLECTION_SORT_ORDERS = [
29
29
  ] as const;
30
30
  export type CollectionSortOrder = (typeof COLLECTION_SORT_ORDERS)[number];
31
31
 
32
- export const NAV_ITEM_TYPES = ["link", "system", "collection"] as const;
32
+ export const NAV_ITEM_TYPES = ["link", "system", "collection", "page"] as const;
33
33
  export type NavItemType = (typeof NAV_ITEM_TYPES)[number];
34
34
 
35
35
  export const NAV_ITEM_PLACEMENTS = ["header", "more"] as const;
@@ -81,6 +81,27 @@ export const SYSTEM_NAV_KEYS = {
81
81
  }
82
82
  >;
83
83
 
84
+ export interface DefaultNavigationProfile {
85
+ version: number;
86
+ systemKeys: readonly SystemNavKey[];
87
+ }
88
+
89
+ /**
90
+ * Append-only default navigation profiles. A profile is materialized during
91
+ * initial setup/provisioning and incomplete-setup recovery. Existing rows are
92
+ * never removed, updated, or reordered to match a profile.
93
+ */
94
+ export const DEFAULT_NAVIGATION_PROFILES = {
95
+ 1: {
96
+ version: 1,
97
+ systemKeys: ["featured", "collections", "archive", "rss", "settings"],
98
+ },
99
+ } as const satisfies Record<number, DefaultNavigationProfile>;
100
+
101
+ export const DEFAULT_NAVIGATION_PROFILE_VERSION = 1 as const;
102
+ export const DEFAULT_NAVIGATION_PROFILE =
103
+ DEFAULT_NAVIGATION_PROFILES[DEFAULT_NAVIGATION_PROFILE_VERSION];
104
+
84
105
  export const MAX_MEDIA_ATTACHMENTS = 20;
85
106
  export const MAX_THREAD_POSTS = 20;
86
107
  export const MAX_PINNED_POSTS = 3;
@@ -132,7 +132,7 @@ export interface Collection {
132
132
  }
133
133
 
134
134
  export interface CollectionDirectoryCollection extends Collection {
135
- postCount: number;
135
+ threadCount: number;
136
136
  recentActivityAt: number;
137
137
  }
138
138
 
@@ -166,9 +166,9 @@ export interface CollectionsDirectoryData {
166
166
  directoryItems: CollectionDirectoryEntry[];
167
167
  }
168
168
 
169
- export interface PostCollection {
169
+ export interface ThreadCollection {
170
170
  siteId: string;
171
- postId: string;
171
+ threadId: string;
172
172
  collectionId: string;
173
173
  }
174
174
 
@@ -178,6 +178,7 @@ export interface NavItem {
178
178
  type: NavItemType;
179
179
  systemKey?: SystemNavKey;
180
180
  collectionId?: string;
181
+ postId?: string;
181
182
  label: string;
182
183
  url: string;
183
184
  placement: NavItemPlacement;
@@ -228,12 +229,12 @@ export interface ApiToken {
228
229
  updatedAt: number;
229
230
  }
230
231
 
231
- /** Latest-reply context for a thread root, used in timeline display */
232
+ /** Bounded reply context for a thread root, used in timeline display. */
232
233
  export interface ThreadTimelineContext {
233
- /** Second chronological post in the thread (the first published reply). */
234
- secondReply: Post | null;
235
- /** Penultimate chronological post in the thread, when distinct from latest. */
236
- penultimateReply: Post | null;
234
+ /** Earliest published replies, in chronological order. */
235
+ leadingReplies: Post[];
236
+ /** Published replies immediately before latest, in chronological order. */
237
+ trailingReplies: Post[];
237
238
  latestReply: Post;
238
239
  totalReplyCount: number;
239
240
  }
@@ -35,17 +35,17 @@ export interface TextAttachmentContent {
35
35
  }
36
36
 
37
37
  /**
38
- * A single collection membership entry, with all the per-row metadata the
39
- * `post_collection` junction table carries. Used by the Hugo importer to
38
+ * A single Thread collection membership entry, with all the per-row metadata
39
+ * the `thread_collection` junction table carries. Used by the Hugo importer to
40
40
  * restore per-entry `createdAt` / `position` / `pinnedAt` losslessly.
41
41
  */
42
- export interface PostCollectionEntry {
42
+ export interface ThreadCollectionEntry {
43
43
  collectionId: string;
44
- /** Unix seconds — when the post was added to the collection. Defaults to now() when omitted. */
44
+ /** Unix seconds — when the Thread was added to the collection. Defaults to now() when omitted. */
45
45
  createdAt?: number;
46
46
  /** Sort position within the collection. Defaults to append-at-end when omitted. */
47
47
  position?: number;
48
- /** Unix seconds when the post was pinned in this specific collection; null means unpinned. */
48
+ /** Unix seconds when the Thread was pinned in this specific collection; null means unpinned. */
49
49
  pinnedAt?: number | null;
50
50
  }
51
51
 
@@ -79,9 +79,10 @@ export interface CreatePost {
79
79
  quoteText?: string;
80
80
  rating?: number;
81
81
  /**
82
- * Simple slug/ID list of collections to add the post to. Uses `now()` for
83
- * `createdAt` and append-at-end for `position`. For lossless import that
84
- * preserves those fields, use `collectionEntries` instead.
82
+ * Simple slug/ID list for the shared Thread collection set. Set this while
83
+ * creating a Thread root; reply creation rejects non-empty membership input.
84
+ * Uses `now()` for `createdAt` and append-at-end for `position`. For lossless
85
+ * import that preserves those fields, use `collectionEntries` instead.
85
86
  */
86
87
  collectionIds?: string[];
87
88
  /**
@@ -89,7 +90,7 @@ export interface CreatePost {
89
90
  * `collectionIds` and restores per-entry `createdAt` / `position` /
90
91
  * `pinnedAt` losslessly.
91
92
  */
92
- collectionEntries?: PostCollectionEntry[];
93
+ collectionEntries?: ThreadCollectionEntry[];
93
94
  replyToId?: string;
94
95
  quietReply?: boolean;
95
96
  publishedAt?: number;
@@ -116,7 +117,7 @@ export interface UpdatePost {
116
117
  quoteText?: string | null;
117
118
  rating?: number | null;
118
119
  collectionIds?: string[];
119
- collectionEntries?: PostCollectionEntry[];
120
+ collectionEntries?: ThreadCollectionEntry[];
120
121
  publishedAt?: number;
121
122
  attachments?: PostAttachmentInput[];
122
123
  }
@@ -142,6 +143,13 @@ export type CreateNavItem =
142
143
  url: string;
143
144
  placement?: NavItemPlacement;
144
145
  position?: string;
146
+ }
147
+ | {
148
+ type: "page";
149
+ postId: string;
150
+ label?: string;
151
+ placement?: NavItemPlacement;
152
+ position?: string;
145
153
  };
146
154
 
147
155
  export interface UpdateNavItem {
@@ -30,6 +30,8 @@ export interface HomePageProps {
30
30
  export interface PostPageProps {
31
31
  post: PostView;
32
32
  threadPosts?: PostView[];
33
+ /** Hide owner-only actions while preserving the public post rendering. */
34
+ isPreview?: boolean;
33
35
  }
34
36
 
35
37
  /** Props for the featured page component */
@@ -108,7 +110,10 @@ export interface CollectionPageProps {
108
110
  defaultSort: CollectionSortOrder;
109
111
  showRatingSort: boolean;
110
112
  isAuthenticated: boolean;
113
+ isInNavigation?: boolean;
111
114
  sitePathPrefix?: string;
115
+ /** Href for this collection selection's Atom feed when feeds are enabled. */
116
+ feedHref?: string;
112
117
  }
113
118
 
114
119
  /** Props for the collection editor page component */
@@ -123,6 +128,7 @@ export interface CollectionEditorPageProps {
123
128
  export interface CollectionsPageProps {
124
129
  items: CollectionDirectoryItem[];
125
130
  isAuthenticated: boolean;
131
+ navigationCollectionIds?: string[];
126
132
  sitePathPrefix?: string;
127
133
  siteOrigin?: string;
128
134
  }
@@ -179,8 +185,8 @@ export interface TimelineCardProps {
179
185
  /** Props for thread inline preview */
180
186
  export interface ThreadPreviewProps {
181
187
  rootPost: PostView;
182
- secondReply?: PostView;
183
- penultimateReply?: PostView;
188
+ leadingReplies: PostView[];
189
+ trailingReplies: PostView[];
184
190
  latestReply: PostView;
185
191
  totalReplyCount: number;
186
192
  }
@@ -26,6 +26,11 @@ declare module "*.js?raw" {
26
26
  export default content;
27
27
  }
28
28
 
29
+ declare module "*.md?raw" {
30
+ const content: string;
31
+ export default content;
32
+ }
33
+
29
34
  declare module "*.xml?raw" {
30
35
  const content: string;
31
36
  export default content;