@cherry-markdown/cherry-markdown-dev 0.8.58-dev

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 (319) hide show
  1. package/package.json +149 -0
  2. package/src/Cherry.config.js +625 -0
  3. package/src/Cherry.js +1104 -0
  4. package/src/CherryStatic.js +70 -0
  5. package/src/Editor.js +748 -0
  6. package/src/Engine.js +381 -0
  7. package/src/Event.js +140 -0
  8. package/src/Factory.js +180 -0
  9. package/src/Logger.js +31 -0
  10. package/src/Previewer.js +1183 -0
  11. package/src/Sanitizer.js +4 -0
  12. package/src/Sanitizer.node.js +7 -0
  13. package/src/UrlCache.js +98 -0
  14. package/src/addons/advance/cherry-table-echarts-plugin.js +170 -0
  15. package/src/addons/cherry-code-block-mermaid-plugin.js +158 -0
  16. package/src/addons/cherry-code-block-plantuml-plugin.js +106 -0
  17. package/src/core/HookCenter.js +297 -0
  18. package/src/core/HooksConfig.js +100 -0
  19. package/src/core/ParagraphBase.js +332 -0
  20. package/src/core/SentenceBase.js +65 -0
  21. package/src/core/SyntaxBase.js +194 -0
  22. package/src/core/hooks/AutoLink.js +232 -0
  23. package/src/core/hooks/BackgroundColor.js +46 -0
  24. package/src/core/hooks/Blockquote.js +70 -0
  25. package/src/core/hooks/Br.js +85 -0
  26. package/src/core/hooks/CodeBlock.js +446 -0
  27. package/src/core/hooks/Color.js +46 -0
  28. package/src/core/hooks/CommentReference.js +96 -0
  29. package/src/core/hooks/Detail.js +108 -0
  30. package/src/core/hooks/Emoji.config.js +1825 -0
  31. package/src/core/hooks/Emoji.js +119 -0
  32. package/src/core/hooks/Emphasis.js +113 -0
  33. package/src/core/hooks/Footnote.js +125 -0
  34. package/src/core/hooks/FrontMatter.js +51 -0
  35. package/src/core/hooks/Header.js +234 -0
  36. package/src/core/hooks/HighLight.js +37 -0
  37. package/src/core/hooks/Hr.js +52 -0
  38. package/src/core/hooks/HtmlBlock.js +184 -0
  39. package/src/core/hooks/Image.js +174 -0
  40. package/src/core/hooks/InlineCode.js +48 -0
  41. package/src/core/hooks/InlineMath.js +107 -0
  42. package/src/core/hooks/Link.js +160 -0
  43. package/src/core/hooks/List.js +264 -0
  44. package/src/core/hooks/MathBlock.js +103 -0
  45. package/src/core/hooks/Panel.js +145 -0
  46. package/src/core/hooks/Paragraph.js +84 -0
  47. package/src/core/hooks/Ruby.js +34 -0
  48. package/src/core/hooks/Size.js +51 -0
  49. package/src/core/hooks/Strikethrough.js +54 -0
  50. package/src/core/hooks/Sub.js +47 -0
  51. package/src/core/hooks/SuggestList.js +333 -0
  52. package/src/core/hooks/Suggester.js +707 -0
  53. package/src/core/hooks/Sup.js +47 -0
  54. package/src/core/hooks/Table.js +275 -0
  55. package/src/core/hooks/Toc.js +292 -0
  56. package/src/core/hooks/Transfer.js +47 -0
  57. package/src/core/hooks/Underline.js +37 -0
  58. package/src/index.core.js +29 -0
  59. package/src/index.engine.core.js +68 -0
  60. package/src/index.engine.js +28 -0
  61. package/src/index.js +32 -0
  62. package/src/libs/mermaidAPI.8.4.8.js +1 -0
  63. package/src/libs/mermaidAPI.8.5.2.js +42 -0
  64. package/src/libs/rawdeflate.js +1663 -0
  65. package/src/locales/en_US.js +139 -0
  66. package/src/locales/index.js +25 -0
  67. package/src/locales/ru_RU.js +139 -0
  68. package/src/locales/zh_CN.js +142 -0
  69. package/src/sass/base.scss +26 -0
  70. package/src/sass/bubble_formula.scss +166 -0
  71. package/src/sass/ch-icon.scss +118 -0
  72. package/src/sass/cherry.scss +1116 -0
  73. package/src/sass/components/bubble.scss +173 -0
  74. package/src/sass/components/shortcut_key_config.scss +108 -0
  75. package/src/sass/formula_utils_bubble.scss +82 -0
  76. package/src/sass/icon_template.scss +24 -0
  77. package/src/sass/icons/uEA03-list.svg +19 -0
  78. package/src/sass/icons/uEA04-check.svg +14 -0
  79. package/src/sass/icons/uEA09-square.svg +10 -0
  80. package/src/sass/icons/uEA0A-bold.svg +20 -0
  81. package/src/sass/icons/uEA0B-code.svg +18 -0
  82. package/src/sass/icons/uEA0C-color.svg +13 -0
  83. package/src/sass/icons/uEA0D-header.svg +8 -0
  84. package/src/sass/icons/uEA0E-image.svg +15 -0
  85. package/src/sass/icons/uEA0F-italic.svg +8 -0
  86. package/src/sass/icons/uEA10-link.svg +16 -0
  87. package/src/sass/icons/uEA11-ol.svg +21 -0
  88. package/src/sass/icons/uEA12-size.svg +11 -0
  89. package/src/sass/icons/uEA13-strike.svg +16 -0
  90. package/src/sass/icons/uEA14-table.svg +12 -0
  91. package/src/sass/icons/uEA15-ul.svg +17 -0
  92. package/src/sass/icons/uEA16-underline.svg +13 -0
  93. package/src/sass/icons/uEA17-word.svg +16 -0
  94. package/src/sass/icons/uEA18-blockquote.svg +11 -0
  95. package/src/sass/icons/uEA19-font.svg +10 -0
  96. package/src/sass/icons/uEA1F-insertClass.svg +39 -0
  97. package/src/sass/icons/uEA20-insertFlow.svg +8 -0
  98. package/src/sass/icons/uEA21-insertFormula.svg +23 -0
  99. package/src/sass/icons/uEA22-insertGantt.svg +13 -0
  100. package/src/sass/icons/uEA23-insertGraph.svg +13 -0
  101. package/src/sass/icons/uEA24-insertPie.svg +19 -0
  102. package/src/sass/icons/uEA25-insertSeq.svg +20 -0
  103. package/src/sass/icons/uEA26-insertState.svg +35 -0
  104. package/src/sass/icons/uEA27-line.svg +11 -0
  105. package/src/sass/icons/uEA28-preview.svg +18 -0
  106. package/src/sass/icons/uEA29-previewClose.svg +24 -0
  107. package/src/sass/icons/uEA2A-toc.svg +24 -0
  108. package/src/sass/icons/uEA2D-sub.svg +15 -0
  109. package/src/sass/icons/uEA2E-sup.svg +15 -0
  110. package/src/sass/icons/uEA2F-h1.svg +16 -0
  111. package/src/sass/icons/uEA30-h2.svg +20 -0
  112. package/src/sass/icons/uEA31-h3.svg +23 -0
  113. package/src/sass/icons/uEA32-h4.svg +16 -0
  114. package/src/sass/icons/uEA33-h5.svg +20 -0
  115. package/src/sass/icons/uEA34-h6.svg +17 -0
  116. package/src/sass/icons/uEA35-video.svg +20 -0
  117. package/src/sass/icons/uEA36-insert.svg +25 -0
  118. package/src/sass/icons/uEA37-little_table.svg +30 -0
  119. package/src/sass/icons/uEA38-pdf.svg +27 -0
  120. package/src/sass/icons/uEA39-checklist.svg +22 -0
  121. package/src/sass/icons/uEA40-close.svg +12 -0
  122. package/src/sass/icons/uEA41-fullscreen.svg +81 -0
  123. package/src/sass/icons/uEA42-minscreen.svg +77 -0
  124. package/src/sass/icons/uEA43-insertChart.svg +23 -0
  125. package/src/sass/icons/uEA44-question.svg +25 -0
  126. package/src/sass/icons/uEA45-settings.svg +32 -0
  127. package/src/sass/icons/uEA46-ok.svg +7 -0
  128. package/src/sass/icons/uEA47-br.svg +22 -0
  129. package/src/sass/icons/uEA48-normal.svg +15 -0
  130. package/src/sass/icons/uEA49-undo.svg +19 -0
  131. package/src/sass/icons/uEA50-redo.svg +21 -0
  132. package/src/sass/icons/uEA51-copy.svg +6 -0
  133. package/src/sass/icons/uEA52-phone.svg +5 -0
  134. package/src/sass/icons/uEA53-cherry-table-delete.svg +17 -0
  135. package/src/sass/icons/uEA54-cherry-table-insert-bottom.svg +16 -0
  136. package/src/sass/icons/uEA55-cherry-table-insert-left.svg +15 -0
  137. package/src/sass/icons/uEA56-cherry-table-insert-right.svg +16 -0
  138. package/src/sass/icons/uEA57-cherry-table-insert-top.svg +16 -0
  139. package/src/sass/icons/uEA58-sort-s.svg +13 -0
  140. package/src/sass/icons/uEA59-pinyin.svg +1 -0
  141. package/src/sass/icons/uEA5A-create.svg +24 -0
  142. package/src/sass/icons/uEA5B-download.svg +34 -0
  143. package/src/sass/icons/uEA5C-edit.svg +3 -0
  144. package/src/sass/icons/uEA5D-export.svg +53 -0
  145. package/src/sass/icons/uEA5E-folder-open.svg +3 -0
  146. package/src/sass/icons/uEA5F-folder.svg +3 -0
  147. package/src/sass/icons/uEA60-help.svg +5 -0
  148. package/src/sass/icons/uEA61-pen-fill.svg +13 -0
  149. package/src/sass/icons/uEA62-pen.svg +3 -0
  150. package/src/sass/icons/uEA64-tips.svg +5 -0
  151. package/src/sass/icons/uEA65-warn.svg +5 -0
  152. package/src/sass/icons/uEA66-mistake.svg +4 -0
  153. package/src/sass/icons/uEA67-success.svg +4 -0
  154. package/src/sass/icons/uEA68-danger.svg +4 -0
  155. package/src/sass/icons/uEA69-info.svg +5 -0
  156. package/src/sass/icons/uEA6A-primary.svg +5 -0
  157. package/src/sass/icons/uEA6B-warning.svg +5 -0
  158. package/src/sass/icons/uEA6C-justify.svg +19 -0
  159. package/src/sass/icons/uEA6D-justifyCenter.svg +19 -0
  160. package/src/sass/icons/uEA6E-justifyLeft.svg +19 -0
  161. package/src/sass/icons/uEA6F-justifyRight.svg +19 -0
  162. package/src/sass/icons/uEA70-chevronsLeft.svg +1 -0
  163. package/src/sass/icons/uEA71-chevronsRight.svg +1 -0
  164. package/src/sass/icons/uEA72-trendingUp.svg +1 -0
  165. package/src/sass/icons/uEA74-codeBlock.svg +1 -0
  166. package/src/sass/icons/uEA75-expand.svg +3 -0
  167. package/src/sass/icons/uEA76-unExpand.svg +3 -0
  168. package/src/sass/icons/uEA77-swap-vert.svg +1 -0
  169. package/src/sass/icons/uEA78-swap.svg +1 -0
  170. package/src/sass/icons/uEA79-keyboard.svg +1 -0
  171. package/src/sass/icons/uEA7A-command.svg +1 -0
  172. package/src/sass/icons/uEA7B-search.svg +1 -0
  173. package/src/sass/index.scss +3 -0
  174. package/src/sass/markdown.scss +668 -0
  175. package/src/sass/markdown_pure.scss +9 -0
  176. package/src/sass/prettyprint/prettyprint.scss +118 -0
  177. package/src/sass/previewer.scss +179 -0
  178. package/src/sass/print.scss +13 -0
  179. package/src/sass/prism/coy.scss +220 -0
  180. package/src/sass/prism/dark.scss +132 -0
  181. package/src/sass/prism/default.scss +143 -0
  182. package/src/sass/prism/funky.scss +133 -0
  183. package/src/sass/prism/okaidia.scss +126 -0
  184. package/src/sass/prism/one-dark.scss +440 -0
  185. package/src/sass/prism/one-light.scss +428 -0
  186. package/src/sass/prism/solarized-light.scss +153 -0
  187. package/src/sass/prism/tomorrow-night.scss +125 -0
  188. package/src/sass/prism/twilight.scss +202 -0
  189. package/src/sass/prism/vs-dark.scss +275 -0
  190. package/src/sass/prism/vs-light.scss +168 -0
  191. package/src/sass/themes/blue.scss +411 -0
  192. package/src/sass/themes/dark.scss +517 -0
  193. package/src/sass/themes/default.scss +255 -0
  194. package/src/sass/themes/green.scss +395 -0
  195. package/src/sass/themes/light.scss +368 -0
  196. package/src/sass/themes/red.scss +397 -0
  197. package/src/sass/themes/violet.scss +410 -0
  198. package/src/sass/variable.scss +84 -0
  199. package/src/toolbars/Bubble.js +234 -0
  200. package/src/toolbars/BubbleFormula.js +298 -0
  201. package/src/toolbars/BubbleTable.js +147 -0
  202. package/src/toolbars/FloatMenu.js +131 -0
  203. package/src/toolbars/HiddenToolbar.js +36 -0
  204. package/src/toolbars/HookCenter.js +234 -0
  205. package/src/toolbars/MenuBase.js +569 -0
  206. package/src/toolbars/PreviewerBubble.js +608 -0
  207. package/src/toolbars/ShortcutKeyConfigPanel.js +345 -0
  208. package/src/toolbars/Sidebar.js +36 -0
  209. package/src/toolbars/Toc.js +242 -0
  210. package/src/toolbars/Toolbar.js +449 -0
  211. package/src/toolbars/ToolbarRight.js +37 -0
  212. package/src/toolbars/hooks/Audio.js +79 -0
  213. package/src/toolbars/hooks/BarTable.js +41 -0
  214. package/src/toolbars/hooks/Bold.js +73 -0
  215. package/src/toolbars/hooks/Br.js +34 -0
  216. package/src/toolbars/hooks/ChangeLocale.js +62 -0
  217. package/src/toolbars/hooks/ChatGpt.js +182 -0
  218. package/src/toolbars/hooks/CheckList.js +41 -0
  219. package/src/toolbars/hooks/Code.js +49 -0
  220. package/src/toolbars/hooks/CodeTheme.js +66 -0
  221. package/src/toolbars/hooks/Color.js +298 -0
  222. package/src/toolbars/hooks/Copy.js +141 -0
  223. package/src/toolbars/hooks/Detail.js +69 -0
  224. package/src/toolbars/hooks/DrawIo.js +57 -0
  225. package/src/toolbars/hooks/Export.js +49 -0
  226. package/src/toolbars/hooks/File.js +79 -0
  227. package/src/toolbars/hooks/Formula.js +69 -0
  228. package/src/toolbars/hooks/FullScreen.js +50 -0
  229. package/src/toolbars/hooks/Graph.js +263 -0
  230. package/src/toolbars/hooks/H1.js +71 -0
  231. package/src/toolbars/hooks/H2.js +71 -0
  232. package/src/toolbars/hooks/H3.js +71 -0
  233. package/src/toolbars/hooks/Header.js +118 -0
  234. package/src/toolbars/hooks/Hr.js +35 -0
  235. package/src/toolbars/hooks/Image.js +91 -0
  236. package/src/toolbars/hooks/InlineCode.js +53 -0
  237. package/src/toolbars/hooks/Insert.js +193 -0
  238. package/src/toolbars/hooks/Italic.js +72 -0
  239. package/src/toolbars/hooks/Justify.js +49 -0
  240. package/src/toolbars/hooks/LineTable.js +41 -0
  241. package/src/toolbars/hooks/Link.js +49 -0
  242. package/src/toolbars/hooks/List.js +55 -0
  243. package/src/toolbars/hooks/MobilePreview.js +44 -0
  244. package/src/toolbars/hooks/Ol.js +41 -0
  245. package/src/toolbars/hooks/Panel.js +140 -0
  246. package/src/toolbars/hooks/Pdf.js +78 -0
  247. package/src/toolbars/hooks/Publish.js +123 -0
  248. package/src/toolbars/hooks/QuickTable.js +43 -0
  249. package/src/toolbars/hooks/Quote.js +45 -0
  250. package/src/toolbars/hooks/Redo.js +33 -0
  251. package/src/toolbars/hooks/Ruby.js +59 -0
  252. package/src/toolbars/hooks/Search.js +53 -0
  253. package/src/toolbars/hooks/Settings.js +220 -0
  254. package/src/toolbars/hooks/ShortcutKey.js +62 -0
  255. package/src/toolbars/hooks/Size.js +118 -0
  256. package/src/toolbars/hooks/Split.js +37 -0
  257. package/src/toolbars/hooks/Strikethrough.js +71 -0
  258. package/src/toolbars/hooks/Sub.js +58 -0
  259. package/src/toolbars/hooks/Sup.js +58 -0
  260. package/src/toolbars/hooks/SwitchModel.js +56 -0
  261. package/src/toolbars/hooks/Table.js +56 -0
  262. package/src/toolbars/hooks/Theme.js +62 -0
  263. package/src/toolbars/hooks/Toc.js +35 -0
  264. package/src/toolbars/hooks/TogglePreview.js +91 -0
  265. package/src/toolbars/hooks/Ul.js +41 -0
  266. package/src/toolbars/hooks/Underline.js +68 -0
  267. package/src/toolbars/hooks/Undo.js +30 -0
  268. package/src/toolbars/hooks/Video.js +79 -0
  269. package/src/toolbars/hooks/Word.js +78 -0
  270. package/src/toolbars/hooks/WordCount.js +106 -0
  271. package/src/utils/autoindent.js +58 -0
  272. package/src/utils/cm-search-replace.js +794 -0
  273. package/src/utils/code-preview-language-setting.js +180 -0
  274. package/src/utils/codeBlockContentHandler.js +400 -0
  275. package/src/utils/config.js +174 -0
  276. package/src/utils/copy.js +55 -0
  277. package/src/utils/dialog.js +214 -0
  278. package/src/utils/dom.js +163 -0
  279. package/src/utils/downloadUtil.js +23 -0
  280. package/src/utils/env.js +22 -0
  281. package/src/utils/error.js +61 -0
  282. package/src/utils/event.js +38 -0
  283. package/src/utils/export.js +166 -0
  284. package/src/utils/file.js +164 -0
  285. package/src/utils/formulaUtilsHandler.js +232 -0
  286. package/src/utils/htmlparser.js +976 -0
  287. package/src/utils/image.js +99 -0
  288. package/src/utils/imgSizeHandler.js +279 -0
  289. package/src/utils/lazyLoadImg.js +327 -0
  290. package/src/utils/lineFeed.js +49 -0
  291. package/src/utils/listContentHandler.js +227 -0
  292. package/src/utils/lookbehind-replace.js +81 -0
  293. package/src/utils/mathjax.js +89 -0
  294. package/src/utils/myersDiff.js +211 -0
  295. package/src/utils/pasteHelper.js +253 -0
  296. package/src/utils/platformTransform.js +71 -0
  297. package/src/utils/recount-pos.js +59 -0
  298. package/src/utils/regexp.js +295 -0
  299. package/src/utils/sanitize.js +477 -0
  300. package/src/utils/selection.js +50 -0
  301. package/src/utils/shortcutKey.js +291 -0
  302. package/src/utils/svgUtils.js +96 -0
  303. package/src/utils/tableContentHandler.js +876 -0
  304. package/test/core/CommonMark.spec.ts +62 -0
  305. package/test/core/hooks/AutoLink.spec.ts +28 -0
  306. package/test/core/hooks/List.spec.ts +79 -0
  307. package/test/core/hooks/__snapshots__/List.spec.ts.snap +11 -0
  308. package/test/example.md +778 -0
  309. package/test/node.js +10 -0
  310. package/test/suites/commonmark.spec.json +5218 -0
  311. package/test/tsconfig.test.json +6 -0
  312. package/test/utils/regexp.spec.ts +28 -0
  313. package/types/cherry.d.ts +675 -0
  314. package/types/codemirror.d.ts +22 -0
  315. package/types/editor.d.ts +72 -0
  316. package/types/global.d.ts +16 -0
  317. package/types/menus.d.ts +24 -0
  318. package/types/previewer.d.ts +53 -0
  319. package/types/syntax.d.ts +52 -0
package/src/Editor.js ADDED
@@ -0,0 +1,748 @@
1
+ /**
2
+ * Copyright (C) 2021 THL A29 Limited, a Tencent company.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ // @ts-check
17
+ import codemirror from 'codemirror';
18
+ // import 'codemirror/mode/markdown/markdown';
19
+ import 'codemirror/mode/gfm/gfm'; // https://codemirror.net/mode/gfm/index.html
20
+ import 'codemirror/mode/yaml-frontmatter/yaml-frontmatter'; // https://codemirror.net/5/mode/yaml-frontmatter/index.html
21
+ // import 'codemirror/mode/xml/xml';
22
+ import 'codemirror/addon/edit/continuelist';
23
+ import 'codemirror/addon/edit/closetag';
24
+ import 'codemirror/addon/fold/xml-fold';
25
+ import 'codemirror/addon/edit/matchtags';
26
+ import 'codemirror/addon/display/placeholder';
27
+ import 'codemirror/keymap/sublime';
28
+ import 'codemirror/keymap/vim';
29
+
30
+ // import 'cm-search-replace/src/search';
31
+ import 'codemirror/addon/search/searchcursor';
32
+ import 'codemirror/addon/scroll/annotatescrollbar';
33
+ import 'codemirror/addon/search/matchesonscrollbar';
34
+ // import 'codemirror/addon/selection/active-line';
35
+ // import 'codemirror/addon/edit/matchbrackets';
36
+ import htmlParser from '@/utils/htmlparser';
37
+ import pasteHelper from '@/utils/pasteHelper';
38
+ import { addEvent } from './utils/event';
39
+ import Logger from '@/Logger';
40
+ import { handleFileUploadCallback } from '@/utils/file';
41
+ import { createElement } from './utils/dom';
42
+ import { longTextReg, base64Reg, imgDrawioXmlReg } from './utils/regexp';
43
+ import { handleNewlineIndentList } from './utils/autoindent';
44
+
45
+ /**
46
+ * @typedef {import('~types/editor').EditorConfiguration} EditorConfiguration
47
+ * @typedef {import('~types/editor').EditorEventCallback} EditorEventCallback
48
+ * @typedef {import('codemirror')} CodeMirror
49
+ */
50
+
51
+ /** @type {import('~types/editor')} */
52
+ export default class Editor {
53
+ /**
54
+ * @constructor
55
+ * @param {Partial<EditorConfiguration>} options
56
+ */
57
+ constructor(options) {
58
+ /**
59
+ * @property
60
+ * @type {EditorConfiguration}
61
+ */
62
+ this.options = {
63
+ id: 'code', // textarea 的id属性值
64
+ name: 'code', // textarea 的name属性值
65
+ autoSave2Textarea: false,
66
+ editorDom: document.createElement('div'),
67
+ wrapperDom: null,
68
+ autoScrollByCursor: true,
69
+ convertWhenPaste: true,
70
+ keyMap: 'sublime',
71
+ showFullWidthMark: true,
72
+ showSuggestList: true,
73
+ codemirror: {
74
+ lineNumbers: false, // 显示行数
75
+ cursorHeight: 0.85, // 光标高度,0.85好看一些
76
+ indentUnit: 4, // 缩进单位为4
77
+ tabSize: 4, // 一个tab转换成的空格数量
78
+ // styleActiveLine: false, // 当前行背景高亮
79
+ // matchBrackets: true, // 括号匹配
80
+ // mode: 'gfm', // 从markdown模式改成gfm模式,以使用默认高亮规则
81
+ mode: {
82
+ name: 'yaml-frontmatter', // yaml-frontmatter在gfm的基础上增加了对yaml的支持
83
+ base: {
84
+ name: 'gfm',
85
+ gitHubSpice: false, // 修复github风格的markdown语法高亮,见[issue#925](https://github.com/Tencent/cherry-markdown/issues/925)
86
+ },
87
+ },
88
+ lineWrapping: true, // 自动换行
89
+ indentWithTabs: true, // 缩进用tab表示
90
+ autofocus: true,
91
+ theme: 'default',
92
+ autoCloseTags: true, // 输入html标签时自动补充闭合标签
93
+ extraKeys: {
94
+ Enter: handleNewlineIndentList,
95
+ }, // 增加markdown回车自动补全
96
+ matchTags: { bothTags: true }, // 自动高亮选中的闭合html标签
97
+ placeholder: '',
98
+ // 设置为 contenteditable 对输入法定位更友好
99
+ // 但已知会影响某些悬浮菜单的定位,如粘贴选择文本或markdown模式的菜单
100
+ // inputStyle: 'contenteditable',
101
+ keyMap: 'sublime',
102
+ },
103
+ toolbars: {},
104
+ onKeydown() {},
105
+ onChange() {},
106
+ onFocus() {},
107
+ onBlur() {},
108
+ onPaste: this.onPaste,
109
+ onScroll: this.onScroll,
110
+ };
111
+ /**
112
+ * @property
113
+ * @private
114
+ * @type {{ timer?: number; destinationTop?: number }}
115
+ */
116
+ this.animation = {};
117
+ this.selectAll = false;
118
+ const { codemirror, ...restOptions } = options;
119
+ if (codemirror) {
120
+ Object.assign(this.options.codemirror, codemirror);
121
+ }
122
+ Object.assign(this.options, restOptions);
123
+ this.options.codemirror.keyMap = this.options.keyMap;
124
+ this.$cherry = this.options.$cherry;
125
+ this.instanceId = this.$cherry.getInstanceId();
126
+ }
127
+
128
+ /**
129
+ * 禁用快捷键
130
+ * @param {boolean} disable 是否禁用快捷键
131
+ */
132
+ disableShortcut = (disable = true) => {
133
+ if (disable) {
134
+ this.editor.setOption('keyMap', 'default');
135
+ } else {
136
+ this.editor.setOption('keyMap', this.options.keyMap);
137
+ }
138
+ };
139
+
140
+ /**
141
+ * 在onChange后处理draw.io的xml数据和图片的base64数据,对这种超大的数据增加省略号,
142
+ * 以及对全角符号进行特殊染色。
143
+ */
144
+ dealSpecialWords = () => {
145
+ /**
146
+ * 如果编辑器隐藏了,则不再处理(否则有性能问题)
147
+ * - 性能问题出现的原因:
148
+ * 1. 纯预览模式下,cherry的高度可能会被设置成auto(也就是没有滚动条)
149
+ * 2. 这时候codemirror的高度也是auto,其“视窗懒加载”提升性能的手段就失效了
150
+ * 3. 这时再大量的调用markText等api就会非常耗时
151
+ * - 经过上述分析,最好的判断应该是判断**编辑器高度是否为auto**,但考虑到一般只有纯预览模式才大概率设置成auto,所以就只判断纯预览模式了
152
+ */
153
+ if (this.$cherry.status.editor === 'hide') {
154
+ return;
155
+ }
156
+ this.formatBigData2Mark(base64Reg, 'cm-url base64');
157
+ this.formatBigData2Mark(imgDrawioXmlReg, 'cm-url drawio');
158
+ this.formatBigData2Mark(longTextReg, 'cm-url long-text');
159
+ this.formatFullWidthMark();
160
+ };
161
+
162
+ /**
163
+ * 把大字符串变成省略号
164
+ * @param {*} reg 正则
165
+ * @param {*} className 利用codemirror的MarkText生成的新元素的class
166
+ */
167
+ formatBigData2Mark = (reg, className) => {
168
+ const codemirror = this.editor;
169
+ const searcher = codemirror.getSearchCursor(reg);
170
+
171
+ let oneSearch = searcher.findNext();
172
+ for (; oneSearch !== false; oneSearch = searcher.findNext()) {
173
+ const target = searcher.from();
174
+ if (!target) {
175
+ continue;
176
+ }
177
+ const bigString = oneSearch[2] ?? '';
178
+ const targetChFrom = target.ch + oneSearch[1]?.length;
179
+ const targetChTo = targetChFrom + bigString.length;
180
+ const targetLine = target.line;
181
+ const begin = { line: targetLine, ch: targetChFrom };
182
+ const end = { line: targetLine, ch: targetChTo };
183
+ // 如果所在区域已经有mark了,则不再增加mark
184
+ if (codemirror.findMarks(begin, end).length > 0) {
185
+ continue;
186
+ }
187
+ const newSpan = createElement('span', `cm-string ${className}`, { title: bigString });
188
+ newSpan.textContent = bigString;
189
+ codemirror.markText(begin, end, { replacedWith: newSpan, atomic: true });
190
+ }
191
+ };
192
+
193
+ /**
194
+ * 高亮全角符号 ·|¥|、|:|“|”|【|】|(|)|《|》
195
+ * full width翻译为全角
196
+ */
197
+ formatFullWidthMark() {
198
+ if (!this.options.showFullWidthMark) {
199
+ return;
200
+ }
201
+ const { editor } = this;
202
+ const regex = /[·¥、:“”【】()《》]/; // 此处以仅匹配单个全角符号
203
+ const searcher = editor.getSearchCursor(regex);
204
+ let oneSearch = searcher.findNext();
205
+ // 防止出现错误的mark
206
+ editor.getAllMarks().forEach(function (mark) {
207
+ if (mark.className === 'cm-fullWidth') {
208
+ const range = JSON.parse(JSON.stringify(mark.find()));
209
+ const markedText = editor.getRange(range.from, range.to);
210
+ if (!regex.test(markedText)) {
211
+ mark.clear();
212
+ }
213
+ }
214
+ });
215
+ for (; oneSearch !== false; oneSearch = searcher.findNext()) {
216
+ const target = searcher.from();
217
+ if (!target) {
218
+ continue;
219
+ }
220
+ const from = { line: target.line, ch: target.ch };
221
+ const to = { line: target.line, ch: target.ch + 1 };
222
+ // 当没有标记时再进行标记,判断textMaker的className必须为"cm-fullWidth",
223
+ // 因为cm的addon里会引入className: "CodeMirror-composing"的textMaker干扰判断
224
+ const existMarksLength = editor.findMarks(from, to).filter((item) => {
225
+ return item.className === 'cm-fullWidth';
226
+ });
227
+ if (existMarksLength.length === 0) {
228
+ editor.markText(from, to, {
229
+ className: 'cm-fullWidth',
230
+ title: '按住Ctrl/Cmd点击切换成半角(Hold down Ctrl/Cmd and click to switch to half-width)',
231
+ });
232
+ }
233
+ }
234
+ }
235
+
236
+ /**
237
+ *
238
+ * @param {CodeMirror.Editor} codemirror
239
+ * @param {MouseEvent} evt
240
+ */
241
+ toHalfWidth(codemirror, evt) {
242
+ const { target } = evt;
243
+ if (!(target instanceof HTMLElement)) {
244
+ return;
245
+ }
246
+ // 针对windows用户为Ctrl按键,Mac用户为Cmd按键
247
+ if (target.classList.contains('cm-fullWidth') && (evt.ctrlKey || evt.metaKey) && evt.buttons === 1) {
248
+ const rect = target.getBoundingClientRect();
249
+ // 由于是一个字符,所以肯定在一行
250
+ const from = codemirror.coordsChar({ left: rect.left, top: rect.top });
251
+ const to = { line: from.line, ch: from.ch + 1 };
252
+ codemirror.setSelection(from, to);
253
+ codemirror.replaceSelection(
254
+ target.innerText
255
+ .replace('·', '`')
256
+ .replace('¥', '$')
257
+ .replace('、', '/')
258
+ .replace(':', ':')
259
+ .replace('“', '"')
260
+ .replace('”', '"')
261
+ .replace('【', '[')
262
+ .replace('】', ']')
263
+ .replace('(', '(')
264
+ .replace(')', ')')
265
+ .replace('《', '<')
266
+ .replace('》', '>'),
267
+ );
268
+ }
269
+ }
270
+ /**
271
+ *
272
+ * @param {KeyboardEvent} e
273
+ * @param {CodeMirror.Editor} codemirror
274
+ */
275
+ onKeyup = (e, codemirror) => {
276
+ const { line: targetLine } = codemirror.getCursor();
277
+ this.previewer.highlightLine(targetLine + 1);
278
+ };
279
+
280
+ /**
281
+ *
282
+ * @param {ClipboardEvent} e
283
+ * @param {CodeMirror.Editor} codemirror
284
+ */
285
+ onPaste(e, codemirror) {
286
+ let { clipboardData } = e;
287
+ if (clipboardData) {
288
+ this.handlePaste(e, clipboardData, codemirror);
289
+ } else {
290
+ ({ clipboardData } = window);
291
+ this.handlePaste(e, clipboardData, codemirror);
292
+ }
293
+ }
294
+
295
+ /**
296
+ *
297
+ * @param {ClipboardEvent} event
298
+ * @param {ClipboardEvent['clipboardData']} clipboardData
299
+ * @param {CodeMirror.Editor} codemirror
300
+ * @returns {boolean | void}
301
+ */
302
+ handlePaste(event, clipboardData, codemirror) {
303
+ const onPasteRet = this.$cherry.options.callback.onPaste(clipboardData, this.$cherry);
304
+ if (onPasteRet !== false && typeof onPasteRet === 'string') {
305
+ event.preventDefault();
306
+ codemirror.replaceSelection(onPasteRet);
307
+ return;
308
+ }
309
+ let html = clipboardData.getData('Text/Html');
310
+ const { items } = clipboardData;
311
+ // 清空注释
312
+ html = html.replace(/<!--[^>]+>/g, '');
313
+ /**
314
+ * 处理“右键复制图片”场景
315
+ * 在这种场景下,我们希望粘贴进来的图片可以走文件上传逻辑,所以当检测到这种场景后,我们会清空html
316
+ */
317
+ if (
318
+ /<body>\s*<img [^>]+>\s*<\/body>/.test(html) &&
319
+ items[1]?.kind === 'file' &&
320
+ items[1]?.type.match(/^image\//i)
321
+ ) {
322
+ html = '';
323
+ }
324
+ const codemirrorDoc = codemirror.getDoc();
325
+ this.fileUploadCount = 0;
326
+ // 只要有html内容,就不处理剪切板里的其他内容,这么做的后果是粘贴excel内容时,只会粘贴html内容,不会把excel对应的截图粘进来
327
+ for (let i = 0; !html && i < items.length; i++) {
328
+ const item = items[i];
329
+ // 判断是否为图片数据
330
+ if (item && item.kind === 'file' && item.type.match(/^image\//i)) {
331
+ // 读取该图片
332
+ const file = item.getAsFile();
333
+ this.$cherry.options.callback.fileUpload(file, (url, params = {}) => {
334
+ this.fileUploadCount += 1;
335
+ if (typeof url !== 'string') {
336
+ return;
337
+ }
338
+ const mdStr = `${this.fileUploadCount > 1 ? '\n' : ''}${handleFileUploadCallback(url, params, file)}`;
339
+ codemirrorDoc.replaceSelection(mdStr);
340
+ // if (this.pasterHtml) {
341
+ // // 如果同时粘贴了html内容和文件内容,则在文件上传完成后强制让光标处于非选中状态,以防止自动选中的html内容被文件内容替换掉
342
+ // const { line, ch } = codemirror.getCursor();
343
+ // codemirror.setSelection({ line, ch }, { line, ch });
344
+ // codemirrorDoc.replaceSelection(mdStr, 'end');
345
+ // } else {
346
+ // codemirrorDoc.replaceSelection(mdStr);
347
+ // }
348
+ });
349
+ event.preventDefault();
350
+ }
351
+ }
352
+
353
+ // 复制html转换markdown
354
+ const htmlText = clipboardData.getData('text/plain');
355
+ if (!html || !this.options.convertWhenPaste) {
356
+ return true;
357
+ }
358
+
359
+ let divObj = document.createElement('DIV');
360
+ divObj.innerHTML = html;
361
+ html = divObj.innerHTML;
362
+ const mdText = htmlParser.run(html);
363
+ if (typeof mdText === 'string' && mdText.trim().length > 0) {
364
+ const range = codemirror.listSelections();
365
+ if (codemirror.getSelections().length <= 1 && range[0] && range[0].anchor) {
366
+ const currentCursor = {};
367
+ currentCursor.line = range[0].anchor.line;
368
+ currentCursor.ch = range[0].anchor.ch;
369
+ codemirrorDoc.replaceSelection(mdText);
370
+ pasteHelper.showSwitchBtnAfterPasteHtml(this.$cherry, currentCursor, codemirror, htmlText, mdText);
371
+ } else {
372
+ codemirrorDoc.replaceSelection(mdText);
373
+ }
374
+ event.preventDefault();
375
+ }
376
+ divObj = null;
377
+ }
378
+
379
+ /**
380
+ *
381
+ * @param {CodeMirror.Editor} codemirror
382
+ */
383
+ onScroll = (codemirror) => {
384
+ this.$cherry.$event.emit('cleanAllSubMenus'); // 滚动时清除所有子菜单,这不应该在Bubble中处理,我们关注的是编辑器的滚动 add by ufec
385
+ if (this.disableScrollListener) {
386
+ this.disableScrollListener = false;
387
+ return;
388
+ }
389
+ const scroller = codemirror.getScrollerElement();
390
+ if (scroller.scrollTop <= 0) {
391
+ this.previewer.scrollToLineNum(0);
392
+ return;
393
+ }
394
+ if (scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 20) {
395
+ this.previewer.scrollToLineNum(null); // 滚动到底
396
+ return;
397
+ }
398
+ const currentTop = codemirror.getScrollInfo().top;
399
+ const targetLine = codemirror.lineAtHeight(currentTop, 'local');
400
+ const lineRect = codemirror.charCoords({ line: targetLine, ch: 0 }, 'local');
401
+ const lineHeight = codemirror.getLineHandle(targetLine).height;
402
+ const lineTop = lineRect.bottom - lineHeight; // 直接用lineRect.top在自动折行时计算的是最后一行的top
403
+ const percent = (100 * (currentTop - lineTop)) / lineHeight / 100;
404
+ // console.log(percent);
405
+ // codemirror中行号以0开始,所以需要+1
406
+ this.previewer.scrollToLineNum(targetLine + 1, percent);
407
+ };
408
+
409
+ /**
410
+ *
411
+ * @param {CodeMirror.Editor} codemirror
412
+ * @param {MouseEvent} evt
413
+ */
414
+ onMouseDown = (codemirror, evt) => {
415
+ this.$cherry.$event.emit('cleanAllSubMenus'); // Bubble中处理需要考虑太多,直接在编辑器中处理可包括Bubble中所有情况,因为产生Bubble的前提是光标在编辑器中 add by ufec
416
+ const { line: targetLine } = codemirror.getCursor();
417
+ const top = Math.abs(evt.y - codemirror.getWrapperElement().getBoundingClientRect().y);
418
+ this.previewer.scrollToLineNumWithOffset(targetLine + 1, top);
419
+ this.toHalfWidth(codemirror, evt);
420
+ };
421
+
422
+ /**
423
+ * 光标变化事件
424
+ */
425
+ onCursorActivity = () => {
426
+ this.refreshWritingStatus();
427
+ };
428
+
429
+ /**
430
+ *
431
+ * @param {*} previewer
432
+ */
433
+ init(previewer) {
434
+ const textArea = this.options.editorDom.querySelector(`#${this.options.id}`);
435
+ if (!(textArea instanceof HTMLTextAreaElement)) {
436
+ throw new Error('The specific element is not a textarea.');
437
+ }
438
+ const editor = codemirror.fromTextArea(textArea, this.options.codemirror);
439
+ // 以下逻辑是针对\t等空白字符的处理,似乎已经不需要了,先注释掉,等有反馈了再考虑加回来
440
+ // editor.addOverlay({
441
+ // name: 'invisibles',
442
+ // token: function nextToken(stream) {
443
+ // let tokenClass;
444
+ // let spaces = 0;
445
+ // let peek = stream.peek() === ' ';
446
+ // if (peek) {
447
+ // while (peek && spaces < Number.MAX_VALUE) {
448
+ // spaces += 1;
449
+ // stream.next();
450
+ // peek = stream.peek() === ' ';
451
+ // }
452
+ // tokenClass = `whitespace whitespace-${spaces}`;
453
+ // } else {
454
+ // while (!stream.eol()) {
455
+ // stream.next();
456
+ // }
457
+ // tokenClass = '';
458
+ // }
459
+ // return tokenClass;
460
+ // },
461
+ // });
462
+ this.previewer = previewer;
463
+ this.disableScrollListener = false;
464
+
465
+ if (this.options.value) {
466
+ editor.setOption('value', this.options.value);
467
+ }
468
+
469
+ editor.on('blur', (codemirror, evt) => {
470
+ this.options.onBlur(evt, codemirror);
471
+ this.$cherry.$event.emit('blur', { evt, cherry: this.$cherry });
472
+ });
473
+
474
+ editor.on('focus', (codemirror, evt) => {
475
+ this.options.onFocus(evt, codemirror);
476
+ this.$cherry.$event.emit('focus', { evt, cherry: this.$cherry });
477
+ });
478
+
479
+ editor.on('change', (codemirror, evt) => {
480
+ this.options.onChange(evt, codemirror);
481
+ this.dealSpecialWords();
482
+ if (this.options.autoSave2Textarea) {
483
+ // @ts-ignore
484
+ // 将codemirror里的内容回写到textarea里
485
+ codemirror.save();
486
+ }
487
+ });
488
+
489
+ editor.on('keydown', (codemirror, evt) => {
490
+ this.options.onKeydown(evt, codemirror);
491
+ });
492
+
493
+ editor.on('keyup', (codemirror, evt) => {
494
+ this.onKeyup(evt, codemirror);
495
+ });
496
+
497
+ editor.on('paste', (codemirror, evt) => {
498
+ this.options.onPaste.call(this, evt, codemirror);
499
+ });
500
+
501
+ if (this.options.autoScrollByCursor) {
502
+ editor.on('mousedown', (codemirror, evt) => {
503
+ setTimeout(() => {
504
+ this.onMouseDown(codemirror, evt);
505
+ });
506
+ });
507
+ }
508
+
509
+ editor.on('drop', (codemirror, evt) => {
510
+ const files = evt.dataTransfer.files || [];
511
+ if (files && files.length > 0) {
512
+ // 增加延时,让drop的位置变成codemirror的光标位置
513
+ setTimeout(() => {
514
+ for (let i = 0; i < files.length; i++) {
515
+ const file = files[i];
516
+ const fileType = file.type || '';
517
+ // text格式或md格式文件,直接读取内容,不做上传文件的操作
518
+ if (/\.(text|md)/.test(file.name) || /^text/i.test(fileType)) {
519
+ continue;
520
+ }
521
+ this.$cherry.options.callback.fileUpload(file, (url, params = {}) => {
522
+ if (typeof url !== 'string') {
523
+ return;
524
+ }
525
+ // 拖拽上传文件时,强制改成没有文字选择区的状态
526
+ codemirror.setSelection(codemirror.getCursor());
527
+ const mdStr = handleFileUploadCallback(url, params, file);
528
+ // 当批量上传文件时,每个被插入的文件中间需要加个换行,但单个上传文件的时候不需要加换行
529
+ const insertValue = i > 0 ? `\n${mdStr} ` : `${mdStr} `;
530
+ codemirror.replaceSelection(insertValue);
531
+ this.dealSpecialWords();
532
+ });
533
+ }
534
+ }, 50);
535
+ }
536
+ });
537
+
538
+ editor.on('scroll', (codemirror) => {
539
+ this.options.onScroll(codemirror);
540
+ this.options.writingStyle === 'focus' && this.refreshWritingStatus();
541
+ });
542
+
543
+ editor.on('cursorActivity', () => {
544
+ this.onCursorActivity();
545
+ });
546
+ editor.on('beforeChange', (codemirror) => {
547
+ this.selectAll = this.editor.getValue() === codemirror.getSelection();
548
+ });
549
+
550
+ addEvent(
551
+ this.getEditorDom(),
552
+ 'wheel',
553
+ () => {
554
+ // 鼠标滚轮滚动时,强制监听滚动事件
555
+ this.disableScrollListener = false;
556
+ // 打断滚动动画
557
+ cancelAnimationFrame(this.animation.timer);
558
+ this.animation.timer = 0;
559
+ },
560
+ false,
561
+ );
562
+
563
+ /**
564
+ * @property
565
+ * @type {CodeMirror.Editor}
566
+ */
567
+ this.editor = editor;
568
+
569
+ if (this.options.writingStyle !== 'normal') {
570
+ this.initWritingStyle();
571
+ }
572
+ // 处理特殊字符,主要将base64等大文本替换成占位符,以提高可读性
573
+ this.dealSpecialWords();
574
+
575
+ this.domWidth = this.getEditorDom().offsetWidth;
576
+ // 监听编辑器宽度变化
577
+ const resizeObserver = new ResizeObserver((entries) => {
578
+ if (this.getEditorDom().offsetWidth !== this.domWidth && this.$cherry.status.editor === 'show') {
579
+ this.domWidth = this.getEditorDom().offsetWidth;
580
+ this.editor.refresh();
581
+ }
582
+ });
583
+ resizeObserver.observe(this.getEditorDom());
584
+ }
585
+
586
+ /**
587
+ *
588
+ * @param {number | null} beginLine 起始行,传入null时跳转到文档尾部
589
+ * @param {number} [endLine] 终止行
590
+ * @param {number} [percent] 百分比,取值0~1
591
+ */
592
+ jumpToLine(beginLine, endLine, percent) {
593
+ if (beginLine === null) {
594
+ cancelAnimationFrame(this.animation.timer);
595
+ this.disableScrollListener = true;
596
+ this.editor.scrollIntoView({
597
+ line: this.editor.lineCount() - 1,
598
+ ch: 1,
599
+ });
600
+ this.animation.timer = 0;
601
+ return;
602
+ }
603
+ const position = this.editor.charCoords({ line: beginLine, ch: 0 }, 'local');
604
+ let { top } = position;
605
+ const positionEnd = this.editor.charCoords({ line: beginLine + endLine, ch: 0 }, 'local');
606
+ const height = positionEnd.top - position.top;
607
+ top += height * percent;
608
+ this.animation.destinationTop = Math.ceil(top - 15);
609
+ if (this.animation.timer) {
610
+ return;
611
+ }
612
+ const animationHandler = () => {
613
+ const currentTop = this.editor.getScrollInfo().top;
614
+ const delta = this.animation.destinationTop - currentTop;
615
+ // 100毫秒内完成动画
616
+ const move = Math.ceil(Math.min(Math.abs(delta), Math.max(1, Math.abs(delta) / (100 / 16.7))));
617
+ // console.log('should scroll: ', move, delta, currentTop, this.animation.destinationTop);
618
+ if (delta > 0) {
619
+ if (currentTop >= this.animation.destinationTop) {
620
+ this.animation.timer = 0;
621
+ return;
622
+ }
623
+ this.disableScrollListener = true;
624
+ this.editor.scrollTo(null, currentTop + move);
625
+ } else if (delta < 0) {
626
+ if (currentTop <= this.animation.destinationTop || currentTop <= 0) {
627
+ this.animation.timer = 0;
628
+ return;
629
+ }
630
+ this.disableScrollListener = true;
631
+ this.editor.scrollTo(null, currentTop - move);
632
+ } else {
633
+ this.animation.timer = 0;
634
+ return;
635
+ }
636
+ // 无法再继续滚动
637
+ if (currentTop === this.editor.getScrollInfo().top || move >= Math.abs(delta)) {
638
+ this.animation.timer = 0;
639
+ return;
640
+ }
641
+ this.animation.timer = requestAnimationFrame(animationHandler);
642
+ };
643
+ this.animation.timer = requestAnimationFrame(animationHandler);
644
+ }
645
+
646
+ /**
647
+ *
648
+ * @param {number | null} lineNum
649
+ * @param {number} [endLine]
650
+ * @param {number} [percent]
651
+ */
652
+ scrollToLineNum(lineNum, endLine, percent) {
653
+ if (lineNum === null) {
654
+ this.jumpToLine(null);
655
+ return;
656
+ }
657
+ const $lineNum = Math.max(0, lineNum);
658
+ this.jumpToLine($lineNum, endLine, percent);
659
+ Logger.log('滚动预览区域,左侧应scroll to ', $lineNum);
660
+ }
661
+
662
+ /**
663
+ *
664
+ * @returns {HTMLElement}
665
+ */
666
+ getEditorDom() {
667
+ return this.options.editorDom;
668
+ }
669
+
670
+ /**
671
+ *
672
+ * @param {string} event 事件名
673
+ * @param {EditorEventCallback} callback 回调函数
674
+ */
675
+ addListener(event, callback) {
676
+ this.editor.on(event, callback);
677
+ }
678
+
679
+ /**
680
+ * 初始化书写风格
681
+ */
682
+ initWritingStyle() {
683
+ const { writingStyle } = this.options;
684
+ const className = `cherry-editor-writing-style--${writingStyle}`;
685
+ const editorDom = this.getEditorDom();
686
+ // 重置状态
687
+ Array.from(editorDom.classList)
688
+ .filter((className) => className.startsWith('cherry-editor-writing-style--'))
689
+ .forEach((className) => editorDom.classList.remove(className));
690
+ if (writingStyle === 'normal') {
691
+ return;
692
+ }
693
+ editorDom.classList.add(className);
694
+ this.refreshWritingStatus();
695
+ }
696
+
697
+ /**
698
+ * 刷新书写状态
699
+ */
700
+ refreshWritingStatus() {
701
+ const { writingStyle } = this.options;
702
+ if (writingStyle !== 'focus' && writingStyle !== 'typewriter') {
703
+ return;
704
+ }
705
+ const className = `cherry-editor-writing-style--${writingStyle}`;
706
+ /**
707
+ * @type {HTMLStyleElement}
708
+ */
709
+ const style = document.querySelector('#cherry-editor-writing-style') || document.createElement('style');
710
+ style.id = 'cherry-editor-writing-style';
711
+ Array.from(document.head.childNodes).find((node) => node === style) || document.head.appendChild(style);
712
+ const { sheet } = style;
713
+ Array.from(Array(sheet.cssRules.length)).forEach(() => sheet.deleteRule(0));
714
+ if (writingStyle === 'focus') {
715
+ const editorDomRect = this.getEditorDom().getBoundingClientRect();
716
+ // 获取光标所在位置
717
+ const { top, bottom } = this.editor.charCoords(this.editor.getCursor());
718
+ // 光标上部距离编辑器顶部距离(不包含菜单)
719
+ const topHeight = top - editorDomRect.top;
720
+ // 光标下部距离编辑器底部距离
721
+ const bottomHeight = editorDomRect.height - (bottom - editorDomRect.top);
722
+ sheet.insertRule(`.${className}::before { height: ${topHeight > 0 ? topHeight : 0}px; }`, 0);
723
+ sheet.insertRule(`.${className}::after { height: ${bottomHeight > 0 ? bottomHeight : 0}px; }`, 0);
724
+ }
725
+ if (writingStyle === 'typewriter') {
726
+ // 编辑器顶/底部填充的空白高度 (用于内容不足时使光标所在行滚动到编辑器中央)
727
+ const height = this.editor.getScrollInfo().clientHeight / 2;
728
+ sheet.insertRule(`.${className} .CodeMirror-lines::before { height: ${height}px; }`, 0);
729
+ sheet.insertRule(`.${className} .CodeMirror-lines::after { height: ${height}px; }`, 0);
730
+ this.editor.scrollTo(null, this.editor.cursorCoords(null, 'local').top - height);
731
+ }
732
+ }
733
+
734
+ /**
735
+ * 修改书写风格
736
+ */
737
+ setWritingStyle(writingStyle) {
738
+ this.options.writingStyle = writingStyle;
739
+ this.initWritingStyle();
740
+ }
741
+
742
+ /**
743
+ * 设置编辑器值
744
+ */
745
+ setValue(value = '') {
746
+ this.editor.setOption('value', value);
747
+ }
748
+ }