|
@@ -1,6 +1,6 @@
|
|
|
-import {Constants} from "../constants";
|
|
|
-import {hidePanel} from "../toolbar/setToolbar";
|
|
|
-import {isCtrl, isFirefox} from "../util/compatibility";
|
|
|
+import { Constants } from "../constants"
|
|
|
+import { hidePanel } from "../toolbar/setToolbar"
|
|
|
+import { isCtrl, isFirefox } from "../util/compatibility"
|
|
|
import {
|
|
|
blurEvent,
|
|
|
copyEvent, cutEvent, dblclickEvent,
|
|
@@ -9,42 +9,42 @@ import {
|
|
|
hotkeyEvent,
|
|
|
scrollCenter,
|
|
|
selectEvent,
|
|
|
-} from "../util/editorCommonEvent";
|
|
|
-import {isHeadingMD, isHrMD, paste} from "../util/fixBrowserBehavior";
|
|
|
+} from "../util/editorCommonEvent"
|
|
|
+import { isHeadingMD, isHrMD, paste } from "../util/fixBrowserBehavior"
|
|
|
import {
|
|
|
hasClosestBlock, hasClosestByAttribute,
|
|
|
hasClosestByClassName, hasClosestByMatchTag,
|
|
|
-} from "../util/hasClosest";
|
|
|
-import {hasClosestByHeadings} from "../util/hasClosestByHeadings";
|
|
|
+} from "../util/hasClosest"
|
|
|
+import { hasClosestByHeadings } from "../util/hasClosestByHeadings"
|
|
|
import {
|
|
|
getCursorPosition,
|
|
|
getEditorRange,
|
|
|
getSelectPosition,
|
|
|
- setRangeByWbr,
|
|
|
-} from "../util/selection";
|
|
|
-import {clickToc, renderToc} from "../util/toc";
|
|
|
-import {afterRenderEvent} from "./afterRenderEvent";
|
|
|
-import {genImagePopover, genLinkRefPopover, highlightToolbarWYSIWYG} from "./highlightToolbarWYSIWYG";
|
|
|
-import {getRenderElementNextNode, modifyPre} from "./inlineTag";
|
|
|
-import {input} from "./input";
|
|
|
-import {showCode} from "./showCode";
|
|
|
-import {getMarkdown} from "../markdown/getMarkdown";
|
|
|
+ setRangeByWbr, setSelectionFocus,
|
|
|
+} from "../util/selection"
|
|
|
+import { clickToc, renderToc } from "../util/toc"
|
|
|
+import { afterRenderEvent } from "./afterRenderEvent"
|
|
|
+import { genImagePopover, genLinkRefPopover, highlightToolbarWYSIWYG } from "./highlightToolbarWYSIWYG"
|
|
|
+import { getRenderElementNextNode, modifyPre } from "./inlineTag"
|
|
|
+import { input } from "./input"
|
|
|
+import { showCode } from "./showCode"
|
|
|
+import { getMarkdown } from "../markdown/getMarkdown"
|
|
|
|
|
|
class WYSIWYG {
|
|
|
- public range: Range;
|
|
|
- public element: HTMLPreElement;
|
|
|
- public popover: HTMLDivElement;
|
|
|
- public selectPopover: HTMLDivElement;
|
|
|
- public afterRenderTimeoutId: number;
|
|
|
- public hlToolbarTimeoutId: number;
|
|
|
- public preventInput: boolean;
|
|
|
- public composingLock = false;
|
|
|
- public commentIds: string[] = [];
|
|
|
- private scrollListener: () => void;
|
|
|
+ public range: Range
|
|
|
+ public element: HTMLPreElement
|
|
|
+ public popover: HTMLDivElement
|
|
|
+ public selectPopover: HTMLDivElement
|
|
|
+ public afterRenderTimeoutId: number
|
|
|
+ public hlToolbarTimeoutId: number
|
|
|
+ public preventInput: boolean
|
|
|
+ public composingLock = false
|
|
|
+ public commentIds: string[] = []
|
|
|
+ private scrollListener: () => void
|
|
|
|
|
|
constructor(vditor: IVditor) {
|
|
|
- const divElement = document.createElement("div");
|
|
|
- divElement.className = "vditor-wysiwyg";
|
|
|
+ const divElement = document.createElement("div")
|
|
|
+ divElement.className = "vditor-wysiwyg"
|
|
|
|
|
|
divElement.innerHTML = `<pre class="vditor-reset" placeholder="${vditor.options.placeholder}"
|
|
|
contenteditable="true" spellcheck="false"></pre>
|
|
@@ -53,436 +53,439 @@ class WYSIWYG {
|
|
|
<button type="button" aria-label="${window.VditorI18n.comment}" class="vditor-icon vditor-tooltipped vditor-tooltipped__n">
|
|
|
<svg><use xlink:href="#vditor-icon-comment"></use></svg>
|
|
|
</button>
|
|
|
-</div>`;
|
|
|
+</div>`
|
|
|
|
|
|
- this.element = divElement.firstElementChild as HTMLPreElement;
|
|
|
- this.popover = divElement.firstElementChild.nextElementSibling as HTMLDivElement;
|
|
|
- this.selectPopover = divElement.lastElementChild as HTMLDivElement;
|
|
|
+ this.element = divElement.firstElementChild as HTMLPreElement
|
|
|
+ this.popover = divElement.firstElementChild.nextElementSibling as HTMLDivElement
|
|
|
+ this.selectPopover = divElement.lastElementChild as HTMLDivElement
|
|
|
|
|
|
- this.bindEvent(vditor);
|
|
|
+ this.bindEvent(vditor)
|
|
|
|
|
|
- focusEvent(vditor, this.element);
|
|
|
- dblclickEvent(vditor, this.element);
|
|
|
- blurEvent(vditor, this.element);
|
|
|
- hotkeyEvent(vditor, this.element);
|
|
|
- selectEvent(vditor, this.element);
|
|
|
- dropEvent(vditor, this.element);
|
|
|
- copyEvent(vditor, this.element, this.copy);
|
|
|
- cutEvent(vditor, this.element, this.copy);
|
|
|
+ focusEvent(vditor, this.element)
|
|
|
+ dblclickEvent(vditor, this.element)
|
|
|
+ blurEvent(vditor, this.element)
|
|
|
+ hotkeyEvent(vditor, this.element)
|
|
|
+ selectEvent(vditor, this.element)
|
|
|
+ dropEvent(vditor, this.element)
|
|
|
+ copyEvent(vditor, this.element, this.copy)
|
|
|
+ cutEvent(vditor, this.element, this.copy)
|
|
|
|
|
|
if (vditor.options.comment.enable) {
|
|
|
this.selectPopover.querySelector("button").onclick = () => {
|
|
|
- const id = Lute.NewNodeID();
|
|
|
- const range = getSelection().getRangeAt(0);
|
|
|
- const rangeClone = range.cloneRange();
|
|
|
- const contents = range.extractContents();
|
|
|
- let blockStartElement: HTMLElement;
|
|
|
- let blockEndElement: HTMLElement;
|
|
|
- let removeStart = false;
|
|
|
- let removeEnd = false;
|
|
|
+ const id = Lute.NewNodeID()
|
|
|
+ const range = getSelection().getRangeAt(0)
|
|
|
+ const rangeClone = range.cloneRange()
|
|
|
+ const contents = range.extractContents()
|
|
|
+ let blockStartElement: HTMLElement
|
|
|
+ let blockEndElement: HTMLElement
|
|
|
+ let removeStart = false
|
|
|
+ let removeEnd = false
|
|
|
contents.childNodes.forEach((item: HTMLElement, index: number) => {
|
|
|
- let wrap = false;
|
|
|
+ let wrap = false
|
|
|
if (item.nodeType === 3) {
|
|
|
- wrap = true;
|
|
|
+ wrap = true
|
|
|
} else if (!item.classList.contains("vditor-comment")) {
|
|
|
- wrap = true;
|
|
|
+ wrap = true
|
|
|
} else if (item.classList.contains("vditor-comment")) {
|
|
|
- item.setAttribute("data-cmtids", item.getAttribute("data-cmtids") + " " + id);
|
|
|
+ item.setAttribute("data-cmtids", item.getAttribute("data-cmtids") + " " + id)
|
|
|
}
|
|
|
if (wrap) {
|
|
|
if (item.nodeType !== 3 && item.getAttribute("data-block") === "0"
|
|
|
&& index === 0 && rangeClone.startOffset > 0) {
|
|
|
item.innerHTML =
|
|
|
- `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
|
|
|
- blockStartElement = item;
|
|
|
+ `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`
|
|
|
+ blockStartElement = item
|
|
|
} else if (item.nodeType !== 3 && item.getAttribute("data-block") === "0"
|
|
|
&& index === contents.childNodes.length - 1
|
|
|
&& rangeClone.endOffset < rangeClone.endContainer.textContent.length) {
|
|
|
item.innerHTML =
|
|
|
- `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
|
|
|
- blockEndElement = item;
|
|
|
+ `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`
|
|
|
+ blockEndElement = item
|
|
|
} else if (item.nodeType !== 3 && item.getAttribute("data-block") === "0") {
|
|
|
if (index === 0) {
|
|
|
- removeStart = true;
|
|
|
+ removeStart = true
|
|
|
} else if (index === contents.childNodes.length - 1) {
|
|
|
- removeEnd = true;
|
|
|
+ removeEnd = true
|
|
|
}
|
|
|
item.innerHTML =
|
|
|
- `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
|
|
|
+ `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`
|
|
|
} else {
|
|
|
- const commentElement = document.createElement("span");
|
|
|
- commentElement.classList.add("vditor-comment");
|
|
|
- commentElement.setAttribute("data-cmtids", id);
|
|
|
- item.parentNode.insertBefore(commentElement, item);
|
|
|
- commentElement.appendChild(item);
|
|
|
+ const commentElement = document.createElement("span")
|
|
|
+ commentElement.classList.add("vditor-comment")
|
|
|
+ commentElement.setAttribute("data-cmtids", id)
|
|
|
+ item.parentNode.insertBefore(commentElement, item)
|
|
|
+ commentElement.appendChild(item)
|
|
|
}
|
|
|
}
|
|
|
- });
|
|
|
- const startElement = hasClosestBlock(rangeClone.startContainer);
|
|
|
+ })
|
|
|
+ const startElement = hasClosestBlock(rangeClone.startContainer)
|
|
|
if (startElement) {
|
|
|
if (blockStartElement) {
|
|
|
- startElement.insertAdjacentHTML("beforeend", blockStartElement.innerHTML);
|
|
|
- blockStartElement.remove();
|
|
|
+ startElement.insertAdjacentHTML("beforeend", blockStartElement.innerHTML)
|
|
|
+ blockStartElement.remove()
|
|
|
} else if (startElement.textContent.trim().replace(Constants.ZWSP, "") === "" && removeStart) {
|
|
|
- startElement.remove();
|
|
|
+ startElement.remove()
|
|
|
}
|
|
|
}
|
|
|
- const endElement = hasClosestBlock(rangeClone.endContainer);
|
|
|
+ const endElement = hasClosestBlock(rangeClone.endContainer)
|
|
|
if (endElement) {
|
|
|
if (blockEndElement) {
|
|
|
- endElement.insertAdjacentHTML("afterbegin", blockEndElement.innerHTML);
|
|
|
- blockEndElement.remove();
|
|
|
+ endElement.insertAdjacentHTML("afterbegin", blockEndElement.innerHTML)
|
|
|
+ blockEndElement.remove()
|
|
|
} else if (endElement.textContent.trim().replace(Constants.ZWSP, "") === "" && removeEnd) {
|
|
|
- endElement.remove();
|
|
|
+ endElement.remove()
|
|
|
}
|
|
|
}
|
|
|
- range.insertNode(contents);
|
|
|
- vditor.options.comment.add(id, range.toString(), this.getComments(vditor, true));
|
|
|
+ range.insertNode(contents)
|
|
|
+ vditor.options.comment.add(id, range.toString(), this.getComments(vditor, true))
|
|
|
afterRenderEvent(vditor, {
|
|
|
enableAddUndoStack: true,
|
|
|
enableHint: false,
|
|
|
enableInput: false,
|
|
|
- });
|
|
|
- this.hideComment();
|
|
|
- };
|
|
|
+ })
|
|
|
+ this.hideComment()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public getComments(vditor: IVditor, getData = false) {
|
|
|
if (vditor.currentMode === "wysiwyg" && vditor.options.comment.enable) {
|
|
|
- this.commentIds = [];
|
|
|
+ this.commentIds = []
|
|
|
this.element.querySelectorAll(".vditor-comment").forEach((item) => {
|
|
|
this.commentIds =
|
|
|
- this.commentIds.concat(item.getAttribute("data-cmtids").split(" "));
|
|
|
- });
|
|
|
- this.commentIds = Array.from(new Set(this.commentIds));
|
|
|
+ this.commentIds.concat(item.getAttribute("data-cmtids").split(" "))
|
|
|
+ })
|
|
|
+ this.commentIds = Array.from(new Set(this.commentIds))
|
|
|
|
|
|
- const comments: ICommentsData[] = [];
|
|
|
+ const comments: ICommentsData[] = []
|
|
|
if (getData) {
|
|
|
this.commentIds.forEach((id) => {
|
|
|
comments.push({
|
|
|
id,
|
|
|
top:
|
|
|
(this.element.querySelector(`.vditor-comment[data-cmtids="${id}"]`) as HTMLElement).offsetTop,
|
|
|
- });
|
|
|
- });
|
|
|
- return comments;
|
|
|
+ })
|
|
|
+ })
|
|
|
+ return comments
|
|
|
}
|
|
|
} else {
|
|
|
- return [];
|
|
|
+ return []
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public triggerRemoveComment(vditor: IVditor) {
|
|
|
const difference = (a: string[], b: string[]) => {
|
|
|
- const s = new Set(b);
|
|
|
- return a.filter((x) => !s.has(x));
|
|
|
- };
|
|
|
+ const s = new Set(b)
|
|
|
+ return a.filter((x) => !s.has(x))
|
|
|
+ }
|
|
|
if (vditor.currentMode === "wysiwyg" && vditor.options.comment.enable && vditor.wysiwyg.commentIds.length > 0) {
|
|
|
- const oldIds = JSON.parse(JSON.stringify(this.commentIds));
|
|
|
- this.getComments(vditor);
|
|
|
- const removedIds = difference(oldIds, this.commentIds);
|
|
|
+ const oldIds = JSON.parse(JSON.stringify(this.commentIds))
|
|
|
+ this.getComments(vditor)
|
|
|
+ const removedIds = difference(oldIds, this.commentIds)
|
|
|
if (removedIds.length > 0) {
|
|
|
- vditor.options.comment.remove(removedIds);
|
|
|
+ vditor.options.comment.remove(removedIds)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public showComment() {
|
|
|
- const position = getCursorPosition(this.element);
|
|
|
- this.selectPopover.setAttribute("style", `left:${position.left}px;display:block;top:${Math.max(-8, position.top - 21)}px`);
|
|
|
+ const position = getCursorPosition(this.element)
|
|
|
+ this.selectPopover.setAttribute("style", `left:${position.left}px;display:block;top:${Math.max(-8, position.top - 21)}px`)
|
|
|
}
|
|
|
|
|
|
public hideComment() {
|
|
|
- this.selectPopover.setAttribute("style", "display:none");
|
|
|
+ this.selectPopover.setAttribute("style", "display:none")
|
|
|
}
|
|
|
|
|
|
public unbindListener() {
|
|
|
- window.removeEventListener("scroll", this.scrollListener);
|
|
|
+ window.removeEventListener("scroll", this.scrollListener)
|
|
|
}
|
|
|
|
|
|
private copy(event: ClipboardEvent, vditor: IVditor) {
|
|
|
- const range = getSelection().getRangeAt(0);
|
|
|
+ const range = getSelection().getRangeAt(0)
|
|
|
if (range.toString() === "") {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
- event.stopPropagation();
|
|
|
- event.preventDefault();
|
|
|
+ event.stopPropagation()
|
|
|
+ event.preventDefault()
|
|
|
|
|
|
- const codeElement = hasClosestByMatchTag(range.startContainer, "CODE");
|
|
|
- const codeEndElement = hasClosestByMatchTag(range.endContainer, "CODE");
|
|
|
+ const codeElement = hasClosestByMatchTag(range.startContainer, "CODE")
|
|
|
+ const codeEndElement = hasClosestByMatchTag(range.endContainer, "CODE")
|
|
|
if (codeElement && codeEndElement && codeEndElement.isSameNode(codeElement)) {
|
|
|
- let codeText = "";
|
|
|
+ let codeText = ""
|
|
|
if (codeElement.parentElement.tagName === "PRE") {
|
|
|
- codeText = range.toString();
|
|
|
+ codeText = range.toString()
|
|
|
} else {
|
|
|
- codeText = "`" + range.toString() + "`";
|
|
|
+ codeText = "`" + range.toString() + "`"
|
|
|
}
|
|
|
- event.clipboardData.setData("text/plain", codeText);
|
|
|
- event.clipboardData.setData("text/html", "");
|
|
|
- return;
|
|
|
+ event.clipboardData.setData("text/plain", codeText)
|
|
|
+ event.clipboardData.setData("text/html", "")
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- const aElement = hasClosestByMatchTag(range.startContainer, "A");
|
|
|
- const aEndElement = hasClosestByMatchTag(range.endContainer, "A");
|
|
|
+ const aElement = hasClosestByMatchTag(range.startContainer, "A")
|
|
|
+ const aEndElement = hasClosestByMatchTag(range.endContainer, "A")
|
|
|
if (aElement && aEndElement && aEndElement.isSameNode(aElement)) {
|
|
|
- let aTitle = aElement.getAttribute("title") || "";
|
|
|
+ let aTitle = aElement.getAttribute("title") || ""
|
|
|
if (aTitle) {
|
|
|
- aTitle = ` "${aTitle}"`;
|
|
|
+ aTitle = ` "${aTitle}"`
|
|
|
}
|
|
|
event.clipboardData.setData("text/plain",
|
|
|
- `[${range.toString()}](${aElement.getAttribute("href")}${aTitle})`);
|
|
|
- event.clipboardData.setData("text/html", "");
|
|
|
- return;
|
|
|
+ `[${range.toString()}](${aElement.getAttribute("href")}${aTitle})`)
|
|
|
+ event.clipboardData.setData("text/html", "")
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- const tempElement = document.createElement("div");
|
|
|
- tempElement.appendChild(range.cloneContents());
|
|
|
+ const tempElement = document.createElement("div")
|
|
|
+ tempElement.appendChild(range.cloneContents())
|
|
|
|
|
|
- event.clipboardData.setData("text/plain", vditor.lute.VditorDOM2Md(tempElement.innerHTML).trim());
|
|
|
- event.clipboardData.setData("text/html", "");
|
|
|
+ event.clipboardData.setData("text/plain", vditor.lute.VditorDOM2Md(tempElement.innerHTML).trim())
|
|
|
+ event.clipboardData.setData("text/html", "")
|
|
|
}
|
|
|
|
|
|
private bindEvent(vditor: IVditor) {
|
|
|
- this.unbindListener();
|
|
|
+ this.unbindListener()
|
|
|
window.addEventListener("scroll", this.scrollListener = () => {
|
|
|
- hidePanel(vditor, ["hint"]);
|
|
|
+ hidePanel(vditor, ["hint"])
|
|
|
if (this.popover.style.display !== "block" || this.selectPopover.style.display !== "block") {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
- const top = parseInt(this.popover.getAttribute("data-top"), 10);
|
|
|
+ const top = parseInt(this.popover.getAttribute("data-top"), 10)
|
|
|
if (vditor.options.height !== "auto") {
|
|
|
if (vditor.options.toolbarConfig.pin && vditor.toolbar.element.getBoundingClientRect().top === 0) {
|
|
|
const popoverTop = Math.max(window.scrollY - vditor.element.offsetTop - 8,
|
|
|
- Math.min(top - vditor.wysiwyg.element.scrollTop, this.element.clientHeight - 21)) + "px";
|
|
|
+ Math.min(top - vditor.wysiwyg.element.scrollTop, this.element.clientHeight - 21)) + "px"
|
|
|
if (this.popover.style.display === "block") {
|
|
|
- this.popover.style.top = popoverTop;
|
|
|
+ this.popover.style.top = popoverTop
|
|
|
}
|
|
|
if (this.selectPopover.style.display === "block") {
|
|
|
- this.selectPopover.style.top = popoverTop;
|
|
|
+ this.selectPopover.style.top = popoverTop
|
|
|
}
|
|
|
}
|
|
|
- return;
|
|
|
+ return
|
|
|
} else if (!vditor.options.toolbarConfig.pin) {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
- const popoverTop1 = Math.max(top, (window.scrollY - vditor.element.offsetTop - 8)) + "px";
|
|
|
+ const popoverTop1 = Math.max(top, (window.scrollY - vditor.element.offsetTop - 8)) + "px"
|
|
|
if (this.popover.style.display === "block") {
|
|
|
- this.popover.style.top = popoverTop1;
|
|
|
+ this.popover.style.top = popoverTop1
|
|
|
}
|
|
|
if (this.selectPopover.style.display === "block") {
|
|
|
- this.selectPopover.style.top = popoverTop1;
|
|
|
+ this.selectPopover.style.top = popoverTop1
|
|
|
}
|
|
|
- });
|
|
|
+ })
|
|
|
|
|
|
this.element.addEventListener("scroll", () => {
|
|
|
- hidePanel(vditor, ["hint"]);
|
|
|
+ hidePanel(vditor, ["hint"])
|
|
|
if (vditor.options.comment && vditor.options.comment.enable && vditor.options.comment.scroll) {
|
|
|
- vditor.options.comment.scroll(vditor.wysiwyg.element.scrollTop);
|
|
|
+ vditor.options.comment.scroll(vditor.wysiwyg.element.scrollTop)
|
|
|
}
|
|
|
if (this.popover.style.display !== "block") {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
- const top = parseInt(this.popover.getAttribute("data-top"), 10) - vditor.wysiwyg.element.scrollTop;
|
|
|
- let max = -8;
|
|
|
+ const top = parseInt(this.popover.getAttribute("data-top"), 10) - vditor.wysiwyg.element.scrollTop
|
|
|
+ let max = -8
|
|
|
if (vditor.options.toolbarConfig.pin && vditor.toolbar.element.getBoundingClientRect().top === 0) {
|
|
|
- max = window.scrollY - vditor.element.offsetTop + max;
|
|
|
+ max = window.scrollY - vditor.element.offsetTop + max
|
|
|
}
|
|
|
- const topPx = Math.max(max, Math.min(top, this.element.clientHeight - 21)) + "px";
|
|
|
- this.popover.style.top = topPx;
|
|
|
- this.selectPopover.style.top = topPx;
|
|
|
- });
|
|
|
+ const topPx = Math.max(max, Math.min(top, this.element.clientHeight - 21)) + "px"
|
|
|
+ this.popover.style.top = topPx
|
|
|
+ this.selectPopover.style.top = topPx
|
|
|
+ })
|
|
|
|
|
|
this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => {
|
|
|
paste(vditor, event, {
|
|
|
pasteCode: (code: string) => {
|
|
|
- const range = getEditorRange(vditor);
|
|
|
- const node = document.createElement("template");
|
|
|
- node.innerHTML = code;
|
|
|
- range.insertNode(node.content.cloneNode(true));
|
|
|
- const blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
|
|
|
+ const range = getEditorRange(vditor)
|
|
|
+ const node = document.createElement("template")
|
|
|
+ node.innerHTML = code
|
|
|
+ range.insertNode(node.content.cloneNode(true))
|
|
|
+ const blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0")
|
|
|
if (blockElement) {
|
|
|
- blockElement.outerHTML = vditor.lute.SpinVditorDOM(blockElement.outerHTML);
|
|
|
+ blockElement.outerHTML = vditor.lute.SpinVditorDOM(blockElement.outerHTML)
|
|
|
} else {
|
|
|
- vditor.wysiwyg.element.innerHTML = vditor.lute.SpinVditorDOM(vditor.wysiwyg.element.innerHTML);
|
|
|
+ vditor.wysiwyg.element.innerHTML = vditor.lute.SpinVditorDOM(vditor.wysiwyg.element.innerHTML)
|
|
|
}
|
|
|
- setRangeByWbr(vditor.wysiwyg.element, range);
|
|
|
+ setRangeByWbr(vditor.wysiwyg.element, range)
|
|
|
},
|
|
|
- });
|
|
|
- });
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
// 中文处理
|
|
|
this.element.addEventListener("compositionstart", () => {
|
|
|
- this.composingLock = true;
|
|
|
- });
|
|
|
+ this.composingLock = true
|
|
|
+ })
|
|
|
|
|
|
this.element.addEventListener("compositionend", (event: InputEvent) => {
|
|
|
- const headingElement = hasClosestByHeadings(getSelection().getRangeAt(0).startContainer);
|
|
|
+ const headingElement = hasClosestByHeadings(getSelection().getRangeAt(0).startContainer)
|
|
|
if (headingElement && headingElement.textContent === "") {
|
|
|
// heading 为空删除 https://github.com/Vanessa219/vditor/issues/150
|
|
|
- renderToc(vditor);
|
|
|
- return;
|
|
|
+ renderToc(vditor)
|
|
|
+ return
|
|
|
}
|
|
|
if (!isFirefox()) {
|
|
|
- input(vditor, getSelection().getRangeAt(0).cloneRange(), event);
|
|
|
+ input(vditor, getSelection().getRangeAt(0).cloneRange(), event)
|
|
|
}
|
|
|
- this.composingLock = false;
|
|
|
- });
|
|
|
+ this.composingLock = false
|
|
|
+ })
|
|
|
|
|
|
this.element.addEventListener("input", (event: InputEvent) => {
|
|
|
if (event.inputType === "deleteByDrag" || event.inputType === "insertFromDrop") {
|
|
|
// https://github.com/Vanessa219/vditor/issues/801 编辑器内容拖拽问题
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
if (this.preventInput) {
|
|
|
- this.preventInput = false;
|
|
|
- afterRenderEvent(vditor);
|
|
|
- return;
|
|
|
+ this.preventInput = false
|
|
|
+ afterRenderEvent(vditor)
|
|
|
+ return
|
|
|
}
|
|
|
if (this.composingLock || event.data === "‘" || event.data === "“" || event.data === "《") {
|
|
|
- afterRenderEvent(vditor);
|
|
|
- return;
|
|
|
+ afterRenderEvent(vditor)
|
|
|
+ return
|
|
|
}
|
|
|
- const range = getSelection().getRangeAt(0);
|
|
|
- let blockElement = hasClosestBlock(range.startContainer);
|
|
|
+ const range = getSelection().getRangeAt(0)
|
|
|
+ let blockElement = hasClosestBlock(range.startContainer)
|
|
|
if (!blockElement) {
|
|
|
// 没有被块元素包裹
|
|
|
- modifyPre(vditor, range);
|
|
|
- blockElement = hasClosestBlock(range.startContainer);
|
|
|
+ modifyPre(vditor, range)
|
|
|
+ blockElement = hasClosestBlock(range.startContainer)
|
|
|
}
|
|
|
if (!blockElement) {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// 前后空格处理
|
|
|
- const startOffset = getSelectPosition(blockElement, vditor.wysiwyg.element, range).start;
|
|
|
+ const startOffset = getSelectPosition(blockElement, vditor.wysiwyg.element, range).start
|
|
|
|
|
|
// 开始可以输入空格
|
|
|
- let startSpace = true;
|
|
|
+ let startSpace = true
|
|
|
for (let i = startOffset - 1; i > blockElement.textContent.substr(0, startOffset).lastIndexOf("\n"); i--) {
|
|
|
if (blockElement.textContent.charAt(i) !== " " &&
|
|
|
// 多个 tab 前删除不形成代码块 https://github.com/Vanessa219/vditor/issues/162 1
|
|
|
blockElement.textContent.charAt(i) !== "\t") {
|
|
|
- startSpace = false;
|
|
|
- break;
|
|
|
+ startSpace = false
|
|
|
+ break
|
|
|
}
|
|
|
}
|
|
|
if (startOffset === 0) {
|
|
|
- startSpace = false;
|
|
|
+ startSpace = false
|
|
|
}
|
|
|
|
|
|
// 结尾可以输入空格
|
|
|
- let endSpace = true;
|
|
|
+ let endSpace = true
|
|
|
for (let i = startOffset - 1; i < blockElement.textContent.length; i++) {
|
|
|
if (blockElement.textContent.charAt(i) !== " " && blockElement.textContent.charAt(i) !== "\n") {
|
|
|
- endSpace = false;
|
|
|
- break;
|
|
|
+ endSpace = false
|
|
|
+ break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// https://github.com/Vanessa219/vditor/issues/729
|
|
|
if (endSpace && /^#{1,6} $/.test(blockElement.textContent)) {
|
|
|
- endSpace = false;
|
|
|
+ endSpace = false
|
|
|
}
|
|
|
|
|
|
- const headingElement = hasClosestByHeadings(getSelection().getRangeAt(0).startContainer);
|
|
|
+ const headingElement = hasClosestByHeadings(getSelection().getRangeAt(0).startContainer)
|
|
|
if (headingElement && headingElement.textContent === "") {
|
|
|
// heading 为空删除 https://github.com/Vanessa219/vditor/issues/150
|
|
|
- renderToc(vditor);
|
|
|
- headingElement.remove();
|
|
|
+ renderToc(vditor)
|
|
|
+ headingElement.remove()
|
|
|
}
|
|
|
|
|
|
if ((startSpace && blockElement.getAttribute("data-type") !== "code-block")
|
|
|
|| endSpace || isHeadingMD(blockElement.innerHTML) ||
|
|
|
(isHrMD(blockElement.innerHTML) && blockElement.previousElementSibling)) {
|
|
|
if (typeof vditor.options.input === "function") {
|
|
|
- vditor.options.input(getMarkdown(vditor));
|
|
|
+ vditor.options.input(getMarkdown(vditor))
|
|
|
}
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
// https://github.com/Vanessa219/vditor/issues/1565
|
|
|
- if (event.inputType === "insertParagraph" && this.element.innerHTML === '<p><br></p><p><br></p>') {
|
|
|
- blockElement.previousElementSibling.remove();
|
|
|
+ if (event.inputType === "insertParagraph" && this.element.innerHTML === "<p><br></p><p><br></p>") {
|
|
|
+ blockElement.previousElementSibling.remove()
|
|
|
}
|
|
|
|
|
|
- input(vditor, range, event);
|
|
|
- });
|
|
|
+ input(vditor, range, event)
|
|
|
+ })
|
|
|
|
|
|
this.element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
|
|
|
if (event.target.tagName === "INPUT") {
|
|
|
- const checkElement = event.target as HTMLInputElement;
|
|
|
+ const checkElement = event.target as HTMLInputElement
|
|
|
if (checkElement.checked) {
|
|
|
- checkElement.setAttribute("checked", "checked");
|
|
|
+ checkElement.setAttribute("checked", "checked")
|
|
|
} else {
|
|
|
- checkElement.removeAttribute("checked");
|
|
|
+ checkElement.removeAttribute("checked")
|
|
|
+ }
|
|
|
+ this.preventInput = true
|
|
|
+ if (getSelection().rangeCount > 0) {
|
|
|
+ setSelectionFocus(getSelection().getRangeAt(0))
|
|
|
}
|
|
|
- this.preventInput = true;
|
|
|
- afterRenderEvent(vditor);
|
|
|
- return;
|
|
|
+ afterRenderEvent(vditor)
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
if (event.target.tagName === "IMG" &&
|
|
|
// plantuml 图片渲染不进行提示
|
|
|
!event.target.parentElement.classList.contains("vditor-wysiwyg__preview")) {
|
|
|
if (event.target.getAttribute("data-type") === "link-ref") {
|
|
|
- genLinkRefPopover(vditor, event.target);
|
|
|
+ genLinkRefPopover(vditor, event.target)
|
|
|
} else {
|
|
|
- genImagePopover(event, vditor);
|
|
|
+ genImagePopover(event, vditor)
|
|
|
}
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// 打开链接
|
|
|
- const a = hasClosestByMatchTag(event.target, "A");
|
|
|
+ const a = hasClosestByMatchTag(event.target, "A")
|
|
|
if (a) {
|
|
|
if (vditor.options.link.click) {
|
|
|
- vditor.options.link.click(a);
|
|
|
+ vditor.options.link.click(a)
|
|
|
} else if (vditor.options.link.isOpen) {
|
|
|
- window.open(a.getAttribute("href"));
|
|
|
+ window.open(a.getAttribute("href"))
|
|
|
}
|
|
|
- event.preventDefault();
|
|
|
- return;
|
|
|
+ event.preventDefault()
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- const range = getEditorRange(vditor);
|
|
|
+ const range = getEditorRange(vditor)
|
|
|
if (event.target.isEqualNode(this.element) && this.element.lastElementChild && range.collapsed) {
|
|
|
- const lastRect = this.element.lastElementChild.getBoundingClientRect();
|
|
|
+ const lastRect = this.element.lastElementChild.getBoundingClientRect()
|
|
|
if (event.y > lastRect.top + lastRect.height) {
|
|
|
if (this.element.lastElementChild.tagName === "P" &&
|
|
|
this.element.lastElementChild.textContent.trim().replace(Constants.ZWSP, "") === "") {
|
|
|
- range.selectNodeContents(this.element.lastElementChild);
|
|
|
- range.collapse(false);
|
|
|
+ range.selectNodeContents(this.element.lastElementChild)
|
|
|
+ range.collapse(false)
|
|
|
} else {
|
|
|
this.element.insertAdjacentHTML("beforeend",
|
|
|
- `<p data-block="0">${Constants.ZWSP}<wbr></p>`);
|
|
|
- setRangeByWbr(this.element, range);
|
|
|
+ `<p data-block="0">${Constants.ZWSP}<wbr></p>`)
|
|
|
+ setRangeByWbr(this.element, range)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- highlightToolbarWYSIWYG(vditor);
|
|
|
+ highlightToolbarWYSIWYG(vditor)
|
|
|
|
|
|
// 点击后光标落于预览区,需展开代码块
|
|
|
- let previewElement = hasClosestByClassName(event.target, "vditor-wysiwyg__preview");
|
|
|
+ let previewElement = hasClosestByClassName(event.target, "vditor-wysiwyg__preview")
|
|
|
if (!previewElement) {
|
|
|
previewElement =
|
|
|
- hasClosestByClassName(getEditorRange(vditor).startContainer, "vditor-wysiwyg__preview");
|
|
|
+ hasClosestByClassName(getEditorRange(vditor).startContainer, "vditor-wysiwyg__preview")
|
|
|
}
|
|
|
if (previewElement) {
|
|
|
- showCode(previewElement, vditor);
|
|
|
+ showCode(previewElement, vditor)
|
|
|
}
|
|
|
|
|
|
- clickToc(event, vditor);
|
|
|
- });
|
|
|
+ clickToc(event, vditor)
|
|
|
+ })
|
|
|
|
|
|
this.element.addEventListener("keyup", (event: KeyboardEvent & { target: HTMLElement }) => {
|
|
|
if (event.isComposing || isCtrl(event)) {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
// 除 md 处理、cell 内换行、table 添加新行/列、代码块语言切换、block render 换行、跳出/逐层跳出 blockquote、h6 换行、
|
|
|
// 任务列表换行、软换行外需在换行时调整文档位置
|
|
|
if (event.key === "Enter") {
|
|
|
- scrollCenter(vditor);
|
|
|
+ scrollCenter(vditor)
|
|
|
}
|
|
|
if ((event.key === "Backspace" || event.key === "Delete") &&
|
|
|
vditor.wysiwyg.element.innerHTML !== "" && vditor.wysiwyg.element.childNodes.length === 1 &&
|
|
@@ -490,66 +493,66 @@ class WYSIWYG {
|
|
|
&& vditor.wysiwyg.element.firstElementChild.childElementCount === 0
|
|
|
&& (vditor.wysiwyg.element.textContent === "" || vditor.wysiwyg.element.textContent === "\n")) {
|
|
|
// 为空时显示 placeholder
|
|
|
- vditor.wysiwyg.element.innerHTML = "";
|
|
|
+ vditor.wysiwyg.element.innerHTML = ""
|
|
|
}
|
|
|
- const range = getEditorRange(vditor);
|
|
|
+ const range = getEditorRange(vditor)
|
|
|
if (event.key === "Backspace") {
|
|
|
// firefox headings https://github.com/Vanessa219/vditor/issues/211
|
|
|
if (isFirefox() && range.startContainer.textContent === "\n" && range.startOffset === 1) {
|
|
|
- range.startContainer.textContent = "";
|
|
|
+ range.startContainer.textContent = ""
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 没有被块元素包裹
|
|
|
- modifyPre(vditor, range);
|
|
|
+ modifyPre(vditor, range)
|
|
|
|
|
|
- highlightToolbarWYSIWYG(vditor);
|
|
|
+ highlightToolbarWYSIWYG(vditor)
|
|
|
|
|
|
if (event.key !== "ArrowDown" && event.key !== "ArrowRight" && event.key !== "Backspace"
|
|
|
&& event.key !== "ArrowLeft" && event.key !== "ArrowUp") {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
|
- vditor.hint.render(vditor);
|
|
|
+ vditor.hint.render(vditor)
|
|
|
}
|
|
|
|
|
|
// 上下左右,删除遇到块预览的处理
|
|
|
- let previewElement = hasClosestByClassName(range.startContainer, "vditor-wysiwyg__preview");
|
|
|
+ let previewElement = hasClosestByClassName(range.startContainer, "vditor-wysiwyg__preview")
|
|
|
if (!previewElement && range.startContainer.nodeType !== 3 && range.startOffset > 0) {
|
|
|
// table 前删除遇到代码块
|
|
|
- const blockRenderElement = range.startContainer as HTMLElement;
|
|
|
+ const blockRenderElement = range.startContainer as HTMLElement
|
|
|
if (blockRenderElement.classList.contains("vditor-wysiwyg__block")) {
|
|
|
- previewElement = blockRenderElement.lastElementChild as HTMLElement;
|
|
|
+ previewElement = blockRenderElement.lastElementChild as HTMLElement
|
|
|
}
|
|
|
}
|
|
|
if (!previewElement) {
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
- const previousElement = previewElement.previousElementSibling as HTMLElement;
|
|
|
+ const previousElement = previewElement.previousElementSibling as HTMLElement
|
|
|
if (previousElement.style.display === "none") {
|
|
|
if (event.key === "ArrowDown" || event.key === "ArrowRight") {
|
|
|
- showCode(previewElement, vditor);
|
|
|
+ showCode(previewElement, vditor)
|
|
|
} else {
|
|
|
- showCode(previewElement, vditor, false);
|
|
|
+ showCode(previewElement, vditor, false)
|
|
|
}
|
|
|
- return;
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- let codeElement = previewElement.previousElementSibling as HTMLElement;
|
|
|
+ let codeElement = previewElement.previousElementSibling as HTMLElement
|
|
|
if (codeElement.tagName === "PRE") {
|
|
|
- codeElement = codeElement.firstElementChild as HTMLElement;
|
|
|
+ codeElement = codeElement.firstElementChild as HTMLElement
|
|
|
}
|
|
|
|
|
|
if (event.key === "ArrowDown" || event.key === "ArrowRight") {
|
|
|
- const blockRenderElement = previewElement.parentElement;
|
|
|
- let nextNode = getRenderElementNextNode(blockRenderElement) as HTMLElement;
|
|
|
+ const blockRenderElement = previewElement.parentElement
|
|
|
+ let nextNode = getRenderElementNextNode(blockRenderElement) as HTMLElement
|
|
|
if (nextNode && nextNode.nodeType !== 3) {
|
|
|
// 下一节点依旧为代码渲染块
|
|
|
- const nextRenderElement = nextNode.querySelector(".vditor-wysiwyg__preview") as HTMLElement;
|
|
|
+ const nextRenderElement = nextNode.querySelector(".vditor-wysiwyg__preview") as HTMLElement
|
|
|
if (nextRenderElement) {
|
|
|
- showCode(nextRenderElement, vditor);
|
|
|
- return;
|
|
|
+ showCode(nextRenderElement, vditor)
|
|
|
+ return
|
|
|
}
|
|
|
}
|
|
|
// 跳过渲染块,光标移动到下一个节点
|
|
@@ -557,19 +560,19 @@ class WYSIWYG {
|
|
|
// inline
|
|
|
while (nextNode.textContent.length === 0 && nextNode.nextSibling) {
|
|
|
// https://github.com/Vanessa219/vditor/issues/100 2
|
|
|
- nextNode = nextNode.nextSibling as HTMLElement;
|
|
|
+ nextNode = nextNode.nextSibling as HTMLElement
|
|
|
}
|
|
|
- range.setStart(nextNode, 1);
|
|
|
+ range.setStart(nextNode, 1)
|
|
|
} else {
|
|
|
// block
|
|
|
- range.setStart(nextNode.firstChild, 0);
|
|
|
+ range.setStart(nextNode.firstChild, 0)
|
|
|
}
|
|
|
} else {
|
|
|
- range.selectNodeContents(codeElement);
|
|
|
- range.collapse(false);
|
|
|
+ range.selectNodeContents(codeElement)
|
|
|
+ range.collapse(false)
|
|
|
}
|
|
|
- });
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export {WYSIWYG};
|
|
|
+export { WYSIWYG }
|