@8btc/mditor 0.0.24 → 0.0.26

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.
@@ -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
+ };
@@ -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
- * - 数学公式选区:仅复制可见文本内容(不转换为 Markdown/TeX/MathML)
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
- const startPreview = hasClosestByClassName(
578
+ // 数学公式选区:复制公式源码
579
+ // 检查选区是否在数学公式元素内
580
+ let mathElement = hasClosestByClassName(
571
581
  range.startContainer,
572
- "vditor-wysiwyg__preview"
582
+ "language-math"
573
583
  ) as HTMLElement;
574
- const endPreview = hasClosestByClassName(
584
+ const mathEndElement = hasClosestByClassName(
575
585
  range.endContainer,
576
- "vditor-wysiwyg__preview"
586
+ "language-math"
577
587
  ) as HTMLElement;
578
- const isMathPreview = (el: HTMLElement) => {
579
- const first = el.firstElementChild as HTMLElement | null;
580
- return !!first && first.classList.contains("language-math");
581
- };
582
- if (
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
- // 默认:转换为 Markdown 文本
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
- const startPreview = hasClosestByClassName(
651
+ // 检查选区是否在数学公式元素内
652
+ let mathElement = hasClosestByClassName(
634
653
  range.startContainer,
635
- "vditor-wysiwyg__preview"
654
+ "language-math"
636
655
  ) as HTMLElement;
637
- const endPreview = hasClosestByClassName(
656
+ const mathEndElement = hasClosestByClassName(
638
657
  range.endContainer,
639
- "vditor-wysiwyg__preview"
658
+ "language-math"
640
659
  ) as HTMLElement;
641
- const isMathPreview = (el: HTMLElement) => {
642
- const first = el.firstElementChild as HTMLElement | null;
643
- return !!first && first.classList.contains("language-math");
644
- };
645
- if (
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,