editor-svg 1.0.0

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 (472) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintrc +46 -0
  3. package/.prettierrc +8 -0
  4. package/AGENTS.md +186 -0
  5. package/CHANGELOG.md +2543 -0
  6. package/CLAUDE.md +110 -0
  7. package/LICENSE +21 -0
  8. package/README.md +110 -0
  9. package/cypress/e2e/control/checkbox.cy.ts +46 -0
  10. package/cypress/e2e/control/select.cy.ts +56 -0
  11. package/cypress/e2e/control/text.cy.ts +43 -0
  12. package/cypress/e2e/editor.cy.ts +67 -0
  13. package/cypress/e2e/menus/block.cy.ts +38 -0
  14. package/cypress/e2e/menus/checkbox.cy.ts +33 -0
  15. package/cypress/e2e/menus/codeblock.cy.ts +34 -0
  16. package/cypress/e2e/menus/date.cy.ts +28 -0
  17. package/cypress/e2e/menus/format.cy.ts +40 -0
  18. package/cypress/e2e/menus/hyperlink.cy.ts +39 -0
  19. package/cypress/e2e/menus/image.cy.ts +25 -0
  20. package/cypress/e2e/menus/latex.cy.ts +34 -0
  21. package/cypress/e2e/menus/pagebreak.cy.ts +21 -0
  22. package/cypress/e2e/menus/painter.cy.ts +53 -0
  23. package/cypress/e2e/menus/print.cy.ts +25 -0
  24. package/cypress/e2e/menus/row.cy.ts +103 -0
  25. package/cypress/e2e/menus/search.cy.ts +112 -0
  26. package/cypress/e2e/menus/separator.cy.ts +32 -0
  27. package/cypress/e2e/menus/table.cy.ts +25 -0
  28. package/cypress/e2e/menus/text.cy.ts +304 -0
  29. package/cypress/e2e/menus/title.cy.ts +43 -0
  30. package/cypress/e2e/menus/undoRedo.cy.ts +49 -0
  31. package/cypress/e2e/menus/watermark.cy.ts +64 -0
  32. package/cypress/fixtures/example.json +3 -0
  33. package/cypress/fixtures/test.png +0 -0
  34. package/cypress/global.d.ts +13 -0
  35. package/cypress/support/commands.ts +5 -0
  36. package/cypress/support/e2e.ts +1 -0
  37. package/cypress/tsconfig.json +21 -0
  38. package/cypress.config.ts +10 -0
  39. package/docs/.vitepress/config.ts +191 -0
  40. package/docs/.vitepress/theme/components/DeepWikiBadge.vue +21 -0
  41. package/docs/.vitepress/theme/components/ZreadBadge.vue +21 -0
  42. package/docs/.vitepress/theme/index.ts +27 -0
  43. package/docs/en/guide/api-common.md +49 -0
  44. package/docs/en/guide/api-instance.md +34 -0
  45. package/docs/en/guide/command-execute.md +1167 -0
  46. package/docs/en/guide/command-get.md +355 -0
  47. package/docs/en/guide/contextmenu-custom.md +44 -0
  48. package/docs/en/guide/contextmenu-internal.md +61 -0
  49. package/docs/en/guide/eventbus.md +260 -0
  50. package/docs/en/guide/i18n.md +112 -0
  51. package/docs/en/guide/listener.md +126 -0
  52. package/docs/en/guide/option.md +214 -0
  53. package/docs/en/guide/override.md +57 -0
  54. package/docs/en/guide/plugin-custom.md +25 -0
  55. package/docs/en/guide/plugin-internal.md +125 -0
  56. package/docs/en/guide/schema.md +237 -0
  57. package/docs/en/guide/shortcut-custom.md +22 -0
  58. package/docs/en/guide/shortcut-internal.md +189 -0
  59. package/docs/en/guide/start.md +97 -0
  60. package/docs/en/index.md +43 -0
  61. package/docs/guide/api-common.md +49 -0
  62. package/docs/guide/api-instance.md +34 -0
  63. package/docs/guide/command-execute.md +1157 -0
  64. package/docs/guide/command-get.md +353 -0
  65. package/docs/guide/contextmenu-custom.md +44 -0
  66. package/docs/guide/contextmenu-internal.md +61 -0
  67. package/docs/guide/eventbus.md +260 -0
  68. package/docs/guide/i18n.md +111 -0
  69. package/docs/guide/listener.md +126 -0
  70. package/docs/guide/option.md +214 -0
  71. package/docs/guide/override.md +57 -0
  72. package/docs/guide/plugin-custom.md +25 -0
  73. package/docs/guide/plugin-internal.md +125 -0
  74. package/docs/guide/schema.md +237 -0
  75. package/docs/guide/shortcut-custom.md +22 -0
  76. package/docs/guide/shortcut-internal.md +189 -0
  77. package/docs/guide/start.md +97 -0
  78. package/docs/index.md +43 -0
  79. package/docs/public/favicon.png +0 -0
  80. package/favicon.png +0 -0
  81. package/index.html +435 -0
  82. package/package.json +55 -0
  83. package/pnpm-lock.yaml +4113 -0
  84. package/scripts/release.js +41 -0
  85. package/scripts/verifyCommit.js +19 -0
  86. package/src/assets/images/alignment.svg +1 -0
  87. package/src/assets/images/arrow-left.svg +1 -0
  88. package/src/assets/images/arrow-right.svg +1 -0
  89. package/src/assets/images/block.svg +1 -0
  90. package/src/assets/images/bold.svg +1 -0
  91. package/src/assets/images/catalog.svg +1 -0
  92. package/src/assets/images/center.svg +1 -0
  93. package/src/assets/images/checkbox.svg +1 -0
  94. package/src/assets/images/close.svg +1 -0
  95. package/src/assets/images/codeblock.svg +1 -0
  96. package/src/assets/images/color.svg +1 -0
  97. package/src/assets/images/control.svg +1 -0
  98. package/src/assets/images/date.svg +1 -0
  99. package/src/assets/images/exit-fullscreen.svg +1 -0
  100. package/src/assets/images/format.svg +1 -0
  101. package/src/assets/images/highlight.svg +1 -0
  102. package/src/assets/images/hyperlink.svg +1 -0
  103. package/src/assets/images/image.svg +1 -0
  104. package/src/assets/images/italic.svg +1 -0
  105. package/src/assets/images/justify.svg +7 -0
  106. package/src/assets/images/latex.svg +1 -0
  107. package/src/assets/images/left.svg +1 -0
  108. package/src/assets/images/line-dash-dot-dot.svg +1 -0
  109. package/src/assets/images/line-dash-dot.svg +1 -0
  110. package/src/assets/images/line-dash-large-gap.svg +1 -0
  111. package/src/assets/images/line-dash-small-gap.svg +1 -0
  112. package/src/assets/images/line-dot.svg +1 -0
  113. package/src/assets/images/line-double.svg +1 -0
  114. package/src/assets/images/line-single.svg +1 -0
  115. package/src/assets/images/line-wavy.svg +1 -0
  116. package/src/assets/images/list.svg +1 -0
  117. package/src/assets/images/option.svg +1 -0
  118. package/src/assets/images/page-break.svg +1 -0
  119. package/src/assets/images/page-mode.svg +1 -0
  120. package/src/assets/images/page-scale-add.svg +1 -0
  121. package/src/assets/images/page-scale-minus.svg +1 -0
  122. package/src/assets/images/painter.svg +1 -0
  123. package/src/assets/images/paper-direction.svg +1 -0
  124. package/src/assets/images/paper-margin.svg +1 -0
  125. package/src/assets/images/paper-size.svg +1 -0
  126. package/src/assets/images/print.svg +1 -0
  127. package/src/assets/images/radio.svg +4 -0
  128. package/src/assets/images/redo.svg +1 -0
  129. package/src/assets/images/request-fullscreen.svg +1 -0
  130. package/src/assets/images/right.svg +1 -0
  131. package/src/assets/images/row-margin.svg +1 -0
  132. package/src/assets/images/search.svg +1 -0
  133. package/src/assets/images/separator.svg +1 -0
  134. package/src/assets/images/signature-undo.svg +1 -0
  135. package/src/assets/images/signature.svg +1 -0
  136. package/src/assets/images/size-add.svg +1 -0
  137. package/src/assets/images/size-minus.svg +1 -0
  138. package/src/assets/images/strikeout.svg +1 -0
  139. package/src/assets/images/subscript.svg +1 -0
  140. package/src/assets/images/superscript.svg +1 -0
  141. package/src/assets/images/table.svg +1 -0
  142. package/src/assets/images/title.svg +1 -0
  143. package/src/assets/images/trash.svg +1 -0
  144. package/src/assets/images/underline.svg +1 -0
  145. package/src/assets/images/undo.svg +1 -0
  146. package/src/assets/images/watermark.svg +1 -0
  147. package/src/assets/images/word-tool.svg +1 -0
  148. package/src/assets/snapshots/main_v0.2.1.png +0 -0
  149. package/src/assets/snapshots/main_v0.2.2.png +0 -0
  150. package/src/assets/snapshots/main_v0.3.0.png +0 -0
  151. package/src/assets/snapshots/main_v0.3.1.png +0 -0
  152. package/src/assets/snapshots/main_v0.5.0.png +0 -0
  153. package/src/assets/snapshots/main_v0.5.1.png +0 -0
  154. package/src/assets/snapshots/main_v0.6.0.png +0 -0
  155. package/src/assets/snapshots/main_v0.6.1.png +0 -0
  156. package/src/assets/snapshots/main_v0.7.0.png +0 -0
  157. package/src/assets/snapshots/main_v0.7.1.png +0 -0
  158. package/src/assets/snapshots/main_v0.7.2.png +0 -0
  159. package/src/assets/snapshots/main_v0.7.3.png +0 -0
  160. package/src/assets/snapshots/main_v0.7.4.png +0 -0
  161. package/src/assets/snapshots/main_v0.7.6.png +0 -0
  162. package/src/assets/snapshots/main_v0.7.7.png +0 -0
  163. package/src/assets/snapshots/main_v0.8.0.png +0 -0
  164. package/src/assets/snapshots/main_v0.8.5.png +0 -0
  165. package/src/assets/snapshots/main_v0.8.6.png +0 -0
  166. package/src/assets/snapshots/main_v0.8.7.png +0 -0
  167. package/src/assets/snapshots/main_v0.8.8.png +0 -0
  168. package/src/assets/snapshots/main_v0.9.0.png +0 -0
  169. package/src/assets/snapshots/main_v0.9.1.png +0 -0
  170. package/src/assets/snapshots/main_v0.9.2.png +0 -0
  171. package/src/assets/snapshots/main_v0.9.23.png +0 -0
  172. package/src/assets/snapshots/main_v0.9.28.png +0 -0
  173. package/src/assets/snapshots/main_v0.9.29.png +0 -0
  174. package/src/assets/snapshots/main_v0.9.3.png +0 -0
  175. package/src/assets/snapshots/main_v0.9.30.png +0 -0
  176. package/src/assets/snapshots/main_v0.9.32.png +0 -0
  177. package/src/assets/snapshots/main_v0.9.35.png +0 -0
  178. package/src/assets/snapshots/main_v0.9.4.png +0 -0
  179. package/src/assets/snapshots/main_v0.9.5.png +0 -0
  180. package/src/assets/snapshots/main_v0.9.6.png +0 -0
  181. package/src/assets/snapshots/main_v0.9.8.png +0 -0
  182. package/src/components/dialog/Dialog.ts +171 -0
  183. package/src/components/dialog/dialog.css +131 -0
  184. package/src/components/signature/Signature.ts +340 -0
  185. package/src/components/signature/signature.css +132 -0
  186. package/src/editor/assets/css/block/block.css +21 -0
  187. package/src/editor/assets/css/contextmenu/contextmenu.css +196 -0
  188. package/src/editor/assets/css/control/calculator.css +85 -0
  189. package/src/editor/assets/css/control/select.css +44 -0
  190. package/src/editor/assets/css/date/datePicker.css +233 -0
  191. package/src/editor/assets/css/hyperlink/hyperlink.css +26 -0
  192. package/src/editor/assets/css/index.css +78 -0
  193. package/src/editor/assets/css/previewer/previewer.css +122 -0
  194. package/src/editor/assets/css/resizer/resizer.css +74 -0
  195. package/src/editor/assets/css/table/table.css +155 -0
  196. package/src/editor/assets/css/zone/zone.css +61 -0
  197. package/src/editor/assets/images/close.svg +1 -0
  198. package/src/editor/assets/images/delete-col.svg +1 -0
  199. package/src/editor/assets/images/delete-row-col.svg +1 -0
  200. package/src/editor/assets/images/delete-row.svg +1 -0
  201. package/src/editor/assets/images/delete-table.svg +1 -0
  202. package/src/editor/assets/images/image-change.svg +1 -0
  203. package/src/editor/assets/images/image-download.svg +1 -0
  204. package/src/editor/assets/images/image-next.svg +1 -0
  205. package/src/editor/assets/images/image-pre.svg +1 -0
  206. package/src/editor/assets/images/image.svg +1 -0
  207. package/src/editor/assets/images/insert-bottom-row.svg +1 -0
  208. package/src/editor/assets/images/insert-left-col.svg +1 -0
  209. package/src/editor/assets/images/insert-right-col.svg +1 -0
  210. package/src/editor/assets/images/insert-row-col.svg +1 -0
  211. package/src/editor/assets/images/insert-top-row.svg +1 -0
  212. package/src/editor/assets/images/merge-cancel-cell.svg +1 -0
  213. package/src/editor/assets/images/merge-cell.svg +1 -0
  214. package/src/editor/assets/images/original-size.svg +1 -0
  215. package/src/editor/assets/images/print.svg +1 -0
  216. package/src/editor/assets/images/rotate.svg +1 -0
  217. package/src/editor/assets/images/submenu-dropdown.svg +1 -0
  218. package/src/editor/assets/images/table-border-all.svg +1 -0
  219. package/src/editor/assets/images/table-border-dash.svg +1 -0
  220. package/src/editor/assets/images/table-border-empty.svg +1 -0
  221. package/src/editor/assets/images/table-border-external.svg +1 -0
  222. package/src/editor/assets/images/table-border-internal.svg +1 -0
  223. package/src/editor/assets/images/table-border-td-back.svg +1 -0
  224. package/src/editor/assets/images/table-border-td-bottom.svg +1 -0
  225. package/src/editor/assets/images/table-border-td-forward.svg +1 -0
  226. package/src/editor/assets/images/table-border-td-left.svg +1 -0
  227. package/src/editor/assets/images/table-border-td-right.svg +1 -0
  228. package/src/editor/assets/images/table-border-td-top.svg +1 -0
  229. package/src/editor/assets/images/table-border-td.svg +1 -0
  230. package/src/editor/assets/images/vertical-align-bottom.svg +1 -0
  231. package/src/editor/assets/images/vertical-align-middle.svg +1 -0
  232. package/src/editor/assets/images/vertical-align-top.svg +1 -0
  233. package/src/editor/assets/images/vertical-align.svg +1 -0
  234. package/src/editor/assets/images/zoom-in.svg +1 -0
  235. package/src/editor/assets/images/zoom-out.svg +1 -0
  236. package/src/editor/core/actuator/Actuator.ts +21 -0
  237. package/src/editor/core/actuator/handlers/positionContextChange.ts +13 -0
  238. package/src/editor/core/command/Command.ts +312 -0
  239. package/src/editor/core/command/CommandAdapt.ts +2733 -0
  240. package/src/editor/core/contextmenu/ContextMenu.ts +363 -0
  241. package/src/editor/core/contextmenu/menus/controlMenus.ts +25 -0
  242. package/src/editor/core/contextmenu/menus/globalMenus.ts +66 -0
  243. package/src/editor/core/contextmenu/menus/hyperlinkMenus.ts +58 -0
  244. package/src/editor/core/contextmenu/menus/imageMenus.ts +134 -0
  245. package/src/editor/core/contextmenu/menus/tableMenus.ts +331 -0
  246. package/src/editor/core/cursor/Cursor.ts +248 -0
  247. package/src/editor/core/cursor/CursorAgent.ts +75 -0
  248. package/src/editor/core/draw/Draw.ts +2934 -0
  249. package/src/editor/core/draw/control/Control.ts +1767 -0
  250. package/src/editor/core/draw/control/checkbox/CheckboxControl.ts +154 -0
  251. package/src/editor/core/draw/control/date/DateControl.ts +363 -0
  252. package/src/editor/core/draw/control/interactive/ControlSearch.ts +214 -0
  253. package/src/editor/core/draw/control/number/Calculator.ts +183 -0
  254. package/src/editor/core/draw/control/number/NumberControl.ts +183 -0
  255. package/src/editor/core/draw/control/radio/RadioControl.ts +68 -0
  256. package/src/editor/core/draw/control/richtext/Border.ts +52 -0
  257. package/src/editor/core/draw/control/select/SelectControl.ts +567 -0
  258. package/src/editor/core/draw/control/text/TextControl.ts +280 -0
  259. package/src/editor/core/draw/frame/Background.ts +117 -0
  260. package/src/editor/core/draw/frame/Badge.ts +88 -0
  261. package/src/editor/core/draw/frame/Footer.ts +155 -0
  262. package/src/editor/core/draw/frame/Header.ts +158 -0
  263. package/src/editor/core/draw/frame/LineNumber.ts +43 -0
  264. package/src/editor/core/draw/frame/Margin.ts +53 -0
  265. package/src/editor/core/draw/frame/PageBorder.ts +47 -0
  266. package/src/editor/core/draw/frame/PageNumber.ts +88 -0
  267. package/src/editor/core/draw/frame/Placeholder.ts +114 -0
  268. package/src/editor/core/draw/frame/Watermark.ts +188 -0
  269. package/src/editor/core/draw/graffiti/Graffiti.ts +125 -0
  270. package/src/editor/core/draw/interactive/Area.ts +312 -0
  271. package/src/editor/core/draw/interactive/Group.ts +198 -0
  272. package/src/editor/core/draw/interactive/Search.ts +527 -0
  273. package/src/editor/core/draw/particle/CheckboxParticle.ts +111 -0
  274. package/src/editor/core/draw/particle/HyperlinkParticle.ts +86 -0
  275. package/src/editor/core/draw/particle/ImageParticle.ts +280 -0
  276. package/src/editor/core/draw/particle/LabelParticle.ts +79 -0
  277. package/src/editor/core/draw/particle/LineBreakParticle.ts +55 -0
  278. package/src/editor/core/draw/particle/ListParticle.ts +255 -0
  279. package/src/editor/core/draw/particle/PageBreakParticle.ts +54 -0
  280. package/src/editor/core/draw/particle/RadioParticle.ts +99 -0
  281. package/src/editor/core/draw/particle/SeparatorParticle.ts +37 -0
  282. package/src/editor/core/draw/particle/SubscriptParticle.ts +23 -0
  283. package/src/editor/core/draw/particle/SuperscriptParticle.ts +23 -0
  284. package/src/editor/core/draw/particle/TextParticle.ts +174 -0
  285. package/src/editor/core/draw/particle/WhiteSpaceParticle.ts +32 -0
  286. package/src/editor/core/draw/particle/block/BlockParticle.ts +76 -0
  287. package/src/editor/core/draw/particle/block/modules/BaseBlock.ts +280 -0
  288. package/src/editor/core/draw/particle/block/modules/IFrameBlock.ts +47 -0
  289. package/src/editor/core/draw/particle/block/modules/VideoBlock.ts +61 -0
  290. package/src/editor/core/draw/particle/date/DateParticle.ts +111 -0
  291. package/src/editor/core/draw/particle/date/DatePicker.ts +577 -0
  292. package/src/editor/core/draw/particle/latex/LaTexParticle.ts +43 -0
  293. package/src/editor/core/draw/particle/latex/utils/LaTexUtils.ts +1196 -0
  294. package/src/editor/core/draw/particle/latex/utils/hershey.ts +1632 -0
  295. package/src/editor/core/draw/particle/latex/utils/symbols.ts +318 -0
  296. package/src/editor/core/draw/particle/previewer/Previewer.ts +582 -0
  297. package/src/editor/core/draw/particle/table/TableOperate.ts +988 -0
  298. package/src/editor/core/draw/particle/table/TableParticle.ts +558 -0
  299. package/src/editor/core/draw/particle/table/TableTool.ts +551 -0
  300. package/src/editor/core/draw/richtext/AbstractRichText.ts +59 -0
  301. package/src/editor/core/draw/richtext/Highlight.ts +24 -0
  302. package/src/editor/core/draw/richtext/Strikeout.ts +28 -0
  303. package/src/editor/core/draw/richtext/Underline.ts +106 -0
  304. package/src/editor/core/event/CanvasEvent.ts +215 -0
  305. package/src/editor/core/event/GlobalEvent.ts +173 -0
  306. package/src/editor/core/event/eventbus/EventBus.ts +50 -0
  307. package/src/editor/core/event/handlers/click.ts +234 -0
  308. package/src/editor/core/event/handlers/composition.ts +45 -0
  309. package/src/editor/core/event/handlers/copy.ts +72 -0
  310. package/src/editor/core/event/handlers/cut.ts +47 -0
  311. package/src/editor/core/event/handlers/drag.ts +66 -0
  312. package/src/editor/core/event/handlers/drop.ts +28 -0
  313. package/src/editor/core/event/handlers/input.ts +129 -0
  314. package/src/editor/core/event/handlers/keydown/backspace.ts +161 -0
  315. package/src/editor/core/event/handlers/keydown/delete.ts +119 -0
  316. package/src/editor/core/event/handlers/keydown/end.ts +69 -0
  317. package/src/editor/core/event/handlers/keydown/enter.ts +126 -0
  318. package/src/editor/core/event/handlers/keydown/home.ts +69 -0
  319. package/src/editor/core/event/handlers/keydown/index.ts +85 -0
  320. package/src/editor/core/event/handlers/keydown/left.ts +162 -0
  321. package/src/editor/core/event/handlers/keydown/right.ts +178 -0
  322. package/src/editor/core/event/handlers/keydown/tab.ts +41 -0
  323. package/src/editor/core/event/handlers/keydown/updown.ts +342 -0
  324. package/src/editor/core/event/handlers/mousedown.ts +262 -0
  325. package/src/editor/core/event/handlers/mouseleave.ts +14 -0
  326. package/src/editor/core/event/handlers/mousemove.ts +133 -0
  327. package/src/editor/core/event/handlers/mouseup.ts +341 -0
  328. package/src/editor/core/event/handlers/paste.ts +231 -0
  329. package/src/editor/core/history/HistoryManager.ts +61 -0
  330. package/src/editor/core/i18n/I18n.ts +51 -0
  331. package/src/editor/core/i18n/lang/en.json +92 -0
  332. package/src/editor/core/i18n/lang/zh-CN.json +92 -0
  333. package/src/editor/core/listener/Listener.ts +41 -0
  334. package/src/editor/core/observer/ImageObserver.ts +19 -0
  335. package/src/editor/core/observer/MouseObserver.ts +56 -0
  336. package/src/editor/core/observer/ScrollObserver.ts +88 -0
  337. package/src/editor/core/observer/SelectionObserver.ts +143 -0
  338. package/src/editor/core/override/Override.ts +14 -0
  339. package/src/editor/core/plugin/Plugin.ts +17 -0
  340. package/src/editor/core/position/Position.ts +870 -0
  341. package/src/editor/core/range/RangeManager.ts +723 -0
  342. package/src/editor/core/register/Register.ts +28 -0
  343. package/src/editor/core/shortcut/Shortcut.ts +80 -0
  344. package/src/editor/core/shortcut/keys/listKeys.ts +22 -0
  345. package/src/editor/core/shortcut/keys/richtextKeys.ts +102 -0
  346. package/src/editor/core/shortcut/keys/titleKeys.ts +62 -0
  347. package/src/editor/core/worker/WorkerManager.ts +96 -0
  348. package/src/editor/core/worker/works/catalog.ts +189 -0
  349. package/src/editor/core/worker/works/group.ts +34 -0
  350. package/src/editor/core/worker/works/value.ts +32 -0
  351. package/src/editor/core/worker/works/wordCount.ts +132 -0
  352. package/src/editor/core/zone/Zone.ts +183 -0
  353. package/src/editor/core/zone/ZoneTip.ts +108 -0
  354. package/src/editor/dataset/constant/Background.ts +10 -0
  355. package/src/editor/dataset/constant/Badge.ts +6 -0
  356. package/src/editor/dataset/constant/Checkbox.ts +12 -0
  357. package/src/editor/dataset/constant/Common.ts +44 -0
  358. package/src/editor/dataset/constant/ContextMenu.ts +61 -0
  359. package/src/editor/dataset/constant/Control.ts +14 -0
  360. package/src/editor/dataset/constant/Cursor.ts +11 -0
  361. package/src/editor/dataset/constant/Editor.ts +19 -0
  362. package/src/editor/dataset/constant/Element.ts +173 -0
  363. package/src/editor/dataset/constant/Footer.ts +10 -0
  364. package/src/editor/dataset/constant/Graffiti.ts +6 -0
  365. package/src/editor/dataset/constant/Group.ts +10 -0
  366. package/src/editor/dataset/constant/Header.ts +10 -0
  367. package/src/editor/dataset/constant/ImgCaption.ts +8 -0
  368. package/src/editor/dataset/constant/Label.ts +8 -0
  369. package/src/editor/dataset/constant/LineBreak.ts +7 -0
  370. package/src/editor/dataset/constant/LineNumber.ts +11 -0
  371. package/src/editor/dataset/constant/List.ts +26 -0
  372. package/src/editor/dataset/constant/PageBorder.ts +8 -0
  373. package/src/editor/dataset/constant/PageBreak.ts +7 -0
  374. package/src/editor/dataset/constant/PageNumber.ts +22 -0
  375. package/src/editor/dataset/constant/Placeholder.ts +9 -0
  376. package/src/editor/dataset/constant/Radio.ts +12 -0
  377. package/src/editor/dataset/constant/Regular.ts +23 -0
  378. package/src/editor/dataset/constant/Separator.ts +6 -0
  379. package/src/editor/dataset/constant/Shortcut.ts +3 -0
  380. package/src/editor/dataset/constant/Table.ts +9 -0
  381. package/src/editor/dataset/constant/Title.ts +38 -0
  382. package/src/editor/dataset/constant/Watermark.ts +17 -0
  383. package/src/editor/dataset/constant/WhiteSpace.ts +7 -0
  384. package/src/editor/dataset/constant/Zone.ts +5 -0
  385. package/src/editor/dataset/enum/Area.ts +5 -0
  386. package/src/editor/dataset/enum/Background.ts +11 -0
  387. package/src/editor/dataset/enum/Block.ts +4 -0
  388. package/src/editor/dataset/enum/Common.ts +30 -0
  389. package/src/editor/dataset/enum/Control.ts +39 -0
  390. package/src/editor/dataset/enum/Editor.ts +51 -0
  391. package/src/editor/dataset/enum/Element.ts +21 -0
  392. package/src/editor/dataset/enum/ElementStyle.ts +12 -0
  393. package/src/editor/dataset/enum/Event.ts +5 -0
  394. package/src/editor/dataset/enum/KeyMap.ts +85 -0
  395. package/src/editor/dataset/enum/LineNumber.ts +4 -0
  396. package/src/editor/dataset/enum/List.ts +23 -0
  397. package/src/editor/dataset/enum/Observer.ts +6 -0
  398. package/src/editor/dataset/enum/Row.ts +7 -0
  399. package/src/editor/dataset/enum/Text.ts +13 -0
  400. package/src/editor/dataset/enum/Title.ts +8 -0
  401. package/src/editor/dataset/enum/VerticalAlign.ts +5 -0
  402. package/src/editor/dataset/enum/Watermark.ts +4 -0
  403. package/src/editor/dataset/enum/table/Table.ts +19 -0
  404. package/src/editor/dataset/enum/table/TableTool.ts +4 -0
  405. package/src/editor/index.ts +241 -0
  406. package/src/editor/interface/Area.ts +68 -0
  407. package/src/editor/interface/Background.ts +9 -0
  408. package/src/editor/interface/Badge.ts +17 -0
  409. package/src/editor/interface/Block.ts +18 -0
  410. package/src/editor/interface/Catalog.ts +11 -0
  411. package/src/editor/interface/Checkbox.ts +17 -0
  412. package/src/editor/interface/Command.ts +3 -0
  413. package/src/editor/interface/Common.ts +43 -0
  414. package/src/editor/interface/Control.ts +250 -0
  415. package/src/editor/interface/Cursor.ts +7 -0
  416. package/src/editor/interface/Draw.ts +86 -0
  417. package/src/editor/interface/Editor.ts +172 -0
  418. package/src/editor/interface/Element.ts +273 -0
  419. package/src/editor/interface/Event.ts +27 -0
  420. package/src/editor/interface/EventBus.ts +46 -0
  421. package/src/editor/interface/Footer.ts +9 -0
  422. package/src/editor/interface/Graffiti.ts +15 -0
  423. package/src/editor/interface/Group.ts +8 -0
  424. package/src/editor/interface/Header.ts +9 -0
  425. package/src/editor/interface/Label.ts +8 -0
  426. package/src/editor/interface/LineBreak.ts +5 -0
  427. package/src/editor/interface/LineNumber.ts +10 -0
  428. package/src/editor/interface/Listener.ts +88 -0
  429. package/src/editor/interface/Margin.ts +1 -0
  430. package/src/editor/interface/PageBorder.ts +8 -0
  431. package/src/editor/interface/PageBreak.ts +5 -0
  432. package/src/editor/interface/PageNumber.ts +16 -0
  433. package/src/editor/interface/Placeholder.ts +7 -0
  434. package/src/editor/interface/Plugin.ts +8 -0
  435. package/src/editor/interface/Position.ts +113 -0
  436. package/src/editor/interface/Previewer.ts +15 -0
  437. package/src/editor/interface/Radio.ts +17 -0
  438. package/src/editor/interface/Range.ts +61 -0
  439. package/src/editor/interface/Row.ts +25 -0
  440. package/src/editor/interface/Search.ts +36 -0
  441. package/src/editor/interface/Separator.ts +4 -0
  442. package/src/editor/interface/Text.ts +15 -0
  443. package/src/editor/interface/Title.ts +32 -0
  444. package/src/editor/interface/Watermark.ts +16 -0
  445. package/src/editor/interface/WhiteSpace.ts +5 -0
  446. package/src/editor/interface/Zone.ts +3 -0
  447. package/src/editor/interface/contextmenu/ContextMenu.ts +76 -0
  448. package/src/editor/interface/i18n/I18n.ts +7 -0
  449. package/src/editor/interface/shortcut/Shortcut.ts +14 -0
  450. package/src/editor/interface/table/Colgroup.ts +4 -0
  451. package/src/editor/interface/table/Table.ts +9 -0
  452. package/src/editor/interface/table/Td.ts +36 -0
  453. package/src/editor/interface/table/Tr.ts +11 -0
  454. package/src/editor/types/index.d.ts +5 -0
  455. package/src/editor/utils/clipboard.ts +94 -0
  456. package/src/editor/utils/element.ts +1877 -0
  457. package/src/editor/utils/hotkey.ts +5 -0
  458. package/src/editor/utils/index.ts +445 -0
  459. package/src/editor/utils/option.ts +253 -0
  460. package/src/editor/utils/paragraph.ts +28 -0
  461. package/src/editor/utils/print.ts +99 -0
  462. package/src/editor/utils/ua.ts +13 -0
  463. package/src/main.ts +2053 -0
  464. package/src/mock.ts +611 -0
  465. package/src/plugins/copy/index.ts +30 -0
  466. package/src/plugins/markdown/index.ts +118 -0
  467. package/src/style.css +1079 -0
  468. package/src/utils/index.ts +45 -0
  469. package/src/utils/prism.ts +89 -0
  470. package/src/vite-env.d.ts +1 -0
  471. package/tsconfig.json +25 -0
  472. package/vite.config.ts +46 -0
@@ -0,0 +1,1877 @@
1
+ import {
2
+ cloneProperty,
3
+ deepClone,
4
+ deepCloneOmitKeys,
5
+ deleteProperty,
6
+ getUUID,
7
+ isArrayEqual,
8
+ omitObject,
9
+ pickObject,
10
+ splitText
11
+ } from '.'
12
+ import { IFrameBlock } from '../core/draw/particle/block/modules/IFrameBlock'
13
+ import { LaTexParticle } from '../core/draw/particle/latex/LaTexParticle'
14
+ import { NON_BREAKING_SPACE, ZERO } from '../dataset/constant/Common'
15
+ import {
16
+ AREA_CONTEXT_ATTR,
17
+ BLOCK_ELEMENT_TYPE,
18
+ CONTROL_STYLE_ATTR,
19
+ EDITOR_ELEMENT_CONTEXT_ATTR,
20
+ EDITOR_ELEMENT_ZIP_ATTR,
21
+ EDITOR_ROW_ATTR,
22
+ INLINE_NODE_NAME,
23
+ TABLE_CONTEXT_ATTR,
24
+ TABLE_TD_ZIP_ATTR,
25
+ TEXTLIKE_ELEMENT_TYPE,
26
+ TITLE_CONTEXT_ATTR
27
+ } from '../dataset/constant/Element'
28
+ import {
29
+ listStyleCSSMapping,
30
+ listTypeElementMapping,
31
+ ulStyleMapping
32
+ } from '../dataset/constant/List'
33
+ import { START_LINE_BREAK_REG } from '../dataset/constant/Regular'
34
+ import {
35
+ titleNodeNameMapping,
36
+ titleOrderNumberMapping,
37
+ titleSizeMapping
38
+ } from '../dataset/constant/Title'
39
+ import { BlockType } from '../dataset/enum/Block'
40
+ import { ImageDisplay, LocationPosition } from '../dataset/enum/Common'
41
+ import { ControlComponent, ControlType } from '../dataset/enum/Control'
42
+ import { EditorMode } from '../dataset/enum/Editor'
43
+ import { ElementType } from '../dataset/enum/Element'
44
+ import { ListStyle, ListType, UlStyle } from '../dataset/enum/List'
45
+ import { RowFlex } from '../dataset/enum/Row'
46
+ import { TableBorder, TdBorder } from '../dataset/enum/table/Table'
47
+ import { VerticalAlign } from '../dataset/enum/VerticalAlign'
48
+ import { DeepRequired } from '../interface/Common'
49
+ import { IControlSelect } from '../interface/Control'
50
+ import { IEditorOption } from '../interface/Editor'
51
+ import { IElement } from '../interface/Element'
52
+ import { IRowElement } from '../interface/Row'
53
+ import { ITd } from '../interface/table/Td'
54
+ import { ITr } from '../interface/table/Tr'
55
+ import { mergeOption } from './option'
56
+
57
+ export function unzipElementList(elementList: IElement[]): IElement[] {
58
+ const result: IElement[] = []
59
+ for (let v = 0; v < elementList.length; v++) {
60
+ const valueItem = elementList[v]
61
+ const textList = splitText(valueItem.value)
62
+ for (let d = 0; d < textList.length; d++) {
63
+ result.push({ ...valueItem, value: textList[d] })
64
+ }
65
+ }
66
+ return result
67
+ }
68
+
69
+ interface IFormatElementListOption {
70
+ isHandleFirstElement?: boolean // 根据上下文确定首字符处理逻辑(处理首字符补偿)
71
+ isForceCompensation?: boolean // 强制补偿字符
72
+ editorOptions: DeepRequired<IEditorOption>
73
+ }
74
+
75
+ export function formatElementList(
76
+ elementList: IElement[],
77
+ options: IFormatElementListOption
78
+ ) {
79
+ const {
80
+ isHandleFirstElement = true,
81
+ isForceCompensation = false,
82
+ editorOptions
83
+ } = options
84
+ const startElement = elementList[0]
85
+ // 非首字符零宽节点文本元素则补偿-列表元素内部会补偿此处忽略
86
+ if (
87
+ isForceCompensation ||
88
+ (isHandleFirstElement &&
89
+ startElement?.type !== ElementType.LIST &&
90
+ ((startElement?.type && startElement.type !== ElementType.TEXT) ||
91
+ !START_LINE_BREAK_REG.test(startElement?.value)))
92
+ ) {
93
+ elementList.unshift({
94
+ value: ZERO
95
+ })
96
+ }
97
+ let i = 0
98
+ while (i < elementList.length) {
99
+ let el = elementList[i]
100
+ // 优先处理虚拟元素
101
+ if (el.type === ElementType.TITLE) {
102
+ // 移除父节点
103
+ elementList.splice(i, 1)
104
+ // 格式化元素
105
+ const valueList = el.valueList || []
106
+ formatElementList(valueList, {
107
+ ...options,
108
+ isHandleFirstElement: false,
109
+ isForceCompensation: false
110
+ })
111
+ // 追加节点
112
+ if (valueList.length) {
113
+ const titleId = el.titleId || getUUID()
114
+ const titleOptions = editorOptions.title
115
+ for (let v = 0; v < valueList.length; v++) {
116
+ const value = valueList[v]
117
+ value.title = el.title
118
+ if (el.level) {
119
+ value.titleId = titleId
120
+ value.level = el.level
121
+ }
122
+ // 文本型元素设置字体及加粗
123
+ if (isTextLikeElement(value)) {
124
+ if (!value.size) {
125
+ value.size = titleOptions[titleSizeMapping[value.level!]]
126
+ }
127
+ if (value.bold === undefined) {
128
+ value.bold = true
129
+ }
130
+ }
131
+ elementList.splice(i, 0, value)
132
+ i++
133
+ }
134
+ }
135
+ i--
136
+ } else if (el.type === ElementType.LIST) {
137
+ // 移除父节点
138
+ elementList.splice(i, 1)
139
+ // 格式化元素
140
+ const valueList = el.valueList || []
141
+ formatElementList(valueList, {
142
+ ...options,
143
+ isHandleFirstElement: true,
144
+ isForceCompensation: false
145
+ })
146
+ // 追加节点
147
+ if (valueList.length) {
148
+ const listId = getUUID()
149
+ for (let v = 0; v < valueList.length; v++) {
150
+ const value = valueList[v]
151
+ value.listId = listId
152
+ value.listType = el.listType
153
+ value.listStyle = el.listStyle
154
+ elementList.splice(i, 0, value)
155
+ i++
156
+ }
157
+ }
158
+ i--
159
+ } else if (el.type === ElementType.AREA) {
160
+ // 移除父节点
161
+ elementList.splice(i, 1)
162
+ // 格式化元素
163
+ const valueList = el?.valueList || []
164
+ formatElementList(valueList, {
165
+ ...options,
166
+ isHandleFirstElement: true,
167
+ isForceCompensation: true
168
+ })
169
+ if (valueList.length) {
170
+ const areaId = getUUID()
171
+ for (let v = 0; v < valueList.length; v++) {
172
+ const value = valueList[v]
173
+ value.areaId = el.areaId || areaId
174
+ value.area = el.area
175
+ value.areaIndex = v
176
+ if (value.type === ElementType.TABLE) {
177
+ const trList = value.trList!
178
+ for (let r = 0; r < trList.length; r++) {
179
+ const tr = trList[r]
180
+ for (let d = 0; d < tr.tdList.length; d++) {
181
+ const td = tr.tdList[d]
182
+ const tdValueList = td.value
183
+ for (let t = 0; t < tdValueList.length; t++) {
184
+ const tdValue = tdValueList[t]
185
+ tdValue.areaId = el.areaId || areaId
186
+ tdValue.area = el.area
187
+ }
188
+ }
189
+ }
190
+ }
191
+ elementList.splice(i, 0, value)
192
+ i++
193
+ }
194
+ }
195
+ i--
196
+ } else if (el.type === ElementType.TABLE) {
197
+ const tableId = el.id || getUUID()
198
+ el.id = tableId
199
+ if (el.trList) {
200
+ const { defaultTrMinHeight } = editorOptions.table
201
+ for (let t = 0; t < el.trList.length; t++) {
202
+ const tr = el.trList[t]
203
+ const trId = tr.id || getUUID()
204
+ tr.id = trId
205
+ if (!tr.minHeight || tr.minHeight < defaultTrMinHeight) {
206
+ tr.minHeight = defaultTrMinHeight
207
+ }
208
+ if (tr.height < tr.minHeight) {
209
+ tr.height = tr.minHeight
210
+ }
211
+ for (let d = 0; d < tr.tdList.length; d++) {
212
+ const td = tr.tdList[d]
213
+ const tdId = td.id || getUUID()
214
+ td.id = tdId
215
+ formatElementList(td.value, {
216
+ ...options,
217
+ isHandleFirstElement: true,
218
+ isForceCompensation: true
219
+ })
220
+ // 首字符字体大小默认使用首个字符元素字体大小
221
+ if (
222
+ !td.value[0].size &&
223
+ td.value[1]?.size &&
224
+ isTextLikeElement(td.value[1])
225
+ ) {
226
+ td.value[0].size = td.value[1].size
227
+ }
228
+ for (let v = 0; v < td.value.length; v++) {
229
+ const value = td.value[v]
230
+ value.tdId = tdId
231
+ value.trId = trId
232
+ value.tableId = tableId
233
+ }
234
+ }
235
+ }
236
+ }
237
+ } else if (el.type === ElementType.HYPERLINK) {
238
+ // 移除父节点
239
+ elementList.splice(i, 1)
240
+ // 元素展开
241
+ const valueList = unzipElementList(el.valueList || [])
242
+ // 追加节点
243
+ if (valueList.length) {
244
+ const hyperlinkId = getUUID()
245
+ for (let v = 0; v < valueList.length; v++) {
246
+ const value = valueList[v]
247
+ value.type = el.type
248
+ value.url = el.url
249
+ value.hyperlinkId = hyperlinkId
250
+ elementList.splice(i, 0, value)
251
+ i++
252
+ }
253
+ }
254
+ i--
255
+ } else if (el.type === ElementType.DATE) {
256
+ // 移除父节点
257
+ elementList.splice(i, 1)
258
+ // 元素展开
259
+ const valueList = unzipElementList(el.valueList || [])
260
+ // 追加节点
261
+ if (valueList.length) {
262
+ const dateId = getUUID()
263
+ for (let v = 0; v < valueList.length; v++) {
264
+ const value = valueList[v]
265
+ value.type = el.type
266
+ value.dateFormat = el.dateFormat
267
+ value.dateId = dateId
268
+ elementList.splice(i, 0, value)
269
+ i++
270
+ }
271
+ }
272
+ i--
273
+ } else if (el.type === ElementType.CONTROL) {
274
+ // 兼容控件内容类型错误
275
+ if (!el.control) {
276
+ i++
277
+ continue
278
+ }
279
+ const {
280
+ prefix,
281
+ postfix,
282
+ preText,
283
+ postText,
284
+ value,
285
+ placeholder,
286
+ code,
287
+ type,
288
+ valueSets
289
+ } = el.control
290
+ const {
291
+ editorOptions: {
292
+ control: controlOption,
293
+ checkbox: checkboxOption,
294
+ radio: radioOption
295
+ }
296
+ } = options
297
+ const controlId = el.controlId || getUUID()
298
+ // 移除父节点
299
+ elementList.splice(i, 1)
300
+ // 控件上下文提取(压缩后的控件上下文无法提取)
301
+ const controlContext = pickObject(el, [
302
+ ...EDITOR_ELEMENT_CONTEXT_ATTR,
303
+ ...EDITOR_ROW_ATTR
304
+ ])
305
+ // 控件设置的默认样式(以前缀为基准)
306
+ const controlDefaultStyle = pickObject(
307
+ <IElement>(<unknown>el.control),
308
+ CONTROL_STYLE_ATTR
309
+ )
310
+ // 前后缀个性化设置
311
+ const thePrePostfixArg: Omit<IElement, 'value'> = {
312
+ ...controlDefaultStyle,
313
+ color: editorOptions.control.bracketColor
314
+ }
315
+ // 前缀
316
+ const prefixStrList = splitText(prefix || controlOption.prefix)
317
+ for (let p = 0; p < prefixStrList.length; p++) {
318
+ const value = prefixStrList[p]
319
+ elementList.splice(i, 0, {
320
+ ...controlContext,
321
+ ...thePrePostfixArg,
322
+ controlId,
323
+ value,
324
+ type: el.type,
325
+ control: el.control,
326
+ controlComponent: ControlComponent.PREFIX
327
+ })
328
+ i++
329
+ }
330
+ // 前文本
331
+ if (preText) {
332
+ const preTextStrList = splitText(preText)
333
+ for (let p = 0; p < preTextStrList.length; p++) {
334
+ const value = preTextStrList[p]
335
+ elementList.splice(i, 0, {
336
+ ...controlContext,
337
+ ...controlDefaultStyle,
338
+ controlId,
339
+ value,
340
+ type: el.type,
341
+ control: el.control,
342
+ controlComponent: ControlComponent.PRE_TEXT
343
+ })
344
+ i++
345
+ }
346
+ }
347
+ // 值
348
+ if (
349
+ (value && value.length) ||
350
+ type === ControlType.CHECKBOX ||
351
+ type === ControlType.RADIO ||
352
+ (type === ControlType.SELECT && code && (!value || !value.length))
353
+ ) {
354
+ let valueList: IElement[] = value ? deepClone(value) : []
355
+ if (type === ControlType.CHECKBOX) {
356
+ const codeList = code ? code.split(',') : []
357
+ if (Array.isArray(valueSets) && valueSets.length) {
358
+ // 拆分valueList优先使用其属性
359
+ const valueStyleList = valueList.reduce(
360
+ (pre, cur) =>
361
+ pre.concat(
362
+ cur.value.split('').map(v => ({ ...cur, value: v }))
363
+ ),
364
+ [] as IElement[]
365
+ )
366
+ let valueStyleIndex = 0
367
+ for (let v = 0; v < valueSets.length; v++) {
368
+ const valueSet = valueSets[v]
369
+ // checkbox组件
370
+ elementList.splice(i, 0, {
371
+ ...controlContext,
372
+ ...controlDefaultStyle,
373
+ controlId,
374
+ value: '',
375
+ type: el.type,
376
+ control: el.control,
377
+ controlComponent: ControlComponent.CHECKBOX,
378
+ checkbox: {
379
+ code: valueSet.code,
380
+ value: codeList.includes(valueSet.code)
381
+ }
382
+ })
383
+ i++
384
+ // 文本
385
+ const valueStrList = splitText(valueSet.value)
386
+ for (let e = 0; e < valueStrList.length; e++) {
387
+ const value = valueStrList[e]
388
+ const isLastLetter = e === valueStrList.length - 1
389
+ elementList.splice(i, 0, {
390
+ ...controlContext,
391
+ ...controlDefaultStyle,
392
+ ...valueStyleList[valueStyleIndex],
393
+ controlId,
394
+ value: value === '\n' ? ZERO : value,
395
+ letterSpacing: isLastLetter ? checkboxOption.gap : 0,
396
+ control: el.control,
397
+ controlComponent: ControlComponent.VALUE
398
+ })
399
+ valueStyleIndex++
400
+ i++
401
+ }
402
+ }
403
+ }
404
+ } else if (type === ControlType.RADIO) {
405
+ if (Array.isArray(valueSets) && valueSets.length) {
406
+ // 拆分valueList优先使用其属性
407
+ const valueStyleList = valueList.reduce(
408
+ (pre, cur) =>
409
+ pre.concat(
410
+ cur.value.split('').map(v => ({ ...cur, value: v }))
411
+ ),
412
+ [] as IElement[]
413
+ )
414
+ let valueStyleIndex = 0
415
+ for (let v = 0; v < valueSets.length; v++) {
416
+ const valueSet = valueSets[v]
417
+ // radio组件
418
+ elementList.splice(i, 0, {
419
+ ...controlContext,
420
+ ...controlDefaultStyle,
421
+ controlId,
422
+ value: '',
423
+ type: el.type,
424
+ control: el.control,
425
+ controlComponent: ControlComponent.RADIO,
426
+ radio: {
427
+ code: valueSet.code,
428
+ value: code === valueSet.code
429
+ }
430
+ })
431
+ i++
432
+ // 文本
433
+ const valueStrList = splitText(valueSet.value)
434
+ for (let e = 0; e < valueStrList.length; e++) {
435
+ const value = valueStrList[e]
436
+ const isLastLetter = e === valueStrList.length - 1
437
+ elementList.splice(i, 0, {
438
+ ...controlContext,
439
+ ...controlDefaultStyle,
440
+ ...valueStyleList[valueStyleIndex],
441
+ controlId,
442
+ value: value === '\n' ? ZERO : value,
443
+ letterSpacing: isLastLetter ? radioOption.gap : 0,
444
+ control: el.control,
445
+ controlComponent: ControlComponent.VALUE
446
+ })
447
+ valueStyleIndex++
448
+ i++
449
+ }
450
+ }
451
+ }
452
+ } else {
453
+ if (!value || !value.length) {
454
+ if (Array.isArray(valueSets) && valueSets.length) {
455
+ const valueSet = valueSets.find(v => v.code === code)
456
+ if (valueSet) {
457
+ valueList = [
458
+ {
459
+ value: valueSet.value
460
+ }
461
+ ]
462
+ }
463
+ }
464
+ }
465
+ formatElementList(valueList, {
466
+ ...options,
467
+ isHandleFirstElement: false,
468
+ isForceCompensation: false
469
+ })
470
+ for (let v = 0; v < valueList.length; v++) {
471
+ const element = valueList[v]
472
+ const value = element.value
473
+ elementList.splice(i, 0, {
474
+ ...controlContext,
475
+ ...controlDefaultStyle,
476
+ ...element,
477
+ controlId,
478
+ value: value === '\n' ? ZERO : value,
479
+ type: element.type || ElementType.TEXT,
480
+ control: el.control,
481
+ controlComponent: ControlComponent.VALUE
482
+ })
483
+ i++
484
+ }
485
+ }
486
+ } else if (placeholder) {
487
+ // placeholder
488
+ const thePlaceholderArgs: Omit<IElement, 'value'> = {
489
+ ...controlDefaultStyle,
490
+ color: editorOptions.control.placeholderColor
491
+ }
492
+ const placeholderStrList = splitText(placeholder)
493
+ for (let p = 0; p < placeholderStrList.length; p++) {
494
+ const value = placeholderStrList[p]
495
+ elementList.splice(i, 0, {
496
+ ...controlContext,
497
+ ...thePlaceholderArgs,
498
+ controlId,
499
+ value: value === '\n' ? ZERO : value,
500
+ type: el.type,
501
+ control: el.control,
502
+ controlComponent: ControlComponent.PLACEHOLDER
503
+ })
504
+ i++
505
+ }
506
+ }
507
+ // 后文本
508
+ if (postText) {
509
+ const postTextStrList = splitText(postText)
510
+ for (let p = 0; p < postTextStrList.length; p++) {
511
+ const value = postTextStrList[p]
512
+ elementList.splice(i, 0, {
513
+ ...controlContext,
514
+ ...controlDefaultStyle,
515
+ controlId,
516
+ value,
517
+ type: el.type,
518
+ control: el.control,
519
+ controlComponent: ControlComponent.POST_TEXT
520
+ })
521
+ i++
522
+ }
523
+ }
524
+ // 后缀
525
+ const postfixStrList = splitText(postfix || controlOption.postfix)
526
+ for (let p = 0; p < postfixStrList.length; p++) {
527
+ const value = postfixStrList[p]
528
+ elementList.splice(i, 0, {
529
+ ...controlContext,
530
+ ...thePrePostfixArg,
531
+ controlId,
532
+ value,
533
+ type: el.type,
534
+ control: el.control,
535
+ controlComponent: ControlComponent.POSTFIX
536
+ })
537
+ i++
538
+ }
539
+ i--
540
+ } else if (
541
+ (!el.type || TEXTLIKE_ELEMENT_TYPE.includes(el.type)) &&
542
+ el.value?.length > 1
543
+ ) {
544
+ elementList.splice(i, 1)
545
+ const valueList = splitText(el.value)
546
+ for (let v = 0; v < valueList.length; v++) {
547
+ elementList.splice(i + v, 0, { ...el, value: valueList[v] })
548
+ }
549
+ el = elementList[i]
550
+ }
551
+ if (el.value === '\n' || el.value == '\r\n') {
552
+ el.value = ZERO
553
+ }
554
+ if (el.type === ElementType.IMAGE || el.type === ElementType.BLOCK) {
555
+ el.id = el.id || getUUID()
556
+ }
557
+ if (el.type === ElementType.LATEX) {
558
+ const { svg, width, height } = LaTexParticle.convertLaTextToSVG(el.value)
559
+ el.width = el.width || width
560
+ el.height = el.height || height
561
+ el.laTexSVG = svg
562
+ el.id = el.id || getUUID()
563
+ }
564
+ i++
565
+ }
566
+ }
567
+
568
+ export function isSameElementExceptValue(
569
+ source: IElement,
570
+ target: IElement
571
+ ): boolean {
572
+ const sourceKeys = Object.keys(source)
573
+ const targetKeys = Object.keys(target)
574
+ if (sourceKeys.length !== targetKeys.length) return false
575
+ for (let s = 0; s < sourceKeys.length; s++) {
576
+ const key = sourceKeys[s] as never
577
+ // 值不需要校验
578
+ if (key === 'value') continue
579
+ // groupIds数组需特殊校验数组是否相等
580
+ if (
581
+ key === 'groupIds' &&
582
+ Array.isArray(source[key]) &&
583
+ Array.isArray(target[key]) &&
584
+ isArrayEqual(source[key], target[key])
585
+ ) {
586
+ continue
587
+ }
588
+ if (source[key] !== target[key]) {
589
+ return false
590
+ }
591
+ }
592
+ return true
593
+ }
594
+ interface IPickElementOption {
595
+ extraPickAttrs?: Array<keyof IElement>
596
+ }
597
+ export function pickElementAttr(
598
+ payload: IElement,
599
+ option: IPickElementOption = {}
600
+ ): IElement {
601
+ const { extraPickAttrs } = option
602
+ const zipAttrs = [...EDITOR_ELEMENT_ZIP_ATTR]
603
+ if (extraPickAttrs) {
604
+ zipAttrs.push(...extraPickAttrs)
605
+ }
606
+ const element: IElement = {
607
+ value: payload.value === ZERO ? `\n` : payload.value
608
+ }
609
+ zipAttrs.forEach(attr => {
610
+ const value = payload[attr] as never
611
+ if (value !== undefined) {
612
+ element[attr] = value
613
+ }
614
+ })
615
+ return element
616
+ }
617
+
618
+ interface IZipElementListOption {
619
+ extraPickAttrs?: Array<keyof IElement>
620
+ isClassifyArea?: boolean
621
+ isClone?: boolean
622
+ }
623
+ export function zipElementList(
624
+ payload: IElement[],
625
+ options: IZipElementListOption = {}
626
+ ): IElement[] {
627
+ const { extraPickAttrs, isClassifyArea = false, isClone = true } = options
628
+ const elementList = isClone ? deepClone(payload) : payload
629
+ const zipElementListData: IElement[] = []
630
+ let e = 0
631
+ while (e < elementList.length) {
632
+ let element = elementList[e]
633
+ // 上下文首字符(占位符)-列表首字符要保留避免是复选框
634
+ if (
635
+ e === 0 &&
636
+ element.value === ZERO &&
637
+ !element.listId &&
638
+ (!element.type || element.type === ElementType.TEXT)
639
+ ) {
640
+ e++
641
+ continue
642
+ }
643
+ // 优先处理虚拟元素,后表格、超链接、日期、控件特殊处理
644
+ if (element.areaId) {
645
+ const areaId = element.areaId
646
+ const area = element.area
647
+ // 收集并压缩数据
648
+ const valueList: IElement[] = []
649
+ while (e < elementList.length) {
650
+ const areaE = elementList[e]
651
+ if (areaId !== areaE.areaId) {
652
+ e--
653
+ break
654
+ }
655
+ delete areaE.area
656
+ delete areaE.areaId
657
+ valueList.push(areaE)
658
+ e++
659
+ }
660
+ const areaElementList = zipElementList(valueList, options)
661
+ // 不归类区域元素
662
+ if (isClassifyArea) {
663
+ const areaElement: IElement = {
664
+ type: ElementType.AREA,
665
+ value: '',
666
+ areaId,
667
+ area
668
+ }
669
+ areaElement.valueList = areaElementList
670
+ element = areaElement
671
+ } else {
672
+ zipElementListData.splice(e, 0, ...areaElementList)
673
+ continue
674
+ }
675
+ } else if (element.titleId && element.level) {
676
+ // 标题处理
677
+ const titleId = element.titleId
678
+ if (titleId) {
679
+ const level = element.level
680
+ const titleElement: IElement = {
681
+ type: ElementType.TITLE,
682
+ title: element.title,
683
+ titleId,
684
+ value: '',
685
+ level
686
+ }
687
+ const valueList: IElement[] = []
688
+ while (e < elementList.length) {
689
+ const titleE = elementList[e]
690
+ if (titleId !== titleE.titleId) {
691
+ e--
692
+ break
693
+ }
694
+ delete titleE.level
695
+ delete titleE.title
696
+ valueList.push(titleE)
697
+ e++
698
+ }
699
+ titleElement.valueList = zipElementList(valueList, options)
700
+ element = titleElement
701
+ }
702
+ } else if (element.listId && element.listType) {
703
+ // 列表处理
704
+ const listId = element.listId
705
+ if (listId) {
706
+ const listType = element.listType
707
+ const listStyle = element.listStyle
708
+ const listElement: IElement = {
709
+ type: ElementType.LIST,
710
+ value: '',
711
+ listId,
712
+ listType,
713
+ listStyle
714
+ }
715
+ const valueList: IElement[] = []
716
+ while (e < elementList.length) {
717
+ const listE = elementList[e]
718
+ if (listId !== listE.listId) {
719
+ e--
720
+ break
721
+ }
722
+ delete listE.listType
723
+ delete listE.listStyle
724
+ valueList.push(listE)
725
+ e++
726
+ }
727
+ listElement.valueList = zipElementList(valueList, options)
728
+ element = listElement
729
+ }
730
+ } else if (element.type === ElementType.TABLE) {
731
+ // 分页表格先进行合并
732
+ if (element.pagingId) {
733
+ let tableIndex = e + 1
734
+ let combineCount = 0
735
+ while (tableIndex < elementList.length) {
736
+ const nextElement = elementList[tableIndex]
737
+ if (nextElement.pagingId === element.pagingId) {
738
+ element.height! += nextElement.height!
739
+ element.trList!.push(...nextElement.trList!)
740
+ tableIndex++
741
+ combineCount++
742
+ } else {
743
+ break
744
+ }
745
+ }
746
+ e += combineCount
747
+ }
748
+ if (element.trList) {
749
+ for (let t = 0; t < element.trList.length; t++) {
750
+ const tr = element.trList[t]
751
+ delete tr.id
752
+ for (let d = 0; d < tr.tdList.length; d++) {
753
+ const td = tr.tdList[d]
754
+ const zipTd: ITd = {
755
+ colspan: td.colspan,
756
+ rowspan: td.rowspan,
757
+ value: zipElementList(td.value, {
758
+ ...options,
759
+ isClassifyArea: false
760
+ })
761
+ }
762
+ // 压缩单元格属性
763
+ TABLE_TD_ZIP_ATTR.forEach(attr => {
764
+ const value = td[attr] as never
765
+ if (value !== undefined) {
766
+ zipTd[attr] = value
767
+ }
768
+ })
769
+ tr.tdList[d] = zipTd
770
+ }
771
+ }
772
+ }
773
+ } else if (element.type === ElementType.HYPERLINK) {
774
+ // 超链接处理
775
+ const hyperlinkId = element.hyperlinkId
776
+ if (hyperlinkId) {
777
+ const hyperlinkElement: IElement = {
778
+ type: ElementType.HYPERLINK,
779
+ value: '',
780
+ url: element.url
781
+ }
782
+ const valueList: IElement[] = []
783
+ while (e < elementList.length) {
784
+ const hyperlinkE = elementList[e]
785
+ if (hyperlinkId !== hyperlinkE.hyperlinkId) {
786
+ e--
787
+ break
788
+ }
789
+ delete hyperlinkE.type
790
+ delete hyperlinkE.url
791
+ valueList.push(hyperlinkE)
792
+ e++
793
+ }
794
+ hyperlinkElement.valueList = zipElementList(valueList, options)
795
+ element = hyperlinkElement
796
+ }
797
+ } else if (element.type === ElementType.DATE) {
798
+ const dateId = element.dateId
799
+ if (dateId) {
800
+ const dateElement: IElement = {
801
+ type: ElementType.DATE,
802
+ value: '',
803
+ dateFormat: element.dateFormat
804
+ }
805
+ const valueList: IElement[] = []
806
+ while (e < elementList.length) {
807
+ const dateE = elementList[e]
808
+ if (dateId !== dateE.dateId) {
809
+ e--
810
+ break
811
+ }
812
+ delete dateE.type
813
+ delete dateE.dateFormat
814
+ valueList.push(dateE)
815
+ e++
816
+ }
817
+ dateElement.valueList = zipElementList(valueList, options)
818
+ element = dateElement
819
+ }
820
+ } else if (element.controlId) {
821
+ const controlId = element.controlId
822
+ // 控件包含前后缀则转换为控件
823
+ if (element.controlComponent === ControlComponent.PREFIX) {
824
+ const valueList: IElement[] = []
825
+ let isFull = false
826
+ let start = e
827
+ while (start < elementList.length) {
828
+ const controlE = elementList[start]
829
+ if (controlId !== controlE.controlId) break
830
+ if (controlE.controlComponent === ControlComponent.VALUE) {
831
+ delete controlE.control
832
+ delete controlE.controlId
833
+ valueList.push(controlE)
834
+ }
835
+ if (controlE.controlComponent === ControlComponent.POSTFIX) {
836
+ isFull = true
837
+ }
838
+ start++
839
+ }
840
+ if (isFull) {
841
+ // 以前缀为基准更新控件默认样式
842
+ const controlDefaultStyle = <IControlSelect>(
843
+ (<unknown>pickObject(element, CONTROL_STYLE_ATTR))
844
+ )
845
+ const control = {
846
+ ...element.control!,
847
+ ...controlDefaultStyle
848
+ }
849
+ const controlElement: IElement = {
850
+ ...pickObject(element, EDITOR_ROW_ATTR),
851
+ type: ElementType.CONTROL,
852
+ value: '',
853
+ control,
854
+ controlId
855
+ }
856
+ controlElement.control!.value = zipElementList(valueList, options)
857
+ element = pickElementAttr(controlElement, { extraPickAttrs })
858
+ // 控件元素数量 - 1(当前元素)
859
+ e += start - e - 1
860
+ }
861
+ }
862
+ // 不完整的控件元素不转化为控件,如果不是文本则直接忽略
863
+ if (element.controlComponent) {
864
+ delete element.control
865
+ delete element.controlId
866
+ if (
867
+ element.controlComponent !== ControlComponent.VALUE &&
868
+ element.controlComponent !== ControlComponent.PRE_TEXT &&
869
+ element.controlComponent !== ControlComponent.POST_TEXT
870
+ ) {
871
+ e++
872
+ continue
873
+ }
874
+ }
875
+ }
876
+ // 组合元素
877
+ const pickElement = pickElementAttr(element, { extraPickAttrs })
878
+ if (
879
+ !element.type ||
880
+ element.type === ElementType.TEXT ||
881
+ element.type === ElementType.SUBSCRIPT ||
882
+ element.type === ElementType.SUPERSCRIPT
883
+ ) {
884
+ while (e < elementList.length) {
885
+ const nextElement = elementList[e + 1]
886
+ e++
887
+ if (
888
+ nextElement &&
889
+ isSameElementExceptValue(
890
+ pickElement,
891
+ pickElementAttr(nextElement, { extraPickAttrs })
892
+ )
893
+ ) {
894
+ const nextValue =
895
+ nextElement.value === ZERO ? '\n' : nextElement.value
896
+ pickElement.value += nextValue
897
+ } else {
898
+ break
899
+ }
900
+ }
901
+ } else {
902
+ e++
903
+ }
904
+ zipElementListData.push(pickElement)
905
+ }
906
+ return zipElementListData
907
+ }
908
+
909
+ export function convertTextAlignToRowFlex(node: HTMLElement) {
910
+ const textAlign = window.getComputedStyle(node).textAlign
911
+ switch (textAlign) {
912
+ case 'left':
913
+ case 'start':
914
+ return RowFlex.LEFT
915
+ case 'center':
916
+ return RowFlex.CENTER
917
+ case 'right':
918
+ case 'end':
919
+ return RowFlex.RIGHT
920
+ case 'justify':
921
+ return RowFlex.ALIGNMENT
922
+ case 'justify-all':
923
+ return RowFlex.JUSTIFY
924
+ default:
925
+ return RowFlex.LEFT
926
+ }
927
+ }
928
+
929
+ export function convertRowFlexToTextAlign(rowFlex: RowFlex) {
930
+ return rowFlex === RowFlex.ALIGNMENT ? 'justify' : rowFlex
931
+ }
932
+
933
+ export function convertRowFlexToJustifyContent(rowFlex: RowFlex) {
934
+ switch (rowFlex) {
935
+ case RowFlex.LEFT:
936
+ return 'flex-start'
937
+ case RowFlex.CENTER:
938
+ return 'center'
939
+ case RowFlex.RIGHT:
940
+ return 'flex-end'
941
+ case RowFlex.ALIGNMENT:
942
+ case RowFlex.JUSTIFY:
943
+ return 'space-between'
944
+ default:
945
+ return 'flex-start'
946
+ }
947
+ }
948
+
949
+ export function isTextLikeElement(element: IElement): boolean {
950
+ return !element.type || TEXTLIKE_ELEMENT_TYPE.includes(element.type)
951
+ }
952
+
953
+ export function isTextElement(element: IElement): boolean {
954
+ return !element.type || element.type === ElementType.TEXT
955
+ }
956
+
957
+ export function getElementListText(elementList: IElement[]): string {
958
+ return elementList
959
+ .filter(el => isTextLikeElement(el))
960
+ .map(el => el.value)
961
+ .join('')
962
+ .replace(new RegExp(ZERO, 'g'), '')
963
+ }
964
+
965
+ export function getAnchorElement(
966
+ elementList: IElement[],
967
+ anchorIndex: number
968
+ ): IElement | null {
969
+ const anchorElement = elementList[anchorIndex]
970
+ if (!anchorElement) return null
971
+ const anchorNextElement = elementList[anchorIndex + 1]
972
+ // 非列表元素 && 当前元素是换行符 && 下一个元素不是换行符 && 区域相同 => 则以下一个元素作为参考元素
973
+ return !anchorElement.listId &&
974
+ anchorElement.value === ZERO &&
975
+ anchorNextElement &&
976
+ anchorNextElement.value !== ZERO &&
977
+ anchorElement.areaId === anchorNextElement.areaId
978
+ ? anchorNextElement
979
+ : anchorElement
980
+ }
981
+
982
+ export interface IFormatElementContextOption {
983
+ isBreakWhenWrap?: boolean
984
+ ignoreContextKeys?: Array<keyof IElement>
985
+ editorOptions?: DeepRequired<IEditorOption>
986
+ }
987
+
988
+ export function formatElementContext(
989
+ sourceElementList: IElement[],
990
+ formatElementList: IElement[],
991
+ anchorIndex: number,
992
+ options?: IFormatElementContextOption
993
+ ) {
994
+ let copyElement = getAnchorElement(sourceElementList, anchorIndex)
995
+ if (!copyElement) return
996
+ const {
997
+ isBreakWhenWrap = false,
998
+ editorOptions,
999
+ ignoreContextKeys = []
1000
+ } = options || {}
1001
+ const { mode } = editorOptions || {}
1002
+ // 非设计模式时:标题元素禁用时不复制标题属性
1003
+ if (mode !== EditorMode.DESIGN && copyElement.title?.disabled) {
1004
+ copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR)
1005
+ }
1006
+ // 是否已经换行
1007
+ let isBreakWarped = false
1008
+ for (let e = 0; e < formatElementList.length; e++) {
1009
+ const targetElement = formatElementList[e]
1010
+ if (
1011
+ isBreakWhenWrap &&
1012
+ !copyElement.listId &&
1013
+ START_LINE_BREAK_REG.test(targetElement.value)
1014
+ ) {
1015
+ isBreakWarped = true
1016
+ }
1017
+ // 1. 即使换行停止也要处理表格上下文信息
1018
+ // 2. 定位元素非列表,无需处理粘贴列表的上下文,仅处理表格及行上下文信息
1019
+ if (
1020
+ isBreakWarped ||
1021
+ (!copyElement.listId && targetElement.type === ElementType.LIST)
1022
+ ) {
1023
+ const cloneAttr = [
1024
+ ...TABLE_CONTEXT_ATTR,
1025
+ ...EDITOR_ROW_ATTR,
1026
+ ...AREA_CONTEXT_ATTR
1027
+ ]
1028
+ deleteProperty(cloneAttr, ignoreContextKeys)
1029
+ cloneProperty<IElement>(cloneAttr, copyElement!, targetElement)
1030
+ targetElement.valueList?.forEach(valueItem => {
1031
+ cloneProperty<IElement>(cloneAttr, copyElement!, valueItem)
1032
+ })
1033
+ continue
1034
+ }
1035
+ if (targetElement.valueList?.length) {
1036
+ formatElementContext(
1037
+ sourceElementList,
1038
+ targetElement.valueList,
1039
+ anchorIndex,
1040
+ options
1041
+ )
1042
+ }
1043
+ // 非块类元素,需处理行属性
1044
+ const cloneAttr = [...EDITOR_ELEMENT_CONTEXT_ATTR]
1045
+ if (!getIsBlockElement(targetElement)) {
1046
+ cloneAttr.push(...EDITOR_ROW_ATTR)
1047
+ }
1048
+ deleteProperty(cloneAttr, ignoreContextKeys)
1049
+ cloneProperty<IElement>(cloneAttr, copyElement, targetElement)
1050
+ }
1051
+ }
1052
+
1053
+ export function convertElementToDom(
1054
+ element: IElement,
1055
+ options: DeepRequired<IEditorOption>
1056
+ ): HTMLElement {
1057
+ let tagName: keyof HTMLElementTagNameMap = 'span'
1058
+ if (element.type === ElementType.SUPERSCRIPT) {
1059
+ tagName = 'sup'
1060
+ } else if (element.type === ElementType.SUBSCRIPT) {
1061
+ tagName = 'sub'
1062
+ }
1063
+ const dom = document.createElement(tagName)
1064
+ dom.style.fontFamily = element.font || options.defaultFont
1065
+ if (element.rowFlex) {
1066
+ dom.style.textAlign = convertRowFlexToTextAlign(element.rowFlex)
1067
+ }
1068
+ if (element.color) {
1069
+ dom.style.color = element.color
1070
+ }
1071
+ if (element.bold) {
1072
+ dom.style.fontWeight = '600'
1073
+ }
1074
+ if (element.italic) {
1075
+ dom.style.fontStyle = 'italic'
1076
+ }
1077
+ dom.style.fontSize = `${element.size || options.defaultSize}px`
1078
+ if (element.highlight) {
1079
+ dom.style.backgroundColor = element.highlight
1080
+ }
1081
+ if (element.underline) {
1082
+ dom.style.textDecoration = 'underline'
1083
+ dom.style.textDecorationStyle = element.textDecoration?.style || 'solid'
1084
+ }
1085
+ if (element.strikeout) {
1086
+ dom.style.textDecoration += ' line-through'
1087
+ }
1088
+ if (element.type) {
1089
+ dom.setAttribute('data-type', element.type)
1090
+ }
1091
+ if (element.rowMargin) {
1092
+ dom.style.lineHeight = (
1093
+ element.rowMargin ?? options.defaultRowMargin
1094
+ ).toString()
1095
+ }
1096
+ dom.innerText = element.value.replace(new RegExp(`${ZERO}`, 'g'), '\n')
1097
+ return dom
1098
+ }
1099
+
1100
+ export function splitListElement(
1101
+ elementList: IElement[]
1102
+ ): Map<number, IElement[]> {
1103
+ let curListIndex = 0
1104
+ const listElementListMap: Map<number, IElement[]> = new Map()
1105
+ for (let e = 0; e < elementList.length; e++) {
1106
+ const element = elementList[e]
1107
+ // 移除列表首行换行字符-如果是复选框直接忽略
1108
+ if (e === 0) {
1109
+ if (element.checkbox) continue
1110
+ element.value = element.value.replace(START_LINE_BREAK_REG, '')
1111
+ }
1112
+ if (element.listWrap) {
1113
+ const listElementList = listElementListMap.get(curListIndex) || []
1114
+ listElementList.push(element)
1115
+ listElementListMap.set(curListIndex, listElementList)
1116
+ } else {
1117
+ const valueList = element.value.split('\n')
1118
+ for (let c = 0; c < valueList.length; c++) {
1119
+ if (c > 0) {
1120
+ curListIndex += 1
1121
+ }
1122
+ const value = valueList[c]
1123
+ const listElementList = listElementListMap.get(curListIndex) || []
1124
+ listElementList.push({
1125
+ ...element,
1126
+ value
1127
+ })
1128
+ listElementListMap.set(curListIndex, listElementList)
1129
+ }
1130
+ }
1131
+ }
1132
+ return listElementListMap
1133
+ }
1134
+
1135
+ export interface IElementListGroupRowFlex {
1136
+ rowFlex: RowFlex | null
1137
+ data: IElement[]
1138
+ }
1139
+
1140
+ export function groupElementListByRowFlex(
1141
+ elementList: IElement[]
1142
+ ): IElementListGroupRowFlex[] {
1143
+ const elementListGroupList: IElementListGroupRowFlex[] = []
1144
+ if (!elementList.length) return elementListGroupList
1145
+ let currentRowFlex: RowFlex | null = elementList[0]?.rowFlex || null
1146
+ elementListGroupList.push({
1147
+ rowFlex: currentRowFlex,
1148
+ data: [elementList[0]]
1149
+ })
1150
+ for (let e = 1; e < elementList.length; e++) {
1151
+ const element = elementList[e]
1152
+ const rowFlex = element.rowFlex || null
1153
+ // 行布局相同&非块元素时追加数据,否则新增分组
1154
+ if (
1155
+ currentRowFlex === rowFlex &&
1156
+ !getIsBlockElement(element) &&
1157
+ !getIsBlockElement(elementList[e - 1])
1158
+ ) {
1159
+ const lastElementListGroup =
1160
+ elementListGroupList[elementListGroupList.length - 1]
1161
+ lastElementListGroup.data.push(element)
1162
+ } else {
1163
+ elementListGroupList.push({
1164
+ rowFlex,
1165
+ data: [element]
1166
+ })
1167
+ currentRowFlex = rowFlex
1168
+ }
1169
+ }
1170
+ // 压缩数据
1171
+ for (let g = 0; g < elementListGroupList.length; g++) {
1172
+ const elementListGroup = elementListGroupList[g]
1173
+ elementListGroup.data = zipElementList(elementListGroup.data)
1174
+ }
1175
+ return elementListGroupList
1176
+ }
1177
+
1178
+ export function createDomFromElementList(
1179
+ elementList: IElement[],
1180
+ options?: IEditorOption
1181
+ ) {
1182
+ const editorOptions = mergeOption(options)
1183
+ function buildDom(payload: IElement[]): HTMLDivElement {
1184
+ const clipboardDom = document.createElement('div')
1185
+ for (let e = 0; e < payload.length; e++) {
1186
+ const element = payload[e]
1187
+ // 构造表格
1188
+ if (element.type === ElementType.TABLE) {
1189
+ const tableDom: HTMLTableElement = document.createElement('table')
1190
+ tableDom.setAttribute('cellSpacing', '0')
1191
+ tableDom.setAttribute('cellpadding', '0')
1192
+ tableDom.setAttribute('border', '0')
1193
+ const borderStyle = '1px solid #000000'
1194
+ // 表格边框
1195
+ if (!element.borderType || element.borderType === TableBorder.ALL) {
1196
+ tableDom.style.borderTop = borderStyle
1197
+ tableDom.style.borderLeft = borderStyle
1198
+ } else if (element.borderType === TableBorder.EXTERNAL) {
1199
+ tableDom.style.border = borderStyle
1200
+ }
1201
+ tableDom.style.width = `${element.width}px`
1202
+ // colgroup
1203
+ const colgroupDom = document.createElement('colgroup')
1204
+ for (let c = 0; c < element.colgroup!.length; c++) {
1205
+ const colgroup = element.colgroup![c]
1206
+ const colDom = document.createElement('col')
1207
+ colDom.setAttribute('width', `${colgroup.width}`)
1208
+ colgroupDom.append(colDom)
1209
+ }
1210
+ tableDom.append(colgroupDom)
1211
+ // tr
1212
+ const trList = element.trList!
1213
+ for (let t = 0; t < trList.length; t++) {
1214
+ const trDom = document.createElement('tr')
1215
+ const tr = trList[t]
1216
+ trDom.style.height = `${tr.height}px`
1217
+ for (let d = 0; d < tr.tdList.length; d++) {
1218
+ const tdDom = document.createElement('td')
1219
+ if (!element.borderType || element.borderType === TableBorder.ALL) {
1220
+ tdDom.style.borderBottom = tdDom.style.borderRight = '1px solid'
1221
+ }
1222
+ const td = tr.tdList[d]
1223
+ tdDom.colSpan = td.colspan
1224
+ tdDom.rowSpan = td.rowspan
1225
+ tdDom.style.verticalAlign = td.verticalAlign || 'top'
1226
+ // 单元格边框
1227
+ if (td.borderTypes?.includes(TdBorder.TOP)) {
1228
+ tdDom.style.borderTop = borderStyle
1229
+ }
1230
+ if (td.borderTypes?.includes(TdBorder.RIGHT)) {
1231
+ tdDom.style.borderRight = borderStyle
1232
+ }
1233
+ if (td.borderTypes?.includes(TdBorder.BOTTOM)) {
1234
+ tdDom.style.borderBottom = borderStyle
1235
+ }
1236
+ if (td.borderTypes?.includes(TdBorder.LEFT)) {
1237
+ tdDom.style.borderLeft = borderStyle
1238
+ }
1239
+ const childDom = createDomFromElementList(td.value!, options)
1240
+ tdDom.innerHTML = childDom.innerHTML
1241
+ if (td.backgroundColor) {
1242
+ tdDom.style.backgroundColor = td.backgroundColor
1243
+ }
1244
+ trDom.append(tdDom)
1245
+ }
1246
+ tableDom.append(trDom)
1247
+ }
1248
+ clipboardDom.append(tableDom)
1249
+ } else if (element.type === ElementType.HYPERLINK) {
1250
+ const a = document.createElement('a')
1251
+ a.innerText = element.valueList!.map(v => v.value).join('')
1252
+ if (element.url) {
1253
+ a.href = element.url
1254
+ }
1255
+ clipboardDom.append(a)
1256
+ } else if (element.type === ElementType.TITLE) {
1257
+ const h = document.createElement(
1258
+ `h${titleOrderNumberMapping[element.level!]}`
1259
+ )
1260
+ const childDom = buildDom(element.valueList!)
1261
+ h.innerHTML = childDom.innerHTML
1262
+ clipboardDom.append(h)
1263
+ } else if (element.type === ElementType.LIST) {
1264
+ const list = document.createElement(
1265
+ listTypeElementMapping[element.listType!]
1266
+ )
1267
+ if (element.listStyle) {
1268
+ list.style.listStyleType = listStyleCSSMapping[element.listStyle]
1269
+ }
1270
+ // 按照换行符拆分
1271
+ const zipList = zipElementList(element.valueList!)
1272
+ const listElementListMap = splitListElement(zipList)
1273
+ listElementListMap.forEach(listElementList => {
1274
+ const li = document.createElement('li')
1275
+ const childDom = buildDom(listElementList)
1276
+ li.innerHTML = childDom.innerHTML
1277
+ list.append(li)
1278
+ })
1279
+ clipboardDom.append(list)
1280
+ } else if (element.type === ElementType.IMAGE) {
1281
+ const img = document.createElement('img')
1282
+ if (element.value) {
1283
+ img.src = element.value
1284
+ img.width = element.width!
1285
+ img.height = element.height!
1286
+ }
1287
+ clipboardDom.append(img)
1288
+ } else if (element.type === ElementType.BLOCK) {
1289
+ if (element.block?.type === BlockType.VIDEO) {
1290
+ const src = element.block.videoBlock?.src
1291
+ if (src) {
1292
+ const video = document.createElement('video')
1293
+ video.style.display = 'block'
1294
+ video.controls = true
1295
+ video.src = src
1296
+ video.width = element.width! || options?.width || window.innerWidth
1297
+ video.height = element.height!
1298
+ clipboardDom.append(video)
1299
+ }
1300
+ } else if (element.block?.type === BlockType.IFRAME) {
1301
+ const { src, srcdoc, sandbox, allow } =
1302
+ element.block.iframeBlock || {}
1303
+ if (src || srcdoc) {
1304
+ const iframe = document.createElement('iframe')
1305
+ iframe.sandbox.add(...(sandbox || IFrameBlock.sandbox))
1306
+ iframe.setAttribute('allow', [allow || IFrameBlock.allow].join(' '))
1307
+ iframe.style.display = 'block'
1308
+ iframe.style.border = 'none'
1309
+ if (src) {
1310
+ iframe.src = src
1311
+ } else if (srcdoc) {
1312
+ iframe.srcdoc = srcdoc
1313
+ }
1314
+ iframe.width = `${
1315
+ element.width || options?.width || window.innerWidth
1316
+ }`
1317
+ iframe.height = `${element.height!}`
1318
+ clipboardDom.append(iframe)
1319
+ }
1320
+ }
1321
+ } else if (element.type === ElementType.SEPARATOR) {
1322
+ const hr = document.createElement('hr')
1323
+ if (element.dashArray?.length) {
1324
+ hr.setAttribute('data-dash-array', element.dashArray.join(','))
1325
+ }
1326
+ clipboardDom.append(hr)
1327
+ } else if (element.type === ElementType.CHECKBOX) {
1328
+ const checkbox = document.createElement('input')
1329
+ checkbox.type = 'checkbox'
1330
+ if (element.checkbox?.value) {
1331
+ checkbox.setAttribute('checked', 'true')
1332
+ }
1333
+ clipboardDom.append(checkbox)
1334
+ } else if (element.type === ElementType.RADIO) {
1335
+ const radio = document.createElement('input')
1336
+ radio.type = 'radio'
1337
+ if (element.radio?.value) {
1338
+ radio.setAttribute('checked', 'true')
1339
+ }
1340
+ clipboardDom.append(radio)
1341
+ } else if (element.type === ElementType.TAB) {
1342
+ const tab = document.createElement('span')
1343
+ tab.innerHTML = `${NON_BREAKING_SPACE}${NON_BREAKING_SPACE}`
1344
+ clipboardDom.append(tab)
1345
+ } else if (element.type === ElementType.CONTROL) {
1346
+ const controlElement = document.createElement('span')
1347
+ const childDom = buildDom(element.control?.value || [])
1348
+ controlElement.innerHTML = childDom.innerHTML
1349
+ clipboardDom.append(controlElement)
1350
+ } else if (element.type === ElementType.PAGE_BREAK) {
1351
+ const pageBreakElement = document.createElement('div')
1352
+ pageBreakElement.style.breakAfter = 'page'
1353
+ clipboardDom.append(pageBreakElement)
1354
+ } else if (
1355
+ !element.type ||
1356
+ element.type === ElementType.LATEX ||
1357
+ TEXTLIKE_ELEMENT_TYPE.includes(element.type)
1358
+ ) {
1359
+ let text = ''
1360
+ if (element.type === ElementType.DATE) {
1361
+ text = element.valueList?.map(v => v.value).join('') || ''
1362
+ } else {
1363
+ text = element.value
1364
+ }
1365
+ if (!text) continue
1366
+ const dom = convertElementToDom(element, editorOptions)
1367
+ // 前一个元素是标题,移除首行换行符
1368
+ if (payload[e - 1]?.type === ElementType.TITLE) {
1369
+ text = text.replace(/^\n/, '')
1370
+ }
1371
+ dom.innerText = text.replace(new RegExp(`${ZERO}`, 'g'), '\n')
1372
+ clipboardDom.append(dom)
1373
+ }
1374
+ }
1375
+ return clipboardDom
1376
+ }
1377
+ // 按行布局分类创建dom
1378
+ const clipboardDom = document.createElement('div')
1379
+ const groupElementList = groupElementListByRowFlex(elementList)
1380
+ for (let g = 0; g < groupElementList.length; g++) {
1381
+ const elementGroupRowFlex = groupElementList[g]
1382
+ // 行布局样式设置
1383
+ const isDefaultRowFlex =
1384
+ !elementGroupRowFlex.rowFlex ||
1385
+ elementGroupRowFlex.rowFlex === RowFlex.LEFT
1386
+ // 块元素使用flex否则使用text-align
1387
+ const rowFlexDom = document.createElement('div')
1388
+ if (!isDefaultRowFlex) {
1389
+ const firstElement = elementGroupRowFlex.data[0]
1390
+ if (getIsBlockElement(firstElement)) {
1391
+ rowFlexDom.style.display = 'flex'
1392
+ rowFlexDom.style.justifyContent = convertRowFlexToJustifyContent(
1393
+ firstElement.rowFlex!
1394
+ )
1395
+ } else {
1396
+ rowFlexDom.style.textAlign = convertRowFlexToTextAlign(
1397
+ elementGroupRowFlex.rowFlex!
1398
+ )
1399
+ if (elementGroupRowFlex.rowFlex === 'justify') {
1400
+ rowFlexDom.style.textAlignLast = 'justify'
1401
+ }
1402
+ }
1403
+ }
1404
+ // 布局内容
1405
+ rowFlexDom.innerHTML = buildDom(elementGroupRowFlex.data).innerHTML
1406
+ // 未设置行布局时无需行布局容器
1407
+ if (!isDefaultRowFlex) {
1408
+ clipboardDom.append(rowFlexDom)
1409
+ } else {
1410
+ rowFlexDom.childNodes.forEach(child => {
1411
+ clipboardDom.append(child.cloneNode(true))
1412
+ })
1413
+ }
1414
+ }
1415
+ return clipboardDom
1416
+ }
1417
+
1418
+ export function convertTextNodeToElement(
1419
+ textNode: Element | Node
1420
+ ): IElement | null {
1421
+ if (!textNode || textNode.nodeType !== 3) return null
1422
+ const parentNode = <HTMLElement>textNode.parentNode
1423
+ const anchorNode =
1424
+ parentNode.nodeName === 'FONT'
1425
+ ? <HTMLElement>parentNode.parentNode
1426
+ : parentNode
1427
+ const rowFlex = convertTextAlignToRowFlex(anchorNode)
1428
+ const value = textNode.textContent
1429
+ const style = window.getComputedStyle(anchorNode)
1430
+ if (!value || anchorNode.nodeName === 'STYLE') return null
1431
+ const element: IElement = {
1432
+ value,
1433
+ color: style.color,
1434
+ bold: Number(style.fontWeight) > 500,
1435
+ italic: style.fontStyle.includes('italic'),
1436
+ size: Math.floor(parseFloat(style.fontSize))
1437
+ }
1438
+ // 元素类型-默认文本
1439
+ if (anchorNode.nodeName === 'SUB' || style.verticalAlign === 'sub') {
1440
+ element.type = ElementType.SUBSCRIPT
1441
+ } else if (anchorNode.nodeName === 'SUP' || style.verticalAlign === 'super') {
1442
+ element.type = ElementType.SUPERSCRIPT
1443
+ }
1444
+ // 行对齐
1445
+ if (rowFlex !== RowFlex.LEFT) {
1446
+ element.rowFlex = rowFlex
1447
+ }
1448
+ // 高亮色
1449
+ if (style.backgroundColor !== 'rgba(0, 0, 0, 0)') {
1450
+ element.highlight = style.backgroundColor
1451
+ }
1452
+ // 下划线
1453
+ if (style.textDecorationLine.includes('underline')) {
1454
+ element.underline = true
1455
+ }
1456
+ // 删除线
1457
+ if (style.textDecorationLine.includes('line-through')) {
1458
+ element.strikeout = true
1459
+ }
1460
+ return element
1461
+ }
1462
+
1463
+ export interface IGetElementListByHTMLOption {
1464
+ innerWidth: number
1465
+ }
1466
+
1467
+ export function getElementListByHTML(
1468
+ htmlText: string,
1469
+ options: IGetElementListByHTMLOption
1470
+ ): IElement[] {
1471
+ const elementList: IElement[] = []
1472
+ function findTextNode(dom: Element | Node) {
1473
+ if (dom.nodeType === 3) {
1474
+ const element = convertTextNodeToElement(dom)
1475
+ if (element) {
1476
+ elementList.push(element)
1477
+ }
1478
+ } else if (dom.nodeType === 1) {
1479
+ const childNodes = dom.childNodes
1480
+ for (let n = 0; n < childNodes.length; n++) {
1481
+ const node = childNodes[n]
1482
+ // br元素与display:block元素需换行
1483
+ if (node.nodeName === 'BR') {
1484
+ elementList.push({
1485
+ value: '\n'
1486
+ })
1487
+ } else if (node.nodeName === 'A') {
1488
+ const aElement = node as HTMLLinkElement
1489
+ const value = aElement.innerText
1490
+ if (value) {
1491
+ elementList.push({
1492
+ type: ElementType.HYPERLINK,
1493
+ value: '',
1494
+ valueList: [
1495
+ {
1496
+ value
1497
+ }
1498
+ ],
1499
+ url: aElement.href
1500
+ })
1501
+ }
1502
+ } else if (/H[1-6]/.test(node.nodeName)) {
1503
+ const hElement = node as HTMLTitleElement
1504
+ const valueList = getElementListByHTML(
1505
+ replaceHTMLElementTag(hElement, 'div').outerHTML,
1506
+ options
1507
+ )
1508
+ elementList.push({
1509
+ value: '',
1510
+ type: ElementType.TITLE,
1511
+ level: titleNodeNameMapping[node.nodeName],
1512
+ valueList
1513
+ })
1514
+ if (
1515
+ node.nextSibling &&
1516
+ !INLINE_NODE_NAME.includes(node.nextSibling.nodeName)
1517
+ ) {
1518
+ elementList.push({
1519
+ value: '\n'
1520
+ })
1521
+ }
1522
+ } else if (node.nodeName === 'UL' || node.nodeName === 'OL') {
1523
+ const listNode = node as HTMLOListElement | HTMLUListElement
1524
+ const listElement: IElement = {
1525
+ value: '',
1526
+ type: ElementType.LIST,
1527
+ valueList: []
1528
+ }
1529
+ if (node.nodeName === 'OL') {
1530
+ listElement.listType = ListType.OL
1531
+ } else {
1532
+ listElement.listType = ListType.UL
1533
+ listElement.listStyle = <ListStyle>(
1534
+ (<unknown>listNode.style.listStyleType)
1535
+ )
1536
+ }
1537
+ listNode.querySelectorAll('li').forEach(li => {
1538
+ const liValueList = getElementListByHTML(li.innerHTML, options)
1539
+ liValueList.forEach(list => {
1540
+ if (list.value === '\n') {
1541
+ list.listWrap = true
1542
+ }
1543
+ })
1544
+ liValueList.unshift({
1545
+ value: '\n'
1546
+ })
1547
+ listElement.valueList!.push(...liValueList)
1548
+ })
1549
+ elementList.push(listElement)
1550
+ } else if (node.nodeName === 'HR') {
1551
+ elementList.push({
1552
+ value: '\n',
1553
+ type: ElementType.SEPARATOR
1554
+ })
1555
+ } else if (node.nodeName === 'IMG') {
1556
+ const { src, width, height } = node as HTMLImageElement
1557
+ if (src && width && height) {
1558
+ elementList.push({
1559
+ width,
1560
+ height,
1561
+ value: src,
1562
+ type: ElementType.IMAGE,
1563
+ rowFlex: convertTextAlignToRowFlex(node.parentElement!)
1564
+ })
1565
+ }
1566
+ } else if (node.nodeName === 'VIDEO') {
1567
+ const { src, width, height } = node as HTMLVideoElement
1568
+ if (src && width && height) {
1569
+ elementList.push({
1570
+ value: '',
1571
+ type: ElementType.BLOCK,
1572
+ block: {
1573
+ type: BlockType.VIDEO,
1574
+ videoBlock: {
1575
+ src
1576
+ }
1577
+ },
1578
+ width,
1579
+ height
1580
+ })
1581
+ }
1582
+ } else if (node.nodeName === 'IFRAME') {
1583
+ const { src, srcdoc, width, height } = node as HTMLIFrameElement
1584
+ if ((src || srcdoc) && width && height) {
1585
+ elementList.push({
1586
+ value: '',
1587
+ type: ElementType.BLOCK,
1588
+ block: {
1589
+ type: BlockType.IFRAME,
1590
+ iframeBlock: {
1591
+ src,
1592
+ srcdoc
1593
+ }
1594
+ },
1595
+ width: parseInt(width),
1596
+ height: parseInt(height)
1597
+ })
1598
+ }
1599
+ } else if (node.nodeName === 'TABLE') {
1600
+ const tableElement = node as HTMLTableElement
1601
+ const element: IElement = {
1602
+ type: ElementType.TABLE,
1603
+ value: '\n',
1604
+ colgroup: [],
1605
+ trList: []
1606
+ }
1607
+ // colgroup
1608
+ const colElements = tableElement.querySelectorAll('colgroup col')
1609
+ // 基础数据
1610
+ tableElement.querySelectorAll('tr').forEach(trElement => {
1611
+ const trHeightStr = Number(
1612
+ window.getComputedStyle(trElement).height.replace('px', '')
1613
+ )
1614
+ const tr: ITr = {
1615
+ height: trHeightStr,
1616
+ minHeight: trHeightStr,
1617
+ tdList: []
1618
+ }
1619
+ trElement.querySelectorAll('th,td').forEach(tdElement => {
1620
+ const tableCell = <HTMLTableCellElement>tdElement
1621
+ const valueList = getElementListByHTML(
1622
+ tableCell.innerHTML,
1623
+ options
1624
+ )
1625
+ const td: ITd = {
1626
+ colspan: tableCell.colSpan,
1627
+ rowspan: tableCell.rowSpan,
1628
+ value: valueList,
1629
+ verticalAlign: window.getComputedStyle(tdElement)
1630
+ .verticalAlign as VerticalAlign,
1631
+ width: parseFloat(window.getComputedStyle(tdElement).width)
1632
+ }
1633
+ if (tableCell.style.backgroundColor) {
1634
+ td.backgroundColor = tableCell.style.backgroundColor
1635
+ }
1636
+ tr.tdList.push(td)
1637
+ })
1638
+ element.trList!.push(tr)
1639
+ })
1640
+ if (element.trList!.length) {
1641
+ // 列选项数据
1642
+ const tdCount = element.trList![0].tdList.reduce(
1643
+ (pre, cur) => pre + cur.colspan,
1644
+ 0
1645
+ )
1646
+ const width = Math.ceil(options.innerWidth / tdCount)
1647
+ for (let i = 0; i < tdCount; i++) {
1648
+ const colElement = colElements[i]?.getAttribute('width')
1649
+ element.colgroup!.push({
1650
+ width: colElement ? parseFloat(colElement) : width
1651
+ })
1652
+ }
1653
+ elementList.push(element)
1654
+ }
1655
+ } else if (
1656
+ node.nodeName === 'INPUT' &&
1657
+ (<HTMLInputElement>node).type === ControlComponent.CHECKBOX
1658
+ ) {
1659
+ elementList.push({
1660
+ type: ElementType.CHECKBOX,
1661
+ value: '',
1662
+ checkbox: {
1663
+ value: (<HTMLInputElement>node).checked
1664
+ }
1665
+ })
1666
+ } else if (
1667
+ node.nodeName === 'INPUT' &&
1668
+ (<HTMLInputElement>node).type === ControlComponent.RADIO
1669
+ ) {
1670
+ elementList.push({
1671
+ type: ElementType.RADIO,
1672
+ value: '',
1673
+ radio: {
1674
+ value: (<HTMLInputElement>node).checked
1675
+ }
1676
+ })
1677
+ } else {
1678
+ findTextNode(node)
1679
+ if (node.nodeType === 1 && n !== childNodes.length - 1) {
1680
+ const nodeElement = node as Element
1681
+ const display = window.getComputedStyle(nodeElement).display
1682
+ if (
1683
+ display === 'block' &&
1684
+ !/(\n|\r\n)$/.test(nodeElement.textContent!)
1685
+ ) {
1686
+ elementList.push({
1687
+ value: '\n'
1688
+ })
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ // 追加dom - 使用Shadow DOM隔离外部样式影响
1696
+ const clipboardHost = document.createElement('div')
1697
+ document.body.appendChild(clipboardHost)
1698
+ const shadowRoot = clipboardHost.attachShadow({ mode: 'open' })
1699
+ const clipboardDom = document.createElement('div')
1700
+ clipboardDom.innerHTML = htmlText
1701
+ shadowRoot.appendChild(clipboardDom)
1702
+ const deleteNodes: ChildNode[] = []
1703
+ clipboardDom.childNodes.forEach(child => {
1704
+ if (child.nodeType !== 1 && !child.textContent?.trim()) {
1705
+ deleteNodes.push(child)
1706
+ }
1707
+ })
1708
+ deleteNodes.forEach(node => node.remove())
1709
+ // 搜索文本节点
1710
+ findTextNode(clipboardDom)
1711
+ // 移除dom
1712
+ clipboardHost.remove()
1713
+ return elementList
1714
+ }
1715
+
1716
+ export function getTextFromElementList(elementList: IElement[]) {
1717
+ function buildText(payload: IElement[]): string {
1718
+ let text = ''
1719
+ for (let e = 0; e < payload.length; e++) {
1720
+ const element = payload[e]
1721
+ // 构造表格
1722
+ if (element.type === ElementType.TABLE) {
1723
+ text += `\n`
1724
+ const trList = element.trList!
1725
+ for (let t = 0; t < trList.length; t++) {
1726
+ const tr = trList[t]
1727
+ for (let d = 0; d < tr.tdList.length; d++) {
1728
+ const td = tr.tdList[d]
1729
+ const tdText = buildText(zipElementList(td.value!))
1730
+ const isFirst = d === 0
1731
+ const isLast = tr.tdList.length - 1 === d
1732
+ text += `${!isFirst ? ` ` : ``}${tdText}${isLast ? `\n` : ``}`
1733
+ }
1734
+ }
1735
+ } else if (element.type === ElementType.TAB) {
1736
+ text += `\t`
1737
+ } else if (element.type === ElementType.HYPERLINK) {
1738
+ text += element.valueList!.map(v => v.value).join('')
1739
+ } else if (element.type === ElementType.TITLE) {
1740
+ text += `${buildText(zipElementList(element.valueList!))}`
1741
+ } else if (element.type === ElementType.LIST) {
1742
+ // 按照换行符拆分
1743
+ const zipList = zipElementList(element.valueList!)
1744
+ const listElementListMap = splitListElement(zipList)
1745
+ // 无序列表前缀
1746
+ let ulListStyleText = ''
1747
+ if (element.listType === ListType.UL) {
1748
+ ulListStyleText =
1749
+ ulStyleMapping[<UlStyle>(<unknown>element.listStyle)]
1750
+ }
1751
+ listElementListMap.forEach((listElementList, listIndex) => {
1752
+ const isLast = listElementListMap.size - 1 === listIndex
1753
+ text += `\n${ulListStyleText || `${listIndex + 1}.`}${buildText(
1754
+ listElementList
1755
+ )}${isLast ? `\n` : ``}`
1756
+ })
1757
+ } else if (element.type === ElementType.CHECKBOX) {
1758
+ text += element.checkbox?.value ? `☑` : `□`
1759
+ } else if (element.type === ElementType.RADIO) {
1760
+ text += element.radio?.value ? `☉` : `○`
1761
+ } else if (
1762
+ !element.type ||
1763
+ element.type === ElementType.LATEX ||
1764
+ TEXTLIKE_ELEMENT_TYPE.includes(element.type)
1765
+ ) {
1766
+ let textLike = ''
1767
+ if (element.type === ElementType.CONTROL) {
1768
+ const controlValue = element.control!.value?.[0]?.value || ''
1769
+ textLike = controlValue
1770
+ ? `${element.control?.preText || ''}${controlValue}${
1771
+ element.control?.postText || ''
1772
+ }`
1773
+ : ''
1774
+ } else if (element.type === ElementType.DATE) {
1775
+ textLike = element.valueList?.map(v => v.value).join('') || ''
1776
+ } else {
1777
+ textLike = element.value
1778
+ }
1779
+ text += textLike.replace(new RegExp(`${ZERO}`, 'g'), '\n')
1780
+ }
1781
+ }
1782
+ return text
1783
+ }
1784
+ return buildText(zipElementList(elementList))
1785
+ }
1786
+
1787
+ export function getSlimCloneElementList(elementList: IElement[]) {
1788
+ return deepCloneOmitKeys<IElement[], IRowElement>(elementList, [
1789
+ 'metrics',
1790
+ 'style'
1791
+ ])
1792
+ }
1793
+
1794
+ export function getIsBlockElement(element?: IElement) {
1795
+ return (
1796
+ !!element?.type &&
1797
+ (BLOCK_ELEMENT_TYPE.includes(element.type) ||
1798
+ element.imgDisplay === ImageDisplay.INLINE)
1799
+ )
1800
+ }
1801
+
1802
+ export function replaceHTMLElementTag(
1803
+ oldDom: HTMLElement,
1804
+ tagName: keyof HTMLElementTagNameMap
1805
+ ): HTMLElement {
1806
+ const newDom = document.createElement(tagName)
1807
+ for (let i = 0; i < oldDom.attributes.length; i++) {
1808
+ const attr = oldDom.attributes[i]
1809
+ newDom.setAttribute(attr.name, attr.value)
1810
+ }
1811
+ newDom.innerHTML = oldDom.innerHTML
1812
+ return newDom
1813
+ }
1814
+
1815
+ export function pickSurroundElementList(elementList: IElement[]) {
1816
+ const surroundElementList = []
1817
+ for (let e = 0; e < elementList.length; e++) {
1818
+ const element = elementList[e]
1819
+ if (element.imgDisplay === ImageDisplay.SURROUND) {
1820
+ surroundElementList.push(element)
1821
+ }
1822
+ }
1823
+ return surroundElementList
1824
+ }
1825
+
1826
+ export function deleteSurroundElementList(
1827
+ elementList: IElement[],
1828
+ pageNo: number
1829
+ ) {
1830
+ for (let s = elementList.length - 1; s >= 0; s--) {
1831
+ const surroundElement = elementList[s]
1832
+ if (surroundElement.imgFloatPosition?.pageNo === pageNo) {
1833
+ elementList.splice(s, 1)
1834
+ }
1835
+ }
1836
+ }
1837
+
1838
+ export function getNonHideElementIndex(
1839
+ elementList: IElement[],
1840
+ index: number,
1841
+ position: LocationPosition = LocationPosition.BEFORE
1842
+ ) {
1843
+ if (
1844
+ !elementList[index]?.hide &&
1845
+ !elementList[index]?.control?.hide &&
1846
+ !elementList[index]?.area?.hide
1847
+ ) {
1848
+ return index
1849
+ }
1850
+ let i = index
1851
+ if (position === LocationPosition.BEFORE) {
1852
+ i = index - 1
1853
+ while (i > 0) {
1854
+ if (
1855
+ !elementList[i]?.hide &&
1856
+ !elementList[i]?.control?.hide &&
1857
+ !elementList[i]?.area?.hide
1858
+ ) {
1859
+ return i
1860
+ }
1861
+ i--
1862
+ }
1863
+ } else {
1864
+ i = index + 1
1865
+ while (i < elementList.length) {
1866
+ if (
1867
+ !elementList[i]?.hide &&
1868
+ !elementList[i]?.control?.hide &&
1869
+ !elementList[i]?.area?.hide
1870
+ ) {
1871
+ return i
1872
+ }
1873
+ i++
1874
+ }
1875
+ }
1876
+ return i
1877
+ }