@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
@@ -14,6 +14,8 @@ import Suggestion, {
14
14
  import type { Editor, Range } from "@tiptap/core";
15
15
  import { Plugin, PluginKey, type EditorState } from "@tiptap/pm/state";
16
16
  import { escapeHtml } from "../../lib/html.js";
17
+ import { getConfiguredTableControlLabels } from "./table-controls.js";
18
+ import { openTableSizePicker } from "./table-size-picker.js";
17
19
  import { getBestFieldSearchRank, normalizeSearch } from "../search-rank.js";
18
20
  import {
19
21
  getFixedFloatingContainerRect,
@@ -165,11 +167,16 @@ const SLASH_COMMANDS: SlashCommandItem[] = [
165
167
  },
166
168
  {
167
169
  label: "Table",
168
- description: "Insert a 3 by 3 table.",
170
+ description: "Insert a table.",
169
171
  group: "structure",
170
172
  keywords: ["grid", "rows", "columns", "spreadsheet"],
171
173
  icon: ICONS.table,
172
174
  command: (editor, range) => {
175
+ const labels = getConfiguredTableControlLabels(editor);
176
+ if (labels) {
177
+ openTableSizePicker(editor, range, labels);
178
+ return;
179
+ }
173
180
  editor
174
181
  .chain()
175
182
  .focus()
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Compose Structural Keymap
3
+ *
4
+ * Keeps Jant's small set of product-specific block-boundary decisions in one
5
+ * place while delegating every unowned case to Tiptap's official keymaps.
6
+ */
7
+
8
+ import { Extension, type Editor } from "@tiptap/core";
9
+ import type { Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model";
10
+
11
+ type Direction = "backward" | "forward";
12
+
13
+ interface DirectParagraph {
14
+ kind: "plain" | "quote";
15
+ topIndex: number;
16
+ quoteChildIndex?: number;
17
+ }
18
+
19
+ function locateDirectParagraph($cursor: ResolvedPos): DirectParagraph | null {
20
+ const { schema } = $cursor.doc.type;
21
+ if ($cursor.parent.type !== schema.nodes.paragraph) return null;
22
+
23
+ if ($cursor.depth === 1) {
24
+ return { kind: "plain", topIndex: $cursor.index(0) };
25
+ }
26
+
27
+ if ($cursor.depth === 2 && $cursor.node(1).type === schema.nodes.blockquote) {
28
+ return {
29
+ kind: "quote",
30
+ topIndex: $cursor.index(0),
31
+ quoteChildIndex: $cursor.index(1),
32
+ };
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ function paragraphKindAtQuoteEdge(
39
+ node: ProseMirrorNode,
40
+ direction: Direction,
41
+ ): DirectParagraph["kind"] | null {
42
+ if (node.type.name !== "blockquote") return null;
43
+
44
+ const edge = direction === "backward" ? node.lastChild : node.firstChild;
45
+ return edge?.type.name === "paragraph" ? "quote" : null;
46
+ }
47
+
48
+ function findNeighborParagraphKind(
49
+ $cursor: ResolvedPos,
50
+ current: DirectParagraph,
51
+ direction: Direction,
52
+ ): DirectParagraph["kind"] | null {
53
+ const offset = direction === "backward" ? -1 : 1;
54
+
55
+ if (current.kind === "quote") {
56
+ const quote = $cursor.doc.child(current.topIndex);
57
+ const childIndex = current.quoteChildIndex;
58
+ if (childIndex === undefined) return null;
59
+
60
+ const neighborIndex = childIndex + offset;
61
+ if (neighborIndex >= 0 && neighborIndex < quote.childCount) {
62
+ return quote.child(neighborIndex).type.name === "paragraph"
63
+ ? "quote"
64
+ : null;
65
+ }
66
+ }
67
+
68
+ const topIndex = current.topIndex + offset;
69
+ if (topIndex < 0 || topIndex >= $cursor.doc.childCount) return null;
70
+
71
+ const topNode = $cursor.doc.child(topIndex);
72
+ if (topNode.type.name === "paragraph") return "plain";
73
+
74
+ return paragraphKindAtQuoteEdge(topNode, direction);
75
+ }
76
+
77
+ function joinBlockquoteParagraphBoundary(
78
+ editor: Editor,
79
+ direction: Direction,
80
+ ): boolean {
81
+ const { selection } = editor.state;
82
+ if (!selection.empty) return false;
83
+
84
+ const { $from } = selection;
85
+ const atBoundary =
86
+ direction === "backward"
87
+ ? $from.parentOffset === 0
88
+ : $from.parentOffset === $from.parent.content.size;
89
+ if (!atBoundary) return false;
90
+
91
+ const current = locateDirectParagraph($from);
92
+ if (!current) return false;
93
+
94
+ const neighborKind = findNeighborParagraphKind($from, current, direction);
95
+ if (!neighborKind || (current.kind !== "quote" && neighborKind !== "quote")) {
96
+ return false;
97
+ }
98
+
99
+ return direction === "backward"
100
+ ? editor.commands.joinTextblockBackward()
101
+ : editor.commands.joinTextblockForward();
102
+ }
103
+
104
+ function findDirectListItemDepth($cursor: ResolvedPos): number | null {
105
+ const listItemDepth = $cursor.depth - 1;
106
+ if (
107
+ listItemDepth <= 0 ||
108
+ $cursor.node(listItemDepth).type.name !== "listItem"
109
+ ) {
110
+ return null;
111
+ }
112
+
113
+ const listDepth = listItemDepth - 1;
114
+ const listName = $cursor.node(listDepth).type.name;
115
+ return listName === "bulletList" || listName === "orderedList"
116
+ ? listItemDepth
117
+ : null;
118
+ }
119
+
120
+ function joinListItemBackward(editor: Editor): boolean {
121
+ const { selection } = editor.state;
122
+ if (!selection.empty) return false;
123
+
124
+ const { $from } = selection;
125
+ if ($from.parentOffset !== 0 || !$from.parent.isTextblock) return false;
126
+
127
+ const listItemDepth = findDirectListItemDepth($from);
128
+ if (listItemDepth === null) return false;
129
+
130
+ const listItem = $from.node(listItemDepth);
131
+ const childIndex = $from.index(listItemDepth);
132
+
133
+ if (childIndex > 0) {
134
+ const previousChild = listItem.child(childIndex - 1);
135
+ if (previousChild.isTextblock) {
136
+ return editor.commands.joinTextblockBackward();
137
+ }
138
+
139
+ // A paragraph following a nested structural block has no unambiguous
140
+ // one-key merge target. Consume Backspace instead of letting ListKeymap
141
+ // lift and split the entire list item.
142
+ return true;
143
+ }
144
+
145
+ const listDepth = listItemDepth - 1;
146
+ const itemIndex = $from.index(listDepth);
147
+ if (itemIndex === 0) {
148
+ // Let the official ListKeymap lift a first/nested-first item.
149
+ return false;
150
+ }
151
+
152
+ if (editor.commands.joinItemBackward()) return true;
153
+
154
+ // `joinItemBackward` cannot join when the previous item already owns a
155
+ // nested list. The official textblock join safely removes the current marker
156
+ // while preserving both subtrees in their existing document order.
157
+ if (editor.commands.joinBackward()) return true;
158
+
159
+ // A same-level previous item exists, so never fall through to the official
160
+ // lift behavior, which would split the surrounding list into top-level blocks.
161
+ return true;
162
+ }
163
+
164
+ function handleBackspace(editor: Editor): boolean {
165
+ const { selection } = editor.state;
166
+ if (
167
+ !selection.empty ||
168
+ !selection.$from.parent.isTextblock ||
169
+ selection.$from.parentOffset !== 0
170
+ ) {
171
+ return false;
172
+ }
173
+
174
+ // Let the official input-rule undo run before any Jant boundary behavior, so
175
+ // Backspace immediately after `- ` or `> ` reverses that structural change.
176
+ if (editor.commands.undoInputRule()) return true;
177
+
178
+ return (
179
+ joinListItemBackward(editor) ||
180
+ joinBlockquoteParagraphBoundary(editor, "backward")
181
+ );
182
+ }
183
+
184
+ export const StructuralKeymap = Extension.create({
185
+ name: "structuralKeymap",
186
+ priority: 1000,
187
+
188
+ addKeyboardShortcuts() {
189
+ return {
190
+ Backspace: ({ editor }) => handleBackspace(editor),
191
+ "Mod-Backspace": ({ editor }) => handleBackspace(editor),
192
+ "Shift-Backspace": ({ editor }) => handleBackspace(editor),
193
+ Delete: ({ editor }) =>
194
+ joinBlockquoteParagraphBoundary(editor, "forward"),
195
+ "Mod-Delete": ({ editor }) =>
196
+ joinBlockquoteParagraphBoundary(editor, "forward"),
197
+ Tab: ({ editor }) => {
198
+ if (!editor.isActive("listItem")) return false;
199
+
200
+ editor.commands.sinkListItem("listItem");
201
+ return true;
202
+ },
203
+ "Shift-Tab": ({ editor }) => {
204
+ if (!editor.isActive("listItem")) return false;
205
+
206
+ editor.commands.liftListItem("listItem");
207
+ return true;
208
+ },
209
+ };
210
+ },
211
+ });
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Tab Indent Extension
2
+ * Code Block Indent Extension
3
3
  *
4
- * Handles Tab / Shift-Tab in two contexts:
4
+ * Handles Tab / Shift-Tab inside code blocks by inserting or removing 2-space
5
+ * indentation. When text is selected across multiple lines, every selected
6
+ * line is indented or outdented.
5
7
  *
6
- * 1. **Code blocks** — inserts or removes 2-space indentation.
7
- * When text is selected across multiple lines, indents/outdents each line.
8
- * 2. **Lists** — sinks or lifts the current list item.
9
- *
10
- * Normal paragraphs are left alone so Tab keeps its default focus-navigation
11
- * behavior for keyboard accessibility.
8
+ * List indentation belongs to StructuralKeymap. Normal paragraphs are left
9
+ * alone so Tab keeps its default focus-navigation behavior for accessibility.
12
10
  */
13
11
 
14
12
  import { Extension } from "@tiptap/core";
@@ -16,18 +14,12 @@ import { TextSelection } from "@tiptap/pm/state";
16
14
 
17
15
  const INDENT = " "; // 2 spaces
18
16
 
19
- export const TabIndent = Extension.create({
20
- name: "tabIndent",
17
+ export const CodeBlockIndent = Extension.create({
18
+ name: "codeBlockIndent",
21
19
 
22
20
  addKeyboardShortcuts() {
23
21
  return {
24
22
  Tab: ({ editor }) => {
25
- // ── Lists ──────────────────────────────────────────────────
26
- if (editor.isActive("listItem")) {
27
- return editor.commands.sinkListItem("listItem");
28
- }
29
-
30
- // ── Code blocks ────────────────────────────────────────────
31
23
  if (editor.isActive("codeBlock")) {
32
24
  const { state, dispatch } = editor.view;
33
25
  const { selection } = state;
@@ -88,12 +80,6 @@ export const TabIndent = Extension.create({
88
80
  },
89
81
 
90
82
  "Shift-Tab": ({ editor }) => {
91
- // ── Lists ──────────────────────────────────────────────────
92
- if (editor.isActive("listItem")) {
93
- return editor.commands.liftListItem("listItem");
94
- }
95
-
96
- // ── Code blocks ────────────────────────────────────────────
97
83
  if (editor.isActive("codeBlock")) {
98
84
  const { state, dispatch } = editor.view;
99
85
  const { selection } = state;
@@ -0,0 +1,30 @@
1
+ /** Localized labels shared by compose SSR and client-side table UI. */
2
+ export interface TableControlLabels {
3
+ toolbarLabel: string;
4
+ addRowAbove: string;
5
+ addRowBelow: string;
6
+ addColumnBefore: string;
7
+ addColumnAfter: string;
8
+ options: string;
9
+ deleteRow: string;
10
+ deleteColumn: string;
11
+ toggleHeaderRow: string;
12
+ deleteTable: string;
13
+ sizePickerLabel: string;
14
+ insertTableSize: string;
15
+ }
16
+
17
+ export const DEFAULT_TABLE_CONTROL_LABELS: TableControlLabels = {
18
+ toolbarLabel: "Table controls",
19
+ addRowAbove: "Add row above",
20
+ addRowBelow: "Add row below",
21
+ addColumnBefore: "Add column before",
22
+ addColumnAfter: "Add column after",
23
+ options: "Table options",
24
+ deleteRow: "Delete row",
25
+ deleteColumn: "Delete column",
26
+ toggleHeaderRow: "Toggle header row",
27
+ deleteTable: "Delete table",
28
+ sizePickerLabel: "Choose table size",
29
+ insertTableSize: "Insert %rows% by %cols% table",
30
+ };