vditor
Version:
♏ 易于使用的 Markdown 编辑器,为适配不同的应用场景而生
1,140 lines (1,069 loc) • 48.5 kB
text/typescript
import {Constants} from "../constants";
import {disableToolbar} from "../toolbar/setToolbar";
import {enableToolbar} from "../toolbar/setToolbar";
import {removeCurrentToolbar} from "../toolbar/setToolbar";
import {setCurrentToolbar} from "../toolbar/setToolbar";
import {isCtrl, updateHotkeyTip} from "../util/compatibility";
import {scrollCenter} from "../util/editorCommonEvent";
import {
deleteColumn,
deleteRow,
insertColumn,
insertRow,
insertRowAbove,
setTableAlign,
} from "../util/fixBrowserBehavior";
import {
hasClosestByAttribute,
hasClosestByClassName,
hasClosestByMatchTag,
} from "../util/hasClosest";
import {
hasClosestByHeadings,
hasClosestByTag,
} from "../util/hasClosestByHeadings";
import {processCodeRender} from "../util/processCode";
import {
getEditorRange,
selectIsEditor,
setRangeByWbr,
setSelectionFocus,
} from "../util/selection";
import {afterRenderEvent} from "./afterRenderEvent";
import {removeBlockElement} from "./processKeydown";
import {renderToc} from "../util/toc";
import {getMarkdown} from "../markdown/getMarkdown";
export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
clearTimeout(vditor.wysiwyg.hlToolbarTimeoutId);
vditor.wysiwyg.hlToolbarTimeoutId = window.setTimeout(() => {
if (
vditor.wysiwyg.element.getAttribute("contenteditable") === "false"
) {
return;
}
if (!selectIsEditor(vditor.wysiwyg.element)) {
return;
}
removeCurrentToolbar(vditor.toolbar.elements, Constants.EDIT_TOOLBARS);
enableToolbar(vditor.toolbar.elements, Constants.EDIT_TOOLBARS);
const range = getSelection().getRangeAt(0);
let typeElement = range.startContainer as HTMLElement;
if (range.startContainer.nodeType === 3) {
typeElement = range.startContainer.parentElement;
} else {
typeElement = typeElement.childNodes[
range.startOffset >= typeElement.childNodes.length
? typeElement.childNodes.length - 1
: range.startOffset
] as HTMLElement;
}
const footnotesElement = hasClosestByAttribute(typeElement, "data-type", "footnotes-block");
if (footnotesElement) {
vditor.wysiwyg.popover.innerHTML = "";
genClose(footnotesElement, vditor);
setPopoverPosition(vditor, footnotesElement);
return;
}
// 工具栏高亮和禁用
const liElement = hasClosestByMatchTag(typeElement, "LI");
if (liElement) {
if (liElement.classList.contains("vditor-task")) {
setCurrentToolbar(vditor.toolbar.elements, ["check"]);
} else if (liElement.parentElement.tagName === "OL") {
setCurrentToolbar(vditor.toolbar.elements, ["ordered-list"]);
} else if (liElement.parentElement.tagName === "UL") {
setCurrentToolbar(vditor.toolbar.elements, ["list"]);
}
enableToolbar(vditor.toolbar.elements, ["outdent", "indent"]);
} else {
disableToolbar(vditor.toolbar.elements, ["outdent", "indent"]);
}
if (hasClosestByMatchTag(typeElement, "BLOCKQUOTE")) {
setCurrentToolbar(vditor.toolbar.elements, ["quote"]);
}
if (
hasClosestByMatchTag(typeElement, "B") ||
hasClosestByMatchTag(typeElement, "STRONG")
) {
setCurrentToolbar(vditor.toolbar.elements, ["bold"]);
}
if (
hasClosestByMatchTag(typeElement, "I") ||
hasClosestByMatchTag(typeElement, "EM")
) {
setCurrentToolbar(vditor.toolbar.elements, ["italic"]);
}
if (
hasClosestByMatchTag(typeElement, "STRIKE") ||
hasClosestByMatchTag(typeElement, "S")
) {
setCurrentToolbar(vditor.toolbar.elements, ["strike"]);
}
// comments
vditor.wysiwyg.element
.querySelectorAll(".vditor-comment--focus")
.forEach((item) => {
item.classList.remove("vditor-comment--focus");
});
const commentElement = hasClosestByClassName(typeElement, "vditor-comment");
if (commentElement) {
let ids = commentElement.getAttribute("data-cmtids").split(" ");
if (ids.length > 1 && commentElement.nextSibling.isSameNode(commentElement.nextElementSibling)) {
const nextIds = commentElement.nextElementSibling
.getAttribute("data-cmtids")
.split(" ");
ids.find((id) => {
if (nextIds.includes(id)) {
ids = [id];
return true;
}
});
}
vditor.wysiwyg.element
.querySelectorAll(".vditor-comment")
.forEach((item) => {
if (item.getAttribute("data-cmtids").indexOf(ids[0]) > -1) {
item.classList.add("vditor-comment--focus");
}
});
}
const aElement = hasClosestByMatchTag(typeElement, "A");
if (aElement) {
setCurrentToolbar(vditor.toolbar.elements, ["link"]);
}
const tableElement = hasClosestByMatchTag(typeElement, "TABLE") as HTMLTableElement;
const headingElement = hasClosestByHeadings(typeElement) as HTMLElement;
if (hasClosestByMatchTag(typeElement, "CODE")) {
if (hasClosestByMatchTag(typeElement, "PRE")) {
disableToolbar(vditor.toolbar.elements, [
"headings",
"bold",
"italic",
"strike",
"line",
"quote",
"list",
"ordered-list",
"check",
"code",
"inline-code",
"upload",
"link",
"table",
"record",
]);
setCurrentToolbar(vditor.toolbar.elements, ["code"]);
} else {
disableToolbar(vditor.toolbar.elements, [
"headings",
"bold",
"italic",
"strike",
"line",
"quote",
"list",
"ordered-list",
"check",
"code",
"upload",
"link",
"table",
"record",
]);
setCurrentToolbar(vditor.toolbar.elements, ["inline-code"]);
}
} else if (headingElement) {
disableToolbar(vditor.toolbar.elements, ["bold"]);
setCurrentToolbar(vditor.toolbar.elements, ["headings"]);
} else if (tableElement) {
disableToolbar(vditor.toolbar.elements, ["table"]);
}
// toc popover
const tocElement = hasClosestByClassName(typeElement, "vditor-toc") as HTMLElement;
if (tocElement) {
vditor.wysiwyg.popover.innerHTML = "";
genClose(tocElement, vditor);
setPopoverPosition(vditor, tocElement);
return;
}
// quote popover
const blockquoteElement = hasClosestByTag(typeElement, "BLOCKQUOTE") as HTMLTableElement;
if (blockquoteElement) {
vditor.wysiwyg.popover.innerHTML = "";
genUp(range, blockquoteElement, vditor);
genDown(range, blockquoteElement, vditor);
genClose(blockquoteElement, vditor);
setPopoverPosition(vditor, blockquoteElement);
}
// list item popover
if (liElement) {
vditor.wysiwyg.popover.innerHTML = "";
genUp(range, liElement, vditor);
genDown(range, liElement, vditor);
genClose(liElement, vditor);
setPopoverPosition(vditor, liElement);
}
// table popover
if (tableElement) {
const lang: keyof II18n | "" = vditor.options.lang;
const options: IOptions = vditor.options;
vditor.wysiwyg.popover.innerHTML = "";
const updateTable = () => {
const oldRow = tableElement.rows.length;
const oldColumn = tableElement.rows[0].cells.length;
const row = parseInt(input.value, 10) || oldRow;
const column = parseInt(input2.value, 10) || oldColumn;
if (row === oldRow && oldColumn === column) {
return;
}
if (oldColumn !== column) {
const columnDiff = column - oldColumn;
for (let i = 0; i < tableElement.rows.length; i++) {
if (columnDiff > 0) {
for (let j = 0; j < columnDiff; j++) {
if (i === 0) {
tableElement.rows[i].lastElementChild.insertAdjacentHTML("afterend", "<th> </th>");
} else {
tableElement.rows[i].lastElementChild.insertAdjacentHTML("afterend", "<td> </td>");
}
}
} else {
for (let k = oldColumn - 1; k >= column; k--) {
tableElement.rows[i].cells[k].remove();
}
}
}
}
if (oldRow !== row) {
const rowDiff = row - oldRow;
if (rowDiff > 0) {
let rowHTML = "<tr>";
for (let m = 0; m < column; m++) {
rowHTML += "<td> </td>";
}
for (let l = 0; l < rowDiff; l++) {
if (tableElement.querySelector("tbody")) {
tableElement
.querySelector("tbody")
.insertAdjacentHTML("beforeend", rowHTML);
} else {
tableElement
.querySelector("thead")
.insertAdjacentHTML("afterend", rowHTML + "</tr>");
}
}
} else {
for (let m = oldRow - 1; m >= row; m--) {
tableElement.rows[m].remove();
if (tableElement.rows.length === 1) {
tableElement.querySelector("tbody").remove();
}
}
}
}
if (typeof vditor.options.input === "function") {
vditor.options.input(getMarkdown(vditor));
}
};
const setAlign = (type: string) => {
setTableAlign(tableElement, type);
if (type === "right") {
left.classList.remove("vditor-icon--current");
center.classList.remove("vditor-icon--current");
right.classList.add("vditor-icon--current");
} else if (type === "center") {
left.classList.remove("vditor-icon--current");
right.classList.remove("vditor-icon--current");
center.classList.add("vditor-icon--current");
} else {
center.classList.remove("vditor-icon--current");
right.classList.remove("vditor-icon--current");
left.classList.add("vditor-icon--current");
}
setSelectionFocus(range);
afterRenderEvent(vditor);
};
const td = hasClosestByMatchTag(typeElement, "TD");
const th = hasClosestByMatchTag(typeElement, "TH");
let alignType = "left";
if (td) {
alignType = td.getAttribute("align") || "left";
} else if (th) {
alignType = th.getAttribute("align") || "center";
}
const left = document.createElement("button");
left.setAttribute("type", "button");
left.setAttribute("aria-label", window.VditorI18n.alignLeft + "<" + updateHotkeyTip("⇧⌘L") + ">");
left.setAttribute("data-type", "left");
left.innerHTML =
'<svg><use xlink:href="#vditor-icon-align-left"></use></svg>';
left.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n" +
(alignType === "left" ? " vditor-icon--current" : "");
left.onclick = () => {
setAlign("left");
};
const center = document.createElement("button");
center.setAttribute("type", "button");
center.setAttribute("aria-label", window.VditorI18n.alignCenter + "<" + updateHotkeyTip("⇧⌘C") + ">");
center.setAttribute("data-type", "center");
center.innerHTML =
'<svg><use xlink:href="#vditor-icon-align-center"></use></svg>';
center.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n" +
(alignType === "center" ? " vditor-icon--current" : "");
center.onclick = () => {
setAlign("center");
};
const right = document.createElement("button");
right.setAttribute("type", "button");
right.setAttribute("aria-label", window.VditorI18n.alignRight + "<" + updateHotkeyTip("⇧⌘R") + ">");
right.setAttribute("data-type", "right");
right.innerHTML =
'<svg><use xlink:href="#vditor-icon-align-right"></use></svg>';
right.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n" +
(alignType === "right" ? " vditor-icon--current" : "");
right.onclick = () => {
setAlign("right");
};
const insertRowElement = document.createElement("button");
insertRowElement.setAttribute("type", "button");
insertRowElement.setAttribute("aria-label", window.VditorI18n.insertRowBelow + "<" + updateHotkeyTip("⌘=") + ">");
insertRowElement.setAttribute("data-type", "insertRow");
insertRowElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-insert-row"></use></svg>';
insertRowElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
insertRowElement.onclick = () => {
const startContainer = getSelection().getRangeAt(0)
.startContainer;
const cellElement =
hasClosestByMatchTag(startContainer, "TD") ||
hasClosestByMatchTag(startContainer, "TH");
if (cellElement) {
insertRow(vditor, range, cellElement);
}
};
const insertRowBElement = document.createElement("button");
insertRowBElement.setAttribute("type", "button");
insertRowBElement.setAttribute("aria-label",
window.VditorI18n.insertRowAbove + "<" + updateHotkeyTip("⇧⌘F") + ">");
insertRowBElement.setAttribute("data-type", "insertRow");
insertRowBElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-insert-rowb"></use></svg>';
insertRowBElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
insertRowBElement.onclick = () => {
const startContainer = getSelection().getRangeAt(0)
.startContainer;
const cellElement =
hasClosestByMatchTag(startContainer, "TD") ||
hasClosestByMatchTag(startContainer, "TH");
if (cellElement) {
insertRowAbove(vditor, range, cellElement);
}
};
const insertColumnElement = document.createElement("button");
insertColumnElement.setAttribute("type", "button");
insertColumnElement.setAttribute("aria-label", window.VditorI18n.insertColumnRight + "<" + updateHotkeyTip("⇧⌘=") + ">");
insertColumnElement.setAttribute("data-type", "insertColumn");
insertColumnElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-insert-column"></use></svg>';
insertColumnElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
insertColumnElement.onclick = () => {
const startContainer = getSelection().getRangeAt(0)
.startContainer;
const cellElement =
hasClosestByMatchTag(startContainer, "TD") ||
hasClosestByMatchTag(startContainer, "TH");
if (cellElement) {
insertColumn(vditor, tableElement, cellElement);
}
};
const insertColumnBElement = document.createElement("button");
insertColumnBElement.setAttribute("type", "button");
insertColumnBElement.setAttribute("aria-label", window.VditorI18n.insertColumnLeft + "<" + updateHotkeyTip("⇧⌘G") + ">");
insertColumnBElement.setAttribute("data-type", "insertColumn");
insertColumnBElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-insert-columnb"></use></svg>';
insertColumnBElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
insertColumnBElement.onclick = () => {
const startContainer = getSelection().getRangeAt(0)
.startContainer;
const cellElement =
hasClosestByMatchTag(startContainer, "TD") ||
hasClosestByMatchTag(startContainer, "TH");
if (cellElement) {
insertColumn(vditor, tableElement, cellElement, "beforebegin");
}
};
const deleteRowElement = document.createElement("button");
deleteRowElement.setAttribute("type", "button");
deleteRowElement.setAttribute("aria-label", window.VditorI18n["delete-row"] + "<" + updateHotkeyTip("⌘-") + ">");
deleteRowElement.setAttribute("data-type", "deleteRow");
deleteRowElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-delete-row"></use></svg>';
deleteRowElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
deleteRowElement.onclick = () => {
const startContainer = getSelection().getRangeAt(0)
.startContainer;
const cellElement =
hasClosestByMatchTag(startContainer, "TD") ||
hasClosestByMatchTag(startContainer, "TH");
if (cellElement) {
deleteRow(vditor, range, cellElement);
}
};
const deleteColumnElement = document.createElement("button");
deleteColumnElement.setAttribute("type", "button");
deleteColumnElement.setAttribute("aria-label", window.VditorI18n["delete-column"] + "<" + updateHotkeyTip("⇧⌘-") + ">");
deleteColumnElement.setAttribute("data-type", "deleteColumn");
deleteColumnElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-delete-column"></use></svg>';
deleteColumnElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
deleteColumnElement.onclick = () => {
const startContainer = getSelection().getRangeAt(0)
.startContainer;
const cellElement =
hasClosestByMatchTag(startContainer, "TD") ||
hasClosestByMatchTag(startContainer, "TH");
if (cellElement) {
deleteColumn(vditor, range, tableElement, cellElement);
}
};
const inputWrap = document.createElement("span");
inputWrap.setAttribute("aria-label", window.VditorI18n.row);
inputWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input = document.createElement("input");
inputWrap.appendChild(input);
input.type = "number";
input.min = "1";
input.className = "vditor-input";
input.style.width = "42px";
input.style.textAlign = "center";
input.setAttribute("placeholder", window.VditorI18n.row);
input.value = tableElement.rows.length.toString();
input.oninput = () => {
updateTable();
};
input.onkeydown = (event) => {
if (event.isComposing) {
return;
}
if (event.key === "Tab") {
input2.focus();
input2.select();
event.preventDefault();
return;
}
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
};
const input2Wrap = document.createElement("span");
input2Wrap.setAttribute("aria-label", window.VditorI18n.column);
input2Wrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input2 = document.createElement("input");
input2Wrap.appendChild(input2);
input2.type = "number";
input2.min = "1";
input2.className = "vditor-input";
input2.style.width = "42px";
input2.style.textAlign = "center";
input2.setAttribute("placeholder", window.VditorI18n.column);
input2.value = tableElement.rows[0].cells.length.toString();
input2.oninput = () => {
updateTable();
};
input2.onkeydown = (event) => {
if (event.isComposing) {
return;
}
if (event.key === "Tab") {
input.focus();
input.select();
event.preventDefault();
return;
}
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
};
genUp(range, tableElement, vditor);
genDown(range, tableElement, vditor);
genClose(tableElement, vditor);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", left);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", center);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", right);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", insertRowBElement);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", insertRowElement);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", insertColumnBElement);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", insertColumnElement);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", deleteRowElement);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", deleteColumnElement);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
vditor.wysiwyg.popover.insertAdjacentHTML("beforeend", " x ");
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input2Wrap);
setPopoverPosition(vditor, tableElement);
}
// link ref popover
const linkRefElement = hasClosestByAttribute(typeElement, "data-type", "link-ref");
if (linkRefElement) {
genLinkRefPopover(vditor, linkRefElement, range);
}
// footnote popover
const footnotesRefElement = hasClosestByAttribute(typeElement, "data-type", "footnotes-ref");
if (footnotesRefElement) {
const lang: keyof II18n | "" = vditor.options.lang;
const options: IOptions = vditor.options;
vditor.wysiwyg.popover.innerHTML = "";
const inputWrap = document.createElement("span");
inputWrap.setAttribute("aria-label", window.VditorI18n.footnoteRef + "<" + updateHotkeyTip("⌥Enter") + ">");
inputWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input = document.createElement("input");
inputWrap.appendChild(input);
input.className = "vditor-input";
input.setAttribute("placeholder", window.VditorI18n.footnoteRef + "<" + updateHotkeyTip("⌥Enter") + ">");
input.style.width = "120px";
input.value = footnotesRefElement.getAttribute("data-footnotes-label");
input.oninput = () => {
if (input.value.trim() !== "") {
footnotesRefElement.setAttribute("data-footnotes-label", input.value);
}
if (typeof vditor.options.input === "function") {
vditor.options.input(getMarkdown(vditor));
}
};
input.onkeydown = (event) => {
if (event.isComposing) {
return;
}
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
};
genClose(footnotesRefElement, vditor);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
setPopoverPosition(vditor, footnotesRefElement);
}
// block popover: math-inline, math-block, html-block, html-inline, code-block, html-entity
let blockRenderElement = hasClosestByClassName(typeElement, "vditor-wysiwyg__block") as HTMLElement;
const isBlock = blockRenderElement ? blockRenderElement.getAttribute("data-type").indexOf("block") > -1 : false;
vditor.wysiwyg.element
.querySelectorAll(".vditor-wysiwyg__preview")
.forEach((itemElement) => {
if (!blockRenderElement || (blockRenderElement && isBlock && !blockRenderElement.contains(itemElement))) {
const previousElement = itemElement.previousElementSibling as HTMLElement;
previousElement.style.display = "none";
}
});
if (blockRenderElement && isBlock) {
vditor.wysiwyg.popover.innerHTML = "";
genUp(range, blockRenderElement, vditor);
genDown(range, blockRenderElement, vditor);
genClose(blockRenderElement, vditor);
if (blockRenderElement.getAttribute("data-type") === "code-block") {
const languageWrap = document.createElement("span");
languageWrap.setAttribute("aria-label", window.VditorI18n.language + "<" + updateHotkeyTip("⌥Enter") + ">");
languageWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const language = document.createElement("input");
languageWrap.appendChild(language);
const codeElement =
blockRenderElement.firstElementChild.firstElementChild;
language.className = "vditor-input";
language.setAttribute("placeholder",
window.VditorI18n.language + "<" + updateHotkeyTip("⌥Enter") + ">");
language.value =
codeElement.className.indexOf("language-") > -1
? codeElement.className.split("-")[1].split(" ")[0]
: "";
language.oninput = (e: InputEvent) => {
if (language.value.trim() !== "") {
codeElement.className = `language-${language.value}`;
} else {
codeElement.className = "";
vditor.hint.recentLanguage = "";
}
if (blockRenderElement.lastElementChild.classList.contains("vditor-wysiwyg__preview")) {
blockRenderElement.lastElementChild.innerHTML =
blockRenderElement.firstElementChild.innerHTML;
processCodeRender(blockRenderElement.lastElementChild as HTMLElement, vditor);
}
afterRenderEvent(vditor);
// 当鼠标点选语言时,触发自定义input事件
if (e.detail === 1) {
// 选择语言后,输入焦点切换到代码输入框
range.setStart(codeElement.firstChild, 0);
range.collapse(true);
setSelectionFocus(range);
}
};
language.onkeydown = (event: KeyboardEvent) => {
if (event.isComposing) {
return;
}
if (removeBlockElement(vditor, event)) {
return;
}
if (
event.key === "Escape" &&
vditor.hint.element.style.display === "block"
) {
vditor.hint.element.style.display = "none";
event.preventDefault();
return;
}
vditor.hint.select(event, vditor);
focusToElement(event, range);
};
language.onkeyup = (event: KeyboardEvent) => {
if (
event.isComposing ||
event.key === "Enter" ||
event.key === "ArrowUp" ||
event.key === "Escape" ||
event.key === "ArrowDown"
) {
return;
}
const matchLangData: IHintData[] = [];
const key = language.value.substring(0, language.selectionStart);
(vditor.options.preview.hljs.langs || Constants.ALIAS_CODE_LANGUAGES.concat((window.hljs?.listLanguages() ?? []).sort())).forEach((keyName) => {
if (keyName.indexOf(key.toLowerCase()) > -1) {
matchLangData.push({
html: keyName,
value: keyName,
});
}
});
vditor.hint.genHTML(matchLangData, key, vditor);
event.preventDefault();
};
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", languageWrap);
}
setPopoverPosition(vditor, blockRenderElement);
} else {
blockRenderElement = undefined;
}
if (headingElement) {
vditor.wysiwyg.popover.innerHTML = "";
const inputWrap = document.createElement("span");
inputWrap.setAttribute("aria-label", "ID" + "<" + updateHotkeyTip("⌥Enter") + ">");
inputWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input = document.createElement("input");
inputWrap.appendChild(input);
input.className = "vditor-input";
input.setAttribute("placeholder", "ID" + "<" + updateHotkeyTip("⌥Enter") + ">");
input.style.width = "120px";
input.value = headingElement.getAttribute("data-id") || "";
input.oninput = () => {
headingElement.setAttribute("data-id", input.value);
if (typeof vditor.options.input === "function") {
vditor.options.input(getMarkdown(vditor));
}
};
input.onkeydown = (event) => {
if (event.isComposing) {
return;
}
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
};
genUp(range, headingElement, vditor);
genDown(range, headingElement, vditor);
genClose(headingElement, vditor);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
setPopoverPosition(vditor, headingElement);
}
// a popover
if (aElement) {
genAPopover(vditor, aElement, range);
}
if (
!blockquoteElement &&
!liElement &&
!tableElement &&
!blockRenderElement &&
!aElement &&
!linkRefElement &&
!footnotesRefElement &&
!headingElement &&
!tocElement
) {
const blockElement = hasClosestByAttribute(typeElement, "data-block", "0");
if (
blockElement &&
blockElement.parentElement.isEqualNode(vditor.wysiwyg.element)
) {
vditor.wysiwyg.popover.innerHTML = "";
genUp(range, blockElement, vditor);
genDown(range, blockElement, vditor);
genClose(blockElement, vditor);
setPopoverPosition(vditor, blockElement);
} else {
vditor.wysiwyg.popover.style.display = "none";
}
}
// 反斜杠特殊处理
vditor.wysiwyg.element
.querySelectorAll('span[data-type="backslash"] > span')
.forEach((item: HTMLElement) => {
item.style.display = "none";
});
const backslashElement = hasClosestByAttribute(range.startContainer, "data-type", "backslash");
if (backslashElement) {
backslashElement.querySelector("span").style.display = "inline";
}
}, 200);
};
const setPopoverPosition = (vditor: IVditor, element: HTMLElement) => {
let targetElement = element;
const tableElement = hasClosestByMatchTag(element, "TABLE");
if (tableElement) {
targetElement = tableElement;
}
vditor.wysiwyg.popover.style.left = "0";
vditor.wysiwyg.popover.style.display = "block";
vditor.wysiwyg.popover.style.top =
Math.max(-8, targetElement.offsetTop - 21 - vditor.wysiwyg.element.scrollTop) + "px";
vditor.wysiwyg.popover.style.left =
Math.min(targetElement.offsetLeft, vditor.wysiwyg.element.clientWidth - vditor.wysiwyg.popover.clientWidth) + "px";
vditor.wysiwyg.popover.setAttribute("data-top", (targetElement.offsetTop - 21).toString());
};
export const genLinkRefPopover = (vditor: IVditor, linkRefElement: HTMLElement, range = getSelection().getRangeAt(0)) => {
vditor.wysiwyg.popover.innerHTML = "";
const updateLinkRef = () => {
if (input.value.trim() !== "") {
if (linkRefElement.tagName === "IMG") {
linkRefElement.setAttribute("alt", input.value);
} else {
linkRefElement.textContent = input.value;
}
}
// data-link-label
if (input1.value.trim() !== "") {
linkRefElement.setAttribute("data-link-label", input1.value);
}
if (typeof vditor.options.input === "function") {
vditor.options.input(getMarkdown(vditor));
}
};
const inputWrap = document.createElement("span");
inputWrap.setAttribute("aria-label", window.VditorI18n.textIsNotEmpty);
inputWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input = document.createElement("input");
inputWrap.appendChild(input);
input.className = "vditor-input";
input.setAttribute("placeholder", window.VditorI18n.textIsNotEmpty);
input.style.width = "120px";
input.value =
linkRefElement.getAttribute("alt") || linkRefElement.textContent;
input.oninput = () => {
updateLinkRef();
};
input.onkeydown = (event) => {
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
linkHotkey(vditor, linkRefElement, event, input1);
};
const input1Wrap = document.createElement("span");
input1Wrap.setAttribute("aria-label", window.VditorI18n.linkRef);
input1Wrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input1 = document.createElement("input");
input1Wrap.appendChild(input1);
input1.className = "vditor-input";
input1.setAttribute("placeholder", window.VditorI18n.linkRef);
input1.value = linkRefElement.getAttribute("data-link-label");
input1.oninput = () => {
updateLinkRef();
};
input1.onkeydown = (event) => {
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
linkHotkey(vditor, linkRefElement, event, input);
};
genClose(linkRefElement, vditor);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input1Wrap);
setPopoverPosition(vditor, linkRefElement);
};
const genUp = (range: Range, element: HTMLElement, vditor: IVditor) => {
const previousElement = element.previousElementSibling;
if (
!previousElement ||
(!element.parentElement.isEqualNode(vditor.wysiwyg.element) &&
element.tagName !== "LI")
) {
return;
}
const upElement = document.createElement("button");
upElement.setAttribute("type", "button");
upElement.setAttribute("data-type", "up");
upElement.setAttribute("aria-label", window.VditorI18n.up + "<" + updateHotkeyTip("⇧⌘U") + ">");
upElement.innerHTML = '<svg><use xlink:href="#vditor-icon-up"></use></svg>';
upElement.className = "vditor-icon vditor-tooltipped vditor-tooltipped__n";
upElement.onclick = () => {
range.insertNode(document.createElement("wbr"));
previousElement.insertAdjacentElement("beforebegin", element);
setRangeByWbr(vditor.wysiwyg.element, range);
afterRenderEvent(vditor);
highlightToolbarWYSIWYG(vditor);
scrollCenter(vditor);
};
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", upElement);
};
const genDown = (range: Range, element: HTMLElement, vditor: IVditor) => {
const nextElement = element.nextElementSibling;
if (
!nextElement ||
(!element.parentElement.isEqualNode(vditor.wysiwyg.element) &&
element.tagName !== "LI")
) {
return;
}
const downElement = document.createElement("button");
downElement.setAttribute("type", "button");
downElement.setAttribute("data-type", "down");
downElement.setAttribute("aria-label", window.VditorI18n.down + "<" + updateHotkeyTip("⇧⌘D") + ">");
downElement.innerHTML =
'<svg><use xlink:href="#vditor-icon-down"></use></svg>';
downElement.className =
"vditor-icon vditor-tooltipped vditor-tooltipped__n";
downElement.onclick = () => {
range.insertNode(document.createElement("wbr"));
nextElement.insertAdjacentElement("afterend", element);
setRangeByWbr(vditor.wysiwyg.element, range);
afterRenderEvent(vditor);
highlightToolbarWYSIWYG(vditor);
scrollCenter(vditor);
};
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", downElement);
};
const genClose = (element: HTMLElement, vditor: IVditor) => {
const close = document.createElement("button");
close.setAttribute("type", "button");
close.setAttribute("data-type", "remove");
close.setAttribute("aria-label", window.VditorI18n.remove + "<" + updateHotkeyTip("⇧⌘X") + ">");
close.innerHTML =
'<svg><use xlink:href="#vditor-icon-trashcan"></use></svg>';
close.className = "vditor-icon vditor-tooltipped vditor-tooltipped__n";
close.onclick = () => {
const range = getEditorRange(vditor);
range.setStartAfter(element);
setSelectionFocus(range);
element.remove();
afterRenderEvent(vditor);
highlightToolbarWYSIWYG(vditor);
if (["H1", "H2", "H3", "H4", "H5", "H6"].includes(element.tagName)) {
renderToc(vditor);
}
};
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", close);
};
const linkHotkey = (
vditor: IVditor,
element: HTMLElement,
event: KeyboardEvent,
nextInputElement: HTMLInputElement,
) => {
if (event.isComposing) {
return;
}
if (event.key === "Tab") {
nextInputElement.focus();
nextInputElement.select();
event.preventDefault();
return;
}
if (
!isCtrl(event) &&
!event.shiftKey &&
event.altKey &&
event.key === "Enter"
) {
const range = getEditorRange(vditor);
// firefox 不会打断 link https://github.com/Vanessa219/vditor/issues/193
element.insertAdjacentHTML("afterend", Constants.ZWSP);
range.setStartAfter(element.nextSibling);
range.collapse(true);
setSelectionFocus(range);
event.preventDefault();
}
};
export const genAPopover = (vditor: IVditor, aElement: HTMLElement, range: Range) => {
vditor.wysiwyg.popover.innerHTML = "";
const updateA = () => {
if (input.value.trim() !== "") {
aElement.innerHTML = input.value;
}
aElement.setAttribute("href", input1.value);
aElement.setAttribute("title", input2.value);
afterRenderEvent(vditor);
};
aElement.querySelectorAll("[data-marker]").forEach((item: HTMLElement) => {
item.removeAttribute("data-marker");
});
const inputWrap = document.createElement("span");
inputWrap.setAttribute("aria-label", window.VditorI18n.textIsNotEmpty);
inputWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input = document.createElement("input");
inputWrap.appendChild(input);
input.className = "vditor-input";
input.setAttribute("placeholder", window.VditorI18n.textIsNotEmpty);
input.style.width = "120px";
input.value = aElement.innerHTML || "";
input.oninput = () => {
updateA();
};
input.onkeydown = (event) => {
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
linkHotkey(vditor, aElement, event, input1);
};
const input1Wrap = document.createElement("span");
input1Wrap.setAttribute("aria-label", window.VditorI18n.link);
input1Wrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input1 = document.createElement("input");
input1Wrap.appendChild(input1);
input1.className = "vditor-input";
input1.setAttribute("placeholder", window.VditorI18n.link);
input1.value = aElement.getAttribute("href") || "";
input1.oninput = () => {
updateA();
};
input1.onkeydown = (event) => {
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
linkHotkey(vditor, aElement, event, input2);
};
const input2Wrap = document.createElement("span");
input2Wrap.setAttribute("aria-label", window.VditorI18n.tooltipText);
input2Wrap.className = "vditor-tooltipped vditor-tooltipped__n";
const input2 = document.createElement("input");
input2Wrap.appendChild(input2);
input2.className = "vditor-input";
input2.setAttribute("placeholder", window.VditorI18n.tooltipText);
input2.style.width = "60px";
input2.value = aElement.getAttribute("title") || "";
input2.oninput = () => {
updateA();
};
input2.onkeydown = (event) => {
if (removeBlockElement(vditor, event)) {
return;
}
if (focusToElement(event, range)) {
return;
}
linkHotkey(vditor, aElement, event, input);
};
genClose(aElement, vditor);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input1Wrap);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", input2Wrap);
setPopoverPosition(vditor, aElement);
};
export const genImagePopover = (event: Event, vditor: IVditor) => {
const imgElement = event.target as HTMLImageElement;
vditor.wysiwyg.popover.innerHTML = "";
const updateImg = () => {
imgElement.setAttribute("src", inputElement.value);
imgElement.setAttribute("alt", alt.value);
imgElement.setAttribute("title", title.value);
if (typeof vditor.options.input === "function") {
vditor.options.input(getMarkdown(vditor));
}
};
const inputWrap = document.createElement("span");
inputWrap.setAttribute("aria-label", window.VditorI18n.imageURL);
inputWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const inputElement = document.createElement("input");
inputWrap.appendChild(inputElement);
inputElement.className = "vditor-input";
inputElement.setAttribute("placeholder", window.VditorI18n.imageURL);
inputElement.value = imgElement.getAttribute("src") || "";
inputElement.oninput = () => {
updateImg();
};
inputElement.onkeydown = (elementEvent) => {
removeBlockElement(vditor, elementEvent);
};
const altWrap = document.createElement("span");
altWrap.setAttribute("aria-label", window.VditorI18n.alternateText);
altWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const alt = document.createElement("input");
altWrap.appendChild(alt);
alt.className = "vditor-input";
alt.setAttribute("placeholder", window.VditorI18n.alternateText);
alt.style.width = "52px";
alt.value = imgElement.getAttribute("alt") || "";
alt.oninput = () => {
updateImg();
};
alt.onkeydown = (elementEvent) => {
removeBlockElement(vditor, elementEvent);
};
const titleWrap = document.createElement("span");
titleWrap.setAttribute("aria-label", window.VditorI18n.title);
titleWrap.className = "vditor-tooltipped vditor-tooltipped__n";
const title = document.createElement("input");
titleWrap.appendChild(title);
title.className = "vditor-input";
title.setAttribute("placeholder", window.VditorI18n.title);
title.value = imgElement.getAttribute("title") || "";
title.oninput = () => {
updateImg();
};
title.onkeydown = (elementEvent) => {
removeBlockElement(vditor, elementEvent);
};
genClose(imgElement, vditor);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", inputWrap);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", altWrap);
vditor.wysiwyg.popover.insertAdjacentElement("beforeend", titleWrap);
setPopoverPosition(vditor, imgElement);
};
const focusToElement = (event: KeyboardEvent, range: Range) => {
if ((!isCtrl(event) && !event.shiftKey && event.key === "Enter") || event.key === "Escape") {
if (range) {
setSelectionFocus(range);
}
event.preventDefault();
event.stopPropagation();
return true;
}
};