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
package/src/main.ts ADDED
@@ -0,0 +1,2053 @@
1
+ import { commentList, data, options } from './mock'
2
+ import './style.css'
3
+ import prism from 'prismjs'
4
+ import Editor, {
5
+ BlockType,
6
+ Command,
7
+ ControlState,
8
+ ControlType,
9
+ EditorMode,
10
+ EditorZone,
11
+ ElementType,
12
+ IBlock,
13
+ ICatalogItem,
14
+ IElement,
15
+ KeyMap,
16
+ ListStyle,
17
+ ListType,
18
+ PageMode,
19
+ PaperDirection,
20
+ RowFlex,
21
+ TextDecorationStyle,
22
+ TitleLevel,
23
+ splitText
24
+ } from './editor'
25
+ import { Dialog } from './components/dialog/Dialog'
26
+ import { formatPrismToken } from './utils/prism'
27
+ import { Signature } from './components/signature/Signature'
28
+ import { debounce, nextTick, scrollIntoView } from './utils'
29
+
30
+ window.onload = function () {
31
+ const isApple =
32
+ typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)
33
+
34
+ // 1. 初始化编辑器
35
+ const container = document.querySelector<HTMLDivElement>('.editor')!
36
+ const instance = new Editor(
37
+ container,
38
+ {
39
+ header: [
40
+ {
41
+ value: '第一人民医院',
42
+ size: 32,
43
+ rowFlex: RowFlex.CENTER
44
+ },
45
+ {
46
+ value: '\n门诊病历',
47
+ size: 18,
48
+ rowFlex: RowFlex.CENTER
49
+ },
50
+ {
51
+ value: '\n',
52
+ type: ElementType.SEPARATOR
53
+ }
54
+ ],
55
+ main: <IElement[]>data,
56
+ footer: [
57
+ {
58
+ value: 'canvas-editor',
59
+ size: 12
60
+ }
61
+ ]
62
+ },
63
+ options
64
+ )
65
+ console.log('实例: ', instance)
66
+ // cypress使用
67
+ Reflect.set(window, 'editor', instance)
68
+
69
+ // 菜单弹窗销毁
70
+ window.addEventListener(
71
+ 'click',
72
+ evt => {
73
+ const visibleDom = document.querySelector('.visible')
74
+ if (!visibleDom || visibleDom.contains(<Node>evt.target)) return
75
+ visibleDom.classList.remove('visible')
76
+ },
77
+ {
78
+ capture: true
79
+ }
80
+ )
81
+
82
+
83
+ // 2. | 撤销 | 重做 | 格式刷 | 清除格式 |
84
+ const undoDom = document.querySelector<HTMLDivElement>('.menu-item__undo')!
85
+ undoDom.title = `撤销(${isApple ? '⌘' : 'Ctrl'}+Z)`
86
+ undoDom.onclick = function () {
87
+ console.log('undo')
88
+ instance.command.executeUndo()
89
+ }
90
+
91
+ const redoDom = document.querySelector<HTMLDivElement>('.menu-item__redo')!
92
+ redoDom.title = `重做(${isApple ? '⌘' : 'Ctrl'}+Y)`
93
+ redoDom.onclick = function () {
94
+ console.log('redo')
95
+ instance.command.executeRedo()
96
+ }
97
+
98
+ const painterDom = document.querySelector<HTMLDivElement>(
99
+ '.menu-item__painter'
100
+ )!
101
+
102
+ let isFirstClick = true
103
+ let painterTimeout: number
104
+ painterDom.onclick = function () {
105
+ if (isFirstClick) {
106
+ isFirstClick = false
107
+ painterTimeout = window.setTimeout(() => {
108
+ console.log('painter-click')
109
+ isFirstClick = true
110
+ instance.command.executePainter({
111
+ isDblclick: false
112
+ })
113
+ }, 200)
114
+ } else {
115
+ window.clearTimeout(painterTimeout)
116
+ }
117
+ }
118
+
119
+ painterDom.ondblclick = function () {
120
+ console.log('painter-dblclick')
121
+ isFirstClick = true
122
+ window.clearTimeout(painterTimeout)
123
+ instance.command.executePainter({
124
+ isDblclick: true
125
+ })
126
+ }
127
+
128
+ document.querySelector<HTMLDivElement>('.menu-item__format')!.onclick =
129
+ function () {
130
+ console.log('format')
131
+ instance.command.executeFormat()
132
+ }
133
+
134
+ // 3. | 字体 | 字体变大 | 字体变小 | 加粗 | 斜体 | 下划线 | 删除线 | 上标 | 下标 | 字体颜色 | 背景色 |
135
+ const fontDom = document.querySelector<HTMLDivElement>('.menu-item__font')!
136
+ const fontSelectDom = fontDom.querySelector<HTMLDivElement>('.select')!
137
+ const fontOptionDom = fontDom.querySelector<HTMLDivElement>('.options')!
138
+ fontDom.onclick = function () {
139
+ console.log('font')
140
+ fontOptionDom.classList.toggle('visible')
141
+ }
142
+ fontOptionDom.onclick = function (evt) {
143
+ const li = evt.target as HTMLLIElement
144
+ instance.command.executeFont(li.dataset.family!)
145
+ }
146
+
147
+ const sizeSetDom = document.querySelector<HTMLDivElement>('.menu-item__size')!
148
+ const sizeSelectDom = sizeSetDom.querySelector<HTMLDivElement>('.select')!
149
+ const sizeOptionDom = sizeSetDom.querySelector<HTMLDivElement>('.options')!
150
+ sizeSetDom.title = `设置字号`
151
+ sizeSetDom.onclick = function () {
152
+ console.log('size')
153
+ sizeOptionDom.classList.toggle('visible')
154
+ }
155
+ sizeOptionDom.onclick = function (evt) {
156
+ const li = evt.target as HTMLLIElement
157
+ instance.command.executeSize(Number(li.dataset.size!))
158
+ }
159
+
160
+ const sizeAddDom = document.querySelector<HTMLDivElement>(
161
+ '.menu-item__size-add'
162
+ )!
163
+ sizeAddDom.title = `增大字号(${isApple ? '⌘' : 'Ctrl'}+[)`
164
+ sizeAddDom.onclick = function () {
165
+ console.log('size-add')
166
+ instance.command.executeSizeAdd()
167
+ }
168
+
169
+ const sizeMinusDom = document.querySelector<HTMLDivElement>(
170
+ '.menu-item__size-minus'
171
+ )!
172
+ sizeMinusDom.title = `减小字号(${isApple ? '⌘' : 'Ctrl'}+])`
173
+ sizeMinusDom.onclick = function () {
174
+ console.log('size-minus')
175
+ instance.command.executeSizeMinus()
176
+ }
177
+
178
+ const boldDom = document.querySelector<HTMLDivElement>('.menu-item__bold')!
179
+ boldDom.title = `加粗(${isApple ? '⌘' : 'Ctrl'}+B)`
180
+ boldDom.onclick = function () {
181
+ console.log('bold')
182
+ instance.command.executeBold()
183
+ }
184
+
185
+ const italicDom =
186
+ document.querySelector<HTMLDivElement>('.menu-item__italic')!
187
+ italicDom.title = `斜体(${isApple ? '⌘' : 'Ctrl'}+I)`
188
+ italicDom.onclick = function () {
189
+ console.log('italic')
190
+ instance.command.executeItalic()
191
+ }
192
+
193
+ const underlineDom = document.querySelector<HTMLDivElement>(
194
+ '.menu-item__underline'
195
+ )!
196
+ underlineDom.title = `下划线(${isApple ? '⌘' : 'Ctrl'}+U)`
197
+ const underlineOptionDom =
198
+ underlineDom.querySelector<HTMLDivElement>('.options')!
199
+ underlineDom.querySelector<HTMLSpanElement>('.select')!.onclick =
200
+ function () {
201
+ underlineOptionDom.classList.toggle('visible')
202
+ }
203
+ underlineDom.querySelector<HTMLElement>('i')!.onclick = function () {
204
+ console.log('underline')
205
+ instance.command.executeUnderline()
206
+ underlineOptionDom.classList.remove('visible')
207
+ }
208
+ underlineDom.querySelector<HTMLUListElement>('ul')!.onmousedown = function (
209
+ evt
210
+ ) {
211
+ const li = evt.target as HTMLLIElement
212
+ const decorationStyle = <TextDecorationStyle>li.dataset.decorationStyle
213
+ instance.command.executeUnderline({
214
+ style: decorationStyle
215
+ })
216
+ underlineOptionDom.classList.remove('visible')
217
+ }
218
+
219
+ const strikeoutDom = document.querySelector<HTMLDivElement>(
220
+ '.menu-item__strikeout'
221
+ )!
222
+ strikeoutDom.onclick = function () {
223
+ console.log('strikeout')
224
+ instance.command.executeStrikeout()
225
+ }
226
+
227
+ const superscriptDom = document.querySelector<HTMLDivElement>(
228
+ '.menu-item__superscript'
229
+ )!
230
+ superscriptDom.title = `上标(${isApple ? '⌘' : 'Ctrl'}+Shift+,)`
231
+ superscriptDom.onclick = function () {
232
+ console.log('superscript')
233
+ instance.command.executeSuperscript()
234
+ }
235
+
236
+ const subscriptDom = document.querySelector<HTMLDivElement>(
237
+ '.menu-item__subscript'
238
+ )!
239
+ subscriptDom.title = `下标(${isApple ? '⌘' : 'Ctrl'}+Shift+.)`
240
+ subscriptDom.onclick = function () {
241
+ console.log('subscript')
242
+ instance.command.executeSubscript()
243
+ }
244
+
245
+ const colorControlDom = document.querySelector<HTMLInputElement>('#color')!
246
+ colorControlDom.oninput = function () {
247
+ instance.command.executeColor(colorControlDom.value)
248
+ }
249
+ const colorDom = document.querySelector<HTMLDivElement>('.menu-item__color')!
250
+ const colorSpanDom = colorDom.querySelector('span')!
251
+ colorDom.onclick = function () {
252
+ console.log('color')
253
+ colorControlDom.click()
254
+ }
255
+
256
+ const highlightControlDom =
257
+ document.querySelector<HTMLInputElement>('#highlight')!
258
+ highlightControlDom.oninput = function () {
259
+ instance.command.executeHighlight(highlightControlDom.value)
260
+ }
261
+ const highlightDom = document.querySelector<HTMLDivElement>(
262
+ '.menu-item__highlight'
263
+ )!
264
+ const highlightSpanDom = highlightDom.querySelector('span')!
265
+ highlightDom.onclick = function () {
266
+ console.log('highlight')
267
+ highlightControlDom?.click()
268
+ }
269
+
270
+ const titleDom = document.querySelector<HTMLDivElement>('.menu-item__title')!
271
+ const titleSelectDom = titleDom.querySelector<HTMLDivElement>('.select')!
272
+ const titleOptionDom = titleDom.querySelector<HTMLDivElement>('.options')!
273
+ titleOptionDom.querySelectorAll('li').forEach((li, index) => {
274
+ li.title = `Ctrl+${isApple ? 'Option' : 'Alt'}+${index}`
275
+ })
276
+
277
+ titleDom.onclick = function () {
278
+ console.log('title')
279
+ titleOptionDom.classList.toggle('visible')
280
+ }
281
+ titleOptionDom.onclick = function (evt) {
282
+ const li = evt.target as HTMLLIElement
283
+ const level = <TitleLevel>li.dataset.level
284
+ instance.command.executeTitle(level || null)
285
+ }
286
+
287
+ const leftDom = document.querySelector<HTMLDivElement>('.menu-item__left')!
288
+ leftDom.title = `左对齐(${isApple ? '⌘' : 'Ctrl'}+L)`
289
+ leftDom.onclick = function () {
290
+ console.log('left')
291
+ instance.command.executeRowFlex(RowFlex.LEFT)
292
+ }
293
+
294
+ const centerDom =
295
+ document.querySelector<HTMLDivElement>('.menu-item__center')!
296
+ centerDom.title = `居中对齐(${isApple ? '⌘' : 'Ctrl'}+E)`
297
+ centerDom.onclick = function () {
298
+ console.log('center')
299
+ instance.command.executeRowFlex(RowFlex.CENTER)
300
+ }
301
+
302
+ const rightDom = document.querySelector<HTMLDivElement>('.menu-item__right')!
303
+ rightDom.title = `右对齐(${isApple ? '⌘' : 'Ctrl'}+R)`
304
+ rightDom.onclick = function () {
305
+ console.log('right')
306
+ instance.command.executeRowFlex(RowFlex.RIGHT)
307
+ }
308
+
309
+ const alignmentDom = document.querySelector<HTMLDivElement>(
310
+ '.menu-item__alignment'
311
+ )!
312
+ alignmentDom.title = `两端对齐(${isApple ? '⌘' : 'Ctrl'}+J)`
313
+ alignmentDom.onclick = function () {
314
+ console.log('alignment')
315
+ instance.command.executeRowFlex(RowFlex.ALIGNMENT)
316
+ }
317
+
318
+ const justifyDom = document.querySelector<HTMLDivElement>(
319
+ '.menu-item__justify'
320
+ )!
321
+ justifyDom.title = `分散对齐(${isApple ? '⌘' : 'Ctrl'}+Shift+J)`
322
+ justifyDom.onclick = function () {
323
+ console.log('justify')
324
+ instance.command.executeRowFlex(RowFlex.JUSTIFY)
325
+ }
326
+
327
+ const rowMarginDom = document.querySelector<HTMLDivElement>(
328
+ '.menu-item__row-margin'
329
+ )!
330
+ const rowOptionDom = rowMarginDom.querySelector<HTMLDivElement>('.options')!
331
+ rowMarginDom.onclick = function () {
332
+ console.log('row-margin')
333
+ rowOptionDom.classList.toggle('visible')
334
+ }
335
+ rowOptionDom.onclick = function (evt) {
336
+ const li = evt.target as HTMLLIElement
337
+ instance.command.executeRowMargin(Number(li.dataset.rowmargin!))
338
+ }
339
+
340
+ const listDom = document.querySelector<HTMLDivElement>('.menu-item__list')!
341
+ listDom.title = `列表(${isApple ? '⌘' : 'Ctrl'}+Shift+U)`
342
+ const listOptionDom = listDom.querySelector<HTMLDivElement>('.options')!
343
+ listDom.onclick = function () {
344
+ console.log('list')
345
+ listOptionDom.classList.toggle('visible')
346
+ }
347
+ listOptionDom.onclick = function (evt) {
348
+ const li = evt.target as HTMLLIElement
349
+ const listType = <ListType>li.dataset.listType || null
350
+ const listStyle = <ListStyle>(<unknown>li.dataset.listStyle)
351
+ instance.command.executeList(listType, listStyle)
352
+ }
353
+
354
+ // 4. | 表格 | 图片 | 超链接 | 分割线 | 水印 | 代码块 | 分隔符 | 控件 | 复选框 | LaTeX | 日期选择器
355
+ const tableDom = document.querySelector<HTMLDivElement>('.menu-item__table')!
356
+ const tablePanelContainer = document.querySelector<HTMLDivElement>(
357
+ '.menu-item__table__collapse'
358
+ )!
359
+ const tableClose = document.querySelector<HTMLDivElement>('.table-close')!
360
+ const tableTitle = document.querySelector<HTMLDivElement>('.table-select')!
361
+ const tablePanel = document.querySelector<HTMLDivElement>('.table-panel')!
362
+ // 绘制行列
363
+ const tableCellList: HTMLDivElement[][] = []
364
+ for (let i = 0; i < 10; i++) {
365
+ const tr = document.createElement('tr')
366
+ tr.classList.add('table-row')
367
+ const trCellList: HTMLDivElement[] = []
368
+ for (let j = 0; j < 10; j++) {
369
+ const td = document.createElement('td')
370
+ td.classList.add('table-cel')
371
+ tr.append(td)
372
+ trCellList.push(td)
373
+ }
374
+ tablePanel.append(tr)
375
+ tableCellList.push(trCellList)
376
+ }
377
+ let colIndex = 0
378
+ let rowIndex = 0
379
+ // 移除所有格选择
380
+ function removeAllTableCellSelect() {
381
+ tableCellList.forEach(tr => {
382
+ tr.forEach(td => td.classList.remove('active'))
383
+ })
384
+ }
385
+ // 设置标题内容
386
+ function setTableTitle(payload: string) {
387
+ tableTitle.innerText = payload
388
+ }
389
+ // 恢复初始状态
390
+ function recoveryTable() {
391
+ // 还原选择样式、标题、选择行列
392
+ removeAllTableCellSelect()
393
+ setTableTitle('插入')
394
+ colIndex = 0
395
+ rowIndex = 0
396
+ // 隐藏panel
397
+ tablePanelContainer.style.display = 'none'
398
+ }
399
+ tableDom.onclick = function () {
400
+ console.log('table')
401
+ tablePanelContainer!.style.display = 'block'
402
+ }
403
+ tablePanel.onmousemove = function (evt) {
404
+ const celSize = 16
405
+ const rowMarginTop = 10
406
+ const celMarginRight = 6
407
+ const { offsetX, offsetY } = evt
408
+ // 移除所有选择
409
+ removeAllTableCellSelect()
410
+ colIndex = Math.ceil(offsetX / (celSize + celMarginRight)) || 1
411
+ rowIndex = Math.ceil(offsetY / (celSize + rowMarginTop)) || 1
412
+ // 改变选择样式
413
+ tableCellList.forEach((tr, trIndex) => {
414
+ tr.forEach((td, tdIndex) => {
415
+ if (tdIndex < colIndex && trIndex < rowIndex) {
416
+ td.classList.add('active')
417
+ }
418
+ })
419
+ })
420
+ // 改变表格标题
421
+ setTableTitle(`${rowIndex}×${colIndex}`)
422
+ }
423
+ tableClose.onclick = function () {
424
+ recoveryTable()
425
+ }
426
+ tablePanel.onclick = function () {
427
+ // 应用选择
428
+ instance.command.executeInsertTable(rowIndex, colIndex)
429
+ recoveryTable()
430
+ }
431
+
432
+ const imageDom = document.querySelector<HTMLDivElement>('.menu-item__image')!
433
+ const imageFileDom = document.querySelector<HTMLInputElement>('#image')!
434
+ imageDom.onclick = function () {
435
+ imageFileDom.click()
436
+ }
437
+ imageFileDom.onchange = function () {
438
+ const file = imageFileDom.files![0]!
439
+ const fileReader = new FileReader()
440
+ fileReader.readAsDataURL(file)
441
+ fileReader.onload = function () {
442
+ // 计算宽高
443
+ const image = new Image()
444
+ const value = fileReader.result as string
445
+ image.src = value
446
+ image.onload = function () {
447
+ instance.command.executeImage({
448
+ value,
449
+ width: image.width,
450
+ height: image.height
451
+ })
452
+ imageFileDom.value = ''
453
+ }
454
+ }
455
+ }
456
+
457
+ const hyperlinkDom = document.querySelector<HTMLDivElement>(
458
+ '.menu-item__hyperlink'
459
+ )!
460
+ hyperlinkDom.onclick = function () {
461
+ console.log('hyperlink')
462
+ new Dialog({
463
+ title: '超链接',
464
+ data: [
465
+ {
466
+ type: 'text',
467
+ label: '文本',
468
+ name: 'name',
469
+ required: true,
470
+ placeholder: '请输入文本',
471
+ value: instance.command.getRangeText()
472
+ },
473
+ {
474
+ type: 'text',
475
+ label: '链接',
476
+ name: 'url',
477
+ required: true,
478
+ placeholder: '请输入链接'
479
+ }
480
+ ],
481
+ onConfirm: payload => {
482
+ const name = payload.find(p => p.name === 'name')?.value
483
+ if (!name) return
484
+ const url = payload.find(p => p.name === 'url')?.value
485
+ if (!url) return
486
+ instance.command.executeHyperlink({
487
+ url,
488
+ valueList: splitText(name).map(n => ({
489
+ value: n,
490
+ size: 16
491
+ }))
492
+ })
493
+ }
494
+ })
495
+ }
496
+
497
+ const separatorDom = document.querySelector<HTMLDivElement>(
498
+ '.menu-item__separator'
499
+ )!
500
+ const separatorOptionDom =
501
+ separatorDom.querySelector<HTMLDivElement>('.options')!
502
+ separatorDom.onclick = function () {
503
+ console.log('separator')
504
+ separatorOptionDom.classList.toggle('visible')
505
+ }
506
+ separatorOptionDom.onmousedown = function (evt) {
507
+ let payload: number[] = []
508
+ const li = evt.target as HTMLLIElement
509
+ const separatorDash = li.dataset.separator?.split(',').map(Number)
510
+ if (separatorDash) {
511
+ const isSingleLine = separatorDash.every(d => d === 0)
512
+ if (!isSingleLine) {
513
+ payload = separatorDash
514
+ }
515
+ }
516
+ instance.command.executeSeparator(payload)
517
+ }
518
+
519
+ const pageBreakDom = document.querySelector<HTMLDivElement>(
520
+ '.menu-item__page-break'
521
+ )!
522
+ pageBreakDom.onclick = function () {
523
+ console.log('pageBreak')
524
+ instance.command.executePageBreak()
525
+ }
526
+
527
+ const watermarkDom = document.querySelector<HTMLDivElement>(
528
+ '.menu-item__watermark'
529
+ )!
530
+ const watermarkOptionDom =
531
+ watermarkDom.querySelector<HTMLDivElement>('.options')!
532
+ watermarkDom.onclick = function () {
533
+ console.log('watermark')
534
+ watermarkOptionDom.classList.toggle('visible')
535
+ }
536
+ watermarkOptionDom.onmousedown = function (evt) {
537
+ const li = evt.target as HTMLLIElement
538
+ const menu = li.dataset.menu!
539
+ watermarkOptionDom.classList.toggle('visible')
540
+ if (menu === 'add') {
541
+ new Dialog({
542
+ title: '水印',
543
+ data: [
544
+ {
545
+ type: 'text',
546
+ label: '内容',
547
+ name: 'data',
548
+ required: true,
549
+ placeholder: '请输入内容'
550
+ },
551
+ {
552
+ type: 'color',
553
+ label: '颜色',
554
+ name: 'color',
555
+ required: true,
556
+ value: '#AEB5C0'
557
+ },
558
+ {
559
+ type: 'number',
560
+ label: '字体大小',
561
+ name: 'size',
562
+ required: true,
563
+ value: '120'
564
+ },
565
+ {
566
+ type: 'number',
567
+ label: '透明度',
568
+ name: 'opacity',
569
+ required: true,
570
+ value: '0.3'
571
+ },
572
+ {
573
+ type: 'select',
574
+ label: '重复',
575
+ name: 'repeat',
576
+ value: '0',
577
+ required: false,
578
+ options: [
579
+ {
580
+ label: '不重复',
581
+ value: '0'
582
+ },
583
+ {
584
+ label: '重复',
585
+ value: '1'
586
+ }
587
+ ]
588
+ },
589
+ {
590
+ type: 'number',
591
+ label: '水平间隔',
592
+ name: 'horizontalGap',
593
+ required: false,
594
+ value: '10'
595
+ },
596
+ {
597
+ type: 'number',
598
+ label: '垂直间隔',
599
+ name: 'verticalGap',
600
+ required: false,
601
+ value: '10'
602
+ }
603
+ ],
604
+ onConfirm: payload => {
605
+ const nullableIndex = payload.findIndex(p => !p.value)
606
+ if (~nullableIndex) return
607
+ const watermark = payload.reduce(
608
+ (pre, cur) => {
609
+ pre[cur.name] = cur.value
610
+ return pre
611
+ },
612
+ <any>{}
613
+ )
614
+ const repeat = watermark.repeat === '1'
615
+ instance.command.executeAddWatermark({
616
+ data: watermark.data,
617
+ color: watermark.color,
618
+ size: Number(watermark.size),
619
+ opacity: Number(watermark.opacity),
620
+ repeat,
621
+ gap:
622
+ repeat && watermark.horizontalGap && watermark.verticalGap
623
+ ? [
624
+ Number(watermark.horizontalGap),
625
+ Number(watermark.verticalGap)
626
+ ]
627
+ : undefined
628
+ })
629
+ }
630
+ })
631
+ } else {
632
+ instance.command.executeDeleteWatermark()
633
+ }
634
+ }
635
+
636
+ const codeblockDom = document.querySelector<HTMLDivElement>(
637
+ '.menu-item__codeblock'
638
+ )!
639
+ codeblockDom.onclick = function () {
640
+ console.log('codeblock')
641
+ new Dialog({
642
+ title: '代码块',
643
+ data: [
644
+ {
645
+ type: 'textarea',
646
+ name: 'codeblock',
647
+ placeholder: '请输入代码',
648
+ width: 500,
649
+ height: 300
650
+ }
651
+ ],
652
+ onConfirm: payload => {
653
+ const codeblock = payload.find(p => p.name === 'codeblock')?.value
654
+ if (!codeblock) return
655
+ const tokenList = prism.tokenize(codeblock, prism.languages.javascript)
656
+ const formatTokenList = formatPrismToken(tokenList)
657
+ const elementList: IElement[] = []
658
+ for (let i = 0; i < formatTokenList.length; i++) {
659
+ const formatToken = formatTokenList[i]
660
+ const tokenStringList = splitText(formatToken.content)
661
+ for (let j = 0; j < tokenStringList.length; j++) {
662
+ const value = tokenStringList[j]
663
+ const element: IElement = {
664
+ value
665
+ }
666
+ if (formatToken.color) {
667
+ element.color = formatToken.color
668
+ }
669
+ if (formatToken.bold) {
670
+ element.bold = true
671
+ }
672
+ if (formatToken.italic) {
673
+ element.italic = true
674
+ }
675
+ elementList.push(element)
676
+ }
677
+ }
678
+ elementList.unshift({
679
+ value: '\n'
680
+ })
681
+ instance.command.executeInsertElementList(elementList)
682
+ }
683
+ })
684
+ }
685
+
686
+ const controlDom = document.querySelector<HTMLDivElement>(
687
+ '.menu-item__control'
688
+ )!
689
+ const controlOptionDom = controlDom.querySelector<HTMLDivElement>('.options')!
690
+ controlDom.onclick = function () {
691
+ console.log('control')
692
+ controlOptionDom.classList.toggle('visible')
693
+ }
694
+ controlOptionDom.onmousedown = function (evt) {
695
+ controlOptionDom.classList.toggle('visible')
696
+ const li = evt.target as HTMLLIElement
697
+ const type = <ControlType>li.dataset.control
698
+ switch (type) {
699
+ case ControlType.TEXT:
700
+ new Dialog({
701
+ title: '文本控件',
702
+ data: [
703
+ {
704
+ type: 'text',
705
+ label: '占位符',
706
+ name: 'placeholder',
707
+ required: true,
708
+ placeholder: '请输入占位符'
709
+ },
710
+ {
711
+ type: 'text',
712
+ label: '默认值',
713
+ name: 'value',
714
+ placeholder: '请输入默认值'
715
+ }
716
+ ],
717
+ onConfirm: payload => {
718
+ const placeholder = payload.find(
719
+ p => p.name === 'placeholder'
720
+ )?.value
721
+ if (!placeholder) return
722
+ const value = payload.find(p => p.name === 'value')?.value || ''
723
+ instance.command.executeInsertControl({
724
+ type: ElementType.CONTROL,
725
+ value: '',
726
+ control: {
727
+ type,
728
+ value: value
729
+ ? [
730
+ {
731
+ value
732
+ }
733
+ ]
734
+ : null,
735
+ placeholder
736
+ }
737
+ })
738
+ }
739
+ })
740
+ break
741
+ case ControlType.SELECT:
742
+ new Dialog({
743
+ title: '列举控件',
744
+ data: [
745
+ {
746
+ type: 'text',
747
+ label: '占位符',
748
+ name: 'placeholder',
749
+ required: true,
750
+ placeholder: '请输入占位符'
751
+ },
752
+ {
753
+ type: 'text',
754
+ label: '默认值',
755
+ name: 'code',
756
+ placeholder: '请输入默认值'
757
+ },
758
+ {
759
+ type: 'textarea',
760
+ label: '值集',
761
+ name: 'valueSets',
762
+ required: true,
763
+ height: 100,
764
+ placeholder: `请输入值集JSON,例:\n[{\n"value":"有",\n"code":"98175"\n}]`
765
+ }
766
+ ],
767
+ onConfirm: payload => {
768
+ const placeholder = payload.find(
769
+ p => p.name === 'placeholder'
770
+ )?.value
771
+ if (!placeholder) return
772
+ const valueSets = payload.find(p => p.name === 'valueSets')?.value
773
+ if (!valueSets) return
774
+ const code = payload.find(p => p.name === 'code')?.value
775
+ instance.command.executeInsertControl({
776
+ type: ElementType.CONTROL,
777
+ value: '',
778
+ control: {
779
+ type,
780
+ code,
781
+ value: null,
782
+ placeholder,
783
+ valueSets: JSON.parse(valueSets)
784
+ }
785
+ })
786
+ }
787
+ })
788
+ break
789
+ case ControlType.CHECKBOX:
790
+ new Dialog({
791
+ title: '复选框控件',
792
+ data: [
793
+ {
794
+ type: 'text',
795
+ label: '默认值',
796
+ name: 'code',
797
+ placeholder: '请输入默认值,多个值以英文逗号分割'
798
+ },
799
+ {
800
+ type: 'textarea',
801
+ label: '值集',
802
+ name: 'valueSets',
803
+ required: true,
804
+ height: 100,
805
+ placeholder: `请输入值集JSON,例:\n[{\n"value":"有",\n"code":"98175"\n}]`
806
+ }
807
+ ],
808
+ onConfirm: payload => {
809
+ const valueSets = payload.find(p => p.name === 'valueSets')?.value
810
+ if (!valueSets) return
811
+ const code = payload.find(p => p.name === 'code')?.value
812
+ instance.command.executeInsertControl({
813
+ type: ElementType.CONTROL,
814
+ value: '',
815
+ control: {
816
+ type,
817
+ code,
818
+ value: null,
819
+ valueSets: JSON.parse(valueSets)
820
+ }
821
+ })
822
+ }
823
+ })
824
+ break
825
+ case ControlType.RADIO:
826
+ new Dialog({
827
+ title: '单选框控件',
828
+ data: [
829
+ {
830
+ type: 'text',
831
+ label: '默认值',
832
+ name: 'code',
833
+ placeholder: '请输入默认值'
834
+ },
835
+ {
836
+ type: 'textarea',
837
+ label: '值集',
838
+ name: 'valueSets',
839
+ required: true,
840
+ height: 100,
841
+ placeholder: `请输入值集JSON,例:\n[{\n"value":"有",\n"code":"98175"\n}]`
842
+ }
843
+ ],
844
+ onConfirm: payload => {
845
+ const valueSets = payload.find(p => p.name === 'valueSets')?.value
846
+ if (!valueSets) return
847
+ const code = payload.find(p => p.name === 'code')?.value
848
+ instance.command.executeInsertControl({
849
+ type: ElementType.CONTROL,
850
+ value: '',
851
+ control: {
852
+ type,
853
+ code,
854
+ value: null,
855
+ valueSets: JSON.parse(valueSets)
856
+ }
857
+ })
858
+ }
859
+ })
860
+ break
861
+ case ControlType.DATE:
862
+ new Dialog({
863
+ title: '日期控件',
864
+ data: [
865
+ {
866
+ type: 'text',
867
+ label: '占位符',
868
+ name: 'placeholder',
869
+ required: true,
870
+ placeholder: '请输入占位符'
871
+ },
872
+ {
873
+ type: 'text',
874
+ label: '默认值',
875
+ name: 'value',
876
+ placeholder: '请输入默认值'
877
+ },
878
+ {
879
+ type: 'select',
880
+ label: '日期格式',
881
+ name: 'dateFormat',
882
+ value: 'yyyy-MM-dd hh:mm:ss',
883
+ required: true,
884
+ options: [
885
+ {
886
+ label: 'yyyy-MM-dd hh:mm:ss',
887
+ value: 'yyyy-MM-dd hh:mm:ss'
888
+ },
889
+ {
890
+ label: 'yyyy-MM-dd',
891
+ value: 'yyyy-MM-dd'
892
+ }
893
+ ]
894
+ }
895
+ ],
896
+ onConfirm: payload => {
897
+ const placeholder = payload.find(
898
+ p => p.name === 'placeholder'
899
+ )?.value
900
+ if (!placeholder) return
901
+ const value = payload.find(p => p.name === 'value')?.value || ''
902
+ const dateFormat =
903
+ payload.find(p => p.name === 'dateFormat')?.value || ''
904
+ instance.command.executeInsertControl({
905
+ type: ElementType.CONTROL,
906
+ value: '',
907
+ control: {
908
+ type,
909
+ dateFormat,
910
+ value: value
911
+ ? [
912
+ {
913
+ value
914
+ }
915
+ ]
916
+ : null,
917
+ placeholder
918
+ }
919
+ })
920
+ }
921
+ })
922
+ break
923
+ case ControlType.NUMBER:
924
+ new Dialog({
925
+ title: '数值控件',
926
+ data: [
927
+ {
928
+ type: 'text',
929
+ label: '占位符',
930
+ name: 'placeholder',
931
+ required: true,
932
+ placeholder: '请输入占位符'
933
+ },
934
+ {
935
+ type: 'text',
936
+ label: '默认值',
937
+ name: 'value',
938
+ placeholder: '请输入默认值'
939
+ }
940
+ ],
941
+ onConfirm: payload => {
942
+ const placeholder = payload.find(
943
+ p => p.name === 'placeholder'
944
+ )?.value
945
+ if (!placeholder) return
946
+ const value = payload.find(p => p.name === 'value')?.value || ''
947
+ instance.command.executeInsertControl({
948
+ type: ElementType.CONTROL,
949
+ value: '',
950
+ control: {
951
+ type,
952
+ value: value
953
+ ? [
954
+ {
955
+ value
956
+ }
957
+ ]
958
+ : null,
959
+ placeholder
960
+ }
961
+ })
962
+ }
963
+ })
964
+ break
965
+ default:
966
+ break
967
+ }
968
+ }
969
+
970
+ const checkboxDom = document.querySelector<HTMLDivElement>(
971
+ '.menu-item__checkbox'
972
+ )!
973
+ checkboxDom.onclick = function () {
974
+ console.log('checkbox')
975
+ instance.command.executeInsertElementList([
976
+ {
977
+ type: ElementType.CHECKBOX,
978
+ checkbox: {
979
+ value: false
980
+ },
981
+ value: ''
982
+ }
983
+ ])
984
+ }
985
+
986
+ const radioDom = document.querySelector<HTMLDivElement>('.menu-item__radio')!
987
+ radioDom.onclick = function () {
988
+ console.log('radio')
989
+ instance.command.executeInsertElementList([
990
+ {
991
+ type: ElementType.RADIO,
992
+ checkbox: {
993
+ value: false
994
+ },
995
+ value: ''
996
+ }
997
+ ])
998
+ }
999
+
1000
+ const latexDom = document.querySelector<HTMLDivElement>('.menu-item__latex')!
1001
+ latexDom.onclick = function () {
1002
+ console.log('LaTeX')
1003
+ new Dialog({
1004
+ title: 'LaTeX',
1005
+ data: [
1006
+ {
1007
+ type: 'textarea',
1008
+ height: 100,
1009
+ name: 'value',
1010
+ placeholder: '请输入LaTeX文本'
1011
+ }
1012
+ ],
1013
+ onConfirm: payload => {
1014
+ const value = payload.find(p => p.name === 'value')?.value
1015
+ if (!value) return
1016
+ instance.command.executeInsertElementList([
1017
+ {
1018
+ type: ElementType.LATEX,
1019
+ value
1020
+ }
1021
+ ])
1022
+ }
1023
+ })
1024
+ }
1025
+
1026
+ const dateDom = document.querySelector<HTMLDivElement>('.menu-item__date')!
1027
+ const dateDomOptionDom = dateDom.querySelector<HTMLDivElement>('.options')!
1028
+ dateDom.onclick = function () {
1029
+ console.log('date')
1030
+ dateDomOptionDom.classList.toggle('visible')
1031
+ // 定位调整
1032
+ const bodyRect = document.body.getBoundingClientRect()
1033
+ const dateDomOptionRect = dateDomOptionDom.getBoundingClientRect()
1034
+ if (dateDomOptionRect.left + dateDomOptionRect.width > bodyRect.width) {
1035
+ dateDomOptionDom.style.right = '0px'
1036
+ dateDomOptionDom.style.left = 'unset'
1037
+ } else {
1038
+ dateDomOptionDom.style.right = 'unset'
1039
+ dateDomOptionDom.style.left = '0px'
1040
+ }
1041
+ // 当前日期
1042
+ const date = new Date()
1043
+ const year = date.getFullYear().toString()
1044
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
1045
+ const day = date.getDate().toString().padStart(2, '0')
1046
+ const hour = date.getHours().toString().padStart(2, '0')
1047
+ const minute = date.getMinutes().toString().padStart(2, '0')
1048
+ const second = date.getSeconds().toString().padStart(2, '0')
1049
+ const dateString = `${year}-${month}-${day}`
1050
+ const dateTimeString = `${dateString} ${hour}:${minute}:${second}`
1051
+ dateDomOptionDom.querySelector<HTMLLIElement>('li:first-child')!.innerText =
1052
+ dateString
1053
+ dateDomOptionDom.querySelector<HTMLLIElement>('li:last-child')!.innerText =
1054
+ dateTimeString
1055
+ }
1056
+ dateDomOptionDom.onmousedown = function (evt) {
1057
+ const li = evt.target as HTMLLIElement
1058
+ const dateFormat = li.dataset.format!
1059
+ dateDomOptionDom.classList.toggle('visible')
1060
+ instance.command.executeInsertElementList([
1061
+ {
1062
+ type: ElementType.DATE,
1063
+ value: '',
1064
+ dateFormat,
1065
+ valueList: [
1066
+ {
1067
+ value: li.innerText.trim()
1068
+ }
1069
+ ]
1070
+ }
1071
+ ])
1072
+ }
1073
+
1074
+ const blockDom = document.querySelector<HTMLDivElement>('.menu-item__block')!
1075
+ blockDom.onclick = function () {
1076
+ console.log('block')
1077
+ new Dialog({
1078
+ title: '内容块',
1079
+ data: [
1080
+ {
1081
+ type: 'select',
1082
+ label: '类型',
1083
+ name: 'type',
1084
+ value: 'iframe',
1085
+ required: true,
1086
+ options: [
1087
+ {
1088
+ label: '网址',
1089
+ value: 'iframe'
1090
+ },
1091
+ {
1092
+ label: '视频',
1093
+ value: 'video'
1094
+ }
1095
+ ]
1096
+ },
1097
+ {
1098
+ type: 'number',
1099
+ label: '宽度',
1100
+ name: 'width',
1101
+ placeholder: '请输入宽度(默认页面内宽度)'
1102
+ },
1103
+ {
1104
+ type: 'number',
1105
+ label: '高度',
1106
+ name: 'height',
1107
+ required: true,
1108
+ placeholder: '请输入高度'
1109
+ },
1110
+ {
1111
+ type: 'input',
1112
+ label: '地址',
1113
+ name: 'src',
1114
+ required: false,
1115
+ placeholder: '请输入地址'
1116
+ },
1117
+ {
1118
+ type: 'textarea',
1119
+ label: 'HTML',
1120
+ height: 100,
1121
+ name: 'srcdoc',
1122
+ required: false,
1123
+ placeholder: '请输入HTML代码(仅网址类型有效)'
1124
+ }
1125
+ ],
1126
+ onConfirm: payload => {
1127
+ const type = payload.find(p => p.name === 'type')?.value
1128
+ if (!type) return
1129
+ const width = payload.find(p => p.name === 'width')?.value
1130
+ const height = payload.find(p => p.name === 'height')?.value
1131
+ if (!height) return
1132
+ // 地址或HTML代码至少存在一项
1133
+ const src = payload.find(p => p.name === 'src')?.value
1134
+ const srcdoc = payload.find(p => p.name === 'srcdoc')?.value
1135
+ const block: IBlock = {
1136
+ type: <BlockType>type
1137
+ }
1138
+ if (block.type === BlockType.IFRAME) {
1139
+ if (!src && !srcdoc) return
1140
+ block.iframeBlock = {
1141
+ src,
1142
+ srcdoc
1143
+ }
1144
+ } else if (block.type === BlockType.VIDEO) {
1145
+ if (!src) return
1146
+ block.videoBlock = {
1147
+ src
1148
+ }
1149
+ }
1150
+ const blockElement: IElement = {
1151
+ type: ElementType.BLOCK,
1152
+ value: '',
1153
+ height: Number(height),
1154
+ block
1155
+ }
1156
+ if (width) {
1157
+ blockElement.width = Number(width)
1158
+ }
1159
+ instance.command.executeInsertElementList([blockElement])
1160
+ }
1161
+ })
1162
+ }
1163
+
1164
+ // 5. | 搜索&替换 | 打印 |
1165
+ const searchCollapseDom = document.querySelector<HTMLDivElement>(
1166
+ '.menu-item__search__collapse'
1167
+ )!
1168
+ const searchInputDom = document.querySelector<HTMLInputElement>(
1169
+ '.menu-item__search__collapse__search input'
1170
+ )!
1171
+ const replaceInputDom = document.querySelector<HTMLInputElement>(
1172
+ '.menu-item__search__collapse__replace input'
1173
+ )!
1174
+ const searchRegInputDom =
1175
+ document.querySelector<HTMLInputElement>('#option-reg')!
1176
+ const searchCaseInputDom =
1177
+ document.querySelector<HTMLInputElement>('#option-case')!
1178
+ const searchSelectionInputDom =
1179
+ document.querySelector<HTMLInputElement>('#option-selection')!
1180
+ const searchDom =
1181
+ document.querySelector<HTMLDivElement>('.menu-item__search')!
1182
+ searchDom.title = `搜索与替换(${isApple ? '⌘' : 'Ctrl'}+F)`
1183
+ const searchResultDom =
1184
+ searchCollapseDom.querySelector<HTMLLabelElement>('.search-result')!
1185
+ function setSearchResult() {
1186
+ const result = instance.command.getSearchNavigateInfo()
1187
+ if (result) {
1188
+ const { index, count } = result
1189
+ searchResultDom.innerText = `${index}/${count}`
1190
+ } else {
1191
+ searchResultDom.innerText = ''
1192
+ }
1193
+ }
1194
+ searchDom.onclick = function () {
1195
+ console.log('search')
1196
+ searchCollapseDom.style.display = 'block'
1197
+ const bodyRect = document.body.getBoundingClientRect()
1198
+ const searchRect = searchDom.getBoundingClientRect()
1199
+ const searchCollapseRect = searchCollapseDom.getBoundingClientRect()
1200
+ if (searchRect.left + searchCollapseRect.width > bodyRect.width) {
1201
+ searchCollapseDom.style.right = '0px'
1202
+ searchCollapseDom.style.left = 'unset'
1203
+ } else {
1204
+ searchCollapseDom.style.right = 'unset'
1205
+ }
1206
+ searchInputDom.focus()
1207
+ }
1208
+ searchCollapseDom.querySelector<HTMLSpanElement>('span')!.onclick =
1209
+ function () {
1210
+ searchCollapseDom.style.display = 'none'
1211
+ searchInputDom.value = ''
1212
+ replaceInputDom.value = ''
1213
+ instance.command.executeSearch(null)
1214
+ setSearchResult()
1215
+ }
1216
+
1217
+ function emitSearch() {
1218
+ instance.command.executeSearch(searchInputDom.value || null, {
1219
+ isRegEnable: searchRegInputDom.checked,
1220
+ isIgnoreCase: searchCaseInputDom.checked,
1221
+ isLimitSelection: searchSelectionInputDom.checked
1222
+ })
1223
+ setSearchResult()
1224
+ }
1225
+
1226
+ searchInputDom.oninput = emitSearch
1227
+ searchRegInputDom.onchange = emitSearch
1228
+ searchCaseInputDom.onchange = emitSearch
1229
+ searchSelectionInputDom.onchange = emitSearch
1230
+ searchInputDom.onkeydown = function (evt) {
1231
+ if (evt.key === 'Enter') {
1232
+ emitSearch()
1233
+ }
1234
+ }
1235
+ searchCollapseDom.querySelector<HTMLButtonElement>('button')!.onclick =
1236
+ function () {
1237
+ const searchValue = searchInputDom.value
1238
+ const replaceValue = replaceInputDom.value
1239
+ if (searchValue && searchValue !== replaceValue) {
1240
+ instance.command.executeReplace(replaceValue)
1241
+ }
1242
+ }
1243
+ searchCollapseDom.querySelector<HTMLDivElement>('.arrow-left')!.onclick =
1244
+ function () {
1245
+ instance.command.executeSearchNavigatePre()
1246
+ setSearchResult()
1247
+ }
1248
+ searchCollapseDom.querySelector<HTMLDivElement>('.arrow-right')!.onclick =
1249
+ function () {
1250
+ instance.command.executeSearchNavigateNext()
1251
+ setSearchResult()
1252
+ }
1253
+
1254
+ const printDom = document.querySelector<HTMLDivElement>('.menu-item__print')!
1255
+ printDom.title = `打印(${isApple ? '⌘' : 'Ctrl'}+P)`
1256
+ printDom.onclick = function () {
1257
+ console.log('print')
1258
+ instance.command.executePrint()
1259
+ }
1260
+
1261
+ // 6. 目录显隐 | 页面模式 | 纸张缩放 | 纸张大小 | 纸张方向 | 页边距 | 全屏 | 设置
1262
+ const editorOptionDom =
1263
+ document.querySelector<HTMLDivElement>('.editor-option')!
1264
+ editorOptionDom.onclick = function () {
1265
+ const options = instance.command.getOptions()
1266
+ new Dialog({
1267
+ title: '编辑器配置',
1268
+ data: [
1269
+ {
1270
+ type: 'textarea',
1271
+ name: 'option',
1272
+ width: 350,
1273
+ height: 300,
1274
+ required: true,
1275
+ value: JSON.stringify(options, null, 2),
1276
+ placeholder: '请输入编辑器配置'
1277
+ }
1278
+ ],
1279
+ onConfirm: payload => {
1280
+ const newOptionValue = payload.find(p => p.name === 'option')?.value
1281
+ if (!newOptionValue) return
1282
+ const newOption = JSON.parse(newOptionValue)
1283
+ instance.command.executeUpdateOptions(newOption)
1284
+ }
1285
+ })
1286
+ }
1287
+
1288
+ async function updateCatalog() {
1289
+ const catalog = await instance.command.getCatalog()
1290
+ const catalogMainDom =
1291
+ document.querySelector<HTMLDivElement>('.catalog__main')!
1292
+ catalogMainDom.innerHTML = ''
1293
+ if (catalog) {
1294
+ const appendCatalog = (
1295
+ parent: HTMLDivElement,
1296
+ catalogItems: ICatalogItem[]
1297
+ ) => {
1298
+ for (let c = 0; c < catalogItems.length; c++) {
1299
+ const catalogItem = catalogItems[c]
1300
+ const catalogItemDom = document.createElement('div')
1301
+ catalogItemDom.classList.add('catalog-item')
1302
+ // 渲染
1303
+ const catalogItemContentDom = document.createElement('div')
1304
+ catalogItemContentDom.classList.add('catalog-item__content')
1305
+ const catalogItemContentSpanDom = document.createElement('span')
1306
+ catalogItemContentSpanDom.innerText = catalogItem.name
1307
+ catalogItemContentDom.append(catalogItemContentSpanDom)
1308
+ // 定位
1309
+ catalogItemContentDom.onclick = () => {
1310
+ instance.command.executeLocationCatalog(catalogItem.id)
1311
+ }
1312
+ catalogItemDom.append(catalogItemContentDom)
1313
+ if (catalogItem.subCatalog && catalogItem.subCatalog.length) {
1314
+ appendCatalog(catalogItemDom, catalogItem.subCatalog)
1315
+ }
1316
+ // 追加
1317
+ parent.append(catalogItemDom)
1318
+ }
1319
+ }
1320
+ appendCatalog(catalogMainDom, catalog)
1321
+ }
1322
+ }
1323
+ let isCatalogShow = true
1324
+ const catalogDom = document.querySelector<HTMLElement>('.catalog')!
1325
+ const catalogModeDom =
1326
+ document.querySelector<HTMLDivElement>('.catalog-mode')!
1327
+ const catalogHeaderCloseDom = document.querySelector<HTMLDivElement>(
1328
+ '.catalog__header__close'
1329
+ )!
1330
+ const switchCatalog = () => {
1331
+ isCatalogShow = !isCatalogShow
1332
+ if (isCatalogShow) {
1333
+ catalogDom.style.display = 'block'
1334
+ updateCatalog()
1335
+ } else {
1336
+ catalogDom.style.display = 'none'
1337
+ }
1338
+ }
1339
+ catalogModeDom.onclick = switchCatalog
1340
+ catalogHeaderCloseDom.onclick = switchCatalog
1341
+
1342
+ const pageModeDom = document.querySelector<HTMLDivElement>('.page-mode')!
1343
+ const pageModeOptionsDom =
1344
+ pageModeDom.querySelector<HTMLDivElement>('.options')!
1345
+ pageModeDom.onclick = function () {
1346
+ pageModeOptionsDom.classList.toggle('visible')
1347
+ }
1348
+ pageModeOptionsDom.onclick = function (evt) {
1349
+ const li = evt.target as HTMLLIElement
1350
+ instance.command.executePageMode(<PageMode>li.dataset.pageMode!)
1351
+ }
1352
+
1353
+ document.querySelector<HTMLDivElement>('.page-scale-percentage')!.onclick =
1354
+ function () {
1355
+ console.log('page-scale-recovery')
1356
+ instance.command.executePageScaleRecovery()
1357
+ }
1358
+
1359
+ document.querySelector<HTMLDivElement>('.page-scale-minus')!.onclick =
1360
+ function () {
1361
+ console.log('page-scale-minus')
1362
+ instance.command.executePageScaleMinus()
1363
+ }
1364
+
1365
+ document.querySelector<HTMLDivElement>('.page-scale-add')!.onclick =
1366
+ function () {
1367
+ console.log('page-scale-add')
1368
+ instance.command.executePageScaleAdd()
1369
+ }
1370
+
1371
+ // 纸张大小
1372
+ const paperSizeDom = document.querySelector<HTMLDivElement>('.paper-size')!
1373
+ const paperSizeDomOptionsDom =
1374
+ paperSizeDom.querySelector<HTMLDivElement>('.options')!
1375
+ paperSizeDom.onclick = function () {
1376
+ paperSizeDomOptionsDom.classList.toggle('visible')
1377
+ }
1378
+ paperSizeDomOptionsDom.onclick = function (evt) {
1379
+ const li = evt.target as HTMLLIElement
1380
+ const paperType = li.dataset.paperSize!
1381
+ const [width, height] = paperType.split('*').map(Number)
1382
+ instance.command.executePaperSize(width, height)
1383
+ // 纸张状态回显
1384
+ paperSizeDomOptionsDom
1385
+ .querySelectorAll('li')
1386
+ .forEach(child => child.classList.remove('active'))
1387
+ li.classList.add('active')
1388
+ }
1389
+
1390
+ // 纸张方向
1391
+ const paperDirectionDom =
1392
+ document.querySelector<HTMLDivElement>('.paper-direction')!
1393
+ const paperDirectionDomOptionsDom =
1394
+ paperDirectionDom.querySelector<HTMLDivElement>('.options')!
1395
+ paperDirectionDom.onclick = function () {
1396
+ paperDirectionDomOptionsDom.classList.toggle('visible')
1397
+ }
1398
+ paperDirectionDomOptionsDom.onclick = function (evt) {
1399
+ const li = evt.target as HTMLLIElement
1400
+ const paperDirection = li.dataset.paperDirection!
1401
+ instance.command.executePaperDirection(<PaperDirection>paperDirection)
1402
+ // 纸张方向状态回显
1403
+ paperDirectionDomOptionsDom
1404
+ .querySelectorAll('li')
1405
+ .forEach(child => child.classList.remove('active'))
1406
+ li.classList.add('active')
1407
+ }
1408
+
1409
+ // 页面边距
1410
+ const paperMarginDom =
1411
+ document.querySelector<HTMLDivElement>('.paper-margin')!
1412
+ paperMarginDom.onclick = function () {
1413
+ const [topMargin, rightMargin, bottomMargin, leftMargin] =
1414
+ instance.command.getPaperMargin()
1415
+ new Dialog({
1416
+ title: '页边距',
1417
+ data: [
1418
+ {
1419
+ type: 'text',
1420
+ label: '上边距',
1421
+ name: 'top',
1422
+ required: true,
1423
+ value: `${topMargin}`,
1424
+ placeholder: '请输入上边距'
1425
+ },
1426
+ {
1427
+ type: 'text',
1428
+ label: '下边距',
1429
+ name: 'bottom',
1430
+ required: true,
1431
+ value: `${bottomMargin}`,
1432
+ placeholder: '请输入下边距'
1433
+ },
1434
+ {
1435
+ type: 'text',
1436
+ label: '左边距',
1437
+ name: 'left',
1438
+ required: true,
1439
+ value: `${leftMargin}`,
1440
+ placeholder: '请输入左边距'
1441
+ },
1442
+ {
1443
+ type: 'text',
1444
+ label: '右边距',
1445
+ name: 'right',
1446
+ required: true,
1447
+ value: `${rightMargin}`,
1448
+ placeholder: '请输入右边距'
1449
+ }
1450
+ ],
1451
+ onConfirm: payload => {
1452
+ const top = payload.find(p => p.name === 'top')?.value
1453
+ if (!top) return
1454
+ const bottom = payload.find(p => p.name === 'bottom')?.value
1455
+ if (!bottom) return
1456
+ const left = payload.find(p => p.name === 'left')?.value
1457
+ if (!left) return
1458
+ const right = payload.find(p => p.name === 'right')?.value
1459
+ if (!right) return
1460
+ instance.command.executeSetPaperMargin([
1461
+ Number(top),
1462
+ Number(right),
1463
+ Number(bottom),
1464
+ Number(left)
1465
+ ])
1466
+ }
1467
+ })
1468
+ }
1469
+
1470
+ // 全屏
1471
+ const fullscreenDom = document.querySelector<HTMLDivElement>('.fullscreen')!
1472
+ fullscreenDom.onclick = toggleFullscreen
1473
+ window.addEventListener('keydown', evt => {
1474
+ if (evt.key === 'F11') {
1475
+ toggleFullscreen()
1476
+ evt.preventDefault()
1477
+ }
1478
+ })
1479
+ document.addEventListener('fullscreenchange', () => {
1480
+ fullscreenDom.classList.toggle('exist')
1481
+ })
1482
+ function toggleFullscreen() {
1483
+ console.log('fullscreen')
1484
+ if (!document.fullscreenElement) {
1485
+ document.documentElement.requestFullscreen()
1486
+ } else {
1487
+ document.exitFullscreen()
1488
+ }
1489
+ }
1490
+
1491
+ // 7. 编辑器使用模式
1492
+ let modeIndex = 0
1493
+ const modeList = [
1494
+ {
1495
+ mode: EditorMode.EDIT,
1496
+ name: '编辑模式'
1497
+ },
1498
+ {
1499
+ mode: EditorMode.CLEAN,
1500
+ name: '清洁模式'
1501
+ },
1502
+ {
1503
+ mode: EditorMode.READONLY,
1504
+ name: '只读模式'
1505
+ },
1506
+ {
1507
+ mode: EditorMode.FORM,
1508
+ name: '表单模式'
1509
+ },
1510
+ {
1511
+ mode: EditorMode.PRINT,
1512
+ name: '打印模式'
1513
+ },
1514
+ {
1515
+ mode: EditorMode.DESIGN,
1516
+ name: '设计模式'
1517
+ },
1518
+ {
1519
+ mode: EditorMode.GRAFFITI,
1520
+ name: '涂鸦模式'
1521
+ }
1522
+ ]
1523
+ const modeElement = document.querySelector<HTMLDivElement>('.editor-mode')!
1524
+ modeElement.onclick = function () {
1525
+ // 模式选择循环
1526
+ modeIndex === modeList.length - 1 ? (modeIndex = 0) : modeIndex++
1527
+ // 设置模式
1528
+ const { name, mode } = modeList[modeIndex]
1529
+ modeElement.innerText = name
1530
+ instance.command.executeMode(mode)
1531
+ // 设置菜单栏权限视觉反馈
1532
+ const isReadonly = mode === EditorMode.READONLY
1533
+ const enableMenuList = ['search', 'print']
1534
+ document.querySelectorAll<HTMLDivElement>('.menu-item>div').forEach(dom => {
1535
+ const menu = dom.dataset.menu
1536
+ isReadonly && (!menu || !enableMenuList.includes(menu))
1537
+ ? dom.classList.add('disable')
1538
+ : dom.classList.remove('disable')
1539
+ })
1540
+ }
1541
+
1542
+ // 模拟批注
1543
+ const commentDom = document.querySelector<HTMLDivElement>('.comment')!
1544
+ async function updateComment() {
1545
+ const groupIds = await instance.command.getGroupIds()
1546
+ for (const comment of commentList) {
1547
+ const activeCommentDom = commentDom.querySelector<HTMLDivElement>(
1548
+ `.comment-item[data-id='${comment.id}']`
1549
+ )
1550
+ // 编辑器是否存在对应成组id
1551
+ if (groupIds.includes(comment.id)) {
1552
+ // 当前dom是否存在-不存在则追加
1553
+ if (!activeCommentDom) {
1554
+ const commentItem = document.createElement('div')
1555
+ commentItem.classList.add('comment-item')
1556
+ commentItem.setAttribute('data-id', comment.id)
1557
+ commentItem.onclick = () => {
1558
+ instance.command.executeLocationGroup(comment.id)
1559
+ }
1560
+ commentDom.append(commentItem)
1561
+ // 选区信息
1562
+ const commentItemTitle = document.createElement('div')
1563
+ commentItemTitle.classList.add('comment-item__title')
1564
+ commentItemTitle.append(document.createElement('span'))
1565
+ const commentItemTitleContent = document.createElement('span')
1566
+ commentItemTitleContent.innerText = comment.rangeText
1567
+ commentItemTitle.append(commentItemTitleContent)
1568
+ const closeDom = document.createElement('i')
1569
+ closeDom.onclick = () => {
1570
+ instance.command.executeDeleteGroup(comment.id)
1571
+ }
1572
+ commentItemTitle.append(closeDom)
1573
+ commentItem.append(commentItemTitle)
1574
+ // 基础信息
1575
+ const commentItemInfo = document.createElement('div')
1576
+ commentItemInfo.classList.add('comment-item__info')
1577
+ const commentItemInfoName = document.createElement('span')
1578
+ commentItemInfoName.innerText = comment.userName
1579
+ const commentItemInfoDate = document.createElement('span')
1580
+ commentItemInfoDate.innerText = comment.createdDate
1581
+ commentItemInfo.append(commentItemInfoName)
1582
+ commentItemInfo.append(commentItemInfoDate)
1583
+ commentItem.append(commentItemInfo)
1584
+ // 详细评论
1585
+ const commentItemContent = document.createElement('div')
1586
+ commentItemContent.classList.add('comment-item__content')
1587
+ commentItemContent.innerText = comment.content
1588
+ commentItem.append(commentItemContent)
1589
+ commentDom.append(commentItem)
1590
+ }
1591
+ } else {
1592
+ // 编辑器内不存在对应成组id则dom则移除
1593
+ activeCommentDom?.remove()
1594
+ }
1595
+ }
1596
+ }
1597
+ // 8. 内部事件监听
1598
+ instance.listener.rangeStyleChange = function (payload) {
1599
+ // 控件类型
1600
+ payload.type === ElementType.SUBSCRIPT
1601
+ ? subscriptDom.classList.add('active')
1602
+ : subscriptDom.classList.remove('active')
1603
+ payload.type === ElementType.SUPERSCRIPT
1604
+ ? superscriptDom.classList.add('active')
1605
+ : superscriptDom.classList.remove('active')
1606
+ payload.type === ElementType.SEPARATOR
1607
+ ? separatorDom.classList.add('active')
1608
+ : separatorDom.classList.remove('active')
1609
+ separatorOptionDom
1610
+ .querySelectorAll('li')
1611
+ .forEach(li => li.classList.remove('active'))
1612
+ if (payload.type === ElementType.SEPARATOR) {
1613
+ const separator = payload.dashArray.join(',') || '0,0'
1614
+ const curSeparatorDom = separatorOptionDom.querySelector<HTMLLIElement>(
1615
+ `[data-separator='${separator}']`
1616
+ )!
1617
+ if (curSeparatorDom) {
1618
+ curSeparatorDom.classList.add('active')
1619
+ }
1620
+ }
1621
+
1622
+ // 富文本
1623
+ fontOptionDom
1624
+ .querySelectorAll<HTMLLIElement>('li')
1625
+ .forEach(li => li.classList.remove('active'))
1626
+ const curFontDom = fontOptionDom.querySelector<HTMLLIElement>(
1627
+ `[data-family='${payload.font}']`
1628
+ )
1629
+ if (curFontDom) {
1630
+ fontSelectDom.innerText = curFontDom.innerText
1631
+ fontSelectDom.style.fontFamily = payload.font
1632
+ curFontDom.classList.add('active')
1633
+ }
1634
+ sizeOptionDom
1635
+ .querySelectorAll<HTMLLIElement>('li')
1636
+ .forEach(li => li.classList.remove('active'))
1637
+ const curSizeDom = sizeOptionDom.querySelector<HTMLLIElement>(
1638
+ `[data-size='${payload.size}']`
1639
+ )
1640
+ if (curSizeDom) {
1641
+ sizeSelectDom.innerText = curSizeDom.innerText
1642
+ curSizeDom.classList.add('active')
1643
+ } else {
1644
+ sizeSelectDom.innerText = `${payload.size}`
1645
+ }
1646
+ payload.bold
1647
+ ? boldDom.classList.add('active')
1648
+ : boldDom.classList.remove('active')
1649
+ payload.italic
1650
+ ? italicDom.classList.add('active')
1651
+ : italicDom.classList.remove('active')
1652
+ payload.underline
1653
+ ? underlineDom.classList.add('active')
1654
+ : underlineDom.classList.remove('active')
1655
+ payload.strikeout
1656
+ ? strikeoutDom.classList.add('active')
1657
+ : strikeoutDom.classList.remove('active')
1658
+ if (payload.color) {
1659
+ colorDom.classList.add('active')
1660
+ colorControlDom.value = payload.color
1661
+ colorSpanDom.style.backgroundColor = payload.color
1662
+ } else {
1663
+ colorDom.classList.remove('active')
1664
+ colorControlDom.value = '#000000'
1665
+ colorSpanDom.style.backgroundColor = '#000000'
1666
+ }
1667
+ if (payload.highlight) {
1668
+ highlightDom.classList.add('active')
1669
+ highlightControlDom.value = payload.highlight
1670
+ highlightSpanDom.style.backgroundColor = payload.highlight
1671
+ } else {
1672
+ highlightDom.classList.remove('active')
1673
+ highlightControlDom.value = '#ffff00'
1674
+ highlightSpanDom.style.backgroundColor = '#ffff00'
1675
+ }
1676
+
1677
+ // 行布局
1678
+ leftDom.classList.remove('active')
1679
+ centerDom.classList.remove('active')
1680
+ rightDom.classList.remove('active')
1681
+ alignmentDom.classList.remove('active')
1682
+ justifyDom.classList.remove('active')
1683
+ if (payload.rowFlex && payload.rowFlex === 'right') {
1684
+ rightDom.classList.add('active')
1685
+ } else if (payload.rowFlex && payload.rowFlex === 'center') {
1686
+ centerDom.classList.add('active')
1687
+ } else if (payload.rowFlex && payload.rowFlex === 'alignment') {
1688
+ alignmentDom.classList.add('active')
1689
+ } else if (payload.rowFlex && payload.rowFlex === 'justify') {
1690
+ justifyDom.classList.add('active')
1691
+ } else {
1692
+ leftDom.classList.add('active')
1693
+ }
1694
+
1695
+ // 行间距
1696
+ rowOptionDom
1697
+ .querySelectorAll<HTMLLIElement>('li')
1698
+ .forEach(li => li.classList.remove('active'))
1699
+ const curRowMarginDom = rowOptionDom.querySelector<HTMLLIElement>(
1700
+ `[data-rowmargin='${payload.rowMargin}']`
1701
+ )!
1702
+ curRowMarginDom.classList.add('active')
1703
+
1704
+ // 功能
1705
+ payload.undo
1706
+ ? undoDom.classList.remove('no-allow')
1707
+ : undoDom.classList.add('no-allow')
1708
+ payload.redo
1709
+ ? redoDom.classList.remove('no-allow')
1710
+ : redoDom.classList.add('no-allow')
1711
+ payload.painter
1712
+ ? painterDom.classList.add('active')
1713
+ : painterDom.classList.remove('active')
1714
+
1715
+ // 标题
1716
+ titleOptionDom
1717
+ .querySelectorAll<HTMLLIElement>('li')
1718
+ .forEach(li => li.classList.remove('active'))
1719
+ if (payload.level) {
1720
+ const curTitleDom = titleOptionDom.querySelector<HTMLLIElement>(
1721
+ `[data-level='${payload.level}']`
1722
+ )!
1723
+ titleSelectDom.innerText = curTitleDom.innerText
1724
+ curTitleDom.classList.add('active')
1725
+ } else {
1726
+ titleSelectDom.innerText = '正文'
1727
+ titleOptionDom.querySelector('li:first-child')!.classList.add('active')
1728
+ }
1729
+
1730
+ // 列表
1731
+ listOptionDom
1732
+ .querySelectorAll<HTMLLIElement>('li')
1733
+ .forEach(li => li.classList.remove('active'))
1734
+ if (payload.listType) {
1735
+ listDom.classList.add('active')
1736
+ const listType = payload.listType
1737
+ const listStyle =
1738
+ payload.listType === ListType.OL ? ListStyle.DECIMAL : payload.listType
1739
+ const curListDom = listOptionDom.querySelector<HTMLLIElement>(
1740
+ `[data-list-type='${listType}'][data-list-style='${listStyle}']`
1741
+ )
1742
+ if (curListDom) {
1743
+ curListDom.classList.add('active')
1744
+ }
1745
+ } else {
1746
+ listDom.classList.remove('active')
1747
+ }
1748
+
1749
+ // 批注
1750
+ commentDom
1751
+ .querySelectorAll<HTMLDivElement>('.comment-item')
1752
+ .forEach(commentItemDom => {
1753
+ commentItemDom.classList.remove('active')
1754
+ })
1755
+ if (payload.groupIds) {
1756
+ const [id] = payload.groupIds
1757
+ const activeCommentDom = commentDom.querySelector<HTMLDivElement>(
1758
+ `.comment-item[data-id='${id}']`
1759
+ )
1760
+ if (activeCommentDom) {
1761
+ activeCommentDom.classList.add('active')
1762
+ scrollIntoView(commentDom, activeCommentDom)
1763
+ }
1764
+ }
1765
+
1766
+ // 行列信息
1767
+ const rangeContext = instance.command.getRangeContext()
1768
+ if (rangeContext) {
1769
+ document.querySelector<HTMLSpanElement>('.row-no')!.innerText = `${
1770
+ rangeContext.startRowNo + 1
1771
+ }`
1772
+ document.querySelector<HTMLSpanElement>('.col-no')!.innerText = `${
1773
+ rangeContext.startColNo + 1
1774
+ }`
1775
+ }
1776
+ }
1777
+
1778
+ instance.listener.visiblePageNoListChange = function (payload) {
1779
+ const text = payload.map(i => i + 1).join('、')
1780
+ document.querySelector<HTMLSpanElement>('.page-no-list')!.innerText = text
1781
+ }
1782
+
1783
+ instance.listener.pageSizeChange = function (payload) {
1784
+ document.querySelector<HTMLSpanElement>('.page-size')!.innerText =
1785
+ `${payload}`
1786
+ }
1787
+
1788
+ instance.listener.intersectionPageNoChange = function (payload) {
1789
+ document.querySelector<HTMLSpanElement>('.page-no')!.innerText = `${
1790
+ payload + 1
1791
+ }`
1792
+ }
1793
+
1794
+ instance.listener.pageScaleChange = function (payload) {
1795
+ document.querySelector<HTMLSpanElement>(
1796
+ '.page-scale-percentage'
1797
+ )!.innerText = `${Math.floor(payload * 10 * 10)}%`
1798
+ }
1799
+
1800
+ instance.listener.controlChange = function (payload) {
1801
+ const disableMenusInControlContext = [
1802
+ 'table',
1803
+ 'hyperlink',
1804
+ 'separator',
1805
+ 'page-break',
1806
+ 'control'
1807
+ ]
1808
+ // 菜单操作权限
1809
+ disableMenusInControlContext.forEach(menu => {
1810
+ const menuDom = document.querySelector<HTMLDivElement>(
1811
+ `.menu-item__${menu}`
1812
+ )!
1813
+ payload.state === ControlState.ACTIVE
1814
+ ? menuDom.classList.add('disable')
1815
+ : menuDom.classList.remove('disable')
1816
+ })
1817
+ }
1818
+
1819
+ instance.listener.pageModeChange = function (payload) {
1820
+ const activeMode = pageModeOptionsDom.querySelector<HTMLLIElement>(
1821
+ `[data-page-mode='${payload}']`
1822
+ )!
1823
+ pageModeOptionsDom
1824
+ .querySelectorAll('li')
1825
+ .forEach(li => li.classList.remove('active'))
1826
+ activeMode.classList.add('active')
1827
+ }
1828
+
1829
+ const handleContentChange = async function () {
1830
+ // 字数
1831
+ const wordCount = await instance.command.getWordCount()
1832
+ document.querySelector<HTMLSpanElement>('.word-count')!.innerText = `${
1833
+ wordCount || 0
1834
+ }`
1835
+ // 目录
1836
+ if (isCatalogShow) {
1837
+ nextTick(() => {
1838
+ updateCatalog()
1839
+ })
1840
+ }
1841
+ // 批注
1842
+ nextTick(() => {
1843
+ updateComment()
1844
+ })
1845
+ }
1846
+ instance.listener.contentChange = debounce(handleContentChange, 200)
1847
+ handleContentChange()
1848
+
1849
+ instance.listener.saved = function (payload) {
1850
+ console.log('elementList: ', payload)
1851
+ }
1852
+
1853
+ // 9. 右键菜单注册
1854
+ instance.register.contextMenuList([
1855
+ {
1856
+ name: '批注',
1857
+ when: payload => {
1858
+ return (
1859
+ !payload.isReadonly &&
1860
+ payload.editorHasSelection &&
1861
+ payload.zone === EditorZone.MAIN
1862
+ )
1863
+ },
1864
+ callback: (command: Command) => {
1865
+ new Dialog({
1866
+ title: '批注',
1867
+ data: [
1868
+ {
1869
+ type: 'textarea',
1870
+ label: '批注',
1871
+ height: 100,
1872
+ name: 'value',
1873
+ required: true,
1874
+ placeholder: '请输入批注'
1875
+ }
1876
+ ],
1877
+ onConfirm: payload => {
1878
+ const value = payload.find(p => p.name === 'value')?.value
1879
+ if (!value) return
1880
+ const groupId = command.executeSetGroup()
1881
+ if (!groupId) return
1882
+ commentList.push({
1883
+ id: groupId,
1884
+ content: value,
1885
+ userName: 'Hufe',
1886
+ rangeText: command.getRangeText(),
1887
+ createdDate: new Date().toLocaleString()
1888
+ })
1889
+ }
1890
+ })
1891
+ }
1892
+ },
1893
+ {
1894
+ name: '新增题注',
1895
+ icon: 'caption',
1896
+ when: payload => {
1897
+ return (
1898
+ !payload.isReadonly &&
1899
+ payload.startElement?.type === ElementType.IMAGE &&
1900
+ !payload.startElement?.imgCaption
1901
+ )
1902
+ },
1903
+ callback: (command: Command) => {
1904
+ new Dialog({
1905
+ title: '新增题注',
1906
+ data: [
1907
+ {
1908
+ type: 'text',
1909
+ label: '题注内容',
1910
+ name: 'value',
1911
+ required: true,
1912
+ placeholder: '请输入题注内容,使用{imageNo}表示图片序号'
1913
+ }
1914
+ ],
1915
+ onConfirm: payload => {
1916
+ const value = payload.find(p => p.name === 'value')?.value
1917
+ if (!value) return
1918
+ command.executeSetImageCaption({
1919
+ value
1920
+ })
1921
+ }
1922
+ })
1923
+ }
1924
+ },
1925
+ {
1926
+ name: '修改题注',
1927
+ icon: 'caption',
1928
+ when: payload => {
1929
+ return (
1930
+ !payload.isReadonly &&
1931
+ payload.startElement?.type === ElementType.IMAGE &&
1932
+ !!payload.startElement?.imgCaption
1933
+ )
1934
+ },
1935
+ callback: (command: Command, context) => {
1936
+ const currentCaption = context.startElement?.imgCaption
1937
+ new Dialog({
1938
+ title: '修改题注',
1939
+ data: [
1940
+ {
1941
+ type: 'text',
1942
+ label: '题注内容',
1943
+ name: 'value',
1944
+ required: true,
1945
+ value: currentCaption?.value,
1946
+ placeholder: '请输入题注内容,使用{imageNo}表示图片序号'
1947
+ }
1948
+ ],
1949
+ onConfirm: payload => {
1950
+ const value = payload.find(p => p.name === 'value')?.value
1951
+ command.executeSetImageCaption({
1952
+ ...currentCaption,
1953
+ value: value || ''
1954
+ })
1955
+ }
1956
+ })
1957
+ }
1958
+ },
1959
+ {
1960
+ name: '签名',
1961
+ icon: 'signature',
1962
+ when: payload => {
1963
+ return !payload.isReadonly && payload.editorTextFocus
1964
+ },
1965
+ callback: (command: Command) => {
1966
+ new Signature({
1967
+ onConfirm(payload) {
1968
+ if (!payload) return
1969
+ const { value, width, height } = payload
1970
+ if (!value || !width || !height) return
1971
+ command.executeInsertElementList([
1972
+ {
1973
+ value,
1974
+ width,
1975
+ height,
1976
+ type: ElementType.IMAGE
1977
+ }
1978
+ ])
1979
+ }
1980
+ })
1981
+ }
1982
+ },
1983
+ {
1984
+ name: '格式整理',
1985
+ icon: 'word-tool',
1986
+ when: payload => {
1987
+ return !payload.isReadonly
1988
+ },
1989
+ callback: (command: Command) => {
1990
+ command.executeWordTool()
1991
+ }
1992
+ },
1993
+ {
1994
+ name: '清空涂鸦信息',
1995
+ when: payload => {
1996
+ return payload.options.mode === EditorMode.GRAFFITI
1997
+ },
1998
+ callback: (command: Command) => {
1999
+ command.executeClearGraffiti()
2000
+ }
2001
+ }
2002
+ ])
2003
+
2004
+ // 10. 快捷键注册
2005
+ instance.register.shortcutList([
2006
+ {
2007
+ key: KeyMap.P,
2008
+ mod: true,
2009
+ isGlobal: true,
2010
+ callback: (command: Command) => {
2011
+ command.executePrint()
2012
+ }
2013
+ },
2014
+ {
2015
+ key: KeyMap.F,
2016
+ mod: true,
2017
+ isGlobal: true,
2018
+ callback: (command: Command) => {
2019
+ const text = command.getRangeText()
2020
+ searchDom.click()
2021
+ if (text) {
2022
+ searchInputDom.value = text
2023
+ instance.command.executeSearch(text)
2024
+ setSearchResult()
2025
+ }
2026
+ }
2027
+ },
2028
+ {
2029
+ key: KeyMap.MINUS,
2030
+ ctrl: true,
2031
+ isGlobal: true,
2032
+ callback: (command: Command) => {
2033
+ command.executePageScaleMinus()
2034
+ }
2035
+ },
2036
+ {
2037
+ key: KeyMap.EQUAL,
2038
+ ctrl: true,
2039
+ isGlobal: true,
2040
+ callback: (command: Command) => {
2041
+ command.executePageScaleAdd()
2042
+ }
2043
+ },
2044
+ {
2045
+ key: KeyMap.ZERO,
2046
+ ctrl: true,
2047
+ isGlobal: true,
2048
+ callback: (command: Command) => {
2049
+ command.executePageScaleRecovery()
2050
+ }
2051
+ }
2052
+ ])
2053
+ }