vditor 3.11.1 → 3.11.3

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 (52) hide show
  1. package/README.md +13 -2
  2. package/dist/css/content-theme/ant-design.css +10 -0
  3. package/dist/css/content-theme/dark.css +10 -0
  4. package/dist/css/content-theme/light.css +13 -0
  5. package/dist/css/content-theme/wechat.css +10 -0
  6. package/dist/index.css +133 -4
  7. package/dist/index.js +715 -57
  8. package/dist/index.min.js +1 -1
  9. package/dist/js/i18n/es_ES.js +86 -0
  10. package/dist/js/i18n/ja_JP.js +2 -2
  11. package/dist/js/i18n/vi_VN.js +86 -0
  12. package/dist/js/i18n/zh_TW.js +8 -8
  13. package/dist/js/icons/ant.js +1 -1
  14. package/dist/js/icons/material.js +1 -1
  15. package/dist/js/lute/lute.min.js +87 -71
  16. package/dist/js/mermaid/mermaid.min.js +2027 -1047
  17. package/dist/js/wavedrom/LICENSE.WaveDrom +21 -0
  18. package/dist/js/wavedrom/wavedrom.min.js +9 -0
  19. package/dist/method.d.ts +2 -0
  20. package/dist/method.js +319 -8
  21. package/dist/method.min.js +1 -1
  22. package/dist/ts/constants.d.ts +4 -0
  23. package/dist/ts/markdown/adapterRender.d.ts +4 -0
  24. package/dist/ts/markdown/imageCaptionRender.d.ts +2 -0
  25. package/dist/ts/markdown/wavedromRender.d.ts +1 -0
  26. package/dist/ts/util/fixBrowserBehavior.d.ts +1 -0
  27. package/dist/types/index.d.ts +47 -3
  28. package/package.json +7 -4
  29. package/src/assets/less/_reset.less +103 -2
  30. package/src/assets/less/index.less +20 -0
  31. package/src/index.ts +4 -1
  32. package/src/method.ts +3 -0
  33. package/src/ts/constants.ts +5 -1
  34. package/src/ts/export/index.ts +2 -0
  35. package/src/ts/ir/index.ts +26 -0
  36. package/src/ts/ir/process.ts +24 -1
  37. package/src/ts/markdown/adapterRender.ts +4 -0
  38. package/src/ts/markdown/getHTML.ts +6 -3
  39. package/src/ts/markdown/imageCaptionRender.ts +175 -0
  40. package/src/ts/markdown/mermaidRender.ts +1 -1
  41. package/src/ts/markdown/previewRender.ts +8 -2
  42. package/src/ts/markdown/setLute.ts +3 -0
  43. package/src/ts/markdown/wavedromRender.ts +54 -0
  44. package/src/ts/preview/index.ts +5 -0
  45. package/src/ts/util/Options.ts +1 -1
  46. package/src/ts/util/fixBrowserBehavior.ts +165 -21
  47. package/src/ts/util/processCode.ts +3 -0
  48. package/src/ts/util/selection.ts +7 -2
  49. package/src/ts/wysiwyg/afterRenderEvent.ts +2 -0
  50. package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +23 -4
  51. package/src/ts/wysiwyg/input.ts +5 -0
  52. package/src/ts/wysiwyg/setHeading.ts +110 -6
@@ -0,0 +1,54 @@
1
+ import {Constants} from "../constants";
2
+ import {addScript} from "../util/addScript";
3
+ import {looseJsonParse} from "../util/function";
4
+ import {wavedromRenderAdapter} from "./adapterRender";
5
+
6
+ declare const wavedrom: {
7
+ renderWaveElement(index: number, source: object, element: HTMLElement, skin: object, notFirstSignal: boolean): void;
8
+ waveSkin: object;
9
+ };
10
+
11
+ let wavedromIndex = 0;
12
+
13
+ export const wavedromRender = (element: (HTMLElement | Document) = document, cdn = Constants.CDN) => {
14
+ const wavedromElements = wavedromRenderAdapter.getElements(element);
15
+ if (wavedromElements.length === 0) {
16
+ return;
17
+ }
18
+
19
+ addScript(`${cdn}/dist/js/wavedrom/wavedrom.min.js?v=3.6.2`, "vditorWavedromScript").then(() => {
20
+ wavedromElements.forEach(async (item: HTMLElement) => {
21
+ if (item.parentElement.classList.contains("vditor-wysiwyg__pre") ||
22
+ item.parentElement.classList.contains("vditor-ir__marker--pre")) {
23
+ return;
24
+ }
25
+
26
+ const code = wavedromRenderAdapter.getCode(item);
27
+ if (item.getAttribute("data-processed") === "true" || code.trim() === "") {
28
+ return;
29
+ }
30
+
31
+ let renderElement = item;
32
+ if (item.tagName === "CODE") {
33
+ renderElement = document.createElement("div");
34
+ Array.from(item.attributes).forEach((attribute) => {
35
+ renderElement.setAttribute(attribute.name, attribute.value);
36
+ });
37
+ item.replaceWith(renderElement);
38
+ }
39
+
40
+ try {
41
+ const source = await looseJsonParse(code);
42
+ wavedrom.renderWaveElement(wavedromIndex, source, renderElement, wavedrom.waveSkin, false);
43
+ wavedromIndex++;
44
+ } catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ const lineBreak = document.createElement("br");
47
+ renderElement.replaceChildren(document.createTextNode("wavedrom render error:"), lineBreak,
48
+ document.createTextNode(message));
49
+ renderElement.classList.add("vditor-reset--error");
50
+ }
51
+ renderElement.setAttribute("data-processed", "true");
52
+ });
53
+ });
54
+ };
@@ -5,6 +5,7 @@ import {flowchartRender} from "../markdown/flowchartRender";
5
5
  import {getMarkdown} from "../markdown/getMarkdown";
6
6
  import {graphvizRender} from "../markdown/graphvizRender";
7
7
  import {highlightRender} from "../markdown/highlightRender";
8
+ import {renderImageCaptions} from "../markdown/imageCaptionRender";
8
9
  import {mathRender} from "../markdown/mathRender";
9
10
  import {mediaRender} from "../markdown/mediaRender";
10
11
  import {mermaidRender} from "../markdown/mermaidRender";
@@ -12,6 +13,7 @@ import {SMILESRender} from "../markdown/SMILESRender";
12
13
  import {markmapRender} from "../markdown/markmapRender";
13
14
  import {mindmapRender} from "../markdown/mindmapRender";
14
15
  import {plantumlRender} from "../markdown/plantumlRender";
16
+ import {wavedromRender} from "../markdown/wavedromRender";
15
17
  import {getEventName} from "../util/compatibility";
16
18
  import {hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest";
17
19
  import {hasClosestByTag} from "../util/hasClosestByHeadings";
@@ -152,6 +154,7 @@ export class Preview {
152
154
 
153
155
  if (value) {
154
156
  this.previewElement.innerHTML = value;
157
+ renderImageCaptions(this.previewElement, "preview", vditor.options.preview.markdown.imageCaption);
155
158
  return;
156
159
  }
157
160
 
@@ -205,6 +208,7 @@ export class Preview {
205
208
  }
206
209
 
207
210
  private afterRender(vditor: IVditor, startTime: number) {
211
+ renderImageCaptions(this.previewElement, "preview", vditor.options.preview.markdown.imageCaption);
208
212
  if (vditor.options.preview.parse) {
209
213
  vditor.options.preview.parse(this.element);
210
214
  }
@@ -229,6 +233,7 @@ export class Preview {
229
233
  SMILESRender(vditor.preview.previewElement, vditor.options.cdn, vditor.options.theme);
230
234
  flowchartRender(vditor.preview.previewElement, vditor.options.cdn);
231
235
  graphvizRender(vditor.preview.previewElement, vditor.options.cdn);
236
+ wavedromRender(vditor.preview.previewElement, vditor.options.cdn);
232
237
  chartRender(vditor.preview.previewElement, vditor.options.cdn, vditor.options.theme);
233
238
  mindmapRender(vditor.preview.previewElement, vditor.options.cdn, vditor.options.theme);
234
239
  plantumlRender(vditor.preview.previewElement, vditor.options.cdn);
@@ -290,7 +290,7 @@ export class Options {
290
290
  hotkey: "⇧⌘H",
291
291
  icon: '<svg><use xlink:href="#vditor-icon-line"></use></svg>',
292
292
  name: "line",
293
- prefix: "---",
293
+ prefix: "***",
294
294
  tipPosition: "n",
295
295
  },
296
296
  {
@@ -52,7 +52,7 @@ export const fixCJKPosition = (range: Range, vditor: IVditor, event: KeyboardEve
52
52
 
53
53
  // https://github.com/Vanessa219/vditor/issues/1289 WKWebView切换输入法产生六分之一空格,造成光标错位
54
54
  if (pLiElement.nodeValue) {
55
- pLiElement.nodeValue = pLiElement.nodeValue.replace(/\u2006/g, '');
55
+ pLiElement.nodeValue = pLiElement.nodeValue.replace(/\u2006/g, "");
56
56
  }
57
57
 
58
58
  const zwspNode = document.createTextNode(Constants.ZWSP);
@@ -453,6 +453,41 @@ export const execAfterRender = (vditor: IVditor, options = {
453
453
  }
454
454
  };
455
455
 
456
+ const isEmptyListItem = (liElement: HTMLElement) => {
457
+ if (liElement.textContent.split(Constants.ZWSP).join("").trim() !== "") {
458
+ return false;
459
+ }
460
+ return !liElement.querySelector([
461
+ "audio", "canvas", "embed", "hr", "iframe", "img", "object", "ol", "pre", "svg", "table", "ul", "video",
462
+ "input:not([type='checkbox'])",
463
+ ].join(", "));
464
+ };
465
+
466
+ export const exitEmptyListItem = (liElement: HTMLElement) => {
467
+ const listElement = liElement.parentElement;
468
+ const listParent = listElement.parentElement;
469
+ const paragraphElement = document.createElement("p");
470
+ paragraphElement.setAttribute("data-block", "0");
471
+ paragraphElement.innerHTML = "<wbr>";
472
+
473
+ let trailingListElement: HTMLElement | undefined;
474
+ if (liElement.nextElementSibling) {
475
+ trailingListElement = listElement.cloneNode(false) as HTMLElement;
476
+ while (liElement.nextElementSibling) {
477
+ trailingListElement.appendChild(liElement.nextElementSibling);
478
+ }
479
+ listParent.insertBefore(trailingListElement, listElement.nextSibling);
480
+ }
481
+
482
+ liElement.remove();
483
+ if (listElement.childElementCount === 0) {
484
+ listParent.replaceChild(paragraphElement, listElement);
485
+ } else {
486
+ listParent.insertBefore(paragraphElement, trailingListElement || listElement.nextSibling);
487
+ }
488
+ return paragraphElement;
489
+ };
490
+
456
491
  export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | false, event: KeyboardEvent) => {
457
492
  const startContainer = range.startContainer;
458
493
  const liElement = hasClosestByMatchTag(startContainer, "LI");
@@ -471,6 +506,28 @@ export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | f
471
506
  return true;
472
507
  }
473
508
 
509
+ // 嵌套列表末尾的空列表项回车后转为父列表项中的段落
510
+ // https://github.com/Vanessa219/vditor/issues/939
511
+ if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Enter" && range.collapsed &&
512
+ !liElement.nextElementSibling && liElement.parentElement.parentElement.tagName === "LI" &&
513
+ isEmptyListItem(liElement)) {
514
+ const paragraphElement = exitEmptyListItem(liElement);
515
+ setRangeByWbr(paragraphElement, range);
516
+ execAfterRender(vditor);
517
+ event.preventDefault();
518
+ return true;
519
+ }
520
+
521
+ // 空列表项删除标记后退出当前列表
522
+ if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
523
+ liElement.textContent.trim().replace(Constants.ZWSP, "") === "" && range.toString() === "") {
524
+ const paragraphElement = exitEmptyListItem(liElement);
525
+ setRangeByWbr(paragraphElement, range);
526
+ execAfterRender(vditor);
527
+ event.preventDefault();
528
+ return true;
529
+ }
530
+
474
531
  if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
475
532
  !liElement.previousElementSibling && range.toString() === "" &&
476
533
  getSelectPosition(liElement, vditor[vditor.currentMode].element, range).start === 0) {
@@ -488,20 +545,6 @@ export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | f
488
545
  return true;
489
546
  }
490
547
 
491
- // 空列表删除后与上一级段落对齐
492
- if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
493
- liElement.textContent.trim().replace(Constants.ZWSP, "") === "" &&
494
- range.toString() === "" && liElement.previousElementSibling?.tagName === "LI") {
495
- liElement.previousElementSibling.insertAdjacentText("beforeend", "\n\n");
496
- range.selectNodeContents(liElement.previousElementSibling);
497
- range.collapse(false);
498
- liElement.remove();
499
- setRangeByWbr(vditor[vditor.currentMode].element, range);
500
- execAfterRender(vditor);
501
- event.preventDefault();
502
- return true;
503
- }
504
-
505
548
  if (!isCtrl(event) && !event.altKey && event.key === "Tab") {
506
549
  // 光标位于第一/零字符时,tab 用于列表的缩进
507
550
  let isFirst = false;
@@ -1021,6 +1064,27 @@ export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEven
1021
1064
  const startContainer = range.startContainer;
1022
1065
  const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE");
1023
1066
  if (blockquoteElement && range.toString() === "") {
1067
+ if (pElement && event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey &&
1068
+ pElement.parentElement.isSameNode(blockquoteElement) &&
1069
+ hasClosestByMatchTag(blockquoteElement.parentElement, "LI") &&
1070
+ (range.collapsed || !range.cloneContents().firstElementChild) &&
1071
+ pElement.innerHTML.replace(Constants.ZWSP, "") !== "\n" &&
1072
+ pElement.innerHTML.replace(Constants.ZWSP, "") !== "") {
1073
+ // 列表内的引用由浏览器处理回车时会新建列表项 https://github.com/Vanessa219/vditor/issues/1925
1074
+ const trailingRange = range.cloneRange();
1075
+ trailingRange.setEnd(pElement, pElement.childNodes.length);
1076
+ const paragraphElement = document.createElement("p");
1077
+ paragraphElement.setAttribute("data-block", "0");
1078
+ paragraphElement.appendChild(trailingRange.extractContents());
1079
+ pElement.insertAdjacentElement("afterend", paragraphElement);
1080
+ range.setStart(paragraphElement, 0);
1081
+ range.collapse(true);
1082
+ setSelectionFocus(range);
1083
+ execAfterRender(vditor);
1084
+ event.preventDefault();
1085
+ return true;
1086
+ }
1087
+
1024
1088
  if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey &&
1025
1089
  getSelectPosition(blockquoteElement, vditor[vditor.currentMode].element, range).start === 0) {
1026
1090
  // Backspace: 光标位于引用中的第零个字符,仅删除引用标签
@@ -1068,9 +1132,15 @@ export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEven
1068
1132
  return true;
1069
1133
  }
1070
1134
 
1071
- if (insertAfterBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
1135
+ const itemElement = blockquoteElement.parentElement.tagName === "LI" ? blockquoteElement.parentElement : null;
1136
+ const afterBlockElement = !blockquoteElement.nextElementSibling && itemElement?.nextElementSibling?.tagName === "LI" ?
1137
+ itemElement : blockquoteElement;
1138
+ if (insertAfterBlock(vditor, event, range, blockquoteElement, afterBlockElement)) {
1072
1139
  return true;
1073
1140
  }
1141
+ if (event.key === "ArrowUp" && itemElement && !blockquoteElement.previousElementSibling) {
1142
+ return false;
1143
+ }
1074
1144
  if (insertBeforeBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
1075
1145
  return true;
1076
1146
  }
@@ -1255,7 +1325,7 @@ export const fixFirefoxArrowUpTable = (event: KeyboardEvent, blockElement: false
1255
1325
  return false;
1256
1326
  };
1257
1327
 
1258
- export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & { target: HTMLElement }, callback: {
1328
+ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & {target: HTMLElement}, callback: {
1259
1329
  pasteCode(code: string): void,
1260
1330
  }) => {
1261
1331
  if (vditor[vditor.currentMode].element.getAttribute("contenteditable") !== "true") {
@@ -1290,6 +1360,12 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1290
1360
  return ["", Lute.WalkContinue];
1291
1361
  }
1292
1362
 
1363
+ const parent = node.__internal_object__.Parent;
1364
+ // 链接引用的目标地址由定义节点提供,不在引用位置重复渲染
1365
+ if (parent.Type === 33 && parent.LinkType === 3) {
1366
+ return ["", Lute.WalkContinue];
1367
+ }
1368
+
1293
1369
  if (vditor.options.upload.renderLinkDest) {
1294
1370
  return vditor.options.upload.renderLinkDest(vditor, node, entering);
1295
1371
  }
@@ -1404,6 +1480,11 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1404
1480
  if (textHTML.trim() !== "") {
1405
1481
  const tempElement = document.createElement("div");
1406
1482
  tempElement.innerHTML = textHTML;
1483
+ if (!vditor.options.upload.base64ToLink) {
1484
+ // word 复制的图文混合,替换为 link: <v:imagedata src="file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png" o:title="">
1485
+ await processVMLImage(vditor, tempElement, ("clipboardData" in event ? event.clipboardData : event.dataTransfer).getData("text/rtf"));
1486
+ }
1487
+
1407
1488
  tempElement.querySelectorAll("[style]").forEach((e) => {
1408
1489
  e.removeAttribute("style");
1409
1490
  });
@@ -1440,14 +1521,14 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1440
1521
  if (file && file.type.startsWith("image")) {
1441
1522
  fileReader.readAsDataURL(file);
1442
1523
  fileReader.onload = () => {
1443
- let imgHTML = ''
1524
+ let imgHTML = "";
1444
1525
  if (vditor.currentMode === "wysiwyg") {
1445
1526
  imgHTML += `<img alt="${file.name}" src="${fileReader.result.toString()}">\n`;
1446
1527
  } else {
1447
1528
  imgHTML += `![${file.name}](${fileReader.result.toString()})\n`;
1448
1529
  }
1449
1530
  document.execCommand("insertHTML", false, imgHTML);
1450
- }
1531
+ };
1451
1532
  }
1452
1533
  }
1453
1534
  } else if (textPlain.trim() !== "" && files.length === 0) {
@@ -1458,11 +1539,11 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1458
1539
  if (vditor.currentMode === "ir") {
1459
1540
  renderers.Md2VditorIRDOM = {renderLinkDest};
1460
1541
  vditor.lute.SetJSRenderers({renderers});
1461
- insertHTML(vditor.lute.Md2VditorIRDOM(textPlain), vditor);
1542
+ insertHTML(Lute.Sanitize(vditor.lute.Md2VditorIRDOM(textPlain)), vditor);
1462
1543
  } else if (vditor.currentMode === "wysiwyg") {
1463
1544
  renderers.Md2VditorDOM = {renderLinkDest};
1464
1545
  vditor.lute.SetJSRenderers({renderers});
1465
- insertHTML(vditor.lute.Md2VditorDOM(textPlain), vditor);
1546
+ insertHTML(Lute.Sanitize(vditor.lute.Md2VditorDOM(textPlain)), vditor);
1466
1547
  } else {
1467
1548
  renderers.Md2VditorSVDOM = {renderLinkDest};
1468
1549
  vditor.lute.SetJSRenderers({renderers});
@@ -1499,3 +1580,66 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1499
1580
  scrollCenter(vditor);
1500
1581
  }
1501
1582
  };
1583
+
1584
+ const processVMLImage = async (vditor: IVditor, root: Element, rtfData: string) => {
1585
+ if (!rtfData) {
1586
+ return;
1587
+
1588
+ }
1589
+
1590
+ const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
1591
+ const regexPicture = new RegExp("(?:(" + regexPictureHeader.source + "))([\\da-fA-F\\s]+)\\}", "g");
1592
+ const regImages = rtfData.match(regexPicture);
1593
+ const images = [];
1594
+ if (regImages) {
1595
+ for (const image of regImages) {
1596
+ let imageType;
1597
+
1598
+ if (image.includes("\\pngblip")) {
1599
+ imageType = "image/png";
1600
+ } else if (image.includes("\\jpegblip")) {
1601
+ imageType = "image/jpeg";
1602
+ }
1603
+
1604
+ if (imageType) {
1605
+ images.push({
1606
+ hex: image.replace(regexPictureHeader, "").replace(/[^\da-fA-F]/g, ""),
1607
+ type: imageType,
1608
+ });
1609
+ }
1610
+ }
1611
+ }
1612
+
1613
+ const shapes: Array<{shape: Element, img: Element}> = [];
1614
+ walk(root, (child: Element) => {
1615
+ if (child.tagName === "V:SHAPE") {
1616
+ walk(child, (sub) => {
1617
+ if (sub.tagName === "V:IMAGEDATA") shapes.push({shape: child, img: sub});
1618
+ });
1619
+ return false;
1620
+ }
1621
+ });
1622
+ for (let i = 0; i < shapes.length; i++) {
1623
+ const img = document.createElement("img");
1624
+ const newSrc = "data:" + images[i].type + ";base64," + btoa((images[i].hex.match(/\w{2}/g) || []).map(char => {
1625
+ return String.fromCharCode(parseInt(char, 16));
1626
+ }).join(""));
1627
+ img.src = newSrc;
1628
+ img.title = shapes[i].img.getAttribute("title");
1629
+ shapes[i].shape.parentNode.replaceChild(img, shapes[i].shape);
1630
+ }
1631
+
1632
+ const imgs = root.querySelectorAll("img");
1633
+ for (let i = 0; i < imgs.length; i++) {
1634
+ const src = imgs[i].src || "";
1635
+ if (src) imgs[i].src = await vditor.options.upload.base64ToLink(src);
1636
+ }
1637
+ };
1638
+
1639
+ const walk = (el: Element, fn: (el: Element) => boolean | void) => {
1640
+ const goNext = fn(el);
1641
+ if (goNext !== false)
1642
+ for (let i = 0; i < el.children.length; i++) {
1643
+ walk(el.children[i], fn);
1644
+ }
1645
+ };
@@ -10,6 +10,7 @@ import {markmapRender} from "../markdown/markmapRender";
10
10
  import {mindmapRender} from "../markdown/mindmapRender";
11
11
  import {plantumlRender} from "../markdown/plantumlRender";
12
12
  import {SMILESRender} from "../markdown/SMILESRender";
13
+ import {wavedromRender} from "../markdown/wavedromRender";
13
14
 
14
15
  export const processPasteCode = (html: string, text: string, type = "sv") => {
15
16
  const tempElement = document.createElement("div");
@@ -82,6 +83,8 @@ export const processCodeRender = (previewPanel: HTMLElement, vditor: IVditor) =>
82
83
  plantumlRender(previewPanel, vditor.options.cdn);
83
84
  } else if (language === "graphviz") {
84
85
  graphvizRender(previewPanel, vditor.options.cdn);
86
+ } else if (language === "wavedrom") {
87
+ wavedromRender(previewPanel, vditor.options.cdn);
85
88
  } else if (language === "math") {
86
89
  mathRender(previewPanel, {cdn: vditor.options.cdn, math: vditor.options.preview.math});
87
90
  } else {
@@ -1,6 +1,6 @@
1
1
  import {Constants} from "../constants";
2
2
  import {isChrome} from "./compatibility";
3
- import {hasClosestBlock, hasClosestByClassName} from "./hasClosest";
3
+ import {hasClosestBlock, hasClosestByClassName, hasClosestByMatchTag} from "./hasClosest";
4
4
 
5
5
  export const getEditorRange = (vditor: IVditor) => {
6
6
  let range: Range;
@@ -262,7 +262,12 @@ export const insertHTML = (html: string, vditor: IVditor) => {
262
262
  if (!blockElement) {
263
263
  vditor[vditor.currentMode].element.insertAdjacentHTML("beforeend", pasteElement.innerHTML);
264
264
  } else {
265
- blockElement.insertAdjacentHTML("afterend", pasteElement.innerHTML);
265
+ const liElement = hasClosestByMatchTag(range.startContainer, "LI");
266
+ if (liElement && pasteElement.firstElementChild.tagName === "UL") {
267
+ liElement.insertAdjacentHTML("afterend", pasteElement.firstElementChild.innerHTML);
268
+ } else {
269
+ blockElement.insertAdjacentHTML("afterend", pasteElement.innerHTML);
270
+ }
266
271
  }
267
272
  setRangeByWbr(vditor[vditor.currentMode].element, range);
268
273
  } else {
@@ -1,4 +1,5 @@
1
1
  import {getMarkdown} from "../markdown/getMarkdown";
2
+ import {renderImageCaptions} from "../markdown/imageCaptionRender";
2
3
  import {accessLocalStorage} from "../util/compatibility";
3
4
 
4
5
  export const afterRenderEvent = (vditor: IVditor, options = {
@@ -6,6 +7,7 @@ export const afterRenderEvent = (vditor: IVditor, options = {
6
7
  enableHint: false,
7
8
  enableInput: true,
8
9
  }) => {
10
+ renderImageCaptions(vditor.wysiwyg.element, "wysiwyg", vditor.options.preview.markdown.imageCaption);
9
11
  if (options.enableHint) {
10
12
  vditor.hint.render(vditor);
11
13
  }
@@ -33,6 +33,7 @@ import {afterRenderEvent} from "./afterRenderEvent";
33
33
  import {removeBlockElement} from "./processKeydown";
34
34
  import {renderToc} from "../util/toc";
35
35
  import {getMarkdown} from "../markdown/getMarkdown";
36
+ import {renderImageCaptions} from "../markdown/imageCaptionRender";
36
37
 
37
38
  export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
38
39
  clearTimeout(vditor.wysiwyg.hlToolbarTimeoutId);
@@ -65,6 +66,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
65
66
  if (footnotesElement) {
66
67
  vditor.wysiwyg.popover.innerHTML = "";
67
68
  genClose(footnotesElement, vditor);
69
+ customWysiwygToolbar(vditor, "footnotes-block")
68
70
  setPopoverPosition(vditor, footnotesElement);
69
71
  return;
70
72
  }
@@ -195,6 +197,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
195
197
  if (tocElement) {
196
198
  vditor.wysiwyg.popover.innerHTML = "";
197
199
  genClose(tocElement, vditor);
200
+ customWysiwygToolbar(vditor, "vditor-toc")
198
201
  setPopoverPosition(vditor, tocElement);
199
202
  return;
200
203
  }
@@ -206,6 +209,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
206
209
  genUp(range, blockquoteElement, vditor);
207
210
  genDown(range, blockquoteElement, vditor);
208
211
  genClose(blockquoteElement, vditor);
212
+ customWysiwygToolbar(vditor, "blockquote")
209
213
  setPopoverPosition(vditor, blockquoteElement);
210
214
  }
211
215
 
@@ -216,7 +220,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
216
220
  genUp(range, liElement, vditor);
217
221
  genDown(range, liElement, vditor);
218
222
  genClose(liElement, vditor);
219
-
223
+ customWysiwygToolbar(vditor, "li")
220
224
  setPopoverPosition(vditor, liElement);
221
225
  }
222
226
 
@@ -549,6 +553,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
549
553
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
550
554
  vditor.wysiwyg.popover.insertAdjacentHTML("beforeend", " x ");
551
555
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input2Wrap);
556
+ customWysiwygToolbar(vditor, "table")
552
557
  setPopoverPosition(vditor, tableElement);
553
558
  }
554
559
 
@@ -596,6 +601,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
596
601
 
597
602
  genClose(footnotesRefElement, vditor);
598
603
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
604
+ customWysiwygToolbar(vditor, "footnotes-ref")
599
605
  setPopoverPosition(vditor, footnotesRefElement);
600
606
  }
601
607
 
@@ -684,7 +690,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
684
690
  }
685
691
  const matchLangData: IHintData[] = [];
686
692
  const key = language.value.substring(0, language.selectionStart);
687
- (vditor.options.preview.hljs.langs || Constants.ALIAS_CODE_LANGUAGES.concat((window.hljs?.listLanguages() ?? []).sort())).forEach((keyName) => {
693
+ (vditor.options.preview.hljs.langs || Constants.ALIAS_CODE_LANGUAGES.concat((window.hljs?.listLanguages() ?? []).sort())).forEach((keyName) => {
688
694
  if (keyName.indexOf(key.toLowerCase()) > -1) {
689
695
  matchLangData.push({
690
696
  html: keyName,
@@ -696,6 +702,9 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
696
702
  event.preventDefault();
697
703
  };
698
704
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", languageWrap);
705
+ customWysiwygToolbar(vditor, "code-block")
706
+ } else {
707
+ customWysiwygToolbar(vditor, "block")
699
708
  }
700
709
  setPopoverPosition(vditor, blockRenderElement);
701
710
  } else {
@@ -735,6 +744,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
735
744
  genDown(range, headingElement, vditor);
736
745
  genClose(headingElement, vditor);
737
746
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
747
+ customWysiwygToolbar(vditor, "heading")
738
748
  setPopoverPosition(vditor, headingElement);
739
749
  }
740
750
 
@@ -763,7 +773,7 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
763
773
  genUp(range, blockElement, vditor);
764
774
  genDown(range, blockElement, vditor);
765
775
  genClose(blockElement, vditor);
766
-
776
+ customWysiwygToolbar(vditor, "block")
767
777
  setPopoverPosition(vditor, blockElement);
768
778
  } else {
769
779
  vditor.wysiwyg.popover.style.display = "none";
@@ -864,6 +874,7 @@ export const genLinkRefPopover = (vditor: IVditor, linkRefElement: HTMLElement,
864
874
  genClose(linkRefElement, vditor);
865
875
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
866
876
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input1Wrap);
877
+ customWysiwygToolbar(vditor, "link-ref")
867
878
  setPopoverPosition(vditor, linkRefElement);
868
879
  };
869
880
 
@@ -1058,6 +1069,7 @@ export const genAPopover = (vditor: IVditor, aElement: HTMLElement, range: Range
1058
1069
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
1059
1070
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input1Wrap);
1060
1071
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input2Wrap);
1072
+ customWysiwygToolbar(vditor, "a")
1061
1073
  setPopoverPosition(vditor, aElement);
1062
1074
  };
1063
1075
 
@@ -1068,6 +1080,7 @@ export const genImagePopover = (event: Event, vditor: IVditor) => {
1068
1080
  imgElement.setAttribute("src", inputElement.value);
1069
1081
  imgElement.setAttribute("alt", alt.value);
1070
1082
  imgElement.setAttribute("title", title.value);
1083
+ renderImageCaptions(vditor.wysiwyg.element, "wysiwyg", vditor.options.preview.markdown.imageCaption);
1071
1084
  if (typeof vditor.options.input === "function") {
1072
1085
  vditor.options.input(getMarkdown(vditor));
1073
1086
  }
@@ -1122,7 +1135,7 @@ export const genImagePopover = (event: Event, vditor: IVditor) => {
1122
1135
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
1123
1136
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", altWrap);
1124
1137
  vditor.wysiwyg.popover.insertAdjacentElement("beforeend", titleWrap);
1125
-
1138
+ customWysiwygToolbar(vditor, "image")
1126
1139
  setPopoverPosition(vditor, imgElement);
1127
1140
  };
1128
1141
 
@@ -1137,3 +1150,9 @@ const focusToElement = (event: KeyboardEvent, range: Range) => {
1137
1150
  return true;
1138
1151
  }
1139
1152
  };
1153
+
1154
+ const customWysiwygToolbar = (vditor: IVditor, type: TWYSISYGToolbar) => {
1155
+ if (vditor.options.customWysiwygToolbar) {
1156
+ vditor.options.customWysiwygToolbar(type, vditor.wysiwyg.popover);
1157
+ }
1158
+ };
@@ -71,6 +71,7 @@ export const input = (vditor: IVditor, range: Range, event?: InputEvent) => {
71
71
 
72
72
  const isWYSIWYGElement = blockElement.isEqualNode(vditor.wysiwyg.element);
73
73
  const footnoteElement = hasClosestByAttribute(blockElement, "data-type", "footnotes-block");
74
+ const calloutElement = hasClosestByAttribute(range.startContainer, "data-type", "callout");
74
75
 
75
76
  if (!isWYSIWYGElement) {
76
77
  // 列表需要到最顶层
@@ -90,6 +91,10 @@ export const input = (vditor: IVditor, range: Range, event?: InputEvent) => {
90
91
  blockElement = footnoteElement;
91
92
  }
92
93
 
94
+ if (calloutElement) {
95
+ blockElement = calloutElement;
96
+ }
97
+
93
98
  html = blockElement.outerHTML;
94
99
 
95
100
  if (blockElement.tagName === "UL" || blockElement.tagName === "OL") {