@jant/core 0.6.14 → 0.6.15

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 (311) hide show
  1. package/bin/commands/import-site.js +81 -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-BxCOR3Uc.js +6 -0
  12. package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
  13. package/dist/client/.vite/manifest.json +3 -3
  14. package/dist/client/_assets/client-B_eGKN5p.css +2 -0
  15. package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
  16. package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
  17. package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
  18. package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
  19. package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
  20. package/dist/index.js +3 -3
  21. package/dist/node.js +5 -5
  22. package/package.json +11 -10
  23. package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
  24. package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
  25. package/src/__tests__/export-hugo-build.test.ts +63 -4
  26. package/src/__tests__/export-import-roundtrip.test.ts +6 -4
  27. package/src/__tests__/export-service.test.ts +142 -9
  28. package/src/__tests__/helpers/app.ts +3 -0
  29. package/src/__tests__/helpers/export-fixtures.ts +2 -0
  30. package/src/__tests__/import-site-command.test.ts +65 -0
  31. package/src/__tests__/vite-config.test.ts +45 -0
  32. package/src/app.tsx +16 -3
  33. package/src/client/__tests__/collection-created-notice.test.ts +36 -0
  34. package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
  35. package/src/client/__tests__/collection-page-actions.test.ts +71 -2
  36. package/src/client/__tests__/compose-bridge.test.ts +64 -0
  37. package/src/client/__tests__/feed-video-player.test.ts +16 -1
  38. package/src/client/__tests__/footnote-rail.test.ts +472 -0
  39. package/src/client/__tests__/hydrate-partial.test.ts +3 -0
  40. package/src/client/__tests__/toast.test.ts +18 -1
  41. package/src/client/collection-created-notice.ts +66 -0
  42. package/src/client/collection-form-bridge.ts +17 -0
  43. package/src/client/collection-navigation.ts +44 -0
  44. package/src/client/collection-page-actions.ts +57 -1
  45. package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
  46. package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
  47. package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
  48. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
  49. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  50. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  51. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  52. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  53. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  54. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  55. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  56. package/src/client/components/collection-manager-types.ts +9 -3
  57. package/src/client/components/compose-types.ts +8 -0
  58. package/src/client/components/jant-collection-directory.ts +165 -30
  59. package/src/client/components/jant-command-palette.ts +2 -1
  60. package/src/client/components/jant-compose-dialog.ts +85 -21
  61. package/src/client/components/jant-compose-editor.ts +13 -7
  62. package/src/client/components/jant-compose-fullscreen.ts +24 -4
  63. package/src/client/components/jant-config-editor.ts +722 -0
  64. package/src/client/components/jant-media-lightbox.ts +100 -18
  65. package/src/client/components/jant-nav-manager.ts +1283 -54
  66. package/src/client/components/jant-post-form.ts +3 -3
  67. package/src/client/components/jant-post-menu.ts +86 -74
  68. package/src/client/components/nav-manager-types.ts +51 -2
  69. package/src/client/components/post-form-types.ts +1 -1
  70. package/src/client/compose-bridge.ts +4 -1
  71. package/src/client/feed-video-player.ts +62 -2
  72. package/src/client/footnote-rail.ts +478 -0
  73. package/src/client/hydrate-partial.ts +2 -0
  74. package/src/client/media-lightbox-events.ts +63 -0
  75. package/src/client/site-header-fragment.ts +38 -0
  76. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  77. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  78. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  79. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
  80. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  81. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  82. package/src/client/tiptap/bubble-menu.ts +88 -10
  83. package/src/client/tiptap/continuous-lists.ts +102 -0
  84. package/src/client/tiptap/create-editor.ts +3 -0
  85. package/src/client/tiptap/extensions.ts +14 -5
  86. package/src/client/tiptap/markdown-clipboard.ts +608 -11
  87. package/src/client/tiptap/slash-commands.ts +8 -1
  88. package/src/client/tiptap/structural-keymap.ts +211 -0
  89. package/src/client/tiptap/tab-indent.ts +8 -22
  90. package/src/client/tiptap/table-control-labels.ts +30 -0
  91. package/src/client/tiptap/table-controls.ts +537 -0
  92. package/src/client/tiptap/table-size-picker.ts +254 -0
  93. package/src/client/toast.ts +8 -6
  94. package/src/client-auth.ts +1 -0
  95. package/src/client.ts +1 -0
  96. package/src/db/__tests__/d1-query.test.ts +43 -1
  97. package/src/db/__tests__/migration-runner.test.ts +63 -0
  98. package/src/db/__tests__/migrations.test.ts +469 -0
  99. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  100. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  101. package/src/db/migrations/0028_superb_post.sql +2 -0
  102. package/src/db/migrations/0029_boring_magma.sql +1 -0
  103. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  104. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  105. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  106. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  107. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  108. package/src/db/migrations/meta/_journal.json +28 -0
  109. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  110. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  111. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  112. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  113. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  114. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  115. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  116. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  117. package/src/db/migrations/pg/meta/_journal.json +28 -0
  118. package/src/db/pg/schema.ts +28 -20
  119. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  120. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  121. package/src/db/schema.ts +28 -20
  122. package/src/i18n/__tests__/detect.test.ts +8 -2
  123. package/src/i18n/detect.ts +14 -5
  124. package/src/i18n/locales/public/en.po +133 -30
  125. package/src/i18n/locales/public/en.ts +1 -1
  126. package/src/i18n/locales/public/zh-Hans.po +128 -25
  127. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  128. package/src/i18n/locales/public/zh-Hant.po +128 -25
  129. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  130. package/src/i18n/locales/settings/en.po +513 -10
  131. package/src/i18n/locales/settings/en.ts +1 -1
  132. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  133. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  134. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  135. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  136. package/src/lib/__tests__/api-settings.test.ts +299 -0
  137. package/src/lib/__tests__/constants.test.ts +5 -0
  138. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  139. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  140. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  141. package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
  142. package/src/lib/__tests__/markdown.test.ts +8 -6
  143. package/src/lib/__tests__/navigation.test.ts +59 -2
  144. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  145. package/src/lib/__tests__/post-display.test.ts +64 -0
  146. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  147. package/src/lib/__tests__/summary.test.ts +82 -0
  148. package/src/lib/__tests__/timeline.test.ts +109 -70
  149. package/src/lib/__tests__/timezones.test.ts +31 -3
  150. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  151. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  152. package/src/lib/__tests__/view.test.ts +53 -0
  153. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  154. package/src/lib/api-settings.ts +239 -12
  155. package/src/lib/collection-sort.ts +7 -5
  156. package/src/lib/constants.ts +2 -0
  157. package/src/lib/feed-path.ts +25 -0
  158. package/src/lib/feed-policy.ts +64 -0
  159. package/src/lib/footnotes.ts +264 -0
  160. package/src/lib/github-sync-site-config.ts +2 -0
  161. package/src/lib/hugo-markdown.ts +10 -3
  162. package/src/lib/jant-branding.ts +1 -0
  163. package/src/lib/markdown-manager.ts +188 -5
  164. package/src/lib/markdown.ts +10 -3
  165. package/src/lib/navigation.ts +16 -3
  166. package/src/lib/post-body-html.ts +131 -0
  167. package/src/lib/post-display.ts +7 -1
  168. package/src/lib/render.tsx +9 -2
  169. package/src/lib/resolve-config.ts +100 -16
  170. package/src/lib/schemas.ts +133 -1
  171. package/src/lib/settings-paths.ts +5 -0
  172. package/src/lib/site-skill.ts +78 -0
  173. package/src/lib/summary.ts +16 -5
  174. package/src/lib/timeline.ts +98 -122
  175. package/src/lib/timezones.ts +54 -14
  176. package/src/lib/tiptap-render.ts +355 -81
  177. package/src/lib/view.ts +30 -20
  178. package/src/lib/worker-response-cache.ts +11 -18
  179. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  180. package/src/middleware/config.ts +2 -2
  181. package/src/middleware/public-content-access.ts +87 -0
  182. package/src/node/__tests__/cli-export.test.ts +14 -0
  183. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  184. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  185. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  186. package/src/node/index.ts +2 -1
  187. package/src/preset.css +16 -2
  188. package/src/routes/__tests__/compose.test.ts +51 -0
  189. package/src/routes/__tests__/site-skill.test.ts +112 -0
  190. package/src/routes/api/__tests__/collections.test.ts +91 -19
  191. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  192. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  193. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  194. package/src/routes/api/__tests__/settings.test.ts +217 -0
  195. package/src/routes/api/collections.ts +22 -26
  196. package/src/routes/api/export.ts +2 -0
  197. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  198. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  199. package/src/routes/api/internal/post-body-html.ts +43 -0
  200. package/src/routes/api/internal/sites.ts +24 -0
  201. package/src/routes/api/nav-items.ts +24 -1
  202. package/src/routes/api/posts.ts +2 -2
  203. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  204. package/src/routes/api/public/__tests__/posts.test.ts +208 -0
  205. package/src/routes/api/public/archive.ts +4 -0
  206. package/src/routes/api/public/posts.ts +41 -24
  207. package/src/routes/api/search.ts +3 -0
  208. package/src/routes/api/settings.ts +33 -0
  209. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  210. package/src/routes/compose.tsx +2 -1
  211. package/src/routes/dash/settings.tsx +45 -2
  212. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  213. package/src/routes/feed/feed.ts +23 -14
  214. package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
  215. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  216. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  217. package/src/routes/pages/__tests__/preview.test.ts +174 -0
  218. package/src/routes/pages/__tests__/search.test.ts +69 -0
  219. package/src/routes/pages/archive.tsx +27 -7
  220. package/src/routes/pages/collection.tsx +32 -19
  221. package/src/routes/pages/collections.tsx +6 -1
  222. package/src/routes/pages/page.tsx +83 -11
  223. package/src/routes/site-skill.ts +18 -0
  224. package/src/services/__tests__/auth.test.ts +7 -5
  225. package/src/services/__tests__/collection.test.ts +159 -44
  226. package/src/services/__tests__/navigation.test.ts +229 -21
  227. package/src/services/__tests__/post-timeline.test.ts +109 -30
  228. package/src/services/__tests__/post.test.ts +567 -5
  229. package/src/services/__tests__/search.test.ts +48 -1
  230. package/src/services/__tests__/settings.test.ts +8 -0
  231. package/src/services/__tests__/site-admin.test.ts +121 -1
  232. package/src/services/auth.ts +2 -2
  233. package/src/services/bootstrap.ts +3 -2
  234. package/src/services/collection.ts +226 -167
  235. package/src/services/export-theme/assets/client-site.css +1 -1
  236. package/src/services/export-theme/assets/client-site.js +46 -25
  237. package/src/services/export-theme/layouts/_default/list.html +18 -4
  238. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  239. package/src/services/export-theme/layouts/featured/list.html +5 -4
  240. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  241. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  242. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  243. package/src/services/export-theme/styles/main.css +30 -2
  244. package/src/services/export.ts +91 -80
  245. package/src/services/mcp.ts +37 -27
  246. package/src/services/media.ts +1 -1
  247. package/src/services/navigation.ts +185 -46
  248. package/src/services/post.ts +874 -271
  249. package/src/services/search.ts +8 -2
  250. package/src/services/settings.ts +2 -10
  251. package/src/services/site-admin.ts +63 -43
  252. package/src/style-cjk-jp.css +1 -1
  253. package/src/style-cjk-kr.css +1 -1
  254. package/src/style-cjk-tc.css +1 -1
  255. package/src/style-cjk.css +1 -1
  256. package/src/styles/components.css +540 -0
  257. package/src/styles/site-media.css +111 -19
  258. package/src/styles/tokens.css +14 -9
  259. package/src/styles/ui.css +829 -29
  260. package/src/types/bindings.ts +3 -0
  261. package/src/types/config.ts +243 -12
  262. package/src/types/constants.ts +22 -1
  263. package/src/types/entities.ts +9 -8
  264. package/src/types/operations.ts +18 -10
  265. package/src/types/props.ts +8 -2
  266. package/src/types/raw-assets.d.ts +5 -0
  267. package/src/types/views.ts +16 -4
  268. package/src/ui/__tests__/font-themes.test.ts +42 -30
  269. package/src/ui/compose/ComposeDialog.tsx +92 -2
  270. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  271. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  272. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  273. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  274. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  275. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  276. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  277. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  278. package/src/ui/feed/LinkCard.tsx +2 -12
  279. package/src/ui/feed/NoteCard.tsx +10 -20
  280. package/src/ui/feed/QuoteCard.tsx +2 -12
  281. package/src/ui/feed/ThreadPreview.tsx +26 -34
  282. package/src/ui/feed/TimelineFeed.tsx +2 -2
  283. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  284. package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
  285. package/src/ui/feed/thread-preview-state.ts +5 -7
  286. package/src/ui/font-themes.ts +74 -26
  287. package/src/ui/layouts/BaseLayout.tsx +22 -8
  288. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  289. package/src/ui/pages/CollectionPage.tsx +101 -22
  290. package/src/ui/pages/CollectionsPage.tsx +3 -1
  291. package/src/ui/pages/PostPage.tsx +39 -12
  292. package/src/ui/pages/SearchPage.tsx +6 -14
  293. package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
  294. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  295. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  296. package/src/ui/shared/CollectionsManager.tsx +17 -7
  297. package/src/ui/shared/DraftPreviewBar.tsx +33 -0
  298. package/src/ui/shared/HomePageBranding.tsx +2 -2
  299. package/src/ui/shared/MediaGallery.tsx +1 -0
  300. package/src/ui/shared/PostFooter.tsx +3 -1
  301. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  302. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  303. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  304. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  305. package/src/ui/shared/collection-management-labels.ts +33 -2
  306. package/src/ui/shared/navigation-labels.ts +2 -4
  307. package/src/ui/shared/post-article-attributes.ts +46 -0
  308. package/dist/app-DZDlmh7C.js +0 -6
  309. package/dist/client/_assets/client-Cevfpm8H.css +0 -2
  310. package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
  311. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
@@ -891,7 +891,7 @@ export class JantComposeDialog extends LitElement {
891
891
  if (requestId !== this._openEditRequestId) return;
892
892
  }
893
893
 
894
- if (post.collectionIds?.length) {
894
+ if (!post.replyToId && post.collectionIds?.length) {
895
895
  this._collectionIds = post.collectionIds;
896
896
  }
897
897
 
@@ -1008,6 +1008,9 @@ export class JantComposeDialog extends LitElement {
1008
1008
  if (options?.restoreDraft !== false) {
1009
1009
  await this.restoreLocalDraft({ expectedReplyToId: id });
1010
1010
  }
1011
+ // Collection membership belongs to the existing Thread. Reply compose
1012
+ // must not restore or submit stale per-compose Collection state.
1013
+ this._collectionIds = [];
1011
1014
 
1012
1015
  this.closest("dialog")?.showModal();
1013
1016
  await this.updateComplete;
@@ -1371,9 +1374,10 @@ export class JantComposeDialog extends LitElement {
1371
1374
  : this._visibility
1372
1375
  : undefined,
1373
1376
  rating: editorData.rating,
1374
- collectionIds: isRoot ? [...this._collectionIds] : [],
1377
+ collectionIds: isRoot && !this._replyToId ? [...this._collectionIds] : [],
1375
1378
  attachments: orderedAttachments,
1376
1379
  replyToId: isRoot ? (this._replyToId ?? undefined) : undefined,
1380
+ quietReply: isRoot ? this._quietReply || undefined : undefined,
1377
1381
  replyThreadRootId: isRoot
1378
1382
  ? (this._replyThreadRootId ?? undefined)
1379
1383
  : undefined,
@@ -1441,7 +1445,7 @@ export class JantComposeDialog extends LitElement {
1441
1445
  status,
1442
1446
  visibility: this._visibilityLocked ? undefined : this._visibility,
1443
1447
  rating: editorData.rating,
1444
- collectionIds: [...this._collectionIds],
1448
+ collectionIds: this._replyToId ? [] : [...this._collectionIds],
1445
1449
  attachments: orderedAttachments,
1446
1450
  editPostId: this._editPostId ?? this._draftSourceId ?? undefined,
1447
1451
  replyToId: this._replyToId ?? undefined,
@@ -2437,15 +2441,7 @@ export class JantComposeDialog extends LitElement {
2437
2441
  this._handleConfirmSave();
2438
2442
  } else if ((ke.metaKey || ke.ctrlKey) && ke.key === "Enter") {
2439
2443
  e.preventDefault();
2440
- if (this._attachedPanelOpen) {
2441
- this._doneAttachedPanel();
2442
- return;
2443
- }
2444
- if (!this._canPublish()) {
2445
- this._focusBlockedSubmitField("published");
2446
- return;
2447
- }
2448
- void this._submit("published");
2444
+ this._publishFromShortcut();
2449
2445
  } else if (
2450
2446
  (ke.metaKey || ke.ctrlKey) &&
2451
2447
  !ke.altKey &&
@@ -2471,6 +2467,18 @@ export class JantComposeDialog extends LitElement {
2471
2467
  }
2472
2468
  };
2473
2469
 
2470
+ private _publishFromShortcut() {
2471
+ if (this._attachedPanelOpen) {
2472
+ this._doneAttachedPanel();
2473
+ return;
2474
+ }
2475
+ if (!this._canPublish()) {
2476
+ this._focusBlockedSubmitField("published");
2477
+ return;
2478
+ }
2479
+ void this._submit("published");
2480
+ }
2481
+
2474
2482
  private _handleAltPanelOpen = (e: Event) => {
2475
2483
  const detail = (e as CustomEvent<{ index: number }>).detail;
2476
2484
  this._altPanelIndex = detail.index;
@@ -2500,7 +2508,10 @@ export class JantComposeDialog extends LitElement {
2500
2508
  private _handleFullscreenClose = (
2501
2509
  e: CustomEvent<ComposeFullscreenCloseDetail>,
2502
2510
  ) => {
2503
- const editor = this._editor;
2511
+ const editors = Array.from(
2512
+ this.querySelectorAll<JantComposeEditor>("jant-compose-editor"),
2513
+ );
2514
+ const editor = editors[e.detail.editorIndex ?? 0] ?? this._editor;
2504
2515
  if (editor) {
2505
2516
  editor.setEditorState(
2506
2517
  e.detail.json as import("@tiptap/core").JSONContent,
@@ -2511,9 +2522,16 @@ export class JantComposeDialog extends LitElement {
2511
2522
  // Adopt any in-flight inline image uploads from the fullscreen editor
2512
2523
  // so blob: placeholder URLs get replaced when uploads complete.
2513
2524
  editor.adoptPendingUploads();
2514
- this.updateComplete.then(() => editor.focusSelection(e.detail.selection));
2525
+ if (e.detail.intent !== "publish") {
2526
+ this.updateComplete.then(() =>
2527
+ editor.focusSelection(e.detail.selection),
2528
+ );
2529
+ }
2515
2530
  }
2516
2531
  this._replyExpanded = e.detail.replyExpanded;
2532
+ if (e.detail.intent === "publish") {
2533
+ this._publishFromShortcut();
2534
+ }
2517
2535
  };
2518
2536
 
2519
2537
  private _buildFullscreenReplyContext(): ComposeFullscreenReplyContext | null {
@@ -2530,6 +2548,11 @@ export class JantComposeDialog extends LitElement {
2530
2548
  e: CustomEvent<ComposeFullscreenOpenDetail>,
2531
2549
  ) => {
2532
2550
  e.detail.replyContext = this._buildFullscreenReplyContext();
2551
+ const editors = Array.from(
2552
+ this.querySelectorAll<JantComposeEditor>("jant-compose-editor"),
2553
+ );
2554
+ const editorIndex = editors.indexOf(e.target as JantComposeEditor);
2555
+ e.detail.editorIndex = editorIndex >= 0 ? editorIndex : 0;
2533
2556
  };
2534
2557
 
2535
2558
  private _handleAttachedPanelOpen = (e: Event) => {
@@ -2551,6 +2574,7 @@ export class JantComposeDialog extends LitElement {
2551
2574
  placeholder: this.labels.attachedTextPlaceholder,
2552
2575
  content,
2553
2576
  toolbarMode: "compose",
2577
+ tableControlLabels: this.labels.tableControls,
2554
2578
  });
2555
2579
  this._focusAttachedEditorBoundary(content);
2556
2580
  });
@@ -2638,6 +2662,7 @@ export class JantComposeDialog extends LitElement {
2638
2662
  const allDraftItems = (posts as Record<string, unknown>[]).map(
2639
2663
  (p): DraftItem => ({
2640
2664
  id: p.id as string,
2665
+ slug: p.slug as string,
2641
2666
  format: p.format as ComposeFormat,
2642
2667
  title:
2643
2668
  ((p.format as ComposeFormat) === "quote"
@@ -2744,7 +2769,7 @@ export class JantComposeDialog extends LitElement {
2744
2769
  this._visibility = post.visibility ?? "public";
2745
2770
  this._visibilityLocked = Boolean(post.replyToId);
2746
2771
 
2747
- if (post.collectionIds?.length) {
2772
+ if (!post.replyToId && post.collectionIds?.length) {
2748
2773
  this._collectionIds = post.collectionIds;
2749
2774
  }
2750
2775
 
@@ -3168,7 +3193,9 @@ export class JantComposeDialog extends LitElement {
3168
3193
  return;
3169
3194
  }
3170
3195
 
3171
- this._collectionIds = [...(draft.collectionIds ?? [])];
3196
+ this._collectionIds = draft.replyToId
3197
+ ? []
3198
+ : [...(draft.collectionIds ?? [])];
3172
3199
  this._slug = draft.slug ?? "";
3173
3200
  this._slugTaken = false;
3174
3201
  this._slugCheckLoading = false;
@@ -3309,7 +3336,9 @@ export class JantComposeDialog extends LitElement {
3309
3336
 
3310
3337
  // Restore metadata
3311
3338
  this._format = draft.format;
3312
- this._collectionIds = [...(draft.collectionIds ?? [])];
3339
+ this._collectionIds = this._replyToId
3340
+ ? []
3341
+ : [...(draft.collectionIds ?? [])];
3313
3342
  this._slug = draft.slug ?? "";
3314
3343
  this._publishedAtInput = draft.publishedAtInput ?? "";
3315
3344
  this._publishedAtTimeMinutes = draft.publishedAtTimeMinutes ?? null;
@@ -3426,6 +3455,8 @@ export class JantComposeDialog extends LitElement {
3426
3455
 
3427
3456
  private _renderDraftItem(draft: DraftItem) {
3428
3457
  const preview = this._getDraftPreview(draft);
3458
+ const menuId = `draft-actions-${draft.id}`;
3459
+ const menuOpen = this._draftMenuOpenId === draft.id;
3429
3460
 
3430
3461
  return html`
3431
3462
  <div class="compose-draft-item" @click=${() => this._loadDraft(draft.id)}>
@@ -3442,7 +3473,7 @@ export class JantComposeDialog extends LitElement {
3442
3473
  </div>
3443
3474
  </div>
3444
3475
  <div class="relative">
3445
- ${this._draftMenuOpenId === draft.id
3476
+ ${menuOpen
3446
3477
  ? html`<div
3447
3478
  class="compose-dropdown-backdrop"
3448
3479
  @click=${(e: Event) => {
@@ -3454,6 +3485,10 @@ export class JantComposeDialog extends LitElement {
3454
3485
  <button
3455
3486
  type="button"
3456
3487
  class="compose-draft-more"
3488
+ aria-label=${this.labels.draftActions}
3489
+ aria-haspopup="menu"
3490
+ aria-expanded=${menuOpen ? "true" : "false"}
3491
+ aria-controls=${menuId}
3457
3492
  @click=${(e: Event) => {
3458
3493
  e.stopPropagation();
3459
3494
  this._draftMenuOpenId =
@@ -3466,12 +3501,34 @@ export class JantComposeDialog extends LitElement {
3466
3501
  <circle cx="12" cy="8" r="1.2" />
3467
3502
  </svg>
3468
3503
  </button>
3469
- ${this._draftMenuOpenId === draft.id
3504
+ ${menuOpen
3470
3505
  ? html`
3471
- <div class="compose-dropdown compose-dropdown-right">
3506
+ <div
3507
+ id=${menuId}
3508
+ class="compose-dropdown compose-dropdown-right"
3509
+ role="menu"
3510
+ >
3511
+ <a
3512
+ class="compose-dropdown-item"
3513
+ href=${publicPath(`/preview/${draft.slug}`)}
3514
+ target="_blank"
3515
+ rel="noopener noreferrer"
3516
+ role="menuitem"
3517
+ @click=${(e: Event) => {
3518
+ e.stopPropagation();
3519
+ this._draftMenuOpenId = null;
3520
+ }}
3521
+ >
3522
+ ${this.labels.previewDraft}
3523
+ </a>
3524
+ <div
3525
+ class="compose-dropdown-separator"
3526
+ role="separator"
3527
+ ></div>
3472
3528
  <button
3473
3529
  type="button"
3474
3530
  class="compose-dropdown-item compose-dropdown-item-danger"
3531
+ role="menuitem"
3475
3532
  @click=${(e: Event) => {
3476
3533
  e.stopPropagation();
3477
3534
  this._deleteDraft(draft.id);
@@ -5254,6 +5311,12 @@ export class JantComposeDialog extends LitElement {
5254
5311
  "jant-compose-editor",
5255
5312
  );
5256
5313
  editors[this._focusedThreadIndex]?.focusInput();
5314
+ const threadLayout = this.querySelector<HTMLElement>(
5315
+ ".compose-thread-compose-layout",
5316
+ );
5317
+ if (threadLayout) {
5318
+ threadLayout.scrollTop = threadLayout.scrollHeight;
5319
+ }
5257
5320
  });
5258
5321
  }
5259
5322
 
@@ -5535,11 +5598,12 @@ export class JantComposeDialog extends LitElement {
5535
5598
  : html`<div
5536
5599
  class=${classMap({
5537
5600
  "compose-action-row": true,
5601
+ "compose-action-row-without-collection": !!this._replyToId,
5538
5602
  "compose-action-row-overlay-open":
5539
5603
  this._showPublishPanel || this._showCollection,
5540
5604
  })}
5541
5605
  >
5542
- ${this._renderCollectionSelector()}
5606
+ ${this._replyToId ? nothing : this._renderCollectionSelector()}
5543
5607
  ${this._renderPublishButton()}
5544
5608
  </div>
5545
5609
  ${this._renderQuickActionsRow()}`}
@@ -1006,6 +1006,7 @@ export class JantComposeEditor extends LitElement {
1006
1006
  rehost: (src) => this.#rehostInlineImage(src),
1007
1007
  },
1008
1008
  imageNodeLabels: getInlineImageNodeLabels(this.labels),
1009
+ tableControlLabels: this.labels.tableControls,
1009
1010
  });
1010
1011
  this._lastEditorSelection = this._readEditorSelection();
1011
1012
 
@@ -1709,6 +1710,16 @@ export class JantComposeEditor extends LitElement {
1709
1710
  this._lastFocusedField = target;
1710
1711
  }
1711
1712
 
1713
+ private _handleTitleKeydown(e: globalThis.KeyboardEvent) {
1714
+ if (e.isComposing || e.keyCode === 229) return;
1715
+ if (e.key !== "Enter" || e.metaKey || e.ctrlKey || e.altKey) {
1716
+ return;
1717
+ }
1718
+
1719
+ e.preventDefault();
1720
+ this._editor?.commands.focus("start");
1721
+ }
1722
+
1712
1723
  private _toggleEmojiPicker() {
1713
1724
  if (this._showEmojiPicker) {
1714
1725
  this.closeEmojiPicker();
@@ -1974,13 +1985,7 @@ export class JantComposeEditor extends LitElement {
1974
1985
  .value=${this._title}
1975
1986
  @input=${(e: Event) => this._onInput("_title", e)}
1976
1987
  @focus=${(e: Event) => this._onFieldFocus(e)}
1977
- @keydown=${(e: globalThis.KeyboardEvent) => {
1978
- if (e.isComposing || e.keyCode === 229) return;
1979
- if (e.key === "Enter") {
1980
- e.preventDefault();
1981
- this._editor?.commands.focus("start");
1982
- }
1983
- }}
1988
+ @keydown=${this._handleTitleKeydown}
1984
1989
  class="compose-input compose-note-title"
1985
1990
  placeholder=${this.labels.titlePlaceholder}
1986
1991
  />
@@ -2053,6 +2058,7 @@ export class JantComposeEditor extends LitElement {
2053
2058
  .value=${this._title}
2054
2059
  @input=${(e: Event) => this._onInput("_title", e)}
2055
2060
  @focus=${(e: Event) => this._onFieldFocus(e)}
2061
+ @keydown=${this._handleTitleKeydown}
2056
2062
  @blur=${() => {
2057
2063
  this._showLinkTitleValidation = true;
2058
2064
  }}
@@ -23,7 +23,8 @@ import { createTiptapEditor } from "../tiptap/create-editor.js";
23
23
  import { uploadAndInsertInlineImage } from "../tiptap/inline-image-upload.js";
24
24
  import { getInlineImageNodeLabels } from "./inline-image-issues.js";
25
25
 
26
- const ESCAPE_OVERLAY_SELECTOR = ".tiptap-slash-menu, .tiptap-link-input";
26
+ const ESCAPE_OVERLAY_SELECTOR =
27
+ ".tiptap-slash-menu, .tiptap-link-input, .tiptap-table-size-picker, .tiptap-table-options:not([hidden])";
27
28
 
28
29
  export class JantComposeFullscreen extends LitElement {
29
30
  static properties = {
@@ -45,6 +46,7 @@ export class JantComposeFullscreen extends LitElement {
45
46
  private _editor: Editor | null = null;
46
47
  private _content: JSONContent | null = null;
47
48
  private _selection: ComposeEditorSelection | null = null;
49
+ private _editorIndex = 0;
48
50
  private _fileInput: HTMLInputElement | null = null;
49
51
  #inlineImageUploadPromises = new Set<Promise<void>>();
50
52
 
@@ -130,6 +132,7 @@ export class JantComposeFullscreen extends LitElement {
130
132
  this._showTitle = e.detail.showTitle || e.detail.title.trim().length > 0;
131
133
  this._replyContext = e.detail.replyContext ?? null;
132
134
  this._replyExpanded = e.detail.replyContext?.expanded ?? false;
135
+ this._editorIndex = e.detail.editorIndex ?? 0;
133
136
  this._open = true;
134
137
  this.updateComplete.then(() => {
135
138
  const dialog = this.querySelector<HTMLDialogElement>(
@@ -161,6 +164,7 @@ export class JantComposeFullscreen extends LitElement {
161
164
  uploadInlineImage: (file) => this._uploadAndInsertImage(file),
162
165
  },
163
166
  imageNodeLabels: getInlineImageNodeLabels(this.labels),
167
+ tableControlLabels: this.labels.tableControls,
164
168
  });
165
169
 
166
170
  const selection = this._selection;
@@ -189,9 +193,18 @@ export class JantComposeFullscreen extends LitElement {
189
193
  };
190
194
 
191
195
  private _onDocumentKeydown = (e: globalThis.KeyboardEvent) => {
192
- if (!this._open || e.key !== "Escape") return;
193
- // Let IME consume Escape during composition (e.g. CJK candidate dismiss).
196
+ if (!this._open) return;
197
+ // Let IME consume keys during composition (e.g. CJK candidate selection).
194
198
  if (e.isComposing || e.keyCode === 229) return;
199
+
200
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
201
+ e.preventDefault();
202
+ e.stopPropagation();
203
+ this._publish();
204
+ return;
205
+ }
206
+
207
+ if (e.key !== "Escape") return;
195
208
  if (this._hasActiveEscapeOverlay()) return;
196
209
 
197
210
  e.preventDefault();
@@ -210,7 +223,7 @@ export class JantComposeFullscreen extends LitElement {
210
223
  ).some((el) => getComputedStyle(el).display !== "none");
211
224
  }
212
225
 
213
- private _finishClose() {
226
+ private _finishClose(intent?: "publish") {
214
227
  const json = this._editor?.getJSON() ?? this._content;
215
228
  const selection = this._editor
216
229
  ? {
@@ -238,6 +251,8 @@ export class JantComposeFullscreen extends LitElement {
238
251
  showTitle: this._showTitle || this._title.trim().length > 0,
239
252
  selection,
240
253
  replyExpanded: this._replyExpanded,
254
+ intent,
255
+ editorIndex: this._editorIndex,
241
256
  },
242
257
  }),
243
258
  );
@@ -248,6 +263,11 @@ export class JantComposeFullscreen extends LitElement {
248
263
  this._finishClose();
249
264
  }
250
265
 
266
+ private _publish() {
267
+ if (!this._open) return;
268
+ this._finishClose("publish");
269
+ }
270
+
251
271
  private _revealTitle() {
252
272
  this._showTitle = true;
253
273
  this.updateComplete.then(() => {