@jant/core 0.6.15 → 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 (537) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/import-site.js +67 -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-C-oi_t8W.js → app-K_Aa1MMn.js} +52623 -13193
  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-CsFx6F_M.js → github-sync-BPAvT999.js} +2343 -110
  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__/mise-config.test.ts +22 -0
  51. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  52. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  53. package/src/app.tsx +71 -16
  54. package/src/auth.ts +18 -1
  55. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  56. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  57. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  58. package/src/client/__tests__/compose-bridge.test.ts +524 -2
  59. package/src/client/__tests__/compose-launch.test.ts +62 -1
  60. package/src/client/__tests__/compose-shortcuts.test.ts +57 -8
  61. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  62. package/src/client/__tests__/copy-field.test.ts +162 -0
  63. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  64. package/src/client/__tests__/image-processor.test.ts +324 -2
  65. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  66. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  67. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  68. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  69. package/src/client/__tests__/toast.test.ts +88 -1
  70. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  71. package/src/client/__tests__/video-processor.test.ts +181 -0
  72. package/src/client/audio-processor.ts +24 -15
  73. package/src/client/audio-waveform.ts +49 -0
  74. package/src/client/collection-dialog-host.ts +72 -0
  75. package/src/client/collection-navigation.ts +51 -16
  76. package/src/client/collection-page-actions.ts +26 -0
  77. package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
  78. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  79. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  80. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  81. package/src/client/components/__tests__/jant-command-palette.test.ts +79 -0
  82. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  83. package/src/client/components/__tests__/jant-compose-dialog.test.ts +3283 -389
  84. package/src/client/components/__tests__/jant-compose-editor.test.ts +581 -86
  85. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +34 -89
  86. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  87. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  88. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  89. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  90. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  91. package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
  92. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  93. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  94. package/src/client/components/collection-dialog-types.ts +49 -0
  95. package/src/client/components/collection-manager-types.ts +32 -1
  96. package/src/client/components/collection-types.ts +1 -15
  97. package/src/client/components/compose-format-convert.ts +5 -1
  98. package/src/client/components/compose-types.ts +121 -3
  99. package/src/client/components/jant-collection-dialog.ts +722 -0
  100. package/src/client/components/jant-collection-directory.ts +567 -182
  101. package/src/client/components/jant-collection-form.ts +53 -232
  102. package/src/client/components/jant-command-palette.ts +106 -49
  103. package/src/client/components/jant-compose-dialog.ts +2757 -1194
  104. package/src/client/components/jant-compose-editor.ts +866 -397
  105. package/src/client/components/jant-compose-fullscreen.ts +53 -47
  106. package/src/client/components/jant-config-editor.ts +65 -53
  107. package/src/client/components/jant-confirm-dialog.ts +13 -11
  108. package/src/client/components/jant-locale-picker.ts +418 -0
  109. package/src/client/components/jant-media-lightbox.ts +146 -136
  110. package/src/client/components/jant-nav-manager.ts +690 -366
  111. package/src/client/components/jant-post-menu.ts +1142 -268
  112. package/src/client/components/jant-post-picker.ts +324 -0
  113. package/src/client/components/jant-repo-picker.ts +138 -106
  114. package/src/client/components/jant-settings-avatar.ts +31 -27
  115. package/src/client/components/jant-settings-general.ts +617 -543
  116. package/src/client/components/jant-settings-language.ts +1087 -0
  117. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  118. package/src/client/components/jant-text-preview.ts +94 -86
  119. package/src/client/components/nav-manager-types.ts +62 -1
  120. package/src/client/components/settings-types.ts +14 -29
  121. package/src/client/components/smart-collection-conditions.ts +285 -0
  122. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  123. package/src/client/compose-bridge.ts +336 -242
  124. package/src/client/compose-launch.ts +89 -2
  125. package/src/client/compose-shortcuts.ts +9 -7
  126. package/src/client/compose-triggers.ts +50 -0
  127. package/src/client/copy-field.ts +108 -0
  128. package/src/client/custom-url-menu.ts +60 -4
  129. package/src/client/icons.ts +14 -0
  130. package/src/client/image-plan.ts +165 -0
  131. package/src/client/image-processor.ts +195 -117
  132. package/src/client/image-worker-client.ts +45 -0
  133. package/src/client/image-worker.ts +135 -0
  134. package/src/client/lazy-entries.ts +95 -0
  135. package/src/client/lazy-slugify.ts +2 -2
  136. package/src/client/media-scroll-hint.ts +43 -2
  137. package/src/client/mediabunny.ts +29 -0
  138. package/src/client/post-picker.ts +41 -0
  139. package/src/client/post-refresh.ts +135 -0
  140. package/src/client/runtime-paths.ts +74 -1
  141. package/src/client/settings-bridge.ts +4 -10
  142. package/src/client/site-header-nav.js +18 -6
  143. package/src/client/smart-collection-dialog-host.ts +116 -0
  144. package/src/client/smart-collection-page-actions.ts +208 -0
  145. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  146. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  147. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  148. package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
  149. package/src/client/tiptap/bubble-menu.ts +47 -16
  150. package/src/client/tiptap/floating-position.ts +216 -2
  151. package/src/client/tiptap/inline-image-upload.ts +7 -3
  152. package/src/client/tiptap/link-input-rules.ts +4 -4
  153. package/src/client/tiptap/link-toolbar.ts +22 -9
  154. package/src/client/toast.ts +143 -38
  155. package/src/client/types/vite-worker.d.ts +12 -0
  156. package/src/client/upload-with-metadata.ts +3 -13
  157. package/src/client/video-processor.ts +351 -107
  158. package/src/client-auth.ts +14 -31
  159. package/src/client-compose.ts +19 -0
  160. package/src/client-manage.ts +13 -0
  161. package/src/client-settings.ts +14 -0
  162. package/src/client-site.ts +10 -3
  163. package/src/client.ts +4 -0
  164. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  165. package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
  166. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  167. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  168. package/src/db/__tests__/thread-activity.test.ts +113 -0
  169. package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
  170. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  171. package/src/db/migrations/0031_many_ego.sql +3 -0
  172. package/src/db/migrations/0032_married_tomas.sql +3 -0
  173. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  174. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  175. package/src/db/migrations/meta/0031_snapshot.json +2562 -0
  176. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  177. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  178. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  179. package/src/db/migrations/meta/_journal.json +28 -0
  180. package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
  181. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  182. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  183. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  184. package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
  185. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  186. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  187. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  188. package/src/db/migrations/pg/meta/_journal.json +28 -0
  189. package/src/db/pg/schema.ts +213 -27
  190. package/src/db/post-visibility.ts +117 -0
  191. package/src/db/schema.ts +242 -27
  192. package/src/db/thread-activity.ts +101 -0
  193. package/src/i18n/__tests__/detect.test.ts +26 -81
  194. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  195. package/src/i18n/context.tsx +12 -2
  196. package/src/i18n/detect.ts +7 -66
  197. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  198. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  199. package/src/i18n/locales/public/en.po +568 -168
  200. package/src/i18n/locales/public/en.ts +1 -1
  201. package/src/i18n/locales/public/zh-Hans.po +562 -162
  202. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  203. package/src/i18n/locales/public/zh-Hant.po +562 -162
  204. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  205. package/src/i18n/locales/settings/en.po +551 -69
  206. package/src/i18n/locales/settings/en.ts +1 -1
  207. package/src/i18n/locales/settings/zh-Hans.po +597 -115
  208. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  209. package/src/i18n/locales/settings/zh-Hant.po +610 -128
  210. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  211. package/src/i18n/locales.ts +94 -0
  212. package/src/i18n/middleware.ts +12 -2
  213. package/src/i18n/supported-locales.ts +80 -0
  214. package/src/lib/__tests__/api-settings.test.ts +11 -2
  215. package/src/lib/__tests__/asset-path.test.ts +92 -0
  216. package/src/lib/__tests__/deferred.test.ts +90 -0
  217. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  218. package/src/lib/__tests__/discover.test.ts +410 -0
  219. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  220. package/src/lib/__tests__/feed.test.ts +1691 -12
  221. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  222. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  223. package/src/lib/__tests__/http-cache.test.ts +162 -0
  224. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  225. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  226. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  227. package/src/lib/__tests__/markdown.test.ts +16 -0
  228. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  229. package/src/lib/__tests__/navigation.test.ts +135 -1
  230. package/src/lib/__tests__/resolve-config.test.ts +64 -3
  231. package/src/lib/__tests__/schemas.test.ts +42 -4
  232. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  233. package/src/lib/__tests__/slug.test.ts +88 -0
  234. package/src/lib/__tests__/slugify.test.ts +85 -0
  235. package/src/lib/__tests__/storage.test.ts +32 -0
  236. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  237. package/src/lib/__tests__/translit.test.ts +87 -0
  238. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  239. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  240. package/src/lib/__tests__/url.test.ts +93 -34
  241. package/src/lib/__tests__/view-language.test.ts +413 -0
  242. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  243. package/src/lib/address.ts +57 -0
  244. package/src/lib/api-posts.ts +12 -0
  245. package/src/lib/asset-path.ts +98 -1
  246. package/src/lib/build-env.ts +16 -0
  247. package/src/lib/collection-groups.ts +5 -1
  248. package/src/lib/collection-paths.ts +3 -10
  249. package/src/lib/constants.ts +28 -3
  250. package/src/lib/copy-field.ts +36 -0
  251. package/src/lib/crypto.ts +40 -0
  252. package/src/lib/csp-builder.ts +2 -1
  253. package/src/lib/deferred.ts +79 -0
  254. package/src/lib/discover-ping.ts +141 -0
  255. package/src/lib/discover.ts +437 -0
  256. package/src/lib/env.ts +134 -0
  257. package/src/lib/errors.ts +33 -0
  258. package/src/lib/feed-policy.ts +236 -2
  259. package/src/lib/feed.ts +1021 -66
  260. package/src/lib/filter-dimensions.ts +1349 -0
  261. package/src/lib/github-sync-site-config.ts +3 -0
  262. package/src/lib/hosted-signin.ts +1 -1
  263. package/src/lib/http-cache.ts +148 -0
  264. package/src/lib/hugo-markdown.ts +21 -0
  265. package/src/lib/icons.ts +9 -2
  266. package/src/lib/ids.ts +3 -0
  267. package/src/lib/jant-docs.ts +42 -0
  268. package/src/lib/lang-detect.ts +308 -0
  269. package/src/lib/link-preview.ts +25 -0
  270. package/src/lib/markdown-manager.ts +6 -1
  271. package/src/lib/media-helpers.ts +13 -1
  272. package/src/lib/navigation.ts +78 -11
  273. package/src/lib/pagination.ts +28 -24
  274. package/src/lib/per-language-surfaces.ts +67 -0
  275. package/src/lib/post-display.ts +42 -9
  276. package/src/lib/post-meta.ts +19 -0
  277. package/src/lib/render.tsx +55 -7
  278. package/src/lib/resolve-config.ts +53 -5
  279. package/src/lib/schemas.ts +201 -10
  280. package/src/lib/site-header-fragment.tsx +2 -1
  281. package/src/lib/site-unavailable-page.ts +47 -0
  282. package/src/lib/slug.ts +51 -7
  283. package/src/lib/slugify.ts +113 -0
  284. package/src/lib/startup-config.ts +4 -6
  285. package/src/lib/storage.ts +7 -0
  286. package/src/lib/summary.ts +50 -0
  287. package/src/lib/thread-fold.ts +161 -0
  288. package/src/lib/time.ts +32 -0
  289. package/src/lib/timeline.ts +81 -10
  290. package/src/lib/tiptap-render.ts +5 -4
  291. package/src/lib/translit.ts +288 -0
  292. package/src/lib/upload.ts +16 -13
  293. package/src/lib/url.ts +188 -24
  294. package/src/lib/version.ts +10 -8
  295. package/src/lib/view-language.ts +436 -0
  296. package/src/lib/view.ts +79 -23
  297. package/src/lib/viewer-context.ts +62 -0
  298. package/src/middleware/__tests__/auth.test.ts +70 -2
  299. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  300. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  301. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  302. package/src/middleware/__tests__/session.test.ts +181 -33
  303. package/src/middleware/auth.ts +50 -31
  304. package/src/middleware/cache-control.ts +75 -10
  305. package/src/middleware/config.ts +6 -6
  306. package/src/middleware/error-handler.ts +12 -0
  307. package/src/middleware/onboarding.ts +32 -10
  308. package/src/middleware/secure-headers.ts +1 -1
  309. package/src/middleware/session.ts +96 -8
  310. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  311. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  312. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  313. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  314. package/src/node/__tests__/runtime.test.ts +37 -0
  315. package/src/node/index.ts +1 -0
  316. package/src/preset.css +76 -22
  317. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  318. package/src/routes/api/__tests__/collections.test.ts +114 -0
  319. package/src/routes/api/__tests__/discover.test.ts +222 -0
  320. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  321. package/src/routes/api/__tests__/palette.test.ts +44 -0
  322. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  323. package/src/routes/api/__tests__/settings.test.ts +24 -3
  324. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  325. package/src/routes/api/collections.ts +49 -6
  326. package/src/routes/api/custom-urls.ts +0 -1
  327. package/src/routes/api/discover.ts +95 -0
  328. package/src/routes/api/export.ts +3 -0
  329. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  330. package/src/routes/api/nav-items.ts +55 -8
  331. package/src/routes/api/posts.ts +172 -9
  332. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  333. package/src/routes/api/public/__tests__/posts.test.ts +38 -0
  334. package/src/routes/api/public/archive.ts +77 -81
  335. package/src/routes/api/public/posts.ts +30 -6
  336. package/src/routes/api/smart-collections.ts +132 -0
  337. package/src/routes/api/telegram.ts +2 -40
  338. package/src/routes/api/uploads.ts +1 -2
  339. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  340. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  341. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  342. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  343. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  344. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  345. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  346. package/src/routes/auth/setup.tsx +934 -186
  347. package/src/routes/auth/signin.tsx +10 -8
  348. package/src/routes/compose.tsx +11 -2
  349. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  350. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  351. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  352. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  353. package/src/routes/dash/custom-urls.tsx +1 -37
  354. package/src/routes/dash/settings.tsx +502 -41
  355. package/src/routes/discover-announce.ts +96 -0
  356. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  357. package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
  358. package/src/routes/feed/feed.ts +69 -40
  359. package/src/routes/feed/sitemap.ts +95 -11
  360. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  361. package/src/routes/pages/__tests__/archive-params.test.ts +657 -1
  362. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  363. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  364. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  365. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  366. package/src/routes/pages/__tests__/featured.test.ts +6 -2
  367. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  368. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  369. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  370. package/src/routes/pages/__tests__/preview.test.ts +3 -1
  371. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  372. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  373. package/src/routes/pages/archive.tsx +507 -357
  374. package/src/routes/pages/collection.tsx +94 -96
  375. package/src/routes/pages/collections.tsx +38 -50
  376. package/src/routes/pages/featured.tsx +32 -13
  377. package/src/routes/pages/home.tsx +25 -6
  378. package/src/routes/pages/language.tsx +113 -0
  379. package/src/routes/pages/latest.tsx +26 -16
  380. package/src/routes/pages/new.tsx +3 -0
  381. package/src/routes/pages/page.tsx +183 -19
  382. package/src/routes/pages/partials.tsx +41 -0
  383. package/src/routes/pages/search.tsx +18 -3
  384. package/src/routes/pages/smart-collection.tsx +340 -0
  385. package/src/routes/pages/subscribe.tsx +182 -0
  386. package/src/runtime/__tests__/index.test.ts +19 -0
  387. package/src/runtime/__tests__/node.test.ts +129 -4
  388. package/src/runtime/cloudflare.ts +21 -2
  389. package/src/runtime/index.ts +27 -1
  390. package/src/runtime/node.ts +18 -1
  391. package/src/runtime/site.ts +95 -24
  392. package/src/services/__tests__/auth.test.ts +116 -7
  393. package/src/services/__tests__/collection.test.ts +139 -4
  394. package/src/services/__tests__/language.test.ts +495 -0
  395. package/src/services/__tests__/navigation.test.ts +213 -10
  396. package/src/services/__tests__/path.test.ts +180 -0
  397. package/src/services/__tests__/post-language.test.ts +830 -0
  398. package/src/services/__tests__/post-timeline.test.ts +142 -11
  399. package/src/services/__tests__/post.test.ts +323 -9
  400. package/src/services/__tests__/settings.test.ts +228 -17
  401. package/src/services/__tests__/site-admin.test.ts +5 -5
  402. package/src/services/__tests__/smart-collection.test.ts +651 -0
  403. package/src/services/about-page.ts +22 -1
  404. package/src/services/auth.ts +51 -49
  405. package/src/services/bootstrap.ts +188 -38
  406. package/src/services/collection-directory-position.ts +76 -0
  407. package/src/services/collection.ts +330 -47
  408. package/src/services/custom-url.ts +27 -22
  409. package/src/services/export-theme/assets/client-site.css +1 -1
  410. package/src/services/export-theme/assets/client-site.js +123 -123
  411. package/src/services/export-theme/layouts/_default/rss.xml +77 -1
  412. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  413. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  414. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  415. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  416. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  417. package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
  418. package/src/services/export-theme/styles/main.css +13 -1
  419. package/src/services/export.ts +281 -24
  420. package/src/services/index.ts +34 -3
  421. package/src/services/language.ts +412 -0
  422. package/src/services/mcp.ts +3 -1
  423. package/src/services/media.ts +14 -6
  424. package/src/services/navigation.ts +429 -67
  425. package/src/services/path.ts +225 -7
  426. package/src/services/post.ts +1318 -252
  427. package/src/services/search.ts +48 -2
  428. package/src/services/settings.ts +238 -43
  429. package/src/services/site-admin.ts +24 -7
  430. package/src/services/site.ts +77 -1
  431. package/src/services/smart-collection.ts +636 -0
  432. package/src/services/telegram.ts +20 -22
  433. package/src/services/upload-session.ts +1 -3
  434. package/src/style-author.css +27 -0
  435. package/src/styles/components-author.css +1622 -0
  436. package/src/styles/components.css +24 -1585
  437. package/src/styles/site-media.css +20 -6
  438. package/src/styles/tokens.css +144 -13
  439. package/src/styles/ui-author.css +7602 -0
  440. package/src/styles/ui.css +1224 -7276
  441. package/src/types/app-context.ts +7 -0
  442. package/src/types/bindings.ts +3 -0
  443. package/src/types/config.ts +94 -15
  444. package/src/types/constants.ts +188 -16
  445. package/src/types/entities.ts +92 -1
  446. package/src/types/operations.ts +64 -2
  447. package/src/types/props.ts +168 -30
  448. package/src/types/views.ts +61 -1
  449. package/src/ui/__tests__/font-themes.test.ts +52 -12
  450. package/src/ui/compose/ComposeDialog.tsx +215 -21
  451. package/src/ui/compose/ComposePrompt.tsx +1 -5
  452. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  453. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  454. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  455. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  456. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  457. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  458. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  459. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  460. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  461. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  462. package/src/ui/dash/settings/GeneralContent.tsx +275 -113
  463. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  464. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  465. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  466. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  467. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  468. package/src/ui/dash/settings/discover-copy.ts +116 -0
  469. package/src/ui/feed/LinkCard.tsx +55 -45
  470. package/src/ui/feed/LinkPreview.tsx +2 -7
  471. package/src/ui/feed/NoteCard.tsx +9 -4
  472. package/src/ui/feed/PostStatusBadges.tsx +103 -17
  473. package/src/ui/feed/QuoteCard.tsx +55 -30
  474. package/src/ui/feed/ThreadPreview.tsx +6 -2
  475. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  476. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  477. package/src/ui/feed/thread-preview-state.ts +20 -6
  478. package/src/ui/font-themes.ts +39 -29
  479. package/src/ui/layouts/BaseLayout.tsx +155 -10
  480. package/src/ui/layouts/SiteLayout.tsx +192 -34
  481. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  482. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  483. package/src/ui/pages/ArchivePage.tsx +401 -301
  484. package/src/ui/pages/CollectionPage.tsx +86 -76
  485. package/src/ui/pages/CollectionsPage.tsx +5 -0
  486. package/src/ui/pages/ComposePage.tsx +8 -0
  487. package/src/ui/pages/PostPage.tsx +47 -0
  488. package/src/ui/pages/SearchPage.tsx +3 -3
  489. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  490. package/src/ui/pages/SubscribePage.tsx +236 -0
  491. package/src/ui/pages/__tests__/ArchivePage.test.tsx +122 -3
  492. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  493. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  494. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  495. package/src/ui/shared/CollectionsManager.tsx +70 -17
  496. package/src/ui/shared/CopyField.tsx +127 -0
  497. package/src/ui/shared/DraftPreviewBar.tsx +25 -9
  498. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  499. package/src/ui/shared/MediaGallery.tsx +125 -46
  500. package/src/ui/shared/Pagination.tsx +20 -5
  501. package/src/ui/shared/PostFooter.tsx +55 -23
  502. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  503. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  504. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  505. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  506. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  507. package/src/ui/shared/archive-labels.ts +98 -0
  508. package/src/ui/shared/collection-management-labels.ts +94 -16
  509. package/src/ui/shared/custom-icons.ts +5 -0
  510. package/src/ui/shared/navigation-labels.ts +45 -71
  511. package/src/ui/shared/post-article-attributes.ts +8 -0
  512. package/src/ui/shared/smart-collection-labels.ts +343 -0
  513. package/dist/app-BxCOR3Uc.js +0 -6
  514. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  515. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  516. package/dist/client/_assets/chunks/url-pLre2DM_.js +0 -1
  517. package/dist/client/_assets/client-B_eGKN5p.css +0 -2
  518. package/dist/client/_assets/client-DxY5BFe8.js +0 -296
  519. package/dist/client/_assets/client-auth-CakPESv9.js +0 -5533
  520. package/dist/env-OHRKGcMj.js +0 -281
  521. package/dist/github-api-BgSiE71w.js +0 -176
  522. package/dist/github-app-BbklkFmU.js +0 -275
  523. package/dist/github-sync-Cq1pzzOI.js +0 -431
  524. package/dist/github-sync-ppWXN3jb.js +0 -4
  525. package/dist/url-BMYO-Zlt.js +0 -395
  526. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  527. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  528. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  529. package/src/client/collection-created-notice.ts +0 -66
  530. package/src/client/collection-form-bridge.ts +0 -191
  531. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  532. package/src/client/components/jant-post-form.ts +0 -422
  533. package/src/client/components/post-form-template.ts +0 -352
  534. package/src/client/components/post-form-types.ts +0 -96
  535. package/src/client/media-metadata.ts +0 -247
  536. package/src/client/post-form-bridge.ts +0 -193
  537. 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,6 +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 { isFullUrl, toInternalPath } from "../../lib/url.js";
17
+ import { navPath, sitePathPrefix } from "../runtime-paths.js";
16
18
  import { getBestFieldSearchRank, normalizeSearch } from "../search-rank.js";
17
19
 
18
20
  // ---------------------------------------------------------------------------
@@ -22,7 +24,8 @@ import { getBestFieldSearchRank, normalizeSearch } from "../search-rank.js";
22
24
  interface PaletteItem {
23
25
  title: string;
24
26
  path: string;
25
- type: "post" | "collection" | "system";
27
+ type: "post" | "collection" | "smart_collection" | "system";
28
+ status?: "draft" | "published";
26
29
  }
27
30
 
28
31
  interface CommandItem {
@@ -56,6 +59,9 @@ const ICONS = {
56
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>`,
57
60
  // clipboard/collection icon from PostFooter
58
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>`,
59
65
  // settings gear from SettingsRootContent
60
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>`,
61
67
  // zap/lightning for commands
@@ -71,6 +77,29 @@ export function getPathCommandTarget(query: string): string {
71
77
  return stripped ? `/${stripped}` : "/";
72
78
  }
73
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
+
74
103
  export function getPathCommandSearchQuery(query: string): string {
75
104
  return query
76
105
  .trim()
@@ -244,6 +273,7 @@ export class JantCommandPalette extends LitElement {
244
273
  if (first === ">" || first === ">") return "command";
245
274
  if (first === "?" || first === "?") return "search";
246
275
  if (first === "/" || first === "/") return "path";
276
+ if (getPastedAddressTarget(this._query)) return "path";
247
277
  return "navigate";
248
278
  }
249
279
 
@@ -324,8 +354,11 @@ export class JantCommandPalette extends LitElement {
324
354
  }
325
355
 
326
356
  if (mode === "path") {
327
- const target = getPathCommandTarget(this._query);
328
- 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);
329
362
  const items: DisplayItem[] = [
330
363
  {
331
364
  label: `Go to ${target}`,
@@ -351,7 +384,7 @@ export class JantCommandPalette extends LitElement {
351
384
  const q = normalizeSearch(this._query);
352
385
  const navItems: DisplayItem[] = this.#navigateItems.map((item) => ({
353
386
  label: item.title,
354
- secondary: item.path,
387
+ secondary: item.status === "draft" ? `Draft · ${item.path}` : item.path,
355
388
  icon: ICONS[item.type],
356
389
  }));
357
390
 
@@ -378,7 +411,11 @@ export class JantCommandPalette extends LitElement {
378
411
  #executeSearch(query: string) {
379
412
  saveHistory(SEARCH_HISTORY_KEY, query);
380
413
  this.close();
381
- window.location.href = `/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)}`);
382
419
  }
383
420
 
384
421
  #executeItem(index: number) {
@@ -407,7 +444,7 @@ export class JantCommandPalette extends LitElement {
407
444
  if (displayItem?.pathTarget) {
408
445
  saveHistory(NAV_HISTORY_KEY, displayItem.pathTarget);
409
446
  this.close();
410
- window.location.href = displayItem.pathTarget;
447
+ window.location.href = navPath(displayItem.pathTarget);
411
448
  return;
412
449
  }
413
450
 
@@ -429,11 +466,19 @@ export class JantCommandPalette extends LitElement {
429
466
  saveHistory(NAV_HISTORY_KEY, item.path);
430
467
  this.close();
431
468
  if (item.type === "system") {
432
- window.location.href = item.path;
469
+ window.location.href = navPath(item.path);
433
470
  } else if (item.type === "collection") {
434
- window.location.href = `/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}`);
435
476
  } else {
436
- window.location.href = `/${item.path}`;
477
+ window.location.href = navPath(
478
+ item.status === "draft"
479
+ ? `/preview/${item.path}?edit=1`
480
+ : `/${item.path}`,
481
+ );
437
482
  }
438
483
  }
439
484
  }
@@ -548,19 +593,23 @@ export class JantCommandPalette extends LitElement {
548
593
  <span class="command-palette-result-icon">${unsafeSVG(item.icon)}</span>
549
594
  <span class="command-palette-result-body">
550
595
  <span class="command-palette-result-title">${item.label}</span>
551
- ${item.secondary
552
- ? html`<span class="command-palette-result-path"
553
- >${item.secondary}</span
554
- >`
555
- : nothing}
596
+ ${
597
+ item.secondary
598
+ ? html`<span class="command-palette-result-path"
599
+ >${item.secondary}</span
600
+ >`
601
+ : nothing
602
+ }
556
603
  </span>
557
- ${item.shortcut
558
- ? html`<kbd
559
- class="command-palette-result-shortcut"
560
- aria-label="Command or Control plus Enter"
561
- >${item.shortcut}</kbd
562
- >`
563
- : 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
+ }
564
613
  </div>
565
614
  `;
566
615
  }
@@ -600,39 +649,47 @@ export class JantCommandPalette extends LitElement {
600
649
  role="combobox"
601
650
  aria-expanded="true"
602
651
  aria-controls="command-palette-results"
603
- aria-activedescendant=${items.length > 0
604
- ? `command-palette-item-${this._selectedIndex}`
605
- : ""}
652
+ aria-activedescendant=${
653
+ items.length > 0
654
+ ? `command-palette-item-${this._selectedIndex}`
655
+ : ""
656
+ }
606
657
  autocomplete="off"
607
658
  spellcheck="false"
608
659
  />
609
- ${this._loading
610
- ? html`<span class="command-palette-spinner"></span>`
611
- : nothing}
660
+ ${
661
+ this._loading
662
+ ? html`<span class="command-palette-spinner"></span>`
663
+ : nothing
664
+ }
612
665
  </div>
613
666
 
614
- ${items.length > 0
615
- ? html`
616
- <div
617
- id="command-palette-results"
618
- class="command-palette-results-container"
619
- role="listbox"
620
- >
621
- ${resultItems.length > 0
622
- ? html`<div class="command-palette-results">
623
- ${resultItems.map(({ item, index }) =>
624
- this.#renderItem(item, index),
625
- )}
626
- </div>`
627
- : nothing}
628
- ${footerItems.map(({ item, index }) =>
629
- this.#renderItem(item, index, true),
630
- )}
631
- </div>
632
- `
633
- : this._query.trim() && !this._loading
634
- ? html`<div class="command-palette-empty">No results</div>`
635
- : 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
+ }
636
693
  </div>
637
694
  </dialog>
638
695
  `;