@jant/core 0.6.14 → 0.6.16

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 (350) hide show
  1. package/bin/commands/import-site.js +88 -13
  2. package/bin/commands/migrate.js +55 -0
  3. package/bin/commands/posts/rebuild-html.js +184 -0
  4. package/bin/commands/site/snapshot/import.js +14 -10
  5. package/bin/lib/d1-query.js +23 -5
  6. package/bin/lib/migration-runner.js +29 -7
  7. package/bin/lib/node-database.js +32 -0
  8. package/bin/lib/site-snapshot.js +347 -46
  9. package/bin/lib/sql-export.js +1 -1
  10. package/bin/lib/thread-collection-migration.js +326 -0
  11. package/dist/app-C75Zc8dD.js +6 -0
  12. package/dist/{app-2i7-WQHg.js → app-C7_4oab9.js} +7774 -6425
  13. package/dist/client/.vite/manifest.json +8 -8
  14. package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
  15. package/dist/client/_assets/{client-BvID5ucz.js → client-Bq0vre8Y.js} +59 -38
  16. package/dist/client/_assets/client-D3VZyG4L.css +2 -0
  17. package/dist/client/_assets/client-auth-F2rb9WfF.js +5571 -0
  18. package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
  19. package/dist/{export-DWn149b7.js → export-Dl5KCiEs.js} +1348 -128
  20. package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
  21. package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
  22. package/dist/{github-sync-C0zyF6XS.js → github-sync-Bqg62IvV.js} +3 -3
  23. package/dist/{github-sync-Ckk0sJxK.js → github-sync-HdK2EgvJ.js} +3 -3
  24. package/dist/index.js +5 -5
  25. package/dist/node.js +7 -7
  26. package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
  27. package/package.json +11 -10
  28. package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
  29. package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
  30. package/src/__tests__/export-hugo-build.test.ts +63 -4
  31. package/src/__tests__/export-import-roundtrip.test.ts +6 -4
  32. package/src/__tests__/export-service.test.ts +142 -9
  33. package/src/__tests__/helpers/app.ts +3 -0
  34. package/src/__tests__/helpers/export-fixtures.ts +2 -0
  35. package/src/__tests__/import-site-command.test.ts +65 -0
  36. package/src/__tests__/mise-config.test.ts +22 -0
  37. package/src/__tests__/vite-config.test.ts +45 -0
  38. package/src/app.tsx +16 -3
  39. package/src/client/__tests__/collection-created-notice.test.ts +36 -0
  40. package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
  41. package/src/client/__tests__/collection-page-actions.test.ts +71 -2
  42. package/src/client/__tests__/compose-bridge.test.ts +316 -2
  43. package/src/client/__tests__/compose-shortcuts.test.ts +20 -0
  44. package/src/client/__tests__/feed-video-player.test.ts +16 -1
  45. package/src/client/__tests__/footnote-rail.test.ts +472 -0
  46. package/src/client/__tests__/hydrate-partial.test.ts +3 -0
  47. package/src/client/__tests__/toast.test.ts +82 -1
  48. package/src/client/collection-created-notice.ts +66 -0
  49. package/src/client/collection-form-bridge.ts +17 -0
  50. package/src/client/collection-navigation.ts +44 -0
  51. package/src/client/collection-page-actions.ts +57 -1
  52. package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
  53. package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
  54. package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
  55. package/src/client/components/__tests__/jant-compose-dialog.test.ts +2268 -206
  56. package/src/client/components/__tests__/jant-compose-editor.test.ts +147 -59
  57. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +114 -64
  58. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  59. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  60. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  61. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  62. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  63. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  64. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  65. package/src/client/components/collection-manager-types.ts +9 -3
  66. package/src/client/components/compose-format-convert.ts +5 -1
  67. package/src/client/components/compose-types.ts +55 -3
  68. package/src/client/components/jant-collection-directory.ts +165 -30
  69. package/src/client/components/jant-command-palette.ts +14 -7
  70. package/src/client/components/jant-compose-dialog.ts +1542 -694
  71. package/src/client/components/jant-compose-editor.ts +237 -99
  72. package/src/client/components/jant-compose-fullscreen.ts +27 -9
  73. package/src/client/components/jant-config-editor.ts +722 -0
  74. package/src/client/components/jant-media-lightbox.ts +100 -18
  75. package/src/client/components/jant-nav-manager.ts +1283 -54
  76. package/src/client/components/jant-post-form.ts +3 -3
  77. package/src/client/components/jant-post-menu.ts +177 -95
  78. package/src/client/components/nav-manager-types.ts +51 -2
  79. package/src/client/components/post-form-types.ts +1 -1
  80. package/src/client/compose-bridge.ts +147 -123
  81. package/src/client/compose-launch.ts +13 -0
  82. package/src/client/compose-shortcuts.ts +5 -1
  83. package/src/client/feed-video-player.ts +62 -2
  84. package/src/client/footnote-rail.ts +478 -0
  85. package/src/client/hydrate-partial.ts +2 -0
  86. package/src/client/media-lightbox-events.ts +63 -0
  87. package/src/client/post-refresh.ts +135 -0
  88. package/src/client/site-header-fragment.ts +38 -0
  89. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  90. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  91. package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
  92. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  93. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
  94. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  95. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  96. package/src/client/tiptap/bubble-menu.ts +88 -10
  97. package/src/client/tiptap/continuous-lists.ts +102 -0
  98. package/src/client/tiptap/create-editor.ts +3 -0
  99. package/src/client/tiptap/extensions.ts +14 -5
  100. package/src/client/tiptap/link-input-rules.ts +4 -4
  101. package/src/client/tiptap/markdown-clipboard.ts +608 -11
  102. package/src/client/tiptap/slash-commands.ts +8 -1
  103. package/src/client/tiptap/structural-keymap.ts +211 -0
  104. package/src/client/tiptap/tab-indent.ts +8 -22
  105. package/src/client/tiptap/table-control-labels.ts +30 -0
  106. package/src/client/tiptap/table-controls.ts +537 -0
  107. package/src/client/tiptap/table-size-picker.ts +254 -0
  108. package/src/client/toast.ts +133 -39
  109. package/src/client-auth.ts +1 -0
  110. package/src/client.ts +1 -0
  111. package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
  112. package/src/db/__tests__/d1-query.test.ts +43 -1
  113. package/src/db/__tests__/migration-runner.test.ts +63 -0
  114. package/src/db/__tests__/migrations.test.ts +469 -0
  115. package/src/db/__tests__/thread-activity.test.ts +113 -0
  116. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  117. package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
  118. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  119. package/src/db/migrations/0028_superb_post.sql +2 -0
  120. package/src/db/migrations/0029_boring_magma.sql +1 -0
  121. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  122. package/src/db/migrations/0031_many_ego.sql +3 -0
  123. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  124. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  125. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  126. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  127. package/src/db/migrations/meta/0031_snapshot.json +2562 -0
  128. package/src/db/migrations/meta/_journal.json +35 -0
  129. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  130. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  131. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  132. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  133. package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
  134. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  135. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  136. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  137. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  138. package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
  139. package/src/db/migrations/pg/meta/_journal.json +35 -0
  140. package/src/db/pg/schema.ts +46 -20
  141. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  142. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  143. package/src/db/schema.ts +48 -20
  144. package/src/db/thread-activity.ts +101 -0
  145. package/src/i18n/__tests__/detect.test.ts +8 -2
  146. package/src/i18n/detect.ts +14 -5
  147. package/src/i18n/locales/public/en.po +222 -51
  148. package/src/i18n/locales/public/en.ts +1 -1
  149. package/src/i18n/locales/public/zh-Hans.po +216 -45
  150. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  151. package/src/i18n/locales/public/zh-Hant.po +216 -45
  152. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  153. package/src/i18n/locales/settings/en.po +513 -10
  154. package/src/i18n/locales/settings/en.ts +1 -1
  155. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  156. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  157. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  158. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  159. package/src/lib/__tests__/api-settings.test.ts +299 -0
  160. package/src/lib/__tests__/constants.test.ts +5 -0
  161. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  162. package/src/lib/__tests__/feed.test.ts +168 -0
  163. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  164. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  165. package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
  166. package/src/lib/__tests__/markdown.test.ts +24 -6
  167. package/src/lib/__tests__/navigation.test.ts +59 -2
  168. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  169. package/src/lib/__tests__/post-display.test.ts +64 -0
  170. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  171. package/src/lib/__tests__/slug.test.ts +18 -0
  172. package/src/lib/__tests__/summary.test.ts +82 -0
  173. package/src/lib/__tests__/timeline.test.ts +109 -70
  174. package/src/lib/__tests__/timezones.test.ts +31 -3
  175. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  176. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  177. package/src/lib/__tests__/translit.test.ts +87 -0
  178. package/src/lib/__tests__/url.test.ts +53 -0
  179. package/src/lib/__tests__/view.test.ts +53 -0
  180. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  181. package/src/lib/api-settings.ts +239 -12
  182. package/src/lib/collection-sort.ts +7 -5
  183. package/src/lib/constants.ts +2 -0
  184. package/src/lib/feed-path.ts +25 -0
  185. package/src/lib/feed-policy.ts +64 -0
  186. package/src/lib/feed.ts +113 -11
  187. package/src/lib/footnotes.ts +264 -0
  188. package/src/lib/github-sync-site-config.ts +2 -0
  189. package/src/lib/hugo-markdown.ts +15 -3
  190. package/src/lib/jant-branding.ts +1 -0
  191. package/src/lib/link-preview.ts +25 -0
  192. package/src/lib/markdown-manager.ts +193 -5
  193. package/src/lib/markdown.ts +10 -3
  194. package/src/lib/navigation.ts +16 -3
  195. package/src/lib/post-body-html.ts +131 -0
  196. package/src/lib/post-display.ts +47 -8
  197. package/src/lib/render.tsx +9 -2
  198. package/src/lib/resolve-config.ts +100 -16
  199. package/src/lib/schemas.ts +133 -1
  200. package/src/lib/settings-paths.ts +5 -0
  201. package/src/lib/site-skill.ts +78 -0
  202. package/src/lib/summary.ts +16 -5
  203. package/src/lib/timeline.ts +137 -131
  204. package/src/lib/timezones.ts +54 -14
  205. package/src/lib/tiptap-render.ts +359 -83
  206. package/src/lib/translit.ts +288 -0
  207. package/src/lib/url.ts +123 -12
  208. package/src/lib/view.ts +58 -20
  209. package/src/lib/worker-response-cache.ts +11 -18
  210. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  211. package/src/middleware/config.ts +2 -2
  212. package/src/middleware/public-content-access.ts +87 -0
  213. package/src/node/__tests__/cli-export.test.ts +14 -0
  214. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  215. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  216. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  217. package/src/node/index.ts +2 -1
  218. package/src/preset.css +16 -2
  219. package/src/routes/__tests__/compose.test.ts +51 -0
  220. package/src/routes/__tests__/site-skill.test.ts +112 -0
  221. package/src/routes/api/__tests__/collections.test.ts +91 -19
  222. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  223. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  224. package/src/routes/api/__tests__/palette.test.ts +44 -0
  225. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  226. package/src/routes/api/__tests__/settings.test.ts +217 -0
  227. package/src/routes/api/collections.ts +22 -26
  228. package/src/routes/api/export.ts +2 -0
  229. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  230. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  231. package/src/routes/api/internal/post-body-html.ts +43 -0
  232. package/src/routes/api/internal/sites.ts +24 -0
  233. package/src/routes/api/nav-items.ts +24 -1
  234. package/src/routes/api/posts.ts +14 -10
  235. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  236. package/src/routes/api/public/__tests__/posts.test.ts +246 -0
  237. package/src/routes/api/public/archive.ts +4 -0
  238. package/src/routes/api/public/posts.ts +48 -24
  239. package/src/routes/api/search.ts +3 -0
  240. package/src/routes/api/settings.ts +33 -0
  241. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  242. package/src/routes/compose.tsx +8 -3
  243. package/src/routes/dash/settings.tsx +45 -2
  244. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  245. package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
  246. package/src/routes/feed/feed.ts +23 -14
  247. package/src/routes/pages/__tests__/archive-params.test.ts +384 -3
  248. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  249. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  250. package/src/routes/pages/__tests__/featured.test.ts +6 -2
  251. package/src/routes/pages/__tests__/preview.test.ts +176 -0
  252. package/src/routes/pages/__tests__/search.test.ts +69 -0
  253. package/src/routes/pages/archive.tsx +90 -22
  254. package/src/routes/pages/collection.tsx +32 -19
  255. package/src/routes/pages/collections.tsx +6 -1
  256. package/src/routes/pages/page.tsx +86 -11
  257. package/src/routes/site-skill.ts +18 -0
  258. package/src/services/__tests__/auth.test.ts +7 -5
  259. package/src/services/__tests__/collection.test.ts +190 -44
  260. package/src/services/__tests__/navigation.test.ts +229 -21
  261. package/src/services/__tests__/post-timeline.test.ts +249 -39
  262. package/src/services/__tests__/post.test.ts +844 -5
  263. package/src/services/__tests__/search.test.ts +48 -1
  264. package/src/services/__tests__/settings.test.ts +8 -0
  265. package/src/services/__tests__/site-admin.test.ts +121 -1
  266. package/src/services/auth.ts +2 -2
  267. package/src/services/bootstrap.ts +3 -2
  268. package/src/services/collection.ts +224 -167
  269. package/src/services/export-theme/assets/client-site.css +1 -1
  270. package/src/services/export-theme/assets/client-site.js +46 -25
  271. package/src/services/export-theme/layouts/_default/list.html +18 -4
  272. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  273. package/src/services/export-theme/layouts/featured/list.html +5 -4
  274. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  275. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  276. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  277. package/src/services/export-theme/styles/main.css +30 -2
  278. package/src/services/export.ts +94 -85
  279. package/src/services/mcp.ts +37 -27
  280. package/src/services/media.ts +1 -1
  281. package/src/services/navigation.ts +185 -46
  282. package/src/services/path.ts +4 -1
  283. package/src/services/post.ts +1142 -342
  284. package/src/services/search.ts +17 -2
  285. package/src/services/settings.ts +2 -10
  286. package/src/services/site-admin.ts +63 -43
  287. package/src/style-cjk-jp.css +1 -1
  288. package/src/style-cjk-kr.css +1 -1
  289. package/src/style-cjk-tc.css +1 -1
  290. package/src/style-cjk.css +1 -1
  291. package/src/styles/components.css +540 -14
  292. package/src/styles/site-media.css +111 -19
  293. package/src/styles/tokens.css +76 -10
  294. package/src/styles/ui.css +2227 -1347
  295. package/src/types/bindings.ts +3 -0
  296. package/src/types/config.ts +243 -12
  297. package/src/types/constants.ts +22 -1
  298. package/src/types/entities.ts +14 -8
  299. package/src/types/operations.ts +18 -10
  300. package/src/types/props.ts +20 -2
  301. package/src/types/raw-assets.d.ts +5 -0
  302. package/src/types/views.ts +22 -4
  303. package/src/ui/__tests__/font-themes.test.ts +42 -30
  304. package/src/ui/compose/ComposeDialog.tsx +109 -19
  305. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  306. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  307. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  308. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  309. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  310. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  311. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  312. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  313. package/src/ui/feed/LinkCard.tsx +2 -12
  314. package/src/ui/feed/LinkPreview.tsx +2 -7
  315. package/src/ui/feed/NoteCard.tsx +10 -20
  316. package/src/ui/feed/PostStatusBadges.tsx +42 -3
  317. package/src/ui/feed/QuoteCard.tsx +2 -12
  318. package/src/ui/feed/ThreadPreview.tsx +26 -34
  319. package/src/ui/feed/TimelineFeed.tsx +2 -2
  320. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  321. package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
  322. package/src/ui/feed/thread-preview-state.ts +5 -7
  323. package/src/ui/font-themes.ts +74 -26
  324. package/src/ui/layouts/BaseLayout.tsx +22 -8
  325. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  326. package/src/ui/pages/ArchivePage.tsx +131 -24
  327. package/src/ui/pages/CollectionPage.tsx +101 -22
  328. package/src/ui/pages/CollectionsPage.tsx +3 -1
  329. package/src/ui/pages/PostPage.tsx +39 -12
  330. package/src/ui/pages/SearchPage.tsx +6 -14
  331. package/src/ui/pages/__tests__/ArchivePage.test.tsx +70 -0
  332. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  333. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  334. package/src/ui/shared/CollectionsManager.tsx +17 -7
  335. package/src/ui/shared/DraftPreviewBar.tsx +49 -0
  336. package/src/ui/shared/HomePageBranding.tsx +2 -2
  337. package/src/ui/shared/MediaGallery.tsx +1 -0
  338. package/src/ui/shared/PostFooter.tsx +38 -13
  339. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  340. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  341. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  342. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  343. package/src/ui/shared/collection-management-labels.ts +33 -2
  344. package/src/ui/shared/custom-icons.ts +5 -0
  345. package/src/ui/shared/navigation-labels.ts +2 -4
  346. package/src/ui/shared/post-article-attributes.ts +52 -0
  347. package/dist/app-DZDlmh7C.js +0 -6
  348. package/dist/client/_assets/client-Cevfpm8H.css +0 -2
  349. package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
  350. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
@@ -327,7 +327,7 @@ describe("JantPostMenu", () => {
327
327
  }
328
328
 
329
329
  if (
330
- url === "/api/collections/collection-1/posts/post-1" &&
330
+ url === "/api/collections/collection-1/threads/post-1" &&
331
331
  method === "DELETE"
332
332
  ) {
333
333
  selectedIds.splice(0, 1);
@@ -397,7 +397,7 @@ describe("JantPostMenu", () => {
397
397
  keydown(requireElement(options[0] ?? null, "expected first option"), " ");
398
398
  await Promise.resolve();
399
399
  await menu.updateComplete;
400
- expect(menu._postCollectionIds).toEqual([]);
400
+ expect(menu._threadCollectionIds).toEqual([]);
401
401
 
402
402
  options = Array.from(
403
403
  menu.querySelectorAll<HTMLButtonElement>(".post-menu-picker-option"),
@@ -412,6 +412,48 @@ describe("JantPostMenu", () => {
412
412
  expect(document.activeElement).toBe(searchInput);
413
413
  });
414
414
 
415
+ it("derives Child action scope from Thread identity", async () => {
416
+ const { menu, trigger } = await createMenu();
417
+ const article = requireElement(
418
+ trigger.closest<HTMLElement>("article[data-post]"),
419
+ "expected post article",
420
+ );
421
+ article.dataset.postId = "reply-1";
422
+ article.dataset.threadRootId = "post-1";
423
+ document.body.insertAdjacentHTML(
424
+ "afterbegin",
425
+ '<div data-collection-id="collection-1"></div>',
426
+ );
427
+
428
+ click(trigger);
429
+ await menu.updateComplete;
430
+
431
+ expect(menu.textContent).toContain("Edit");
432
+ expect(menu.textContent).toContain("Add to Featured");
433
+ expect(menu.textContent).toContain("Delete");
434
+ expect(menu.querySelector("[data-post-menu-open-collections]")).toBeNull();
435
+ expect(menu.querySelector("[data-post-menu-open-visibility]")).toBeNull();
436
+ expect(menu.textContent).not.toContain("Pin this post");
437
+ expect(menu.textContent).not.toContain("Pin in collection");
438
+ });
439
+
440
+ it("blocks the collection shortcut for a Child without a reply marker", async () => {
441
+ const fetchMock = vi.mocked(globalThis.fetch);
442
+ const { menu, trigger } = await createMenu();
443
+ const article = requireElement(
444
+ trigger.closest<HTMLElement>("article[data-post]"),
445
+ "expected post article",
446
+ );
447
+ article.dataset.postId = "reply-1";
448
+ article.dataset.threadRootId = "post-1";
449
+
450
+ menu.openCollectionsForPost(article);
451
+ await menu.updateComplete;
452
+
453
+ expect(menu.textContent?.trim()).toBe("");
454
+ expect(fetchMock).not.toHaveBeenCalled();
455
+ });
456
+
415
457
  it("closes the collection picker with Enter without toggling the focused option", async () => {
416
458
  const selectedIds = ["collection-1"];
417
459
  const fetchMock = vi.fn(
@@ -466,7 +508,7 @@ describe("JantPostMenu", () => {
466
508
  keydown(firstOption, "Enter");
467
509
  await menu.updateComplete;
468
510
 
469
- expect(menu._postCollectionIds).toEqual(["collection-1"]);
511
+ expect(menu._threadCollectionIds).toEqual(["collection-1"]);
470
512
  expect(menu.textContent?.trim()).toBe("");
471
513
  expect(trigger.getAttribute("aria-expanded")).toBe("false");
472
514
  });
@@ -564,7 +606,7 @@ describe("JantPostMenu", () => {
564
606
  }
565
607
 
566
608
  if (
567
- url === "/api/collections/collection-3/posts" &&
609
+ url === "/api/collections/collection-3/threads" &&
568
610
  method === "POST"
569
611
  ) {
570
612
  selectedIds.push("collection-3");
@@ -572,7 +614,7 @@ describe("JantPostMenu", () => {
572
614
  }
573
615
 
574
616
  if (
575
- url === "/api/collections/collection-2/posts/post-1" &&
617
+ url === "/api/collections/collection-2/threads/post-1" &&
576
618
  method === "DELETE"
577
619
  ) {
578
620
  const index = selectedIds.indexOf("collection-2");
@@ -51,9 +51,9 @@ const labels: SettingsLabels = {
51
51
  contentLanguagePreview: "Readers and search engines see",
52
52
  dashboardLanguage: "Dashboard language",
53
53
  dashboardLanguageHelp: "The language this admin dashboard shows in.",
54
- cjkFont: "CJK Font",
54
+ cjkFont: "CJK fallback",
55
55
  cjkFontHelp:
56
- "Load a serif font optimized for Chinese, Japanese, or Korean content.",
56
+ "Used when the content language has no built-in font profile. Your font theme still controls serif and sans styling.",
57
57
  timeZone: "Time Zone",
58
58
  feeds: "Feeds",
59
59
  mainRssFeed: "Main RSS feed",
@@ -96,9 +96,9 @@ const labels: SettingsLabels = {
96
96
  contentLanguagePreview: "Readers and search engines see",
97
97
  dashboardLanguage: "Dashboard language",
98
98
  dashboardLanguageHelp: "The language this admin dashboard shows in.",
99
- cjkFont: "CJK Font",
99
+ cjkFont: "CJK fallback",
100
100
  cjkFontHelp:
101
- "Load a serif font optimized for Chinese, Japanese, or Korean content.",
101
+ "Used when the content language has no built-in font profile. Your font theme still controls serif and sans styling.",
102
102
  timeZone: "Time Zone",
103
103
  feeds: "Feeds",
104
104
  mainRssFeed: "Main RSS feed",
@@ -137,7 +137,7 @@ const timezones: SettingsTimezone[] = [
137
137
  ];
138
138
 
139
139
  const cjkFonts: SettingsCjkFont[] = [
140
- { value: "off", label: "None" },
140
+ { value: "off", label: "Follow content language" },
141
141
  { value: "zh-Hans", label: "简体中文 (Simplified Chinese)" },
142
142
  ];
143
143
 
@@ -17,6 +17,12 @@ export interface CollectionManagerLabels {
17
17
  dividerLabelPlaceholder: string;
18
18
  newCollection: string;
19
19
  edit: string;
20
+ addToNavigation: string;
21
+ addingToNavigation: string;
22
+ addedToNavigation: string;
23
+ editNavigation: string;
24
+ addToNavigationFailed: string;
25
+ notNow: string;
20
26
  label: string;
21
27
  url: string;
22
28
  linkLabelPlaceholder: string;
@@ -31,8 +37,8 @@ export interface CollectionManagerLabels {
31
37
  deleteLink: string;
32
38
  confirmDeleteLink: string;
33
39
  cancel: string;
34
- entrySingular: string;
35
- entryPlural: string;
40
+ threadSingular: string;
41
+ threadPlural: string;
36
42
  emptyState: string;
37
43
  orderSaved: string;
38
44
  saved: string;
@@ -50,7 +56,7 @@ export interface ManagedCollection {
50
56
  title: string;
51
57
  description: string | null;
52
58
  sortOrder: string;
53
- postCount: number;
59
+ threadCount: number;
54
60
  recentActivityAt: number;
55
61
  }
56
62
 
@@ -249,7 +249,11 @@ export function convertComposeFormat(
249
249
  if (prepend.length) content.unshift(...prepend);
250
250
 
251
251
  out.bodyJson = content.length ? { type: "doc", content } : null;
252
- out.showTitle = to === "note" && out.title.trim().length > 0;
252
+ // A title carried in from another format has nowhere to show unless the note's
253
+ // title field is open, so opening it is the only way not to drop it. The
254
+ // toggle is otherwise left alone, so note → link → note keeps the user's
255
+ // choice instead of resetting it.
256
+ if (to === "note" && out.title.trim().length > 0) out.showTitle = true;
253
257
 
254
258
  return out;
255
259
  }
@@ -7,9 +7,22 @@
7
7
 
8
8
  import type { JSONContent } from "@tiptap/core";
9
9
  import type { CollectionFormLabels } from "./collection-types.js";
10
+ import type { TableControlLabels } from "../tiptap/table-control-labels.js";
10
11
 
11
12
  export type ComposeFormat = "note" | "link" | "quote";
12
13
 
14
+ /**
15
+ * What one composer row says about itself. An editor owns its fields, so it
16
+ * owns these two answers too — the dialog reduces them over the rows it has
17
+ * instead of reading editors back out of the DOM while it renders.
18
+ */
19
+ export interface ComposeRowStatus {
20
+ /** Anything worth keeping: the check behind "discard this?" and autosave. */
21
+ hasContent: boolean;
22
+ /** Enough to publish this row: its format's requirements met, fields valid. */
23
+ publishable: boolean;
24
+ }
25
+
13
26
  export interface ComposeAttachment {
14
27
  clientId: string;
15
28
  file: File;
@@ -24,6 +37,12 @@ export interface ComposeAttachment {
24
37
  status: "pending" | "processing" | "uploading" | "done" | "error";
25
38
  progress: number | null;
26
39
  mediaId: string | null;
40
+ /**
41
+ * Server URL of the uploaded file, set when the upload completes. Unlike
42
+ * `previewUrl` (a blob URL revoked on editor reset) it stays valid, so
43
+ * draft restore can rehydrate the attachment after the dialog closed.
44
+ */
45
+ remoteUrl: string | null;
27
46
  alt: string;
28
47
  error: string | null;
29
48
  /** Text content preview for text files (first ~100 chars) */
@@ -46,6 +65,7 @@ export interface AttachedTextItem {
46
65
 
47
66
  export interface DraftItem {
48
67
  id: string;
68
+ slug: string;
49
69
  format: ComposeFormat;
50
70
  title: string | null;
51
71
  bodyText: string | null;
@@ -62,6 +82,23 @@ export interface DraftItem {
62
82
  }[];
63
83
  }
64
84
 
85
+ /**
86
+ * Snapshot of a media attachment whose upload completed. Stored in the local
87
+ * draft (and passed by the bridge on failure restore) so uploaded images
88
+ * survive a failed publish or a page reload — the bytes are already on the
89
+ * server, only the reference needs restoring.
90
+ */
91
+ export interface LocalDraftMedia {
92
+ clientId: string;
93
+ mediaId: string;
94
+ url: string;
95
+ mimeType: string;
96
+ name?: string;
97
+ alt?: string;
98
+ summary?: string | null;
99
+ chars?: number | null;
100
+ }
101
+
65
102
  export interface LocalDraft {
66
103
  format: ComposeFormat;
67
104
  title: string;
@@ -85,9 +122,15 @@ export interface LocalDraft {
85
122
  summary: string;
86
123
  }>;
87
124
  attachmentOrder?: string[];
125
+ mediaAttachments?: LocalDraftMedia[];
88
126
  /** Present when the draft is a multi-post thread */
89
127
  threadItems?: Array<{
90
128
  format: ComposeFormat;
129
+ /** Per-post publish date; empty means "follows the thread". */
130
+ publishedAtInput?: string;
131
+ publishedAtTimeMinutes?: number | null;
132
+ /** Per-post permalink; empty means the server assigns a random id. */
133
+ slug?: string;
91
134
  title: string;
92
135
  bodyJson: JSONContent | null;
93
136
  url: string;
@@ -100,6 +143,7 @@ export interface LocalDraft {
100
143
  summary: string;
101
144
  }>;
102
145
  attachmentOrder?: string[];
146
+ mediaAttachments?: LocalDraftMedia[];
103
147
  }>;
104
148
  savedAt: number;
105
149
  }
@@ -180,6 +224,8 @@ export interface ComposeLabels {
180
224
  discardChangesConfirm: string;
181
225
  drafts: string;
182
226
  draftsEmpty: string;
227
+ previewDraft: string;
228
+ draftActions: string;
183
229
  deleteDraft: string;
184
230
  draftDeleted: string;
185
231
  publishFailedDraft: string;
@@ -225,10 +271,11 @@ export interface ComposeLabels {
225
271
  threadLimitReached: string;
226
272
  showMore: string;
227
273
  showLess: string;
228
- newThread: string;
229
- replyTitle: string;
230
- editTitle: string;
274
+ closeCompose: string;
275
+ editing: string;
276
+ composeDialogLabel: string;
231
277
  slashHint: string;
278
+ tableControls: TableControlLabels;
232
279
  collectionFormLabels: CollectionFormLabels;
233
280
  }
234
281
 
@@ -250,6 +297,7 @@ export interface ComposeFullscreenOpenDetail {
250
297
  selection?: ComposeEditorSelection | null;
251
298
  labels?: ComposeLabels;
252
299
  replyContext?: ComposeFullscreenReplyContext | null;
300
+ editorIndex?: number;
253
301
  }
254
302
 
255
303
  export interface ComposeFullscreenCloseDetail {
@@ -258,6 +306,8 @@ export interface ComposeFullscreenCloseDetail {
258
306
  showTitle: boolean;
259
307
  selection?: ComposeEditorSelection | null;
260
308
  replyExpanded: boolean;
309
+ intent?: "publish";
310
+ editorIndex?: number;
261
311
  }
262
312
 
263
313
  export type ComposeVisibility = "public" | "latest_hidden" | "private";
@@ -293,6 +343,8 @@ export interface ComposeSubmitDetail {
293
343
  collectionIds: string[];
294
344
  attachments: ComposeSubmitAttachment[];
295
345
  editPostId?: string;
346
+ /** Identifies an existing server draft loaded through the drafts workflow. */
347
+ draftSourceId?: string;
296
348
  replyToId?: string;
297
349
  quietReply?: boolean;
298
350
  replyThreadRootId?: string;
@@ -27,13 +27,16 @@ import {
27
27
  } from "../sortable-list.js";
28
28
  import { showConfirmDialog } from "../confirm.js";
29
29
  import { publicPath } from "../runtime-paths.js";
30
- import { showToast } from "../toast.js";
30
+ import { showToast, showToastWithAction } from "../toast.js";
31
+ import { addCollectionToNavigation } from "../collection-navigation.js";
32
+ import { consumeCollectionCreatedNotice } from "../collection-created-notice.js";
31
33
  import { getDividerCollectionGroup } from "../../lib/collection-groups.js";
32
34
  import {
33
35
  getCollectionEditPath,
34
36
  getCollectionSelectionPath,
35
37
  getCollectionsDirectoryPath,
36
38
  } from "../../lib/collection-paths.js";
39
+ import { NAVIGATION_SETTINGS_PATH } from "../../lib/settings-paths.js";
37
40
  import { render as renderMarkdown } from "../../lib/markdown.js";
38
41
  import { formatRelativeAge, toISOString } from "../../lib/time.js";
39
42
  import {
@@ -53,7 +56,7 @@ interface CollectionsResponse {
53
56
  title: string;
54
57
  description: string | null;
55
58
  sortOrder: string;
56
- postCount: number;
59
+ threadCount: number;
57
60
  recentActivityAt: number;
58
61
  }>;
59
62
  directoryItems?: Array<{
@@ -77,6 +80,10 @@ export class JantCollectionsManager extends LitElement {
77
80
  static properties = {
78
81
  items: { type: Array },
79
82
  labels: { type: Object },
83
+ navigationCollectionIds: {
84
+ type: Array,
85
+ attribute: "navigation-collection-ids",
86
+ },
80
87
 
81
88
  _items: { state: true },
82
89
  _reorderMode: { state: true },
@@ -91,12 +98,14 @@ export class JantCollectionsManager extends LitElement {
91
98
  _newLinkUrl: { state: true },
92
99
  _newLinkDescription: { state: true },
93
100
  _addingLink: { state: true },
94
- _hoveringId: { state: true },
95
101
  _showItemMenuId: { state: true },
102
+ _addingToNavigationId: { state: true },
103
+ _createdCollectionId: { state: true },
96
104
  };
97
105
 
98
106
  declare items: CollectionManagerItem[];
99
107
  declare labels: CollectionManagerLabels;
108
+ declare navigationCollectionIds: string[];
100
109
 
101
110
  declare _items: CollectionManagerItem[];
102
111
  declare _reorderMode: boolean;
@@ -111,8 +120,9 @@ export class JantCollectionsManager extends LitElement {
111
120
  declare _newLinkUrl: string;
112
121
  declare _newLinkDescription: string;
113
122
  declare _addingLink: boolean;
114
- declare _hoveringId: string | null;
115
123
  declare _showItemMenuId: string | null;
124
+ declare _addingToNavigationId: string | null;
125
+ declare _createdCollectionId: string | null;
116
126
 
117
127
  #sortable: { destroy(): void } | null = null;
118
128
  #initialized = false;
@@ -189,12 +199,17 @@ export class JantCollectionsManager extends LitElement {
189
199
  connectedCallback() {
190
200
  super.connectedCallback();
191
201
  this.#bindManagerRoot();
202
+ const createdCollectionId = consumeCollectionCreatedNotice();
203
+ if (createdCollectionId) {
204
+ this._createdCollectionId = createdCollectionId;
205
+ }
192
206
  }
193
207
 
194
208
  constructor() {
195
209
  super();
196
210
  this.items = [];
197
211
  this.labels = {} as CollectionManagerLabels;
212
+ this.navigationCollectionIds = [];
198
213
 
199
214
  this._items = [];
200
215
  this._reorderMode = false;
@@ -209,8 +224,9 @@ export class JantCollectionsManager extends LitElement {
209
224
  this._newLinkUrl = "";
210
225
  this._newLinkDescription = "";
211
226
  this._addingLink = false;
212
- this._hoveringId = null;
213
227
  this._showItemMenuId = null;
228
+ this._addingToNavigationId = null;
229
+ this._createdCollectionId = null;
214
230
  }
215
231
 
216
232
  protected update(
@@ -247,7 +263,7 @@ export class JantCollectionsManager extends LitElement {
247
263
 
248
264
  #countLabel(count: number) {
249
265
  return `${count} ${
250
- count === 1 ? this.labels.entrySingular : this.labels.entryPlural
266
+ count === 1 ? this.labels.threadSingular : this.labels.threadPlural
251
267
  }`;
252
268
  }
253
269
 
@@ -323,7 +339,7 @@ export class JantCollectionsManager extends LitElement {
323
339
  title: collection.title,
324
340
  description: collection.description,
325
341
  sortOrder: collection.sortOrder,
326
- postCount: collection.postCount ?? 0,
342
+ threadCount: collection.threadCount ?? 0,
327
343
  recentActivityAt: collection.recentActivityAt,
328
344
  });
329
345
  }
@@ -666,6 +682,32 @@ export class JantCollectionsManager extends LitElement {
666
682
  }
667
683
  }
668
684
 
685
+ async #addCollectionToNavigation(collectionId: string) {
686
+ if (!collectionId || this._addingToNavigationId) return;
687
+
688
+ const usesInlineNotice = this._createdCollectionId === collectionId;
689
+ this._showItemMenuId = null;
690
+ this._addingToNavigationId = collectionId;
691
+ document.removeEventListener("click", this.#closeItemMenu);
692
+
693
+ try {
694
+ await addCollectionToNavigation(collectionId);
695
+ this.navigationCollectionIds = [
696
+ ...new Set([...this.navigationCollectionIds, collectionId]),
697
+ ];
698
+ if (!usesInlineNotice) {
699
+ showToastWithAction(this.labels.addedToNavigation, {
700
+ label: this.labels.editNavigation,
701
+ href: publicPath(NAVIGATION_SETTINGS_PATH),
702
+ });
703
+ }
704
+ } catch {
705
+ showToast(this.labels.addToNavigationFailed, "error");
706
+ } finally {
707
+ this._addingToNavigationId = null;
708
+ }
709
+ }
710
+
669
711
  #toggleLinkEdit(item: CollectionManagerItem) {
670
712
  if (item.type !== "link") return;
671
713
 
@@ -839,13 +881,17 @@ export class JantCollectionsManager extends LitElement {
839
881
  ${collection.description
840
882
  ? html`
841
883
  <div class="collection-directory-description prose">
842
- ${unsafeHTML(renderMarkdown(collection.description))}
884
+ ${unsafeHTML(
885
+ renderMarkdown(collection.description, {
886
+ namespace: collection.id,
887
+ }),
888
+ )}
843
889
  </div>
844
890
  `
845
891
  : nothing}
846
892
  <p class="collection-directory-summary">
847
893
  <span class="collection-directory-meta"
848
- >${this.#countLabel(collection.postCount)}</span
894
+ >${this.#countLabel(collection.threadCount)}</span
849
895
  >
850
896
  <span class="collection-directory-meta-separator" aria-hidden="true"
851
897
  >/</span
@@ -895,24 +941,17 @@ export class JantCollectionsManager extends LitElement {
895
941
  <div
896
942
  class=${classMap({
897
943
  "group relative": true,
944
+ "collection-directory-managed-row": true,
898
945
  "z-50":
899
946
  this._showItemMenuId === item.id || this._editingLinkId === item.id,
900
947
  })}
901
- @mouseenter=${() => {
902
- this._hoveringId = item.id;
903
- }}
904
- @mouseleave=${() => {
905
- if (this._hoveringId === item.id) this._hoveringId = null;
906
- }}
907
948
  >
908
949
  <div
909
950
  class="collection-directory-item collection-directory-item-manageable"
910
951
  >
911
952
  ${body}
912
953
  </div>
913
- ${this._hoveringId === item.id || this._showItemMenuId === item.id
914
- ? this.#renderItemMenu(item)
915
- : nothing}
954
+ ${this.#renderItemMenu(item)}
916
955
  </div>
917
956
  `;
918
957
  }
@@ -1036,7 +1075,9 @@ export class JantCollectionsManager extends LitElement {
1036
1075
  ${item.description
1037
1076
  ? html`
1038
1077
  <div class="collection-directory-description prose">
1039
- ${unsafeHTML(renderMarkdown(item.description))}
1078
+ ${unsafeHTML(
1079
+ renderMarkdown(item.description, { namespace: item.id }),
1080
+ )}
1040
1081
  </div>
1041
1082
  `
1042
1083
  : html`
@@ -1084,25 +1125,17 @@ export class JantCollectionsManager extends LitElement {
1084
1125
  <div
1085
1126
  class=${classMap({
1086
1127
  "group relative": true,
1128
+ "collection-directory-managed-row": true,
1087
1129
  "z-50":
1088
1130
  this._showItemMenuId === item.id || this._editingLinkId === item.id,
1089
1131
  })}
1090
- @mouseenter=${() => {
1091
- this._hoveringId = item.id;
1092
- }}
1093
- @mouseleave=${() => {
1094
- if (this._hoveringId === item.id) this._hoveringId = null;
1095
- }}
1096
1132
  >
1097
1133
  <div
1098
1134
  class="collection-directory-item collection-directory-item-link collection-directory-item-manageable"
1099
1135
  >
1100
1136
  ${body}
1101
1137
  </div>
1102
- ${this._hoveringId === item.id || this._showItemMenuId === item.id
1103
- ? this.#renderItemMenu(item)
1104
- : nothing}
1105
- ${this.#renderLinkEditPanel(item)}
1138
+ ${this.#renderItemMenu(item)} ${this.#renderLinkEditPanel(item)}
1106
1139
  </div>
1107
1140
  `;
1108
1141
  }
@@ -1166,6 +1199,31 @@ export class JantCollectionsManager extends LitElement {
1166
1199
  >
1167
1200
  ${this.labels.edit}
1168
1201
  </a>
1202
+ ${this.navigationCollectionIds.includes(collection.id)
1203
+ ? html`
1204
+ <a
1205
+ href=${publicPath(NAVIGATION_SETTINGS_PATH)}
1206
+ class="collections-page-menu-item"
1207
+ >
1208
+ ${this.labels.editNavigation}
1209
+ </a>
1210
+ `
1211
+ : html`
1212
+ <button
1213
+ type="button"
1214
+ class="collections-page-menu-item"
1215
+ ?disabled=${this._addingToNavigationId ===
1216
+ collection.id}
1217
+ @click=${() =>
1218
+ void this.#addCollectionToNavigation(
1219
+ collection.id,
1220
+ )}
1221
+ >
1222
+ ${this._addingToNavigationId === collection.id
1223
+ ? this.labels.addingToNavigation
1224
+ : this.labels.addToNavigation}
1225
+ </button>
1226
+ `}
1169
1227
  <button
1170
1228
  type="button"
1171
1229
  class="collections-page-menu-item collections-page-menu-item-danger"
@@ -1398,9 +1456,86 @@ export class JantCollectionsManager extends LitElement {
1398
1456
  `;
1399
1457
  }
1400
1458
 
1459
+ #renderCreatedCollectionNotice() {
1460
+ if (!this._createdCollectionId) return nothing;
1461
+
1462
+ const collection = this._items.find(
1463
+ (item) => item.collection?.id === this._createdCollectionId,
1464
+ )?.collection;
1465
+ if (!collection) return nothing;
1466
+
1467
+ const isInNavigation = this.navigationCollectionIds.includes(collection.id);
1468
+
1469
+ return html`
1470
+ <section
1471
+ class="collection-created-notice"
1472
+ role="status"
1473
+ aria-labelledby="collection-created-notice-title"
1474
+ >
1475
+ <p
1476
+ id="collection-created-notice-title"
1477
+ class="collection-created-notice-message"
1478
+ >
1479
+ ${isInNavigation
1480
+ ? this.labels.addedToNavigation
1481
+ : this.labels.formLabels.createdLabel}
1482
+ </p>
1483
+ <div class="collection-created-notice-actions">
1484
+ ${isInNavigation
1485
+ ? html`
1486
+ <a
1487
+ href=${publicPath(NAVIGATION_SETTINGS_PATH)}
1488
+ class="collection-created-notice-action collection-created-notice-action-primary"
1489
+ >
1490
+ ${this.labels.editNavigation}
1491
+ </a>
1492
+ `
1493
+ : html`
1494
+ <button
1495
+ type="button"
1496
+ class="collection-created-notice-action collection-created-notice-action-primary"
1497
+ ?disabled=${this._addingToNavigationId === collection.id}
1498
+ @click=${() =>
1499
+ void this.#addCollectionToNavigation(collection.id)}
1500
+ >
1501
+ ${this._addingToNavigationId === collection.id
1502
+ ? this.labels.addingToNavigation
1503
+ : this.labels.addToNavigation}
1504
+ </button>
1505
+ `}
1506
+ <button
1507
+ type="button"
1508
+ class="collection-created-notice-dismiss"
1509
+ aria-label=${this.labels.notNow}
1510
+ title=${this.labels.notNow}
1511
+ @click=${() => {
1512
+ this._createdCollectionId = null;
1513
+ }}
1514
+ >
1515
+ <svg
1516
+ xmlns="http://www.w3.org/2000/svg"
1517
+ width="14"
1518
+ height="14"
1519
+ viewBox="0 0 24 24"
1520
+ fill="none"
1521
+ stroke="currentColor"
1522
+ stroke-width="2"
1523
+ stroke-linecap="round"
1524
+ stroke-linejoin="round"
1525
+ aria-hidden="true"
1526
+ >
1527
+ <path d="M18 6 6 18" />
1528
+ <path d="m6 6 12 12" />
1529
+ </svg>
1530
+ </button>
1531
+ </div>
1532
+ </section>
1533
+ `;
1534
+ }
1535
+
1401
1536
  render() {
1402
1537
  return html`
1403
- ${this.#renderCreateLinkForm()}
1538
+ ${this.#renderCreatedCollectionNotice()} ${this.#renderCreateLinkForm()}
1404
1539
  ${this.#hasDirectoryContent()
1405
1540
  ? html`
1406
1541
  <div id="collections-manager-list" class="collection-directory">