@jant/core 0.6.16 → 0.7.0

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 (508) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/import-site.js +60 -4
  4. package/bin/commands/reset-password.js +21 -8
  5. package/bin/commands/site/snapshot/export.js +1 -1
  6. package/bin/commands/site/snapshot/import.js +1 -1
  7. package/bin/lib/d1-query.js +5 -16
  8. package/bin/lib/node-env.js +40 -4
  9. package/bin/lib/site-selection.js +85 -29
  10. package/bin/lib/wrangler-cli.js +28 -2
  11. package/bin/lib/wrangler-json.js +35 -0
  12. package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
  13. package/dist/client/.vite/manifest.json +255 -90
  14. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  15. package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
  16. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  17. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  18. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  19. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  20. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  21. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  22. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  23. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  24. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  25. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  26. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  27. package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
  28. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  29. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  30. package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
  31. package/dist/client/_assets/client-GiYENVw8.css +2 -0
  32. package/dist/client/_assets/client-VnFxJN7G.js +295 -0
  33. package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
  34. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  35. package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
  36. package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
  37. package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
  38. package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
  39. package/dist/index.js +2 -5
  40. package/dist/node.js +5 -7
  41. package/package.json +3 -3
  42. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  43. package/src/__tests__/basecoat-variants.test.ts +121 -0
  44. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  45. package/src/__tests__/export-hugo-build.test.ts +260 -0
  46. package/src/__tests__/export-service.test.ts +231 -0
  47. package/src/__tests__/helpers/component-css.ts +91 -0
  48. package/src/__tests__/helpers/db.ts +33 -0
  49. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  50. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  51. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  52. package/src/app.tsx +71 -16
  53. package/src/auth.ts +18 -1
  54. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  55. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  56. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  57. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  58. package/src/client/__tests__/compose-launch.test.ts +62 -1
  59. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  60. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  61. package/src/client/__tests__/copy-field.test.ts +162 -0
  62. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  63. package/src/client/__tests__/image-processor.test.ts +324 -2
  64. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  65. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  66. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  67. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  68. package/src/client/__tests__/toast.test.ts +23 -0
  69. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  70. package/src/client/__tests__/video-processor.test.ts +181 -0
  71. package/src/client/audio-processor.ts +24 -15
  72. package/src/client/audio-waveform.ts +49 -0
  73. package/src/client/collection-dialog-host.ts +72 -0
  74. package/src/client/collection-navigation.ts +51 -16
  75. package/src/client/collection-page-actions.ts +26 -0
  76. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  77. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  78. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  79. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  80. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  81. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  82. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  83. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  84. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  85. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  86. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  87. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  88. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  89. package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
  90. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  91. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  92. package/src/client/components/collection-dialog-types.ts +49 -0
  93. package/src/client/components/collection-manager-types.ts +32 -1
  94. package/src/client/components/collection-types.ts +1 -15
  95. package/src/client/components/compose-types.ts +75 -1
  96. package/src/client/components/jant-collection-dialog.ts +722 -0
  97. package/src/client/components/jant-collection-directory.ts +567 -182
  98. package/src/client/components/jant-collection-form.ts +53 -232
  99. package/src/client/components/jant-command-palette.ts +100 -49
  100. package/src/client/components/jant-compose-dialog.ts +1486 -707
  101. package/src/client/components/jant-compose-editor.ts +673 -336
  102. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  103. package/src/client/components/jant-config-editor.ts +65 -53
  104. package/src/client/components/jant-confirm-dialog.ts +13 -11
  105. package/src/client/components/jant-locale-picker.ts +418 -0
  106. package/src/client/components/jant-media-lightbox.ts +146 -136
  107. package/src/client/components/jant-nav-manager.ts +690 -366
  108. package/src/client/components/jant-post-menu.ts +1086 -282
  109. package/src/client/components/jant-post-picker.ts +324 -0
  110. package/src/client/components/jant-repo-picker.ts +138 -106
  111. package/src/client/components/jant-settings-avatar.ts +31 -27
  112. package/src/client/components/jant-settings-general.ts +617 -543
  113. package/src/client/components/jant-settings-language.ts +1087 -0
  114. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  115. package/src/client/components/jant-text-preview.ts +94 -86
  116. package/src/client/components/nav-manager-types.ts +62 -1
  117. package/src/client/components/settings-types.ts +14 -29
  118. package/src/client/components/smart-collection-conditions.ts +285 -0
  119. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  120. package/src/client/compose-bridge.ts +214 -141
  121. package/src/client/compose-launch.ts +76 -2
  122. package/src/client/compose-shortcuts.ts +6 -8
  123. package/src/client/compose-triggers.ts +50 -0
  124. package/src/client/copy-field.ts +108 -0
  125. package/src/client/custom-url-menu.ts +60 -4
  126. package/src/client/icons.ts +14 -0
  127. package/src/client/image-plan.ts +165 -0
  128. package/src/client/image-processor.ts +195 -117
  129. package/src/client/image-worker-client.ts +45 -0
  130. package/src/client/image-worker.ts +135 -0
  131. package/src/client/lazy-entries.ts +95 -0
  132. package/src/client/lazy-slugify.ts +2 -2
  133. package/src/client/media-scroll-hint.ts +43 -2
  134. package/src/client/mediabunny.ts +29 -0
  135. package/src/client/post-picker.ts +41 -0
  136. package/src/client/runtime-paths.ts +74 -1
  137. package/src/client/settings-bridge.ts +4 -10
  138. package/src/client/site-header-nav.js +18 -6
  139. package/src/client/smart-collection-dialog-host.ts +116 -0
  140. package/src/client/smart-collection-page-actions.ts +208 -0
  141. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  142. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  143. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  144. package/src/client/tiptap/bubble-menu.ts +47 -16
  145. package/src/client/tiptap/floating-position.ts +216 -2
  146. package/src/client/tiptap/inline-image-upload.ts +7 -3
  147. package/src/client/tiptap/link-toolbar.ts +22 -9
  148. package/src/client/toast.ts +13 -0
  149. package/src/client/types/vite-worker.d.ts +12 -0
  150. package/src/client/upload-with-metadata.ts +3 -13
  151. package/src/client/video-processor.ts +351 -107
  152. package/src/client-auth.ts +14 -31
  153. package/src/client-compose.ts +19 -0
  154. package/src/client-manage.ts +13 -0
  155. package/src/client-settings.ts +14 -0
  156. package/src/client-site.ts +10 -3
  157. package/src/client.ts +4 -0
  158. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  159. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  160. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  161. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  162. package/src/db/migrations/0032_married_tomas.sql +3 -0
  163. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  164. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  165. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  166. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  167. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  168. package/src/db/migrations/meta/_journal.json +21 -0
  169. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  170. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  171. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  172. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  173. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  174. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  175. package/src/db/migrations/pg/meta/_journal.json +21 -0
  176. package/src/db/pg/schema.ts +195 -27
  177. package/src/db/post-visibility.ts +117 -0
  178. package/src/db/schema.ts +222 -27
  179. package/src/i18n/__tests__/detect.test.ts +26 -81
  180. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  181. package/src/i18n/context.tsx +12 -2
  182. package/src/i18n/detect.ts +7 -66
  183. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  184. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  185. package/src/i18n/locales/public/en.po +490 -158
  186. package/src/i18n/locales/public/en.ts +1 -1
  187. package/src/i18n/locales/public/zh-Hans.po +487 -155
  188. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  189. package/src/i18n/locales/public/zh-Hant.po +487 -155
  190. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  191. package/src/i18n/locales/settings/en.po +551 -69
  192. package/src/i18n/locales/settings/en.ts +1 -1
  193. package/src/i18n/locales/settings/zh-Hans.po +597 -115
  194. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  195. package/src/i18n/locales/settings/zh-Hant.po +610 -128
  196. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  197. package/src/i18n/locales.ts +94 -0
  198. package/src/i18n/middleware.ts +12 -2
  199. package/src/i18n/supported-locales.ts +80 -0
  200. package/src/lib/__tests__/api-settings.test.ts +11 -2
  201. package/src/lib/__tests__/asset-path.test.ts +92 -0
  202. package/src/lib/__tests__/deferred.test.ts +90 -0
  203. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  204. package/src/lib/__tests__/discover.test.ts +410 -0
  205. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  206. package/src/lib/__tests__/feed.test.ts +1526 -15
  207. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  208. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  209. package/src/lib/__tests__/http-cache.test.ts +162 -0
  210. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  211. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  212. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  213. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  214. package/src/lib/__tests__/navigation.test.ts +135 -1
  215. package/src/lib/__tests__/resolve-config.test.ts +64 -3
  216. package/src/lib/__tests__/schemas.test.ts +42 -4
  217. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  218. package/src/lib/__tests__/slug.test.ts +70 -0
  219. package/src/lib/__tests__/slugify.test.ts +85 -0
  220. package/src/lib/__tests__/storage.test.ts +32 -0
  221. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  222. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  223. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  224. package/src/lib/__tests__/url.test.ts +78 -72
  225. package/src/lib/__tests__/view-language.test.ts +413 -0
  226. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  227. package/src/lib/address.ts +57 -0
  228. package/src/lib/api-posts.ts +12 -0
  229. package/src/lib/asset-path.ts +98 -1
  230. package/src/lib/build-env.ts +16 -0
  231. package/src/lib/collection-groups.ts +5 -1
  232. package/src/lib/collection-paths.ts +3 -10
  233. package/src/lib/constants.ts +28 -3
  234. package/src/lib/copy-field.ts +36 -0
  235. package/src/lib/crypto.ts +40 -0
  236. package/src/lib/csp-builder.ts +2 -1
  237. package/src/lib/deferred.ts +79 -0
  238. package/src/lib/discover-ping.ts +141 -0
  239. package/src/lib/discover.ts +437 -0
  240. package/src/lib/env.ts +134 -0
  241. package/src/lib/errors.ts +33 -0
  242. package/src/lib/feed-policy.ts +236 -2
  243. package/src/lib/feed.ts +926 -73
  244. package/src/lib/filter-dimensions.ts +1349 -0
  245. package/src/lib/github-sync-site-config.ts +3 -0
  246. package/src/lib/hosted-signin.ts +1 -1
  247. package/src/lib/http-cache.ts +148 -0
  248. package/src/lib/hugo-markdown.ts +16 -0
  249. package/src/lib/icons.ts +9 -2
  250. package/src/lib/ids.ts +3 -0
  251. package/src/lib/jant-docs.ts +42 -0
  252. package/src/lib/lang-detect.ts +308 -0
  253. package/src/lib/media-helpers.ts +13 -1
  254. package/src/lib/navigation.ts +78 -11
  255. package/src/lib/pagination.ts +28 -24
  256. package/src/lib/per-language-surfaces.ts +67 -0
  257. package/src/lib/post-meta.ts +19 -0
  258. package/src/lib/render.tsx +55 -7
  259. package/src/lib/resolve-config.ts +53 -5
  260. package/src/lib/schemas.ts +201 -10
  261. package/src/lib/site-header-fragment.tsx +2 -1
  262. package/src/lib/site-unavailable-page.ts +47 -0
  263. package/src/lib/slug.ts +51 -7
  264. package/src/lib/slugify.ts +113 -0
  265. package/src/lib/startup-config.ts +4 -6
  266. package/src/lib/storage.ts +7 -0
  267. package/src/lib/summary.ts +50 -0
  268. package/src/lib/thread-fold.ts +161 -0
  269. package/src/lib/time.ts +32 -0
  270. package/src/lib/timeline.ts +42 -1
  271. package/src/lib/tiptap-render.ts +1 -2
  272. package/src/lib/upload.ts +16 -13
  273. package/src/lib/url.ts +139 -86
  274. package/src/lib/version.ts +10 -8
  275. package/src/lib/view-language.ts +436 -0
  276. package/src/lib/view.ts +51 -23
  277. package/src/lib/viewer-context.ts +62 -0
  278. package/src/middleware/__tests__/auth.test.ts +70 -2
  279. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  280. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  281. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  282. package/src/middleware/__tests__/session.test.ts +181 -33
  283. package/src/middleware/auth.ts +50 -31
  284. package/src/middleware/cache-control.ts +75 -10
  285. package/src/middleware/config.ts +6 -6
  286. package/src/middleware/error-handler.ts +12 -0
  287. package/src/middleware/onboarding.ts +32 -10
  288. package/src/middleware/secure-headers.ts +1 -1
  289. package/src/middleware/session.ts +96 -8
  290. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  291. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  292. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  293. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  294. package/src/node/__tests__/runtime.test.ts +37 -0
  295. package/src/node/index.ts +1 -0
  296. package/src/preset.css +76 -22
  297. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  298. package/src/routes/api/__tests__/collections.test.ts +114 -0
  299. package/src/routes/api/__tests__/discover.test.ts +222 -0
  300. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  301. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  302. package/src/routes/api/__tests__/settings.test.ts +24 -3
  303. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  304. package/src/routes/api/collections.ts +49 -6
  305. package/src/routes/api/custom-urls.ts +0 -1
  306. package/src/routes/api/discover.ts +95 -0
  307. package/src/routes/api/export.ts +3 -0
  308. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  309. package/src/routes/api/nav-items.ts +55 -8
  310. package/src/routes/api/posts.ts +159 -0
  311. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  312. package/src/routes/api/public/archive.ts +77 -81
  313. package/src/routes/api/public/posts.ts +23 -6
  314. package/src/routes/api/smart-collections.ts +132 -0
  315. package/src/routes/api/telegram.ts +2 -40
  316. package/src/routes/api/uploads.ts +1 -2
  317. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  318. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  319. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  320. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  321. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  322. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  323. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  324. package/src/routes/auth/setup.tsx +934 -186
  325. package/src/routes/auth/signin.tsx +10 -8
  326. package/src/routes/compose.tsx +5 -0
  327. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  328. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  329. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  330. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  331. package/src/routes/dash/custom-urls.tsx +1 -37
  332. package/src/routes/dash/settings.tsx +502 -41
  333. package/src/routes/discover-announce.ts +96 -0
  334. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  335. package/src/routes/feed/feed.ts +69 -40
  336. package/src/routes/feed/sitemap.ts +95 -11
  337. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  338. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  339. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  340. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  341. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  342. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  343. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  344. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  345. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  346. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  347. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  348. package/src/routes/pages/archive.tsx +471 -369
  349. package/src/routes/pages/collection.tsx +94 -96
  350. package/src/routes/pages/collections.tsx +38 -50
  351. package/src/routes/pages/featured.tsx +32 -13
  352. package/src/routes/pages/home.tsx +25 -6
  353. package/src/routes/pages/language.tsx +113 -0
  354. package/src/routes/pages/latest.tsx +26 -16
  355. package/src/routes/pages/new.tsx +3 -0
  356. package/src/routes/pages/page.tsx +178 -17
  357. package/src/routes/pages/partials.tsx +41 -0
  358. package/src/routes/pages/search.tsx +18 -3
  359. package/src/routes/pages/smart-collection.tsx +340 -0
  360. package/src/routes/pages/subscribe.tsx +182 -0
  361. package/src/runtime/__tests__/index.test.ts +19 -0
  362. package/src/runtime/__tests__/node.test.ts +129 -4
  363. package/src/runtime/cloudflare.ts +21 -2
  364. package/src/runtime/index.ts +27 -1
  365. package/src/runtime/node.ts +18 -1
  366. package/src/runtime/site.ts +95 -24
  367. package/src/services/__tests__/auth.test.ts +116 -7
  368. package/src/services/__tests__/collection.test.ts +108 -4
  369. package/src/services/__tests__/language.test.ts +495 -0
  370. package/src/services/__tests__/navigation.test.ts +213 -10
  371. package/src/services/__tests__/path.test.ts +180 -0
  372. package/src/services/__tests__/post-language.test.ts +830 -0
  373. package/src/services/__tests__/post.test.ts +46 -9
  374. package/src/services/__tests__/settings.test.ts +228 -17
  375. package/src/services/__tests__/site-admin.test.ts +5 -5
  376. package/src/services/__tests__/smart-collection.test.ts +651 -0
  377. package/src/services/about-page.ts +22 -1
  378. package/src/services/auth.ts +51 -49
  379. package/src/services/bootstrap.ts +188 -38
  380. package/src/services/collection-directory-position.ts +76 -0
  381. package/src/services/collection.ts +331 -46
  382. package/src/services/custom-url.ts +27 -22
  383. package/src/services/export-theme/assets/client-site.css +1 -1
  384. package/src/services/export-theme/assets/client-site.js +123 -123
  385. package/src/services/export-theme/layouts/_default/rss.xml +77 -1
  386. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  387. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  388. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  389. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  390. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  391. package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
  392. package/src/services/export-theme/styles/main.css +13 -1
  393. package/src/services/export.ts +278 -19
  394. package/src/services/index.ts +34 -3
  395. package/src/services/language.ts +412 -0
  396. package/src/services/mcp.ts +3 -1
  397. package/src/services/media.ts +14 -6
  398. package/src/services/navigation.ts +429 -67
  399. package/src/services/path.ts +221 -6
  400. package/src/services/post.ts +1021 -152
  401. package/src/services/search.ts +39 -2
  402. package/src/services/settings.ts +238 -43
  403. package/src/services/site-admin.ts +24 -7
  404. package/src/services/site.ts +77 -1
  405. package/src/services/smart-collection.ts +636 -0
  406. package/src/services/telegram.ts +20 -22
  407. package/src/services/upload-session.ts +1 -3
  408. package/src/style-author.css +27 -0
  409. package/src/styles/components-author.css +1622 -0
  410. package/src/styles/components.css +24 -1571
  411. package/src/styles/site-media.css +16 -2
  412. package/src/styles/tokens.css +82 -12
  413. package/src/styles/ui-author.css +7602 -0
  414. package/src/styles/ui.css +1087 -7219
  415. package/src/types/app-context.ts +7 -0
  416. package/src/types/bindings.ts +3 -0
  417. package/src/types/config.ts +94 -15
  418. package/src/types/constants.ts +188 -16
  419. package/src/types/entities.ts +87 -1
  420. package/src/types/operations.ts +64 -2
  421. package/src/types/props.ts +157 -31
  422. package/src/types/views.ts +55 -1
  423. package/src/ui/__tests__/font-themes.test.ts +52 -12
  424. package/src/ui/compose/ComposeDialog.tsx +201 -7
  425. package/src/ui/compose/ComposePrompt.tsx +1 -5
  426. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  427. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  428. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  429. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  430. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  431. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  432. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  433. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  434. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  435. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  436. package/src/ui/dash/settings/GeneralContent.tsx +275 -113
  437. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  438. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  439. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  440. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  441. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  442. package/src/ui/dash/settings/discover-copy.ts +116 -0
  443. package/src/ui/feed/LinkCard.tsx +55 -45
  444. package/src/ui/feed/NoteCard.tsx +9 -4
  445. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  446. package/src/ui/feed/QuoteCard.tsx +55 -30
  447. package/src/ui/feed/ThreadPreview.tsx +6 -2
  448. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  449. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  450. package/src/ui/feed/thread-preview-state.ts +20 -6
  451. package/src/ui/font-themes.ts +39 -29
  452. package/src/ui/layouts/BaseLayout.tsx +155 -10
  453. package/src/ui/layouts/SiteLayout.tsx +192 -34
  454. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  455. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  456. package/src/ui/pages/ArchivePage.tsx +282 -289
  457. package/src/ui/pages/CollectionPage.tsx +86 -76
  458. package/src/ui/pages/CollectionsPage.tsx +5 -0
  459. package/src/ui/pages/ComposePage.tsx +8 -0
  460. package/src/ui/pages/PostPage.tsx +47 -0
  461. package/src/ui/pages/SearchPage.tsx +3 -3
  462. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  463. package/src/ui/pages/SubscribePage.tsx +236 -0
  464. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  465. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  466. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  467. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  468. package/src/ui/shared/CollectionsManager.tsx +70 -17
  469. package/src/ui/shared/CopyField.tsx +127 -0
  470. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  471. package/src/ui/shared/MediaGallery.tsx +125 -46
  472. package/src/ui/shared/Pagination.tsx +20 -5
  473. package/src/ui/shared/PostFooter.tsx +20 -11
  474. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  475. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  476. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  477. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  478. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  479. package/src/ui/shared/archive-labels.ts +98 -0
  480. package/src/ui/shared/collection-management-labels.ts +94 -16
  481. package/src/ui/shared/navigation-labels.ts +45 -71
  482. package/src/ui/shared/post-article-attributes.ts +2 -0
  483. package/src/ui/shared/smart-collection-labels.ts +343 -0
  484. package/dist/app-C75Zc8dD.js +0 -6
  485. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  486. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  487. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  488. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  489. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  490. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  491. package/dist/env-BPYViDJJ.js +0 -281
  492. package/dist/github-api-BNF35Lkx.js +0 -176
  493. package/dist/github-app-DdiD-bC4.js +0 -275
  494. package/dist/github-sync-Bqg62IvV.js +0 -4
  495. package/dist/github-sync-HdK2EgvJ.js +0 -431
  496. package/dist/url-CbLAtlZe.js +0 -900
  497. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  498. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  499. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  500. package/src/client/collection-created-notice.ts +0 -66
  501. package/src/client/collection-form-bridge.ts +0 -191
  502. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  503. package/src/client/components/jant-post-form.ts +0 -422
  504. package/src/client/components/post-form-template.ts +0 -352
  505. package/src/client/components/post-form-types.ts +0 -96
  506. package/src/client/media-metadata.ts +0 -247
  507. package/src/client/post-form-bridge.ts +0 -193
  508. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -1 +1 @@
1
- /*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"++YsxG\":[\"讀者和搜尋引擎看到的是\"],\"+4Z6iP\":[\"先在 GitHub 上建立儲存庫 — 可以是空的。\"],\"+9JI/F\":[\"連線後會將您的網站同步到 \",[\"repo\"],\" 的預設分支,並疊加在其現有歷史之上。Jant 管理路徑外的既有檔案會保留。此操作無法復原。\"],\"+AXdXp\":[\"標籤與 URL 為必填\"],\"+K0AvT\":[\"解除連線\"],\"+wgt7C\":[\"低飽和的紅棕底,溫暖、有泥土感。\"],\"+zy2Nq\":[\"類型\"],\"/0D1Xp\":[\"編輯選集\"],\"/3H2/s\":[\"此託管網站透過 \",[\"providerLabel\"],\" 登入。請在該處管理密碼與託管存取權限。\"],\"/PXXBT\":[\"近乎純白,帶一點暖意。\"],\"/PoNoq\":[\"編輯連結\"],\"/zOUxl\":[\"連結至 Telegram 機器人的 QR 碼\"],\"05DXsb\":[\"連接或管理用於從 Telegram 發文的 Bot。\"],\"0OGSSc\":[\"頭像顯示已更新.\"],\"0UzCUX\":[\"更新您用來登入的密碼\"],\"0bdA9b\":[\"開啟 Telegram 以連線\"],\"0gECZD\":[\"想寫更完整的介紹?\"],\"0uIjy/\":[\"淺鼠尾草綠底,配同色系的綠。\"],\"1DahsC\":[\"搜尋設定\"],\"1F6Mzc\":[\"目前尚無導覽項目。請新增連結或在下方啟用系統項目。\"],\"1H7gng\":[\"後台語言\"],\"1MYU3o\":[\"無法建立頁面。請檢查資料後再試一次。\"],\"1mbBbL\":[\"手動連接\"],\"1njn7W\":[\"淺色\"],\"1qGdnL\":[\"預設主題。沉靜,適合長時間閱讀。\"],\"21mg6u\":[\"選擇一則尚未加入導覽的有標題筆記,或建立新頁面。\"],\"2C7mSG\":[\"選集連結\"],\"2DoBvq\":[\"訂閱來源\"],\"2Et8jU\":[\"設定每頁搜尋結果數;重設後繼承預設分頁大小。\"],\"2FYpfJ\":[\"更多\"],\"2Ithfh\":[\"傳送任何文字給機器人,該文字會作為筆記已發佈。\"],\"2PTjMB\":[\"我想刪除 \",[\"siteName\"]],\"2cFU6q\":[\"網站頁尾\"],\"2fPEPI\":[\"沉靜、偏冷的底色。\"],\"2lkk2l\":[\"微黃的紙底,配橄欖綠。\"],\"2oWZo7\":[\"最近一次提交\"],\"2r8pkt\":[\"無法載入頁面。請稍後再試。\"],\"2uuy4H\":[\"已透過個人存取權杖連線\"],\"2wK4BX\":[\"編輯 About 頁面\"],\"35x8eZ\":[\"顯示 \",[\"shown\"],\" 共 \",[\"total\"]],\"39QGku\":[\"開啟機器人並傳送綁定碼,之後你傳給它的任何訊息都會成為筆記。\"],\"3B0RD1\":[\"上傳個人資料圖片與產生的網站圖示。\"],\"3Cw1AI\":[\"新增選集\"],\"3VrybB\":[\"重新導向\"],\"3Yvsaz\":[\"302 (暫時)\"],\"3n0zbB\":[\"在示範模式中已停用工作階段管理。請改用共用示範工作階段。\"],\"3sYJi5\":[\"下載與 Hugo 相容的封存 — 以靜態方式託管或移轉到另一個 Jant\"],\"3vMdv3\":[\"此連結為保留連結。請選擇其他連結。\"],\"3wKq0C\":[\"無法儲存。請稍後再試。\"],\"49Bsal\":[\"Feed 設定已更新。\"],\"4J/OYU\":[\"選集已建立。\"],\"4Jge8E\":[\"目前的工作階段\"],\"4KIa+q\":[\"已下載匯出檔案。\"],\"4Q0iPK\":[\"跟隨裝置外觀或強制使用淺色或深色主題。\"],\"4cEClj\":[\"工作階段\"],\"4yyXWu\":[\"淺骨白底,配低飽和的綠。\"],\"4zGJ5E\":[\"永久刪除帳戶\"],\"5QlUIt\":[\"倉庫為空。準備連線。\"],\"5VQnR3\":[\"當你想要一個永遠不會改變的 feed URL 時,請使用這些。\"],\"5dpcN1\":[\"輸入以搜尋全部\"],\"5f1Wo9\":[\"已以 \",[\"account\"],\" 連線\"],\"5o8R81\":[\"極淺的象牙底,配淡淡的茶綠。\"],\"6ArdBh\":[\"將精選文章用於 /feed.\"],\"6DjeBT\":[\"示範網站會始終對搜尋引擎保持隱藏。\"],\"6E3aK4\":[\"管理託管\"],\"6FFB7q\":[\"使用最新的公開貼文作為 /feed。\"],\"6K1Vef\":[\"確定要永久刪除此部落格嗎?此動作無法復原。\"],\"6NpNLc\":[\"此儲存庫已有內容。\"],\"6Pa5AP\":[\"連接或管理 GitHub 內容自動備份。\"],\"6V3Ea3\":[\"已複製\"],\"6WdDG7\":[\"頁面\"],\"71WIgc\":[\"取得新代碼\"],\"71of4k\":[\"限制每個 RSS feed 中包含的貼文數。\"],\"746NHh\":[\"此部落格\"],\"7811AW\":[\"此儲存庫已在備份本網站。\"],\"7FCZPo\":[\"內容語言\"],\"7FaY4u\":[\"用法\"],\"7G9YLi\":[\"允許搜尋引擎收錄我的網站\"],\"7GISOt\":[\"儲存機器人權杖\"],\"7MZxzw\":[\"密碼已變更.\"],\"7vhWI8\":[\"新密碼\"],\"81nFIS\":[\"密碼不符。請確認兩個欄位相同。\"],\"87a/t/\":[\"標籤\"],\"89Upyo\":[\"該主題目前不可用。請選擇其他主題。\"],\"8BfEpW\":[\"託管帳號\"],\"8N/Mcp\":[\"封存篩選參數 (例如 format=note&view=list)\"],\"8T46pB\":[\"機器人權杖\"],\"8U2Z7f\":[\"新增自訂 URL\"],\"8ZsakT\":[\"密碼\"],\"8gEghq\":[\"明亮的暖白底,配苔綠點綴。\"],\"9+vGLh\":[\"自訂 CSS\"],\"9As8Nu\":[\"在 GitHub 上建立一個\"],\"9EjI4j\":[\"暖沙色\"],\"9FctRm\":[\"使用小寫字母、數字和連字號。\"],\"9J6wUO\":[\"建議新增的連結\"],\"9Lsvt5\":[\"已於 \",[\"date\"],\" 登入\"],\"9T7Cwm\":[\"重新導向、自訂路徑與網址控制\"],\"9aUyym\":[\"查看您在哪裡已登入,並撤銷舊的工作階段\"],\"9mqQnx\":[\"限制自動產生摘要所使用的段落數。\"],\"A1taO8\":[\"搜尋\"],\"A7tRC9\":[\"正規的 /feed URL 會回傳哪個文章串流。\"],\"ADTn/D\":[\"允許不使用 API 權杖讀取已發佈內容。\"],\"AELGTd\":[\"立即加入導覽,或開啟編輯器補充詳細資料。\"],\"ANzR96\":[\"設定編輯器\"],\"AeXO77\":[\"帳戶\"],\"AnY+O9\":[\"在首頁底部顯示「Build with Jant」\"],\"ApZDMk\":[\"此圖會用於您的 favicon 和 apple-touch-icon。為達最佳效果,請上傳至少 512x512 像素、背景為純色的正方形 PNG。\"],\"Aysjjh\":[\"選擇 Jant 全域使用的配色方案。\"],\"B495Gs\":[\"封存\"],\"B4ESok\":[\"API 參考\"],\"BJ0tsF\":[\"搜尋執行階段設定並開啟進階設定\"],\"BTYdze\":[\"連結已新增到導覽。\"],\"BzEFor\":[\"或\"],\"C0/57J\":[\"這是選集連結的最後一部分。\"],\"CDAdlf\":[\"移除機器人\"],\"CTAEes\":[\"選擇儲存庫\"],\"CjZZgz\":[\"此儲存庫已有提交紀錄\"],\"Cl92kR\":[\"建立新選集\"],\"D8k2s6\":[\"連接 Telegram\"],\"DCKkhU\":[\"目前密碼\"],\"DKKKeF\":[\"在 \",[\"providerLabel\"],\" 管理密碼與託管存取\"],\"DVFXa6\":[\"最乾淨、最中性的一檔。\"],\"DdXUay\":[\"在公開標頭顯示網站頭像。\"],\"EO3I6h\":[\"上傳未成功. 請稍後再試.\"],\"Eax/et\":[\"強調色\"],\"EbDLD+\":[\"選擇 Jant 全域使用的字體。\"],\"Enslfm\":[\"目標網址\"],\"F53UDu\":[\"冷白\"],\"F7FKwe\":[\"你在此處貼上的任何內容都能完全存取訪客的瀏覽器。僅使用來自你信任來源的程式碼。\"],\"F8SNqr\":[\"暖橘\"],\"FMUJSP\":[\"帳戶與資料\"],\"Fe1cvJ\":[\"延遲新貼文和回覆進入訂閱源的時間。設為 0 可關閉延遲。\"],\"Fk3SSD\":[\"平靜自然,看著舒服。\"],\"FkMol5\":[\"精選\"],\"G/1oP+\":[\"移除 webhook 並停止同步。您的儲存庫內容不會被刪除。\"],\"G0qJsQ\":[\"找不到安全權杖。請重新整理頁面後再試一次。\"],\"G0tHaW\":[\"建立一個不會出現在 Latest 的公開頁面。\"],\"G2fuEb\":[\"已鎖定\"],\"G39wnK\":[\"將內容備份並與 GitHub 儲存庫同步\"],\"GMMWcy\":[\"名稱, 中繼資料, 語言, 和 搜尋預設值\"],\"GXsAby\":[\"撤銷\"],\"GxkJXS\":[\"上傳中...\"],\"GzKzUa\":[\"試用限制\"],\"H4x7Sk\":[\"後台介面顯示的語言。可選 English、简体中文 和 繁體中文。\"],\"HCNlq3\":[\"乾淨、高對比,接近純白。\"],\"HHDyGw\":[\"柔綠\"],\"HKH+W+\":[\"資料\"],\"Hp1l6f\":[\"目前\"],\"HxlY7t\":[\"變更此設定會更新訂閱者從 /feed 取得的內容。\"],\"HxuOlm\":[\"網站頁首\"],\"I4HRxU\":[\"已發布的全部貼文,包括從 Latest 中隱藏的那些。\"],\"I6gXOa\":[\"路徑\"],\"I76CzF\":[\"中性灰\"],\"ID38tA\":[\"示範模式下帳號刪除已停用。共用示範會另行重置。\"],\"IF9tPu\":[\"何時使用網站匯出、資料庫備份與復原演練。\"],\"IW5PBo\":[\"複製權杖\"],\"IagCbF\":[\"URL\"],\"IfB3m6\":[\"你撰寫內容所用的語言。透過 HTML lang 和 RSS 向讀者與搜尋引擎宣告。支援任意 BCP 47 標籤。\"],\"IreQBq\":[\"儲存庫\"],\"J6bLeg\":[\"新增自訂連結到任何 URL\"],\"JL7LF5\":[\"可用的 CSS 變數、data 屬性與範例.\"],\"JcD7qf\":[\"更多操作\"],\"JjX0OO\":[\"請立即複製您的權杖 — 它不會再顯示。\"],\"JrFTcr\":[\"連線中…\"],\"JuN5GC\":[\"未選取檔案。請選擇要上傳的檔案。\"],\"K+0Hu0\":[\"找不到可加入的相符頁面。換個標題搜尋,或建立新頁面。\"],\"K/F6pa\":[\"儲存中…\"],\"KDw4GX\":[\"重試\"],\"KSgo21\":[\"選擇一個儲存庫\"],\"KVVYBh\":[\"新增選集到導覽\"],\"KiJn9B\":[\"筆記\"],\"Kk7jwL\":[\"設定每頁封存貼文數;重設後繼承預設分頁大小。\"],\"KwOLJF\":[\"Jant 最初的配色,日常穩妥之選。\"],\"L+rMC9\":[\"還原為預設值\"],\"L27RpE\":[\"頁面已建立。\"],\"L3DEwT\":[\"移除這個頭像?您的 favicon 與頁首圖示會回復為預設。\"],\"L4t4/q\":[\"3月14日\"],\"L86zmP\":[\"在完整表單中編輯首頁和中繼資料使用的多行簡介。\"],\"LJOxNn\":[\"CJK 字型備援\"],\"LdyooL\":[\"連結\"],\"LjwaJ/\":[\"登入、匯出和刪除\"],\"M/D8PK\":[\"+ 安裝到其他帳戶\"],\"M/haSd\":[\"永遠顯示主題的淺色版本。\"],\"M1co/O\":[\"已設定\"],\"M2kIWU\":[\"字型主題\"],\"M4tzVU\":[\"最新文章\"],\"M6CbAU\":[\"切換編輯面板\"],\"MHrjPM\":[\"標題\"],\"MKIM3K\":[\"搜尋頁面\"],\"MaYYE6\":[\"透過向 Telegram 機器人傳送訊息來發佈筆記\"],\"Me5t5H\":[\"連接 GitHub 倉庫,自動將你的文章備份為 Markdown 檔案。GitHub 上的編輯會同步回你的網站。\"],\"MnbH31\":[\"頁面\"],\"Mq9FZ1\":[\"正在建立頁面…\"],\"Mr4QPw\":[\"要斷開 Telegram?您可以隨時使用新的綁定代碼重新連接。\"],\"MtENL9\":[\"調整您的網站外觀、可讀性與執行效能。\"],\"N/8NPV\":[\"在刪除前,請先下載網站匯出檔案。刪除後無法恢復此帳號。\"],\"N7UNHY\":[\"精選 RSS 來源\"],\"NHnUHF\":[\"頁首上的網站圖示與個人標記\"],\"NU2Fqi\":[\"儲存 CSS\"],\"NVjhde\":[\"暖羊皮紙\"],\"Nldjdr\":[\"尚未有自訂 URL。建立一個自訂 URL 以為文章新增重新導向或自訂路徑。\"],\"O7rgs6\":[\"頁首 RSS 指向你的 \",[\"feed\"],\" 訂閱 (/feed)。在「一般」中變更 /feed 回傳的內容。\"],\"OJxdgi\":[\"連結不能超過 200 個字元。\"],\"OSJXFg\":[\"套用於整個網站, 包括管理頁面. 選擇一個調色盤, 然後決定它是跟隨系統還是維持固定.\"],\"OeUWA7\":[\"加入頁面\"],\"OuuMXJ\":[\"在 </body> 結束標籤之前新增全站 HTML。\"],\"Ox3+3h\":[\"無相符結果。\"],\"PEUV5I\":[\"程式碼注入已更新。\"],\"PHh52z\":[\"暖而厚重,棕調明顯。\"],\"PXj9lw\":[\"停止接受來自 Telegram 的貼文。您現有的筆記會保持已發佈。\"],\"PZ7HJ8\":[\"部落格大頭貼\"],\"Pbm2/N\":[\"建立選集\"],\"Pwqkdw\":[\"載入中…\"],\"PxJ9W6\":[\"產生權杖\"],\"Q/6Y+2\":[\"需要在目標儲存庫上擁有 Contents(讀/寫)和 Webhooks(讀/寫)權限。\"],\"Q/O0X4\":[\"此設定未儲存。請檢查設定值並再試一次。\"],\"Q30z/l\":[\"要從導覽移除這個選集嗎?選集本身不會被刪除。\"],\"Q99OtV\":[\"將選集釘選到導覽列。最近 48 小時內更新的選集旁會顯示一個 * 號。\"],\"QCwsv1\":[\"暖白\"],\"QKvrmL\":[\"暖中性,適合長讀。\"],\"QZmz0H\":[\"內建連結\"],\"Qnrzvb\":[\"已啟用的權杖\"],\"R6Z4LE\":[\"下載失敗。請再試一次。\"],\"R9Khdg\":[\"自動\"],\"RcdDOS\":[\"在 Telegram 上向 @BotFather 發送訊息以建立機器人,然後貼上它提供給你的權杖\"],\"RdVIcf\":[\"奶油配深咖\"],\"Rn2p1h\":[\"沒有任何項目符合此搜尋。請嘗試不同的名稱或描述。\"],\"RxsRD6\":[\"時區\"],\"SDND4q\":[\"未設定\"],\"SJmfuf\":[\"網站名稱\"],\"SKZhW9\":[\"權杖名稱 (API 權杖名稱欄位。)\"],\"SSsoa4\":[\"加入導覽\"],\"SVQQPe\":[\"無法連線。請檢查錯誤並再試一次。\"],\"SWb0z+\":[\"此網址為保留網址。請選擇其他網址。\"],\"SchpMp\":[\"Telegram\"],\"SqKp3o\":[\"此標題會用於整個網站、瀏覽器分頁與訂閱來源。\"],\"SrGs4T\":[\"淺藍灰底,冷靜而均勻。\"],\"T41PG1\":[\"限制自動產生摘要所使用的字元數。\"],\"TN0mN4\":[\"搜尋和編輯執行階段設定。變更會立即生效;重設會恢復環境變數或內建預設值。\"],\"TSCeuF\":[\"無法建立選集。請檢查資料後再試一次。\"],\"TpF3v+\":[\"在 </head> 之前注入。用於分析、自訂 meta 標籤,以及必須提前載入的樣式。\"],\"Tu6bMZ\":[\"建立 About 頁面\"],\"Tz0i8g\":[\"設定\"],\"U5v6Gh\":[\"編輯頁面\"],\"UFK415\":[\"用於分析與小工具的網站全域 HTML\"],\"UTvFQq\":[\"開啟 \",[\"linkOpen\"],\"@\",[\"botUsername\"],[\"linkClose\"],\" 並傳送:\"],\"UUn+Y5\":[\"開啟設定\"],\"UaZwcz\":[\"建立後可以設定更多選項。\"],\"Uj/btJ\":[\"在我的網站頁首顯示大頭貼\"],\"UsODUn\":[\"選擇一個帳戶\"],\"UxKoFf\":[\"導覽列\"],\"V+bhUy\":[\"安裝 GitHub App\"],\"V0fyg5\":[\"選集已加入導覽。\"],\"V4WsyL\":[\"新增連結\"],\"V5pZwT\":[\"搜尋設定已更新。\"],\"VXUPla\":[\"使用 GitHub App 連線\"],\"VhMDMg\":[\"變更密碼\"],\"Vn3jYy\":[\"導覽項目\"],\"VoZYGU\":[\"這會永久刪除您所有的資料 — 文章、媒體、選集、設定,以及您的帳戶。您的部落格將被重設為初始設定狀態。此操作無法復原。\"],\"WUnFK2\":[\"偏冷的淺白底,配深靛藍文字。高對比。\"],\"Wa9q4P\":[\"純白\"],\"Wb7EHo\":[\"About 頁面\"],\"Weq9zb\":[\"一般\"],\"Wi9i06\":[\"依照每位訪客的系統偏好。\"],\"Wildi8\":[\"沒有可加入的頁面。建立頁面後即可加入導覽。\"],\"Wx1M8N\":[\"安裝 GitHub App,以授予存取權而無需管理個人權杖。權限以每個儲存庫為範圍,並可在 GitHub 上撤銷。\"],\"X+8FMk\":[\"目前的密碼不符。請再試一次。\"],\"X1G9eY\":[\"導覽列預覽\"],\"X2Cbc2\":[\"封存 RSS 來源\"],\"X5P6yv\":[\"最近更新\"],\"X9Hujr\":[\"手動推送\"],\"Xsu5WL\":[\"在專用程式碼編輯器中新增全站 CSS。\"],\"XtBJV8\":[\"正在檢查儲存庫…\"],\"Xtc16w\":[\"重新整理儲存庫清單\"],\"Y+7JGK\":[\"建立頁面\"],\"Y/F35r\":[\"使用 curl 建立貼文:\"],\"Y/N5N7\":[\"柔和的奶油底,配低飽和的綠。\"],\"YF6zHf\":[\"網站設定已更新.\"],\"YdG2RF\":[\"匯出網站\"],\"YkgZi7\":[\"連接一個 Telegram 機器人,之後你傳給它的任何訊息都會以筆記形式已發佈。\"],\"YwhjRx\":[\"管理帳戶\"],\"Yxp859\":[\"暖奶油色\"],\"ZDY7Fy\":[\"同步中…\"],\"ZQKLI1\":[\"危險區域\"],\"ZS/CBL\":[\"刪除此導覽連結?訪客將不再在您的網站頁首看到它。\"],\"ZXZrAo\":[\"頁面網址不能超過 200 個字元。\"],\"ZgZX+d\":[\"純白底配中性灰。不帶任何色偏。\"],\"Zgq+c2\":[\"設定預設每頁顯示的項目數。\"],\"ZhhOwV\":[\"引用\"],\"ZiooJI\":[\"API 權杖\"],\"Zm7Qb0\":[\"備份與還原指南\"],\"ZmUkwN\":[\"新增自訂連結到導覽\"],\"a14mj8\":[\"未知裝置\"],\"a1iEgy\":[\"暖奶油底,配深咖啡棕點綴。\"],\"a3LDKx\":[\"安全性\"],\"aAIQg2\":[\"外觀\"],\"aFkzVF\":[\"目標文章或選集的 slug\"],\"aiSl4C\":[\"當內容語言沒有內建設定檔時使用的襯線備援字型。\"],\"alKG0+\":[\"字型主題\"],\"anibOb\":[\"關於本部落格\"],\"any7NR\":[\"主題指南\"],\"b+/jO6\":[\"301 (永久)\"],\"b+FyBD\":[\"將頁面加入導覽\"],\"bHOiy1\":[\"示範模式已停用變更密碼功能。請使用共用示範帳號登入。\"],\"bHYIks\":[\"登出\"],\"bV2vng\":[\"暖陶土色\"],\"bbR5vW\":[\"這套配色\"],\"bbzY7X\":[\"管理登入安全、站點匯出和無法復原的操作。\"],\"bfHZ7r\":[\"選擇用於顯示日期和時間的時區。\"],\"bi10qS\":[\"立即加入導覽,或開啟編輯器補充內容。\"],\"bmrL08\":[\"示範模式會隱藏會話、密碼更改與帳號刪除。匯出功能仍可使用。\"],\"brfpw9\":[\"在完整表單中編輯顯示在公開頁面底部的多行頁尾。\"],\"bviiKV\":[\"這個主題下的標題、正文和連結。\"],\"c3MN2z\":[\"所有可用的端點與請求格式。\"],\"cS7/bk\":[\"移除已儲存的機器人權杖?其 webhook 會被刪除,任何已連接的帳號將會被斷線。\"],\"cSDy01\":[\"自訂 CSS 已更新.\"],\"clzoNp\":[\"始終顯示深色主題。\"],\"cnGeoo\":[\"刪除\"],\"cwaLCZ\":[\"頁面已加入導覽。\"],\"d3FRkY\":[\"無法複製. 請再試一次.\"],\"d5oGUo\":[\"在 GitHub 建立新儲存庫\"],\"dEgA5A\":[\"取消\"],\"dTXUY+\":[\"確認刪除帳號\"],\"dYKrp3\":[\"從最新中隱藏 (不出現在 Latest 中,但仍可透過固定連結和集合存取的狀態。)\"],\"dk7TCH\":[\"永久刪除所有資料並重設部落格\"],\"ds0nJk\":[\"在 head 標籤的閉合標記之前加入全站 HTML。\"],\"dsWkIw\":[\"要與 GitHub 斷開連線嗎? webhook 將會被移除。您的儲存庫內容不會被刪除。\"],\"e/tSI5\":[\"導覽順序已更新。\"],\"eOL7vn\":[\"色彩極少,干擾最小。\"],\"ePK91l\":[\"編輯\"],\"ebQKK7\":[\"網站\"],\"egK+Yy\":[\"供腳本與自動化使用的 Bearer 權杖\"],\"ehj/zN\":[\"重新導向類型\"],\"eneWvv\":[\"草稿\"],\"erTMh7\":[\"上次同步\"],\"f+m8jj\":[\"已複製訂閱網址。\"],\"f8fH8W\":[\"設計\"],\"fDz6PV\":[\"選擇用於內容備份的儲存庫。\"],\"fKRAwQ\":[\"沙色底,配綠色點綴。\"],\"fWYqkz\":[\"程式碼注入\"],\"fYXQnC\":[\"暖色裡最濃的一檔,溫馨。\"],\"fttd2R\":[\"我的選集\"],\"gZ5owP\":[\"搜尋儲存庫\"],\"gbqbh6\":[\"可以放心離開此頁面 — 同步會在背景繼續進行。\"],\"gkFvVN\":[\"在 </body> 之前注入。用於聊天小工具和不應阻塞頁面載入的腳本。\"],\"gtQsRO\":[\"建立自訂網址\"],\"hBO/y4\":[\"安全權杖已過期。請重新整理頁面並再試一次。\"],\"hGmyDl\":[\"權杖讓您從腳本, 捷徑和其他工具存取 API 無需登入\"],\"hIHkRy\":[\"已透過 GitHub 應用程式連線\"],\"hJHHsU\":[\"發佈網站、封存與選集的 Atom feeds。\"],\"hdSi1b\":[\"輸入 \",[\"repo\"],\" 以確認\"],\"he3ygx\":[\"複製\"],\"hvGGMK\":[\"從導覽中移除此頁面?頁面本身不會被刪除。\"],\"i0qMbr\":[\"首頁\"],\"iEUzMn\":[\"系統\"],\"iH8pgl\":[\"返回\"],\"iSLIjg\":[\"連接\"],\"iVOMRi\":[\"首頁設定已更新。\"],\"icB4Cv\":[\"將連結拖到此處以顯示於「更多」選單下方\"],\"id3vuh\":[\"已設定 Telegram,但無法連線到機器人。請檢查機器人權杖並再試一次。\"],\"idD8Ev\":[\"已儲存\"],\"ihn4zD\":[\"搜尋…\"],\"iiDXZc\":[\"顯示於所有文章與頁面的底部。\"],\"itS31B\":[\"更暖一些,像略舊的紙。\"],\"iwQZvS\":[\"冷藍灰\"],\"j4VrG6\":[\"下載匯出 ZIP\"],\"j5nQL2\":[\"例如 iOS 捷徑\"],\"jUV7CU\":[\"上傳大頭貼\"],\"jVUmOK\":[\"支援 Markdown\"],\"jdVYS8\":[\"選一個適合你文字的。\"],\"jgBjXJ\":[\"要撤銷這個權杖嗎?任何使用它的腳本都會停止運作。\"],\"jpctdh\":[\"檢視\"],\"k1ifdL\":[\"處理中...\"],\"kLw03Y\":[\"亮紙白\"],\"kMXclu\":[\"下載網站匯出檔案\"],\"kNiQp6\":[\"已釘選\"],\"kQ3Otm\":[\"建立新頁面\"],\"kRhzWq\":[\"GitHub 同步\"],\"kVQs7s\":[\"細緻的樣式覆寫\"],\"ke1gWS\":[\"自訂 URL\"],\"kfcRb0\":[\"頭像\"],\"kp8wiR\":[\"正在檢查網址…\"],\"kxDZ2i\":[\"此程式碼會在您網站的每個頁面上執行。\"],\"l2Op2p\":[\"查詢參數\"],\"lLW3vJ\":[\"目標 slug\"],\"lV04bQ\":[\"溫暖、有泥土感,但不顯暗。\"],\"lYHJih\":[\"撤銷此工作階段?該裝置將需要重新登入。\"],\"m16xKo\":[\"新增\"],\"mLOk1i\":[\"立即將所有文章推送到 GitHub,而不是等待下一次自動同步。\"],\"mSNmrX\":[\"列出貼文:\"],\"n8+EXe\":[\"新增網站中已經存在的常用入口。\"],\"nG2qTk\":[\"示例連結\"],\"nK07ni\":[\"為您的網站選擇一種排版風格。每個主題會同時改變字體配對與閱讀節奏。\"],\"nbfdhU\":[\"第三方整合\"],\"ntJYyh\":[\"在 \",[\"providerLabel\"],\" 管理網域、方案和計費\"],\"o/vNDE\":[\"讓您覆寫任何主題變數。\"],\"o5rj+L\":[\"目前還沒有選集。在這裡建立一個並加入導覽。\"],\"oGC9uP\":[\"owner/repo\"],\"oH2JHg\":[\"我們會預先填入名稱 \",[\"name\"],\"。返回時清單會重新整理。\"],\"oKOOsY\":[\"色彩主題\"],\"oL535e\":[\"尚未同步\"],\"oNA4If\":[\"所有選集已經在您的導覽中。\"],\"oUn9Z7\":[\"近中性的淺灰,配鋼藍點綴。\"],\"ofdy2l\":[\"帶橘調的底色。最暖的一套。\"],\"pNXxtS\":[\"選擇要向讀者與搜尋引擎宣告的內容語言。\"],\"pZq3aX\":[\"上傳失敗。請再試一次。\"],\"pgTIrt\":[\"選擇要與此網站同步的 GitHub 帳號和儲存庫.\"],\"psoxDF\":[\"該字型主題無法使用. 請選擇其他主題.\"],\"pt4OhQ\":[\"/about 已被佔用。重新命名該項目後再建立 About 頁面。\"],\"pvnfJD\":[\"深色\"],\"q+hNag\":[\"選集\"],\"q/T5GS\":[\"私人 Jant 儀表板使用的語言。\"],\"qSgO/Y\":[\"跟隨內容語言\"],\"qdcESc\":[\"建立新儲存庫\"],\"qkSJzV\":[\"正在搜尋頁面…\"],\"r5EW6f\":[\"此儲存庫已在備份另一個 Jant 網站 (\",[\"host\"],\"). 請選擇其他儲存庫.\"],\"rEspiY\":[\"導覽位置已更新。\"],\"rFmBG3\":[\"色彩主題\"],\"re0mF0\":[\"頁面是公開的,但不會出現在 Latest。\"],\"rlonmB\":[\"無法刪除。請稍後再試。\"],\"s3jOk7\":[\"請輸入頁面標題。\"],\"satWc6\":[\"主要 RSS 訂閱\"],\"sgr2wQ\":[\"選集\"],\"sj4LLc\":[\"僅顯示已修改項目\"],\"slujBW\":[\"只能使用小寫字母、數字和連字號。\"],\"soRdOu\":[\"米白紙底,配深森綠。\"],\"sqxcaY\":[\"建立於 \",[\"date\"]],\"sxkWRg\":[\"進階\"],\"t/YqKh\":[\"移除\"],\"t3hvHq\":[\"立即同步\"],\"tJ4H0O\":[\"您的 Telegram 帳號\"],\"tfDRzk\":[\"儲存\"],\"tgWuMB\":[\"已修改\"],\"tvgAq5\":[\"尚未授權任何帳戶\"],\"u1VTd3\":[\"調色盤、表面色調與整體氛圍\"],\"u3wRF+\":[\"已發佈\"],\"u6KOjV\":[\"想要更細緻的控制?\"],\"uKLe0J\":[[\"count\"],\" 個設定已顯示\"],\"uVZcIA\":[\"此網址已被使用。請選擇其他網址。\"],\"udPwLB\":[\"頁首\"],\"ui6aMF\":[\"這些裝置目前已登入您的帳號。撤銷任何您不認識的工作階段。\"],\"vBEKwo\":[\"在此管理本網站的活動工作階段。密碼與託管存取由 \",[\"providerLabel\"],\" 管理。\"],\"vOnuOa\":[\"在頁首和更多選單中顯示或隱藏內建目的地。\"],\"vRldcl\":[\"字體選擇與閱讀質感\"],\"vSYKYI\":[\"主要訂閱來源\"],\"vTuib7\":[\"這會控制 /feed 回傳的內容。\"],\"vdFnYM\":[\"重設連結\"],\"vmQmHx\":[\"新增自訂 CSS 以覆寫任何樣式。使用像 [data-page]、[data-post]、[data-format] 這類資料屬性來選取特定元素。\"],\"vzX5FB\":[\"刪除帳號\"],\"w8Rv8T\":[\"標籤為必填\"],\"wL3cK8\":[\"最新\"],\"wLSxGY\":[\"當內容語言沒有內建字型設定時使用。字型主題仍會決定襯線與無襯線樣式。\"],\"wW6NCp\":[\"上次錯誤\"],\"wc+17X\":[\"/* 在此放入您的自訂 CSS */\"],\"wuLtXn\":[\"目前沒有任何活動中的工作階段。已登入的裝置會顯示在此處。\"],\"x+HGBk\":[\"要求搜尋引擎不要索引公開頁面或將它們包含在搜尋結果中。\"],\"xCWek4\":[\"檔案儲存尚未設定。請檢查您的伺服器設定。\"],\"xHt036\":[\"個人存取權杖\"],\"xKeZ0l\":[\"更偏暖、偏土的色調。\"],\"xYbozN\":[\"柔象牙白\"],\"xqViCq\":[\"暖象牙白\"],\"xxaahd\":[\"想要更冷、更利落的觀感時。\"],\"y/awtV\":[\"頁首連結、內建目的地與更多選單\"],\"y28hnO\":[\"文章\"],\"y8Md/V\":[\"語言與時間已更新。\"],\"y9W9vo\":[\"正在建立選集…\"],\"yNCqOt\":[\"最新 RSS 來源\"],\"yQ3kNF\":[\"請輸入以下短語以確認:\"],\"ydq1k2\":[\"請先選擇一個帳戶\"],\"yjjCV8\":[\"固定的 RSS 檔案網址\"],\"yjkELF\":[\"確認新密碼\"],\"ym+ccl\":[\"在首頁顯示 Jant 的署名。\"],\"yzF66j\":[\"連結\"],\"z6wakA\":[\"顯示在您的主頁上的簡短介紹。\"],\"zEizrk\":[\"最後使用於 \",[\"date\"]],\"zSURJW\":[\"沒有符合的儲存庫.\"],\"zUlHGd\":[\"頁面網址\"],\"zXH2jX\":[\"語言與時區\"],\"zcDmsG\":[\"精選文章\"],\"zlcDd2\":[\"刪除此自訂 URL?使用該 URL 的訪客將不會再被重新導向。\"],\"zwBp5t\":[\"私有\"]}")as Messages;
1
+ /*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+4Z6iP\":[\"先在 GitHub 上建立儲存庫 — 可以是空的。\"],\"+9JI/F\":[\"連線後會將你的網站同步到 \",[\"repo\"],\" 的預設分支,並疊加在其現有歷史之上。Jant 管理路徑外的既有檔案會保留。此操作無法復原。\"],\"+AXdXp\":[\"標籤與 URL 為必填\"],\"+K0AvT\":[\"解除連線\"],\"+V/jXc\":[\"直接指向 /feed,即 Atom 原始檔案,目前返回的是「\",[\"feed\"],\"」。適合已經在用閱讀器的讀者。\"],\"+VEqyj\":[\"設為主語言\"],\"+jXHMG\":[[\"count\",\"plural\",{\"other\":[\"還有 \",\"#\",\" 篇\",[\"language\"],\"貼文。先修改它們的語言,或保留該語言。\"]}]],\"+wgt7C\":[\"低飽和的紅棕底,溫暖、有泥土感。\"],\"+zy2Nq\":[\"類型\"],\"/0D1Xp\":[\"編輯選集\"],\"/3H2/s\":[\"此託管網站透過 \",[\"providerLabel\"],\" 登入。請在該處管理密碼與託管存取權限。\"],\"/PXXBT\":[\"近乎純白,帶一點暖意。\"],\"/PoNoq\":[\"編輯連結\"],\"/zOUxl\":[\"連結至 Telegram 機器人的 QR 碼\"],\"05DXsb\":[\"連接或管理用於從 Telegram 發文的 Bot。\"],\"0OGSSc\":[\"頭像顯示已更新。\"],\"0UzCUX\":[\"更新你用來登入的密碼\"],\"0VVvqu\":[\"多語言已啟用。\"],\"0bdA9b\":[\"啟用 Telegram 以連線\"],\"0dieZ/\":[\"/archive 開啟時用哪種版式。讀者可以在頁面上自行切換。\"],\"0gECZD\":[\"想寫更完整的介紹?\"],\"0uIjy/\":[\"淺鼠尾草綠底,配同色系的綠。\"],\"1DahsC\":[\"搜尋設定\"],\"1F6Mzc\":[\"目前尚無導覽項目。請新增連結或在下方啟用系統項目。\"],\"1H7gng\":[\"後台語言\"],\"1MYU3o\":[\"無法建立頁面。請檢查資料後再試一次。\"],\"1mbBbL\":[\"手動連接\"],\"1njn7W\":[\"淺色\"],\"1qGdnL\":[\"預設主題。沉靜,適合長時間閱讀。\"],\"1wdDm9\":[\"新增語言\"],\"21mg6u\":[\"選擇一則尚未加入導覽的有標題筆記,或建立新頁面。\"],\"2C7mSG\":[\"選集連結\"],\"2DoBvq\":[\"訂閱來源\"],\"2Et8jU\":[\"設定每頁搜尋結果數;重設後繼承預設分頁大小。\"],\"2FYpfJ\":[\"更多\"],\"2Ithfh\":[\"傳送任何文字給機器人,該文字會作為筆記已發佈。\"],\"2PTjMB\":[\"我想刪除 \",[\"siteName\"]],\"2QRni+\":[\"時區已更新。\"],\"2UrpGC\":[\"訂閱源網址已送達。目錄會在 \",[\"hours\"],\" 小時內首次讀取。\"],\"2ZqpRB\":[\"關閉\"],\"2cFU6q\":[\"網站頁尾\"],\"2fPEPI\":[\"沉靜、偏冷的底色。\"],\"2lkk2l\":[\"微黃的紙底,配橄欖綠。\"],\"2oWZo7\":[\"最近一次提交\"],\"2r8pkt\":[\"無法載入頁面。請稍後再試。\"],\"2uuy4H\":[\"已透過個人存取權杖連線\"],\"2wK4BX\":[\"編輯 About 頁面\"],\"35x8eZ\":[\"顯示 \",[\"shown\"],\" 共 \",[\"total\"]],\"39QGku\":[\"啟用機器人並傳送綁定碼,之後你傳給它的任何訊息都會成為筆記。\"],\"3B0RD1\":[\"上傳個人資料圖片與產生的網站圖示。\"],\"3Cw1AI\":[\"新增選集\"],\"3VrybB\":[\"重新導向\"],\"3Yvsaz\":[\"302 (暫時)\"],\"3n0zbB\":[\"在示範模式中已停用工作階段管理。請改用共用示範工作階段。\"],\"3sYJi5\":[\"下載與 Hugo 相容的封存 — 以靜態方式託管或移轉到另一個 Jant\"],\"3vMdv3\":[\"此連結為保留連結。請選擇其他連結。\"],\"3wKq0C\":[\"無法儲存。請稍後再試。\"],\"49Bsal\":[\"Feed 設定已更新。\"],\"4D09NB\":[\"連結到選集頁。\"],\"4J/OYU\":[\"選集已建立。\"],\"4Jge8E\":[\"目前的工作階段\"],\"4KIa+q\":[\"已下載匯出檔案。\"],\"4Q0iPK\":[\"跟隨裝置外觀或強制使用淺色或深色主題。\"],\"4cEClj\":[\"工作階段\"],\"4n7WY4\":[\"站名、可見性、訂閱來源與時區\"],\"4tcgrg\":[\"查看這些貼文\"],\"4yyXWu\":[\"淺骨白底,配低飽和的綠。\"],\"4zGJ5E\":[\"永久刪除帳戶\"],\"5QlUIt\":[\"倉庫為空。準備連線。\"],\"5VQnR3\":[\"當你想要一個永遠不會改變的 feed URL 時,請使用這些。\"],\"5dpcN1\":[\"輸入以搜尋全部\"],\"5f1Wo9\":[\"已以 \",[\"account\"],\" 連線\"],\"5o8R81\":[\"極淺的象牙底,配淡淡的茶綠。\"],\"6ArdBh\":[\"將 Featured 貼文用於 /feed。\"],\"6DjeBT\":[\"示範網站會始終對搜尋引擎保持隱藏。\"],\"6E3aK4\":[\"管理託管\"],\"6FFB7q\":[\"使用最新的公開貼文作為 /feed。\"],\"6K1Vef\":[\"確定要永久刪除此部落格嗎?此動作無法復原。\"],\"6NpNLc\":[\"此儲存庫已有內容。\"],\"6Pa5AP\":[\"連接或管理 GitHub 內容自動備份。\"],\"6V3Ea3\":[\"已複製\"],\"6WdDG7\":[\"頁面\"],\"71WIgc\":[\"取得新代碼\"],\"71of4k\":[\"限制每個 RSS feed 中包含的貼文數。\"],\"746NHh\":[\"此部落格\"],\"7811AW\":[\"此儲存庫已在備份本網站。\"],\"7FCZPo\":[\"內容語言\"],\"7FaY4u\":[\"用法\"],\"7G9YLi\":[\"允許搜尋引擎索引我的網站\"],\"7GISOt\":[\"儲存機器人權杖\"],\"7MZxzw\":[\"密碼已變更。\"],\"7p5kLi\":[\"後台\"],\"7vhWI8\":[\"新密碼\"],\"81nFIS\":[\"密碼不符。請確認兩個欄位相同。\"],\"87a/t/\":[\"標籤\"],\"89Upyo\":[\"該主題目前不可用。請選擇其他主題。\"],\"8BfEpW\":[\"託管帳號\"],\"8NIu3g\":[\"該頁面尚未有標題,因此選單沒有可顯示的項目。\"],\"8T46pB\":[\"機器人權杖\"],\"8U2Z7f\":[\"新增自訂 URL\"],\"8ZsakT\":[\"密碼\"],\"8bpHix\":[\"帳戶建立失敗,檢查填寫內容後重試。\"],\"8gEghq\":[\"明亮的暖白底,配苔綠點綴。\"],\"9+vGLh\":[\"自訂 CSS\"],\"9As8Nu\":[\"在 GitHub 上建立一個\"],\"9EjI4j\":[\"暖沙色\"],\"9Fa2Ns\":[\"根位址將恢復顯示所有語言,\",[\"prefix\"],\" 這類位址會重新導向到它,既有的連結和訂閱仍然有效。貼文網址和每篇貼文的語言都不受影響,隨時可以重新啟用。\"],\"9FctRm\":[\"使用小寫字母、數字和連字號。\"],\"9J6wUO\":[\"建議新增的連結\"],\"9Lsvt5\":[\"已於 \",[\"date\"],\" 登入\"],\"9SHZas\":[\"登入後顯示「設定」,未登入時顯示「登入」。\"],\"9T7Cwm\":[\"重新導向、自訂路徑與網址控制\"],\"9aUyym\":[\"查看你在哪裡已登入,並撤銷舊的工作階段\"],\"9mqQnx\":[\"限制自動產生摘要所使用的段落數。\"],\"A7tRC9\":[\"正規的 /feed URL 會回傳哪個貼文串流。\"],\"ADTn/D\":[\"允許不使用 API 權杖讀取已發佈內容。\"],\"AELGTd\":[\"立即加入導覽,或啟用編輯器補充詳細資料。\"],\"ANzR96\":[\"設定編輯器\"],\"AQL8b1\":[\"你寫作的主要語言是?\"],\"AeXO77\":[\"帳戶\"],\"AnY+O9\":[\"在首頁底部顯示「Build with Jant」\"],\"ApZDMk\":[\"此圖會用於你的 favicon 和 apple-touch-icon。為達最佳效果,請上傳至少 512x512 像素、背景為純色的正方形 PNG。\"],\"Aysjjh\":[\"選擇 Jant 全域使用的配色方案。\"],\"B495Gs\":[\"封存\"],\"B4ESok\":[\"API 參考\"],\"B5P9HE\":[\"提交我的網站\"],\"BJ0tsF\":[\"搜尋執行階段設定並啟用進階設定\"],\"BTYdze\":[\"連結已新增到導覽。\"],\"BszQc4\":[\"根位址(/、/feed)顯示這個語言的內容。\"],\"BzEFor\":[\"或\"],\"C0/57J\":[\"這是選集連結的最後一部分。\"],\"CDAdlf\":[\"移除機器人\"],\"CTAEes\":[\"選擇儲存庫\"],\"CjZZgz\":[\"此儲存庫已有提交紀錄\"],\"Cl92kR\":[\"建立新選集\"],\"D35jh6\":[\"管理後台的顯示語言,只有你看得到。\"],\"D8k2s6\":[\"連接 Telegram\"],\"DCKkhU\":[\"目前密碼\"],\"DKKKeF\":[\"在 \",[\"providerLabel\"],\" 管理密碼與託管存取\"],\"DVFXa6\":[\"最乾淨、最中性的一檔。\"],\"DdXUay\":[\"在公開標頭顯示網站頭像。\"],\"E3hjKv\":[\"新增為連結\"],\"EKvXGx\":[\"該位址位於其他網站。Navigation 將其視為連結。\"],\"EO3I6h\":[\"上傳未成功。請稍後再試。\"],\"Eax/et\":[\"強調色\"],\"EbDLD+\":[\"選擇 Jant 全域使用的字體。\"],\"Enslfm\":[\"目標網址\"],\"F53UDu\":[\"冷白\"],\"F7FKwe\":[\"你在此處貼上的任何內容都能完全存取訪客的瀏覽器。僅使用來自你信任來源的程式碼。\"],\"F8SNqr\":[\"暖橘\"],\"FBgjRN\":[\"搜尋頁面,或貼上位址\"],\"FMUJSP\":[\"帳戶與資料\"],\"FUfPPw\":[\"根位址(/、/feed、/archive)將改為提供「\",[\"next\"],\"」的內容,「\",[\"previous\"],\"」移至 \",[\"prefix\"],\"。貼文網址不會改變,但訂閱 /feed 的讀者從此收到的是「\",[\"next\"],\"」的貼文。\"],\"Fe1cvJ\":[\"延遲新貼文和回覆進入訂閱源的時間。設為 0 可關閉延遲。\"],\"Fk3SSD\":[\"平靜自然,看著舒服。\"],\"FkMol5\":[\"Featured\"],\"FwL2Zk\":[[\"count\",\"plural\",{\"other\":[\"還有 \",\"#\",\" 篇\",[\"language\"],\"貼文,但列表裡沒有這個語言。把它加回列表,或先修改這些貼文的語言。\"]}]],\"G/1oP+\":[\"移除 webhook 並停止同步。你的儲存庫內容不會被刪除。\"],\"G0qJsQ\":[\"找不到安全權杖。請重新整理頁面後再試一次。\"],\"G0tHaW\":[\"建立一個不會出現在 Latest 的公開頁面。\"],\"G2fuEb\":[\"已鎖定\"],\"G39wnK\":[\"將內容備份並與 GitHub 儲存庫同步\"],\"G5oCui\":[\"URL 結構、每種語言的訂閱來源,以及譯本關聯。\"],\"G9peMt\":[\"建立帳戶\"],\"GAmD3h\":[\"語言\"],\"GBkMNw\":[[\"name\"],\" 是 Jant 社群人工維護的目錄,旨在幫助使用者發現新的 Jant 部落格和內容。\"],\"GXsAby\":[\"撤銷\"],\"GdGiNi\":[\"第 \",[\"current\"],\" 步,共 \",[\"total\"],\" 步\"],\"GorKul\":[\"歡迎使用 Jant\"],\"GxkJXS\":[\"上傳中...\"],\"GzKzUa\":[\"試用限制\"],\"HCNlq3\":[\"乾淨、高對比,接近純白。\"],\"HHDyGw\":[\"柔綠\"],\"HKH+W+\":[\"資料\"],\"Hf/w/z\":[\"示範網站不會出現在 Discover 中。\"],\"Hp1l6f\":[\"目前\"],\"HxTUmw\":[\"新增 \",[\"language\"]],\"HxlY7t\":[\"變更此設定會更新訂閱者從 /feed 取得的內容。\"],\"HxuOlm\":[\"網站頁首\"],\"I0ijRI\":[\"全部 feed 網址\"],\"I4HRxU\":[\"已發布的全部貼文,包括從 Latest 中隱藏的那些。\"],\"I6gXOa\":[\"路徑\"],\"I76CzF\":[\"中性灰\"],\"ID38tA\":[\"示範模式下帳號刪除已停用。共用示範會另行重置。\"],\"IF9tPu\":[\"何時使用網站匯出、資料庫備份與復原演練。\"],\"IW5PBo\":[\"複製權杖\"],\"IagCbF\":[\"URL\"],\"IreQBq\":[\"儲存庫\"],\"J36Xsm\":[\"在 \",[\"address\"],\" 找不到任何頁面。請檢查位址,或以標題搜尋。\"],\"J6bLeg\":[\"新增自訂連結到任何 URL\"],\"JL7LF5\":[\"可用的 CSS 變數、data 屬性與範例。\"],\"JcD7qf\":[\"更多操作\"],\"JjX0OO\":[\"請立即複製你的權杖 — 它不會再顯示。\"],\"JrFTcr\":[\"連線中…\"],\"JuN5GC\":[\"未選取檔案。請選擇要上傳的檔案。\"],\"K+0Hu0\":[\"找不到可加入的相符頁面。換個標題搜尋,或建立新頁面。\"],\"K/F6pa\":[\"儲存中…\"],\"KDw4GX\":[\"重試\"],\"KSgo21\":[\"選擇一個儲存庫\"],\"KVVYBh\":[\"新增選集到導覽\"],\"KiJn9B\":[\"筆記\"],\"Kk7jwL\":[\"設定每頁封存貼文數;重設後繼承預設分頁大小。\"],\"KwOLJF\":[\"Jant 最初的配色,日常穩妥之選。\"],\"L+rMC9\":[\"還原為預設值\"],\"L27RpE\":[\"頁面已建立。\"],\"L3DEwT\":[\"移除這個頭像?你的 favicon 與頁首圖示會回復為預設。\"],\"L4t4/q\":[\"3月14日\"],\"L86zmP\":[\"在完整表單中編輯首頁和中繼資料使用的多行簡介。\"],\"LdyooL\":[\"連結\"],\"LhMjLm\":[\"時間\"],\"LjwaJ/\":[\"登入、匯出和刪除\"],\"M/D8PK\":[\"+ 安裝到其他帳戶\"],\"M/haSd\":[\"永遠顯示主題的淺色版本。\"],\"M/kDIW\":[\"搜尋引擎看到的語言。\"],\"M1co/O\":[\"已設定\"],\"M2hGyf\":[\"指向 /subscribe,一個列出你所有 feed 網址、帶複製按鈕的頁面。適合還沒在用閱讀器的讀者。\"],\"M2kIWU\":[\"字型主題\"],\"M4tzVU\":[\"Latest 貼文\"],\"M6CbAU\":[\"切換編輯面板\"],\"M7wPsD\":[\"切換\"],\"MHrjPM\":[\"標題\"],\"MKIM3K\":[\"搜尋頁面\"],\"MaYYE6\":[\"透過向 Telegram 機器人傳送訊息來發佈筆記\"],\"Me5t5H\":[\"連接 GitHub 倉庫,自動把貼文備份成 Markdown。GitHub 上的修改會同步回網站。\"],\"MeCJlL\":[\"正在提交網站,重新整理後可以看到結果。\"],\"MnbH31\":[\"頁面\"],\"Mq9FZ1\":[\"正在建立頁面…\"],\"Mr4QPw\":[\"要斷開 Telegram?你可以隨時使用新的綁定代碼重新連接。\"],\"MtENL9\":[\"調整你的網站外觀、可讀性與執行效能。\"],\"N/8NPV\":[\"在刪除前,請先下載網站匯出檔案。刪除後無法恢復此帳號。\"],\"N7UNHY\":[\"Featured RSS 來源\"],\"NHnUHF\":[\"頁首上的網站圖示與個人標記\"],\"NU2Fqi\":[\"儲存 CSS\"],\"NVjhde\":[\"暖羊皮紙\"],\"Nldjdr\":[\"還沒有自訂 URL。建立一個,給貼文加重新導向或自訂路徑。\"],\"O3oNi5\":[\"電子郵件\"],\"OJxdgi\":[\"連結不能超過 200 個字元。\"],\"OSJXFg\":[\"套用於整個網站,包括管理頁面。選擇一個調色盤,然後決定它是跟隨系統還是維持固定。\"],\"OeUWA7\":[\"加入頁面\"],\"OuuMXJ\":[\"在 </body> 結束標籤之前新增全站 HTML。\"],\"Ox3+3h\":[\"無相符結果。\"],\"P7Eeay\":[\"完整貼文\"],\"PEUV5I\":[\"程式碼注入已更新。\"],\"PHh52z\":[\"暖而厚重,棕調明顯。\"],\"PXj9lw\":[\"停止接受來自 Telegram 的貼文。你現有的筆記會保持已發佈。\"],\"PZ7HJ8\":[\"部落格大頭貼\"],\"Pbm2/N\":[\"建立選集\"],\"Pwqkdw\":[\"載入中…\"],\"PxJ9W6\":[\"產生權杖\"],\"Q/6Y+2\":[\"需要在目標儲存庫上擁有 Contents(讀/寫)和 Webhooks(讀/寫)權限。\"],\"Q/O0X4\":[\"此設定未儲存。請檢查設定值並再試一次。\"],\"Q30z/l\":[\"要從導覽移除這個選集嗎?選集本身不會被刪除。\"],\"Q99OtV\":[\"將選集釘選到導覽列。最近 48 小時內更新的選集旁會顯示一個 * 號。\"],\"QCwsv1\":[\"暖白\"],\"QKvrmL\":[\"暖中性,適合長讀。\"],\"QZmz0H\":[\"內建連結\"],\"Qnrzvb\":[\"已啟用的權杖\"],\"R6Z4LE\":[\"下載失敗。請再試一次。\"],\"R9Khdg\":[\"自動\"],\"RDjuBN\":[\"初始設定\"],\"RRo9kN\":[\"語言已更新。\"],\"RcdDOS\":[\"在 Telegram 上向 @BotFather 發送訊息以建立機器人,然後貼上它提供給你的權杖\"],\"RdVIcf\":[\"奶油配深咖\"],\"Ri/9PY\":[\"目前仍只向 Discover 提供精選貼文,但沒有任何貼文標為 Featured,訂閱源裡沒有可顯示的內容。取消勾選再重新勾選,即可讓 Discover 讀取全部公開貼文。\"],\"Rn2p1h\":[\"沒有任何項目符合此搜尋。請嘗試不同的名稱或描述。\"],\"RxsRD6\":[\"時區\"],\"SDND4q\":[\"未設定\"],\"SJmfuf\":[\"網站名稱\"],\"SKZhW9\":[\"權杖名稱 (API 權杖名稱欄位。)\"],\"SSsoa4\":[\"加入導覽\"],\"SVQQPe\":[\"無法連線。請檢查錯誤並再試一次。\"],\"SWb0z+\":[\"此網址為保留網址。請選擇其他網址。\"],\"SYGk01\":[\"以網址前綴提供的語言。在「語言」頁管理。\"],\"SchpMp\":[\"Telegram\"],\"SeY4vo\":[\"移除 \",[\"language\"],\" 後就只剩主語言了,多語言會一併關閉。根位址將恢復顯示所有語言,\",[\"prefix\"],\" 不再可用,每篇貼文的語言維持不變。\"],\"SqKp3o\":[\"此標題會用於整個網站、瀏覽器分頁與訂閱來源。\"],\"SrGs4T\":[\"淺藍灰底,冷靜而均勻。\"],\"T/R+Qz\":[\"主語言\"],\"T17dXm\":[\"每種語言是否擁有獨立的首頁、彙整和訂閱來源。在「語言」頁管理。\"],\"T41PG1\":[\"限制自動產生摘要所使用的字元數。\"],\"TN0mN4\":[\"搜尋和編輯執行階段設定。變更會立即生效;重設會恢復環境變數或內建預設值。\"],\"TSCeuF\":[\"無法建立選集。請檢查資料後再試一次。\"],\"TpF3v+\":[\"在 </head> 之前注入。用於分析、自訂 meta 標籤,以及必須提前載入的樣式。\"],\"Tu6bMZ\":[\"建立 About 頁面\"],\"Tz0i8g\":[\"設定\"],\"U5J7jI\":[\"網站可見性已更新。\"],\"U5v6Gh\":[\"編輯頁面\"],\"UFK415\":[\"用於分析與小工具的網站全域 HTML\"],\"UTvFQq\":[\"啟用 \",[\"linkOpen\"],\"@\",[\"botUsername\"],[\"linkClose\"],\" 並傳送:\"],\"UUn+Y5\":[\"啟用設定\"],\"UaZwcz\":[\"建立後可以設定更多選項。\"],\"Uj/btJ\":[\"在我的網站頁首顯示大頭貼\"],\"UsODUn\":[\"選擇一個帳戶\"],\"UxKoFf\":[\"導覽列\"],\"UyMSeQ\":[\"在該位址\"],\"V+bhUy\":[\"安裝 GitHub App\"],\"V0fyg5\":[\"選集已加入導覽。\"],\"V4WsyL\":[\"新增連結\"],\"V5pZwT\":[\"搜尋設定已更新。\"],\"V7dQi9\":[\"語言已移除。\"],\"VXUPla\":[\"使用 GitHub App 連線\"],\"Vh+JIV\":[\"多語言指南\"],\"VhMDMg\":[\"變更密碼\"],\"Vn3jYy\":[\"導覽項目\"],\"VoZYGU\":[\"這會永久刪除你的全部資料——貼文、媒體、選集、設定和帳戶。部落格會重設為初始設定狀態。此操作無法復原。\"],\"VoarBQ\":[\"所有貼文的網址都不會改變,也可以隨時再關閉。\"],\"VqyJ7Y\":[\"主要語言已變更。\"],\"WL7KjI\":[\"卡片網格\"],\"WUnFK2\":[\"偏冷的淺白底,配深靛藍文字。高對比。\"],\"Wa9q4P\":[\"純白\"],\"Wb7EHo\":[\"About 頁面\"],\"Weq9zb\":[\"一般\"],\"Wi9i06\":[\"依照每位訪客的系統偏好。\"],\"Wildi8\":[\"沒有可加入的頁面。建立頁面後即可加入導覽。\"],\"Wx1M8N\":[\"安裝 GitHub App,以授予存取權而無需管理個人權杖。權限以每個儲存庫為範圍,並可在 GitHub 上撤銷。\"],\"X+8FMk\":[\"目前的密碼不符。請再試一次。\"],\"X1G9eY\":[\"導覽列預覽\"],\"X2Cbc2\":[\"封存 RSS 來源\"],\"X5P6yv\":[\"最近更新\"],\"X9Hujr\":[\"手動推送\"],\"XQQOyj\":[\"該頁面為草稿。請先發佈,然後再加入。\"],\"XeRkls\":[[\"count\",\"plural\",{\"other\":[\"其中 \",\"#\",\" 篇還沒有語言,會標記為「\",[\"language\"],\"」。\"]}]],\"Xsu5WL\":[\"在專用程式碼編輯器中新增全站 CSS。\"],\"XtBJV8\":[\"正在檢查儲存庫…\"],\"Xtc16w\":[\"重新整理儲存庫清單\"],\"Y+7JGK\":[\"建立頁面\"],\"Y/F35r\":[\"使用 curl 建立貼文:\"],\"Y/N5N7\":[\"柔和的奶油底,配低飽和的綠。\"],\"YF6zHf\":[\"網站設定已更新。\"],\"YVcVlW\":[\"移除並關閉\"],\"Ya/FAk\":[\"至少再新增一種語言才能啟用。\"],\"YdG2RF\":[\"匯出網站\"],\"YkgZi7\":[\"連接一個 Telegram 機器人,之後你傳給它的任何訊息都會以筆記形式已發佈。\"],\"YwhjRx\":[\"管理帳戶\"],\"Yxp859\":[\"暖奶油色\"],\"Z5HWHd\":[\"已啟用\"],\"ZDY7Fy\":[\"同步中…\"],\"ZQKLI1\":[\"危險區域\"],\"ZS/CBL\":[\"刪除此導覽連結?訪客將不再在你的網站頁首看到它。\"],\"ZXZrAo\":[\"頁面網址不能超過 200 個字元。\"],\"ZgZX+d\":[\"純白底配中性灰。不帶任何色偏。\"],\"Zgq+c2\":[\"設定預設每頁顯示的項目數。\"],\"ZhhOwV\":[\"引用\"],\"ZiooJI\":[\"API 權杖\"],\"Zm7Qb0\":[\"備份與還原指南\"],\"ZmUkwN\":[\"新增自訂連結到導覽\"],\"a14mj8\":[\"未知裝置\"],\"a1iEgy\":[\"暖奶油底,配深咖啡棕點綴。\"],\"a3LDKx\":[\"安全性\"],\"aAIQg2\":[\"外觀\"],\"aFkzVF\":[\"目標貼文或選集的 slug\"],\"aR3U86\":[\"多語言已啟用。\",[\"count\",\"plural\",{\"other\":[\"#\",\" 篇貼文\"]}],\"已標記為「\",[\"language\"],\"」。\"],\"aV6XWN\":[\"多語言已關閉。你的語言設定仍然保留。\"],\"alKG0+\":[\"字型主題\"],\"anibOb\":[\"關於本部落格\"],\"any7NR\":[\"主題指南\"],\"asq0vL\":[\"你寫作使用的語言。\"],\"b+/jO6\":[\"301 (永久)\"],\"b+FyBD\":[\"將頁面加入導覽\"],\"b1FYSK\":[\"主要語言\"],\"bHOiy1\":[\"示範模式已停用變更密碼功能。請使用共用示範帳號登入。\"],\"bHYIks\":[\"登出\"],\"bV2vng\":[\"暖陶土色\"],\"bbR5vW\":[\"這套配色\"],\"bbzY7X\":[\"管理登入安全、站點匯出和無法復原的操作。\"],\"bfHZ7r\":[\"選擇用於顯示日期和時間的時區。\"],\"bi10qS\":[\"立即加入導覽,或啟用編輯器補充內容。\"],\"bkMuwo\":[\"連結到你標為 Featured 的貼文。\"],\"bmrL08\":[\"示範模式會隱藏會話、密碼更改與帳號刪除。匯出功能仍可使用。\"],\"bph1Dd\":[\"已關閉搜尋引擎索引,所以預設不會出現在 Jant Discover。勾選上面的選項仍會加入。\"],\"brfpw9\":[\"在完整表單中編輯顯示在公開頁面底部的多行頁尾。\"],\"bviiKV\":[\"這個主題下的標題、正文和連結。\"],\"c1BGrV\":[\"已在導覽中。請在上方清單中拖曳以移動\"],\"c1iSrq\":[\"Discover 社群規則\"],\"c3MN2z\":[\"所有可用的端點與請求格式。\"],\"cHh/Zu\":[\"啟用多語言\"],\"cS7/bk\":[\"移除已儲存的機器人權杖?其 webhook 會被刪除,任何已連接的帳號將會被斷線。\"],\"cSDy01\":[\"自訂 CSS 已更新。\"],\"clzoNp\":[\"始終顯示深色主題。\"],\"cnGeoo\":[\"刪除\"],\"cwaLCZ\":[\"頁面已加入導覽。\"],\"d1lzY/\":[\"語言已新增。\"],\"d3FRkY\":[\"無法複製。請再試一次。\"],\"d5oGUo\":[\"在 GitHub 建立新儲存庫\"],\"dB1Nr4\":[\"開始寫作\"],\"dEgA5A\":[\"取消\"],\"dTXUY+\":[\"確認刪除帳號\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dk7TCH\":[\"永久刪除所有資料並重設部落格\"],\"ds0nJk\":[\"在 head 標籤的閉合標記之前加入全站 HTML。\"],\"dsWkIw\":[\"要與 GitHub 斷開連線嗎?webhook 會被移除。你的儲存庫內容不會被刪除。\"],\"dwcK1n\":[\"或手動提交網址\"],\"e/tSI5\":[\"導覽順序已更新。\"],\"eOL7vn\":[\"色彩極少,干擾最小。\"],\"ePK91l\":[\"編輯\"],\"ebQKK7\":[\"網站\"],\"eeamrQ\":[\"Discover 讀取的是 Atom 訂閱源,需要先開啟訂閱源。\"],\"egK+Yy\":[\"供腳本與自動化使用的 Bearer 權杖\"],\"egOXl0\":[\"Jant Discover\"],\"ehj/zN\":[\"重新導向類型\"],\"ej6Rji\":[\"若有別的語言的貼文,之後可以在那篇貼文的選單裡單獨更正。\"],\"eneWvv\":[\"草稿\"],\"erTMh7\":[\"上次同步\"],\"f+m8jj\":[\"已複製訂閱網址。\"],\"f8fH8W\":[\"設計\"],\"fDz6PV\":[\"選擇用於內容備份的儲存庫。\"],\"fKRAwQ\":[\"沙色底,配綠色點綴。\"],\"fWYqkz\":[\"程式碼注入\"],\"fYXQnC\":[\"暖色裡最濃的一檔,溫馨。\"],\"fttd2R\":[\"我的選集\"],\"gZ5owP\":[\"搜尋儲存庫\"],\"gbqbh6\":[\"可以放心離開此頁面 — 同步會在背景繼續進行。\"],\"gkFvVN\":[\"在 </body> 之前注入。用於聊天小工具和不應阻塞頁面載入的腳本。\"],\"gtQsRO\":[\"建立自訂網址\"],\"hBO/y4\":[\"安全權杖已過期。請重新整理頁面並再試一次。\"],\"hGmyDl\":[\"權杖讓你從腳本,捷徑和其他工具存取 API 無需登入\"],\"hIHkRy\":[\"已透過 GitHub 應用程式連線\"],\"hJHHsU\":[\"發佈網站、封存與選集的 Atom feeds。\"],\"hcH8MY\":[\"設定你的網站\"],\"hdSi1b\":[\"輸入 \",[\"repo\"],\" 以確認\"],\"he3ygx\":[\"複製\"],\"hjeS3W\":[\"連結到 Latest。首頁展示的就是這個列表。\"],\"hjvSlw\":[\"語言已移除,多語言已關閉。\"],\"hvGGMK\":[\"從導覽中移除此頁面?頁面本身不會被刪除。\"],\"i0qMbr\":[\"首頁\"],\"iEUzMn\":[\"系統\"],\"iH8pgl\":[\"返回\"],\"iMkWoF\":[\"啟用\"],\"iSLIjg\":[\"連接\"],\"iVOMRi\":[\"首頁設定已更新。\"],\"icB4Cv\":[\"將連結拖到此處以顯示於「更多」選單下方\"],\"id3vuh\":[\"已設定 Telegram,但無法連線到機器人。請檢查機器人權杖並再試一次。\"],\"idD8Ev\":[\"已儲存\"],\"ihn4zD\":[\"搜尋…\"],\"iiDXZc\":[\"顯示於所有貼文與頁面的底部。\"],\"itS31B\":[\"更暖一些,像略舊的紙。\"],\"iwQZvS\":[\"冷藍灰\"],\"j4VrG6\":[\"下載匯出 ZIP\"],\"j5nQL2\":[\"例如 iOS 捷徑\"],\"jNE7O+\":[\"還沒有公開貼文。jant.me 的收錄條件是\",[\"minCount\",\"plural\",{\"other\":[\"滿 \",\"#\",\" 篇\"]}],\"。\"],\"jUV7CU\":[\"上傳大頭貼\"],\"jVUmOK\":[\"支援 Markdown\"],\"jdVYS8\":[\"選一個適合你文字的。\"],\"jgBjXJ\":[\"要撤銷這個權杖嗎?任何使用它的腳本都會停止運作。\"],\"jpctdh\":[\"檢視\"],\"k1ifdL\":[\"處理中...\"],\"kLw03Y\":[\"亮紙白\"],\"kMXclu\":[\"下載網站匯出檔案\"],\"kNiQp6\":[\"已釘選\"],\"kQ3Otm\":[\"建立新頁面\"],\"kRhzWq\":[\"GitHub 同步\"],\"kVQs7s\":[\"細緻的樣式覆寫\"],\"ke1gWS\":[\"自訂 URL\"],\"kfcRb0\":[\"頭像\"],\"kp8wiR\":[\"正在檢查網址…\"],\"kxDZ2i\":[\"此程式碼會在你網站的每個頁面上執行。\"],\"kzvWob\":[\"連結到貼文歸檔。\"],\"lLW3vJ\":[\"目標 slug\"],\"lLsfOE\":[\"關閉多語言?\"],\"lV04bQ\":[\"溫暖、有泥土感,但不顯暗。\"],\"lYHJih\":[\"撤銷此工作階段?該裝置將需要重新登入。\"],\"m16xKo\":[\"新增\"],\"mLOk1i\":[\"立即將所有貼文推送到 GitHub,而不是等待下一次自動同步。\"],\"mSNmrX\":[\"列出貼文:\"],\"n8+EXe\":[\"新增網站中已經存在的常用入口。\"],\"nG2qTk\":[\"示例連結\"],\"nK07ni\":[\"為你的網站選擇一種排版風格。每個主題會同時改變字體配對與閱讀節奏。\"],\"nbfdhU\":[\"第三方整合\"],\"ntJYyh\":[\"在 \",[\"providerLabel\"],\" 管理網域、方案和計費\"],\"o/vNDE\":[\"讓你覆寫任何主題變數。\"],\"o5rj+L\":[\"目前還沒有選集。在這裡建立一個並加入導覽。\"],\"oGC9uP\":[\"owner/repo\"],\"oH2JHg\":[\"我們會預先填入名稱 \",[\"name\"],\"。返回時清單會重新整理。\"],\"oKOOsY\":[\"色彩主題\"],\"oL535e\":[\"尚未同步\"],\"oNA4If\":[\"所有選集已經在你的導覽中。\"],\"oUn9Z7\":[\"近中性的淺灰,配鋼藍點綴。\"],\"ofdy2l\":[\"帶橘調的底色。最暖的一套。\"],\"olouYg\":[\"導覽會將該位址儲存為連結。\"],\"oxj6Pk\":[\"其他語言\"],\"pIpTqF\":[\"每種語言都有獨立的首頁、彙整和訂閱來源。啟用後,發佈時可以選擇不同的語言,也可以為既有的貼文關聯一篇其他語言的貼文。\"],\"pNXxtS\":[\"選擇要向讀者與搜尋引擎宣告的內容語言。\"],\"pZq3aX\":[\"上傳失敗。請再試一次。\"],\"pgJImo\":[\"把主要語言改為其他語言?\"],\"pgTIrt\":[\"選擇要與此網站同步的 GitHub 帳號和儲存庫。\"],\"psoxDF\":[\"該字型主題無法使用。請選擇其他主題。\"],\"pt4OhQ\":[\"/about 已被佔用。重新命名該項目後再建立 About 頁面。\"],\"pvnfJD\":[\"深色\"],\"q+hNag\":[\"選集\"],\"q/T5GS\":[\"私人 Jant 儀表板使用的語言。\"],\"qSgO/Y\":[\"跟隨內容語言\"],\"qZ9jzv\":[\"網站可見性\"],\"qdcESc\":[\"建立新儲存庫\"],\"qkSJzV\":[\"正在搜尋頁面…\"],\"r5EW6f\":[\"此儲存庫已在備份另一個 Jant 網站 (\",[\"host\"],\"). 請選擇其他儲存庫。\"],\"rCPrbS\":[[\"language\"],\"的操作\"],\"rEspiY\":[\"導覽位置已更新。\"],\"rFmBG3\":[\"色彩主題\"],\"re0mF0\":[\"頁面是公開的,但不會出現在 Latest。\"],\"rlonmB\":[\"無法刪除。請稍後再試。\"],\"s3jOk7\":[\"請輸入頁面標題。\"],\"sROtqR\":[\"無法連線目錄:\",[\"reason\"]],\"sRmGLp\":[\"之後可以在設定中更改。\"],\"satWc6\":[\"主要 RSS 訂閱\"],\"sgr2wQ\":[\"選集\"],\"sj4LLc\":[\"僅顯示已修改項目\"],\"sjej3N\":[\"移除「\",[\"language\"],\"」\"],\"slujBW\":[\"只能使用小寫字母、數字和連字號。\"],\"soRdOu\":[\"米白紙底,配深森綠。\"],\"sqxcaY\":[\"建立於 \",[\"date\"]],\"sxkWRg\":[\"進階\"],\"t/YqKh\":[\"移除\"],\"t3hvHq\":[\"立即同步\"],\"tJ4H0O\":[\"你的 Telegram 帳號\"],\"tfDRzk\":[\"儲存\"],\"tgWuMB\":[\"已修改\"],\"tvgAq5\":[\"尚未授權任何帳戶\"],\"u1VTd3\":[\"調色盤、表面色調與整體氛圍\"],\"u3wRF+\":[\"已發佈\"],\"u6KOjV\":[\"想要更細緻的控制?\"],\"uKLe0J\":[[\"count\"],\" 個設定已顯示\"],\"uVZcIA\":[\"此網址已被使用。請選擇其他網址。\"],\"udPwLB\":[\"頁首\"],\"ugHDtd\":[[\"about\"],\"貼文設為 Featured \",[\"hours\"],\" 小時後會出現在 Discover 首頁,Link 和 Quote 類型的貼文發佈 \",[\"hours\"],\" 小時後出現在 \",[\"links\"],\" 和 \",[\"quotes\"],\" 列表。這段期間你可以繼續修改,詳見 \",[\"rules\"],\"。\"],\"ui6aMF\":[\"這些裝置目前已登入你的帳號。撤銷任何你不認識的工作階段。\"],\"vBEKwo\":[\"在此管理本網站的活動工作階段。密碼與託管存取由 \",[\"providerLabel\"],\" 管理。\"],\"vOnuOa\":[\"在頁首和更多選單中顯示或隱藏內建目的地。\"],\"vRldcl\":[\"字體選擇與閱讀質感\"],\"vSYKYI\":[\"主要訂閱來源\"],\"vTuib7\":[\"這會控制 /feed 回傳的內容。\"],\"vXCC6J\":[\"有欄位填寫不正確,檢查後重試。\"],\"vXIe7J\":[\"語言\"],\"vXsx3x\":[\"該頁面為私人頁面,因此沒有人能從選單開啟它。\"],\"vdFnYM\":[\"重設連結\"],\"vmQmHx\":[\"新增自訂 CSS 以覆寫任何樣式。使用像 [data-page]、[data-post]、[data-format] 這類資料屬性來選取特定元素。\"],\"vpSPA1\":[\"缺少驗證金鑰,檢查環境變數設定。\"],\"vzX5FB\":[\"刪除帳號\"],\"w615zC\":[\"多語言\"],\"w8Rv8T\":[\"標籤為必填\"],\"wL3cK8\":[\"Latest\"],\"wW6NCp\":[\"上次錯誤\"],\"wc+17X\":[\"/* 在此放入你的自訂 CSS */\"],\"wuLtXn\":[\"目前沒有任何活動中的工作階段。已登入的裝置會顯示在此處。\"],\"x+HGBk\":[\"要求搜尋引擎不要索引公開頁面或將它們包含在搜尋結果中。\"],\"xCWek4\":[\"檔案儲存尚未設定。請檢查你的伺服器設定。\"],\"xCbjQn\":[\"允許我的部落格出現在 \",[\"name\"],\" 中\"],\"xGVfLh\":[\"繼續\"],\"xHt036\":[\"個人存取權杖\"],\"xKeZ0l\":[\"更偏暖、偏土的色調。\"],\"xYbozN\":[\"柔象牙白\"],\"xqViCq\":[\"暖象牙白\"],\"xxaahd\":[\"想要更冷、更利落的觀感時。\"],\"y/awtV\":[\"頁首連結、內建目的地與更多選單\"],\"y28hnO\":[\"貼文\"],\"y9W9vo\":[\"正在建立選集…\"],\"yNCqOt\":[\"Latest RSS 來源\"],\"yQ3kNF\":[\"請輸入以下短語以確認:\"],\"ydq1k2\":[\"請先選擇一個帳戶\"],\"yjjCV8\":[\"固定的 RSS 檔案網址\"],\"yjkELF\":[\"確認新密碼\"],\"ym+ccl\":[\"在首頁顯示 Jant 的署名。\"],\"yzF66j\":[\"連結\"],\"z6wakA\":[\"顯示在你的主頁上的簡短介紹。\"],\"zEizrk\":[\"最後使用於 \",[\"date\"]],\"zPqly9\":[\"這個站點沒有可提交的目錄。\"],\"zSURJW\":[\"沒有符合的儲存庫。\"],\"zUlHGd\":[\"頁面網址\"],\"zYRGIR\":[\"尚未提交。還沒有目錄知道這個網站存在。\"],\"zcDmsG\":[\"Featured 貼文\"],\"zlcDd2\":[\"刪除此自訂 URL?使用該 URL 的訪客將不會再被重新導向。\"],\"ztPYIR\":[\"啟用時會一次性標記既有貼文\"],\"zwBp5t\":[\"私有\"],\"zxexio\":[\"內容語言、介面語言、多語言\"]}")as Messages;
@@ -57,6 +57,63 @@ export function normalizeContentLanguage(value: string): string {
57
57
  }
58
58
  }
59
59
 
60
+ /**
61
+ * URL prefix form of a content language tag.
62
+ *
63
+ * A purely mechanical lowercase of the canonical tag, so it never needs storing
64
+ * separately. Canonical tags are one-to-one with their lowercase forms, so two
65
+ * different languages can never collide on one prefix.
66
+ *
67
+ * @param tag - Canonical BCP 47 tag
68
+ * @returns Lowercase prefix used in `/{prefix}/...` URLs
69
+ * @example
70
+ * toLanguagePrefix("zh-Hant"); // "zh-hant"
71
+ */
72
+ export function toLanguagePrefix(tag: string): string {
73
+ return tag.toLowerCase();
74
+ }
75
+
76
+ /**
77
+ * Parse the stored `ADDITIONAL_LANGUAGES` value into canonical tags.
78
+ *
79
+ * Tolerant by design — it reads a settings row that predates any given
80
+ * validation rule. Blank and unparseable entries are dropped, tags are
81
+ * canonicalized, duplicates collapse, and order is preserved because it is the
82
+ * order the language switcher renders in.
83
+ *
84
+ * @param value - Raw comma-separated settings value
85
+ * @returns Canonical tags, deduplicated, in the stored order
86
+ * @example
87
+ * parseLanguageList(" en , ZH-hant ,en "); // ["en", "zh-Hant"]
88
+ */
89
+ export function parseLanguageList(value: string | null | undefined): string[] {
90
+ if (!value) return [];
91
+
92
+ const seen = new Set<string>();
93
+ const result: string[] = [];
94
+ for (const token of value.split(",")) {
95
+ const trimmed = token.trim();
96
+ if (!trimmed || !isValidContentLanguage(trimmed)) continue;
97
+ const tag = normalizeContentLanguage(trimmed);
98
+ if (seen.has(tag)) continue;
99
+ seen.add(tag);
100
+ result.push(tag);
101
+ }
102
+ return result;
103
+ }
104
+
105
+ /**
106
+ * Serialize canonical tags back into the stored settings form.
107
+ *
108
+ * @param tags - Canonical BCP 47 tags in switcher order
109
+ * @returns Comma-separated value for `ADDITIONAL_LANGUAGES`
110
+ * @example
111
+ * formatLanguageList(["en", "ja"]); // "en,ja"
112
+ */
113
+ export function formatLanguageList(tags: readonly string[]): string {
114
+ return tags.join(",");
115
+ }
116
+
60
117
  /**
61
118
  * Resolve a content language tag to the catalog locale that should drive the
62
119
  * dashboard UI for that user.
@@ -94,3 +151,40 @@ export function resolveCatalogLocale(tag: string): Locale {
94
151
 
95
152
  return baseLocale;
96
153
  }
154
+
155
+ /**
156
+ * Decide whether first-run setup should pin the dashboard's own locale.
157
+ *
158
+ * The dashboard follows the content language unless `DASHBOARD_LANGUAGE` says
159
+ * otherwise, so setup's only job is to notice when the browser knows something
160
+ * following would miss. It knows something in exactly one case: it names a
161
+ * catalog that is neither the fallback nor the one the content language
162
+ * already resolves to.
163
+ *
164
+ * That covers both directions of the mismatch this exists for. Someone writing
165
+ * an English blog from a Chinese browser gets a Chinese dashboard, because the
166
+ * browser is the only thing that said "Chinese". Someone writing a Chinese blog
167
+ * from an English browser keeps a Chinese dashboard, because `en` is what every
168
+ * unconfigured browser reports and is no evidence against the language they
169
+ * just chose by hand.
170
+ *
171
+ * @param contentLanguage - The BCP 47 tag the author chose to publish in
172
+ * @param browserLanguage - What the browser reported, if anything
173
+ * @returns The catalog to pin, or `null` to follow the content language
174
+ * @example
175
+ * resolveFirstRunDashboardLocale("zh-Hans", "en"); // null — follow the content
176
+ * resolveFirstRunDashboardLocale("en", "zh-CN"); // "zh-Hans"
177
+ */
178
+ export function resolveFirstRunDashboardLocale(
179
+ contentLanguage: string,
180
+ browserLanguage?: string | null,
181
+ ): Locale | null {
182
+ const browser = browserLanguage?.trim();
183
+ if (!browser) return null;
184
+
185
+ const browserCatalog = resolveCatalogLocale(browser);
186
+ if (browserCatalog === baseLocale) return null;
187
+ if (browserCatalog === resolveCatalogLocale(contentLanguage)) return null;
188
+
189
+ return browserCatalog;
190
+ }
@@ -12,6 +12,7 @@ import {
12
12
  normalizeContentLanguage,
13
13
  resolveCatalogLocale,
14
14
  } from "./i18n.js";
15
+ import { detectLocaleFromHeader } from "./detect.js";
15
16
 
16
17
  declare module "hono" {
17
18
  interface ContextVariableMap {
@@ -26,6 +27,11 @@ declare module "hono" {
26
27
  * paths activate the catalog locale resolved from the user's configured
27
28
  * `SITE_LANGUAGE`; everything else is forced to `baseLocale` (English).
28
29
  *
30
+ * `/setup` counts as one of them. It is read by exactly one person — the
31
+ * author, before they have a dashboard — so it belongs on the author's side of
32
+ * this split, not the readers'. On a site with no language configured yet, the
33
+ * browser's own is what it resolves from.
34
+ *
29
35
  * Why: Lingui computes message IDs from `message` text alone (the `comment`
30
36
  * field is a translator note and does not disambiguate). Shared strings like
31
37
  * "Latest" / "Featured" collide between public navigation labels and settings
@@ -33,7 +39,7 @@ declare module "hono" {
33
39
  * translations into the public header. Scoping activation by route keeps
34
40
  * public pages in English without requiring per-call-site `context:` tags.
35
41
  */
36
- const ADMIN_PATH_PREFIXES = ["/settings", "/dash"] as const;
42
+ const ADMIN_PATH_PREFIXES = ["/settings", "/dash", "/setup"] as const;
37
43
 
38
44
  function isAdminPath(path: string): boolean {
39
45
  return ADMIN_PATH_PREFIXES.some(
@@ -69,7 +75,11 @@ export function i18nMiddleware(): MiddlewareHandler {
69
75
  const dashboardSetting = c.get("allSettings")?.DASHBOARD_LANGUAGE;
70
76
  const dashboardLocale = isLocale(dashboardSetting)
71
77
  ? dashboardSetting
72
- : resolveCatalogLocale(contentLang);
78
+ : isValidContentLanguage(rawSetting)
79
+ ? resolveCatalogLocale(contentLang)
80
+ : // Before a language is configured — first-run setup — the only
81
+ // signal about the person reading is the browser they arrived in.
82
+ detectLocaleFromHeader(c.req.header("Accept-Language"));
73
83
  const uiLang = isAdminPath(c.req.path) ? dashboardLocale : baseLocale;
74
84
  const i18n = createI18n(uiLang);
75
85
 
@@ -141,6 +141,86 @@ export function getSupportedLocaleEntries(): LocaleEntry[] {
141
141
  return entriesCache;
142
142
  }
143
143
 
144
+ /**
145
+ * Pick the curated tag that best matches an `Accept-Language` header.
146
+ *
147
+ * Used to prefill the setup form's content-language field. It only ever
148
+ * suggests: setup renders the result in a visible dropdown the author confirms,
149
+ * so a near miss costs one click rather than a silently wrong `<html lang>`.
150
+ *
151
+ * Matching narrows in three steps — exact tag, then language plus script, then
152
+ * bare language — because `zh-CN` should land on `zh-Hans` rather than falling
153
+ * through to English.
154
+ *
155
+ * @param header - Raw `Accept-Language` header value
156
+ * @returns A tag from the curated list; `baseLocale` when nothing matches
157
+ * @example
158
+ * resolveSupportedLocaleTag("zh-CN,en;q=0.8"); // "zh-Hans"
159
+ * resolveSupportedLocaleTag("pt-BR"); // "pt-BR"
160
+ * resolveSupportedLocaleTag("xx"); // "en"
161
+ */
162
+ export function resolveSupportedLocaleTag(
163
+ header: string | undefined | null,
164
+ ): string {
165
+ if (!header?.trim()) return baseLocale;
166
+
167
+ const candidates = header
168
+ .split(",")
169
+ .map((part) => {
170
+ const [tag = "", ...params] = part.trim().split(";");
171
+ const qParam = params.find((param) =>
172
+ param.trim().toLowerCase().startsWith("q="),
173
+ );
174
+ const q = qParam ? Number.parseFloat(qParam.trim().slice(2)) : 1;
175
+ return { tag: tag.trim(), q: Number.isNaN(q) ? 1 : q };
176
+ })
177
+ .filter((entry) => entry.tag && entry.tag !== "*" && entry.q > 0)
178
+ .sort((a, b) => b.q - a.q);
179
+
180
+ const supported = getSupportedLocaleEntries();
181
+
182
+ for (const { tag } of candidates) {
183
+ let parsed: Intl.Locale;
184
+ try {
185
+ parsed = new Intl.Locale(tag);
186
+ } catch {
187
+ continue;
188
+ }
189
+
190
+ const exact = supported.find(
191
+ (entry) => entry.tag.toLowerCase() === parsed.baseName.toLowerCase(),
192
+ );
193
+ if (exact) return exact.tag;
194
+
195
+ // `maximize()` fills in the implied script, which is what turns zh-CN into
196
+ // zh-Hans and zh-TW into zh-Hant.
197
+ const maximized = parsed.maximize();
198
+ const withScript = supported.find((entry) => {
199
+ try {
200
+ const entryLocale = new Intl.Locale(entry.tag).maximize();
201
+ return (
202
+ entryLocale.language === maximized.language &&
203
+ entryLocale.script === maximized.script
204
+ );
205
+ } catch {
206
+ return false;
207
+ }
208
+ });
209
+ if (withScript) return withScript.tag;
210
+
211
+ const byLanguage = supported.find((entry) => {
212
+ try {
213
+ return new Intl.Locale(entry.tag).language === parsed.language;
214
+ } catch {
215
+ return false;
216
+ }
217
+ });
218
+ if (byLanguage) return byLanguage.tag;
219
+ }
220
+
221
+ return baseLocale;
222
+ }
223
+
144
224
  /**
145
225
  * Find a curated entry by tag, or build one on the fly. Used when the stored
146
226
  * setting value is something the picker doesn't list (e.g. a Welsh blogger
@@ -17,8 +17,8 @@ describe("editable settings registry", () => {
17
17
  "SITE_DESCRIPTION",
18
18
  "SITE_LANGUAGE",
19
19
  "DASHBOARD_LANGUAGE",
20
- "CJK_SERIF_FONT",
21
20
  "MAIN_RSS_FEED",
21
+ "ARCHIVE_DEFAULT_LAYOUT",
22
22
  "PAGE_SIZE",
23
23
  "SEARCH_PAGE_SIZE",
24
24
  "ARCHIVE_PAGE_SIZE",
@@ -34,6 +34,15 @@ describe("editable settings registry", () => {
34
34
  "RSS_FEEDS_ENABLED",
35
35
  ]);
36
36
  expect(editableSettingKeys).not.toContain("AUTH_SECRET");
37
+ // The multilingual keys are deliberately DB-only. Making them editable
38
+ // here would let `PUT /api/settings` turn multilingual on without the
39
+ // confirmation that stamps existing posts — every unstamped post would
40
+ // then vanish from the root view — and add a language without the
41
+ // URL-prefix conflict check.
42
+ expect(editableSettingKeys).not.toContain("MULTILINGUAL_ENABLED");
43
+ expect(editableSettingKeys).not.toContain("ADDITIONAL_LANGUAGES");
44
+ expect(resettableConfigEditorKeys).not.toContain("MULTILINGUAL_ENABLED");
45
+ expect(resettableConfigEditorKeys).not.toContain("ADDITIONAL_LANGUAGES");
37
46
  expect(editableSettingKeys).not.toContain("GITHUB_SYNC_TOKEN");
38
47
  expect(editableSettingKeys).not.toContain("SITE_AVATAR");
39
48
  expect(configEditorVisibleKeys).toEqual(
@@ -139,7 +148,7 @@ describe("editable settings registry", () => {
139
148
  mode: "edit",
140
149
  type: "number",
141
150
  value: "80",
142
- fallbackValue: "50",
151
+ fallbackValue: "25",
143
152
  modified: true,
144
153
  min: 1,
145
154
  max: 100,
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  ASSET_BASE_PATH,
4
+ getPreconnectHints,
4
5
  getPublicAssetBasePath,
5
6
  isAssetPath,
6
7
  toAssetPath,
@@ -52,3 +53,94 @@ describe("toPublicAssetPath", () => {
52
53
  );
53
54
  });
54
55
  });
56
+
57
+ describe("getPreconnectHints", () => {
58
+ const site = "https://example.com";
59
+
60
+ it("hints both connection kinds for a CDN asset host", () => {
61
+ expect(
62
+ getPreconnectHints({
63
+ assetBasePath: "https://cdn.example.com/jant",
64
+ siteUrl: site,
65
+ }),
66
+ ).toEqual([
67
+ { href: "https://cdn.example.com", crossorigin: false },
68
+ { href: "https://cdn.example.com", crossorigin: true },
69
+ ]);
70
+ });
71
+
72
+ it("adds the media host after the asset host", () => {
73
+ expect(
74
+ getPreconnectHints({
75
+ assetBasePath: "https://cdn.example.com/jant",
76
+ mediaBaseUrl: "https://media.example.com",
77
+ siteUrl: site,
78
+ }),
79
+ ).toEqual([
80
+ { href: "https://cdn.example.com", crossorigin: false },
81
+ { href: "https://cdn.example.com", crossorigin: true },
82
+ { href: "https://media.example.com", crossorigin: false },
83
+ ]);
84
+ });
85
+
86
+ it("hints a shared asset and media host only once", () => {
87
+ expect(
88
+ getPreconnectHints({
89
+ assetBasePath: "https://cdn.example.com/_assets",
90
+ mediaBaseUrl: "https://cdn.example.com/media",
91
+ siteUrl: site,
92
+ }),
93
+ ).toEqual([
94
+ { href: "https://cdn.example.com", crossorigin: false },
95
+ { href: "https://cdn.example.com", crossorigin: true },
96
+ ]);
97
+ });
98
+
99
+ it("hints the media host on its own when assets are same-origin", () => {
100
+ expect(
101
+ getPreconnectHints({
102
+ assetBasePath: "/_assets",
103
+ mediaBaseUrl: "https://media.example.com",
104
+ siteUrl: site,
105
+ }),
106
+ ).toEqual([{ href: "https://media.example.com", crossorigin: false }]);
107
+ });
108
+
109
+ it("hints nothing when everything shares the site's origin", () => {
110
+ expect(
111
+ getPreconnectHints({
112
+ assetBasePath: "/blog/_assets",
113
+ mediaBaseUrl: "",
114
+ siteUrl: "https://example.com/blog",
115
+ }),
116
+ ).toEqual([]);
117
+ expect(
118
+ getPreconnectHints({
119
+ assetBasePath: "https://example.com/_assets",
120
+ mediaBaseUrl: "https://example.com/media",
121
+ siteUrl: site,
122
+ }),
123
+ ).toEqual([]);
124
+ });
125
+
126
+ it("hints anyway when the site URL is missing or unparseable", () => {
127
+ expect(
128
+ getPreconnectHints({ assetBasePath: "https://cdn.example.com" }),
129
+ ).toEqual([
130
+ { href: "https://cdn.example.com", crossorigin: false },
131
+ { href: "https://cdn.example.com", crossorigin: true },
132
+ ]);
133
+ expect(
134
+ getPreconnectHints({
135
+ assetBasePath: "https://cdn.example.com",
136
+ siteUrl: "not a url",
137
+ }),
138
+ ).toHaveLength(2);
139
+ });
140
+
141
+ it("skips a base URL it cannot parse", () => {
142
+ expect(
143
+ getPreconnectHints({ assetBasePath: "https://", siteUrl: site }),
144
+ ).toEqual([]);
145
+ });
146
+ });
@@ -0,0 +1,90 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { runDeferred, type DeferredPendingEnv } from "../deferred.js";
3
+
4
+ /** A context whose `executionCtx` throws on access, the way Hono's does. */
5
+ function contextWithoutExecutionCtx(env: object = {}) {
6
+ return {
7
+ env,
8
+ get executionCtx(): { waitUntil: (promise: Promise<unknown>) => void } {
9
+ throw new Error("This context has no ExecutionContext");
10
+ },
11
+ };
12
+ }
13
+
14
+ describe("runDeferred", () => {
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+
19
+ it("hands the promise to waitUntil so a Worker stays alive for it", async () => {
20
+ const waitUntil = vi.fn<(promise: Promise<unknown>) => void>();
21
+ let done = false;
22
+
23
+ runDeferred({ env: {}, executionCtx: { waitUntil } }, "Test", async () => {
24
+ done = true;
25
+ });
26
+
27
+ expect(waitUntil).toHaveBeenCalledOnce();
28
+ await waitUntil.mock.calls[0]?.[0];
29
+ expect(done).toBe(true);
30
+ });
31
+
32
+ // Hono's `c.executionCtx` is a throwing getter, not an undefined property,
33
+ // so reaching for it at all is what has to be guarded.
34
+ it("still runs the work when there is no ExecutionContext", async () => {
35
+ const work = vi.fn(async () => {});
36
+
37
+ runDeferred(contextWithoutExecutionCtx(), "Test", work);
38
+
39
+ expect(work).toHaveBeenCalledOnce();
40
+ await work.mock.results[0]?.value;
41
+ });
42
+
43
+ // The whole reason this helper exists: nothing awaits the promise, so an
44
+ // unhandled rejection has nobody to reject to and ends a Node process.
45
+ it("swallows and logs a failure instead of rejecting", async () => {
46
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
47
+ const pending: Promise<unknown>[] = [];
48
+ const env: DeferredPendingEnv = { __pendingDeferred: pending };
49
+
50
+ runDeferred(contextWithoutExecutionCtx(env), "Discover announcement", () =>
51
+ Promise.reject(new Error("directory unreachable")),
52
+ );
53
+
54
+ await expect(pending[0]).resolves.toBeUndefined();
55
+ expect(error).toHaveBeenCalledWith(
56
+ "[Jant] Discover announcement background error: directory unreachable",
57
+ );
58
+ });
59
+
60
+ it("labels a non-Error rejection too", async () => {
61
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
62
+ const pending: Promise<unknown>[] = [];
63
+
64
+ runDeferred(
65
+ contextWithoutExecutionCtx({ __pendingDeferred: pending }),
66
+ "Telegram",
67
+ () => Promise.reject("boom"),
68
+ );
69
+
70
+ await pending[0];
71
+ expect(error).toHaveBeenCalledWith(
72
+ "[Jant] Telegram background error: boom",
73
+ );
74
+ });
75
+
76
+ it("collects the promise only when a test registered the array", () => {
77
+ const pending: Promise<unknown>[] = [];
78
+
79
+ runDeferred(
80
+ contextWithoutExecutionCtx({ __pendingDeferred: pending }),
81
+ "Test",
82
+ async () => {},
83
+ );
84
+ // A production binding carries no such array; nothing is collected and
85
+ // nothing throws.
86
+ runDeferred(contextWithoutExecutionCtx({}), "Test", async () => {});
87
+
88
+ expect(pending).toHaveLength(1);
89
+ });
90
+ });