@jant/core 0.6.16 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (508) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/import-site.js +60 -4
  4. package/bin/commands/reset-password.js +21 -8
  5. package/bin/commands/site/snapshot/export.js +1 -1
  6. package/bin/commands/site/snapshot/import.js +1 -1
  7. package/bin/lib/d1-query.js +5 -16
  8. package/bin/lib/node-env.js +40 -4
  9. package/bin/lib/site-selection.js +85 -29
  10. package/bin/lib/wrangler-cli.js +28 -2
  11. package/bin/lib/wrangler-json.js +35 -0
  12. package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
  13. package/dist/client/.vite/manifest.json +255 -90
  14. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  15. package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
  16. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  17. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  18. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  19. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  20. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  21. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  22. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  23. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  24. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  25. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  26. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  27. package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
  28. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  29. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  30. package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
  31. package/dist/client/_assets/client-GiYENVw8.css +2 -0
  32. package/dist/client/_assets/client-VnFxJN7G.js +295 -0
  33. package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
  34. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  35. package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
  36. package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
  37. package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
  38. package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
  39. package/dist/index.js +2 -5
  40. package/dist/node.js +5 -7
  41. package/package.json +3 -3
  42. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  43. package/src/__tests__/basecoat-variants.test.ts +121 -0
  44. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  45. package/src/__tests__/export-hugo-build.test.ts +260 -0
  46. package/src/__tests__/export-service.test.ts +231 -0
  47. package/src/__tests__/helpers/component-css.ts +91 -0
  48. package/src/__tests__/helpers/db.ts +33 -0
  49. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  50. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  51. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  52. package/src/app.tsx +71 -16
  53. package/src/auth.ts +18 -1
  54. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  55. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  56. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  57. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  58. package/src/client/__tests__/compose-launch.test.ts +62 -1
  59. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  60. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  61. package/src/client/__tests__/copy-field.test.ts +162 -0
  62. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  63. package/src/client/__tests__/image-processor.test.ts +324 -2
  64. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  65. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  66. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  67. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  68. package/src/client/__tests__/toast.test.ts +23 -0
  69. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  70. package/src/client/__tests__/video-processor.test.ts +181 -0
  71. package/src/client/audio-processor.ts +24 -15
  72. package/src/client/audio-waveform.ts +49 -0
  73. package/src/client/collection-dialog-host.ts +72 -0
  74. package/src/client/collection-navigation.ts +51 -16
  75. package/src/client/collection-page-actions.ts +26 -0
  76. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  77. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  78. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  79. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  80. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  81. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  82. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  83. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  84. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  85. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  86. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  87. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  88. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  89. package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
  90. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  91. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  92. package/src/client/components/collection-dialog-types.ts +49 -0
  93. package/src/client/components/collection-manager-types.ts +32 -1
  94. package/src/client/components/collection-types.ts +1 -15
  95. package/src/client/components/compose-types.ts +75 -1
  96. package/src/client/components/jant-collection-dialog.ts +722 -0
  97. package/src/client/components/jant-collection-directory.ts +567 -182
  98. package/src/client/components/jant-collection-form.ts +53 -232
  99. package/src/client/components/jant-command-palette.ts +100 -49
  100. package/src/client/components/jant-compose-dialog.ts +1486 -707
  101. package/src/client/components/jant-compose-editor.ts +673 -336
  102. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  103. package/src/client/components/jant-config-editor.ts +65 -53
  104. package/src/client/components/jant-confirm-dialog.ts +13 -11
  105. package/src/client/components/jant-locale-picker.ts +418 -0
  106. package/src/client/components/jant-media-lightbox.ts +146 -136
  107. package/src/client/components/jant-nav-manager.ts +690 -366
  108. package/src/client/components/jant-post-menu.ts +1086 -282
  109. package/src/client/components/jant-post-picker.ts +324 -0
  110. package/src/client/components/jant-repo-picker.ts +138 -106
  111. package/src/client/components/jant-settings-avatar.ts +31 -27
  112. package/src/client/components/jant-settings-general.ts +617 -543
  113. package/src/client/components/jant-settings-language.ts +1087 -0
  114. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  115. package/src/client/components/jant-text-preview.ts +94 -86
  116. package/src/client/components/nav-manager-types.ts +62 -1
  117. package/src/client/components/settings-types.ts +14 -29
  118. package/src/client/components/smart-collection-conditions.ts +285 -0
  119. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  120. package/src/client/compose-bridge.ts +214 -141
  121. package/src/client/compose-launch.ts +76 -2
  122. package/src/client/compose-shortcuts.ts +6 -8
  123. package/src/client/compose-triggers.ts +50 -0
  124. package/src/client/copy-field.ts +108 -0
  125. package/src/client/custom-url-menu.ts +60 -4
  126. package/src/client/icons.ts +14 -0
  127. package/src/client/image-plan.ts +165 -0
  128. package/src/client/image-processor.ts +195 -117
  129. package/src/client/image-worker-client.ts +45 -0
  130. package/src/client/image-worker.ts +135 -0
  131. package/src/client/lazy-entries.ts +95 -0
  132. package/src/client/lazy-slugify.ts +2 -2
  133. package/src/client/media-scroll-hint.ts +43 -2
  134. package/src/client/mediabunny.ts +29 -0
  135. package/src/client/post-picker.ts +41 -0
  136. package/src/client/runtime-paths.ts +74 -1
  137. package/src/client/settings-bridge.ts +4 -10
  138. package/src/client/site-header-nav.js +18 -6
  139. package/src/client/smart-collection-dialog-host.ts +116 -0
  140. package/src/client/smart-collection-page-actions.ts +208 -0
  141. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  142. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  143. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  144. package/src/client/tiptap/bubble-menu.ts +47 -16
  145. package/src/client/tiptap/floating-position.ts +216 -2
  146. package/src/client/tiptap/inline-image-upload.ts +7 -3
  147. package/src/client/tiptap/link-toolbar.ts +22 -9
  148. package/src/client/toast.ts +13 -0
  149. package/src/client/types/vite-worker.d.ts +12 -0
  150. package/src/client/upload-with-metadata.ts +3 -13
  151. package/src/client/video-processor.ts +351 -107
  152. package/src/client-auth.ts +14 -31
  153. package/src/client-compose.ts +19 -0
  154. package/src/client-manage.ts +13 -0
  155. package/src/client-settings.ts +14 -0
  156. package/src/client-site.ts +10 -3
  157. package/src/client.ts +4 -0
  158. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  159. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  160. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  161. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  162. package/src/db/migrations/0032_married_tomas.sql +3 -0
  163. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  164. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  165. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  166. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  167. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  168. package/src/db/migrations/meta/_journal.json +21 -0
  169. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  170. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  171. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  172. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  173. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  174. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  175. package/src/db/migrations/pg/meta/_journal.json +21 -0
  176. package/src/db/pg/schema.ts +195 -27
  177. package/src/db/post-visibility.ts +117 -0
  178. package/src/db/schema.ts +222 -27
  179. package/src/i18n/__tests__/detect.test.ts +26 -81
  180. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  181. package/src/i18n/context.tsx +12 -2
  182. package/src/i18n/detect.ts +7 -66
  183. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  184. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  185. package/src/i18n/locales/public/en.po +490 -158
  186. package/src/i18n/locales/public/en.ts +1 -1
  187. package/src/i18n/locales/public/zh-Hans.po +487 -155
  188. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  189. package/src/i18n/locales/public/zh-Hant.po +487 -155
  190. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  191. package/src/i18n/locales/settings/en.po +551 -69
  192. package/src/i18n/locales/settings/en.ts +1 -1
  193. package/src/i18n/locales/settings/zh-Hans.po +597 -115
  194. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  195. package/src/i18n/locales/settings/zh-Hant.po +610 -128
  196. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  197. package/src/i18n/locales.ts +94 -0
  198. package/src/i18n/middleware.ts +12 -2
  199. package/src/i18n/supported-locales.ts +80 -0
  200. package/src/lib/__tests__/api-settings.test.ts +11 -2
  201. package/src/lib/__tests__/asset-path.test.ts +92 -0
  202. package/src/lib/__tests__/deferred.test.ts +90 -0
  203. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  204. package/src/lib/__tests__/discover.test.ts +410 -0
  205. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  206. package/src/lib/__tests__/feed.test.ts +1526 -15
  207. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  208. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  209. package/src/lib/__tests__/http-cache.test.ts +162 -0
  210. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  211. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  212. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  213. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  214. package/src/lib/__tests__/navigation.test.ts +135 -1
  215. package/src/lib/__tests__/resolve-config.test.ts +64 -3
  216. package/src/lib/__tests__/schemas.test.ts +42 -4
  217. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  218. package/src/lib/__tests__/slug.test.ts +70 -0
  219. package/src/lib/__tests__/slugify.test.ts +85 -0
  220. package/src/lib/__tests__/storage.test.ts +32 -0
  221. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  222. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  223. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  224. package/src/lib/__tests__/url.test.ts +78 -72
  225. package/src/lib/__tests__/view-language.test.ts +413 -0
  226. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  227. package/src/lib/address.ts +57 -0
  228. package/src/lib/api-posts.ts +12 -0
  229. package/src/lib/asset-path.ts +98 -1
  230. package/src/lib/build-env.ts +16 -0
  231. package/src/lib/collection-groups.ts +5 -1
  232. package/src/lib/collection-paths.ts +3 -10
  233. package/src/lib/constants.ts +28 -3
  234. package/src/lib/copy-field.ts +36 -0
  235. package/src/lib/crypto.ts +40 -0
  236. package/src/lib/csp-builder.ts +2 -1
  237. package/src/lib/deferred.ts +79 -0
  238. package/src/lib/discover-ping.ts +141 -0
  239. package/src/lib/discover.ts +437 -0
  240. package/src/lib/env.ts +134 -0
  241. package/src/lib/errors.ts +33 -0
  242. package/src/lib/feed-policy.ts +236 -2
  243. package/src/lib/feed.ts +926 -73
  244. package/src/lib/filter-dimensions.ts +1349 -0
  245. package/src/lib/github-sync-site-config.ts +3 -0
  246. package/src/lib/hosted-signin.ts +1 -1
  247. package/src/lib/http-cache.ts +148 -0
  248. package/src/lib/hugo-markdown.ts +16 -0
  249. package/src/lib/icons.ts +9 -2
  250. package/src/lib/ids.ts +3 -0
  251. package/src/lib/jant-docs.ts +42 -0
  252. package/src/lib/lang-detect.ts +308 -0
  253. package/src/lib/media-helpers.ts +13 -1
  254. package/src/lib/navigation.ts +78 -11
  255. package/src/lib/pagination.ts +28 -24
  256. package/src/lib/per-language-surfaces.ts +67 -0
  257. package/src/lib/post-meta.ts +19 -0
  258. package/src/lib/render.tsx +55 -7
  259. package/src/lib/resolve-config.ts +53 -5
  260. package/src/lib/schemas.ts +201 -10
  261. package/src/lib/site-header-fragment.tsx +2 -1
  262. package/src/lib/site-unavailable-page.ts +47 -0
  263. package/src/lib/slug.ts +51 -7
  264. package/src/lib/slugify.ts +113 -0
  265. package/src/lib/startup-config.ts +4 -6
  266. package/src/lib/storage.ts +7 -0
  267. package/src/lib/summary.ts +50 -0
  268. package/src/lib/thread-fold.ts +161 -0
  269. package/src/lib/time.ts +32 -0
  270. package/src/lib/timeline.ts +42 -1
  271. package/src/lib/tiptap-render.ts +1 -2
  272. package/src/lib/upload.ts +16 -13
  273. package/src/lib/url.ts +139 -86
  274. package/src/lib/version.ts +10 -8
  275. package/src/lib/view-language.ts +436 -0
  276. package/src/lib/view.ts +51 -23
  277. package/src/lib/viewer-context.ts +62 -0
  278. package/src/middleware/__tests__/auth.test.ts +70 -2
  279. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  280. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  281. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  282. package/src/middleware/__tests__/session.test.ts +181 -33
  283. package/src/middleware/auth.ts +50 -31
  284. package/src/middleware/cache-control.ts +75 -10
  285. package/src/middleware/config.ts +6 -6
  286. package/src/middleware/error-handler.ts +12 -0
  287. package/src/middleware/onboarding.ts +32 -10
  288. package/src/middleware/secure-headers.ts +1 -1
  289. package/src/middleware/session.ts +96 -8
  290. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  291. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  292. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  293. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  294. package/src/node/__tests__/runtime.test.ts +37 -0
  295. package/src/node/index.ts +1 -0
  296. package/src/preset.css +76 -22
  297. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  298. package/src/routes/api/__tests__/collections.test.ts +114 -0
  299. package/src/routes/api/__tests__/discover.test.ts +222 -0
  300. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  301. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  302. package/src/routes/api/__tests__/settings.test.ts +24 -3
  303. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  304. package/src/routes/api/collections.ts +49 -6
  305. package/src/routes/api/custom-urls.ts +0 -1
  306. package/src/routes/api/discover.ts +95 -0
  307. package/src/routes/api/export.ts +3 -0
  308. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  309. package/src/routes/api/nav-items.ts +55 -8
  310. package/src/routes/api/posts.ts +159 -0
  311. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  312. package/src/routes/api/public/archive.ts +77 -81
  313. package/src/routes/api/public/posts.ts +23 -6
  314. package/src/routes/api/smart-collections.ts +132 -0
  315. package/src/routes/api/telegram.ts +2 -40
  316. package/src/routes/api/uploads.ts +1 -2
  317. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  318. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  319. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  320. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  321. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  322. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  323. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  324. package/src/routes/auth/setup.tsx +934 -186
  325. package/src/routes/auth/signin.tsx +10 -8
  326. package/src/routes/compose.tsx +5 -0
  327. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  328. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  329. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  330. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  331. package/src/routes/dash/custom-urls.tsx +1 -37
  332. package/src/routes/dash/settings.tsx +502 -41
  333. package/src/routes/discover-announce.ts +96 -0
  334. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  335. package/src/routes/feed/feed.ts +69 -40
  336. package/src/routes/feed/sitemap.ts +95 -11
  337. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  338. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  339. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  340. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  341. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  342. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  343. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  344. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  345. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  346. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  347. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  348. package/src/routes/pages/archive.tsx +471 -369
  349. package/src/routes/pages/collection.tsx +94 -96
  350. package/src/routes/pages/collections.tsx +38 -50
  351. package/src/routes/pages/featured.tsx +32 -13
  352. package/src/routes/pages/home.tsx +25 -6
  353. package/src/routes/pages/language.tsx +113 -0
  354. package/src/routes/pages/latest.tsx +26 -16
  355. package/src/routes/pages/new.tsx +3 -0
  356. package/src/routes/pages/page.tsx +178 -17
  357. package/src/routes/pages/partials.tsx +41 -0
  358. package/src/routes/pages/search.tsx +18 -3
  359. package/src/routes/pages/smart-collection.tsx +340 -0
  360. package/src/routes/pages/subscribe.tsx +182 -0
  361. package/src/runtime/__tests__/index.test.ts +19 -0
  362. package/src/runtime/__tests__/node.test.ts +129 -4
  363. package/src/runtime/cloudflare.ts +21 -2
  364. package/src/runtime/index.ts +27 -1
  365. package/src/runtime/node.ts +18 -1
  366. package/src/runtime/site.ts +95 -24
  367. package/src/services/__tests__/auth.test.ts +116 -7
  368. package/src/services/__tests__/collection.test.ts +108 -4
  369. package/src/services/__tests__/language.test.ts +495 -0
  370. package/src/services/__tests__/navigation.test.ts +213 -10
  371. package/src/services/__tests__/path.test.ts +180 -0
  372. package/src/services/__tests__/post-language.test.ts +830 -0
  373. package/src/services/__tests__/post.test.ts +46 -9
  374. package/src/services/__tests__/settings.test.ts +228 -17
  375. package/src/services/__tests__/site-admin.test.ts +5 -5
  376. package/src/services/__tests__/smart-collection.test.ts +651 -0
  377. package/src/services/about-page.ts +22 -1
  378. package/src/services/auth.ts +51 -49
  379. package/src/services/bootstrap.ts +188 -38
  380. package/src/services/collection-directory-position.ts +76 -0
  381. package/src/services/collection.ts +331 -46
  382. package/src/services/custom-url.ts +27 -22
  383. package/src/services/export-theme/assets/client-site.css +1 -1
  384. package/src/services/export-theme/assets/client-site.js +123 -123
  385. package/src/services/export-theme/layouts/_default/rss.xml +77 -1
  386. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  387. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  388. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  389. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  390. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  391. package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
  392. package/src/services/export-theme/styles/main.css +13 -1
  393. package/src/services/export.ts +278 -19
  394. package/src/services/index.ts +34 -3
  395. package/src/services/language.ts +412 -0
  396. package/src/services/mcp.ts +3 -1
  397. package/src/services/media.ts +14 -6
  398. package/src/services/navigation.ts +429 -67
  399. package/src/services/path.ts +221 -6
  400. package/src/services/post.ts +1021 -152
  401. package/src/services/search.ts +39 -2
  402. package/src/services/settings.ts +238 -43
  403. package/src/services/site-admin.ts +24 -7
  404. package/src/services/site.ts +77 -1
  405. package/src/services/smart-collection.ts +636 -0
  406. package/src/services/telegram.ts +20 -22
  407. package/src/services/upload-session.ts +1 -3
  408. package/src/style-author.css +27 -0
  409. package/src/styles/components-author.css +1622 -0
  410. package/src/styles/components.css +24 -1571
  411. package/src/styles/site-media.css +16 -2
  412. package/src/styles/tokens.css +82 -12
  413. package/src/styles/ui-author.css +7602 -0
  414. package/src/styles/ui.css +1087 -7219
  415. package/src/types/app-context.ts +7 -0
  416. package/src/types/bindings.ts +3 -0
  417. package/src/types/config.ts +94 -15
  418. package/src/types/constants.ts +188 -16
  419. package/src/types/entities.ts +87 -1
  420. package/src/types/operations.ts +64 -2
  421. package/src/types/props.ts +157 -31
  422. package/src/types/views.ts +55 -1
  423. package/src/ui/__tests__/font-themes.test.ts +52 -12
  424. package/src/ui/compose/ComposeDialog.tsx +201 -7
  425. package/src/ui/compose/ComposePrompt.tsx +1 -5
  426. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  427. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  428. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  429. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  430. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  431. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  432. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  433. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  434. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  435. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  436. package/src/ui/dash/settings/GeneralContent.tsx +275 -113
  437. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  438. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  439. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  440. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  441. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  442. package/src/ui/dash/settings/discover-copy.ts +116 -0
  443. package/src/ui/feed/LinkCard.tsx +55 -45
  444. package/src/ui/feed/NoteCard.tsx +9 -4
  445. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  446. package/src/ui/feed/QuoteCard.tsx +55 -30
  447. package/src/ui/feed/ThreadPreview.tsx +6 -2
  448. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  449. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  450. package/src/ui/feed/thread-preview-state.ts +20 -6
  451. package/src/ui/font-themes.ts +39 -29
  452. package/src/ui/layouts/BaseLayout.tsx +155 -10
  453. package/src/ui/layouts/SiteLayout.tsx +192 -34
  454. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  455. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  456. package/src/ui/pages/ArchivePage.tsx +282 -289
  457. package/src/ui/pages/CollectionPage.tsx +86 -76
  458. package/src/ui/pages/CollectionsPage.tsx +5 -0
  459. package/src/ui/pages/ComposePage.tsx +8 -0
  460. package/src/ui/pages/PostPage.tsx +47 -0
  461. package/src/ui/pages/SearchPage.tsx +3 -3
  462. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  463. package/src/ui/pages/SubscribePage.tsx +236 -0
  464. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  465. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  466. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  467. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  468. package/src/ui/shared/CollectionsManager.tsx +70 -17
  469. package/src/ui/shared/CopyField.tsx +127 -0
  470. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  471. package/src/ui/shared/MediaGallery.tsx +125 -46
  472. package/src/ui/shared/Pagination.tsx +20 -5
  473. package/src/ui/shared/PostFooter.tsx +20 -11
  474. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  475. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  476. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  477. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  478. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  479. package/src/ui/shared/archive-labels.ts +98 -0
  480. package/src/ui/shared/collection-management-labels.ts +94 -16
  481. package/src/ui/shared/navigation-labels.ts +45 -71
  482. package/src/ui/shared/post-article-attributes.ts +2 -0
  483. package/src/ui/shared/smart-collection-labels.ts +343 -0
  484. package/dist/app-C75Zc8dD.js +0 -6
  485. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  486. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  487. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  488. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  489. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  490. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  491. package/dist/env-BPYViDJJ.js +0 -281
  492. package/dist/github-api-BNF35Lkx.js +0 -176
  493. package/dist/github-app-DdiD-bC4.js +0 -275
  494. package/dist/github-sync-Bqg62IvV.js +0 -4
  495. package/dist/github-sync-HdK2EgvJ.js +0 -431
  496. package/dist/url-CbLAtlZe.js +0 -900
  497. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  498. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  499. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  500. package/src/client/collection-created-notice.ts +0 -66
  501. package/src/client/collection-form-bridge.ts +0 -191
  502. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  503. package/src/client/components/jant-post-form.ts +0 -422
  504. package/src/client/components/post-form-template.ts +0 -352
  505. package/src/client/components/post-form-types.ts +0 -96
  506. package/src/client/media-metadata.ts +0 -247
  507. package/src/client/post-form-bridge.ts +0 -193
  508. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -1,30 +1,28 @@
1
1
  /**
2
- * Collection Form Component
2
+ * Collection Quick-Create Form
3
3
  *
4
- * Handles create/edit collection form interactions for:
5
- * - title, slug, description, and sort order
6
- * - quick-create slug preview/editing
7
- * - dispatching `jant:collection-submit` for the bridge layer
4
+ * A title and an address, nothing else — the two things a collection cannot be
5
+ * created without. It exists for the places where creating a collection is a
6
+ * step inside another task: picking one while composing, or putting one in the
7
+ * navigation. Everything a collection *can* have is decided in the collection
8
+ * dialog, which is where an author who came to make a collection already is.
9
+ *
10
+ * Submitting dispatches `jant:collection-submit`; the surrounding dialog owns
11
+ * the request and what happens after it.
8
12
  *
9
13
  * Light DOM only — BaseCoat and Tailwind classes apply directly.
10
14
  */
11
15
 
12
16
  import { LitElement, html, nothing } from "lit";
13
17
  import type { PropertyValueMap } from "lit";
14
- import type { Editor } from "@tiptap/core";
15
18
  import {
16
19
  MAX_COLLECTION_SLUG_LENGTH,
17
20
  MAX_COLLECTION_TITLE_LENGTH,
18
- type CollectionSortOrder,
19
21
  } from "../../types.js";
20
22
  import { getCollectionPagePath } from "../../lib/collection-paths.js";
21
23
  import { getSlugValidationIssue, truncateSlug } from "../../lib/slug-format.js";
22
24
  import { slugify } from "../lazy-slugify.js";
23
25
  import { publicPath } from "../runtime-paths.js";
24
- import {
25
- createSettingsEditor,
26
- jsonToMarkdown,
27
- } from "../tiptap/create-editor.js";
28
26
  import type {
29
27
  CollectionFormInitial,
30
28
  CollectionFormLabels,
@@ -36,14 +34,9 @@ export class JantCollectionForm extends LitElement {
36
34
  labels: { type: Object },
37
35
  initial: { type: Object },
38
36
  action: { type: String },
39
- cancelHref: { type: String, attribute: "cancel-href" },
40
- isEdit: { type: Boolean, attribute: "is-edit" },
41
- variant: { type: String },
42
37
 
43
38
  _title: { state: true },
44
39
  _slug: { state: true },
45
- _description: { state: true },
46
- _sortOrder: { state: true },
47
40
  _showSlugEditor: { state: true },
48
41
  _slugEdited: { state: true },
49
42
  _suggestedSlug: { state: true },
@@ -53,21 +46,15 @@ export class JantCollectionForm extends LitElement {
53
46
  declare labels: CollectionFormLabels;
54
47
  declare initial: CollectionFormInitial;
55
48
  declare action: string;
56
- declare cancelHref: string;
57
- declare isEdit: boolean;
58
- declare variant: "full" | "quick";
59
49
 
60
50
  declare _title: string;
61
51
  declare _slug: string;
62
- declare _description: string;
63
- declare _sortOrder: CollectionSortOrder;
64
52
  declare _showSlugEditor: boolean;
65
53
  declare _slugEdited: boolean;
66
54
  declare _suggestedSlug: string;
67
55
  declare _loading: boolean;
68
56
 
69
57
  #initialized = false;
70
- private _descEditor: Editor | null = null;
71
58
  #boundKeydown: ((e: KeyboardEvent) => void) | null = null;
72
59
 
73
60
  createRenderRoot() {
@@ -78,21 +65,11 @@ export class JantCollectionForm extends LitElement {
78
65
  constructor() {
79
66
  super();
80
67
  this.labels = {} as CollectionFormLabels;
81
- this.initial = {
82
- title: "",
83
- slug: "",
84
- description: "",
85
- sortOrder: "newest",
86
- };
68
+ this.initial = { title: "", slug: "" };
87
69
  this.action = "";
88
- this.cancelHref = "/";
89
- this.isEdit = false;
90
- this.variant = "full";
91
70
 
92
71
  this._title = "";
93
72
  this._slug = "";
94
- this._description = "";
95
- this._sortOrder = "newest";
96
73
  this._showSlugEditor = false;
97
74
  this._slugEdited = false;
98
75
  this._suggestedSlug = "";
@@ -117,8 +94,6 @@ export class JantCollectionForm extends LitElement {
117
94
  this.#boundKeydown = null;
118
95
  }
119
96
  super.disconnectedCallback();
120
- this._descEditor?.destroy();
121
- this._descEditor = null;
122
97
  }
123
98
 
124
99
  protected update(
@@ -144,46 +119,15 @@ export class JantCollectionForm extends LitElement {
144
119
  this._title = this.initial.title ?? "";
145
120
  this._slug = this.initial.slug ?? "";
146
121
  this._suggestedSlug = this.initial.slug ?? "";
147
- this._description = this.initial.description ?? "";
148
- this._sortOrder = this.initial.sortOrder ?? "newest";
149
- this._slugEdited = this.isEdit || Boolean(this._slug.trim());
150
- this._showSlugEditor = this.variant !== "quick";
151
-
152
- if (this.variant !== "quick") {
153
- this.updateComplete.then(() => this.#initDescEditor());
154
- }
155
- }
156
-
157
- #initDescEditor() {
158
- const container = this.querySelector<HTMLElement>(
159
- "[data-collection-desc-editor]",
160
- );
161
- if (!container || this._descEditor) return;
162
-
163
- this._descEditor = createSettingsEditor({
164
- element: container,
165
- placeholder: this.isEdit ? undefined : this.labels.descriptionPlaceholder,
166
- content: this._description || undefined,
167
- onUpdate: (markdown) => {
168
- this._description = markdown;
169
- },
170
- });
171
-
172
- // Normalize initial markdown through the editor round-trip
173
- this._description = jsonToMarkdown(this._descEditor.getJSON());
174
-
175
- const pm = container.querySelector<HTMLElement>(".ProseMirror");
176
- if (pm) {
177
- pm.style.outline = "none";
178
- pm.style.minHeight = "5rem";
179
- }
122
+ this._slugEdited = Boolean(this._slug.trim());
123
+ this._showSlugEditor = false;
180
124
  }
181
125
 
182
126
  async #handleTitleInput(event: Event) {
183
127
  const target = event.target as HTMLInputElement;
184
128
  this._title = target.value;
185
129
 
186
- if (this.isEdit || this._slugEdited) {
130
+ if (this._slugEdited) {
187
131
  return;
188
132
  }
189
133
 
@@ -237,9 +181,7 @@ export class JantCollectionForm extends LitElement {
237
181
  if (!this._suggestedSlug) return;
238
182
  this._slug = this._suggestedSlug;
239
183
  this._slugEdited = false;
240
- if (this.variant === "quick") {
241
- this._showSlugEditor = false;
242
- }
184
+ this._showSlugEditor = false;
243
185
  }
244
186
 
245
187
  #getCollectionLinkPreview(): string {
@@ -276,7 +218,7 @@ export class JantCollectionForm extends LitElement {
276
218
  </p>`;
277
219
  }
278
220
 
279
- #renderQuickSlugControls() {
221
+ #renderSlugControls() {
280
222
  const hasPreview = Boolean(this._slug.trim());
281
223
  const canResetToTitle =
282
224
  this._showSlugEditor &&
@@ -293,17 +235,19 @@ export class JantCollectionForm extends LitElement {
293
235
  <div class="field">
294
236
  <div class="collection-quick-link-row">
295
237
  <label class="label mb-0">${this.labels.slugLabel}</label>
296
- ${canResetToTitle
297
- ? html`
298
- <button
299
- type="button"
300
- class="collection-quick-link-action"
301
- @click=${() => this.#resetSlugToSuggested()}
302
- >
303
- ${this.labels.resetSlugLabel}
304
- </button>
305
- `
306
- : nothing}
238
+ ${
239
+ canResetToTitle
240
+ ? html`
241
+ <button
242
+ type="button"
243
+ class="collection-quick-link-action"
244
+ @click=${() => this.#resetSlugToSuggested()}
245
+ >
246
+ ${this.labels.resetSlugLabel}
247
+ </button>
248
+ `
249
+ : nothing
250
+ }
307
251
  </div>
308
252
  <input
309
253
  type="text"
@@ -313,9 +257,9 @@ export class JantCollectionForm extends LitElement {
313
257
  pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
314
258
  maxlength=${MAX_COLLECTION_SLUG_LENGTH}
315
259
  .value=${this._slug}
316
- aria-invalid=${this.#getSlugValidationMessage()
317
- ? "true"
318
- : "false"}
260
+ aria-invalid=${
261
+ this.#getSlugValidationMessage() ? "true" : "false"
262
+ }
319
263
  placeholder="my-collection"
320
264
  @input=${(event: Event) => this.#handleSlugInput(event)}
321
265
  />
@@ -369,9 +313,7 @@ export class JantCollectionForm extends LitElement {
369
313
  }
370
314
 
371
315
  if (!slug || this.#getSlugValidationMessage()) {
372
- if (this.variant === "quick" && !this._showSlugEditor) {
373
- this.#showSlugEditor();
374
- }
316
+ this.#showSlugEditor();
375
317
  this.updateComplete.then(() => {
376
318
  this.querySelector<HTMLInputElement>(
377
319
  "[data-collection-slug-input]",
@@ -382,17 +324,7 @@ export class JantCollectionForm extends LitElement {
382
324
 
383
325
  const detail: CollectionSubmitDetail = {
384
326
  endpoint: this.action,
385
- isEdit: this.isEdit,
386
- data: {
387
- title,
388
- slug,
389
- description:
390
- this.variant === "quick"
391
- ? undefined
392
- : this._description.trim() || undefined,
393
- sortOrder:
394
- this.variant === "quick" ? undefined : this._sortOrder || undefined,
395
- },
327
+ data: { title, slug },
396
328
  };
397
329
 
398
330
  this.dispatchEvent(
@@ -404,141 +336,30 @@ export class JantCollectionForm extends LitElement {
404
336
  }
405
337
 
406
338
  render() {
407
- const isQuick = this.variant === "quick";
408
- const formClass = isQuick
409
- ? "flex flex-col gap-4"
410
- : "collection-editor-form";
411
-
412
339
  return html`
413
340
  <form
414
- class=${formClass}
341
+ class="flex flex-col gap-4"
415
342
  @submit=${(event: Event) => void this.#handleSubmit(event)}
416
343
  >
417
- ${isQuick
418
- ? html`
419
- <div class="field">
420
- <label class="label">${this.labels.titleLabel}</label>
421
- <input
422
- type="text"
423
- class="input"
424
- data-collection-title-input
425
- required
426
- maxlength=${MAX_COLLECTION_TITLE_LENGTH}
427
- .value=${this._title}
428
- placeholder=${this.isEdit
429
- ? nothing
430
- : this.labels.titlePlaceholder}
431
- @input=${(event: Event) => void this.#handleTitleInput(event)}
432
- />
433
- </div>
434
-
435
- ${this.#renderQuickSlugControls()}
436
- `
437
- : html`
438
- <div class="collection-editor-primary-grid">
439
- <div class="field collection-editor-title-field">
440
- <label class="label">${this.labels.titleLabel}</label>
441
- <input
442
- type="text"
443
- class="input"
444
- data-collection-title-input
445
- required
446
- maxlength=${MAX_COLLECTION_TITLE_LENGTH}
447
- .value=${this._title}
448
- placeholder=${this.isEdit
449
- ? nothing
450
- : this.labels.titlePlaceholder}
451
- @input=${(event: Event) =>
452
- void this.#handleTitleInput(event)}
453
- />
454
- </div>
455
-
456
- <div class="field collection-editor-slug-field">
457
- <label class="label">${this.labels.slugLabel}</label>
458
- <input
459
- type="text"
460
- class="input"
461
- data-collection-slug-input
462
- required
463
- pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
464
- maxlength=${MAX_COLLECTION_SLUG_LENGTH}
465
- .value=${this._slug}
466
- aria-invalid=${this.#getSlugValidationMessage()
467
- ? "true"
468
- : "false"}
469
- placeholder=${this.isEdit ? nothing : "my-collection"}
470
- @input=${(event: Event) => this.#handleSlugInput(event)}
471
- />
472
- ${this.#renderSlugHelper()}
473
- </div>
474
- </div>
475
-
476
- <div class="collection-editor-secondary-grid">
477
- <div class="field collection-editor-description-field">
478
- <label class="label">${this.labels.descriptionLabel}</label>
479
- <div
480
- class="settings-tiptap-editor"
481
- data-collection-desc-editor
482
- ></div>
483
- </div>
484
-
485
- <div class="field collection-editor-sort-field">
486
- <label class="label">${this.labels.sortOrderLabel}</label>
487
- <select
488
- class="select"
489
- .value=${this._sortOrder}
490
- @change=${(event: Event) => {
491
- const target = event.target as HTMLSelectElement;
492
- this._sortOrder = target.value as CollectionSortOrder;
493
- }}
494
- >
495
- <option value="newest">${this.labels.sortNewest}</option>
496
- <option value="oldest">${this.labels.sortOldest}</option>
497
- <option value="rating_desc">
498
- ${this.labels.sortRatingDesc}
499
- </option>
500
- </select>
501
- </div>
502
- </div>
503
- `}
504
- ${isQuick
505
- ? html`
506
- <button type="submit" class="sr-only">
507
- ${this.labels.quickSubmitLabel}
508
- </button>
509
- `
510
- : html`
511
- <div class="collection-editor-actions">
512
- <button
513
- type="submit"
514
- class="btn collection-editor-submit"
515
- ?disabled=${this._loading}
516
- >
517
- ${this._loading
518
- ? html`<svg
519
- class="animate-spin size-4"
520
- xmlns="http://www.w3.org/2000/svg"
521
- viewBox="0 0 24 24"
522
- fill="none"
523
- stroke="currentColor"
524
- stroke-width="2"
525
- stroke-linecap="round"
526
- stroke-linejoin="round"
527
- role="status"
528
- >
529
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
530
- </svg>`
531
- : nothing}
532
- ${this.labels.submitLabel}
533
- </button>
534
- <a
535
- href=${this.cancelHref}
536
- class="btn-outline collection-editor-cancel"
537
- >
538
- ${this.labels.cancelLabel}
539
- </a>
540
- </div>
541
- `}
344
+ <div class="field">
345
+ <label class="label">${this.labels.titleLabel}</label>
346
+ <input
347
+ type="text"
348
+ class="input"
349
+ data-collection-title-input
350
+ required
351
+ maxlength=${MAX_COLLECTION_TITLE_LENGTH}
352
+ .value=${this._title}
353
+ placeholder=${this.labels.titlePlaceholder}
354
+ @input=${(event: Event) => void this.#handleTitleInput(event)}
355
+ />
356
+ </div>
357
+
358
+ ${this.#renderSlugControls()}
359
+
360
+ <button type="submit" class="sr-only">
361
+ ${this.labels.quickSubmitLabel}
362
+ </button>
542
363
  </form>
543
364
  `;
544
365
  }
@@ -13,7 +13,8 @@ import { classMap } from "lit/directives/class-map.js";
13
13
  import { unsafeSVG } from "lit/directives/unsafe-svg.js";
14
14
  import { NAVIGATION_SETTINGS_PATH } from "../../lib/settings-paths.js";
15
15
  import { openNewCompose } from "../compose-launch.js";
16
- import { publicPath } from "../runtime-paths.js";
16
+ import { isFullUrl, toInternalPath } from "../../lib/url.js";
17
+ import { navPath, sitePathPrefix } from "../runtime-paths.js";
17
18
  import { getBestFieldSearchRank, normalizeSearch } from "../search-rank.js";
18
19
 
19
20
  // ---------------------------------------------------------------------------
@@ -23,7 +24,7 @@ import { getBestFieldSearchRank, normalizeSearch } from "../search-rank.js";
23
24
  interface PaletteItem {
24
25
  title: string;
25
26
  path: string;
26
- type: "post" | "collection" | "system";
27
+ type: "post" | "collection" | "smart_collection" | "system";
27
28
  status?: "draft" | "published";
28
29
  }
29
30
 
@@ -58,6 +59,9 @@ const ICONS = {
58
59
  post: `<svg ${ICON_ATTRS}><path d="M8 2v4"/><path d="M12 2v4"/><path d="M16 2v4"/><rect width="16" height="18" x="4" y="4" rx="2"/><path d="M8 10h6"/><path d="M8 14h8"/><path d="M8 18h5"/></svg>`,
59
60
  // clipboard/collection icon from PostFooter
60
61
  collection: `<svg ${ICON_ATTRS} viewBox="0 0 16 16" stroke-width="1.35"><rect x="3" y="5.05" width="10" height="8.15" rx="2.2"/><path d="M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85"/></svg>`,
62
+ // funnel — the same icon the directory and the page use, so a smart
63
+ // collection reads as a collection with a rule behind it everywhere at once.
64
+ smart_collection: `<svg ${ICON_ATTRS}><path d="M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"/></svg>`,
61
65
  // settings gear from SettingsRootContent
62
66
  system: `<svg ${ICON_ATTRS}><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
63
67
  // zap/lightning for commands
@@ -73,6 +77,29 @@ export function getPathCommandTarget(query: string): string {
73
77
  return stripped ? `/${stripped}` : "/";
74
78
  }
75
79
 
80
+ /**
81
+ * The local path a pasted full URL points at, when it points at this site.
82
+ *
83
+ * Copying an address out of the browser bar and dropping it in the palette
84
+ * should go there, the same as typing `/`. Someone else's URL is left to
85
+ * navigate mode, where it falls through to a full-text search — looking for
86
+ * the post where you linked something is the likelier intent.
87
+ *
88
+ * @param query - Raw palette query
89
+ * @returns Internal path with its query string, or null when not our URL
90
+ */
91
+ export function getPastedAddressTarget(query: string): string | null {
92
+ const trimmed = query.trim();
93
+ if (!isFullUrl(trimmed)) return null;
94
+
95
+ return toInternalPath(trimmed, {
96
+ siteOrigins: [window.location.origin],
97
+ sitePathPrefix: sitePathPrefix(),
98
+ // A pasted `/en/about` was copied from that view and belongs there.
99
+ keepQuery: true,
100
+ });
101
+ }
102
+
76
103
  export function getPathCommandSearchQuery(query: string): string {
77
104
  return query
78
105
  .trim()
@@ -246,6 +273,7 @@ export class JantCommandPalette extends LitElement {
246
273
  if (first === ">" || first === ">") return "command";
247
274
  if (first === "?" || first === "?") return "search";
248
275
  if (first === "/" || first === "/") return "path";
276
+ if (getPastedAddressTarget(this._query)) return "path";
249
277
  return "navigate";
250
278
  }
251
279
 
@@ -326,8 +354,11 @@ export class JantCommandPalette extends LitElement {
326
354
  }
327
355
 
328
356
  if (mode === "path") {
329
- const target = getPathCommandTarget(this._query);
330
- const searchQuery = getPathCommandSearchQuery(this._query);
357
+ const pasted = getPastedAddressTarget(this._query);
358
+ const target = pasted ?? getPathCommandTarget(this._query);
359
+ // A pasted URL is already an answer; only a typed path is also plausible
360
+ // as search words.
361
+ const searchQuery = pasted ? "" : getPathCommandSearchQuery(this._query);
331
362
  const items: DisplayItem[] = [
332
363
  {
333
364
  label: `Go to ${target}`,
@@ -380,7 +411,11 @@ export class JantCommandPalette extends LitElement {
380
411
  #executeSearch(query: string) {
381
412
  saveHistory(SEARCH_HISTORY_KEY, query);
382
413
  this.close();
383
- window.location.href = publicPath(`/search?q=${encodeURIComponent(query)}`);
414
+ // navPath, not publicPath: a search run from inside /en belongs in /en.
415
+ // The palette jumps to settings pages and posts through this same code
416
+ // path, so it cannot assert the kind of any one target — navPath decides
417
+ // per target and leaves the site-wide ones alone.
418
+ window.location.href = navPath(`/search?q=${encodeURIComponent(query)}`);
384
419
  }
385
420
 
386
421
  #executeItem(index: number) {
@@ -409,7 +444,7 @@ export class JantCommandPalette extends LitElement {
409
444
  if (displayItem?.pathTarget) {
410
445
  saveHistory(NAV_HISTORY_KEY, displayItem.pathTarget);
411
446
  this.close();
412
- window.location.href = publicPath(displayItem.pathTarget);
447
+ window.location.href = navPath(displayItem.pathTarget);
413
448
  return;
414
449
  }
415
450
 
@@ -431,11 +466,15 @@ export class JantCommandPalette extends LitElement {
431
466
  saveHistory(NAV_HISTORY_KEY, item.path);
432
467
  this.close();
433
468
  if (item.type === "system") {
434
- window.location.href = publicPath(item.path);
469
+ window.location.href = navPath(item.path);
435
470
  } else if (item.type === "collection") {
436
- window.location.href = publicPath(`/collections/${item.path}`);
471
+ window.location.href = navPath(`/collections/${item.path}`);
472
+ } else if (item.type === "smart_collection") {
473
+ // Straight to its address. `/collections/{slug}` redirects for a
474
+ // collection but names nothing for a smart collection.
475
+ window.location.href = navPath(`/${item.path}`);
437
476
  } else {
438
- window.location.href = publicPath(
477
+ window.location.href = navPath(
439
478
  item.status === "draft"
440
479
  ? `/preview/${item.path}?edit=1`
441
480
  : `/${item.path}`,
@@ -554,19 +593,23 @@ export class JantCommandPalette extends LitElement {
554
593
  <span class="command-palette-result-icon">${unsafeSVG(item.icon)}</span>
555
594
  <span class="command-palette-result-body">
556
595
  <span class="command-palette-result-title">${item.label}</span>
557
- ${item.secondary
558
- ? html`<span class="command-palette-result-path"
559
- >${item.secondary}</span
560
- >`
561
- : nothing}
596
+ ${
597
+ item.secondary
598
+ ? html`<span class="command-palette-result-path"
599
+ >${item.secondary}</span
600
+ >`
601
+ : nothing
602
+ }
562
603
  </span>
563
- ${item.shortcut
564
- ? html`<kbd
565
- class="command-palette-result-shortcut"
566
- aria-label="Command or Control plus Enter"
567
- >${item.shortcut}</kbd
568
- >`
569
- : nothing}
604
+ ${
605
+ item.shortcut
606
+ ? html`<kbd
607
+ class="command-palette-result-shortcut"
608
+ aria-label="Command or Control plus Enter"
609
+ >${item.shortcut}</kbd
610
+ >`
611
+ : nothing
612
+ }
570
613
  </div>
571
614
  `;
572
615
  }
@@ -606,39 +649,47 @@ export class JantCommandPalette extends LitElement {
606
649
  role="combobox"
607
650
  aria-expanded="true"
608
651
  aria-controls="command-palette-results"
609
- aria-activedescendant=${items.length > 0
610
- ? `command-palette-item-${this._selectedIndex}`
611
- : ""}
652
+ aria-activedescendant=${
653
+ items.length > 0
654
+ ? `command-palette-item-${this._selectedIndex}`
655
+ : ""
656
+ }
612
657
  autocomplete="off"
613
658
  spellcheck="false"
614
659
  />
615
- ${this._loading
616
- ? html`<span class="command-palette-spinner"></span>`
617
- : nothing}
660
+ ${
661
+ this._loading
662
+ ? html`<span class="command-palette-spinner"></span>`
663
+ : nothing
664
+ }
618
665
  </div>
619
666
 
620
- ${items.length > 0
621
- ? html`
622
- <div
623
- id="command-palette-results"
624
- class="command-palette-results-container"
625
- role="listbox"
626
- >
627
- ${resultItems.length > 0
628
- ? html`<div class="command-palette-results">
629
- ${resultItems.map(({ item, index }) =>
630
- this.#renderItem(item, index),
631
- )}
632
- </div>`
633
- : nothing}
634
- ${footerItems.map(({ item, index }) =>
635
- this.#renderItem(item, index, true),
636
- )}
637
- </div>
638
- `
639
- : this._query.trim() && !this._loading
640
- ? html`<div class="command-palette-empty">No results</div>`
641
- : nothing}
667
+ ${
668
+ items.length > 0
669
+ ? html`
670
+ <div
671
+ id="command-palette-results"
672
+ class="command-palette-results-container"
673
+ role="listbox"
674
+ >
675
+ ${
676
+ resultItems.length > 0
677
+ ? html`<div class="command-palette-results">
678
+ ${resultItems.map(({ item, index }) =>
679
+ this.#renderItem(item, index),
680
+ )}
681
+ </div>`
682
+ : nothing
683
+ }
684
+ ${footerItems.map(({ item, index }) =>
685
+ this.#renderItem(item, index, true),
686
+ )}
687
+ </div>
688
+ `
689
+ : this._query.trim() && !this._loading
690
+ ? html`<div class="command-palette-empty">No results</div>`
691
+ : nothing
692
+ }
642
693
  </div>
643
694
  </dialog>
644
695
  `;