@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/Cherry.js ADDED
@@ -0,0 +1,1104 @@
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
+ import mergeWith from 'lodash/mergeWith';
17
+ import Editor from './Editor';
18
+ import Engine from './Engine';
19
+ import Previewer from './Previewer';
20
+ import Bubble from './toolbars/Bubble';
21
+ import FloatMenu from './toolbars/FloatMenu';
22
+ import Toolbar from './toolbars/Toolbar';
23
+ import ToolbarRight from './toolbars/ToolbarRight';
24
+ import Toc from './toolbars/Toc';
25
+ import { createElement } from './utils/dom';
26
+ import Sidebar from './toolbars/Sidebar';
27
+ import HiddenToolbar from './toolbars/HiddenToolbar';
28
+ import {
29
+ customizer,
30
+ getThemeFromLocal,
31
+ changeTheme,
32
+ getCodeThemeFromLocal,
33
+ testHasLocal,
34
+ changeCodeTheme,
35
+ getCodeWrapFromLocal,
36
+ saveCodeWrapToLocal,
37
+ } from './utils/config';
38
+ import NestedError, { $expectTarget } from './utils/error';
39
+ import getPosBydiffs from './utils/recount-pos';
40
+ import defaultConfig from './Cherry.config';
41
+ import cloneDeep from 'lodash/cloneDeep';
42
+ import Event from './Event';
43
+ import locales from '@/locales/index';
44
+
45
+ import { urlProcessorProxy } from './UrlCache';
46
+ import { CherryStatic } from './CherryStatic';
47
+ import { LIST_CONTENT } from '@/utils/regexp';
48
+
49
+ /** @typedef {import('~types/cherry').CherryOptions} CherryOptions */
50
+ export default class Cherry extends CherryStatic {
51
+ /**
52
+ * @protected
53
+ */
54
+ static initialized = false;
55
+ /**
56
+ * @readonly
57
+ */
58
+ static config = {
59
+ /** @type {CherryOptions} */
60
+ defaults: defaultConfig,
61
+ };
62
+
63
+ /**
64
+ * @param {CherryOptions} options
65
+ */
66
+ constructor(options) {
67
+ super();
68
+ Cherry.initialized = true;
69
+ const defaultConfigCopy = cloneDeep(Cherry.config.defaults);
70
+ this.defaultToolbar = defaultConfigCopy.toolbars.toolbar;
71
+ $expectTarget(options, Object);
72
+ /**
73
+ * @property
74
+ * @type {CherryOptions}
75
+ */
76
+ this.options = mergeWith({}, defaultConfigCopy, options, customizer);
77
+
78
+ this.storageFloatPreviewerWrapData = {
79
+ x: 50,
80
+ y: 58,
81
+ width: 800,
82
+ height: 500,
83
+ };
84
+
85
+ this.locales = locales;
86
+ if (this.options.locales) {
87
+ this.locales = {
88
+ ...this.options.locales,
89
+ ...this.locales,
90
+ };
91
+ }
92
+
93
+ // loading the locale
94
+ this.locale = this.locales[this.options.locale];
95
+
96
+ if (typeof this.options.engine.global.urlProcessor === 'function') {
97
+ this.options.engine.global.urlProcessor = urlProcessorProxy(this.options.engine.global.urlProcessor);
98
+ this.options.callback.urlProcessor = this.options.engine.global.urlProcessor;
99
+ } else {
100
+ this.options.callback.urlProcessor = urlProcessorProxy(this.options.callback.urlProcessor);
101
+ }
102
+
103
+ this.status = {
104
+ toolbar: 'show',
105
+ previewer: 'show',
106
+ editor: 'show',
107
+ };
108
+
109
+ if (this.options.isPreviewOnly || this.options.editor.defaultModel === 'previewOnly') {
110
+ this.options.toolbars.showToolbar = false;
111
+ this.options.editor.defaultModel = 'previewOnly';
112
+ this.status.editor = 'hide';
113
+ this.status.toolbar = 'hide';
114
+ }
115
+
116
+ /**
117
+ * @property
118
+ * @type {string} 实例ID
119
+ */
120
+ this.instanceId = `cherry-${new Date().getTime()}${Math.random()}`;
121
+ this.options.instanceId = this.instanceId;
122
+ this.lastMarkdownText = '';
123
+ this.$event = new Event(this.instanceId);
124
+
125
+ if (this.options.engine.global.flowSessionCursor === 'default') {
126
+ this.options.engine.global.flowSessionCursor = '<span class="cherry-flow-session-cursor"></span>';
127
+ }
128
+ /**
129
+ * @type {import('./Engine').default}
130
+ */
131
+ this.engine = new Engine(this.options, this);
132
+ this.init();
133
+ }
134
+
135
+ /**
136
+ * 初始化工具栏、编辑区、预览区等
137
+ * @private
138
+ */
139
+ init() {
140
+ this.storeDocumentScroll();
141
+ let mountEl = this.options.id ? document.getElementById(this.options.id) : this.options.el;
142
+
143
+ if (!mountEl) {
144
+ if (!this.options.forceAppend) {
145
+ return false;
146
+ }
147
+ this.noMountEl = true;
148
+ mountEl = document.createElement('div');
149
+ mountEl.id = this.options.id || 'cherry-markdown';
150
+ document.body.appendChild(mountEl);
151
+ }
152
+
153
+ if (!mountEl.style.height) {
154
+ mountEl.style.height = this.options.editor.height;
155
+ }
156
+ this.cherryDom = mountEl;
157
+
158
+ // 生成名称空间
159
+ if (typeof this.options.themeNameSpace === 'string') {
160
+ this.nameSpace = this.options.themeNameSpace;
161
+ } else {
162
+ this.nameSpace = this.options.nameSpace;
163
+ }
164
+
165
+ // 蒙层dom,用来拖拽编辑区&预览区宽度时展示蒙层
166
+ const wrapperDom = this.createWrapper();
167
+ // 创建编辑区
168
+ const editor = this.createEditor();
169
+ // 创建预览区
170
+ const previewer = this.createPreviewer();
171
+
172
+ if (this.options.toolbars.showToolbar === false || this.options.toolbars.toolbar === false) {
173
+ // 即便配置了不展示工具栏,也要让工具栏加载对应的语法hook
174
+ wrapperDom.classList.add('cherry--no-toolbar');
175
+ this.options.toolbars.toolbar = this.options.toolbars.toolbar
176
+ ? this.options.toolbars.toolbar
177
+ : this.defaultToolbar;
178
+ }
179
+ $expectTarget(this.options.toolbars.toolbar, Array);
180
+ // 创建顶部工具栏
181
+ this.createToolbar();
182
+ this.createToolbarRight();
183
+
184
+ const wrapperFragment = document.createDocumentFragment();
185
+ wrapperFragment.appendChild(this.toolbar.options.dom);
186
+ wrapperFragment.appendChild(editor.options.editorDom);
187
+ if (!this.options.previewer.dom) {
188
+ wrapperFragment.appendChild(previewer.options.previewerDom);
189
+ }
190
+ wrapperFragment.appendChild(previewer.options.virtualDragLineDom);
191
+ wrapperFragment.appendChild(previewer.options.editorMaskDom);
192
+ wrapperFragment.appendChild(previewer.options.previewerMaskDom);
193
+
194
+ wrapperDom.appendChild(wrapperFragment);
195
+ this.wrapperDom = wrapperDom;
196
+ // 创建预览区域的侧边工具栏
197
+ this.createSidebar();
198
+ this.createHiddenToolbar();
199
+ mountEl.appendChild(wrapperDom);
200
+
201
+ editor.init(previewer);
202
+ // 创建bubble工具栏,所谓bubble工具栏,是指在编辑区选中文本时悬浮出现的工具栏
203
+ this.createBubble();
204
+ // 创建float工具栏,所谓float工具栏,是指当编辑区光标处于新行时,在行内联想出的工具栏
205
+ this.createFloatMenu();
206
+ previewer.init(editor);
207
+
208
+ previewer.registerAfterUpdate(this.engine.mounted.bind(this.engine));
209
+
210
+ // default value init
211
+ this.initText(editor.editor);
212
+
213
+ this.$event.on('toolbarHide', () => {
214
+ this.status.toolbar = 'hide';
215
+ });
216
+ this.$event.on('toolbarShow', () => {
217
+ this.status.toolbar = 'show';
218
+ });
219
+ this.$event.on('previewerClose', () => {
220
+ this.status.previewer = 'hide';
221
+ });
222
+ this.$event.on('previewerOpen', () => {
223
+ this.status.previewer = 'show';
224
+ });
225
+ this.$event.on('editorClose', () => {
226
+ this.status.editor = 'hide';
227
+ // 关闭编辑区时,需要清除所有高亮
228
+ this.previewer.highlightLine(0);
229
+ });
230
+ this.$event.on('editorOpen', () => {
231
+ this.status.editor = 'show';
232
+ });
233
+
234
+ // 切换模式,有纯预览模式、纯编辑模式、双栏编辑模式
235
+ this.switchModel(this.options.editor.defaultModel, this.options.toolbars.showToolbar);
236
+
237
+ // 如果配置了初始化后根据hash自动滚动
238
+ if (this.options.autoScrollByHashAfterInit) {
239
+ setTimeout(this.scrollByHash.bind(this));
240
+ }
241
+ // 强制进行一次渲染 // 不记得为啥要强制渲染了,先屏蔽了
242
+ // this.editText(null, this.editor.editor);
243
+ this.createToc();
244
+ this.$event.bindCallbacksByOptions(this.options);
245
+ this.restoreDocumentScroll();
246
+ }
247
+
248
+ /**
249
+ * 记忆页面的滚动高度,在cherry初始化后恢复到这个高度
250
+ */
251
+ storeDocumentScroll() {
252
+ if (!this.options.editor.keepDocumentScrollAfterInit) {
253
+ return;
254
+ }
255
+ this.needRestoreDocumentScroll = true;
256
+ this.documentElementScrollTop = document.documentElement.scrollTop;
257
+ this.documentElementScrollLeft = document.documentElement.scrollLeft;
258
+ }
259
+
260
+ /**
261
+ * 在cherry初始化后恢复到这个高度
262
+ */
263
+ restoreDocumentScroll() {
264
+ if (!this.options.editor.keepDocumentScrollAfterInit || !this.needRestoreDocumentScroll) {
265
+ return;
266
+ }
267
+ this.needRestoreDocumentScroll = false;
268
+ window.scrollTo(this.documentElementScrollLeft, this.documentElementScrollTop);
269
+ }
270
+
271
+ destroy() {
272
+ if (this.noMountEl) {
273
+ this.cherryDom.remove();
274
+ } else {
275
+ this.wrapperDom.remove();
276
+ }
277
+ this.$event.clearAll();
278
+ }
279
+
280
+ on(eventName, callback) {
281
+ if (this.$event.Events[eventName]) {
282
+ if (/^(afterInit|afterChange)$/.test(eventName)) {
283
+ // 做特殊处理
284
+ return this.$event.on(eventName, (msg) => {
285
+ callback(msg.markdownText, msg.html);
286
+ });
287
+ }
288
+ return this.$event.on(eventName, callback);
289
+ }
290
+ switch (eventName) {
291
+ case 'urlProcessor':
292
+ this.options.callback.urlProcessor = urlProcessorProxy(callback);
293
+ break;
294
+ default:
295
+ this.options.callback[eventName] = callback;
296
+ }
297
+ }
298
+
299
+ off(eventName, callback) {
300
+ if (this.$event.Events[eventName]) {
301
+ return this.$event.off(eventName, callback);
302
+ }
303
+ this.options.callback[eventName] = () => {};
304
+ }
305
+
306
+ createToc() {
307
+ if (this.options.toolbars.toc === false) {
308
+ this.toc = false;
309
+ return;
310
+ }
311
+ this.toc = new Toc({
312
+ $cherry: this,
313
+ // @ts-ignore
314
+ updateLocationHash: this.options.toolbars.toc.updateLocationHash ?? true,
315
+ // @ts-ignore
316
+ position: this.options.toolbars.toc.position ?? 'absolute',
317
+ // @ts-ignore
318
+ cssText: this.options.toolbars.toc.cssText ?? '',
319
+ // @ts-ignore
320
+ defaultModel: this.options.toolbars.toc.defaultModel ?? 'pure',
321
+ // @ts-ignore
322
+ showAutoNumber: this.options.toolbars.toc.showAutoNumber ?? false,
323
+ });
324
+ }
325
+
326
+ /**
327
+ * 滚动到hash位置,实际上就是通过修改location.hash来触发hashChange事件,剩下的就交给浏览器了
328
+ */
329
+ scrollByHash() {
330
+ if (location.hash) {
331
+ try {
332
+ const { hash } = location;
333
+ // 检查是否有对应id的元素
334
+ const testDom = document.getElementById(hash.replace('#', ''));
335
+ if (testDom && this.previewer.getDomContainer().contains(testDom)) {
336
+ location.hash = '';
337
+ location.hash = hash;
338
+ }
339
+ } catch (error) {
340
+ // empty
341
+ }
342
+ }
343
+ }
344
+
345
+ $t(str) {
346
+ return this.locale[str] ? this.locale[str] : str;
347
+ }
348
+
349
+ addLocale(key, value) {
350
+ this.locale[key] = value;
351
+ }
352
+
353
+ addLocales(locales) {
354
+ this.locale = Object.assign(this.locale, locales);
355
+ }
356
+
357
+ getLocales() {
358
+ return this.locale;
359
+ }
360
+
361
+ /**
362
+ * 切换编辑模式
363
+ * @param {'edit&preview'|'editOnly'|'previewOnly'} [model=edit&preview] 模式类型
364
+ * 一般纯预览模式和纯编辑模式适合在屏幕较小的终端使用,比如手机移动端
365
+ */
366
+ switchModel(model = 'edit&preview', showToolbar = true) {
367
+ switch (model) {
368
+ case 'edit&preview':
369
+ if (this.previewer) {
370
+ this.previewer.editOnly(true);
371
+ this.previewer.recoverPreviewer();
372
+ }
373
+ if (this.toolbar && showToolbar) {
374
+ this.toolbar.showToolbar();
375
+ }
376
+ if (showToolbar) {
377
+ this.wrapperDom.classList.remove('cherry--no-toolbar');
378
+ } else {
379
+ this.wrapperDom.classList.add('cherry--no-toolbar');
380
+ }
381
+ break;
382
+ case 'editOnly':
383
+ if (!this.previewer.isPreviewerHidden()) {
384
+ this.previewer.editOnly(true);
385
+ }
386
+ if (this.toolbar && showToolbar) {
387
+ this.toolbar.showToolbar();
388
+ }
389
+ if (showToolbar) {
390
+ this.wrapperDom.classList.remove('cherry--no-toolbar');
391
+ } else {
392
+ this.wrapperDom.classList.add('cherry--no-toolbar');
393
+ }
394
+ break;
395
+ case 'previewOnly':
396
+ this.previewer.previewOnly();
397
+ this.toolbar && this.toolbar.previewOnly();
398
+ this.wrapperDom.classList.add('cherry--no-toolbar');
399
+ break;
400
+ }
401
+ }
402
+
403
+ /**
404
+ * 获取实例id
405
+ * @returns {string}
406
+ * @public
407
+ */
408
+ getInstanceId() {
409
+ return this.instanceId;
410
+ }
411
+
412
+ /**
413
+ * 获取编辑器状态
414
+ * @returns {Object}
415
+ */
416
+ getStatus() {
417
+ return this.status;
418
+ }
419
+
420
+ /**
421
+ * 获取编辑区内的markdown源码内容
422
+ * @returns markdown源码内容
423
+ */
424
+ getValue() {
425
+ return this.editor.editor.getValue();
426
+ }
427
+
428
+ /**
429
+ * 获取编辑区内的markdown源码内容
430
+ * @returns {string} markdown源码内容
431
+ */
432
+ getMarkdown() {
433
+ return this.getValue();
434
+ }
435
+
436
+ /**
437
+ * 获取CodeMirror 实例
438
+ * @returns { CodeMirror.Editor } CodeMirror实例
439
+ */
440
+ getCodeMirror() {
441
+ return this.editor.editor;
442
+ }
443
+
444
+ /**
445
+ * 获取预览区内的html内容
446
+ * @param {boolean} [wrapTheme=true] 是否在外层包裹主题class
447
+ * @returns {string} html内容
448
+ */
449
+ getHtml(wrapTheme = true) {
450
+ return this.previewer.getValue(wrapTheme);
451
+ }
452
+ /**
453
+ * 获取Previewer 预览实例
454
+ * @returns {Previewer} Previewer 预览实例
455
+ */
456
+ getPreviewer() {
457
+ return this.previewer;
458
+ }
459
+
460
+ /**
461
+ * @typedef {{
462
+ * level: number;
463
+ * id: string;
464
+ * text: string;
465
+ * }[]} HeaderList
466
+ * 获取目录,目录由head1~6组成
467
+ * @returns {HeaderList} 标题head数组
468
+ */
469
+ getToc() {
470
+ const str = this.getHtml();
471
+ /** @type {({level: number;id: string;text: string})[]} */
472
+ const headerList = [];
473
+ const headerRegex = /<h([1-6]).*?id="([^"]+?)".*?>(.+?)<\/h[0-6]>/g;
474
+ str.replace(headerRegex, (match, level, id, text) => {
475
+ headerList.push({ level: +level, id, text: text.replace(/<a .+?<\/a>/, '') });
476
+ return match;
477
+ });
478
+ return headerList;
479
+ }
480
+
481
+ /**
482
+ * 覆盖编辑区的内容
483
+ * @param {string} content markdown内容
484
+ * @param {boolean} keepCursor 是否保持光标位置
485
+ */
486
+ setValue(content, keepCursor = false) {
487
+ if (keepCursor === false) {
488
+ return this.editor.editor.setValue(content);
489
+ }
490
+ const codemirror = this.editor.editor;
491
+ const old = this.getValue();
492
+ const pos = codemirror.getDoc().indexFromPos(codemirror.getCursor());
493
+ const newPos = getPosBydiffs(pos, old, content);
494
+ codemirror.setValue(content);
495
+ const cursor = codemirror.getDoc().posFromIndex(newPos);
496
+ codemirror.setCursor(cursor);
497
+ this.editor.dealSpecialWords();
498
+ }
499
+
500
+ /**
501
+ * 在光标处或者指定行+偏移量插入内容
502
+ * @param {string} content 被插入的文本
503
+ * @param {boolean} [isSelect=false] 是否选中刚插入的内容
504
+ * @param {[number, number]|false} [anchor=false] [x,y] 代表x+1行,y+1字符偏移量,默认false 会从光标处插入
505
+ * @param {boolean} [focus=true] 保持编辑器处于focus状态
506
+ */
507
+ insert(content, isSelect = false, anchor = false, focus = true) {
508
+ if (anchor) {
509
+ this.editor.editor.setSelection({ line: anchor[0], ch: anchor[1] }, { line: anchor[0], ch: anchor[1] });
510
+ }
511
+ this.editor.editor.replaceSelection(content, isSelect ? 'around' : 'end');
512
+ focus && this.editor.editor.focus();
513
+ }
514
+
515
+ /**
516
+ * 在光标处或者指定行+偏移量插入内容
517
+ * @param {string} content 被插入的文本
518
+ * @param {boolean} [isSelect=false] 是否选中刚插入的内容
519
+ * @param {[number, number]|false} [anchor=false] [x,y] 代表x+1行,y+1字符偏移量,默认false 会从光标处插入
520
+ * @param {boolean} [focus=true] 保持编辑器处于focus状态
521
+ * @returns
522
+ */
523
+ insertValue(content, isSelect = false, anchor = false, focus = true) {
524
+ return this.insert(content, isSelect, anchor, focus);
525
+ }
526
+
527
+ /**
528
+ * 强制重新渲染预览区域
529
+ */
530
+ refreshPreviewer() {
531
+ try {
532
+ const markdownText = this.getValue();
533
+ const html = this.engine.makeHtml(markdownText);
534
+ this.previewer.refresh(html);
535
+ } catch (e) {
536
+ throw new NestedError(e);
537
+ }
538
+ }
539
+
540
+ /**
541
+ * 覆盖编辑区的内容
542
+ * @param {string} content markdown内容
543
+ * @param {boolean} [keepCursor=false] 是否保持光标位置
544
+ */
545
+ setMarkdown(content, keepCursor = false) {
546
+ return this.setValue(content, keepCursor);
547
+ }
548
+
549
+ /**
550
+ * @private
551
+ * @returns
552
+ */
553
+ createWrapper() {
554
+ let mainTheme = '';
555
+ let toolbarTheme = '';
556
+ let inlineCodeTheme = '';
557
+ let codeBlockTheme = '';
558
+ if (testHasLocal(this.nameSpace, 'theme')) {
559
+ mainTheme = getThemeFromLocal(true, this.nameSpace);
560
+ } else {
561
+ mainTheme = this.options.themeSettings.mainTheme;
562
+ mainTheme = mainTheme.replace(/theme__/g, '');
563
+ mainTheme = `theme__${mainTheme}`;
564
+ }
565
+ if (typeof this.options.toolbars.theme === 'string') {
566
+ toolbarTheme = this.options.toolbars.theme === 'dark' ? 'dark' : 'light';
567
+ } else {
568
+ toolbarTheme = this.options.themeSettings.toolbarTheme === 'dark' ? 'dark' : 'light';
569
+ }
570
+ // @ts-ignore
571
+ if (typeof this.options.engine.syntax.inlineCode.theme === 'string') {
572
+ inlineCodeTheme =
573
+ /** @type {{theme?: string;}} */ (this.options.engine.syntax.inlineCode).theme === 'black' ? 'black' : 'red';
574
+ } else {
575
+ inlineCodeTheme = this.options.themeSettings.inlineCodeTheme === 'black' ? 'black' : 'red';
576
+ }
577
+ // @ts-ignore
578
+ if (typeof this.options.engine.syntax.codeBlock.theme === 'string') {
579
+ codeBlockTheme = /** @type {{theme?: string;}} */ (this.options.engine.syntax.codeBlock).theme;
580
+ } else {
581
+ codeBlockTheme = this.options.themeSettings.codeBlockTheme;
582
+ }
583
+ if (testHasLocal(this.nameSpace, 'codeTheme')) {
584
+ codeBlockTheme = getCodeThemeFromLocal(this.nameSpace);
585
+ }
586
+ if (codeBlockTheme === 'dark') codeBlockTheme = 'tomorrow-night';
587
+ else if (codeBlockTheme === 'light') codeBlockTheme = 'solarized-light';
588
+ // @ts-ignore
589
+ const codeWrap = getCodeWrapFromLocal(this.nameSpace, this.options.engine.syntax.codeBlock.wrap);
590
+ const wrapperDom = createElement('div', ['cherry', 'clearfix', mainTheme].join(' '), {
591
+ 'data-toolbarTheme': toolbarTheme,
592
+ 'data-inlineCodeTheme': inlineCodeTheme,
593
+ 'data-codeBlockTheme': codeBlockTheme,
594
+ 'data-codeWrap': codeWrap === 'wrap' ? 'wrap' : 'nowrap',
595
+ });
596
+ this.wrapperDom = wrapperDom;
597
+ return wrapperDom;
598
+ }
599
+
600
+ getCodeWrap() {
601
+ return this.wrapperDom.dataset.codeWrap || 'wrap';
602
+ }
603
+
604
+ setCodeWrap(codeWrap) {
605
+ this.wrapperDom.dataset.codeWrap = codeWrap === 'wrap' ? 'wrap' : 'nowrap';
606
+ saveCodeWrapToLocal(this.nameSpace, codeWrap);
607
+ }
608
+
609
+ /**
610
+ * @private
611
+ * @returns {Toolbar}
612
+ */
613
+ createToolbar() {
614
+ if (!this.toolbarContainer) {
615
+ const dom = createElement('div', 'cherry-toolbar');
616
+ this.toolbarContainer = dom;
617
+ }
618
+ if (this.options.toolbars.shortcutKey && Object.keys(this.options.toolbars.shortcutKey).length > 0) {
619
+ console.warn(
620
+ 'options.shortcutKey is deprecated, please use shortcutKeySettings.shortcutKeyMap instead, get more info at https://github.com/Tencent/cherry-markdown/wiki',
621
+ );
622
+ }
623
+ this.toolbar = new Toolbar({
624
+ dom: this.toolbarContainer,
625
+ $cherry: this,
626
+ buttonConfig: this.options.toolbars.toolbar,
627
+ customMenu: this.options.toolbars.customMenu,
628
+ });
629
+ return this.toolbar;
630
+ }
631
+
632
+ /**
633
+ * 动态重置工具栏配置
634
+ * @public
635
+ * @param {'toolbar'|'toolbarRight'|'sidebar'|'bubble'|'float'} [type] 修改工具栏的类型
636
+ * @param {Array} [toolbar] 要重置的对应工具栏配置
637
+ * @returns {Boolean}
638
+ */
639
+ resetToolbar(type, toolbar) {
640
+ const $type = /(toolbar|toolbarRight|sidebar|bubble|float|toc)/.test(type) ? type : false;
641
+ if ($type === false) {
642
+ return false;
643
+ }
644
+ if (this.toolbarContainer) {
645
+ this.toolbarContainer.innerHTML = '';
646
+ }
647
+ if (this.toolbarFloatContainer) {
648
+ this.toolbarFloatContainer.innerHTML = '';
649
+ }
650
+ if (this.toolbarBubbleContainer) {
651
+ this.toolbarBubbleContainer.innerHTML = '';
652
+ }
653
+ if (this.sidebarDom) {
654
+ this.sidebarDom.innerHTML = '';
655
+ }
656
+ if (this.toc) {
657
+ // @ts-ignore
658
+ this.toc.tocDom.remove();
659
+ }
660
+ this.cherryDom.querySelectorAll('.cherry-dropdown').forEach((item) => {
661
+ item.remove();
662
+ });
663
+ this.options.toolbars[type] = toolbar;
664
+ this.createToolbar();
665
+ this.createToolbarRight();
666
+ this.createBubble();
667
+ this.createFloatMenu();
668
+ this.createSidebar();
669
+ this.createHiddenToolbar();
670
+ this.createToc();
671
+ return true;
672
+ }
673
+
674
+ /**
675
+ * @private
676
+ * @returns {Toolbar}
677
+ */
678
+ createToolbarRight() {
679
+ this.toolbarRight = new ToolbarRight({
680
+ dom: this.toolbarContainer,
681
+ $cherry: this,
682
+ buttonConfig: this.options.toolbars.toolbarRight,
683
+ customMenu: this.options.toolbars.customMenu,
684
+ });
685
+ this.toolbar.collectMenuInfo(this.toolbarRight);
686
+ return this.toolbarRight;
687
+ }
688
+
689
+ /**
690
+ * @private
691
+ * @returns
692
+ */
693
+ createSidebar() {
694
+ if (this.options.toolbars.sidebar) {
695
+ $expectTarget(this.options.toolbars.sidebar, Array);
696
+ let init = false;
697
+ if (!this.sidebarDom) {
698
+ init = true;
699
+ const externalClass = this.options.toolbars.theme === 'dark' ? 'dark' : '';
700
+ const dom = createElement('div', `cherry-sidebar ${externalClass}`);
701
+ this.sidebarDom = dom;
702
+ }
703
+ this.sidebar = new Sidebar({
704
+ dom: this.sidebarDom,
705
+ $cherry: this,
706
+ buttonConfig: this.options.toolbars.sidebar,
707
+ customMenu: this.options.toolbars.customMenu,
708
+ });
709
+ this.toolbar.collectMenuInfo(this.sidebar);
710
+ if (init === true) {
711
+ this.wrapperDom.appendChild(this.sidebarDom);
712
+ }
713
+ }
714
+ }
715
+
716
+ createHiddenToolbar() {
717
+ if (this.options.toolbars.hiddenToolbar) {
718
+ $expectTarget(this.options.toolbars.hiddenToolbar, Array);
719
+ this.hiddenToolbar = new HiddenToolbar({
720
+ $cherry: this,
721
+ buttonConfig: this.options.toolbars.hiddenToolbar,
722
+ customMenu: this.options.toolbars.customMenu,
723
+ });
724
+ this.toolbar.collectMenuInfo(this.hiddenToolbar);
725
+ }
726
+ }
727
+
728
+ /**
729
+ * @private
730
+ * @returns
731
+ */
732
+ createFloatMenu() {
733
+ if (this.options.toolbars.float) {
734
+ if (!this.toolbarFloatContainer) {
735
+ const dom = createElement('div', 'cherry-floatmenu');
736
+ this.toolbarFloatContainer = dom;
737
+ }
738
+ $expectTarget(this.options.toolbars.float, Array);
739
+ this.floatMenu = new FloatMenu({
740
+ dom: this.toolbarFloatContainer,
741
+ $cherry: this,
742
+ buttonConfig: this.options.toolbars.float,
743
+ customMenu: this.options.toolbars.customMenu,
744
+ });
745
+ this.toolbar.collectMenuInfo(this.floatMenu);
746
+ }
747
+ }
748
+
749
+ /**
750
+ * @private
751
+ * @returns
752
+ */
753
+ createBubble() {
754
+ if (this.options.toolbars.bubble) {
755
+ if (!this.toolbarBubbleContainer) {
756
+ const dom = createElement('div', 'cherry-bubble');
757
+ this.toolbarBubbleContainer = dom;
758
+ }
759
+ $expectTarget(this.options.toolbars.bubble, Array);
760
+ this.bubble = new Bubble({
761
+ dom: this.toolbarBubbleContainer,
762
+ $cherry: this,
763
+ buttonConfig: this.options.toolbars.bubble,
764
+ customMenu: this.options.toolbars.customMenu,
765
+ engine: this.engine,
766
+ });
767
+ this.toolbar.collectMenuInfo(this.bubble);
768
+ }
769
+ }
770
+
771
+ /**
772
+ * @private
773
+ * @returns {import('@/Editor').default}
774
+ */
775
+ createEditor() {
776
+ const textArea = createElement('textarea', '', {
777
+ id: this.options.editor.id ?? 'code',
778
+ name: this.options.editor.name ?? 'code',
779
+ });
780
+ textArea.textContent = this.options.value;
781
+ const editor = createElement('div', 'cherry-editor');
782
+ editor.appendChild(textArea);
783
+
784
+ if (typeof this.options.fileUpload === 'function') {
785
+ this.options.callback.fileUpload = this.options.fileUpload;
786
+ }
787
+
788
+ this.editor = new Editor({
789
+ $cherry: this,
790
+ editorDom: editor,
791
+ wrapperDom: this.wrapperDom,
792
+ value: this.options.value,
793
+ onKeydown: this.fireShortcutKey.bind(this),
794
+ onChange: this.editText.bind(this),
795
+ toolbars: this.options.toolbars,
796
+ autoScrollByCursor: this.options.autoScrollByCursor,
797
+ ...this.options.editor,
798
+ });
799
+ return this.editor;
800
+ }
801
+
802
+ /**
803
+ * @private
804
+ * @returns {import('@/Previewer').default}
805
+ */
806
+ createPreviewer() {
807
+ /** @type {HTMLDivElement} */
808
+ let previewer;
809
+ const anchorStyle =
810
+ (this.options.engine.syntax.header && this.options.engine.syntax.header.anchorStyle) || 'default';
811
+ const autonumberClass = anchorStyle === 'autonumber' ? ' head-num' : '';
812
+ const { className, dom, enablePreviewerBubble, floatWhenClosePreviewer } = this.options.previewer;
813
+ let mainTheme = '';
814
+ if (testHasLocal(this.nameSpace, 'theme')) {
815
+ mainTheme = getThemeFromLocal(true, this.nameSpace);
816
+ } else {
817
+ mainTheme = this.options.themeSettings.mainTheme;
818
+ }
819
+ const previewerClassName = ['cherry-previewer cherry-markdown', className || '', autonumberClass, mainTheme].join(
820
+ ' ',
821
+ );
822
+ if (dom) {
823
+ previewer = dom;
824
+ previewer.className += ` ${previewerClassName}`;
825
+ } else {
826
+ previewer = createElement('div', previewerClassName);
827
+ }
828
+ const virtualDragLine = createElement('div', 'cherry-drag');
829
+ const editorMask = createElement('div', 'cherry-editor-mask');
830
+ const previewerMask = createElement('div', 'cherry-previewer-mask');
831
+
832
+ this.previewer = new Previewer({
833
+ $cherry: this,
834
+ virtualDragLineDom: virtualDragLine,
835
+ editorMaskDom: editorMask,
836
+ previewerMaskDom: previewerMask,
837
+ previewerDom: previewer,
838
+ value: this.options.value,
839
+ isPreviewOnly: this.options.isPreviewOnly,
840
+ enablePreviewerBubble,
841
+ floatWhenClosePreviewer,
842
+ lazyLoadImg: this.options.previewer.lazyLoadImg,
843
+ });
844
+
845
+ return this.previewer;
846
+ }
847
+
848
+ clearFloatPreviewer() {
849
+ this.wrapperDom.appendChild(this.previewer.getDom());
850
+ this.storageFloatPreviewerWrapData = {
851
+ x: this.floatPreviewerWrapDom.offsetLeft,
852
+ y: this.floatPreviewerWrapDom.offsetTop,
853
+ height: this.floatPreviewerWrapDom.offsetHeight,
854
+ width: this.floatPreviewerWrapDom.offsetWidth,
855
+ };
856
+ this.floatPreviewerWrapDom.remove();
857
+ this.removeFloatPreviewerListener();
858
+ }
859
+
860
+ handleFloatPreviewerMouseDown = (evt) => {
861
+ if (evt.target !== this.floatPreviewerHeaderDom) return;
862
+ evt.preventDefault();
863
+ this.floatPreviewerInitOffsetX = evt.offsetX;
864
+ this.floatPreviewerInitOffsetY = evt.offsetY;
865
+ this.floatPreviewerWrapDom.classList.add('float-previewer-dragging');
866
+ };
867
+
868
+ handleFloatPreviewerMouseMove = (evt) => {
869
+ if (!this.floatPreviewerWrapDom.classList.contains('float-previewer-dragging')) return;
870
+ evt.preventDefault();
871
+ const { clientX, clientY } = evt;
872
+ let newRight = clientX - this.floatPreviewerInitOffsetX;
873
+ let newTop = clientY - this.floatPreviewerInitOffsetY;
874
+ if (newRight < 0) {
875
+ newRight = 0;
876
+ }
877
+ if (newTop < 0) {
878
+ newTop = 0;
879
+ }
880
+ if (newRight + this.floatPreviewerWrapDom.offsetWidth > this.pageWidth) {
881
+ newRight = this.pageWidth - this.floatPreviewerWrapDom.offsetWidth;
882
+ }
883
+ if (newTop + this.floatPreviewerWrapDom.offsetHeight > this.pageHeight) {
884
+ newTop = this.pageHeight - this.floatPreviewerWrapDom.offsetHeight;
885
+ }
886
+ requestAnimationFrame(() => {
887
+ this.floatPreviewerWrapDom.style.left = `${newRight}px`;
888
+ this.floatPreviewerWrapDom.style.top = `${newTop}px`;
889
+ });
890
+ };
891
+
892
+ handleFloatPreviewerMouseUp = (evt) => {
893
+ this.floatPreviewerWrapDom.classList.remove('float-previewer-dragging');
894
+ };
895
+
896
+ createFloatPreviewerListener() {
897
+ document.addEventListener('mousedown', this.handleFloatPreviewerMouseDown);
898
+ document.addEventListener('mousemove', this.handleFloatPreviewerMouseMove);
899
+ document.addEventListener('mouseup', this.handleFloatPreviewerMouseUp);
900
+ }
901
+
902
+ removeFloatPreviewerListener() {
903
+ document.removeEventListener('mousedown', this.handleFloatPreviewerMouseDown);
904
+ document.removeEventListener('mousemove', this.handleFloatPreviewerMouseMove);
905
+ document.removeEventListener('mouseup', this.handleFloatPreviewerMouseUp);
906
+ }
907
+
908
+ createFloatPreviewer() {
909
+ const floatPreviewerWrap = createElement('div', 'float-previewer-wrap');
910
+ const floatPreviewerHeader = createElement('div', 'float-previewer-header');
911
+ const floatPreviewerTitle = createElement('div', 'float-previewer-title');
912
+ floatPreviewerTitle.innerHTML = '预览';
913
+ floatPreviewerWrap.style.left = `${this.storageFloatPreviewerWrapData.x}px`;
914
+ floatPreviewerWrap.style.top = `${this.storageFloatPreviewerWrapData.y}px`;
915
+ floatPreviewerWrap.style.height = `${this.storageFloatPreviewerWrapData.height}px`;
916
+ floatPreviewerWrap.style.width = `${this.storageFloatPreviewerWrapData.width}px`;
917
+ floatPreviewerHeader.appendChild(floatPreviewerTitle);
918
+ floatPreviewerWrap.appendChild(floatPreviewerHeader);
919
+ floatPreviewerWrap.appendChild(this.previewer.getDom());
920
+ this.wrapperDom.appendChild(floatPreviewerWrap);
921
+
922
+ this.floatPreviewerHeaderDom = floatPreviewerHeader;
923
+ this.floatPreviewerWrapDom = floatPreviewerWrap;
924
+ this.pageWidth = document.body.clientWidth;
925
+ this.pageHeight = document.body.clientHeight;
926
+
927
+ this.createFloatPreviewerListener();
928
+ }
929
+
930
+ /**
931
+ * @private
932
+ * @param {import('codemirror').Editor} codemirror
933
+ */
934
+ initText(codemirror) {
935
+ try {
936
+ const markdownText = codemirror.getValue();
937
+ this.lastMarkdownText = markdownText;
938
+ const html = this.engine.makeHtml(markdownText);
939
+ this.previewer.update(html);
940
+ this.$event.emit('afterInit', { markdownText, html });
941
+ } catch (e) {
942
+ throw new NestedError(e);
943
+ }
944
+ }
945
+
946
+ /**
947
+ * @private
948
+ * @param {Event} _evt
949
+ * @param {import('codemirror').Editor} codemirror
950
+ */
951
+ editText(_evt, codemirror) {
952
+ try {
953
+ if (this.timer) {
954
+ clearTimeout(this.timer);
955
+ this.timer = null;
956
+ }
957
+ const interval = this.options.engine.global.flowSessionContext ? 10 : 50;
958
+ this.timer = setTimeout(() => {
959
+ const markdownText = codemirror.getValue();
960
+ if (markdownText !== this.lastMarkdownText) {
961
+ this.lastMarkdownText = markdownText;
962
+ const html = this.engine.makeHtml(markdownText);
963
+ this.previewer.update(html);
964
+ this.$event.emit('afterChange', {
965
+ markdownText,
966
+ html,
967
+ });
968
+ }
969
+ // 强制每次编辑(包括undo、redo)编辑器都会自动滚动到光标位置
970
+ if (!this.options.editor.keepDocumentScrollAfterInit) {
971
+ codemirror.scrollIntoView(null);
972
+ }
973
+ }, interval);
974
+ } catch (e) {
975
+ throw new NestedError(e);
976
+ }
977
+ }
978
+
979
+ /**
980
+ * @private
981
+ * @param {any} cb
982
+ */
983
+ onChange(cb) {
984
+ this.editor.editor.on('change', (codeMirror) => {
985
+ cb({
986
+ markdown: codeMirror.getValue(), // 后续可以按需增加html或其他状态
987
+ });
988
+ });
989
+ }
990
+
991
+ /**
992
+ * @private
993
+ * @param {KeyboardEvent} evt
994
+ */
995
+ fireShortcutKey(evt) {
996
+ const cursor = this.editor.editor.getCursor();
997
+ const lineContent = this.editor.editor.getLine(cursor.line);
998
+ // shift + tab 已经被绑定为缩进,所以这里不做处理
999
+ if (!evt.shiftKey && evt.key === 'Tab' && LIST_CONTENT.test(lineContent)) {
1000
+ // 每按一次Tab,如果当前光标在行首或者行尾,就在行首加一个\t
1001
+ if (cursor.ch === 0 || cursor.ch === lineContent.length || cursor.ch === lineContent.length + 1) {
1002
+ evt.preventDefault();
1003
+ this.editor.editor.setSelection({ line: cursor.line, ch: 0 }, { line: cursor.line, ch: lineContent.length });
1004
+ this.editor.editor.replaceSelection(`\t${lineContent}`, 'around');
1005
+ const newCursor = this.editor.editor.getCursor();
1006
+ this.editor.editor.setSelection(newCursor, newCursor);
1007
+ }
1008
+ }
1009
+ if (this.toolbar.matchShortcutKey(evt)) {
1010
+ // 快捷键
1011
+ const needPreventDefault = this.toolbar.fireShortcutKey(evt);
1012
+ if (needPreventDefault) {
1013
+ evt.preventDefault();
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+ /**
1019
+ * 导出预览区域内容
1020
+ * @public
1021
+ * @param {'pdf' | 'img' | 'markdown' | 'html'} [type='pdf']
1022
+ * 'pdf':导出成pdf文件; 'img':导出成png图片; 'markdown':导出成markdown文件; 'html':导出成html文件;
1023
+ * @param {string} [fileName] 导出文件名(默认为当前第一行内容|'cherry-export')
1024
+ */
1025
+ export(type = 'pdf', fileName = '') {
1026
+ this.previewer.export(type, fileName);
1027
+ }
1028
+
1029
+ /**
1030
+ * 修改主题
1031
+ * @param {string} theme option.themeSettings.themeList 里的className
1032
+ */
1033
+ setTheme(theme = 'default') {
1034
+ this.$event.emit('changeMainTheme', theme);
1035
+ changeTheme(this, theme);
1036
+ }
1037
+
1038
+ /**
1039
+ * 修改代码块主题
1040
+ * @param {string} theme option.themeSettings.codeBlockTheme
1041
+ */
1042
+ setCodeBlockTheme(theme = 'default') {
1043
+ this.$event.emit('changeCodeBlockTheme', theme);
1044
+ changeCodeTheme(this, theme);
1045
+ }
1046
+
1047
+ /**
1048
+ * 修改书写风格
1049
+ * @param {string} writingStyle normal 普通 | typewriter 打字机 | focus 专注
1050
+ */
1051
+ setWritingStyle(writingStyle) {
1052
+ this.editor.setWritingStyle(writingStyle);
1053
+ }
1054
+
1055
+ /**
1056
+ * 修改语言
1057
+ * @param {string} locale
1058
+ * @returns {boolean} false: 修改失败,因为没有对应的语言;true: 修改成功
1059
+ */
1060
+ setLocale(locale) {
1061
+ if (!this.locales[locale]) {
1062
+ return false;
1063
+ }
1064
+ this.options.locale = locale;
1065
+ this.locale = this.locales[locale];
1066
+ this.$event.emit('afterChangeLocale', locale);
1067
+ this.resetToolbar('toolbar', this.options.toolbars.toolbar || []);
1068
+ return true;
1069
+ }
1070
+
1071
+ /**
1072
+ * 切换TOC的模式(极简 or 展开)
1073
+ * @param {'full'|'pure'|''} focusModel 是否强制切换模式,如果为空,则根据当前模式切换
1074
+ */
1075
+ toggleToc(focusModel = '') {
1076
+ if (!this.toc) {
1077
+ return;
1078
+ }
1079
+ let targetModel = 'full';
1080
+ if (focusModel === '') {
1081
+ // @ts-ignore
1082
+ const { model } = this.toc;
1083
+ targetModel = model === 'full' ? 'pure' : 'full';
1084
+ } else {
1085
+ targetModel = focusModel;
1086
+ }
1087
+ // @ts-ignore
1088
+ this.toc.$switchModel(targetModel);
1089
+ // @ts-ignore
1090
+ this.toc.setModelToLocalStorage(targetModel);
1091
+ }
1092
+
1093
+ /**
1094
+ * 清空流程会话中添加的虚拟光标
1095
+ */
1096
+ clearFlowSessionCursor() {
1097
+ if (this.options.engine.global.flowSessionCursor) {
1098
+ this.previewer.getDom().innerHTML = this.previewer
1099
+ .getDom()
1100
+ // @ts-ignore
1101
+ .innerHTML.replaceAll(this.options.engine.global.flowSessionCursor, '');
1102
+ }
1103
+ }
1104
+ }