cherry-muse 1.0.1

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 (226) hide show
  1. package/LICENSE +162 -0
  2. package/README.md +139 -0
  3. package/dist/addons/cherry-code-block-card-plugin.js +1 -0
  4. package/dist/addons/cherry-code-block-echarts-plugin.js +1 -0
  5. package/dist/addons/cherry-code-block-mermaid-plugin.js +1 -0
  6. package/dist/cherry-markdown.core.common.js +1 -0
  7. package/dist/cherry-markdown.core.js +1 -0
  8. package/dist/cherry-markdown.css +4089 -0
  9. package/dist/cherry-markdown.engine.core.common.js +1 -0
  10. package/dist/cherry-markdown.engine.core.esm.js +1 -0
  11. package/dist/cherry-markdown.engine.core.js +1 -0
  12. package/dist/cherry-markdown.esm.js +1 -0
  13. package/dist/cherry-markdown.js +70837 -0
  14. package/dist/cherry-markdown.js.map +1 -0
  15. package/dist/cherry-markdown.min.css +1 -0
  16. package/dist/cherry-markdown.min.js +1 -0
  17. package/dist/stats.html +4838 -0
  18. package/examples/scripts/pinyin/README.md +53 -0
  19. package/package.json +167 -0
  20. package/src/Cherry.config.js +411 -0
  21. package/src/Cherry.js +788 -0
  22. package/src/CherryStatic.js +70 -0
  23. package/src/Editor.js +746 -0
  24. package/src/Engine.js +334 -0
  25. package/src/Event.js +74 -0
  26. package/src/Factory.js +180 -0
  27. package/src/Logger.js +31 -0
  28. package/src/Previewer.js +1147 -0
  29. package/src/Sanitizer.js +4 -0
  30. package/src/Sanitizer.node.js +7 -0
  31. package/src/Stats.js +101 -0
  32. package/src/Theme.js +46 -0
  33. package/src/UrlCache.js +98 -0
  34. package/src/addons/cherry-code-block-card-plugin.js +213 -0
  35. package/src/addons/cherry-code-block-echarts-plugin.js +161 -0
  36. package/src/addons/cherry-code-block-mermaid-plugin.js +118 -0
  37. package/src/core/HookCenter.js +303 -0
  38. package/src/core/HooksConfig.js +106 -0
  39. package/src/core/ParagraphBase.js +314 -0
  40. package/src/core/SentenceBase.js +65 -0
  41. package/src/core/SyntaxBase.js +197 -0
  42. package/src/core/hooks/AutoLink.js +251 -0
  43. package/src/core/hooks/BackgroundColor.js +46 -0
  44. package/src/core/hooks/Badge.js +100 -0
  45. package/src/core/hooks/Blockquote.js +113 -0
  46. package/src/core/hooks/Br.js +85 -0
  47. package/src/core/hooks/CodeBlock.js +876 -0
  48. package/src/core/hooks/Color.js +78 -0
  49. package/src/core/hooks/CommentReference.js +96 -0
  50. package/src/core/hooks/Detail.js +138 -0
  51. package/src/core/hooks/Emoji.config.js +9388 -0
  52. package/src/core/hooks/Emoji.js +223 -0
  53. package/src/core/hooks/Emphasis.js +113 -0
  54. package/src/core/hooks/Footnote.js +125 -0
  55. package/src/core/hooks/FrontMatter.js +52 -0
  56. package/src/core/hooks/FrontMatterVars.js +82 -0
  57. package/src/core/hooks/Header.js +229 -0
  58. package/src/core/hooks/HighLight.js +37 -0
  59. package/src/core/hooks/Hr.js +52 -0
  60. package/src/core/hooks/HtmlBlock.js +159 -0
  61. package/src/core/hooks/Iframe.js +80 -0
  62. package/src/core/hooks/Image.js +276 -0
  63. package/src/core/hooks/InlineCode.js +45 -0
  64. package/src/core/hooks/InlineMath.js +142 -0
  65. package/src/core/hooks/Link.js +169 -0
  66. package/src/core/hooks/List.js +260 -0
  67. package/src/core/hooks/Mark.js +55 -0
  68. package/src/core/hooks/MathBlock.js +97 -0
  69. package/src/core/hooks/Panel.js +170 -0
  70. package/src/core/hooks/Paragraph.js +84 -0
  71. package/src/core/hooks/Ruby.js +34 -0
  72. package/src/core/hooks/Size.js +84 -0
  73. package/src/core/hooks/Strikethrough.js +54 -0
  74. package/src/core/hooks/Sub.js +47 -0
  75. package/src/core/hooks/SuggestList.js +317 -0
  76. package/src/core/hooks/Suggester.js +759 -0
  77. package/src/core/hooks/Sup.js +47 -0
  78. package/src/core/hooks/Table.js +315 -0
  79. package/src/core/hooks/Toc.js +290 -0
  80. package/src/core/hooks/Transfer.js +47 -0
  81. package/src/core/hooks/Underline.js +37 -0
  82. package/src/index.core.js +29 -0
  83. package/src/index.engine.core.js +62 -0
  84. package/src/index.engine.js +30 -0
  85. package/src/index.js +28 -0
  86. package/src/locales/index.js +21 -0
  87. package/src/locales/zh_CN.js +170 -0
  88. package/src/sass/cherry.scss +122 -0
  89. package/src/sass/components/bubble.scss +122 -0
  90. package/src/sass/components/codemirror.scss +628 -0
  91. package/src/sass/components/dropdown.scss +37 -0
  92. package/src/sass/components/editor.scss +78 -0
  93. package/src/sass/components/preview.scss +71 -0
  94. package/src/sass/components/prism.scss +142 -0
  95. package/src/sass/components/stats.scss +32 -0
  96. package/src/sass/components/toc.scss +184 -0
  97. package/src/sass/components/toolbar.scss +117 -0
  98. package/src/sass/core/AutoLink.scss +20 -0
  99. package/src/sass/core/BackgroundColor.scss +0 -0
  100. package/src/sass/core/Badge.scss +116 -0
  101. package/src/sass/core/Blockquote.scss +12 -0
  102. package/src/sass/core/Br.scss +0 -0
  103. package/src/sass/core/Card.scss +219 -0
  104. package/src/sass/core/CodeBlock.scss +205 -0
  105. package/src/sass/core/Color.scss +37 -0
  106. package/src/sass/core/CommentReference.scss +0 -0
  107. package/src/sass/core/Detail.scss +107 -0
  108. package/src/sass/core/Emoji.scss +127 -0
  109. package/src/sass/core/Emphasis.scss +9 -0
  110. package/src/sass/core/Footnote.scss +21 -0
  111. package/src/sass/core/FrontMatterVars.scss +19 -0
  112. package/src/sass/core/Header.scss +103 -0
  113. package/src/sass/core/HighLight.scss +0 -0
  114. package/src/sass/core/Hr.scss +10 -0
  115. package/src/sass/core/HtmlBlock.scss +0 -0
  116. package/src/sass/core/Iframe.scss +36 -0
  117. package/src/sass/core/Image.scss +59 -0
  118. package/src/sass/core/InlineCode.scss +10 -0
  119. package/src/sass/core/InlineMath.scss +11 -0
  120. package/src/sass/core/Link.scss +16 -0
  121. package/src/sass/core/List.scss +61 -0
  122. package/src/sass/core/Mark.scss +15 -0
  123. package/src/sass/core/MathBlock.scss +0 -0
  124. package/src/sass/core/Panel.scss +150 -0
  125. package/src/sass/core/Paragraph.scss +6 -0
  126. package/src/sass/core/Ruby.scss +0 -0
  127. package/src/sass/core/Size.scss +8 -0
  128. package/src/sass/core/Strikethrough.scss +0 -0
  129. package/src/sass/core/Sub.scss +5 -0
  130. package/src/sass/core/Suggester.scss +62 -0
  131. package/src/sass/core/Sup.scss +5 -0
  132. package/src/sass/core/Table.scss +127 -0
  133. package/src/sass/core/Toc.scss +28 -0
  134. package/src/sass/core/Transfer.scss +0 -0
  135. package/src/sass/core/Underline.scss +0 -0
  136. package/src/sass/google-fonts.scss +34 -0
  137. package/src/sass/index.scss +3 -0
  138. package/src/sass/prism/dark.scss +131 -0
  139. package/src/sass/prism/light.scss +143 -0
  140. package/src/sass/variables/colors.scss +96 -0
  141. package/src/toolbars/Bubble.js +232 -0
  142. package/src/toolbars/BubbleTable.js +147 -0
  143. package/src/toolbars/HookCenter.js +185 -0
  144. package/src/toolbars/MenuBase.js +357 -0
  145. package/src/toolbars/PreviewerBubble.js +558 -0
  146. package/src/toolbars/Toc.js +246 -0
  147. package/src/toolbars/Toolbar.js +401 -0
  148. package/src/toolbars/hooks/Audio.js +53 -0
  149. package/src/toolbars/hooks/Badge.js +80 -0
  150. package/src/toolbars/hooks/BarTable.js +41 -0
  151. package/src/toolbars/hooks/Bold.js +70 -0
  152. package/src/toolbars/hooks/Br.js +34 -0
  153. package/src/toolbars/hooks/Card.js +64 -0
  154. package/src/toolbars/hooks/CheckList.js +41 -0
  155. package/src/toolbars/hooks/Code.js +46 -0
  156. package/src/toolbars/hooks/Color.js +285 -0
  157. package/src/toolbars/hooks/Copy.js +139 -0
  158. package/src/toolbars/hooks/Detail.js +70 -0
  159. package/src/toolbars/hooks/ECharts.js +303 -0
  160. package/src/toolbars/hooks/Emoji.js +303 -0
  161. package/src/toolbars/hooks/Export.js +47 -0
  162. package/src/toolbars/hooks/File.js +54 -0
  163. package/src/toolbars/hooks/Formula.js +36 -0
  164. package/src/toolbars/hooks/FullScreen.js +55 -0
  165. package/src/toolbars/hooks/Graph.js +281 -0
  166. package/src/toolbars/hooks/H1.js +71 -0
  167. package/src/toolbars/hooks/H2.js +71 -0
  168. package/src/toolbars/hooks/H3.js +71 -0
  169. package/src/toolbars/hooks/Header.js +100 -0
  170. package/src/toolbars/hooks/Hr.js +35 -0
  171. package/src/toolbars/hooks/Iframe.js +35 -0
  172. package/src/toolbars/hooks/Image.js +60 -0
  173. package/src/toolbars/hooks/Insert.js +36 -0
  174. package/src/toolbars/hooks/Italic.js +70 -0
  175. package/src/toolbars/hooks/LineTable.js +41 -0
  176. package/src/toolbars/hooks/Link.js +46 -0
  177. package/src/toolbars/hooks/List.js +55 -0
  178. package/src/toolbars/hooks/Ol.js +41 -0
  179. package/src/toolbars/hooks/Panel.js +155 -0
  180. package/src/toolbars/hooks/Quote.js +45 -0
  181. package/src/toolbars/hooks/Redo.js +33 -0
  182. package/src/toolbars/hooks/Ruby.js +59 -0
  183. package/src/toolbars/hooks/Size.js +100 -0
  184. package/src/toolbars/hooks/Split.js +37 -0
  185. package/src/toolbars/hooks/Strikethrough.js +65 -0
  186. package/src/toolbars/hooks/Sub.js +58 -0
  187. package/src/toolbars/hooks/Sup.js +58 -0
  188. package/src/toolbars/hooks/SwitchModel.js +78 -0
  189. package/src/toolbars/hooks/Table.js +56 -0
  190. package/src/toolbars/hooks/Toc.js +35 -0
  191. package/src/toolbars/hooks/TogglePreview.js +79 -0
  192. package/src/toolbars/hooks/Ul.js +41 -0
  193. package/src/toolbars/hooks/Underline.js +65 -0
  194. package/src/toolbars/hooks/Undo.js +30 -0
  195. package/src/toolbars/hooks/Video.js +53 -0
  196. package/src/utils/LazyLoadImg.js +341 -0
  197. package/src/utils/autoindent.js +58 -0
  198. package/src/utils/codeBlockContentHandler.js +351 -0
  199. package/src/utils/config.js +98 -0
  200. package/src/utils/copy.js +55 -0
  201. package/src/utils/dialog.js +196 -0
  202. package/src/utils/dom.js +162 -0
  203. package/src/utils/downloadUtil.js +23 -0
  204. package/src/utils/env.js +22 -0
  205. package/src/utils/error.js +61 -0
  206. package/src/utils/event.js +38 -0
  207. package/src/utils/export.js +115 -0
  208. package/src/utils/file.js +121 -0
  209. package/src/utils/formulaUtilsHandler.js +230 -0
  210. package/src/utils/htmlparser.js +977 -0
  211. package/src/utils/image.js +99 -0
  212. package/src/utils/imgSizeHandler.js +279 -0
  213. package/src/utils/jsonUtils.js +17 -0
  214. package/src/utils/lineFeed.js +49 -0
  215. package/src/utils/listContentHandler.js +227 -0
  216. package/src/utils/lookbehind-replace.js +81 -0
  217. package/src/utils/mathjax.js +89 -0
  218. package/src/utils/myersDiff.js +211 -0
  219. package/src/utils/pasteHelper.js +253 -0
  220. package/src/utils/recount-pos.js +59 -0
  221. package/src/utils/regexp.js +295 -0
  222. package/src/utils/sanitize.js +477 -0
  223. package/src/utils/selection.js +50 -0
  224. package/src/utils/svgUtils.js +96 -0
  225. package/src/utils/tableContentHandler.js +592 -0
  226. package/tools/README.md +3 -0
package/src/Cherry.js ADDED
@@ -0,0 +1,788 @@
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 Toolbar from './toolbars/Toolbar';
21
+ import { createElement } from './utils/dom';
22
+ import { customizer, getThemeFromLocal } from './utils/config';
23
+ import NestedError, { $expectTarget } from './utils/error';
24
+ import getPosBydiffs from './utils/recount-pos';
25
+ import defaultConfig from './Cherry.config';
26
+ import cloneDeep from 'lodash/cloneDeep';
27
+ import Event from './Event';
28
+ import locales from './locales/index';
29
+
30
+ import { urlProcessorProxy } from './UrlCache';
31
+ import { CherryStatic } from './CherryStatic';
32
+ import { LIST_CONTENT } from './utils/regexp';
33
+ import { Theme } from './Theme';
34
+ import Bubble from './toolbars/Bubble';
35
+ import Toc from '@/toolbars/Toc';
36
+ import Stats from './Stats';
37
+
38
+ export default class Cherry extends CherryStatic {
39
+ /**
40
+ * @protected
41
+ */
42
+ static initialized = false;
43
+ /**
44
+ * @readonly
45
+ */
46
+ static config = {
47
+ defaults: defaultConfig,
48
+ };
49
+
50
+ constructor(options) {
51
+ super();
52
+ Cherry.initialized = true;
53
+ const defaultConfigCopy = cloneDeep(Cherry.config.defaults);
54
+ this.defaultToolbar = defaultConfigCopy.toolbars.toolbar;
55
+ $expectTarget(options, Object);
56
+
57
+ this.options = mergeWith({}, defaultConfigCopy, options, customizer);
58
+
59
+ // loading the locale
60
+ this.locale = locales[this.options.locale];
61
+
62
+ if (typeof this.options.engine.global.urlProcessor === 'function') {
63
+ this.options.engine.global.urlProcessor = urlProcessorProxy(this.options.engine.global.urlProcessor);
64
+ }
65
+
66
+ this.status = {
67
+ toolbar: 'show',
68
+ previewer: 'show',
69
+ editor: 'show',
70
+ };
71
+
72
+ if (this.options.isPreviewOnly || this.options.editor.defaultModel === 'previewOnly') {
73
+ this.options.toolbars.showToolbar = false;
74
+ this.options.editor.defaultModel = 'previewOnly';
75
+ // 在纯预览模式下不应触发编辑器聚焦导致的页面滚动
76
+ this.options.editor.codemirror = this.options.editor.codemirror || {};
77
+ this.options.editor.codemirror.autofocus = false;
78
+ this.status.editor = 'hide';
79
+ this.status.toolbar = 'hide';
80
+ }
81
+
82
+ /**
83
+ * @property
84
+ * @type {string} 实例ID
85
+ */
86
+ this.instanceId = `cherry-${new Date().getTime()}${Math.random()}`;
87
+ this.options.instanceId = this.instanceId;
88
+
89
+ this.engine = new Engine(this.options, this);
90
+ this.init();
91
+ }
92
+
93
+ /**
94
+ * 初始化工具栏、编辑区、预览区等
95
+ * @private
96
+ */
97
+ init() {
98
+ let mountEl = this.options.id ? document.getElementById(this.options.id) : this.options.el;
99
+
100
+ if (!mountEl) {
101
+ if (!this.options.forceAppend) {
102
+ return false;
103
+ }
104
+ this.noMountEl = true;
105
+ mountEl = document.createElement('div');
106
+ mountEl.id = this.options.id || 'cherry-markdown';
107
+ document.body.appendChild(mountEl);
108
+ }
109
+
110
+ if (!mountEl.style.height) {
111
+ mountEl.style.height = this.options.editor.height;
112
+ }
113
+ this.cherryDom = mountEl;
114
+
115
+ // 蒙层dom,用来拖拽编辑区&预览区宽度时展示蒙层
116
+ const wrapperDom = this.createWrapper();
117
+ // 创建编辑区
118
+ const editor = this.createEditor();
119
+
120
+ // 创建预览区
121
+ const previewer = this.createPreviewer();
122
+
123
+ if (this.options.toolbars.showToolbar === false || this.options.toolbars.toolbar === false) {
124
+ // 即便配置了不展示工具栏,也要让工具栏加载对应的语法hook
125
+ wrapperDom.classList.add('cherry--no-toolbar');
126
+ this.options.toolbars.toolbar = this.options.toolbars.toolbar
127
+ ? this.options.toolbars.toolbar
128
+ : this.defaultToolbar;
129
+ }
130
+ $expectTarget(this.options.toolbars.toolbar, Array);
131
+ // 创建顶部工具栏
132
+ this.createToolbar();
133
+ this.createToolbarRight();
134
+
135
+ const wrapperFragment = document.createDocumentFragment();
136
+ wrapperFragment.appendChild(this.toolbar.options.dom);
137
+ wrapperFragment.appendChild(editor.options.editorDom);
138
+
139
+ if (!this.options.previewer.dom) {
140
+ wrapperFragment.appendChild(previewer.options.previewerDom);
141
+ }
142
+ wrapperFragment.appendChild(previewer.options.virtualDragLineDom);
143
+ wrapperFragment.appendChild(previewer.options.editorMaskDom);
144
+ wrapperFragment.appendChild(previewer.options.previewerMaskDom);
145
+
146
+ wrapperDom.appendChild(wrapperFragment);
147
+ mountEl.appendChild(wrapperDom);
148
+
149
+ editor.init(previewer);
150
+
151
+ // 初始化统计模块
152
+ if (this.options.editor.showStats !== false) {
153
+ this.stats = new Stats({
154
+ $cherry: this,
155
+ });
156
+ }
157
+
158
+ this.createBubble();
159
+
160
+ previewer.init(editor);
161
+
162
+ previewer.registerAfterUpdate(this.engine.mounted.bind(this.engine));
163
+
164
+ // default value init
165
+ this.initText(editor.editor);
166
+
167
+ Event.on(this.instanceId, Event.Events.toolbarHide, () => {
168
+ this.status.toolbar = 'hide';
169
+ });
170
+ Event.on(this.instanceId, Event.Events.toolbarShow, () => {
171
+ this.status.toolbar = 'show';
172
+ });
173
+ Event.on(this.instanceId, Event.Events.previewerClose, () => {
174
+ this.status.previewer = 'hide';
175
+ });
176
+ Event.on(this.instanceId, Event.Events.previewerOpen, () => {
177
+ this.status.previewer = 'show';
178
+ });
179
+ Event.on(this.instanceId, Event.Events.editorClose, () => {
180
+ this.status.editor = 'hide';
181
+ // 关闭编辑区时,需要清除所有高亮
182
+ this.previewer.highlightLine(0);
183
+ });
184
+ Event.on(this.instanceId, Event.Events.editorOpen, () => {
185
+ this.status.editor = 'show';
186
+ });
187
+
188
+ // 切换模式,有纯预览模式、纯编辑模式、双栏编辑模式
189
+ this.switchModel(this.options.editor.defaultModel, this.options.toolbars.showToolbar);
190
+
191
+ // 如果配置了初始化后根据hash自动滚动
192
+ if (this.options.autoScrollByHashAfterInit) {
193
+ setTimeout(this.scrollByHash.bind(this));
194
+ }
195
+ // 强制进行一次渲染
196
+ Theme.init(this);
197
+ //
198
+ this.createToc();
199
+ }
200
+
201
+ /**
202
+ * @private
203
+ * @returns
204
+ */
205
+ createBubble() {
206
+ if (this.options.toolbars.bubble) {
207
+ const dom = createElement('div', 'cherry-bubble');
208
+ $expectTarget(this.options.toolbars.bubble, Array);
209
+ this.bubble = new Bubble({
210
+ dom,
211
+ $cherry: this,
212
+ buttonConfig: this.options.toolbars.bubble,
213
+ customMenu: this.options.toolbars.customMenu,
214
+ engine: this.engine,
215
+ });
216
+ }
217
+ }
218
+ createToc() {
219
+ if (this.options.toolbars.toc === false) {
220
+ this.toc = false;
221
+ return;
222
+ }
223
+ this.toc = new Toc({
224
+ $cherry: this,
225
+ // @ts-ignore
226
+ updateLocationHash: this.options.toolbars.toc.updateLocationHash ?? true,
227
+ // @ts-ignore
228
+ position: this.options.toolbars.toc.position ?? 'absolute',
229
+ // @ts-ignore
230
+ cssText: this.options.toolbars.toc.cssText ?? '',
231
+ // @ts-ignore
232
+ defaultModel: this.options.toolbars.toc.defaultModel ?? 'pure',
233
+ // @ts-ignore
234
+ showAutoNumber: this.options.toolbars.toc.showAutoNumber ?? false,
235
+ });
236
+ }
237
+
238
+ /**
239
+ * 滚动到hash位置,实际上就是通过修改location.hash来触发hashChange事件,剩下的就交给浏览器了
240
+ */
241
+ scrollByHash() {
242
+ if (location.hash) {
243
+ try {
244
+ const { hash } = location;
245
+ // 检查是否有对应id的元素
246
+ const testDom = document.getElementById(hash.replace('#', ''));
247
+ if (testDom && this.previewer.getDomContainer().contains(testDom)) {
248
+ location.hash = '';
249
+ location.hash = hash;
250
+ }
251
+ } catch (error) {
252
+ // empty
253
+ }
254
+ }
255
+ }
256
+
257
+ $t(str) {
258
+ return this.locale[str] ? this.locale[str] : str;
259
+ }
260
+
261
+ addLocale(key, value) {
262
+ this.locale[key] = value;
263
+ }
264
+
265
+ addLocales(locales) {
266
+ this.locale = Object.assign(this.locale, locales);
267
+ }
268
+
269
+ getLocales() {
270
+ return this.locale;
271
+ }
272
+ /**
273
+ * 切换编辑模式
274
+ * @param {'edit&preview'|'editOnly'|'previewOnly'} [model=edit&preview] 模式类型
275
+ * 一般纯预览模式和纯编辑模式适合在屏幕较小的终端使用,比如手机移动端
276
+ */
277
+ switchModel(model = 'edit&preview', showToolbar = true) {
278
+ this.model = model;
279
+ switch (model) {
280
+ case 'edit&preview':
281
+ if (this.previewer) {
282
+ this.previewer.editOnly(true);
283
+ this.previewer.recoverPreviewer();
284
+ }
285
+ if (this.toolbar && showToolbar) {
286
+ this.toolbar.showToolbar();
287
+ }
288
+ break;
289
+ case 'editOnly':
290
+ if (!this.previewer.isPreviewerHidden()) {
291
+ this.previewer.editOnly(true);
292
+ }
293
+ if (this.toolbar && showToolbar) {
294
+ this.toolbar.showToolbar();
295
+ }
296
+ break;
297
+ case 'previewOnly':
298
+ this.previewer.previewOnly();
299
+ this.toolbar && this.toolbar.previewOnly();
300
+ break;
301
+ }
302
+ if (showToolbar) {
303
+ this.wrapperDom.classList.remove('cherry--no-toolbar');
304
+ } else {
305
+ this.wrapperDom.classList.add('cherry--no-toolbar');
306
+ }
307
+
308
+ Event.emit(this.instanceId, Event.Events.modelChange, model);
309
+ }
310
+
311
+ /**
312
+ * 获取实例id
313
+ * @returns {string}
314
+ * @public
315
+ */
316
+ getInstanceId() {
317
+ return this.instanceId;
318
+ }
319
+
320
+ /**
321
+ * 获取编辑器状态
322
+ * @returns {Object}
323
+ */
324
+ getStatus() {
325
+ return this.status;
326
+ }
327
+
328
+ /**
329
+ * 获取编辑区内的markdown源码内容
330
+ * @returns string
331
+ */
332
+ getValue() {
333
+ try {
334
+ return this.editor.editor.getValue();
335
+ } catch (e) {
336
+ return '';
337
+ }
338
+ }
339
+
340
+ /**
341
+ * 获取编辑区内的markdown源码内容
342
+ * @returns {string} markdown源码内容
343
+ */
344
+ getMarkdown() {
345
+ return this.getValue();
346
+ }
347
+
348
+ /**
349
+ * 获取CodeMirror 实例
350
+ * @returns { CodeMirror.Editor } CodeMirror实例
351
+ */
352
+ getCodeMirror() {
353
+ return this.editor.editor;
354
+ }
355
+
356
+ /**
357
+ * 获取预览区内的html内容
358
+ * @param {boolean} [wrapTheme=true] 是否在外层包裹主题class
359
+ * @returns {string} html内容
360
+ */
361
+ getHtml(wrapTheme = true) {
362
+ return this.previewer.getValue(wrapTheme);
363
+ }
364
+ /**
365
+ * 获取Previewer 预览实例
366
+ * @returns {Previewer} Previewer 预览实例
367
+ */
368
+ getPreviewer() {
369
+ return this.previewer;
370
+ }
371
+
372
+ /**
373
+ * @typedef {{
374
+ * level: number;
375
+ * id: string;
376
+ * text: string;
377
+ * }[]} HeaderList
378
+ * 获取目录,目录由head1~6组成
379
+ * @returns {HeaderList} 标题head数组
380
+ */
381
+ getToc() {
382
+ const str = this.getHtml();
383
+ /** @type {({level: number;id: string;text: string})[]} */
384
+ const headerList = [];
385
+ const headerRegex = /<h([1-6]).*?id="([^"]+?)".*?>(.+?)<\/h[0-6]>/g;
386
+ str.replace(headerRegex, (match, level, id, text) => {
387
+ headerList.push({ level: +level, id, text: text.replace(/<a .+?<\/a>/, '') });
388
+ return match;
389
+ });
390
+ return headerList;
391
+ }
392
+
393
+ /**
394
+ * 覆盖编辑区的内容
395
+ * @param {string} content markdown内容
396
+ * @param {boolean} keepCursor 是否保持光标位置
397
+ */
398
+ setValue(content, keepCursor = false) {
399
+ this.editor.storeDocumentScroll();
400
+ if (keepCursor === false) {
401
+ return this.editor.editor.setValue(content);
402
+ }
403
+ const codemirror = this.editor.editor;
404
+ const old = this.getValue();
405
+ const pos = codemirror.getDoc().indexFromPos(codemirror.getCursor());
406
+ const newPos = getPosBydiffs(pos, old, content);
407
+ codemirror.setValue(content);
408
+ const cursor = codemirror.getDoc().posFromIndex(newPos);
409
+ codemirror.setCursor(cursor);
410
+ this.editor.dealSpecialWords();
411
+ }
412
+
413
+ /**
414
+ * 在光标处或者指定行+偏移量插入内容
415
+ * @param {string} content 被插入的文本
416
+ * @param {boolean} [isSelect=false] 是否选中刚插入的内容
417
+ * @param {[number, number]|false} [anchor=false] [x,y] 代表x+1行,y+1字符偏移量,默认false 会从光标处插入
418
+ * @param {boolean} [focus=true] 保持编辑器处于focus状态
419
+ */
420
+ insert(content, isSelect = false, anchor = false, focus = true) {
421
+ if (anchor) {
422
+ this.editor.editor.setSelection({ line: anchor[0], ch: anchor[1] }, { line: anchor[0], ch: anchor[1] });
423
+ }
424
+ this.editor.editor.replaceSelection(content, isSelect ? 'around' : 'end');
425
+ focus && this.editor.editor.focus();
426
+ }
427
+
428
+ /**
429
+ * 在光标处或者指定行+偏移量插入内容
430
+ * @param {string} content 被插入的文本
431
+ * @param {boolean} [isSelect=false] 是否选中刚插入的内容
432
+ * @param {[number, number]|false} [anchor=false] [x,y] 代表x+1行,y+1字符偏移量,默认false 会从光标处插入
433
+ * @param {boolean} [focus=true] 保持编辑器处于focus状态
434
+ * @returns
435
+ */
436
+ insertValue(content, isSelect = false, anchor = false, focus = true) {
437
+ return this.insert(content, isSelect, anchor, focus);
438
+ }
439
+
440
+ /**
441
+ * 强制重新渲染预览区域
442
+ */
443
+ refreshPreviewer() {
444
+ try {
445
+ const markdownText = this.getValue();
446
+ const html = this.engine.makeHtml(markdownText);
447
+ this.previewer.refresh(html);
448
+ } catch (e) {
449
+ throw new NestedError(e);
450
+ }
451
+ }
452
+
453
+ /**
454
+ * 覆盖编辑区的内容
455
+ * @param {string} content markdown内容
456
+ * @param {boolean} [keepCursor=false] 是否保持光标位置
457
+ */
458
+ setMarkdown(content, keepCursor = false) {
459
+ return this.setValue(content, keepCursor);
460
+ }
461
+
462
+ /**
463
+ * @private
464
+ * @returns
465
+ */
466
+ createWrapper() {
467
+ const wrapperDom = createElement('div', ['cherry', 'clearfix', Theme.getTheme()].join(' '), {
468
+ 'data-toolbarTheme': '',
469
+ 'data-inlineCodeTheme': Theme.getTheme(),
470
+ 'data-codeBlockTheme': Theme.getTheme(),
471
+ });
472
+ this.wrapperDom = wrapperDom;
473
+ return wrapperDom;
474
+ }
475
+
476
+ /**
477
+ * @private
478
+ * @returns {Toolbar}
479
+ */
480
+ createToolbar() {
481
+ if (!this.toolbarContainer) {
482
+ const dom = createElement('div', 'cherry-toolbar');
483
+ this.toolbarContainer = dom;
484
+ }
485
+ this.toolbar = new Toolbar({
486
+ dom: this.toolbarContainer,
487
+ $cherry: this,
488
+ buttonConfig: this.options.toolbars.toolbar,
489
+ customMenu: this.options.toolbars.customMenu,
490
+ shortcutKey: this.options.toolbars.shortcutKey,
491
+ });
492
+ return this.toolbar;
493
+ }
494
+
495
+ createToolbarRight() {
496
+ if (!this.toolbarContainer) {
497
+ return null;
498
+ }
499
+ const config = this.options.toolbars.toolbarRight;
500
+ const toolbarRightDom = this.toolbarContainer.querySelector('.toolbar-right');
501
+ if (toolbarRightDom) {
502
+ toolbarRightDom.remove();
503
+ }
504
+ if (config === false || config === null || typeof config === 'undefined') {
505
+ this.toolbarRight = null;
506
+ return null;
507
+ }
508
+ $expectTarget(config, Array);
509
+ if (config.length === 0) {
510
+ this.toolbarRight = null;
511
+ return null;
512
+ }
513
+ this.toolbarRight = new Toolbar({
514
+ dom: this.toolbarContainer,
515
+ position: 'right',
516
+ $cherry: this,
517
+ buttonConfig: config,
518
+ customMenu: this.options.toolbars.customMenu,
519
+ shortcutKey: this.options.toolbars.shortcutKey,
520
+ });
521
+ if (this.toolbar) {
522
+ this.toolbar.collectMenuInfo(this.toolbarRight);
523
+ }
524
+ return this.toolbarRight;
525
+ }
526
+
527
+ /**
528
+ * 动态重置工具栏配置
529
+ * @public
530
+ * @param {'toolbar'|'sidebar'|'bubble'|'float'} [type] 修改工具栏的类型
531
+ * @param {Array} [toolbar] 要重置的对应工具栏配置
532
+ * @returns {Boolean}
533
+ */
534
+ resetToolbar(type, toolbar) {
535
+ const $type = /(toolbar|toolbarRight|sidebar|bubble|float|toc)/.test(type) ? type : false;
536
+ if ($type === false) {
537
+ return false;
538
+ }
539
+ if (this.toolbarContainer) {
540
+ this.toolbarContainer.innerHTML = '';
541
+ }
542
+
543
+ if (this.toc) {
544
+ // @ts-ignore
545
+ this.toc.tocDom.remove();
546
+ }
547
+ this.cherryDom.querySelectorAll('.cherry-dropdown').forEach((item) => {
548
+ item.remove();
549
+ });
550
+ this.options.toolbars[type] = toolbar;
551
+ this.createToolbar();
552
+ this.createToolbarRight();
553
+ this.createBubble();
554
+ // this.createHiddenToolbar();
555
+ this.createToc();
556
+ return true;
557
+ }
558
+
559
+ /**
560
+ * @private
561
+ * @returns {import('@/Editor').default}
562
+ */
563
+ createEditor() {
564
+ const textArea = createElement('textarea', '', {
565
+ id: this.options.editor.id ?? 'code',
566
+ name: this.options.editor.name ?? 'code',
567
+ });
568
+ textArea.textContent = this.options.value;
569
+ const editor = createElement('div', 'cherry-editor');
570
+ editor.appendChild(textArea);
571
+
572
+ this.editor = new Editor({
573
+ $cherry: this,
574
+ editorDom: editor,
575
+ wrapperDom: this.wrapperDom,
576
+ value: this.options.value,
577
+ onKeydown: this.fireShortcutKey.bind(this),
578
+ onChange: this.editText.bind(this),
579
+ toolbars: this.options.toolbars,
580
+ fileUpload: this.options.fileUpload,
581
+ autoScrollByCursor: this.options.autoScrollByCursor,
582
+ ...this.options.editor,
583
+ });
584
+
585
+ return this.editor;
586
+ }
587
+
588
+ /**
589
+ * @private
590
+ * @returns {import('@/Previewer').default}
591
+ */
592
+ createPreviewer() {
593
+ /** @type {HTMLDivElement} */
594
+ let previewer;
595
+ const anchorStyle =
596
+ (this.options.engine.syntax.header && this.options.engine.syntax.header.anchorStyle) || 'default';
597
+ const autonumberClass = anchorStyle === 'autonumber' ? ' head-num' : '';
598
+ const { className, dom, enablePreviewerBubble } = this.options.previewer;
599
+ const previewerClassName = [
600
+ 'cherry-previewer',
601
+ 'cherry-markdown',
602
+ className || '',
603
+ autonumberClass,
604
+ getThemeFromLocal(true),
605
+ ].join(' ');
606
+ if (dom) {
607
+ previewer = dom;
608
+ previewer.className += ` ${previewerClassName} `;
609
+ } else {
610
+ previewer = createElement('div', previewerClassName);
611
+ }
612
+ const virtualDragLine = createElement('div', 'cherry-drag');
613
+ const editorMask = createElement('div', 'cherry-editor-mask');
614
+ const previewerMask = createElement('div', 'cherry-previewer-mask');
615
+
616
+ this.previewer = new Previewer({
617
+ $cherry: this,
618
+ virtualDragLineDom: virtualDragLine,
619
+ editorMaskDom: editorMask,
620
+ previewerMaskDom: previewerMask,
621
+ previewerDom: previewer,
622
+ value: this.options.value,
623
+ isPreviewOnly: this.options.isPreviewOnly,
624
+ enablePreviewerBubble,
625
+ lazyLoadImg: this.options.previewer.lazyLoadImg,
626
+ });
627
+
628
+ return this.previewer;
629
+ }
630
+
631
+ /**
632
+ * @private
633
+ * @param {import('codemirror').Editor} codemirror
634
+ */
635
+ initText(codemirror) {
636
+ try {
637
+ const markdownText = codemirror.getValue();
638
+ const html = this.engine.makeHtml(markdownText);
639
+ this.previewer.update(html);
640
+ if (this.options.callback.afterInit) {
641
+ this.options.callback.afterInit(markdownText, html);
642
+ }
643
+ } catch (e) {
644
+ throw new NestedError(e);
645
+ }
646
+ }
647
+
648
+ /**
649
+ * @private
650
+ * @param {Event} _evt
651
+ * @param {import('codemirror').Editor} codemirror
652
+ */
653
+ editText(_evt, codemirror) {
654
+ const that = this;
655
+ try {
656
+ if (this.timer) {
657
+ clearTimeout(this.timer);
658
+ this.timer = null;
659
+ }
660
+ const interval = this.options.engine.global.flowSessionContext ? 10 : 50;
661
+ this.timer = setTimeout(() => {
662
+ const markdownText = codemirror.getValue();
663
+
664
+ // 更新统计信息
665
+ if (this.stats) {
666
+ this.stats.update(markdownText);
667
+ }
668
+
669
+ if (markdownText !== this.lastMarkdownText) {
670
+ this.lastMarkdownText = markdownText;
671
+ const html = this.engine.makeHtml(markdownText);
672
+ this.previewer.update(html);
673
+ Event.emit('editor', 'afterChange', {
674
+ markdownText,
675
+ html,
676
+ });
677
+ }
678
+
679
+ Event.emit('editor', 'change', that);
680
+ // 仅在编辑器可见时才触发编辑器滚动与文档滚动恢复,避免影响纯预览页面滚动
681
+ if (this.status.editor === 'show') {
682
+ codemirror.scrollIntoView(null);
683
+ this.editor.restoreDocumentScroll();
684
+ }
685
+ }, interval);
686
+ } catch (e) {
687
+ throw new NestedError(e);
688
+ }
689
+ }
690
+
691
+ /**
692
+ * @private
693
+ * @param {any} cb
694
+ */
695
+ onChange(cb) {
696
+ this.editor.editor.on('change', (codeMirror) => {
697
+ cb({
698
+ markdown: codeMirror.getValue(), // 后续可以按需增加html或其他状态
699
+ });
700
+ });
701
+ }
702
+
703
+ /**
704
+ * @private
705
+ * @param {KeyboardEvent} evt
706
+ */
707
+ fireShortcutKey(evt) {
708
+ const cursor = this.editor.editor.getCursor();
709
+ const lineContent = this.editor.editor.getLine(cursor.line);
710
+ // shift + tab 已经被绑定为缩进,所以这里不做处理
711
+ if (!evt.shiftKey && evt.key === 'Tab' && LIST_CONTENT.test(lineContent)) {
712
+ // 每按一次Tab,如果当前光标在行首或者行尾,就在行首加一个\t
713
+ if (cursor.ch === 0 || cursor.ch === lineContent.length || cursor.ch === lineContent.length + 1) {
714
+ evt.preventDefault();
715
+ this.editor.editor.setSelection({ line: cursor.line, ch: 0 }, { line: cursor.line, ch: lineContent.length });
716
+ this.editor.editor.replaceSelection(`\t${lineContent}`, 'around');
717
+ const newCursor = this.editor.editor.getCursor();
718
+ this.editor.editor.setSelection(newCursor, newCursor);
719
+ }
720
+ }
721
+ if (this.toolbar.matchShortcutKey(evt)) {
722
+ // 快捷键
723
+ evt.preventDefault();
724
+ this.toolbar.fireShortcutKey(evt);
725
+ }
726
+ }
727
+
728
+ /**
729
+ * 导出预览区域内容
730
+ * @public
731
+ * @param {'pdf' | 'markdown' | 'html'} [type='pdf']
732
+ * 'pdf':导出成pdf文件; 'img':导出成png图片; 'markdown':导出成markdown文件; 'html':导出成html文件;
733
+ * @param {string} [fileName] 导出文件名(默认为当前第一行内容|'cherry-export')
734
+ */
735
+ export(type = 'pdf', fileName = '') {
736
+ this.previewer.export(type, fileName);
737
+ }
738
+
739
+ /**
740
+ * 修改主题
741
+ * @param {string} theme option.theme里的className
742
+ */
743
+ setTheme(theme = 'light') {
744
+ Theme.setTheme(this, theme);
745
+ }
746
+
747
+ /**
748
+ * 修改书写风格
749
+ * @param {string} writingStyle normal 普通 | typewriter 打字机 | focus 专注
750
+ */
751
+ setWritingStyle(writingStyle) {
752
+ this.editor.setWritingStyle(writingStyle);
753
+ }
754
+
755
+ /**
756
+ * 切换TOC的模式(极简 or 展开)
757
+ * @param {'full'|'pure'|''} focusModel 是否强制切换模式,如果为空,则根据当前模式切换
758
+ */
759
+ toggleToc(focusModel = '') {
760
+ if (!this.toc) {
761
+ return;
762
+ }
763
+ let targetModel = 'full';
764
+ if (focusModel === '') {
765
+ // @ts-ignore
766
+ const { model } = this.toc;
767
+ targetModel = model === 'full' ? 'pure' : 'full';
768
+ } else {
769
+ targetModel = focusModel;
770
+ }
771
+ // @ts-ignore
772
+ this.toc.$switchModel(targetModel);
773
+ // @ts-ignore
774
+ this.toc.setModelToLocalStorage(targetModel);
775
+ }
776
+
777
+ /**
778
+ * 清空流程会话中添加的虚拟光标
779
+ */
780
+ clearFlowSessionCursor() {
781
+ if (this.options.engine.global.flowSessionCursor) {
782
+ this.previewer.getDom().innerHTML = this.previewer
783
+ .getDom()
784
+ // @ts-ignore
785
+ .innerHTML.replaceAll(this.options.engine.global.flowSessionCursor, '');
786
+ }
787
+ }
788
+ }