@jant/core 0.6.16 → 0.7.1

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 (592) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/export.js +3 -1
  4. package/bin/commands/import-site.js +749 -220
  5. package/bin/commands/reset-password.js +21 -8
  6. package/bin/commands/setup.js +136 -0
  7. package/bin/commands/site/export.js +71 -34
  8. package/bin/commands/site/pull-media.js +2 -6
  9. package/bin/commands/site/snapshot/export.js +19 -61
  10. package/bin/commands/site/snapshot/import.js +23 -24
  11. package/bin/lib/d1-query.js +92 -18
  12. package/bin/lib/hugo-markdown.js +4 -0
  13. package/bin/lib/node-env.js +40 -4
  14. package/bin/lib/site-pull-media.js +21 -28
  15. package/bin/lib/site-selection.js +94 -29
  16. package/bin/lib/site-snapshot.js +338 -3
  17. package/bin/lib/sql-export.js +68 -5
  18. package/bin/lib/wrangler-cli.js +37 -2
  19. package/bin/lib/wrangler-json.js +35 -0
  20. package/bin/lib/zip-archive.js +187 -0
  21. package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
  22. package/dist/client/.vite/manifest.json +255 -90
  23. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  24. package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
  25. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  26. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  27. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  28. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  29. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  30. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  31. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  32. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  33. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  34. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  35. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  36. package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
  37. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  38. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  39. package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
  40. package/dist/client/_assets/client-DAhoqPdr.css +2 -0
  41. package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
  42. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  43. package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
  44. package/dist/client/_assets/client-knSEyUJO.js +295 -0
  45. package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
  46. package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
  47. package/dist/github-sync-Gw4orAHk.js +8398 -0
  48. package/dist/index.js +2 -5
  49. package/dist/node.js +11 -9
  50. package/package.json +15 -5
  51. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  52. package/src/__tests__/basecoat-variants.test.ts +121 -0
  53. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  54. package/src/__tests__/dev-scripts.test.ts +203 -0
  55. package/src/__tests__/export-collection-order.test.ts +276 -0
  56. package/src/__tests__/export-feed-ids.test.ts +184 -0
  57. package/src/__tests__/export-feed-order.test.ts +294 -0
  58. package/src/__tests__/export-hugo-build.test.ts +390 -0
  59. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  60. package/src/__tests__/export-service.test.ts +842 -28
  61. package/src/__tests__/export-smart-collection.test.ts +329 -0
  62. package/src/__tests__/helpers/component-css.ts +91 -0
  63. package/src/__tests__/helpers/db.ts +33 -0
  64. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  65. package/src/__tests__/helpers/hugo-site.ts +146 -0
  66. package/src/__tests__/import-site-command.test.ts +487 -1
  67. package/src/__tests__/mise-config.test.ts +75 -4
  68. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  69. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  70. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  71. package/src/__tests__/snapshot-settings.test.ts +97 -0
  72. package/src/__tests__/snapshot-tables.test.ts +219 -0
  73. package/src/__tests__/sql-export.test.ts +173 -0
  74. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  75. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  76. package/src/__tests__/zip-archive.test.ts +106 -0
  77. package/src/app.tsx +86 -22
  78. package/src/auth.ts +18 -1
  79. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  80. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  81. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  82. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  83. package/src/client/__tests__/compose-launch.test.ts +62 -1
  84. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  85. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  86. package/src/client/__tests__/copy-field.test.ts +162 -0
  87. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  88. package/src/client/__tests__/image-processor.test.ts +324 -2
  89. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  90. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  91. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  92. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  93. package/src/client/__tests__/toast.test.ts +23 -0
  94. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  95. package/src/client/__tests__/video-processor.test.ts +181 -0
  96. package/src/client/audio-processor.ts +24 -15
  97. package/src/client/audio-waveform.ts +49 -0
  98. package/src/client/collection-dialog-host.ts +72 -0
  99. package/src/client/collection-navigation.ts +51 -16
  100. package/src/client/collection-page-actions.ts +26 -0
  101. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  102. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  103. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  104. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  105. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  106. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  107. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  108. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  109. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  110. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  111. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  112. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  113. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  114. package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
  115. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  116. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  117. package/src/client/components/collection-dialog-types.ts +49 -0
  118. package/src/client/components/collection-manager-types.ts +32 -1
  119. package/src/client/components/collection-types.ts +1 -15
  120. package/src/client/components/compose-types.ts +75 -1
  121. package/src/client/components/jant-collection-dialog.ts +722 -0
  122. package/src/client/components/jant-collection-directory.ts +567 -182
  123. package/src/client/components/jant-collection-form.ts +53 -232
  124. package/src/client/components/jant-command-palette.ts +100 -49
  125. package/src/client/components/jant-compose-dialog.ts +1486 -707
  126. package/src/client/components/jant-compose-editor.ts +673 -336
  127. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  128. package/src/client/components/jant-config-editor.ts +65 -53
  129. package/src/client/components/jant-confirm-dialog.ts +13 -11
  130. package/src/client/components/jant-locale-picker.ts +418 -0
  131. package/src/client/components/jant-media-lightbox.ts +146 -136
  132. package/src/client/components/jant-nav-manager.ts +690 -366
  133. package/src/client/components/jant-post-menu.ts +1086 -282
  134. package/src/client/components/jant-post-picker.ts +324 -0
  135. package/src/client/components/jant-repo-picker-types.ts +6 -1
  136. package/src/client/components/jant-repo-picker.ts +137 -108
  137. package/src/client/components/jant-settings-avatar.ts +31 -27
  138. package/src/client/components/jant-settings-general.ts +622 -543
  139. package/src/client/components/jant-settings-language.ts +1087 -0
  140. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  141. package/src/client/components/jant-text-preview.ts +94 -86
  142. package/src/client/components/nav-manager-types.ts +62 -1
  143. package/src/client/components/settings-types.ts +14 -29
  144. package/src/client/components/smart-collection-conditions.ts +285 -0
  145. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  146. package/src/client/compose-bridge.ts +214 -141
  147. package/src/client/compose-launch.ts +76 -2
  148. package/src/client/compose-shortcuts.ts +6 -8
  149. package/src/client/compose-triggers.ts +50 -0
  150. package/src/client/copy-field.ts +108 -0
  151. package/src/client/custom-url-menu.ts +60 -4
  152. package/src/client/icons.ts +14 -0
  153. package/src/client/image-plan.ts +165 -0
  154. package/src/client/image-processor.ts +195 -117
  155. package/src/client/image-worker-client.ts +45 -0
  156. package/src/client/image-worker.ts +135 -0
  157. package/src/client/lazy-entries.ts +95 -0
  158. package/src/client/lazy-slugify.ts +2 -2
  159. package/src/client/media-scroll-hint.ts +43 -2
  160. package/src/client/mediabunny.ts +29 -0
  161. package/src/client/post-picker.ts +41 -0
  162. package/src/client/runtime-paths.ts +74 -1
  163. package/src/client/settings-bridge.ts +4 -10
  164. package/src/client/site-header-nav.js +18 -6
  165. package/src/client/smart-collection-dialog-host.ts +116 -0
  166. package/src/client/smart-collection-page-actions.ts +208 -0
  167. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  168. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  169. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  170. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  171. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  172. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  173. package/src/client/tiptap/bubble-menu.ts +47 -16
  174. package/src/client/tiptap/extensions.ts +0 -3
  175. package/src/client/tiptap/floating-position.ts +216 -2
  176. package/src/client/tiptap/inline-image-upload.ts +7 -3
  177. package/src/client/tiptap/link-toolbar.ts +22 -9
  178. package/src/client/tiptap/structural-keymap.ts +158 -47
  179. package/src/client/toast.ts +13 -0
  180. package/src/client/types/vite-worker.d.ts +12 -0
  181. package/src/client/upload-with-metadata.ts +3 -13
  182. package/src/client/video-processor.ts +351 -107
  183. package/src/client-auth.ts +14 -31
  184. package/src/client-compose.ts +19 -0
  185. package/src/client-manage.ts +13 -0
  186. package/src/client-settings.ts +14 -0
  187. package/src/client-site.ts +10 -3
  188. package/src/client.ts +4 -0
  189. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  190. package/src/db/__tests__/d1-query.test.ts +56 -1
  191. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  192. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  193. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  194. package/src/db/migrations/0032_married_tomas.sql +3 -0
  195. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  196. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  197. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  198. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  199. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  200. package/src/db/migrations/meta/_journal.json +21 -0
  201. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  202. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  203. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  204. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  205. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  206. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  207. package/src/db/migrations/pg/meta/_journal.json +21 -0
  208. package/src/db/pg/schema.ts +195 -27
  209. package/src/db/post-visibility.ts +117 -0
  210. package/src/db/schema.ts +222 -27
  211. package/src/i18n/__tests__/detect.test.ts +26 -81
  212. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  213. package/src/i18n/context.tsx +12 -2
  214. package/src/i18n/detect.ts +7 -66
  215. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  216. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  217. package/src/i18n/locales/public/en.po +490 -158
  218. package/src/i18n/locales/public/en.ts +1 -1
  219. package/src/i18n/locales/public/zh-Hans.po +487 -155
  220. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  221. package/src/i18n/locales/public/zh-Hant.po +487 -155
  222. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  223. package/src/i18n/locales/settings/en.po +555 -73
  224. package/src/i18n/locales/settings/en.ts +1 -1
  225. package/src/i18n/locales/settings/zh-Hans.po +601 -119
  226. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  227. package/src/i18n/locales/settings/zh-Hant.po +614 -132
  228. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  229. package/src/i18n/locales.ts +94 -0
  230. package/src/i18n/middleware.ts +12 -2
  231. package/src/i18n/supported-locales.ts +80 -0
  232. package/src/lib/__tests__/api-settings.test.ts +11 -2
  233. package/src/lib/__tests__/asset-path.test.ts +92 -0
  234. package/src/lib/__tests__/deferred.test.ts +90 -0
  235. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  236. package/src/lib/__tests__/discover.test.ts +410 -0
  237. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  238. package/src/lib/__tests__/feed.test.ts +1526 -15
  239. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  240. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  241. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  242. package/src/lib/__tests__/http-cache.test.ts +162 -0
  243. package/src/lib/__tests__/image.test.ts +27 -1
  244. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  245. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  246. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  247. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  248. package/src/lib/__tests__/markdown.test.ts +10 -0
  249. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  250. package/src/lib/__tests__/navigation.test.ts +135 -1
  251. package/src/lib/__tests__/resolve-config.test.ts +131 -3
  252. package/src/lib/__tests__/schemas.test.ts +69 -5
  253. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  254. package/src/lib/__tests__/slug.test.ts +70 -0
  255. package/src/lib/__tests__/slugify.test.ts +85 -0
  256. package/src/lib/__tests__/storage.test.ts +32 -0
  257. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  258. package/src/lib/__tests__/timeline.test.ts +87 -0
  259. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  260. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  261. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  262. package/src/lib/__tests__/url.test.ts +78 -72
  263. package/src/lib/__tests__/view-language.test.ts +413 -0
  264. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  265. package/src/lib/address.ts +57 -0
  266. package/src/lib/api-posts.ts +12 -0
  267. package/src/lib/asset-path.ts +98 -1
  268. package/src/lib/build-env.ts +16 -0
  269. package/src/lib/collection-groups.ts +5 -1
  270. package/src/lib/collection-paths.ts +3 -10
  271. package/src/lib/constants.ts +28 -3
  272. package/src/lib/copy-field.ts +36 -0
  273. package/src/lib/crypto.ts +40 -0
  274. package/src/lib/csp-builder.ts +2 -1
  275. package/src/lib/deferred.ts +79 -0
  276. package/src/lib/discover-ping.ts +141 -0
  277. package/src/lib/discover.ts +439 -0
  278. package/src/lib/env.ts +134 -0
  279. package/src/lib/errors.ts +33 -0
  280. package/src/lib/feed-policy.ts +236 -2
  281. package/src/lib/feed.ts +926 -73
  282. package/src/lib/filter-dimensions.ts +1349 -0
  283. package/src/lib/github-sync-repo-name.ts +45 -0
  284. package/src/lib/github-sync-site-config.ts +3 -0
  285. package/src/lib/hosted-signin.ts +1 -1
  286. package/src/lib/http-cache.ts +148 -0
  287. package/src/lib/hugo-markdown.ts +62 -0
  288. package/src/lib/icons.ts +9 -2
  289. package/src/lib/ids.ts +3 -0
  290. package/src/lib/image.ts +17 -4
  291. package/src/lib/jant-docs.ts +42 -0
  292. package/src/lib/lang-detect.ts +308 -0
  293. package/src/lib/markdown-manager.ts +392 -2
  294. package/src/lib/media-helpers.ts +13 -1
  295. package/src/lib/navigation.ts +78 -11
  296. package/src/lib/pagination.ts +28 -24
  297. package/src/lib/per-language-surfaces.ts +67 -0
  298. package/src/lib/post-body-html.ts +11 -4
  299. package/src/lib/post-meta.ts +19 -0
  300. package/src/lib/render.tsx +55 -7
  301. package/src/lib/resolve-config.ts +111 -6
  302. package/src/lib/schemas.ts +251 -15
  303. package/src/lib/site-header-fragment.tsx +2 -1
  304. package/src/lib/site-unavailable-page.ts +47 -0
  305. package/src/lib/slug.ts +51 -7
  306. package/src/lib/slugify.ts +113 -0
  307. package/src/lib/startup-config.ts +4 -6
  308. package/src/lib/storage.ts +7 -0
  309. package/src/lib/summary.ts +50 -0
  310. package/src/lib/thread-fold.ts +161 -0
  311. package/src/lib/time.ts +32 -0
  312. package/src/lib/timeline.ts +43 -2
  313. package/src/lib/tiptap-render.ts +1 -2
  314. package/src/lib/tiptap-to-markdown.ts +13 -7
  315. package/src/lib/upload.ts +16 -13
  316. package/src/lib/url.ts +159 -86
  317. package/src/lib/version.ts +10 -8
  318. package/src/lib/view-language.ts +436 -0
  319. package/src/lib/view.ts +53 -25
  320. package/src/lib/viewer-context.ts +62 -0
  321. package/src/middleware/__tests__/auth.test.ts +70 -2
  322. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  323. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  324. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  325. package/src/middleware/__tests__/session.test.ts +181 -33
  326. package/src/middleware/auth.ts +50 -31
  327. package/src/middleware/cache-control.ts +75 -10
  328. package/src/middleware/config.ts +6 -6
  329. package/src/middleware/error-handler.ts +12 -0
  330. package/src/middleware/onboarding.ts +32 -10
  331. package/src/middleware/secure-headers.ts +1 -1
  332. package/src/middleware/session.ts +96 -8
  333. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  334. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  335. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  336. package/src/node/__tests__/cli-setup.test.ts +163 -0
  337. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  338. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  339. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  340. package/src/node/__tests__/runtime.test.ts +75 -0
  341. package/src/node/index.ts +3 -0
  342. package/src/node/request-handler.ts +3 -1
  343. package/src/preset.css +76 -22
  344. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  345. package/src/routes/api/__tests__/collections.test.ts +114 -0
  346. package/src/routes/api/__tests__/discover.test.ts +222 -0
  347. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  348. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  349. package/src/routes/api/__tests__/posts.test.ts +24 -0
  350. package/src/routes/api/__tests__/settings.test.ts +24 -3
  351. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  352. package/src/routes/api/__tests__/upload.test.ts +34 -0
  353. package/src/routes/api/collections.ts +49 -6
  354. package/src/routes/api/custom-urls.ts +0 -1
  355. package/src/routes/api/discover.ts +95 -0
  356. package/src/routes/api/export.ts +6 -3
  357. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  358. package/src/routes/api/internal/sites.ts +0 -1
  359. package/src/routes/api/nav-items.ts +55 -8
  360. package/src/routes/api/posts.ts +161 -0
  361. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  362. package/src/routes/api/public/archive.ts +77 -81
  363. package/src/routes/api/public/posts.ts +44 -7
  364. package/src/routes/api/smart-collections.ts +132 -0
  365. package/src/routes/api/telegram.ts +2 -40
  366. package/src/routes/api/upload.ts +10 -3
  367. package/src/routes/api/uploads.ts +1 -2
  368. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  369. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  370. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  371. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  372. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  373. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  374. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  375. package/src/routes/auth/setup.tsx +934 -186
  376. package/src/routes/auth/signin.tsx +10 -8
  377. package/src/routes/compose.tsx +9 -0
  378. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  379. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  380. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  381. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  382. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  383. package/src/routes/dash/custom-urls.tsx +1 -37
  384. package/src/routes/dash/settings.tsx +714 -111
  385. package/src/routes/discover-announce.ts +96 -0
  386. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  387. package/src/routes/feed/feed.ts +69 -40
  388. package/src/routes/feed/sitemap.ts +95 -11
  389. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  390. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  391. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  392. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  393. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  394. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  395. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  396. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  397. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  398. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  399. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  400. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  401. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  402. package/src/routes/pages/archive.tsx +487 -379
  403. package/src/routes/pages/collection.tsx +94 -96
  404. package/src/routes/pages/collections.tsx +38 -50
  405. package/src/routes/pages/featured.tsx +43 -18
  406. package/src/routes/pages/home.tsx +25 -6
  407. package/src/routes/pages/language.tsx +113 -0
  408. package/src/routes/pages/latest.tsx +26 -16
  409. package/src/routes/pages/new.tsx +3 -0
  410. package/src/routes/pages/page.tsx +228 -45
  411. package/src/routes/pages/partials.tsx +41 -0
  412. package/src/routes/pages/search.tsx +22 -5
  413. package/src/routes/pages/smart-collection.tsx +340 -0
  414. package/src/routes/pages/subscribe.tsx +182 -0
  415. package/src/runtime/__tests__/index.test.ts +19 -0
  416. package/src/runtime/__tests__/node.test.ts +129 -4
  417. package/src/runtime/__tests__/readiness.test.ts +23 -0
  418. package/src/runtime/cloudflare.ts +21 -2
  419. package/src/runtime/index.ts +27 -1
  420. package/src/runtime/node.ts +67 -1
  421. package/src/runtime/readiness.ts +15 -0
  422. package/src/runtime/site.ts +95 -24
  423. package/src/services/__tests__/auth.test.ts +116 -7
  424. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  425. package/src/services/__tests__/collection.test.ts +108 -4
  426. package/src/services/__tests__/custom-url.test.ts +34 -0
  427. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  428. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  429. package/src/services/__tests__/language.test.ts +495 -0
  430. package/src/services/__tests__/media.test.ts +31 -0
  431. package/src/services/__tests__/navigation.test.ts +213 -10
  432. package/src/services/__tests__/path.test.ts +236 -0
  433. package/src/services/__tests__/post-language.test.ts +830 -0
  434. package/src/services/__tests__/post-timeline.test.ts +127 -0
  435. package/src/services/__tests__/post.test.ts +120 -9
  436. package/src/services/__tests__/settings.test.ts +228 -17
  437. package/src/services/__tests__/site-admin.test.ts +5 -5
  438. package/src/services/__tests__/smart-collection.test.ts +651 -0
  439. package/src/services/about-page.ts +22 -1
  440. package/src/services/auth.ts +51 -49
  441. package/src/services/bootstrap.ts +408 -39
  442. package/src/services/collection-directory-position.ts +76 -0
  443. package/src/services/collection.ts +331 -46
  444. package/src/services/custom-url.ts +29 -24
  445. package/src/services/export-theme/assets/client-site.css +1 -1
  446. package/src/services/export-theme/assets/client-site.js +123 -123
  447. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  448. package/src/services/export-theme/layouts/_default/list.html +2 -63
  449. package/src/services/export-theme/layouts/_default/rss.xml +103 -38
  450. package/src/services/export-theme/layouts/collections/list.html +3 -3
  451. package/src/services/export-theme/layouts/featured/list.html +1 -4
  452. package/src/services/export-theme/layouts/index.html +40 -26
  453. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  454. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  455. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  456. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  457. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  458. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  459. package/src/services/export-theme/layouts/partials/head.html +1 -1
  460. package/src/services/export-theme/layouts/partials/header.html +5 -3
  461. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  462. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  463. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  464. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  465. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  466. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  467. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  468. package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
  469. package/src/services/export-theme/layouts/post/list.html +1 -1
  470. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  471. package/src/services/export-theme/styles/main.css +13 -2
  472. package/src/services/export-theme/theme.toml +1 -1
  473. package/src/services/export.ts +880 -72
  474. package/src/services/github-app-installations.ts +171 -4
  475. package/src/services/github-sync.ts +69 -15
  476. package/src/services/index.ts +34 -3
  477. package/src/services/language.ts +412 -0
  478. package/src/services/mcp.ts +22 -2
  479. package/src/services/media.ts +22 -7
  480. package/src/services/navigation.ts +429 -67
  481. package/src/services/path.ts +254 -6
  482. package/src/services/post.ts +1234 -190
  483. package/src/services/search.ts +39 -2
  484. package/src/services/settings.ts +238 -43
  485. package/src/services/site-admin.ts +27 -14
  486. package/src/services/site.ts +160 -32
  487. package/src/services/smart-collection.ts +636 -0
  488. package/src/services/telegram.ts +20 -22
  489. package/src/services/upload-session.ts +1 -3
  490. package/src/style-author.css +27 -0
  491. package/src/styles/components-author.css +1622 -0
  492. package/src/styles/components.css +24 -1571
  493. package/src/styles/site-media.css +16 -2
  494. package/src/styles/tokens.css +82 -12
  495. package/src/styles/ui-author.css +7602 -0
  496. package/src/styles/ui.css +1090 -7216
  497. package/src/types/app-context.ts +23 -0
  498. package/src/types/bindings.ts +8 -0
  499. package/src/types/config.ts +94 -15
  500. package/src/types/constants.ts +188 -16
  501. package/src/types/entities.ts +87 -1
  502. package/src/types/operations.ts +74 -2
  503. package/src/types/props.ts +157 -31
  504. package/src/types/views.ts +55 -1
  505. package/src/ui/__tests__/font-themes.test.ts +52 -12
  506. package/src/ui/compose/ComposeDialog.tsx +201 -7
  507. package/src/ui/compose/ComposePrompt.tsx +1 -5
  508. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  509. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  510. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  511. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  512. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  513. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  514. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  515. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  516. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  517. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  518. package/src/ui/dash/settings/GeneralContent.tsx +277 -113
  519. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  520. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  521. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  522. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  523. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  524. package/src/ui/dash/settings/discover-copy.ts +116 -0
  525. package/src/ui/feed/LinkCard.tsx +55 -45
  526. package/src/ui/feed/NoteCard.tsx +9 -4
  527. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  528. package/src/ui/feed/QuoteCard.tsx +55 -30
  529. package/src/ui/feed/ThreadPreview.tsx +6 -2
  530. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  531. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  532. package/src/ui/feed/thread-preview-state.ts +20 -6
  533. package/src/ui/font-themes.ts +39 -29
  534. package/src/ui/layouts/BaseLayout.tsx +155 -10
  535. package/src/ui/layouts/SiteLayout.tsx +192 -34
  536. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  537. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  538. package/src/ui/pages/ArchivePage.tsx +282 -289
  539. package/src/ui/pages/CollectionPage.tsx +86 -76
  540. package/src/ui/pages/CollectionsPage.tsx +5 -0
  541. package/src/ui/pages/ComposePage.tsx +8 -0
  542. package/src/ui/pages/PostPage.tsx +47 -0
  543. package/src/ui/pages/SearchPage.tsx +3 -3
  544. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  545. package/src/ui/pages/SubscribePage.tsx +236 -0
  546. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  547. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  548. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  549. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  550. package/src/ui/shared/CollectionsManager.tsx +70 -17
  551. package/src/ui/shared/CopyField.tsx +127 -0
  552. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  553. package/src/ui/shared/MediaGallery.tsx +125 -46
  554. package/src/ui/shared/Pagination.tsx +20 -5
  555. package/src/ui/shared/PostFooter.tsx +20 -11
  556. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  557. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  558. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  559. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  560. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  561. package/src/ui/shared/archive-labels.ts +98 -0
  562. package/src/ui/shared/collection-management-labels.ts +94 -16
  563. package/src/ui/shared/navigation-labels.ts +45 -71
  564. package/src/ui/shared/post-article-attributes.ts +2 -0
  565. package/src/ui/shared/smart-collection-labels.ts +343 -0
  566. package/dist/app-C75Zc8dD.js +0 -6
  567. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  568. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  569. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  570. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  571. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  572. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  573. package/dist/env-BPYViDJJ.js +0 -281
  574. package/dist/export-Dl5KCiEs.js +0 -5562
  575. package/dist/github-api-BNF35Lkx.js +0 -176
  576. package/dist/github-app-DdiD-bC4.js +0 -275
  577. package/dist/github-sync-Bqg62IvV.js +0 -4
  578. package/dist/github-sync-HdK2EgvJ.js +0 -431
  579. package/dist/url-CbLAtlZe.js +0 -900
  580. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  581. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  582. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  583. package/src/client/collection-created-notice.ts +0 -66
  584. package/src/client/collection-form-bridge.ts +0 -191
  585. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  586. package/src/client/components/jant-post-form.ts +0 -422
  587. package/src/client/components/post-form-template.ts +0 -352
  588. package/src/client/components/post-form-types.ts +0 -96
  589. package/src/client/media-metadata.ts +0 -247
  590. package/src/client/post-form-bridge.ts +0 -193
  591. package/src/client/tiptap/exitable-marks.ts +0 -73
  592. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -310,47 +310,51 @@ export class JantTextPreview extends LitElement {
310
310
  </svg>
311
311
  </button>
312
312
  <div class="text-preview-toolbar-actions">
313
- ${this.#shareHref
314
- ? html`<button
315
- type="button"
316
- class="text-preview-btn"
317
- @click=${() => this.#copyLink()}
318
- ?disabled=${this._loading}
319
- title="Copy link"
320
- aria-label="Copy shareable link"
321
- >
322
- ${this._linkCopied
323
- ? html`<svg
324
- width="16"
325
- height="16"
326
- viewBox="0 0 24 24"
327
- fill="none"
328
- stroke="currentColor"
329
- stroke-width="2"
330
- stroke-linecap="round"
331
- stroke-linejoin="round"
332
- >
333
- <path d="M20 6 9 17l-5-5" />
334
- </svg>`
335
- : html`<svg
336
- width="16"
337
- height="16"
338
- viewBox="0 0 24 24"
339
- fill="none"
340
- stroke="currentColor"
341
- stroke-width="2"
342
- stroke-linecap="round"
343
- stroke-linejoin="round"
344
- >
345
- <path
346
- d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
347
- />
348
- <path
349
- d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
350
- />
351
- </svg>`}
352
- </button>`
353
- : nothing}
313
+ ${
314
+ this.#shareHref
315
+ ? html`<button
316
+ type="button"
317
+ class="text-preview-btn"
318
+ @click=${() => this.#copyLink()}
319
+ ?disabled=${this._loading}
320
+ title="Copy link"
321
+ aria-label="Copy shareable link"
322
+ >
323
+ ${
324
+ this._linkCopied
325
+ ? html`<svg
326
+ width="16"
327
+ height="16"
328
+ viewBox="0 0 24 24"
329
+ fill="none"
330
+ stroke="currentColor"
331
+ stroke-width="2"
332
+ stroke-linecap="round"
333
+ stroke-linejoin="round"
334
+ >
335
+ <path d="M20 6 9 17l-5-5" />
336
+ </svg>`
337
+ : html`<svg
338
+ width="16"
339
+ height="16"
340
+ viewBox="0 0 24 24"
341
+ fill="none"
342
+ stroke="currentColor"
343
+ stroke-width="2"
344
+ stroke-linecap="round"
345
+ stroke-linejoin="round"
346
+ >
347
+ <path
348
+ d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
349
+ />
350
+ <path
351
+ d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
352
+ />
353
+ </svg>`
354
+ }
355
+ </button>`
356
+ : nothing
357
+ }
354
358
  <button
355
359
  type="button"
356
360
  class="text-preview-btn"
@@ -359,54 +363,58 @@ export class JantTextPreview extends LitElement {
359
363
  title="Copy text"
360
364
  aria-label="Copy text content"
361
365
  >
362
- ${this._copied
363
- ? html`<svg
364
- width="16"
365
- height="16"
366
- viewBox="0 0 24 24"
367
- fill="none"
368
- stroke="currentColor"
369
- stroke-width="2"
370
- stroke-linecap="round"
371
- stroke-linejoin="round"
372
- >
373
- <path d="M20 6 9 17l-5-5" />
374
- </svg>`
375
- : html`<svg
376
- width="16"
377
- height="16"
378
- viewBox="0 0 24 24"
379
- fill="none"
380
- stroke="currentColor"
381
- stroke-width="2"
382
- stroke-linecap="round"
383
- stroke-linejoin="round"
384
- >
385
- <rect width="14" height="14" x="8" y="8" rx="2" />
386
- <path
387
- d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
388
- />
389
- </svg>`}
366
+ ${
367
+ this._copied
368
+ ? html`<svg
369
+ width="16"
370
+ height="16"
371
+ viewBox="0 0 24 24"
372
+ fill="none"
373
+ stroke="currentColor"
374
+ stroke-width="2"
375
+ stroke-linecap="round"
376
+ stroke-linejoin="round"
377
+ >
378
+ <path d="M20 6 9 17l-5-5" />
379
+ </svg>`
380
+ : html`<svg
381
+ width="16"
382
+ height="16"
383
+ viewBox="0 0 24 24"
384
+ fill="none"
385
+ stroke="currentColor"
386
+ stroke-width="2"
387
+ stroke-linecap="round"
388
+ stroke-linejoin="round"
389
+ >
390
+ <rect width="14" height="14" x="8" y="8" rx="2" />
391
+ <path
392
+ d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
393
+ />
394
+ </svg>`
395
+ }
390
396
  </button>
391
397
  </div>
392
398
  </div>
393
- ${this._loading
394
- ? html`<div class="text-preview-loading">
395
- <svg
396
- class="animate-spin size-5"
397
- viewBox="0 0 24 24"
398
- fill="none"
399
- stroke="currentColor"
400
- stroke-width="2"
401
- stroke-linecap="round"
402
- stroke-linejoin="round"
403
- >
404
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
405
- </svg>
406
- </div>`
407
- : html`<div class="text-preview-body prose">
408
- ${unsafeHTML(this._html)}
409
- </div>`}
399
+ ${
400
+ this._loading
401
+ ? html`<div class="text-preview-loading">
402
+ <svg
403
+ class="animate-spin size-5"
404
+ viewBox="0 0 24 24"
405
+ fill="none"
406
+ stroke="currentColor"
407
+ stroke-width="2"
408
+ stroke-linecap="round"
409
+ stroke-linejoin="round"
410
+ >
411
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
412
+ </svg>
413
+ </div>`
414
+ : html`<div class="text-preview-body prose">
415
+ ${unsafeHTML(this._html)}
416
+ </div>`
417
+ }
410
418
  </div>
411
419
  </dialog>
412
420
  `;
@@ -7,12 +7,17 @@ import type { CollectionFormLabels } from "./collection-types.js";
7
7
 
8
8
  export interface NavManagerItem {
9
9
  id: string;
10
- type: "link" | "system" | "collection" | "page";
10
+ type: "link" | "system" | "collection" | "smart_collection" | "page";
11
11
  systemKey?: SystemNavKey;
12
12
  collectionId?: string;
13
+ smartCollectionId?: string;
13
14
  postId?: string;
15
+ /** Author's override, or `""` when the item follows its target's title */
14
16
  label: string;
17
+ /** Resolved label from the server-rendered list */
15
18
  displayLabel?: string;
19
+ /** Target's current title, present on mutation responses */
20
+ targetTitle?: string;
16
21
  url: string;
17
22
  placement?: "header" | "more";
18
23
  }
@@ -24,6 +29,36 @@ export interface NavManagerPage {
24
29
  updatedAt: number;
25
30
  }
26
31
 
32
+ /**
33
+ * What the server made of an address the author pasted into the page picker:
34
+ * the item it could become, or why it cannot become one.
35
+ *
36
+ * `address` is what to show back and what to prefill the link form with — the
37
+ * public path for anything on this site, the pasted URL for anything else.
38
+ */
39
+ export type NavAddressResolution =
40
+ | { kind: "page"; address: string; page: NavManagerPage }
41
+ | {
42
+ kind: "collection";
43
+ address: string;
44
+ collection: { id: string; title: string; slug: string };
45
+ }
46
+ | {
47
+ kind: "smart_collection";
48
+ address: string;
49
+ smartCollection: { id: string; title: string; slug: string };
50
+ }
51
+ | {
52
+ kind:
53
+ | "external"
54
+ | "link_only"
55
+ | "untitled"
56
+ | "unpublished"
57
+ | "private"
58
+ | "not_found";
59
+ address: string;
60
+ };
61
+
27
62
  export interface SystemNavConfig {
28
63
  key: SystemNavKey;
29
64
  label: string;
@@ -37,6 +72,14 @@ export interface NavManagerCollection {
37
72
  slug: string;
38
73
  /** Group label from directory divider, if this collection belongs to one */
39
74
  group?: string | null;
75
+ /**
76
+ * True for a collection whose members come from conditions.
77
+ *
78
+ * Both kinds belong in navigation — a reader following either lands on a
79
+ * page of posts — so the picker lists them together and marks which is
80
+ * which, rather than keeping two lists that would need two empty states.
81
+ */
82
+ isSmart?: boolean;
40
83
  }
41
84
 
42
85
  export interface NavManagerSuggestedLink {
@@ -84,7 +127,17 @@ export interface NavManagerLabels {
84
127
  addPageDescription: string;
85
128
  addPage: string;
86
129
  searchPages: string;
130
+ searchPagesHint: string;
87
131
  recentPages: string;
132
+ addressMatch: string;
133
+ addressAlreadyAdded: string;
134
+ addressNotFound: string;
135
+ addressUnpublished: string;
136
+ addressPrivate: string;
137
+ addressUntitled: string;
138
+ addressExternal: string;
139
+ addressLinkOnly: string;
140
+ addressAddAsLink: string;
88
141
  searchingPages: string;
89
142
  noMatchingPages: string;
90
143
  noPages: string;
@@ -111,6 +164,8 @@ export interface NavManagerLabels {
111
164
  back: string;
112
165
  collection: string;
113
166
  addCollection: string;
167
+ /** Tooltip on the icon marking a smart collection in the picker. */
168
+ smartCollectionLabel?: string;
114
169
  addCollectionToNavigation: string;
115
170
  addCollectionDescription: string;
116
171
  allCollectionsAdded: string;
@@ -126,6 +181,12 @@ export interface NavManagerLabels {
126
181
  confirmDeleteCollection: string;
127
182
  headerSection: string;
128
183
  moreSection: string;
184
+ /**
185
+ * Label on the preview's overflow button. Mirrors the real site header, so it
186
+ * comes from the public nav catalog (untranslated) rather than `moreSection`,
187
+ * which is the translated settings section heading.
188
+ */
189
+ previewMore: string;
129
190
  moreEmptyHint: string;
130
191
  placementSaved: string;
131
192
  cancel: string;
@@ -23,9 +23,9 @@ export interface SettingsLabels {
23
23
  aboutPageConflict: string;
24
24
  createAboutPage: string;
25
25
  editAboutPage: string;
26
- languageAndTime: string;
26
+ timeSection: string;
27
27
  home: string;
28
- search: string;
28
+ siteVisibility: string;
29
29
  siteName: string;
30
30
  aboutBlog: string;
31
31
  aboutBlogHelp: string;
@@ -37,6 +37,7 @@ export interface SettingsLabels {
37
37
  mainRssFeedWarning: string;
38
38
  availableFeedUrls: string;
39
39
  availableFeedUrlsHelp: string;
40
+ feedsDocs: string;
40
41
  mainFeedUrl: string;
41
42
  latestFeedUrl: string;
42
43
  featuredFeedUrl: string;
@@ -48,23 +49,20 @@ export interface SettingsLabels {
48
49
  featuredFeedOptionDescription: string;
49
50
  showJantBrandingOnHome: string;
50
51
  markdownSupported: string;
51
- siteLanguage: string;
52
- siteLanguageHelp: string;
53
- /** Placeholder shown inside the locale combobox search field. */
54
- siteLanguageSearchPlaceholder: string;
55
- /** Empty-state message when the search filters out every option. */
56
- siteLanguageNoMatches: string;
57
- /** Lead text before the live `<html lang>` preview. */
58
- contentLanguagePreview: string;
59
- dashboardLanguage: string;
60
- dashboardLanguageHelp: string;
61
- cjkFont: string;
62
- cjkFontHelp: string;
63
52
  timeZone: string;
64
53
 
65
54
  // Search
66
55
  allowIndexing: string;
67
56
  demoSeoLocked: string;
57
+ discoverName: string;
58
+ discoverRules: string;
59
+ discoverEnabled: string;
60
+ discoverIntro: string;
61
+ discoverDemoLocked: string;
62
+ discoverFeedsOffLocked: string;
63
+ discoverSearchOff: string;
64
+ discoverAnnounce: string;
65
+ discoverAnnounceManual: string;
68
66
 
69
67
  // Actions
70
68
  save: string;
@@ -80,29 +78,16 @@ export interface SettingsTimezone {
80
78
  label: string;
81
79
  }
82
80
 
83
- /** CJK font option for the select dropdown */
84
- export interface SettingsCjkFont {
85
- value: string;
86
- label: string;
87
- }
88
-
89
- /** Dashboard UI language option for the select dropdown */
90
- export interface SettingsDashboardLanguage {
91
- value: string;
92
- label: string;
93
- }
94
-
95
81
  export interface SettingsInitialData {
96
82
  siteName: string;
97
83
  siteDescription: string;
98
- siteLanguage: string;
99
- dashboardLanguage: string;
100
- cjkSerifFont: string;
101
84
  mainRssFeed: string;
102
85
  timeZone: string;
103
86
  siteFooter: string;
104
87
  showJantBrandingOnHome: boolean;
105
88
  noindex: boolean;
89
+ /** Stored Discover choice; "" when the owner has never used the control. */
90
+ discover: string;
106
91
  }
107
92
 
108
93
  export type SettingsAboutPageStatus =
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Condition controls for the smart collection dialog.
3
+ *
4
+ * Each control renders one dimension and produces a string in that dimension's
5
+ * **URL spelling** — the same one a query string carries. That is the whole
6
+ * trick: the dialog never encodes a value itself, so it cannot invent a second
7
+ * vocabulary beside the registry's, and adding a dimension that reuses an
8
+ * existing control shape adds nothing here at all.
9
+ *
10
+ * There is no "unset" option in any control. A row exists or it does not, and
11
+ * that is already how "unset" is said; a second way to say it would be two
12
+ * switches for one state.
13
+ */
14
+
15
+ import { html, type TemplateResult } from "lit";
16
+ import {
17
+ FILTER_DIMENSION_KEYS,
18
+ FILTER_DIMENSIONS,
19
+ parsePostFilterSelection,
20
+ serializePostFilterSelection,
21
+ type PostFilterSelection,
22
+ } from "../../lib/filter-dimensions.js";
23
+ import { FORMATS, MEDIA_KINDS } from "../../types/constants.js";
24
+ import type {
25
+ SmartCollectionDialogLabels,
26
+ SmartConditionRow,
27
+ } from "./smart-collection-dialog-types.js";
28
+
29
+ export type DimensionKey = (typeof FILTER_DIMENSION_KEYS)[number];
30
+
31
+ type Emit = (value: string) => void;
32
+
33
+ interface DimensionControl {
34
+ /** The value a freshly added row starts on. */
35
+ defaultValue: string;
36
+ render(
37
+ value: string,
38
+ emit: Emit,
39
+ labels: SmartCollectionDialogLabels,
40
+ ): TemplateResult;
41
+ }
42
+
43
+ /** A plain `<select>` over a fixed list of URL values. */
44
+ function selectControl(
45
+ dimension: DimensionKey,
46
+ values: readonly string[],
47
+ ): DimensionControl["render"] {
48
+ return (value, emit, labels) => html`
49
+ <select
50
+ class="select smart-condition-select"
51
+ .value=${value}
52
+ @change=${(event: Event) =>
53
+ emit((event.target as HTMLSelectElement).value)}
54
+ >
55
+ ${values.map(
56
+ (option) => html`
57
+ <option value=${option} ?selected=${option === value}>
58
+ ${labels.values[`${dimension}.${option}`] ?? option}
59
+ </option>
60
+ `,
61
+ )}
62
+ </select>
63
+ `;
64
+ }
65
+
66
+ /** `any` / `none`, the two states a presence dimension has. */
67
+ function presenceControl(dimension: DimensionKey): DimensionControl["render"] {
68
+ return selectControl(dimension, ["any", "none"]);
69
+ }
70
+
71
+ /**
72
+ * The media control, over the one folded vocabulary.
73
+ *
74
+ * `any`, `none`, or a set of kinds — one value, because a presence flag beside
75
+ * a kind list would allow "no media, of kinds image and video", which means
76
+ * nothing. Ticking a kind while `any` or `none` is selected replaces it.
77
+ */
78
+ const mediaControl: DimensionControl["render"] = (value, emit, labels) => {
79
+ const kinds = value === "any" || value === "none" ? [] : value.split(",");
80
+
81
+ const toggleKind = (kind: string, checked: boolean) => {
82
+ const next = checked
83
+ ? [...kinds, kind]
84
+ : kinds.filter((entry) => entry !== kind);
85
+ // An empty set is not a third meaning — it falls back to "has media".
86
+ emit(next.length > 0 ? next.join(",") : "any");
87
+ };
88
+
89
+ return html`
90
+ <div class="smart-condition-media">
91
+ <select
92
+ class="select smart-condition-select"
93
+ .value=${value === "none" ? "none" : "any"}
94
+ @change=${(event: Event) =>
95
+ emit((event.target as HTMLSelectElement).value)}
96
+ >
97
+ <option value="any" ?selected=${value !== "none"}>
98
+ ${labels.values["media.any"] ?? "any"}
99
+ </option>
100
+ <option value="none" ?selected=${value === "none"}>
101
+ ${labels.values["media.none"] ?? "none"}
102
+ </option>
103
+ </select>
104
+ ${
105
+ value === "none"
106
+ ? null
107
+ : html`
108
+ <div class="smart-condition-media-kinds">
109
+ ${MEDIA_KINDS.map(
110
+ (kind) => html`
111
+ <label class="smart-condition-kind">
112
+ <input
113
+ class="input"
114
+ type="checkbox"
115
+ .checked=${kinds.includes(kind)}
116
+ @change=${(event: Event) =>
117
+ toggleKind(
118
+ kind,
119
+ (event.target as HTMLInputElement).checked,
120
+ )}
121
+ />
122
+ <span>${labels.values[`media.${kind}`] ?? kind}</span>
123
+ </label>
124
+ `,
125
+ )}
126
+ </div>
127
+ `
128
+ }
129
+ </div>
130
+ `;
131
+ };
132
+
133
+ /** A year, typed. Bounded below by the first year a Unix timestamp can name. */
134
+ const yearControl: DimensionControl["render"] = (value, emit) => html`
135
+ <input
136
+ class="input smart-condition-year"
137
+ type="number"
138
+ min="1971"
139
+ step="1"
140
+ .value=${value}
141
+ @input=${(event: Event) => emit((event.target as HTMLInputElement).value)}
142
+ />
143
+ `;
144
+
145
+ /**
146
+ * A collection, chosen from the ones this site has.
147
+ *
148
+ * The options are filled in by the dialog host, which knows the site's
149
+ * collections; the control only spells the chosen slug.
150
+ */
151
+ const collectionControl: DimensionControl["render"] = (value, emit, labels) => {
152
+ const options = Object.entries(labels.values)
153
+ .filter(([key]) => key.startsWith("collection."))
154
+ .map(([key, label]) => [key.slice("collection.".length), label] as const);
155
+
156
+ return html`
157
+ <select
158
+ class="select smart-condition-select"
159
+ .value=${value}
160
+ @change=${(event: Event) =>
161
+ emit((event.target as HTMLSelectElement).value)}
162
+ >
163
+ ${options.map(
164
+ ([slug, label]) => html`
165
+ <option value=${slug} ?selected=${slug === value}>${label}</option>
166
+ `,
167
+ )}
168
+ </select>
169
+ `;
170
+ };
171
+
172
+ /** One control per dimension. Adding a dimension that reuses a shape adds none. */
173
+ export const DIMENSION_CONTROLS: Record<DimensionKey, DimensionControl> = {
174
+ collection: { defaultValue: "", render: collectionControl },
175
+ format: {
176
+ defaultValue: FORMATS[0],
177
+ render: selectControl("format", FORMATS),
178
+ },
179
+ title: { defaultValue: "any", render: presenceControl("title") },
180
+ year: {
181
+ defaultValue: String(new Date().getUTCFullYear()),
182
+ render: yearControl,
183
+ },
184
+ media: { defaultValue: "any", render: mediaControl },
185
+ replies: { defaultValue: "any", render: presenceControl("replies") },
186
+ visibility: {
187
+ defaultValue: "public",
188
+ // `private` is absent on purpose: a smart collection is a published page,
189
+ // so it can never name the one set only its author can see.
190
+ render: selectControl("visibility", ["public", "featured", "hidden"]),
191
+ },
192
+ };
193
+
194
+ /** The empty selection — no conditions, which means every post. */
195
+ export function emptySelection(): PostFilterSelection {
196
+ return {};
197
+ }
198
+
199
+ /**
200
+ * Read a stored selection into condition rows.
201
+ *
202
+ * Goes through the registry's serializer so every row holds the same URL
203
+ * spelling the controls produce.
204
+ *
205
+ * @param selection - A stored selection, as the API returns it
206
+ * @returns One row per set dimension, in registry order
207
+ * @example
208
+ * selectionToRows({ format: "quote" }); // [{ key: "format", value: "quote" }]
209
+ */
210
+ export function selectionToRows(
211
+ selection: Record<string, unknown>,
212
+ ): SmartConditionRow[] {
213
+ const params = serializePostFilterSelection(
214
+ selection as PostFilterSelection,
215
+ { collections: collectionVocabulary() },
216
+ );
217
+
218
+ const rows: SmartConditionRow[] = [];
219
+ for (const key of FILTER_DIMENSION_KEYS) {
220
+ const value = params.get(FILTER_DIMENSIONS[key].url.param);
221
+ if (value !== null) rows.push({ key, value });
222
+ }
223
+ return rows;
224
+ }
225
+
226
+ /**
227
+ * Turn condition rows back into a selection the API accepts.
228
+ *
229
+ * Runs the rows through the registry's own parser rather than mapping them by
230
+ * hand — the values are already in the URL spelling, so this is exactly the
231
+ * read the archive performs.
232
+ *
233
+ * @param rows - The dialog's condition rows
234
+ * @returns The selection, with any unreadable row dropped
235
+ * @example
236
+ * rowsToSelection([{ key: "format", value: "quote" }]); // { format: "quote" }
237
+ */
238
+ export function rowsToSelection(
239
+ rows: readonly SmartConditionRow[],
240
+ ): PostFilterSelection {
241
+ const params = new URLSearchParams();
242
+ for (const row of rows) {
243
+ if (!row.value) continue;
244
+ const dimension = FILTER_DIMENSIONS[row.key];
245
+ if (dimension) params.set(dimension.url.param, row.value);
246
+ }
247
+
248
+ const { selection } = parsePostFilterSelection(
249
+ (key) => params.get(key) ?? undefined,
250
+ { collections: collectionVocabulary() },
251
+ );
252
+ return selection;
253
+ }
254
+
255
+ /**
256
+ * The site's collections, as slug↔id lookups.
257
+ *
258
+ * Filled in by the dialog host before the dialog opens. Kept module-level
259
+ * because both conversions above need it and neither is called from a place
260
+ * that has it to hand.
261
+ */
262
+ let vocabulary = {
263
+ idBySlug: new Map<string, string>(),
264
+ slugById: new Map<string, string>(),
265
+ titleById: new Map<string, string>(),
266
+ };
267
+
268
+ export function setCollectionVocabulary(
269
+ collections: readonly { id: string; slug: string; title: string }[],
270
+ ): void {
271
+ const idBySlug = new Map<string, string>();
272
+ const slugById = new Map<string, string>();
273
+ const titleById = new Map<string, string>();
274
+ for (const collection of collections) {
275
+ idBySlug.set(collection.slug, collection.id);
276
+ slugById.set(collection.id, collection.slug);
277
+ titleById.set(collection.id, collection.title);
278
+ }
279
+ vocabulary = { idBySlug, slugById, titleById };
280
+ }
281
+
282
+ /** The current lookups. Exported so the upgrade check can resolve a slug. */
283
+ export function collectionVocabulary() {
284
+ return vocabulary;
285
+ }