@cherry-markdown/cherry-markdown-dev 0.9.0-dev.202504110650.834443a → 0.9.0-dev.202504160300.d23d141

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 (327) hide show
  1. package/README.md +2 -2
  2. package/dist/addons/advance/cherry-table-echarts-plugin.js +1 -1
  3. package/dist/addons/cherry-code-block-mermaid-plugin.js +1 -1
  4. package/dist/cherry-markdown.core.common.js +1 -1
  5. package/dist/cherry-markdown.core.js +1 -1
  6. package/dist/cherry-markdown.engine.core.common.js +1 -1
  7. package/dist/cherry-markdown.engine.core.esm.js +1 -1
  8. package/dist/cherry-markdown.engine.core.js +1 -1
  9. package/dist/cherry-markdown.esm.js +1 -1
  10. package/dist/cherry-markdown.js +1 -1
  11. package/dist/cherry-markdown.min.js +1 -1
  12. package/dist/fonts/ch-icon.eot +0 -0
  13. package/dist/fonts/ch-icon.ttf +0 -0
  14. package/dist/fonts/ch-icon.woff +0 -0
  15. package/dist/fonts/ch-icon.woff2 +0 -0
  16. package/package.json +1 -3
  17. package/src/Cherry.config.js +0 -634
  18. package/src/Cherry.js +0 -1104
  19. package/src/CherryStatic.js +0 -70
  20. package/src/Editor.js +0 -748
  21. package/src/Engine.js +0 -402
  22. package/src/Event.js +0 -140
  23. package/src/Factory.js +0 -180
  24. package/src/Logger.js +0 -31
  25. package/src/Previewer.js +0 -1192
  26. package/src/Sanitizer.js +0 -4
  27. package/src/Sanitizer.node.js +0 -7
  28. package/src/UrlCache.js +0 -98
  29. package/src/addons/advance/cherry-table-echarts-plugin.js +0 -170
  30. package/src/addons/cherry-code-block-mermaid-plugin.js +0 -158
  31. package/src/addons/cherry-code-block-plantuml-plugin.js +0 -106
  32. package/src/core/HookCenter.js +0 -297
  33. package/src/core/HooksConfig.js +0 -105
  34. package/src/core/ParagraphBase.js +0 -332
  35. package/src/core/SentenceBase.js +0 -65
  36. package/src/core/SyntaxBase.js +0 -194
  37. package/src/core/hooks/AutoLink.js +0 -232
  38. package/src/core/hooks/BackgroundColor.js +0 -46
  39. package/src/core/hooks/Blockquote.js +0 -70
  40. package/src/core/hooks/Br.js +0 -85
  41. package/src/core/hooks/CodeBlock.js +0 -456
  42. package/src/core/hooks/Color.js +0 -46
  43. package/src/core/hooks/CommentReference.js +0 -96
  44. package/src/core/hooks/Detail.js +0 -108
  45. package/src/core/hooks/Emoji.config.js +0 -1825
  46. package/src/core/hooks/Emoji.js +0 -119
  47. package/src/core/hooks/Emphasis.js +0 -113
  48. package/src/core/hooks/Footnote.js +0 -125
  49. package/src/core/hooks/FrontMatter.js +0 -51
  50. package/src/core/hooks/Header.js +0 -234
  51. package/src/core/hooks/HighLight.js +0 -37
  52. package/src/core/hooks/Hr.js +0 -52
  53. package/src/core/hooks/HtmlBlock.js +0 -199
  54. package/src/core/hooks/Image.js +0 -174
  55. package/src/core/hooks/InlineCode.js +0 -48
  56. package/src/core/hooks/InlineMath.js +0 -108
  57. package/src/core/hooks/Link.js +0 -160
  58. package/src/core/hooks/List.js +0 -264
  59. package/src/core/hooks/MathBlock.js +0 -104
  60. package/src/core/hooks/Panel.js +0 -145
  61. package/src/core/hooks/Paragraph.js +0 -84
  62. package/src/core/hooks/Ruby.js +0 -34
  63. package/src/core/hooks/Size.js +0 -51
  64. package/src/core/hooks/Strikethrough.js +0 -54
  65. package/src/core/hooks/Sub.js +0 -47
  66. package/src/core/hooks/SuggestList.js +0 -333
  67. package/src/core/hooks/Suggester.js +0 -707
  68. package/src/core/hooks/Sup.js +0 -47
  69. package/src/core/hooks/Table.js +0 -275
  70. package/src/core/hooks/Toc.js +0 -292
  71. package/src/core/hooks/Transfer.js +0 -47
  72. package/src/core/hooks/Underline.js +0 -37
  73. package/src/index.core.js +0 -29
  74. package/src/index.engine.core.js +0 -68
  75. package/src/index.engine.js +0 -28
  76. package/src/index.js +0 -32
  77. package/src/libs/mermaidAPI.8.4.8.js +0 -1
  78. package/src/libs/mermaidAPI.8.5.2.js +0 -42
  79. package/src/libs/rawdeflate.js +0 -1663
  80. package/src/locales/en_US.js +0 -139
  81. package/src/locales/index.js +0 -25
  82. package/src/locales/ru_RU.js +0 -139
  83. package/src/locales/zh_CN.js +0 -142
  84. package/src/sass/base.scss +0 -26
  85. package/src/sass/bubble_formula.scss +0 -166
  86. package/src/sass/ch-icon.scss +0 -118
  87. package/src/sass/cherry.scss +0 -1116
  88. package/src/sass/components/bubble.scss +0 -173
  89. package/src/sass/components/shortcut_key_config.scss +0 -108
  90. package/src/sass/formula_utils_bubble.scss +0 -82
  91. package/src/sass/icon_template.scss +0 -24
  92. package/src/sass/icons/uEA03-list.svg +0 -19
  93. package/src/sass/icons/uEA04-check.svg +0 -14
  94. package/src/sass/icons/uEA09-square.svg +0 -10
  95. package/src/sass/icons/uEA0A-bold.svg +0 -20
  96. package/src/sass/icons/uEA0B-code.svg +0 -18
  97. package/src/sass/icons/uEA0C-color.svg +0 -13
  98. package/src/sass/icons/uEA0D-header.svg +0 -8
  99. package/src/sass/icons/uEA0E-image.svg +0 -15
  100. package/src/sass/icons/uEA0F-italic.svg +0 -8
  101. package/src/sass/icons/uEA10-link.svg +0 -16
  102. package/src/sass/icons/uEA11-ol.svg +0 -21
  103. package/src/sass/icons/uEA12-size.svg +0 -11
  104. package/src/sass/icons/uEA13-strike.svg +0 -16
  105. package/src/sass/icons/uEA14-table.svg +0 -12
  106. package/src/sass/icons/uEA15-ul.svg +0 -17
  107. package/src/sass/icons/uEA16-underline.svg +0 -13
  108. package/src/sass/icons/uEA17-word.svg +0 -16
  109. package/src/sass/icons/uEA18-blockquote.svg +0 -11
  110. package/src/sass/icons/uEA19-font.svg +0 -10
  111. package/src/sass/icons/uEA1F-insertClass.svg +0 -39
  112. package/src/sass/icons/uEA20-insertFlow.svg +0 -8
  113. package/src/sass/icons/uEA21-insertFormula.svg +0 -23
  114. package/src/sass/icons/uEA22-insertGantt.svg +0 -13
  115. package/src/sass/icons/uEA23-insertGraph.svg +0 -13
  116. package/src/sass/icons/uEA24-insertPie.svg +0 -19
  117. package/src/sass/icons/uEA25-insertSeq.svg +0 -20
  118. package/src/sass/icons/uEA26-insertState.svg +0 -35
  119. package/src/sass/icons/uEA27-line.svg +0 -11
  120. package/src/sass/icons/uEA28-preview.svg +0 -18
  121. package/src/sass/icons/uEA29-previewClose.svg +0 -24
  122. package/src/sass/icons/uEA2A-toc.svg +0 -24
  123. package/src/sass/icons/uEA2D-sub.svg +0 -15
  124. package/src/sass/icons/uEA2E-sup.svg +0 -15
  125. package/src/sass/icons/uEA2F-h1.svg +0 -16
  126. package/src/sass/icons/uEA30-h2.svg +0 -20
  127. package/src/sass/icons/uEA31-h3.svg +0 -23
  128. package/src/sass/icons/uEA32-h4.svg +0 -16
  129. package/src/sass/icons/uEA33-h5.svg +0 -20
  130. package/src/sass/icons/uEA34-h6.svg +0 -17
  131. package/src/sass/icons/uEA35-video.svg +0 -20
  132. package/src/sass/icons/uEA36-insert.svg +0 -25
  133. package/src/sass/icons/uEA37-little_table.svg +0 -30
  134. package/src/sass/icons/uEA38-pdf.svg +0 -27
  135. package/src/sass/icons/uEA39-checklist.svg +0 -22
  136. package/src/sass/icons/uEA40-close.svg +0 -12
  137. package/src/sass/icons/uEA41-fullscreen.svg +0 -81
  138. package/src/sass/icons/uEA42-minscreen.svg +0 -77
  139. package/src/sass/icons/uEA43-insertChart.svg +0 -23
  140. package/src/sass/icons/uEA44-question.svg +0 -25
  141. package/src/sass/icons/uEA45-settings.svg +0 -32
  142. package/src/sass/icons/uEA46-ok.svg +0 -7
  143. package/src/sass/icons/uEA47-br.svg +0 -22
  144. package/src/sass/icons/uEA48-normal.svg +0 -15
  145. package/src/sass/icons/uEA49-undo.svg +0 -19
  146. package/src/sass/icons/uEA50-redo.svg +0 -21
  147. package/src/sass/icons/uEA51-copy.svg +0 -6
  148. package/src/sass/icons/uEA52-phone.svg +0 -5
  149. package/src/sass/icons/uEA53-cherry-table-delete.svg +0 -17
  150. package/src/sass/icons/uEA54-cherry-table-insert-bottom.svg +0 -16
  151. package/src/sass/icons/uEA55-cherry-table-insert-left.svg +0 -15
  152. package/src/sass/icons/uEA56-cherry-table-insert-right.svg +0 -16
  153. package/src/sass/icons/uEA57-cherry-table-insert-top.svg +0 -16
  154. package/src/sass/icons/uEA58-sort-s.svg +0 -13
  155. package/src/sass/icons/uEA59-pinyin.svg +0 -1
  156. package/src/sass/icons/uEA5A-create.svg +0 -24
  157. package/src/sass/icons/uEA5B-download.svg +0 -34
  158. package/src/sass/icons/uEA5C-edit.svg +0 -3
  159. package/src/sass/icons/uEA5D-export.svg +0 -53
  160. package/src/sass/icons/uEA5E-folder-open.svg +0 -3
  161. package/src/sass/icons/uEA5F-folder.svg +0 -3
  162. package/src/sass/icons/uEA60-help.svg +0 -5
  163. package/src/sass/icons/uEA61-pen-fill.svg +0 -13
  164. package/src/sass/icons/uEA62-pen.svg +0 -3
  165. package/src/sass/icons/uEA64-tips.svg +0 -5
  166. package/src/sass/icons/uEA65-warn.svg +0 -5
  167. package/src/sass/icons/uEA66-mistake.svg +0 -4
  168. package/src/sass/icons/uEA67-success.svg +0 -4
  169. package/src/sass/icons/uEA68-danger.svg +0 -4
  170. package/src/sass/icons/uEA69-info.svg +0 -5
  171. package/src/sass/icons/uEA6A-primary.svg +0 -5
  172. package/src/sass/icons/uEA6B-warning.svg +0 -5
  173. package/src/sass/icons/uEA6C-justify.svg +0 -19
  174. package/src/sass/icons/uEA6D-justifyCenter.svg +0 -19
  175. package/src/sass/icons/uEA6E-justifyLeft.svg +0 -19
  176. package/src/sass/icons/uEA6F-justifyRight.svg +0 -19
  177. package/src/sass/icons/uEA70-chevronsLeft.svg +0 -1
  178. package/src/sass/icons/uEA71-chevronsRight.svg +0 -1
  179. package/src/sass/icons/uEA72-trendingUp.svg +0 -1
  180. package/src/sass/icons/uEA74-codeBlock.svg +0 -1
  181. package/src/sass/icons/uEA75-expand.svg +0 -3
  182. package/src/sass/icons/uEA76-unExpand.svg +0 -3
  183. package/src/sass/icons/uEA77-swap-vert.svg +0 -1
  184. package/src/sass/icons/uEA78-swap.svg +0 -1
  185. package/src/sass/icons/uEA79-keyboard.svg +0 -1
  186. package/src/sass/icons/uEA7A-command.svg +0 -1
  187. package/src/sass/icons/uEA7B-search.svg +0 -1
  188. package/src/sass/index.scss +0 -3
  189. package/src/sass/markdown.scss +0 -668
  190. package/src/sass/markdown_pure.scss +0 -9
  191. package/src/sass/prettyprint/prettyprint.scss +0 -118
  192. package/src/sass/previewer.scss +0 -179
  193. package/src/sass/print.scss +0 -13
  194. package/src/sass/prism/coy.scss +0 -220
  195. package/src/sass/prism/dark.scss +0 -132
  196. package/src/sass/prism/default.scss +0 -143
  197. package/src/sass/prism/funky.scss +0 -133
  198. package/src/sass/prism/okaidia.scss +0 -126
  199. package/src/sass/prism/one-dark.scss +0 -440
  200. package/src/sass/prism/one-light.scss +0 -428
  201. package/src/sass/prism/solarized-light.scss +0 -153
  202. package/src/sass/prism/tomorrow-night.scss +0 -125
  203. package/src/sass/prism/twilight.scss +0 -202
  204. package/src/sass/prism/vs-dark.scss +0 -275
  205. package/src/sass/prism/vs-light.scss +0 -168
  206. package/src/sass/themes/blue.scss +0 -411
  207. package/src/sass/themes/dark.scss +0 -517
  208. package/src/sass/themes/default.scss +0 -255
  209. package/src/sass/themes/green.scss +0 -395
  210. package/src/sass/themes/light.scss +0 -368
  211. package/src/sass/themes/red.scss +0 -397
  212. package/src/sass/themes/violet.scss +0 -410
  213. package/src/sass/variable.scss +0 -84
  214. package/src/toolbars/Bubble.js +0 -234
  215. package/src/toolbars/BubbleFormula.js +0 -298
  216. package/src/toolbars/BubbleTable.js +0 -147
  217. package/src/toolbars/FloatMenu.js +0 -131
  218. package/src/toolbars/HiddenToolbar.js +0 -36
  219. package/src/toolbars/HookCenter.js +0 -231
  220. package/src/toolbars/MenuBase.js +0 -569
  221. package/src/toolbars/PreviewerBubble.js +0 -608
  222. package/src/toolbars/ShortcutKeyConfigPanel.js +0 -345
  223. package/src/toolbars/Sidebar.js +0 -36
  224. package/src/toolbars/Toc.js +0 -242
  225. package/src/toolbars/Toolbar.js +0 -449
  226. package/src/toolbars/ToolbarRight.js +0 -37
  227. package/src/toolbars/hooks/Audio.js +0 -79
  228. package/src/toolbars/hooks/BarTable.js +0 -41
  229. package/src/toolbars/hooks/Bold.js +0 -73
  230. package/src/toolbars/hooks/Br.js +0 -34
  231. package/src/toolbars/hooks/ChangeLocale.js +0 -62
  232. package/src/toolbars/hooks/ChatGpt.js +0 -182
  233. package/src/toolbars/hooks/CheckList.js +0 -41
  234. package/src/toolbars/hooks/Code.js +0 -49
  235. package/src/toolbars/hooks/CodeTheme.js +0 -66
  236. package/src/toolbars/hooks/Color.js +0 -298
  237. package/src/toolbars/hooks/Copy.js +0 -141
  238. package/src/toolbars/hooks/Detail.js +0 -69
  239. package/src/toolbars/hooks/DrawIo.js +0 -57
  240. package/src/toolbars/hooks/Export.js +0 -49
  241. package/src/toolbars/hooks/File.js +0 -79
  242. package/src/toolbars/hooks/Formula.js +0 -69
  243. package/src/toolbars/hooks/FullScreen.js +0 -50
  244. package/src/toolbars/hooks/Graph.js +0 -263
  245. package/src/toolbars/hooks/H1.js +0 -71
  246. package/src/toolbars/hooks/H2.js +0 -71
  247. package/src/toolbars/hooks/H3.js +0 -71
  248. package/src/toolbars/hooks/Header.js +0 -118
  249. package/src/toolbars/hooks/Hr.js +0 -35
  250. package/src/toolbars/hooks/Image.js +0 -91
  251. package/src/toolbars/hooks/InlineCode.js +0 -53
  252. package/src/toolbars/hooks/Insert.js +0 -193
  253. package/src/toolbars/hooks/Italic.js +0 -72
  254. package/src/toolbars/hooks/Justify.js +0 -49
  255. package/src/toolbars/hooks/LineTable.js +0 -41
  256. package/src/toolbars/hooks/Link.js +0 -49
  257. package/src/toolbars/hooks/List.js +0 -55
  258. package/src/toolbars/hooks/MobilePreview.js +0 -44
  259. package/src/toolbars/hooks/Ol.js +0 -41
  260. package/src/toolbars/hooks/Panel.js +0 -140
  261. package/src/toolbars/hooks/Pdf.js +0 -78
  262. package/src/toolbars/hooks/Publish.js +0 -123
  263. package/src/toolbars/hooks/QuickTable.js +0 -43
  264. package/src/toolbars/hooks/Quote.js +0 -45
  265. package/src/toolbars/hooks/Redo.js +0 -33
  266. package/src/toolbars/hooks/Ruby.js +0 -59
  267. package/src/toolbars/hooks/Search.js +0 -53
  268. package/src/toolbars/hooks/Settings.js +0 -220
  269. package/src/toolbars/hooks/ShortcutKey.js +0 -62
  270. package/src/toolbars/hooks/Size.js +0 -118
  271. package/src/toolbars/hooks/Split.js +0 -37
  272. package/src/toolbars/hooks/Strikethrough.js +0 -71
  273. package/src/toolbars/hooks/Sub.js +0 -58
  274. package/src/toolbars/hooks/Sup.js +0 -58
  275. package/src/toolbars/hooks/SwitchModel.js +0 -56
  276. package/src/toolbars/hooks/Table.js +0 -56
  277. package/src/toolbars/hooks/Theme.js +0 -62
  278. package/src/toolbars/hooks/Toc.js +0 -35
  279. package/src/toolbars/hooks/TogglePreview.js +0 -91
  280. package/src/toolbars/hooks/Ul.js +0 -41
  281. package/src/toolbars/hooks/Underline.js +0 -68
  282. package/src/toolbars/hooks/Undo.js +0 -30
  283. package/src/toolbars/hooks/Video.js +0 -79
  284. package/src/toolbars/hooks/Word.js +0 -78
  285. package/src/toolbars/hooks/WordCount.js +0 -106
  286. package/src/utils/autoindent.js +0 -58
  287. package/src/utils/cm-search-replace.js +0 -794
  288. package/src/utils/code-preview-language-setting.js +0 -180
  289. package/src/utils/codeBlockContentHandler.js +0 -400
  290. package/src/utils/config.js +0 -174
  291. package/src/utils/copy.js +0 -55
  292. package/src/utils/dialog.js +0 -214
  293. package/src/utils/dom.js +0 -163
  294. package/src/utils/downloadUtil.js +0 -23
  295. package/src/utils/env.js +0 -22
  296. package/src/utils/error.js +0 -61
  297. package/src/utils/event.js +0 -38
  298. package/src/utils/export.js +0 -166
  299. package/src/utils/file.js +0 -164
  300. package/src/utils/formulaUtilsHandler.js +0 -232
  301. package/src/utils/htmlparser.js +0 -976
  302. package/src/utils/image.js +0 -99
  303. package/src/utils/imgSizeHandler.js +0 -279
  304. package/src/utils/lazyLoadImg.js +0 -327
  305. package/src/utils/lineFeed.js +0 -49
  306. package/src/utils/listContentHandler.js +0 -227
  307. package/src/utils/lookbehind-replace.js +0 -81
  308. package/src/utils/mathjax.js +0 -89
  309. package/src/utils/myersDiff.js +0 -211
  310. package/src/utils/pasteHelper.js +0 -253
  311. package/src/utils/platformTransform.js +0 -71
  312. package/src/utils/recount-pos.js +0 -59
  313. package/src/utils/regexp.js +0 -295
  314. package/src/utils/sanitize.js +0 -477
  315. package/src/utils/selection.js +0 -50
  316. package/src/utils/shortcutKey.js +0 -291
  317. package/src/utils/svgUtils.js +0 -96
  318. package/src/utils/tableContentHandler.js +0 -876
  319. package/test/core/CommonMark.spec.ts +0 -62
  320. package/test/core/hooks/AutoLink.spec.ts +0 -28
  321. package/test/core/hooks/List.spec.ts +0 -79
  322. package/test/core/hooks/__snapshots__/List.spec.ts.snap +0 -11
  323. package/test/example.md +0 -778
  324. package/test/node.js +0 -10
  325. package/test/suites/commonmark.spec.json +0 -5218
  326. package/test/tsconfig.test.json +0 -6
  327. package/test/utils/regexp.spec.ts +0 -28
package/src/Previewer.js DELETED
@@ -1,1192 +0,0 @@
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 vDH from 'virtual-dom/h';
17
- import vDDiff from 'virtual-dom/diff';
18
- import vDPatch from 'virtual-dom/patch';
19
- import MyersDiff from './utils/myersDiff';
20
- import { getBlockTopAndHeightWithMargin } from './utils/dom';
21
- import Logger from './Logger';
22
- // import locale from './utils/locale';
23
- import { addEvent, removeEvent } from './utils/event';
24
- import { exportPDF, exportScreenShot, exportMarkdownFile, exportHTMLFile } from './utils/export';
25
- import PreviewerBubble from './toolbars/PreviewerBubble';
26
- import LazyLoadImg from '@/utils/lazyLoadImg';
27
-
28
- let onScroll = () => {}; // store in memory for remove event
29
-
30
- /**
31
- * 作用:
32
- * dom更新
33
- * 局部加载(分片)
34
- * 与左侧输入区域滚动同步
35
- */
36
- export default class Previewer {
37
- /**
38
- * @property
39
- * @private
40
- * @type {boolean} 等待预览区域更新。预览区域更新时,预览区的滚动不会引起编辑器滚动,避免因插入的元素高度变化导致编辑区域跳动
41
- */
42
- applyingDomChanges = false;
43
-
44
- /**
45
- * @property
46
- * @private
47
- * @type {number} 释放同步滚动锁定的定时器ID
48
- */
49
- syncScrollLockTimer = 0;
50
-
51
- /**
52
- * @property
53
- * @public
54
- * @type {boolean} 是否为移动端预览模式
55
- */
56
- isMobilePreview = false;
57
-
58
- /**
59
- *
60
- * @param {Partial<import('~types/previewer').PreviewerOptions>} options 预览区域设置
61
- */
62
- constructor(options) {
63
- /**
64
- * @property
65
- * @type {import('~types/previewer').PreviewerOptions}
66
- */
67
- this.options = {
68
- previewerDom: document.createElement('div'),
69
- virtualDragLineDom: document.createElement('div'),
70
- editorMaskDom: document.createElement('div'),
71
- previewerMaskDom: document.createElement('div'),
72
- minBlockPercentage: 0.2, // editor或previewer所占宽度比例的最小值
73
- value: '',
74
- enablePreviewerBubble: true,
75
- floatWhenClosePreviewer: false, // 是否在关闭预览区时,将预览区浮动
76
- afterUpdateCallBack: [],
77
- isPreviewOnly: false,
78
- previewerCache: {
79
- // 关闭/开启预览区时缓存的previewer数据
80
- html: '',
81
- htmlChanged: false,
82
- layout: {},
83
- },
84
- /**
85
- * 配置图片懒加载的逻辑
86
- * 如果不希望图片懒加载,可配置成 lazyLoadImg = {maxNumPerTime: 6, autoLoadImgNum: -1}
87
- */
88
- lazyLoadImg: {
89
- // 加载图片时如果需要展示loading图,则配置loading图的地址
90
- loadingImgPath: '',
91
- // 同一时间最多有几个图片请求,最大同时加载6张图片
92
- maxNumPerTime: 2,
93
- // 不进行懒加载处理的图片数量,如果为0,即所有图片都进行懒加载处理, 如果设置为-1,则所有图片都不进行懒加载处理
94
- noLoadImgNum: 5,
95
- // 首次自动加载几张图片(不论图片是否滚动到视野内),autoLoadImgNum = -1 表示会自动加载完所有图片
96
- autoLoadImgNum: 5,
97
- // 针对加载失败的图片 或 beforeLoadOneImgCallback 返回false 的图片,最多尝试加载几次,为了防止死循环,最多5次。以图片的src为纬度统计重试次数
98
- maxTryTimesPerSrc: 2,
99
- // 加载一张图片之前的回调函数,函数return false 会终止加载操作
100
- beforeLoadOneImgCallback: (img) => {},
101
- // 加载一张图片失败之后的回调函数
102
- failLoadOneImgCallback: (img) => {},
103
- // 加载一张图片之后的回调函数,如果图片加载失败,则不会回调该函数
104
- afterLoadOneImgCallback: (img) => {},
105
- // 加载完所有图片后调用的回调函数
106
- afterLoadAllImgCallback: () => {},
107
- },
108
- };
109
-
110
- Object.assign(this.options, options);
111
- this.$cherry = this.options.$cherry;
112
- this.instanceId = this.$cherry.getInstanceId();
113
- /**
114
- * @property
115
- * @private
116
- * @type {{ timer?: number; destinationTop?: number }}
117
- */
118
- this.animation = {};
119
- }
120
-
121
- init(editor) {
122
- /**
123
- * @property
124
- * @private
125
- * @type {boolean} 禁用滚动事件监听
126
- */
127
- this.disableScrollListener = false;
128
- this.bindScroll();
129
- this.editor = editor;
130
- this.bindDrag();
131
- this.$initPreviewerBubble();
132
- this.lazyLoadImg = new LazyLoadImg(this.options.lazyLoadImg, this);
133
- this.lazyLoadImg.doLazyLoad();
134
- this.bindClick();
135
- this.onMouseDown();
136
- this.onSizeChange();
137
- }
138
-
139
- /**
140
- * “监听”编辑器的尺寸变化,变化时更新拖拽条的位置
141
- */
142
- onSizeChange() {
143
- // 创建一个新的 ResizeObserver 实例
144
- const resizeObserver = new ResizeObserver(() => {
145
- this.syncVirtualLayoutFromReal();
146
- this.subMenusPositionChange();
147
- });
148
- // 开始监听元素
149
- resizeObserver.observe(this.$cherry.wrapperDom);
150
- }
151
-
152
- subMenusPositionChange() {
153
- ['toolbar', 'sidebar', 'toolbarRight'].forEach((toolbarName) => {
154
- if (this.$cherry[toolbarName]) {
155
- this.$cherry[toolbarName].updateSubMenuPosition();
156
- }
157
- });
158
- }
159
-
160
- $initPreviewerBubble() {
161
- this.previewerBubble = new PreviewerBubble(this);
162
- }
163
-
164
- /**
165
- * @returns {HTMLElement}
166
- */
167
- getDomContainer() {
168
- return this.isMobilePreview
169
- ? this.options.previewerDom.querySelector('.cherry-mobile-previewer-content')
170
- : this.options.previewerDom;
171
- }
172
-
173
- getDom() {
174
- return this.options.previewerDom;
175
- }
176
-
177
- /**
178
- * 获取预览区内的html内容
179
- * @param {boolean} wrapTheme 是否在外层包裹主题class
180
- * @returns html内容
181
- */
182
- getValue(wrapTheme = true) {
183
- let html = '';
184
- if (this.isPreviewerHidden()) {
185
- html = this.options.previewerCache.html;
186
- } else {
187
- html = this.getDomContainer().innerHTML;
188
- }
189
- // 需要未加载的图片替换成原始图片
190
- html = this.lazyLoadImg.changeDataSrc2Src(html);
191
- if (!wrapTheme || !this.$cherry.wrapperDom) {
192
- return html;
193
- }
194
- const inlineCodeTheme = this.$cherry.wrapperDom.getAttribute('data-inline-code-theme');
195
- const codeBlockTheme = this.$cherry.wrapperDom.getAttribute('data-code-block-theme');
196
- return `<div data-inline-code-theme="${inlineCodeTheme}" data-code-block-theme="${codeBlockTheme}">${html}</div>`;
197
- }
198
-
199
- isPreviewerHidden() {
200
- return this.options.previewerDom.classList.contains('cherry-previewer--hidden');
201
- }
202
-
203
- isPreviewerFloat() {
204
- const floatDom = this.$cherry.cherryDom.querySelector('.float-previewer-wrap');
205
- return this.$cherry.cherryDom.contains(floatDom);
206
- }
207
-
208
- isPreviewerNeedFloat() {
209
- return this.options.floatWhenClosePreviewer;
210
- }
211
-
212
- calculateRealLayout(editorWidth) {
213
- // 根据editor的绝对宽度计算editor和previewer的百分比宽度
214
- const editorDomWidth = this.editor.options.editorDom.getBoundingClientRect().width;
215
- const previewerDomWidth = this.options.previewerDom.getBoundingClientRect().width;
216
- const totalWidth = editorDomWidth + previewerDomWidth;
217
- let editorPercentage = +(editorWidth / totalWidth).toFixed(3);
218
- if (editorPercentage < this.options.minBlockPercentage) {
219
- editorPercentage = +this.options.minBlockPercentage.toFixed(3);
220
- } else if (editorPercentage > 1 - this.options.minBlockPercentage) {
221
- editorPercentage = +(1 - this.options.minBlockPercentage).toFixed(3);
222
- }
223
- const previewerPercentage = +(1 - editorPercentage).toFixed(3);
224
- const res = {
225
- editorPercentage: `${editorPercentage * 100}%`,
226
- previewerPercentage: `${previewerPercentage * 100}%`,
227
- };
228
- return res;
229
- }
230
-
231
- setRealLayout(editorPercentage, previewerPercentage) {
232
- // 主动设置editor,previewer宽度,按百分比计算
233
- let $editorPercentage = editorPercentage;
234
- let $previewerPercentage = previewerPercentage;
235
- if (!$editorPercentage || !$previewerPercentage) {
236
- $editorPercentage = '50%';
237
- $previewerPercentage = '50%';
238
- }
239
- this.editor.options.editorDom.style.width = $editorPercentage;
240
- this.options.previewerDom.style.width = $previewerPercentage;
241
-
242
- this.syncVirtualLayoutFromReal();
243
- }
244
-
245
- syncVirtualLayoutFromReal() {
246
- // 通过editor和previewer的百分比宽度,同步更新mask和dragLine的px宽度及位置
247
- const editorPos = this.editor.options.editorDom.getBoundingClientRect();
248
- const previewerPos = this.options.previewerDom.getBoundingClientRect();
249
- const editorHeight = editorPos.height;
250
- const editorTop = this.editor.options.editorDom.offsetTop;
251
- const editorLeft = editorPos.left;
252
- const editorWidth = editorPos.width;
253
- const previewerLeft = previewerPos.left ? previewerPos.left - editorLeft : 0;
254
- const previewerWidth = previewerPos.width || 0;
255
-
256
- const { editorMaskDom, previewerMaskDom, virtualDragLineDom: virtualLineDom } = this.options;
257
-
258
- this.$tryChangeValue(virtualLineDom, 'top', `${editorTop}px`);
259
- this.$tryChangeValue(virtualLineDom, 'left', `${previewerLeft}px`);
260
- this.$tryChangeValue(virtualLineDom, 'bottom', '0px');
261
-
262
- this.$tryChangeValue(editorMaskDom, 'height', `${editorHeight}px`);
263
- this.$tryChangeValue(editorMaskDom, 'top', `${editorTop}px`);
264
- this.$tryChangeValue(editorMaskDom, 'left', '0px');
265
- this.$tryChangeValue(editorMaskDom, 'width', `${editorWidth}px`);
266
-
267
- this.$tryChangeValue(previewerMaskDom, 'height', `${editorHeight}px`);
268
- this.$tryChangeValue(previewerMaskDom, 'top', `${editorTop}px`);
269
- this.$tryChangeValue(previewerMaskDom, 'left', `${previewerLeft}px`);
270
- this.$tryChangeValue(previewerMaskDom, 'width', `${previewerWidth}px`);
271
- }
272
-
273
- $tryChangeValue(obj, key, value) {
274
- if (obj.style[key] !== value) {
275
- obj.style[key] = value;
276
- }
277
- }
278
-
279
- calculateVirtualLayout(editorLeft, editorRight) {
280
- // 计算mask和dragline应处在的位置,按px计算
281
- const editorDomWidth = this.editor.options.editorDom.getBoundingClientRect().width;
282
- const previewerDomWidth = this.options.previewerDom.getBoundingClientRect().width;
283
- const totalWidth = editorDomWidth + previewerDomWidth;
284
- const startWidth = editorLeft.toFixed(0);
285
- let leftWidth = editorRight - editorLeft;
286
- if (leftWidth < totalWidth * this.options.minBlockPercentage) {
287
- leftWidth = +(totalWidth * this.options.minBlockPercentage).toFixed(0);
288
- } else if (leftWidth > totalWidth * (1 - this.options.minBlockPercentage)) {
289
- leftWidth = +(totalWidth * (1 - this.options.minBlockPercentage)).toFixed(0);
290
- }
291
- const rightWidth = totalWidth - leftWidth;
292
- const ret = {
293
- startWidth: parseInt(startWidth, 10), // 起始位置(左侧留白)
294
- leftWidth, // 左侧mask宽度
295
- rightWidth, // 右侧mask宽度
296
- };
297
- return ret;
298
- }
299
-
300
- setVirtualLayout(startWidth, leftWidth, rightWidth) {
301
- // 主动设置mask和dragLine位置,按px计算
302
- const { editorMaskDom, previewerMaskDom, virtualDragLineDom: virtualLineDom } = this.options;
303
- const $startWidth = 0; // =startWidth
304
-
305
- editorMaskDom.style.left = `${$startWidth}px`;
306
- editorMaskDom.style.width = `${leftWidth}px`;
307
-
308
- virtualLineDom.style.left = `${$startWidth + leftWidth}px`;
309
-
310
- previewerMaskDom.style.left = `${$startWidth + leftWidth}px`;
311
- previewerMaskDom.style.width = `${rightWidth}px`;
312
- }
313
-
314
- bindDrag() {
315
- const dragLineMouseMove = (mouseMoveEvent) => {
316
- // 阻止事件冒泡
317
- if (mouseMoveEvent && mouseMoveEvent.stopPropagation) {
318
- mouseMoveEvent.stopPropagation();
319
- } else {
320
- mouseMoveEvent.cancelBubble = true;
321
- }
322
- // 取消默认事件
323
- if (mouseMoveEvent.preventDefault) {
324
- mouseMoveEvent.preventDefault();
325
- } else {
326
- window.event.returnValue = false;
327
- }
328
-
329
- const editorLeft = this.editor.options.editorDom.getBoundingClientRect().left;
330
- const editorRight = mouseMoveEvent.clientX;
331
- const virtualLayout = this.calculateVirtualLayout(editorLeft, editorRight);
332
- this.setVirtualLayout(virtualLayout.startWidth, virtualLayout.leftWidth, virtualLayout.rightWidth);
333
- return false;
334
- };
335
-
336
- const dragLineMouseUp = (mouseUpEvent) => {
337
- // 阻止事件冒泡
338
- if (mouseUpEvent && mouseUpEvent.stopPropagation) {
339
- mouseUpEvent.stopPropagation();
340
- } else {
341
- mouseUpEvent.cancelBubble = true;
342
- }
343
- // 取消默认事件
344
- if (mouseUpEvent.preventDefault) {
345
- mouseUpEvent.preventDefault();
346
- } else {
347
- window.event.returnValue = false;
348
- }
349
-
350
- // 重新设置editor和previewer宽度占比
351
- const editorLeft = this.editor.options.editorDom.getBoundingClientRect().left;
352
- const editorRight = mouseUpEvent.clientX;
353
- const layout = this.calculateRealLayout(editorRight - editorLeft);
354
- this.setRealLayout(layout.editorPercentage, layout.previewerPercentage);
355
- // 去掉蒙层和虚拟拖动条
356
- this.editor.options.editorDom.classList.remove('no-select');
357
- this.options.previewerDom.classList.remove('no-select');
358
- this.options.editorMaskDom.classList.remove('cherry-editor-mask--show');
359
- this.options.previewerMaskDom.classList.remove('cherry-previewer-mask--show');
360
- this.options.virtualDragLineDom.classList.remove('cherry-drag--show');
361
- // 刷新codemirror宽度
362
- this.editor.editor.refresh();
363
- // 取消事件绑定
364
- removeEvent(document, 'mousemove', dragLineMouseMove, false);
365
- removeEvent(document, 'mouseup', dragLineMouseUp, false);
366
- return false;
367
- };
368
-
369
- const dragLineMouseDown = (mouseDownEvent) => {
370
- // 阻止事件冒泡
371
- if (mouseDownEvent && mouseDownEvent.stopPropagation) {
372
- mouseDownEvent.stopPropagation();
373
- } else {
374
- mouseDownEvent.cancelBubble = true;
375
- }
376
- // 取消默认事件
377
- if (mouseDownEvent.preventDefault) {
378
- mouseDownEvent.preventDefault();
379
- } else {
380
- window.event.returnValue = false;
381
- }
382
-
383
- this.syncVirtualLayoutFromReal();
384
-
385
- const editorLeft = this.editor.options.editorDom.getBoundingClientRect().left;
386
- const editorRight = mouseDownEvent.clientX;
387
- const virtualLayout = this.calculateVirtualLayout(editorLeft, editorRight);
388
- this.setVirtualLayout(virtualLayout.startWidth, virtualLayout.leftWidth, virtualLayout.rightWidth);
389
- if (!this.options.virtualDragLineDom.classList.contains('cherry-drag--show')) {
390
- // 增加蒙层防止选中editor或previewer内容
391
- this.options.virtualDragLineDom.classList.add('cherry-drag--show');
392
- this.options.editorMaskDom.classList.add('cherry-editor-mask--show');
393
- this.options.previewerMaskDom.classList.add('cherry-previewer-mask--show');
394
- this.options.previewerDom.classList.add('no-select');
395
- this.editor.options.editorDom.classList.add('no-select');
396
- // 绑定事件
397
- addEvent(document, 'mousemove', dragLineMouseMove, false);
398
- addEvent(document, 'mouseup', dragLineMouseUp, false);
399
- }
400
- return false;
401
- };
402
-
403
- addEvent(this.options.virtualDragLineDom, 'mousedown', dragLineMouseDown, false);
404
- addEvent(window, 'resize', this.syncVirtualLayoutFromReal.bind(this), false);
405
- this.setRealLayout();
406
- }
407
-
408
- bindScroll() {
409
- const domContainer = this.getDomContainer();
410
- onScroll = () => {
411
- if (this.applyingDomChanges) {
412
- Logger.log(new Date(), 'sync scroll locked');
413
- return;
414
- }
415
- if (this.disableScrollListener) {
416
- this.disableScrollListener = false;
417
- return;
418
- }
419
- if (domContainer.scrollTop <= 0) {
420
- this.editor.scrollToLineNum(0, 0, 1);
421
- return;
422
- }
423
- // 判定预览区域是否滚动到底部的逻辑,增加10px的冗余
424
- if (domContainer.scrollTop + domContainer.offsetHeight + 10 > domContainer.scrollHeight) {
425
- this.editor.scrollToLineNum(null);
426
- return;
427
- }
428
- const domPosition = domContainer.getBoundingClientRect();
429
- let targetElement;
430
- let lines = 0;
431
- const elements = domContainer.children;
432
- for (let i = 0; i < elements.length; i++) {
433
- const element = elements[i];
434
- if (element.getBoundingClientRect().top < domPosition.top) {
435
- targetElement = element;
436
- const currentLines = element.getAttribute('data-lines') ?? 0;
437
- lines += +currentLines;
438
- } else {
439
- break;
440
- }
441
- }
442
- if (!targetElement) {
443
- this.editor.scrollToLineNum(0, 0, 1);
444
- return;
445
- }
446
- // markdown元素存在margin,getBoundingRect不能获取到margin
447
- const mdElementStyle = getComputedStyle(targetElement);
448
- const marginTop = parseFloat(mdElementStyle.marginTop);
449
- const marginBottom = parseFloat(mdElementStyle.marginBottom);
450
- // markdown元素基于当前页面的矩形模型
451
- const mdRect = targetElement.getBoundingClientRect();
452
- const mdActualHeight = mdRect.height + marginTop + marginBottom;
453
- // (mdRect.y - marginTop)为顶部触达区域,basePoint.y为预览区域的顶部,故可视范围应减去预览区域的偏移
454
- const mdOffsetTop = mdRect.y - marginTop - domPosition.y;
455
- const lineNum = +targetElement.getAttribute('data-lines'); // 当前markdown元素所占行数
456
- const percent = (100 * Math.abs(mdOffsetTop)) / mdActualHeight / 100;
457
- // console.log('destLine:', lines, percent,
458
- // mdRect.height + marginTop + marginBottom, mdOffsetTop, mdElement);
459
- // if(mdOffsetTop < 0) {
460
- return this.editor.scrollToLineNum(lines - lineNum, lineNum, percent);
461
- // }
462
- // return this.editor.scrollToLineNum(lines - lineNum, 0, 0);
463
- };
464
- addEvent(domContainer, 'scroll', onScroll, false);
465
- addEvent(
466
- domContainer,
467
- 'wheel',
468
- () => {
469
- // 鼠标滚轮滚动时,强制监听滚动事件
470
- this.disableScrollListener = false;
471
- // 打断滚动动画
472
- cancelAnimationFrame(this.animation.timer);
473
- this.animation.timer = 0;
474
- },
475
- false,
476
- );
477
- }
478
-
479
- removeScroll() {
480
- const domContainer = this.getDomContainer();
481
- removeEvent(domContainer, 'scroll', onScroll, false);
482
- }
483
-
484
- $html2H(dom) {
485
- if (typeof dom === 'undefined') {
486
- return vDH('span', {}, []);
487
- }
488
- if (!dom.tagName) {
489
- return dom.textContent;
490
- }
491
- const { tagName } = dom;
492
-
493
- // skip all children if data-cm-atomic attribute is set
494
- const isAtomic = 'true' === dom.getAttribute('data-cm-atomic');
495
-
496
- const myAttrs = this.$getAttrsForH(dom.attributes);
497
- const children = [];
498
- if (!isAtomic && dom.childNodes && dom.childNodes.length > 0) {
499
- for (let i = 0; i < dom.childNodes.length; i++) {
500
- children.push(this.$html2H(dom.childNodes[i]));
501
- }
502
- }
503
- return vDH(tagName, myAttrs, children);
504
- }
505
-
506
- $getAttrsForH(obj) {
507
- if (!obj) {
508
- return {};
509
- }
510
- const ret = { dataset: {} };
511
- for (let i = 0; i < obj.length; i++) {
512
- let { name } = obj[i];
513
- const { value } = obj[i];
514
- if (/^(width|height)$/i.test(name)) {
515
- if (isNaN(value)) {
516
- ret.style = ret.style ? ret.style : [];
517
- ret.style.push(`${name}:${value}`);
518
- continue;
519
- }
520
- }
521
- if (/^(class|id|href|rel|target|src|title|controls|align|width|height|style|open|contenteditable)$/i.test(name)) {
522
- name = name === 'class' ? 'className' : name;
523
- name = name === 'contenteditable' ? 'contentEditable' : name;
524
- if (name === 'style') {
525
- ret.style = ret.style ? ret.style : [];
526
- ret.style.push(value);
527
- } else if (name === 'open') {
528
- // 只要有open这个属性,就一定是true
529
- ret[name] = true;
530
- } else {
531
- ret[name] = value;
532
- }
533
- } else {
534
- // jsDom属性里面rowspan的S要大写,否则应用到html的dom节点会变成data-rowspan
535
- // https://stackoverflow.com/q/29774686
536
- if ('colspan' === name) {
537
- name = 'colSpan';
538
- } else if ('rowspan' === name) {
539
- name = 'rowSpan';
540
- }
541
- if (/^data-/i.test(name)) {
542
- name = name.replace(/^data-/i, '');
543
- } else {
544
- ret[name] = value;
545
- }
546
- ret.dataset[name] = value;
547
- }
548
- }
549
- if (ret.style) {
550
- ret.style = { cssText: ret.style.join(';') }; // see virtual-dom implementation
551
- }
552
- return ret;
553
- }
554
-
555
- $updateDom(newDom, oldDom) {
556
- const diff = vDDiff(this.$html2H(oldDom), this.$html2H(newDom));
557
- return vDPatch(oldDom, diff);
558
- }
559
-
560
- $testChild(dom) {
561
- if (!dom.parentNode) {
562
- return true;
563
- }
564
- if (dom.parentNode.classList.contains('cherry-previewer')) {
565
- return true;
566
- }
567
- if (dom.parentNode.getAttribute('data-sign')) {
568
- return false;
569
- }
570
- return this.$testChild(dom.parentNode);
571
- }
572
- _testMaxIndex(index, arr) {
573
- if (!arr) {
574
- return false;
575
- }
576
- for (let i = 0; i < arr.length; i++) {
577
- if (index <= arr[i]) {
578
- return true;
579
- }
580
- }
581
- return false;
582
- }
583
- $getSignData(list) {
584
- // const list = dom.querySelectorAll('[data-sign]');
585
- const ret = { list: [], signs: {} };
586
- for (let i = 0; i < list.length; i++) {
587
- if (!list[i].getAttribute('data-sign')) {
588
- continue;
589
- }
590
- const sign = list[i].getAttribute('data-sign');
591
- ret.list.push({ sign, dom: list[i] });
592
- if (!ret.signs[sign]) {
593
- ret.signs[sign] = [];
594
- }
595
- ret.signs[sign].push(i);
596
- }
597
- return ret;
598
- }
599
-
600
- _hasNewSign(list, sign, signIndex) {
601
- if (list.length > 0) {
602
- let resSign;
603
- list.forEach((listItem, i) => {
604
- // hash精度校准
605
- if (listItem.sign.slice(0, 12) === sign.slice(0, 12) && i > signIndex) {
606
- resSign = {
607
- index: i > signIndex ? i : signIndex,
608
- sign,
609
- };
610
- }
611
- });
612
- return resSign;
613
- }
614
- return false;
615
- }
616
-
617
- $dealWithMyersDiffResult(result, oldContent, newContent, domContainer) {
618
- result.forEach((change) => {
619
- if (newContent[change.newIndex].dom) {
620
- // 把已经加载过的图片的data-src变成src
621
- newContent[change.newIndex].dom.innerHTML = this.lazyLoadImg.changeLoadedDataSrc2Src(
622
- newContent[change.newIndex].dom.innerHTML,
623
- );
624
- }
625
- switch (change.type) {
626
- case 'delete':
627
- domContainer.removeChild(oldContent[change.oldIndex].dom);
628
- break;
629
- case 'insert':
630
- if (oldContent[change.oldIndex]) {
631
- domContainer.insertBefore(newContent[change.newIndex].dom, oldContent[change.oldIndex].dom);
632
- } else {
633
- domContainer.appendChild(newContent[change.newIndex].dom);
634
- }
635
- break;
636
- case 'update':
637
- try {
638
- // 处理表格包含图表的特殊场景
639
- let hasUpdate = false;
640
- if (
641
- newContent[change.newIndex].dom.className === 'cherry-table-container' &&
642
- newContent[change.newIndex].dom.querySelector('.cherry-table-figure') &&
643
- oldContent[change.oldIndex].dom.querySelector('.cherry-table-figure')
644
- ) {
645
- oldContent[change.oldIndex].dom
646
- .querySelector('.cherry-table-figure')
647
- .replaceWith(newContent[change.newIndex].dom.querySelector('.cherry-table-figure'));
648
- oldContent[change.oldIndex].dom.dataset.sign = newContent[change.oldIndex].dom.dataset.sign;
649
- this.$updateDom(
650
- newContent[change.newIndex].dom.querySelector('.cherry-table'),
651
- oldContent[change.oldIndex].dom.querySelector('.cherry-table'),
652
- );
653
- hasUpdate = true;
654
- } else if (newContent[change.newIndex].dom.querySelector('svg')) {
655
- throw new Error(); // SVG暂不使用patch更新
656
- }
657
- if (!hasUpdate) {
658
- this.$updateDom(newContent[change.newIndex].dom, oldContent[change.oldIndex].dom);
659
- }
660
- } catch (e) {
661
- domContainer.insertBefore(newContent[change.newIndex].dom, oldContent[change.oldIndex].dom);
662
- domContainer.removeChild(oldContent[change.oldIndex].dom);
663
- }
664
- }
665
- });
666
- }
667
-
668
- $dealUpdate(domContainer, oldHtmlList, newHtmlList) {
669
- if (newHtmlList.list !== oldHtmlList.list) {
670
- if (newHtmlList.list.length && oldHtmlList.list.length) {
671
- const myersDiff = new MyersDiff(newHtmlList.list, oldHtmlList.list, (obj, index) => obj[index].sign);
672
- const res = myersDiff.doDiff();
673
- Logger.log(res);
674
- this.$dealWithMyersDiffResult(res, oldHtmlList.list, newHtmlList.list, domContainer);
675
- } else if (newHtmlList.list.length && !oldHtmlList.list.length) {
676
- // 全新增
677
- Logger.log('add all');
678
- newHtmlList.list.forEach((piece) => {
679
- domContainer.appendChild(piece.dom);
680
- });
681
- } else if (!newHtmlList.list.length && oldHtmlList.list.length) {
682
- // 全删除
683
- Logger.log('delete all');
684
- oldHtmlList.list.forEach((piece) => {
685
- domContainer.removeChild(piece.dom);
686
- });
687
- }
688
- }
689
- }
690
-
691
- /**
692
- * 强制重新渲染预览区域
693
- */
694
- refresh(html) {
695
- const domContainer = this.getDomContainer();
696
- domContainer.innerHTML = html;
697
- }
698
-
699
- update(html) {
700
- // 更新时保留图片懒加载逻辑
701
- const newHtml = this.lazyLoadImg.changeSrc2DataSrc(html);
702
- if (!this.isPreviewerHidden()) {
703
- // 标记当前正在更新预览区域,锁定同步滚动功能
704
- window.clearTimeout(this.syncScrollLockTimer);
705
- this.applyingDomChanges = true;
706
- // 预览区未隐藏时,直接更新
707
- const domContainer = this.getDomContainer();
708
- if (this.editor.selectAll) {
709
- domContainer.innerHTML = '';
710
- }
711
- let tmpDiv = null;
712
- if (typeof window.DOMParser !== 'undefined') {
713
- // 如果支持DOMParser,则使用DOMParser将html字符串转成对应的HtmlElement
714
- // 使用DOMParser是为了防止newHtml里的图片等资源自动加载
715
- const parser = new DOMParser();
716
- const doc = parser.parseFromString(newHtml, 'text/html');
717
- tmpDiv = doc.querySelector('body');
718
- } else {
719
- tmpDiv = document.createElement('div');
720
- tmpDiv.innerHTML = newHtml;
721
- }
722
- const newHtmlList = this.$getSignData(tmpDiv.children);
723
- const oldHtmlList = this.$getSignData(domContainer.children);
724
-
725
- try {
726
- this.$dealUpdate(domContainer, oldHtmlList, newHtmlList);
727
- this.afterUpdate();
728
- } finally {
729
- // 延时释放同步滚动功能,在DOM更新完成后执行
730
- this.syncScrollLockTimer = window.setTimeout(() => {
731
- this.applyingDomChanges = false;
732
- }, 50);
733
- }
734
- } else {
735
- // 预览区隐藏时,先缓存起来,等到预览区打开再一次性更新
736
- this.doHtmlCache(newHtml);
737
- }
738
- }
739
-
740
- $dealEditAndPreviewOnly(isEditOnly = true) {
741
- let fullEditorLayout = {
742
- editorPercentage: '0%',
743
- previewerPercentage: '100%',
744
- };
745
- if (isEditOnly) {
746
- fullEditorLayout = {
747
- editorPercentage: '100%',
748
- previewerPercentage: '0%',
749
- };
750
- }
751
- const editorWidth = this.editor.options.editorDom.getBoundingClientRect().width;
752
- const layout = this.calculateRealLayout(editorWidth);
753
- this.options.previewerCache.layout = layout;
754
- this.setRealLayout(fullEditorLayout.editorPercentage, fullEditorLayout.previewerPercentage);
755
- this.options.virtualDragLineDom.classList.add('cherry-drag--hidden');
756
- const { previewerDom } = this.options;
757
- const { editorDom } = this.editor.options;
758
- if (isEditOnly) {
759
- previewerDom.classList.add('cherry-previewer--hidden');
760
- editorDom.classList.add('cherry-editor--full');
761
- previewerDom.classList.remove('cherry-preview--full');
762
- editorDom.classList.remove('cherry-editor--hidden');
763
- } else {
764
- previewerDom.classList.add('cherry-preview--full');
765
- editorDom.classList.add('cherry-editor--hidden');
766
- previewerDom.classList.remove('cherry-previewer--hidden');
767
- editorDom.classList.remove('cherry-editor--full');
768
- }
769
- setTimeout(() => this.editor.editor.refresh(), 0);
770
- }
771
-
772
- previewOnly() {
773
- this.$dealEditAndPreviewOnly(false);
774
- if (this.options.previewerCache.htmlChanged) {
775
- this.update(this.options.previewerCache.html);
776
- }
777
- this.cleanHtmlCache();
778
- this.$cherry.$event.emit('previewerOpen');
779
- this.$cherry.$event.emit('editorClose');
780
- }
781
-
782
- editOnly(dealToolbar = false) {
783
- this.$dealEditAndPreviewOnly(true);
784
- this.cleanHtmlCache();
785
- this.$cherry.$event.emit('previewerClose');
786
- this.$cherry.$event.emit('editorOpen');
787
- }
788
-
789
- floatPreviewer() {
790
- const fullEditorLayout = {
791
- editorPercentage: '100%',
792
- previewerPercentage: '100%',
793
- };
794
- const editorWidth = this.editor.options.editorDom.getBoundingClientRect().width;
795
- const layout = this.calculateRealLayout(editorWidth);
796
- this.options.previewerCache.layout = layout;
797
- this.setRealLayout(fullEditorLayout.editorPercentage, fullEditorLayout.previewerPercentage);
798
- this.options.virtualDragLineDom.classList.add('cherry-drag--hidden');
799
- this.$cherry.createFloatPreviewer();
800
- }
801
-
802
- recoverFloatPreviewer() {
803
- this.recoverPreviewer(true);
804
- this.$cherry.clearFloatPreviewer();
805
- }
806
-
807
- recoverPreviewer(dealToolbar = false) {
808
- this.options.previewerDom.classList.remove('cherry-previewer--hidden');
809
- this.options.virtualDragLineDom.classList.remove('cherry-drag--hidden');
810
- this.editor.options.editorDom.classList.remove('cherry-editor--full');
811
- // 恢复现场
812
- const { layout } = this.options.previewerCache;
813
- this.setRealLayout(layout.editorPercentage, layout.previewerPercentage);
814
- if (this.options.previewerCache.htmlChanged) {
815
- this.update(this.options.previewerCache.html);
816
- }
817
- this.cleanHtmlCache();
818
-
819
- this.$cherry.$event.emit('previewerOpen');
820
- this.$cherry.$event.emit('editorOpen');
821
-
822
- setTimeout(() => this.editor.editor.refresh(), 0);
823
- }
824
-
825
- doHtmlCache(html) {
826
- this.options.previewerCache.html = html;
827
- this.options.previewerCache.htmlChanged = true;
828
- }
829
-
830
- cleanHtmlCache() {
831
- this.options.previewerCache.html = '';
832
- this.options.previewerCache.htmlChanged = false;
833
- this.options.previewerCache.layout = {};
834
- }
835
-
836
- afterUpdate() {
837
- this.options.afterUpdateCallBack.map((fn) => fn());
838
- if (this.highlightLineNum === undefined) {
839
- this.highlightLineNum = 0;
840
- }
841
- this.highlightLine(this.highlightLineNum);
842
- }
843
-
844
- registerAfterUpdate(fn) {
845
- if (Array.isArray(fn)) {
846
- this.options.afterUpdateCallBack = this.options.afterUpdateCallBack.concat(fn);
847
- } else if (!fn) {
848
- throw new Error('[markdown error]: Previewer registerAfterUpdate params are undefined');
849
- } else {
850
- this.options.afterUpdateCallBack.push(fn);
851
- }
852
- }
853
-
854
- /**
855
- * 根据行号计算出top值
856
- * @param {Number} lineNum
857
- * @param {Number} linePercent
858
- * @return {Number} top
859
- */
860
- $getTopByLineNum(lineNum, linePercent = 0) {
861
- const domContainer = this.getDomContainer();
862
- if (lineNum === null) {
863
- return domContainer.scrollHeight;
864
- }
865
- const $lineNum = typeof lineNum === 'number' ? lineNum : parseInt(lineNum, 10);
866
- const doms = /** @type {NodeListOf<HTMLElement>}*/ (domContainer.querySelectorAll('[data-sign]'));
867
- let lines = 0;
868
- const containerY = domContainer.offsetTop;
869
- for (let index = 0; index < doms.length; index++) {
870
- if (doms[index].parentNode !== domContainer) {
871
- continue;
872
- }
873
- const blockLines = parseInt(doms[index].getAttribute('data-lines'), 10);
874
- if (lines + blockLines < $lineNum) {
875
- lines += blockLines;
876
- continue;
877
- } else {
878
- // 基础定位,区块高度及offsetTop会受到block margin合并的影响
879
- const { height: blockHeight, offsetTop } = getBlockTopAndHeightWithMargin(doms[index]);
880
- const blockY = offsetTop - containerY;
881
- let scrollTo = blockY + blockHeight * linePercent;
882
- // 区块多于1行
883
- if (blockLines > 1) {
884
- // 高度百分比计算
885
- // 该区块已经滚动过的行,不包括当前行,减一
886
- const overScrolledLines = blockLines - Math.abs($lineNum - (lines + blockLines)) - 1;
887
- const overScrolledHeight = (overScrolledLines / blockLines) * blockHeight; // 已经滚过的高度
888
- const blockLineHeight = blockHeight / blockLines; // 该区块每一行的高度
889
- // 应该滚动到的位置
890
- scrollTo = blockY + overScrolledHeight + blockLineHeight * linePercent;
891
- // console.log('overscrolled:', overScrolledHeight, blockLineHeight, linePercent);
892
- }
893
- // console.log('滚动编辑区域,左侧应scroll to ', lineNum, '::',scrollTo);
894
- return scrollTo;
895
- }
896
- }
897
- // 如果计算完预览区域所有的行号依然<左侧光标所在的行号,则预览区域直接滚到最低部
898
- return domContainer.scrollHeight;
899
- }
900
-
901
- /**
902
- * 高亮预览区域对应的行
903
- * @param {Number} lineNum
904
- */
905
- highlightLine(lineNum) {
906
- const domContainer = this.getDomContainer();
907
- // 先取消所有行的高亮效果
908
- domContainer.querySelectorAll('.cherry-highlight-line').forEach((element) => {
909
- element.classList.remove('cherry-highlight-line');
910
- });
911
- // 只有双栏模式下才需要高亮光标对应的预览区域
912
- if (this.$cherry?.status?.previewer !== 'show' || this.$cherry?.status?.editor !== 'show') {
913
- return;
914
- }
915
- const doms = /** @type {NodeListOf<HTMLElement>}*/ (domContainer.querySelectorAll('[data-sign]'));
916
- let lines = 0;
917
- for (let index = 0; index < doms.length; index++) {
918
- if (doms[index].parentNode !== domContainer) {
919
- continue;
920
- }
921
- const blockLines = parseInt(doms[index].getAttribute('data-lines'), 10);
922
- if (lines + blockLines < lineNum) {
923
- lines += blockLines;
924
- continue;
925
- } else {
926
- this.highlightLineNum = lineNum;
927
- doms[index].classList.add('cherry-highlight-line');
928
- return;
929
- }
930
- }
931
- }
932
-
933
- /**
934
- * 滚动到对应行号位置并加上偏移量
935
- * @param {Number} lineNum
936
- * @param {Number} offset
937
- */
938
- scrollToLineNumWithOffset(lineNum, offset) {
939
- const top = this.$getTopByLineNum(lineNum) - offset;
940
- this.$scrollAnimation(top);
941
- this.highlightLine(lineNum);
942
- }
943
-
944
- /**
945
- * 滚动到对应位置
946
- * @param {number} scrollTop 元素的id属性值
947
- * @param {'auto'|'smooth'|'instant'} behavior 滚动方式
948
- */
949
- scrollToTop(scrollTop, behavior = 'auto') {
950
- const previewDom = this.getDomContainer();
951
- const scrollDom = this.getDomCanScroll(previewDom);
952
- scrollDom.scrollTo({
953
- top: scrollTop,
954
- left: 0,
955
- behavior,
956
- });
957
- }
958
-
959
- /**
960
- * 滚动到对应id的位置,实现锚点滚动能力
961
- * @param {string} id 元素的id属性值
962
- * @param {'smooth'|'instant'|'auto'} behavior 滚动方式
963
- * @return {boolean} 是否有对应id的元素并执行滚动
964
- */
965
- scrollToId(id, behavior = 'smooth') {
966
- const previewDom = this.getDomContainer();
967
- const scrollDom = this.getDomCanScroll(previewDom);
968
- // 设置未加载图片的默认尺寸
969
- const images = previewDom.getElementsByTagName('img');
970
- const modifiedImages = new Set(); // 记录被修改过样式的图片
971
- let isDealScroll = false;
972
- Array.from(images).forEach((img) => {
973
- if (!img.hasAttribute('width') && !img.hasAttribute('height') && !img.style.width && !img.style.height) {
974
- // img.style.minHeight = '200px';
975
- // img.style.aspectRatio = '16/9';
976
- modifiedImages.add(img);
977
- }
978
- });
979
-
980
- let $id = id.replace(/^\s*#/, '').trim();
981
- $id = /[%:]/.test($id) ? $id : encodeURIComponent($id);
982
- const target = previewDom.querySelector(`[id="${$id}"]`) ?? false;
983
- if (target === false) {
984
- return false;
985
- }
986
-
987
- let scrollTop = 0;
988
-
989
- if (scrollDom.nodeName === 'HTML') {
990
- scrollTop = scrollDom.scrollTop + target.getBoundingClientRect().y - 10;
991
- } else {
992
- scrollTop = scrollDom.scrollTop + target.getBoundingClientRect().y - scrollDom.getBoundingClientRect().y - 10;
993
- }
994
-
995
- // 创建一个函数来清理图片样式并重新滚动
996
- const cleanupAndScroll = () => {
997
- // modifiedImages.forEach((img) => {
998
- // img.style.minHeight = '';
999
- // img.style.aspectRatio = '';
1000
- // });
1001
- modifiedImages.clear();
1002
-
1003
- // 重新计算位置并滚动
1004
- let newScrollTop = 0;
1005
- if (scrollDom.nodeName === 'HTML') {
1006
- newScrollTop = scrollDom.scrollTop + target.getBoundingClientRect().y - 10;
1007
- } else {
1008
- newScrollTop =
1009
- scrollDom.scrollTop + target.getBoundingClientRect().y - scrollDom.getBoundingClientRect().y - 10;
1010
- }
1011
- // 如果位置有变化,使用instant行为重新滚动
1012
- if (Math.abs(newScrollTop - scrollTop) > 5) {
1013
- scrollDom.scrollTo({
1014
- top: newScrollTop,
1015
- left: 0,
1016
- behavior: 'instant',
1017
- });
1018
- }
1019
- };
1020
-
1021
- // 监听滚动结束事件
1022
- const handleScrollEnd = () => {
1023
- if (isDealScroll) {
1024
- return;
1025
- }
1026
- isDealScroll = true;
1027
- // 移除滚动事件监听器
1028
- scrollDom.removeEventListener('scrollend', handleScrollEnd);
1029
- // 等待一小段时间确保图片开始加载
1030
- setTimeout(() => {
1031
- // 获取所有修改过的图片的加载状态
1032
- const imageLoadPromises = Array.from(modifiedImages).map((img) => {
1033
- if (img.complete) return Promise.resolve();
1034
- return new Promise((resolve) => {
1035
- const onLoad = () => {
1036
- img.removeEventListener('load', onLoad);
1037
- img.removeEventListener('error', onLoad);
1038
- resolve();
1039
- };
1040
- img.addEventListener('load', onLoad);
1041
- img.addEventListener('error', onLoad);
1042
- });
1043
- });
1044
-
1045
- // 等待所有图片加载完成后再清理样式
1046
- Promise.all(imageLoadPromises).then(() => {
1047
- // 使用 requestAnimationFrame 确保在下一帧渲染时处理
1048
- requestAnimationFrame(cleanupAndScroll);
1049
- });
1050
- }, 100);
1051
- };
1052
-
1053
- // 添加滚动结束事件监听器
1054
- scrollDom.addEventListener('scrollend', handleScrollEnd);
1055
-
1056
- // 如果浏览器不支持 scrollend 事件,使用 setTimeout 作为后备方案
1057
- setTimeout(() => {
1058
- scrollDom.removeEventListener('scrollend', handleScrollEnd);
1059
- handleScrollEnd();
1060
- }, 1000);
1061
-
1062
- // 开始滚动
1063
- scrollDom.scrollTo({
1064
- top: scrollTop,
1065
- left: 0,
1066
- behavior,
1067
- });
1068
-
1069
- return true;
1070
- }
1071
-
1072
- /**
1073
- * 实现滚动动画
1074
- * @param { Number } targetY 目标位置
1075
- */
1076
- $scrollAnimation(targetY) {
1077
- this.animation.destinationTop = targetY;
1078
- if (this.animation.timer) {
1079
- return;
1080
- }
1081
- const animationHandler = () => {
1082
- const dom = this.getDomContainer();
1083
- const currentTop = dom.scrollTop;
1084
- const delta = this.animation.destinationTop - currentTop;
1085
- // 100毫秒内完成动画
1086
- const move = Math.ceil(Math.min(Math.abs(delta), Math.max(1, Math.abs(delta) / (100 / 16.7))));
1087
- if (delta === 0 || currentTop >= dom.scrollHeight || move > Math.abs(delta)) {
1088
- cancelAnimationFrame(this.animation.timer);
1089
- this.animation.timer = 0;
1090
- return;
1091
- }
1092
- this.disableScrollListener = true;
1093
- this.getDomContainer().scrollTo(null, currentTop + (delta / Math.abs(delta)) * move);
1094
- this.animation.timer = requestAnimationFrame(animationHandler);
1095
- };
1096
- this.animation.timer = requestAnimationFrame(animationHandler);
1097
- }
1098
-
1099
- scrollToLineNum(lineNum, linePercent) {
1100
- const top = this.$getTopByLineNum(lineNum, linePercent);
1101
- this.$scrollAnimation(top);
1102
- }
1103
-
1104
- /**
1105
- * 获取有滚动条的dom
1106
- */
1107
- getDomCanScroll(currentDom = this.getDomContainer()) {
1108
- if (currentDom.scrollHeight > currentDom.clientHeight || currentDom.clientHeight < window.innerHeight) {
1109
- return currentDom;
1110
- }
1111
- if (currentDom.parentElement) {
1112
- if (currentDom.nodeName === 'BODY') {
1113
- // 如果当前是body了,再往上就是html了
1114
- if (document.documentElement.scrollHeight > document.documentElement.clientHeight) {
1115
- return document.documentElement;
1116
- }
1117
- return currentDom;
1118
- }
1119
- return this.getDomCanScroll(currentDom.parentElement);
1120
- }
1121
- }
1122
-
1123
- scrollToHeadByIndex(index) {
1124
- const previewDom = this.getDomContainer();
1125
- const targetHead = previewDom.querySelectorAll('h1,h2,h3,h4,h5,h6,h7,h8')[index] ?? false;
1126
- if (targetHead !== false) {
1127
- this.scrollToId(targetHead.id);
1128
- }
1129
- }
1130
-
1131
- bindClick() {
1132
- this.getDomContainer().addEventListener('click', (event) => {
1133
- if (this.$cherry.options.callback.onClickPreview) {
1134
- const ret = this.$cherry.options.callback.onClickPreview(event);
1135
- // @ts-ignore
1136
- if (ret === false) {
1137
- return ret;
1138
- }
1139
- }
1140
- // 如果配置了点击toc目录不更新location hash
1141
- // @ts-ignore
1142
- if (this.$cherry.options.toolbars.toc?.updateLocationHash === false) {
1143
- const { target } = event;
1144
- if (target instanceof Element && target.nodeName === 'A' && /level-\d+/.test(target.className)) {
1145
- const liNode = target.parentElement;
1146
- const index = Array.from(liNode.parentElement.children).indexOf(liNode) - 1;
1147
- this.scrollToHeadByIndex(index);
1148
- event.stopPropagation();
1149
- event.preventDefault();
1150
- }
1151
- /** 增加个潜规则逻辑,脚注跳转时是否更新location hash也跟随options.toolbars.toc.updateLocationHash 的配置 */
1152
- if (target instanceof Element && target.nodeName === 'A' && /(footnote|footnote-ref)/.test(target.className)) {
1153
- const id = target.getAttribute('href');
1154
- this.scrollToId(id);
1155
- event.stopPropagation();
1156
- event.preventDefault();
1157
- }
1158
- }
1159
- });
1160
- }
1161
-
1162
- onMouseDown() {
1163
- addEvent(this.getDomContainer(), 'mousedown', () => {
1164
- setTimeout(() => {
1165
- this.$cherry.$event.emit('cleanAllSubMenus');
1166
- });
1167
- });
1168
- }
1169
- /**
1170
- * 导出预览区域内容
1171
- * @public
1172
- * @param {'pdf' | 'img' | 'screenShot' | 'markdown' | 'html'} [type='pdf']
1173
- * 'pdf':导出成pdf文件; 'img' | screenShot:导出成png图片; 'markdown':导出成markdown文件; 'html':导出成html文件;
1174
- * @param {string} [fileName] 导出文件名
1175
- */
1176
- export(type = 'pdf', fileName = '') {
1177
- let name = fileName;
1178
- if (!fileName) {
1179
- const { innerText } = this.getDomContainer();
1180
- name = /^\s*([^\s][^\n]*)\n/.test(innerText) ? innerText.match(/^\s*([^\s][^\n]*)\n/)[1] : 'cherry-export';
1181
- }
1182
- if (type === 'pdf') {
1183
- exportPDF(this.getDomContainer(), name);
1184
- } else if (type === 'screenShot' || type === 'img') {
1185
- exportScreenShot(this.getDomContainer(), name);
1186
- } else if (type === 'markdown') {
1187
- exportMarkdownFile(this.$cherry.getMarkdown(), name);
1188
- } else if (type === 'html') {
1189
- exportHTMLFile(this.getValue(), name);
1190
- }
1191
- }
1192
- }