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,229 @@
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 { compileRegExp } from '@/utils/regexp';
18
+ import { calculateLinesOfParagraph } from '@/utils/lineFeed';
19
+
20
+ const ATX_HEADER = 'atx';
21
+ const SETEXT_HEADER = 'setext';
22
+ const toDashChars = /[\s\-_]/;
23
+ const alphabetic = /[A-Za-z]/;
24
+ const numeric = /[0-9]/;
25
+
26
+ export default class Header extends ParagraphBase {
27
+ static HOOK_NAME = 'header';
28
+
29
+ constructor({ externals, config } = { config: undefined, externals: undefined }) {
30
+ super({ needCache: true });
31
+ this.strict = config ? !!config.strict : true;
32
+ this.RULE = this.rule();
33
+ this.headerIDCache = [];
34
+ this.headerIDCounter = {};
35
+ this.config = config || {};
36
+ // TODO: AllowCustomID
37
+ }
38
+
39
+ $parseTitleText(html = '') {
40
+ if (typeof html !== 'string') {
41
+ return '';
42
+ }
43
+ return html.replace(/<.*?>/g, '').replace(/&#60;/g, '<').replace(/&#62;/g, '>');
44
+ }
45
+
46
+ /**
47
+ * refer:
48
+ * @see https://github.com/vsch/flexmark-java/blob/8bf621924158dfed8b84120479c82704020a6927/flexmark
49
+ * /src/main/java/com/vladsch/flexmark/html/renderer/HeaderIdGenerator.java#L90-L113
50
+ *
51
+ * @param {string} headerText
52
+ * @param {boolean} [toLowerCase]
53
+ * @returns
54
+ */
55
+ $generateId(headerText, toLowerCase = true) {
56
+ const len = headerText.length;
57
+ let id = '';
58
+ for (let i = 0; i < len; i++) {
59
+ const c = headerText.charAt(i);
60
+ if (alphabetic.test(c)) {
61
+ id += toLowerCase ? c.toLowerCase() : c;
62
+ } else if (numeric.test(c)) {
63
+ id += c;
64
+ } else if (toDashChars.test(c)) {
65
+ id += id.length < 1 || id.charAt(id.length - 1) !== '-' ? '-' : '';
66
+ } else if (c.charCodeAt(0) > 255) {
67
+ // unicode
68
+ try {
69
+ id += encodeURIComponent(c);
70
+ } catch (error) {
71
+ // empty
72
+ }
73
+ }
74
+ }
75
+ return id;
76
+ }
77
+
78
+ generateIDNoDup(headerText) {
79
+ // 处理被引擎转换过的实体字符
80
+ const unescapedHeaderText = headerText.replace(/&#60;/g, '<').replace(/&#62;/g, '>');
81
+ let newId = this.$generateId(unescapedHeaderText, true);
82
+ const idIndex = this.headerIDCache.indexOf(newId);
83
+ if (idIndex !== -1) {
84
+ this.headerIDCounter[idIndex] += 1;
85
+ newId += `-${this.headerIDCounter[idIndex] + 1}`;
86
+ } else {
87
+ const newIndex = this.headerIDCache.push(newId);
88
+ this.headerIDCounter[newIndex - 1] = 1;
89
+ }
90
+ return newId;
91
+ }
92
+
93
+ $wrapHeader(text, level, dataLines, sentenceMakeFunc) {
94
+ // 需要经过一次escape
95
+ const processedText = sentenceMakeFunc(text.trim());
96
+ let { html } = processedText;
97
+ const customIDRegex = /\s+\{#([A-Za-z0-9-]+)\}$/; // ?<id>
98
+ const idMatch = html.match(customIDRegex);
99
+ let anchorID;
100
+ if (idMatch !== null) {
101
+ html = html.substring(0, idMatch.index);
102
+ [, anchorID] = idMatch;
103
+ }
104
+ const headerTextRaw = this.$parseTitleText(html);
105
+ if (!anchorID) {
106
+ const replaceFootNote = /~fn#([0-9]+)#/g;
107
+ anchorID = this.generateIDNoDup(headerTextRaw.replace(replaceFootNote, ''));
108
+ }
109
+ const safeAnchorID = `safe_${anchorID}`; // transform header id to avoid being sanitized
110
+ const sign = this.$engine.md5(`${level}-${processedText.sign}-${anchorID}-${dataLines}`);
111
+ const result = [
112
+ `<h${level} id="${safeAnchorID}" data-sign="${sign}" data-lines="${dataLines}">`,
113
+ this.$getAnchor(anchorID, html),
114
+ // `${html}`,
115
+ `</h${level}>`,
116
+ ].join('');
117
+ return { html: result, sign: `${sign}` };
118
+ }
119
+
120
+ $getAnchor(anchorID, text) {
121
+ const anchorStyle = this.config.anchorStyle || 'default';
122
+ if (anchorStyle === 'none') {
123
+ return '';
124
+ }
125
+ return `${text} <a class="anchor" href="#${anchorID}">#</a>`;
126
+ }
127
+
128
+ beforeMakeHtml(str) {
129
+ let $str = str;
130
+ if (this.$engine.$cherry.options.engine.global.flowSessionContext) {
131
+ // 适配流式会话的场景,文章末尾的段横线标题语法(`\n-`)失效
132
+ $str = $str.replace(/(\n\s*-{1,})\s*$/, '$1 ');
133
+ }
134
+ // atx 优先
135
+ if (this.test($str, ATX_HEADER)) {
136
+ $str = $str.replace(this.RULE[ATX_HEADER].reg, (match, lines, level, text) => {
137
+ if (text.trim() === '') {
138
+ return match;
139
+ }
140
+ return this.getCacheWithSpace(this.pushCache(match), match, true);
141
+ });
142
+ }
143
+ // 按照目前的引擎,每个hook只会执行一次,所以需要并行执行替换
144
+ if (this.test($str, SETEXT_HEADER)) {
145
+ $str = $str.replace(this.RULE[SETEXT_HEADER].reg, (match, lines, text) => {
146
+ if (text.trim() === '' || this.isContainsCache(text)) {
147
+ return match;
148
+ }
149
+ return this.getCacheWithSpace(this.pushCache(match), match, true);
150
+ });
151
+ }
152
+ return $str;
153
+ }
154
+
155
+ makeHtml(str, sentenceMakeFunc) {
156
+ // 先还原
157
+ let $str = this.restoreCache(str);
158
+ // atx 优先
159
+ if (this.test($str, ATX_HEADER)) {
160
+ $str = $str.replace(this.RULE[ATX_HEADER].reg, (match, lines, level, text) => {
161
+ // 其中有两行是beforeMake加上的
162
+ const lineCount = calculateLinesOfParagraph(lines, this.getLineCount(match.replace(/^\n+/, '')));
163
+ const $text = text.replace(/\s+#+\s*$/, ''); // close tag
164
+ const { html: result, sign } = this.$wrapHeader($text, level.length, lineCount, sentenceMakeFunc);
165
+ // 文章的开头不加换行
166
+ return this.getCacheWithSpace(this.pushCache(result, sign, lineCount), match, true);
167
+ });
168
+ }
169
+ // 按照目前的引擎,每个hook只会执行一次,所以需要并行执行替换
170
+ if (this.test($str, SETEXT_HEADER)) {
171
+ $str = $str.replace(this.RULE[SETEXT_HEADER].reg, (match, lines, text, level) => {
172
+ if (this.isContainsCache(text)) {
173
+ return match;
174
+ }
175
+ // 其中有两行是beforeMake加上的
176
+ const lineCount = calculateLinesOfParagraph(lines, this.getLineCount(match.replace(/^\n+/, '')));
177
+ const headerLevel = level[0] === '-' ? 2 : 1; // =: H1, -: H2
178
+ const { html: result, sign } = this.$wrapHeader(text, headerLevel, lineCount, sentenceMakeFunc);
179
+ // 文章的开头不加换行
180
+ return this.getCacheWithSpace(this.pushCache(result, sign, lineCount), match, true);
181
+ });
182
+ }
183
+ return $str;
184
+ }
185
+
186
+ afterMakeHtml(html) {
187
+ const $html = super.afterMakeHtml(html);
188
+ this.headerIDCache = [];
189
+ this.headerIDCounter = {};
190
+ return $html;
191
+ }
192
+
193
+ test(str, flavor) {
194
+ return this.RULE[flavor].reg && this.RULE[flavor].reg.test(str);
195
+ }
196
+
197
+ /**
198
+ * TODO: fix type errors, prefer use `rules` for multiple spec instead
199
+ * @returns
200
+ */
201
+ rule() {
202
+ // setext Header
203
+ // TODO: 支持多行标题
204
+ const setext = {
205
+ begin: '(?:^|\\n)(\\n*)', // (?<lines>\\n*)
206
+ content: [
207
+ '(?:\\h*',
208
+ '(.+)', // (?<text>.+)
209
+ ')\\n',
210
+ '(?:\\h*',
211
+ '([=]+|[-]+)', // (?<level>[=]+|[-]+)
212
+ ')',
213
+ ].join(''),
214
+ end: '(?=$|\\n)',
215
+ };
216
+ setext.reg = compileRegExp(setext, 'g', true);
217
+
218
+ // atx header
219
+ const atx = {
220
+ begin: '(?:^|\\n)(\\n*)(?:\\h*(#{1,6}))', // (?<lines>\\n*), (?<level>#{1,6})
221
+ content: '(.+?)', // '(?<text>.+?)'
222
+ end: '(?=$|\\n)',
223
+ };
224
+ this.strict && (atx.begin += '(?=\\h+)'); // (?=\\s+) for strict mode
225
+ atx.reg = compileRegExp(atx, 'g', true);
226
+
227
+ return /** @type {any} */ ({ setext, atx });
228
+ }
229
+ }
@@ -0,0 +1,37 @@
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 '../SyntaxBase.js';
17
+
18
+ export default class HighLight extends SyntaxBase {
19
+ static HOOK_NAME = 'highLight';
20
+
21
+ makeHtml(str) {
22
+ if (!this.test(str)) {
23
+ return str;
24
+ }
25
+ return str.replace(this.RULE.reg, '$1<mark>$2</mark>$3');
26
+ }
27
+
28
+ rule() {
29
+ const ret = {
30
+ begin: '(^| )==',
31
+ end: '==( |$|\\n)',
32
+ content: '([^\\n]+?)',
33
+ };
34
+ ret.reg = new RegExp(ret.begin + ret.content + ret.end, 'g');
35
+ return ret;
36
+ }
37
+ }
@@ -0,0 +1,52 @@
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 { prependLineFeedForParagraph } from '@/utils/lineFeed';
18
+ /**
19
+ * 分割线语法
20
+ */
21
+ export default class Hr extends ParagraphBase {
22
+ static HOOK_NAME = 'hr';
23
+
24
+ constructor() {
25
+ super({ needCache: true });
26
+ }
27
+
28
+ beforeMakeHtml(str) {
29
+ return str.replace(this.RULE.reg, (match, preLines) => {
30
+ const lineCount = (preLines.match(/\n/g) || []).length + 1;
31
+ // 计算签名,签名可能会重复,符合预期
32
+ const sign = `hr${lineCount}`;
33
+ const placeHolder = this.pushCache(`<hr data-sign="${sign}" data-lines="${lineCount}" />`, sign);
34
+ return prependLineFeedForParagraph(match, placeHolder);
35
+ });
36
+ }
37
+
38
+ makeHtml(str, sentenceMakeFunc) {
39
+ return str;
40
+ }
41
+
42
+ rule() {
43
+ // 分割线必须有从新行开始,比如以换行结束
44
+ const ret = {
45
+ begin: '(?:^|\\n)(\\n*)[ ]*',
46
+ end: '(?=$|\\n)',
47
+ content: '((?:-[ \\t]*){3,}|(?:\\*[ \\t]*){3,}|(?:_[ \\t]*){3,})',
48
+ };
49
+ ret.reg = new RegExp(ret.begin + ret.content + ret.end, 'g');
50
+ return ret;
51
+ }
52
+ }
@@ -0,0 +1,159 @@
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 {
18
+ whiteList,
19
+ convertHTMLNumberToName,
20
+ // isValidScheme, encodeURIOnce,
21
+ escapeHTMLEntitiesWithoutSemicolon,
22
+ } from '@/utils/sanitize';
23
+ import { sanitizer } from '@/Sanitizer';
24
+ import { isBrowser } from '@/utils/env';
25
+
26
+ /**
27
+ * encode unsafe link-related attributes
28
+ */
29
+ const unsafeAttributes = ['href', 'src'];
30
+
31
+ sanitizer.addHook('afterSanitizeAttributes', (node) => {
32
+ unsafeAttributes.forEach((attr) => {
33
+ if (!node.hasAttribute(attr)) {
34
+ return;
35
+ }
36
+ const value = node.getAttribute(attr);
37
+ // encode unsafe backslash in link attributes
38
+ node.setAttribute(attr, value.replace(/\\/g, '%5c'));
39
+ });
40
+ });
41
+
42
+ export default class HtmlBlock extends ParagraphBase {
43
+ static HOOK_NAME = 'htmlBlock';
44
+ constructor() {
45
+ super({ needCache: true });
46
+ }
47
+
48
+ // ref: http://www.vfmd.org/vfmd-spec/specification/#procedure-for-detecting-automatic-links
49
+ isAutoLinkTag(tagMatch) {
50
+ const REGEX_GROUP = [
51
+ /^<([a-z][a-z0-9+.-]{1,31}:\/\/[^<> `]+)>$/i,
52
+ /^<(mailto:[^<> `]+)>$/i,
53
+ /^<([^()<>[\]:'@\\,"\s`]+@[^()<>[\]:'@\\,"\s`.]+\.[^()<>[\]:'@\\,"\s`]+)>$/i,
54
+ ];
55
+ return REGEX_GROUP.some((regex) => regex.test(tagMatch));
56
+ }
57
+
58
+ isHtmlComment(match) {
59
+ const htmlComment = /^<!--.*?-->$/;
60
+ return htmlComment.test(match);
61
+ }
62
+
63
+ beforeMakeHtml(str, sentenceMakeFunc) {
64
+ if (this.$engine.htmlWhiteListAppend) {
65
+ /**
66
+ * @property
67
+ * @type {false | RegExp}
68
+ */
69
+ this.htmlWhiteListAppend = new RegExp(`^(${this.$engine.htmlWhiteListAppend})( |$|/)`, 'i');
70
+ /**
71
+ * @property
72
+ * @type {string[]}
73
+ */
74
+ this.htmlWhiteList = this.$engine.htmlWhiteListAppend.split('|');
75
+ } else {
76
+ this.htmlWhiteListAppend = false;
77
+ this.htmlWhiteList = [];
78
+ }
79
+
80
+ let $str = str;
81
+ $str = convertHTMLNumberToName($str);
82
+ $str = escapeHTMLEntitiesWithoutSemicolon($str);
83
+ $str = $str.replace(/<[/]?(.*?)>/g, (whole, m1) => {
84
+ // 匹配到非白名单且非AutoLink语法的尖括号会被转义
85
+ // 如果是HTML注释,放行
86
+ if (!whiteList.test(m1) && !this.isAutoLinkTag(whole) && !this.isHtmlComment(whole)) {
87
+ if (this.htmlWhiteListAppend === false || !this.htmlWhiteListAppend.test(m1)) {
88
+ return whole.replace(/</g, '&#60;').replace(/>/g, '&#62;');
89
+ }
90
+ }
91
+ // 到达此分支的包含被尖括号包裹的AutoLink语法以及在白名单内的HTML标签
92
+ // 没有被AutoLink解析并渲染的标签会被DOMPurify过滤掉,正常情况下不会出现遗漏
93
+ // 临时替换完整的HTML标签首尾为$#60;和$#62;,供下一步剔除损坏的HTML标签
94
+ return whole.replace(/</g, '$#60;').replace(/>/g, '$#62;');
95
+ });
96
+ // 替换所有形如「<abcd」和「</abcd」的左尖括号
97
+ $str = $str.replace(/<(?=\/?(\w|\n|$))/g, '&#60;');
98
+ // 还原被替换的尖括号
99
+ $str = $str.replace(/\$#60;/g, '<').replace(/\$#62;/g, '>');
100
+ return $str;
101
+ }
102
+
103
+ // beforeMakeHtml(str) {
104
+ // return str;
105
+ // }
106
+
107
+ makeHtml(str, sentenceMakeFunc) {
108
+ return str;
109
+ }
110
+
111
+ afterMakeHtml(str) {
112
+ let $str = str;
113
+ const config = {
114
+ ALLOW_UNKNOWN_PROTOCOLS: true,
115
+ ADD_ATTR: ['target'],
116
+ };
117
+ if (this.htmlWhiteListAppend !== false) {
118
+ config.ADD_TAGS = this.htmlWhiteList;
119
+ if (this.htmlWhiteListAppend.test('style') || this.htmlWhiteListAppend.test('ALL')) {
120
+ $str = $str.replace(/<style(>| [^>]*>).*?<\/style>/gi, (match) => {
121
+ return match.replace(/<br>/gi, '');
122
+ });
123
+ }
124
+ if (this.htmlWhiteListAppend.test('iframe') || this.htmlWhiteListAppend.test('ALL')) {
125
+ config.ADD_ATTR = config.ADD_ATTR.concat([
126
+ 'align',
127
+ 'frameborder',
128
+ 'height',
129
+ 'longdesc',
130
+ 'marginheight',
131
+ 'marginwidth',
132
+ 'name',
133
+ 'sandbox',
134
+ 'scrolling',
135
+ 'seamless',
136
+ 'src',
137
+ 'srcdoc',
138
+ 'width',
139
+ ]);
140
+ config.SANITIZE_DOM = false;
141
+ $str = $str.replace(/<iframe(>| [^>]*>).*?<\/iframe>/gi, (match) => {
142
+ return match.replace(/<br>/gi, '').replace(/\n/g, '');
143
+ });
144
+ }
145
+ if (this.htmlWhiteListAppend.test('script') || this.htmlWhiteListAppend.test('ALL')) {
146
+ // 如果允许script或者输入了ALL,则不做任何过滤了
147
+ $str = $str.replace(/<script(>| [^>]*>).*?<\/script>/gi, (match) => {
148
+ return match.replace(/<br>/gi, '');
149
+ });
150
+ return $str;
151
+ }
152
+ }
153
+ // node 环境下不输出sign和lines
154
+ if (!isBrowser()) {
155
+ config.FORBID_ATTR = ['data-sign', 'data-lines'];
156
+ }
157
+ return sanitizer.sanitize($str, config);
158
+ }
159
+ }
@@ -0,0 +1,80 @@
1
+ import { isValidScheme } from '@/utils/sanitize';
2
+ import { prependLineFeedForParagraph } from '@/utils/lineFeed';
3
+ import SyntaxBase from '@/core/SyntaxBase';
4
+
5
+ export default class Iframe extends SyntaxBase {
6
+ static HOOK_NAME = 'iframe';
7
+
8
+ rule() {
9
+ const ret = {
10
+ begin: /^@@/m, // 严格匹配行首的@@
11
+ content: /https?:\/\/\S+/, // 匹配到第一个空格/换行立即停止
12
+ end: /(?=\s|$)/, // 结束条件简化为空格或行尾
13
+ };
14
+
15
+ ret.reg = new RegExp(
16
+ `${ret.begin.source}\\s*` + // 允许@@后跟空格
17
+ `(${
18
+ // 第1捕获组:完整URL(含参数)
19
+ ret.content.source
20
+ })${ret.end.source}`,
21
+ 'gm', // 全局+多行模式(确保^生效)
22
+ );
23
+ return ret;
24
+ }
25
+
26
+ makeHtml(str, sentenceMakeFunc) {
27
+ return str.replace(this.RULE.reg, (match, url) => {
28
+ const replacedUrl = url.trim().replace(/&amp;/g, '&');
29
+ const attrs = {
30
+ width: '100%',
31
+ height: '400px',
32
+ sandbox: 'allow-scripts',
33
+ };
34
+
35
+ const iframe = `<iframe src="${replacedUrl}"
36
+
37
+ width="${attrs.width}"
38
+ height="${attrs.height}"
39
+ sandbox="${attrs.sandbox}"
40
+
41
+ frameborder="0"
42
+ loading="lazy"
43
+ allow="fullscreen"
44
+ ></iframe>`;
45
+ return iframe;
46
+ });
47
+ }
48
+
49
+ overlayMode() {
50
+ return {
51
+ inIframe: false,
52
+ name: 'iframe',
53
+ token(stream, state) {
54
+ // 检测@@起始标记
55
+ if (!this.inIframe && stream.match(/^@@/)) {
56
+ this.inIframe = true;
57
+ return 'iframe-marker';
58
+ }
59
+
60
+ // 处理iframe内容区域
61
+ if (this.inIframe) {
62
+ // 匹配到空白字符结束
63
+ if (stream.eatWhile(/.*?$/)) {
64
+ this.inIframe = false;
65
+ return 'iframe-url';
66
+ }
67
+
68
+ // 遇到换行符自动结束
69
+ if (this.sol()) {
70
+ state.inIframe = false;
71
+ }
72
+ }
73
+
74
+ // 非iframe内容继续默认处理
75
+ stream.next();
76
+ return null;
77
+ },
78
+ };
79
+ }
80
+ }