@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.
- package/package.json +149 -0
- package/src/Cherry.config.js +625 -0
- package/src/Cherry.js +1104 -0
- package/src/CherryStatic.js +70 -0
- package/src/Editor.js +748 -0
- package/src/Engine.js +381 -0
- package/src/Event.js +140 -0
- package/src/Factory.js +180 -0
- package/src/Logger.js +31 -0
- package/src/Previewer.js +1183 -0
- package/src/Sanitizer.js +4 -0
- package/src/Sanitizer.node.js +7 -0
- package/src/UrlCache.js +98 -0
- package/src/addons/advance/cherry-table-echarts-plugin.js +170 -0
- package/src/addons/cherry-code-block-mermaid-plugin.js +158 -0
- package/src/addons/cherry-code-block-plantuml-plugin.js +106 -0
- package/src/core/HookCenter.js +297 -0
- package/src/core/HooksConfig.js +100 -0
- package/src/core/ParagraphBase.js +332 -0
- package/src/core/SentenceBase.js +65 -0
- package/src/core/SyntaxBase.js +194 -0
- package/src/core/hooks/AutoLink.js +232 -0
- package/src/core/hooks/BackgroundColor.js +46 -0
- package/src/core/hooks/Blockquote.js +70 -0
- package/src/core/hooks/Br.js +85 -0
- package/src/core/hooks/CodeBlock.js +446 -0
- package/src/core/hooks/Color.js +46 -0
- package/src/core/hooks/CommentReference.js +96 -0
- package/src/core/hooks/Detail.js +108 -0
- package/src/core/hooks/Emoji.config.js +1825 -0
- package/src/core/hooks/Emoji.js +119 -0
- package/src/core/hooks/Emphasis.js +113 -0
- package/src/core/hooks/Footnote.js +125 -0
- package/src/core/hooks/FrontMatter.js +51 -0
- package/src/core/hooks/Header.js +234 -0
- package/src/core/hooks/HighLight.js +37 -0
- package/src/core/hooks/Hr.js +52 -0
- package/src/core/hooks/HtmlBlock.js +184 -0
- package/src/core/hooks/Image.js +174 -0
- package/src/core/hooks/InlineCode.js +48 -0
- package/src/core/hooks/InlineMath.js +107 -0
- package/src/core/hooks/Link.js +160 -0
- package/src/core/hooks/List.js +264 -0
- package/src/core/hooks/MathBlock.js +103 -0
- package/src/core/hooks/Panel.js +145 -0
- package/src/core/hooks/Paragraph.js +84 -0
- package/src/core/hooks/Ruby.js +34 -0
- package/src/core/hooks/Size.js +51 -0
- package/src/core/hooks/Strikethrough.js +54 -0
- package/src/core/hooks/Sub.js +47 -0
- package/src/core/hooks/SuggestList.js +333 -0
- package/src/core/hooks/Suggester.js +707 -0
- package/src/core/hooks/Sup.js +47 -0
- package/src/core/hooks/Table.js +275 -0
- package/src/core/hooks/Toc.js +292 -0
- package/src/core/hooks/Transfer.js +47 -0
- package/src/core/hooks/Underline.js +37 -0
- package/src/index.core.js +29 -0
- package/src/index.engine.core.js +68 -0
- package/src/index.engine.js +28 -0
- package/src/index.js +32 -0
- package/src/libs/mermaidAPI.8.4.8.js +1 -0
- package/src/libs/mermaidAPI.8.5.2.js +42 -0
- package/src/libs/rawdeflate.js +1663 -0
- package/src/locales/en_US.js +139 -0
- package/src/locales/index.js +25 -0
- package/src/locales/ru_RU.js +139 -0
- package/src/locales/zh_CN.js +142 -0
- package/src/sass/base.scss +26 -0
- package/src/sass/bubble_formula.scss +166 -0
- package/src/sass/ch-icon.scss +118 -0
- package/src/sass/cherry.scss +1116 -0
- package/src/sass/components/bubble.scss +173 -0
- package/src/sass/components/shortcut_key_config.scss +108 -0
- package/src/sass/formula_utils_bubble.scss +82 -0
- package/src/sass/icon_template.scss +24 -0
- package/src/sass/icons/uEA03-list.svg +19 -0
- package/src/sass/icons/uEA04-check.svg +14 -0
- package/src/sass/icons/uEA09-square.svg +10 -0
- package/src/sass/icons/uEA0A-bold.svg +20 -0
- package/src/sass/icons/uEA0B-code.svg +18 -0
- package/src/sass/icons/uEA0C-color.svg +13 -0
- package/src/sass/icons/uEA0D-header.svg +8 -0
- package/src/sass/icons/uEA0E-image.svg +15 -0
- package/src/sass/icons/uEA0F-italic.svg +8 -0
- package/src/sass/icons/uEA10-link.svg +16 -0
- package/src/sass/icons/uEA11-ol.svg +21 -0
- package/src/sass/icons/uEA12-size.svg +11 -0
- package/src/sass/icons/uEA13-strike.svg +16 -0
- package/src/sass/icons/uEA14-table.svg +12 -0
- package/src/sass/icons/uEA15-ul.svg +17 -0
- package/src/sass/icons/uEA16-underline.svg +13 -0
- package/src/sass/icons/uEA17-word.svg +16 -0
- package/src/sass/icons/uEA18-blockquote.svg +11 -0
- package/src/sass/icons/uEA19-font.svg +10 -0
- package/src/sass/icons/uEA1F-insertClass.svg +39 -0
- package/src/sass/icons/uEA20-insertFlow.svg +8 -0
- package/src/sass/icons/uEA21-insertFormula.svg +23 -0
- package/src/sass/icons/uEA22-insertGantt.svg +13 -0
- package/src/sass/icons/uEA23-insertGraph.svg +13 -0
- package/src/sass/icons/uEA24-insertPie.svg +19 -0
- package/src/sass/icons/uEA25-insertSeq.svg +20 -0
- package/src/sass/icons/uEA26-insertState.svg +35 -0
- package/src/sass/icons/uEA27-line.svg +11 -0
- package/src/sass/icons/uEA28-preview.svg +18 -0
- package/src/sass/icons/uEA29-previewClose.svg +24 -0
- package/src/sass/icons/uEA2A-toc.svg +24 -0
- package/src/sass/icons/uEA2D-sub.svg +15 -0
- package/src/sass/icons/uEA2E-sup.svg +15 -0
- package/src/sass/icons/uEA2F-h1.svg +16 -0
- package/src/sass/icons/uEA30-h2.svg +20 -0
- package/src/sass/icons/uEA31-h3.svg +23 -0
- package/src/sass/icons/uEA32-h4.svg +16 -0
- package/src/sass/icons/uEA33-h5.svg +20 -0
- package/src/sass/icons/uEA34-h6.svg +17 -0
- package/src/sass/icons/uEA35-video.svg +20 -0
- package/src/sass/icons/uEA36-insert.svg +25 -0
- package/src/sass/icons/uEA37-little_table.svg +30 -0
- package/src/sass/icons/uEA38-pdf.svg +27 -0
- package/src/sass/icons/uEA39-checklist.svg +22 -0
- package/src/sass/icons/uEA40-close.svg +12 -0
- package/src/sass/icons/uEA41-fullscreen.svg +81 -0
- package/src/sass/icons/uEA42-minscreen.svg +77 -0
- package/src/sass/icons/uEA43-insertChart.svg +23 -0
- package/src/sass/icons/uEA44-question.svg +25 -0
- package/src/sass/icons/uEA45-settings.svg +32 -0
- package/src/sass/icons/uEA46-ok.svg +7 -0
- package/src/sass/icons/uEA47-br.svg +22 -0
- package/src/sass/icons/uEA48-normal.svg +15 -0
- package/src/sass/icons/uEA49-undo.svg +19 -0
- package/src/sass/icons/uEA50-redo.svg +21 -0
- package/src/sass/icons/uEA51-copy.svg +6 -0
- package/src/sass/icons/uEA52-phone.svg +5 -0
- package/src/sass/icons/uEA53-cherry-table-delete.svg +17 -0
- package/src/sass/icons/uEA54-cherry-table-insert-bottom.svg +16 -0
- package/src/sass/icons/uEA55-cherry-table-insert-left.svg +15 -0
- package/src/sass/icons/uEA56-cherry-table-insert-right.svg +16 -0
- package/src/sass/icons/uEA57-cherry-table-insert-top.svg +16 -0
- package/src/sass/icons/uEA58-sort-s.svg +13 -0
- package/src/sass/icons/uEA59-pinyin.svg +1 -0
- package/src/sass/icons/uEA5A-create.svg +24 -0
- package/src/sass/icons/uEA5B-download.svg +34 -0
- package/src/sass/icons/uEA5C-edit.svg +3 -0
- package/src/sass/icons/uEA5D-export.svg +53 -0
- package/src/sass/icons/uEA5E-folder-open.svg +3 -0
- package/src/sass/icons/uEA5F-folder.svg +3 -0
- package/src/sass/icons/uEA60-help.svg +5 -0
- package/src/sass/icons/uEA61-pen-fill.svg +13 -0
- package/src/sass/icons/uEA62-pen.svg +3 -0
- package/src/sass/icons/uEA64-tips.svg +5 -0
- package/src/sass/icons/uEA65-warn.svg +5 -0
- package/src/sass/icons/uEA66-mistake.svg +4 -0
- package/src/sass/icons/uEA67-success.svg +4 -0
- package/src/sass/icons/uEA68-danger.svg +4 -0
- package/src/sass/icons/uEA69-info.svg +5 -0
- package/src/sass/icons/uEA6A-primary.svg +5 -0
- package/src/sass/icons/uEA6B-warning.svg +5 -0
- package/src/sass/icons/uEA6C-justify.svg +19 -0
- package/src/sass/icons/uEA6D-justifyCenter.svg +19 -0
- package/src/sass/icons/uEA6E-justifyLeft.svg +19 -0
- package/src/sass/icons/uEA6F-justifyRight.svg +19 -0
- package/src/sass/icons/uEA70-chevronsLeft.svg +1 -0
- package/src/sass/icons/uEA71-chevronsRight.svg +1 -0
- package/src/sass/icons/uEA72-trendingUp.svg +1 -0
- package/src/sass/icons/uEA74-codeBlock.svg +1 -0
- package/src/sass/icons/uEA75-expand.svg +3 -0
- package/src/sass/icons/uEA76-unExpand.svg +3 -0
- package/src/sass/icons/uEA77-swap-vert.svg +1 -0
- package/src/sass/icons/uEA78-swap.svg +1 -0
- package/src/sass/icons/uEA79-keyboard.svg +1 -0
- package/src/sass/icons/uEA7A-command.svg +1 -0
- package/src/sass/icons/uEA7B-search.svg +1 -0
- package/src/sass/index.scss +3 -0
- package/src/sass/markdown.scss +668 -0
- package/src/sass/markdown_pure.scss +9 -0
- package/src/sass/prettyprint/prettyprint.scss +118 -0
- package/src/sass/previewer.scss +179 -0
- package/src/sass/print.scss +13 -0
- package/src/sass/prism/coy.scss +220 -0
- package/src/sass/prism/dark.scss +132 -0
- package/src/sass/prism/default.scss +143 -0
- package/src/sass/prism/funky.scss +133 -0
- package/src/sass/prism/okaidia.scss +126 -0
- package/src/sass/prism/one-dark.scss +440 -0
- package/src/sass/prism/one-light.scss +428 -0
- package/src/sass/prism/solarized-light.scss +153 -0
- package/src/sass/prism/tomorrow-night.scss +125 -0
- package/src/sass/prism/twilight.scss +202 -0
- package/src/sass/prism/vs-dark.scss +275 -0
- package/src/sass/prism/vs-light.scss +168 -0
- package/src/sass/themes/blue.scss +411 -0
- package/src/sass/themes/dark.scss +517 -0
- package/src/sass/themes/default.scss +255 -0
- package/src/sass/themes/green.scss +395 -0
- package/src/sass/themes/light.scss +368 -0
- package/src/sass/themes/red.scss +397 -0
- package/src/sass/themes/violet.scss +410 -0
- package/src/sass/variable.scss +84 -0
- package/src/toolbars/Bubble.js +234 -0
- package/src/toolbars/BubbleFormula.js +298 -0
- package/src/toolbars/BubbleTable.js +147 -0
- package/src/toolbars/FloatMenu.js +131 -0
- package/src/toolbars/HiddenToolbar.js +36 -0
- package/src/toolbars/HookCenter.js +234 -0
- package/src/toolbars/MenuBase.js +569 -0
- package/src/toolbars/PreviewerBubble.js +608 -0
- package/src/toolbars/ShortcutKeyConfigPanel.js +345 -0
- package/src/toolbars/Sidebar.js +36 -0
- package/src/toolbars/Toc.js +242 -0
- package/src/toolbars/Toolbar.js +449 -0
- package/src/toolbars/ToolbarRight.js +37 -0
- package/src/toolbars/hooks/Audio.js +79 -0
- package/src/toolbars/hooks/BarTable.js +41 -0
- package/src/toolbars/hooks/Bold.js +73 -0
- package/src/toolbars/hooks/Br.js +34 -0
- package/src/toolbars/hooks/ChangeLocale.js +62 -0
- package/src/toolbars/hooks/ChatGpt.js +182 -0
- package/src/toolbars/hooks/CheckList.js +41 -0
- package/src/toolbars/hooks/Code.js +49 -0
- package/src/toolbars/hooks/CodeTheme.js +66 -0
- package/src/toolbars/hooks/Color.js +298 -0
- package/src/toolbars/hooks/Copy.js +141 -0
- package/src/toolbars/hooks/Detail.js +69 -0
- package/src/toolbars/hooks/DrawIo.js +57 -0
- package/src/toolbars/hooks/Export.js +49 -0
- package/src/toolbars/hooks/File.js +79 -0
- package/src/toolbars/hooks/Formula.js +69 -0
- package/src/toolbars/hooks/FullScreen.js +50 -0
- package/src/toolbars/hooks/Graph.js +263 -0
- package/src/toolbars/hooks/H1.js +71 -0
- package/src/toolbars/hooks/H2.js +71 -0
- package/src/toolbars/hooks/H3.js +71 -0
- package/src/toolbars/hooks/Header.js +118 -0
- package/src/toolbars/hooks/Hr.js +35 -0
- package/src/toolbars/hooks/Image.js +91 -0
- package/src/toolbars/hooks/InlineCode.js +53 -0
- package/src/toolbars/hooks/Insert.js +193 -0
- package/src/toolbars/hooks/Italic.js +72 -0
- package/src/toolbars/hooks/Justify.js +49 -0
- package/src/toolbars/hooks/LineTable.js +41 -0
- package/src/toolbars/hooks/Link.js +49 -0
- package/src/toolbars/hooks/List.js +55 -0
- package/src/toolbars/hooks/MobilePreview.js +44 -0
- package/src/toolbars/hooks/Ol.js +41 -0
- package/src/toolbars/hooks/Panel.js +140 -0
- package/src/toolbars/hooks/Pdf.js +78 -0
- package/src/toolbars/hooks/Publish.js +123 -0
- package/src/toolbars/hooks/QuickTable.js +43 -0
- package/src/toolbars/hooks/Quote.js +45 -0
- package/src/toolbars/hooks/Redo.js +33 -0
- package/src/toolbars/hooks/Ruby.js +59 -0
- package/src/toolbars/hooks/Search.js +53 -0
- package/src/toolbars/hooks/Settings.js +220 -0
- package/src/toolbars/hooks/ShortcutKey.js +62 -0
- package/src/toolbars/hooks/Size.js +118 -0
- package/src/toolbars/hooks/Split.js +37 -0
- package/src/toolbars/hooks/Strikethrough.js +71 -0
- package/src/toolbars/hooks/Sub.js +58 -0
- package/src/toolbars/hooks/Sup.js +58 -0
- package/src/toolbars/hooks/SwitchModel.js +56 -0
- package/src/toolbars/hooks/Table.js +56 -0
- package/src/toolbars/hooks/Theme.js +62 -0
- package/src/toolbars/hooks/Toc.js +35 -0
- package/src/toolbars/hooks/TogglePreview.js +91 -0
- package/src/toolbars/hooks/Ul.js +41 -0
- package/src/toolbars/hooks/Underline.js +68 -0
- package/src/toolbars/hooks/Undo.js +30 -0
- package/src/toolbars/hooks/Video.js +79 -0
- package/src/toolbars/hooks/Word.js +78 -0
- package/src/toolbars/hooks/WordCount.js +106 -0
- package/src/utils/autoindent.js +58 -0
- package/src/utils/cm-search-replace.js +794 -0
- package/src/utils/code-preview-language-setting.js +180 -0
- package/src/utils/codeBlockContentHandler.js +400 -0
- package/src/utils/config.js +174 -0
- package/src/utils/copy.js +55 -0
- package/src/utils/dialog.js +214 -0
- package/src/utils/dom.js +163 -0
- package/src/utils/downloadUtil.js +23 -0
- package/src/utils/env.js +22 -0
- package/src/utils/error.js +61 -0
- package/src/utils/event.js +38 -0
- package/src/utils/export.js +166 -0
- package/src/utils/file.js +164 -0
- package/src/utils/formulaUtilsHandler.js +232 -0
- package/src/utils/htmlparser.js +976 -0
- package/src/utils/image.js +99 -0
- package/src/utils/imgSizeHandler.js +279 -0
- package/src/utils/lazyLoadImg.js +327 -0
- package/src/utils/lineFeed.js +49 -0
- package/src/utils/listContentHandler.js +227 -0
- package/src/utils/lookbehind-replace.js +81 -0
- package/src/utils/mathjax.js +89 -0
- package/src/utils/myersDiff.js +211 -0
- package/src/utils/pasteHelper.js +253 -0
- package/src/utils/platformTransform.js +71 -0
- package/src/utils/recount-pos.js +59 -0
- package/src/utils/regexp.js +295 -0
- package/src/utils/sanitize.js +477 -0
- package/src/utils/selection.js +50 -0
- package/src/utils/shortcutKey.js +291 -0
- package/src/utils/svgUtils.js +96 -0
- package/src/utils/tableContentHandler.js +876 -0
- package/test/core/CommonMark.spec.ts +62 -0
- package/test/core/hooks/AutoLink.spec.ts +28 -0
- package/test/core/hooks/List.spec.ts +79 -0
- package/test/core/hooks/__snapshots__/List.spec.ts.snap +11 -0
- package/test/example.md +778 -0
- package/test/node.js +10 -0
- package/test/suites/commonmark.spec.json +5218 -0
- package/test/tsconfig.test.json +6 -0
- package/test/utils/regexp.spec.ts +28 -0
- package/types/cherry.d.ts +675 -0
- package/types/codemirror.d.ts +22 -0
- package/types/editor.d.ts +72 -0
- package/types/global.d.ts +16 -0
- package/types/menus.d.ts +24 -0
- package/types/previewer.d.ts +53 -0
- package/types/syntax.d.ts +52 -0
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
/**
|
|
17
|
+
* 为段落前加换行符
|
|
18
|
+
* @param {string} match 匹配全文
|
|
19
|
+
* @param {string} processedContent 加入的内容
|
|
20
|
+
*/
|
|
21
|
+
export function prependLineFeedForParagraph(match, processedContent, canNestedInList = false) {
|
|
22
|
+
if (!/^\n/.test(match)) {
|
|
23
|
+
return processedContent;
|
|
24
|
+
}
|
|
25
|
+
if (canNestedInList) {
|
|
26
|
+
const leadingLinesCount = match.match(/^\n+/g)?.[0]?.length ?? 0;
|
|
27
|
+
// 前置换行符数量大于2时,补充两个换行符,否则只补充一个
|
|
28
|
+
if (leadingLinesCount > 1) {
|
|
29
|
+
return `\n\n${processedContent}`;
|
|
30
|
+
}
|
|
31
|
+
return `\n${processedContent}`;
|
|
32
|
+
}
|
|
33
|
+
return `\n\n${processedContent}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 计算段落所占行数,必须传入通过 prependLineFeedForParagraph 方法处理后的内容,才能计算准确
|
|
38
|
+
* @param {string} preLinesMatch 前置匹配行
|
|
39
|
+
* @param {number} contentLines 实际内容行数
|
|
40
|
+
*/
|
|
41
|
+
export function calculateLinesOfParagraph(preLinesMatch, contentLines) {
|
|
42
|
+
let preLineCount = (preLinesMatch.match(/\n/g) || []).length;
|
|
43
|
+
// 前置行匹配文本为空,说明是全文开头
|
|
44
|
+
// 非全文开头前面必有两个从 prependLineFeed 方法新增加的换行符
|
|
45
|
+
if (preLinesMatch !== '') {
|
|
46
|
+
preLineCount -= 2;
|
|
47
|
+
}
|
|
48
|
+
return preLineCount + contentLines;
|
|
49
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
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
|
+
|
|
17
|
+
import { getValueWithoutCode, LIST_CONTENT } from '@/utils/regexp';
|
|
18
|
+
|
|
19
|
+
export default class ListHandler {
|
|
20
|
+
/** @type{HTMLElement} */
|
|
21
|
+
bubbleContainer = null;
|
|
22
|
+
|
|
23
|
+
regList = LIST_CONTENT;
|
|
24
|
+
|
|
25
|
+
/** @type{Array.<import('codemirror').Position>} */
|
|
26
|
+
range = [];
|
|
27
|
+
|
|
28
|
+
/** @type{import('codemirror').Position} */
|
|
29
|
+
position = { line: 0, ch: 0 };
|
|
30
|
+
|
|
31
|
+
input = false;
|
|
32
|
+
|
|
33
|
+
isCheckbox = false;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} trigger 触发方式
|
|
37
|
+
* @param {HTMLParagraphElement} target 目标dom
|
|
38
|
+
* @param {HTMLDivElement} container bubble容器
|
|
39
|
+
* @param {HTMLDivElement} previewerDom 预览器dom
|
|
40
|
+
* @param {import('../Editor').default} editor 编辑器实例
|
|
41
|
+
*/
|
|
42
|
+
constructor(trigger, target, container, previewerDom, editor, options = {}) {
|
|
43
|
+
this.trigger = trigger;
|
|
44
|
+
this.target = target;
|
|
45
|
+
this.container = container;
|
|
46
|
+
this.previewerDom = previewerDom;
|
|
47
|
+
this.editor = editor;
|
|
48
|
+
this.insertLineBreak = false; // 是否为插入换行符
|
|
49
|
+
this.handleEditablesInputBinded = this.handleEditablesInput.bind(this); // 保证this指向正确以及能够正确移除事件
|
|
50
|
+
this.handleEditablesUnfocusBinded = this.handleEditablesUnfocus.bind(this);
|
|
51
|
+
this.target.addEventListener('input', this.handleEditablesInputBinded, false);
|
|
52
|
+
this.target.addEventListener('focusout', this.handleEditablesUnfocusBinded, false);
|
|
53
|
+
this.setSelection();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 触发事件
|
|
58
|
+
* @param {string} type 事件类型
|
|
59
|
+
* @param {Event} event 事件对象
|
|
60
|
+
*/
|
|
61
|
+
emit(type, event) {
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 'remove':
|
|
64
|
+
return this.remove();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
remove() {
|
|
69
|
+
if (this.bubbleContainer) {
|
|
70
|
+
this.bubbleContainer.style.display = 'none';
|
|
71
|
+
if (this.bubbleContainer.children[0] instanceof HTMLTextAreaElement) {
|
|
72
|
+
this.bubbleContainer.children[0].value = ''; // 清空内容
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
this.target.removeAttribute('contenteditable');
|
|
76
|
+
this.target.removeEventListener('input', this.handleEditablesInputBinded, false);
|
|
77
|
+
this.target.removeEventListener('focusout', this.handleEditablesUnfocusBinded, false);
|
|
78
|
+
const cursor = this.editor.editor.getCursor(); // 获取光标位置
|
|
79
|
+
this.editor.editor.setSelection(cursor, cursor); // 取消选中
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setSelection() {
|
|
83
|
+
const allLi = Array.from(this.previewerDom.querySelectorAll('li.cherry-list-item')); // 预览区域内所有的li
|
|
84
|
+
const targetLiIdx = allLi.findIndex((li) => li === this.target.parentElement);
|
|
85
|
+
if (targetLiIdx === -1) {
|
|
86
|
+
return; // 没有找到li
|
|
87
|
+
}
|
|
88
|
+
const contents = getValueWithoutCode(this?.editor.editor.getValue())?.split('\n') ?? [];
|
|
89
|
+
let contentsLiCount = 0; // 编辑器中是列表的数量
|
|
90
|
+
let targetLine = -1; // 行
|
|
91
|
+
let targetCh = -1; // 列
|
|
92
|
+
const targetContent = []; // 当前点击的li的内容
|
|
93
|
+
for (let lineIdx = 0; lineIdx < contents.length; lineIdx++) {
|
|
94
|
+
const lineContent = contents[lineIdx];
|
|
95
|
+
if (!lineContent || lineContent === '/n') {
|
|
96
|
+
if (targetContent.length <= 0) {
|
|
97
|
+
continue;
|
|
98
|
+
} else {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 匹配是否符合列表的正则
|
|
103
|
+
const regRes = this.regList.exec(lineContent);
|
|
104
|
+
if (regRes !== null) {
|
|
105
|
+
if (targetContent.length > 0) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const [, indent, identifier, checkbox, content] = regRes;
|
|
109
|
+
if (contentsLiCount === targetLiIdx && indent !== undefined) {
|
|
110
|
+
targetLine = lineIdx;
|
|
111
|
+
// eslint-disable-next-line prefer-destructuring
|
|
112
|
+
targetContent.push(content); // 这里只取一个没必要解构
|
|
113
|
+
targetCh = lineContent.indexOf(content);
|
|
114
|
+
// 1. 这种需要特殊处理,需要跳过一个空格位,否则层级会错乱
|
|
115
|
+
if (identifier?.endsWith('.')) {
|
|
116
|
+
targetCh += 1;
|
|
117
|
+
}
|
|
118
|
+
// checkbox 编辑的元素内以选中的checkbox开头时需要去掉,否则会被解析
|
|
119
|
+
if (checkbox) {
|
|
120
|
+
this.isCheckbox = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
contentsLiCount += 1;
|
|
124
|
+
} else if (targetContent.length > 0) {
|
|
125
|
+
targetContent.push(lineContent);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const from = { line: targetLine, ch: targetCh };
|
|
129
|
+
const to = {
|
|
130
|
+
line: targetLine + targetContent.length - 1,
|
|
131
|
+
ch: targetCh + targetContent[targetContent.length - 1]?.length,
|
|
132
|
+
};
|
|
133
|
+
this.editor.editor.setSelection(from, to);
|
|
134
|
+
this.range = [from, to];
|
|
135
|
+
this.position = this.editor.editor.getCursor(); // 输入就获取光标位置,防止后面点到编辑器dom的时候光标位置不对
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 处理contenteditable元素的输入事件
|
|
140
|
+
* @param {InputEvent} event
|
|
141
|
+
*/
|
|
142
|
+
handleEditablesInput(event) {
|
|
143
|
+
this.input = true;
|
|
144
|
+
event.stopPropagation();
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
/** @typedef {'insertText'|'insertFromPaste'|'insertParagraph'|'insertLineBreak'|'deleteContentBackward'|'deleteContentForward'|'deleteByCut'|'deleteContentForward'|'deleteWordBackward'} InputType*/
|
|
147
|
+
if (event.target instanceof HTMLParagraphElement) {
|
|
148
|
+
if (event.inputType === 'insertParagraph' || event.inputType === 'insertLineBreak') {
|
|
149
|
+
this.insertLineBreak = true;
|
|
150
|
+
this.handleInsertLineBreak(event);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 处理contenteditable元素的失去焦点事件
|
|
157
|
+
* @param {FocusEvent} event
|
|
158
|
+
*/
|
|
159
|
+
handleEditablesUnfocus(event) {
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
if (event.target instanceof HTMLParagraphElement) {
|
|
163
|
+
if (this.input) {
|
|
164
|
+
if (!this.insertLineBreak) {
|
|
165
|
+
const replaceHtml = !this.isCheckbox
|
|
166
|
+
? event.target.innerHTML
|
|
167
|
+
: event.target.innerHTML.replace(/<span class="ch-icon ch-icon-(square|check)"><\/span>/, '');
|
|
168
|
+
const md = this.editor.$cherry.engine.makeMarkdown(replaceHtml);
|
|
169
|
+
const [from, to] = this.range;
|
|
170
|
+
this.editor.editor.replaceRange(md, from, to);
|
|
171
|
+
}
|
|
172
|
+
this.isCheckbox = false;
|
|
173
|
+
this.input = false;
|
|
174
|
+
this.insertLineBreak = false;
|
|
175
|
+
}
|
|
176
|
+
this.remove();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {InputEvent} event
|
|
182
|
+
*/
|
|
183
|
+
handleInsertLineBreak(event) {
|
|
184
|
+
/** @type {Array<string>} */
|
|
185
|
+
let splitInnerText = [];
|
|
186
|
+
// @ts-ignore
|
|
187
|
+
if ('innerText' in event.target && typeof event.target.innerText === 'string') {
|
|
188
|
+
// @ts-ignore
|
|
189
|
+
splitInnerText = event.target.innerText.split('\n');
|
|
190
|
+
}
|
|
191
|
+
// 只有第一段是换行,后面的换行都应该认为是另一行
|
|
192
|
+
const [before, ...after] = splitInnerText;
|
|
193
|
+
// 获取当前光标位置
|
|
194
|
+
const cursor = this.editor.editor.getCursor();
|
|
195
|
+
// 获取光标行的内容
|
|
196
|
+
const lineContent = this.editor.editor.getLine(cursor.line);
|
|
197
|
+
const regRes = this.regList.exec(lineContent);
|
|
198
|
+
let insertContent = '\n- ';
|
|
199
|
+
if (regRes !== null) {
|
|
200
|
+
// 存在选中的checkbox则替换为未选中的checkbox,其他的保持原样
|
|
201
|
+
insertContent = `\n${regRes[1]}${regRes[2]?.replace('[x]', '[ ] ')}`;
|
|
202
|
+
}
|
|
203
|
+
insertContent += after?.join('') ?? '';
|
|
204
|
+
// 把当前行内容剪掉
|
|
205
|
+
this.editor.editor.replaceRange(
|
|
206
|
+
before,
|
|
207
|
+
{
|
|
208
|
+
line: cursor.line,
|
|
209
|
+
ch: regRes[2]?.length ?? 0,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
line: cursor.line,
|
|
213
|
+
ch: lineContent.length,
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
// 在当前行的末尾插入一个换行符,这会创建一个新行
|
|
217
|
+
this.editor.editor.replaceRange(insertContent, {
|
|
218
|
+
line: cursor.line,
|
|
219
|
+
ch: lineContent.length,
|
|
220
|
+
});
|
|
221
|
+
// 将光标移动到新行
|
|
222
|
+
this.editor.editor.setCursor({ line: cursor.line + 1, ch: insertContent.length + 1 });
|
|
223
|
+
// 将光标聚焦到编辑器上
|
|
224
|
+
this.editor.editor.focus();
|
|
225
|
+
this.remove();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param {string} str
|
|
19
|
+
* @param {{replacedText:string;begin:number;length:number;}[]} buffer
|
|
20
|
+
*/
|
|
21
|
+
function replaceStringByBuffer(str, buffer) {
|
|
22
|
+
if (!buffer.length) {
|
|
23
|
+
return str;
|
|
24
|
+
}
|
|
25
|
+
const slicedString = [];
|
|
26
|
+
let offset = 0;
|
|
27
|
+
buffer.forEach((buf, index) => {
|
|
28
|
+
slicedString.push(str.slice(offset, buf.begin));
|
|
29
|
+
slicedString.push(buf.replacedText);
|
|
30
|
+
offset = buf.begin + buf.length;
|
|
31
|
+
if (index === buffer.length - 1) {
|
|
32
|
+
slicedString.push(str.slice(offset));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
// console.log(slicedString, slicedString.join(''));
|
|
36
|
+
return slicedString.join('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} str 原始字符串
|
|
41
|
+
* @param {RegExp} regex 正则
|
|
42
|
+
* @param {(...args: any[])=>string} replacer 字符串替换函数
|
|
43
|
+
* @param {boolean} [continuousMatch=false] 是否连续匹配,主要用于需要后向断言的连续语法匹配
|
|
44
|
+
* @param {number} [rollbackLength=1] 连续匹配时,每次指针回退的长度,默认为 1
|
|
45
|
+
*/
|
|
46
|
+
export function replaceLookbehind(str, regex, replacer, continuousMatch = false, rollbackLength = 1) {
|
|
47
|
+
if (!regex) {
|
|
48
|
+
return str;
|
|
49
|
+
}
|
|
50
|
+
// 从头开始匹配
|
|
51
|
+
regex.lastIndex = 0;
|
|
52
|
+
let args;
|
|
53
|
+
let lastIndex = 0;
|
|
54
|
+
const replaceBuffer = [];
|
|
55
|
+
while ((args = regex.exec(str)) !== null) {
|
|
56
|
+
const replaceInfo = {
|
|
57
|
+
begin: args.index,
|
|
58
|
+
length: args[0].length,
|
|
59
|
+
};
|
|
60
|
+
if (continuousMatch && args.index === lastIndex - rollbackLength) {
|
|
61
|
+
const [match, , ...restArgs] = args;
|
|
62
|
+
// 丢弃 leadingChar,需要调整begin和length
|
|
63
|
+
replaceBuffer.push({
|
|
64
|
+
begin: replaceInfo.begin + rollbackLength,
|
|
65
|
+
length: replaceInfo.length - rollbackLength,
|
|
66
|
+
replacedText: replacer(match.slice(rollbackLength), '', ...restArgs),
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
replaceBuffer.push({
|
|
70
|
+
...replaceInfo,
|
|
71
|
+
replacedText: replacer(...args),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// console.log(args);
|
|
75
|
+
lastIndex = regex.lastIndex;
|
|
76
|
+
regex.lastIndex -= rollbackLength;
|
|
77
|
+
}
|
|
78
|
+
// 正则复位,避免影响其他逻辑
|
|
79
|
+
regex.lastIndex = 0;
|
|
80
|
+
return replaceStringByBuffer(str, replaceBuffer);
|
|
81
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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 { isBrowser } from './env';
|
|
17
|
+
import { PUNCTUATION } from './regexp';
|
|
18
|
+
import { escapeHTMLSpecialChar } from './sanitize';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 装饰器,挂载对应的模块到实例上
|
|
22
|
+
*/
|
|
23
|
+
export function LoadMathModule() {
|
|
24
|
+
if (!isBrowser()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
this.katex = this.externals?.katex ?? window.katex;
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
this.MathJax = this.externals?.MathJax ?? window.MathJax;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const configureMathJax = (usePlugins) => {
|
|
34
|
+
if (!isBrowser()) {
|
|
35
|
+
console.log('mathjax disabled');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const plugins = usePlugins
|
|
39
|
+
? ['input/asciimath', '[tex]/noerrors', '[tex]/cancel', '[tex]/color', '[tex]/boldsymbol', 'ui/safe']
|
|
40
|
+
: ['ui/safe'];
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
window.MathJax = {
|
|
43
|
+
startup: {
|
|
44
|
+
elements: ['.Cherry-Math', '.Cherry-InlineMath'],
|
|
45
|
+
typeset: true,
|
|
46
|
+
},
|
|
47
|
+
tex: {
|
|
48
|
+
inlineMath: [
|
|
49
|
+
['$', '$'],
|
|
50
|
+
['\\(', '\\)'],
|
|
51
|
+
],
|
|
52
|
+
displayMath: [
|
|
53
|
+
['$$', '$$'],
|
|
54
|
+
['\\[', '\\]'],
|
|
55
|
+
],
|
|
56
|
+
tags: 'ams',
|
|
57
|
+
packages: { '[+]': ['noerrors', 'cancel', 'color'] },
|
|
58
|
+
macros: {
|
|
59
|
+
bm: ['{\\boldsymbol{#1}}', 1],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
options: {
|
|
63
|
+
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'a'],
|
|
64
|
+
ignoreHtmlClass: 'tex2jax_ignore',
|
|
65
|
+
processHtmlClass: 'tex2jax_process',
|
|
66
|
+
// 关闭 mathjax 菜单
|
|
67
|
+
enableMenu: false,
|
|
68
|
+
},
|
|
69
|
+
loader: {
|
|
70
|
+
load: plugins,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const noEscape = ['&', '<', '>', '"', "'"]; // 需要转换为HTML实体字符的符号
|
|
76
|
+
|
|
77
|
+
// 用于预处理会在Markdown中被反转义的字符,如:\\ 会被反转义为 \
|
|
78
|
+
export const escapeFormulaPunctuations = (formula) => {
|
|
79
|
+
const $formula = formula.replace(new RegExp(PUNCTUATION, 'g'), (match) => {
|
|
80
|
+
if (noEscape.indexOf(match) !== -1) {
|
|
81
|
+
// HTML特殊字符需要转换为实体字符,防XSS注入
|
|
82
|
+
return escapeHTMLSpecialChar(match);
|
|
83
|
+
}
|
|
84
|
+
return `\\${match}`; // 先转义特殊字符,防止在afterMakeHtml中被反转义
|
|
85
|
+
});
|
|
86
|
+
return $formula;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default {};
|
|
@@ -0,0 +1,211 @@
|
|
|
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
|
+
/**
|
|
17
|
+
* Myers' Diff 算法
|
|
18
|
+
* 用来对两个数列/字符串做diff,得到增/删元素的最简形式
|
|
19
|
+
* 参考文献: http://www.xmailserver.org/diff2.pdf
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import Logger from '@/Logger';
|
|
23
|
+
|
|
24
|
+
export default class MyersDiff {
|
|
25
|
+
constructor(newObj, oldObj, getElement) {
|
|
26
|
+
this.options = {
|
|
27
|
+
newObj, // 用于diff的新列表/字符串
|
|
28
|
+
oldObj, // 用于diff的旧列表/字符串
|
|
29
|
+
getElement, // 获取用于比较的元素的函数
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 执行diff操作
|
|
35
|
+
*/
|
|
36
|
+
doDiff() {
|
|
37
|
+
const snakes = this.findSnakes(this.options.newObj, this.options.oldObj);
|
|
38
|
+
const result = this.assembleResult(snakes, this.options.newObj, this.options.oldObj);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 用于判断列表/字符串元素是否相等的判据函数
|
|
44
|
+
*/
|
|
45
|
+
getElement(obj, index) {
|
|
46
|
+
if (typeof this.options.getElement === 'function') {
|
|
47
|
+
// 支持传入自定义的比较函数
|
|
48
|
+
return this.options.getElement(obj, index);
|
|
49
|
+
}
|
|
50
|
+
return obj[index];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 寻找从起点到终点的折线
|
|
55
|
+
*/
|
|
56
|
+
findSnakes(newObj, oldObj) {
|
|
57
|
+
const newLen = newObj.length || 0; // 新diff对象的长度
|
|
58
|
+
const oldLen = oldObj.length || 0; // 旧diff对象的长度
|
|
59
|
+
const lengthSum = newLen + oldLen; // 长度之和
|
|
60
|
+
const v = { 1: 0 }; // "每个节点的深度值"的缓存对象
|
|
61
|
+
const allSnakes = { 0: { 1: 0 } }; // "每个节点对应的折线"的缓存对象
|
|
62
|
+
|
|
63
|
+
// d是起点到对应节点的编辑距离,简而言之,若把新增一个节点或删除一个节点都视作一次"操作",那么通过d次操作可以从起点到达对应节点
|
|
64
|
+
for (let d = 0; d <= lengthSum; d++) {
|
|
65
|
+
const tmp = {};
|
|
66
|
+
for (let k = -d; k <= d; k += 2) {
|
|
67
|
+
// 转换坐标系,k可以视为对应节点(x,y)的x坐标值减y坐标值
|
|
68
|
+
const down = k === -d || (k !== d && v[k - 1] < v[k + 1]);
|
|
69
|
+
const kPrev = down ? k + 1 : k - 1;
|
|
70
|
+
|
|
71
|
+
const xStart = v[kPrev];
|
|
72
|
+
// let yStart = xStart - kPrev;
|
|
73
|
+
const xMid = down ? xStart : xStart + 1;
|
|
74
|
+
const yMid = xMid - k;
|
|
75
|
+
let xEnd = xMid;
|
|
76
|
+
let yEnd = yMid;
|
|
77
|
+
|
|
78
|
+
while (xEnd < oldLen && yEnd < newLen && this.getElement(oldObj, xEnd) === this.getElement(newObj, yEnd)) {
|
|
79
|
+
xEnd += 1;
|
|
80
|
+
yEnd += 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
v[k] = xEnd;
|
|
84
|
+
tmp[k] = xEnd;
|
|
85
|
+
|
|
86
|
+
if (xEnd >= oldLen && yEnd >= newLen) {
|
|
87
|
+
// 成功抵达终点
|
|
88
|
+
allSnakes[d] = tmp;
|
|
89
|
+
return this.$backtraceSnakes(allSnakes, newLen, oldLen, d);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
allSnakes[d] = tmp;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 回溯,找出关键路径对应的折线
|
|
100
|
+
*/
|
|
101
|
+
$backtraceSnakes(allSnakes, newLen, oldLen, d) {
|
|
102
|
+
const keySnakes = [];
|
|
103
|
+
const p = { x: oldLen, y: newLen }; // 模拟节点,从终点开始
|
|
104
|
+
|
|
105
|
+
// 执行回溯,倒回起点,找到并记录关键路径
|
|
106
|
+
for (let i = d; i > 0; i--) {
|
|
107
|
+
const v = allSnakes[i];
|
|
108
|
+
const vPrev = allSnakes[i - 1];
|
|
109
|
+
const k = p.x - p.y;
|
|
110
|
+
|
|
111
|
+
const xEnd = v[k];
|
|
112
|
+
// let yEnd = xEnd - k;
|
|
113
|
+
|
|
114
|
+
const down = k === -i || (k !== i && vPrev[k + 1] > vPrev[k - 1]);
|
|
115
|
+
const kPrev = down ? k + 1 : k - 1;
|
|
116
|
+
|
|
117
|
+
const xStart = vPrev[kPrev];
|
|
118
|
+
const yStart = xStart - kPrev;
|
|
119
|
+
|
|
120
|
+
const xMid = down ? xStart : xStart + 1;
|
|
121
|
+
// let yMid = xMid - k;
|
|
122
|
+
|
|
123
|
+
keySnakes.unshift({ xStart, xMid, xEnd });
|
|
124
|
+
|
|
125
|
+
p.x = xStart;
|
|
126
|
+
p.y = yStart;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return keySnakes;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 组装出返回值
|
|
134
|
+
*/
|
|
135
|
+
assembleResult(snakes, newObj, oldObj) {
|
|
136
|
+
const grayColor = 'color: gray';
|
|
137
|
+
const redColor = 'color: red';
|
|
138
|
+
const greenColor = 'color: green';
|
|
139
|
+
const blueColor = 'color: blue';
|
|
140
|
+
let consoleStr = '';
|
|
141
|
+
const args = [];
|
|
142
|
+
let yOffset = 0;
|
|
143
|
+
const result = []; // 返回的操作集
|
|
144
|
+
let change = {}; // 本次操作
|
|
145
|
+
let lastChange = {}; // 缓存上一次操作
|
|
146
|
+
let firstDeleteChange = {}; // 连续删除时用来缓存最初的删除
|
|
147
|
+
snakes.forEach((snake, index) => {
|
|
148
|
+
let currentPos = snake.xStart;
|
|
149
|
+
|
|
150
|
+
if (index === 0 && snake.xStart !== 0) {
|
|
151
|
+
for (let j = 0; j < snake.xStart; j++) {
|
|
152
|
+
consoleStr += `%c${this.getElement(oldObj, j)}, `;
|
|
153
|
+
args.push(grayColor);
|
|
154
|
+
yOffset += 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (snake.xMid - snake.xStart === 1) {
|
|
159
|
+
// 删除
|
|
160
|
+
change = {
|
|
161
|
+
type: 'delete',
|
|
162
|
+
oldIndex: snake.xStart,
|
|
163
|
+
newIndex: 0,
|
|
164
|
+
};
|
|
165
|
+
if (lastChange.type === 'delete' && lastChange.oldIndex === change.oldIndex - 1) {
|
|
166
|
+
// 检测到连续删除,缓存最初的删除
|
|
167
|
+
firstDeleteChange = firstDeleteChange ? lastChange : firstDeleteChange;
|
|
168
|
+
}
|
|
169
|
+
result.push(change);
|
|
170
|
+
lastChange = change;
|
|
171
|
+
consoleStr += `%c${this.getElement(oldObj, snake.xStart)}, `;
|
|
172
|
+
args.push(redColor);
|
|
173
|
+
currentPos = snake.xMid;
|
|
174
|
+
} else {
|
|
175
|
+
// 添加
|
|
176
|
+
change = {
|
|
177
|
+
type: 'insert',
|
|
178
|
+
oldIndex: snake.xStart,
|
|
179
|
+
newIndex: yOffset,
|
|
180
|
+
};
|
|
181
|
+
if (lastChange.type === 'delete' && lastChange.oldIndex === change.oldIndex - 1) {
|
|
182
|
+
// 和上一条删除合并为"更新"
|
|
183
|
+
result.pop();
|
|
184
|
+
firstDeleteChange = firstDeleteChange ? lastChange : firstDeleteChange;
|
|
185
|
+
change = {
|
|
186
|
+
type: 'update',
|
|
187
|
+
oldIndex: firstDeleteChange.oldIndex, // 合并时,更新目标定位连续删除块中的首个元素
|
|
188
|
+
newIndex: yOffset,
|
|
189
|
+
};
|
|
190
|
+
args.push(blueColor);
|
|
191
|
+
} else {
|
|
192
|
+
args.push(greenColor);
|
|
193
|
+
}
|
|
194
|
+
firstDeleteChange = {};
|
|
195
|
+
result.push(change);
|
|
196
|
+
lastChange = change;
|
|
197
|
+
consoleStr += `%c${this.getElement(newObj, yOffset)}, `;
|
|
198
|
+
yOffset += 1;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 不变
|
|
202
|
+
for (let i = 0; i < snake.xEnd - currentPos; i++) {
|
|
203
|
+
consoleStr += `%c${this.getElement(oldObj, currentPos + i)}, `;
|
|
204
|
+
args.push(grayColor);
|
|
205
|
+
yOffset += 1;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
Logger.log(consoleStr, ...args);
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
}
|