@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
@@ -24,6 +24,7 @@ function renderNavigationContent(
24
24
  directoryData: { collections: [], items: [], directoryItems: [] },
25
25
  suggestedLinks: [],
26
26
  mainRssFeed: "latest",
27
+ rssFeedsEnabled: true,
27
28
  siteName: "Test Site",
28
29
  ...props,
29
30
  }),
@@ -53,6 +54,28 @@ describe("NavigationContent", () => {
53
54
  expect(html).toContain("Built-in links");
54
55
  });
55
56
 
57
+ it("hides the saved RSS item only from the preview when feeds are off", () => {
58
+ const html = renderNavigationContent({
59
+ rssFeedsEnabled: false,
60
+ navItems: [
61
+ {
62
+ id: "nav_rss",
63
+ type: "system",
64
+ systemKey: "rss",
65
+ label: "Saved feed",
66
+ url: "/feed",
67
+ placement: "header",
68
+ position: "a0",
69
+ createdAt: 1,
70
+ updatedAt: 1,
71
+ },
72
+ ],
73
+ });
74
+
75
+ expect(html).not.toContain(">Saved feed<");
76
+ expect(html).toContain("&quot;systemKey&quot;:&quot;rss&quot;");
77
+ });
78
+
56
79
  it("serializes suggested links with translated target labels", () => {
57
80
  const html = renderNavigationContent({
58
81
  suggestedLinks: [
@@ -0,0 +1,578 @@
1
+ /**
2
+ * Config Editor — searchable runtime preferences with a server-rendered
3
+ * fallback and a Light DOM Lit enhancement.
4
+ */
5
+
6
+ import { msg } from "@lingui/core/macro";
7
+ import { useLingui } from "../../../i18n/context.js";
8
+ import { getOrBuildEntry } from "../../../i18n/supported-locales.js";
9
+ import { getTimeZoneOptions } from "../../../lib/timezones.js";
10
+ import { toPublicPath } from "../../../lib/url.js";
11
+ import type {
12
+ ConfigEditorFieldState,
13
+ ConfigEditorVisibleKey,
14
+ } from "../../../types.js";
15
+
16
+ interface FieldPresentation {
17
+ description: string;
18
+ optionLabels?: Record<string, string>;
19
+ }
20
+
21
+ export function ConfigEditorContent({
22
+ fields,
23
+ endpoint,
24
+ sitePathPrefix,
25
+ }: {
26
+ fields: ConfigEditorFieldState[];
27
+ endpoint: string;
28
+ sitePathPrefix: string;
29
+ }) {
30
+ const { i18n } = useLingui();
31
+
32
+ const presentations: Record<ConfigEditorVisibleKey, FieldPresentation> = {
33
+ SITE_NAME: {
34
+ description: i18n._(
35
+ msg({
36
+ message: "The title used across your site, browser tabs, and feeds.",
37
+ comment: "@context: Config Editor setting description",
38
+ }),
39
+ ),
40
+ },
41
+ SITE_DESCRIPTION: {
42
+ description: i18n._(
43
+ msg({
44
+ message:
45
+ "Edit the multi-line introduction used on your home page and in metadata.",
46
+ comment: "@context: Config Editor linked setting description",
47
+ }),
48
+ ),
49
+ },
50
+ SITE_LANGUAGE: {
51
+ description: i18n._(
52
+ msg({
53
+ message:
54
+ "Choose the content language announced to readers and search engines.",
55
+ comment: "@context: Config Editor setting description",
56
+ }),
57
+ ),
58
+ },
59
+ DASHBOARD_LANGUAGE: {
60
+ description: i18n._(
61
+ msg({
62
+ message: "The language used by the private Jant dashboard.",
63
+ comment: "@context: Config Editor setting description",
64
+ }),
65
+ ),
66
+ optionLabels: {
67
+ "": i18n._(
68
+ msg({
69
+ message: "Follow content language",
70
+ comment:
71
+ "@context: Config Editor enum option for no explicit dashboard language",
72
+ }),
73
+ ),
74
+ en: "English",
75
+ "zh-Hans": "简体中文",
76
+ "zh-Hant": "繁體中文",
77
+ },
78
+ },
79
+ CJK_SERIF_FONT: {
80
+ description: i18n._(
81
+ msg({
82
+ message:
83
+ "The serif fallback used when the content language has no built-in profile.",
84
+ comment: "@context: Config Editor setting description",
85
+ }),
86
+ ),
87
+ optionLabels: {
88
+ off: i18n._(
89
+ msg({
90
+ message: "Follow content language",
91
+ comment:
92
+ "@context: Config Editor enum option for automatic CJK font fallback",
93
+ }),
94
+ ),
95
+ "zh-Hans": "简体中文",
96
+ "zh-Hant": "繁體中文",
97
+ ja: "日本語",
98
+ ko: "한국어",
99
+ },
100
+ },
101
+ MAIN_RSS_FEED: {
102
+ description: i18n._(
103
+ msg({
104
+ message: "Which post stream the canonical /feed URL returns.",
105
+ comment: "@context: Config Editor setting description",
106
+ }),
107
+ ),
108
+ optionLabels: {
109
+ featured: i18n._(
110
+ msg({
111
+ message: "Featured posts",
112
+ comment: "@context: Config Editor RSS feed enum option",
113
+ }),
114
+ ),
115
+ latest: i18n._(
116
+ msg({
117
+ message: "Latest posts",
118
+ comment: "@context: Config Editor RSS feed enum option",
119
+ }),
120
+ ),
121
+ },
122
+ },
123
+ PAGE_SIZE: {
124
+ description: i18n._(
125
+ msg({
126
+ message: "Set the default number of items shown per page.",
127
+ comment: "@context: Config Editor setting description",
128
+ }),
129
+ ),
130
+ },
131
+ SEARCH_PAGE_SIZE: {
132
+ description: i18n._(
133
+ msg({
134
+ message:
135
+ "Set results per search page; reset to inherit the main page size.",
136
+ comment: "@context: Config Editor setting description",
137
+ }),
138
+ ),
139
+ },
140
+ ARCHIVE_PAGE_SIZE: {
141
+ description: i18n._(
142
+ msg({
143
+ message:
144
+ "Set posts per archive page; reset to inherit the main page size.",
145
+ comment: "@context: Config Editor setting description",
146
+ }),
147
+ ),
148
+ },
149
+ SUMMARY_MAX_PARAGRAPHS: {
150
+ description: i18n._(
151
+ msg({
152
+ message:
153
+ "Limit paragraphs used in automatically generated summaries.",
154
+ comment: "@context: Config Editor setting description",
155
+ }),
156
+ ),
157
+ },
158
+ SUMMARY_MAX_CHARS: {
159
+ description: i18n._(
160
+ msg({
161
+ message:
162
+ "Limit characters used in automatically generated summaries.",
163
+ comment: "@context: Config Editor setting description",
164
+ }),
165
+ ),
166
+ },
167
+ RSS_FEED_LIMIT: {
168
+ description: i18n._(
169
+ msg({
170
+ message: "Limit posts included in each RSS feed.",
171
+ comment: "@context: Config Editor setting description",
172
+ }),
173
+ ),
174
+ },
175
+ RSS_PUBLISH_DELAY_SECONDS: {
176
+ description: i18n._(
177
+ msg({
178
+ message:
179
+ "Delay new posts and replies before they appear in feeds. Use 0 to turn this off.",
180
+ comment: "@context: Config Editor setting description",
181
+ }),
182
+ ),
183
+ },
184
+ THEME: {
185
+ description: i18n._(
186
+ msg({
187
+ message: "Choose the color palette used across Jant.",
188
+ comment: "@context: Config Editor linked setting description",
189
+ }),
190
+ ),
191
+ },
192
+ CUSTOM_CSS: {
193
+ description: i18n._(
194
+ msg({
195
+ message: "Add site-wide CSS in the dedicated code editor.",
196
+ comment: "@context: Config Editor linked setting description",
197
+ }),
198
+ ),
199
+ },
200
+ CUSTOM_HEAD_HTML: {
201
+ description: i18n._(
202
+ msg({
203
+ message: "Add site-wide HTML before the closing head tag.",
204
+ comment: "@context: Config Editor linked setting description",
205
+ }),
206
+ ),
207
+ },
208
+ CUSTOM_BODY_END_HTML: {
209
+ description: i18n._(
210
+ msg({
211
+ message: "Add site-wide HTML before the closing body tag.",
212
+ comment: "@context: Config Editor linked setting description",
213
+ }),
214
+ ),
215
+ },
216
+ SITE_AVATAR: {
217
+ description: i18n._(
218
+ msg({
219
+ message: "Upload the profile image and generated site icons.",
220
+ comment: "@context: Config Editor linked setting description",
221
+ }),
222
+ ),
223
+ },
224
+ SHOW_HEADER_AVATAR: {
225
+ description: i18n._(
226
+ msg({
227
+ message: "Show the site avatar in the public header.",
228
+ comment: "@context: Config Editor linked setting description",
229
+ }),
230
+ ),
231
+ },
232
+ FONT_THEME: {
233
+ description: i18n._(
234
+ msg({
235
+ message: "Choose the typography used across Jant.",
236
+ comment: "@context: Config Editor linked setting description",
237
+ }),
238
+ ),
239
+ },
240
+ THEME_MODE: {
241
+ description: i18n._(
242
+ msg({
243
+ message:
244
+ "Follow the device appearance or force a light or dark theme.",
245
+ comment: "@context: Config Editor linked setting description",
246
+ }),
247
+ ),
248
+ },
249
+ TIME_ZONE: {
250
+ description: i18n._(
251
+ msg({
252
+ message: "Choose the time zone used to display dates and times.",
253
+ comment: "@context: Config Editor setting description",
254
+ }),
255
+ ),
256
+ },
257
+ SITE_FOOTER: {
258
+ description: i18n._(
259
+ msg({
260
+ message:
261
+ "Edit the multi-line footer rendered at the bottom of public pages.",
262
+ comment: "@context: Config Editor linked setting description",
263
+ }),
264
+ ),
265
+ },
266
+ SHOW_JANT_BRANDING_ON_HOME: {
267
+ description: i18n._(
268
+ msg({
269
+ message: "Show the Jant credit on the home page.",
270
+ comment: "@context: Config Editor setting description",
271
+ }),
272
+ ),
273
+ },
274
+ NOINDEX: {
275
+ description: i18n._(
276
+ msg({
277
+ message:
278
+ "Ask search engines not to index public pages or include them in results.",
279
+ comment: "@context: Config Editor setting description",
280
+ }),
281
+ ),
282
+ },
283
+ PUBLIC_API_ENABLED: {
284
+ description: i18n._(
285
+ msg({
286
+ message: "Allow published content to be read without an API token.",
287
+ comment: "@context: Config Editor setting description",
288
+ }),
289
+ ),
290
+ },
291
+ RSS_FEEDS_ENABLED: {
292
+ description: i18n._(
293
+ msg({
294
+ message: "Publish Atom feeds for the site, archive, and collections.",
295
+ comment: "@context: Config Editor setting description",
296
+ }),
297
+ ),
298
+ },
299
+ GITHUB_SYNC_ENABLED: {
300
+ description: i18n._(
301
+ msg({
302
+ message: "Connect or manage automatic content backups to GitHub.",
303
+ comment: "@context: Config Editor linked setting description",
304
+ }),
305
+ ),
306
+ },
307
+ GITHUB_SYNC_REPO: {
308
+ description: i18n._(
309
+ msg({
310
+ message: "Choose the repository used for content backups.",
311
+ comment: "@context: Config Editor linked setting description",
312
+ }),
313
+ ),
314
+ },
315
+ TELEGRAM_BOT_USERNAME: {
316
+ description: i18n._(
317
+ msg({
318
+ message: "Connect or manage the bot used to post from Telegram.",
319
+ comment: "@context: Config Editor linked setting description",
320
+ }),
321
+ ),
322
+ },
323
+ };
324
+
325
+ const localizedFields = fields.map((field) => {
326
+ let optionLabels = presentations[field.key].optionLabels;
327
+ if (field.key === "SITE_LANGUAGE") {
328
+ optionLabels = Object.fromEntries(
329
+ (field.options ?? []).map((option) => [
330
+ option,
331
+ `${getOrBuildEntry(option).native} — ${option}`,
332
+ ]),
333
+ );
334
+ } else if (field.key === "TIME_ZONE") {
335
+ const timeZoneLabels = new Map(
336
+ getTimeZoneOptions(field.value).map((entry) => [
337
+ entry.value,
338
+ entry.label,
339
+ ]),
340
+ );
341
+ optionLabels = Object.fromEntries(
342
+ (field.options ?? []).map((option) => [
343
+ option,
344
+ `${timeZoneLabels.get(option) ?? option} — ${option}`,
345
+ ]),
346
+ );
347
+ }
348
+
349
+ return {
350
+ ...field,
351
+ ...presentations[field.key],
352
+ ...(optionLabels && { optionLabels }),
353
+ ...(field.settingsPath && {
354
+ settingsPath: toPublicPath(field.settingsPath, sitePathPrefix),
355
+ }),
356
+ };
357
+ });
358
+ const initialData = JSON.stringify({ fields: localizedFields }).replace(
359
+ /</g,
360
+ "\\u003c",
361
+ );
362
+ const countTemplate = i18n._(
363
+ msg({
364
+ message: "{count} settings shown",
365
+ comment: "@context: Config Editor live search result count",
366
+ }),
367
+ { count: "{count}" },
368
+ );
369
+ const modifiedLabel = i18n._(
370
+ msg({
371
+ message: "Modified",
372
+ comment: "@context: Config Editor badge for a database override",
373
+ }),
374
+ );
375
+ const labels = JSON.stringify({
376
+ search: i18n._(
377
+ msg({
378
+ message: "Search settings",
379
+ comment: "@context: Config Editor search input placeholder",
380
+ }),
381
+ ),
382
+ modifiedOnly: i18n._(
383
+ msg({
384
+ message: "Show only modified",
385
+ comment: "@context: Config Editor filter checkbox",
386
+ }),
387
+ ),
388
+ modified: modifiedLabel,
389
+ locked: i18n._(
390
+ msg({
391
+ message: "Locked",
392
+ comment:
393
+ "@context: Config Editor badge for a setting locked by the runtime",
394
+ }),
395
+ ),
396
+ reset: i18n._(
397
+ msg({
398
+ message: "Reset to default",
399
+ comment: "@context: Config Editor row action",
400
+ }),
401
+ ),
402
+ saving: i18n._(
403
+ msg({
404
+ message: "Saving…",
405
+ comment: "@context: Config Editor row save status",
406
+ }),
407
+ ),
408
+ saved: i18n._(
409
+ msg({
410
+ message: "Saved",
411
+ comment: "@context: Config Editor row save status",
412
+ }),
413
+ ),
414
+ saveError: i18n._(
415
+ msg({
416
+ message: "This setting wasn't saved. Check the value and try again.",
417
+ comment: "@context: Config Editor fallback error message",
418
+ }),
419
+ ),
420
+ noMatches: i18n._(
421
+ msg({
422
+ message:
423
+ "Nothing matches this search. Try a different name or description.",
424
+ comment: "@context: Config Editor empty search state",
425
+ }),
426
+ ),
427
+ countTemplate,
428
+ openSetting: i18n._(
429
+ msg({
430
+ message: "Open setting",
431
+ comment: "@context: Config Editor link to a dedicated settings page",
432
+ }),
433
+ ),
434
+ configured: i18n._(
435
+ msg({
436
+ message: "Configured",
437
+ comment: "@context: Config Editor linked setting status",
438
+ }),
439
+ ),
440
+ notConfigured: i18n._(
441
+ msg({
442
+ message: "Not configured",
443
+ comment: "@context: Config Editor linked setting status",
444
+ }),
445
+ ),
446
+ }).replace(/</g, "\\u003c");
447
+
448
+ return (
449
+ <div class="config-editor-page">
450
+ <header class="page-intro">
451
+ <h1 class="page-intro-title">
452
+ {i18n._(
453
+ msg({
454
+ message: "Config Editor",
455
+ comment: "@context: Config Editor page title",
456
+ }),
457
+ )}
458
+ </h1>
459
+ <p class="page-intro-description">
460
+ {i18n._(
461
+ msg({
462
+ message:
463
+ "Search and edit runtime settings. Changes apply immediately; reset restores the environment or built-in default.",
464
+ comment: "@context: Config Editor page description",
465
+ }),
466
+ )}
467
+ </p>
468
+ </header>
469
+
470
+ <jant-config-editor
471
+ endpoint={endpoint}
472
+ initial-data={initialData}
473
+ labels={labels}
474
+ >
475
+ <div class="config-editor-fallback">
476
+ <div class="config-editor-toolbar">
477
+ <input
478
+ type="search"
479
+ name="config-search"
480
+ class="input config-editor-search-input"
481
+ placeholder={i18n._(
482
+ msg({
483
+ message: "Search settings",
484
+ comment: "@context: Config Editor search input placeholder",
485
+ }),
486
+ )}
487
+ disabled
488
+ />
489
+ </div>
490
+ <div class="config-editor-toolbar-meta">
491
+ <p class="config-editor-count">
492
+ {countTemplate.replace("{count}", String(localizedFields.length))}
493
+ </p>
494
+ </div>
495
+ <ul class="config-editor-list">
496
+ {localizedFields.map((field) => (
497
+ <li class="config-editor-row" key={field.key}>
498
+ <div class="config-editor-copy">
499
+ <div class="config-editor-heading">
500
+ <code class="config-editor-key" translate="no">
501
+ {field.key}
502
+ </code>
503
+ {field.modified ? (
504
+ <span class="config-editor-state config-editor-modified">
505
+ {modifiedLabel}
506
+ </span>
507
+ ) : null}
508
+ </div>
509
+ <p class="config-editor-description">{field.description}</p>
510
+ </div>
511
+ <div class="config-editor-value-column">
512
+ <div class="config-editor-control-row">
513
+ {field.mode === "link" ? (
514
+ <a
515
+ class="config-editor-open-control"
516
+ href={field.settingsPath}
517
+ >
518
+ <span class="config-editor-linked-value" translate="no">
519
+ {field.display === "configured"
520
+ ? field.value === "true"
521
+ ? i18n._(
522
+ msg({
523
+ message: "Configured",
524
+ comment:
525
+ "@context: Config Editor linked setting status",
526
+ }),
527
+ )
528
+ : i18n._(
529
+ msg({
530
+ message: "Not configured",
531
+ comment:
532
+ "@context: Config Editor linked setting status",
533
+ }),
534
+ )
535
+ : field.value}
536
+ </span>
537
+ <span class="config-editor-open-action">
538
+ {i18n._(
539
+ msg({
540
+ message: "Open setting",
541
+ comment:
542
+ "@context: Config Editor link to a dedicated settings page",
543
+ }),
544
+ )}
545
+ <svg
546
+ xmlns="http://www.w3.org/2000/svg"
547
+ width="15"
548
+ height="15"
549
+ viewBox="0 0 24 24"
550
+ fill="none"
551
+ stroke="currentColor"
552
+ stroke-width="2"
553
+ stroke-linecap="round"
554
+ stroke-linejoin="round"
555
+ aria-hidden="true"
556
+ >
557
+ <path d="M5 12h14" />
558
+ <path d="m13 6 6 6-6 6" />
559
+ </svg>
560
+ </span>
561
+ </a>
562
+ ) : (
563
+ <output class="config-editor-fallback-value">
564
+ {field.type === "enum"
565
+ ? (field.optionLabels?.[field.value] ?? field.value)
566
+ : field.value}
567
+ </output>
568
+ )}
569
+ </div>
570
+ </div>
571
+ </li>
572
+ ))}
573
+ </ul>
574
+ </div>
575
+ </jant-config-editor>
576
+ </div>
577
+ );
578
+ }
@@ -193,15 +193,16 @@ export function GeneralContent({
193
193
  ),
194
194
  cjkFont: i18n._(
195
195
  msg({
196
- message: "CJK Font",
197
- comment: "@context: Settings form field for CJK serif font selection",
196
+ message: "CJK fallback",
197
+ comment:
198
+ "@context: Settings field for a manual Chinese, Japanese, or Korean font fallback",
198
199
  }),
199
200
  ),
200
201
  cjkFontHelp: i18n._(
201
202
  msg({
202
203
  message:
203
- "Load a serif font optimized for Chinese, Japanese, or Korean content.",
204
- comment: "@context: Help text for CJK serif font selection",
204
+ "Used when the content language has no built-in font profile. Your font theme still controls serif and sans styling.",
205
+ comment: "@context: Help text for the manual CJK font fallback setting",
205
206
  }),
206
207
  ),
207
208
  timeZone: i18n._(
@@ -395,7 +396,16 @@ export function GeneralContent({
395
396
  ]).replace(/</g, "\\u003c");
396
397
 
397
398
  const cjkFontsJson = JSON.stringify([
398
- { value: "off", label: "None" },
399
+ {
400
+ value: "off",
401
+ label: i18n._(
402
+ msg({
403
+ message: "Follow content language",
404
+ comment:
405
+ "@context: CJK font fallback option that uses the configured content language",
406
+ }),
407
+ ),
408
+ },
399
409
  {
400
410
  value: "zh-Hans",
401
411
  label: "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
@@ -5,6 +5,7 @@
5
5
  import { msg } from "@lingui/core/macro";
6
6
  import { coalesceDisplayText } from "../../../lib/display-text.js";
7
7
  import { useLingui } from "../../../i18n/context.js";
8
+ import { NAVIGATION_SETTINGS_PATH } from "../../../lib/settings-paths.js";
8
9
  import { extractDomain, toPublicPath } from "../../../lib/url.js";
9
10
  import {
10
11
  SettingsDirectoryLink,
@@ -27,6 +28,7 @@ const ICONS = {
27
28
  gitBranch: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>`,
28
29
  send: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>`,
29
30
  cloud: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>`,
31
+ sliders: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="1" x2="7" y1="14" y2="14"/><line x1="9" x2="15" y1="8" y2="8"/><line x1="17" x2="23" y1="16" y2="16"/></svg>`,
30
32
  };
31
33
 
32
34
  export function SettingsRootContent({
@@ -161,7 +163,7 @@ export function SettingsRootContent({
161
163
  )}
162
164
  />
163
165
  <SettingsDirectoryLink
164
- href={toPublicPath("/settings/navigation", sitePathPrefix)}
166
+ href={toPublicPath(NAVIGATION_SETTINGS_PATH, sitePathPrefix)}
165
167
  icon={ICONS.menu}
166
168
  name={i18n._(
167
169
  msg({
@@ -317,6 +319,24 @@ export function SettingsRootContent({
317
319
  }),
318
320
  )}
319
321
  />
322
+ <SettingsDirectoryLink
323
+ href={toPublicPath("/settings/config", sitePathPrefix)}
324
+ icon={ICONS.sliders}
325
+ tone="subtle"
326
+ name={i18n._(
327
+ msg({
328
+ message: "Config Editor",
329
+ comment: "@context: Settings item — Config Editor",
330
+ }),
331
+ )}
332
+ description={i18n._(
333
+ msg({
334
+ message:
335
+ "Search runtime settings and open advanced configuration",
336
+ comment: "@context: Settings item description for Config Editor",
337
+ }),
338
+ )}
339
+ />
320
340
  </SettingsDirectorySection>
321
341
 
322
342
  <SettingsDirectorySection