@8btc/mditor 0.0.24 → 0.0.25
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/dist/css/content-theme/dark.css +10 -0
- package/dist/css/content-theme/light.css +10 -0
- package/dist/index.css +58 -2
- package/dist/index.js +222 -29
- package/dist/index.min.js +1 -1
- package/dist/method.js +122 -4
- package/dist/method.min.js +1 -1
- package/dist/ts/ir/index.d.ts +2 -0
- package/dist/ts/preview/index.d.ts +2 -0
- package/dist/ts/sv/index.d.ts +2 -0
- package/dist/ts/util/mathSelection.d.ts +14 -0
- package/dist/ts/wysiwyg/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/assets/less/_content.less +12 -0
- package/src/assets/less/_ir.less +12 -0
- package/src/assets/less/_sv.less +12 -0
- package/src/assets/less/_wysiwyg.less +12 -0
- package/src/assets/less/index.less +5 -0
- package/src/ts/ir/index.ts +43 -0
- package/src/ts/markdown/previewRender.ts +77 -0
- package/src/ts/preview/index.ts +12 -0
- package/src/ts/sv/index.ts +12 -0
- package/src/ts/util/hasClosest.ts +29 -8
- package/src/ts/util/mathSelection.ts +59 -0
- package/src/ts/wysiwyg/index.ts +61 -33
package/src/assets/less/_sv.less
CHANGED
|
@@ -129,4 +129,16 @@
|
|
|
129
129
|
font-weight: bold;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
// 数学公式选中高亮样式
|
|
134
|
+
.language-math {
|
|
135
|
+
user-select: all;
|
|
136
|
+
cursor: text;
|
|
137
|
+
transition: background-color 0.1s ease;
|
|
138
|
+
|
|
139
|
+
&.vditor-math--selected {
|
|
140
|
+
background-color: highlight !important;
|
|
141
|
+
color: highlighttext !important;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
132
144
|
}
|
|
@@ -90,6 +90,18 @@
|
|
|
90
90
|
font-size: 100% !important;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// 数学公式选中高亮样式
|
|
94
|
+
.language-math {
|
|
95
|
+
user-select: all;
|
|
96
|
+
cursor: text;
|
|
97
|
+
transition: background-color 0.1s ease;
|
|
98
|
+
|
|
99
|
+
&.vditor-math--selected {
|
|
100
|
+
background-color: highlight !important;
|
|
101
|
+
color: highlighttext !important;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
& > .vditor-reset > h1:before,
|
|
94
106
|
& > .vditor-reset > h2:before,
|
|
95
107
|
& > .vditor-reset > h3:before,
|
package/src/ts/ir/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { highlightToolbarIR } from "./highlightToolbarIR";
|
|
|
21
21
|
import { input } from "./input";
|
|
22
22
|
import { processAfterRender, processHint } from "./process";
|
|
23
23
|
import { hidePanel } from "../toolbar/setToolbar";
|
|
24
|
+
import { bindMathSelectionListener } from "../util/mathSelection";
|
|
24
25
|
|
|
25
26
|
class IR {
|
|
26
27
|
public range: Range;
|
|
@@ -29,6 +30,7 @@ class IR {
|
|
|
29
30
|
public hlToolbarTimeoutId: number;
|
|
30
31
|
public composingLock: boolean = false;
|
|
31
32
|
public preventInput: boolean;
|
|
33
|
+
private mathSelectionCleanup: () => void;
|
|
32
34
|
|
|
33
35
|
constructor(vditor: IVditor) {
|
|
34
36
|
const divElement = document.createElement("div");
|
|
@@ -52,6 +54,16 @@ class IR {
|
|
|
52
54
|
dropEvent(vditor, this.element);
|
|
53
55
|
copyEvent(vditor, this.element, this.copy);
|
|
54
56
|
cutEvent(vditor, this.element, this.copy);
|
|
57
|
+
|
|
58
|
+
// 绑定数学公式选中高亮监听
|
|
59
|
+
this.mathSelectionCleanup = bindMathSelectionListener(this.element);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public unbindListener() {
|
|
63
|
+
// 清理数学公式选中监听
|
|
64
|
+
if (this.mathSelectionCleanup) {
|
|
65
|
+
this.mathSelectionCleanup();
|
|
66
|
+
}
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
private copy(event: ClipboardEvent, vditor: IVditor) {
|
|
@@ -62,8 +74,39 @@ class IR {
|
|
|
62
74
|
event.stopPropagation();
|
|
63
75
|
event.preventDefault();
|
|
64
76
|
|
|
77
|
+
// 检查选区是否在数学公式元素内
|
|
78
|
+
let mathElement = hasClosestByClassName(
|
|
79
|
+
range.startContainer,
|
|
80
|
+
"language-math"
|
|
81
|
+
) as HTMLElement;
|
|
82
|
+
const mathEndElement = hasClosestByClassName(
|
|
83
|
+
range.endContainer,
|
|
84
|
+
"language-math"
|
|
85
|
+
) as HTMLElement;
|
|
86
|
+
|
|
87
|
+
// 如果选区的起点和终点都在同一个数学公式元素内,复制源码
|
|
88
|
+
if (mathElement && mathEndElement && mathElement.isSameNode(mathEndElement)) {
|
|
89
|
+
const mathSource = mathElement.getAttribute("data-math") || range.toString();
|
|
90
|
+
event.clipboardData.setData("text/plain", mathSource);
|
|
91
|
+
event.clipboardData.setData("text/html", "");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 处理包含多个公式的混合内容
|
|
65
96
|
const tempElement = document.createElement("div");
|
|
66
97
|
tempElement.appendChild(range.cloneContents());
|
|
98
|
+
|
|
99
|
+
const mathElements = tempElement.querySelectorAll(".language-math");
|
|
100
|
+
if (mathElements.length > 0) {
|
|
101
|
+
// 替换所有数学公式为源码
|
|
102
|
+
mathElements.forEach((mathEl: HTMLElement) => {
|
|
103
|
+
const mathSource = mathEl.getAttribute("data-math");
|
|
104
|
+
if (mathSource) {
|
|
105
|
+
const textNode = document.createTextNode(mathSource);
|
|
106
|
+
mathEl.parentNode.replaceChild(textNode, mathEl);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
67
110
|
|
|
68
111
|
event.clipboardData.setData("text/plain", vditor.lute.VditorIRDOM2Md(tempElement.innerHTML).trim());
|
|
69
112
|
event.clipboardData.setData("text/html", "");
|
|
@@ -24,6 +24,7 @@ import { plantumlRender } from "./plantumlRender";
|
|
|
24
24
|
import { setLute } from "./setLute";
|
|
25
25
|
import { speechRender } from "./speechRender";
|
|
26
26
|
import { selectionRender } from "./selectionRender";
|
|
27
|
+
import { bindMathSelectionListener } from "../util/mathSelection";
|
|
27
28
|
|
|
28
29
|
const mergeOptions = (options?: IPreviewOptions) => {
|
|
29
30
|
const defaultOption: IPreviewOptions = {
|
|
@@ -207,6 +208,82 @@ export const previewRender = async (
|
|
|
207
208
|
if (mergedOptions.lazyLoadImage) {
|
|
208
209
|
lazyLoadImageRender(previewElement);
|
|
209
210
|
}
|
|
211
|
+
// 绑定数学公式选中高亮监听
|
|
212
|
+
bindMathSelectionListener(previewElement);
|
|
213
|
+
|
|
214
|
+
// 绑定复制事件,处理数学公式源码复制
|
|
215
|
+
previewElement.addEventListener("copy", (event: ClipboardEvent) => {
|
|
216
|
+
const selection = window.getSelection();
|
|
217
|
+
if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const range = selection.getRangeAt(0);
|
|
222
|
+
|
|
223
|
+
// 检查选区是否只包含单个数学公式
|
|
224
|
+
let mathElement = hasClosestByClassName(
|
|
225
|
+
range.startContainer,
|
|
226
|
+
"language-math"
|
|
227
|
+
) as HTMLElement;
|
|
228
|
+
const mathEndElement = hasClosestByClassName(
|
|
229
|
+
range.endContainer,
|
|
230
|
+
"language-math"
|
|
231
|
+
) as HTMLElement;
|
|
232
|
+
|
|
233
|
+
// 如果选区的起点和终点都在同一个数学公式元素内,复制源码
|
|
234
|
+
if (
|
|
235
|
+
mathElement &&
|
|
236
|
+
mathEndElement &&
|
|
237
|
+
mathElement.isSameNode(mathEndElement)
|
|
238
|
+
) {
|
|
239
|
+
event.stopPropagation();
|
|
240
|
+
event.preventDefault();
|
|
241
|
+
const mathSource =
|
|
242
|
+
mathElement.getAttribute("data-math") || range.toString();
|
|
243
|
+
event.clipboardData.setData("text/plain", mathSource);
|
|
244
|
+
event.clipboardData.setData("text/html", "");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 处理包含多个公式的混合内容
|
|
249
|
+
const fragment = range.cloneContents();
|
|
250
|
+
const mathElements = fragment.querySelectorAll(".language-math");
|
|
251
|
+
|
|
252
|
+
if (mathElements.length > 0) {
|
|
253
|
+
event.stopPropagation();
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
|
|
256
|
+
// 创建临时容器来处理内容
|
|
257
|
+
const tempDiv = document.createElement("div");
|
|
258
|
+
tempDiv.appendChild(fragment);
|
|
259
|
+
|
|
260
|
+
// 替换所有数学公式为源码
|
|
261
|
+
tempDiv
|
|
262
|
+
.querySelectorAll(".language-math")
|
|
263
|
+
.forEach((mathEl: HTMLElement) => {
|
|
264
|
+
const mathSource = mathEl.getAttribute("data-math");
|
|
265
|
+
console.log(mathEl.tagName, "mathEl");
|
|
266
|
+
if (mathSource) {
|
|
267
|
+
if (mathEl.tagName === "SPAN") {
|
|
268
|
+
const textNode = document.createTextNode(
|
|
269
|
+
`$${mathSource}$`
|
|
270
|
+
);
|
|
271
|
+
mathEl.parentNode.replaceChild(textNode, mathEl);
|
|
272
|
+
} else {
|
|
273
|
+
const textNode = document.createTextNode(
|
|
274
|
+
`$$${mathSource}$$`
|
|
275
|
+
);
|
|
276
|
+
mathEl.parentNode.replaceChild(textNode, mathEl);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// 获取纯文本内容
|
|
282
|
+
const plainText = tempDiv.textContent || tempDiv.innerText || "";
|
|
283
|
+
event.clipboardData.setData("text/plain", plainText);
|
|
284
|
+
event.clipboardData.setData("text/html", "");
|
|
285
|
+
}
|
|
286
|
+
});
|
|
210
287
|
|
|
211
288
|
previewElement.addEventListener(
|
|
212
289
|
"click",
|
package/src/ts/preview/index.ts
CHANGED
|
@@ -19,11 +19,13 @@ import {
|
|
|
19
19
|
import { setSelectionFocus } from "../util/selection";
|
|
20
20
|
import { selectionRender } from "../markdown/selectionRender";
|
|
21
21
|
import { previewImage } from "./image";
|
|
22
|
+
import { bindMathSelectionListener } from "../util/mathSelection";
|
|
22
23
|
|
|
23
24
|
export class Preview {
|
|
24
25
|
public element: HTMLElement;
|
|
25
26
|
public previewElement: HTMLElement;
|
|
26
27
|
private mdTimeoutId: number;
|
|
28
|
+
private mathSelectionCleanup: () => void;
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* 构造预览区域容器,注册复制与点击事件。
|
|
@@ -98,6 +100,16 @@ export class Preview {
|
|
|
98
100
|
}
|
|
99
101
|
);
|
|
100
102
|
this.element.appendChild(this.previewElement);
|
|
103
|
+
|
|
104
|
+
// 绑定数学公式选中高亮监听
|
|
105
|
+
this.mathSelectionCleanup = bindMathSelectionListener(this.previewElement);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public unbindListener() {
|
|
109
|
+
// 清理数学公式选中监听
|
|
110
|
+
if (this.mathSelectionCleanup) {
|
|
111
|
+
this.mathSelectionCleanup();
|
|
112
|
+
}
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
/**
|
package/src/ts/sv/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { paste } from "../util/fixBrowserBehavior";
|
|
|
12
12
|
import { getSelectText } from "../util/getSelectText";
|
|
13
13
|
import { inputEvent } from "./inputEvent";
|
|
14
14
|
import { processAfterRender } from "./process";
|
|
15
|
+
import { bindMathSelectionListener } from "../util/mathSelection";
|
|
15
16
|
|
|
16
17
|
class Editor {
|
|
17
18
|
public range: Range;
|
|
@@ -20,6 +21,7 @@ class Editor {
|
|
|
20
21
|
public processTimeoutId: number;
|
|
21
22
|
public hlToolbarTimeoutId: number;
|
|
22
23
|
public preventInput: boolean;
|
|
24
|
+
private mathSelectionCleanup: () => void;
|
|
23
25
|
|
|
24
26
|
constructor(vditor: IVditor) {
|
|
25
27
|
this.element = document.createElement("pre");
|
|
@@ -40,6 +42,16 @@ class Editor {
|
|
|
40
42
|
dropEvent(vditor, this.element);
|
|
41
43
|
copyEvent(vditor, this.element, this.copy);
|
|
42
44
|
cutEvent(vditor, this.element, this.copy);
|
|
45
|
+
|
|
46
|
+
// 绑定数学公式选中高亮监听
|
|
47
|
+
this.mathSelectionCleanup = bindMathSelectionListener(this.element);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public unbindListener() {
|
|
51
|
+
// 清理数学公式选中监听
|
|
52
|
+
if (this.mathSelectionCleanup) {
|
|
53
|
+
this.mathSelectionCleanup();
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
private copy(event: ClipboardEvent, vditor: IVditor) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {hasClosestByTag} from "./hasClosestByHeadings";
|
|
1
|
+
import { hasClosestByTag } from "./hasClosestByHeadings";
|
|
2
2
|
|
|
3
3
|
export const hasTopClosestByClassName = (element: Node, className: string) => {
|
|
4
4
|
let closest = hasClosestByClassName(element, className);
|
|
@@ -15,12 +15,20 @@ export const hasTopClosestByClassName = (element: Node, className: string) => {
|
|
|
15
15
|
return closest || false;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export const hasTopClosestByAttribute = (
|
|
18
|
+
export const hasTopClosestByAttribute = (
|
|
19
|
+
element: Node,
|
|
20
|
+
attr: string,
|
|
21
|
+
value: string
|
|
22
|
+
) => {
|
|
19
23
|
let closest = hasClosestByAttribute(element, attr, value);
|
|
20
24
|
let parentClosest: boolean | HTMLElement = false;
|
|
21
25
|
let findTop = false;
|
|
22
26
|
while (closest && !closest.classList.contains("vditor-reset") && !findTop) {
|
|
23
|
-
parentClosest = hasClosestByAttribute(
|
|
27
|
+
parentClosest = hasClosestByAttribute(
|
|
28
|
+
closest.parentElement,
|
|
29
|
+
attr,
|
|
30
|
+
value
|
|
31
|
+
);
|
|
24
32
|
if (parentClosest) {
|
|
25
33
|
closest = parentClosest;
|
|
26
34
|
} else {
|
|
@@ -49,13 +57,20 @@ export const getTopList = (element: Node) => {
|
|
|
49
57
|
const topUlElement = hasTopClosestByTag(element, "UL");
|
|
50
58
|
const topOlElement = hasTopClosestByTag(element, "OL");
|
|
51
59
|
let topListElement = topUlElement;
|
|
52
|
-
if (
|
|
60
|
+
if (
|
|
61
|
+
topOlElement &&
|
|
62
|
+
(!topUlElement || (topUlElement && topOlElement.contains(topUlElement)))
|
|
63
|
+
) {
|
|
53
64
|
topListElement = topOlElement;
|
|
54
65
|
}
|
|
55
66
|
return topListElement;
|
|
56
67
|
};
|
|
57
68
|
|
|
58
|
-
export const hasClosestByAttribute = (
|
|
69
|
+
export const hasClosestByAttribute = (
|
|
70
|
+
element: Node,
|
|
71
|
+
attr: string,
|
|
72
|
+
value: string
|
|
73
|
+
) => {
|
|
59
74
|
if (!element) {
|
|
60
75
|
return false;
|
|
61
76
|
}
|
|
@@ -84,13 +99,18 @@ export const hasClosestBlock = (element: Node) => {
|
|
|
84
99
|
let e = element as HTMLElement;
|
|
85
100
|
let isClosest = false;
|
|
86
101
|
|
|
87
|
-
const blockElement = hasClosestByAttribute(
|
|
102
|
+
const blockElement = hasClosestByAttribute(
|
|
103
|
+
element as HTMLElement,
|
|
104
|
+
"data-block",
|
|
105
|
+
"0"
|
|
106
|
+
);
|
|
88
107
|
if (blockElement) {
|
|
89
108
|
return blockElement;
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
while (e && !isClosest && !e.classList.contains("vditor-reset")) {
|
|
93
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
e.tagName === "H1" ||
|
|
94
114
|
e.tagName === "H2" ||
|
|
95
115
|
e.tagName === "H3" ||
|
|
96
116
|
e.tagName === "H4" ||
|
|
@@ -99,7 +119,8 @@ export const hasClosestBlock = (element: Node) => {
|
|
|
99
119
|
e.tagName === "P" ||
|
|
100
120
|
e.tagName === "BLOCKQUOTE" ||
|
|
101
121
|
e.tagName === "OL" ||
|
|
102
|
-
e.tagName === "UL"
|
|
122
|
+
e.tagName === "UL"
|
|
123
|
+
) {
|
|
103
124
|
isClosest = true;
|
|
104
125
|
} else {
|
|
105
126
|
e = e.parentElement;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数学公式选中高亮工具
|
|
3
|
+
* 监听选区变化,为选中的数学公式添加高亮样式
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const MATH_SELECTED_CLASS = "vditor-math--selected";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 检查并更新数学公式的选中高亮状态
|
|
10
|
+
* @param container 容器元素(编辑器或预览区域)
|
|
11
|
+
*/
|
|
12
|
+
export const updateMathSelection = (container: HTMLElement) => {
|
|
13
|
+
const selection = window.getSelection();
|
|
14
|
+
|
|
15
|
+
// 获取所有数学公式元素
|
|
16
|
+
const mathElements = container.querySelectorAll(".language-math");
|
|
17
|
+
|
|
18
|
+
// 移除所有旧的高亮
|
|
19
|
+
mathElements.forEach((el) => el.classList.remove(MATH_SELECTED_CLASS));
|
|
20
|
+
|
|
21
|
+
// 无选中内容则返回
|
|
22
|
+
if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 检测选中的公式节点并添加高亮
|
|
27
|
+
mathElements.forEach((element) => {
|
|
28
|
+
if (selection.containsNode(element, true)) {
|
|
29
|
+
element.classList.add(MATH_SELECTED_CLASS);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 为容器绑定数学公式选中高亮监听
|
|
36
|
+
* @param container 容器元素(编辑器或预览区域)
|
|
37
|
+
*/
|
|
38
|
+
export const bindMathSelectionListener = (container: HTMLElement) => {
|
|
39
|
+
const updateHandler = () => updateMathSelection(container);
|
|
40
|
+
|
|
41
|
+
// 监听选区变化
|
|
42
|
+
document.addEventListener("selectionchange", updateHandler);
|
|
43
|
+
|
|
44
|
+
// 监听鼠标抬起(处理拖拽选择)
|
|
45
|
+
container.addEventListener("mouseup", updateHandler);
|
|
46
|
+
|
|
47
|
+
// 监听键盘选择(Shift + 方向键)
|
|
48
|
+
container.addEventListener("keyup", (e: KeyboardEvent) => {
|
|
49
|
+
if (e.shiftKey && ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
|
|
50
|
+
updateHandler();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 返回清理函数
|
|
55
|
+
return () => {
|
|
56
|
+
document.removeEventListener("selectionchange", updateHandler);
|
|
57
|
+
container.removeEventListener("mouseup", updateHandler);
|
|
58
|
+
};
|
|
59
|
+
};
|
package/src/ts/wysiwyg/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { input } from "./input";
|
|
|
38
38
|
import { showCode } from "./showCode";
|
|
39
39
|
import { getSelectText } from "../util/getSelectText";
|
|
40
40
|
import { getMarkdown } from "../markdown/getMarkdown";
|
|
41
|
+
import { bindMathSelectionListener } from "../util/mathSelection";
|
|
41
42
|
|
|
42
43
|
class WYSIWYG {
|
|
43
44
|
public range: Range;
|
|
@@ -55,6 +56,7 @@ class WYSIWYG {
|
|
|
55
56
|
public commentIds: string[] = [];
|
|
56
57
|
private vditor: IVditor;
|
|
57
58
|
private scrollListener: () => void;
|
|
59
|
+
private mathSelectionCleanup: () => void;
|
|
58
60
|
|
|
59
61
|
constructor(vditor: IVditor) {
|
|
60
62
|
this.vditor = vditor;
|
|
@@ -322,6 +324,9 @@ class WYSIWYG {
|
|
|
322
324
|
};
|
|
323
325
|
}
|
|
324
326
|
}
|
|
327
|
+
|
|
328
|
+
// 绑定数学公式选中高亮监听
|
|
329
|
+
this.mathSelectionCleanup = bindMathSelectionListener(this.element);
|
|
325
330
|
}
|
|
326
331
|
|
|
327
332
|
public getComments(vditor: IVditor, getData = false) {
|
|
@@ -517,12 +522,16 @@ class WYSIWYG {
|
|
|
517
522
|
|
|
518
523
|
public unbindListener() {
|
|
519
524
|
window.removeEventListener("scroll", this.scrollListener);
|
|
525
|
+
// 清理数学公式选中监听
|
|
526
|
+
if (this.mathSelectionCleanup) {
|
|
527
|
+
this.mathSelectionCleanup();
|
|
528
|
+
}
|
|
520
529
|
}
|
|
521
530
|
|
|
522
531
|
/**
|
|
523
532
|
* 复制事件处理
|
|
524
533
|
* - 代码块与链接:按原有规则格式化复制
|
|
525
|
-
* -
|
|
534
|
+
* - 数学公式选区:复制公式源码(与 sv 模式一致)
|
|
526
535
|
* - 其他内容:转换为 Markdown 文本复制
|
|
527
536
|
*/
|
|
528
537
|
private copy(event: ClipboardEvent, vditor: IVditor) {
|
|
@@ -566,33 +575,42 @@ class WYSIWYG {
|
|
|
566
575
|
return;
|
|
567
576
|
}
|
|
568
577
|
|
|
569
|
-
//
|
|
570
|
-
|
|
578
|
+
// 数学公式选区:复制公式源码
|
|
579
|
+
// 检查选区是否在数学公式元素内
|
|
580
|
+
let mathElement = hasClosestByClassName(
|
|
571
581
|
range.startContainer,
|
|
572
|
-
"
|
|
582
|
+
"language-math"
|
|
573
583
|
) as HTMLElement;
|
|
574
|
-
const
|
|
584
|
+
const mathEndElement = hasClosestByClassName(
|
|
575
585
|
range.endContainer,
|
|
576
|
-
"
|
|
586
|
+
"language-math"
|
|
577
587
|
) as HTMLElement;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
startPreview &&
|
|
584
|
-
endPreview &&
|
|
585
|
-
startPreview.isSameNode(endPreview) &&
|
|
586
|
-
isMathPreview(startPreview)
|
|
587
|
-
) {
|
|
588
|
-
event.clipboardData.setData("text/plain", range.toString());
|
|
588
|
+
|
|
589
|
+
// 如果选区的起点和终点都在同一个数学公式元素内
|
|
590
|
+
if (mathElement && mathEndElement && mathElement.isSameNode(mathEndElement)) {
|
|
591
|
+
const mathSource = mathElement.getAttribute("data-math") || range.toString();
|
|
592
|
+
event.clipboardData.setData("text/plain", mathSource);
|
|
589
593
|
event.clipboardData.setData("text/html", "");
|
|
590
594
|
return;
|
|
591
595
|
}
|
|
592
596
|
|
|
593
|
-
//
|
|
597
|
+
// 处理包含多个公式的混合内容
|
|
594
598
|
const tempElement = document.createElement("div");
|
|
595
599
|
tempElement.appendChild(range.cloneContents());
|
|
600
|
+
|
|
601
|
+
const mathElements = tempElement.querySelectorAll(".language-math");
|
|
602
|
+
if (mathElements.length > 0) {
|
|
603
|
+
// 替换所有数学公式为源码
|
|
604
|
+
mathElements.forEach((mathEl: HTMLElement) => {
|
|
605
|
+
const mathSource = mathEl.getAttribute("data-math");
|
|
606
|
+
if (mathSource) {
|
|
607
|
+
const textNode = document.createTextNode(mathSource);
|
|
608
|
+
mathEl.parentNode.replaceChild(textNode, mathEl);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 默认:转换为 Markdown 文本
|
|
596
614
|
event.clipboardData.setData(
|
|
597
615
|
"text/plain",
|
|
598
616
|
vditor.lute.VditorDOM2Md(tempElement.innerHTML).trim()
|
|
@@ -630,28 +648,38 @@ class WYSIWYG {
|
|
|
630
648
|
html: "",
|
|
631
649
|
};
|
|
632
650
|
}
|
|
633
|
-
|
|
651
|
+
// 检查选区是否在数学公式元素内
|
|
652
|
+
let mathElement = hasClosestByClassName(
|
|
634
653
|
range.startContainer,
|
|
635
|
-
"
|
|
654
|
+
"language-math"
|
|
636
655
|
) as HTMLElement;
|
|
637
|
-
const
|
|
656
|
+
const mathEndElement = hasClosestByClassName(
|
|
638
657
|
range.endContainer,
|
|
639
|
-
"
|
|
658
|
+
"language-math"
|
|
640
659
|
) as HTMLElement;
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
startPreview &&
|
|
647
|
-
endPreview &&
|
|
648
|
-
startPreview.isSameNode(endPreview) &&
|
|
649
|
-
isMathPreview(startPreview)
|
|
650
|
-
) {
|
|
651
|
-
return { plainText: range.toString(), html: "" };
|
|
660
|
+
|
|
661
|
+
// 如果选区的起点和终点都在同一个数学公式元素内
|
|
662
|
+
if (mathElement && mathEndElement && mathElement.isSameNode(mathEndElement)) {
|
|
663
|
+
const mathSource = mathElement.getAttribute("data-math") || range.toString();
|
|
664
|
+
return { plainText: mathSource, html: "" };
|
|
652
665
|
}
|
|
666
|
+
|
|
667
|
+
// 处理包含多个公式的混合内容
|
|
653
668
|
const tempElement = document.createElement("div");
|
|
654
669
|
tempElement.appendChild(range.cloneContents());
|
|
670
|
+
|
|
671
|
+
const mathElements = tempElement.querySelectorAll(".language-math");
|
|
672
|
+
if (mathElements.length > 0) {
|
|
673
|
+
// 替换所有数学公式为源码
|
|
674
|
+
mathElements.forEach((mathEl: HTMLElement) => {
|
|
675
|
+
const mathSource = mathEl.getAttribute("data-math");
|
|
676
|
+
if (mathSource) {
|
|
677
|
+
const textNode = document.createTextNode(mathSource);
|
|
678
|
+
mathEl.parentNode.replaceChild(textNode, mathEl);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
|
|
655
683
|
return {
|
|
656
684
|
plainText: vditor.lute.VditorDOM2Md(tempElement.innerHTML).trim(),
|
|
657
685
|
html: tempElement.innerHTML,
|