@notectl/core 1.0.3 → 1.0.5

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 (312) hide show
  1. package/README.md +51 -148
  2. package/dist/chunks/AttrRegistry-DTsHyDVA.mjs +16 -0
  3. package/dist/chunks/AttrRegistry-DTsHyDVA.mjs.map +1 -0
  4. package/dist/chunks/Commands-DLP85Mp5.mjs +461 -0
  5. package/dist/chunks/Commands-DLP85Mp5.mjs.map +1 -0
  6. package/dist/chunks/Document-CI3iCwEP.mjs +177 -0
  7. package/dist/chunks/Document-CI3iCwEP.mjs.map +1 -0
  8. package/dist/chunks/HTMLUtils-0wvCRvJc.mjs +7 -0
  9. package/dist/chunks/HTMLUtils-0wvCRvJc.mjs.map +1 -0
  10. package/dist/chunks/NodeResolver-D68ZLXti.mjs +81 -0
  11. package/dist/chunks/NodeResolver-D68ZLXti.mjs.map +1 -0
  12. package/dist/chunks/NodeSpec-NZVvcCuH.mjs +8 -0
  13. package/dist/chunks/NodeSpec-NZVvcCuH.mjs.map +1 -0
  14. package/dist/chunks/Plugin-B9OZ6Y4c.mjs +15 -0
  15. package/dist/chunks/Plugin-B9OZ6Y4c.mjs.map +1 -0
  16. package/dist/chunks/Selection-D5ZjXoB7.mjs +44 -0
  17. package/dist/chunks/Selection-D5ZjXoB7.mjs.map +1 -0
  18. package/dist/chunks/ToolbarItem-ugDymuXi.mjs +7 -0
  19. package/dist/chunks/ToolbarItem-ugDymuXi.mjs.map +1 -0
  20. package/dist/chunks/ToolbarPlugin-D5VqeBPT.mjs +398 -0
  21. package/dist/chunks/ToolbarPlugin-D5VqeBPT.mjs.map +1 -0
  22. package/dist/chunks/TypeBrands-C09nE2qO.mjs +27 -0
  23. package/dist/chunks/TypeBrands-C09nE2qO.mjs.map +1 -0
  24. package/dist/chunks/color-picker-Csf0wx5a.mjs +5 -0
  25. package/dist/chunks/color-picker-Csf0wx5a.mjs.map +1 -0
  26. package/dist/chunks/index-CPPb-Jah.mjs +569 -0
  27. package/dist/chunks/index-CPPb-Jah.mjs.map +1 -0
  28. package/dist/commands/Commands.d.ts +71 -0
  29. package/dist/commands/Commands.d.ts.map +1 -0
  30. package/dist/commands/PasteCommand.d.ts +9 -0
  31. package/dist/commands/PasteCommand.d.ts.map +1 -0
  32. package/dist/decorations/Decoration.d.ts +76 -0
  33. package/dist/decorations/Decoration.d.ts.map +1 -0
  34. package/dist/decorations/PositionMapping.d.ts +9 -0
  35. package/dist/decorations/PositionMapping.d.ts.map +1 -0
  36. package/dist/editor/Announcer.d.ts +12 -0
  37. package/dist/editor/Announcer.d.ts.map +1 -0
  38. package/dist/editor/DocumentParser.d.ts +5 -0
  39. package/dist/editor/DocumentParser.d.ts.map +1 -0
  40. package/dist/editor/DocumentSerializer.d.ts +5 -0
  41. package/dist/editor/DocumentSerializer.d.ts.map +1 -0
  42. package/dist/editor/EditorDOM.d.ts +18 -0
  43. package/dist/editor/EditorDOM.d.ts.map +1 -0
  44. package/dist/editor/EditorThemeController.d.ts +21 -0
  45. package/dist/editor/EditorThemeController.d.ts.map +1 -0
  46. package/dist/editor/NotectlEditor.d.ts +131 -0
  47. package/dist/editor/NotectlEditor.d.ts.map +1 -0
  48. package/dist/editor/styles/base.d.ts +3 -0
  49. package/dist/editor/styles/base.d.ts.map +1 -0
  50. package/dist/editor/styles/code-block.d.ts +3 -0
  51. package/dist/editor/styles/code-block.d.ts.map +1 -0
  52. package/dist/editor/styles/color-picker.d.ts +3 -0
  53. package/dist/editor/styles/color-picker.d.ts.map +1 -0
  54. package/dist/editor/styles/font-select.d.ts +3 -0
  55. package/dist/editor/styles/font-select.d.ts.map +1 -0
  56. package/dist/editor/styles/font-size-select.d.ts +3 -0
  57. package/dist/editor/styles/font-size-select.d.ts.map +1 -0
  58. package/dist/editor/styles/heading-select.d.ts +3 -0
  59. package/dist/editor/styles/heading-select.d.ts.map +1 -0
  60. package/dist/editor/styles/image.d.ts +3 -0
  61. package/dist/editor/styles/image.d.ts.map +1 -0
  62. package/dist/editor/styles/list.d.ts +3 -0
  63. package/dist/editor/styles/list.d.ts.map +1 -0
  64. package/dist/editor/styles/reduced-motion.d.ts +3 -0
  65. package/dist/editor/styles/reduced-motion.d.ts.map +1 -0
  66. package/dist/editor/styles/table.d.ts +3 -0
  67. package/dist/editor/styles/table.d.ts.map +1 -0
  68. package/dist/editor/styles/toolbar.d.ts +3 -0
  69. package/dist/editor/styles/toolbar.d.ts.map +1 -0
  70. package/dist/editor/styles.d.ts +12 -0
  71. package/dist/editor/styles.d.ts.map +1 -0
  72. package/dist/editor/theme/ThemeEngine.d.ts +6 -0
  73. package/dist/editor/theme/ThemeEngine.d.ts.map +1 -0
  74. package/dist/editor/theme/ThemeTokens.d.ts +73 -0
  75. package/dist/editor/theme/ThemeTokens.d.ts.map +1 -0
  76. package/dist/fonts.d.ts +17 -0
  77. package/dist/fonts.d.ts.map +1 -0
  78. package/dist/fonts.mjs +7 -0
  79. package/dist/fonts.mjs.map +1 -0
  80. package/dist/index.d.ts +81 -2360
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/input/ClipboardHandler.d.ts +29 -0
  83. package/dist/input/ClipboardHandler.d.ts.map +1 -0
  84. package/dist/input/InputHandler.d.ts +32 -0
  85. package/dist/input/InputHandler.d.ts.map +1 -0
  86. package/dist/input/InputRule.d.ts +9 -0
  87. package/dist/input/InputRule.d.ts.map +1 -0
  88. package/dist/input/InternalClipboard.d.ts +23 -0
  89. package/dist/input/InternalClipboard.d.ts.map +1 -0
  90. package/dist/input/KeyboardHandler.d.ts +42 -0
  91. package/dist/input/KeyboardHandler.d.ts.map +1 -0
  92. package/dist/input/Keymap.d.ts +11 -0
  93. package/dist/input/Keymap.d.ts.map +1 -0
  94. package/dist/input/PasteHandler.d.ts +38 -0
  95. package/dist/input/PasteHandler.d.ts.map +1 -0
  96. package/dist/input/SourceNormalizer.d.ts +7 -0
  97. package/dist/input/SourceNormalizer.d.ts.map +1 -0
  98. package/dist/model/AttrRegistry.d.ts +39 -0
  99. package/dist/model/AttrRegistry.d.ts.map +1 -0
  100. package/dist/model/BuiltinSpecs.d.ts +4 -0
  101. package/dist/model/BuiltinSpecs.d.ts.map +1 -0
  102. package/dist/model/ContentModel.d.ts +12 -0
  103. package/dist/model/ContentModel.d.ts.map +1 -0
  104. package/dist/model/ContentSlice.d.ts +17 -0
  105. package/dist/model/ContentSlice.d.ts.map +1 -0
  106. package/dist/model/Document.d.ts +127 -0
  107. package/dist/model/Document.d.ts.map +1 -0
  108. package/dist/model/HTMLParser.d.ts +36 -0
  109. package/dist/model/HTMLParser.d.ts.map +1 -0
  110. package/dist/model/HTMLUtils.d.ts +6 -0
  111. package/dist/model/HTMLUtils.d.ts.map +1 -0
  112. package/dist/model/InlineNodeSpec.d.ts +20 -0
  113. package/dist/model/InlineNodeSpec.d.ts.map +1 -0
  114. package/dist/model/MarkSpec.d.ts +22 -0
  115. package/dist/model/MarkSpec.d.ts.map +1 -0
  116. package/dist/model/NodeResolver.d.ts +36 -0
  117. package/dist/model/NodeResolver.d.ts.map +1 -0
  118. package/dist/model/NodeSpec.d.ts +67 -0
  119. package/dist/model/NodeSpec.d.ts.map +1 -0
  120. package/dist/model/ParseRule.d.ts +11 -0
  121. package/dist/model/ParseRule.d.ts.map +1 -0
  122. package/dist/model/SanitizeConfig.d.ts +9 -0
  123. package/dist/model/SanitizeConfig.d.ts.map +1 -0
  124. package/dist/model/Schema.d.ts +17 -0
  125. package/dist/model/Schema.d.ts.map +1 -0
  126. package/dist/model/SchemaRegistry.d.ts +81 -0
  127. package/dist/model/SchemaRegistry.d.ts.map +1 -0
  128. package/dist/model/Selection.d.ts +52 -0
  129. package/dist/model/Selection.d.ts.map +1 -0
  130. package/dist/model/TypeBrands.d.ts +28 -0
  131. package/dist/model/TypeBrands.d.ts.map +1 -0
  132. package/dist/notectl-core.mjs +2576 -11104
  133. package/dist/notectl-core.mjs.map +1 -1
  134. package/dist/notectl-core.umd.js +6810 -0
  135. package/dist/notectl-core.umd.js.map +1 -0
  136. package/dist/plugins/EventBus.d.ts +13 -0
  137. package/dist/plugins/EventBus.d.ts.map +1 -0
  138. package/dist/plugins/Plugin.d.ts +85 -0
  139. package/dist/plugins/Plugin.d.ts.map +1 -0
  140. package/dist/plugins/PluginManager.d.ts +66 -0
  141. package/dist/plugins/PluginManager.d.ts.map +1 -0
  142. package/dist/plugins/alignment/AlignmentPlugin.d.ts +48 -0
  143. package/dist/plugins/alignment/AlignmentPlugin.d.ts.map +1 -0
  144. package/dist/plugins/alignment/index.d.ts +2 -0
  145. package/dist/plugins/alignment/index.d.ts.map +1 -0
  146. package/dist/plugins/alignment.mjs +92 -0
  147. package/dist/plugins/alignment.mjs.map +1 -0
  148. package/dist/plugins/blockquote/BlockquotePlugin.d.ts +30 -0
  149. package/dist/plugins/blockquote/BlockquotePlugin.d.ts.map +1 -0
  150. package/dist/plugins/blockquote/index.d.ts +2 -0
  151. package/dist/plugins/blockquote/index.d.ts.map +1 -0
  152. package/dist/plugins/blockquote.mjs +58 -0
  153. package/dist/plugins/blockquote.mjs.map +1 -0
  154. package/dist/plugins/code-block/CodeBlockNodeView.d.ts +5 -0
  155. package/dist/plugins/code-block/CodeBlockNodeView.d.ts.map +1 -0
  156. package/dist/plugins/code-block/CodeBlockPlugin.d.ts +135 -0
  157. package/dist/plugins/code-block/CodeBlockPlugin.d.ts.map +1 -0
  158. package/dist/plugins/code-block/index.d.ts +2 -0
  159. package/dist/plugins/code-block/index.d.ts.map +1 -0
  160. package/dist/plugins/code-block.mjs +6 -0
  161. package/dist/plugins/code-block.mjs.map +1 -0
  162. package/dist/plugins/font/FontPlugin.d.ts +85 -0
  163. package/dist/plugins/font/FontPlugin.d.ts.map +1 -0
  164. package/dist/plugins/font/StarterFonts.d.ts +10 -0
  165. package/dist/plugins/font/StarterFonts.d.ts.map +1 -0
  166. package/dist/plugins/font/index.d.ts +2 -0
  167. package/dist/plugins/font/index.d.ts.map +1 -0
  168. package/dist/plugins/font-size/FontSizePlugin.d.ts +65 -0
  169. package/dist/plugins/font-size/FontSizePlugin.d.ts.map +1 -0
  170. package/dist/plugins/font-size/index.d.ts +2 -0
  171. package/dist/plugins/font-size/index.d.ts.map +1 -0
  172. package/dist/plugins/font-size.mjs +220 -0
  173. package/dist/plugins/font-size.mjs.map +1 -0
  174. package/dist/plugins/font.mjs +186 -0
  175. package/dist/plugins/font.mjs.map +1 -0
  176. package/dist/plugins/hard-break/HardBreakPlugin.d.ts +16 -0
  177. package/dist/plugins/hard-break/HardBreakPlugin.d.ts.map +1 -0
  178. package/dist/plugins/hard-break/index.d.ts +2 -0
  179. package/dist/plugins/hard-break/index.d.ts.map +1 -0
  180. package/dist/plugins/hard-break.mjs +28 -0
  181. package/dist/plugins/hard-break.mjs.map +1 -0
  182. package/dist/plugins/heading/BlockTypePickerEntry.d.ts +20 -0
  183. package/dist/plugins/heading/BlockTypePickerEntry.d.ts.map +1 -0
  184. package/dist/plugins/heading/HeadingPlugin.d.ts +78 -0
  185. package/dist/plugins/heading/HeadingPlugin.d.ts.map +1 -0
  186. package/dist/plugins/heading/index.d.ts +3 -0
  187. package/dist/plugins/heading/index.d.ts.map +1 -0
  188. package/dist/plugins/heading.mjs +188 -0
  189. package/dist/plugins/heading.mjs.map +1 -0
  190. package/dist/plugins/highlight/HighlightPlugin.d.ts +37 -0
  191. package/dist/plugins/highlight/HighlightPlugin.d.ts.map +1 -0
  192. package/dist/plugins/highlight/index.d.ts +2 -0
  193. package/dist/plugins/highlight/index.d.ts.map +1 -0
  194. package/dist/plugins/highlight.mjs +126 -0
  195. package/dist/plugins/highlight.mjs.map +1 -0
  196. package/dist/plugins/horizontal-rule/HorizontalRulePlugin.d.ts +29 -0
  197. package/dist/plugins/horizontal-rule/HorizontalRulePlugin.d.ts.map +1 -0
  198. package/dist/plugins/horizontal-rule/index.d.ts +2 -0
  199. package/dist/plugins/horizontal-rule/index.d.ts.map +1 -0
  200. package/dist/plugins/horizontal-rule.mjs +59 -0
  201. package/dist/plugins/horizontal-rule.mjs.map +1 -0
  202. package/dist/plugins/image/ImageCommands.d.ts +26 -0
  203. package/dist/plugins/image/ImageCommands.d.ts.map +1 -0
  204. package/dist/plugins/image/ImageNodeView.d.ts +6 -0
  205. package/dist/plugins/image/ImageNodeView.d.ts.map +1 -0
  206. package/dist/plugins/image/ImagePlugin.d.ts +43 -0
  207. package/dist/plugins/image/ImagePlugin.d.ts.map +1 -0
  208. package/dist/plugins/image/ImageUpload.d.ts +48 -0
  209. package/dist/plugins/image/ImageUpload.d.ts.map +1 -0
  210. package/dist/plugins/image/index.d.ts +3 -0
  211. package/dist/plugins/image/index.d.ts.map +1 -0
  212. package/dist/plugins/image.mjs +381 -0
  213. package/dist/plugins/image.mjs.map +1 -0
  214. package/dist/plugins/link/LinkPlugin.d.ts +32 -0
  215. package/dist/plugins/link/LinkPlugin.d.ts.map +1 -0
  216. package/dist/plugins/link/index.d.ts +2 -0
  217. package/dist/plugins/link/index.d.ts.map +1 -0
  218. package/dist/plugins/link.mjs +127 -0
  219. package/dist/plugins/link.mjs.map +1 -0
  220. package/dist/plugins/list/ListPlugin.d.ts +51 -0
  221. package/dist/plugins/list/ListPlugin.d.ts.map +1 -0
  222. package/dist/plugins/list/index.d.ts +2 -0
  223. package/dist/plugins/list/index.d.ts.map +1 -0
  224. package/dist/plugins/list.mjs +193 -0
  225. package/dist/plugins/list.mjs.map +1 -0
  226. package/dist/plugins/strikethrough/StrikethroughPlugin.d.ts +23 -0
  227. package/dist/plugins/strikethrough/StrikethroughPlugin.d.ts.map +1 -0
  228. package/dist/plugins/strikethrough/index.d.ts +2 -0
  229. package/dist/plugins/strikethrough/index.d.ts.map +1 -0
  230. package/dist/plugins/strikethrough.mjs +35 -0
  231. package/dist/plugins/strikethrough.mjs.map +1 -0
  232. package/dist/plugins/super-sub/SuperSubPlugin.d.ts +44 -0
  233. package/dist/plugins/super-sub/SuperSubPlugin.d.ts.map +1 -0
  234. package/dist/plugins/super-sub/index.d.ts +2 -0
  235. package/dist/plugins/super-sub/index.d.ts.map +1 -0
  236. package/dist/plugins/super-sub.mjs +81 -0
  237. package/dist/plugins/super-sub.mjs.map +1 -0
  238. package/dist/plugins/table/TableCommands.d.ts +50 -0
  239. package/dist/plugins/table/TableCommands.d.ts.map +1 -0
  240. package/dist/plugins/table/TableControls.d.ts +10 -0
  241. package/dist/plugins/table/TableControls.d.ts.map +1 -0
  242. package/dist/plugins/table/TableControlsDOM.d.ts +18 -0
  243. package/dist/plugins/table/TableControlsDOM.d.ts.map +1 -0
  244. package/dist/plugins/table/TableControlsLayout.d.ts +34 -0
  245. package/dist/plugins/table/TableControlsLayout.d.ts.map +1 -0
  246. package/dist/plugins/table/TableHelpers.d.ts +39 -0
  247. package/dist/plugins/table/TableHelpers.d.ts.map +1 -0
  248. package/dist/plugins/table/TableNavigation.d.ts +4 -0
  249. package/dist/plugins/table/TableNavigation.d.ts.map +1 -0
  250. package/dist/plugins/table/TableNodeViews.d.ts +18 -0
  251. package/dist/plugins/table/TableNodeViews.d.ts.map +1 -0
  252. package/dist/plugins/table/TablePlugin.d.ts +39 -0
  253. package/dist/plugins/table/TablePlugin.d.ts.map +1 -0
  254. package/dist/plugins/table/TableSelection.d.ts +26 -0
  255. package/dist/plugins/table/TableSelection.d.ts.map +1 -0
  256. package/dist/plugins/table/TableTestUtils.d.ts +45 -0
  257. package/dist/plugins/table/TableTestUtils.d.ts.map +1 -0
  258. package/dist/plugins/table/index.d.ts +4 -0
  259. package/dist/plugins/table/index.d.ts.map +1 -0
  260. package/dist/plugins/table.mjs +663 -0
  261. package/dist/plugins/table.mjs.map +1 -0
  262. package/dist/plugins/text-color/TextColorPlugin.d.ts +37 -0
  263. package/dist/plugins/text-color/TextColorPlugin.d.ts.map +1 -0
  264. package/dist/plugins/text-color/index.d.ts +2 -0
  265. package/dist/plugins/text-color/index.d.ts.map +1 -0
  266. package/dist/plugins/text-color.mjs +126 -0
  267. package/dist/plugins/text-color.mjs.map +1 -0
  268. package/dist/plugins/text-formatting/TextFormattingPlugin.d.ts +34 -0
  269. package/dist/plugins/text-formatting/TextFormattingPlugin.d.ts.map +1 -0
  270. package/dist/plugins/text-formatting/index.d.ts +2 -0
  271. package/dist/plugins/text-formatting/index.d.ts.map +1 -0
  272. package/dist/plugins/text-formatting.mjs +52 -0
  273. package/dist/plugins/text-formatting.mjs.map +1 -0
  274. package/dist/plugins/toolbar/ToolbarItem.d.ts +66 -0
  275. package/dist/plugins/toolbar/ToolbarItem.d.ts.map +1 -0
  276. package/dist/plugins/toolbar/ToolbarKeyboardNav.d.ts +25 -0
  277. package/dist/plugins/toolbar/ToolbarKeyboardNav.d.ts.map +1 -0
  278. package/dist/plugins/toolbar/ToolbarPlugin.d.ts +47 -0
  279. package/dist/plugins/toolbar/ToolbarPlugin.d.ts.map +1 -0
  280. package/dist/plugins/toolbar/ToolbarPopupController.d.ts +25 -0
  281. package/dist/plugins/toolbar/ToolbarPopupController.d.ts.map +1 -0
  282. package/dist/plugins/toolbar/ToolbarRenderers.d.ts +19 -0
  283. package/dist/plugins/toolbar/ToolbarRenderers.d.ts.map +1 -0
  284. package/dist/plugins/toolbar/ToolbarTooltip.d.ts +17 -0
  285. package/dist/plugins/toolbar/ToolbarTooltip.d.ts.map +1 -0
  286. package/dist/plugins/toolbar/index.d.ts +4 -0
  287. package/dist/plugins/toolbar/index.d.ts.map +1 -0
  288. package/dist/plugins/toolbar.mjs +8 -0
  289. package/dist/plugins/toolbar.mjs.map +1 -0
  290. package/dist/state/EditorState.d.ts +40 -0
  291. package/dist/state/EditorState.d.ts.map +1 -0
  292. package/dist/state/History.d.ts +38 -0
  293. package/dist/state/History.d.ts.map +1 -0
  294. package/dist/state/StepApplication.d.ts +5 -0
  295. package/dist/state/StepApplication.d.ts.map +1 -0
  296. package/dist/state/Transaction.d.ts +185 -0
  297. package/dist/state/Transaction.d.ts.map +1 -0
  298. package/dist/test/PluginTestUtils.d.ts +127 -0
  299. package/dist/test/PluginTestUtils.d.ts.map +1 -0
  300. package/dist/test/TestUtils.d.ts +153 -0
  301. package/dist/test/TestUtils.d.ts.map +1 -0
  302. package/dist/view/EditorView.d.ts +74 -0
  303. package/dist/view/EditorView.d.ts.map +1 -0
  304. package/dist/view/NodeView.d.ts +18 -0
  305. package/dist/view/NodeView.d.ts.map +1 -0
  306. package/dist/view/Reconciler.d.ts +23 -0
  307. package/dist/view/Reconciler.d.ts.map +1 -0
  308. package/dist/view/SelectionSync.d.ts +8 -0
  309. package/dist/view/SelectionSync.d.ts.map +1 -0
  310. package/package.json +112 -11
  311. package/dist/notectl-core.js +0 -1463
  312. package/dist/notectl-core.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.mjs","sources":["../../src/plugins/image/ImageCommands.ts","../../src/plugins/image/ImageNodeView.ts","../../src/plugins/image/ImageUpload.ts","../../src/plugins/image/ImagePlugin.ts","../../src/editor/styles/image.ts"],"sourcesContent":["/**\n * Image plugin commands: insert, remove, and attribute updates.\n * Supports both root-level and nested contexts (e.g. images inside table cells).\n */\n\nimport type { BlockAttrs, BlockNode } from '../../model/Document.js';\nimport { createBlockNode, getBlockChildren, isBlockNode } from '../../model/Document.js';\nimport {\n\tcreateCollapsedSelection,\n\tcreateNodeSelection,\n\tisNodeSelection,\n} from '../../model/Selection.js';\nimport type { BlockId } from '../../model/TypeBrands.js';\nimport { nodeType } from '../../model/TypeBrands.js';\nimport type { EditorState } from '../../state/EditorState.js';\nimport type { PluginContext } from '../Plugin.js';\nimport type { ImageAttrs } from './ImageUpload.js';\n\n/**\n * Inserts an image block. When the cursor is inside a table cell,\n * the image is inserted as a child of the cell. Otherwise it is\n * inserted after the current block at document root.\n */\nexport function insertImage(\n\tcontext: PluginContext,\n\tattrs: Partial<ImageAttrs> & { readonly src: string },\n): boolean {\n\tconst state = context.getState();\n\tconst sel = state.selection;\n\n\tconst imageAttrs: BlockAttrs = {\n\t\tsrc: attrs.src,\n\t\talt: attrs.alt ?? '',\n\t\talign: attrs.align ?? 'center',\n\t\t...(attrs.width !== undefined ? { width: attrs.width } : {}),\n\t\t...(attrs.height !== undefined ? { height: attrs.height } : {}),\n\t};\n\n\tconst anchorBlockId: BlockId = isNodeSelection(sel) ? sel.nodeId : sel.anchor.blockId;\n\n\t// Check if we're inside a table cell\n\tconst cellId: BlockId | undefined = findTableCellAncestor(state, anchorBlockId);\n\tif (cellId) {\n\t\treturn insertImageIntoCell(state, context, cellId, imageAttrs);\n\t}\n\n\treturn insertImageAtRoot(state, context, anchorBlockId, imageAttrs);\n}\n\n/** Finds a table_cell ancestor for the given block (or the block itself). */\nfunction findTableCellAncestor(state: EditorState, bid: BlockId): BlockId | undefined {\n\tconst block: BlockNode | undefined = state.getBlock(bid);\n\tif (block?.type === 'table_cell') return bid;\n\n\tconst path: BlockId[] | undefined = state.getNodePath(bid);\n\tif (!path) return undefined;\n\n\tfor (const id of path) {\n\t\tconst node: BlockNode | undefined = state.getBlock(id);\n\t\tif (node?.type === 'table_cell') return id;\n\t}\n\treturn undefined;\n}\n\n/** Inserts an image at document root after the anchor block. */\nfunction insertImageAtRoot(\n\tstate: EditorState,\n\tcontext: PluginContext,\n\tanchorBlockId: BlockId,\n\timageAttrs: BlockAttrs,\n): boolean {\n\tconst blockIndex: number = state.doc.children.findIndex((b) => b.id === anchorBlockId);\n\tif (blockIndex === -1) return false;\n\n\tconst imageBlock: BlockNode = createBlockNode(nodeType('image'), [], undefined, imageAttrs);\n\tconst newParagraph: BlockNode = createBlockNode(nodeType('paragraph'));\n\n\tconst tr = state\n\t\t.transaction('command')\n\t\t.insertNode([], blockIndex + 1, imageBlock)\n\t\t.insertNode([], blockIndex + 2, newParagraph)\n\t\t.setSelection(createNodeSelection(imageBlock.id, []))\n\t\t.build();\n\n\tcontext.dispatch(tr);\n\treturn true;\n}\n\n/** Inserts an image as a child of a table cell. */\nfunction insertImageIntoCell(\n\tstate: EditorState,\n\tcontext: PluginContext,\n\tcellId: BlockId,\n\timageAttrs: BlockAttrs,\n): boolean {\n\tconst cellPath: BlockId[] | undefined = state.getNodePath(cellId);\n\tif (!cellPath) return false;\n\n\tconst imageBlock: BlockNode = createBlockNode(nodeType('image'), [], undefined, imageAttrs);\n\n\t// Remove existing block children first (if any)\n\tconst cell: BlockNode | undefined = state.getBlock(cellId);\n\tif (!cell) return false;\n\n\tconst builder = state.transaction('command');\n\tconst blockChildren: readonly BlockNode[] = getBlockChildren(cell);\n\tfor (let i = blockChildren.length - 1; i >= 0; i--) {\n\t\t// Find the raw index in children array\n\t\tconst rawIndex: number = cell.children.findIndex(\n\t\t\t(c) => isBlockNode(c) && c.id === blockChildren[i]?.id,\n\t\t);\n\t\tif (rawIndex !== -1) {\n\t\t\tbuilder.removeNode(cellPath, rawIndex);\n\t\t}\n\t}\n\n\t// Insert image at index 0 (before any text nodes)\n\tbuilder.insertNode(cellPath, 0, imageBlock);\n\tbuilder.setSelection(createNodeSelection(imageBlock.id, [...cellPath, imageBlock.id]));\n\n\tcontext.dispatch(builder.build());\n\treturn true;\n}\n\n/** Removes the currently selected image block (works for nested images too). */\nexport function removeImage(context: PluginContext): boolean {\n\tconst state = context.getState();\n\tconst sel = state.selection;\n\tif (!isNodeSelection(sel)) return false;\n\n\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\tif (!block || block.type !== 'image') return false;\n\n\tconst nodePath: BlockId[] | undefined = state.getNodePath(sel.nodeId);\n\tif (!nodePath || nodePath.length === 0) return false;\n\n\t// parentPath = all IDs except the last (the image itself)\n\tconst parentPath: BlockId[] = nodePath.slice(0, -1);\n\n\t// Find the index of the image in its parent's children\n\tlet parent: BlockNode | { readonly children: readonly BlockNode[] } | undefined;\n\tif (parentPath.length === 0) {\n\t\tparent = state.doc;\n\t} else {\n\t\tconst parentId: BlockId | undefined = parentPath[parentPath.length - 1];\n\t\tparent = parentId ? state.getBlock(parentId) : undefined;\n\t}\n\tif (!parent) return false;\n\n\tconst imageIndex: number = parent.children.findIndex(\n\t\t(c) => isBlockNode(c) && c.id === sel.nodeId,\n\t);\n\tif (imageIndex === -1) return false;\n\n\t// Determine where to place the cursor after removal\n\tconst selectionTarget: BlockId | undefined = findSelectionTargetAfterRemoval(\n\t\tstate,\n\t\tparentPath,\n\t\tsel.nodeId,\n\t);\n\tif (!selectionTarget) return false;\n\n\tconst tr = state\n\t\t.transaction('command')\n\t\t.removeNode(parentPath, imageIndex)\n\t\t.setSelection(createCollapsedSelection(selectionTarget, 0))\n\t\t.build();\n\n\tcontext.dispatch(tr);\n\treturn true;\n}\n\n/** Finds a suitable block to select after removing an image. */\nfunction findSelectionTargetAfterRemoval(\n\tstate: EditorState,\n\tparentPath: readonly BlockId[],\n\tremovedId: BlockId,\n): BlockId | undefined {\n\tif (parentPath.length === 0) {\n\t\t// Root level: select adjacent block\n\t\tconst rootChildren: readonly BlockNode[] = state.doc.children;\n\t\tconst idx: number = rootChildren.findIndex((b) => b.id === removedId);\n\t\tconst adjacent: BlockNode | undefined = rootChildren[idx + 1] ?? rootChildren[idx - 1];\n\t\treturn adjacent?.id;\n\t}\n\n\t// Nested (e.g. inside table cell): select the cell itself\n\tconst cellId: BlockId | undefined = parentPath[parentPath.length - 1];\n\treturn cellId;\n}\n\n/** Updates attributes on the currently selected image block. */\nexport function setImageAttr(context: PluginContext, attrs: Partial<ImageAttrs>): boolean {\n\tconst state = context.getState();\n\tconst sel = state.selection;\n\tif (!isNodeSelection(sel)) return false;\n\n\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\tif (!block || block.type !== 'image') return false;\n\n\tconst path: BlockId[] | undefined = state.getNodePath(sel.nodeId);\n\tif (!path) return false;\n\n\tconst merged: BlockAttrs = { ...(block.attrs ?? {}), ...attrs };\n\tconst tr = state.transaction('command').setNodeAttr(path, merged).build();\n\n\tcontext.dispatch(tr);\n\treturn true;\n}\n\n/** Minimum image width in pixels (shared with NodeView). */\nexport const MIN_IMAGE_WIDTH = 50;\n\n/**\n * Resizes the currently selected image by a pixel delta, preserving aspect ratio.\n * Returns false if no image is selected or the image has no width/height attributes.\n */\nexport function resizeImageByDelta(\n\tcontext: PluginContext,\n\tdelta: number,\n\tmaxWidth: number,\n): boolean {\n\tconst state = context.getState();\n\tconst sel = state.selection;\n\tif (!isNodeSelection(sel)) return false;\n\n\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\tif (!block || block.type !== 'image') return false;\n\n\tconst currentWidth: number | undefined = block.attrs?.width as number | undefined;\n\tconst currentHeight: number | undefined = block.attrs?.height as number | undefined;\n\tif (currentWidth === undefined || currentHeight === undefined) return false;\n\n\tconst aspectRatio: number = currentHeight > 0 ? currentWidth / currentHeight : 1;\n\tconst clampedWidth: number = Math.max(MIN_IMAGE_WIDTH, Math.min(maxWidth, currentWidth + delta));\n\tconst newHeight: number = Math.round(clampedWidth / aspectRatio);\n\n\tif (clampedWidth === currentWidth && newHeight === currentHeight) return false;\n\n\treturn setImageAttr(context, { width: clampedWidth, height: newHeight });\n}\n\n/** Resets the currently selected image to its natural size by removing width/height. */\nexport function resetImageSize(context: PluginContext): boolean {\n\tconst state = context.getState();\n\tconst sel = state.selection;\n\tif (!isNodeSelection(sel)) return false;\n\n\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\tif (!block || block.type !== 'image') return false;\n\n\tconst path: BlockId[] | undefined = state.getNodePath(sel.nodeId);\n\tif (!path) return false;\n\n\tconst attrs: BlockAttrs = { ...(block.attrs ?? {}) };\n\tconst cleaned: BlockAttrs = Object.fromEntries(\n\t\tObject.entries(attrs).filter(([k]) => k !== 'width' && k !== 'height'),\n\t);\n\n\tconst tr = state.transaction('command').setNodeAttr(path, cleaned).build();\n\tcontext.dispatch(tr);\n\treturn true;\n}\n\n/** Registers all image commands on the plugin context. */\nexport function registerImageCommands(context: PluginContext): void {\n\tcontext.registerCommand('insertImage', () => {\n\t\treturn insertImage(context, { src: '' });\n\t});\n\n\tcontext.registerCommand('removeImage', () => {\n\t\treturn removeImage(context);\n\t});\n}\n","/**\n * NodeView factory for image blocks.\n * Renders <figure> with <img>, handles selection state, upload overlay,\n * resize with 4 corner handles + live size indicator, and a keyboard-hint\n * showing resize shortcuts when the image is selected.\n */\n\nimport type { BlockAttrs, BlockNode } from '../../model/Document.js';\nimport type { BlockId } from '../../model/TypeBrands.js';\nimport type { EditorState } from '../../state/EditorState.js';\nimport type { Transaction } from '../../state/Transaction.js';\nimport type { NodeView, NodeViewFactory } from '../../view/NodeView.js';\nimport { formatShortcut } from '../toolbar/ToolbarItem.js';\nimport type { ImageKeymap, ImagePluginConfig, UploadState } from './ImageUpload.js';\n\nconst ALIGNMENT_CLASSES: Record<string, string> = {\n\tleft: 'notectl-image--left',\n\tcenter: 'notectl-image--center',\n\tright: 'notectl-image--right',\n};\n\nconst MIN_IMAGE_WIDTH = 50;\n\ntype HandlePosition = 'nw' | 'ne' | 'sw' | 'se';\n\nconst HANDLE_CURSORS: Readonly<Record<HandlePosition, string>> = {\n\tnw: 'nwse-resize',\n\tne: 'nesw-resize',\n\tsw: 'nesw-resize',\n\tse: 'nwse-resize',\n};\n\n/** +1 means dragging right increases width; -1 means dragging left does. */\nconst HANDLE_X_SIGN: Readonly<Record<HandlePosition, 1 | -1>> = {\n\tnw: -1,\n\tne: 1,\n\tsw: -1,\n\tse: 1,\n};\n\nconst HANDLE_LABELS: Readonly<Record<HandlePosition, string>> = {\n\tnw: 'Resize top-left',\n\tne: 'Resize top-right',\n\tsw: 'Resize bottom-left',\n\tse: 'Resize bottom-right',\n};\n\n// --- Global cursor override during resize ---\n\nlet activeCursorStyle: HTMLStyleElement | null = null;\n\nfunction setGlobalResizeCursor(cursor: string): void {\n\tclearGlobalResizeCursor();\n\tactiveCursorStyle = document.createElement('style');\n\tactiveCursorStyle.textContent = `*{cursor:${cursor}!important;user-select:none!important}`;\n\tdocument.head.appendChild(activeCursorStyle);\n}\n\nfunction clearGlobalResizeCursor(): void {\n\tif (activeCursorStyle) {\n\t\tactiveCursorStyle.remove();\n\t\tactiveCursorStyle = null;\n\t}\n}\n\n/** Builds the keyboard-hint text from the resolved keymap. */\nfunction buildKeyboardHintText(\n\tresolvedKeymap: Readonly<Record<keyof ImageKeymap, string | null>>,\n): string {\n\tconst shrink: string | null = resolvedKeymap.shrinkWidth ?? null;\n\tconst grow: string | null = resolvedKeymap.growWidth ?? null;\n\tif (!shrink || !grow) return '';\n\treturn `${formatShortcut(shrink)} / ${formatShortcut(grow)} to resize`;\n}\n\n/** Creates a NodeViewFactory for image blocks. */\nexport function createImageNodeViewFactory(\n\tconfig: ImagePluginConfig,\n\tuploadStates: Map<BlockId, UploadState>,\n\tresolvedKeymap?: Readonly<Record<keyof ImageKeymap, string | null>>,\n): NodeViewFactory {\n\treturn (\n\t\tnode: BlockNode,\n\t\tgetState: () => EditorState,\n\t\tdispatch: (tr: Transaction) => void,\n\t): NodeView => {\n\t\t// --- DOM Construction ---\n\t\tconst figure: HTMLElement = document.createElement('figure');\n\t\tfigure.className = 'notectl-image';\n\t\tfigure.setAttribute('data-block-id', node.id);\n\t\tfigure.setAttribute('data-void', 'true');\n\t\tfigure.setAttribute('data-selectable', 'true');\n\n\t\tconst container: HTMLDivElement = document.createElement('div');\n\t\tcontainer.className = 'notectl-image__container';\n\n\t\tconst img: HTMLImageElement = document.createElement('img');\n\t\timg.className = 'notectl-image__img';\n\t\timg.draggable = false;\n\n\t\tconst overlay: HTMLDivElement = document.createElement('div');\n\t\toverlay.className = 'notectl-image__overlay';\n\n\t\tcontainer.appendChild(img);\n\t\tcontainer.appendChild(overlay);\n\t\tfigure.appendChild(container);\n\n\t\tlet currentNodeId: BlockId = node.id;\n\t\tlet resizeOverlay: HTMLDivElement | null = null;\n\n\t\t// --- ARIA Label ---\n\n\t\tfunction updateAriaLabel(n: BlockNode): void {\n\t\t\tconst alt: string = (n.attrs?.alt as string | undefined) ?? '';\n\t\t\tconst width: number | undefined = n.attrs?.width as number | undefined;\n\t\t\tconst height: number | undefined = n.attrs?.height as number | undefined;\n\n\t\t\tconst parts: string[] = [];\n\t\t\tif (alt) {\n\t\t\t\tparts.push(alt);\n\t\t\t} else {\n\t\t\t\tparts.push('Image');\n\t\t\t}\n\t\t\tif (width !== undefined && height !== undefined) {\n\t\t\t\tparts.push(`${width} by ${height} pixels`);\n\t\t\t}\n\t\t\tfigure.setAttribute('aria-label', parts.join(', '));\n\t\t}\n\n\t\t// --- Attribute Application ---\n\n\t\tfunction applyAttrs(n: BlockNode): void {\n\t\t\tconst src: string = (n.attrs?.src as string | undefined) ?? '';\n\t\t\tconst alt: string = (n.attrs?.alt as string | undefined) ?? '';\n\t\t\tconst width: number | undefined = n.attrs?.width as number | undefined;\n\t\t\tconst height: number | undefined = n.attrs?.height as number | undefined;\n\t\t\tconst align: string = (n.attrs?.align as string | undefined) ?? 'center';\n\n\t\t\t// Only update src when it changes to avoid re-triggering load events\n\t\t\tif (img.src !== src) img.src = src;\n\t\t\timg.alt = alt;\n\n\t\t\tif (width !== undefined) {\n\t\t\t\timg.style.width = `${width}px`;\n\t\t\t} else {\n\t\t\t\timg.style.width = '';\n\t\t\t}\n\t\t\tif (height !== undefined) {\n\t\t\t\timg.style.height = `${height}px`;\n\t\t\t} else {\n\t\t\t\timg.style.height = '';\n\t\t\t}\n\n\t\t\tfor (const cls of Object.values(ALIGNMENT_CLASSES)) {\n\t\t\t\tfigure.classList.remove(cls);\n\t\t\t}\n\t\t\tconst alignClass: string | undefined = ALIGNMENT_CLASSES[align];\n\t\t\tif (alignClass) {\n\t\t\t\tfigure.classList.add(alignClass);\n\t\t\t}\n\n\t\t\tconst uploadState: UploadState = uploadStates.get(n.id) ?? 'idle';\n\t\t\tconst isUploading: boolean = uploadState === 'uploading';\n\t\t\toverlay.classList.toggle('notectl-image__overlay--uploading', isUploading);\n\t\t\toverlay.classList.toggle('notectl-image__overlay--error', uploadState === 'error');\n\n\t\t\tif (isUploading) {\n\t\t\t\toverlay.textContent = 'Uploading...';\n\t\t\t} else if (uploadState === 'error') {\n\t\t\t\toverlay.textContent = 'Upload failed';\n\t\t\t} else {\n\t\t\t\toverlay.textContent = '';\n\t\t\t}\n\n\t\t\tif (isUploading) {\n\t\t\t\tfigure.setAttribute('aria-busy', 'true');\n\t\t\t} else {\n\t\t\t\tfigure.removeAttribute('aria-busy');\n\t\t\t}\n\n\t\t\tupdateAriaLabel(n);\n\t\t}\n\n\t\tapplyAttrs(node);\n\n\t\t// --- Commit natural dimensions on first load ---\n\n\t\tlet dimensionsCommitted = false;\n\n\t\timg.addEventListener('load', () => {\n\t\t\tif (dimensionsCommitted) return;\n\n\t\t\tconst state: EditorState = getState();\n\t\t\tconst block: BlockNode | undefined = state.getBlock(currentNodeId);\n\t\t\tif (!block || block.type !== 'image') return;\n\n\t\t\tconst hasWidth: boolean = block.attrs?.width !== undefined;\n\t\t\tconst hasHeight: boolean = block.attrs?.height !== undefined;\n\t\t\tif (hasWidth && hasHeight) {\n\t\t\t\tdimensionsCommitted = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst naturalW: number = img.naturalWidth;\n\t\t\tconst naturalH: number = img.naturalHeight;\n\t\t\tif (naturalW === 0 || naturalH === 0) return;\n\n\t\t\tconst clampedW: number = Math.min(naturalW, config.maxWidth);\n\t\t\tconst ratio: number = naturalW > 0 ? naturalH / naturalW : 1;\n\t\t\tconst clampedH: number = Math.round(clampedW * ratio);\n\n\t\t\tconst path: BlockId[] | undefined = state.getNodePath(currentNodeId);\n\t\t\tif (!path) return;\n\n\t\t\tconst merged: BlockAttrs = {\n\t\t\t\t...(block.attrs ?? {}),\n\t\t\t\twidth: clampedW,\n\t\t\t\theight: clampedH,\n\t\t\t};\n\n\t\t\tconst tr: Transaction = state.transaction('command').setNodeAttr(path, merged).build();\n\t\t\tdispatch(tr);\n\t\t\tdimensionsCommitted = true;\n\t\t});\n\n\t\t// --- Resize Logic ---\n\n\t\tfunction clampWidth(width: number): number {\n\t\t\treturn Math.max(MIN_IMAGE_WIDTH, Math.min(config.maxWidth, width));\n\t\t}\n\n\t\tfunction commitResize(nodeId: BlockId, width: number, height: number): void {\n\t\t\tconst state: EditorState = getState();\n\t\t\tconst block: BlockNode | undefined = state.getBlock(nodeId);\n\t\t\tif (!block) return;\n\n\t\t\tconst prevWidth: number | undefined = block.attrs?.width as number | undefined;\n\t\t\tconst prevHeight: number | undefined = block.attrs?.height as number | undefined;\n\t\t\tif (prevWidth === width && prevHeight === height) return;\n\n\t\t\tconst path: BlockId[] | undefined = state.getNodePath(nodeId);\n\t\t\tif (!path) return;\n\n\t\t\tconst merged: BlockAttrs = {\n\t\t\t\t...(block.attrs ?? {}),\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t};\n\n\t\t\tconst tr: Transaction = state.transaction('command').setNodeAttr(path, merged).build();\n\t\t\tdispatch(tr);\n\t\t}\n\n\t\tfunction attachHandleListeners(\n\t\t\thandle: HTMLDivElement,\n\t\t\tposition: HandlePosition,\n\t\t\tnodeId: BlockId,\n\t\t\tsizeIndicator: HTMLDivElement,\n\t\t): void {\n\t\t\tlet startX = 0;\n\t\t\tlet startWidth = 0;\n\t\t\tlet aspectRatio = 1;\n\n\t\t\tconst onPointerMove = (e: PointerEvent): void => {\n\t\t\t\tconst deltaX: number = e.clientX - startX;\n\t\t\t\tconst newWidth: number = clampWidth(startWidth + deltaX * HANDLE_X_SIGN[position]);\n\t\t\t\tconst newHeight: number = Math.round(newWidth / aspectRatio);\n\n\t\t\t\timg.style.width = `${newWidth}px`;\n\t\t\t\timg.style.height = `${newHeight}px`;\n\n\t\t\t\tsizeIndicator.textContent = `${Math.round(newWidth)} \\u00D7 ${newHeight}`;\n\t\t\t};\n\n\t\t\tconst onPointerUp = (e: PointerEvent): void => {\n\t\t\t\tdocument.removeEventListener('pointermove', onPointerMove);\n\t\t\t\tdocument.removeEventListener('pointerup', onPointerUp);\n\n\t\t\t\tfigure.classList.remove('notectl-image--resizing');\n\t\t\t\tsizeIndicator.classList.remove('notectl-image__size-indicator--visible');\n\t\t\t\tclearGlobalResizeCursor();\n\n\t\t\t\tconst finalWidth: number = Math.round(img.getBoundingClientRect().width);\n\t\t\t\tconst finalHeight: number = Math.round(finalWidth / aspectRatio);\n\n\t\t\t\tcommitResize(nodeId, finalWidth, finalHeight);\n\n\t\t\t\t(e.target as HTMLElement | null)?.releasePointerCapture?.(e.pointerId);\n\t\t\t};\n\n\t\t\thandle.addEventListener('pointerdown', (e: PointerEvent) => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\n\t\t\t\tstartX = e.clientX;\n\t\t\t\tstartWidth = img.getBoundingClientRect().width;\n\t\t\t\tconst imgHeight: number = img.getBoundingClientRect().height;\n\t\t\t\taspectRatio = imgHeight > 0 ? startWidth / imgHeight : 1;\n\n\t\t\t\tfigure.classList.add('notectl-image--resizing');\n\t\t\t\tsizeIndicator.textContent = `${Math.round(startWidth)} \\u00D7 ${Math.round(imgHeight)}`;\n\t\t\t\tsizeIndicator.classList.add('notectl-image__size-indicator--visible');\n\t\t\t\tsetGlobalResizeCursor(HANDLE_CURSORS[position]);\n\n\t\t\t\t(e.target as HTMLElement).setPointerCapture(e.pointerId);\n\t\t\t\tdocument.addEventListener('pointermove', onPointerMove);\n\t\t\t\tdocument.addEventListener('pointerup', onPointerUp);\n\t\t\t});\n\t\t}\n\n\t\tfunction createResizeOverlay(nodeId: BlockId): void {\n\t\t\tif (!config.resizable || resizeOverlay) return;\n\n\t\t\tresizeOverlay = document.createElement('div');\n\t\t\tresizeOverlay.className = 'notectl-image__resize-overlay';\n\n\t\t\tconst sizeIndicator: HTMLDivElement = document.createElement('div');\n\t\t\tsizeIndicator.className = 'notectl-image__size-indicator';\n\t\t\tresizeOverlay.appendChild(sizeIndicator);\n\n\t\t\tconst positions: readonly HandlePosition[] = ['nw', 'ne', 'sw', 'se'];\n\t\t\tfor (const pos of positions) {\n\t\t\t\tconst handle: HTMLDivElement = document.createElement('div');\n\t\t\t\thandle.className = `notectl-image__resize-handle notectl-image__resize-handle--${pos}`;\n\t\t\t\thandle.setAttribute('role', 'separator');\n\t\t\t\thandle.setAttribute('aria-label', HANDLE_LABELS[pos]);\n\t\t\t\tattachHandleListeners(handle, pos, nodeId, sizeIndicator);\n\t\t\t\tresizeOverlay.appendChild(handle);\n\t\t\t}\n\n\t\t\t// Keyboard hint (hidden from screenreaders, visual only)\n\t\t\tif (resolvedKeymap) {\n\t\t\t\tconst hintText: string = buildKeyboardHintText(resolvedKeymap);\n\t\t\t\tif (hintText) {\n\t\t\t\t\tconst hint: HTMLDivElement = document.createElement('div');\n\t\t\t\t\thint.className = 'notectl-image__keyboard-hint';\n\t\t\t\t\thint.setAttribute('aria-hidden', 'true');\n\t\t\t\t\thint.textContent = hintText;\n\t\t\t\t\tresizeOverlay.appendChild(hint);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcontainer.appendChild(resizeOverlay);\n\t\t}\n\n\t\tfunction removeResizeOverlay(): void {\n\t\t\tclearGlobalResizeCursor();\n\t\t\tif (resizeOverlay) {\n\t\t\t\tresizeOverlay.remove();\n\t\t\t\tresizeOverlay = null;\n\t\t\t}\n\t\t}\n\n\t\t// --- NodeView Interface ---\n\n\t\treturn {\n\t\t\tdom: figure,\n\t\t\tcontentDOM: null,\n\n\t\t\tupdate(updatedNode: BlockNode): boolean {\n\t\t\t\tif (updatedNode.type !== 'image') return false;\n\t\t\t\tcurrentNodeId = updatedNode.id;\n\t\t\t\tfigure.setAttribute('data-block-id', updatedNode.id);\n\t\t\t\tapplyAttrs(updatedNode);\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tselectNode(): void {\n\t\t\t\tfigure.classList.add('notectl-image--selected');\n\t\t\t\tcreateResizeOverlay(currentNodeId);\n\t\t\t},\n\n\t\t\tdeselectNode(): void {\n\t\t\t\tfigure.classList.remove('notectl-image--selected');\n\t\t\t\tremoveResizeOverlay();\n\t\t\t},\n\n\t\t\tdestroy(): void {\n\t\t\t\tremoveResizeOverlay();\n\t\t\t\t// Do NOT revoke blob URLs here — the URL may still be referenced\n\t\t\t\t// by the clipboard (cut/paste) or undo history. Blob URLs are\n\t\t\t\t// automatically released when the document is unloaded.\n\t\t\t},\n\t\t};\n\t};\n}\n","/**\n * Image plugin types, configuration, and service key.\n */\n\nimport { ServiceKey } from '../Plugin.js';\n\n// --- Image Attributes ---\n\nexport interface ImageAttrs {\n\treadonly src: string;\n\treadonly alt: string;\n\treadonly width?: number;\n\treadonly height?: number;\n\treadonly align: 'left' | 'center' | 'right';\n}\n\n// --- Upload Types ---\n\nexport interface ImageUploadResult {\n\treadonly url: string;\n\treadonly width?: number;\n\treadonly height?: number;\n}\n\nexport interface ImageUploadService {\n\tupload(file: File): Promise<ImageUploadResult>;\n}\n\nexport const IMAGE_UPLOAD_SERVICE = new ServiceKey<ImageUploadService>('image:upload');\n\nexport type UploadState = 'idle' | 'uploading' | 'complete' | 'error';\n\n// --- Keyboard Bindings ---\n\n/**\n * Configurable keyboard bindings for image resize actions.\n * Omit a slot to use the default; set to `null` to disable the binding.\n *\n * Key descriptor format: `'Mod-Shift-ArrowRight'`, etc.\n * `Mod` resolves to Cmd on macOS, Ctrl on Windows/Linux.\n */\nexport interface ImageKeymap {\n\treadonly growWidth?: string | null;\n\treadonly shrinkWidth?: string | null;\n\treadonly growWidthLarge?: string | null;\n\treadonly shrinkWidthLarge?: string | null;\n\treadonly resetSize?: string | null;\n}\n\nexport const DEFAULT_IMAGE_KEYMAP: Readonly<Record<keyof ImageKeymap, string>> = {\n\tgrowWidth: 'Mod-Shift-ArrowRight',\n\tshrinkWidth: 'Mod-Shift-ArrowLeft',\n\tgrowWidthLarge: 'Mod-Shift-Alt-ArrowRight',\n\tshrinkWidthLarge: 'Mod-Shift-Alt-ArrowLeft',\n\tresetSize: 'Mod-Shift-0',\n};\n\n// --- Configuration ---\n\nexport interface ImagePluginConfig {\n\treadonly maxWidth: number;\n\treadonly maxFileSize: number;\n\treadonly acceptedTypes: readonly string[];\n\treadonly resizable: boolean;\n\treadonly separatorAfter?: boolean;\n\t/** Pixels to grow/shrink per small resize step. @default 10 */\n\treadonly resizeStep?: number;\n\t/** Pixels to grow/shrink per large resize step. @default 50 */\n\treadonly resizeStepLarge?: number;\n\t/** Customize keyboard bindings for image resize actions. */\n\treadonly keymap?: ImageKeymap;\n}\n\nexport const DEFAULT_IMAGE_CONFIG: ImagePluginConfig = {\n\tmaxWidth: 800,\n\tmaxFileSize: 10 * 1024 * 1024,\n\tacceptedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml'],\n\tresizable: true,\n\tresizeStep: 10,\n\tresizeStepLarge: 50,\n};\n","/**\n * ImagePlugin: registers an image void block type with NodeSpec,\n * NodeView, commands, file handler, toolbar button, and accessible\n * keyboard resize with screenreader announcements.\n */\n\nimport { IMAGE_CSS } from '../../editor/styles/image.js';\nimport type { BlockAttrs, BlockNode } from '../../model/Document.js';\nimport { escapeHTML } from '../../model/HTMLUtils.js';\nimport { createBlockElement } from '../../model/NodeSpec.js';\nimport { isNodeSelection } from '../../model/Selection.js';\nimport type { BlockId } from '../../model/TypeBrands.js';\nimport type { EditorState } from '../../state/EditorState.js';\nimport type { Transaction } from '../../state/Transaction.js';\nimport type { Plugin, PluginContext } from '../Plugin.js';\nimport { formatShortcut } from '../toolbar/ToolbarItem.js';\nimport {\n\tinsertImage,\n\tregisterImageCommands,\n\tresetImageSize,\n\tresizeImageByDelta,\n} from './ImageCommands.js';\nimport { createImageNodeViewFactory } from './ImageNodeView.js';\nimport {\n\tDEFAULT_IMAGE_CONFIG,\n\tDEFAULT_IMAGE_KEYMAP,\n\tIMAGE_UPLOAD_SERVICE,\n\ttype ImageKeymap,\n\ttype ImagePluginConfig,\n\ttype UploadState,\n} from './ImageUpload.js';\n\n// --- Attribute Registry Augmentation ---\n\ndeclare module '../../model/AttrRegistry.js' {\n\tinterface NodeAttrRegistry {\n\t\timage: {\n\t\t\tsrc: string;\n\t\t\talt: string;\n\t\t\twidth?: number;\n\t\t\theight?: number;\n\t\t\talign: 'left' | 'center' | 'right';\n\t\t};\n\t}\n}\n\n// --- Plugin ---\n\nexport class ImagePlugin implements Plugin {\n\treadonly id = 'image';\n\treadonly name = 'Image';\n\treadonly priority = 45;\n\n\tprivate readonly config: ImagePluginConfig;\n\tprivate readonly resolvedKeymap: Readonly<Record<keyof ImageKeymap, string | null>>;\n\tprivate readonly uploadStates = new Map<BlockId, UploadState>();\n\tprivate readonly blobUrls = new Set<string>();\n\tprivate context: PluginContext | null = null;\n\n\tconstructor(config?: Partial<ImagePluginConfig>) {\n\t\tthis.config = { ...DEFAULT_IMAGE_CONFIG, ...config };\n\t\tthis.resolvedKeymap = { ...DEFAULT_IMAGE_KEYMAP, ...config?.keymap };\n\t}\n\n\tinit(context: PluginContext): void {\n\t\tcontext.registerStyleSheet(IMAGE_CSS);\n\t\tthis.context = context;\n\t\tthis.registerNodeSpec(context);\n\t\tthis.registerNodeView(context);\n\t\tregisterImageCommands(context);\n\t\tthis.registerResizeCommands(context);\n\t\tthis.registerResizeKeymaps(context);\n\t\tthis.registerFileHandler(context);\n\t\tthis.registerToolbarItem(context);\n\t}\n\n\tdestroy(): void {\n\t\tfor (const url of this.blobUrls) {\n\t\t\tURL.revokeObjectURL(url);\n\t\t}\n\t\tthis.blobUrls.clear();\n\t\tthis.uploadStates.clear();\n\t\tthis.context = null;\n\t}\n\n\tonStateChange(oldState: EditorState, newState: EditorState, _tr: Transaction): void {\n\t\tif (!this.context) return;\n\n\t\t// Clean up upload states for removed blocks (searches full tree)\n\t\tfor (const id of this.uploadStates.keys()) {\n\t\t\tif (!newState.getBlock(id)) {\n\t\t\t\tthis.uploadStates.delete(id);\n\t\t\t}\n\t\t}\n\n\t\t// Announce image selection for screenreaders\n\t\tconst oldIsImage: boolean = this.isImageSelected(oldState);\n\t\tconst nowIsImage: boolean = this.isImageSelected(newState);\n\n\t\tif (!oldIsImage && nowIsImage) {\n\t\t\tthis.announceImageSelection(newState);\n\t\t}\n\t}\n\n\tprivate registerNodeSpec(context: PluginContext): void {\n\t\tcontext.registerNodeSpec({\n\t\t\ttype: 'image',\n\t\t\tgroup: 'block',\n\t\t\tisVoid: true,\n\t\t\tselectable: true,\n\t\t\tattrs: {\n\t\t\t\tsrc: { default: '' },\n\t\t\t\talt: { default: '' },\n\t\t\t\talign: { default: 'center' },\n\t\t\t},\n\t\t\ttoDOM(node) {\n\t\t\t\tconst figure = createBlockElement('figure', node.id);\n\t\t\t\tfigure.className = 'notectl-image';\n\t\t\t\tfigure.setAttribute('data-void', 'true');\n\t\t\t\tfigure.setAttribute('data-selectable', 'true');\n\n\t\t\t\tconst imgContainer: HTMLDivElement = document.createElement('div');\n\t\t\t\timgContainer.className = 'notectl-image__container';\n\n\t\t\t\tconst alt: string = (node.attrs?.alt as string | undefined) ?? '';\n\t\t\t\tconst width: number | undefined = node.attrs?.width as number | undefined;\n\t\t\t\tconst height: number | undefined = node.attrs?.height as number | undefined;\n\n\t\t\t\tconst img: HTMLImageElement = document.createElement('img');\n\t\t\t\timg.className = 'notectl-image__img';\n\t\t\t\timg.src = (node.attrs?.src as string | undefined) ?? '';\n\t\t\t\timg.alt = alt;\n\t\t\t\timg.draggable = false;\n\n\t\t\t\tif (width !== undefined) img.style.width = `${width}px`;\n\t\t\t\tif (height !== undefined) img.style.height = `${height}px`;\n\n\t\t\t\tconst align: string = (node.attrs?.align as string | undefined) ?? 'center';\n\t\t\t\tconst alignClass: string | undefined = {\n\t\t\t\t\tleft: 'notectl-image--left',\n\t\t\t\t\tcenter: 'notectl-image--center',\n\t\t\t\t\tright: 'notectl-image--right',\n\t\t\t\t}[align];\n\t\t\t\tif (alignClass) figure.classList.add(alignClass);\n\n\t\t\t\tconst parts: string[] = [alt || 'Image'];\n\t\t\t\tif (width !== undefined && height !== undefined) {\n\t\t\t\t\tparts.push(`${width} by ${height} pixels`);\n\t\t\t\t}\n\t\t\t\tfigure.setAttribute('aria-label', parts.join(', '));\n\n\t\t\t\timgContainer.appendChild(img);\n\t\t\t\tfigure.appendChild(imgContainer);\n\t\t\t\treturn figure;\n\t\t\t},\n\t\t\ttoHTML(node) {\n\t\t\t\tconst src: string = escapeHTML((node.attrs?.src as string | undefined) ?? '');\n\t\t\t\tconst alt: string = escapeHTML((node.attrs?.alt as string | undefined) ?? '');\n\t\t\t\tconst width: number | undefined = node.attrs?.width as number | undefined;\n\t\t\t\tconst height: number | undefined = node.attrs?.height as number | undefined;\n\t\t\t\tconst align: string = (node.attrs?.align as string | undefined) ?? 'center';\n\n\t\t\t\tconst sizeAttrs: string =\n\t\t\t\t\t(width !== undefined ? ` width=\"${width}\"` : '') +\n\t\t\t\t\t(height !== undefined ? ` height=\"${height}\"` : '');\n\n\t\t\t\treturn `<figure class=\"notectl-image notectl-image--${escapeHTML(align)}\"><img src=\"${src}\" alt=\"${alt}\"${sizeAttrs}></figure>`;\n\t\t\t},\n\t\t\tparseHTML: [\n\t\t\t\t{\n\t\t\t\t\ttag: 'figure',\n\t\t\t\t\tgetAttrs(el) {\n\t\t\t\t\t\tconst img: HTMLImageElement | null = el.querySelector('img');\n\t\t\t\t\t\tif (!img) return false;\n\t\t\t\t\t\tconst attrs: Record<string, string | number | boolean> = {\n\t\t\t\t\t\t\tsrc: img.getAttribute('src') ?? '',\n\t\t\t\t\t\t\talt: img.getAttribute('alt') ?? '',\n\t\t\t\t\t\t\talign: 'center',\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst width: string | null = img.getAttribute('width');\n\t\t\t\t\t\tconst height: string | null = img.getAttribute('height');\n\t\t\t\t\t\tif (width) attrs.width = Number.parseInt(width, 10);\n\t\t\t\t\t\tif (height) attrs.height = Number.parseInt(height, 10);\n\n\t\t\t\t\t\tif (el.classList.contains('notectl-image--left')) attrs.align = 'left';\n\t\t\t\t\t\tif (el.classList.contains('notectl-image--right')) attrs.align = 'right';\n\t\t\t\t\t\treturn attrs;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttag: 'img',\n\t\t\t\t\tgetAttrs(el) {\n\t\t\t\t\t\tconst attrs: Record<string, string | number | boolean> = {\n\t\t\t\t\t\t\tsrc: el.getAttribute('src') ?? '',\n\t\t\t\t\t\t\talt: el.getAttribute('alt') ?? '',\n\t\t\t\t\t\t\talign: 'center',\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst width: string | null = el.getAttribute('width');\n\t\t\t\t\t\tconst height: string | null = el.getAttribute('height');\n\t\t\t\t\t\tif (width) attrs.width = Number.parseInt(width, 10);\n\t\t\t\t\t\tif (height) attrs.height = Number.parseInt(height, 10);\n\t\t\t\t\t\treturn attrs;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\tsanitize: {\n\t\t\t\ttags: ['figure', 'img'],\n\t\t\t\tattrs: ['src', 'alt', 'width', 'height', 'class'],\n\t\t\t},\n\t\t});\n\t}\n\n\tprivate registerNodeView(context: PluginContext): void {\n\t\tcontext.registerNodeView(\n\t\t\t'image',\n\t\t\tcreateImageNodeViewFactory(this.config, this.uploadStates, this.resolvedKeymap),\n\t\t);\n\t}\n\n\tprivate registerResizeCommands(context: PluginContext): void {\n\t\tconst step: number = this.config.resizeStep ?? 10;\n\t\tconst stepLarge: number = this.config.resizeStepLarge ?? 50;\n\t\tconst maxWidth: number = this.config.maxWidth;\n\n\t\tcontext.registerCommand('resizeImageGrow', () => {\n\t\t\tconst result: boolean = resizeImageByDelta(context, step, maxWidth);\n\t\t\tif (result) this.announceCurrentSize(context);\n\t\t\treturn result;\n\t\t});\n\n\t\tcontext.registerCommand('resizeImageShrink', () => {\n\t\t\tconst result: boolean = resizeImageByDelta(context, -step, maxWidth);\n\t\t\tif (result) this.announceCurrentSize(context);\n\t\t\treturn result;\n\t\t});\n\n\t\tcontext.registerCommand('resizeImageGrowLarge', () => {\n\t\t\tconst result: boolean = resizeImageByDelta(context, stepLarge, maxWidth);\n\t\t\tif (result) this.announceCurrentSize(context);\n\t\t\treturn result;\n\t\t});\n\n\t\tcontext.registerCommand('resizeImageShrinkLarge', () => {\n\t\t\tconst result: boolean = resizeImageByDelta(context, -stepLarge, maxWidth);\n\t\t\tif (result) this.announceCurrentSize(context);\n\t\t\treturn result;\n\t\t});\n\n\t\tcontext.registerCommand('resetImageSize', () => {\n\t\t\tconst result: boolean = resetImageSize(context);\n\t\t\tif (result) context.announce('Image reset to natural size.');\n\t\t\treturn result;\n\t\t});\n\t}\n\n\tprivate registerResizeKeymaps(context: PluginContext): void {\n\t\tconst bindings: Record<string, () => boolean> = {};\n\t\tconst commands: Record<keyof ImageKeymap, string> = {\n\t\t\tgrowWidth: 'resizeImageGrow',\n\t\t\tshrinkWidth: 'resizeImageShrink',\n\t\t\tgrowWidthLarge: 'resizeImageGrowLarge',\n\t\t\tshrinkWidthLarge: 'resizeImageShrinkLarge',\n\t\t\tresetSize: 'resetImageSize',\n\t\t};\n\n\t\tfor (const [slot, commandName] of Object.entries(commands)) {\n\t\t\tconst binding: string | null = this.resolvedKeymap[slot as keyof ImageKeymap] ?? null;\n\t\t\tif (binding) {\n\t\t\t\tbindings[binding] = () => context.executeCommand(commandName);\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(bindings).length > 0) {\n\t\t\tcontext.registerKeymap(bindings);\n\t\t}\n\t}\n\n\tprivate registerFileHandler(context: PluginContext): void {\n\t\tcontext.registerFileHandler('image/*', async (files, _position) => {\n\t\t\tconst imageFiles: File[] = files.filter((f) => this.isAcceptedType(f.type));\n\t\t\tif (imageFiles.length === 0) return false;\n\n\t\t\tfor (const file of imageFiles) {\n\t\t\t\tthis.handleFileInsert(context, file);\n\t\t\t}\n\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tprivate handleFileInsert(context: PluginContext, file: File): void {\n\t\tif (file.size > this.config.maxFileSize) return;\n\n\t\tconst blobUrl: string = URL.createObjectURL(file);\n\t\tthis.blobUrls.add(blobUrl);\n\n\t\tconst inserted: boolean = insertImage(context, { src: blobUrl });\n\t\tif (!inserted) {\n\t\t\tURL.revokeObjectURL(blobUrl);\n\t\t\tthis.blobUrls.delete(blobUrl);\n\t\t\treturn;\n\t\t}\n\n\t\t// Find the newly inserted image block\n\t\tconst state: EditorState = context.getState();\n\t\tconst sel = state.selection;\n\t\tif (!isNodeSelection(sel)) return;\n\t\tconst imageBlockId: BlockId = sel.nodeId;\n\n\t\tthis.uploadStates.set(imageBlockId, 'uploading');\n\n\t\t// Upload if service is registered\n\t\tconst uploadService = context.getService(IMAGE_UPLOAD_SERVICE);\n\t\tif (uploadService) {\n\t\t\tthis.uploadFile(context, file, imageBlockId, blobUrl);\n\t\t} else {\n\t\t\tthis.uploadStates.set(imageBlockId, 'complete');\n\t\t}\n\t}\n\n\tprivate async uploadFile(\n\t\tcontext: PluginContext,\n\t\tfile: File,\n\t\timageBlockId: BlockId,\n\t\tblobUrl: string,\n\t): Promise<void> {\n\t\tconst uploadService = context.getService(IMAGE_UPLOAD_SERVICE);\n\t\tif (!uploadService) return;\n\n\t\ttry {\n\t\t\tconst result = await uploadService.upload(file);\n\t\t\tthis.uploadStates.set(imageBlockId, 'complete');\n\n\t\t\t// Replace blob URL with uploaded URL\n\t\t\tconst state: EditorState = context.getState();\n\t\t\tconst block: BlockNode | undefined = state.getBlock(imageBlockId);\n\t\t\tif (!block) return;\n\n\t\t\tconst path: BlockId[] | undefined = state.getNodePath(imageBlockId);\n\t\t\tif (!path) return;\n\n\t\t\tconst merged: BlockAttrs = {\n\t\t\t\t...(block.attrs ?? {}),\n\t\t\t\tsrc: result.url,\n\t\t\t\t...(result.width !== undefined ? { width: result.width } : {}),\n\t\t\t\t...(result.height !== undefined ? { height: result.height } : {}),\n\t\t\t};\n\n\t\t\tconst tr: Transaction = state.transaction('command').setNodeAttr(path, merged).build();\n\t\t\tcontext.dispatch(tr);\n\n\t\t\t// Clean up blob URL\n\t\t\tURL.revokeObjectURL(blobUrl);\n\t\t\tthis.blobUrls.delete(blobUrl);\n\t\t} catch {\n\t\t\tthis.uploadStates.set(imageBlockId, 'error');\n\t\t\tcontext.announce('Image upload failed.');\n\t\t}\n\t}\n\n\tprivate registerToolbarItem(context: PluginContext): void {\n\t\tconst icon =\n\t\t\t'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z\"/></svg>';\n\n\t\tcontext.registerToolbarItem({\n\t\t\tid: 'image',\n\t\t\tgroup: 'insert',\n\t\t\ticon,\n\t\t\tlabel: 'Insert Image',\n\t\t\ttooltip: 'Insert Image',\n\t\t\tcommand: 'insertImage',\n\t\t\tpriority: 50,\n\t\t\tpopupType: 'custom',\n\t\t\tseparatorAfter: this.config.separatorAfter,\n\t\t\trenderPopup: (container, ctx, onClose) => {\n\t\t\t\tthis.renderImagePopup(container, ctx, onClose);\n\t\t\t},\n\t\t});\n\t}\n\n\tprivate renderImagePopup(\n\t\tcontainer: HTMLElement,\n\t\tcontext: PluginContext,\n\t\tonClose: () => void,\n\t): void {\n\t\tcontainer.style.padding = '8px';\n\t\tcontainer.style.minWidth = '240px';\n\n\t\t// --- File upload ---\n\t\tconst fileInput: HTMLInputElement = document.createElement('input');\n\t\tfileInput.type = 'file';\n\t\tfileInput.accept = this.config.acceptedTypes.join(',');\n\t\tfileInput.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden;opacity:0;';\n\n\t\tconst uploadBtn: HTMLButtonElement = document.createElement('button');\n\t\tuploadBtn.type = 'button';\n\t\tuploadBtn.textContent = 'Upload from computer';\n\t\tuploadBtn.setAttribute('aria-label', 'Upload image from computer');\n\t\tuploadBtn.style.cssText =\n\t\t\t'display:block;width:100%;padding:8px 12px;cursor:pointer;' +\n\t\t\t'text-align:center;box-sizing:border-box;' +\n\t\t\t'border:1px solid var(--notectl-border);border-radius:4px;' +\n\t\t\t'background:var(--notectl-surface-raised);color:var(--notectl-fg);';\n\n\t\tuploadBtn.addEventListener('mousedown', (e: MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tfileInput.click();\n\t\t});\n\n\t\tfileInput.addEventListener('change', () => {\n\t\t\tconst file: File | undefined = fileInput.files?.[0];\n\t\t\tif (file) {\n\t\t\t\tthis.handleFileInsert(context, file);\n\t\t\t\tonClose();\n\t\t\t\tcontext.getContainer().focus();\n\t\t\t}\n\t\t});\n\n\t\tcontainer.appendChild(fileInput);\n\t\tcontainer.appendChild(uploadBtn);\n\n\t\t// --- Separator ---\n\t\tconst separator: HTMLDivElement = document.createElement('div');\n\t\tseparator.style.cssText =\n\t\t\t'display:flex;align-items:center;margin:8px 0;' +\n\t\t\t'color:var(--notectl-fg-muted);font-size:12px;';\n\t\tconst line1: HTMLSpanElement = document.createElement('span');\n\t\tline1.style.cssText = 'flex:1;height:1px;background:var(--notectl-border);';\n\t\tconst orText: HTMLSpanElement = document.createElement('span');\n\t\torText.textContent = 'or';\n\t\torText.style.cssText = 'padding:0 8px;';\n\t\tconst line2: HTMLSpanElement = document.createElement('span');\n\t\tline2.style.cssText = 'flex:1;height:1px;background:var(--notectl-border);';\n\t\tseparator.appendChild(line1);\n\t\tseparator.appendChild(orText);\n\t\tseparator.appendChild(line2);\n\t\tcontainer.appendChild(separator);\n\n\t\t// --- URL input ---\n\t\tconst urlInput: HTMLInputElement = document.createElement('input');\n\t\turlInput.type = 'url';\n\t\turlInput.placeholder = 'https://...';\n\t\turlInput.setAttribute('aria-label', 'Image URL');\n\t\turlInput.style.cssText =\n\t\t\t'width:100%;padding:6px 8px;box-sizing:border-box;' +\n\t\t\t'border:1px solid var(--notectl-border);border-radius:4px;' +\n\t\t\t'background:var(--notectl-bg);color:var(--notectl-fg);';\n\n\t\tconst insertBtn: HTMLButtonElement = document.createElement('button');\n\t\tinsertBtn.type = 'button';\n\t\tinsertBtn.textContent = 'Insert';\n\t\tinsertBtn.setAttribute('aria-label', 'Insert image');\n\t\tinsertBtn.style.cssText =\n\t\t\t'width:100%;padding:8px 12px;margin-top:4px;cursor:pointer;' +\n\t\t\t'border:1px solid var(--notectl-border);border-radius:4px;' +\n\t\t\t'background:var(--notectl-surface-raised);color:var(--notectl-fg);';\n\n\t\tconst applyUrl = (): void => {\n\t\t\tconst src: string = urlInput.value.trim();\n\t\t\tif (src) {\n\t\t\t\tinsertImage(context, { src });\n\t\t\t\tonClose();\n\t\t\t\tcontext.getContainer().focus();\n\t\t\t}\n\t\t};\n\n\t\tinsertBtn.addEventListener('mousedown', (e: MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tapplyUrl();\n\t\t});\n\n\t\turlInput.addEventListener('keydown', (e: KeyboardEvent) => {\n\t\t\tif (e.key === 'Enter') {\n\t\t\t\te.preventDefault();\n\t\t\t\tapplyUrl();\n\t\t\t}\n\t\t});\n\n\t\tcontainer.appendChild(urlInput);\n\t\tcontainer.appendChild(insertBtn);\n\n\t\trequestAnimationFrame(() => urlInput.focus());\n\t}\n\n\tprivate isAcceptedType(mimeType: string): boolean {\n\t\treturn this.config.acceptedTypes.some(\n\t\t\t(accepted) =>\n\t\t\t\taccepted === mimeType ||\n\t\t\t\t(accepted.endsWith('/*') && mimeType.startsWith(accepted.slice(0, -1))),\n\t\t);\n\t}\n\n\tprivate isImageSelected(state: EditorState): boolean {\n\t\tconst sel = state.selection;\n\t\tif (!isNodeSelection(sel)) return false;\n\t\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\t\treturn block?.type === 'image';\n\t}\n\n\tprivate announceImageSelection(state: EditorState): void {\n\t\tif (!this.context) return;\n\t\tconst sel = state.selection;\n\t\tif (!isNodeSelection(sel)) return;\n\n\t\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\t\tif (!block || block.type !== 'image') return;\n\n\t\tconst alt: string = (block.attrs?.alt as string | undefined) ?? '';\n\t\tconst width: number | undefined = block.attrs?.width as number | undefined;\n\t\tconst height: number | undefined = block.attrs?.height as number | undefined;\n\n\t\tconst parts: string[] = ['Image selected.'];\n\t\tif (alt) parts.push(`Alt text: ${alt}.`);\n\t\tif (width !== undefined && height !== undefined) {\n\t\t\tparts.push(`Size: ${width} by ${height} pixels.`);\n\t\t}\n\n\t\tconst shrinkKey: string | null = this.resolvedKeymap.shrinkWidth ?? null;\n\t\tconst growKey: string | null = this.resolvedKeymap.growWidth ?? null;\n\t\tif (shrinkKey && growKey) {\n\t\t\tparts.push(`${formatShortcut(shrinkKey)} / ${formatShortcut(growKey)} to resize.`);\n\t\t}\n\n\t\tthis.context.announce(parts.join(' '));\n\t}\n\n\tprivate announceCurrentSize(context: PluginContext): void {\n\t\tconst state = context.getState();\n\t\tconst sel = state.selection;\n\t\tif (!isNodeSelection(sel)) return;\n\n\t\tconst block: BlockNode | undefined = state.getBlock(sel.nodeId);\n\t\tif (!block || block.type !== 'image') return;\n\n\t\tconst width: number | undefined = block.attrs?.width as number | undefined;\n\t\tconst height: number | undefined = block.attrs?.height as number | undefined;\n\t\tif (width !== undefined && height !== undefined) {\n\t\t\tcontext.announce(`Image resized to ${width} by ${height} pixels.`);\n\t\t}\n\t}\n}\n","/** Image styles — display, alignment, resize handles, upload overlay. */\nexport const IMAGE_CSS = `\n/* Image */\n.notectl-image {\n\tmargin: 8px 0;\n\tuser-select: none;\n\tline-height: 0;\n}\n\n.notectl-image--center {\n\ttext-align: center;\n}\n\n.notectl-image--left {\n\ttext-align: left;\n}\n\n.notectl-image--right {\n\ttext-align: right;\n}\n\n.notectl-image__container {\n\tdisplay: inline-block;\n\tposition: relative;\n\tline-height: 0;\n}\n\n.notectl-image__img {\n\tdisplay: block;\n\tmax-width: 100%;\n\tborder-radius: 4px;\n}\n\n.notectl-image--selected .notectl-image__container {\n\toutline: 2px solid var(--notectl-primary);\n\toutline-offset: 2px;\n\tborder-radius: 6px;\n}\n\n/* Alignment toolbar (appears on image selection) */\n.notectl-image__align-toolbar {\n\tdisplay: flex;\n\tgap: 2px;\n\tposition: absolute;\n\ttop: -36px;\n\tleft: 50%;\n\ttransform: translateX(-50%);\n\tbackground: var(--notectl-surface-overlay);\n\tborder: 1px solid var(--notectl-border);\n\tborder-radius: 6px;\n\tpadding: 2px;\n\tbox-shadow: 0 2px 8px var(--notectl-shadow);\n\tz-index: 10;\n}\n\n.notectl-image__align-btn {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 28px;\n\theight: 28px;\n\tborder: none;\n\tbackground: transparent;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\tcolor: var(--notectl-fg);\n}\n\n.notectl-image__align-btn:hover {\n\tbackground: var(--notectl-hover-bg);\n}\n\n.notectl-image__align-btn--active {\n\tbackground: var(--notectl-active-bg);\n\tcolor: var(--notectl-primary-fg);\n}\n\n.notectl-image__align-btn svg {\n\twidth: 16px;\n\theight: 16px;\n\tfill: currentColor;\n}\n\n/* Upload overlay */\n.notectl-image__overlay {\n\tposition: absolute;\n\tinset: 0;\n\tdisplay: none;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground: rgba(255, 255, 255, 0.8);\n\tborder-radius: 4px;\n\tfont-size: 13px;\n\tcolor: var(--notectl-fg-muted);\n}\n\n.notectl-image__overlay--uploading {\n\tdisplay: flex;\n}\n\n.notectl-image__overlay--error {\n\tdisplay: flex;\n\tbackground: var(--notectl-danger-muted);\n\tcolor: var(--notectl-danger);\n}\n\n/* Resize overlay + handles */\n.notectl-image__resize-overlay {\n\tposition: absolute;\n\tinset: 0;\n\tpointer-events: none;\n}\n\n.notectl-image__resize-handle {\n\tposition: absolute;\n\twidth: 10px;\n\theight: 10px;\n\tbackground: var(--notectl-bg);\n\tborder: 2px solid var(--notectl-primary);\n\tborder-radius: 50%;\n\tpointer-events: all;\n\tz-index: 2;\n\ttransition: transform 0.1s, background 0.1s;\n}\n\n.notectl-image__resize-handle:hover {\n\tbackground: var(--notectl-primary);\n\ttransform: scale(1.3);\n}\n\n.notectl-image--resizing .notectl-image__resize-handle {\n\tbackground: var(--notectl-primary);\n}\n\n.notectl-image__resize-handle--nw {\n\ttop: -5px;\n\tleft: -5px;\n\tcursor: nwse-resize;\n}\n\n.notectl-image__resize-handle--ne {\n\ttop: -5px;\n\tright: -5px;\n\tcursor: nesw-resize;\n}\n\n.notectl-image__resize-handle--sw {\n\tbottom: -5px;\n\tleft: -5px;\n\tcursor: nesw-resize;\n}\n\n.notectl-image__resize-handle--se {\n\tbottom: -5px;\n\tright: -5px;\n\tcursor: nwse-resize;\n}\n\n/* Size indicator tooltip */\n.notectl-image__size-indicator {\n\tposition: absolute;\n\tbottom: -28px;\n\tleft: 50%;\n\ttransform: translateX(-50%);\n\tpadding: 2px 8px;\n\tbackground: var(--notectl-tooltip-bg);\n\tcolor: var(--notectl-tooltip-fg);\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tborder-radius: 4px;\n\twhite-space: nowrap;\n\tpointer-events: none;\n\topacity: 0;\n\ttransition: opacity 0.15s;\n\tz-index: 3;\n}\n\n.notectl-image__size-indicator--visible {\n\topacity: 1;\n}\n\n/* Keyboard resize hint (shown on selection, hidden during drag) */\n.notectl-image__keyboard-hint {\n\tdisplay: none;\n\tposition: absolute;\n\tbottom: -28px;\n\tleft: 50%;\n\ttransform: translateX(-50%);\n\tpadding: 2px 8px;\n\tfont-size: 11px;\n\tcolor: var(--notectl-fg-muted);\n\twhite-space: nowrap;\n\tpointer-events: none;\n\tz-index: 3;\n}\n\n.notectl-image--selected .notectl-image__keyboard-hint {\n\tdisplay: block;\n}\n\n.notectl-image--resizing .notectl-image__keyboard-hint {\n\tdisplay: none;\n}\n\n/* Resizing feedback */\n.notectl-image--resizing .notectl-image__img {\n\topacity: 0.85;\n}\n`;\n"],"names":["insertImage","context","attrs","state","getState","sel","selection","imageAttrs","src","alt","align","width","height","anchorBlockId","isNodeSelection","nodeId","anchor","blockId","cellId","bid","block","getBlock","type","path","getNodePath","id","node","cellPath","imageBlock","createBlockNode","nodeType","cell","builder","transaction","blockChildren","getBlockChildren","i","length","rawIndex","children","findIndex","c","isBlockNode","removeNode","insertNode","setSelection","createNodeSelection","dispatch","build","blockIndex","doc","b","newParagraph","tr","resizeImageByDelta","delta","maxWidth","currentWidth","currentHeight","aspectRatio","clampedWidth","Math","max","min","newHeight","round","merged","setNodeAttr","ALIGNMENT_CLASSES","left","center","right","HANDLE_CURSORS","nw","ne","sw","se","HANDLE_X_SIGN","HANDLE_LABELS","activeCursorStyle","clearGlobalResizeCursor","remove","createImageNodeViewFactory","config","uploadStates","resolvedKeymap","figure","document","createElement","className","setAttribute","container","img","draggable","overlay","appendChild","currentNodeId","resizeOverlay","applyAttrs","n","style","cls","Object","values","classList","alignClass","add","uploadState","get","isUploading","toggle","textContent","removeAttribute","parts","push","join","dimensionsCommitted","attachHandleListeners","handle","position","sizeIndicator","startX","startWidth","onPointerMove","e","deltaX","clientX","newWidth","onPointerUp","removeEventListener","finalWidth","getBoundingClientRect","finalHeight","prevWidth","prevHeight","target","releasePointerCapture","pointerId","addEventListener","preventDefault","stopPropagation","imgHeight","cursor","head","setPointerCapture","removeResizeOverlay","hasWidth","hasHeight","naturalW","naturalWidth","naturalH","naturalHeight","clampedW","ratio","clampedH","dom","contentDOM","update","updatedNode","resizable","positions","pos","hintText","shrink","shrinkWidth","grow","growWidth","formatShortcut","hint","destroy","IMAGE_UPLOAD_SERVICE","ServiceKey","DEFAULT_IMAGE_KEYMAP","growWidthLarge","shrinkWidthLarge","resetSize","DEFAULT_IMAGE_CONFIG","maxFileSize","acceptedTypes","resizeStep","resizeStepLarge","ImagePlugin","name","priority","Map","blobUrls","Set","this","keymap","registerStyleSheet","registerNodeSpec","registerNodeView","registerCommand","nodePath","parentPath","slice","parent","parentId","imageIndex","selectionTarget","removedId","rootChildren","idx","adjacent","createCollapsedSelection","registerResizeCommands","registerResizeKeymaps","registerFileHandler","registerToolbarItem","url","URL","revokeObjectURL","clear","oldState","newState","_tr","keys","delete","oldIsImage","isImageSelected","nowIsImage","announceImageSelection","group","isVoid","selectable","default","createBlockElement","imgContainer","escapeHTML","sizeAttrs","parseHTML","tag","el","querySelector","getAttribute","Number","parseInt","contains","sanitize","tags","step","stepLarge","result","announceCurrentSize","cleaned","fromEntries","entries","filter","k","announce","bindings","commands","slot","commandName","binding","executeCommand","registerKeymap","async","files","_position","imageFiles","f","isAcceptedType","file","handleFileInsert","size","blobUrl","createObjectURL","imageBlockId","set","getService","uploadFile","uploadService","upload","icon","label","tooltip","command","popupType","separatorAfter","renderPopup","ctx","onClose","renderImagePopup","padding","minWidth","fileInput","accept","cssText","uploadBtn","click","getContainer","focus","separator","line1","orText","line2","urlInput","placeholder","insertBtn","applyUrl","value","trim","key","requestAnimationFrame","mimeType","some","accepted","endsWith","startsWith","shrinkKey","growKey"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { Plugin, PluginContext } from '../Plugin.js';
2
+ declare module '../../model/AttrRegistry.js' {
3
+ interface MarkAttrRegistry {
4
+ link: {
5
+ href: string;
6
+ };
7
+ }
8
+ }
9
+ export interface LinkConfig {
10
+ /** Whether to add rel="noopener noreferrer" and target="_blank" by default. */
11
+ readonly openInNewTab: boolean;
12
+ /** When true, a separator is rendered after the link toolbar item. */
13
+ readonly separatorAfter?: boolean;
14
+ }
15
+ export declare class LinkPlugin implements Plugin {
16
+ readonly id = "link";
17
+ readonly name = "Link";
18
+ readonly priority = 25;
19
+ private readonly config;
20
+ constructor(config?: Partial<LinkConfig>);
21
+ init(context: PluginContext): void;
22
+ private registerMarkSpec;
23
+ private registerCommands;
24
+ private registerKeymap;
25
+ private registerToolbarItem;
26
+ private isLinkActive;
27
+ private toggleLink;
28
+ private addLink;
29
+ private removeLink;
30
+ private renderLinkPopup;
31
+ }
32
+ //# sourceMappingURL=LinkPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LinkPlugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/link/LinkPlugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAK1D,OAAO,QAAQ,6BAA6B,CAAC;IAC5C,UAAU,gBAAgB;QACzB,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACvB;CACD;AAID,MAAM,WAAW,UAAU;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CAClC;AAQD,qBAAa,UAAW,YAAW,MAAM;IACxC,QAAQ,CAAC,EAAE,UAAU;IACrB,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAExB,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IAIxC,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAOlC,OAAO,CAAC,gBAAgB;IAoCxB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,UAAU;IAkElB,OAAO,CAAC,eAAe;CA4DvB"}
@@ -0,0 +1,2 @@
1
+ export { LinkPlugin, type LinkConfig, } from './LinkPlugin.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/link/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,KAAK,UAAU,GACf,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,127 @@
1
+ import { t } from "../chunks/Commands-DLP85Mp5.mjs";
2
+ import { e, h as n } from "../chunks/Document-CI3iCwEP.mjs";
3
+ import { e as r } from "../chunks/HTMLUtils-0wvCRvJc.mjs";
4
+ import { a as o, i, s } from "../chunks/Selection-D5ZjXoB7.mjs";
5
+ import { m as a } from "../chunks/TypeBrands-C09nE2qO.mjs";
6
+ import { f as c } from "../chunks/ToolbarItem-ugDymuXi.mjs";
7
+ const l = { openInNewTab: true };
8
+ class p {
9
+ id = "link";
10
+ name = "Link";
11
+ priority = 25;
12
+ config;
13
+ constructor(t2) {
14
+ this.config = { ...l, ...t2 };
15
+ }
16
+ init(t2) {
17
+ this.registerMarkSpec(t2), this.registerCommands(t2), this.registerKeymap(t2), this.registerToolbarItem(t2);
18
+ }
19
+ registerMarkSpec(t2) {
20
+ const e2 = this.config.openInNewTab;
21
+ t2.registerMarkSpec({ type: "link", rank: 10, attrs: { href: { default: "" } }, toDOM(t3) {
22
+ const n2 = document.createElement("a"), r2 = t3.attrs?.href ?? "";
23
+ return n2.setAttribute("href", r2), e2 && (n2.setAttribute("target", "_blank"), n2.setAttribute("rel", "noopener noreferrer")), n2;
24
+ }, toHTMLString: (t3, e3) => `<a href="${r(String(t3.attrs?.href ?? ""))}">${e3}</a>`, parseHTML: [{ tag: "a", getAttrs: (t3) => ({ href: t3.getAttribute("href") ?? "" }) }], sanitize: { tags: ["a"], attrs: ["href", "target", "rel"] } });
25
+ }
26
+ registerCommands(t2) {
27
+ t2.registerCommand("toggleLink", () => {
28
+ const e2 = t2.getState();
29
+ return this.toggleLink(t2, e2);
30
+ }), t2.registerCommand("setLink", () => false), t2.registerCommand("removeLink", () => {
31
+ const e2 = t2.getState();
32
+ return this.removeLink(t2, e2);
33
+ });
34
+ }
35
+ registerKeymap(t2) {
36
+ t2.registerKeymap({ "Mod-K": () => t2.executeCommand("toggleLink") });
37
+ }
38
+ registerToolbarItem(t2) {
39
+ t2.registerToolbarItem({ id: "link", group: "insert", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>', label: "Link", tooltip: `Insert Link (${c("Mod-K")})`, command: "toggleLink", priority: 60, popupType: "custom", separatorAfter: this.config.separatorAfter, renderPopup: (t3, e2) => {
40
+ this.renderLinkPopup(t3, e2);
41
+ }, isActive: (t3) => this.isLinkActive(t3), isEnabled: (t3) => !o(t3.selection) });
42
+ }
43
+ isLinkActive(t2) {
44
+ const r2 = t2.selection;
45
+ if (i(r2)) return false;
46
+ if (o(r2)) {
47
+ const o2 = t2.getBlock(r2.anchor.blockId);
48
+ if (!o2) return false;
49
+ const i2 = e(o2, r2.anchor.offset);
50
+ return n(i2, a("link"));
51
+ }
52
+ const s2 = t2.getBlock(r2.anchor.blockId);
53
+ if (!s2) return false;
54
+ const c2 = e(s2, r2.anchor.offset);
55
+ return n(c2, a("link"));
56
+ }
57
+ toggleLink(t2, e2) {
58
+ return !!this.isLinkActive(e2) && this.removeLink(t2, e2);
59
+ }
60
+ addLink(e2, n2, r2) {
61
+ const c2 = n2.selection;
62
+ if (i(c2)) return false;
63
+ if (o(c2)) return false;
64
+ const l2 = s(c2, n2.getBlockOrder()), p2 = n2.transaction("command"), d = { type: a("link"), attrs: { href: r2 } };
65
+ return t(n2, l2, (t2, e3, n3) => {
66
+ p2.addMark(t2, e3, n3, d);
67
+ }), p2.setSelection(c2), e2.dispatch(p2.build()), true;
68
+ }
69
+ removeLink(e2, r2) {
70
+ const c2 = r2.selection;
71
+ if (i(c2)) return false;
72
+ const l2 = r2.getBlockOrder(), p2 = o(c2) ? { from: c2.anchor, to: c2.anchor } : s(c2, l2), d = r2.transaction("command");
73
+ if (o(c2)) {
74
+ const t2 = r2.getBlock(c2.anchor.blockId);
75
+ if (!t2) return false;
76
+ const e3 = [];
77
+ let o2 = 0;
78
+ for (const r3 of t2.children) {
79
+ if (!("text" in r3)) continue;
80
+ const t3 = o2 + r3.text.length;
81
+ e3.push({ pos: o2, end: t3, hasLink: n(r3.marks, a("link")) }), o2 = t3;
82
+ }
83
+ const i2 = e3.findIndex((t3) => c2.anchor.offset >= t3.pos && c2.anchor.offset <= t3.end), s2 = i2 >= 0 ? e3[i2] : void 0;
84
+ if (-1 === i2 || !s2?.hasLink) return false;
85
+ let l3 = i2;
86
+ for (; l3 > 0 && e3[l3 - 1]?.hasLink; ) l3--;
87
+ let p3 = i2;
88
+ for (; p3 < e3.length - 1 && e3[p3 + 1]?.hasLink; ) p3++;
89
+ const m = e3[l3], h = e3[p3];
90
+ if (!m || !h) return false;
91
+ const u = m.pos, k = h.end;
92
+ d.removeMark(c2.anchor.blockId, u, k, { type: a("link") });
93
+ } else t(r2, p2, (t2, e3, n2) => {
94
+ d.removeMark(t2, e3, n2, { type: a("link") });
95
+ });
96
+ return d.setSelection(c2), e2.dispatch(d.build()), true;
97
+ }
98
+ renderLinkPopup(t2, e2) {
99
+ t2.style.padding = "8px", t2.style.minWidth = "200px";
100
+ const n2 = e2.getState();
101
+ if (this.isLinkActive(n2)) {
102
+ const n3 = document.createElement("button");
103
+ n3.type = "button", n3.textContent = "Remove Link", n3.setAttribute("aria-label", "Remove link"), n3.style.cssText = "width:100%;padding:6px 12px;cursor:pointer;", n3.addEventListener("mousedown", (t3) => {
104
+ t3.preventDefault(), t3.stopPropagation(), e2.executeCommand("removeLink");
105
+ }), t2.appendChild(n3);
106
+ } else {
107
+ const n3 = document.createElement("input");
108
+ n3.type = "url", n3.placeholder = "https://...", n3.setAttribute("aria-label", "URL"), n3.style.cssText = "width:100%;padding:4px;box-sizing:border-box;";
109
+ const r2 = document.createElement("button");
110
+ r2.type = "button", r2.textContent = "Apply", r2.setAttribute("aria-label", "Apply link"), r2.style.cssText = "width:100%;padding:6px 12px;margin-top:4px;cursor:pointer;", r2.addEventListener("mousedown", (t3) => {
111
+ t3.preventDefault(), t3.stopPropagation();
112
+ const r3 = n3.value.trim();
113
+ r3 && this.addLink(e2, e2.getState(), r3);
114
+ }), n3.addEventListener("keydown", (t3) => {
115
+ if ("Enter" === t3.key) {
116
+ t3.preventDefault();
117
+ const r3 = n3.value.trim();
118
+ r3 && this.addLink(e2, e2.getState(), r3);
119
+ }
120
+ }), t2.appendChild(n3), t2.appendChild(r2), requestAnimationFrame(() => n3.focus());
121
+ }
122
+ }
123
+ }
124
+ export {
125
+ p as LinkPlugin
126
+ };
127
+ //# sourceMappingURL=link.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.mjs","sources":["../../src/plugins/link/LinkPlugin.ts"],"sourcesContent":["/**\n * LinkPlugin: registers a link mark type with href attribute,\n * toggle command, keyboard shortcut (Mod-K), and toolbar button\n * with a URL input popup.\n */\n\nimport { forEachBlockInRange } from '../../commands/Commands.js';\nimport { getBlockMarksAtOffset, hasMark } from '../../model/Document.js';\nimport { escapeHTML } from '../../model/HTMLUtils.js';\nimport { isCollapsed, isNodeSelection, selectionRange } from '../../model/Selection.js';\nimport { markType } from '../../model/TypeBrands.js';\nimport type { EditorState } from '../../state/EditorState.js';\nimport type { Plugin, PluginContext } from '../Plugin.js';\nimport { formatShortcut } from '../toolbar/ToolbarItem.js';\n\n// --- Attribute Registry Augmentation ---\n\ndeclare module '../../model/AttrRegistry.js' {\n\tinterface MarkAttrRegistry {\n\t\tlink: { href: string };\n\t}\n}\n\n// --- Configuration ---\n\nexport interface LinkConfig {\n\t/** Whether to add rel=\"noopener noreferrer\" and target=\"_blank\" by default. */\n\treadonly openInNewTab: boolean;\n\t/** When true, a separator is rendered after the link toolbar item. */\n\treadonly separatorAfter?: boolean;\n}\n\nconst DEFAULT_CONFIG: LinkConfig = {\n\topenInNewTab: true,\n};\n\n// --- Plugin ---\n\nexport class LinkPlugin implements Plugin {\n\treadonly id = 'link';\n\treadonly name = 'Link';\n\treadonly priority = 25;\n\n\tprivate readonly config: LinkConfig;\n\n\tconstructor(config?: Partial<LinkConfig>) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\tinit(context: PluginContext): void {\n\t\tthis.registerMarkSpec(context);\n\t\tthis.registerCommands(context);\n\t\tthis.registerKeymap(context);\n\t\tthis.registerToolbarItem(context);\n\t}\n\n\tprivate registerMarkSpec(context: PluginContext): void {\n\t\tconst openInNewTab = this.config.openInNewTab;\n\n\t\tcontext.registerMarkSpec({\n\t\t\ttype: 'link',\n\t\t\trank: 10,\n\t\t\tattrs: {\n\t\t\t\thref: { default: '' },\n\t\t\t},\n\t\t\ttoDOM(mark) {\n\t\t\t\tconst a = document.createElement('a');\n\t\t\t\tconst href = mark.attrs?.href ?? '';\n\t\t\t\ta.setAttribute('href', href);\n\t\t\t\tif (openInNewTab) {\n\t\t\t\t\ta.setAttribute('target', '_blank');\n\t\t\t\t\ta.setAttribute('rel', 'noopener noreferrer');\n\t\t\t\t}\n\t\t\t\treturn a;\n\t\t\t},\n\t\t\ttoHTMLString: (mark, content) => {\n\t\t\t\tconst href: string = escapeHTML(String(mark.attrs?.href ?? ''));\n\t\t\t\treturn `<a href=\"${href}\">${content}</a>`;\n\t\t\t},\n\t\t\tparseHTML: [\n\t\t\t\t{\n\t\t\t\t\ttag: 'a',\n\t\t\t\t\tgetAttrs: (el) => {\n\t\t\t\t\t\tconst href: string = el.getAttribute('href') ?? '';\n\t\t\t\t\t\treturn { href };\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\tsanitize: { tags: ['a'], attrs: ['href', 'target', 'rel'] },\n\t\t});\n\t}\n\n\tprivate registerCommands(context: PluginContext): void {\n\t\tcontext.registerCommand('toggleLink', () => {\n\t\t\tconst state = context.getState();\n\t\t\treturn this.toggleLink(context, state);\n\t\t});\n\n\t\tcontext.registerCommand('setLink', () => {\n\t\t\t// setLink is called by the popup after URL input\n\t\t\t// The actual URL is set via the popup's custom render\n\t\t\treturn false;\n\t\t});\n\n\t\tcontext.registerCommand('removeLink', () => {\n\t\t\tconst state = context.getState();\n\t\t\treturn this.removeLink(context, state);\n\t\t});\n\t}\n\n\tprivate registerKeymap(context: PluginContext): void {\n\t\tcontext.registerKeymap({\n\t\t\t'Mod-K': () => context.executeCommand('toggleLink'),\n\t\t});\n\t}\n\n\tprivate registerToolbarItem(context: PluginContext): void {\n\t\tconst icon =\n\t\t\t'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z\"/></svg>';\n\n\t\tcontext.registerToolbarItem({\n\t\t\tid: 'link',\n\t\t\tgroup: 'insert',\n\t\t\ticon,\n\t\t\tlabel: 'Link',\n\t\t\ttooltip: `Insert Link (${formatShortcut('Mod-K')})`,\n\t\t\tcommand: 'toggleLink',\n\t\t\tpriority: 60,\n\t\t\tpopupType: 'custom',\n\t\t\tseparatorAfter: this.config.separatorAfter,\n\t\t\trenderPopup: (container, ctx) => {\n\t\t\t\tthis.renderLinkPopup(container, ctx);\n\t\t\t},\n\t\t\tisActive: (state) => this.isLinkActive(state),\n\t\t\tisEnabled: (state) => !isCollapsed(state.selection),\n\t\t});\n\t}\n\n\tprivate isLinkActive(state: EditorState): boolean {\n\t\tconst sel = state.selection;\n\t\tif (isNodeSelection(sel)) return false;\n\t\tif (isCollapsed(sel)) {\n\t\t\tconst block = state.getBlock(sel.anchor.blockId);\n\t\t\tif (!block) return false;\n\t\t\tconst marks = getBlockMarksAtOffset(block, sel.anchor.offset);\n\t\t\treturn hasMark(marks, markType('link'));\n\t\t}\n\n\t\t// Check if link is active anywhere in selection\n\t\tconst block = state.getBlock(sel.anchor.blockId);\n\t\tif (!block) return false;\n\t\tconst marks = getBlockMarksAtOffset(block, sel.anchor.offset);\n\t\treturn hasMark(marks, markType('link'));\n\t}\n\n\tprivate toggleLink(context: PluginContext, state: EditorState): boolean {\n\t\tif (this.isLinkActive(state)) {\n\t\t\treturn this.removeLink(context, state);\n\t\t}\n\t\t// Adding links requires the toolbar popup for URL input\n\t\treturn false;\n\t}\n\n\tprivate addLink(context: PluginContext, state: EditorState, href: string): boolean {\n\t\tconst sel = state.selection;\n\t\tif (isNodeSelection(sel)) return false;\n\t\tif (isCollapsed(sel)) return false;\n\n\t\tconst range = selectionRange(sel, state.getBlockOrder());\n\t\tconst builder = state.transaction('command');\n\t\tconst mark = { type: markType('link'), attrs: { href } };\n\n\t\tforEachBlockInRange(state, range, (blockId, from, to) => {\n\t\t\tbuilder.addMark(blockId, from, to, mark);\n\t\t});\n\n\t\tbuilder.setSelection(sel);\n\t\tcontext.dispatch(builder.build());\n\t\treturn true;\n\t}\n\n\tprivate removeLink(context: PluginContext, state: EditorState): boolean {\n\t\tconst sel = state.selection;\n\t\tif (isNodeSelection(sel)) return false;\n\t\tconst blockOrder = state.getBlockOrder();\n\t\tconst range = isCollapsed(sel)\n\t\t\t? { from: sel.anchor, to: sel.anchor }\n\t\t\t: selectionRange(sel, blockOrder);\n\n\t\tconst builder = state.transaction('command');\n\n\t\tif (isCollapsed(sel)) {\n\t\t\t// Remove link from entire link span around cursor\n\t\t\tconst block = state.getBlock(sel.anchor.blockId);\n\t\t\tif (!block) return false;\n\n\t\t\t// Find the extent of the link mark around the cursor.\n\t\t\t// Build a list of text children with their positions first,\n\t\t\t// then scan backward and forward from the cursor node.\n\t\t\tconst textChildren: { pos: number; end: number; hasLink: boolean }[] = [];\n\t\t\tlet pos = 0;\n\t\t\tfor (const child of block.children) {\n\t\t\t\tif (!('text' in child)) continue;\n\t\t\t\tconst end = pos + child.text.length;\n\t\t\t\ttextChildren.push({ pos, end, hasLink: hasMark(child.marks, markType('link')) });\n\t\t\t\tpos = end;\n\t\t\t}\n\n\t\t\t// Find the text child containing the cursor\n\t\t\tconst cursorIdx = textChildren.findIndex(\n\t\t\t\t(c) => sel.anchor.offset >= c.pos && sel.anchor.offset <= c.end,\n\t\t\t);\n\t\t\tconst cursorEntry = cursorIdx >= 0 ? textChildren[cursorIdx] : undefined;\n\t\t\tif (cursorIdx === -1 || !cursorEntry?.hasLink) return false;\n\n\t\t\t// Scan backward from cursor node to find link start\n\t\t\tlet startIdx = cursorIdx;\n\t\t\twhile (startIdx > 0 && textChildren[startIdx - 1]?.hasLink) {\n\t\t\t\tstartIdx--;\n\t\t\t}\n\n\t\t\t// Scan forward from cursor node to find link end\n\t\t\tlet endIdx = cursorIdx;\n\t\t\twhile (endIdx < textChildren.length - 1 && textChildren[endIdx + 1]?.hasLink) {\n\t\t\t\tendIdx++;\n\t\t\t}\n\n\t\t\tconst startEntry = textChildren[startIdx];\n\t\t\tconst endEntry = textChildren[endIdx];\n\t\t\tif (!startEntry || !endEntry) return false;\n\n\t\t\tconst linkStart = startEntry.pos;\n\t\t\tconst linkEnd = endEntry.end;\n\n\t\t\tbuilder.removeMark(sel.anchor.blockId, linkStart, linkEnd, { type: markType('link') });\n\t\t} else {\n\t\t\t// Remove link from selection range\n\t\t\tforEachBlockInRange(state, range, (blockId, from, to) => {\n\t\t\t\tbuilder.removeMark(blockId, from, to, { type: markType('link') });\n\t\t\t});\n\t\t}\n\n\t\tbuilder.setSelection(sel);\n\t\tcontext.dispatch(builder.build());\n\t\treturn true;\n\t}\n\n\tprivate renderLinkPopup(container: HTMLElement, context: PluginContext): void {\n\t\tcontainer.style.padding = '8px';\n\t\tcontainer.style.minWidth = '200px';\n\n\t\tconst state = context.getState();\n\t\tconst isActive = this.isLinkActive(state);\n\n\t\tif (isActive) {\n\t\t\t// Show remove link button\n\t\t\tconst removeBtn = document.createElement('button');\n\t\t\tremoveBtn.type = 'button';\n\t\t\tremoveBtn.textContent = 'Remove Link';\n\t\t\tremoveBtn.setAttribute('aria-label', 'Remove link');\n\t\t\tremoveBtn.style.cssText = 'width:100%;padding:6px 12px;cursor:pointer;';\n\t\t\tremoveBtn.addEventListener('mousedown', (e) => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t\tcontext.executeCommand('removeLink');\n\t\t\t});\n\t\t\tcontainer.appendChild(removeBtn);\n\t\t} else {\n\t\t\t// Show URL input\n\t\t\tconst input = document.createElement('input');\n\t\t\tinput.type = 'url';\n\t\t\tinput.placeholder = 'https://...';\n\t\t\tinput.setAttribute('aria-label', 'URL');\n\t\t\tinput.style.cssText = 'width:100%;padding:4px;box-sizing:border-box;';\n\n\t\t\tconst applyBtn = document.createElement('button');\n\t\t\tapplyBtn.type = 'button';\n\t\t\tapplyBtn.textContent = 'Apply';\n\t\t\tapplyBtn.setAttribute('aria-label', 'Apply link');\n\t\t\tapplyBtn.style.cssText = 'width:100%;padding:6px 12px;margin-top:4px;cursor:pointer;';\n\n\t\t\tapplyBtn.addEventListener('mousedown', (e) => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t\tconst href = input.value.trim();\n\t\t\t\tif (href) {\n\t\t\t\t\tthis.addLink(context, context.getState(), href);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tinput.addEventListener('keydown', (e) => {\n\t\t\t\tif (e.key === 'Enter') {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tconst href = input.value.trim();\n\t\t\t\t\tif (href) {\n\t\t\t\t\t\tthis.addLink(context, context.getState(), href);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tcontainer.appendChild(input);\n\t\t\tcontainer.appendChild(applyBtn);\n\n\t\t\t// Auto-focus input\n\t\t\trequestAnimationFrame(() => input.focus());\n\t\t}\n\t}\n}\n"],"names":["DEFAULT_CONFIG","openInNewTab","LinkPlugin","id","name","priority","config","this","context","registerMarkSpec","registerCommands","registerKeymap","registerToolbarItem","type","rank","attrs","href","default","mark","a","document","createElement","setAttribute","toHTMLString","content","escapeHTML","String","parseHTML","tag","getAttrs","el","getAttribute","sanitize","tags","registerCommand","state","getState","toggleLink","removeLink","executeCommand","group","icon","label","tooltip","formatShortcut","command","popupType","separatorAfter","renderPopup","container","ctx","renderLinkPopup","isActive","isLinkActive","isEnabled","isCollapsed","selection","sel","isNodeSelection","block","getBlock","anchor","blockId","marks","getBlockMarksAtOffset","offset","hasMark","markType","range","selectionRange","getBlockOrder","builder","transaction","forEachBlockInRange","from","to","addMark","setSelection","dispatch","build","blockOrder","textChildren","pos","child","children","end","text","length","push","hasLink","cursorIdx","findIndex","c","cursorEntry","startIdx","endIdx","startEntry","endEntry","linkStart","linkEnd","removeMark","style","padding","minWidth","removeBtn","textContent","cssText","addEventListener","e","preventDefault","stopPropagation","appendChild","input","placeholder","applyBtn","value","trim","addLink","key","requestAnimationFrame","focus"],"mappings":";;;;;;AAgCA,MAAMA,IAA6B,EAClCC,mBAAc;AAKR,MAAMC,EAAAA;AAAAA,EACHC,KAAK;AAAA,EACLC,OAAO;AAAA,EACPC,WAAW;AAAA,EAEHC;AAAAA,EAEjB,YAAYA,IAAAA;AACXC,SAAKD,SAAS,KAAKN,GAAAA,GAAmBM,GAAAA;AAAAA,EACvC;AAAA,EAEA,KAAKE;AACJD,SAAKE,iBAAiBD,EAAAA,GACtBD,KAAKG,iBAAiBF,EAAAA,GACtBD,KAAKI,eAAeH,EAAAA,GACpBD,KAAKK,oBAAoBJ,EAAAA;AAAAA,EAC1B;AAAA,EAEQ,iBAAiBA;AACxB,UAAMP,KAAeM,KAAKD,OAAOL;AAEjCO,IAAAA,GAAQC,iBAAiB,EACxBI,MAAM,QACNC,MAAM,IACNC,OAAO,EACNC,MAAM,EAAEC,SAAS,GAAA,EAAA,GAElB,MAAMC,IAAAA;AACL,YAAMC,KAAIC,SAASC,cAAc,MAC3BL,KAAOE,GAAKH,OAAOC,QAAQ;AAMjC,aALAG,GAAEG,aAAa,QAAQN,EAAAA,GACnBf,OACHkB,GAAEG,aAAa,UAAU,QAAA,GACzBH,GAAEG,aAAa,OAAO,yBAEhBH;AAAAA,IACR,GACAI,cAAc,CAACL,IAAMM,OAEb,YADcC,EAAWC,OAAOR,GAAKH,OAAOC,QAAQ,EAAA,CAAA,CAAA,KAC/BQ,UAE7BG,WAAW,CACV,EACCC,KAAK,KACLC,UAAWC,CAAAA,QAEH,EAAEd,MADYc,GAAGC,aAAa,WAAW,GAAA,GAAA,CAAA,GAKnDC,UAAU,EAAEC,MAAM,CAAC,GAAA,GAAMlB,OAAO,CAAC,QAAQ,UAAU;EAErD;AAAA,EAEQ,iBAAiBP,IAAAA;AACxBA,IAAAA,GAAQ0B,gBAAgB,cAAc;AACrC,YAAMC,KAAQ3B,GAAQ4B,SAAAA;AACtB,aAAO7B,KAAK8B,WAAW7B,IAAS2B,EAAAA;AAAAA,IAAAA,CAAAA,GAGjC3B,GAAQ0B,gBAAgB,WAAW,MAAA,QAMnC1B,GAAQ0B,gBAAgB,cAAc,MAAA;AACrC,YAAMC,KAAQ3B,GAAQ4B,SAAAA;AACtB,aAAO7B,KAAK+B,WAAW9B,IAAS2B,EAAAA;AAAAA,IAAAA,CAAAA;AAAAA,EAElC;AAAA,EAEQ,eAAe3B,IAAAA;AACtBA,IAAAA,GAAQG,eAAe,EACtB,SAAS,MAAMH,GAAQ+B,eAAe,YAAA,EAAA,CAAA;AAAA,EAExC;AAAA,EAEQ,oBAAoB/B,IAAAA;AAI3BA,IAAAA,GAAQI,oBAAoB,EAC3BT,IAAI,QACJqC,OAAO,UACPC,MALA,qSAMAC,OAAO,QACPC,SAAS,gBAAgBC,EAAe,OAAA,CAAA,KACxCC,SAAS,cACTxC,UAAU,IACVyC,WAAW,UACXC,gBAAgBxC,KAAKD,OAAOyC,gBAC5BC,aAAa,CAACC,IAAWC,OAAAA;AACxB3C,WAAK4C,gBAAgBF,IAAWC,EAAAA;AAAAA,IAAAA,GAEjCE,UAAWjB,CAAAA,OAAU5B,KAAK8C,aAAalB,KACvCmB,WAAYnB,CAAAA,OAAAA,CAAWoB,EAAYpB,GAAMqB,SAAAA,EAAAA,CAAAA;AAAAA,EAE3C;AAAA,EAEQ,aAAarB,IAAAA;AACpB,UAAMsB,KAAMtB,GAAMqB;AAClB,QAAIE,EAAgBD,EAAAA,EAAM,QAAA;AAC1B,QAAIF,EAAYE,EAAAA,GAAM;AACrB,YAAME,KAAQxB,GAAMyB,SAASH,GAAII,OAAOC,OAAAA;AACxC,UAAA,CAAKH,GAAO,QAAA;AACZ,YAAMI,KAAQC,EAAsBL,IAAOF,GAAII,OAAOI,MAAAA;AACtD,aAAOC,EAAQH,IAAOI,EAAS;IAChC;AAGA,UAAMR,KAAQxB,GAAMyB,SAASH,GAAII,OAAOC;AACxC,QAAA,CAAKH,GAAO;AACZ,UAAMI,KAAQC,EAAsBL,IAAOF,GAAII,OAAOI;AACtD,WAAOC,EAAQH,IAAOI,EAAS,MAAA,CAAA;AAAA,EAChC;AAAA,EAEQ,WAAW3D,IAAwB2B,IAAAA;AAC1C,WAAA,CAAA,CAAI5B,KAAK8C,aAAalB,EAAAA,KACd5B,KAAK+B,WAAW9B,IAAS2B;EAIlC;AAAA,EAEQ,QAAQ3B,IAAwB2B,IAAoBnB,IAAAA;AAC3D,UAAMyC,KAAMtB,GAAMqB;AAClB,QAAIE,EAAgBD,EAAAA,EAAM,QAAA;AAC1B,QAAIF,EAAYE,EAAAA,EAAM;AAEtB,UAAMW,KAAQC,EAAeZ,IAAKtB,GAAMmC,cAAAA,CAAAA,GAClCC,KAAUpC,GAAMqC,YAAY,YAC5BtD,IAAO,EAAEL,MAAMsD,EAAS,MAAA,GAASpD,OAAO,EAAEC,MAAAA,GAAAA,EAAAA;AAQhD,WANAyD,EAAoBtC,IAAOiC,IAAO,CAACN,IAASY,IAAMC,OAAAA;AACjDJ,MAAAA,GAAQK,QAAQd,IAASY,IAAMC,IAAIzD,CAAAA;AAAAA,IAAAA,CAAAA,GAGpCqD,GAAQM,aAAapB,EAAAA,GACrBjD,GAAQsE,SAASP,GAAQQ,MAAAA,CAAAA,GAAAA;AAAAA,EAE1B;AAAA,EAEQ,WAAWvE,IAAwB2B,IAAAA;AAC1C,UAAMsB,KAAMtB,GAAMqB;AAClB,QAAIE,EAAgBD,IAAM,QAAA;AAC1B,UAAMuB,KAAa7C,GAAMmC,iBACnBF,KAAQb,EAAYE,EAAAA,IACvB,EAAEiB,MAAMjB,GAAII,QAAQc,IAAIlB,GAAII,OAAAA,IAC5BQ,EAAeZ,IAAKuB,EAAAA,GAEjBT,IAAUpC,GAAMqC,YAAY,SAAA;AAElC,QAAIjB,EAAYE,EAAAA,GAAM;AAErB,YAAME,KAAQxB,GAAMyB,SAASH,GAAII,OAAOC,OAAAA;AACxC,UAAA,CAAKH,GAAO,QAAA;AAKZ,YAAMsB,KAAiE,CAAA;AACvE,UAAIC,KAAM;AACV,iBAAWC,MAASxB,GAAMyB,UAAU;AACnC,YAAA,EAAM,UAAUD,IAAQ;AACxB,cAAME,KAAMH,KAAMC,GAAMG,KAAKC;AAC7BN,QAAAA,GAAaO,KAAK,EAAEN,KAAAA,IAAKG,KAAAA,IAAKI,SAASvB,EAAQiB,GAAMpB,OAAOI,EAAS,MAAA,CAAA,EAAA,CAAA,GACrEe,KAAMG;AAAAA,MACP;AAGA,YAAMK,KAAYT,GAAaU,UAC7BC,CAAAA,OAAMnC,GAAII,OAAOI,UAAU2B,GAAEV,OAAOzB,GAAII,OAAOI,UAAU2B,GAAEP,MAEvDQ,KAAcH,MAAa,IAAIT,GAAaS,EAAAA,IAAAA;AAClD,UAAA,OAAIA,OAAqBG,IAAaJ,QAAS;AAG/C,UAAIK,KAAWJ;AACf,aAAOI,KAAW,KAAKb,GAAaa,KAAW,CAAA,GAAIL,UAClDK,CAAAA;AAID,UAAIC,KAASL;AACb,aAAOK,KAASd,GAAaM,SAAS,KAAKN,GAAac,KAAS,CAAA,GAAIN,UACpEM,CAAAA;AAGD,YAAMC,IAAaf,GAAaa,EAAAA,GAC1BG,IAAWhB,GAAac,EAAAA;AAC9B,WAAKC,KAAAA,CAAeC,EAAU;AAE9B,YAAMC,IAAYF,EAAWd,KACvBiB,IAAUF,EAASZ;AAEzBd,QAAQ6B,WAAW3C,GAAII,OAAOC,SAASoC,GAAWC,GAAS,EAAEtF,MAAMsD,EAAS,MAAA,EAAA,CAAA;AAAA,IAC7E,MAECM,GAAoBtC,IAAOiC,IAAO,CAACN,IAASY,IAAMC,OAAAA;AACjDJ,QAAQ6B,WAAWtC,IAASY,IAAMC,IAAI,EAAE9D,MAAMsD,EAAS,MAAA,EAAA,CAAA;AAAA,IAAA,CAAA;AAMzD,WAFAI,EAAQM,aAAapB,EAAAA,GACrBjD,GAAQsE,SAASP,EAAQQ,MAAAA,CAAAA,GAAAA;AAAAA,EAE1B;AAAA,EAEQ,gBAAgB9B,IAAwBzC,IAAAA;AAC/CyC,IAAAA,GAAUoD,MAAMC,UAAU,OAC1BrD,GAAUoD,MAAME,WAAW;AAE3B,UAAMpE,KAAQ3B,GAAQ4B,SAAAA;AAGtB,QAFiB7B,KAAK8C,aAAalB,EAAAA,GAErB;AAEb,YAAMqE,KAAYpF,SAASC,cAAc,QAAA;AACzCmF,MAAAA,GAAU3F,OAAO,UACjB2F,GAAUC,cAAc,eACxBD,GAAUlF,aAAa,cAAc,gBACrCkF,GAAUH,MAAMK,UAAU,+CAC1BF,GAAUG,iBAAiB,aAAcC,CAAAA;AACxCA,QAAAA,GAAEC,eAAAA,GACFD,GAAEE,gBAAAA,GACFtG,GAAQ+B,eAAe,YAAA;AAAA,MAAA,CAAA,GAExBU,GAAU8D,YAAYP,EAAAA;AAAAA,IACvB,OAAO;AAEN,YAAMQ,KAAQ5F,SAASC,cAAc,OAAA;AACrC2F,MAAAA,GAAMnG,OAAO,OACbmG,GAAMC,cAAc,eACpBD,GAAM1F,aAAa,cAAc,KAAA,GACjC0F,GAAMX,MAAMK,UAAU;AAEtB,YAAMQ,KAAW9F,SAASC,cAAc,QAAA;AACxC6F,MAAAA,GAASrG,OAAO,UAChBqG,GAAST,cAAc,SACvBS,GAAS5F,aAAa,cAAc,YAAA,GACpC4F,GAASb,MAAMK,UAAU,8DAEzBQ,GAASP,iBAAiB,aAAcC,CAAAA;AACvCA,QAAAA,GAAEC,eAAAA,GACFD,GAAEE,gBAAAA;AACF,cAAM9F,KAAOgG,GAAMG,MAAMC,KAAAA;AACrBpG,QAAAA,MACHT,KAAK8G,QAAQ7G,IAASA,GAAQ4B,SAAAA,GAAYpB,EAAAA;AAAAA,MAAAA,CAAAA,GAI5CgG,GAAML,iBAAiB,WAAYC,CAAAA,OAAAA;AAClC,YAAc,YAAVA,GAAEU,KAAiB;AACtBV,UAAAA,GAAEC,eAAAA;AACF,gBAAM7F,KAAOgG,GAAMG,MAAMC,KAAAA;AACrBpG,UAAAA,MACHT,KAAK8G,QAAQ7G,IAASA,GAAQ4B,SAAAA,GAAYpB;QAE5C;AAAA,MAAA,CAAA,GAGDiC,GAAU8D,YAAYC,EAAAA,GACtB/D,GAAU8D,YAAYG,EAAAA,GAGtBK,sBAAsB,MAAMP,GAAMQ,MAAAA,CAAAA;AAAAA,IACnC;AAAA,EACD;AAAA;"}
@@ -0,0 +1,51 @@
1
+ import { Plugin, PluginContext } from '../Plugin.js';
2
+ export type ListType = 'bullet' | 'ordered' | 'checklist';
3
+ declare module '../../model/AttrRegistry.js' {
4
+ interface NodeAttrRegistry {
5
+ list_item: {
6
+ listType: ListType;
7
+ indent: number;
8
+ checked: boolean;
9
+ };
10
+ }
11
+ }
12
+ export interface ListConfig {
13
+ /** Which list types to enable. Defaults to all. */
14
+ readonly types: readonly ListType[];
15
+ /** Maximum indent depth. Defaults to 4. */
16
+ readonly maxIndent: number;
17
+ /** When true, a separator is rendered after the last list toolbar item. */
18
+ readonly separatorAfter?: boolean;
19
+ }
20
+ export declare class ListPlugin implements Plugin {
21
+ readonly id = "list";
22
+ readonly name = "List";
23
+ readonly priority = 35;
24
+ private readonly config;
25
+ constructor(config?: Partial<ListConfig>);
26
+ init(context: PluginContext): void;
27
+ private registerNodeSpec;
28
+ private registerCommands;
29
+ private registerKeymaps;
30
+ private registerInputRules;
31
+ private registerToolbarItems;
32
+ private toggleList;
33
+ private indent;
34
+ private outdent;
35
+ private setIndent;
36
+ private toggleChecked;
37
+ /**
38
+ * Handles Backspace at the start of a list item.
39
+ * Converts the list item back to a paragraph, preserving text.
40
+ */
41
+ private handleBackspace;
42
+ /**
43
+ * Handles Enter inside a list item.
44
+ * Empty item → exit list (convert to paragraph).
45
+ * Non-empty item → split and create a new list item with the same type.
46
+ */
47
+ private handleEnter;
48
+ private isListActive;
49
+ private getEnabledTypes;
50
+ }
51
+ //# sourceMappingURL=ListPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListPlugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/list/ListPlugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI1D,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;AAE1D,OAAO,QAAQ,6BAA6B,CAAC;IAC5C,UAAU,gBAAgB;QACzB,SAAS,EAAE;YAAE,QAAQ,EAAE,QAAQ,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;KACpE;CACD;AAID,MAAM,WAAW,UAAU;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CAClC;AAkDD,qBAAa,UAAW,YAAW,MAAM;IACxC,QAAQ,CAAC,EAAE,UAAU;IACrB,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAExB,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IAIxC,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IASlC,OAAO,CAAC,gBAAgB;IAmDxB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,UAAU;IAoClB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,aAAa;IAoBrB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAmBvB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IA4CnB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;CAGvB"}
@@ -0,0 +1,2 @@
1
+ export { ListPlugin, type ListConfig, type ListType, } from './ListPlugin.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/list/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,KAAK,UAAU,EACf,KAAK,QAAQ,GACb,MAAM,iBAAiB,CAAC"}