wendang-react 0.0.0-stage → 0.5.6

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 (548) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +381 -0
  3. package/README.md +478 -2
  4. package/dist/bindings/yjs.js +5 -0
  5. package/dist/chunks/AnchoredPanel-D3nte06T.js +68 -0
  6. package/dist/chunks/MindMapCanvas-D5XwjVH1.js +2072 -0
  7. package/dist/chunks/MindMapViewer-CQTQAgNq.js +180 -0
  8. package/dist/chunks/SpreadsheetViewer-DN_QQGud.js +157 -0
  9. package/dist/chunks/code-highlight-BmaqTe1m.js +30 -0
  10. package/dist/chunks/code-highlight-engine-BFDzDPzy.js +40 -0
  11. package/dist/chunks/comment-hint-DN7LKJAn.js +296 -0
  12. package/dist/chunks/composition-state-Bl-U1R_2.js +40 -0
  13. package/dist/chunks/extensions-B-CVVQhp.js +4210 -0
  14. package/dist/chunks/fuzzy-match-DYQR-4w2.js +89 -0
  15. package/dist/chunks/hooks-C53Dp0pr.js +35 -0
  16. package/dist/chunks/host-C-V688Rq.js +197 -0
  17. package/dist/chunks/index-Ii536CFG.js +720 -0
  18. package/dist/chunks/katex-WjrodtTU.js +71 -0
  19. package/dist/chunks/mind-map-controller-B6SxK3GZ.js +479 -0
  20. package/dist/chunks/mind-map-delete-CVsxdXfs.js +31 -0
  21. package/dist/chunks/mind-map-input-CEWRz_t6.js +43 -0
  22. package/dist/chunks/mind-topic-text-BBNTbFSe.js +446 -0
  23. package/dist/chunks/nodes-iKqTGr_N.js +291 -0
  24. package/dist/chunks/popover-placement-D3xVYdkt.js +47 -0
  25. package/dist/chunks/worksheet-engine-TJ54m0KC.js +125 -0
  26. package/dist/chunks/worksheet-preview-DsDVWxIM.js +129 -0
  27. package/dist/document/viewer.js +287 -0
  28. package/dist/document.js +3620 -0
  29. package/dist/index.js +8 -0
  30. package/dist/mindmap/viewer.js +6 -0
  31. package/dist/mindmap.js +123 -0
  32. package/dist/shared.js +52 -0
  33. package/dist/spreadsheet/viewer.js +5 -0
  34. package/dist/spreadsheet/xlsx.js +299 -0
  35. package/dist/spreadsheet.js +120 -0
  36. package/dist/styles/callout.css +34 -0
  37. package/dist/styles/code-block.css +58 -0
  38. package/dist/styles/content-style.css +619 -0
  39. package/dist/styles/editor-interaction.css +621 -0
  40. package/dist/styles/file-video.css +12 -0
  41. package/dist/styles/image-preview.css +103 -0
  42. package/dist/styles/mind-map-block.css +14 -0
  43. package/dist/styles/mind-map-canvas.css +80 -0
  44. package/dist/styles/mind-map-comments.css +93 -0
  45. package/dist/styles/mind-map-details.css +7 -0
  46. package/dist/styles/mind-map-preview.css +7 -0
  47. package/dist/styles/mind-map-topic.css +112 -0
  48. package/dist/styles/page-picker.css +47 -0
  49. package/dist/styles/renderer.css +45 -0
  50. package/dist/styles/spreadsheet-editor.css +3 -0
  51. package/dist/styles/tokens.css +154 -0
  52. package/dist/styles/video-dialog.css +41 -0
  53. package/dist/styles/video-embed.css +35 -0
  54. package/dist/styles.css +2615 -0
  55. package/dist/types/bindings/yjs.d.ts +3 -0
  56. package/dist/types/document/viewer.d.ts +3 -0
  57. package/dist/types/document.d.ts +7 -0
  58. package/dist/types/editor/BlockAffordance.d.ts +47 -0
  59. package/dist/types/editor/CalloutView.d.ts +2 -0
  60. package/dist/types/editor/CodeBlockView.d.ts +9 -0
  61. package/dist/types/editor/DocumentColorPanel.d.ts +8 -0
  62. package/dist/types/editor/DocumentEditor.d.ts +71 -0
  63. package/dist/types/editor/ImagePreview.d.ts +10 -0
  64. package/dist/types/editor/LinkPopover.d.ts +21 -0
  65. package/dist/types/editor/PagePicker.d.ts +7 -0
  66. package/dist/types/editor/SelectionToolbar.d.ts +11 -0
  67. package/dist/types/editor/SlashMenu.d.ts +30 -0
  68. package/dist/types/editor/TableControls.d.ts +9 -0
  69. package/dist/types/editor/VideoEmbedDialog.d.ts +6 -0
  70. package/dist/types/editor/document-gestures.d.ts +13 -0
  71. package/dist/types/editor/extensions.d.ts +29 -0
  72. package/dist/types/editor/nodes.d.ts +20 -0
  73. package/dist/types/editor/same-document.d.ts +2 -0
  74. package/dist/types/engine/collab/presence.d.ts +1 -0
  75. package/dist/types/engine/editor/block-commands.d.ts +35 -0
  76. package/dist/types/engine/editor/block-conversion.d.ts +12 -0
  77. package/dist/types/engine/editor/block-drag.d.ts +16 -0
  78. package/dist/types/engine/editor/block-menu-placement.d.ts +16 -0
  79. package/dist/types/engine/editor/callout-commands.d.ts +4 -0
  80. package/dist/types/engine/editor/collab-change-origin.d.ts +2 -0
  81. package/dist/types/engine/editor/delete-block.d.ts +2 -0
  82. package/dist/types/engine/editor/extensions/block-format.d.ts +19 -0
  83. package/dist/types/engine/editor/extensions/block-handle.d.ts +17 -0
  84. package/dist/types/engine/editor/extensions/block-menu-highlight.d.ts +4 -0
  85. package/dist/types/engine/editor/extensions/block-selection.d.ts +117 -0
  86. package/dist/types/engine/editor/extensions/code-highlight.d.ts +2 -0
  87. package/dist/types/engine/editor/extensions/collab-cursor.d.ts +13 -0
  88. package/dist/types/engine/editor/extensions/ime-guard.d.ts +8 -0
  89. package/dist/types/engine/editor/extensions/inline-comments.d.ts +15 -0
  90. package/dist/types/engine/editor/extensions/paste-sanitizer.d.ts +4 -0
  91. package/dist/types/engine/editor/extensions/slash.d.ts +16 -0
  92. package/dist/types/engine/editor/extensions/table-interaction.d.ts +9 -0
  93. package/dist/types/engine/editor/extensions/upload-placeholder.d.ts +48 -0
  94. package/dist/types/engine/editor/image-preview-gesture.d.ts +3 -0
  95. package/dist/types/engine/editor/ime/composition-state.d.ts +9 -0
  96. package/dist/types/engine/editor/insert-block.d.ts +6 -0
  97. package/dist/types/engine/editor/insert-picker-logic.d.ts +13 -0
  98. package/dist/types/engine/editor/link-edit.d.ts +10 -0
  99. package/dist/types/engine/editor/mind-map-controller.d.ts +63 -0
  100. package/dist/types/engine/editor/mind-map-delete.d.ts +7 -0
  101. package/dist/types/engine/editor/mind-map-drag.d.ts +16 -0
  102. package/dist/types/engine/editor/mind-map-input.d.ts +16 -0
  103. package/dist/types/engine/editor/mind-map-selection.d.ts +11 -0
  104. package/dist/types/engine/editor/nodes/columns.d.ts +6 -0
  105. package/dist/types/engine/editor/nodes/file-nodeview.d.ts +4 -0
  106. package/dist/types/engine/editor/nodes/file.d.ts +2 -0
  107. package/dist/types/engine/editor/nodes/image-gallery.d.ts +2 -0
  108. package/dist/types/engine/editor/nodes/image-nodeview.d.ts +4 -0
  109. package/dist/types/engine/editor/nodes/image.d.ts +1 -0
  110. package/dist/types/engine/editor/nodes/inline-style.d.ts +4 -0
  111. package/dist/types/engine/editor/nodes/math-nodeview.d.ts +2 -0
  112. package/dist/types/engine/editor/nodes/math.d.ts +3 -0
  113. package/dist/types/engine/editor/nodes/mention.d.ts +2 -0
  114. package/dist/types/engine/editor/nodes/mind-map-schema.d.ts +6 -0
  115. package/dist/types/engine/editor/nodes/page-link.d.ts +2 -0
  116. package/dist/types/engine/editor/nodes/table-cell.d.ts +4 -0
  117. package/dist/types/engine/editor/nodes/video-embed-schema.d.ts +2 -0
  118. package/dist/types/engine/editor/popover-placement.d.ts +35 -0
  119. package/dist/types/engine/editor/table-clipboard.d.ts +3 -0
  120. package/dist/types/engine/editor/table-commands.d.ts +20 -0
  121. package/dist/types/engine/editor/table-geometry.d.ts +34 -0
  122. package/dist/types/engine/editor/table-menu-placement.d.ts +30 -0
  123. package/dist/types/engine/editor/tracked-target.d.ts +8 -0
  124. package/dist/types/engine/editor/trailing-paragraph.d.ts +2 -0
  125. package/dist/types/engine/host.d.ts +2 -0
  126. package/dist/types/engine/renderer/attachments.d.ts +11 -0
  127. package/dist/types/engine/renderer/katex.d.ts +7 -0
  128. package/dist/types/engine/renderer/lazy-visible.d.ts +11 -0
  129. package/dist/types/engine/shared/callout.d.ts +1 -0
  130. package/dist/types/engine/shared/code-block.d.ts +1 -0
  131. package/dist/types/engine/shared/code-highlight-engine.d.ts +2 -0
  132. package/dist/types/engine/shared/code-highlight.d.ts +8 -0
  133. package/dist/types/engine/shared/comment-hint-position.d.ts +20 -0
  134. package/dist/types/engine/shared/comment-hint.d.ts +2 -0
  135. package/dist/types/engine/shared/document-colors.d.ts +1 -0
  136. package/dist/types/engine/shared/fuzzy-match.d.ts +13 -0
  137. package/dist/types/engine/shared/image-gallery.d.ts +1 -0
  138. package/dist/types/engine/shared/inline-comments.d.ts +1 -0
  139. package/dist/types/engine/shared/mind-map-phase2.d.ts +1 -0
  140. package/dist/types/engine/shared/mind-map.d.ts +1 -0
  141. package/dist/types/engine/shared/mind-topic-text.d.ts +1 -0
  142. package/dist/types/engine/shared/modal-stack.d.ts +6 -0
  143. package/dist/types/engine/shared/page-candidates.d.ts +6 -0
  144. package/dist/types/engine/shared/pinyin.d.ts +6 -0
  145. package/dist/types/engine/shared/preview-items.d.ts +10 -0
  146. package/dist/types/engine/shared/safe-href.d.ts +1 -0
  147. package/dist/types/engine/shared/table-cell-format.d.ts +1 -0
  148. package/dist/types/engine/shared/table-layout.d.ts +1 -0
  149. package/dist/types/engine/shared/video-embed.d.ts +1 -0
  150. package/dist/types/engine/shared/video-feedback.d.ts +1 -0
  151. package/dist/types/engine/shared/workbook.d.ts +1 -0
  152. package/dist/types/engine/worksheets/worksheet-engine.d.ts +16 -0
  153. package/dist/types/engine/worksheets/worksheet-format.d.ts +20 -0
  154. package/dist/types/engine/worksheets/worksheet-preview.d.ts +17 -0
  155. package/dist/types/engine/worksheets/worksheet-xlsx.d.ts +21 -0
  156. package/dist/types/engine/wui/core/dialog.d.ts +5 -0
  157. package/dist/types/engine/wui/core/listbox.d.ts +8 -0
  158. package/dist/types/engine/wui/core/placement.d.ts +40 -0
  159. package/dist/types/hooks.d.ts +7 -0
  160. package/dist/types/host-types.d.ts +58 -0
  161. package/dist/types/host.d.ts +24 -0
  162. package/dist/types/index.d.ts +5 -0
  163. package/dist/types/mindmap/Comments.d.ts +9 -0
  164. package/dist/types/mindmap/Details.d.ts +13 -0
  165. package/dist/types/mindmap/Field.d.ts +10 -0
  166. package/dist/types/mindmap/MindMapCanvas.d.ts +19 -0
  167. package/dist/types/mindmap/MindMapEditor.d.ts +30 -0
  168. package/dist/types/mindmap/MindMapViewer.d.ts +23 -0
  169. package/dist/types/mindmap/TopicLabel.d.ts +4 -0
  170. package/dist/types/mindmap/viewer.d.ts +2 -0
  171. package/dist/types/mindmap.d.ts +4 -0
  172. package/dist/types/renderer/CodeToolbar.d.ts +10 -0
  173. package/dist/types/renderer/DocumentViewer.d.ts +23 -0
  174. package/dist/types/renderer/media.d.ts +33 -0
  175. package/dist/types/shared.d.ts +12 -0
  176. package/dist/types/spreadsheet/SpreadsheetEditor.d.ts +23 -0
  177. package/dist/types/spreadsheet/SpreadsheetViewer.d.ts +13 -0
  178. package/dist/types/spreadsheet/viewer.d.ts +2 -0
  179. package/dist/types/spreadsheet/xlsx.d.ts +2 -0
  180. package/dist/types/spreadsheet.d.ts +3 -0
  181. package/dist/types/wui/AnchoredPanel.d.ts +14 -0
  182. package/dist/types/wui/Dropdown.d.ts +18 -0
  183. package/dist/types/wui/Icon.d.ts +7 -0
  184. package/dist/types/wui/Tooltip.d.ts +5 -0
  185. package/dist/types/wui/index.d.ts +69 -0
  186. package/package.json +122 -3
  187. package/third-party-notices/@fast-csv__format@4.3.5-LICENSE.txt +21 -0
  188. package/third-party-notices/@fast-csv__parse@4.3.6-LICENSE.txt +21 -0
  189. package/third-party-notices/@flatten-js__interval-tree@1.1.3-LICENSE.txt +21 -0
  190. package/third-party-notices/@floating-ui__core@1.8.0-LICENSE.txt +20 -0
  191. package/third-party-notices/@floating-ui__dom@1.8.0-LICENSE.txt +20 -0
  192. package/third-party-notices/@floating-ui__react-dom@2.1.9-LICENSE.txt +20 -0
  193. package/third-party-notices/@floating-ui__utils@0.2.12-LICENSE.txt +20 -0
  194. package/third-party-notices/@grpc__grpc-js@1.14.6-LICENSE.txt +201 -0
  195. package/third-party-notices/@grpc__proto-loader@0.8.1-LICENSE.txt +201 -0
  196. package/third-party-notices/@js-sdsl__ordered-map@4.4.2-LICENSE.txt +21 -0
  197. package/third-party-notices/@protobufjs__aspromise@1.1.2-LICENSE.txt +26 -0
  198. package/third-party-notices/@protobufjs__base64@1.1.2-LICENSE.txt +26 -0
  199. package/third-party-notices/@protobufjs__codegen@2.0.5-LICENSE.txt +26 -0
  200. package/third-party-notices/@protobufjs__eventemitter@1.1.1-LICENSE.txt +26 -0
  201. package/third-party-notices/@protobufjs__fetch@1.1.1-LICENSE.txt +26 -0
  202. package/third-party-notices/@protobufjs__float@1.0.2-LICENSE.txt +26 -0
  203. package/third-party-notices/@protobufjs__path@1.1.2-LICENSE.txt +26 -0
  204. package/third-party-notices/@protobufjs__pool@1.1.0-LICENSE.txt +26 -0
  205. package/third-party-notices/@protobufjs__utf8@1.1.2-LICENSE.txt +26 -0
  206. package/third-party-notices/@radix-ui__primitive@1.2.0-LICENSE.txt +21 -0
  207. package/third-party-notices/@radix-ui__react-arrow@1.1.17-LICENSE.txt +21 -0
  208. package/third-party-notices/@radix-ui__react-collection@1.2.0-LICENSE.txt +21 -0
  209. package/third-party-notices/@radix-ui__react-compose-refs@1.1.5-LICENSE.txt +21 -0
  210. package/third-party-notices/@radix-ui__react-context@1.3.0-LICENSE.txt +21 -0
  211. package/third-party-notices/@radix-ui__react-dialog@1.3.1-LICENSE.txt +21 -0
  212. package/third-party-notices/@radix-ui__react-direction@1.2.0-LICENSE.txt +21 -0
  213. package/third-party-notices/@radix-ui__react-dismissable-layer@1.2.1-LICENSE.txt +21 -0
  214. package/third-party-notices/@radix-ui__react-dropdown-menu@2.2.1-LICENSE.txt +21 -0
  215. package/third-party-notices/@radix-ui__react-focus-guards@1.2.0-LICENSE.txt +21 -0
  216. package/third-party-notices/@radix-ui__react-focus-scope@1.3.1-LICENSE.txt +21 -0
  217. package/third-party-notices/@radix-ui__react-hover-card@1.2.1-LICENSE.txt +21 -0
  218. package/third-party-notices/@radix-ui__react-id@1.1.4-LICENSE.txt +21 -0
  219. package/third-party-notices/@radix-ui__react-menu@2.2.1-LICENSE.txt +21 -0
  220. package/third-party-notices/@radix-ui__react-popover@1.3.1-LICENSE.txt +21 -0
  221. package/third-party-notices/@radix-ui__react-popper@1.4.0-LICENSE.txt +21 -0
  222. package/third-party-notices/@radix-ui__react-portal@1.2.0-LICENSE.txt +21 -0
  223. package/third-party-notices/@radix-ui__react-presence@1.1.11-LICENSE.txt +21 -0
  224. package/third-party-notices/@radix-ui__react-primitive@2.2.0-LICENSE.txt +21 -0
  225. package/third-party-notices/@radix-ui__react-roving-focus@1.2.1-LICENSE.txt +21 -0
  226. package/third-party-notices/@radix-ui__react-separator@1.2.0-LICENSE.txt +21 -0
  227. package/third-party-notices/@radix-ui__react-slot@1.5.0-LICENSE.txt +21 -0
  228. package/third-party-notices/@radix-ui__react-use-callback-ref@1.1.5-LICENSE.txt +21 -0
  229. package/third-party-notices/@radix-ui__react-use-controllable-state@1.3.0-LICENSE.txt +21 -0
  230. package/third-party-notices/@radix-ui__react-use-effect-event@0.0.5-LICENSE.txt +21 -0
  231. package/third-party-notices/@radix-ui__react-use-is-hydrated@0.1.3-LICENSE.txt +21 -0
  232. package/third-party-notices/@radix-ui__react-use-layout-effect@1.1.4-LICENSE.txt +21 -0
  233. package/third-party-notices/@radix-ui__react-use-rect@1.1.4-LICENSE.txt +21 -0
  234. package/third-party-notices/@radix-ui__react-use-size@1.1.5-LICENSE.txt +21 -0
  235. package/third-party-notices/@radix-ui__rect@1.1.3-LICENSE.txt +21 -0
  236. package/third-party-notices/@tiptap__core@3.31.4-LICENSE.md.txt +21 -0
  237. package/third-party-notices/@tiptap__extension-blockquote@3.31.4-LICENSE.md.txt +21 -0
  238. package/third-party-notices/@tiptap__extension-bold@3.31.4-LICENSE.md.txt +21 -0
  239. package/third-party-notices/@tiptap__extension-bullet-list@3.31.4-LICENSE.md.txt +21 -0
  240. package/third-party-notices/@tiptap__extension-code-block@3.31.4-LICENSE.md.txt +21 -0
  241. package/third-party-notices/@tiptap__extension-code@3.31.4-LICENSE.md.txt +21 -0
  242. package/third-party-notices/@tiptap__extension-collaboration@3.31.4-LICENSE.md.txt +21 -0
  243. package/third-party-notices/@tiptap__extension-document@3.31.4-LICENSE.md.txt +21 -0
  244. package/third-party-notices/@tiptap__extension-dropcursor@3.31.4-LICENSE.md.txt +21 -0
  245. package/third-party-notices/@tiptap__extension-gapcursor@3.31.4-LICENSE.md.txt +21 -0
  246. package/third-party-notices/@tiptap__extension-hard-break@3.31.4-LICENSE.md.txt +21 -0
  247. package/third-party-notices/@tiptap__extension-heading@3.31.4-LICENSE.md.txt +21 -0
  248. package/third-party-notices/@tiptap__extension-horizontal-rule@3.31.4-LICENSE.md.txt +21 -0
  249. package/third-party-notices/@tiptap__extension-image@3.31.4-LICENSE.md.txt +21 -0
  250. package/third-party-notices/@tiptap__extension-italic@3.31.4-LICENSE.md.txt +21 -0
  251. package/third-party-notices/@tiptap__extension-link@3.31.4-LICENSE.md.txt +21 -0
  252. package/third-party-notices/@tiptap__extension-list-item@3.31.4-LICENSE.md.txt +21 -0
  253. package/third-party-notices/@tiptap__extension-list-keymap@3.31.4-LICENSE.md.txt +21 -0
  254. package/third-party-notices/@tiptap__extension-list@3.31.4-LICENSE.md.txt +21 -0
  255. package/third-party-notices/@tiptap__extension-ordered-list@3.31.4-LICENSE.md.txt +21 -0
  256. package/third-party-notices/@tiptap__extension-paragraph@3.31.4-LICENSE.md.txt +21 -0
  257. package/third-party-notices/@tiptap__extension-placeholder@3.31.4-LICENSE.md.txt +21 -0
  258. package/third-party-notices/@tiptap__extension-strike@3.31.4-LICENSE.md.txt +21 -0
  259. package/third-party-notices/@tiptap__extension-table-cell@3.31.4-LICENSE.md.txt +21 -0
  260. package/third-party-notices/@tiptap__extension-table-header@3.31.4-LICENSE.md.txt +21 -0
  261. package/third-party-notices/@tiptap__extension-table-row@3.31.4-LICENSE.md.txt +21 -0
  262. package/third-party-notices/@tiptap__extension-table@3.31.4-LICENSE.md.txt +21 -0
  263. package/third-party-notices/@tiptap__extension-task-item@3.31.4-LICENSE.md.txt +21 -0
  264. package/third-party-notices/@tiptap__extension-task-list@3.31.4-LICENSE.md.txt +21 -0
  265. package/third-party-notices/@tiptap__extension-text@3.31.4-LICENSE.md.txt +21 -0
  266. package/third-party-notices/@tiptap__extension-underline@3.31.4-LICENSE.md.txt +21 -0
  267. package/third-party-notices/@tiptap__extensions@3.31.4-LICENSE.md.txt +21 -0
  268. package/third-party-notices/@tiptap__pm@3.31.4-LICENSE.txt +21 -0
  269. package/third-party-notices/@tiptap__starter-kit@3.31.4-LICENSE.md.txt +21 -0
  270. package/third-party-notices/@tiptap__y-tiptap@3.0.7-LICENSE.txt +22 -0
  271. package/third-party-notices/@types__hast@3.0.5-LICENSE.txt +21 -0
  272. package/third-party-notices/@types__node@14.18.63-LICENSE.txt +21 -0
  273. package/third-party-notices/@types__node@26.6.2-LICENSE.txt +21 -0
  274. package/third-party-notices/@types__react-dom@19.3.0-LICENSE.txt +21 -0
  275. package/third-party-notices/@types__react@19.3.0-LICENSE.txt +21 -0
  276. package/third-party-notices/@types__unist@3.0.3-LICENSE.txt +21 -0
  277. package/third-party-notices/@univerjs__core@1.1.0-LICENSE.txt +176 -0
  278. package/third-party-notices/@univerjs__design@1.1.0-LICENSE.txt +176 -0
  279. package/third-party-notices/@univerjs__docs-ui@1.1.0-LICENSE.txt +176 -0
  280. package/third-party-notices/@univerjs__docs@1.1.0-LICENSE.txt +176 -0
  281. package/third-party-notices/@univerjs__drawing@1.1.0-LICENSE.txt +176 -0
  282. package/third-party-notices/@univerjs__engine-formula@1.1.0-LICENSE.txt +176 -0
  283. package/third-party-notices/@univerjs__engine-render@1.1.0-LICENSE.txt +176 -0
  284. package/third-party-notices/@univerjs__icons@1.46.0-LICENSE.txt +21 -0
  285. package/third-party-notices/@univerjs__network@1.1.0-LICENSE.txt +176 -0
  286. package/third-party-notices/@univerjs__preset-sheets-core@1.1.0-LICENSE.txt +176 -0
  287. package/third-party-notices/@univerjs__preset-sheets-filter@1.1.0-LICENSE.txt +176 -0
  288. package/third-party-notices/@univerjs__preset-sheets-sort@1.1.0-LICENSE.txt +176 -0
  289. package/third-party-notices/@univerjs__presets@1.1.0-LICENSE.txt +176 -0
  290. package/third-party-notices/@univerjs__rpc@1.1.0-LICENSE.txt +176 -0
  291. package/third-party-notices/@univerjs__sheets-filter-ui@1.1.0-LICENSE.txt +176 -0
  292. package/third-party-notices/@univerjs__sheets-filter@1.1.0-LICENSE.txt +176 -0
  293. package/third-party-notices/@univerjs__sheets-formula-ui@1.1.0-LICENSE.txt +176 -0
  294. package/third-party-notices/@univerjs__sheets-formula@1.1.0-LICENSE.txt +176 -0
  295. package/third-party-notices/@univerjs__sheets-numfmt-ui@1.1.0-LICENSE.txt +176 -0
  296. package/third-party-notices/@univerjs__sheets-numfmt@1.1.0-LICENSE.txt +176 -0
  297. package/third-party-notices/@univerjs__sheets-sort-ui@1.1.0-LICENSE.txt +176 -0
  298. package/third-party-notices/@univerjs__sheets-sort@1.1.0-LICENSE.txt +176 -0
  299. package/third-party-notices/@univerjs__sheets-ui@1.1.0-LICENSE.txt +176 -0
  300. package/third-party-notices/@univerjs__sheets@1.1.0-LICENSE.txt +176 -0
  301. package/third-party-notices/@univerjs__telemetry@1.1.0-LICENSE.txt +176 -0
  302. package/third-party-notices/@univerjs__themes@1.1.0-LICENSE.txt +176 -0
  303. package/third-party-notices/@univerjs__ui@1.1.0-LICENSE.txt +176 -0
  304. package/third-party-notices/@wendellhu__redi@1.1.3-LICENSE.txt +7 -0
  305. package/third-party-notices/ansi-regex@5.0.1-license.txt +9 -0
  306. package/third-party-notices/ansi-styles@4.3.0-license.txt +9 -0
  307. package/third-party-notices/archiver-utils@2.1.0-LICENSE.txt +22 -0
  308. package/third-party-notices/archiver-utils@3.0.4-LICENSE.txt +22 -0
  309. package/third-party-notices/archiver@5.3.2-LICENSE.txt +22 -0
  310. package/third-party-notices/aria-hidden@1.2.6-LICENSE.txt +21 -0
  311. package/third-party-notices/array-buffer-byte-length@1.0.2-LICENSE.txt +21 -0
  312. package/third-party-notices/arraybuffer.prototype.slice@1.0.4-LICENSE.txt +21 -0
  313. package/third-party-notices/async-function@1.0.0-LICENSE.txt +21 -0
  314. package/third-party-notices/async-lock@1.4.1-LICENSE.txt +22 -0
  315. package/third-party-notices/async@3.2.6-LICENSE.txt +19 -0
  316. package/third-party-notices/available-typed-arrays@1.0.7-LICENSE.txt +21 -0
  317. package/third-party-notices/balanced-match@1.0.2-LICENSE.md.txt +21 -0
  318. package/third-party-notices/base64-js@1.5.1-LICENSE.txt +21 -0
  319. package/third-party-notices/big-integer@1.6.52-LICENSE.txt +24 -0
  320. package/third-party-notices/bl@4.1.0-LICENSE.md.txt +13 -0
  321. package/third-party-notices/bluebird@3.4.7-LICENSE.txt +21 -0
  322. package/third-party-notices/brace-expansion@1.1.21-LICENSE.txt +21 -0
  323. package/third-party-notices/brace-expansion@2.1.7-LICENSE.txt +21 -0
  324. package/third-party-notices/buffer-crc32@0.2.13-LICENSE.txt +19 -0
  325. package/third-party-notices/buffer-indexof-polyfill@1.0.2-LICENSE.txt +22 -0
  326. package/third-party-notices/buffer@5.7.1-LICENSE.txt +21 -0
  327. package/third-party-notices/call-bind-apply-helpers@1.0.2-LICENSE.txt +21 -0
  328. package/third-party-notices/call-bind@1.0.9-LICENSE.txt +21 -0
  329. package/third-party-notices/call-bound@1.0.4-LICENSE.txt +21 -0
  330. package/third-party-notices/class-variance-authority@0.7.1-LICENSE.txt +190 -0
  331. package/third-party-notices/cliui@8.0.1-LICENSE.txt.txt +14 -0
  332. package/third-party-notices/clsx@2.1.1-license.txt +9 -0
  333. package/third-party-notices/cmdk@1.1.1-LICENSE.md.txt +21 -0
  334. package/third-party-notices/collapse-white-space@2.1.0-license.txt +22 -0
  335. package/third-party-notices/color-convert@2.0.1-LICENSE.txt +21 -0
  336. package/third-party-notices/color-name@1.1.4-LICENSE.txt +8 -0
  337. package/third-party-notices/commander@15.0.0-LICENSE.txt +22 -0
  338. package/third-party-notices/compress-commons@4.1.2-LICENSE.txt +22 -0
  339. package/third-party-notices/concat-map@0.0.1-LICENSE.txt +18 -0
  340. package/third-party-notices/core-util-is@1.0.3-LICENSE.txt +19 -0
  341. package/third-party-notices/crc-32@1.2.2-LICENSE.txt +201 -0
  342. package/third-party-notices/crc32-stream@4.0.3-LICENSE.txt +22 -0
  343. package/third-party-notices/csstype@3.2.3-LICENSE.txt +19 -0
  344. package/third-party-notices/data-view-buffer@1.0.2-LICENSE.txt +21 -0
  345. package/third-party-notices/data-view-byte-length@1.0.2-LICENSE.txt +21 -0
  346. package/third-party-notices/data-view-byte-offset@1.0.1-LICENSE.txt +21 -0
  347. package/third-party-notices/dayjs@1.11.23-LICENSE.txt +21 -0
  348. package/third-party-notices/decimal.js@10.6.0-LICENCE.md.txt +23 -0
  349. package/third-party-notices/define-data-property@1.1.4-LICENSE.txt +21 -0
  350. package/third-party-notices/define-properties@1.2.1-LICENSE.txt +21 -0
  351. package/third-party-notices/dequal@2.0.3-license.txt +21 -0
  352. package/third-party-notices/detect-node-es@1.1.0-LICENSE.txt +21 -0
  353. package/third-party-notices/devlop@1.1.0-license.txt +22 -0
  354. package/third-party-notices/dunder-proto@1.0.1-LICENSE.txt +21 -0
  355. package/third-party-notices/duplexer2@0.1.4-LICENSE.md.txt +26 -0
  356. package/third-party-notices/end-of-stream@1.4.5-LICENSE.txt +21 -0
  357. package/third-party-notices/es-abstract-get@1.0.0-LICENSE.txt +21 -0
  358. package/third-party-notices/es-abstract@1.24.2-LICENSE.txt +21 -0
  359. package/third-party-notices/es-define-property@1.0.1-LICENSE.txt +21 -0
  360. package/third-party-notices/es-errors@1.3.0-LICENSE.txt +21 -0
  361. package/third-party-notices/es-object-atoms@1.1.2-LICENSE.txt +21 -0
  362. package/third-party-notices/es-set-tostringtag@2.1.0-LICENSE.txt +21 -0
  363. package/third-party-notices/es-to-primitive@1.3.4-LICENSE.txt +22 -0
  364. package/third-party-notices/escalade@3.2.0-license.txt +9 -0
  365. package/third-party-notices/exceljs@4.4.0-LICENSE.txt +22 -0
  366. package/third-party-notices/fast-csv@4.3.6-LICENSE.txt +21 -0
  367. package/third-party-notices/fast-diff@1.3.0-LICENSE.txt +201 -0
  368. package/third-party-notices/for-each@0.3.5-LICENSE.txt +22 -0
  369. package/third-party-notices/fs-constants@1.0.0-LICENSE.txt +21 -0
  370. package/third-party-notices/fs.realpath@1.0.0-LICENSE.txt +43 -0
  371. package/third-party-notices/fstream@1.0.12-LICENSE.txt +15 -0
  372. package/third-party-notices/function-bind@1.1.2-LICENSE.txt +20 -0
  373. package/third-party-notices/function.prototype.name@1.2.0-LICENSE.txt +21 -0
  374. package/third-party-notices/functions-have-names@1.2.3-LICENSE.txt +21 -0
  375. package/third-party-notices/generator-function@2.0.1-LICENSE.md.txt +7 -0
  376. package/third-party-notices/get-caller-file@2.0.5-LICENSE.md.txt +6 -0
  377. package/third-party-notices/get-intrinsic@1.3.0-LICENSE.txt +21 -0
  378. package/third-party-notices/get-nonce@1.0.1-LICENSE.txt +21 -0
  379. package/third-party-notices/get-proto@1.0.1-LICENSE.txt +21 -0
  380. package/third-party-notices/get-symbol-description@1.1.0-LICENSE.txt +21 -0
  381. package/third-party-notices/glob@7.2.3-LICENSE.txt +21 -0
  382. package/third-party-notices/globalthis@1.0.4-LICENSE.txt +21 -0
  383. package/third-party-notices/gopd@1.2.0-LICENSE.txt +21 -0
  384. package/third-party-notices/graceful-fs@4.2.11-LICENSE.txt +15 -0
  385. package/third-party-notices/has-bigints@1.1.0-LICENSE.txt +21 -0
  386. package/third-party-notices/has-property-descriptors@1.0.2-LICENSE.txt +21 -0
  387. package/third-party-notices/has-proto@1.2.0-LICENSE.txt +21 -0
  388. package/third-party-notices/has-symbols@1.1.0-LICENSE.txt +21 -0
  389. package/third-party-notices/has-tostringtag@1.0.2-LICENSE.txt +21 -0
  390. package/third-party-notices/hasown@2.0.4-LICENSE.txt +21 -0
  391. package/third-party-notices/highlight.js@11.11.2-LICENSE.txt +29 -0
  392. package/third-party-notices/ieee754@1.2.1-LICENSE.txt +11 -0
  393. package/third-party-notices/immediate@3.0.6-LICENSE.txt.txt +20 -0
  394. package/third-party-notices/inflight@1.0.6-LICENSE.txt +15 -0
  395. package/third-party-notices/inherits@2.0.4-LICENSE.txt +16 -0
  396. package/third-party-notices/internal-slot@1.1.0-LICENSE.txt +21 -0
  397. package/third-party-notices/is-array-buffer@3.0.5-LICENSE.txt +21 -0
  398. package/third-party-notices/is-async-function@2.1.1-LICENSE.txt +20 -0
  399. package/third-party-notices/is-bigint@1.1.0-LICENSE.txt +21 -0
  400. package/third-party-notices/is-boolean-object@1.2.2-LICENSE.txt +22 -0
  401. package/third-party-notices/is-callable@1.2.7-LICENSE.txt +22 -0
  402. package/third-party-notices/is-data-view@1.0.2-LICENSE.txt +21 -0
  403. package/third-party-notices/is-date-object@1.1.0-LICENSE.txt +22 -0
  404. package/third-party-notices/is-document.all@1.0.0-LICENSE.txt +21 -0
  405. package/third-party-notices/is-finalizationregistry@1.1.1-LICENSE.txt +21 -0
  406. package/third-party-notices/is-fullwidth-code-point@3.0.0-license.txt +9 -0
  407. package/third-party-notices/is-generator-function@1.1.2-LICENSE.txt +20 -0
  408. package/third-party-notices/is-map@2.0.3-LICENSE.txt +21 -0
  409. package/third-party-notices/is-negative-zero@2.0.3-LICENSE.txt +20 -0
  410. package/third-party-notices/is-number-object@1.1.1-LICENSE.txt +22 -0
  411. package/third-party-notices/is-regex@1.2.1-LICENSE.txt +20 -0
  412. package/third-party-notices/is-set@2.0.3-LICENSE.txt +21 -0
  413. package/third-party-notices/is-shared-array-buffer@1.0.4-LICENSE.txt +21 -0
  414. package/third-party-notices/is-string@1.1.1-LICENSE.txt +22 -0
  415. package/third-party-notices/is-symbol@1.1.1-LICENSE.txt +22 -0
  416. package/third-party-notices/is-typed-array@1.1.15-LICENSE.txt +22 -0
  417. package/third-party-notices/is-weakmap@2.0.2-LICENSE.txt +21 -0
  418. package/third-party-notices/is-weakref@1.1.1-LICENSE.txt +21 -0
  419. package/third-party-notices/is-weakset@2.0.4-LICENSE.txt +21 -0
  420. package/third-party-notices/isarray@2.0.5-LICENSE.txt +21 -0
  421. package/third-party-notices/isomorphic.js@0.2.5-LICENSE.txt +21 -0
  422. package/third-party-notices/jszip@3.10.2-LICENSE.markdown.txt +651 -0
  423. package/third-party-notices/katex@0.18.9-LICENSE.txt +21 -0
  424. package/third-party-notices/kdbush@4.1.0-LICENSE.txt +15 -0
  425. package/third-party-notices/lazystream@1.0.1-LICENSE.txt +23 -0
  426. package/third-party-notices/lib0@0.2.119-LICENSE.txt +21 -0
  427. package/third-party-notices/lie@3.3.0-license.md.txt +7 -0
  428. package/third-party-notices/linkifyjs@4.3.3-LICENSE.txt +19 -0
  429. package/third-party-notices/listenercount@1.0.1-LICENSE.md.txt +5 -0
  430. package/third-party-notices/lodash-es@4.18.1-LICENSE.txt +47 -0
  431. package/third-party-notices/lodash.camelcase@4.3.0-LICENSE.txt +47 -0
  432. package/third-party-notices/lodash.defaults@4.2.0-LICENSE.txt +47 -0
  433. package/third-party-notices/lodash.difference@4.5.0-LICENSE.txt +47 -0
  434. package/third-party-notices/lodash.escaperegexp@4.1.2-LICENSE.txt +47 -0
  435. package/third-party-notices/lodash.flatten@4.4.0-LICENSE.txt +47 -0
  436. package/third-party-notices/lodash.groupby@4.6.0-LICENSE.txt +47 -0
  437. package/third-party-notices/lodash.isboolean@3.0.3-LICENSE.txt +22 -0
  438. package/third-party-notices/lodash.isequal@4.5.0-LICENSE.txt +47 -0
  439. package/third-party-notices/lodash.isfunction@3.0.9-LICENSE.txt +47 -0
  440. package/third-party-notices/lodash.isnil@4.0.0-LICENSE.txt +22 -0
  441. package/third-party-notices/lodash.isplainobject@4.0.6-LICENSE.txt +47 -0
  442. package/third-party-notices/lodash.isundefined@3.0.1-LICENSE.txt.txt +22 -0
  443. package/third-party-notices/lodash.union@4.6.0-LICENSE.txt +47 -0
  444. package/third-party-notices/lodash.uniq@4.5.0-LICENSE.txt +47 -0
  445. package/third-party-notices/long@5.3.2-LICENSE.txt +202 -0
  446. package/third-party-notices/lowlight@3.3.0-license.txt +22 -0
  447. package/third-party-notices/math-intrinsics@1.1.0-LICENSE.txt +21 -0
  448. package/third-party-notices/minimatch@3.1.5-LICENSE.txt +15 -0
  449. package/third-party-notices/minimatch@5.1.9-LICENSE.txt +15 -0
  450. package/third-party-notices/minimist@1.2.8-LICENSE.txt +18 -0
  451. package/third-party-notices/mkdirp@0.5.6-LICENSE.txt +21 -0
  452. package/third-party-notices/n-gram@2.0.2-license.txt +22 -0
  453. package/third-party-notices/normalize-path@3.0.0-LICENSE.txt +21 -0
  454. package/third-party-notices/numfmt@4.0.1-LICENSE.txt +19 -0
  455. package/third-party-notices/object-inspect@1.13.4-LICENSE.txt +21 -0
  456. package/third-party-notices/object-keys@1.1.1-LICENSE.txt +21 -0
  457. package/third-party-notices/object.assign@4.1.7-LICENSE.txt +21 -0
  458. package/third-party-notices/once@1.4.0-LICENSE.txt +15 -0
  459. package/third-party-notices/orderedmap@2.1.1-LICENSE.txt +19 -0
  460. package/third-party-notices/own-keys@1.0.2-LICENSE.txt +21 -0
  461. package/third-party-notices/pako@1.0.11-LICENSE.txt +21 -0
  462. package/third-party-notices/path-is-absolute@1.0.1-license.txt +21 -0
  463. package/third-party-notices/possible-typed-array-names@1.1.0-LICENSE.txt +21 -0
  464. package/third-party-notices/process-nextick-args@2.0.1-license.md.txt +19 -0
  465. package/third-party-notices/prosemirror-changeset@2.4.4-LICENSE.txt +19 -0
  466. package/third-party-notices/prosemirror-commands@1.7.2-LICENSE.txt +19 -0
  467. package/third-party-notices/prosemirror-dropcursor@1.8.4-LICENSE.txt +19 -0
  468. package/third-party-notices/prosemirror-gapcursor@1.4.1-LICENSE.txt +19 -0
  469. package/third-party-notices/prosemirror-history@1.5.1-LICENSE.txt +19 -0
  470. package/third-party-notices/prosemirror-inputrules@1.5.1-LICENSE.txt +19 -0
  471. package/third-party-notices/prosemirror-keymap@1.2.3-LICENSE.txt +19 -0
  472. package/third-party-notices/prosemirror-model@1.25.12-LICENSE.txt +19 -0
  473. package/third-party-notices/prosemirror-schema-list@1.5.1-LICENSE.txt +19 -0
  474. package/third-party-notices/prosemirror-state@1.4.4-LICENSE.txt +19 -0
  475. package/third-party-notices/prosemirror-tables@1.8.5-LICENSE.txt +19 -0
  476. package/third-party-notices/prosemirror-transform@1.12.2-LICENSE.txt +19 -0
  477. package/third-party-notices/prosemirror-view@1.42.6-LICENSE.txt +19 -0
  478. package/third-party-notices/protobufjs@7.6.6-LICENSE.txt +39 -0
  479. package/third-party-notices/quickselect@3.0.0-LICENSE.txt +15 -0
  480. package/third-party-notices/rbush@4.0.1-LICENSE.txt +21 -0
  481. package/third-party-notices/react-dom@19.3.1-LICENSE.txt +21 -0
  482. package/third-party-notices/react-remove-scroll@2.7.2-LICENSE.txt +21 -0
  483. package/third-party-notices/react-style-singleton@2.2.3-LICENSE.txt +21 -0
  484. package/third-party-notices/react@19.3.1-LICENSE.txt +21 -0
  485. package/third-party-notices/readable-stream@2.3.8-LICENSE.txt +47 -0
  486. package/third-party-notices/readable-stream@3.6.2-LICENSE.txt +47 -0
  487. package/third-party-notices/readdir-glob@1.1.3-LICENSE.txt +201 -0
  488. package/third-party-notices/reflect.getprototypeof@1.0.10-LICENSE.txt +21 -0
  489. package/third-party-notices/regexp.prototype.flags@1.5.4-LICENSE.txt +22 -0
  490. package/third-party-notices/require-directory@2.1.1-LICENSE.txt +22 -0
  491. package/third-party-notices/rimraf@2.7.1-LICENSE.txt +15 -0
  492. package/third-party-notices/rope-sequence@1.3.4-LICENSE.txt +19 -0
  493. package/third-party-notices/rxjs@7.8.2-LICENSE.txt.txt +202 -0
  494. package/third-party-notices/safe-array-concat@1.1.4-LICENSE.txt +21 -0
  495. package/third-party-notices/safe-buffer@5.1.2-LICENSE.txt +21 -0
  496. package/third-party-notices/safe-buffer@5.2.1-LICENSE.txt +21 -0
  497. package/third-party-notices/safe-push-apply@1.0.0-LICENSE.txt +21 -0
  498. package/third-party-notices/safe-regex-test@1.1.0-LICENSE.txt +21 -0
  499. package/third-party-notices/scheduler@0.28.0-LICENSE.txt +21 -0
  500. package/third-party-notices/set-function-length@1.2.2-LICENSE.txt +21 -0
  501. package/third-party-notices/set-function-name@2.0.2-LICENSE.txt +21 -0
  502. package/third-party-notices/set-proto@1.0.0-LICENSE.txt +21 -0
  503. package/third-party-notices/setimmediate@1.0.5-LICENSE.txt.txt +20 -0
  504. package/third-party-notices/side-channel-list@1.0.1-LICENSE.txt +21 -0
  505. package/third-party-notices/side-channel-map@1.0.1-LICENSE.txt +21 -0
  506. package/third-party-notices/side-channel-weakmap@1.0.2-LICENSE.txt +21 -0
  507. package/third-party-notices/side-channel@1.1.1-LICENSE.txt +21 -0
  508. package/third-party-notices/sonner@2.0.8-LICENSE.md.txt +21 -0
  509. package/third-party-notices/stop-iteration-iterator@1.1.0-LICENSE.txt +21 -0
  510. package/third-party-notices/string-width@4.2.3-license.txt +9 -0
  511. package/third-party-notices/string.prototype.trim@1.2.11-LICENSE.txt +21 -0
  512. package/third-party-notices/string.prototype.trimend@1.0.10-LICENSE.txt +21 -0
  513. package/third-party-notices/string.prototype.trimstart@1.0.8-LICENSE.txt +21 -0
  514. package/third-party-notices/string_decoder@1.1.1-LICENSE.txt +48 -0
  515. package/third-party-notices/string_decoder@1.3.0-LICENSE.txt +48 -0
  516. package/third-party-notices/strip-ansi@6.0.1-license.txt +9 -0
  517. package/third-party-notices/tar-stream@2.2.0-LICENSE.txt +21 -0
  518. package/third-party-notices/tiny-pinyin@1.3.2-LICENSE.txt +21 -0
  519. package/third-party-notices/tmp@0.2.7-LICENSE.txt +21 -0
  520. package/third-party-notices/traverse@0.3.10-LICENSE.txt +21 -0
  521. package/third-party-notices/trigram-utils@2.0.1-license.txt +22 -0
  522. package/third-party-notices/tslib@2.8.1-LICENSE.txt.txt +12 -0
  523. package/third-party-notices/typed-array-buffer@1.0.3-LICENSE.txt +21 -0
  524. package/third-party-notices/typed-array-byte-length@1.0.3-LICENSE.txt +21 -0
  525. package/third-party-notices/typed-array-byte-offset@1.0.5-LICENSE.txt +21 -0
  526. package/third-party-notices/typed-array-length@1.0.8-LICENSE.txt +21 -0
  527. package/third-party-notices/typedarray.prototype.slice@1.0.6-LICENSE.txt +21 -0
  528. package/third-party-notices/unbox-primitive@1.1.0-LICENSE.txt +21 -0
  529. package/third-party-notices/undici-types@8.9.0-LICENSE.txt +21 -0
  530. package/third-party-notices/unzipper@0.10.14-LICENSE.txt +25 -0
  531. package/third-party-notices/use-callback-ref@1.3.3-LICENSE.txt +21 -0
  532. package/third-party-notices/use-sidecar@1.1.3-LICENSE.txt +21 -0
  533. package/third-party-notices/util-deprecate@1.0.2-LICENSE.txt +24 -0
  534. package/third-party-notices/uuid@8.3.2-LICENSE.md.txt +9 -0
  535. package/third-party-notices/w3c-keyname@2.2.8-LICENSE.txt +19 -0
  536. package/third-party-notices/which-boxed-primitive@1.1.1-LICENSE.txt +21 -0
  537. package/third-party-notices/which-builtin-type@1.2.1-LICENSE.txt +21 -0
  538. package/third-party-notices/which-collection@1.0.2-LICENSE.txt +21 -0
  539. package/third-party-notices/which-typed-array@1.1.24-LICENSE.txt +22 -0
  540. package/third-party-notices/wrap-ansi@7.0.0-license.txt +9 -0
  541. package/third-party-notices/wrappy@1.0.2-LICENSE.txt +15 -0
  542. package/third-party-notices/xmlchars@2.2.0-LICENSE.txt +18 -0
  543. package/third-party-notices/y-protocols@1.0.7-LICENSE.txt +21 -0
  544. package/third-party-notices/y18n@5.0.8-LICENSE.txt +13 -0
  545. package/third-party-notices/yargs-parser@21.1.1-LICENSE.txt.txt +14 -0
  546. package/third-party-notices/yargs@17.7.3-LICENSE.txt +21 -0
  547. package/third-party-notices/yjs@13.6.33-LICENSE.txt +23 -0
  548. package/third-party-notices/zip-stream@4.1.1-LICENSE.txt +22 -0
@@ -0,0 +1,3620 @@
1
+ "use client";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
+ import { getMarkRange, Editor, getSchema } from "@tiptap/core";
4
+ import { TextSelection, Selection, NodeSelection } from "@tiptap/pm/state";
5
+ import { createPortal } from "react-dom";
6
+ import { useRef, useState, useLayoutEffect, useEffect, useMemo, useId, forwardRef, useCallback, useImperativeHandle } from "react";
7
+ import { ContentSchemaService, requiredSchemaVersion, SCHEMA_VERSION } from "wendang/schema";
8
+ import { safeHref, TEXT_COLORS, HIGHLIGHT_COLORS, documentColorAttributes, parseVideoUrl } from "wendang/shared";
9
+ import { b as buildColumnsJson, d as dismissSlash, u as unwrapCallout, h as highlightMenuBlock, T as TEXT_ALIGNS, c as currentTableCellPosition, r as readTableGeometry, t as tableHeaderActive, a as tablePositionAtCell, s as selectTableAxis, i as insertTableBoundary, e as CELL_ALIGNS, f as cellPositionAtDOM, g as selectTableCell, j as clippedTableSelection, k as tableSelectionMarkActive, l as setTableSelectionMark, m as setTableHeaderBackground, n as currentCellRect, o as setColumnWidth, p as autoFitColumn, q as distributeTableColumns, v as getSelectedBlocks, B as BlockRangeSelection, w as setBlockRangeSelection, x as setImageRangeSelection, y as pastedImageFiles, z as pastedRemoteImageUrls, A as coreExtensions, D as refreshInlineComments, E as addUploadPlaceholder, F as currentPlaceholderPos, G as insertUploadedBlock, H as removeUploadPlaceholder, I as replacePlaceholderWithNode } from "./chunks/extensions-B-CVVQhp.js";
10
+ import { C } from "./chunks/extensions-B-CVVQhp.js";
11
+ import { a as useContentContext, p as primeAttachment, u as unavailableAttachment } from "./chunks/host-C-V688Rq.js";
12
+ import { c as createMindMapController } from "./chunks/mind-map-controller-B6SxK3GZ.js";
13
+ import { M as MindMapBlock } from "./chunks/MindMapViewer-CQTQAgNq.js";
14
+ import { p as previewItemsFrom, I as ImagePreview, V as VideoView } from "./chunks/mind-topic-text-BBNTbFSe.js";
15
+ import { u as useLatest, a as useReady, b as asError } from "./chunks/hooks-C53Dp0pr.js";
16
+ import { W as WButton, b as WIcon, l as lockScroll, t as trapTab, c as WDialog } from "./chunks/index-Ii536CFG.js";
17
+ import { p as placeAnchoredPopover } from "./chunks/popover-placement-D3xVYdkt.js";
18
+ import { A, a, b } from "./chunks/popover-placement-D3xVYdkt.js";
19
+ import { isSafeHref } from "wendang/internal/safe-href";
20
+ import { createMindMap } from "wendang/internal/mind-map";
21
+ import { s as shouldIgnoreForIme } from "./chunks/composition-state-Bl-U1R_2.js";
22
+ import { DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
23
+ import { createTable } from "@tiptap/extension-table";
24
+ import { createMindMap as createMindMap$1 } from "wendang/mindmap";
25
+ import { closeHistory } from "@tiptap/pm/history";
26
+ import { yUndoPluginKey, ySyncPluginKey } from "@tiptap/y-tiptap";
27
+ import { CellSelection, TableMap } from "@tiptap/pm/tables";
28
+ import { CELL_BACKGROUNDS } from "wendang/internal/table-cell-format";
29
+ import { r as rankPages } from "./chunks/fuzzy-match-DYQR-4w2.js";
30
+ import { inspectVideoUrl, VIDEO_PROVIDER_LABELS, videoSourceUrl } from "wendang/internal/video-embed";
31
+ function linkHrefProblem(value) {
32
+ const href = value.trim();
33
+ if (!href) return "请输入链接地址。";
34
+ if (href.length > 2048 || /[\u0000-\u0020\u007f]/.test(href) || !isSafeHref(href)) {
35
+ return "请输入有效的网页、邮箱或站内链接。";
36
+ }
37
+ if (/^https?:/i.test(href)) {
38
+ try {
39
+ const url = new URL(href);
40
+ if (!/^https?:\/\//i.test(href) || !url.hostname) return "请输入完整的网址,例如 https://example.com。";
41
+ } catch {
42
+ return "请输入完整的网址,例如 https://example.com。";
43
+ }
44
+ }
45
+ if (/^mailto:$/i.test(href)) return "请输入完整的邮箱链接,例如 mailto:name@example.com。";
46
+ return "";
47
+ }
48
+ function createLinkEditTarget(state, selection = state.selection) {
49
+ if (!(selection instanceof TextSelection)) return null;
50
+ const link = state.schema.marks.link;
51
+ if (!link) return null;
52
+ let { from, to } = selection;
53
+ const range = getMarkRange(selection.$from, link);
54
+ if (range && to <= range.to) ({ from, to } = range);
55
+ if (from === to) return null;
56
+ let href = "";
57
+ state.doc.nodesBetween(from, to, (node) => {
58
+ var _a;
59
+ const value = (_a = node.marks.find((mark) => mark.type === link)) == null ? void 0 : _a.attrs.href;
60
+ if (!href && typeof value === "string") href = value;
61
+ });
62
+ return {
63
+ from,
64
+ to,
65
+ valid: true,
66
+ href,
67
+ hasLink: state.doc.rangeHasMark(from, to, link),
68
+ contentJSON: JSON.stringify(state.doc.slice(from, to).toJSON())
69
+ };
70
+ }
71
+ function linkEditTargetCurrent(state, target) {
72
+ return target.valid && target.from < target.to && target.from >= 0 && target.to <= state.doc.content.size && JSON.stringify(state.doc.slice(target.from, target.to).toJSON()) === target.contentJSON;
73
+ }
74
+ function LinkPopover({ target, onEdit, onSave, onRemove, onClose }) {
75
+ const { portal } = useContentContext(), root = useRef(null);
76
+ const [value, setValue] = useState(target.href), [error, setError] = useState(""), [geometry, setGeometry] = useState({ left: target.anchor.left, top: target.anchor.top + target.anchor.height + 8 });
77
+ useLayoutEffect(() => {
78
+ const measure = () => {
79
+ var _a;
80
+ const rect = (_a = root.current) == null ? void 0 : _a.getBoundingClientRect();
81
+ if (!rect) return;
82
+ setGeometry(placeAnchoredPopover({ align: target.mode === "preview" ? "center" : "start", prefer: target.mode === "preview" ? "above" : "below", anchor: target.anchor, host: { left: 0, top: 0 }, card: rect, bounds: { left: 0, top: 0, width: innerWidth, height: innerHeight } }));
83
+ };
84
+ measure();
85
+ const observer = new ResizeObserver(measure);
86
+ if (root.current) observer.observe(root.current);
87
+ return () => observer.disconnect();
88
+ }, [target.mode, target.anchor, portal]);
89
+ useEffect(() => {
90
+ const outside = (event) => {
91
+ var _a;
92
+ if (event.target instanceof Node && !((_a = root.current) == null ? void 0 : _a.contains(event.target))) onClose();
93
+ };
94
+ const escape = (event) => {
95
+ if (event.key === "Escape") {
96
+ event.stopPropagation();
97
+ onClose();
98
+ }
99
+ };
100
+ document.addEventListener("pointerdown", outside);
101
+ document.addEventListener("keydown", escape);
102
+ window.addEventListener("scroll", onClose, true);
103
+ return () => {
104
+ document.removeEventListener("pointerdown", outside);
105
+ document.removeEventListener("keydown", escape);
106
+ window.removeEventListener("scroll", onClose, true);
107
+ };
108
+ }, []);
109
+ useEffect(() => {
110
+ if (target.mode !== "preview") return;
111
+ let timer;
112
+ const move = (event) => {
113
+ var _a;
114
+ const a2 = target.anchor;
115
+ const inside = ((_a = root.current) == null ? void 0 : _a.contains(event.target)) || event.clientX >= a2.left - 5 && event.clientX <= a2.left + a2.width + 5 && event.clientY >= a2.top - 5 && event.clientY <= a2.top + a2.height + 5;
116
+ if (inside) {
117
+ clearTimeout(timer);
118
+ timer = void 0;
119
+ } else if (!timer) timer = setTimeout(onClose, 180);
120
+ };
121
+ document.addEventListener("mousemove", move);
122
+ return () => {
123
+ clearTimeout(timer);
124
+ document.removeEventListener("mousemove", move);
125
+ };
126
+ }, [target.mode, target.anchor, onClose]);
127
+ if (!portal) return null;
128
+ return createPortal(/* @__PURE__ */ jsx("div", { ref: root, className: "wr-link-popover", style: geometry, role: target.mode === "edit" ? "dialog" : "toolbar", "aria-label": target.mode === "edit" ? "编辑链接" : "链接预览", children: target.mode === "preview" ? /* @__PURE__ */ jsxs("div", { className: "wr-toolbar", children: [
129
+ /* @__PURE__ */ jsx("a", { href: safeHref(target.href), target: "_blank", rel: "noopener noreferrer", children: target.href }),
130
+ /* @__PURE__ */ jsx(WButton, { onClick: onEdit, children: "编辑链接" }),
131
+ /* @__PURE__ */ jsx(WButton, { onClick: onRemove, children: "取消链接" })
132
+ ] }) : /* @__PURE__ */ jsxs("form", { onSubmit: (event) => {
133
+ event.preventDefault();
134
+ const problem = linkHrefProblem(value);
135
+ if (problem) {
136
+ setError(problem);
137
+ return;
138
+ }
139
+ onSave(value.trim());
140
+ }, children: [
141
+ /* @__PURE__ */ jsxs("div", { className: "wr-toolbar", children: [
142
+ /* @__PURE__ */ jsx("input", { autoFocus: true, "aria-label": "链接地址", value, onChange: (event) => {
143
+ setValue(event.target.value);
144
+ setError("");
145
+ }, placeholder: "粘贴或输入链接" }),
146
+ /* @__PURE__ */ jsx(WButton, { htmlType: "submit", children: "确认" }),
147
+ /* @__PURE__ */ jsx(WButton, { onClick: onRemove, children: "取消链接" })
148
+ ] }),
149
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
150
+ ] }) }), portal);
151
+ }
152
+ function DocumentColorPanel({ textColor, highlightColor, onTextColor, onHighlightColor, onReset, onClose }) {
153
+ return /* @__PURE__ */ jsxs("div", { className: "document-color-panel", role: "group", "aria-label": "文档颜色", onMouseDown: (event) => event.preventDefault(), children: [
154
+ /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
155
+ /* @__PURE__ */ jsx("div", { className: "color-label", children: "字体颜色" }),
156
+ /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "字体颜色", children: TEXT_COLORS.map((color) => /* @__PURE__ */ jsx(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ className: `color-choice${textColor === color.value ? " selected" : ""}`,
161
+ "aria-label": `字体颜色:${color.name}`,
162
+ title: `字体颜色:${color.name}`,
163
+ "aria-pressed": textColor === color.value,
164
+ onClick: () => onTextColor(color.value),
165
+ children: /* @__PURE__ */ jsx("span", { className: "font-sample", style: textColorStyle(color.value), children: "A" })
166
+ },
167
+ color.name
168
+ )) })
169
+ ] }),
170
+ /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
171
+ /* @__PURE__ */ jsx("div", { className: "color-label", children: "背景颜色" }),
172
+ /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "背景颜色", children: HIGHLIGHT_COLORS.map((color) => /* @__PURE__ */ jsx(
173
+ "button",
174
+ {
175
+ type: "button",
176
+ className: `color-choice background-choice${highlightColor === color.value ? " selected" : ""}${color.value === null ? " no-background" : ""}`,
177
+ style: color.value ? { background: color.value } : void 0,
178
+ "aria-label": `背景颜色:${color.name}`,
179
+ title: `背景颜色:${color.name}`,
180
+ "aria-pressed": highlightColor === color.value,
181
+ onClick: () => onHighlightColor(color.value),
182
+ children: color.value === null && /* @__PURE__ */ jsx("span", { className: "clear-stroke", "aria-hidden": "true" })
183
+ },
184
+ color.name
185
+ )) })
186
+ ] }),
187
+ /* @__PURE__ */ jsx(WButton, { variant: "outline", block: true, onClick: onReset, children: "恢复默认" }),
188
+ onClose && /* @__PURE__ */ jsx(WButton, { variant: "text", block: true, onClick: onClose, children: "关闭" })
189
+ ] });
190
+ }
191
+ function textColorStyle(value) {
192
+ const css = documentColorAttributes("text", value).style;
193
+ if (!css) return {};
194
+ const index = css.indexOf(":");
195
+ return { color: css.slice(index + 1).trim() };
196
+ }
197
+ function SelectionToolbar({ editor, x, y, canComment, onRequestComment, onRequestLink, onRemoveLink }) {
198
+ const [panel, setPanel] = useState("none");
199
+ const active = (name) => editor.isActive(name);
200
+ const textColor = typeof editor.getAttributes("textStyle").color === "string" ? editor.getAttributes("textStyle").color : null;
201
+ const highlight = typeof editor.getAttributes("highlight").color === "string" ? editor.getAttributes("highlight").color : null;
202
+ const hasLink = active("link") || (() => {
203
+ const { from, to } = editor.state.selection;
204
+ try {
205
+ return editor.state.doc.rangeHasMark(from, to, editor.schema.marks.link);
206
+ } catch {
207
+ return false;
208
+ }
209
+ })();
210
+ const chain = () => editor.chain().focus();
211
+ function toggleMark(name) {
212
+ const c = chain();
213
+ if (name === "bold") c.toggleBold().run();
214
+ else if (name === "italic") c.toggleItalic().run();
215
+ else if (name === "strike") c.toggleStrike().run();
216
+ else if (name === "underline") c.toggleUnderline().run();
217
+ else c.toggleCode().run();
218
+ setPanel("none");
219
+ }
220
+ const marks = [
221
+ { name: "bold", icon: "bold", label: "加粗" },
222
+ { name: "italic", icon: "italic", label: "斜体" },
223
+ { name: "strike", icon: "strike", label: "删除线" },
224
+ { name: "underline", icon: "underline", label: "下划线" },
225
+ { name: "code", icon: "inline-code", label: "行内代码" }
226
+ ];
227
+ return /* @__PURE__ */ jsxs(
228
+ "div",
229
+ {
230
+ className: "sel-toolbar",
231
+ role: "toolbar",
232
+ "aria-label": "文字格式",
233
+ style: { left: x, top: y },
234
+ onMouseDown: (event) => event.preventDefault(),
235
+ children: [
236
+ marks.map((mark) => /* @__PURE__ */ jsx(
237
+ "button",
238
+ {
239
+ type: "button",
240
+ className: `tb-btn${active(mark.name) ? " on" : ""}`,
241
+ "aria-label": mark.label,
242
+ "aria-pressed": active(mark.name),
243
+ title: mark.label,
244
+ onClick: () => toggleMark(mark.name),
245
+ children: /* @__PURE__ */ jsx(WIcon, { name: mark.icon, size: 20 })
246
+ },
247
+ mark.name
248
+ )),
249
+ /* @__PURE__ */ jsx(
250
+ "button",
251
+ {
252
+ type: "button",
253
+ className: `tb-btn${hasLink ? " on" : ""}`,
254
+ "aria-label": hasLink ? "编辑链接" : "添加链接",
255
+ title: hasLink ? "编辑链接" : "添加链接",
256
+ "aria-haspopup": "dialog",
257
+ onClick: () => onRequestLink == null ? void 0 : onRequestLink(),
258
+ children: /* @__PURE__ */ jsx(WIcon, { name: "format-link", size: 20 })
259
+ }
260
+ ),
261
+ hasLink && /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn", "aria-label": "取消链接", title: "取消链接", onClick: () => onRemoveLink == null ? void 0 : onRemoveLink(), children: /* @__PURE__ */ jsx(WIcon, { name: "unlink", size: 20 }) }),
262
+ /* @__PURE__ */ jsx("span", { className: "tb-sep", "aria-hidden": "true" }),
263
+ /* @__PURE__ */ jsxs("div", { className: "tb-color-wrap", children: [
264
+ /* @__PURE__ */ jsxs(
265
+ "button",
266
+ {
267
+ type: "button",
268
+ className: `tb-btn${panel === "color" ? " on" : ""}`,
269
+ title: "文字与背景颜色",
270
+ "aria-label": "文字与背景颜色",
271
+ "aria-expanded": panel === "color",
272
+ onClick: () => setPanel(panel === "color" ? "none" : "color"),
273
+ children: [
274
+ /* @__PURE__ */ jsx("span", { className: "color-letter", style: colorLetterStyle(textColor), children: "A" }),
275
+ /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 12, className: "tb-caret" })
276
+ ]
277
+ }
278
+ ),
279
+ panel === "color" && /* @__PURE__ */ jsx("div", { className: "tb-panel", children: /* @__PURE__ */ jsx(
280
+ DocumentColorPanel,
281
+ {
282
+ textColor,
283
+ highlightColor: highlight,
284
+ onTextColor: (value) => {
285
+ const c = chain();
286
+ value ? c.setMark("textStyle", { color: value }).run() : c.unsetMark("textStyle").run();
287
+ },
288
+ onHighlightColor: (value) => {
289
+ const c = chain();
290
+ value ? c.setMark("highlight", { color: value }).run() : c.unsetMark("highlight").run();
291
+ },
292
+ onReset: () => {
293
+ chain().unsetMark("textStyle").unsetMark("highlight").run();
294
+ setPanel("none");
295
+ },
296
+ onClose: () => setPanel("none")
297
+ }
298
+ ) })
299
+ ] }),
300
+ canComment && /* @__PURE__ */ jsxs(Fragment, { children: [
301
+ /* @__PURE__ */ jsx("span", { className: "tb-sep", "aria-hidden": "true" }),
302
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn", "aria-label": "评论选中的文字", title: "评论选中的文字", onClick: () => onRequestComment == null ? void 0 : onRequestComment(), children: /* @__PURE__ */ jsx(WIcon, { name: "comment", size: 20 }) })
303
+ ] })
304
+ ]
305
+ }
306
+ );
307
+ }
308
+ function colorLetterStyle(value) {
309
+ const data = documentColorAttributes("text", value);
310
+ const css = data.style;
311
+ if (!css) return {};
312
+ const index = css.indexOf(":");
313
+ return { [css.slice(0, index).trim()]: css.slice(index + 1).trim() };
314
+ }
315
+ const MAX_GRID = 8;
316
+ const MIN_GRID = 1;
317
+ const MAX_COLUMN_COUNT = 5;
318
+ function buildGridCells(hoverRows, hoverCols) {
319
+ const cells = [];
320
+ for (let row = 1; row <= MAX_GRID; row += 1) {
321
+ for (let col = 1; col <= MAX_GRID; col += 1) {
322
+ cells.push({ row, col, on: row <= hoverRows && col <= hoverCols });
323
+ }
324
+ }
325
+ return cells;
326
+ }
327
+ function clampGrid(value) {
328
+ const n = Math.floor(Number(value));
329
+ if (Number.isNaN(n)) return MIN_GRID;
330
+ return Math.min(Math.max(n, MIN_GRID), MAX_GRID);
331
+ }
332
+ function computePanelTop(preferredTop, height, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY, preferredBottom = preferredTop + height) {
333
+ const fits = (t) => (!Number.isFinite(min) || t >= min) && (!Number.isFinite(max) || t + height <= max);
334
+ const topAligned = preferredTop;
335
+ if (fits(topAligned)) return Math.round(topAligned);
336
+ const bottomAligned = preferredBottom - height;
337
+ if (fits(bottomAligned)) return Math.round(bottomAligned);
338
+ return Math.round(fitRange(topAligned, height, min, max));
339
+ }
340
+ function fitRange(top, height, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY) {
341
+ let t = top;
342
+ if (Number.isFinite(max) && t + height > max) t = max - height;
343
+ if (Number.isFinite(min) && t < min) t = min;
344
+ return Math.round(t);
345
+ }
346
+ const BLOCK_COMMANDS = [
347
+ {
348
+ id: "text",
349
+ label: "正文",
350
+ hint: "/text",
351
+ group: "basic",
352
+ icon: "text",
353
+ convertible: true,
354
+ keywords: ["text", "paragraph", "正文", "zhengwen", "普通文本"],
355
+ run: ({ chain }) => chain().setParagraph().run()
356
+ },
357
+ // ---------------------------------------------------------------- 基础
358
+ {
359
+ id: "h1",
360
+ convertible: true,
361
+ label: "一级标题",
362
+ hint: "/h1",
363
+ group: "basic",
364
+ icon: "h1",
365
+ keywords: ["h1", "标题", "biaoti", "heading", "yijibiaoti"],
366
+ run: ({ chain }) => chain().setHeading({ level: 1 }).run()
367
+ },
368
+ {
369
+ id: "h2",
370
+ convertible: true,
371
+ label: "二级标题",
372
+ hint: "/h2",
373
+ group: "basic",
374
+ icon: "h2",
375
+ keywords: ["h2", "标题", "biaoti", "erjibiaoti"],
376
+ run: ({ chain }) => chain().setHeading({ level: 2 }).run()
377
+ },
378
+ {
379
+ id: "h3",
380
+ convertible: true,
381
+ label: "三级标题",
382
+ hint: "/h3",
383
+ group: "basic",
384
+ icon: "h3",
385
+ keywords: ["h3", "标题", "biaoti", "sanjibiaoti"],
386
+ run: ({ chain }) => chain().setHeading({ level: 3 }).run()
387
+ },
388
+ {
389
+ id: "h4",
390
+ convertible: true,
391
+ label: "四级标题",
392
+ hint: "/h4",
393
+ group: "basic",
394
+ icon: "h4",
395
+ keywords: ["h4", "标题", "biaoti", "sijibiaoti"],
396
+ run: ({ chain }) => chain().setHeading({ level: 4 }).run()
397
+ },
398
+ {
399
+ id: "h5",
400
+ convertible: true,
401
+ label: "五级标题",
402
+ hint: "/h5",
403
+ group: "basic",
404
+ icon: "h5",
405
+ keywords: ["h5", "标题", "biaoti", "wujibiaoti"],
406
+ run: ({ chain }) => chain().setHeading({ level: 5 }).run()
407
+ },
408
+ {
409
+ id: "h6",
410
+ convertible: true,
411
+ label: "六级标题",
412
+ hint: "/h6",
413
+ group: "basic",
414
+ icon: "h6",
415
+ keywords: ["h6", "标题", "biaoti", "liujibiaoti"],
416
+ run: ({ chain }) => chain().setHeading({ level: 6 }).run()
417
+ },
418
+ {
419
+ id: "ordered",
420
+ convertible: true,
421
+ label: "有序列表",
422
+ hint: "/ol",
423
+ group: "basic",
424
+ icon: "ordered-list",
425
+ keywords: ["list", "ol", "列表", "liebiao", "youxuliebiao"],
426
+ run: ({ chain }) => chain().toggleOrderedList().run()
427
+ },
428
+ {
429
+ id: "bullet",
430
+ convertible: true,
431
+ label: "无序列表",
432
+ hint: "/ul",
433
+ group: "basic",
434
+ icon: "bullet-list",
435
+ keywords: ["list", "ul", "列表", "liebiao", "wuxuliebiao"],
436
+ run: ({ chain }) => chain().toggleBulletList().run()
437
+ },
438
+ {
439
+ id: "quote",
440
+ convertible: true,
441
+ label: "引用",
442
+ hint: "/quote",
443
+ group: "basic",
444
+ icon: "quote",
445
+ keywords: ["quote", "引用", "yinyong"],
446
+ run: ({ chain }) => chain().toggleBlockquote().run()
447
+ },
448
+ {
449
+ id: "code",
450
+ convertible: true,
451
+ label: "代码块",
452
+ hint: "/code",
453
+ group: "basic",
454
+ icon: "code",
455
+ keywords: ["code", "代码", "daima", "daimakuai"],
456
+ run: ({ chain }) => chain().toggleCodeBlock().run()
457
+ },
458
+ {
459
+ id: "hr",
460
+ label: "分割线",
461
+ hint: "/hr",
462
+ group: "basic",
463
+ icon: "divider",
464
+ keywords: ["hr", "divider", "分割线", "fengexian"],
465
+ run: ({ chain }) => chain().setHorizontalRule().run()
466
+ },
467
+ {
468
+ id: "link",
469
+ label: "链接",
470
+ hint: "/link",
471
+ group: "basic",
472
+ icon: "link",
473
+ keywords: ["link", "链接", "lianjie", "url"],
474
+ run: ({ chain }) => {
475
+ const href = "https://";
476
+ chain().insertContent({
477
+ type: "text",
478
+ text: href,
479
+ marks: [{ type: "link", attrs: { href } }]
480
+ }).run();
481
+ }
482
+ },
483
+ // ---------------------------------------------------------------- 常用
484
+ {
485
+ id: "mindmap",
486
+ label: "思维导图",
487
+ hint: "/mindmap",
488
+ group: "common",
489
+ icon: "mind-map",
490
+ keywords: ["mindmap", "mind", "思维导图", "导图", "siweidaotu", "脑图"],
491
+ run: ({ chain }) => chain().insertContent([createMindMap(), { type: "paragraph" }]).run()
492
+ },
493
+ {
494
+ id: "task",
495
+ convertible: true,
496
+ label: "任务",
497
+ hint: "/todo",
498
+ group: "common",
499
+ icon: "task",
500
+ keywords: ["task", "todo", "待办", "daiban", "renwu", "checkbox"],
501
+ run: ({ chain }) => chain().toggleTaskList().run()
502
+ },
503
+ {
504
+ id: "image",
505
+ label: "图片",
506
+ hint: "/image",
507
+ group: "common",
508
+ icon: "image",
509
+ keywords: ["image", "img", "图片", "tupian", "photo"],
510
+ run: ({ requestPickFile }) => requestPickFile == null ? void 0 : requestPickFile("image")
511
+ },
512
+ {
513
+ id: "file",
514
+ label: "上传文件",
515
+ hint: "/file",
516
+ group: "common",
517
+ icon: "file",
518
+ keywords: ["file", "文件", "wenjian", "上传", "attachment"],
519
+ run: ({ requestPickFile }) => requestPickFile == null ? void 0 : requestPickFile("file")
520
+ },
521
+ {
522
+ id: "video",
523
+ label: "嵌入视频",
524
+ hint: "/video",
525
+ group: "common",
526
+ icon: "file",
527
+ keywords: ["video", "youtube", "bilibili", "视频", "shipin", "b站"],
528
+ run: ({ requestVideo }) => requestVideo == null ? void 0 : requestVideo()
529
+ },
530
+ {
531
+ id: "table",
532
+ label: "表格",
533
+ hint: "/table",
534
+ group: "common",
535
+ icon: "table",
536
+ keywords: ["table", "表格", "biaoge"],
537
+ panel: "table",
538
+ run: ({ chain, panelValue }) => chain().insertTable({
539
+ rows: clampGrid((panelValue == null ? void 0 : panelValue.rows) ?? 3),
540
+ cols: clampGrid((panelValue == null ? void 0 : panelValue.cols) ?? 3),
541
+ withHeaderRow: true
542
+ }).run()
543
+ },
544
+ {
545
+ id: "columns",
546
+ label: "分栏",
547
+ hint: "/columns",
548
+ group: "common",
549
+ icon: "columns",
550
+ keywords: ["columns", "column", "分栏", "fenlan", "多栏", "duolan", "layout"],
551
+ panel: "columns",
552
+ run: ({ chain, panelValue }) => {
553
+ chain().insertContent(buildColumnsJson((panelValue == null ? void 0 : panelValue.count) ?? 2)).run();
554
+ }
555
+ },
556
+ {
557
+ id: "callout",
558
+ convertible: true,
559
+ label: "高亮块",
560
+ hint: "/callout",
561
+ group: "common",
562
+ icon: "callout",
563
+ keywords: ["callout", "highlight", "高亮", "gaoliang", "提示块", "tishikuai"],
564
+ run: ({ chain }) => {
565
+ chain().insertContent({
566
+ type: "callout",
567
+ attrs: { tone: "info" },
568
+ content: [{ type: "paragraph" }]
569
+ }).run();
570
+ }
571
+ },
572
+ {
573
+ id: "math",
574
+ label: "公式",
575
+ hint: "/math",
576
+ group: "common",
577
+ icon: "math",
578
+ keywords: ["math", "formula", "公式", "gongshi", "latex", "katex"],
579
+ run: ({ chain }) => {
580
+ chain().insertContent({
581
+ type: "mathBlock",
582
+ attrs: { latex: "E = mc^2" }
583
+ }).run();
584
+ }
585
+ },
586
+ {
587
+ id: "page",
588
+ label: "子文档",
589
+ hint: "/page",
590
+ group: "common",
591
+ icon: "page",
592
+ keywords: ["page", "子文档", "ziwendang", "页面", "yemian", "link", "引用"],
593
+ run: ({ requestPickPage }) => requestPickPage == null ? void 0 : requestPickPage()
594
+ }
595
+ ];
596
+ const BLOCK_COMMAND_GROUPS = [
597
+ { id: "basic", label: "基础" },
598
+ { id: "common", label: "常用" }
599
+ ];
600
+ function filterBlockCommands(query) {
601
+ const q = query.trim().toLowerCase();
602
+ if (!q) return BLOCK_COMMANDS;
603
+ return BLOCK_COMMANDS.filter(
604
+ (c) => c.label.toLowerCase().includes(q) || c.hint.toLowerCase().includes(q) || c.keywords.some((k) => k.includes(q) || k.startsWith(q))
605
+ );
606
+ }
607
+ function groupOf(commands) {
608
+ return BLOCK_COMMAND_GROUPS.map((g) => ({
609
+ ...g,
610
+ items: commands.filter((c) => c.group === g.id)
611
+ })).filter((g) => g.items.length > 0);
612
+ }
613
+ function InsertPicker({ mode, x, y, yBottom, boundTop, boundBottom, onPick, onClose }) {
614
+ const root = useRef(null), [top, setTop] = useState(y);
615
+ const [hover, setHover] = useState({ rows: 3, cols: 3 }), [count, setCount] = useState(2);
616
+ useEffect(() => {
617
+ const element = root.current;
618
+ if (!element) return;
619
+ setTop(computePanelTop(y, element.offsetHeight, boundTop, boundBottom, yBottom ?? y + element.offsetHeight));
620
+ }, [mode, y, yBottom, boundTop, boundBottom]);
621
+ const cells = buildGridCells(hover.rows, hover.cols);
622
+ return /* @__PURE__ */ jsxs(
623
+ "div",
624
+ {
625
+ ref: root,
626
+ className: "insert-picker",
627
+ role: "dialog",
628
+ "aria-label": mode === "table" ? "选择表格尺寸" : "选择分栏栏数",
629
+ style: { left: x, top },
630
+ onMouseDown: (event) => event.preventDefault(),
631
+ children: [
632
+ /* @__PURE__ */ jsxs("div", { className: "ip-head", children: [
633
+ /* @__PURE__ */ jsx("span", { children: mode === "table" ? "插入表格" : "选择栏数" }),
634
+ /* @__PURE__ */ jsx("span", { className: "ip-dim", children: mode === "table" ? `${hover.rows} x ${hover.cols}` : `${count} 栏` })
635
+ ] }),
636
+ mode === "table" ? /* @__PURE__ */ jsx("div", { className: "ip-grid", children: cells.map((cell) => /* @__PURE__ */ jsx(
637
+ "button",
638
+ {
639
+ type: "button",
640
+ className: `ip-cell${cell.on ? " on" : ""}`,
641
+ "aria-label": `${cell.row} 行 ${cell.col} 列`,
642
+ onMouseEnter: () => setHover({ rows: cell.row, cols: cell.col }),
643
+ onFocus: () => setHover({ rows: cell.row, cols: cell.col }),
644
+ onClick: () => onPick({ rows: cell.row, cols: cell.col })
645
+ },
646
+ `${cell.row}-${cell.col}`
647
+ )) }) : /* @__PURE__ */ jsx("div", { className: "ip-counts", children: Array.from({ length: MAX_COLUMN_COUNT }, (_, index) => index + 1).map((value) => /* @__PURE__ */ jsxs(
648
+ "button",
649
+ {
650
+ type: "button",
651
+ className: `ip-count${count === value ? " on" : ""}`,
652
+ "aria-label": `${value} 栏`,
653
+ onMouseEnter: () => setCount(value),
654
+ onClick: () => onPick({ count: value }),
655
+ children: [
656
+ /* @__PURE__ */ jsx("span", { className: "ip-count-box", children: Array.from({ length: value }, (_, index) => /* @__PURE__ */ jsx("span", { className: "ip-count-bar" }, index)) }),
657
+ /* @__PURE__ */ jsx("span", { className: "ip-count-num", children: value })
658
+ ]
659
+ },
660
+ value
661
+ )) }),
662
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ip-close", "aria-label": "关闭参数面板", onClick: onClose, children: /* @__PURE__ */ jsx(WIcon, { name: "x", size: 14 }) })
663
+ ]
664
+ }
665
+ );
666
+ }
667
+ function SlashMenu({ editor, from, query, x, y, onClose, onPickFile, onPickPage, onPickVideo }) {
668
+ const filtered = useMemo(() => filterBlockCommands(query), [query]);
669
+ const groups = useMemo(() => groupOf(filtered), [filtered]);
670
+ const [activeIndex, setActiveIndex] = useState(0);
671
+ const [panel, setPanel] = useState(null);
672
+ const [panelY, setPanelY] = useState(0), [panelYBottom, setPanelYBottom] = useState(0);
673
+ const [panelX, setPanelX] = useState(274);
674
+ const [viewport, setViewport] = useState({ top: 0, bottom: 0 });
675
+ const [anchorTop, setAnchorTop] = useState(y);
676
+ const root = useRef(null), menu = useRef(null), viewportRef = useRef(null), content = useRef(null);
677
+ const keyboardNav = useRef(false);
678
+ const flatIndex = (command) => filtered.findIndex((item) => item.id === command.id);
679
+ useEffect(() => {
680
+ setActiveIndex(0);
681
+ setPanel(null);
682
+ }, [query]);
683
+ useEffect(() => {
684
+ const measure = () => {
685
+ var _a;
686
+ const anchor = root.current, element = menu.current;
687
+ if (!anchor || !element) return;
688
+ const parentTop = ((_a = anchor.parentElement) == null ? void 0 : _a.getBoundingClientRect().top) ?? 0;
689
+ const desired = parentTop + y, margin = 8;
690
+ const fitted = Math.round(Math.max(margin, Math.min(desired, window.innerHeight - margin - element.offsetHeight)));
691
+ setAnchorTop(fitted - parentTop);
692
+ setViewport({ top: margin - fitted, bottom: window.innerHeight - margin - fitted });
693
+ };
694
+ const frame = requestAnimationFrame(measure);
695
+ window.addEventListener("resize", measure);
696
+ window.addEventListener("scroll", measure, true);
697
+ return () => {
698
+ cancelAnimationFrame(frame);
699
+ window.removeEventListener("resize", measure);
700
+ window.removeEventListener("scroll", measure, true);
701
+ };
702
+ }, [y, query, filtered.length]);
703
+ useEffect(() => {
704
+ var _a;
705
+ if (!keyboardNav.current) return;
706
+ keyboardNav.current = false;
707
+ const row = (_a = menu.current) == null ? void 0 : _a.querySelectorAll("li")[activeIndex];
708
+ const scroller = content.current;
709
+ if (!row || !scroller) return;
710
+ const rowTop = row.offsetTop, rowBottom = rowTop + row.offsetHeight;
711
+ if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
712
+ else if (rowBottom > scroller.scrollTop + scroller.clientHeight) scroller.scrollTop = rowBottom - scroller.clientHeight;
713
+ }, [activeIndex, filtered]);
714
+ function openPanel(command) {
715
+ var _a, _b, _c;
716
+ if (!(command == null ? void 0 : command.panel)) {
717
+ setPanel(null);
718
+ return;
719
+ }
720
+ if ((panel == null ? void 0 : panel.id) === command.id) return;
721
+ setPanel(command);
722
+ const row = (_a = menu.current) == null ? void 0 : _a.querySelectorAll("li")[flatIndex(command)];
723
+ const view = (_b = viewportRef.current) == null ? void 0 : _b.getBoundingClientRect();
724
+ if (!row || !view) return;
725
+ const rect = row.getBoundingClientRect();
726
+ if (rect.bottom <= view.top || rect.top >= view.bottom) {
727
+ setPanel(null);
728
+ return;
729
+ }
730
+ if (!root.current) return;
731
+ const top = rect.top - root.current.getBoundingClientRect().top;
732
+ setPanelY(top);
733
+ setPanelYBottom(top + rect.height);
734
+ setPanelX((((_c = content.current) == null ? void 0 : _c.getBoundingClientRect().width) ?? 268) + 6);
735
+ }
736
+ function choose(command, value) {
737
+ if (!editor.isEditable || editor.isDestroyed) return;
738
+ if (from < 0 || from > editor.state.selection.from) return;
739
+ const range = { from, to: editor.state.selection.from };
740
+ if (["image", "file", "page", "video"].includes(command.id) && range.to > range.from) editor.chain().deleteRange(range).run();
741
+ command.run({
742
+ chain: () => {
743
+ const chain = editor.chain().focus();
744
+ return range.to > range.from ? chain.deleteRange(range) : chain;
745
+ },
746
+ requestPickFile: onPickFile,
747
+ requestPickPage: onPickPage,
748
+ requestVideo: onPickVideo,
749
+ panelValue: value
750
+ });
751
+ dismissSlash(editor.view);
752
+ onClose();
753
+ }
754
+ function activate(command) {
755
+ if (command.panel) {
756
+ openPanel(command);
757
+ return;
758
+ }
759
+ choose(command);
760
+ }
761
+ function keydown(event) {
762
+ var _a;
763
+ if (shouldIgnoreForIme(event) || !editor.view.dom.contains(event.target)) return;
764
+ if (event.key === "Escape") {
765
+ event.preventDefault();
766
+ event.stopImmediatePropagation();
767
+ if (panel) setPanel(null);
768
+ else {
769
+ dismissSlash(editor.view);
770
+ onClose();
771
+ }
772
+ return;
773
+ }
774
+ if (!filtered.length) return;
775
+ if (["ArrowDown", "ArrowUp", "Enter"].includes(event.key) || event.key === "ArrowRight" && ((_a = filtered[activeIndex]) == null ? void 0 : _a.panel)) {
776
+ event.stopImmediatePropagation();
777
+ }
778
+ if (event.key === "ArrowDown") {
779
+ event.preventDefault();
780
+ keyboardNav.current = true;
781
+ setPanel(null);
782
+ setActiveIndex((activeIndex + 1) % filtered.length);
783
+ } else if (event.key === "ArrowUp") {
784
+ event.preventDefault();
785
+ keyboardNav.current = true;
786
+ setPanel(null);
787
+ setActiveIndex((activeIndex - 1 + filtered.length) % filtered.length);
788
+ } else if (event.key === "Enter") {
789
+ event.preventDefault();
790
+ const command = filtered[activeIndex];
791
+ if (command && (panel == null ? void 0 : panel.id) === command.id) choose(command);
792
+ else if (command) activate(command);
793
+ } else if (event.key === "ArrowRight") {
794
+ const command = filtered[activeIndex];
795
+ if (command == null ? void 0 : command.panel) {
796
+ event.preventDefault();
797
+ openPanel(command);
798
+ }
799
+ }
800
+ }
801
+ useEffect(() => {
802
+ const node = editor.view.dom;
803
+ node.addEventListener("keydown", keydown, true);
804
+ return () => node.removeEventListener("keydown", keydown, true);
805
+ });
806
+ useEffect(() => {
807
+ const outside = (event) => {
808
+ var _a;
809
+ if (event.target instanceof Node && !((_a = root.current) == null ? void 0 : _a.contains(event.target)) && !editor.view.dom.contains(event.target)) onClose();
810
+ };
811
+ document.addEventListener("pointerdown", outside);
812
+ return () => document.removeEventListener("pointerdown", outside);
813
+ }, [onClose, editor]);
814
+ return /* @__PURE__ */ jsxs("div", { ref: root, className: "slash-anchor", style: { left: x, top: anchorTop }, children: [
815
+ /* @__PURE__ */ jsx("div", { ref: menu, className: "slash-menu", role: "listbox", "aria-label": "插入内容", children: /* @__PURE__ */ jsx("div", { ref: viewportRef, children: /* @__PURE__ */ jsxs("div", { ref: content, className: "slash-content", children: [
816
+ groups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
817
+ /* @__PURE__ */ jsx("div", { className: "slash-group", children: group.label }),
818
+ /* @__PURE__ */ jsx("ul", { children: group.items.map((command) => {
819
+ const index = flatIndex(command);
820
+ return /* @__PURE__ */ jsxs(
821
+ "li",
822
+ {
823
+ role: "option",
824
+ "aria-selected": index === activeIndex,
825
+ className: index === activeIndex ? "active" : void 0,
826
+ onMouseMove: () => {
827
+ setActiveIndex(index);
828
+ openPanel(command);
829
+ },
830
+ onMouseDown: (event) => {
831
+ event.preventDefault();
832
+ activate(command);
833
+ },
834
+ children: [
835
+ /* @__PURE__ */ jsx(WIcon, { name: command.icon, size: 18 }),
836
+ /* @__PURE__ */ jsx("span", { className: "slash-label", children: command.label }),
837
+ /* @__PURE__ */ jsx("span", { className: "slash-hint", children: command.hint }),
838
+ command.panel && /* @__PURE__ */ jsx(WIcon, { name: "chevron", size: 16, className: "slash-chevron" })
839
+ ]
840
+ },
841
+ command.id
842
+ );
843
+ }) })
844
+ ] }, group.id)),
845
+ !filtered.length && /* @__PURE__ */ jsx("div", { className: "slash-empty", children: "无匹配命令" })
846
+ ] }) }) }),
847
+ (panel == null ? void 0 : panel.panel) && /* @__PURE__ */ jsx(
848
+ InsertPicker,
849
+ {
850
+ mode: panel.panel,
851
+ x: panelX,
852
+ y: panelY,
853
+ yBottom: panelYBottom,
854
+ boundTop: viewport.top,
855
+ boundBottom: viewport.bottom,
856
+ onPick: (value) => choose(panel, value),
857
+ onClose: () => setPanel(null)
858
+ }
859
+ )
860
+ ] });
861
+ }
862
+ function menuBlockRange(state, pos) {
863
+ const blocks = [];
864
+ state.doc.forEach((node, from2) => blocks.push({ from: from2, to: from2 + node.nodeSize, node }));
865
+ const target = blocks.find((b2) => b2.from === pos);
866
+ if (!target) return null;
867
+ const { from, to, empty: empty2 } = state.selection;
868
+ const chosen = !empty2 && from < target.to && to > target.from ? blocks.filter((b2) => from < b2.to && to > b2.from) : [target];
869
+ return { from: chosen[0].from, to: chosen.at(-1).to, nodes: chosen.map((b2) => b2.node) };
870
+ }
871
+ function containerContent(range) {
872
+ const only = range.nodes.length === 1 ? range.nodes[0] : null;
873
+ return only && ["callout", "blockquote"].includes(only.type.name) ? only.content : range.nodes;
874
+ }
875
+ function canConvertMenuBlocks(state, pos, id) {
876
+ const range = menuBlockRange(state, pos);
877
+ if (!range) return false;
878
+ if (id === "callout" || id === "quote") {
879
+ const type = state.schema.nodes[id === "callout" ? "callout" : "blockquote"];
880
+ return !!(type == null ? void 0 : type.validContent(type.create(null, containerContent(range)).content));
881
+ }
882
+ if (id === "text" && range.nodes.length === 1 && ["callout", "blockquote"].includes(range.nodes[0].type.name)) return true;
883
+ if (!["text", "h1", "h2", "h3", "h4", "h5", "h6", "bullet", "ordered", "task", "code"].includes(id)) return false;
884
+ const allowed = ["paragraph", "heading", "blockquote", "codeBlock", "bulletList", "orderedList", "taskList", "callout", "listItem", "taskItem", "text", "hardBreak"];
885
+ return range.nodes.every((node) => {
886
+ if (!allowed.includes(node.type.name)) return false;
887
+ let valid = true;
888
+ node.descendants((child) => {
889
+ if (!allowed.includes(child.type.name)) valid = false;
890
+ return valid;
891
+ });
892
+ return valid;
893
+ });
894
+ }
895
+ function dispatchContainer(editor, range, type) {
896
+ var _a;
897
+ if (range.nodes.length === 1 && range.nodes[0].type.name === type) return true;
898
+ const wrapper = editor.schema.nodes[type].create(type === "callout" ? { tone: "info" } : null, containerContent(range));
899
+ const tr = editor.state.tr.replaceWith(range.from, range.to, wrapper);
900
+ tr.setSelection(Selection.near(tr.doc.resolve(range.from + 1)));
901
+ const manager = (_a = yUndoPluginKey.getState(editor.state)) == null ? void 0 : _a.undoManager;
902
+ manager == null ? void 0 : manager.stopCapturing();
903
+ editor.view.dispatch(closeHistory(tr));
904
+ manager == null ? void 0 : manager.stopCapturing();
905
+ editor.view.focus();
906
+ return true;
907
+ }
908
+ function convertMenuBlocks(editor, pos, id) {
909
+ var _a, _b;
910
+ if (editor.isDestroyed || !editor.isEditable || !canConvertMenuBlocks(editor.state, pos, id)) return false;
911
+ const range = menuBlockRange(editor.state, pos);
912
+ if (id === "callout" || id === "quote") return dispatchContainer(editor, range, id === "callout" ? "callout" : "blockquote");
913
+ if (id === "text" && range.nodes.length === 1 && range.nodes[0].type.name === "callout") {
914
+ const result2 = unwrapCallout(editor, range.from);
915
+ if (result2) editor.view.focus();
916
+ return result2;
917
+ }
918
+ if (id === "text" && range.nodes.length === 1 && range.nodes[0].type.name === "blockquote") {
919
+ const tr = editor.state.tr.replaceWith(range.from, range.to, range.nodes[0].content);
920
+ const manager2 = (_a = yUndoPluginKey.getState(editor.state)) == null ? void 0 : _a.undoManager;
921
+ manager2 == null ? void 0 : manager2.stopCapturing();
922
+ editor.view.dispatch(closeHistory(tr));
923
+ manager2 == null ? void 0 : manager2.stopCapturing();
924
+ editor.view.focus();
925
+ return true;
926
+ }
927
+ const desiredType = { text: "paragraph", bullet: "bulletList", ordered: "orderedList", task: "taskList", code: "codeBlock" }[id];
928
+ if (range.nodes.length === 1 && range.nodes[0].type.name === desiredType) return true;
929
+ const manager = (_b = yUndoPluginKey.getState(editor.state)) == null ? void 0 : _b.undoManager;
930
+ manager == null ? void 0 : manager.stopCapturing();
931
+ const chain = editor.chain().focus().command(({ tr }) => {
932
+ let end = range.to;
933
+ if (range.nodes.length === 1 && ["callout", "blockquote"].includes(range.nodes[0].type.name)) {
934
+ tr.replaceWith(range.from, range.to, range.nodes[0].content);
935
+ end -= 2;
936
+ }
937
+ tr.setSelection(TextSelection.between(tr.doc.resolve(range.from + 1), tr.doc.resolve(end - 1)));
938
+ closeHistory(tr);
939
+ return true;
940
+ }).clearNodes();
941
+ if (id === "text") chain.setParagraph();
942
+ else if (/^h[1-6]$/.test(id)) chain.setHeading({ level: Number(id.slice(1)) });
943
+ else if (id === "bullet") chain.toggleBulletList();
944
+ else if (id === "ordered") chain.toggleOrderedList();
945
+ else if (id === "task") chain.toggleTaskList();
946
+ else if (id === "code") chain.toggleCodeBlock();
947
+ const result = chain.command(({ tr }) => {
948
+ tr.setSelection(Selection.near(tr.doc.resolve(tr.selection.from)));
949
+ return true;
950
+ }).run();
951
+ manager == null ? void 0 : manager.stopCapturing();
952
+ return result;
953
+ }
954
+ function deleteTopLevelBlock(state, pos) {
955
+ let found = false;
956
+ state.doc.forEach((_node, offset) => {
957
+ if (offset === pos) found = true;
958
+ });
959
+ const node = found ? state.doc.nodeAt(pos) : null;
960
+ if (!node) return null;
961
+ return state.doc.childCount === 1 ? state.tr.replaceWith(0, state.doc.content.size, state.schema.nodes.paragraph.create()) : state.tr.delete(pos, pos + node.nodeSize);
962
+ }
963
+ const BLOCK_SUBMENU_GAP = 6;
964
+ const BLOCK_MENU_VIEW_MARGIN = 8;
965
+ function placeBlockSubmenu(parent, rowTop, panel, viewport) {
966
+ const inset = BLOCK_MENU_VIEW_MARGIN;
967
+ const maxHeight = Math.max(0, viewport.height - inset * 2);
968
+ const right = parent.right + BLOCK_SUBMENU_GAP;
969
+ const left = parent.left - BLOCK_SUBMENU_GAP - panel.width;
970
+ const preferredLeft = right + panel.width <= viewport.width - inset || left < inset ? right : left;
971
+ return {
972
+ left: Math.max(inset, Math.min(preferredLeft, viewport.width - inset - panel.width)),
973
+ top: fitRange(rowTop, Math.min(panel.height, maxHeight), inset, viewport.height - inset),
974
+ maxHeight
975
+ };
976
+ }
977
+ const PLUS = { width: 28, height: 28 }, CHIP = { width: 48, height: 28 }, HANDLE_HEIGHT = 26, GUTTER_GAP$1 = 8;
978
+ function blockAffordanceStyle(info, shell, kind) {
979
+ const size = kind === "plus" ? PLUS : CHIP;
980
+ const preferred = info.contentLeft - GUTTER_GAP$1 - size.width;
981
+ const minLeft = shell ? 8 - shell.getBoundingClientRect().left : 8;
982
+ return {
983
+ left: Math.max(preferred, minLeft),
984
+ top: info.top + (HANDLE_HEIGHT - size.height) / 2
985
+ };
986
+ }
987
+ function BlockPlus({ style, expanded, onClick, onMouseEnter }) {
988
+ return /* @__PURE__ */ jsx(
989
+ "button",
990
+ {
991
+ type: "button",
992
+ className: "block-plus",
993
+ "aria-label": "插入内容",
994
+ title: "在此空行插入内容(表格 / 分栏 / 图片…)",
995
+ "aria-expanded": expanded,
996
+ "aria-haspopup": "menu",
997
+ style,
998
+ onMouseEnter,
999
+ onClick,
1000
+ children: /* @__PURE__ */ jsx(WIcon, { name: "plus", size: 17 })
1001
+ }
1002
+ );
1003
+ }
1004
+ function typeIcon(editor, info) {
1005
+ var _a;
1006
+ if (info.type === "heading") {
1007
+ const level = Number(((_a = editor.state.doc.nodeAt(info.pos)) == null ? void 0 : _a.attrs.level) ?? 1);
1008
+ return `h${Math.min(Math.max(Number.isFinite(level) ? level : 1, 1), 6)}`;
1009
+ }
1010
+ return {
1011
+ paragraph: "text",
1012
+ image: "image",
1013
+ imageGallery: "image",
1014
+ table: "table",
1015
+ blockquote: "quote",
1016
+ codeBlock: "code",
1017
+ bulletList: "bullet-list",
1018
+ orderedList: "ordered-list",
1019
+ taskList: "task",
1020
+ callout: "callout",
1021
+ columns: "columns",
1022
+ mindMap: "mind-map",
1023
+ fileAttachment: "file",
1024
+ videoEmbed: "file",
1025
+ mathBlock: "math"
1026
+ }[info.type] ?? "text";
1027
+ }
1028
+ function BlockChip({ editor, info, style, expanded, onOpen, onOpenFull, onMouseEnter, onHandleMouseDown }) {
1029
+ return /* @__PURE__ */ jsxs("div", { className: "block-chip", role: "toolbar", "aria-label": "块快捷操作", style, onMouseEnter, children: [
1030
+ /* @__PURE__ */ jsx("button", { type: "button", className: "chip-btn", "aria-label": "转换为", title: "转换为…", "aria-expanded": expanded, "aria-haspopup": "menu", onClick: onOpen, children: /* @__PURE__ */ jsx(WIcon, { name: typeIcon(editor, info), size: 17 }) }),
1031
+ /* @__PURE__ */ jsx(
1032
+ "button",
1033
+ {
1034
+ type: "button",
1035
+ className: "chip-btn chip-grip",
1036
+ "aria-label": "拖动或打开块菜单",
1037
+ title: "按住拖动可移动整段;单击打开块菜单",
1038
+ "aria-expanded": expanded,
1039
+ "aria-haspopup": "menu",
1040
+ draggable: false,
1041
+ onMouseDown: (event) => onHandleMouseDown ? onHandleMouseDown(event) : event.preventDefault(),
1042
+ onClick: onOpenFull,
1043
+ children: /* @__PURE__ */ jsx(WIcon, { name: "handle", size: 17 })
1044
+ }
1045
+ )
1046
+ ] });
1047
+ }
1048
+ function BlockMenu(props) {
1049
+ var _a, _b, _c, _d, _e, _f;
1050
+ const { editor, info } = props;
1051
+ const [submenu, setSubmenu] = useState(props.panelOnly ?? null);
1052
+ const [copied, setCopied] = useState(false);
1053
+ const [panel, setPanel] = useState(null);
1054
+ const node = editor.state.doc.nodeAt(info.pos);
1055
+ const chain = () => {
1056
+ const range = menuBlockRange(editor.state, info.pos);
1057
+ const selection = editor.state.selection;
1058
+ const keep = selection instanceof TextSelection && !selection.empty && node && selection.from >= info.pos + 1 && selection.to <= info.pos + node.nodeSize - 1;
1059
+ return editor.chain().focus().command(({ tr }) => {
1060
+ tr.setSelection(TextSelection.between(
1061
+ tr.doc.resolve(keep ? selection.from : ((range == null ? void 0 : range.from) ?? info.pos) + 1),
1062
+ tr.doc.resolve(keep ? selection.to : ((range == null ? void 0 : range.to) ?? info.pos + node.nodeSize) - 1)
1063
+ ));
1064
+ return true;
1065
+ });
1066
+ };
1067
+ const convertibles = BLOCK_COMMANDS.filter((command) => command.convertible);
1068
+ const insertCommands = BLOCK_COMMANDS.filter((command) => command.id !== "link");
1069
+ const currentAlign = String(((_a = node == null ? void 0 : node.attrs) == null ? void 0 : _a.textAlign) ?? "left");
1070
+ const textual = node ? ["paragraph", "heading", "blockquote", "codeBlock", "bulletList", "orderedList", "taskList", "callout"].includes(node.type.name) : false;
1071
+ const target = useRef(null);
1072
+ const submenuRef = useRef(null), rowRef = useRef(null);
1073
+ const [submenuPlace, setSubmenuPlace] = useState();
1074
+ const [place, setPlace] = useState({ left: 0, top: 0 });
1075
+ useEffect(() => {
1076
+ highlightMenuBlock(editor.view, info.pos);
1077
+ const outside = (event) => {
1078
+ var _a2;
1079
+ if (!(event.target instanceof Element) || ((_a2 = target.current) == null ? void 0 : _a2.contains(event.target))) return;
1080
+ if (event.target.closest(".block-chip,.block-plus")) return;
1081
+ props.onClose();
1082
+ };
1083
+ const key = (event) => {
1084
+ var _a2, _b2;
1085
+ if (event.isComposing) return;
1086
+ if (event.key === "Escape") {
1087
+ event.preventDefault();
1088
+ event.stopPropagation();
1089
+ props.onClose();
1090
+ return;
1091
+ }
1092
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
1093
+ const menu = ((_a2 = document.activeElement) == null ? void 0 : _a2.closest('[role="menu"]')) ?? target.current;
1094
+ const items = Array.from((menu == null ? void 0 : menu.querySelectorAll('[role="menuitem"]:not(:disabled)')) ?? []);
1095
+ if (!items.length) return;
1096
+ event.preventDefault();
1097
+ const index = items.indexOf(document.activeElement);
1098
+ (_b2 = items[event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (index + (event.key === "ArrowUp" ? -1 : 1) + items.length) % items.length]) == null ? void 0 : _b2.focus();
1099
+ };
1100
+ const changed = ({ transaction }) => {
1101
+ if (transaction.docChanged) props.onClose();
1102
+ };
1103
+ document.addEventListener("pointerdown", outside, true);
1104
+ document.addEventListener("keydown", key);
1105
+ editor.on("transaction", changed);
1106
+ return () => {
1107
+ document.removeEventListener("pointerdown", outside, true);
1108
+ document.removeEventListener("keydown", key);
1109
+ editor.off("transaction", changed);
1110
+ if (!editor.isDestroyed) highlightMenuBlock(editor.view, null);
1111
+ };
1112
+ }, [editor, info.pos]);
1113
+ useLayoutEffect(() => {
1114
+ var _a2;
1115
+ if (props.panelOnly || !submenuRef.current || !target.current) {
1116
+ setSubmenuPlace(void 0);
1117
+ return;
1118
+ }
1119
+ const anchor = target.current.getBoundingClientRect(), card = submenuRef.current.getBoundingClientRect();
1120
+ const placed = placeBlockSubmenu(anchor, ((_a2 = rowRef.current) == null ? void 0 : _a2.getBoundingClientRect().top) ?? anchor.top, card, { width: innerWidth, height: innerHeight });
1121
+ setSubmenuPlace({ left: placed.left - anchor.left, top: placed.top - anchor.top, maxHeight: placed.maxHeight });
1122
+ }, [submenu, props.panelOnly, place.left, place.top]);
1123
+ useLayoutEffect(() => {
1124
+ const panel2 = target.current;
1125
+ const shell = editor.view.dom.closest(".editor-shell");
1126
+ if (!panel2 || !shell) return;
1127
+ const measure = () => {
1128
+ const host = shell.getBoundingClientRect();
1129
+ const width = panel2.offsetWidth, height = panel2.offsetHeight, margin = 8;
1130
+ const left = Math.min(Math.max(host.left + info.contentLeft, margin), Math.max(margin, innerWidth - margin - width)) - host.left;
1131
+ const top = Math.min(Math.max(host.top + info.top, margin), Math.max(margin, innerHeight - margin - height)) - host.top;
1132
+ setPlace({ left, top });
1133
+ };
1134
+ measure();
1135
+ window.addEventListener("resize", measure);
1136
+ window.addEventListener("scroll", measure, true);
1137
+ return () => {
1138
+ window.removeEventListener("resize", measure);
1139
+ window.removeEventListener("scroll", measure, true);
1140
+ };
1141
+ }, [editor, info.contentLeft, info.top, props.panelOnly, submenu]);
1142
+ useEffect(() => {
1143
+ setSubmenu(props.panelOnly ?? null);
1144
+ }, [props.panelOnly, info.pos]);
1145
+ function runInsert(command) {
1146
+ if (command.panel) {
1147
+ setPanel({ mode: command.panel, x: 0, y: 0 });
1148
+ return;
1149
+ }
1150
+ insertCommand(command);
1151
+ }
1152
+ function insertWithPanel(value) {
1153
+ const command = insertCommands.find((item2) => item2.panel === (panel == null ? void 0 : panel.mode));
1154
+ setPanel(null);
1155
+ if (!command) return;
1156
+ insertCommand(command, value);
1157
+ }
1158
+ function insertCommand(command, value) {
1159
+ if (!node || !editor.isEditable || editor.isDestroyed) return;
1160
+ const reuse = props.panelOnly === "insert-below" && node.isTextblock && node.content.size === 0;
1161
+ const after = info.pos + node.nodeSize, at = reuse ? { from: info.pos, to: after } : after;
1162
+ const media = ["image", "file", "page", "video"].includes(command.id);
1163
+ if (media) {
1164
+ if (!reuse) editor.chain().insertContentAt(after, { type: "paragraph" }).setTextSelection(after + 1).run();
1165
+ else editor.commands.setTextSelection(info.pos + 1);
1166
+ props.onClose();
1167
+ if (command.id === "page") props.onPickPage();
1168
+ else if (command.id === "video") props.onPickVideo();
1169
+ else props.onPickFile(command.id === "image" ? "image" : "file");
1170
+ return;
1171
+ }
1172
+ let content;
1173
+ if (/^h[1-6]$/.test(command.id)) content = { type: "heading", attrs: { level: Number(command.id.slice(1)) } };
1174
+ else if (command.id === "table") content = createTable(editor.schema, (value == null ? void 0 : value.rows) ?? 3, (value == null ? void 0 : value.cols) ?? 3, true).toJSON();
1175
+ else if (command.id === "columns") content = buildColumnsJson((value == null ? void 0 : value.count) ?? 2);
1176
+ else if (command.id === "mindmap") content = [createMindMap$1(), { type: "paragraph" }];
1177
+ else if (["bullet", "ordered", "task"].includes(command.id)) content = {
1178
+ type: command.id === "bullet" ? "bulletList" : command.id === "ordered" ? "orderedList" : "taskList",
1179
+ content: [{ type: command.id === "task" ? "taskItem" : "listItem", attrs: command.id === "task" ? { checked: false } : void 0, content: [{ type: "paragraph" }] }]
1180
+ };
1181
+ else content = {
1182
+ text: { type: "paragraph" },
1183
+ quote: { type: "blockquote", content: [{ type: "paragraph" }] },
1184
+ code: { type: "codeBlock" },
1185
+ hr: { type: "horizontalRule" },
1186
+ callout: { type: "callout", attrs: { tone: "info" }, content: [{ type: "paragraph" }] },
1187
+ math: { type: "mathBlock", attrs: { latex: "E = mc^2" } }
1188
+ }[command.id];
1189
+ if (content) editor.chain().focus().insertContentAt(at, content).run();
1190
+ props.onClose();
1191
+ }
1192
+ function action(name) {
1193
+ if (!node) return;
1194
+ {
1195
+ const tr = deleteTopLevelBlock(editor.state, info.pos);
1196
+ if (tr) editor.view.dispatch(tr);
1197
+ }
1198
+ props.onClose();
1199
+ }
1200
+ async function writeBlockClipboard() {
1201
+ var _a2;
1202
+ if (!node) return;
1203
+ if (typeof ClipboardItem === "undefined" || !((_a2 = navigator.clipboard) == null ? void 0 : _a2.write)) {
1204
+ if (!node.isTextblock) throw new Error("当前浏览器不支持复制此块的富文本结构");
1205
+ await navigator.clipboard.writeText(node.textContent);
1206
+ return;
1207
+ }
1208
+ const container = document.createElement("div");
1209
+ container.append(DOMSerializer.fromSchema(editor.schema).serializeFragment(Fragment$1.from(node)));
1210
+ await navigator.clipboard.write([new ClipboardItem({
1211
+ "text/html": new Blob([container.innerHTML], { type: "text/html" }),
1212
+ "text/plain": new Blob([node.textContent], { type: "text/plain" })
1213
+ })]);
1214
+ }
1215
+ async function copyBlock() {
1216
+ var _a2;
1217
+ try {
1218
+ await writeBlockClipboard();
1219
+ setCopied(true);
1220
+ setTimeout(() => setCopied(false), 1600);
1221
+ } catch {
1222
+ (_a2 = props.onNotice) == null ? void 0 : _a2.call(props, "无法完整复制该块,请选中后使用 Cmd/Ctrl+C。");
1223
+ }
1224
+ }
1225
+ async function cutBlock() {
1226
+ var _a2, _b2;
1227
+ if (!node) return;
1228
+ const originalDoc = editor.state.doc, originalNode = node;
1229
+ try {
1230
+ await writeBlockClipboard();
1231
+ } catch {
1232
+ (_a2 = props.onNotice) == null ? void 0 : _a2.call(props, "无法访问剪贴板,已取消剪切(内容未删除)。");
1233
+ return;
1234
+ }
1235
+ if (editor.isDestroyed || !editor.isEditable || editor.state.doc !== originalDoc || editor.state.doc.nodeAt(info.pos) !== originalNode) {
1236
+ (_b2 = props.onNotice) == null ? void 0 : _b2.call(props, "复制期间正文发生了变化,已取消删除,请重新剪切。");
1237
+ return;
1238
+ }
1239
+ const tr = deleteTopLevelBlock(editor.state, info.pos);
1240
+ if (tr) editor.view.dispatch(tr);
1241
+ props.onClose();
1242
+ }
1243
+ const item = (icon, label, onClick, options = {}) => /* @__PURE__ */ jsxs(
1244
+ "button",
1245
+ {
1246
+ type: "button",
1247
+ role: "menuitem",
1248
+ className: `bm-item${options.active ? " active" : ""}${options.disabled ? " disabled" : ""}${options.danger ? " danger" : ""}`,
1249
+ "aria-haspopup": options.submenu ? "menu" : void 0,
1250
+ "aria-expanded": options.submenu ? submenu === options.submenu : void 0,
1251
+ disabled: options.disabled,
1252
+ "aria-disabled": options.disabled || void 0,
1253
+ onMouseEnter: (event) => {
1254
+ rowRef.current = event.currentTarget;
1255
+ if (options.submenu) setSubmenu(options.submenu);
1256
+ else if (!props.panelOnly) setSubmenu(null);
1257
+ },
1258
+ onClick,
1259
+ children: [
1260
+ /* @__PURE__ */ jsx(WIcon, { name: icon, size: 18 }),
1261
+ /* @__PURE__ */ jsx("span", { children: label }),
1262
+ options.submenu && /* @__PURE__ */ jsx(WIcon, { name: "chevron", size: 16, className: "bm-chevron" })
1263
+ ]
1264
+ },
1265
+ label
1266
+ );
1267
+ return /* @__PURE__ */ jsxs("div", { ref: target, className: "block-anchor", role: "menu", "aria-label": "块操作菜单", style: place, onMouseDown: (event) => event.preventDefault(), children: [
1268
+ !props.panelOnly && /* @__PURE__ */ jsxs("div", { className: "block-menu", children: [
1269
+ item("h1", "转换为", () => setSubmenu(submenu === "format" ? null : "format"), { submenu: "format" }),
1270
+ item("indent", "缩进和对齐", () => setSubmenu(submenu === "indent-align" ? null : "indent-align"), { submenu: "indent-align" }),
1271
+ item("palette", "颜色", () => setSubmenu(submenu === "color" ? null : "color"), { submenu: "color" }),
1272
+ item("plus", "在下方添加", () => setSubmenu(submenu === "insert-below" ? null : "insert-below"), { submenu: "insert-below" }),
1273
+ props.canComment && /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1274
+ props.canComment && item("comment", "评论", () => {
1275
+ var _a2;
1276
+ (_a2 = props.onRequestComment) == null ? void 0 : _a2.call(props);
1277
+ props.onClose();
1278
+ }),
1279
+ item("cut", "剪切", cutBlock),
1280
+ item(copied ? "check" : "copy", copied ? "已复制" : "复制", () => void copyBlock()),
1281
+ props.onCopyLink && item("copy-link", "复制本页链接", () => {
1282
+ var _a2;
1283
+ (_a2 = props.onCopyLink) == null ? void 0 : _a2.call(props);
1284
+ props.onClose();
1285
+ }),
1286
+ /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1287
+ props.onSharePage && item("share", "分享此页面", () => {
1288
+ var _a2;
1289
+ (_a2 = props.onSharePage) == null ? void 0 : _a2.call(props);
1290
+ props.onClose();
1291
+ }),
1292
+ props.onConvertToSubpage && item("page", "转换为子文档", () => {
1293
+ var _a2;
1294
+ (_a2 = props.onConvertToSubpage) == null ? void 0 : _a2.call(props, node == null ? void 0 : node.toJSON());
1295
+ props.onClose();
1296
+ }),
1297
+ props.onSaveAsTemplate && item("file", "保存为模板", () => {
1298
+ var _a2;
1299
+ (_a2 = props.onSaveAsTemplate) == null ? void 0 : _a2.call(props, node == null ? void 0 : node.toJSON());
1300
+ props.onClose();
1301
+ }),
1302
+ /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1303
+ item("trash", "删除", () => action(), { danger: true })
1304
+ ] }),
1305
+ submenu && /* @__PURE__ */ jsxs(
1306
+ "div",
1307
+ {
1308
+ ref: submenuRef,
1309
+ className: `block-submenu${submenu === "color" ? " block-submenu-color" : ""}`,
1310
+ role: submenu === "color" ? "dialog" : "menu",
1311
+ style: submenuPlace ? { position: "absolute", ...submenuPlace, overflowY: "auto" } : void 0,
1312
+ "aria-label": submenu === "color" ? "块颜色" : void 0,
1313
+ onMouseEnter: () => setSubmenu(submenu),
1314
+ children: [
1315
+ submenu === "format" && convertibles.map((command) => {
1316
+ const convertible = canConvertMenuBlocks(editor.state, info.pos, command.id);
1317
+ const active = editableActive(editor, info.pos, command.id);
1318
+ return /* @__PURE__ */ jsxs(
1319
+ "button",
1320
+ {
1321
+ type: "button",
1322
+ role: "menuitem",
1323
+ className: `bm-item${active ? " active" : ""}${convertible ? "" : " disabled"}`,
1324
+ disabled: !convertible,
1325
+ "aria-disabled": !convertible,
1326
+ onClick: () => {
1327
+ convertMenuBlocks(editor, info.pos, command.id);
1328
+ props.onClose();
1329
+ },
1330
+ children: [
1331
+ /* @__PURE__ */ jsx(WIcon, { name: command.icon, size: 18 }),
1332
+ /* @__PURE__ */ jsx("span", { children: command.label }),
1333
+ active && /* @__PURE__ */ jsx(WIcon, { name: "check", size: 16, className: "bm-chevron" })
1334
+ ]
1335
+ },
1336
+ command.id
1337
+ );
1338
+ }),
1339
+ submenu === "indent-align" && /* @__PURE__ */ jsxs(Fragment, { children: [
1340
+ item("indent", "增加缩进", () => {
1341
+ chain().indentBlock().run();
1342
+ props.onClose();
1343
+ }, { disabled: !textual }),
1344
+ item("outdent", "减少缩进", () => {
1345
+ chain().outdentBlock().run();
1346
+ props.onClose();
1347
+ }, { disabled: !textual }),
1348
+ /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1349
+ TEXT_ALIGNS.map((align) => item(
1350
+ `align-${align}`,
1351
+ align === "left" ? "左对齐" : align === "center" ? "居中对齐" : align === "right" ? "右对齐" : "两端对齐",
1352
+ () => {
1353
+ chain().setTextAlign(align).run();
1354
+ props.onClose();
1355
+ },
1356
+ { active: currentAlign === align, disabled: !textual }
1357
+ ))
1358
+ ] }),
1359
+ submenu === "color" && /* @__PURE__ */ jsxs("div", { className: "block-color-panel", role: "group", "aria-label": "文档颜色", children: [
1360
+ /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
1361
+ /* @__PURE__ */ jsx("div", { className: "color-label", children: "字体颜色" }),
1362
+ /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "字体颜色", children: TEXT_COLORS.map((color) => /* @__PURE__ */ jsx(
1363
+ "button",
1364
+ {
1365
+ type: "button",
1366
+ className: `color-choice${textColorActive(editor, color.value) ? " selected" : ""}`,
1367
+ "aria-label": `字体颜色:${color.name}`,
1368
+ title: `字体颜色:${color.name}`,
1369
+ "aria-pressed": textColorActive(editor, color.value),
1370
+ onClick: () => {
1371
+ const attrs = color.value ? { color: color.value } : null;
1372
+ attrs ? chain().setMark("textStyle", attrs).run() : chain().unsetMark("textStyle").run();
1373
+ },
1374
+ children: /* @__PURE__ */ jsx("span", { className: "font-sample", style: color.value ? { color: color.value } : void 0, children: "A" })
1375
+ },
1376
+ color.name
1377
+ )) })
1378
+ ] }),
1379
+ /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
1380
+ /* @__PURE__ */ jsx("div", { className: "color-label", children: "背景颜色" }),
1381
+ /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "背景颜色", children: HIGHLIGHT_COLORS.map((color) => /* @__PURE__ */ jsx(
1382
+ "button",
1383
+ {
1384
+ type: "button",
1385
+ className: `color-choice background-choice${highlightActive(editor, color.value) ? " selected" : ""}${color.value === null ? " no-background" : ""}`,
1386
+ "aria-label": `背景颜色:${color.name}`,
1387
+ title: `背景颜色:${color.name}`,
1388
+ "aria-pressed": highlightActive(editor, color.value),
1389
+ style: color.value ? { background: color.value } : void 0,
1390
+ onClick: () => {
1391
+ const attrs = color.value ? { color: color.value } : null;
1392
+ attrs ? chain().setMark("highlight", attrs).run() : chain().unsetMark("highlight").run();
1393
+ },
1394
+ children: color.value === null && /* @__PURE__ */ jsx("span", { className: "clear-stroke", "aria-hidden": "true" })
1395
+ },
1396
+ color.name
1397
+ )) })
1398
+ ] }),
1399
+ /* @__PURE__ */ jsx(WButton, { variant: "outline", block: true, onClick: () => {
1400
+ chain().unsetMark("textStyle").unsetMark("highlight").run();
1401
+ props.onClose();
1402
+ }, children: "恢复默认" })
1403
+ ] }),
1404
+ submenu === "insert-below" && insertCommands.map((command) => item(command.icon, command.label, () => runInsert(command)))
1405
+ ]
1406
+ }
1407
+ ),
1408
+ panel && /* @__PURE__ */ jsx(
1409
+ InsertPicker,
1410
+ {
1411
+ mode: panel.mode,
1412
+ x: ((submenuPlace == null ? void 0 : submenuPlace.left) ?? 0) + (((_b = submenuRef.current) == null ? void 0 : _b.offsetWidth) ?? 240) + 6,
1413
+ y: Math.max(0, (((_c = rowRef.current) == null ? void 0 : _c.getBoundingClientRect().top) ?? 0) - (((_d = target.current) == null ? void 0 : _d.getBoundingClientRect().top) ?? 0)),
1414
+ boundTop: 8 - (((_e = target.current) == null ? void 0 : _e.getBoundingClientRect().top) ?? 0),
1415
+ boundBottom: innerHeight - 8 - (((_f = target.current) == null ? void 0 : _f.getBoundingClientRect().top) ?? 0),
1416
+ onPick: insertWithPanel,
1417
+ onClose: () => setPanel(null)
1418
+ }
1419
+ )
1420
+ ] });
1421
+ }
1422
+ function editableActive(editor, pos, id) {
1423
+ const range = menuBlockRange(editor.state, pos);
1424
+ if (!range) return false;
1425
+ const node = range.nodes[0];
1426
+ if (!node) return false;
1427
+ if (id === "text") return node.type.name === "paragraph";
1428
+ if (id === "h1") return node.type.name === "heading" && node.attrs.level === 1;
1429
+ if (id === "h2") return node.type.name === "heading" && node.attrs.level === 2;
1430
+ if (id === "h3") return node.type.name === "heading" && node.attrs.level === 3;
1431
+ if (id === "h4") return node.type.name === "heading" && node.attrs.level === 4;
1432
+ if (id === "h5") return node.type.name === "heading" && node.attrs.level === 5;
1433
+ if (id === "h6") return node.type.name === "heading" && node.attrs.level === 6;
1434
+ return node.type.name === { ordered: "orderedList", bullet: "bulletList", quote: "blockquote", code: "codeBlock", task: "taskList", callout: "callout" }[id];
1435
+ }
1436
+ function textColorActive(editor, value) {
1437
+ const color = editor.getAttributes("textStyle").color;
1438
+ return value === null ? typeof color !== "string" : color === value;
1439
+ }
1440
+ function highlightActive(editor, value) {
1441
+ const color = editor.getAttributes("highlight").color;
1442
+ return value === null ? typeof color !== "string" : color === value;
1443
+ }
1444
+ async function writeRichTable(node, schema2) {
1445
+ var _a;
1446
+ if (typeof ClipboardItem === "undefined" || !((_a = navigator.clipboard) == null ? void 0 : _a.write)) {
1447
+ throw new Error("rich clipboard unavailable");
1448
+ }
1449
+ const container = document.createElement("div");
1450
+ container.appendChild(DOMSerializer.fromSchema(schema2).serializeFragment(Fragment$1.from(node)));
1451
+ await navigator.clipboard.write([new ClipboardItem({
1452
+ "text/html": new Blob([container.innerHTML], { type: "text/html" }),
1453
+ "text/plain": new Blob([node.textBetween(0, node.content.size, "\n")], { type: "text/plain" })
1454
+ })]);
1455
+ }
1456
+ async function copyTableToClipboard(editor, pos, cut, write = writeRichTable) {
1457
+ if (editor.isDestroyed || !editor.isEditable) return null;
1458
+ const originalDoc = editor.state.doc;
1459
+ const node = originalDoc.nodeAt(pos);
1460
+ if ((node == null ? void 0 : node.type.spec.tableRole) !== "table") return null;
1461
+ await write(node, editor.state.schema);
1462
+ if (!cut) return "copied";
1463
+ if (editor.isDestroyed || !editor.isEditable || editor.state.doc !== originalDoc || editor.state.doc.nodeAt(pos) !== node) return "changed";
1464
+ editor.view.dispatch(editor.state.tr.deleteRange(pos, pos + node.nodeSize));
1465
+ editor.view.focus();
1466
+ return "cut";
1467
+ }
1468
+ function placeTableToolbar(selection, toolbar, viewport) {
1469
+ const inset = 12, gap = 14;
1470
+ const width = Math.min(toolbar.width, Math.max(0, viewport.width - inset * 2));
1471
+ const above = selection.top - gap - toolbar.height;
1472
+ return {
1473
+ left: Math.max(inset, Math.min(
1474
+ (selection.left + selection.right - width) / 2,
1475
+ viewport.width - width - inset
1476
+ )),
1477
+ top: above >= viewport.topInset ? above : Math.max(viewport.topInset, Math.min(
1478
+ selection.top + 40,
1479
+ viewport.height - toolbar.height - inset
1480
+ ))
1481
+ };
1482
+ }
1483
+ function placeTableMenu(anchor, menu, viewport) {
1484
+ const inset = 12, gap = 6, topInset = viewport.topInset;
1485
+ const anchorTop = Math.max(topInset, Math.min(anchor.top, viewport.height - inset));
1486
+ const anchorBottom = Math.max(anchorTop, Math.min(anchor.bottom, viewport.height - inset));
1487
+ const below = Math.max(0, viewport.height - inset - anchorBottom - gap);
1488
+ const above = Math.max(0, anchorTop - gap - topInset);
1489
+ const useBelow = menu.height <= below || menu.height > above && below >= above;
1490
+ const maxHeight = useBelow ? below : above;
1491
+ const height = Math.min(menu.height, maxHeight);
1492
+ return {
1493
+ left: Math.max(inset, Math.min(anchor.left, viewport.width - menu.width - inset)),
1494
+ top: useBelow ? anchorBottom + gap : anchorTop - gap - height,
1495
+ maxHeight
1496
+ };
1497
+ }
1498
+ const CORNER_W = 44, CORNER_H = 26, GUTTER_GAP = 8;
1499
+ const anchorOf = (event) => {
1500
+ const rect = event.currentTarget.getBoundingClientRect();
1501
+ return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
1502
+ };
1503
+ function TableControls({ editor, hover, revision, onNotice, onBlockDrag }) {
1504
+ var _a, _b, _c;
1505
+ const { portal } = useContentContext(), axisCleanup = useRef(null);
1506
+ const [dismissed, setDismissed] = useState(false), [panel, setPanel] = useState("none");
1507
+ const [menuOpen, setMenuOpen] = useState(false), [menuScope, setMenuScope] = useState("table");
1508
+ const [showStyle, setShowStyle] = useState(false), [widthOpen, setWidthOpen] = useState(false), [width, setWidth] = useState(""), [busy, setBusy] = useState(false);
1509
+ const [toolbarSize, setToolbarSize] = useState({ width: 0, height: 0 });
1510
+ const [menuAnchor, setMenuAnchor] = useState(null), [panelAnchor, setPanelAnchor] = useState(null);
1511
+ const menuRef = useRef(null), panelRef = useRef(null);
1512
+ const [menuPlace, setMenuPlace] = useState({ left: 0, top: 0, maxHeight: 480 }), [panelPlace, setPanelPlace] = useState({ left: 0, top: 0 });
1513
+ useEffect(() => {
1514
+ if (!menuOpen || !menuAnchor) return;
1515
+ const element = menuRef.current;
1516
+ if (!element) return;
1517
+ setMenuPlace(placeTableMenu(menuAnchor, { width: element.offsetWidth, height: element.offsetHeight }, { width: innerWidth, height: innerHeight, topInset: 12 }));
1518
+ }, [menuOpen, menuAnchor, widthOpen, showStyle, menuScope]);
1519
+ useEffect(() => {
1520
+ if (panel === "none" || !panelAnchor) return;
1521
+ const element = panelRef.current;
1522
+ const card = { width: (element == null ? void 0 : element.offsetWidth) ?? 320, height: (element == null ? void 0 : element.offsetHeight) ?? 260 };
1523
+ const placement = placeTableToolbar(panelAnchor, card, { width: innerWidth, height: innerHeight, topInset: 12 });
1524
+ setPanelPlace({ left: placement.left, top: placement.top });
1525
+ }, [panel, panelAnchor]);
1526
+ const selectionRef = useRef(editor.state.selection), docRef = useRef(editor.state.doc);
1527
+ useEffect(() => {
1528
+ if (!selectionRef.current.eq(editor.state.selection) || docRef.current !== editor.state.doc) {
1529
+ setPanel("none");
1530
+ setDismissed(false);
1531
+ setWidthOpen(false);
1532
+ selectionRef.current = editor.state.selection;
1533
+ docRef.current = editor.state.doc;
1534
+ }
1535
+ }, [revision, editor]);
1536
+ const activeCell = currentTableCellPosition(editor.view);
1537
+ const cellPos = (hover == null ? void 0 : hover.cellPos) ?? activeCell;
1538
+ const fresh = cellPos == null ? null : readTableGeometry(editor.view, cellPos);
1539
+ const geometry = fresh && hover ? { ...fresh, rowIndex: hover.rowIndex, colIndex: hover.colIndex } : fresh;
1540
+ const cellSelection = editor.state.selection instanceof CellSelection ? editor.state.selection : null;
1541
+ const showControls = !!hover || !!cellSelection || menuOpen;
1542
+ const canMerge = editor.can().mergeCells(), canSplit = editor.can().splitCell();
1543
+ const headerRow = tableHeaderActive(editor, "row"), headerColumn = tableHeaderActive(editor, "column");
1544
+ const currentAlign = editor.getAttributes("tableCell").align ?? editor.getAttributes("tableHeader").align;
1545
+ const currentTextColor = typeof editor.getAttributes("textStyle").color === "string" ? editor.getAttributes("textStyle").color : null;
1546
+ const currentHighlight = typeof editor.getAttributes("highlight").color === "string" ? editor.getAttributes("highlight").color : null;
1547
+ const currentCellBackground = typeof (editor.getAttributes("tableCell").backgroundColor ?? editor.getAttributes("tableHeader").backgroundColor) === "string" ? editor.getAttributes("tableCell").backgroundColor ?? editor.getAttributes("tableHeader").backgroundColor : null;
1548
+ const chain = () => editor.chain().focus();
1549
+ const shell = editor.view.dom.closest(".editor-shell");
1550
+ useEffect(() => {
1551
+ const context = (event) => {
1552
+ if (!editor.isEditable || !(event.target instanceof Element)) return;
1553
+ const cell = event.target.closest("td,th");
1554
+ if (!cell) return;
1555
+ const pos = cellPositionAtDOM(editor.view, cell);
1556
+ if (pos === null) return;
1557
+ event.preventDefault();
1558
+ selectTableCell(editor.view, pos);
1559
+ setPanel("none");
1560
+ setMenuScope("cell");
1561
+ setShowStyle(false);
1562
+ setMenuAnchor({ left: event.clientX, right: event.clientX, top: event.clientY, bottom: event.clientY });
1563
+ setMenuOpen(true);
1564
+ };
1565
+ editor.view.dom.addEventListener("contextmenu", context, true);
1566
+ return () => {
1567
+ var _a2;
1568
+ if (!editor.isDestroyed) editor.view.dom.removeEventListener("contextmenu", context, true);
1569
+ (_a2 = axisCleanup.current) == null ? void 0 : _a2.call(axisCleanup);
1570
+ };
1571
+ }, [editor]);
1572
+ useEffect(() => {
1573
+ if (!menuOpen && panel === "none") return;
1574
+ const close = (event) => {
1575
+ var _a2, _b2;
1576
+ if (event.target instanceof Element && (((_a2 = menuRef.current) == null ? void 0 : _a2.contains(event.target)) || ((_b2 = panelRef.current) == null ? void 0 : _b2.contains(event.target)) || event.target.closest(".tbl-ui-host"))) return;
1577
+ setMenuOpen(false);
1578
+ setPanel("none");
1579
+ };
1580
+ const resize = () => {
1581
+ setMenuOpen(false);
1582
+ setPanel("none");
1583
+ };
1584
+ document.addEventListener("pointerdown", close, true);
1585
+ window.addEventListener("resize", resize);
1586
+ return () => {
1587
+ document.removeEventListener("pointerdown", close, true);
1588
+ window.removeEventListener("resize", resize);
1589
+ };
1590
+ }, [menuOpen, panel]);
1591
+ function axisDown(axis, index, event) {
1592
+ var _a2;
1593
+ if (!geometry || event.button !== 0) return;
1594
+ event.preventDefault();
1595
+ (_a2 = axisCleanup.current) == null ? void 0 : _a2.call(axisCleanup);
1596
+ const view = editor.view, doc = view.state.doc, original = view.state.selection;
1597
+ const cell = doc.resolve(geometry.cellPos), table = cell.node(-1), start = cell.start(-1), map = TableMap.get(table);
1598
+ const axisCells = axis === "row" ? geometry.rows : geometry.columns;
1599
+ const host = shell == null ? void 0 : shell.getBoundingClientRect();
1600
+ if (!host) return;
1601
+ const select = (endIndex) => {
1602
+ const lo = Math.min(index, endIndex), hi = Math.max(index, endIndex);
1603
+ const first = axis === "row" ? lo * map.width : lo;
1604
+ const last = axis === "row" ? (hi + 1) * map.width - 1 : (map.height - 1) * map.width + hi;
1605
+ view.dispatch(view.state.tr.setSelection(CellSelection.create(doc, start + map.map[first], start + map.map[last])));
1606
+ view.focus();
1607
+ };
1608
+ select(index);
1609
+ const move = (next) => {
1610
+ var _a3;
1611
+ if (!editor.isEditable || view.state.doc !== doc) {
1612
+ (_a3 = axisCleanup.current) == null ? void 0 : _a3.call(axisCleanup);
1613
+ return;
1614
+ }
1615
+ const coordinate = axis === "row" ? next.clientY - host.top : next.clientX - host.left;
1616
+ const closest = axisCells.reduce((best, item) => Math.abs(item.start + item.size / 2 - coordinate) < Math.abs(best.start + best.size / 2 - coordinate) ? item : best);
1617
+ select(closest.index);
1618
+ next.preventDefault();
1619
+ };
1620
+ const key = (next) => {
1621
+ var _a3;
1622
+ if (next.key === "Escape") {
1623
+ if (view.state.doc === doc) view.dispatch(view.state.tr.setSelection(original));
1624
+ next.preventDefault();
1625
+ (_a3 = axisCleanup.current) == null ? void 0 : _a3.call(axisCleanup);
1626
+ }
1627
+ };
1628
+ const done = () => {
1629
+ var _a3;
1630
+ return (_a3 = axisCleanup.current) == null ? void 0 : _a3.call(axisCleanup);
1631
+ };
1632
+ axisCleanup.current = () => {
1633
+ window.removeEventListener("mousemove", move, true);
1634
+ window.removeEventListener("mouseup", done, true);
1635
+ window.removeEventListener("keydown", key, true);
1636
+ window.removeEventListener("blur", done);
1637
+ axisCleanup.current = null;
1638
+ };
1639
+ window.addEventListener("mousemove", move, true);
1640
+ window.addEventListener("mouseup", done, true);
1641
+ window.addEventListener("keydown", key, true);
1642
+ window.addEventListener("blur", done);
1643
+ }
1644
+ useEffect(() => {
1645
+ const escape = (event) => {
1646
+ if (event.key !== "Escape" || event.isComposing) return;
1647
+ if (panel !== "none") {
1648
+ event.preventDefault();
1649
+ setPanel("none");
1650
+ } else if (menuOpen) {
1651
+ event.preventDefault();
1652
+ setMenuOpen(false);
1653
+ } else if (cellSelection) {
1654
+ event.preventDefault();
1655
+ setDismissed(true);
1656
+ editor.view.focus();
1657
+ }
1658
+ };
1659
+ document.addEventListener("keydown", escape);
1660
+ return () => document.removeEventListener("keydown", escape);
1661
+ });
1662
+ function cornerLeft() {
1663
+ if (!geometry || !shell) return 0;
1664
+ return Math.max(geometry.contentLeft - GUTTER_GAP - CORNER_W, 8 - shell.getBoundingClientRect().left);
1665
+ }
1666
+ function selectedBounds() {
1667
+ var _a2;
1668
+ if (!shell || !cellSelection) return null;
1669
+ const rects = [];
1670
+ cellSelection.forEachCell((_cell, pos) => {
1671
+ const dom = editor.view.nodeDOM(pos);
1672
+ if (dom instanceof HTMLElement) rects.push(dom.getBoundingClientRect());
1673
+ });
1674
+ const table = geometry ? (_a2 = editor.view.nodeDOM(geometry.cellPos)) == null ? void 0 : _a2.closest("table") : null;
1675
+ if (!table) return null;
1676
+ const bounds = clippedTableSelection(rects, (table.closest(".tableWrapper") ?? table).getBoundingClientRect());
1677
+ if (!bounds) return null;
1678
+ const host = shell.getBoundingClientRect();
1679
+ const placement = placeTableToolbar(bounds, toolbarSize, { width: innerWidth, height: innerHeight, topInset: 66 });
1680
+ return { left: bounds.left - host.left, top: bounds.top - host.top, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top, toolbarLeft: placement.left, toolbarTop: placement.top };
1681
+ }
1682
+ const selection = selectedBounds();
1683
+ function mark(name) {
1684
+ return tableSelectionMarkActive(editor, name);
1685
+ }
1686
+ function toggleMark(name) {
1687
+ setTableSelectionMark(editor, name, mark(name) ? null : {});
1688
+ }
1689
+ function setColor(name, value) {
1690
+ setTableSelectionMark(editor, name, value ? { color: value } : null);
1691
+ setPanel("none");
1692
+ }
1693
+ function applySwatch(value) {
1694
+ if (panel === "background") {
1695
+ chain().setCellAttribute("backgroundColor", value).run();
1696
+ setPanel("none");
1697
+ } else setColor(panel === "color" ? "textStyle" : "highlight", value);
1698
+ }
1699
+ function showContext(event, scope = "table") {
1700
+ if (!event) return;
1701
+ if (scope === "table" && geometry) selectTableAxis(editor.view, geometry.cellPos, "table");
1702
+ setPanel("none");
1703
+ setShowStyle(false);
1704
+ setMenuScope(scope);
1705
+ setMenuAnchor(anchorOf(event));
1706
+ setMenuOpen(true);
1707
+ }
1708
+ async function copyTable(cut) {
1709
+ const target = tableTarget();
1710
+ if (!target || busy || !editor.isEditable) return;
1711
+ setBusy(true);
1712
+ try {
1713
+ const result = await copyTableToClipboard(editor, target.pos, cut);
1714
+ if (result === "changed") onNotice == null ? void 0 : onNotice("表格已复制;内容发生变化,未执行剪切。");
1715
+ else if (result) onNotice == null ? void 0 : onNotice(result === "cut" ? "表格已剪切。" : "表格已复制。");
1716
+ setMenuOpen(false);
1717
+ } catch {
1718
+ onNotice == null ? void 0 : onNotice("无法写入剪贴板,请允许浏览器访问剪贴板后重试。");
1719
+ } finally {
1720
+ setBusy(false);
1721
+ }
1722
+ }
1723
+ function applyStyle(color) {
1724
+ setTableHeaderBackground(editor, color);
1725
+ setMenuOpen(false);
1726
+ editor.view.focus();
1727
+ }
1728
+ function insertParagraphBelow() {
1729
+ const target = tableTarget();
1730
+ if (!target) return;
1731
+ const end = target.pos + target.node.nodeSize;
1732
+ editor.chain().focus().insertContentAt(end, { type: "paragraph" }).setTextSelection(end + 1).run();
1733
+ setMenuOpen(false);
1734
+ }
1735
+ function showPanel(name, event) {
1736
+ if (panel === name) {
1737
+ setPanel("none");
1738
+ return;
1739
+ }
1740
+ setPanelAnchor(anchorOf(event));
1741
+ setPanel(name);
1742
+ }
1743
+ function tableTarget() {
1744
+ const pos = cellPos == null ? null : tablePositionAtCell(editor.view, cellPos);
1745
+ const node = pos === null ? null : editor.state.doc.nodeAt(pos);
1746
+ return pos !== null && node ? { pos, node } : null;
1747
+ }
1748
+ function distribute() {
1749
+ const target = tableTarget(), dom = target ? editor.view.nodeDOM(target.pos) : null;
1750
+ const table = dom instanceof HTMLElement ? dom.matches("table") ? dom : dom.querySelector("table") : null;
1751
+ const wrapper = (table == null ? void 0 : table.closest(".tableWrapper")) ?? table;
1752
+ if (wrapper) distributeTableColumns(editor, wrapper.clientWidth - 2);
1753
+ setMenuOpen(false);
1754
+ }
1755
+ function applyWidth() {
1756
+ var _a2;
1757
+ const col = (_a2 = currentCellRect(editor)) == null ? void 0 : _a2.col, px = Math.round(Number(width));
1758
+ if (col == null || !Number.isFinite(px) || px < 40 || px > 2e3) return;
1759
+ setColumnWidth(editor, col, px);
1760
+ setWidthOpen(false);
1761
+ setMenuOpen(false);
1762
+ }
1763
+ const colorEntries = [
1764
+ { key: "color", label: "表格文字颜色", tip: "文字颜色", hint: "只改文字本身的颜色,不影响格子底色", colors: TEXT_COLORS, current: currentTextColor },
1765
+ { key: "highlight", label: "表格文字高亮", tip: "文字高亮", hint: "只给文字加荧光底色,格子仍是原本的底色", colors: HIGHLIGHT_COLORS, current: currentHighlight },
1766
+ { key: "background", label: "单元格底色", tip: "单元格底色", hint: "给整个单元格铺底色,与文字高亮互不影响", colors: CELL_BACKGROUNDS, current: currentCellBackground }
1767
+ ];
1768
+ const menuItem = (icon, label, onClick, options = {}) => /* @__PURE__ */ jsxs(
1769
+ "button",
1770
+ {
1771
+ type: "button",
1772
+ role: "menuitem",
1773
+ className: `ctx-item${options.danger ? " danger" : ""}`,
1774
+ disabled: options.disabled,
1775
+ "aria-checked": options.checked,
1776
+ "aria-expanded": options.expanded,
1777
+ onMouseDown: (event) => event.preventDefault(),
1778
+ onClick,
1779
+ children: [
1780
+ /* @__PURE__ */ jsx(WIcon, { name: icon, size: 16 }),
1781
+ /* @__PURE__ */ jsx("span", { children: label }),
1782
+ options.checked && /* @__PURE__ */ jsx(WIcon, { name: "check", size: 16, className: "ctx-chevron" }),
1783
+ options.expanded !== void 0 && /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 14, className: "ctx-chevron" })
1784
+ ]
1785
+ }
1786
+ );
1787
+ const tool = (label, icon, onClick, active = false, disabled = false, extra) => /* @__PURE__ */ jsxs(
1788
+ "button",
1789
+ {
1790
+ type: "button",
1791
+ className: `tool${active ? " on" : ""}`,
1792
+ "aria-label": label,
1793
+ title: label,
1794
+ "aria-pressed": active,
1795
+ "aria-disabled": disabled || void 0,
1796
+ onClick: () => {
1797
+ if (!disabled) onClick();
1798
+ },
1799
+ children: [
1800
+ /* @__PURE__ */ jsx(WIcon, { name: icon, size: 18 }),
1801
+ extra
1802
+ ]
1803
+ }
1804
+ );
1805
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1806
+ /* @__PURE__ */ jsxs("div", { className: "tbl-ui-host", children: [
1807
+ geometry && showControls && /* @__PURE__ */ jsxs(Fragment, { children: [
1808
+ /* @__PURE__ */ jsxs("div", { className: "tbl-corner", style: { left: cornerLeft(), top: geometry.tableTop + (((_a = geometry.rows[0]) == null ? void 0 : _a.size) ?? CORNER_H) / 2 - CORNER_H / 2 }, children: [
1809
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "表格操作", "aria-haspopup": "menu", title: "表格操作", onMouseDown: (event) => event.preventDefault(), onClick: (event) => showContext(event, "table"), children: /* @__PURE__ */ jsx(WIcon, { name: "table", size: 18 }) }),
1810
+ /* @__PURE__ */ jsx(
1811
+ "button",
1812
+ {
1813
+ type: "button",
1814
+ className: "tbl-grip",
1815
+ "aria-label": "拖动或打开表格菜单",
1816
+ "aria-haspopup": "menu",
1817
+ title: "拖动表格",
1818
+ draggable: false,
1819
+ onMouseDown: (event) => {
1820
+ const pos = tablePositionAtCell(editor.view, geometry.cellPos), anchor = anchorOf(event);
1821
+ if (pos !== null) onBlockDrag == null ? void 0 : onBlockDrag(pos, event.nativeEvent, () => {
1822
+ selectTableAxis(editor.view, geometry.cellPos, "table");
1823
+ setMenuScope("table");
1824
+ setShowStyle(false);
1825
+ setMenuAnchor(anchor);
1826
+ setMenuOpen(true);
1827
+ });
1828
+ },
1829
+ onClick: (event) => {
1830
+ if (event.detail === 0) showContext(event, "table");
1831
+ },
1832
+ children: /* @__PURE__ */ jsx(WIcon, { name: "handle", size: 16 })
1833
+ }
1834
+ )
1835
+ ] }),
1836
+ geometry.rows.map((row) => /* @__PURE__ */ jsx(
1837
+ "button",
1838
+ {
1839
+ type: "button",
1840
+ className: `tbl-axis tbl-row-axis${row.index === geometry.rowIndex ? " current" : ""}`,
1841
+ "aria-label": `选择第 ${row.index + 1} 行`,
1842
+ title: `选择第 ${row.index + 1} 行`,
1843
+ style: { left: geometry.tableLeft - 10, top: row.start + row.size / 2 - 12 },
1844
+ onMouseDown: (event) => axisDown("row", row.index, event),
1845
+ onClick: (event) => {
1846
+ if (event.detail === 0) selectTableAxis(editor.view, geometry.cellPos, "row", row.index);
1847
+ }
1848
+ },
1849
+ `row-${row.index}`
1850
+ )),
1851
+ geometry.columns.map((col) => /* @__PURE__ */ jsx(
1852
+ "button",
1853
+ {
1854
+ type: "button",
1855
+ className: `tbl-axis tbl-col-axis${col.index === geometry.colIndex ? " current" : ""}`,
1856
+ "aria-label": `选择第 ${col.index + 1} 列`,
1857
+ title: `选择第 ${col.index + 1} 列`,
1858
+ style: { left: col.start + col.size / 2 - 12, top: geometry.tableTop - 10 },
1859
+ onMouseDown: (event) => axisDown("column", col.index, event),
1860
+ onClick: (event) => {
1861
+ if (event.detail === 0) selectTableAxis(editor.view, geometry.cellPos, "column", col.index);
1862
+ }
1863
+ },
1864
+ `col-${col.index}`
1865
+ )),
1866
+ [...geometry.rows.map((row) => ({ index: row.index, start: row.start })), { index: geometry.rows.length, start: geometry.tableTop + geometry.tableHeight }].map((boundary) => /* @__PURE__ */ jsx(
1867
+ "button",
1868
+ {
1869
+ type: "button",
1870
+ className: "tbl-boundary tbl-boundary-row",
1871
+ "aria-label": `在第 ${boundary.index + 1} 行位置添加一行`,
1872
+ title: "添加一行",
1873
+ style: { left: geometry.tableLeft - 14, top: boundary.start - 10, "--insert-length": `${geometry.tableWidth}px` },
1874
+ onMouseDown: (event) => event.preventDefault(),
1875
+ onClick: () => insertTableBoundary(editor, geometry.cellPos, "row", boundary.index),
1876
+ children: /* @__PURE__ */ jsx(WIcon, { name: "plus", size: 14 })
1877
+ },
1878
+ `row-boundary-${boundary.index}`
1879
+ )),
1880
+ [...geometry.columns.map((col) => ({ index: col.index, start: col.start })), { index: geometry.columns.length, start: (((_b = geometry.columns.at(-1)) == null ? void 0 : _b.start) ?? 0) + (((_c = geometry.columns.at(-1)) == null ? void 0 : _c.size) ?? 0) }].map((boundary) => /* @__PURE__ */ jsx(
1881
+ "button",
1882
+ {
1883
+ type: "button",
1884
+ className: "tbl-boundary tbl-boundary-col",
1885
+ "aria-label": `在第 ${boundary.index + 1} 列位置添加一列`,
1886
+ title: "添加一列",
1887
+ style: { left: boundary.start - 10, top: geometry.tableTop - 14, "--insert-length": `${geometry.tableHeight}px` },
1888
+ onMouseDown: (event) => event.preventDefault(),
1889
+ onClick: () => insertTableBoundary(editor, geometry.cellPos, "column", boundary.index),
1890
+ children: /* @__PURE__ */ jsx(WIcon, { name: "plus", size: 14 })
1891
+ },
1892
+ `col-boundary-${boundary.index}`
1893
+ ))
1894
+ ] }),
1895
+ selection && /* @__PURE__ */ jsxs(Fragment, { children: [
1896
+ /* @__PURE__ */ jsx("div", { className: "tbl-selection", style: { left: selection.left, top: selection.top, width: selection.width, height: selection.height } }),
1897
+ !menuOpen && /* @__PURE__ */ jsx(
1898
+ "button",
1899
+ {
1900
+ type: "button",
1901
+ className: "tbl-selection-more",
1902
+ "aria-label": "打开单元格格式",
1903
+ "aria-expanded": !!cellSelection && !dismissed,
1904
+ style: { left: selection.left + selection.width / 2 - 16, top: selection.top - 10 },
1905
+ onMouseDown: (event) => event.preventDefault(),
1906
+ onClick: () => {
1907
+ if (activeCell == null) return;
1908
+ if (!cellSelection) {
1909
+ editor.view.dispatch(editor.state.tr.setSelection(CellSelection.create(editor.state.doc, activeCell)));
1910
+ editor.view.focus();
1911
+ setDismissed(false);
1912
+ } else setDismissed(!dismissed);
1913
+ },
1914
+ children: "···"
1915
+ }
1916
+ ),
1917
+ cellSelection && !dismissed && !menuOpen && /* @__PURE__ */ jsx(
1918
+ "div",
1919
+ {
1920
+ className: "tbl-cellbar",
1921
+ role: "toolbar",
1922
+ "aria-label": "表格选区格式",
1923
+ ref: (element) => {
1924
+ if (element) {
1925
+ const { width: w, height: h } = element.getBoundingClientRect();
1926
+ if (w !== toolbarSize.width || h !== toolbarSize.height) setToolbarSize({ width: w, height: h });
1927
+ }
1928
+ },
1929
+ style: { left: selection.toolbarLeft, top: selection.toolbarTop },
1930
+ onMouseDown: (event) => event.preventDefault(),
1931
+ children: /* @__PURE__ */ jsxs("div", { className: "tbl-tools", children: [
1932
+ tool("标题列", "table-header-column", () => chain().toggleHeaderColumn().run(), headerColumn),
1933
+ tool("标题行", "table-header-row", () => chain().toggleHeaderRow().run(), headerRow),
1934
+ /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
1935
+ tool("合并单元格", "merge", () => chain().mergeCells().run(), false, !canMerge),
1936
+ tool("拆分单元格", "split", () => chain().splitCell().run(), false, !canSplit),
1937
+ /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
1938
+ /* @__PURE__ */ jsxs("button", { type: "button", className: `tool${panel === "align" ? " on" : ""}`, "aria-label": "单元格对齐", onClick: (event) => showPanel("align", event), children: [
1939
+ /* @__PURE__ */ jsx(WIcon, { name: `align-${currentAlign ?? "left"}`, size: 18 }),
1940
+ /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 12 })
1941
+ ] }),
1942
+ /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
1943
+ tool("表格文字加粗", "bold", () => toggleMark("bold"), mark("bold")),
1944
+ tool("表格文字斜体", "italic", () => toggleMark("italic"), mark("italic")),
1945
+ tool("表格文字删除线", "strike", () => toggleMark("strike"), mark("strike")),
1946
+ tool("表格文字下划线", "underline", () => toggleMark("underline"), mark("underline")),
1947
+ colorEntries.map((item) => /* @__PURE__ */ jsxs(
1948
+ "button",
1949
+ {
1950
+ type: "button",
1951
+ className: `tool color-tool${panel === item.key ? " on" : ""}`,
1952
+ "aria-label": item.label,
1953
+ title: item.tip,
1954
+ "aria-haspopup": "dialog",
1955
+ "aria-expanded": panel === item.key,
1956
+ onClick: (event) => showPanel(item.key, event),
1957
+ children: [
1958
+ item.key === "color" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1959
+ "A",
1960
+ /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 12 })
1961
+ ] }) : item.key === "highlight" ? /* @__PURE__ */ jsx(WIcon, { name: "marker", size: 17 }) : /* @__PURE__ */ jsx("span", { className: "fill-icon" }),
1962
+ /* @__PURE__ */ jsx("span", { className: "color-bar", style: { background: item.current ?? "transparent" }, "aria-hidden": "true" })
1963
+ ]
1964
+ },
1965
+ item.key
1966
+ )),
1967
+ /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
1968
+ tool("选择整个表格", "table", () => selectTableAxis(editor.view, geometry.cellPos, "table")),
1969
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tool", "aria-label": "更多表格操作", "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: (event) => showContext(event, "cell"), children: "···" })
1970
+ ] })
1971
+ }
1972
+ )
1973
+ ] })
1974
+ ] }),
1975
+ portal && createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [
1976
+ panel === "align" && /* @__PURE__ */ jsx("div", { ref: panelRef, className: "tbl-align-menu", style: { position: "fixed", left: panelPlace.left, top: panelPlace.top }, role: "menu", "aria-label": "单元格对齐菜单", onMouseDown: (event) => event.preventDefault(), children: CELL_ALIGNS.map((align) => /* @__PURE__ */ jsxs(
1977
+ "button",
1978
+ {
1979
+ type: "button",
1980
+ role: "menuitemradio",
1981
+ "aria-checked": (currentAlign ?? "left") === align,
1982
+ onClick: () => {
1983
+ chain().setCellAttribute("align", align).run();
1984
+ setPanel("none");
1985
+ },
1986
+ children: [
1987
+ /* @__PURE__ */ jsx(WIcon, { name: `align-${align}`, size: 18 }),
1988
+ /* @__PURE__ */ jsx("span", { children: align === "left" ? "左对齐" : align === "center" ? "居中对齐" : "右对齐" }),
1989
+ (currentAlign ?? "left") === align && /* @__PURE__ */ jsx(WIcon, { name: "check", size: 16 })
1990
+ ]
1991
+ },
1992
+ align
1993
+ )) }),
1994
+ colorEntries.map((item) => panel === item.key && /* @__PURE__ */ jsxs("div", { ref: panelRef, className: "tbl-palette", style: { position: "fixed", left: panelPlace.left, top: panelPlace.top }, role: "dialog", "aria-label": `${item.tip}选择`, onMouseDown: (event) => event.preventDefault(), children: [
1995
+ /* @__PURE__ */ jsxs("div", { className: "palette-head", children: [
1996
+ /* @__PURE__ */ jsx("span", { className: "palette-title", children: item.tip }),
1997
+ /* @__PURE__ */ jsx("span", { className: "palette-hint", children: item.hint })
1998
+ ] }),
1999
+ /* @__PURE__ */ jsx("div", { className: "palette-grid", children: item.colors.map((color) => /* @__PURE__ */ jsx(
2000
+ "button",
2001
+ {
2002
+ type: "button",
2003
+ className: `swatch${item.current === color.value ? " selected" : ""}`,
2004
+ "aria-label": `${item.tip}:${color.value ? color.name : "默认颜色"}`,
2005
+ "aria-pressed": item.current === color.value,
2006
+ onClick: () => applySwatch(color.value),
2007
+ children: color.value && item.key === "color" ? /* @__PURE__ */ jsx("span", { className: "glyph", style: { color: color.value }, children: "A" }) : color.value ? /* @__PURE__ */ jsx("span", { style: { background: color.value } }) : /* @__PURE__ */ jsx("span", { children: "/" })
2008
+ },
2009
+ color.name
2010
+ )) })
2011
+ ] }, item.key)),
2012
+ menuOpen && /* @__PURE__ */ jsx(
2013
+ "div",
2014
+ {
2015
+ ref: menuRef,
2016
+ className: "tbl-ctx",
2017
+ style: { position: "fixed", left: menuPlace.left, top: menuPlace.top, maxHeight: menuPlace.maxHeight, overflow: "auto", width: 216 },
2018
+ role: "menu",
2019
+ "aria-label": "表格操作菜单",
2020
+ onMouseDown: (event) => event.preventDefault(),
2021
+ onContextMenu: (event) => event.preventDefault(),
2022
+ children: menuScope === "table" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2023
+ menuItem("columns", "均分列宽", distribute),
2024
+ menuItem("palette", "表格样式", () => setShowStyle(!showStyle), { expanded: showStyle }),
2025
+ showStyle && /* @__PURE__ */ jsxs("div", { className: "ctx-styles", "aria-label": "表头样式", children: [
2026
+ [{ label: "浅灰表头", color: null }, { label: "蓝色表头", color: "#d0ebff" }, { label: "绿色表头", color: "#d3f9d8" }].map((style) => /* @__PURE__ */ jsxs("button", { type: "button", className: "ctx-item", role: "menuitem", onMouseDown: (event) => event.preventDefault(), onClick: () => applyStyle(style.color), children: [
2027
+ /* @__PURE__ */ jsx("span", { className: "ctx-style-swatch", style: { background: style.color ?? "var(--color-surface-subtle)" } }),
2028
+ style.label
2029
+ ] }, style.label)),
2030
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2031
+ menuItem("table-header-row", "标题行", () => {
2032
+ chain().toggleHeaderRow().run();
2033
+ setMenuOpen(false);
2034
+ }, { checked: headerRow }),
2035
+ menuItem("table-header-column", "标题列", () => {
2036
+ chain().toggleHeaderColumn().run();
2037
+ setMenuOpen(false);
2038
+ }, { checked: headerColumn })
2039
+ ] }),
2040
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2041
+ menuItem("cut", "剪切表格", () => void copyTable(true), { disabled: busy }),
2042
+ menuItem("copy", "复制表格", () => void copyTable(false), { disabled: busy }),
2043
+ menuItem("trash", "删除表格", () => {
2044
+ chain().deleteTable().run();
2045
+ setMenuOpen(false);
2046
+ }, { danger: true }),
2047
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2048
+ menuItem("add-below", "在下方插入段落", insertParagraphBelow)
2049
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2050
+ menuItem("table-row-plus", "在上方插入行", () => {
2051
+ chain().addRowBefore().run();
2052
+ setMenuOpen(false);
2053
+ }),
2054
+ menuItem("table-row-plus", "在下方插入行", () => {
2055
+ chain().addRowAfter().run();
2056
+ setMenuOpen(false);
2057
+ }),
2058
+ menuItem("table-col-plus", "在左侧插入列", () => {
2059
+ chain().addColumnBefore().run();
2060
+ setMenuOpen(false);
2061
+ }),
2062
+ menuItem("table-col-plus", "在右侧插入列", () => {
2063
+ chain().addColumnAfter().run();
2064
+ setMenuOpen(false);
2065
+ }),
2066
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2067
+ menuItem("table", "切换首行表头", () => {
2068
+ chain().toggleHeaderRow().run();
2069
+ setMenuOpen(false);
2070
+ }),
2071
+ menuItem("table", "切换首列表头", () => {
2072
+ chain().toggleHeaderColumn().run();
2073
+ setMenuOpen(false);
2074
+ }),
2075
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2076
+ canMerge && menuItem("merge", "合并单元格", () => {
2077
+ chain().mergeCells().run();
2078
+ setMenuOpen(false);
2079
+ }),
2080
+ canSplit && menuItem("split", "拆分单元格", () => {
2081
+ chain().splitCell().run();
2082
+ setMenuOpen(false);
2083
+ }),
2084
+ /* @__PURE__ */ jsx("div", { className: "ctx-label", children: "对齐" }),
2085
+ /* @__PURE__ */ jsxs("div", { className: "ctx-align", children: [
2086
+ CELL_ALIGNS.map((align) => /* @__PURE__ */ jsx(
2087
+ "button",
2088
+ {
2089
+ type: "button",
2090
+ className: `al-btn${currentAlign === align ? " on" : ""}`,
2091
+ title: align === "left" ? "左对齐" : align === "center" ? "居中" : "右对齐",
2092
+ onClick: () => {
2093
+ chain().setCellAttribute("align", align).run();
2094
+ setMenuOpen(false);
2095
+ },
2096
+ children: /* @__PURE__ */ jsx(WIcon, { name: `align-${align}`, size: 16 })
2097
+ },
2098
+ align
2099
+ )),
2100
+ /* @__PURE__ */ jsx(
2101
+ "button",
2102
+ {
2103
+ type: "button",
2104
+ className: `al-btn${currentAlign === null ? " on" : ""}`,
2105
+ title: "清除对齐",
2106
+ onClick: () => {
2107
+ chain().setCellAttribute("align", null).run();
2108
+ setMenuOpen(false);
2109
+ },
2110
+ children: "默认"
2111
+ }
2112
+ )
2113
+ ] }),
2114
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2115
+ !widthOpen ? menuItem("columns", "列宽…", () => setWidthOpen(true)) : /* @__PURE__ */ jsxs("div", { className: "ctx-width", children: [
2116
+ /* @__PURE__ */ jsx("span", { className: "ctx-label-inline", children: "列宽" }),
2117
+ /* @__PURE__ */ jsx(
2118
+ "input",
2119
+ {
2120
+ className: "ctx-num",
2121
+ type: "number",
2122
+ min: 40,
2123
+ max: 2e3,
2124
+ placeholder: "像素",
2125
+ value: width,
2126
+ "aria-label": "列宽像素",
2127
+ onChange: (event) => setWidth(event.target.value),
2128
+ onKeyDown: (event) => {
2129
+ if (event.key === "Enter") {
2130
+ event.preventDefault();
2131
+ applyWidth();
2132
+ }
2133
+ }
2134
+ }
2135
+ ),
2136
+ /* @__PURE__ */ jsx("span", { className: "faint small", children: "像素" }),
2137
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ctx-mini primary", onClick: applyWidth, children: "应用" })
2138
+ ] }),
2139
+ menuItem("columns", "列宽自适应", () => {
2140
+ var _a2;
2141
+ const col = (_a2 = currentCellRect(editor)) == null ? void 0 : _a2.col;
2142
+ if (col != null) {
2143
+ autoFitColumn(editor, col);
2144
+ setMenuOpen(false);
2145
+ }
2146
+ }),
2147
+ /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2148
+ menuItem("trash", "删除行", () => {
2149
+ chain().deleteRow().run();
2150
+ setMenuOpen(false);
2151
+ }),
2152
+ menuItem("trash", "删除列", () => {
2153
+ chain().deleteColumn().run();
2154
+ setMenuOpen(false);
2155
+ }),
2156
+ menuItem("table", "删除表格", () => {
2157
+ chain().deleteTable().run();
2158
+ setMenuOpen(false);
2159
+ }, { danger: true })
2160
+ ] })
2161
+ }
2162
+ )
2163
+ ] }), portal)
2164
+ ] });
2165
+ }
2166
+ function pickNearestGap(gaps, pointerY) {
2167
+ if (gaps.length === 0) return null;
2168
+ let best = gaps[0];
2169
+ let bestDist = Math.abs(gaps[0].y - pointerY);
2170
+ for (const g of gaps) {
2171
+ const d = Math.abs(g.y - pointerY);
2172
+ if (d < bestDist) {
2173
+ best = g;
2174
+ bestDist = d;
2175
+ }
2176
+ }
2177
+ return best;
2178
+ }
2179
+ function focusTrailingParagraph(state) {
2180
+ const tr = state.tr;
2181
+ const last = tr.doc.lastChild;
2182
+ if ((last == null ? void 0 : last.type.name) !== "paragraph" || last.content.size !== 0) {
2183
+ tr.insert(tr.doc.content.size, state.schema.nodes.paragraph.create());
2184
+ }
2185
+ return tr.setSelection(Selection.atEnd(tr.doc)).setStoredMarks(null);
2186
+ }
2187
+ const IMAGE_GESTURE_SLOP_PX = 6;
2188
+ const IMAGE_PREVIEW_LONG_PRESS_MS = 450;
2189
+ function isImagePreviewTap(movedDistance, elapsedMs) {
2190
+ if (!Number.isFinite(movedDistance) || !Number.isFinite(elapsedMs)) return false;
2191
+ if (movedDistance >= IMAGE_GESTURE_SLOP_PX) return false;
2192
+ return elapsedMs < IMAGE_PREVIEW_LONG_PRESS_MS;
2193
+ }
2194
+ function documentGestures(editor, shell, options) {
2195
+ const view = editor.view;
2196
+ let stop = null, suppressUntil = 0, picked = null;
2197
+ const writable = () => !editor.isDestroyed && editor.isEditable;
2198
+ function pickedImage(pos) {
2199
+ picked = pos;
2200
+ options.onImagePick(pos);
2201
+ }
2202
+ function cancel() {
2203
+ stop == null ? void 0 : stop();
2204
+ stop = null;
2205
+ }
2206
+ function consumeClick() {
2207
+ const consume = Date.now() < suppressUntil;
2208
+ suppressUntil = 0;
2209
+ return consume;
2210
+ }
2211
+ function gaps() {
2212
+ const result = [];
2213
+ view.state.doc.forEach((node, pos) => {
2214
+ const dom = view.nodeDOM(pos);
2215
+ if (!(dom instanceof HTMLElement)) return;
2216
+ const rect = dom.getBoundingClientRect();
2217
+ result.push({ pos, y: rect.top }, { pos: pos + node.nodeSize, y: rect.bottom });
2218
+ });
2219
+ return result;
2220
+ }
2221
+ function start(pos, event, onClick) {
2222
+ if (!writable() || event.button !== 0 || view.composing) return;
2223
+ cancel();
2224
+ let blocks = getSelectedBlocks(view);
2225
+ if (!blocks.some((block) => block.from === pos)) {
2226
+ const node = view.state.doc.nodeAt(pos);
2227
+ if (!node) return;
2228
+ setBlockRangeSelection(view, pos, pos);
2229
+ blocks = getSelectedBlocks(view);
2230
+ }
2231
+ if (!blocks.length) return;
2232
+ const doc = view.state.doc, from = blocks[0].from, to = blocks.at(-1).to;
2233
+ const partial = blocks.some((block) => {
2234
+ var _a;
2235
+ return (_a = block.images) == null ? void 0 : _a.length;
2236
+ });
2237
+ const originalSelection = view.state.selection;
2238
+ const origin = { x: event.clientX, y: event.clientY };
2239
+ let moved = false, pointer = origin, gap = null, raf = 0;
2240
+ const line = document.createElement("div");
2241
+ line.className = "wr-block-drop-line";
2242
+ line.setAttribute("aria-hidden", "true");
2243
+ const oldCursor = document.documentElement.style.cursor, oldSelect = document.body.style.userSelect;
2244
+ function paint() {
2245
+ if (!writable() || view.state.doc !== doc) {
2246
+ cancel();
2247
+ return;
2248
+ }
2249
+ gap = pickNearestGap(gaps(), pointer.y);
2250
+ const rect = view.dom.getBoundingClientRect();
2251
+ if (gap) Object.assign(line.style, { left: `${rect.left}px`, top: `${gap.y}px`, width: `${rect.width}px` });
2252
+ line.hidden = !gap || gap.pos >= from && gap.pos <= to;
2253
+ }
2254
+ const scroll = () => {
2255
+ if (!moved) return;
2256
+ let scroller = view.dom.parentElement;
2257
+ while (scroller && !(scroller.scrollHeight > scroller.clientHeight && /auto|scroll/.test(getComputedStyle(scroller).overflowY))) scroller = scroller.parentElement;
2258
+ const rect = (scroller == null ? void 0 : scroller.getBoundingClientRect()) ?? { top: 0, bottom: innerHeight };
2259
+ const amount = pointer.y < rect.top + 42 ? -14 : pointer.y > rect.bottom - 42 ? 14 : 0;
2260
+ if (amount) {
2261
+ if (scroller) scroller.scrollTop += amount;
2262
+ else window.scrollBy(0, amount);
2263
+ paint();
2264
+ }
2265
+ raf = requestAnimationFrame(scroll);
2266
+ };
2267
+ const move = (next) => {
2268
+ pointer = { x: next.clientX, y: next.clientY };
2269
+ if (!moved && Math.hypot(pointer.x - origin.x, pointer.y - origin.y) < 4) return;
2270
+ if (!moved) {
2271
+ moved = true;
2272
+ options.onStart();
2273
+ options.layer().append(line);
2274
+ document.documentElement.style.cursor = "grabbing";
2275
+ document.body.style.userSelect = "none";
2276
+ raf = requestAnimationFrame(scroll);
2277
+ }
2278
+ paint();
2279
+ next.preventDefault();
2280
+ };
2281
+ const up = (next) => {
2282
+ const target = gap, didMove = moved;
2283
+ cancel();
2284
+ if (!writable() || view.state.doc !== doc) return;
2285
+ if (!didMove) {
2286
+ if (onClick) onClick();
2287
+ else {
2288
+ const at2 = view.posAtCoords({ left: next.clientX, top: next.clientY });
2289
+ if (at2) view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at2.pos))));
2290
+ view.focus();
2291
+ }
2292
+ return;
2293
+ }
2294
+ suppressUntil = Date.now() + 400;
2295
+ if (!target || target.pos >= from && target.pos <= to) return;
2296
+ const selection = view.state.selection;
2297
+ if (partial && (!(selection instanceof BlockRangeSelection) || !selection.eq(originalSelection))) return;
2298
+ const slice = partial ? selection.content() : view.state.doc.slice(from, to);
2299
+ const tr = view.state.tr;
2300
+ if (partial) selection.replace(tr);
2301
+ else tr.delete(from, to);
2302
+ const at = tr.mapping.map(target.pos), steps = tr.steps.length;
2303
+ tr.insert(at, slice.content);
2304
+ if (tr.steps.length === steps) return;
2305
+ view.dispatch(tr.scrollIntoView());
2306
+ view.focus();
2307
+ };
2308
+ const key = (next) => {
2309
+ if (next.key === "Escape" && !next.isComposing) {
2310
+ next.preventDefault();
2311
+ next.stopImmediatePropagation();
2312
+ suppressUntil = Date.now() + 400;
2313
+ cancel();
2314
+ }
2315
+ };
2316
+ const change2 = () => {
2317
+ if (view.state.doc !== doc || !writable()) cancel();
2318
+ };
2319
+ stop = () => {
2320
+ window.removeEventListener("mousemove", move, true);
2321
+ window.removeEventListener("mouseup", up, true);
2322
+ window.removeEventListener("keydown", key, true);
2323
+ window.removeEventListener("blur", cancel);
2324
+ window.removeEventListener("pointercancel", cancel);
2325
+ editor.off("transaction", change2);
2326
+ cancelAnimationFrame(raf);
2327
+ line.remove();
2328
+ document.documentElement.style.cursor = oldCursor;
2329
+ document.body.style.userSelect = oldSelect;
2330
+ options.onEnd();
2331
+ };
2332
+ window.addEventListener("mousemove", move, true);
2333
+ window.addEventListener("mouseup", up, true);
2334
+ window.addEventListener("keydown", key, true);
2335
+ window.addEventListener("blur", cancel);
2336
+ window.addEventListener("pointercancel", cancel);
2337
+ editor.on("transaction", change2);
2338
+ event.preventDefault();
2339
+ }
2340
+ function pickImage(frame) {
2341
+ var _a;
2342
+ try {
2343
+ const pos = view.posAtDOM(frame, 0);
2344
+ if (((_a = view.state.doc.nodeAt(pos)) == null ? void 0 : _a.type.name) !== "image") return;
2345
+ pickedImage(pos);
2346
+ view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)));
2347
+ view.focus();
2348
+ } catch {
2349
+ }
2350
+ }
2351
+ function gallerySelect(gallery, event) {
2352
+ cancel();
2353
+ event.preventDefault();
2354
+ event.stopPropagation();
2355
+ const doc = view.state.doc, origin = { x: event.clientX, y: event.clientY };
2356
+ const frames = Array.from(gallery.children).filter((node) => node instanceof HTMLElement && node.classList.contains("ywiki-image-frame"));
2357
+ const box = document.createElement("div");
2358
+ box.className = "ywiki-gallery-marquee";
2359
+ box.hidden = true;
2360
+ options.layer().append(box);
2361
+ let moved = false;
2362
+ const oldSelect = document.body.style.userSelect;
2363
+ document.body.style.userSelect = "none";
2364
+ const move = (next) => {
2365
+ if (!writable() || view.state.doc !== doc) {
2366
+ cancel();
2367
+ return;
2368
+ }
2369
+ if (!moved && Math.hypot(next.clientX - origin.x, next.clientY - origin.y) < 6) return;
2370
+ moved = true;
2371
+ box.hidden = false;
2372
+ const left = Math.min(origin.x, next.clientX), top = Math.min(origin.y, next.clientY), right = Math.max(origin.x, next.clientX), bottom = Math.max(origin.y, next.clientY);
2373
+ Object.assign(box.style, { left: `${left}px`, top: `${top}px`, width: `${right - left}px`, height: `${bottom - top}px` });
2374
+ for (const frame of frames) {
2375
+ const r = frame.getBoundingClientRect();
2376
+ frame.classList.toggle("is-multi-picked", r.right > left && r.left < right && r.bottom > top && r.top < bottom);
2377
+ }
2378
+ next.preventDefault();
2379
+ };
2380
+ const up = () => {
2381
+ const selected = frames.filter((frame) => frame.classList.contains("is-multi-picked"));
2382
+ cancel();
2383
+ if (!writable() || view.state.doc !== doc) return;
2384
+ if (!moved) {
2385
+ const nearest = frames.sort((a2, b2) => Math.abs(origin.x - (a2.getBoundingClientRect().left + a2.getBoundingClientRect().right) / 2) - Math.abs(origin.x - (b2.getBoundingClientRect().left + b2.getBoundingClientRect().right) / 2))[0];
2386
+ if (nearest) pickImage(nearest);
2387
+ } else if (selected.length === 1) pickImage(selected[0]);
2388
+ else if (selected.length > 1) {
2389
+ const raw = view.posAtDOM(gallery, 0), $raw = view.state.doc.resolve(raw);
2390
+ const pos = $raw.depth ? $raw.before() : raw;
2391
+ const ranges = selected.map((frame) => {
2392
+ const from = view.posAtDOM(frame, 0);
2393
+ return { from, to: from + view.state.doc.nodeAt(from).nodeSize };
2394
+ });
2395
+ setImageRangeSelection(view, pos, ranges);
2396
+ view.focus();
2397
+ }
2398
+ };
2399
+ const key = (next) => {
2400
+ if (next.key === "Escape" && !next.isComposing) {
2401
+ next.preventDefault();
2402
+ cancel();
2403
+ }
2404
+ };
2405
+ stop = () => {
2406
+ window.removeEventListener("mousemove", move, true);
2407
+ window.removeEventListener("mouseup", up, true);
2408
+ window.removeEventListener("keydown", key, true);
2409
+ window.removeEventListener("blur", cancel);
2410
+ window.removeEventListener("pointercancel", cancel);
2411
+ box.remove();
2412
+ document.body.style.userSelect = oldSelect;
2413
+ for (const frame of frames) frame.classList.remove("is-multi-picked");
2414
+ };
2415
+ window.addEventListener("mousemove", move, true);
2416
+ window.addEventListener("mouseup", up, true);
2417
+ window.addEventListener("keydown", key, true);
2418
+ window.addEventListener("blur", cancel);
2419
+ window.addEventListener("pointercancel", cancel);
2420
+ }
2421
+ function down(event) {
2422
+ if (!writable() || view.composing || event.button !== 0 || !(event.target instanceof Element)) return;
2423
+ const target = event.target;
2424
+ if (target.closest("button,input,textarea,select,a,video,audio,.math-node")) return;
2425
+ const blocks = getSelectedBlocks(view);
2426
+ const gallery = target.closest(".ywiki-image-gallery");
2427
+ if (gallery && !target.closest(".ywiki-image-frame") && !gallery.matches(".is-block-selected,.is-block-image-selected")) {
2428
+ gallerySelect(gallery, event);
2429
+ return;
2430
+ }
2431
+ if (event.detail < 2 && blocks.length) {
2432
+ let block = target.closest(".ProseMirror > *");
2433
+ if ((block == null ? void 0 : block.parentElement) !== view.dom) block = null;
2434
+ if (block) {
2435
+ let pos = -1;
2436
+ view.state.doc.forEach((_node, offset) => {
2437
+ if (view.nodeDOM(offset) === block) pos = offset;
2438
+ });
2439
+ const frame2 = target.closest(".ywiki-image-frame");
2440
+ const hit = blocks.find((item) => {
2441
+ var _a;
2442
+ return item.from === pos && (!((_a = item.images) == null ? void 0 : _a.length) || frame2 && item.images.some((range) => view.posAtDOM(frame2, 0) === range.from));
2443
+ });
2444
+ if (hit) {
2445
+ start(hit.from, event);
2446
+ event.stopPropagation();
2447
+ return;
2448
+ }
2449
+ }
2450
+ }
2451
+ const image = target.closest("img.ywiki-image"), frame = image == null ? void 0 : image.closest(".ywiki-image-frame");
2452
+ if (image && frame) {
2453
+ const pos = view.posAtDOM(frame, 0), previous = picked;
2454
+ pickedImage(pos);
2455
+ {
2456
+ cancel();
2457
+ const origin = { x: event.clientX, y: event.clientY, time: Date.now() };
2458
+ let distance = 0;
2459
+ const move = (next) => {
2460
+ distance = Math.max(distance, Math.hypot(next.clientX - origin.x, next.clientY - origin.y));
2461
+ };
2462
+ const up = () => {
2463
+ cancel();
2464
+ if (distance >= 6) {
2465
+ pickedImage(null);
2466
+ return;
2467
+ }
2468
+ if (previous === pos && writable() && image.isConnected && isImagePreviewTap(distance, Date.now() - origin.time)) options.onPreview(image);
2469
+ };
2470
+ stop = () => {
2471
+ window.removeEventListener("mousemove", move, true);
2472
+ window.removeEventListener("mouseup", up, true);
2473
+ window.removeEventListener("blur", cancel);
2474
+ };
2475
+ window.addEventListener("mousemove", move, true);
2476
+ window.addEventListener("mouseup", up, true);
2477
+ window.addEventListener("blur", cancel);
2478
+ }
2479
+ } else pickedImage(null);
2480
+ if (target === view.dom) {
2481
+ const last = view.state.doc.lastChild;
2482
+ const dom = last ? view.nodeDOM(view.state.doc.content.size - last.nodeSize) : null;
2483
+ if (dom instanceof HTMLElement && event.clientY >= dom.getBoundingClientRect().bottom - 4) {
2484
+ event.preventDefault();
2485
+ view.dispatch(focusTrailingParagraph(view.state));
2486
+ view.focus();
2487
+ }
2488
+ }
2489
+ }
2490
+ const nativeDrag = (event) => {
2491
+ if (stop && view.state.selection instanceof BlockRangeSelection) event.preventDefault();
2492
+ };
2493
+ const outside = (event) => {
2494
+ if (!(event.target instanceof Element) || shell.contains(event.target) || event.target.closest(".img-toolbar,.img-preview-mask,.w-popover")) return;
2495
+ pickedImage(null);
2496
+ };
2497
+ const change = ({ transaction }) => {
2498
+ if (transaction.docChanged || view.state.selection instanceof NodeSelection && view.state.selection.from !== picked) pickedImage(null);
2499
+ };
2500
+ editor.on("transaction", change);
2501
+ document.addEventListener("mousedown", outside, true);
2502
+ view.dom.addEventListener("mousedown", down, true);
2503
+ view.dom.addEventListener("dragstart", nativeDrag, true);
2504
+ return { start, consumeClick, cancel, destroy() {
2505
+ cancel();
2506
+ view.dom.removeEventListener("mousedown", down, true);
2507
+ view.dom.removeEventListener("dragstart", nativeDrag, true);
2508
+ editor.off("transaction", change);
2509
+ document.removeEventListener("mousedown", outside, true);
2510
+ } };
2511
+ }
2512
+ function advanceTarget(target, tr) {
2513
+ if (!target.valid || !tr.docChanged) return;
2514
+ const point = target.from === target.to;
2515
+ const from = tr.mapping.mapResult(target.from, 1);
2516
+ const to = tr.mapping.mapResult(target.to, -1);
2517
+ if (from.deleted || target.to > target.from && to.deleted) {
2518
+ if (target.nodeJSON && tr.getMeta("y-sync$")) {
2519
+ const matches = [];
2520
+ tr.doc.descendants((node, pos) => {
2521
+ if (JSON.stringify(node.toJSON()) === target.nodeJSON) matches.push({ pos, node });
2522
+ });
2523
+ if (matches.length === 1) {
2524
+ target.from = matches[0].pos;
2525
+ target.to = target.from + matches[0].node.nodeSize;
2526
+ return;
2527
+ }
2528
+ }
2529
+ target.valid = false;
2530
+ return;
2531
+ }
2532
+ target.from = from.pos;
2533
+ target.to = point ? from.pos : Math.max(from.pos, to.pos);
2534
+ }
2535
+ function isRemoteDocumentChange(transaction) {
2536
+ const meta = transaction.getMeta(ySyncPluginKey);
2537
+ return !!(meta == null ? void 0 : meta.isChangeOrigin) && !meta.isUndoRedoOperation;
2538
+ }
2539
+ function pageCandidates(initial, remote, query, excludeId) {
2540
+ const pages = [...new Map([...initial, ...remote].map((page) => [page.id, page])).values()].filter((page) => page.id !== excludeId);
2541
+ return query.trim() ? rankPages(pages, query.trim()).map((result) => result.page) : pages.sort((a2, b2) => (b2.updatedAt ?? "").localeCompare(a2.updatedAt ?? ""));
2542
+ }
2543
+ function PagePicker({ spaceId, excludeId, title = "选择页面", onClose, onPick }) {
2544
+ const { host, portal } = useContentContext(), root = useRef(null), input = useRef(null);
2545
+ const [initial, setInitial] = useState([]), [remote, setRemote] = useState([]);
2546
+ const [query, setQuery] = useState(""), [error, setError] = useState(""), [loading, setLoading] = useState(true);
2547
+ const [attempt, setAttempt] = useState(0), [active, setActive] = useState(0), id = useId();
2548
+ const close = useRef(onClose);
2549
+ close.current = onClose;
2550
+ useEffect(() => {
2551
+ var _a;
2552
+ let alive = true;
2553
+ setLoading(true);
2554
+ setError("");
2555
+ void (((_a = host.references) == null ? void 0 : _a.search("", { spaceId, excludeId })) ?? Promise.resolve([])).then((items) => {
2556
+ if (alive) setInitial(items);
2557
+ }).catch((error2) => {
2558
+ if (alive) setError(error2 instanceof Error ? error2.message : "页面加载失败");
2559
+ }).finally(() => {
2560
+ if (alive) setLoading(false);
2561
+ });
2562
+ return () => {
2563
+ alive = false;
2564
+ };
2565
+ }, [host.references, spaceId, excludeId, attempt]);
2566
+ useEffect(() => {
2567
+ let alive = true;
2568
+ setRemote([]);
2569
+ if (!query.trim()) return;
2570
+ const timer = setTimeout(() => {
2571
+ var _a;
2572
+ void ((_a = host.references) == null ? void 0 : _a.search(query.trim(), { spaceId, excludeId }).then((items) => {
2573
+ if (alive) setRemote(items);
2574
+ }).catch(() => {
2575
+ }));
2576
+ }, 180);
2577
+ return () => {
2578
+ alive = false;
2579
+ clearTimeout(timer);
2580
+ };
2581
+ }, [query, host.references, spaceId, excludeId]);
2582
+ useEffect(() => {
2583
+ var _a;
2584
+ if (!portal) return;
2585
+ const previous = document.activeElement, scroll = lockScroll();
2586
+ (_a = input.current) == null ? void 0 : _a.focus();
2587
+ function keydown(event) {
2588
+ if (event.isComposing || event.keyCode === 229) return;
2589
+ if (event.key === "Escape") {
2590
+ event.preventDefault();
2591
+ event.stopImmediatePropagation();
2592
+ close.current();
2593
+ } else if (root.current && trapTab(root.current, event)) event.preventDefault();
2594
+ }
2595
+ document.addEventListener("keydown", keydown, true);
2596
+ return () => {
2597
+ scroll.release();
2598
+ document.removeEventListener("keydown", keydown, true);
2599
+ if (previous == null ? void 0 : previous.isConnected) previous.focus();
2600
+ };
2601
+ }, [portal]);
2602
+ const candidates = useMemo(() => pageCandidates(initial, remote, query, excludeId).slice(0, 20), [initial, remote, query, excludeId]);
2603
+ useEffect(() => {
2604
+ setActive(0);
2605
+ }, [candidates]);
2606
+ useEffect(() => {
2607
+ var _a, _b;
2608
+ (_b = (_a = root.current) == null ? void 0 : _a.querySelector(`[id="${id}-${active}"]`)) == null ? void 0 : _b.scrollIntoView({ block: "nearest" });
2609
+ }, [active, id]);
2610
+ const visible = !!candidates.length && !loading && !error;
2611
+ function choose(candidate) {
2612
+ if (!loading && !error) onPick(candidate.id, candidate.title);
2613
+ }
2614
+ if (!portal) return null;
2615
+ return createPortal(/* @__PURE__ */ jsx("div", { className: "picker-backdrop", onMouseDown: (event) => {
2616
+ if (event.target === event.currentTarget) onClose();
2617
+ }, children: /* @__PURE__ */ jsxs("div", { ref: root, className: "picker", role: "dialog", "aria-modal": "true", "aria-label": title, children: [
2618
+ /* @__PURE__ */ jsx(
2619
+ "input",
2620
+ {
2621
+ ref: input,
2622
+ className: "picker-input",
2623
+ type: "text",
2624
+ role: "combobox",
2625
+ "aria-label": title,
2626
+ "aria-autocomplete": "list",
2627
+ "aria-controls": visible ? id : void 0,
2628
+ "aria-expanded": !!visible,
2629
+ "aria-activedescendant": visible ? `${id}-${active}` : void 0,
2630
+ placeholder: "搜索页面标题(支持拼音)…",
2631
+ value: query,
2632
+ onChange: (event) => {
2633
+ setQuery(event.target.value);
2634
+ setActive(0);
2635
+ },
2636
+ onKeyDown: (event) => {
2637
+ if (event.nativeEvent.isComposing || event.keyCode === 229 || !visible) return;
2638
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
2639
+ event.preventDefault();
2640
+ setActive((value) => (value + (event.key === "ArrowDown" ? 1 : -1) + candidates.length) % candidates.length);
2641
+ } else if (event.key === "Enter") {
2642
+ event.preventDefault();
2643
+ const candidate = candidates[active];
2644
+ if (candidate) choose(candidate);
2645
+ }
2646
+ }
2647
+ }
2648
+ ),
2649
+ error ? /* @__PURE__ */ jsxs("p", { role: "alert", children: [
2650
+ error,
2651
+ " ",
2652
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setAttempt((value) => value + 1), children: "重试" })
2653
+ ] }) : loading ? /* @__PURE__ */ jsx("p", { role: "status", children: "加载中…" }) : candidates.length ? /* @__PURE__ */ jsx("ul", { id, className: "picker-list", role: "listbox", "aria-label": "页面候选", children: candidates.map((candidate, index) => /* @__PURE__ */ jsx(
2654
+ "li",
2655
+ {
2656
+ id: `${id}-${index}`,
2657
+ role: "option",
2658
+ "aria-selected": index === active,
2659
+ className: index === active ? "active" : void 0,
2660
+ onMouseEnter: () => setActive(index),
2661
+ onMouseDown: (event) => {
2662
+ event.preventDefault();
2663
+ choose(candidate);
2664
+ },
2665
+ children: /* @__PURE__ */ jsx("span", { className: "picker-title", children: candidate.title })
2666
+ },
2667
+ candidate.id
2668
+ )) }) : /* @__PURE__ */ jsx("p", { className: "picker-empty", children: "没有匹配的页面" })
2669
+ ] }) }), portal);
2670
+ }
2671
+ function videoProblem(value, attempted = false) {
2672
+ const inspection = inspectVideoUrl(value);
2673
+ if (inspection.data) return "";
2674
+ if (inspection.issue === "empty") return attempted ? "请粘贴视频链接。" : "";
2675
+ if (inspection.issue === "short-link") return "B站短链(b23.tv)不能在正文里直接播放,请先在浏览器打开,再复制地址栏里的完整链接。";
2676
+ if (inspection.issue === "missing-id") return "链接里没有识别到视频 ID,请确认复制的是单个视频的播放页,而不是首页或频道页。";
2677
+ if (inspection.issue === "unsupported") return `暂不支持该平台的视频链接。目前支持 ${Object.values(VIDEO_PROVIDER_LABELS).join("、")}。`;
2678
+ return "链接格式不正确,请粘贴以 http:// 或 https:// 开头的完整地址。";
2679
+ }
2680
+ function VideoEmbedDialog({ initialUrl = "", onClose, onInsert }) {
2681
+ const [value, setValue] = useState(initialUrl), [attempted, setAttempted] = useState(false), id = useId();
2682
+ const input = useRef(null);
2683
+ useEffect(() => {
2684
+ var _a;
2685
+ (_a = input.current) == null ? void 0 : _a.focus();
2686
+ }, []);
2687
+ const data = inspectVideoUrl(value).data, problem = videoProblem(value, attempted);
2688
+ function submit() {
2689
+ setAttempted(true);
2690
+ if (data) onInsert(data);
2691
+ }
2692
+ return /* @__PURE__ */ jsx(WDialog, { workspace: true, title: "嵌入视频", onClose, className: "video-dialog", footer: /* @__PURE__ */ jsxs("div", { className: "wr-toolbar", children: [
2693
+ /* @__PURE__ */ jsx(WButton, { onClick: onClose, children: "取消" }),
2694
+ /* @__PURE__ */ jsx(WButton, { variant: "primary", disabled: !data, onClick: submit, children: "插入视频" })
2695
+ ] }), children: /* @__PURE__ */ jsxs("form", { className: "video-form", onSubmit: (event) => {
2696
+ event.preventDefault();
2697
+ submit();
2698
+ }, children: [
2699
+ /* @__PURE__ */ jsxs("div", { className: "video-field", children: [
2700
+ /* @__PURE__ */ jsx("label", { htmlFor: id, children: "视频链接" }),
2701
+ /* @__PURE__ */ jsx(
2702
+ "input",
2703
+ {
2704
+ ref: input,
2705
+ id,
2706
+ type: "url",
2707
+ maxLength: 2048,
2708
+ placeholder: "粘贴视频播放页链接",
2709
+ autoComplete: "off",
2710
+ spellCheck: false,
2711
+ value,
2712
+ onChange: (event) => setValue(event.target.value)
2713
+ }
2714
+ ),
2715
+ /* @__PURE__ */ jsxs("p", { className: "video-hint", children: [
2716
+ "支持 ",
2717
+ Object.values(VIDEO_PROVIDER_LABELS).join("、"),
2718
+ " 的视频页与官方嵌入链接,番剧、剧集和多 P 视频可直接粘贴。"
2719
+ ] })
2720
+ ] }),
2721
+ data ? /* @__PURE__ */ jsxs("p", { className: "video-status video-status--ok", role: "status", children: [
2722
+ "已识别 ",
2723
+ VIDEO_PROVIDER_LABELS[data.provider],
2724
+ " 视频",
2725
+ /* @__PURE__ */ jsx("span", { className: "video-status-meta", children: data.videoId }),
2726
+ data.page > 1 ? ` · 第 ${data.page} P` : "",
2727
+ data.startSeconds ? ` · 从 ${data.startSeconds} 秒开始` : ""
2728
+ ] }) : problem ? /* @__PURE__ */ jsx("p", { className: "video-status video-status--error", role: "alert", children: problem }) : null,
2729
+ data && /* @__PURE__ */ jsx("p", { className: "video-source", children: videoSourceUrl(data) })
2730
+ ] }) });
2731
+ }
2732
+ const empty = { type: "doc", content: [{ type: "paragraph" }] };
2733
+ const DocumentEditor = forwardRef(function DocumentEditor2(props, ref) {
2734
+ const mount = useRef(null), shell = useRef(null), editor = useRef(null);
2735
+ const context = useContentContext(), host = context.host, latest = useLatest(props), hostRef = useLatest(host), emitted = useRef(null);
2736
+ const contextRef = useLatest(context);
2737
+ const [epoch, update] = useState(0), [notice, setNotice] = useState(""), [error, setError] = useState("");
2738
+ const [slash, setSlash] = useState(null);
2739
+ const [hover, setHover] = useState(null), [frozen, setFrozen] = useState(null), [blockMenu, setBlockMenu] = useState(false);
2740
+ const [panelOnly, setPanelOnly] = useState(void 0);
2741
+ const [tableHover, setTableHover] = useState(null);
2742
+ const [link, setLink] = useState(null);
2743
+ const linkRef = useLatest(link);
2744
+ const [video, setVideo] = useState(false), [videoUrl, setVideoUrl] = useState(""), [references, setReferences] = useState(false);
2745
+ const [atoms, setAtoms] = useState([]), disposed = useRef(false), pending = useRef(0), fileInput = useRef(null), fileKind = useRef("image");
2746
+ const [preview, setPreview] = useState(null);
2747
+ const hoverTimer = useRef(null), lastHover = useRef(null);
2748
+ const gestures = useRef(null);
2749
+ const pendingInsert = useRef(null);
2750
+ const remoteTargets = useRef(/* @__PURE__ */ new Set());
2751
+ const [dragging, setDragging] = useState(false);
2752
+ const [imagePick, setImagePick] = useState(null);
2753
+ const menuOpenRef = useLatest(blockMenu);
2754
+ const mindViewState = useRef({ openId: null, maps: {} });
2755
+ const ready = useReady();
2756
+ useEffect(() => () => {
2757
+ if (hoverTimer.current) clearTimeout(hoverTimer.current);
2758
+ }, []);
2759
+ useEffect(() => {
2760
+ const input = fileInput.current, cancel = () => {
2761
+ pendingInsert.current = null;
2762
+ };
2763
+ input == null ? void 0 : input.addEventListener("cancel", cancel);
2764
+ return () => input == null ? void 0 : input.removeEventListener("cancel", cancel);
2765
+ }, []);
2766
+ const fail = useCallback((error2) => {
2767
+ var _a, _b;
2768
+ setError(asError(error2).message);
2769
+ (_b = (_a = latest.current).onError) == null ? void 0 : _b.call(_a, asError(error2));
2770
+ }, [latest]);
2771
+ const tell = useCallback((message) => {
2772
+ var _a, _b;
2773
+ setNotice(message);
2774
+ (_b = (_a = latest.current).onNotice) == null ? void 0 : _b.call(_a, message);
2775
+ }, [latest]);
2776
+ function onBlockHover(info) {
2777
+ if (hoverTimer.current) {
2778
+ clearTimeout(hoverTimer.current);
2779
+ hoverTimer.current = null;
2780
+ }
2781
+ if (info) {
2782
+ lastHover.current = info;
2783
+ setHover(info);
2784
+ return;
2785
+ }
2786
+ hoverTimer.current = setTimeout(() => {
2787
+ hoverTimer.current = null;
2788
+ setHover(null);
2789
+ if (!menuOpenRef.current) setFrozen(null);
2790
+ }, 220);
2791
+ }
2792
+ useEffect(() => {
2793
+ var _a, _b, _c;
2794
+ const target = mount.current;
2795
+ if (!target) return;
2796
+ disposed.current = false;
2797
+ let alive = true;
2798
+ const container = document.createElement("div");
2799
+ target.append(container);
2800
+ let ed2;
2801
+ const atom = ({ node, editor: owner, getPos }) => {
2802
+ const dom = document.createElement("div");
2803
+ dom.contentEditable = "false";
2804
+ dom.dataset.blockChrome = "";
2805
+ const id = crypto.randomUUID();
2806
+ let current = node;
2807
+ if (node.type.name === "mindMap") dom.dataset.mindMapNode = "";
2808
+ const controller = node.type.name === "mindMap" ? createMindMapController(owner, () => {
2809
+ const pos = getPos();
2810
+ return typeof pos === "number" ? pos : void 0;
2811
+ }, {
2812
+ get pageId() {
2813
+ return latest.current.pageId;
2814
+ },
2815
+ canComment: () => !!latest.current.canComment,
2816
+ canExport: () => !!latest.current.allowExport,
2817
+ status: () => latest.current.saveStatus ?? "",
2818
+ fetchAttachment: (id2) => {
2819
+ var _a2;
2820
+ return (((_a2 = hostRef.current.attachments) == null ? void 0 : _a2.fetch) ?? unavailableAttachment)(id2);
2821
+ },
2822
+ get uploadImage() {
2823
+ var _a2;
2824
+ return latest.current.uploadImage ?? ((_a2 = hostRef.current.attachments) == null ? void 0 : _a2.uploadImage);
2825
+ },
2826
+ viewState: latest.current.mindMapViewState ?? mindViewState.current,
2827
+ awareness: latest.current.collabAwareness
2828
+ }) : void 0;
2829
+ const render = () => {
2830
+ let content;
2831
+ if (current.type.name === "mindMap") {
2832
+ content = /* @__PURE__ */ jsx(
2833
+ MindMapBlock,
2834
+ {
2835
+ node: current.toJSON(),
2836
+ controller,
2837
+ pageId: latest.current.pageId,
2838
+ canComment: latest.current.canComment,
2839
+ onCommentsChanged: latest.current.onCommentsChanged
2840
+ }
2841
+ );
2842
+ } else content = /* @__PURE__ */ jsx(VideoView, { attrs: current.attrs });
2843
+ setAtoms((previous) => [...previous.filter((item) => item.id !== id), { id, dom, content }]);
2844
+ };
2845
+ queueMicrotask(() => {
2846
+ if (alive) render();
2847
+ });
2848
+ return {
2849
+ dom,
2850
+ update(next) {
2851
+ if (next.type !== current.type || current.type.name === "mindMap" && next.attrs.id !== current.attrs.id) return false;
2852
+ current = next;
2853
+ if (alive) render();
2854
+ return true;
2855
+ },
2856
+ stopEvent: () => true,
2857
+ ignoreMutation: () => true,
2858
+ destroy() {
2859
+ controller == null ? void 0 : controller.dispose();
2860
+ if (alive) queueMicrotask(() => {
2861
+ if (alive) setAtoms((previous) => previous.filter((item) => item.id !== id));
2862
+ });
2863
+ }
2864
+ };
2865
+ };
2866
+ const menuCoords = (from) => {
2867
+ var _a2;
2868
+ try {
2869
+ const coords = ed2.view.coordsAtPos(from), rect = ((_a2 = shell.current) == null ? void 0 : _a2.getBoundingClientRect()) ?? { left: 0, top: 0 };
2870
+ return { x: coords.left - rect.left, y: coords.bottom - rect.top + 6 };
2871
+ } catch {
2872
+ return { x: 0, y: 0 };
2873
+ }
2874
+ };
2875
+ try {
2876
+ const initial = latest.current.value ?? latest.current.defaultValue ?? empty;
2877
+ new ContentSchemaService().validate(initial, latest.current.schemaVersion ?? requiredSchemaVersion(initial, SCHEMA_VERSION));
2878
+ ed2 = new Editor({
2879
+ element: container,
2880
+ editable: !latest.current.disabled,
2881
+ content: latest.current.collabDoc ? void 0 : initial,
2882
+ extensions: coreExtensions({
2883
+ placeholder: latest.current.placeholder ?? "输入 / 唤起命令,开始记录…",
2884
+ collabDoc: latest.current.collabDoc,
2885
+ collabAwareness: latest.current.collabAwareness,
2886
+ canCopyCode: () => !!latest.current.allowExport,
2887
+ contentContext: () => contextRef.current,
2888
+ attachmentFetcher: (_a = hostRef.current.attachments) == null ? void 0 : _a.fetch,
2889
+ attachmentScope: hostRef.current.scope,
2890
+ inlineComments: { getComments: () => latest.current.inlineComments ?? [], getActiveId: () => latest.current.activeCommentId ?? null, onMarkClick: (id) => {
2891
+ var _a2, _b2;
2892
+ return (_b2 = (_a2 = latest.current).onInlineMarkClick) == null ? void 0 : _b2.call(_a2, id);
2893
+ } },
2894
+ slash: {
2895
+ onOpen: (state) => {
2896
+ setSlash({ ...state, ...menuCoords(state.from) });
2897
+ setBlockMenu(false);
2898
+ setHover(null);
2899
+ },
2900
+ onUpdate: (query) => setSlash((previous) => previous ? { ...previous, query } : null),
2901
+ onClose: () => setSlash(null)
2902
+ },
2903
+ blockHandle: { onHover: onBlockHover },
2904
+ uploadPlaceholder: { onLost: () => tell("上传目标已移除,附件未插入。") },
2905
+ tableInteraction: { onHover: (value) => setTableHover(value) },
2906
+ mindMapRenderer: atom,
2907
+ videoRenderer: atom
2908
+ }),
2909
+ editorProps: {
2910
+ attributes: { role: "textbox", "aria-label": "文档正文", "aria-multiline": "true", class: "editor-canvas ywiki-prose ywiki-content" },
2911
+ handleKeyDown: (_view, event) => {
2912
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
2913
+ event.preventDefault();
2914
+ openLink(ed2);
2915
+ return true;
2916
+ }
2917
+ return false;
2918
+ },
2919
+ handlePaste: (view, event) => {
2920
+ var _a2, _b2;
2921
+ if (!view.editable) return false;
2922
+ const files = pastedImageFiles(event);
2923
+ if (files.length) {
2924
+ void upload(files);
2925
+ return true;
2926
+ }
2927
+ const text = ((_b2 = (_a2 = event.clipboardData) == null ? void 0 : _a2.getData("text/plain")) == null ? void 0 : _b2.trim()) ?? "";
2928
+ if (view.state.selection.empty && !view.state.selection.$from.parent.content.size && parseVideoUrl(text)) {
2929
+ const from = view.state.selection.from;
2930
+ const tr = view.state.tr.insertText(text);
2931
+ tr.addMark(from, from + text.length, view.state.schema.marks.link.create({ href: text }));
2932
+ view.dispatch(tr);
2933
+ pendingInsert.current = { from, to: from + text.length, valid: true };
2934
+ setVideoUrl(text);
2935
+ setVideo(true);
2936
+ return true;
2937
+ }
2938
+ if (pastedRemoteImageUrls(event).length) setTimeout(() => {
2939
+ if (alive) void upgradeRemoteImages(ed2);
2940
+ }, 0);
2941
+ return false;
2942
+ },
2943
+ handleDrop: (view, event) => {
2944
+ var _a2;
2945
+ if (!view.editable || !((_a2 = event.dataTransfer) == null ? void 0 : _a2.files.length)) return false;
2946
+ const at = view.posAtCoords({ left: event.clientX, top: event.clientY });
2947
+ if (!at) return false;
2948
+ event.preventDefault();
2949
+ void upload([...event.dataTransfer.files], at.pos);
2950
+ return true;
2951
+ },
2952
+ handleDOMEvents: {
2953
+ contextmenu: (view, event) => {
2954
+ var _a2;
2955
+ if (!view.editable || !(event.target instanceof Element)) return false;
2956
+ const cell = event.target.closest("td,th");
2957
+ if (cell) return false;
2958
+ const block = event.target.closest(".ProseMirror > *"), hr = (_a2 = shell.current) == null ? void 0 : _a2.getBoundingClientRect();
2959
+ if (!block || block.parentElement !== view.dom || !hr) return false;
2960
+ let pos = -1;
2961
+ view.state.doc.forEach((_node, offset) => {
2962
+ if (view.nodeDOM(offset) === block) pos = offset;
2963
+ });
2964
+ const node = view.state.doc.nodeAt(pos);
2965
+ if (!node) return false;
2966
+ event.preventDefault();
2967
+ setLink(null);
2968
+ setSlash(null);
2969
+ setFrozen({ pos, type: node.type.name, top: event.clientY - hr.top, contentLeft: event.clientX - hr.left, isEmpty: node.isTextblock && !node.content.size });
2970
+ setPanelOnly(void 0);
2971
+ setBlockMenu(true);
2972
+ return true;
2973
+ },
2974
+ mouseover: (view, event) => {
2975
+ var _a2;
2976
+ if (linkRef.current || !view.editable) return false;
2977
+ const anchor = (_a2 = event.target) == null ? void 0 : _a2.closest("a[href]");
2978
+ if (!anchor || !view.dom.contains(anchor)) return false;
2979
+ const pos = view.posAtDOM(anchor, 0), range = getMarkRange(view.state.doc.resolve(pos), view.state.schema.marks.link);
2980
+ if (!range) return false;
2981
+ const rect = anchor.getBoundingClientRect();
2982
+ const tracked = createLinkEditTarget(view.state, TextSelection.create(view.state.doc, range.from, range.to));
2983
+ if (tracked) setLink({ ...tracked, tracked, mode: "preview", anchor: { left: rect.left, top: rect.top, width: rect.width, height: rect.height } });
2984
+ return false;
2985
+ }
2986
+ }
2987
+ },
2988
+ onUpdate: ({ editor: editor2, transaction }) => {
2989
+ var _a2, _b2;
2990
+ emitted.current = editor2.getJSON();
2991
+ update((value) => value + 1);
2992
+ (_b2 = (_a2 = latest.current).onChange) == null ? void 0 : _b2.call(_a2, emitted.current, { remote: isRemoteDocumentChange(transaction) });
2993
+ },
2994
+ onSelectionUpdate: () => update((value) => value + 1),
2995
+ onTransaction: ({ transaction }) => {
2996
+ if (pendingInsert.current) advanceTarget(pendingInsert.current, transaction);
2997
+ for (const target3 of remoteTargets.current) advanceTarget(target3, transaction);
2998
+ const target2 = linkRef.current;
2999
+ if (target2 == null ? void 0 : target2.tracked) {
3000
+ advanceTarget(target2.tracked, transaction);
3001
+ target2.from = target2.tracked.from;
3002
+ target2.to = target2.tracked.to;
3003
+ }
3004
+ update((value) => value + 1);
3005
+ },
3006
+ onBlur: () => {
3007
+ var _a2, _b2;
3008
+ return (_b2 = (_a2 = latest.current).onBlur) == null ? void 0 : _b2.call(_a2);
3009
+ }
3010
+ });
3011
+ editor.current = ed2;
3012
+ if (shell.current) {
3013
+ gestures.current = documentGestures(ed2, shell.current, {
3014
+ layer: () => contextRef.current.portal ?? shell.current ?? document.body,
3015
+ onStart: () => {
3016
+ setDragging(true);
3017
+ setBlockMenu(false);
3018
+ dismissSlash(ed2.view);
3019
+ setSlash(null);
3020
+ setLink(null);
3021
+ },
3022
+ onEnd: () => {
3023
+ setDragging(false);
3024
+ update((value) => value + 1);
3025
+ },
3026
+ onImagePick: setImagePick,
3027
+ onPreview: (image) => {
3028
+ const source = image.getAttribute("src") ?? "";
3029
+ if (!source) return;
3030
+ const items = previewItemsFrom(image, "图片预览");
3031
+ setPreview({ src: source, label: image.alt || "图片预览", items: items.items.length ? items.items : [{ src: source, label: image.alt || "图片预览" }], index: items.items.length ? items.index : 0 });
3032
+ }
3033
+ });
3034
+ }
3035
+ ready.markReady();
3036
+ (_c = (_b = latest.current).onReady) == null ? void 0 : _c.call(_b);
3037
+ } catch (error2) {
3038
+ fail(error2);
3039
+ }
3040
+ return () => {
3041
+ var _a2;
3042
+ alive = false;
3043
+ disposed.current = true;
3044
+ (_a2 = gestures.current) == null ? void 0 : _a2.destroy();
3045
+ gestures.current = null;
3046
+ pendingInsert.current = null;
3047
+ editor.current = null;
3048
+ ed2 == null ? void 0 : ed2.destroy();
3049
+ container.remove();
3050
+ setAtoms([]);
3051
+ };
3052
+ }, [props.collabDoc, props.collabAwareness]);
3053
+ useEffect(() => {
3054
+ const ed2 = editor.current;
3055
+ if (ed2 && !ed2.isDestroyed) ed2.view.dispatch(ed2.state.tr.setMeta("content-context", true));
3056
+ }, [context.portal, context.theme, context.accent, host.preferences, props.allowExport]);
3057
+ useEffect(() => {
3058
+ var _a;
3059
+ const ed2 = editor.current;
3060
+ if (ed2) {
3061
+ ed2.setEditable(!props.disabled, false);
3062
+ ed2.view.dispatch(ed2.state.tr.setMeta("content-capabilities", true));
3063
+ }
3064
+ if (props.disabled) {
3065
+ (_a = gestures.current) == null ? void 0 : _a.cancel();
3066
+ closeBlockMenu();
3067
+ setLink(null);
3068
+ setSlash(null);
3069
+ setHover(null);
3070
+ setFrozen(null);
3071
+ pendingInsert.current = null;
3072
+ setReferences(false);
3073
+ setVideo(false);
3074
+ setTableHover(null);
3075
+ }
3076
+ }, [props.disabled]);
3077
+ useEffect(() => {
3078
+ const ed2 = editor.current;
3079
+ if (!ed2 || !props.value || props.collabDoc || props.value === emitted.current) return;
3080
+ try {
3081
+ new ContentSchemaService().validate(props.value, props.schemaVersion ?? requiredSchemaVersion(props.value, SCHEMA_VERSION));
3082
+ const parsed = ed2.schema.nodeFromJSON(props.value);
3083
+ if (!parsed.eq(ed2.state.doc)) ed2.commands.setContent(props.value, { emitUpdate: false });
3084
+ setError("");
3085
+ } catch (error2) {
3086
+ fail(error2);
3087
+ }
3088
+ }, [props.value, props.collabDoc, fail]);
3089
+ useEffect(() => {
3090
+ if (editor.current) refreshInlineComments(editor.current, props.inlineComments ?? []);
3091
+ }, [props.inlineComments, props.activeCommentId]);
3092
+ const snapshot = () => {
3093
+ var _a;
3094
+ return ((_a = editor.current) == null ? void 0 : _a.getJSON()) ?? latest.current.value ?? latest.current.defaultValue ?? empty;
3095
+ };
3096
+ function hasPendingTasks() {
3097
+ var _a;
3098
+ const ed2 = editor.current;
3099
+ return !!ed2 && (pending.current > 0 || remoteTargets.current.size > 0 || !!pendingInsert.current || references || video || ed2.view.composing || !!((_a = ed2.storage.mindMap) == null ? void 0 : _a.composing) || !!ed2.view.dom.querySelector(".math-latex-input"));
3100
+ }
3101
+ function selectionRect() {
3102
+ const ed2 = editor.current;
3103
+ if (!ed2 || ed2.state.selection.empty) return null;
3104
+ try {
3105
+ const rect = ed2.view.coordsAtPos(ed2.state.selection.from);
3106
+ return { left: rect.left, top: rect.top, width: Math.max(1, rect.right - rect.left), height: Math.max(1, rect.bottom - rect.top) };
3107
+ } catch {
3108
+ return null;
3109
+ }
3110
+ }
3111
+ useImperativeHandle(ref, () => ({
3112
+ isReady: ready.isReady,
3113
+ whenReady: ready.whenReady,
3114
+ getJSON: snapshot,
3115
+ getSnapshot: snapshot,
3116
+ async prepareSnapshot() {
3117
+ if (!editor.current) throw new Error("文档尚未就绪");
3118
+ if (hasPendingTasks()) throw new Error("请先完成上传、公式或组合输入");
3119
+ const doc = snapshot(), schemaVersion = requiredSchemaVersion(doc, latest.current.schemaVersion ?? SCHEMA_VERSION);
3120
+ new ContentSchemaService().validate(doc, schemaVersion);
3121
+ return { doc, schemaVersion };
3122
+ },
3123
+ hasPendingTasks,
3124
+ focus: () => {
3125
+ var _a;
3126
+ (_a = editor.current) == null ? void 0 : _a.commands.focus();
3127
+ },
3128
+ undo: () => {
3129
+ var _a;
3130
+ return ((_a = editor.current) == null ? void 0 : _a.commands.undo()) ?? false;
3131
+ },
3132
+ redo: () => {
3133
+ var _a;
3134
+ return ((_a = editor.current) == null ? void 0 : _a.commands.redo()) ?? false;
3135
+ },
3136
+ hasSelection: () => !!editor.current && !editor.current.state.selection.empty,
3137
+ getSelectionRect: selectionRect,
3138
+ getSelectionForAnchor() {
3139
+ const ed2 = editor.current;
3140
+ if (!ed2 || ed2.state.selection.empty) return null;
3141
+ const { from, to } = ed2.state.selection;
3142
+ const quote = ed2.state.doc.textBetween(from, to, "\n");
3143
+ if (!quote.trim()) return null;
3144
+ return { quote, prefix: ed2.state.doc.textBetween(Math.max(0, from - 64), from, "\n"), suffix: ed2.state.doc.textBetween(to, Math.min(ed2.state.doc.content.size, to + 64), "\n"), start: ed2.state.doc.textBetween(0, from, "\n").length };
3145
+ },
3146
+ setSelection(from, to = from) {
3147
+ var _a;
3148
+ (_a = editor.current) == null ? void 0 : _a.commands.setTextSelection({ from, to });
3149
+ },
3150
+ setContent(doc) {
3151
+ var _a;
3152
+ if (latest.current.collabDoc) return;
3153
+ try {
3154
+ const next = doc ?? empty;
3155
+ new ContentSchemaService().validate(next, latest.current.schemaVersion ?? requiredSchemaVersion(next, SCHEMA_VERSION));
3156
+ (_a = editor.current) == null ? void 0 : _a.commands.setContent(next, { emitUpdate: false });
3157
+ setError("");
3158
+ } catch (error2) {
3159
+ fail(error2);
3160
+ }
3161
+ }
3162
+ }));
3163
+ function openLink(ed2 = editor.current) {
3164
+ if (!ed2 || !ed2.isEditable) return;
3165
+ const tracked = createLinkEditTarget(ed2.state);
3166
+ if (!tracked) {
3167
+ tell("请先选中要添加链接的文字");
3168
+ return;
3169
+ }
3170
+ const rect = ed2.view.coordsAtPos(tracked.from);
3171
+ setLink({ ...tracked, tracked, mode: "edit", anchor: { left: rect.left, top: rect.top, width: Math.max(1, rect.right - rect.left), height: rect.bottom - rect.top } });
3172
+ }
3173
+ function rememberInsert() {
3174
+ const ed2 = editor.current;
3175
+ if (!(ed2 == null ? void 0 : ed2.isEditable)) return;
3176
+ pendingInsert.current = { from: ed2.state.selection.from, to: ed2.state.selection.from, valid: true };
3177
+ }
3178
+ function requestPage() {
3179
+ if (!hostRef.current.references) {
3180
+ tell("未配置资源选择服务");
3181
+ return;
3182
+ }
3183
+ rememberInsert();
3184
+ setReferences(true);
3185
+ }
3186
+ function requestVideo() {
3187
+ rememberInsert();
3188
+ setVideoUrl("");
3189
+ setVideo(true);
3190
+ }
3191
+ function insertPending(content) {
3192
+ const ed2 = editor.current, target = pendingInsert.current;
3193
+ pendingInsert.current = null;
3194
+ if (!(ed2 == null ? void 0 : ed2.isEditable) || !(target == null ? void 0 : target.valid) || target.from > ed2.state.doc.content.size) {
3195
+ tell("插入目标已移除,请重新选择位置");
3196
+ return;
3197
+ }
3198
+ ed2.chain().focus().insertContentAt({ from: target.from, to: target.to }, content).run();
3199
+ }
3200
+ function requestFile(kind) {
3201
+ rememberInsert();
3202
+ fileKind.current = kind;
3203
+ if (fileInput.current) {
3204
+ fileInput.current.accept = kind === "image" ? "image/*" : "";
3205
+ fileInput.current.click();
3206
+ }
3207
+ }
3208
+ async function upload(files, position, kind = "auto") {
3209
+ var _a, _b, _c;
3210
+ const ed2 = editor.current;
3211
+ if (!ed2 || !ed2.isEditable) return;
3212
+ const at = position ?? ed2.state.selection.from;
3213
+ const imageUploader = latest.current.uploadImage ?? ((_a = hostRef.current.attachments) == null ? void 0 : _a.uploadImage);
3214
+ if (kind !== "file" && files.length > 1 && files.every((file) => file.type.startsWith("image/")) && imageUploader) {
3215
+ const id = crypto.randomUUID();
3216
+ pending.current++;
3217
+ addUploadPlaceholder(ed2.view, { id, pos: at, kind: "image", label: `${files.length} 张图片`, file: files[0] });
3218
+ try {
3219
+ if (files.some((file) => file.size > 20 * 1024 * 1024)) throw new Error("图片不能超过 20 MB");
3220
+ const nodes = await Promise.all(files.map(async (file) => {
3221
+ const result = await imageUploader(file);
3222
+ primeAttachment(result.id, file);
3223
+ return ed2.schema.nodes.image.create({ attachmentId: result.id, alt: result.alt });
3224
+ }));
3225
+ if (disposed.current || ed2.isDestroyed || !ed2.isEditable) return;
3226
+ const pos = currentPlaceholderPos(ed2.view, id);
3227
+ if (pos === null) return;
3228
+ const tr = ed2.state.tr;
3229
+ let nextPos = pos;
3230
+ for (let index = 0; index < nodes.length; index += 10) {
3231
+ const chunk = nodes.slice(index, index + 10), block = chunk.length === 1 ? chunk[0] : ed2.schema.nodes.imageGallery.create({ height: 120 }, chunk);
3232
+ const start = tr.mapping.maps.length, at2 = nextPos;
3233
+ insertUploadedBlock(tr, at2, block);
3234
+ nextPos = tr.mapping.slice(start).map(at2, 1);
3235
+ }
3236
+ ed2.view.dispatch(tr);
3237
+ removeUploadPlaceholder(ed2.view, id);
3238
+ } catch (error2) {
3239
+ if (!disposed.current && !ed2.isDestroyed) {
3240
+ removeUploadPlaceholder(ed2.view, id);
3241
+ fail(error2);
3242
+ }
3243
+ } finally {
3244
+ if (!ed2.isDestroyed) removeUploadPlaceholder(ed2.view, id);
3245
+ pending.current--;
3246
+ if (!disposed.current) update((value) => value + 1);
3247
+ }
3248
+ return;
3249
+ }
3250
+ const slots = files.map((file) => ({ file, id: crypto.randomUUID() }));
3251
+ for (const { file, id } of slots) addUploadPlaceholder(ed2.view, { id, pos: at, kind: file.type.startsWith("image/") && kind !== "file" ? "image" : "file", label: file.name, file });
3252
+ pending.current += slots.length;
3253
+ update((value) => value + 1);
3254
+ for (const { file, id } of slots) {
3255
+ if (ed2.isDestroyed) {
3256
+ pending.current--;
3257
+ continue;
3258
+ }
3259
+ const image = file.type.startsWith("image/") && kind !== "file";
3260
+ const uploader = image ? latest.current.uploadImage ?? ((_b = hostRef.current.attachments) == null ? void 0 : _b.uploadImage) : latest.current.uploadFile ?? ((_c = hostRef.current.attachments) == null ? void 0 : _c.uploadFile);
3261
+ try {
3262
+ if (!uploader) throw new Error("未配置附件上传服务");
3263
+ if (file.size > 20 * 1024 * 1024) throw new Error("附件不能超过 20 MB");
3264
+ const result = await uploader(file);
3265
+ if (disposed.current || ed2.isDestroyed || !ed2.isEditable || currentPlaceholderPos(ed2.view, id) === null) continue;
3266
+ primeAttachment(result.id, file);
3267
+ const attrs = image ? { attachmentId: result.id, alt: "alt" in result ? result.alt : file.name } : { attachmentId: result.id, filename: file.name, mimeType: file.type, size: file.size };
3268
+ replacePlaceholderWithNode(ed2.view, id, { type: image ? "image" : "fileAttachment", attrs });
3269
+ } catch (error2) {
3270
+ if (!ed2.isDestroyed && !disposed.current) {
3271
+ removeUploadPlaceholder(ed2.view, id);
3272
+ fail(error2);
3273
+ }
3274
+ } finally {
3275
+ if (!ed2.isDestroyed) removeUploadPlaceholder(ed2.view, id);
3276
+ pending.current--;
3277
+ if (!disposed.current) update((value) => value + 1);
3278
+ }
3279
+ }
3280
+ }
3281
+ async function upgradeRemoteImages(ed2) {
3282
+ var _a, _b;
3283
+ const uploader = latest.current.uploadImage ?? ((_a = hostRef.current.attachments) == null ? void 0 : _a.uploadImage);
3284
+ if (!uploader) {
3285
+ tell("未配置图片上传服务,无法保存粘贴的外链图片");
3286
+ return;
3287
+ }
3288
+ const targets = [];
3289
+ ed2.state.doc.descendants((node, pos) => {
3290
+ if (node.type.name === "image" && !node.attrs.attachmentId && /^https?:\/\//i.test(String(node.attrs.src))) {
3291
+ const target = { from: pos, to: pos + node.nodeSize, valid: true };
3292
+ remoteTargets.current.add(target);
3293
+ targets.push({ target, src: node.attrs.src });
3294
+ }
3295
+ });
3296
+ pending.current += targets.length;
3297
+ update((value) => value + 1);
3298
+ for (const { target, src } of targets) try {
3299
+ const response = await fetch(src, { credentials: "omit", signal: AbortSignal.timeout(15e3) });
3300
+ if (!response.ok || Number(response.headers.get("content-length")) > 20 * 1024 * 1024) throw new Error("外链图片读取失败或超过 20 MB");
3301
+ const blob = await response.blob();
3302
+ if (!blob.type.startsWith("image/") || blob.size > 20 * 1024 * 1024) throw new Error("外链图片格式无效或超过 20 MB");
3303
+ const result = await uploader(new File([blob], "粘贴图片", { type: blob.type }));
3304
+ if (ed2.isDestroyed || !ed2.isEditable || !target.valid || ((_b = ed2.state.doc.nodeAt(target.from)) == null ? void 0 : _b.attrs.src) !== src) continue;
3305
+ const node = ed2.state.doc.nodeAt(target.from);
3306
+ primeAttachment(result.id, blob);
3307
+ ed2.view.dispatch(ed2.state.tr.setNodeMarkup(target.from, void 0, { ...node.attrs, src: null, attachmentId: result.id, alt: result.alt }));
3308
+ } catch (error2) {
3309
+ if (!ed2.isDestroyed) fail(error2);
3310
+ } finally {
3311
+ remoteTargets.current.delete(target);
3312
+ pending.current--;
3313
+ if (!ed2.isDestroyed) update((value) => value + 1);
3314
+ }
3315
+ }
3316
+ const ed = editor.current;
3317
+ const selection = ed == null ? void 0 : ed.state.selection;
3318
+ const selectedImage = selection instanceof NodeSelection && selection.node.type.name === "image" ? { pos: selection.from, node: selection.node } : null;
3319
+ const slashOpen = !!slash;
3320
+ let selectionToolbar = null;
3321
+ let imageToolbar = null;
3322
+ if (ed && shell.current && !props.disabled && !slashOpen && !blockMenu && !link && !dragging) {
3323
+ const hostRect = shell.current.getBoundingClientRect();
3324
+ if (selection instanceof TextSelection && !selection.empty) {
3325
+ try {
3326
+ const start = ed.view.coordsAtPos(selection.from), end = ed.view.coordsAtPos(selection.to);
3327
+ selectionToolbar = { x: (start.left + end.right) / 2 - hostRect.left, y: Math.min(start.top, end.top) - hostRect.top - 8 };
3328
+ } catch {
3329
+ selectionToolbar = null;
3330
+ }
3331
+ }
3332
+ if (selectedImage && imagePick === selectedImage.pos) {
3333
+ try {
3334
+ const dom = ed.view.nodeDOM(selectedImage.pos);
3335
+ if (dom instanceof HTMLElement) {
3336
+ const rect = dom.getBoundingClientRect(), above = rect.top - hostRect.top - 44;
3337
+ imageToolbar = { x: rect.left - hostRect.left + rect.width / 2, y: above < 4 ? rect.bottom - hostRect.top + 10 : above };
3338
+ }
3339
+ } catch {
3340
+ imageToolbar = null;
3341
+ }
3342
+ }
3343
+ }
3344
+ const hoverInfo = blockMenu ? frozen ?? hover : hover;
3345
+ const style = hoverInfo && shell.current ? blockAffordanceStyle(hoverInfo, shell.current, hoverInfo.isEmpty ? "plus" : "chip") : { left: 0, top: 0 };
3346
+ function imageAlign(value) {
3347
+ if (!ed || !selectedImage || !ed.isEditable) return;
3348
+ ed.view.dispatch(ed.state.tr.setNodeMarkup(selectedImage.pos, void 0, { ...selectedImage.node.attrs, align: value }));
3349
+ ed.commands.focus();
3350
+ }
3351
+ function previewImage() {
3352
+ if (!ed || !selectedImage) return;
3353
+ const dom = ed.view.nodeDOM(selectedImage.pos), img = dom instanceof HTMLElement ? dom.querySelector("img") : null;
3354
+ const src = (img == null ? void 0 : img.getAttribute("src")) ?? "";
3355
+ if (!src) return;
3356
+ const { items, index } = previewItemsFrom(img, "图片预览");
3357
+ setPreview({ src, label: selectedImage.node.attrs.alt ? String(selectedImage.node.attrs.alt) : "图片预览", items: items.length ? items : [{ src, label: "图片预览" }], index: items.length ? index : 0 });
3358
+ }
3359
+ function removeImage() {
3360
+ if (!ed || !selectedImage || !ed.isEditable) return;
3361
+ const $pos = ed.state.doc.resolve(selectedImage.pos);
3362
+ if ($pos.parent.type.name === "imageGallery") {
3363
+ const from = $pos.before(), to = from + $pos.parent.nodeSize;
3364
+ const range = new BlockRangeSelection(ed.state.doc.resolve(from), ed.state.doc.resolve(to), [{
3365
+ kind: "images",
3366
+ from,
3367
+ to,
3368
+ images: [{ from: selectedImage.pos, to: selectedImage.pos + selectedImage.node.nodeSize }]
3369
+ }]);
3370
+ const tr = ed.state.tr;
3371
+ range.replace(tr);
3372
+ ed.view.dispatch(tr);
3373
+ ed.view.focus();
3374
+ return;
3375
+ }
3376
+ ed.chain().focus().deleteRange({ from: selectedImage.pos, to: selectedImage.pos + selectedImage.node.nodeSize }).run();
3377
+ }
3378
+ const quickVisible = !!hoverInfo && !props.disabled && !blockMenu && !dragging && !tableHover && hoverInfo.type !== "table" && !slash && !selectionToolbar && !preview && !link;
3379
+ return /* @__PURE__ */ jsxs("div", { ref: shell, className: `wr-document editor-shell${selectedImage && imagePick === selectedImage.pos ? " img-pick-active" : ""}${props.className ? ` ${props.className}` : ""}`, "data-block-select-band": true, children: [
3380
+ notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
3381
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
3382
+ /* @__PURE__ */ jsx("div", { ref: mount, className: "wr-document-mount" }),
3383
+ selectionToolbar && ed && !selectedImage && /* @__PURE__ */ jsx(
3384
+ SelectionToolbar,
3385
+ {
3386
+ editor: ed,
3387
+ x: selectionToolbar.x,
3388
+ y: selectionToolbar.y,
3389
+ canComment: props.canComment,
3390
+ onRequestComment: () => {
3391
+ var _a, _b;
3392
+ return (_b = (_a = latest.current).onRequestComment) == null ? void 0 : _b.call(_a);
3393
+ },
3394
+ onRequestLink: () => openLink(ed),
3395
+ onRemoveLink: () => {
3396
+ ed.chain().focus().unsetLink().run();
3397
+ }
3398
+ }
3399
+ ),
3400
+ imageToolbar && selectedImage && ed && /* @__PURE__ */ jsxs("div", { className: "img-toolbar", role: "toolbar", "aria-label": "图片操作", style: { left: imageToolbar.x, top: imageToolbar.y }, onMouseDown: (event) => event.preventDefault(), children: [
3401
+ ["left", "center", "right"].map((align) => /* @__PURE__ */ jsx(
3402
+ "button",
3403
+ {
3404
+ type: "button",
3405
+ className: `it-btn${selectedImage.node.attrs.align === align ? " on" : ""}`,
3406
+ "aria-label": `图片${align === "left" ? "左" : align === "center" ? "居中" : "右"}对齐`,
3407
+ title: `图片${align === "left" ? "左" : align === "center" ? "居中" : "右"}对齐`,
3408
+ "aria-pressed": selectedImage.node.attrs.align === align,
3409
+ onClick: () => imageAlign(align),
3410
+ children: /* @__PURE__ */ jsx(WIcon, { name: `align-${align}`, size: 16 })
3411
+ },
3412
+ align
3413
+ )),
3414
+ /* @__PURE__ */ jsx("span", { className: "it-sep", "aria-hidden": "true" }),
3415
+ /* @__PURE__ */ jsx("button", { type: "button", className: "it-btn", "aria-label": "编辑图注", title: "编辑图注", onClick: () => {
3416
+ const dom = ed.view.nodeDOM(selectedImage.pos);
3417
+ if (dom instanceof HTMLElement) dom.dispatchEvent(new CustomEvent("ywiki-caption-edit"));
3418
+ }, children: /* @__PURE__ */ jsx(WIcon, { name: "text", size: 16 }) }),
3419
+ /* @__PURE__ */ jsx("button", { type: "button", className: "it-btn", "aria-label": "查看大图", title: "查看大图", onClick: previewImage, children: /* @__PURE__ */ jsx(WIcon, { name: "zoom-in", size: 16 }) }),
3420
+ /* @__PURE__ */ jsx("span", { className: "it-sep", "aria-hidden": "true" }),
3421
+ /* @__PURE__ */ jsx("button", { type: "button", className: "it-btn danger", "aria-label": "删除图片", title: "删除图片", onClick: removeImage, children: /* @__PURE__ */ jsx(WIcon, { name: "trash", size: 16 }) })
3422
+ ] }),
3423
+ hoverInfo && quickVisible && (hoverInfo.isEmpty ? /* @__PURE__ */ jsx(BlockPlus, { info: hoverInfo, style, expanded: blockMenu, onMouseEnter: () => onBlockHover(lastHover.current), onClick: () => {
3424
+ setFrozen(hoverInfo);
3425
+ setPanelOnly("insert-below");
3426
+ setBlockMenu(true);
3427
+ setHover(hoverInfo);
3428
+ ed == null ? void 0 : ed.commands.focus();
3429
+ } }) : /* @__PURE__ */ jsx(
3430
+ BlockChip,
3431
+ {
3432
+ editor: ed,
3433
+ info: hoverInfo,
3434
+ style,
3435
+ expanded: blockMenu,
3436
+ onMouseEnter: () => onBlockHover(lastHover.current),
3437
+ onOpen: () => {
3438
+ setFrozen(hoverInfo);
3439
+ setPanelOnly("format");
3440
+ setBlockMenu(true);
3441
+ setHover(hoverInfo);
3442
+ },
3443
+ onOpenFull: () => {
3444
+ var _a;
3445
+ if ((_a = gestures.current) == null ? void 0 : _a.consumeClick()) return;
3446
+ setFrozen(hoverInfo);
3447
+ setPanelOnly(void 0);
3448
+ setBlockMenu(true);
3449
+ setHover(hoverInfo);
3450
+ },
3451
+ onHandleMouseDown: (event) => {
3452
+ var _a;
3453
+ (_a = gestures.current) == null ? void 0 : _a.start(hoverInfo.pos, event.nativeEvent, () => {
3454
+ });
3455
+ }
3456
+ }
3457
+ )),
3458
+ blockMenu && frozen && ed && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, zIndex: 45, pointerEvents: "none" }, children: /* @__PURE__ */ jsx(
3459
+ BlockMenu,
3460
+ {
3461
+ editor: ed,
3462
+ info: frozen,
3463
+ canComment: props.canComment,
3464
+ panelOnly,
3465
+ onClose: () => {
3466
+ setBlockMenu(false);
3467
+ setFrozen(null);
3468
+ setPanelOnly(void 0);
3469
+ setHover(lastHover.current);
3470
+ },
3471
+ onPickFile: (kind) => {
3472
+ closeBlockMenu();
3473
+ requestFile(kind);
3474
+ },
3475
+ onPickPage: () => {
3476
+ closeBlockMenu();
3477
+ requestPage();
3478
+ },
3479
+ onPickVideo: () => {
3480
+ closeBlockMenu();
3481
+ requestVideo();
3482
+ },
3483
+ onRequestComment: () => {
3484
+ var _a, _b;
3485
+ const node = ed.state.doc.nodeAt(frozen.pos);
3486
+ if (node) ed.commands.setTextSelection({ from: frozen.pos + 1, to: frozen.pos + node.nodeSize - 1 });
3487
+ (_b = (_a = latest.current).onRequestComment) == null ? void 0 : _b.call(_a);
3488
+ },
3489
+ onNotice: tell,
3490
+ onCopyLink: props.pageId ? () => {
3491
+ var _a;
3492
+ const target = (_a = host.references) == null ? void 0 : _a.resolve(props.pageId, "当前内容");
3493
+ if (!(target == null ? void 0 : target.href)) {
3494
+ tell("宿主未配置当前内容链接");
3495
+ return;
3496
+ }
3497
+ void navigator.clipboard.writeText(target.href).catch(() => tell("无法写入剪贴板"));
3498
+ } : void 0,
3499
+ onSharePage: props.onSharePage,
3500
+ onConvertToSubpage: props.onConvertToSubpage,
3501
+ onSaveAsTemplate: props.onSaveAsTemplate
3502
+ }
3503
+ ) }),
3504
+ slash && ed && /* @__PURE__ */ jsx(
3505
+ SlashMenu,
3506
+ {
3507
+ editor: ed,
3508
+ from: slash.from,
3509
+ query: slash.query,
3510
+ x: slash.x,
3511
+ y: slash.y,
3512
+ onClose: () => setSlash(null),
3513
+ onPickFile: requestFile,
3514
+ onPickPage: requestPage,
3515
+ onPickVideo: requestVideo
3516
+ }
3517
+ ),
3518
+ ed && !props.disabled && !dragging && /* @__PURE__ */ jsx(
3519
+ TableControls,
3520
+ {
3521
+ editor: ed,
3522
+ hover: tableHover,
3523
+ revision: epoch,
3524
+ onNotice: tell,
3525
+ onBlockDrag: (pos, event, onClick) => {
3526
+ var _a;
3527
+ return (_a = gestures.current) == null ? void 0 : _a.start(pos, event, onClick);
3528
+ }
3529
+ }
3530
+ ),
3531
+ /* @__PURE__ */ jsx("input", { ref: fileInput, hidden: true, type: "file", multiple: true, onChange: (event) => {
3532
+ const target = pendingInsert.current;
3533
+ pendingInsert.current = null;
3534
+ if (!(target == null ? void 0 : target.valid)) tell("插入目标已移除,请重新选择位置");
3535
+ else if (event.target.files) void upload([...event.target.files], target.from, fileKind.current);
3536
+ event.target.value = "";
3537
+ } }),
3538
+ link && /* @__PURE__ */ jsx(
3539
+ LinkPopover,
3540
+ {
3541
+ target: link,
3542
+ onClose: () => setLink(null),
3543
+ onEdit: () => setLink({ ...link, mode: "edit" }),
3544
+ onSave: (href) => applyLink(href),
3545
+ onRemove: () => applyLink(null)
3546
+ },
3547
+ link.mode
3548
+ ),
3549
+ video && /* @__PURE__ */ jsx(
3550
+ VideoEmbedDialog,
3551
+ {
3552
+ initialUrl: videoUrl,
3553
+ onClose: () => {
3554
+ pendingInsert.current = null;
3555
+ setVideo(false);
3556
+ },
3557
+ onInsert: (attrs) => {
3558
+ insertPending({ type: "videoEmbed", attrs });
3559
+ setVideo(false);
3560
+ }
3561
+ }
3562
+ ),
3563
+ references && /* @__PURE__ */ jsx(
3564
+ PagePicker,
3565
+ {
3566
+ spaceId: props.spaceId,
3567
+ excludeId: props.pageId,
3568
+ title: "选择要引用的页面",
3569
+ onClose: () => {
3570
+ pendingInsert.current = null;
3571
+ setReferences(false);
3572
+ },
3573
+ onPick: (id, title) => {
3574
+ insertPending({ type: "pageLink", attrs: { pageId: id, label: title } });
3575
+ setReferences(false);
3576
+ }
3577
+ }
3578
+ ),
3579
+ preview && /* @__PURE__ */ jsx(ImagePreview, { src: preview.src, label: preview.label, items: preview.items, index: preview.index, onClose: () => setPreview(null) }),
3580
+ atoms.map((atom) => createPortal(atom.content, atom.dom, atom.id))
3581
+ ] });
3582
+ function closeBlockMenu() {
3583
+ setBlockMenu(false);
3584
+ setFrozen(null);
3585
+ setPanelOnly(void 0);
3586
+ }
3587
+ function applyLink(href) {
3588
+ var _a;
3589
+ const ed2 = editor.current, target = (_a = linkRef.current) == null ? void 0 : _a.tracked;
3590
+ if (!(ed2 == null ? void 0 : ed2.isEditable) || !target || !linkEditTargetCurrent(ed2.state, target)) {
3591
+ tell("目标文字或链接已变化,请重新选择");
3592
+ setLink(null);
3593
+ return;
3594
+ }
3595
+ const chain = ed2.chain().focus().setTextSelection({ from: target.from, to: target.to });
3596
+ if (href) chain.setLink({ href }).run();
3597
+ else chain.unsetLink().run();
3598
+ setLink(null);
3599
+ }
3600
+ });
3601
+ let schema;
3602
+ function sameDocument(a2, b2) {
3603
+ if (!a2 || !b2) return a2 === b2;
3604
+ try {
3605
+ schema ?? (schema = getSchema(coreExtensions()));
3606
+ return schema.nodeFromJSON(a2).eq(schema.nodeFromJSON(b2));
3607
+ } catch {
3608
+ return false;
3609
+ }
3610
+ }
3611
+ export {
3612
+ A as ANCHORED_POPOVER_ARROW_INSET,
3613
+ a as ANCHORED_POPOVER_GAP,
3614
+ b as ANCHORED_POPOVER_VIEW_MARGIN,
3615
+ C as COLLAB_FIELD,
3616
+ DocumentEditor,
3617
+ coreExtensions,
3618
+ placeAnchoredPopover,
3619
+ sameDocument
3620
+ };