vditor 3.11.3 → 4.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 (53) hide show
  1. package/README.md +2 -0
  2. package/dist/css/content-theme/ant-design.css +7 -22
  3. package/dist/css/content-theme/dark.css +7 -16
  4. package/dist/css/content-theme/light.css +7 -15
  5. package/dist/css/content-theme/wechat.css +7 -19
  6. package/dist/index.css +132 -135
  7. package/dist/index.js +3791 -4022
  8. package/dist/index.min.js +1 -1
  9. package/dist/js/lute/lute.min.js +2 -2
  10. package/dist/js/mermaid/mermaid.min.js +516 -467
  11. package/dist/method.js +41 -33
  12. package/dist/method.min.js +1 -1
  13. package/dist/ts/constants.d.ts +1 -0
  14. package/dist/ts/sv/index.d.ts +1 -5
  15. package/dist/ts/sv/process.d.ts +1 -3
  16. package/dist/ts/upload/getElement.d.ts +1 -1
  17. package/dist/ts/upload/index.d.ts +2 -0
  18. package/dist/ts/util/editorCommonEvent.d.ts +1 -1
  19. package/dist/types/index.d.ts +10 -5
  20. package/package.json +1 -1
  21. package/src/assets/less/_reset.less +80 -28
  22. package/src/assets/less/_sv.less +2 -98
  23. package/src/assets/less/_wysiwyg.less +30 -0
  24. package/src/assets/less/index.less +14 -20
  25. package/src/index.ts +46 -14
  26. package/src/ts/constants.ts +1 -0
  27. package/src/ts/export/index.ts +1 -0
  28. package/src/ts/hint/index.ts +9 -4
  29. package/src/ts/ir/highlightToolbarIR.ts +4 -4
  30. package/src/ts/markdown/getMarkdown.ts +3 -3
  31. package/src/ts/markdown/mediaRender.ts +23 -38
  32. package/src/ts/markdown/mermaidRender.ts +1 -1
  33. package/src/ts/markdown/previewRender.ts +1 -0
  34. package/src/ts/markdown/setLute.ts +1 -0
  35. package/src/ts/sv/index.ts +37 -76
  36. package/src/ts/sv/process.ts +48 -164
  37. package/src/ts/sv/processKeydown.ts +19 -186
  38. package/src/ts/toolbar/EditMode.ts +3 -10
  39. package/src/ts/toolbar/Emoji.ts +8 -1
  40. package/src/ts/toolbar/Record.ts +10 -2
  41. package/src/ts/toolbar/index.ts +3 -0
  42. package/src/ts/ui/initUI.ts +7 -3
  43. package/src/ts/undo/index.ts +32 -17
  44. package/src/ts/upload/index.ts +42 -10
  45. package/src/ts/util/editorCommonEvent.ts +18 -9
  46. package/src/ts/util/fixBrowserBehavior.ts +106 -36
  47. package/src/ts/util/selection.ts +11 -3
  48. package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +53 -1
  49. package/src/ts/wysiwyg/processKeydown.ts +47 -0
  50. package/dist/ts/sv/combineFootnote.d.ts +0 -6
  51. package/dist/ts/sv/inputEvent.d.ts +0 -1
  52. package/src/ts/sv/combineFootnote.ts +0 -27
  53. package/src/ts/sv/inputEvent.ts +0 -188
@@ -1,13 +1,15 @@
1
1
  import {getSearch} from "../util/function";
2
2
 
3
- const videoRender = (element: HTMLElement, url: string) => {
4
- element.insertAdjacentHTML("afterend", `<video controls="controls" src="${url}"></video>`);
5
- element.remove();
6
- };
7
-
8
- const audioRender = (element: HTMLElement, url: string) => {
9
- element.insertAdjacentHTML("afterend", `<audio controls="controls" src="${url}"></audio>`);
10
- element.remove();
3
+ const replaceMedia = (element: HTMLElement, tag: "video" | "audio" | "iframe", url: string) => {
4
+ const media = element.ownerDocument.createElement(tag);
5
+ if (tag === "iframe") {
6
+ media.className = "iframe__video";
7
+ } else {
8
+ media.setAttribute("controls", "controls");
9
+ }
10
+ // 链接属性经 DOM 解码后可能包含引号,直接设置属性以保留 URL 的数据语义。
11
+ media.setAttribute("src", url);
12
+ element.replaceWith(media);
11
13
  };
12
14
 
13
15
  const iframeRender = (element: HTMLElement, url: string) => {
@@ -21,33 +23,20 @@ const iframeRender = (element: HTMLElement, url: string) => {
21
23
  const tedMatch = url.match(/(?:www\.|\/\/)ted\.com\/talks\/(\w+)/);
22
24
 
23
25
  if (youtubeMatch && youtubeMatch[1].length === 11) {
24
- element.insertAdjacentHTML("afterend",
25
- `<iframe class="iframe__video" src="//www.youtube.com/embed/${youtubeMatch[1] +
26
- (youtubeMatch[2] ? "?start=" + youtubeMatch[2] : "")}"></iframe>`);
27
- element.remove();
26
+ replaceMedia(element, "iframe", `//www.youtube.com/embed/${youtubeMatch[1] +
27
+ (youtubeMatch[2] ? "?start=" + youtubeMatch[2] : "")}`);
28
28
  } else if (youkuMatch && youkuMatch[1]) {
29
- element.insertAdjacentHTML("afterend",
30
- `<iframe class="iframe__video" src="//player.youku.com/embed/${youkuMatch[1]}"></iframe>`);
31
- element.remove();
29
+ replaceMedia(element, "iframe", `//player.youku.com/embed/${youkuMatch[1]}`);
32
30
  } else if (qqMatch && qqMatch[1]) {
33
- element.insertAdjacentHTML("afterend",
34
- `<iframe class="iframe__video" src="https://v.qq.com/txp/iframe/player.html?vid=${qqMatch[1]}"></iframe>`);
35
- element.remove();
31
+ replaceMedia(element, "iframe", `https://v.qq.com/txp/iframe/player.html?vid=${qqMatch[1]}`);
36
32
  } else if (coubMatch && coubMatch[1]) {
37
- element.insertAdjacentHTML("afterend",
38
- `<iframe class="iframe__video"
39
- src="//coub.com/embed/${coubMatch[1]}?muted=false&autostart=false&originalSize=true&startWithHD=true"></iframe>`);
40
- element.remove();
33
+ replaceMedia(element, "iframe",
34
+ `//coub.com/embed/${coubMatch[1]}?muted=false&autostart=false&originalSize=true&startWithHD=true`);
41
35
  } else if (facebookMatch && facebookMatch[0]) {
42
- element.insertAdjacentHTML("afterend",
43
- `<iframe class="iframe__video"
44
- src="https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(facebookMatch[0])}"></iframe>`);
45
- element.remove();
36
+ replaceMedia(element, "iframe",
37
+ `https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(facebookMatch[0])}`);
46
38
  } else if (dailymotionMatch && dailymotionMatch[2]) {
47
- element.insertAdjacentHTML("afterend",
48
- `<iframe class="iframe__video"
49
- src="https://www.dailymotion.com/embed/video/${dailymotionMatch[2]}"></iframe>`);
50
- element.remove();
39
+ replaceMedia(element, "iframe", `https://www.dailymotion.com/embed/video/${dailymotionMatch[2]}`);
51
40
  } else if (url.indexOf("bilibili.com") > -1 && (url.indexOf("bvid=") > -1 || (bilibiliMatch && bilibiliMatch[1]))) {
52
41
  const params: IObject = {
53
42
  bvid: getSearch("bvid", url) || (bilibiliMatch && bilibiliMatch[1]),
@@ -75,13 +64,9 @@ const iframeRender = (element: HTMLElement, url: string) => {
75
64
  src += "&";
76
65
  }
77
66
  });
78
- element.insertAdjacentHTML("afterend",
79
- `<iframe class="iframe__video" src="${src}"></iframe>`);
80
- element.remove();
67
+ replaceMedia(element, "iframe", src);
81
68
  } else if (tedMatch && tedMatch[1]) {
82
- element.insertAdjacentHTML("afterend",
83
- `<iframe class="iframe__video" src="//embed.ted.com/talks/${tedMatch[1]}"></iframe>`);
84
- element.remove();
69
+ replaceMedia(element, "iframe", `//embed.ted.com/talks/${tedMatch[1]}`);
85
70
  }
86
71
  };
87
72
 
@@ -95,9 +80,9 @@ export const mediaRender = (element: HTMLElement) => {
95
80
  return;
96
81
  }
97
82
  if (url.match(/^.+.(mp4|m4v|ogg|ogv|webm)$/)) {
98
- videoRender(aElement, url);
83
+ replaceMedia(aElement, "video", url);
99
84
  } else if (url.match(/^.+.(mp3|wav|flac)$/)) {
100
- audioRender(aElement, url);
85
+ replaceMedia(aElement, "audio", url);
101
86
  } else {
102
87
  iframeRender(aElement, url);
103
88
  }
@@ -13,7 +13,7 @@ export const mermaidRender = (element: (HTMLElement | Document) = document, cdn
13
13
  if (mermaidElements.length === 0) {
14
14
  return;
15
15
  }
16
- addScript(`${cdn}/dist/js/mermaid/mermaid.min.js?v=11.16.1`, "vditorMermaidScript").then(() => {
16
+ addScript(`${cdn}/dist/js/mermaid/mermaid.min.js?v=11.17.2`, "vditorMermaidScript").then(() => {
17
17
  const config: any = {
18
18
  securityLevel: "loose", // 升级后无 https://github.com/siyuan-note/siyuan/issues/3587,可使用该选项
19
19
  altFontFamily: "sans-serif",
@@ -62,6 +62,7 @@ export const md2html = (mdText: string, options?: IPreviewOptions) => {
62
62
  const lute = setLute({
63
63
  autoSpace: mergedOptions.markdown.autoSpace,
64
64
  callout: mergedOptions.markdown.callout,
65
+ inlineMath: mergedOptions.markdown.inlineMath,
65
66
  gfmAutoLink: mergedOptions.markdown.gfmAutoLink,
66
67
  codeBlockPreview: mergedOptions.markdown.codeBlockPreview,
67
68
  emojiSite: mergedOptions.emojiPath,
@@ -6,6 +6,7 @@ export const setLute = (options: ILuteOptions) => {
6
6
  lute.SetInlineMathAllowDigitAfterOpenMarker(options.inlineMathDigit);
7
7
  lute.SetAutoSpace(options.autoSpace);
8
8
  lute.SetCallout(options.callout);
9
+ lute.SetInlineMath(options.inlineMath);
9
10
  lute.SetToC(options.toc);
10
11
  lute.SetFootnotes(options.footnotes);
11
12
  lute.SetFixTermTypo(options.fixTermTypo);
@@ -1,55 +1,39 @@
1
- import {isCtrl, isFirefox} from "../util/compatibility";
2
- import {
3
- blurEvent,
4
- copyEvent, cutEvent,
5
- dropEvent,
6
- focusEvent,
7
- hotkeyEvent,
8
- scrollCenter,
9
- selectEvent,
10
- } from "../util/editorCommonEvent";
1
+ import {getMarkdown} from "../markdown/getMarkdown";
2
+ import {isFirefox} from "../util/compatibility";
3
+ import {dropEvent, focusEvent, hotkeyEvent} from "../util/editorCommonEvent";
11
4
  import {paste} from "../util/fixBrowserBehavior";
12
- import {getSelectText} from "../util/getSelectText";
13
- import {inputEvent} from "./inputEvent";
14
- import {processAfterRender} from "./process";
5
+ import {processAfterRender, processPaste} from "./process";
15
6
 
16
7
  class Editor {
17
- public range: Range;
18
- public element: HTMLPreElement;
8
+ public element: HTMLTextAreaElement;
19
9
  public composingLock: boolean = false;
20
10
  public processTimeoutId: number;
21
- public hlToolbarTimeoutId: number;
22
- public preventInput: boolean;
23
11
 
24
12
  constructor(vditor: IVditor) {
25
- this.element = document.createElement("pre");
13
+ this.element = document.createElement("textarea");
26
14
  this.element.className = "vditor-sv vditor-reset";
27
- this.element.setAttribute("placeholder", vditor.options.placeholder);
28
- this.element.setAttribute("contenteditable", "true");
29
- this.element.setAttribute("spellcheck", "false");
15
+ this.element.placeholder = vditor.options.placeholder;
16
+ this.element.spellcheck = false;
30
17
 
31
18
  this.bindEvent(vditor);
32
-
33
19
  focusEvent(vditor, this.element);
34
- blurEvent(vditor, this.element);
35
20
  hotkeyEvent(vditor, this.element);
36
- selectEvent(vditor, this.element);
37
- dropEvent(vditor, this.element);
38
- copyEvent(vditor, this.element, this.copy);
39
- cutEvent(vditor, this.element, this.copy);
40
- }
41
-
42
- private copy(event: ClipboardEvent, vditor: IVditor) {
43
- event.stopPropagation();
44
- event.preventDefault();
45
- event.clipboardData.setData("text/plain", getSelectText(vditor[vditor.currentMode].element));
21
+ dropEvent(vditor, this.element, (code: string) => {
22
+ processPaste(vditor, code.replace(/&lt;/g, "<").replace(/&amp;/g, "&"));
23
+ });
46
24
  }
47
25
 
48
26
  private bindEvent(vditor: IVditor) {
49
- this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => {
27
+ this.element.addEventListener("blur", () => {
28
+ if (vditor.options.blur) {
29
+ vditor.options.blur(getMarkdown(vditor));
30
+ }
31
+ });
32
+
33
+ this.element.addEventListener("paste", (event: ClipboardEvent & {target: HTMLElement}) => {
50
34
  paste(vditor, event, {
51
35
  pasteCode: (code: string) => {
52
- document.execCommand("insertHTML", false, code);
36
+ processPaste(vditor, code.replace(/&lt;/g, "<").replace(/&amp;/g, "&"));
53
37
  },
54
38
  });
55
39
  });
@@ -62,61 +46,38 @@ class Editor {
62
46
  const textHeight = this.element.clientHeight;
63
47
  const textScrollHeight = this.element.scrollHeight - parseFloat(this.element.style.paddingBottom || "0");
64
48
  const preview = vditor.preview.element;
65
- if ((textScrollTop / textHeight > 0.5)) {
66
- preview.scrollTop = (textScrollTop + textHeight) *
67
- preview.scrollHeight / textScrollHeight - textHeight;
49
+ if (textScrollTop / textHeight > 0.5) {
50
+ preview.scrollTop = (textScrollTop + textHeight) * preview.scrollHeight / textScrollHeight - textHeight;
68
51
  } else {
69
- preview.scrollTop = textScrollTop *
70
- preview.scrollHeight / textScrollHeight;
52
+ preview.scrollTop = textScrollTop * preview.scrollHeight / textScrollHeight;
71
53
  }
72
54
  });
73
55
 
74
- this.element.addEventListener("compositionstart", (event: InputEvent) => {
56
+ this.element.addEventListener("compositionstart", () => {
75
57
  this.composingLock = true;
76
58
  });
77
59
 
78
- this.element.addEventListener("compositionend", (event: InputEvent) => {
60
+ this.element.addEventListener("compositionend", () => {
61
+ this.composingLock = false;
79
62
  if (!isFirefox()) {
80
- inputEvent(vditor, event);
63
+ processAfterRender(vditor);
81
64
  }
82
- this.composingLock = false;
83
65
  });
84
66
 
85
- this.element.addEventListener("input", (event: InputEvent) => {
86
- if (event.inputType === "deleteByDrag" || event.inputType === "insertFromDrop") {
87
- // https://github.com/Vanessa219/vditor/issues/801 编辑器内容拖拽问题
88
- return;
89
- }
90
- if (this.composingLock || event.data === "‘" || event.data === "“" || event.data === "《") {
91
- return;
92
- }
93
- if (this.preventInput) {
94
- this.preventInput = false;
95
- processAfterRender(vditor, {
96
- enableAddUndoStack: true,
97
- enableHint: true,
98
- enableInput: true,
99
- });
100
- return;
67
+ this.element.addEventListener("input", () => {
68
+ if (!this.composingLock) {
69
+ processAfterRender(vditor);
101
70
  }
102
- inputEvent(vditor, event);
103
71
  });
104
72
 
105
- this.element.addEventListener("keyup", (event) => {
106
- if (event.isComposing || isCtrl(event)) {
107
- return;
108
- }
109
- if ((event.key === "Backspace" || event.key === "Delete") &&
110
- vditor.sv.element.innerHTML !== "" && vditor.sv.element.childNodes.length === 1 &&
111
- vditor.sv.element.firstElementChild && vditor.sv.element.firstElementChild.tagName === "DIV"
112
- && vditor.sv.element.firstElementChild.childElementCount === 2
113
- && (vditor.sv.element.firstElementChild.textContent === "" || vditor.sv.element.textContent === "\n")) {
114
- // 为空时显示 placeholder
115
- vditor.sv.element.innerHTML = "";
116
- return;
117
- }
118
- if (event.key === "Enter") {
119
- scrollCenter(vditor);
73
+ this.element.addEventListener("select", () => {
74
+ const selectedText = this.element.value.substring(this.element.selectionStart, this.element.selectionEnd);
75
+ if (selectedText.trim()) {
76
+ if (vditor.options.select) {
77
+ vditor.options.select(selectedText);
78
+ }
79
+ } else if (vditor.options.unSelect) {
80
+ vditor.options.unSelect();
120
81
  }
121
82
  });
122
83
  }
@@ -1,104 +1,14 @@
1
1
  import {getMarkdown} from "../markdown/getMarkdown";
2
2
  import {accessLocalStorage} from "../util/compatibility";
3
- import {scrollCenter} from "../util/editorCommonEvent";
4
- import {hasClosestBlock, hasClosestByAttribute} from "../util/hasClosest";
5
- import {hasClosestByTag} from "../util/hasClosestByHeadings";
6
- import {log} from "../util/log";
7
- import {getEditorRange, setRangeByWbr} from "../util/selection";
8
- import {inputEvent} from "./inputEvent";
9
- import {combineFootnote} from "./combineFootnote";
10
3
 
11
-
12
- export const processPaste = (vditor: IVditor, text: string) => {
13
- const range = getEditorRange(vditor);
14
- range.extractContents();
15
- range.insertNode(document.createTextNode(Lute.Caret));
16
- range.insertNode(document.createTextNode(text));
17
- let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
18
- if (!blockElement) {
19
- blockElement = vditor.sv.element;
20
- }
21
- let spinHTML = vditor.lute.SpinVditorSVDOM(blockElement.textContent)
22
- spinHTML = "<div data-block='0'>" +
23
- spinHTML.replace(/<span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span><span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span></g, '<span data-type="newline"><br /><span style="display: none">\n</span></span><span data-type="newline"><br /><span style="display: none">\n</span></span></div><div data-block="0"><') +
24
- "</div>";
25
- if (blockElement.isEqualNode(vditor.sv.element)) {
26
- blockElement.innerHTML = spinHTML;
27
- } else {
28
- blockElement.outerHTML = spinHTML;
29
- }
30
- combineFootnote(vditor.sv.element)
31
- setRangeByWbr(vditor.sv.element, range);
32
-
33
- scrollCenter(vditor);
34
- };
35
-
36
- export const getSideByType = (spanNode: Node, type: string, isPrevious = true) => {
37
- let sideElement = spanNode as Element;
38
- if (sideElement.nodeType === 3) {
39
- sideElement = sideElement.parentElement;
40
- }
41
- while (sideElement) {
42
- if (sideElement.getAttribute("data-type") === type) {
43
- return sideElement;
44
- }
45
- if (isPrevious) {
46
- sideElement = sideElement.previousElementSibling;
47
- } else {
48
- sideElement = sideElement.nextElementSibling;
49
- }
50
- }
51
- return false;
4
+ export const replaceSelection = (vditor: IVditor, text: string, selectionMode: SelectionMode = "end") => {
5
+ const element = vditor.sv.element;
6
+ element.setRangeText(text, element.selectionStart, element.selectionEnd, selectionMode);
7
+ element.focus();
52
8
  };
53
9
 
54
- export const processSpinVditorSVDOM = (html: string, vditor: IVditor) => {
55
- log("SpinVditorSVDOM", html, "argument", vditor.options.debugger);
56
- const spinHTML = vditor.lute.SpinVditorSVDOM(html)
57
- html = "<div data-block='0'>" +
58
- spinHTML.replace(/<span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span><span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span></g, '<span data-type="newline"><br /><span style="display: none">\n</span></span><span data-type="newline"><br /><span style="display: none">\n</span></span></div><div data-block="0"><') +
59
- "</div>";
60
- log("SpinVditorSVDOM", html, "result", vditor.options.debugger);
61
- return html;
62
- };
63
-
64
- export const processPreviousMarkers = (spanElement: HTMLElement) => {
65
- const spanType = spanElement.getAttribute("data-type");
66
- let previousElement = spanElement.previousElementSibling;
67
- // 有内容的子列表/标题,在其 marker 后换行
68
- let markerText = (spanType && spanType !== "text" && spanType !== "table" && spanType !== "heading-marker" &&
69
- spanType !== "newline" && spanType !== "yaml-front-matter-open-marker" && spanType !== "yaml-front-matter-close-marker"
70
- && spanType !== "code-block-info" && spanType !== "code-block-close-marker" && spanType !== "code-block-open-marker") ?
71
- spanElement.textContent : "";
72
- let hasNL = false;
73
- if (spanType === "newline") {
74
- hasNL = true;
75
- }
76
- while (previousElement && !hasNL) {
77
- const previousType = previousElement.getAttribute("data-type");
78
- if (previousType === "li-marker" || previousType === "blockquote-marker" || previousType === "task-marker" ||
79
- previousType === "padding") {
80
- const previousText = previousElement.textContent;
81
- if (previousType === "li-marker" &&
82
- (spanType === "code-block-open-marker" || spanType === "code-block-info")) {
83
- // https://github.com/Vanessa219/vditor/issues/586
84
- markerText = previousText.replace(/\S/g, " ") + markerText;
85
- } else if (spanType === "code-block-close-marker" &&
86
- previousElement.nextElementSibling.isSameNode(spanElement)) {
87
- // https://github.com/Vanessa219/vditor/issues/594
88
- const openMarker = getSideByType(spanElement, "code-block-open-marker");
89
- if (openMarker && openMarker.previousElementSibling) {
90
- previousElement = openMarker.previousElementSibling;
91
- markerText = previousText + markerText;
92
- }
93
- } else {
94
- markerText = previousText + markerText;
95
- }
96
- } else if (previousType === "newline") {
97
- hasNL = true;
98
- }
99
- previousElement = previousElement.previousElementSibling;
100
- }
101
- return markerText;
10
+ export const processPaste = (vditor: IVditor, text: string) => {
11
+ replaceSelection(vditor, text);
102
12
  };
103
13
 
104
14
  export const processAfterRender = (vditor: IVditor, options = {
@@ -106,10 +16,6 @@ export const processAfterRender = (vditor: IVditor, options = {
106
16
  enableHint: false,
107
17
  enableInput: true,
108
18
  }) => {
109
- if (options.enableHint) {
110
- vditor.hint.render(vditor);
111
- }
112
-
113
19
  vditor.preview.render(vditor);
114
20
 
115
21
  const text = getMarkdown(vditor);
@@ -141,75 +47,53 @@ export const processAfterRender = (vditor: IVditor, options = {
141
47
  };
142
48
 
143
49
  export const processHeading = (vditor: IVditor, value: string) => {
144
- const range = getEditorRange(vditor);
145
- const headingElement = hasClosestByTag(range.startContainer, "SPAN");
146
- if (headingElement && headingElement.textContent.trim() !== "") {
147
- value = "\n" + value;
148
- }
149
- range.collapse(true);
150
- document.execCommand("insertHTML", false, value);
50
+ const element = vditor.sv.element;
51
+ const lineStart = element.value.lastIndexOf("\n", element.selectionStart - 1) + 1;
52
+ const lineEndIndex = element.value.indexOf("\n", element.selectionEnd);
53
+ const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
54
+ const selectedLines = element.value.substring(lineStart, lineEnd);
55
+ const heading = value ? value + selectedLines.replace(/^#{1,6}\s+/, "") : selectedLines.replace(/^#{1,6}\s+/, "");
56
+ element.setRangeText(heading, lineStart, lineEnd, "select");
57
+ element.focus();
58
+ processAfterRender(vditor);
151
59
  };
152
60
 
153
61
  export const processToolbar = (vditor: IVditor, actionBtn: Element, prefix: string, suffix: string) => {
154
- const range = getEditorRange(vditor);
62
+ const element = vditor.sv.element;
155
63
  const commandName = actionBtn.getAttribute("data-type");
156
- // 添加
157
- if (vditor.sv.element.childNodes.length === 0) {
158
- vditor.sv.element.innerHTML = `<span data-type="p" data-block="0"><span data-type="text"><wbr></span></span><span data-type="newline"><br><span style="display: none">
159
- </span></span>`;
160
- setRangeByWbr(vditor.sv.element, range);
161
- }
162
- const blockElement = hasClosestBlock(range.startContainer);
163
- const spanElement = hasClosestByTag(range.startContainer, "SPAN");
164
- if (!blockElement) {
165
- return;
166
- }
167
- if (commandName === "link") {
168
- let html;
169
- if (range.toString() === "") {
170
- html = `${prefix}${Lute.Caret}${suffix}`;
171
- } else {
172
- html = `${prefix}${range.toString()}${suffix.replace(")", Lute.Caret + ")")}`;
173
- }
174
- document.execCommand("insertHTML", false, html);
175
- return;
176
- } else if (commandName === "italic" || commandName === "bold" || commandName === "strike" ||
177
- commandName === "inline-code" || commandName === "code" || commandName === "table" || commandName === "line") {
178
- let html;
179
- // https://github.com/Vanessa219/vditor/issues/563 代码块不需要后面的 ```
180
- if (range.toString() === "") {
181
- html = `${prefix}${Lute.Caret}${commandName === "code" ? "" : suffix}`;
182
- } else {
183
- html = `${prefix}${range.toString()}${Lute.Caret}${commandName === "code" ? "" : suffix}`;
184
- }
185
- if (commandName === "table" || (commandName === "code" && spanElement && spanElement.textContent !== "")) {
186
- html = "\n\n" + html;
187
- } else if (commandName === "line") {
188
- html = `\n\n${prefix}\n${Lute.Caret}`;
189
- }
190
- document.execCommand("insertHTML", false, html);
191
- return;
192
- } else if (commandName === "check" || commandName === "list" || commandName === "ordered-list" ||
64
+ const start = element.selectionStart;
65
+ const end = element.selectionEnd;
66
+ const selectedText = element.value.substring(start, end);
67
+ let text = "";
68
+ let selectionStart = start + prefix.length;
69
+ let selectionEnd = selectionStart + selectedText.length;
70
+
71
+ if (commandName === "check" || commandName === "list" || commandName === "ordered-list" ||
193
72
  commandName === "quote") {
194
- if (spanElement) {
195
- let marker = "* ";
196
- if (commandName === "check") {
197
- marker = "* [ ] ";
198
- } else if (commandName === "ordered-list") {
199
- marker = "1. ";
200
- } else if (commandName === "quote") {
201
- marker = "> ";
202
- }
203
- const newLine = getSideByType(spanElement, "newline");
204
- if (newLine) {
205
- newLine.insertAdjacentText("afterend", marker);
206
- } else {
207
- blockElement.insertAdjacentText("afterbegin", marker);
208
- }
209
- inputEvent(vditor);
210
- return;
211
- }
73
+ const lineStart = element.value.lastIndexOf("\n", start - 1) + 1;
74
+ const lineEndIndex = element.value.indexOf("\n", end);
75
+ const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
76
+ const lines = element.value.substring(lineStart, lineEnd);
77
+ text = lines.split("\n").map((line) => prefix + line).join("\n");
78
+ element.setRangeText(text, lineStart, lineEnd, "select");
79
+ } else if (commandName === "line") {
80
+ text = `${start === 0 ? "" : "\n\n"}${prefix}\n`;
81
+ element.setRangeText(text, start, end, "end");
82
+ } else if (commandName === "code") {
83
+ text = `${prefix}\n${selectedText}${suffix}`;
84
+ element.setRangeText(text, start, end, "end");
85
+ selectionStart = start + prefix.length + 1;
86
+ selectionEnd = selectionStart + selectedText.length;
87
+ element.setSelectionRange(selectionStart, selectionEnd);
88
+ } else if (commandName === "table") {
89
+ text = `${start === 0 ? "" : "\n\n"}${prefix}${suffix}`;
90
+ element.setRangeText(text, start, end, "end");
91
+ } else {
92
+ text = `${prefix}${selectedText}${suffix}`;
93
+ element.setRangeText(text, start, end, "end");
94
+ element.setSelectionRange(selectionStart, selectionEnd);
212
95
  }
213
- setRangeByWbr(vditor.sv.element, range);
96
+
97
+ element.focus();
214
98
  processAfterRender(vditor);
215
99
  };