@jant/core 0.6.13 → 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 (316) 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-HnzmsaqU.js → app-C-oi_t8W.js} +7419 -6350
  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-3_OaxhTs.js → client-DxY5BFe8.js} +58 -37
  16. package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
  17. package/dist/{export-Dfbq9aot.js → export-CsFx6F_M.js} +1273 -131
  18. package/dist/{github-sync--PSoE-Ne.js → github-sync-Cq1pzzOI.js} +1 -1
  19. package/dist/{github-sync-BGTLx8Mf.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-command-palette.test.ts +137 -10
  47. package/src/client/components/__tests__/jant-compose-dialog.test.ts +373 -5
  48. package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
  49. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
  50. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  51. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  52. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  53. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  54. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  55. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  56. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  57. package/src/client/components/collection-manager-types.ts +9 -3
  58. package/src/client/components/compose-types.ts +8 -0
  59. package/src/client/components/jant-collection-directory.ts +165 -30
  60. package/src/client/components/jant-command-palette.ts +102 -71
  61. package/src/client/components/jant-compose-dialog.ts +86 -22
  62. package/src/client/components/jant-compose-editor.ts +13 -7
  63. package/src/client/components/jant-compose-fullscreen.ts +24 -4
  64. package/src/client/components/jant-config-editor.ts +722 -0
  65. package/src/client/components/jant-media-lightbox.ts +100 -18
  66. package/src/client/components/jant-nav-manager.ts +1283 -54
  67. package/src/client/components/jant-post-form.ts +3 -3
  68. package/src/client/components/jant-post-menu.ts +86 -74
  69. package/src/client/components/nav-manager-types.ts +51 -2
  70. package/src/client/components/post-form-types.ts +1 -1
  71. package/src/client/compose-bridge.ts +4 -1
  72. package/src/client/feed-video-player.ts +62 -2
  73. package/src/client/footnote-rail.ts +478 -0
  74. package/src/client/hydrate-partial.ts +2 -0
  75. package/src/client/media-lightbox-events.ts +63 -0
  76. package/src/client/site-header-fragment.ts +38 -0
  77. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  78. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  79. package/src/client/tiptap/__tests__/footnotes.test.ts +284 -0
  80. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  81. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +872 -2
  82. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  83. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  84. package/src/client/tiptap/bubble-menu.ts +91 -13
  85. package/src/client/tiptap/continuous-lists.ts +102 -0
  86. package/src/client/tiptap/create-editor.ts +3 -0
  87. package/src/client/tiptap/extensions.ts +14 -5
  88. package/src/client/tiptap/footnotes.ts +156 -0
  89. package/src/client/tiptap/markdown-clipboard.ts +614 -12
  90. package/src/client/tiptap/slash-commands.ts +8 -1
  91. package/src/client/tiptap/structural-keymap.ts +211 -0
  92. package/src/client/tiptap/tab-indent.ts +8 -22
  93. package/src/client/tiptap/table-control-labels.ts +30 -0
  94. package/src/client/tiptap/table-controls.ts +537 -0
  95. package/src/client/tiptap/table-size-picker.ts +254 -0
  96. package/src/client/toast.ts +8 -6
  97. package/src/client-auth.ts +1 -0
  98. package/src/client.ts +1 -0
  99. package/src/db/__tests__/d1-query.test.ts +43 -1
  100. package/src/db/__tests__/migration-runner.test.ts +63 -0
  101. package/src/db/__tests__/migrations.test.ts +469 -0
  102. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  103. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  104. package/src/db/migrations/0028_superb_post.sql +2 -0
  105. package/src/db/migrations/0029_boring_magma.sql +1 -0
  106. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  107. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  108. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  109. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  110. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  111. package/src/db/migrations/meta/_journal.json +28 -0
  112. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  113. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  114. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  115. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  116. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  117. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  118. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  119. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  120. package/src/db/migrations/pg/meta/_journal.json +28 -0
  121. package/src/db/pg/schema.ts +28 -20
  122. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  123. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  124. package/src/db/schema.ts +28 -20
  125. package/src/i18n/__tests__/detect.test.ts +8 -2
  126. package/src/i18n/detect.ts +14 -5
  127. package/src/i18n/locales/public/en.po +133 -30
  128. package/src/i18n/locales/public/en.ts +1 -1
  129. package/src/i18n/locales/public/zh-Hans.po +128 -25
  130. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  131. package/src/i18n/locales/public/zh-Hant.po +128 -25
  132. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  133. package/src/i18n/locales/settings/en.po +513 -10
  134. package/src/i18n/locales/settings/en.ts +1 -1
  135. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  136. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  137. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  138. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  139. package/src/lib/__tests__/api-settings.test.ts +299 -0
  140. package/src/lib/__tests__/constants.test.ts +5 -0
  141. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  142. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  143. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  144. package/src/lib/__tests__/markdown-to-tiptap.test.ts +36 -4
  145. package/src/lib/__tests__/markdown.test.ts +8 -6
  146. package/src/lib/__tests__/navigation.test.ts +59 -2
  147. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  148. package/src/lib/__tests__/post-display.test.ts +64 -0
  149. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  150. package/src/lib/__tests__/summary.test.ts +82 -0
  151. package/src/lib/__tests__/timeline.test.ts +109 -70
  152. package/src/lib/__tests__/timezones.test.ts +31 -3
  153. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  154. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  155. package/src/lib/__tests__/view.test.ts +53 -0
  156. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  157. package/src/lib/api-settings.ts +239 -12
  158. package/src/lib/collection-sort.ts +7 -5
  159. package/src/lib/constants.ts +2 -0
  160. package/src/lib/feed-path.ts +25 -0
  161. package/src/lib/feed-policy.ts +64 -0
  162. package/src/lib/footnotes.ts +264 -0
  163. package/src/lib/github-sync-site-config.ts +2 -0
  164. package/src/lib/hugo-markdown.ts +10 -3
  165. package/src/lib/jant-branding.ts +1 -0
  166. package/src/lib/markdown-manager.ts +202 -10
  167. package/src/lib/markdown.ts +10 -3
  168. package/src/lib/navigation.ts +16 -3
  169. package/src/lib/post-body-html.ts +131 -0
  170. package/src/lib/post-display.ts +7 -1
  171. package/src/lib/render.tsx +9 -2
  172. package/src/lib/resolve-config.ts +100 -16
  173. package/src/lib/schemas.ts +133 -1
  174. package/src/lib/settings-paths.ts +5 -0
  175. package/src/lib/site-skill.ts +78 -0
  176. package/src/lib/summary.ts +16 -5
  177. package/src/lib/timeline.ts +98 -122
  178. package/src/lib/timezones.ts +54 -14
  179. package/src/lib/tiptap-render.ts +355 -81
  180. package/src/lib/view.ts +30 -20
  181. package/src/lib/worker-response-cache.ts +11 -18
  182. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  183. package/src/middleware/config.ts +2 -2
  184. package/src/middleware/public-content-access.ts +87 -0
  185. package/src/node/__tests__/cli-export.test.ts +14 -0
  186. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  187. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  188. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  189. package/src/node/index.ts +2 -1
  190. package/src/preset.css +16 -2
  191. package/src/routes/__tests__/compose.test.ts +51 -0
  192. package/src/routes/__tests__/site-skill.test.ts +112 -0
  193. package/src/routes/api/__tests__/collections.test.ts +91 -19
  194. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  195. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  196. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  197. package/src/routes/api/__tests__/settings.test.ts +217 -0
  198. package/src/routes/api/collections.ts +22 -26
  199. package/src/routes/api/export.ts +2 -0
  200. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  201. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  202. package/src/routes/api/internal/post-body-html.ts +43 -0
  203. package/src/routes/api/internal/sites.ts +24 -0
  204. package/src/routes/api/nav-items.ts +24 -1
  205. package/src/routes/api/posts.ts +2 -2
  206. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  207. package/src/routes/api/public/__tests__/posts.test.ts +208 -0
  208. package/src/routes/api/public/archive.ts +4 -0
  209. package/src/routes/api/public/posts.ts +41 -24
  210. package/src/routes/api/search.ts +3 -0
  211. package/src/routes/api/settings.ts +33 -0
  212. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  213. package/src/routes/compose.tsx +2 -1
  214. package/src/routes/dash/settings.tsx +45 -2
  215. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  216. package/src/routes/feed/feed.ts +23 -14
  217. package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
  218. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  219. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  220. package/src/routes/pages/__tests__/preview.test.ts +174 -0
  221. package/src/routes/pages/__tests__/search.test.ts +69 -0
  222. package/src/routes/pages/archive.tsx +27 -7
  223. package/src/routes/pages/collection.tsx +32 -19
  224. package/src/routes/pages/collections.tsx +6 -1
  225. package/src/routes/pages/page.tsx +83 -11
  226. package/src/routes/site-skill.ts +18 -0
  227. package/src/services/__tests__/auth.test.ts +7 -5
  228. package/src/services/__tests__/collection.test.ts +159 -44
  229. package/src/services/__tests__/navigation.test.ts +229 -21
  230. package/src/services/__tests__/post-timeline.test.ts +109 -30
  231. package/src/services/__tests__/post.test.ts +567 -5
  232. package/src/services/__tests__/search.test.ts +48 -1
  233. package/src/services/__tests__/settings.test.ts +8 -0
  234. package/src/services/__tests__/site-admin.test.ts +121 -1
  235. package/src/services/auth.ts +2 -2
  236. package/src/services/bootstrap.ts +3 -2
  237. package/src/services/collection.ts +226 -167
  238. package/src/services/export-theme/assets/client-site.css +1 -1
  239. package/src/services/export-theme/assets/client-site.js +46 -25
  240. package/src/services/export-theme/layouts/_default/list.html +18 -4
  241. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  242. package/src/services/export-theme/layouts/featured/list.html +5 -4
  243. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  244. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  245. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  246. package/src/services/export-theme/styles/main.css +30 -2
  247. package/src/services/export.ts +91 -80
  248. package/src/services/mcp.ts +37 -27
  249. package/src/services/media.ts +1 -1
  250. package/src/services/navigation.ts +185 -46
  251. package/src/services/post.ts +874 -271
  252. package/src/services/search.ts +8 -2
  253. package/src/services/settings.ts +2 -10
  254. package/src/services/site-admin.ts +63 -43
  255. package/src/style-cjk-jp.css +1 -1
  256. package/src/style-cjk-kr.css +1 -1
  257. package/src/style-cjk-tc.css +1 -1
  258. package/src/style-cjk.css +1 -1
  259. package/src/styles/components.css +540 -0
  260. package/src/styles/site-media.css +111 -19
  261. package/src/styles/tokens.css +14 -9
  262. package/src/styles/ui.css +952 -64
  263. package/src/types/bindings.ts +3 -0
  264. package/src/types/config.ts +243 -12
  265. package/src/types/constants.ts +22 -1
  266. package/src/types/entities.ts +9 -8
  267. package/src/types/operations.ts +18 -10
  268. package/src/types/props.ts +8 -2
  269. package/src/types/raw-assets.d.ts +5 -0
  270. package/src/types/views.ts +16 -4
  271. package/src/ui/__tests__/font-themes.test.ts +42 -30
  272. package/src/ui/compose/ComposeDialog.tsx +92 -2
  273. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  274. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  275. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  276. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  277. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  278. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  279. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  280. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  281. package/src/ui/feed/LinkCard.tsx +2 -12
  282. package/src/ui/feed/NoteCard.tsx +10 -20
  283. package/src/ui/feed/QuoteCard.tsx +2 -12
  284. package/src/ui/feed/ThreadPreview.tsx +26 -34
  285. package/src/ui/feed/TimelineFeed.tsx +2 -2
  286. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  287. package/src/ui/feed/__tests__/timeline-cards.test.ts +186 -6
  288. package/src/ui/feed/thread-preview-state.ts +5 -7
  289. package/src/ui/font-themes.ts +74 -26
  290. package/src/ui/layouts/BaseLayout.tsx +23 -9
  291. package/src/ui/layouts/SiteLayout.tsx +19 -13
  292. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  293. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +48 -0
  294. package/src/ui/pages/CollectionPage.tsx +101 -22
  295. package/src/ui/pages/CollectionsPage.tsx +3 -1
  296. package/src/ui/pages/PostPage.tsx +39 -12
  297. package/src/ui/pages/SearchPage.tsx +6 -14
  298. package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
  299. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  300. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  301. package/src/ui/shared/CollectionsManager.tsx +17 -7
  302. package/src/ui/shared/DraftPreviewBar.tsx +33 -0
  303. package/src/ui/shared/HomePageBranding.tsx +2 -2
  304. package/src/ui/shared/MediaGallery.tsx +1 -0
  305. package/src/ui/shared/PostFooter.tsx +3 -1
  306. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  307. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  308. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  309. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  310. package/src/ui/shared/collection-management-labels.ts +33 -2
  311. package/src/ui/shared/navigation-labels.ts +2 -4
  312. package/src/ui/shared/post-article-attributes.ts +46 -0
  313. package/dist/app-CWJFPjuR.js +0 -6
  314. package/dist/client/_assets/client-DCrcHVJl.css +0 -2
  315. package/dist/client/_assets/client-auth-TsYq90xm.js +0 -4733
  316. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
@@ -11,6 +11,7 @@
11
11
  import { LitElement, html, nothing } from "lit";
12
12
  import { classMap } from "lit/directives/class-map.js";
13
13
  import { unsafeSVG } from "lit/directives/unsafe-svg.js";
14
+ import { NAVIGATION_SETTINGS_PATH } from "../../lib/settings-paths.js";
14
15
  import { openNewCompose } from "../compose-launch.js";
15
16
  import { getBestFieldSearchRank, normalizeSearch } from "../search-rank.js";
16
17
 
@@ -30,6 +31,19 @@ interface CommandItem {
30
31
  action: () => void;
31
32
  }
32
33
 
34
+ interface DisplayItem {
35
+ label: string;
36
+ secondary?: string;
37
+ icon: string;
38
+ /** For search-mode items: the query to execute */
39
+ searchQuery?: string;
40
+ /** For path-mode items: the local path to open */
41
+ pathTarget?: string;
42
+ /** Keep cross-mode actions visible below the scrollable result list. */
43
+ placement?: "results" | "footer";
44
+ shortcut?: string;
45
+ }
46
+
33
47
  // ---------------------------------------------------------------------------
34
48
  // SVG icon paths (reuse existing Lucide-style icons from the codebase)
35
49
  // ---------------------------------------------------------------------------
@@ -109,7 +123,7 @@ const SYSTEM_PAGES: PaletteItem[] = [
109
123
  { title: "Collections", path: "/collections", type: "system" },
110
124
  { title: "Settings", path: "/settings", type: "system" },
111
125
  { title: "General", path: "/settings/general", type: "system" },
112
- { title: "Navigation", path: "/settings/navigation", type: "system" },
126
+ { title: "Navigation", path: NAVIGATION_SETTINGS_PATH, type: "system" },
113
127
  {
114
128
  title: "Appearance",
115
129
  path: "/settings/appearance/color-theme",
@@ -280,15 +294,7 @@ export class JantCommandPalette extends LitElement {
280
294
  return COMMANDS.filter((c) => !q || c.label.toLowerCase().includes(q));
281
295
  }
282
296
 
283
- get #displayItems(): Array<{
284
- label: string;
285
- secondary?: string;
286
- icon: string;
287
- /** For search-mode items: the query to execute */
288
- searchQuery?: string;
289
- /** For path-mode items: the local path to open */
290
- pathTarget?: string;
291
- }> {
297
+ get #displayItems(): DisplayItem[] {
292
298
  const mode = this.#mode;
293
299
 
294
300
  if (mode === "command") {
@@ -320,13 +326,7 @@ export class JantCommandPalette extends LitElement {
320
326
  if (mode === "path") {
321
327
  const target = getPathCommandTarget(this._query);
322
328
  const searchQuery = getPathCommandSearchQuery(this._query);
323
- const items: Array<{
324
- label: string;
325
- secondary?: string;
326
- icon: string;
327
- searchQuery?: string;
328
- pathTarget?: string;
329
- }> = [
329
+ const items: DisplayItem[] = [
330
330
  {
331
331
  label: `Go to ${target}`,
332
332
  icon: ICONS.path,
@@ -346,25 +346,25 @@ export class JantCommandPalette extends LitElement {
346
346
  }
347
347
 
348
348
  // Navigate mode — show all items when no query (autocomplete)
349
- const navItems: Array<{
350
- label: string;
351
- secondary?: string;
352
- icon: string;
353
- searchQuery?: string;
354
- pathTarget?: string;
355
- }> = this.#navigateItems.map((item) => ({
349
+ // Rank direct destinations and let the scrollable result list handle
350
+ // overflow without hiding valid matches.
351
+ const q = normalizeSearch(this._query);
352
+ const navItems: DisplayItem[] = this.#navigateItems.map((item) => ({
356
353
  label: item.title,
357
- secondary: item.type === "system" ? item.path : item.path,
354
+ secondary: item.path,
358
355
  icon: ICONS[item.type],
359
356
  }));
360
357
 
361
- // When navigate mode has no matches, offer a full-text search fallback
362
- const q = normalizeSearch(this._query);
363
- if (navItems.length === 0 && q) {
358
+ // Full-text search is a persistent action, not another navigation result.
359
+ // Keep it outside the scrollable list while retaining it as the final
360
+ // keyboard option.
361
+ if (q) {
364
362
  navItems.push({
365
- label: `Search for "${this._query.trim()}"`,
363
+ label: `Search all content for "${this._query.trim()}"`,
366
364
  icon: ICONS.search,
367
365
  searchQuery: this._query.trim(),
366
+ placement: "footer",
367
+ ...(navItems.length > 0 ? { shortcut: "⌘/Ctrl ↵" } : {}),
368
368
  });
369
369
  }
370
370
 
@@ -375,6 +375,12 @@ export class JantCommandPalette extends LitElement {
375
375
  // Actions
376
376
  // -----------------------------------------------------------------------
377
377
 
378
+ #executeSearch(query: string) {
379
+ saveHistory(SEARCH_HISTORY_KEY, query);
380
+ this.close();
381
+ window.location.href = `/search?q=${encodeURIComponent(query)}`;
382
+ }
383
+
378
384
  #executeItem(index: number) {
379
385
  const mode = this.#mode;
380
386
 
@@ -391,9 +397,7 @@ export class JantCommandPalette extends LitElement {
391
397
  const displayItem = this.#displayItems[index];
392
398
  const q = displayItem?.searchQuery;
393
399
  if (q) {
394
- saveHistory(SEARCH_HISTORY_KEY, q);
395
- this.close();
396
- window.location.href = `/search?q=${encodeURIComponent(q)}`;
400
+ this.#executeSearch(q);
397
401
  }
398
402
  return;
399
403
  }
@@ -408,10 +412,7 @@ export class JantCommandPalette extends LitElement {
408
412
  }
409
413
 
410
414
  if (displayItem?.searchQuery) {
411
- const q = displayItem.searchQuery;
412
- saveHistory(SEARCH_HISTORY_KEY, q);
413
- this.close();
414
- window.location.href = `/search?q=${encodeURIComponent(q)}`;
415
+ this.#executeSearch(displayItem.searchQuery);
415
416
  }
416
417
  return;
417
418
  }
@@ -419,10 +420,7 @@ export class JantCommandPalette extends LitElement {
419
420
  // Navigate mode — check if the selected item is a search fallback
420
421
  const displayItem = this.#displayItems[index];
421
422
  if (displayItem?.searchQuery) {
422
- const q = displayItem.searchQuery;
423
- saveHistory(SEARCH_HISTORY_KEY, q);
424
- this.close();
425
- window.location.href = `/search?q=${encodeURIComponent(q)}`;
423
+ this.#executeSearch(displayItem.searchQuery);
426
424
  return;
427
425
  }
428
426
 
@@ -475,6 +473,19 @@ export class JantCommandPalette extends LitElement {
475
473
  return;
476
474
  }
477
475
 
476
+ if (
477
+ event.key === "Enter" &&
478
+ (event.metaKey || event.ctrlKey) &&
479
+ this.#mode === "navigate"
480
+ ) {
481
+ const query = this._query.trim();
482
+ if (query) {
483
+ event.preventDefault();
484
+ this.#executeSearch(query);
485
+ return;
486
+ }
487
+ }
488
+
478
489
  if (event.key === "Enter") {
479
490
  event.preventDefault();
480
491
  this.#executeItem(this._selectedIndex);
@@ -521,10 +532,49 @@ export class JantCommandPalette extends LitElement {
521
532
  // Render
522
533
  // -----------------------------------------------------------------------
523
534
 
535
+ #renderItem(item: DisplayItem, index: number, footer = false) {
536
+ return html`
537
+ <div
538
+ id="command-palette-item-${index}"
539
+ class=${classMap({
540
+ "command-palette-result": true,
541
+ "command-palette-result-selected": index === this._selectedIndex,
542
+ "command-palette-search-action": footer,
543
+ })}
544
+ role="option"
545
+ aria-selected=${index === this._selectedIndex}
546
+ @click=${this.#handleItemClick(index)}
547
+ >
548
+ <span class="command-palette-result-icon">${unsafeSVG(item.icon)}</span>
549
+ <span class="command-palette-result-body">
550
+ <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}
556
+ </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}
564
+ </div>
565
+ `;
566
+ }
567
+
524
568
  render() {
525
569
  if (!this._open) return nothing;
526
570
 
527
571
  const items = this.#displayItems;
572
+ const resultItems = items
573
+ .map((item, index) => ({ item, index }))
574
+ .filter(({ item }) => item.placement !== "footer");
575
+ const footerItems = items
576
+ .map((item, index) => ({ item, index }))
577
+ .filter(({ item }) => item.placement === "footer");
528
578
 
529
579
  return html`
530
580
  <dialog
@@ -563,41 +613,22 @@ export class JantCommandPalette extends LitElement {
563
613
 
564
614
  ${items.length > 0
565
615
  ? html`
566
- <ul
616
+ <div
567
617
  id="command-palette-results"
568
- class="command-palette-results"
618
+ class="command-palette-results-container"
569
619
  role="listbox"
570
620
  >
571
- ${items.map(
572
- (item, i) => html`
573
- <li
574
- id="command-palette-item-${i}"
575
- class=${classMap({
576
- "command-palette-result": true,
577
- "command-palette-result-selected":
578
- i === this._selectedIndex,
579
- })}
580
- role="option"
581
- aria-selected=${i === this._selectedIndex}
582
- @click=${this.#handleItemClick(i)}
583
- >
584
- <span class="command-palette-result-icon"
585
- >${unsafeSVG(item.icon)}</span
586
- >
587
- <span class="command-palette-result-body">
588
- <span class="command-palette-result-title"
589
- >${item.label}</span
590
- >
591
- ${item.secondary
592
- ? html`<span class="command-palette-result-path"
593
- >${item.secondary}</span
594
- >`
595
- : nothing}
596
- </span>
597
- </li>
598
- `,
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),
599
630
  )}
600
- </ul>
631
+ </div>
601
632
  `
602
633
  : this._query.trim() && !this._loading
603
634
  ? html`<div class="command-palette-empty">No results</div>`
@@ -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);
@@ -4033,7 +4090,7 @@ export class JantComposeDialog extends LitElement {
4033
4090
  <span class="compose-alt-title">${this.labels.attachedText}</span>
4034
4091
  <button
4035
4092
  type="button"
4036
- class="compose-post-btn ml-auto"
4093
+ class="compose-attached-done"
4037
4094
  @click=${() => this._doneAttachedPanel()}
4038
4095
  >
4039
4096
  ${this.labels.done}
@@ -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(() => {