@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
@@ -0,0 +1,207 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { describe, it, expect, afterEach } from "vitest";
4
+
5
+ import "../jant-locale-picker.js";
6
+ import type { JantLocalePicker } from "../jant-locale-picker.js";
7
+
8
+ const locales = [
9
+ { tag: "en", native: "English", english: "English", coverage: 1 },
10
+ {
11
+ tag: "zh-Hans",
12
+ native: "简体中文",
13
+ english: "Simplified Chinese",
14
+ coverage: 1,
15
+ },
16
+ { tag: "ja", native: "日本語", english: "Japanese", coverage: 0 },
17
+ { tag: "fi", native: "suomi", english: "Finnish", coverage: 0 },
18
+ ];
19
+
20
+ const labels = { search: "Search…", empty: "No matches." };
21
+
22
+ async function createPicker(options?: {
23
+ value?: string;
24
+ exclude?: string[];
25
+ target?: HTMLSelectElement;
26
+ }): Promise<JantLocalePicker> {
27
+ if (options?.target) document.body.appendChild(options.target);
28
+ const el = document.createElement("jant-locale-picker") as JantLocalePicker;
29
+ el.locales = locales;
30
+ el.labels = labels;
31
+ el.value = options?.value ?? "en";
32
+ if (options?.exclude) el.exclude = options.exclude;
33
+ if (options?.target) el.htmlFor = options.target.id;
34
+ document.body.appendChild(el);
35
+ await el.updateComplete;
36
+ return el;
37
+ }
38
+
39
+ function trigger(el: JantLocalePicker): HTMLButtonElement {
40
+ const button = el.querySelector<HTMLButtonElement>("[data-language-trigger]");
41
+ if (!button) throw new Error("trigger missing");
42
+ return button;
43
+ }
44
+
45
+ function optionLabels(el: JantLocalePicker): string[] {
46
+ return [...el.querySelectorAll('[role="option"]')].map(
47
+ (node) => node.textContent?.trim() ?? "",
48
+ );
49
+ }
50
+
51
+ async function press(el: JantLocalePicker, key: string) {
52
+ document.dispatchEvent(
53
+ new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }),
54
+ );
55
+ await el.updateComplete;
56
+ }
57
+
58
+ function makeSelect(id: string, value: string): HTMLSelectElement {
59
+ const select = document.createElement("select");
60
+ select.id = id;
61
+ for (const locale of locales) {
62
+ const option = document.createElement("option");
63
+ option.value = locale.tag;
64
+ option.textContent = locale.native;
65
+ select.appendChild(option);
66
+ }
67
+ select.value = value;
68
+ return select;
69
+ }
70
+
71
+ afterEach(() => {
72
+ document.body.innerHTML = "";
73
+ });
74
+
75
+ describe("JantLocalePicker", () => {
76
+ it("shows the selected language on the trigger", async () => {
77
+ const el = await createPicker({ value: "zh-Hans" });
78
+ expect(trigger(el).textContent).toContain("简体中文");
79
+ });
80
+
81
+ it("filters on native name, English name, or tag", async () => {
82
+ const el = await createPicker();
83
+ trigger(el).click();
84
+ await el.updateComplete;
85
+ expect(optionLabels(el)).toHaveLength(locales.length);
86
+
87
+ const search = el.querySelector<HTMLInputElement>("[data-language-search]");
88
+ if (!search) throw new Error("search missing");
89
+
90
+ search.value = "finn";
91
+ search.dispatchEvent(new Event("input", { bubbles: true }));
92
+ await el.updateComplete;
93
+ expect(optionLabels(el).join()).toContain("suomi");
94
+ expect(optionLabels(el)).toHaveLength(1);
95
+
96
+ search.value = "zh";
97
+ search.dispatchEvent(new Event("input", { bubbles: true }));
98
+ await el.updateComplete;
99
+ expect(optionLabels(el).join()).toContain("简体中文");
100
+ });
101
+
102
+ it("leaves out excluded languages", async () => {
103
+ const el = await createPicker({ exclude: ["ja", "fi"] });
104
+ trigger(el).click();
105
+ await el.updateComplete;
106
+ expect(optionLabels(el)).toHaveLength(2);
107
+ });
108
+
109
+ it("says so when nothing matches", async () => {
110
+ const el = await createPicker();
111
+ trigger(el).click();
112
+ await el.updateComplete;
113
+
114
+ const search = el.querySelector<HTMLInputElement>("[data-language-search]");
115
+ if (search) {
116
+ search.value = "zzzz";
117
+ search.dispatchEvent(new Event("input", { bubbles: true }));
118
+ }
119
+ await el.updateComplete;
120
+ expect(el.textContent).toContain(labels.empty);
121
+ });
122
+
123
+ it("moves through the list with the arrow keys and picks with Enter", async () => {
124
+ const el = await createPicker();
125
+ const chosen: string[] = [];
126
+ el.addEventListener("locale-select", (event) => {
127
+ chosen.push((event as CustomEvent<{ tag: string }>).detail.tag);
128
+ });
129
+
130
+ trigger(el).click();
131
+ await el.updateComplete;
132
+
133
+ await press(el, "ArrowDown");
134
+ await press(el, "Enter");
135
+
136
+ expect(chosen).toEqual(["zh-Hans"]);
137
+ expect(el.value).toBe("zh-Hans");
138
+ });
139
+
140
+ it("wraps around at the end of the list", async () => {
141
+ const el = await createPicker();
142
+ trigger(el).click();
143
+ await el.updateComplete;
144
+
145
+ // Starts on the selected language (index 0), so one step back wraps.
146
+ await press(el, "ArrowUp");
147
+ const active = el.querySelector("[data-language-option][data-active]");
148
+ expect(active?.textContent).toContain("suomi");
149
+ });
150
+
151
+ it("closes on Escape and on an outside click", async () => {
152
+ const el = await createPicker();
153
+
154
+ trigger(el).click();
155
+ await el.updateComplete;
156
+ expect(el.querySelector("[data-language-search]")).not.toBeNull();
157
+
158
+ await press(el, "Escape");
159
+ expect(el.querySelector("[data-language-search]")).toBeNull();
160
+
161
+ trigger(el).click();
162
+ await el.updateComplete;
163
+ document.body.dispatchEvent(new Event("click", { bubbles: true }));
164
+ await el.updateComplete;
165
+ expect(el.querySelector("[data-language-search]")).toBeNull();
166
+ });
167
+
168
+ it("does not let Enter escape to a surrounding form", async () => {
169
+ const el = await createPicker();
170
+ let escaped = 0;
171
+ document.body.addEventListener("keydown", () => {
172
+ escaped++;
173
+ });
174
+
175
+ trigger(el).click();
176
+ await el.updateComplete;
177
+ await press(el, "Enter");
178
+
179
+ expect(escaped).toBe(0);
180
+ });
181
+
182
+ it("writes the choice to the form control it drives", async () => {
183
+ const select = makeSelect("content-language", "en");
184
+ const el = await createPicker({ target: select });
185
+ const events: string[] = [];
186
+ select.addEventListener("input", () => events.push("input"));
187
+ select.addEventListener("change", () => events.push("change"));
188
+
189
+ trigger(el).click();
190
+ await el.updateComplete;
191
+ el.querySelectorAll<HTMLButtonElement>('[role="option"]')[2]?.click();
192
+ await el.updateComplete;
193
+
194
+ expect(select.value).toBe("ja");
195
+ expect(events).toEqual(["input", "change"]);
196
+ });
197
+
198
+ it("takes the plain control out of the layout once it upgrades", async () => {
199
+ const select = makeSelect("content-language", "ja");
200
+ const el = await createPicker({ value: "", target: select });
201
+
202
+ expect(select.hidden).toBe(true);
203
+ // With no value of its own the picker adopts the control's, so it opens on
204
+ // whatever the form would have posted.
205
+ expect(trigger(el).textContent).toContain("日本語");
206
+ });
207
+ });
@@ -49,13 +49,6 @@ const collectionFormLabels: CollectionFormLabels = {
49
49
  quickHint: "More options are available after you create it.",
50
50
  quickSubmitLabel: "Create Collection",
51
51
  createdLabel: "Collection created.",
52
- descriptionLabel: "Description",
53
- descriptionPlaceholder: "What's this collection about?",
54
- sortOrderLabel: "Sort Order",
55
- sortNewest: "Newest first",
56
- sortOldest: "Oldest first",
57
- sortRatingDesc: "Highest rated",
58
- submitLabel: "Save",
59
52
  cancelLabel: "Cancel",
60
53
  };
61
54
 
@@ -95,7 +88,19 @@ const labels: NavManagerLabels = {
95
88
  "Choose a titled note that isn't already in navigation, or create a new page.",
96
89
  addPage: "Add Page",
97
90
  searchPages: "Search pages",
91
+ searchPagesHint: "Search pages, or paste an address",
98
92
  recentPages: "Recently updated",
93
+ addressMatch: "At that address",
94
+ addressAlreadyAdded:
95
+ "Already in navigation. Drag it in the list above to move it.",
96
+ addressNotFound: "Nothing at {address}. Check it, or search by title.",
97
+ addressUnpublished: "That page is a draft. Publish it, then add it.",
98
+ addressPrivate: "That page is private, so nobody could open it from a menu.",
99
+ addressUntitled: "That page has no title yet, so a menu has nothing to show.",
100
+ addressExternal:
101
+ "That address is on another site. Navigation holds it as a link.",
102
+ addressLinkOnly: "Navigation holds that address as a link.",
103
+ addressAddAsLink: "Add as link",
99
104
  searchingPages: "Searching pages…",
100
105
  noMatchingPages: "No matching pages available to add.",
101
106
  noPages: "No pages available.",
@@ -122,6 +127,7 @@ const labels: NavManagerLabels = {
122
127
  back: "Back",
123
128
  headerSection: "Header",
124
129
  moreSection: "More",
130
+ previewMore: "More",
125
131
  moreEmptyHint: "Move links here to hide them under More.",
126
132
  placementSaved: "Navigation placement updated.",
127
133
  cancel: "Cancel",
@@ -448,14 +454,9 @@ describe("JantNavManager", () => {
448
454
  body: JSON.stringify({ title: "Books", slug: "books" }),
449
455
  }),
450
456
  );
451
- const editLink = requireElement(
452
- el.querySelector<HTMLAnchorElement>(
453
- 'a[href="/collections/books/edit?returnTo=%2Fsettings%2Fnavigation"]',
454
- ),
455
- "expected collection edit link",
456
- );
457
- expect(editLink.target).toBe("_blank");
458
- expect(editLink.rel).toContain("noopener");
457
+ // The full editor is the collection dialog opened on top of this one,
458
+ // not a link out to a page that no longer exists.
459
+ expect(findButton(el, "Edit Collection").tagName).toBe("BUTTON");
459
460
  expect(el.collections).toContainEqual({
460
461
  id: "col-books",
461
462
  title: "Books",
@@ -556,6 +557,23 @@ describe("JantNavManager", () => {
556
557
  );
557
558
  });
558
559
 
560
+ it("labels the preview overflow button with the site header's own wording", async () => {
561
+ const el = await createElement();
562
+ el.labels = { ...labels, moreSection: "Overflow", previewMore: "More" };
563
+ await el.updateComplete;
564
+
565
+ const trigger = requireElement(
566
+ el.querySelector<HTMLElement>("[data-preview-more-trigger]"),
567
+ "expected preview more trigger",
568
+ );
569
+ const sectionHeading = Array.from(el.querySelectorAll("section > h2")).map(
570
+ (heading) => heading.textContent?.trim(),
571
+ );
572
+
573
+ expect(trigger.textContent?.trim()).toBe("More");
574
+ expect(sectionHeading).toContain("Overflow");
575
+ });
576
+
559
577
  it("opens and dismisses the preview More popover", async () => {
560
578
  const el = await createElement();
561
579
  const trigger = requireElement(
@@ -611,6 +629,33 @@ describe("JantNavManager", () => {
611
629
  expect(getPreviewHeaderLabels(el)).toContain("Feed");
612
630
  });
613
631
 
632
+ // `subscribe` addresses a page that 404s without feeds, so it has to drop out
633
+ // of the preview alongside `rss` — the server's projection drops both.
634
+ it("hides a saved Subscribe item from the preview too", async () => {
635
+ const el = await createElement();
636
+ el.items = [
637
+ ...items,
638
+ {
639
+ id: "nav-subscribe",
640
+ type: "system",
641
+ systemKey: "subscribe",
642
+ label: "Subscribe",
643
+ displayLabel: "Subscribe",
644
+ url: "/subscribe",
645
+ placement: "header",
646
+ },
647
+ ];
648
+ el.rssFeedsEnabled = false;
649
+ await el.updateComplete;
650
+
651
+ expect(getPreviewHeaderLabels(el)).not.toContain("Subscribe");
652
+ expect(el.querySelector('[data-nav-id="nav-subscribe"]')).not.toBeNull();
653
+
654
+ el.rssFeedsEnabled = true;
655
+ await el.updateComplete;
656
+ expect(getPreviewHeaderLabels(el)).toContain("Subscribe");
657
+ });
658
+
614
659
  it("adds a suggested collection link through the nav items API", async () => {
615
660
  const el = await createElement();
616
661
  el.suggestedLinks = suggestedLinks;
@@ -1020,4 +1065,178 @@ describe("JantNavManager", () => {
1020
1065
  expect(editPageLink.target).toBe("_blank");
1021
1066
  expect(editPageLink.rel).toContain("noopener");
1022
1067
  });
1068
+
1069
+ describe("pasting an address into the page picker", () => {
1070
+ /** Type into the picker and wait out the debounce. */
1071
+ async function search(el: JantNavManager, value: string) {
1072
+ const input = requireElement(
1073
+ el.querySelector<HTMLInputElement>("#nav-page-search"),
1074
+ "page search input",
1075
+ );
1076
+ input.value = value;
1077
+ input.dispatchEvent(new Event("input", { bubbles: true }));
1078
+ await new Promise((resolve) => setTimeout(resolve, 250));
1079
+ await flushAsyncWork();
1080
+ await el.updateComplete;
1081
+ }
1082
+
1083
+ async function openPicker(
1084
+ resolution: unknown,
1085
+ ): Promise<{ el: JantNavManager; fetchMock: ReturnType<typeof vi.fn> }> {
1086
+ const el = await createElement();
1087
+ const fetchMock = vi
1088
+ .fn()
1089
+ .mockImplementation((input: string, init?: globalThis.RequestInit) => {
1090
+ if (input.startsWith("/api/nav-items/pages?")) {
1091
+ return Promise.resolve({
1092
+ ok: true,
1093
+ status: 200,
1094
+ json: async () => ({ pages: [] }),
1095
+ });
1096
+ }
1097
+ if (input.startsWith("/api/nav-items/resolve?")) {
1098
+ return Promise.resolve({
1099
+ ok: true,
1100
+ status: 200,
1101
+ json: async () => ({ resolution }),
1102
+ });
1103
+ }
1104
+ if (input === "/api/nav-items" && init?.method === "POST") {
1105
+ return Promise.resolve({
1106
+ ok: true,
1107
+ status: 201,
1108
+ json: async () => ({
1109
+ id: "nav-added",
1110
+ type: "page",
1111
+ postId: "pst_about",
1112
+ label: "About me",
1113
+ url: "/about-me",
1114
+ placement: "header",
1115
+ }),
1116
+ });
1117
+ }
1118
+ throw new Error(`Unexpected fetch: ${input}`);
1119
+ });
1120
+ vi.stubGlobal("fetch", fetchMock);
1121
+
1122
+ findButton(el, "Add Page").click();
1123
+ await flushAsyncWork();
1124
+ await el.updateComplete;
1125
+ return { el, fetchMock };
1126
+ }
1127
+
1128
+ it("looks the address up instead of searching titles", async () => {
1129
+ const { el, fetchMock } = await openPicker({
1130
+ kind: "page",
1131
+ address: "/about-me",
1132
+ page: {
1133
+ id: "pst_about",
1134
+ title: "About me",
1135
+ slug: "about-me",
1136
+ updatedAt: 123,
1137
+ },
1138
+ });
1139
+
1140
+ await search(el, "https://example.com/about-me");
1141
+
1142
+ expect(fetchMock).toHaveBeenCalledWith(
1143
+ `/api/nav-items/resolve?url=${encodeURIComponent("https://example.com/about-me")}`,
1144
+ expect.objectContaining({
1145
+ headers: expect.objectContaining({ Accept: "application/json" }),
1146
+ }),
1147
+ );
1148
+ expect(el.textContent).toContain("At that address");
1149
+
1150
+ findButton(el, "About me").click();
1151
+ await flushAsyncWork();
1152
+ await el.updateComplete;
1153
+
1154
+ expect(fetchMock).toHaveBeenCalledWith(
1155
+ "/api/nav-items",
1156
+ expect.objectContaining({
1157
+ method: "POST",
1158
+ body: JSON.stringify({
1159
+ type: "page",
1160
+ postId: "pst_about",
1161
+ placement: "header",
1162
+ }),
1163
+ }),
1164
+ );
1165
+ });
1166
+
1167
+ it("says why a page cannot be added, rather than showing nothing", async () => {
1168
+ const { el } = await openPicker({
1169
+ kind: "unpublished",
1170
+ address: "/draft-page",
1171
+ });
1172
+
1173
+ await search(el, "/draft-page");
1174
+
1175
+ expect(el.textContent).toContain("That page is a draft");
1176
+ expect(el.textContent).not.toContain("No matching pages");
1177
+ });
1178
+
1179
+ it("names the address it could not find", async () => {
1180
+ const { el } = await openPicker({
1181
+ kind: "not_found",
1182
+ address: "/typo",
1183
+ });
1184
+
1185
+ await search(el, "/typo");
1186
+
1187
+ expect(el.textContent).toContain("Nothing at /typo.");
1188
+ });
1189
+
1190
+ it("hands an off-site address to the link form, filled in", async () => {
1191
+ const { el } = await openPicker({
1192
+ kind: "external",
1193
+ address: "https://example.org/hello",
1194
+ });
1195
+
1196
+ await search(el, "https://example.org/hello");
1197
+ expect(el.textContent).toContain("That address is on another site");
1198
+
1199
+ findButton(el, "Add as link").click();
1200
+ await el.updateComplete;
1201
+
1202
+ // The picker is out of the way and the URL is already in the form, so
1203
+ // the only thing left to type is the label.
1204
+ expect(el.querySelector("#nav-page-dialog")).toBeNull();
1205
+ expect(
1206
+ requireElement(
1207
+ el.querySelector<HTMLInputElement>("#nav-link-url"),
1208
+ "link url input",
1209
+ ).value,
1210
+ ).toBe("https://example.org/hello");
1211
+ });
1212
+
1213
+ it("does not offer a page that is already in navigation", async () => {
1214
+ const { el } = await openPicker({
1215
+ kind: "page",
1216
+ address: "/about",
1217
+ page: {
1218
+ id: "pst_nav_1",
1219
+ title: "About",
1220
+ slug: "about",
1221
+ updatedAt: 123,
1222
+ },
1223
+ });
1224
+ el.items = [
1225
+ {
1226
+ id: "nav-1",
1227
+ type: "page",
1228
+ postId: "pst_nav_1",
1229
+ label: "About",
1230
+ url: "/about",
1231
+ placement: "header",
1232
+ },
1233
+ ];
1234
+ await el.updateComplete;
1235
+
1236
+ await search(el, "/about");
1237
+
1238
+ expect(el.textContent).toContain("Already in navigation");
1239
+ expect(el.querySelector(".nav-page-result")).toBeNull();
1240
+ });
1241
+ });
1023
1242
  });