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
@@ -0,0 +1,246 @@
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 { createElement } from '@/utils/dom';
17
+ import Event from '../Event';
18
+ import md5 from 'md5';
19
+ /**
20
+ * 悬浮目录
21
+ */
22
+ export default class Toc {
23
+ constructor(options) {
24
+ this.$cherry = options.$cherry;
25
+ this.editor = options.$cherry.editor.editor;
26
+ this.tocStr = '';
27
+ this.updateLocationHash = options.updateLocationHash ?? true;
28
+ this.defaultModel = options.defaultModel ?? 'full';
29
+ this.showAutoNumber = options.showAutoNumber ?? false;
30
+ this.position = options.position ?? 'absolute';
31
+ this.cssText = options.cssText ?? '';
32
+ this.init();
33
+ }
34
+
35
+ init() {
36
+ this.drawDom();
37
+ this.timer = setTimeout(() => {
38
+ this.updateTocList();
39
+ }, 300);
40
+ Event.on('editor', 'change', (editor) => {
41
+ clearTimeout(this.timer);
42
+ this.timer = setTimeout(() => {
43
+ this.updateTocList();
44
+ this.$switchModel(this.model);
45
+ }, 300);
46
+ });
47
+ this.$switchModel(this.getModelFromLocalStorage());
48
+ }
49
+
50
+ getModelFromLocalStorage() {
51
+ if (typeof localStorage === 'undefined') {
52
+ return this.defaultModel;
53
+ }
54
+ return localStorage.getItem('cherry-toc-model') || this.defaultModel;
55
+ }
56
+
57
+ setModelToLocalStorage(model) {
58
+ if (typeof localStorage === 'undefined') {
59
+ return;
60
+ }
61
+ localStorage.setItem('cherry-toc-model', model);
62
+ }
63
+
64
+ drawDom() {
65
+ const tocDom = createElement(
66
+ 'div',
67
+ `cherry-flex-toc cherry-flex-toc__pure${this.showAutoNumber ? ' auto-num' : ''}`,
68
+ );
69
+ if (this.position === 'fixed') {
70
+ tocDom.classList.add('cherry-flex-toc__fixed');
71
+ }
72
+
73
+ if (this.cssText.length > 0) {
74
+ tocDom.style.cssText = this.cssText;
75
+ }
76
+
77
+ const tocHead = createElement('div', 'cherry-toc-head');
78
+ const tocTitle = createElement('span', 'cherry-toc-title');
79
+ tocTitle.append(this.$cherry.locale.toc);
80
+ const tocClose = createElement('span', 'material-symbols-outlined ch-icon ch-icon-chevronsRight ');
81
+ tocClose.append('keyboard_double_arrow_right');
82
+ const tocOpen = createElement('span', 'material-symbols-outlined ch-icon ch-icon-chevronsLeft ');
83
+ tocOpen.append('keyboard_double_arrow_left');
84
+ this.tocClose = tocClose;
85
+ this.tocOpen = tocOpen;
86
+ tocHead.appendChild(tocTitle);
87
+ tocHead.appendChild(tocClose);
88
+ tocHead.appendChild(tocOpen);
89
+ tocDom.appendChild(tocHead);
90
+ const tocListDom = createElement('div', 'cherry-toc-list');
91
+ this.tocListDom = tocListDom;
92
+ tocDom.appendChild(tocListDom);
93
+ this.tocDom = tocDom;
94
+ this.$cherry.wrapperDom.appendChild(tocDom);
95
+ this.bindClickEvent();
96
+ }
97
+
98
+ bindClickEvent() {
99
+ this.tocDom.addEventListener('click', (e) => {
100
+ const a = this.$getClosestNode(e.target, 'A');
101
+ if (a === false) {
102
+ return;
103
+ }
104
+ if (/cherry-toc-one-a/.test(a.className)) {
105
+ const { id, index } = a.dataset;
106
+ if (this.$cherry.status.previewer === 'hide') {
107
+ // editorOnly模式下,需要定位到编辑区对应位置
108
+ const searcher = this.$cherry.editor.editor.getSearchCursor(
109
+ /(?:^|\n)\n*((?:[ \t\u00a0]*#{1,6}).+?|(?:[ \t\u00a0]*.+)\n(?:[ \t\u00a0]*[=]+|[-]+))(?=$|\n)/g,
110
+ );
111
+ for (let i = 0; i <= index; i++) {
112
+ searcher.findNext();
113
+ }
114
+ const target = searcher.from();
115
+ this.$cherry.editor.scrollToLineNum(target.line, target.line + 1, 0);
116
+ } else {
117
+ // 有预览的情况下,直接通过滚动预览区位置实现滚动到锚点
118
+ this.$cherry.previewer.scrollToHeadByIndex(index);
119
+ }
120
+ if (this.updateLocationHash) {
121
+ location.href = id;
122
+ }
123
+ }
124
+ });
125
+ this.tocClose.addEventListener('click', (e) => {
126
+ this.$switchModel('pure');
127
+ this.setModelToLocalStorage('pure');
128
+ });
129
+ this.tocOpen.addEventListener('click', (e) => {
130
+ this.$switchModel('full');
131
+ this.setModelToLocalStorage('full');
132
+ });
133
+ if (window) {
134
+ window.addEventListener('resize', () => {
135
+ this.$switchModel(this.model);
136
+ });
137
+ }
138
+ this.editor.on('scroll', (codemirror, evt) => {
139
+ this.updateTocList(true);
140
+ });
141
+ const scrollDom = this.$cherry.previewer.getDomCanScroll();
142
+ if (scrollDom.nodeName === 'HTML') {
143
+ window.addEventListener('scroll', () => {
144
+ this.updateTocList(true);
145
+ });
146
+ } else {
147
+ scrollDom.addEventListener('scroll', () => {
148
+ this.updateTocList(true);
149
+ });
150
+ }
151
+ }
152
+
153
+ $switchModel(model = 'pure') {
154
+ this.model = model;
155
+ const targetClassName = `cherry-flex-toc__${model}`;
156
+ if (!this.tocDom.classList.contains(targetClassName)) {
157
+ this.tocDom.classList.remove(`cherry-flex-toc__pure`);
158
+ this.tocDom.classList.remove(`cherry-flex-toc__full`);
159
+ this.tocDom.classList.add(targetClassName);
160
+ }
161
+ const list = this.tocListDom.querySelectorAll('.cherry-toc-one-a');
162
+ if (list.length > 0) {
163
+ let targetHeight = 28;
164
+ if (model === 'pure') {
165
+ const { height } = this.tocListDom.getBoundingClientRect();
166
+ const minHeight = Math.floor((height - list.length * 3) / list.length);
167
+ // eslint-disable-next-line no-nested-ternary
168
+ targetHeight = minHeight < 3 ? 3 : minHeight > 10 ? 10 : minHeight;
169
+ }
170
+ for (let i = 0; i < list.length; i++) {
171
+ // @ts-ignore
172
+ if (list[i].style.height !== `${targetHeight}px`) {
173
+ // @ts-ignore
174
+ list[i].style.height = `${targetHeight}px`;
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ $getClosestNode(node, targetNodeName) {
181
+ if (!node || !node.tagName) {
182
+ return false;
183
+ }
184
+ if (node.tagName === targetNodeName) {
185
+ return node;
186
+ }
187
+ if (node.parentNode.tagName === 'BODY') {
188
+ return false;
189
+ }
190
+ return this.$getClosestNode(node.parentNode, targetNodeName);
191
+ }
192
+
193
+ updateTocList(onlyScroll = false) {
194
+ if (onlyScroll === true) {
195
+ // do nothing
196
+ } else {
197
+ const tocList = this.$cherry.getToc();
198
+ let tocStr = '';
199
+ tocList.map((item) => {
200
+ tocStr += item.text;
201
+ return item;
202
+ });
203
+ tocStr = md5(tocStr);
204
+ if (this.tocStr !== tocStr) {
205
+ this.tocStr = tocStr;
206
+ let tocHtml = '';
207
+ let index = 0;
208
+ tocList.map((item) => {
209
+ const text = item.text.replace(/<a .+?<\/a>/g, '');
210
+ const title = text.replace(/<[^>]+?>/g, '');
211
+ tocHtml += `<a class="cherry-toc-one-a cherry-toc-one-a__${item.level > 5 ? 5 : item.level}"
212
+ title="${title}"
213
+ data-index="${index}"
214
+ data-id="#${item.id}"
215
+ >${text}</a>`;
216
+ index += 1;
217
+ return item;
218
+ });
219
+ this.tocListDom.innerHTML = tocHtml;
220
+ }
221
+ }
222
+ // 处理当前标题的高亮
223
+ if (this.$cherry.status.previewer === 'hide') {
224
+ // 似乎没有特别好的办法,先欠着
225
+ } else {
226
+ const scrollDom = this.$cherry.previewer.getDomCanScroll();
227
+ const minY = scrollDom.nodeName === 'HTML' ? 0 : scrollDom.getBoundingClientRect().y;
228
+ const headList = this.$cherry.previewer.getDomContainer().querySelectorAll('h1,h2,h3,h4,h5,h6');
229
+ let index = 0;
230
+ for (; index < headList.length; index++) {
231
+ const { y } = headList[index].getBoundingClientRect();
232
+ if (y > minY + 20) {
233
+ break;
234
+ }
235
+ }
236
+ index = index > 0 ? index - 1 : index;
237
+ this.tocListDom.querySelectorAll('.cherry-toc-one-a').forEach((item, key) => {
238
+ if (key === index) {
239
+ item.classList.add('current');
240
+ } else {
241
+ item.classList.remove('current');
242
+ }
243
+ });
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,401 @@
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 { mac } from 'codemirror/src/util/browser';
17
+ import HookCenter from './HookCenter';
18
+ import Event from '@/Event';
19
+ import { createElement } from '@/utils/dom';
20
+ import Logger from '@/Logger';
21
+
22
+ /**
23
+ * @typedef {()=>void} Bold 向cherry编辑器中插入粗体语法
24
+ * @typedef {()=>void} Italic 向cherry编辑器中插入斜体语法
25
+ * @typedef {(level:1|2|3|4|5|'1'|'2'|'3'|'4'|'5')=>void} Header 向cherry编辑器中插入标题语法
26
+ * - level 标题等级 1~5
27
+ * @typedef {()=>void} Strikethrough 向cherry编辑器中插入删除线语法
28
+ * @typedef {(type:'ol'|'ul'|'checklist'|1|2|3|'1'|'2'|'3')=>void} List 向cherry编辑器中插入有序、无序列表或者checklist语法
29
+ * - ol(1)有序
30
+ * - ul(2)无序列表
31
+ * - checklist(3)checklist
32
+ * @typedef {(insert:'hr'|'br'|'code'|'formula'|'checklist'|'toc'|'link'|'image'|'video'|'audio'|'normal-table'|'normal-table-row*col')=>void} Insert 向cherry编辑器中插入特定语法
33
+ * - hr 水平分割线
34
+ * - br 换行
35
+ * - code 代码块
36
+ * - formula 公式
37
+ * - checklist 检查项
38
+ * - toc 目录
39
+ * - link 链接
40
+ * - image 图片
41
+ * - video 视频
42
+ * - audio 音频
43
+ * - normal-table 插入3行5列的表格
44
+ * - normal-table-row*col 如normal-table-2*4插入2行(包含表头是3行)4列的表格
45
+ * @typedef {(type:'1'|'2'|'3'|'4'|'5'|'6'|1|2|3|4|5|6|'flow'|'sequence'|'state'|'class'|'pie'|'gantt')=>void} Graph 向cherry编辑器中插入画图语法
46
+ * - flow(1) 流程图
47
+ * - sequence(2) 时序图
48
+ * - state(3)状态图
49
+ * - class(4)类图
50
+ * - pie(5)饼图
51
+ * - gantt(6)甘特图
52
+ */
53
+
54
+ export default class Toolbar {
55
+ /**
56
+ * @typedef {{
57
+ * bold?:Bold;
58
+ * italic?:Italic;
59
+ * header?:Header;
60
+ * strikethrough?:Strikethrough;
61
+ * list?:List;
62
+ * insert?:Insert;
63
+ * graph?:Graph;
64
+ * [key:string]:any;
65
+ * }} ToolbarHandlers
66
+ * @type ToolbarHandlers 外部获取 toolbarHandlers 的部分功能
67
+ */
68
+ toolbarHandlers = {};
69
+
70
+ constructor(options) {
71
+ // 存储所有菜单的实例
72
+ this.menus = {};
73
+ // 存储所有快捷键的影射 {快捷键: 菜单名称}
74
+ this.shortcutKeyMap = {};
75
+ // 存储所有二级菜单面板
76
+ this.subMenus = {};
77
+ this.currentActiveSubMenu = null;
78
+ // 默认的菜单配置
79
+ this.options = {
80
+ dom: document.createElement('div'),
81
+ buttonConfig: ['bold'],
82
+ customMenu: [],
83
+ position: 'left',
84
+ };
85
+
86
+ Object.assign(this.options, options);
87
+ this.$cherry = this.options.$cherry;
88
+ this.instanceId = this.$cherry.instanceId;
89
+ this.menus = new HookCenter(this);
90
+ this.drawMenus();
91
+ this.collectShortcutKey();
92
+ this.collectToolbarHandler();
93
+ this.init();
94
+ }
95
+
96
+ init() {
97
+ Event.on(this.instanceId, Event.Events.cleanAllSubMenus, () => this.hideAllSubMenu());
98
+ }
99
+ previewOnly() {
100
+ this.options.dom.classList.add('preview-only');
101
+ this.$cherry.wrapperDom.classList.add('cherry--no-toolbar');
102
+ Event.emit(this.instanceId, Event.Events.toolbarHide);
103
+ }
104
+
105
+ showToolbar() {
106
+ this.options.dom.classList.remove('preview-only');
107
+ this.$cherry.wrapperDom.classList.remove('cherry--no-toolbar');
108
+ Event.emit(this.instanceId, Event.Events.toolbarShow);
109
+ }
110
+
111
+ isHasLevel2Menu(name) {
112
+ // FIXME: return boolean
113
+ return this.menus.level2MenusName[name];
114
+ }
115
+
116
+ isHasConfigMenu(name) {
117
+ // FIXME: return boolean
118
+ return this.menus.hooks[name].subMenuConfig || [];
119
+ }
120
+
121
+ /**
122
+ * 判断是否有子菜单,目前有两种子菜单配置方式:1、通过`subMenuConfig`属性 2、通过`buttonConfig`配置属性
123
+ * @param {string} name
124
+ * @returns {boolean} 是否有子菜单
125
+ */
126
+ isHasSubMenu(name) {
127
+ return Boolean(this.isHasLevel2Menu(name) || this.isHasConfigMenu(name).length > 0);
128
+ }
129
+
130
+ /**
131
+ * 根据配置画出来一级工具栏
132
+ */
133
+ drawMenus() {
134
+ const fragLeft = document.createDocumentFragment();
135
+
136
+ this.menus.level1MenusName.forEach((name) => {
137
+ const btn = this.menus.hooks[name].createBtn();
138
+ if (typeof window === 'object' && 'onpointerup' in window) {
139
+ // 只有先down再up的才触发click逻辑,避免误触(尤其是float menu的场景)
140
+ btn.addEventListener(
141
+ 'pointerdown',
142
+ () => {
143
+ this.isPointerDown = true;
144
+ },
145
+ false,
146
+ );
147
+ btn.addEventListener(
148
+ 'pointerup',
149
+ (event) => {
150
+ this.isPointerDown && this.onClick(event, name);
151
+ this.isPointerDown = false;
152
+ },
153
+ false,
154
+ );
155
+ } else {
156
+ // vscode 插件里不支持 pointer event
157
+ btn.addEventListener(
158
+ 'click',
159
+ (event) => {
160
+ this.onClick(event, name);
161
+ },
162
+ false,
163
+ );
164
+ }
165
+ if (this.isHasSubMenu(name)) {
166
+ btn.classList.add('cherry-toolbar-dropdown');
167
+ }
168
+ fragLeft.appendChild(btn);
169
+ });
170
+
171
+ this.appendMenusToDom(fragLeft);
172
+ }
173
+
174
+ appendMenusToDom(menus) {
175
+ const position = this.options.position === 'right' ? 'right' : 'left';
176
+ const className = `toolbar-${position}`;
177
+ let toolbarContainer = this.options.dom.querySelector(`.${className}`);
178
+ if (!toolbarContainer) {
179
+ toolbarContainer = createElement('div', className);
180
+ this.options.dom.appendChild(toolbarContainer);
181
+ }
182
+ toolbarContainer.appendChild(menus);
183
+ }
184
+
185
+ setSubMenuPosition(menuObj, subMenuObj) {
186
+ const pos = menuObj.getMenuPosition();
187
+ subMenuObj.style.left = `${pos.left + pos.width / 2}px`;
188
+ subMenuObj.style.top = `${pos.top + pos.height}px`;
189
+ subMenuObj.style.position = menuObj.positionModel;
190
+ }
191
+
192
+ drawSubMenus(name) {
193
+ this.subMenus[name] = createElement('div', 'cherry-dropdown', { name });
194
+ this.setSubMenuPosition(this.menus.hooks[name], this.subMenus[name]);
195
+ // 如果有配置的二级菜单
196
+ const level2MenusName = this.isHasLevel2Menu(name);
197
+ if (level2MenusName) {
198
+ level2MenusName.forEach((level2Name) => {
199
+ const subMenu = this.menus.hooks[level2Name];
200
+ if (subMenu !== undefined && typeof subMenu.createBtn === 'function') {
201
+ const btn = subMenu.createBtn(true);
202
+ // 二级菜单的dom认定为一级菜单的
203
+ subMenu.dom = subMenu.dom ? subMenu.dom : this.menus.hooks[name].dom;
204
+ btn.addEventListener('click', (event) => this.onClick(event, level2Name, true), false);
205
+ this.subMenus[name].appendChild(btn);
206
+ }
207
+ });
208
+ }
209
+ // 兼容旧版本配置的二级菜单
210
+ const subMenuConfig = this.isHasConfigMenu(name);
211
+ if (subMenuConfig.length > 0) {
212
+ subMenuConfig.forEach((config) => {
213
+ const btn = this.menus.hooks[name].createSubBtnByConfig(config);
214
+ if (!config?.disabledHideAllSubMenu) {
215
+ btn.addEventListener('click', () => this.hideAllSubMenu(), false);
216
+ }
217
+ this.subMenus[name].appendChild(btn);
218
+ });
219
+ }
220
+ this.$cherry.wrapperDom.appendChild(this.subMenus[name]);
221
+ }
222
+
223
+ /**
224
+ * 处理点击事件
225
+ */
226
+ onClick(event, name, focusEvent = false) {
227
+ const menu = this.menus.hooks[name];
228
+ if (!menu) {
229
+ return;
230
+ }
231
+ if (this.isHasSubMenu(name) && !focusEvent) {
232
+ this.toggleSubMenu(name);
233
+ } else {
234
+ /**
235
+ * 如果定义了hideOtherSubMenu,则隐藏其他二级菜单,但不隐藏自己(因为其二级菜单是自己实现的独立逻辑)
236
+ * 比如:颜色选择器、快捷键配置
237
+ */
238
+ // @ts-ignore
239
+ if (typeof menu.hideOtherSubMenu === 'function') {
240
+ // @ts-ignore
241
+ menu.hideOtherSubMenu(() => this.hideAllSubMenu());
242
+ } else {
243
+ this.hideAllSubMenu();
244
+ }
245
+ menu.fire(event, name);
246
+ }
247
+ }
248
+
249
+ /**
250
+ * 激活二级菜单添加选中颜色
251
+ * @param {string} name
252
+ */
253
+ activeSubMenuItem(name) {
254
+ const subMenu = this.subMenus[name];
255
+ const index = this.menus.hooks?.[name]?.getActiveSubMenuIndex(subMenu);
256
+ subMenu?.querySelectorAll('.cherry-dropdown-item').forEach((item, i) => {
257
+ item.classList.toggle('cherry-dropdown-item__selected', i === index);
258
+ });
259
+ }
260
+ updateSubMenuPosition() {
261
+ if (this.currentActiveSubMenu && this.subMenus[this.currentActiveSubMenu]) {
262
+ this.setSubMenuPosition(this.menus.hooks[this.currentActiveSubMenu], this.subMenus[this.currentActiveSubMenu]);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * 展开/收起二级菜单
268
+ */
269
+ toggleSubMenu(name) {
270
+ if (!this.subMenus[name]) {
271
+ // 如果没有二级菜单,则先画出来,然后再显示
272
+ this.hideAllSubMenu();
273
+ this.drawSubMenus(name);
274
+ this.subMenus[name].style.display = 'block';
275
+ this.activeSubMenuItem(name);
276
+ this.currentActiveSubMenu = name;
277
+ return;
278
+ }
279
+ if (this.subMenus[name].style.display === 'none') {
280
+ // 如果是隐藏的,则先隐藏所有二级菜单,再显示当前二级菜单
281
+ this.hideAllSubMenu();
282
+ this.subMenus[name].style.display = 'block';
283
+ this.setSubMenuPosition(this.menus.hooks[name], this.subMenus[name]);
284
+ this.activeSubMenuItem(name);
285
+ this.currentActiveSubMenu = name;
286
+ } else {
287
+ // 如果是显示的,则隐藏当前二级菜单
288
+ this.subMenus[name].style.display = 'none';
289
+ this.currentActiveSubMenu = null;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * 隐藏所有的二级菜单
295
+ */
296
+ hideAllSubMenu() {
297
+ this.currentActiveSubMenu = null;
298
+ this.$cherry.wrapperDom.querySelectorAll('.cherry-dropdown').forEach((dom) => {
299
+ dom.style.display = 'none';
300
+ });
301
+ }
302
+
303
+ /**
304
+ * 收集工具栏的各项信息,主要有:
305
+ * this.toolbarHandlers
306
+ * this.menus.hooks
307
+ * this.shortcutKeyMap
308
+ * @param {Toolbar} toolbarObj 工具栏对象
309
+ */
310
+ collectMenuInfo(toolbarObj) {
311
+ this.toolbarHandlers = Object.assign({}, this.toolbarHandlers, toolbarObj.toolbarHandlers);
312
+ this.menus.hooks = Object.assign({}, toolbarObj.menus.hooks, this.menus.hooks);
313
+ // 只有没设置自定义快捷键的时候才需要收集其他toolbar对象的快捷键配置
314
+ if (!this.options.shortcutKey || Object.keys(this.options.shortcutKey).length <= 0) {
315
+ this.shortcutKeyMap = Object.assign({}, this.shortcutKeyMap, toolbarObj.shortcutKeyMap);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * 收集快捷键
321
+ */
322
+ collectShortcutKey() {
323
+ if (this.options.shortcutKey && Object.keys(this.options.shortcutKey).length > 0) {
324
+ this.shortcutKeyMap = this.options.shortcutKey;
325
+ } else {
326
+ this.menus.allMenusName.forEach((name) => {
327
+ this.menus.hooks[name].shortcutKeys?.forEach((key) => {
328
+ this.shortcutKeyMap[key] = name;
329
+ });
330
+ });
331
+ }
332
+ }
333
+
334
+ collectToolbarHandler() {
335
+ this.toolbarHandlers = this.menus.allMenusName.reduce((handlerMap, name) => {
336
+ const menuHook = this.menus.hooks[name];
337
+ if (!menuHook) {
338
+ return handlerMap;
339
+ }
340
+ handlerMap[name] = (shortcut, _callback) => {
341
+ if (typeof _callback === 'function') {
342
+ Logger.warn(
343
+ 'MenuBase#onClick param callback is no longer supported. Please register the callback via MenuBase#registerAfterClickCb instead.',
344
+ );
345
+ }
346
+ menuHook.fire.call(menuHook, undefined, shortcut);
347
+ };
348
+ return handlerMap;
349
+ }, {});
350
+ }
351
+
352
+ /**
353
+ * 监测是否有对应的快捷键
354
+ * @param {KeyboardEvent} evt keydown 事件
355
+ * @returns {boolean} 是否有对应的快捷键
356
+ */
357
+ matchShortcutKey(evt) {
358
+ return !!this.shortcutKeyMap[this.getCurrentKey(evt)];
359
+ }
360
+
361
+ /**
362
+ * 触发对应快捷键的事件
363
+ * @param {KeyboardEvent} evt
364
+ */
365
+ fireShortcutKey(evt) {
366
+ const currentKey = this.getCurrentKey(evt);
367
+ this.menus.hooks[this.shortcutKeyMap[currentKey]]?.fire(evt, currentKey);
368
+ }
369
+
370
+ /**
371
+ * 格式化当前按键,mac下的command按键转换为ctrl
372
+ * @param {KeyboardEvent} event
373
+ * @returns
374
+ */
375
+ getCurrentKey(event) {
376
+ let key = '';
377
+ if (event.ctrlKey) {
378
+ key += 'Ctrl-';
379
+ }
380
+
381
+ if (event.altKey) {
382
+ key += 'Alt-';
383
+ }
384
+
385
+ if (event.metaKey && mac) {
386
+ key += 'Ctrl-';
387
+ }
388
+
389
+ // 如果存在shift键
390
+ if (event.shiftKey) {
391
+ key += `Shift-`;
392
+ }
393
+
394
+ // 如果还有第三个键 且不是 shift键
395
+ if (event.key && event.key.toLowerCase() !== 'shift') {
396
+ key += event.key.toLowerCase();
397
+ }
398
+
399
+ return key;
400
+ }
401
+ }
@@ -0,0 +1,53 @@
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 MenuBase from '@/toolbars/MenuBase';
17
+ import { handleUpload, handleParams } from '@/utils/file';
18
+ /**
19
+ * 插入音频
20
+ */
21
+ export default class Audio extends MenuBase {
22
+ constructor($cherry) {
23
+ super($cherry);
24
+ this.setName('audio', 'mic');
25
+ }
26
+
27
+ /**
28
+ * 响应点击事件
29
+ * @param {string} selection 被用户选中的文本内容
30
+ * @returns {string} 回填到编辑器光标位置/选中文本区域的内容
31
+ */
32
+ onClick(selection, shortKey = '') {
33
+ if (this.hasCacheOnce()) {
34
+ // @ts-ignore
35
+ const { name, url, params } = this.getAndCleanCacheOnce();
36
+ const begin = '!audio[';
37
+ const end = `](${url})`;
38
+ this.registerAfterClickCb(() => {
39
+ this.setLessSelection(begin, end);
40
+ });
41
+ const finalName = params.name ? params.name : name;
42
+ return `${begin}${finalName}${handleParams(params)}${end}`;
43
+ }
44
+ const accept = this.$cherry.options?.fileTypeLimitMap?.audio ?? '*';
45
+ // 插入图片,调用上传文件逻辑
46
+ handleUpload(this.editor, 'audio', accept, (name, url, params) => {
47
+ this.setCacheOnce({ name, url, params });
48
+ this.fire(null);
49
+ });
50
+ this.updateMarkdown = false;
51
+ return selection;
52
+ }
53
+ }