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,276 @@
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 SyntaxBase from '@/core/SyntaxBase';
17
+ import { escapeHTMLSpecialCharOnce as $e, encodeURIOnce } from '@/utils/sanitize';
18
+ import imgAltHelper from '@/utils/image';
19
+ import { compileRegExp, isLookbehindSupported, NOT_ALL_WHITE_SPACES_INLINE } from '@/utils/regexp';
20
+ import { replaceLookbehind } from '@/utils/lookbehind-replace';
21
+ import UrlCache from '@/UrlCache';
22
+
23
+ const replacerFactory = function (type, match, leadingChar, alt, link, title, posterContent, config, globalConfig) {
24
+ const refType = typeof link === 'undefined' ? 'ref' : 'url';
25
+ let attrs = '';
26
+ if (refType === 'ref') {
27
+ // TODO: 全局引用
28
+ return match;
29
+ }
30
+
31
+ if (refType === 'url') {
32
+ const extent = imgAltHelper.processExtendAttributesInAlt(alt);
33
+ let { extendStyles: style, extendClasses: classes } = imgAltHelper.processExtendStyleInAlt(alt);
34
+ if (style) {
35
+ style = ` style="${style}" `;
36
+ }
37
+ if (classes) {
38
+ classes = ` class="${classes}" `;
39
+ }
40
+ attrs = title && title.trim() !== '' ? ` title="${$e(title)}"` : '';
41
+ if (posterContent) {
42
+ attrs += ` poster="${encodeURIOnce(posterContent)}"`;
43
+ }
44
+
45
+ const processedURL = globalConfig.urlProcessor(link, type);
46
+ const defaultWrapper = `<${type} src="${UrlCache.set(
47
+ encodeURIOnce(processedURL),
48
+ )}"${attrs} ${extent} ${style} ${classes} controls="controls">${$e(alt || '')}</${type}>`;
49
+ return `${leadingChar}${config.videoWrapper ? config.videoWrapper(link) : defaultWrapper}`;
50
+ }
51
+ // should never happen
52
+ return match;
53
+ };
54
+
55
+ const replacerFileFactory = function (match, leadingChar, alt, link, title, posterContent, config, globalConfig) {
56
+ const fileIcon = {
57
+ file: 'draft', // 默认文件
58
+ 'doc|docx|txt': 'description',
59
+ 'xls|xlsx': 'table',
60
+ 'ppt|pptx': 'video_file',
61
+ pdf: 'picture_as_pdf',
62
+ 'jpg|jpeg|png|gif|bmp|tiff|svg': 'image', // 图像文件
63
+ 'mp3|wav|aac|ogg|flac': 'audio_file', // 音频文件
64
+ 'mp4|avi|mov|wmv|flv|mkv': 'movie', // 视频文件
65
+ 'zip|rar|7z|tar.gz|gz': 'folder_zip', // 压缩文件
66
+ 'exe|dmg|msi': 'memory', // Windows 可执行文件
67
+ 'sh|json|xml|html|htm|css': 'code', // 编程语言和标记语言文件
68
+ apk: 'apk_document', // APK 文件
69
+ };
70
+
71
+ function getIcon(ext) {
72
+ // 将 fileIcon 的键转换为数组,每个元素是该键包含的扩展名数组
73
+ const extensionsMap = Object.entries(fileIcon).map(([key, value]) => ({
74
+ exts: key.split('|'),
75
+ icon: value,
76
+ }));
77
+
78
+ // 查找与给定扩展名匹配的图标
79
+ const match = extensionsMap.find(({ exts }) => exts.includes(ext));
80
+
81
+ // 如果找到匹配的图标,则返回它;否则返回默认图标
82
+ return match ? match.icon : fileIcon.file;
83
+ }
84
+
85
+ const refType = typeof link === 'undefined' ? 'ref' : 'url';
86
+ if (refType === 'ref') {
87
+ return match;
88
+ }
89
+ let titleInfo = alt;
90
+ if (!titleInfo) {
91
+ titleInfo = 'file|file';
92
+ }
93
+ const titleSplit = titleInfo.split('|');
94
+ const [name = 'file', ext = 'file', password = ''] = titleSplit;
95
+
96
+ return `<div class=" cherry-card cherry-file cherry-card-list-container">
97
+ <a href="链接" target="_blank" class="cherry-card-item cherry-card-row-1" >
98
+ <span class="cherry-card-image material-symbols-outlined" >${getIcon(ext)}</span>
99
+ <div class="cherry-card-body ${password !== '' ? '' : 'cherry-no-desc'}">
100
+ <p class="cherry-card-title">${name}</p>
101
+ ${
102
+ password !== ''
103
+ ? `<p class="cherry-card-desc" ><span class="material-symbols-outlined" style="margin-right: 5px">lock_open</span> <span>${password}</span></p>`
104
+ : ''
105
+ }
106
+ </div>
107
+ </a>
108
+ </div>`;
109
+ };
110
+
111
+ export default class Image extends SyntaxBase {
112
+ static HOOK_NAME = 'image';
113
+
114
+ constructor({ config, globalConfig }) {
115
+ super(null);
116
+ this.urlProcessor = globalConfig.urlProcessor;
117
+ // TODO: URL Validator
118
+ this.extendMedia = {
119
+ tag: ['video', 'audio', 'file'],
120
+ replacer: {
121
+ video(match, leadingChar, alt, link, title, poster) {
122
+ return replacerFactory('video', match, leadingChar, alt, link, title, poster, config, globalConfig);
123
+ },
124
+ audio(match, leadingChar, alt, link, title, poster) {
125
+ return replacerFactory('audio', match, leadingChar, alt, link, title, poster, config, globalConfig);
126
+ },
127
+ file(match, leadingChar, alt, link, title, poster) {
128
+ return replacerFileFactory(match, leadingChar, alt, link, title, poster, config, globalConfig);
129
+ },
130
+ },
131
+ };
132
+ this.RULE = this.rule(this.extendMedia);
133
+ }
134
+
135
+ toHtml(match, leadingChar, alt, link, title, ref, extendAttrs) {
136
+ // console.log(match, alt, link, ref, title);
137
+ const refType = typeof link === 'undefined' ? 'ref' : 'url';
138
+ let attrs = '';
139
+ if (refType === 'ref') {
140
+ // 全局引用,理应在CommentReference中被替换,没有被替换说明没有定义引用项
141
+ return match;
142
+ }
143
+ if (refType === 'url') {
144
+ const extent = imgAltHelper.processExtendAttributesInAlt(alt);
145
+ let { extendStyles: style, extendClasses: classes } = imgAltHelper.processExtendStyleInAlt(alt);
146
+ if (style) {
147
+ style = ` style="${style}" `;
148
+ }
149
+ if (classes) {
150
+ classes = ` class="${classes}" `;
151
+ }
152
+ attrs = title && title.trim() !== '' ? ` title="${$e(title.replace(/["']/g, ''))}"` : '';
153
+ let srcProp = 'src';
154
+ let srcValue;
155
+ const cherryOptions = this.$engine.$cherry.options;
156
+ if (cherryOptions.callback && cherryOptions.callback.beforeImageMounted) {
157
+ const imgAttrs = cherryOptions.callback.beforeImageMounted(srcProp, link);
158
+ srcProp = imgAttrs.srcProp || srcProp;
159
+ srcValue = imgAttrs.src || link;
160
+ }
161
+ const extendAttrStr = extendAttrs
162
+ ? extendAttrs
163
+ .replace(/[{}]/g, '')
164
+ .replace(/([^=\s]+)=([^\s]+)/g, '$1="$2"')
165
+ .replace(/&/g, '&amp;') // 对&多做一次转义,cherry现有的机制会自动把&amp;转成&,只有多做一次转义才能抵消cherry的机制
166
+ : '';
167
+ return `${leadingChar}<img ${srcProp}="${UrlCache.set(
168
+ encodeURIOnce(this.urlProcessor(srcValue, 'image')),
169
+ )}" ${extent} ${style} ${classes} alt="${$e(alt || '')}"${attrs} ${extendAttrStr}/>`;
170
+ }
171
+ // should never happen
172
+ return match;
173
+ }
174
+
175
+ toMediaHtml(match, leadingChar, mediaType, alt, link, title, ref, posterWrap, poster, ...args) {
176
+ if (!this.extendMedia.replacer[mediaType]) {
177
+ return match;
178
+ }
179
+ return this.extendMedia.replacer[mediaType].call(this, match, leadingChar, alt, link, title, poster, ...args);
180
+ }
181
+
182
+ makeHtml(str) {
183
+ let $str = str;
184
+ if (this.test($str)) {
185
+ if (isLookbehindSupported()) {
186
+ $str = $str.replace(this.RULE.reg, this.toHtml.bind(this));
187
+ } else {
188
+ $str = replaceLookbehind($str, this.RULE.reg, this.toHtml.bind(this), true, 1);
189
+ }
190
+ }
191
+ if (this.testMedia($str)) {
192
+ if (isLookbehindSupported()) {
193
+ $str = $str.replace(this.RULE.regExtend, this.toMediaHtml.bind(this));
194
+ } else {
195
+ $str = replaceLookbehind($str, this.RULE.regExtend, this.toMediaHtml.bind(this), true, 1);
196
+ }
197
+ }
198
+ return $str;
199
+ }
200
+
201
+ // afterMakeHtml(str) {
202
+ // return UrlCache.restoreAll(str);
203
+ // }
204
+
205
+ testMedia(str) {
206
+ return this.RULE.regExtend && this.RULE.regExtend.test(str);
207
+ }
208
+
209
+ rule(extendMedia) {
210
+ const ret = {
211
+ // lookbehind启用分组是为了和不兼容lookbehind的场景共用一个回调
212
+ begin: isLookbehindSupported() ? '((?<!\\\\))!' : '(^|[^\\\\])!',
213
+ content: [
214
+ '\\[([^\\n]*?)\\]', // ?<alt>
215
+ '[ \\t]*', // any spaces
216
+ `${
217
+ '(?:' +
218
+ '\\(' +
219
+ '([^"][^\\s]+?)' + // ?<link> url
220
+ '(?:[ \\t]((?:".*?")|(?:\'.*?\')))?' + // ?<title> optional
221
+ '\\)' +
222
+ '|' + // or
223
+ '\\[('
224
+ }${NOT_ALL_WHITE_SPACES_INLINE})\\]` + // ?<ref> global ref
225
+ ')',
226
+ ].join(''),
227
+ end: '({[^{}]+?})?', // extend attrs e.g. {width=50 height=60}
228
+ };
229
+ if (extendMedia) {
230
+ const extend = { ...ret };
231
+ // TODO: 支持Lookbehind
232
+ extend.begin = isLookbehindSupported()
233
+ ? `((?<!\\\\))!(${extendMedia.tag.join('|')})`
234
+ : `(^|[^\\\\])!(${extendMedia.tag.join('|')})`;
235
+ extend.end = '({poster=(.*)})?';
236
+ ret.regExtend = compileRegExp(extend, 'g');
237
+ }
238
+ ret.reg = compileRegExp(ret, 'g');
239
+ return ret;
240
+ }
241
+ overlayMode() {
242
+ return {
243
+ inImageContainer: false,
244
+ inImageUrl: false,
245
+ name: 'image',
246
+ token(stream, state) {
247
+ // 检查行的开头是否有 ':::'
248
+
249
+ if (stream.match(/!(\w+)?\[.*?]\(.*?\)/)) {
250
+ stream.backUp(stream.current().length); // 回退以单独处理
251
+ this.inImageContainer = true;
252
+ }
253
+ if (this.inImageContainer) {
254
+ if (stream.peek() === '(') {
255
+ this.inImageUrl = true;
256
+ }
257
+ if (this.inImageUrl && stream.peek() === ')') {
258
+ this.inImageUrl = false;
259
+ this.inImageContainer = false;
260
+ }
261
+ if (stream.match('!') && !this.inImageUrl) {
262
+ return 'image-marker';
263
+ }
264
+ if (stream.match(/video|audio|file/) && stream.peek() === '[') {
265
+ return 'image-type';
266
+ }
267
+ if ((stream.match('#') || stream.match('|')) && !this.inImageUrl) {
268
+ return 'image-split';
269
+ }
270
+ }
271
+ stream.next();
272
+ return null; // 默认返回 null
273
+ },
274
+ };
275
+ }
276
+ }
@@ -0,0 +1,45 @@
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 ParagraphBase from '@/core/ParagraphBase';
17
+ import CodeBlock from './CodeBlock';
18
+ import { compileRegExp } from '@/utils/regexp';
19
+
20
+ export default class InlineCode extends ParagraphBase {
21
+ static HOOK_NAME = 'inlineCode';
22
+
23
+ // constructor() {
24
+ // super();
25
+ // }
26
+
27
+ makeHtml(str) {
28
+ return str;
29
+ }
30
+
31
+ afterMakeHtml(str) {
32
+ let $str = str;
33
+ if (Object.keys(CodeBlock.inlineCodeCache).length > 0) {
34
+ $str = $str.replace(/~~CODE([0-9a-zA-Z]+)\$/g, (match, sign) => CodeBlock.inlineCodeCache[sign]);
35
+ CodeBlock.inlineCodeCache = {};
36
+ }
37
+ return $str;
38
+ }
39
+
40
+ rule() {
41
+ const ret = { begin: '(`+)[ ]*', end: '[ ]*\\1', content: '(.+?(?:\\n.+?)*?)' };
42
+ ret.reg = compileRegExp(ret, 'g');
43
+ return ret;
44
+ }
45
+ }
@@ -0,0 +1,142 @@
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 ParagraphBase from '@/core/ParagraphBase';
17
+ import { getHTML } from '@/utils/dom';
18
+ import { getTableRule, isLookbehindSupported } from '@/utils/regexp';
19
+ import { replaceLookbehind } from '@/utils/lookbehind-replace';
20
+ import { Theme } from '@/Theme';
21
+ import Event from '../../Event';
22
+ /**
23
+ * 行内公式的语法
24
+ * 虽然叫做行内公式,Cherry依然将其视为"段落级语法",因为其具备排他性并且需要优先渲染
25
+ */
26
+ export default class InlineMath extends ParagraphBase {
27
+ static HOOK_NAME = 'inlineMath';
28
+
29
+ constructor({ config }) {
30
+ super({ needCache: true });
31
+ // 非浏览器环境下配置为 node
32
+ const { MathJax, apiHost = 'https://math.vercel.app' } = config;
33
+ this.api = !MathJax && !window.MathJax;
34
+ this.apiHost = apiHost;
35
+ this.MathJax = MathJax || window.MathJax;
36
+ if (this.api) {
37
+ const that = this;
38
+ Event.on('Theme', 'change', function ([isDark]) {
39
+ const images = that.$engine.$cherry.wrapperDom.querySelectorAll('img.Cherry-Math-Latex-Inline');
40
+ console.log(images);
41
+ images.forEach(function (item) {
42
+ if (item instanceof HTMLImageElement) {
43
+ item.src = item.src.replace(isDark ? 'color=black' : 'color=white', isDark ? 'color=white' : 'color=black');
44
+ }
45
+ });
46
+ });
47
+ }
48
+ }
49
+
50
+ toHtml(wholeMatch, leadingChar, m1) {
51
+ if (!m1) {
52
+ return wholeMatch;
53
+ }
54
+
55
+ const linesArr = m1.match(/\n/g);
56
+ const lines = linesArr ? linesArr.length + 2 : 2;
57
+ const sign = this.$engine.md5(wholeMatch);
58
+
59
+ if (this.MathJax?.tex2svg) {
60
+ // MathJax渲染
61
+ const svg = getHTML(this.MathJax.tex2svg(m1, { em: 12, ex: 6, display: false }), true);
62
+ const result = `${leadingChar}<span class="Cherry-InlineMath" data-type="mathBlock" data-lines="${lines}">${svg}</span>`;
63
+ return this.pushCache(result, ParagraphBase.IN_PARAGRAPH_CACHE_KEY_PREFIX + sign);
64
+ }
65
+ const result = `<span class="Cherry-InlineMath " data-type="mathBlock" data-lines="${lines}"><img class="Cherry-Math-Latex-Inline" alt="latex" src="${
66
+ this.apiHost
67
+ }/?from=${encodeURIComponent(m1)}&color=${Theme.isDark() ? 'white' : 'black'}" /></span>`;
68
+
69
+ return this.pushCache(result, ParagraphBase.IN_PARAGRAPH_CACHE_KEY_PREFIX + sign);
70
+ }
71
+
72
+ beforeMakeHtml(str) {
73
+ let $str = str;
74
+ // 格里处理行内公式,让一个td里的行内公式语法生效,让跨td的行内公式语法失效
75
+ $str = $str.replace(getTableRule(true), (whole, ...args) => {
76
+ return whole
77
+ .split('|')
78
+ .map((oneTd) => {
79
+ return this.makeInlineMath(oneTd);
80
+ })
81
+ .join('|')
82
+ .replace(/\\~D/g, '~D') // 出现反斜杠的情况(如/$e=m^2$)会导致多一个反斜杠,这里替换掉
83
+ .replace(/~D/g, '\\~D');
84
+ });
85
+ return this.makeInlineMath($str);
86
+ }
87
+
88
+ makeInlineMath(str) {
89
+ if (!this.test(str)) {
90
+ return str;
91
+ }
92
+ if (isLookbehindSupported()) {
93
+ return str.replace(this.RULE.reg, this.toHtml.bind(this));
94
+ }
95
+ return replaceLookbehind(str, this.RULE.reg, this.toHtml.bind(this), true, 1);
96
+ }
97
+
98
+ makeHtml(str) {
99
+ return str;
100
+ }
101
+
102
+ rule() {
103
+ const ret = {
104
+ begin: isLookbehindSupported() ? '((?<!\\\\))~D\\n?' : '(^|[^\\\\])~D\\n?',
105
+ content: '(.*?)\\n?',
106
+ end: '~D',
107
+ };
108
+ ret.reg = new RegExp(ret.begin + ret.content + ret.end, 'g');
109
+ return ret;
110
+ }
111
+ overlayMode() {
112
+ return {
113
+ name: 'inlineMath',
114
+ inMath: false,
115
+ passLeftKey: false,
116
+ token(stream, state) {
117
+ // 检查行内数学公式
118
+ if (!this.inMath && stream.match(/\$.*?\$/)) {
119
+ this.inMath = true;
120
+ stream.backUp(stream.current().length);
121
+ }
122
+ if (this.inMath) {
123
+ if (stream.match(/\$/)) {
124
+ if (!this.passLeftKey) {
125
+ this.passLeftKey = true;
126
+ } else {
127
+ this.inMath = false;
128
+ this.passLeftKey = false;
129
+ }
130
+ return 'math-container';
131
+ }
132
+ if (stream.match(/.+?\$/)) {
133
+ stream.backUp(1);
134
+ return 'math-text';
135
+ }
136
+ }
137
+ stream.next();
138
+ return null;
139
+ },
140
+ };
141
+ }
142
+ }
@@ -0,0 +1,169 @@
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 SyntaxBase from '@/core/SyntaxBase';
17
+ import { escapeHTMLSpecialChar as _e, isValidScheme, encodeURIOnce } from '@/utils/sanitize';
18
+ import { compileRegExp, isLookbehindSupported, NOT_ALL_WHITE_SPACES_INLINE } from '@/utils/regexp';
19
+ import { replaceLookbehind } from '@/utils/lookbehind-replace';
20
+ import UrlCache from '@/UrlCache';
21
+
22
+ export default class Link extends SyntaxBase {
23
+ static HOOK_NAME = 'link';
24
+
25
+ constructor({ config, globalConfig }) {
26
+ super({ config });
27
+ this.urlProcessor = globalConfig.urlProcessor;
28
+ // eslint-disable-next-line no-nested-ternary
29
+ this.target = config.target ? `target="${config.target}"` : !!config.openNewPage ? 'target="_blank"' : '';
30
+ this.rel = config.rel ? `rel="${config.rel}"` : '';
31
+ }
32
+
33
+ /**
34
+ * 校验link中text的方括号是否符合规则
35
+ * @param {string} rawText
36
+ */
37
+ checkBrackets(rawText) {
38
+ const stack = [];
39
+ const text = `[${rawText}]`;
40
+ // 前方有奇数个\当前字符被转义
41
+ const checkEscape = (place) => text.slice(0, place).match(/\\*$/)[0].length & 1;
42
+ for (let i = text.length - 1; text[i]; i--) {
43
+ if (i === text.length - 1 && checkEscape(i)) break;
44
+ if (text[i] === ']' && !checkEscape(i)) stack.push(']');
45
+ if (text[i] === '[' && !checkEscape(i)) {
46
+ stack.pop();
47
+ if (!stack.length) {
48
+ return {
49
+ isValid: true,
50
+ coreText: text.slice(i + 1, text.length - 1),
51
+ extraLeadingChar: text.slice(0, i),
52
+ };
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ isValid: false, // 方括号匹配不上
58
+ coreText: rawText,
59
+ extraLeadingChar: '',
60
+ };
61
+ }
62
+
63
+ /**
64
+ *
65
+ * @param {string} match 匹配的完整字符串
66
+ * @param {string} leadingChar 正则分组一:前置字符
67
+ * @param {string} text 正则分组二:链接文字
68
+ * @param {string|undefined} link 正则分组三:链接URL
69
+ * @param {string|undefined} title 正则分组四:链接title
70
+ * @param {string|undefined} ref 正则分组五:链接引用
71
+ * @param {string|undefined} target 正则分组六:新窗口打开
72
+ * @returns
73
+ */
74
+ toHtml(match, leadingChar, text, link, title, ref, target) {
75
+ const refType = typeof link === 'undefined' ? 'ref' : 'url';
76
+ let attrs = '';
77
+ if (refType === 'ref') {
78
+ // 全局引用,理应在CommentReference中被替换,没有被替换说明没有定义引用项
79
+ return match;
80
+ }
81
+
82
+ if (refType === 'url') {
83
+ const { isValid, coreText, extraLeadingChar } = this.checkBrackets(text);
84
+ if (!isValid) return match;
85
+ attrs = title && title.trim() !== '' ? ` title="${_e(title.replace(/["']/g, ''))}"` : '';
86
+ if (target) {
87
+ attrs += ` target="${target.replace(/{target\s*=\s*(.*?)}/, '$1')}"`;
88
+ } else {
89
+ // 检查是否为外部链接,非同源时默认使用_blank打开
90
+ const isExternalLink = link.startsWith('http') && !link.startsWith(window.location.origin);
91
+ if (isExternalLink) {
92
+ attrs += ' target="_blank"';
93
+ } else if (this.target) {
94
+ attrs += ` ${this.target}`;
95
+ }
96
+ }
97
+ if (attrs.includes('_blank')) {
98
+ attrs += ' rel="noopener noreferrer"';
99
+ }
100
+ let processedURL = link.trim().replace(/~1D/g, '~D'); // 还原替换的$符号
101
+ const processedText = coreText.replace(/~1D/g, '~D'); // 还原替换的$符号
102
+ // text可能是html标签,依赖htmlBlock进行处理
103
+ if (isValidScheme(processedURL)) {
104
+ processedURL = this.urlProcessor(processedURL, 'link');
105
+ processedURL = encodeURIOnce(processedURL);
106
+ return `${leadingChar + extraLeadingChar}<a href="${UrlCache.set(processedURL)}" ${
107
+ this.rel
108
+ } ${attrs}>${processedText}</a>`;
109
+ }
110
+ return `${leadingChar + extraLeadingChar}<span>${text}</span>`;
111
+ }
112
+ // should never happen
113
+ return match;
114
+ }
115
+
116
+ toStdMarkdown(match) {
117
+ return match;
118
+ }
119
+
120
+ makeHtml(str) {
121
+ let $str = str.replace(this.RULE.reg, (match) => {
122
+ return match.replace(/~D/g, '~1D');
123
+ });
124
+ if (isLookbehindSupported()) {
125
+ $str = $str.replace(this.RULE.reg, this.toHtml.bind(this));
126
+ } else {
127
+ $str = replaceLookbehind($str, this.RULE.reg, this.toHtml.bind(this), true, 1);
128
+ }
129
+ $str = $str.replace(this.RULE.reg, (match) => {
130
+ return match.replace(/~1D/g, '~D');
131
+ });
132
+ return $str;
133
+ }
134
+
135
+ rule() {
136
+ // (?<protocol>\\w+:)\\/\\/
137
+ const ret = {
138
+ // lookbehind启用分组是为了和不兼容lookbehind的场景共用一个回调
139
+ begin: isLookbehindSupported() ? '((?<!\\\\))' : '(^|[^\\\\])',
140
+ content: [
141
+ '\\[([^\\n]*?)\\]', // ?<text>
142
+ '[ \\t]*', // any spaces
143
+ `${
144
+ '(?:' +
145
+ '\\(' +
146
+ /**
147
+ * allow double quotes
148
+ * e.g.
149
+ * [link](") ⭕️ valid
150
+ * [link]("") ⭕️ valid
151
+ * [link](" ") ❌ invalid
152
+ */
153
+ '([^\\s)]+)' + // ?<link> url
154
+ '(?:[ \\t]((?:".*?")|(?:\'.*?\')))?' + // ?<title> optional
155
+ '\\)' +
156
+ '|' + // or
157
+ '\\[('
158
+ }${NOT_ALL_WHITE_SPACES_INLINE})\\]` + // ?<ref> global ref
159
+ ')',
160
+ '(\\{target\\s*=\\s*(_blank|_parent|_self|_top)\\})?',
161
+ ].join(''),
162
+ end: '',
163
+ };
164
+ // let ret = {begin:'((^|[^\\\\])\\*\\*|([\\s]|^)__)',
165
+ // end:'(\\*\\*([\\s\\S]|$)|__([\\s]|$))', content:'([^\\n]+?)'};
166
+ ret.reg = compileRegExp(ret, 'g');
167
+ return ret;
168
+ }
169
+ }