@cherry-markdown/cherry-markdown-dev 0.8.58-dev

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 (319) hide show
  1. package/package.json +149 -0
  2. package/src/Cherry.config.js +625 -0
  3. package/src/Cherry.js +1104 -0
  4. package/src/CherryStatic.js +70 -0
  5. package/src/Editor.js +748 -0
  6. package/src/Engine.js +381 -0
  7. package/src/Event.js +140 -0
  8. package/src/Factory.js +180 -0
  9. package/src/Logger.js +31 -0
  10. package/src/Previewer.js +1183 -0
  11. package/src/Sanitizer.js +4 -0
  12. package/src/Sanitizer.node.js +7 -0
  13. package/src/UrlCache.js +98 -0
  14. package/src/addons/advance/cherry-table-echarts-plugin.js +170 -0
  15. package/src/addons/cherry-code-block-mermaid-plugin.js +158 -0
  16. package/src/addons/cherry-code-block-plantuml-plugin.js +106 -0
  17. package/src/core/HookCenter.js +297 -0
  18. package/src/core/HooksConfig.js +100 -0
  19. package/src/core/ParagraphBase.js +332 -0
  20. package/src/core/SentenceBase.js +65 -0
  21. package/src/core/SyntaxBase.js +194 -0
  22. package/src/core/hooks/AutoLink.js +232 -0
  23. package/src/core/hooks/BackgroundColor.js +46 -0
  24. package/src/core/hooks/Blockquote.js +70 -0
  25. package/src/core/hooks/Br.js +85 -0
  26. package/src/core/hooks/CodeBlock.js +446 -0
  27. package/src/core/hooks/Color.js +46 -0
  28. package/src/core/hooks/CommentReference.js +96 -0
  29. package/src/core/hooks/Detail.js +108 -0
  30. package/src/core/hooks/Emoji.config.js +1825 -0
  31. package/src/core/hooks/Emoji.js +119 -0
  32. package/src/core/hooks/Emphasis.js +113 -0
  33. package/src/core/hooks/Footnote.js +125 -0
  34. package/src/core/hooks/FrontMatter.js +51 -0
  35. package/src/core/hooks/Header.js +234 -0
  36. package/src/core/hooks/HighLight.js +37 -0
  37. package/src/core/hooks/Hr.js +52 -0
  38. package/src/core/hooks/HtmlBlock.js +184 -0
  39. package/src/core/hooks/Image.js +174 -0
  40. package/src/core/hooks/InlineCode.js +48 -0
  41. package/src/core/hooks/InlineMath.js +107 -0
  42. package/src/core/hooks/Link.js +160 -0
  43. package/src/core/hooks/List.js +264 -0
  44. package/src/core/hooks/MathBlock.js +103 -0
  45. package/src/core/hooks/Panel.js +145 -0
  46. package/src/core/hooks/Paragraph.js +84 -0
  47. package/src/core/hooks/Ruby.js +34 -0
  48. package/src/core/hooks/Size.js +51 -0
  49. package/src/core/hooks/Strikethrough.js +54 -0
  50. package/src/core/hooks/Sub.js +47 -0
  51. package/src/core/hooks/SuggestList.js +333 -0
  52. package/src/core/hooks/Suggester.js +707 -0
  53. package/src/core/hooks/Sup.js +47 -0
  54. package/src/core/hooks/Table.js +275 -0
  55. package/src/core/hooks/Toc.js +292 -0
  56. package/src/core/hooks/Transfer.js +47 -0
  57. package/src/core/hooks/Underline.js +37 -0
  58. package/src/index.core.js +29 -0
  59. package/src/index.engine.core.js +68 -0
  60. package/src/index.engine.js +28 -0
  61. package/src/index.js +32 -0
  62. package/src/libs/mermaidAPI.8.4.8.js +1 -0
  63. package/src/libs/mermaidAPI.8.5.2.js +42 -0
  64. package/src/libs/rawdeflate.js +1663 -0
  65. package/src/locales/en_US.js +139 -0
  66. package/src/locales/index.js +25 -0
  67. package/src/locales/ru_RU.js +139 -0
  68. package/src/locales/zh_CN.js +142 -0
  69. package/src/sass/base.scss +26 -0
  70. package/src/sass/bubble_formula.scss +166 -0
  71. package/src/sass/ch-icon.scss +118 -0
  72. package/src/sass/cherry.scss +1116 -0
  73. package/src/sass/components/bubble.scss +173 -0
  74. package/src/sass/components/shortcut_key_config.scss +108 -0
  75. package/src/sass/formula_utils_bubble.scss +82 -0
  76. package/src/sass/icon_template.scss +24 -0
  77. package/src/sass/icons/uEA03-list.svg +19 -0
  78. package/src/sass/icons/uEA04-check.svg +14 -0
  79. package/src/sass/icons/uEA09-square.svg +10 -0
  80. package/src/sass/icons/uEA0A-bold.svg +20 -0
  81. package/src/sass/icons/uEA0B-code.svg +18 -0
  82. package/src/sass/icons/uEA0C-color.svg +13 -0
  83. package/src/sass/icons/uEA0D-header.svg +8 -0
  84. package/src/sass/icons/uEA0E-image.svg +15 -0
  85. package/src/sass/icons/uEA0F-italic.svg +8 -0
  86. package/src/sass/icons/uEA10-link.svg +16 -0
  87. package/src/sass/icons/uEA11-ol.svg +21 -0
  88. package/src/sass/icons/uEA12-size.svg +11 -0
  89. package/src/sass/icons/uEA13-strike.svg +16 -0
  90. package/src/sass/icons/uEA14-table.svg +12 -0
  91. package/src/sass/icons/uEA15-ul.svg +17 -0
  92. package/src/sass/icons/uEA16-underline.svg +13 -0
  93. package/src/sass/icons/uEA17-word.svg +16 -0
  94. package/src/sass/icons/uEA18-blockquote.svg +11 -0
  95. package/src/sass/icons/uEA19-font.svg +10 -0
  96. package/src/sass/icons/uEA1F-insertClass.svg +39 -0
  97. package/src/sass/icons/uEA20-insertFlow.svg +8 -0
  98. package/src/sass/icons/uEA21-insertFormula.svg +23 -0
  99. package/src/sass/icons/uEA22-insertGantt.svg +13 -0
  100. package/src/sass/icons/uEA23-insertGraph.svg +13 -0
  101. package/src/sass/icons/uEA24-insertPie.svg +19 -0
  102. package/src/sass/icons/uEA25-insertSeq.svg +20 -0
  103. package/src/sass/icons/uEA26-insertState.svg +35 -0
  104. package/src/sass/icons/uEA27-line.svg +11 -0
  105. package/src/sass/icons/uEA28-preview.svg +18 -0
  106. package/src/sass/icons/uEA29-previewClose.svg +24 -0
  107. package/src/sass/icons/uEA2A-toc.svg +24 -0
  108. package/src/sass/icons/uEA2D-sub.svg +15 -0
  109. package/src/sass/icons/uEA2E-sup.svg +15 -0
  110. package/src/sass/icons/uEA2F-h1.svg +16 -0
  111. package/src/sass/icons/uEA30-h2.svg +20 -0
  112. package/src/sass/icons/uEA31-h3.svg +23 -0
  113. package/src/sass/icons/uEA32-h4.svg +16 -0
  114. package/src/sass/icons/uEA33-h5.svg +20 -0
  115. package/src/sass/icons/uEA34-h6.svg +17 -0
  116. package/src/sass/icons/uEA35-video.svg +20 -0
  117. package/src/sass/icons/uEA36-insert.svg +25 -0
  118. package/src/sass/icons/uEA37-little_table.svg +30 -0
  119. package/src/sass/icons/uEA38-pdf.svg +27 -0
  120. package/src/sass/icons/uEA39-checklist.svg +22 -0
  121. package/src/sass/icons/uEA40-close.svg +12 -0
  122. package/src/sass/icons/uEA41-fullscreen.svg +81 -0
  123. package/src/sass/icons/uEA42-minscreen.svg +77 -0
  124. package/src/sass/icons/uEA43-insertChart.svg +23 -0
  125. package/src/sass/icons/uEA44-question.svg +25 -0
  126. package/src/sass/icons/uEA45-settings.svg +32 -0
  127. package/src/sass/icons/uEA46-ok.svg +7 -0
  128. package/src/sass/icons/uEA47-br.svg +22 -0
  129. package/src/sass/icons/uEA48-normal.svg +15 -0
  130. package/src/sass/icons/uEA49-undo.svg +19 -0
  131. package/src/sass/icons/uEA50-redo.svg +21 -0
  132. package/src/sass/icons/uEA51-copy.svg +6 -0
  133. package/src/sass/icons/uEA52-phone.svg +5 -0
  134. package/src/sass/icons/uEA53-cherry-table-delete.svg +17 -0
  135. package/src/sass/icons/uEA54-cherry-table-insert-bottom.svg +16 -0
  136. package/src/sass/icons/uEA55-cherry-table-insert-left.svg +15 -0
  137. package/src/sass/icons/uEA56-cherry-table-insert-right.svg +16 -0
  138. package/src/sass/icons/uEA57-cherry-table-insert-top.svg +16 -0
  139. package/src/sass/icons/uEA58-sort-s.svg +13 -0
  140. package/src/sass/icons/uEA59-pinyin.svg +1 -0
  141. package/src/sass/icons/uEA5A-create.svg +24 -0
  142. package/src/sass/icons/uEA5B-download.svg +34 -0
  143. package/src/sass/icons/uEA5C-edit.svg +3 -0
  144. package/src/sass/icons/uEA5D-export.svg +53 -0
  145. package/src/sass/icons/uEA5E-folder-open.svg +3 -0
  146. package/src/sass/icons/uEA5F-folder.svg +3 -0
  147. package/src/sass/icons/uEA60-help.svg +5 -0
  148. package/src/sass/icons/uEA61-pen-fill.svg +13 -0
  149. package/src/sass/icons/uEA62-pen.svg +3 -0
  150. package/src/sass/icons/uEA64-tips.svg +5 -0
  151. package/src/sass/icons/uEA65-warn.svg +5 -0
  152. package/src/sass/icons/uEA66-mistake.svg +4 -0
  153. package/src/sass/icons/uEA67-success.svg +4 -0
  154. package/src/sass/icons/uEA68-danger.svg +4 -0
  155. package/src/sass/icons/uEA69-info.svg +5 -0
  156. package/src/sass/icons/uEA6A-primary.svg +5 -0
  157. package/src/sass/icons/uEA6B-warning.svg +5 -0
  158. package/src/sass/icons/uEA6C-justify.svg +19 -0
  159. package/src/sass/icons/uEA6D-justifyCenter.svg +19 -0
  160. package/src/sass/icons/uEA6E-justifyLeft.svg +19 -0
  161. package/src/sass/icons/uEA6F-justifyRight.svg +19 -0
  162. package/src/sass/icons/uEA70-chevronsLeft.svg +1 -0
  163. package/src/sass/icons/uEA71-chevronsRight.svg +1 -0
  164. package/src/sass/icons/uEA72-trendingUp.svg +1 -0
  165. package/src/sass/icons/uEA74-codeBlock.svg +1 -0
  166. package/src/sass/icons/uEA75-expand.svg +3 -0
  167. package/src/sass/icons/uEA76-unExpand.svg +3 -0
  168. package/src/sass/icons/uEA77-swap-vert.svg +1 -0
  169. package/src/sass/icons/uEA78-swap.svg +1 -0
  170. package/src/sass/icons/uEA79-keyboard.svg +1 -0
  171. package/src/sass/icons/uEA7A-command.svg +1 -0
  172. package/src/sass/icons/uEA7B-search.svg +1 -0
  173. package/src/sass/index.scss +3 -0
  174. package/src/sass/markdown.scss +668 -0
  175. package/src/sass/markdown_pure.scss +9 -0
  176. package/src/sass/prettyprint/prettyprint.scss +118 -0
  177. package/src/sass/previewer.scss +179 -0
  178. package/src/sass/print.scss +13 -0
  179. package/src/sass/prism/coy.scss +220 -0
  180. package/src/sass/prism/dark.scss +132 -0
  181. package/src/sass/prism/default.scss +143 -0
  182. package/src/sass/prism/funky.scss +133 -0
  183. package/src/sass/prism/okaidia.scss +126 -0
  184. package/src/sass/prism/one-dark.scss +440 -0
  185. package/src/sass/prism/one-light.scss +428 -0
  186. package/src/sass/prism/solarized-light.scss +153 -0
  187. package/src/sass/prism/tomorrow-night.scss +125 -0
  188. package/src/sass/prism/twilight.scss +202 -0
  189. package/src/sass/prism/vs-dark.scss +275 -0
  190. package/src/sass/prism/vs-light.scss +168 -0
  191. package/src/sass/themes/blue.scss +411 -0
  192. package/src/sass/themes/dark.scss +517 -0
  193. package/src/sass/themes/default.scss +255 -0
  194. package/src/sass/themes/green.scss +395 -0
  195. package/src/sass/themes/light.scss +368 -0
  196. package/src/sass/themes/red.scss +397 -0
  197. package/src/sass/themes/violet.scss +410 -0
  198. package/src/sass/variable.scss +84 -0
  199. package/src/toolbars/Bubble.js +234 -0
  200. package/src/toolbars/BubbleFormula.js +298 -0
  201. package/src/toolbars/BubbleTable.js +147 -0
  202. package/src/toolbars/FloatMenu.js +131 -0
  203. package/src/toolbars/HiddenToolbar.js +36 -0
  204. package/src/toolbars/HookCenter.js +234 -0
  205. package/src/toolbars/MenuBase.js +569 -0
  206. package/src/toolbars/PreviewerBubble.js +608 -0
  207. package/src/toolbars/ShortcutKeyConfigPanel.js +345 -0
  208. package/src/toolbars/Sidebar.js +36 -0
  209. package/src/toolbars/Toc.js +242 -0
  210. package/src/toolbars/Toolbar.js +449 -0
  211. package/src/toolbars/ToolbarRight.js +37 -0
  212. package/src/toolbars/hooks/Audio.js +79 -0
  213. package/src/toolbars/hooks/BarTable.js +41 -0
  214. package/src/toolbars/hooks/Bold.js +73 -0
  215. package/src/toolbars/hooks/Br.js +34 -0
  216. package/src/toolbars/hooks/ChangeLocale.js +62 -0
  217. package/src/toolbars/hooks/ChatGpt.js +182 -0
  218. package/src/toolbars/hooks/CheckList.js +41 -0
  219. package/src/toolbars/hooks/Code.js +49 -0
  220. package/src/toolbars/hooks/CodeTheme.js +66 -0
  221. package/src/toolbars/hooks/Color.js +298 -0
  222. package/src/toolbars/hooks/Copy.js +141 -0
  223. package/src/toolbars/hooks/Detail.js +69 -0
  224. package/src/toolbars/hooks/DrawIo.js +57 -0
  225. package/src/toolbars/hooks/Export.js +49 -0
  226. package/src/toolbars/hooks/File.js +79 -0
  227. package/src/toolbars/hooks/Formula.js +69 -0
  228. package/src/toolbars/hooks/FullScreen.js +50 -0
  229. package/src/toolbars/hooks/Graph.js +263 -0
  230. package/src/toolbars/hooks/H1.js +71 -0
  231. package/src/toolbars/hooks/H2.js +71 -0
  232. package/src/toolbars/hooks/H3.js +71 -0
  233. package/src/toolbars/hooks/Header.js +118 -0
  234. package/src/toolbars/hooks/Hr.js +35 -0
  235. package/src/toolbars/hooks/Image.js +91 -0
  236. package/src/toolbars/hooks/InlineCode.js +53 -0
  237. package/src/toolbars/hooks/Insert.js +193 -0
  238. package/src/toolbars/hooks/Italic.js +72 -0
  239. package/src/toolbars/hooks/Justify.js +49 -0
  240. package/src/toolbars/hooks/LineTable.js +41 -0
  241. package/src/toolbars/hooks/Link.js +49 -0
  242. package/src/toolbars/hooks/List.js +55 -0
  243. package/src/toolbars/hooks/MobilePreview.js +44 -0
  244. package/src/toolbars/hooks/Ol.js +41 -0
  245. package/src/toolbars/hooks/Panel.js +140 -0
  246. package/src/toolbars/hooks/Pdf.js +78 -0
  247. package/src/toolbars/hooks/Publish.js +123 -0
  248. package/src/toolbars/hooks/QuickTable.js +43 -0
  249. package/src/toolbars/hooks/Quote.js +45 -0
  250. package/src/toolbars/hooks/Redo.js +33 -0
  251. package/src/toolbars/hooks/Ruby.js +59 -0
  252. package/src/toolbars/hooks/Search.js +53 -0
  253. package/src/toolbars/hooks/Settings.js +220 -0
  254. package/src/toolbars/hooks/ShortcutKey.js +62 -0
  255. package/src/toolbars/hooks/Size.js +118 -0
  256. package/src/toolbars/hooks/Split.js +37 -0
  257. package/src/toolbars/hooks/Strikethrough.js +71 -0
  258. package/src/toolbars/hooks/Sub.js +58 -0
  259. package/src/toolbars/hooks/Sup.js +58 -0
  260. package/src/toolbars/hooks/SwitchModel.js +56 -0
  261. package/src/toolbars/hooks/Table.js +56 -0
  262. package/src/toolbars/hooks/Theme.js +62 -0
  263. package/src/toolbars/hooks/Toc.js +35 -0
  264. package/src/toolbars/hooks/TogglePreview.js +91 -0
  265. package/src/toolbars/hooks/Ul.js +41 -0
  266. package/src/toolbars/hooks/Underline.js +68 -0
  267. package/src/toolbars/hooks/Undo.js +30 -0
  268. package/src/toolbars/hooks/Video.js +79 -0
  269. package/src/toolbars/hooks/Word.js +78 -0
  270. package/src/toolbars/hooks/WordCount.js +106 -0
  271. package/src/utils/autoindent.js +58 -0
  272. package/src/utils/cm-search-replace.js +794 -0
  273. package/src/utils/code-preview-language-setting.js +180 -0
  274. package/src/utils/codeBlockContentHandler.js +400 -0
  275. package/src/utils/config.js +174 -0
  276. package/src/utils/copy.js +55 -0
  277. package/src/utils/dialog.js +214 -0
  278. package/src/utils/dom.js +163 -0
  279. package/src/utils/downloadUtil.js +23 -0
  280. package/src/utils/env.js +22 -0
  281. package/src/utils/error.js +61 -0
  282. package/src/utils/event.js +38 -0
  283. package/src/utils/export.js +166 -0
  284. package/src/utils/file.js +164 -0
  285. package/src/utils/formulaUtilsHandler.js +232 -0
  286. package/src/utils/htmlparser.js +976 -0
  287. package/src/utils/image.js +99 -0
  288. package/src/utils/imgSizeHandler.js +279 -0
  289. package/src/utils/lazyLoadImg.js +327 -0
  290. package/src/utils/lineFeed.js +49 -0
  291. package/src/utils/listContentHandler.js +227 -0
  292. package/src/utils/lookbehind-replace.js +81 -0
  293. package/src/utils/mathjax.js +89 -0
  294. package/src/utils/myersDiff.js +211 -0
  295. package/src/utils/pasteHelper.js +253 -0
  296. package/src/utils/platformTransform.js +71 -0
  297. package/src/utils/recount-pos.js +59 -0
  298. package/src/utils/regexp.js +295 -0
  299. package/src/utils/sanitize.js +477 -0
  300. package/src/utils/selection.js +50 -0
  301. package/src/utils/shortcutKey.js +291 -0
  302. package/src/utils/svgUtils.js +96 -0
  303. package/src/utils/tableContentHandler.js +876 -0
  304. package/test/core/CommonMark.spec.ts +62 -0
  305. package/test/core/hooks/AutoLink.spec.ts +28 -0
  306. package/test/core/hooks/List.spec.ts +79 -0
  307. package/test/core/hooks/__snapshots__/List.spec.ts.snap +11 -0
  308. package/test/example.md +778 -0
  309. package/test/node.js +10 -0
  310. package/test/suites/commonmark.spec.json +5218 -0
  311. package/test/tsconfig.test.json +6 -0
  312. package/test/utils/regexp.spec.ts +28 -0
  313. package/types/cherry.d.ts +675 -0
  314. package/types/codemirror.d.ts +22 -0
  315. package/types/editor.d.ts +72 -0
  316. package/types/global.d.ts +16 -0
  317. package/types/menus.d.ts +24 -0
  318. package/types/previewer.d.ts +53 -0
  319. package/types/syntax.d.ts +52 -0
@@ -0,0 +1,707 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tencent is pleased to support the open source community by making CherryMarkdown available.
4
+ *
5
+ * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
6
+ * The below software in this distribution may have been modified by THL A29 Limited ("Tencent Modifications").
7
+ *
8
+ * All Tencent Modifications are Copyright (C) THL A29 Limited.
9
+ *
10
+ * CherryMarkdown is licensed under the Apache License, Version 2.0 (the "License");
11
+ * you may not use this file except in compliance with the License.
12
+ * You may obtain a copy of the License at
13
+ *
14
+ * http://www.apache.org/licenses/LICENSE-2.0
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ import escapeRegExp from 'lodash/escapeRegExp';
23
+ import SyntaxBase from '@/core/SyntaxBase';
24
+ import { allSuggestList, suggesterKeywords } from '@/core/hooks/SuggestList';
25
+ import { Pass } from 'codemirror/src/util/misc';
26
+ import { isLookbehindSupported } from '@/utils/regexp';
27
+ import { replaceLookbehind } from '@/utils/lookbehind-replace';
28
+ import { isBrowser } from '@/utils/env';
29
+
30
+ /**
31
+ * @typedef {import('codemirror')} CodeMirror
32
+ */
33
+
34
+ /**
35
+ * @typedef { Object } SuggestListItemObject 推荐列表项对象
36
+ * @property { string } icon 图标
37
+ * @property { string } label 候选列表回显的内容
38
+ * @property { string } value 点击候选项的时候回填的值
39
+ * @property { string } keyword 关键词,通过关键词控制候选项的显隐
40
+ * @typedef { SuggestListItemObject | string } SuggestListItem 推荐列表项
41
+ * @typedef { Array<SuggestListItem> } SuggestList 推荐列表
42
+ */
43
+
44
+ /**
45
+ * @typedef {object} SuggesterConfigItem
46
+ * @property {function(string, function(SuggestList): void): void} suggestList
47
+ * @property {string} keyword
48
+ * @property {function} suggestListRender
49
+ * @property {function} echo
50
+ * @typedef {object} SuggesterConfig
51
+ * @property {Array<SuggesterConfigItem>} suggester
52
+ */
53
+
54
+ export default class Suggester extends SyntaxBase {
55
+ static HOOK_NAME = 'suggester';
56
+
57
+ constructor({ config, cherry }) {
58
+ /**
59
+ * config.suggester 内容
60
+ * [{
61
+ * 请求url
62
+ suggestList: '',
63
+ 唤醒关键字
64
+ keyword: '@',
65
+ 建议模板 function
66
+ suggestListRender(valueArray) {
67
+
68
+ },
69
+ 回填回调 function
70
+ echo(value) {
71
+
72
+ }]
73
+ *
74
+ */
75
+
76
+ super({ needCache: true });
77
+
78
+ this.config = config;
79
+ this.$cherry = cherry;
80
+ this.suggesterPanel = new SuggesterPanel(cherry);
81
+
82
+ if (!this.inited) {
83
+ this.initConfig(this.config);
84
+ }
85
+
86
+ this.RULE = this.rule();
87
+ }
88
+
89
+ afterInit(callback) {
90
+ // node环境下直接跳过输入联想
91
+ if (!isBrowser()) {
92
+ return;
93
+ }
94
+ if (typeof callback === 'function') {
95
+ callback();
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 初始化配置
101
+ * @param {SuggesterConfig} config
102
+ */
103
+ initConfig(config) {
104
+ let { suggester } = config;
105
+
106
+ this.suggester = {};
107
+ const defaultSuggest = [];
108
+ const that = this;
109
+ // 默认的唤醒关键字
110
+ for (const suggesterKeyword of suggesterKeywords) {
111
+ defaultSuggest.push({
112
+ keyword: suggesterKeyword,
113
+ suggestList(_word, callback) {
114
+ // 将word全转成小写
115
+ const word = _word.toLowerCase();
116
+ const systemSuggestList = allSuggestList(suggesterKeyword, that.$locale);
117
+ // 加个空格就直接退出联想
118
+ if (/^\s$/.test(word)) {
119
+ callback(false);
120
+ return;
121
+ }
122
+ const keyword = word
123
+ .replace(/\s+/g, '') // 删掉空格,避免产生不必要的空数组元素
124
+ .replace(new RegExp(`^${suggesterKeyword}`, 'g'), '') // 删掉word当中suggesterKeywords出现的字符
125
+ .replace(/^[#]+/, '#')
126
+ .replace(/^[/]+/, '/')
127
+ .split('')
128
+ .join('.*?');
129
+ // 匹配任何包含 "keyword" 的字符串,无论 "keyword" 是在字符串的开头、中间还是结尾,并且不区分大小写
130
+ const test = new RegExp(`^.*?${keyword}.*?$`, 'i');
131
+ const suggestList = systemSuggestList.filter((item) => {
132
+ // 处理精确匹配
133
+ if (item.exactMatch) {
134
+ return !word || item.keyword === word;
135
+ }
136
+ // TODO: 首次联想的时候会把所有的候选项列出来,后续可以增加一些机制改成默认拉取一部分候选项
137
+ return !word || test.test(item.keyword);
138
+ });
139
+ // 当没有候选项时直接推出联想
140
+ callback(suggestList.length === 0 ? false : suggestList);
141
+ },
142
+ echo() {
143
+ return '';
144
+ },
145
+ });
146
+ }
147
+ if (!suggester) {
148
+ suggester = defaultSuggest;
149
+ } else {
150
+ suggester = defaultSuggest.concat(suggester);
151
+ }
152
+
153
+ suggester.forEach((configItem) => {
154
+ if (!configItem.suggestList) {
155
+ console.warn('[cherry-suggester]: the suggestList of config is missing.');
156
+ return;
157
+ }
158
+
159
+ if (!configItem.keyword) {
160
+ configItem.keyword = '@';
161
+ }
162
+ this.suggester[configItem.keyword] = configItem;
163
+ });
164
+
165
+ // 反复初始化时, 缓存还在, dom 已更新情况
166
+ if (this.suggesterPanel.hasEditor()) {
167
+ this.suggesterPanel.editor = null;
168
+ }
169
+
170
+ this.inited = true;
171
+ }
172
+
173
+ makeHtml(str) {
174
+ if (!this.RULE.reg) return str;
175
+ if (!this.suggesterPanel.hasEditor() && isBrowser()) {
176
+ const { editor } = this.$engine.$cherry;
177
+ this.suggesterPanel.setEditor(editor);
178
+ this.suggesterPanel.setSuggester(this.suggester);
179
+ this.suggesterPanel.bindEvent();
180
+ }
181
+ if (isLookbehindSupported()) {
182
+ return str.replace(this.RULE.reg, this.toHtml.bind(this));
183
+ }
184
+ return replaceLookbehind(str, this.RULE.reg, this.toHtml.bind(this), true, 1);
185
+ }
186
+
187
+ toHtml(wholeMatch, leadingChar, keyword, text) {
188
+ if (text) {
189
+ return (
190
+ this.suggester[keyword]?.echo?.call(this, text) ||
191
+ `${leadingChar}<span class="cherry-suggestion">${keyword}${text}</span>`
192
+ );
193
+ }
194
+ if (this.suggester[keyword]?.echo === false) {
195
+ return `${leadingChar}`;
196
+ }
197
+ if (!this.suggester[keyword]) {
198
+ return leadingChar + text;
199
+ }
200
+ return text ? leadingChar + text : `${leadingChar}`;
201
+ }
202
+
203
+ rule() {
204
+ if (!this.config?.suggester || Object.keys(this.config?.suggester).length <= 0) {
205
+ return {};
206
+ }
207
+
208
+ let suggester;
209
+ if (Array.isArray(this.config.suggester)) {
210
+ suggester = this.config.suggester.map((obj) => obj.keyword || '');
211
+ } else {
212
+ suggester = Object.keys(this.config.suggester).map((key) => this.config.suggester[key].keyword || '');
213
+ }
214
+
215
+ const keys = suggester.map((key) => escapeRegExp(key)).join('|');
216
+ const reg = new RegExp(
217
+ `${isLookbehindSupported() ? '((?<!\\\\))[ ]' : '(^|[^\\\\])[ ]'}(${keys})(([^${keys}\\s])+)`,
218
+ 'g',
219
+ );
220
+ return /** @type {any} */ ({
221
+ reg,
222
+ });
223
+ }
224
+
225
+ mounted() {
226
+ if (!this.suggesterPanel.hasEditor() && isBrowser()) {
227
+ const { editor } = this.$engine.$cherry;
228
+ this.suggesterPanel.setEditor(editor);
229
+ this.suggesterPanel.setSuggester(this.suggester);
230
+ this.suggesterPanel.bindEvent();
231
+ }
232
+ }
233
+ }
234
+
235
+ class SuggesterPanel {
236
+ constructor(cherry) {
237
+ this.searchCache = false;
238
+ this.searchKeyCache = [];
239
+ this.optionList = [];
240
+ this.cursorMove = true;
241
+ this.suggesterConfig = {};
242
+ this.$cherry = cherry;
243
+ }
244
+
245
+ /**
246
+ * 如果没有panel,则尝试初始化一个,在node模式不初始化
247
+ */
248
+ tryCreatePanel() {
249
+ if (!this.$suggesterPanel && isBrowser() && document) {
250
+ this.$cherry.wrapperDom.appendChild(this.createDom(this.panelWrap));
251
+ this.$suggesterPanel = this.$cherry.wrapperDom.querySelector('.cherry-suggester-panel');
252
+ }
253
+ }
254
+
255
+ panelWrap = `<div class="cherry-suggester-panel"></div>`;
256
+
257
+ hasEditor() {
258
+ return !!this.editor && !!this.editor.editor.display && !!this.editor.editor.display.wrapper;
259
+ }
260
+
261
+ /**
262
+ * 设置编辑器
263
+ * @param {import('@/Editor').default} editor
264
+ */
265
+ setEditor(editor) {
266
+ this.editor = editor;
267
+ }
268
+
269
+ setSuggester(suggester) {
270
+ this.suggesterConfig = suggester;
271
+ }
272
+
273
+ bindEvent() {
274
+ if (!this.editor.options.showSuggestList) {
275
+ return;
276
+ }
277
+ let keyAction = false;
278
+ this.editor.editor.on('change', (codemirror, evt) => {
279
+ keyAction = true;
280
+ this.onCodeMirrorChange(codemirror, evt);
281
+ });
282
+
283
+ this.editor.editor.on('keydown', (codemirror, evt) => {
284
+ keyAction = true;
285
+ if (this.enableRelate()) {
286
+ this.onKeyDown(codemirror, evt);
287
+ }
288
+ });
289
+
290
+ this.editor.editor.on('cursorActivity', () => {
291
+ // 当编辑区光标位置改变时触发
292
+ if (!keyAction) {
293
+ this.stopRelate();
294
+ }
295
+ keyAction = false;
296
+ });
297
+
298
+ const extraKeys = this.editor.editor.getOption('extraKeys');
299
+ const decorateKeys = ['Up', 'Down', 'Enter'];
300
+ decorateKeys.forEach((key) => {
301
+ if (typeof extraKeys[key] === 'function') {
302
+ const proxyTarget = extraKeys[key];
303
+ extraKeys[key] = (codemirror) => {
304
+ if (this.cursorMove) {
305
+ const res = proxyTarget.call(codemirror, codemirror);
306
+
307
+ if (res) {
308
+ return res;
309
+ }
310
+ // logic to decide whether to move up or not
311
+ // return Pass.toString();
312
+ }
313
+ };
314
+ } else if (!extraKeys[key]) {
315
+ extraKeys[key] = () => {
316
+ if (this.cursorMove) {
317
+ // logic to decide whether to move up or not
318
+ return Pass.toString();
319
+ }
320
+ };
321
+ } else if (typeof extraKeys[key] === 'string') {
322
+ const command = extraKeys[key];
323
+ extraKeys[key] = (codemirror) => {
324
+ if (this.cursorMove) {
325
+ this.editor.editor.execCommand(command);
326
+
327
+ // logic to decide whether to move up or not
328
+ // return Pass.toString();
329
+ }
330
+ };
331
+ }
332
+ });
333
+
334
+ this.editor.editor.setOption('extraKeys', extraKeys);
335
+
336
+ this.editor.editor.on('scroll', (codemirror, evt) => {
337
+ if (!this.searchCache) {
338
+ return;
339
+ }
340
+ // 当编辑器滚动时触发
341
+ this.relocatePanel(this.editor.editor);
342
+ });
343
+
344
+ this.onClickPanelItem();
345
+ }
346
+
347
+ onClickPanelItem() {
348
+ this.tryCreatePanel();
349
+ this.$suggesterPanel.addEventListener(
350
+ 'click',
351
+ (evt) => {
352
+ const idx = isChildNode(this.$suggesterPanel, evt.target);
353
+ if (idx > -1) {
354
+ this.pasteSelectResult(idx);
355
+ }
356
+ this.stopRelate();
357
+ },
358
+ false,
359
+ );
360
+
361
+ function isChildNode(parent, node) {
362
+ let res = -1;
363
+ parent.childNodes.forEach((item, idx) => (item === node ? (res = idx) : ''));
364
+ return res;
365
+ }
366
+ }
367
+
368
+ showSuggesterPanel({ left, top, items }) {
369
+ this.tryCreatePanel();
370
+ if (!this.$suggesterPanel && isBrowser()) {
371
+ this.$cherry.wrapperDom.appendChild(this.createDom(this.panelWrap));
372
+ this.$suggesterPanel = this.$cherry.wrapperDom.querySelector('.cherry-suggester-panel');
373
+ }
374
+ this.updatePanel(items);
375
+ this.$suggesterPanel.style.left = `${left}px`;
376
+ this.$suggesterPanel.style.top = `${top}px`;
377
+ this.$suggesterPanel.style.display = 'block';
378
+ this.$suggesterPanel.style.position = 'absolute';
379
+ this.$suggesterPanel.style.zIndex = '100';
380
+ }
381
+
382
+ hideSuggesterPanel() {
383
+ this.tryCreatePanel();
384
+ // const $suggesterPanel = document.querySelector('.cherry-suggester-panel');
385
+ if (this.$suggesterPanel) {
386
+ this.$suggesterPanel.style.display = 'none';
387
+ }
388
+ }
389
+
390
+ /**
391
+ * 更新suggesterPanel
392
+ * @param {SuggestList} suggestList
393
+ */
394
+ updatePanel(suggestList) {
395
+ this.tryCreatePanel();
396
+ let defaultValue = suggestList
397
+ .map((suggest, idx) => {
398
+ if (typeof suggest === 'object' && suggest !== null) {
399
+ let renderContent = suggest.label;
400
+ if (suggest?.icon) {
401
+ renderContent = `<i class="ch-icon ch-icon-${suggest.icon}"></i>${renderContent}`;
402
+ }
403
+ return this.renderPanelItem(renderContent, false);
404
+ }
405
+ return this.renderPanelItem(suggest, false);
406
+ })
407
+ .join('');
408
+ /**
409
+ * @type { SuggesterConfigItem }
410
+ */
411
+ const suggesterConfig = this.suggesterConfig[this.keyword];
412
+ // 用户自定义渲染逻辑 suggestListRender
413
+ if (suggesterConfig && typeof suggesterConfig.suggestListRender === 'function') {
414
+ defaultValue = suggesterConfig.suggestListRender.call(this, suggestList) || defaultValue;
415
+ }
416
+
417
+ this.$suggesterPanel.innerHTML = ''; // 清空
418
+ if (typeof defaultValue === 'string') {
419
+ this.$suggesterPanel.innerHTML = defaultValue;
420
+ } else if (Array.isArray(defaultValue) && defaultValue.length > 0) {
421
+ defaultValue.forEach((item) => {
422
+ this.$suggesterPanel.appendChild(item);
423
+ });
424
+ } else if (typeof defaultValue === 'object' && defaultValue.nodeType === 1) {
425
+ this.$suggesterPanel.appendChild(defaultValue);
426
+ }
427
+ }
428
+
429
+ /**
430
+ * 渲染suggesterPanel item
431
+ * @param {string} item 渲染内容
432
+ * @param {boolean} selected 是否选中
433
+ * @returns {string} html
434
+ */
435
+ renderPanelItem(item, selected) {
436
+ if (selected) {
437
+ return `<div class="cherry-suggester-panel__item cherry-suggester-panel__item--selected">${item}</div>`;
438
+ }
439
+ return `<div class="cherry-suggester-panel__item">${item}</div>`;
440
+ }
441
+
442
+ createDom(string = '') {
443
+ if (!this.template) {
444
+ this.template = document.createElement('div');
445
+ }
446
+
447
+ this.template.innerHTML = string.trim();
448
+
449
+ // Change this to div.childNodes to support multiple top-level nodes
450
+ const frag = document.createDocumentFragment();
451
+ Array.prototype.map.call(this.template.childNodes, (item, idx) => {
452
+ frag.appendChild(item);
453
+ });
454
+ return frag;
455
+ }
456
+
457
+ // 面板重定位
458
+ relocatePanel(codemirror) {
459
+ // 找到光标位置来确定候选框位置
460
+ let $cursor = this.$cherry.wrapperDom.querySelector('.CodeMirror-cursors .CodeMirror-cursor');
461
+ // 当editor选中某一内容时,".CodeMirror-cursor"会消失,此时通过定位".selected"来确定候选框位置
462
+ if (!$cursor) {
463
+ $cursor = this.$cherry.wrapperDom.querySelector('.CodeMirror-selected');
464
+ }
465
+ if (!$cursor) {
466
+ return false;
467
+ }
468
+ const editorDomRect = this.$cherry.wrapperDom.getBoundingClientRect();
469
+ const rect = $cursor.getBoundingClientRect();
470
+ const top = rect.top + rect.height + 5 - editorDomRect.top;
471
+ const left = rect.left - editorDomRect.left;
472
+ this.showSuggesterPanel({ left, top, items: this.optionList });
473
+ }
474
+
475
+ /**
476
+ * 获取光标位置
477
+ * @param {CodeMirror} codemirror
478
+ * @returns {{ left: number, top: number }}
479
+ */
480
+ getCursorPos(codemirror) {
481
+ const $cursor = document.querySelector('.CodeMirror-cursors .CodeMirror-cursor');
482
+ if (!$cursor) return null;
483
+ const pos = codemirror.getCursor();
484
+ const lineHeight = codemirror.lineInfo(pos.line).handle.height;
485
+ const rect = $cursor.getBoundingClientRect();
486
+ const top = rect.top + lineHeight;
487
+ const { left } = rect;
488
+ return { left, top };
489
+ }
490
+
491
+ // 开启关联
492
+ startRelate(codemirror, keyword, from) {
493
+ this.cursorFrom = from;
494
+ this.keyword = keyword;
495
+ this.searchCache = true;
496
+ this.relocatePanel(codemirror);
497
+ }
498
+
499
+ // 关闭关联
500
+ stopRelate() {
501
+ this.hideSuggesterPanel();
502
+ this.cursorFrom = null;
503
+ this.cursorTo = null;
504
+ this.keyword = '';
505
+ this.searchKeyCache = [];
506
+ this.searchCache = false;
507
+ this.cursorMove = true;
508
+ this.optionList = [];
509
+ }
510
+
511
+ /**
512
+ * 粘贴选择结果
513
+ * @param {number} idx 选择的结果索引
514
+ * @param {KeyboardEvent} evt 键盘事件
515
+ */
516
+ pasteSelectResult(idx, evt) {
517
+ if (!this.cursorTo || this.cursorTo === this.cursorFrom) {
518
+ this.cursorTo = JSON.parse(JSON.stringify(this.cursorFrom));
519
+ }
520
+ if (!this.cursorTo) {
521
+ return;
522
+ }
523
+ this.cursorTo.ch += 1;
524
+ const { cursorFrom, cursorTo } = this; // 缓存光标位置
525
+ if (this.optionList[idx]) {
526
+ let result = '';
527
+ if (
528
+ typeof this.optionList[idx] === 'object' &&
529
+ this.optionList[idx] !== null &&
530
+ typeof this.optionList[idx].value === 'string'
531
+ ) {
532
+ result = this.optionList[idx].value;
533
+ } else if (
534
+ typeof this.optionList[idx] === 'object' &&
535
+ this.optionList[idx] !== null &&
536
+ typeof this.optionList[idx].value === 'function'
537
+ ) {
538
+ result = this.optionList[idx].value();
539
+ } else {
540
+ result = ` ${this.keyword}${this.optionList[idx]} `;
541
+ }
542
+ // this.cursorTo.ch = this.cursorFrom.ch + result.length;
543
+ if (result) {
544
+ this.editor.editor.replaceRange(result, cursorFrom, cursorTo);
545
+ }
546
+ // 控制光标左移若干位
547
+ if (this.optionList[idx].goLeft) {
548
+ const cursor = this.editor.editor.getCursor();
549
+ this.editor.editor.setCursor(cursor.line, cursor.ch - this.optionList[idx].goLeft);
550
+ }
551
+ // 控制光标上移若干位
552
+ if (this.optionList[idx].goTop) {
553
+ const cursor = this.editor.editor.getCursor();
554
+ this.editor.editor.setCursor(cursor.line - this.optionList[idx].goTop, cursor.ch);
555
+ }
556
+ // 选中某个范围
557
+ if (this.optionList[idx].selection) {
558
+ const { line } = this.editor.editor.getCursor();
559
+ const { ch } = this.editor.editor.getCursor();
560
+ this.editor.editor.setSelection(
561
+ { line, ch: ch - this.optionList[idx].selection.from },
562
+ { line, ch: ch - this.optionList[idx].selection.to },
563
+ );
564
+ }
565
+ }
566
+ }
567
+
568
+ /**
569
+ * 寻找当前选中项的索引
570
+ * @returns {number}
571
+ */
572
+ findSelectedItemIndex() {
573
+ return Array.prototype.findIndex.call(this.$suggesterPanel.childNodes, (item) =>
574
+ item.classList.contains('cherry-suggester-panel__item--selected'),
575
+ );
576
+ }
577
+
578
+ enableRelate() {
579
+ return this.searchCache;
580
+ }
581
+
582
+ /**
583
+ * codeMirror change事件
584
+ * @param {CodeMirror.Editor} codemirror
585
+ * @param {CodeMirror.EditorChange} evt
586
+ * @returns
587
+ */
588
+ onCodeMirrorChange(codemirror, evt) {
589
+ const { text, from, to, origin } = evt;
590
+ const changeValue = text.length === 1 ? text[0] : '';
591
+
592
+ // 首次输入命中关键词的时候开启联想
593
+ if (!this.enableRelate() && this.suggesterConfig[changeValue]) {
594
+ this.startRelate(codemirror, changeValue, from);
595
+ }
596
+ if (this.enableRelate() && (changeValue || origin === '+delete')) {
597
+ this.cursorTo = to;
598
+ if (changeValue) {
599
+ this.searchKeyCache.push(changeValue);
600
+ } else if (origin === '+delete') {
601
+ this.searchKeyCache.pop();
602
+ if (this.searchKeyCache.length === 0) {
603
+ this.stopRelate();
604
+ return;
605
+ }
606
+ }
607
+ // 展示推荐列表
608
+ if (typeof this.suggesterConfig[this.keyword]?.suggestList === 'function') {
609
+ // 请求api 返回结果拼凑
610
+ this.suggesterConfig[this.keyword].suggestList(this.searchKeyCache.join(''), (res) => {
611
+ // 如果返回了false,则强制退出联想
612
+ if (res === false) {
613
+ this.stopRelate();
614
+ return;
615
+ }
616
+ // 回显命中的结果
617
+ this.optionList = !res || !res.length ? [] : res;
618
+ this.updatePanel(this.optionList);
619
+ });
620
+ }
621
+ }
622
+ }
623
+
624
+ /**
625
+ * 监听方向键选择 options
626
+ * @param {CodeMirror.Editor} codemirror
627
+ * @param {KeyboardEvent} evt
628
+ */
629
+ onKeyDown(codemirror, evt) {
630
+ this.tryCreatePanel();
631
+ if (!this.$suggesterPanel) {
632
+ return false;
633
+ }
634
+ const { keyCode } = evt;
635
+ // up down
636
+ if ([38, 40].includes(keyCode)) {
637
+ // issue 558
638
+ if (this.optionList.length === 0) {
639
+ setTimeout(() => {
640
+ this.stopRelate();
641
+ }, 0);
642
+ return;
643
+ }
644
+
645
+ this.cursorMove = false;
646
+
647
+ const selectedItem =
648
+ this.$suggesterPanel.querySelector('.cherry-suggester-panel__item--selected') ||
649
+ this.$suggesterPanel.querySelector('.cherry-suggester-panel__item:last-child');
650
+ let nextElement = null;
651
+ if (keyCode === 38 && !selectedItem.previousElementSibling) {
652
+ nextElement = this.$suggesterPanel.lastElementChild;
653
+ // codemirror.focus();
654
+ } else if (keyCode === 40 && !selectedItem.nextElementSibling) {
655
+ nextElement = this.$suggesterPanel.firstElementChild;
656
+ // codemirror.focus();
657
+ } else {
658
+ if (keyCode === 38) {
659
+ nextElement = selectedItem.previousElementSibling;
660
+ } else if (keyCode === 40) {
661
+ nextElement = selectedItem.nextElementSibling;
662
+ }
663
+ }
664
+
665
+ selectedItem.classList.remove('cherry-suggester-panel__item--selected');
666
+
667
+ nextElement.classList.add('cherry-suggester-panel__item--selected');
668
+
669
+ // 提示面板高度
670
+ const suggestPanelHeight = this.$suggesterPanel.offsetHeight;
671
+ // 可视区域范围上端
672
+ const viewTop = this.$suggesterPanel.scrollTop;
673
+ // 可视区域范围下端
674
+ const viewBottom = viewTop + suggestPanelHeight;
675
+ // item的上端
676
+ const nextEleTop = nextElement.offsetTop;
677
+ // item高度
678
+ const nextEleHeight = nextElement.offsetHeight;
679
+ // 当前元素全部或部分在可视区域之外,就滚动
680
+ if (nextEleTop < viewTop || nextEleTop + nextEleHeight > viewBottom) {
681
+ this.$suggesterPanel.scrollTop = nextEleTop - suggestPanelHeight / 2;
682
+ }
683
+ } else if (keyCode === 13) {
684
+ const index = this.findSelectedItemIndex();
685
+ if (index >= 0) {
686
+ evt.stopPropagation();
687
+ this.cursorMove = false;
688
+ this.pasteSelectResult(index, evt);
689
+ codemirror.focus();
690
+ }
691
+ // const cache = JSON.parse(JSON.stringify(this.cursorTo));
692
+ // setTimeout(() => {
693
+ // codemirror.setCursor(cache);
694
+ // }, 100);
695
+ setTimeout(() => {
696
+ this.stopRelate();
697
+ }, 0);
698
+ } else if (keyCode === 27 || keyCode === 0x25 || keyCode === 0x27) {
699
+ // 按下esc或者←、→键的时候退出联想
700
+ evt.stopPropagation();
701
+ codemirror.focus();
702
+ setTimeout(() => {
703
+ this.stopRelate();
704
+ }, 0);
705
+ }
706
+ }
707
+ }