siyuan/app/src/protyle/toolbar/index.ts

1752 lines
91 KiB
TypeScript
Raw Normal View History

import {Divider} from "./Divider";
import {Font, hasSameTextStyle, setFontStyle} from "./Font";
import {ToolbarItem} from "./ToolbarItem";
import {
fixTableRange, focusBlock,
focusByRange,
focusByWbr,
getEditorRange,
getSelectionPosition, selectAll,
setFirstNodeRange,
setLastNodeRange
} from "../util/selection";
import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../util/hasClosest";
import {Link} from "./Link";
import {setPosition} from "../../util/setPosition";
import {updateTransaction} from "../wysiwyg/transaction";
import {Constants} from "../../constants";
import {copyPlainText, openByMobile, readText, setStorageVal} from "../util/compatibility";
import {upDownHint} from "../../util/upDownHint";
import {highlightRender} from "../render/highlightRender";
import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock";
import {processRender} from "../util/processCode";
import {BlockRef} from "./BlockRef";
2022-11-25 23:09:16 +08:00
import {hintRenderAssets, hintRenderTemplate, hintRenderWidget} from "../hint/extend";
import {blockRender} from "../render/blockRender";
/// #if !BROWSER
import {openBy} from "../../editor/util";
/// #endif
/// #if !MOBILE
import {moveResize} from "../../dialog/moveResize";
/// #endif
import {fetchPost} from "../../util/fetch";
2022-12-11 00:32:40 +08:00
import {isArrayEqual, isMobile} from "../../util/functions";
import * as dayjs from "dayjs";
import {insertEmptyBlock} from "../../block/util";
import {matchHotKey} from "../util/hotKey";
import {hideElements} from "../ui/hideElements";
import {renderAssetsPreview} from "../../asset/renderAssets";
import {electronUndo} from "../undo";
import {previewTemplate} from "./util";
import {hideMessage, showMessage} from "../../dialog/message";
import {InlineMath} from "./InlineMath";
import {InlineMemo} from "./InlineMemo";
import {mathRender} from "../render/mathRender";
import {linkMenu} from "../../menus/protyle";
import {addScript} from "../util/addScript";
import {confirmDialog} from "../../dialog/confirmDialog";
import {pasteAsPlainText, pasteEscaped, pasteText} from "../util/paste";
export class Toolbar {
public element: HTMLElement;
public subElement: HTMLElement;
public subElementCloseCB: () => void;
public range: Range;
private toolbarHeight: number;
2023-06-01 20:50:49 +08:00
constructor(protyle: IProtyle) {
const options = protyle.options;
const element = document.createElement("div");
element.className = "protyle-toolbar fn__none";
this.element = element;
this.subElement = document.createElement("div");
/// #if MOBILE
this.subElement.className = "protyle-util fn__none protyle-util--mobile";
/// #else
this.subElement.className = "protyle-util fn__none";
/// #endif
this.toolbarHeight = 29;
options.toolbar.forEach((menuItem: IMenuItem) => {
const itemElement = this.genItem(protyle, menuItem);
this.element.appendChild(itemElement);
});
}
public render(protyle: IProtyle, range: Range, event?: KeyboardEvent) {
this.range = range;
let nodeElement = hasClosestBlock(range.startContainer);
if (isMobile() || !nodeElement || protyle.disabled || nodeElement.classList.contains("av")) {
this.element.classList.add("fn__none");
return;
}
// https://github.com/siyuan-note/siyuan/issues/5157
let hasImg = true;
let noText = true;
Array.from(range.cloneContents().childNodes).find(item => {
if (item.nodeType !== 1) {
if (item.textContent.length > 0) {
2022-06-11 00:51:42 +08:00
noText = false;
return true;
}
} else if (!(item as HTMLElement).classList.contains("img")) {
2022-06-11 00:51:42 +08:00
hasImg = false;
return true;
}
2022-06-11 00:51:42 +08:00
});
if ((hasImg && noText) ||
// 拖拽图片到最右侧
(range.commonAncestorContainer.nodeType !== 3 && (range.commonAncestorContainer as HTMLElement).classList.contains("img"))) {
this.element.classList.add("fn__none");
return;
}
// shift+方向键或三击选中,不同的块 https://github.com/siyuan-note/siyuan/issues/3891
const startElement = hasClosestBlock(range.startContainer);
const endElement = hasClosestBlock(range.endContainer);
if (startElement && endElement && !startElement.isSameNode(endElement)) {
if (event) { // 在 keyup 中使用 shift+方向键选中
if (event.key === "ArrowLeft") {
this.range = setLastNodeRange(getContenteditableElement(startElement), range, false);
} else if (event.key === "ArrowRight") {
this.range = setFirstNodeRange(getContenteditableElement(endElement), range);
this.range.collapse(false);
} else if (event.key === "ArrowUp") {
this.range = setFirstNodeRange(getContenteditableElement(endElement), range);
2022-11-08 19:25:07 +08:00
nodeElement = hasClosestBlock(endElement);
if (!nodeElement) {
return;
}
} else if (event.key === "ArrowDown") {
this.range = setLastNodeRange(getContenteditableElement(startElement), range, false);
}
} else {
this.range = setLastNodeRange(getContenteditableElement(nodeElement), range, false);
}
focusByRange(this.range);
if (this.range.toString() === "") {
this.element.classList.add("fn__none");
return;
}
}
// 需放在 range 修改之后,否则 https://github.com/siyuan-note/siyuan/issues/4726
if (nodeElement.getAttribute("data-type") === "NodeCodeBlock") {
this.element.classList.add("fn__none");
return;
}
const rangePosition = getSelectionPosition(nodeElement, range);
this.element.classList.remove("fn__none");
2022-08-06 18:01:20 +08:00
const y = rangePosition.top - this.toolbarHeight - 4;
this.element.setAttribute("data-inity", y + Constants.ZWSP + protyle.contentElement.scrollTop.toString());
setPosition(this.element, rangePosition.left - 52, y);
this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => {
item.classList.remove("protyle-toolbar__item--current");
});
const types = this.getCurrentType();
types.forEach(item => {
2022-09-26 22:02:05 +08:00
if (["search-mark", "a", "block-ref", "virtual-block-ref", "text", "file-annotation-ref", "inline-math",
"inline-memo", "", "backslash"].includes(item)) {
return;
}
2022-09-26 22:02:05 +08:00
const itemElement = this.element.querySelector(`[data-type="${item}"]`);
if (itemElement) {
itemElement.classList.add("protyle-toolbar__item--current");
}
});
}
public getCurrentType(range = this.range) {
let types: string[] = [];
let startElement = range.startContainer as HTMLElement;
if (startElement.nodeType === 3) {
startElement = startElement.parentElement;
} else if (startElement.childElementCount > 0 && startElement.childNodes[range.startOffset]?.nodeType !== 3) {
startElement = startElement.childNodes[range.startOffset] as HTMLElement;
}
if (!startElement || startElement.nodeType === 3) {
return [];
}
if (!["DIV", "TD", "TH", "TR"].includes(startElement.tagName)) {
types = (startElement.getAttribute("data-type") || "").split(" ");
}
let endElement = range.endContainer as HTMLElement;
if (endElement.nodeType === 3) {
endElement = endElement.parentElement;
} else if (endElement.childElementCount > 0 && endElement.childNodes[range.endOffset]?.nodeType !== 3) {
endElement = endElement.childNodes[range.endOffset] as HTMLElement;
}
if (!endElement || endElement.nodeType === 3) {
return [];
}
if (!["DIV", "TD", "TH", "TR"].includes(endElement.tagName) && !startElement.isSameNode(endElement)) {
types = types.concat((endElement.getAttribute("data-type") || "").split(" "));
}
range.cloneContents().childNodes.forEach((item: HTMLElement) => {
if (item.nodeType !== 3) {
types = types.concat((item.getAttribute("data-type") || "").split(" "));
}
});
types = [...new Set(types)];
types.find((item, index) => {
if (item === "") {
types.splice(index, 1);
return true;
}
});
return types;
}
private genItem(protyle: IProtyle, menuItem: IMenuItem) {
let menuItemObj;
switch (menuItem.name) {
case "strong":
case "em":
case "s":
case "code":
case "mark":
case "tag":
case "u":
case "sup":
case "clear":
case "sub":
case "kbd":
menuItemObj = new ToolbarItem(protyle, menuItem);
break;
case "block-ref":
menuItemObj = new BlockRef(protyle, menuItem);
break;
case "inline-math":
menuItemObj = new InlineMath(protyle, menuItem);
break;
case "inline-memo":
menuItemObj = new InlineMemo(protyle, menuItem);
break;
case "|":
menuItemObj = new Divider();
break;
case "text":
menuItemObj = new Font(protyle, menuItem);
break;
case "a":
2023-06-01 20:50:49 +08:00
menuItemObj = new Link(protyle, menuItem);
break;
}
if (!menuItemObj) {
return;
}
return menuItemObj.element;
}
// 合并多个 text 为一个 text
private mergeNode(nodes: NodeListOf<ChildNode>) {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType !== 3 && (nodes[i] as HTMLElement).tagName === "WBR") {
nodes[i].remove();
i--;
}
}
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType === 3) {
if (nodes[i].textContent === "") {
nodes[i].remove();
i--;
} else if (nodes[i + 1] && nodes[i + 1].nodeType === 3) {
nodes[i].textContent = nodes[i].textContent + nodes[i + 1].textContent;
nodes[i + 1].remove();
i--;
}
}
}
}
public setInlineMark(protyle: IProtyle, type: string, action: "range" | "toolbar", textObj?: ITextOption) {
const nodeElement = hasClosestBlock(this.range.startContainer);
if (!nodeElement) {
return;
}
const endElement = hasClosestBlock(this.range.endContainer);
if (!endElement) {
return;
}
// 三击后还没有重新纠正 range 时使用快捷键标记会导致异常 https://github.com/siyuan-note/siyuan/issues/7068
if (!nodeElement.isSameNode(endElement)) {
this.range = setLastNodeRange(getContenteditableElement(nodeElement), this.range, false);
}
const rangeTypes = this.getCurrentType(this.range);
const selectText = this.range.toString();
2022-09-19 16:47:38 +08:00
fixTableRange(this.range);
let previousElement: HTMLElement;
let nextElement: HTMLElement;
2022-09-14 11:18:44 +08:00
let previousIndex: number;
let nextIndex: number;
const previousSibling = hasPreviousSibling(this.range.startContainer);
if (!["DIV", "TD", "TH", "TR"].includes(this.range.startContainer.parentElement.tagName)) {
if (this.range.startOffset === 0 && !previousSibling) {
previousElement = this.range.startContainer.parentElement.previousSibling as HTMLElement;
2022-09-14 11:18:44 +08:00
this.range.setStartBefore(this.range.startContainer.parentElement);
} else {
2022-09-14 11:18:44 +08:00
previousElement = this.range.startContainer.parentElement;
}
} else if (previousSibling && previousSibling.nodeType !== 3 && this.range.startOffset === 0) {
// **aaa**bbb 选中 bbb 加粗
previousElement = previousSibling as HTMLElement;
}
let isEndSpan = false;
2022-09-14 11:18:44 +08:00
const nextSibling = hasNextSibling(this.range.endContainer);
if (!["DIV", "TD", "TH", "TR"].includes(this.range.endContainer.parentElement.tagName)) {
if (this.range.endOffset === this.range.endContainer.textContent.length && !nextSibling) {
nextElement = this.range.endContainer.parentElement.nextSibling as HTMLElement;
2022-09-14 11:18:44 +08:00
this.range.setEndAfter(this.range.endContainer.parentElement);
if (selectText === "") {
isEndSpan = true;
this.range.collapse(false);
}
} else {
2022-09-14 11:18:44 +08:00
nextElement = this.range.endContainer.parentElement;
}
} else if (nextSibling && nextSibling.nodeType !== 3 && this.range.endOffset === this.range.endContainer.textContent.length) {
// aaa**bbb** 选中 aaa 加粗
nextElement = nextSibling as HTMLElement;
}
this.range.insertNode(document.createElement("wbr"));
const html = nodeElement.outerHTML;
const contents = this.range.extractContents();
this.mergeNode(contents.childNodes);
// 选择 span 中的一部分需进行包裹
if (previousElement && nextElement && previousElement.isSameNode(nextElement) && contents.firstChild?.nodeType === 3) {
const attributes = previousElement.attributes;
contents.childNodes.forEach(item => {
const spanElement = document.createElement("span");
for (let i = 0; i < attributes.length; i++) {
spanElement.setAttribute(attributes[i].name, attributes[i].value);
}
spanElement.innerHTML = item.textContent;
item.replaceWith(spanElement);
});
}
2023-03-06 14:02:35 +08:00
const toolbarElement = isMobile() ? document.querySelector("#keyboardToolbar .keyboard__dynamic").nextElementSibling : this.element;
2023-03-06 13:42:50 +08:00
const actionBtn = action === "toolbar" ? toolbarElement.querySelector(`[data-type="${type}"]`) : undefined;
const newNodes: Node[] = [];
if (type === "clear" || actionBtn?.classList.contains("protyle-toolbar__item--current") || (
action === "range" && rangeTypes.length > 0 && rangeTypes.includes(type) && !textObj
)) {
// 移除
if (type === "clear") {
2023-03-06 13:42:50 +08:00
toolbarElement.querySelectorAll('[data-type="em"],[data-type="u"],[data-type="s"],[data-type="mark"],[data-type="sup"],[data-type="sub"],[data-type="strong"]').forEach(item => {
item.classList.remove("protyle-toolbar__item--current");
});
} else if (actionBtn) {
actionBtn.classList.remove("protyle-toolbar__item--current");
}
if (contents.childNodes.length === 0) {
rangeTypes.find((itemType, index) => {
if (type === itemType) {
rangeTypes.splice(index, 1);
return true;
}
});
if (rangeTypes.length === 0 || type === "clear") {
newNodes.push(document.createTextNode(Constants.ZWSP));
} else {
// 遇到以下类型结尾不应继承 https://github.com/siyuan-note/siyuan/issues/7200
let removeIndex = 0;
while (removeIndex < rangeTypes.length) {
if (["inline-memo", "text", "block-ref", "file-annotation-ref", "a"].includes(rangeTypes[removeIndex])) {
rangeTypes.splice(removeIndex, 1);
} else {
++removeIndex;
}
}
const inlineElement = document.createElement("span");
inlineElement.setAttribute("data-type", rangeTypes.join(" "));
inlineElement.textContent = Constants.ZWSP;
newNodes.push(inlineElement);
}
}
contents.childNodes.forEach((item: HTMLElement, index) => {
if (item.nodeType !== 3 && item.tagName !== "BR" && item.tagName !== "IMG") {
const types = (item.getAttribute("data-type") || "").split(" ");
if (type === "clear") {
for (let i = 0; i < types.length; i++) {
if (textObj && textObj.type === "text") {
if ("text" === types[i]) {
types.splice(i, 1);
i--;
}
} else {
if (["kbd", "text", "strong", "em", "u", "s", "mark", "sup", "sub", "code"].includes(types[i])) {
types.splice(i, 1);
i--;
}
}
}
} else {
types.find((itemType, typeIndex) => {
if (type === itemType) {
types.splice(typeIndex, 1);
return true;
}
});
}
if (types.length === 0) {
if (item.textContent === "") {
item.textContent = Constants.ZWSP;
}
newNodes.push(document.createTextNode(item.textContent));
} else {
if (selectText && type === "clear" && textObj && textObj.type === "text") {
// 选中内容中没有样式需要清除时直接返回,否则清除粗体中部分内容会报错
if (item.style.color === "" && item.style.webkitTextFillColor === "" && item.style.webkitTextStroke === "" && item.style.textShadow === "" && item.style.backgroundColor === "" && item.style.fontSize === "") {
item.setAttribute("data-type", types.join(" "));
newNodes.push(item);
return true;
}
}
if (type === "clear") {
item.style.color = "";
item.style.webkitTextFillColor = "";
item.style.webkitTextStroke = "";
item.style.textShadow = "";
item.style.backgroundColor = "";
item.style.fontSize = "";
}
if (index === 0 && previousElement && previousElement.nodeType !== 3 &&
isArrayEqual(types, (previousElement.getAttribute("data-type") || "").split(" ")) &&
hasSameTextStyle(item, previousElement, textObj)) {
previousIndex = previousElement.textContent.length;
previousElement.innerHTML = previousElement.innerHTML + item.innerHTML;
} else if (index === contents.childNodes.length - 1 && nextElement && nextElement.nodeType !== 3 &&
isArrayEqual(types, (nextElement.getAttribute("data-type") || "").split(" ")) &&
hasSameTextStyle(item, nextElement, textObj)) {
nextIndex = item.textContent.length;
nextElement.innerHTML = item.innerHTML + nextElement.innerHTML;
} else {
item.setAttribute("data-type", types.join(" "));
newNodes.push(item);
}
}
} else {
newNodes.push(item);
}
});
} else {
// 添加
2023-03-06 13:42:50 +08:00
if (!this.element.classList.contains("fn__none") && type !== "text" && actionBtn) {
actionBtn.classList.add("protyle-toolbar__item--current");
}
if (selectText === "") {
const inlineElement = document.createElement("span");
2022-09-16 10:13:01 +08:00
rangeTypes.push(type);
// 遇到以下类型结尾不应继承 https://github.com/siyuan-note/siyuan/issues/7200
if (isEndSpan) {
let removeIndex = 0;
while (removeIndex < rangeTypes.length) {
if (["inline-memo", "text", "block-ref", "file-annotation-ref", "a"].includes(rangeTypes[removeIndex])) {
rangeTypes.splice(removeIndex, 1);
} else {
++removeIndex;
}
}
}
inlineElement.setAttribute("data-type", [...new Set(rangeTypes)].join(" "));
inlineElement.textContent = Constants.ZWSP;
setFontStyle(inlineElement, textObj);
newNodes.push(inlineElement);
} else {
// https://github.com/siyuan-note/siyuan/issues/7477
// https://github.com/siyuan-note/siyuan/issues/8825
if (type === "block-ref") {
while (contents.childNodes.length > 1) {
contents.childNodes[0].remove();
}
}
contents.childNodes.forEach((item: HTMLElement, index) => {
if (item.nodeType === 3) {
if (index === 0 && previousElement && previousElement.nodeType !== 3 &&
type === previousElement.getAttribute("data-type") &&
hasSameTextStyle(item, previousElement, textObj)) {
previousIndex = previousElement.textContent.length;
previousElement.innerHTML = previousElement.innerHTML + item.textContent;
} else if (index === contents.childNodes.length - 1 && nextElement && nextElement.nodeType !== 3 &&
type === nextElement.getAttribute("data-type") &&
hasSameTextStyle(item, nextElement, textObj)) {
nextIndex = item.textContent.length;
nextElement.innerHTML = item.textContent + nextElement.innerHTML;
} else {
const inlineElement = document.createElement("span");
inlineElement.setAttribute("data-type", type);
inlineElement.textContent = item.textContent;
setFontStyle(inlineElement, textObj);
newNodes.push(inlineElement);
}
} else {
let types = (item.getAttribute("data-type") || "").split(" ");
for (let i = 0; i < types.length; i++) {
// "backslash", "virtual-block-ref", "search-mark" 只能单独存在
if (["backslash", "virtual-block-ref", "search-mark"].includes(types[i])) {
types.splice(i, 1);
i--;
}
}
types.push(type);
// 上标和下标不能同时存在 https://github.com/siyuan-note/insider/issues/1049
if (type === "sub" && types.includes("sup")) {
types.find((item, index) => {
if (item === "sup") {
2022-09-19 15:33:30 +08:00
types.splice(index, 1);
2023-03-06 13:42:50 +08:00
toolbarElement.querySelector('[data-type="sup"]').classList.remove("protyle-toolbar__item--current");
return true;
}
2022-09-19 15:33:30 +08:00
});
} else if (type === "sup" && types.includes("sub")) {
types.find((item, index) => {
if (item === "sub") {
types.splice(index, 1);
2023-03-06 13:42:50 +08:00
toolbarElement.querySelector('[data-type="sub"]').classList.remove("protyle-toolbar__item--current");
return true;
}
2022-09-19 15:33:30 +08:00
});
} else if (type === "block-ref" && (types.includes("a") || types.includes("file-annotation-ref"))) {
// 虚拟引用和链接/标注不能同时存在
types.find((item, index) => {
if (item === "a" || item === "file-annotation-ref") {
types.splice(index, 1);
return true;
}
});
} else if (type === "a" && (types.includes("block-ref") || types.includes("file-annotation-ref"))) {
// 链接和引用/标注不能同时存在
types.find((item, index) => {
if (item === "block-ref" || item === "file-annotation-ref") {
types.splice(index, 1);
return true;
}
});
} else if (type === "file-annotation-ref" && (types.includes("block-ref") || types.includes("a"))) {
// 引用和链接/标注不能同时存在
types.find((item, index) => {
if (item === "block-ref" || item === "a") {
types.splice(index, 1);
return true;
}
});
} else if (type === "inline-memo" && types.includes("inline-math")) {
// 数学公式和备注不能同时存在
types.find((item, index) => {
if (item === "inline-math") {
types.splice(index, 1);
return true;
}
});
item.textContent = item.getAttribute("data-content");
} else if (type === "inline-math" && types.includes("inline-memo")) {
// 数学公式和备注不能同时存在
types.find((item, index) => {
if (item === "inline-memo") {
types.splice(index, 1);
return true;
}
});
}
types = [...new Set(types)];
if (index === 0 && previousElement && previousElement.nodeType !== 3 &&
isArrayEqual(types, (previousElement.getAttribute("data-type") || "").split(" ")) &&
hasSameTextStyle(item, previousElement, textObj)) {
previousIndex = previousElement.textContent.length;
previousElement.innerHTML = previousElement.innerHTML + item.innerHTML;
} else if (index === contents.childNodes.length - 1 && nextElement && nextElement.nodeType !== 3 &&
isArrayEqual(types, (nextElement.getAttribute("data-type") || "").split(" ")) &&
hasSameTextStyle(item, nextElement, textObj)) {
nextIndex = item.textContent.length;
nextElement.innerHTML = item.innerHTML + nextElement.innerHTML;
} else if (item.tagName !== "BR" && item.tagName !== "IMG") {
if (item.getAttribute("data-type")?.indexOf("backslash") > -1 &&
item.firstChild?.textContent === "\\") {
item.firstChild.remove();
}
item.setAttribute("data-type", types.join(" "));
setFontStyle(item, textObj);
newNodes.push(item);
} else {
newNodes.push(item);
}
}
});
}
}
if (this.range.startContainer.nodeType !== 3 && (this.range.startContainer as HTMLElement).tagName === "SPAN" &&
this.range.startContainer.isSameNode(this.range.endContainer) && !isEndSpan) {
// 切割元素
const startContainer = this.range.startContainer as HTMLElement;
const afterElement = document.createElement("span");
const attributes = startContainer.attributes;
for (let i = 0; i < attributes.length; i++) {
afterElement.setAttribute(attributes[i].name, attributes[i].value);
}
this.range.setEnd(startContainer.lastChild, startContainer.lastChild.textContent.length);
afterElement.append(this.range.extractContents());
startContainer.after(afterElement);
this.range.setStartBefore(afterElement);
this.range.collapse(true);
}
for (let i = 0; i < newNodes.length; i++) {
const currentNewNode = newNodes[i] as HTMLElement;
const nextNewNode = newNodes[i + 1] as HTMLElement;
if (currentNewNode.nodeType !== 3 && nextNewNode && nextNewNode.nodeType !== 3 &&
nextNewNode.tagName === currentNewNode.tagName &&
isArrayEqual((nextNewNode.getAttribute("data-type") || "").split(" "), (currentNewNode.getAttribute("data-type") || "").split(" ")) &&
currentNewNode.getAttribute("data-id") === nextNewNode.getAttribute("data-id") &&
currentNewNode.getAttribute("data-subtype") === nextNewNode.getAttribute("data-subtype") &&
currentNewNode.style.color === nextNewNode.style.color &&
currentNewNode.style.webkitTextFillColor === nextNewNode.style.webkitTextFillColor &&
currentNewNode.style.webkitTextStroke === nextNewNode.style.webkitTextStroke &&
currentNewNode.style.textShadow === nextNewNode.style.textShadow &&
currentNewNode.style.fontSize === nextNewNode.style.fontSize &&
currentNewNode.style.backgroundColor === nextNewNode.style.backgroundColor) {
// 合并相同的 node
2022-11-14 19:02:56 +08:00
const currentType = currentNewNode.getAttribute("data-type");
if (currentType.indexOf("inline-math") > -1) {
// 数学公式合并 data-content https://github.com/siyuan-note/siyuan/issues/6028
2022-10-03 16:22:18 +08:00
nextNewNode.setAttribute("data-content", currentNewNode.getAttribute("data-content") + nextNewNode.getAttribute("data-content"));
} else {
// 测试不存在 https://ld246.com/article/1664454663564 情况,故移除引用合并限制
// 搜索结果引用被高亮隔断需进行合并 https://github.com/siyuan-note/siyuan/issues/7588
nextNewNode.innerHTML = currentNewNode.innerHTML + nextNewNode.innerHTML;
// 如果为备注时,合并备注内容
if (currentType.indexOf("inline-memo") > -1) {
nextNewNode.setAttribute("data-inline-memo-content", (currentNewNode.getAttribute("data-inline-memo-content") || "") +
(nextNewNode.getAttribute("data-inline-memo-content") || ""));
}
}
newNodes.splice(i, 1);
i--;
} else {
this.range.insertNode(currentNewNode);
// https://github.com/siyuan-note/siyuan/issues/6155
if (currentNewNode.nodeType !== 3 && ["code", "tag", "kbd"].includes(type)) {
2022-12-07 22:52:41 +08:00
const previousSibling = hasPreviousSibling(currentNewNode);
if (!previousSibling || previousSibling.textContent.endsWith("\n")) {
currentNewNode.before(document.createTextNode(Constants.ZWSP));
}
if (!currentNewNode.textContent.startsWith(Constants.ZWSP)) {
currentNewNode.textContent = Constants.ZWSP + currentNewNode.textContent;
}
const currentNextSibling = hasNextSibling(currentNewNode);
if (!currentNextSibling ||
(currentNextSibling && (
currentNextSibling.nodeType !== 3 ||
(currentNextSibling.nodeType === 3 && !currentNextSibling.textContent.startsWith(Constants.ZWSP)))
)
) {
currentNewNode.after(document.createTextNode(Constants.ZWSP));
}
}
this.range.collapse(false);
}
}
if (previousElement) {
if (previousElement.nodeType !== 3 && previousElement.textContent === Constants.ZWSP) {
// https://github.com/siyuan-note/siyuan/issues/7548
previousElement.remove();
} else {
this.mergeNode(previousElement.childNodes);
}
}
if (nextElement) {
this.mergeNode(nextElement.childNodes);
}
if (previousIndex) {
this.range.setStart(previousElement.firstChild, previousIndex);
} else if (newNodes.length > 0) {
if (newNodes[0].nodeType !== 3 && (newNodes[0] as HTMLElement).getAttribute("data-type") === "inline-math") {
// 数学公式后面处理
} else {
if (newNodes[0].firstChild) {
if (newNodes[0].firstChild.textContent === Constants.ZWSP) {
// 新建元素时光标消失 https://github.com/siyuan-note/siyuan/issues/6481
// 新建元素粘贴后元素消失 https://ld246.com/article/1665556907936
this.range.setStart(newNodes[0].firstChild, 1);
} else {
this.range.setStart(newNodes[0].firstChild, 0);
}
} else if (newNodes[0].nodeType === 3) {
this.range.setStart(newNodes[0], 0);
} else {
this.range.setStartBefore(newNodes[0]);
}
}
} else if (nextElement) {
// aaa**bbb** 选中 aaa 加粗
this.range.setStart(nextElement.firstChild, 0);
}
if (nextIndex) {
this.range.setEnd(nextElement.lastChild, nextIndex);
} else if (newNodes.length > 0) {
2022-09-14 11:18:44 +08:00
const lastNewNode = newNodes[newNodes.length - 1];
if (lastNewNode.nodeType !== 3 && (lastNewNode as HTMLElement).getAttribute("data-type").indexOf("inline-math") > -1) {
const mathPreviousSibling = hasPreviousSibling(lastNewNode);
if (mathPreviousSibling && mathPreviousSibling.nodeType === 3) {
this.range.setStart(mathPreviousSibling, mathPreviousSibling.textContent.length);
} else {
this.range.setStartBefore(lastNewNode);
}
const mathNextSibling = hasNextSibling(lastNewNode);
if (mathNextSibling && mathNextSibling.nodeType === 3) { // https://github.com/siyuan-note/siyuan/issues/6065
this.range.setEnd(mathNextSibling, 0);
} else {
this.range.setEndAfter(lastNewNode);
}
} else {
if (lastNewNode.lastChild) {
if (lastNewNode.lastChild.textContent === Constants.ZWSP) {
// 新建元素时光标消失 https://github.com/siyuan-note/siyuan/issues/6481
// 新建元素粘贴后元素消失 https://ld246.com/article/1665556907936
this.range.collapse(true);
} else {
this.range.setEnd(lastNewNode.lastChild, lastNewNode.lastChild.textContent.length);
}
} else if (lastNewNode.nodeType === 3) {
this.range.setEnd(lastNewNode, lastNewNode.textContent.length);
if (lastNewNode.textContent === Constants.ZWSP) {
// 粗体后取消粗体光标不存在 https://github.com/siyuan-note/insider/issues/1056
this.range.collapse(false);
}
} else {
// eg: 表格中有3行时选中第二行三级多次加粗会增加换行
this.range.setEndAfter(lastNewNode);
}
}
} else if (previousElement) {
// **aaa**bbb 选中 bbb 加粗
// 需进行 mergeNode ,否用 alt+x 为相同颜色 aaabbb 中的 bbb 再次赋值后无法选中
this.range.setEnd(previousElement.firstChild, previousElement.firstChild.textContent.length);
}
2022-10-16 00:15:53 +08:00
let needFocus = true;
if (type === "inline-math") {
mathRender(nodeElement);
if (selectText === "") {
protyle.toolbar.showRender(protyle, newNodes[0] as HTMLElement, undefined, html);
return;
}
} else if (type === "inline-memo") {
protyle.toolbar.showRender(protyle, newNodes[0] as HTMLElement, newNodes as Element[], html);
return;
} else if (type === "block-ref") {
this.range.collapse(false);
} else if (type === "a") {
2022-09-27 00:13:11 +08:00
const aElement = newNodes[0] as HTMLElement;
if (aElement.textContent.replace(Constants.ZWSP, "") === "" || !aElement.getAttribute("data-href")) {
needFocus = false;
2023-06-01 20:50:49 +08:00
linkMenu(protyle, aElement, aElement.getAttribute("data-href") ? true : false);
} else {
this.range.collapse(false);
}
}
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, html);
const wbrElement = nodeElement.querySelector("wbr");
if (wbrElement) {
wbrElement.remove();
}
if (needFocus) {
focusByRange(this.range);
}
}
public showRender(protyle: IProtyle, renderElement: Element, updateElements?: Element[], oldHTML?: string) {
const nodeElement = hasClosestBlock(renderElement);
if (!nodeElement) {
return;
}
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
const id = nodeElement.getAttribute("data-node-id");
const types = (renderElement.getAttribute("data-type") || "").split(" ");
const html = oldHTML || protyle.lute.SpinBlockDOM(nodeElement.outerHTML);
let title = "HTML";
let placeholder = "";
const isInlineMemo = types.includes("inline-memo");
switch (renderElement.getAttribute("data-subtype")) {
case "abc":
title = window.siyuan.languages.staff;
break;
case "echarts":
title = window.siyuan.languages.chart;
break;
case "flowchart":
title = "Flow Chart";
break;
case "graphviz":
title = "Graphviz";
break;
case "mermaid":
title = "Mermaid";
break;
case "mindmap":
placeholder = `- foo
- bar
- baz`;
title = window.siyuan.languages.mindmap;
break;
case "plantuml":
title = "UML";
break;
case "math":
if (types.includes("NodeMathBlock")) {
title = window.siyuan.languages.math;
} else {
title = window.siyuan.languages["inline-math"];
}
break;
}
if (types.includes("NodeBlockQueryEmbed")) {
title = window.siyuan.languages.blockEmbed;
} else if (isInlineMemo) {
title = window.siyuan.languages.memo;
}
const isPin = this.subElement.querySelector('[data-type="pin"]')?.classList.contains("block__icon--active");
const pinData: IObject = {};
if (isPin) {
const textElement = this.subElement.querySelector(".b3-text-field") as HTMLTextAreaElement;
pinData.styleH = textElement.style.height;
pinData.styleW = textElement.style.width;
} else {
this.subElement.style.width = "";
this.subElement.style.padding = "0";
}
this.subElement.innerHTML = `<div ${(isPin && this.subElement.firstElementChild.getAttribute("data-drag") === "true") ? 'data-drag="true"' : ""}><div class="block__icons block__icons--menu fn__flex" style="border-radius: var(--b3-border-radius-b) var(--b3-border-radius-b) 0 0;">
<span class="fn__flex-1 resize__move">
${title}
</span>
<span class="fn__space"></span>
<button data-type="refresh" class="block__icon block__icon--show b3-tooltips b3-tooltips__nw${(isPin && !this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active")) ? "" : " block__icon--active"}${types.includes("NodeBlockQueryEmbed") ? " fn__none" : ""}" aria-label="${window.siyuan.languages.refresh}"><svg><use xlink:href="#iconRefresh"></use></svg></button>
<span class="fn__space"></span>
<button data-type="before" class="block__icon block__icon--show b3-tooltips b3-tooltips__nw${protyle.disabled ? " fn__none" : ""}" aria-label="${window.siyuan.languages["insert-before"]}"><svg><use xlink:href="#iconBefore"></use></svg></button>
<span class="fn__space${protyle.disabled ? " fn__none" : ""}"></span>
<button data-type="after" class="block__icon block__icon--show b3-tooltips b3-tooltips__nw${protyle.disabled ? " fn__none" : ""}" aria-label="${window.siyuan.languages["insert-after"]}"><svg><use xlink:href="#iconAfter"></use></svg></button>
<span class="fn__space${protyle.disabled ? " fn__none" : ""}"></span>
<button data-type="export" class="block__icon block__icon--show b3-tooltips b3-tooltips__nw" aria-label="${window.siyuan.languages.export} ${window.siyuan.languages.image}"><svg><use xlink:href="#iconImage"></use></svg></button>
<span class="fn__space"></span>
<button data-type="pin" class="block__icon block__icon--show b3-tooltips b3-tooltips__nw${isPin ? " block__icon--active" : ""}" aria-label="${window.siyuan.languages.pin}"><svg><use xlink:href="#iconPin"></use></svg></button>
<span class="fn__space"></span>
<button data-type="close" class="block__icon block__icon--show b3-tooltips b3-tooltips__nw" aria-label="${window.siyuan.languages.close}"><svg style="width: 10px"><use xlink:href="#iconClose"></use></svg></button>
</div>
<textarea ${protyle.disabled ? " readonly" : ""} spellcheck="false" class="b3-text-field b3-text-field--text fn__block" placeholder="${placeholder}" style="${isMobile() ? "" : "width:" + Math.max(480, renderElement.clientWidth * 0.7) + "px"};max-height:50vh;min-height: 48px;min-width: 268px;border-radius: 0 0 var(--b3-border-radius-b) var(--b3-border-radius-b);font-family: var(--b3-font-family-code);"></textarea></div>`;
const autoHeight = () => {
textElement.style.height = textElement.scrollHeight + "px";
if (isMobile()) {
setPosition(this.subElement, 0, 0);
return;
}
if (this.subElement.firstElementChild.getAttribute("data-drag") === "true") {
if (textElement.getBoundingClientRect().bottom > window.innerHeight) {
this.subElement.style.top = window.innerHeight - this.subElement.clientHeight + "px";
}
return;
}
const bottom = nodeRect.bottom === nodeRect.top ? nodeRect.bottom + 26 : nodeRect.bottom;
if (this.subElement.clientHeight <= window.innerHeight - bottom || this.subElement.clientHeight <= nodeRect.top) {
if (types.includes("inline-math") || isInlineMemo) {
setPosition(this.subElement, nodeRect.left, bottom, nodeRect.height || 26);
} else {
setPosition(this.subElement, nodeRect.left + (nodeRect.width - this.subElement.clientWidth) / 2, bottom, nodeRect.height || 26);
}
} else {
setPosition(this.subElement, nodeRect.right, bottom);
}
};
2022-08-21 17:38:09 +08:00
const headerElement = this.subElement.querySelector(".block__icons");
headerElement.addEventListener("click", (event: MouseEvent) => {
const target = event.target as HTMLElement;
const btnElement = hasClosestByClassName(target, "b3-tooltips");
if (!btnElement) {
if (event.detail === 2) {
const pingElement = headerElement.querySelector('[data-type="pin"]');
if (pingElement.classList.contains("block__icon--active")) {
pingElement.classList.remove("block__icon--active");
pingElement.setAttribute("aria-label", window.siyuan.languages.pin);
} else {
pingElement.classList.add("block__icon--active");
pingElement.setAttribute("aria-label", window.siyuan.languages.unpin);
}
2022-08-21 17:38:09 +08:00
event.preventDefault();
event.stopPropagation();
}
return;
}
event.stopPropagation();
switch (btnElement.getAttribute("data-type")) {
case "close":
this.subElement.querySelector('[data-type="pin"]').classList.remove("block__icon--active");
hideElements(["util"], protyle);
break;
case "pin":
if (btnElement.classList.contains("block__icon--active")) {
btnElement.classList.remove("block__icon--active");
btnElement.setAttribute("aria-label", window.siyuan.languages.pin);
} else {
btnElement.classList.add("block__icon--active");
btnElement.setAttribute("aria-label", window.siyuan.languages.unpin);
}
break;
case "refresh":
btnElement.classList.toggle("block__icon--active");
break;
case "before":
insertEmptyBlock(protyle, "beforebegin", id);
hideElements(["util"], protyle);
break;
case "after":
insertEmptyBlock(protyle, "afterend", id);
hideElements(["util"], protyle);
break;
case "export":
exportImg();
break;
}
});
const exportImg = () => {
const msgId = showMessage(window.siyuan.languages.exporting, 0);
if (renderElement.getAttribute("data-subtype") === "plantuml") {
fetch(renderElement.querySelector("img").getAttribute("src")).then(function (response) {
return response.blob();
}).then(function (blob) {
const formData = new FormData();
formData.append("file", blob);
formData.append("type", "image/png");
fetchPost("/api/export/exportAsFile", formData, (response) => {
openByMobile(response.data.file);
hideMessage(msgId);
});
});
return;
}
setTimeout(() => {
addScript("/stage/protyle/js/html2canvas.min.js?v=1.4.1", "protyleHtml2canvas").then(() => {
window.html2canvas(renderElement, {useCORS: true}).then((canvas) => {
canvas.toBlob((blob: Blob) => {
const formData = new FormData();
formData.append("file", blob);
formData.append("type", "image/png");
fetchPost("/api/export/exportAsFile", formData, (response) => {
openByMobile(response.data.file);
hideMessage(msgId);
});
});
});
});
}, Constants.TIMEOUT_LOAD);
2022-12-07 22:52:41 +08:00
};
/// #if !MOBILE
moveResize(this.subElement, () => {
const pinElement = headerElement.querySelector('[data-type="pin"]');
pinElement.classList.add("block__icon--active");
pinElement.setAttribute("aria-label", window.siyuan.languages.unpin);
this.subElement.firstElementChild.setAttribute("data-drag", "true");
});
/// #endif
const textElement = this.subElement.querySelector(".b3-text-field") as HTMLTextAreaElement;
if (types.includes("NodeHTMLBlock")) {
textElement.value = Lute.UnEscapeHTMLStr(renderElement.querySelector("protyle-html").getAttribute("data-content") || "");
} else if (isInlineMemo) {
textElement.value = Lute.UnEscapeHTMLStr(renderElement.getAttribute("data-inline-memo-content") || "");
} else {
textElement.value = Lute.UnEscapeHTMLStr(renderElement.getAttribute("data-content") || "");
}
textElement.addEventListener("input", (event) => {
if (!renderElement.parentElement) {
return;
}
if (textElement.clientHeight !== textElement.scrollHeight) {
autoHeight();
}
if (!this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active")) {
return;
}
if (types.includes("NodeHTMLBlock")) {
renderElement.querySelector("protyle-html").setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
} else if (isInlineMemo) {
let inlineMemoElements;
if (updateElements) {
inlineMemoElements = updateElements;
} else {
inlineMemoElements = [renderElement];
}
inlineMemoElements.forEach((item) => {
item.setAttribute("data-inline-memo-content", Lute.EscapeHTMLStr(textElement.value));
});
} else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render");
}
if (!types.includes("NodeBlockQueryEmbed") || !types.includes("NodeHTMLBlock") || !isInlineMemo) {
processRender(renderElement);
}
event.stopPropagation();
});
textElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
// 阻止 ctrl+m 缩小窗口 https://github.com/siyuan-note/siyuan/issues/5541
if (matchHotKey(window.siyuan.config.keymap.editor.insert["inline-math"].custom, event)) {
event.preventDefault();
return;
}
if (event.isComposing) {
return;
}
if (event.key === "Escape" || matchHotKey("⌘↩", event)) {
this.subElement.querySelector('[data-type="pin"]').classList.remove("block__icon--active");
hideElements(["util"], protyle);
} else if (event.key === "Tab") {
// https://github.com/siyuan-note/siyuan/issues/5270
document.execCommand("insertText", false, "\t");
event.preventDefault();
} else if (electronUndo(event)) {
return;
}
});
this.subElementCloseCB = () => {
if (!renderElement.parentElement || protyle.disabled) {
return;
}
let inlineLastNode: Element;
if (types.includes("NodeHTMLBlock")) {
let html = textElement.value;
if (html) {
2023-04-17 10:49:51 +08:00
// 需移除首尾的空白字符与连续的换行 (空行) https://github.com/siyuan-note/siyuan/issues/7921
html = html.trim().replace(/\n+/g, "\n");
// 需一对 div 标签包裹,否则行内元素会解析错误 https://github.com/siyuan-note/siyuan/issues/6764
if (!(html.startsWith("<div>") && html.endsWith("</div>"))) {
html = `<div>\n${html}\n</div>`;
}
}
renderElement.querySelector("protyle-html").setAttribute("data-content", Lute.EscapeHTMLStr(html));
} else if (isInlineMemo) {
2022-09-19 23:56:47 +08:00
let inlineMemoElements;
if (updateElements) {
2022-09-19 23:56:47 +08:00
inlineMemoElements = updateElements;
} else {
2022-09-19 23:56:47 +08:00
inlineMemoElements = [renderElement];
}
inlineMemoElements.forEach((item, index) => {
if (!textElement.value) {
// https://github.com/siyuan-note/insider/issues/1046
2022-09-19 23:56:47 +08:00
const currentTypes = item.getAttribute("data-type").split(" ");
if (currentTypes.length === 1 && currentTypes[0] === "inline-memo") {
2022-09-20 20:10:42 +08:00
item.outerHTML = item.innerHTML + (index === inlineMemoElements.length - 1 ? "<wbr>" : "");
} else {
currentTypes.find((typeItem, index) => {
if (typeItem === "inline-memo") {
2022-09-19 23:56:47 +08:00
currentTypes.splice(index, 1);
return true;
}
2022-09-19 23:56:47 +08:00
});
item.setAttribute("data-type", currentTypes.join(" "));
item.removeAttribute("data-inline-memo-content");
}
2022-09-20 20:10:42 +08:00
if (index === inlineMemoElements.length - 1) {
inlineLastNode = item;
2022-09-20 20:10:42 +08:00
}
} else {
// 行级备注自动移除换行 https://ld246.com/article/1664205917326
item.setAttribute("data-inline-memo-content", Lute.EscapeHTMLStr(textElement.value.replace(/\n/g, " ")));
}
2022-09-19 23:56:47 +08:00
});
} else if (types.includes("inline-math")) {
// 行内数学公式不允许换行 https://github.com/siyuan-note/siyuan/issues/2187
if (textElement.value) {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value.replace(/\n/g, "")));
renderElement.removeAttribute("data-render");
processRender(renderElement);
} else {
inlineLastNode = renderElement;
// esc 后需要 focus range但点击空白处不能 focus range否则光标无法留在点击位置
2022-09-23 23:30:01 +08:00
renderElement.outerHTML = "<wbr>";
}
} else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render");
if (types.includes("NodeBlockQueryEmbed")) {
blockRender(protyle, renderElement);
} else {
processRender(renderElement);
}
}
// 光标定位
2023-08-16 23:50:24 +08:00
if (getSelection().rangeCount === 0 ||
// $$ 中间输入后再 ESC 光标无法定位
(getSelection().rangeCount > 0 && hasClosestByClassName(getSelection().getRangeAt(0).startContainer, "protyle-util"))
) { // https://ld246.com/article/1665306093005
if (renderElement.tagName === "SPAN") {
if (inlineLastNode) {
if (inlineLastNode.parentElement) {
this.range.setStartAfter(inlineLastNode);
this.range.collapse(true);
focusByRange(this.range);
} else {
focusByWbr(nodeElement, this.range);
}
} else if (renderElement.parentElement) {
this.range.setStartAfter(renderElement);
2022-09-20 20:10:42 +08:00
this.range.collapse(true);
focusByRange(this.range);
}
} else {
focusBlock(renderElement);
renderElement.classList.add("protyle-wysiwyg--select");
2022-09-20 20:10:42 +08:00
}
} else {
// ctrl+M 后点击空白会留下 wbr
nodeElement.querySelector("wbr")?.remove();
}
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
2022-08-21 17:38:09 +08:00
const newHTML = protyle.lute.SpinBlockDOM(nodeElement.outerHTML);
// HTML 块中包含多个 <pre> 时只能保存第一个 https://github.com/siyuan-note/siyuan/issues/5732
if (types.includes("NodeHTMLBlock")) {
const tempElement = document.createElement("template");
tempElement.innerHTML = newHTML;
if (tempElement.content.childElementCount > 1) {
2022-08-28 12:44:28 +08:00
showMessage(window.siyuan.languages.htmlBlockTip);
}
}
updateTransaction(protyle, id, newHTML, html);
2022-09-23 23:30:01 +08:00
};
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
const nodeRect = renderElement.getBoundingClientRect();
this.element.classList.add("fn__none");
if (isPin) {
textElement.style.width = pinData.styleW;
textElement.style.height = pinData.styleH;
} else {
autoHeight();
}
if (!protyle.disabled) {
textElement.select();
}
2023-06-01 20:50:49 +08:00
protyle.app.plugins.forEach(item => {
item.eventBus.emit("open-noneditableblock", {
protyle,
toolbar: this
});
});
}
public showCodeLanguage(protyle: IProtyle, languageElement: HTMLElement) {
const nodeElement = hasClosestBlock(languageElement);
if (!nodeElement) {
return;
}
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
this.range = getEditorRange(nodeElement);
const id = nodeElement.getAttribute("data-node-id");
let oldHtml = nodeElement.outerHTML;
let html = `<div class="b3-list-item">${window.siyuan.languages.clear}</div>`;
const hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort();
hljsLanguages.forEach((item, index) => {
html += `<div class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item}</div>`;
});
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `<div class="fn__flex-column" style="max-height:50vh">
<input placeholder="${window.siyuan.languages.search}" style="margin: 0 8px 4px 8px" class="b3-text-field"/>
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
</div>`;
2023-10-11 11:25:21 +08:00
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
if (event.isComposing) {
return;
}
upDownHint(listElement, event);
if (event.key === "Enter") {
const activeText = this.subElement.querySelector(".b3-list-item--focus").textContent;
languageElement.textContent = activeText === window.siyuan.languages.clear ? "" : activeText;
window.siyuan.storage[Constants.LOCAL_CODELANG] = languageElement.textContent;
setStorageVal(Constants.LOCAL_CODELANG, window.siyuan.storage[Constants.LOCAL_CODELANG]);
const editElement = getContenteditableElement(nodeElement);
2022-05-29 10:06:02 +08:00
const lineNumber = nodeElement.getAttribute("linenumber");
if (lineNumber === "true" || (lineNumber !== "false" && window.siyuan.config.editor.codeSyntaxHighlightLineNum)) {
editElement.classList.add("protyle-linenumber");
} else {
editElement.classList.remove("protyle-linenumber");
}
(editElement as HTMLElement).textContent = editElement.textContent;
editElement.removeAttribute("data-render");
highlightRender(nodeElement);
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
updateTransaction(protyle, id, nodeElement.outerHTML, oldHtml);
oldHtml = nodeElement.outerHTML;
event.preventDefault();
event.stopPropagation();
}
if (event.key === "Escape" || event.key === "Enter") {
this.subElement.classList.add("fn__none");
focusByRange(this.range);
}
});
inputElement.addEventListener("input", (event) => {
const lowerCaseValue = inputElement.value.toLowerCase();
const matchLanguages = hljsLanguages.filter(item => item.includes(lowerCaseValue));
let html = "";
// sort
let matchInput = false;
matchLanguages.sort((a, b) => {
if (a.startsWith(lowerCaseValue) && b.startsWith(lowerCaseValue)) {
if (a.length < b.length) {
return -1;
} else if (a.length === b.length) {
return 0;
} else {
return 1;
}
} else if (a.startsWith(lowerCaseValue)) {
return -1;
} else if (b.startsWith(lowerCaseValue)) {
return 1;
} else {
return 0;
}
}).forEach((item) => {
if (inputElement.value === item) {
2023-03-25 11:12:06 +08:00
matchInput = true;
}
html += `<div class="b3-list-item">${item.replace(lowerCaseValue, "<b>" + lowerCaseValue + "</b>")}</div>`;
});
if (inputElement.value.trim() && !matchInput) {
html = `<div class="b3-list-item"><b>${inputElement.value.replace(/`| /g, "_")}</b></div>${html}`;
}
html = `<div class="b3-list-item">${window.siyuan.languages.clear}</div>` + html;
listElement.innerHTML = html;
if (listElement.firstElementChild.nextElementSibling) {
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
} else {
listElement.firstElementChild.classList.add("b3-list-item--focus");
}
event.stopPropagation();
});
listElement.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const listElement = hasClosestByClassName(target, "b3-list-item");
if (!listElement) {
return;
}
languageElement.textContent = listElement.textContent === window.siyuan.languages.clear ? "" : listElement.textContent;
window.siyuan.storage[Constants.LOCAL_CODELANG] = languageElement.textContent;
setStorageVal(Constants.LOCAL_CODELANG, window.siyuan.storage[Constants.LOCAL_CODELANG]);
const nodeElement = hasClosestBlock(languageElement);
if (nodeElement) {
const editElement = getContenteditableElement(nodeElement);
2022-05-29 10:06:02 +08:00
const lineNumber = nodeElement.getAttribute("linenumber");
if (lineNumber === "true" || (lineNumber !== "false" && window.siyuan.config.editor.codeSyntaxHighlightLineNum)) {
editElement.classList.add("protyle-linenumber");
} else {
editElement.classList.remove("protyle-linenumber");
}
(editElement as HTMLElement).textContent = editElement.textContent;
editElement.removeAttribute("data-render");
highlightRender(nodeElement);
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
updateTransaction(protyle, id, nodeElement.outerHTML, oldHtml);
oldHtml = nodeElement.outerHTML;
this.subElement.classList.add("fn__none");
focusByRange(this.range);
}
});
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
/// #if !MOBILE
const nodeRect = languageElement.getBoundingClientRect();
setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height);
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
this.element.classList.add("fn__none");
inputElement.select();
}
public showTpl(protyle: IProtyle, nodeElement: HTMLElement, range: Range) {
this.range = range;
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `<div style="max-height:50vh" class="fn__flex">
<div class="fn__flex-column" style="${isMobile() ? "width: 100%" : "min-width: 260px;max-width:50vw"}">
2023-07-28 17:33:35 +08:00
<div class="fn__flex" style="margin: 0 8px 4px 8px">
<input class="b3-text-field fn__flex-1"/>
<span class="fn__space"></span>
<span data-type="previous" class="block__icon block__icon--show"><svg><use xlink:href="#iconLeft"></use></svg></span>
<span class="fn__space"></span>
<span data-type="next" class="block__icon block__icon--show"><svg><use xlink:href="#iconRight"></use></svg></span>
</div>
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative"><img style="margin: 0 auto;display: block;width: 64px;height: 64px" src="/stage/loading-pure.svg"></div>
</div>
<div style="width: 520px;${isMobile() || window.outerWidth < window.outerWidth / 2 + 520 ? "display:none" : ""};overflow: auto;"></div>
</div>`;
const listElement = this.subElement.querySelector(".b3-list");
const previewElement = this.subElement.firstElementChild.lastElementChild;
let previewPath = listElement.firstElementChild.getAttribute("data-value");
previewTemplate(previewPath, previewElement, protyle.block.parentID);
listElement.addEventListener("mouseover", (event) => {
const target = event.target as HTMLElement;
const hoverItemElement = hasClosestByClassName(target, "b3-list-item");
if (!hoverItemElement) {
return;
}
const currentPath = hoverItemElement.getAttribute("data-value");
if (previewPath === currentPath) {
return;
}
previewPath = currentPath;
previewTemplate(previewPath, previewElement, protyle.block.parentID);
});
const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
if (event.isComposing) {
return;
}
const isEmpty = !this.subElement.querySelector(".b3-list-item");
if (!isEmpty) {
const currentElement = upDownHint(listElement, event);
if (currentElement) {
const currentPath = currentElement.getAttribute("data-value");
if (previewPath === currentPath) {
return;
}
previewPath = currentPath;
previewTemplate(previewPath, previewElement, protyle.block.parentID);
}
}
if (event.key === "Enter") {
if (!isEmpty) {
hintRenderTemplate(decodeURIComponent(this.subElement.querySelector(".b3-list-item--focus").getAttribute("data-value")), protyle, nodeElement);
} else {
focusByRange(this.range);
}
this.subElement.classList.add("fn__none");
event.preventDefault();
} else if (event.key === "Escape") {
this.subElement.classList.add("fn__none");
focusByRange(this.range);
}
});
inputElement.addEventListener("input", (event) => {
event.stopPropagation();
fetchPost("/api/search/searchTemplate", {
k: inputElement.value,
}, (response) => {
let searchHTML = "";
response.data.blocks.forEach((item: { path: string, content: string }, index: number) => {
searchHTML += `<div data-value="${item.path}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item.content}</div>`;
});
listElement.innerHTML = searchHTML || `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
const currentPath = response.data.blocks[0]?.path;
if (previewPath === currentPath) {
return;
}
2023-03-25 11:12:06 +08:00
previewPath = currentPath;
previewTemplate(previewPath, previewElement, protyle.block.parentID);
});
});
this.subElement.lastElementChild.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
if (target.classList.contains("b3-list--empty")) {
this.subElement.classList.add("fn__none");
focusByRange(this.range);
event.stopPropagation();
return;
}
const iconElement = hasClosestByClassName(target, "b3-list-item__action");
/// #if !BROWSER
if (iconElement && iconElement.getAttribute("data-type") === "open") {
openBy(iconElement.parentElement.getAttribute("data-value"), "folder");
event.stopPropagation();
return;
}
/// #endif
if (iconElement && iconElement.getAttribute("data-type") === "remove") {
confirmDialog(window.siyuan.languages.remove, window.siyuan.languages.confirmDelete + "?", () => {
fetchPost("/api/search/removeTemplate", {path: iconElement.parentElement.getAttribute("data-value")}, () => {
if (iconElement.parentElement.parentElement.childElementCount === 1) {
iconElement.parentElement.parentElement.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
previewTemplate("", previewElement, protyle.block.parentID);
} else {
if (iconElement.parentElement.classList.contains("b3-list-item--focus")) {
const sideElement = iconElement.parentElement.previousElementSibling || iconElement.parentElement.nextElementSibling;
sideElement.classList.add("b3-list-item--focus");
const currentPath = sideElement.getAttribute("data-value");
if (previewPath === currentPath) {
return;
}
previewPath = currentPath;
previewTemplate(previewPath, previewElement, protyle.block.parentID);
}
iconElement.parentElement.remove();
}
});
});
event.stopPropagation();
return;
}
const previousElement = hasClosestByAttribute(target, "data-type", "previous");
if (previousElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowUp"}));
event.stopPropagation();
return;
}
const nextElement = hasClosestByAttribute(target, "data-type", "next");
if (nextElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowDown"}));
event.stopPropagation();
return;
}
const listElement = hasClosestByClassName(target, "b3-list-item");
if (listElement) {
hintRenderTemplate(decodeURIComponent(listElement.getAttribute("data-value")), protyle, nodeElement);
event.stopPropagation();
}
});
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
this.element.classList.add("fn__none");
inputElement.select();
fetchPost("/api/search/searchTemplate", {
k: "",
}, (response) => {
let html = "";
response.data.blocks.forEach((item: { path: string, content: string }, index: number) => {
html += `<div data-value="${item.path}" class="b3-list-item--hide-action b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">
<span class="b3-list-item__text">${item.content}</span>`;
/// #if !BROWSER
html += `<span data-type="open" class="b3-list-item__action b3-tooltips b3-tooltips__w" aria-label="${window.siyuan.languages.showInFolder}">
<svg><use xlink:href="#iconFolder"></use></svg>
</span>`;
/// #endif
html += `<span data-type="remove" class="b3-list-item__action b3-tooltips b3-tooltips__w" aria-label="${window.siyuan.languages.remove}">
<svg><use xlink:href="#iconTrashcan"></use></svg>
</span></div>`;
});
if (html === "") {
html = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
}
this.subElement.querySelector(".b3-list--background").innerHTML = html;
/// #if !MOBILE
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
(this.subElement.firstElementChild as HTMLElement).style.maxHeight = Math.min(window.innerHeight * 0.8, window.innerHeight - this.subElement.getBoundingClientRect().top) - 16 + "px";
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
});
}
public showWidget(protyle: IProtyle, nodeElement: HTMLElement, range: Range) {
this.range = range;
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `<div class="fn__flex-column" style="max-height:50vh">
<input style="margin: 0 8px 4px 8px" class="b3-text-field"/>
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative"><img style="margin: 0 auto;display: block;width: 64px;height:64px" src="/stage/loading-pure.svg"></div>
</div>`;
2023-10-11 11:25:21 +08:00
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
if (event.isComposing) {
return;
}
upDownHint(listElement, event);
if (event.key === "Enter") {
hintRenderWidget(this.subElement.querySelector(".b3-list-item--focus").textContent, protyle);
this.subElement.classList.add("fn__none");
event.preventDefault();
} else if (event.key === "Escape") {
this.subElement.classList.add("fn__none");
focusByRange(this.range);
}
});
inputElement.addEventListener("input", (event) => {
event.stopPropagation();
fetchPost("/api/search/searchWidget", {
k: inputElement.value,
}, (response) => {
let searchHTML = "";
response.data.blocks.forEach((item: { path: string, content: string }, index: number) => {
searchHTML += `<div data-value="${item.path}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item.content}</div>`;
});
listElement.innerHTML = searchHTML;
});
});
this.subElement.lastElementChild.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const listElement = hasClosestByClassName(target, "b3-list-item");
if (!listElement) {
return;
}
hintRenderWidget(listElement.textContent, protyle);
});
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
this.element.classList.add("fn__none");
inputElement.select();
fetchPost("/api/search/searchWidget", {
k: "",
}, (response) => {
let html = "";
response.data.blocks.forEach((item: { content: string }, index: number) => {
html += `<div class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item.content}</div>`;
});
this.subElement.querySelector(".b3-list--background").innerHTML = html;
/// #if !MOBILE
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
});
}
private renderAssetList(listElement: Element, previewElement: Element, k: string, position: IPosition, exts: string[] = []) {
fetchPost("/api/search/searchAsset", {
k,
exts
}, (response) => {
let searchHTML = "";
response.data.forEach((item: { path: string, hName: string }, index: number) => {
searchHTML += `<div data-value="${item.path}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}"><div class="b3-list-item__text">${item.hName}</div></div>`;
});
listElement.innerHTML = searchHTML || `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
if (response.data.length > 0) {
previewElement.innerHTML = renderAssetsPreview(response.data[0].path);
}
/// #if !MOBILE
setPosition(this.subElement, position.x, position.y, position.h, position.w);
/// #endif
});
}
public showAssets(protyle: IProtyle, position: IPosition, callback?: (url: string) => void, exts: string[] = []) {
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `<div style="max-height:50vh" class="fn__flex">
<div class="fn__flex-column" style="${isMobile() ? "width:100%" : "min-width: 260px;max-width:50vw"}">
2023-07-28 17:33:35 +08:00
<div class="fn__flex" style="margin: 0 8px 4px 8px">
<input class="b3-text-field fn__flex-1"/>
<span class="fn__space"></span>
<span data-type="previous" class="block__icon block__icon--show"><svg><use xlink:href="#iconLeft"></use></svg></span>
<span class="fn__space"></span>
<span data-type="next" class="block__icon block__icon--show"><svg><use xlink:href="#iconRight"></use></svg></span>
</div>
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative"><img style="margin: 0 auto;display: block;width: 64px;height: 64px" src="/stage/loading-pure.svg"></div>
</div>
<div style="width: 260px;display: ${isMobile() || window.outerWidth < window.outerWidth / 2 + 260 ? "none" : "flex"};padding: 8px;overflow: auto;justify-content: center;align-items: center;"></div>
</div>`;
const listElement = this.subElement.querySelector(".b3-list");
const previewElement = this.subElement.firstElementChild.lastElementChild;
listElement.addEventListener("mouseover", (event) => {
const target = event.target as HTMLElement;
const hoverItemElement = hasClosestByClassName(target, "b3-list-item");
if (!hoverItemElement) {
return;
}
previewElement.innerHTML = renderAssetsPreview(hoverItemElement.getAttribute("data-value"));
});
const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
if (event.isComposing) {
return;
}
const isEmpty = this.subElement.querySelector(".b3-list--empty");
if (!isEmpty) {
const currentElement = upDownHint(listElement, event);
if (currentElement) {
previewElement.innerHTML = renderAssetsPreview(currentElement.getAttribute("data-value"));
}
}
if (event.key === "Enter") {
if (!isEmpty) {
2023-10-01 10:27:07 +08:00
const currentURL = this.subElement.querySelector(".b3-list-item--focus").getAttribute("data-value");
if (callback) {
callback(currentURL);
} else {
hintRenderAssets(currentURL, protyle);
}
} else if (!callback) {
focusByRange(this.range);
}
this.subElement.classList.add("fn__none");
// 空行处插入 mp3 会多一个空的 mp3 块
event.preventDefault();
} else if (event.key === "Escape") {
this.subElement.classList.add("fn__none");
if (!callback) {
focusByRange(this.range);
}
}
});
inputElement.addEventListener("input", (event) => {
event.stopPropagation();
this.renderAssetList(listElement, previewElement, inputElement.value, position, exts);
});
this.subElement.lastElementChild.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const previousElement = hasClosestByAttribute(target, "data-type", "previous");
if (previousElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowUp"}));
event.stopPropagation();
return;
}
const nextElement = hasClosestByAttribute(target, "data-type", "next");
if (nextElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowDown"}));
event.stopPropagation();
return;
}
if (target.classList.contains("b3-list--empty")) {
this.subElement.classList.add("fn__none");
if (!callback) {
focusByRange(this.range);
}
event.stopPropagation();
return;
}
const listItemElement = hasClosestByClassName(target, "b3-list-item");
if (listItemElement) {
event.stopPropagation();
2023-10-01 10:27:07 +08:00
const currentURL = listItemElement.getAttribute("data-value");
if (callback) {
callback(currentURL);
} else {
hintRenderAssets(currentURL, protyle);
}
}
});
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
/// #if MOBILE
setPosition(this.subElement, 0, 0);
/// #endif
this.element.classList.add("fn__none");
inputElement.select();
this.renderAssetList(listElement, previewElement, "", position, exts);
}
public showContent(protyle: IProtyle, range: Range, nodeElement: Element) {
this.range = range;
hideElements(["hint"], protyle);
this.subElement.style.width = "auto";
this.subElement.style.padding = "0 8px";
2023-08-07 12:38:00 +08:00
let html = "";
const hasCopy = range.toString() !== "" || (range.cloneContents().childNodes[0] as HTMLElement)?.classList?.contains("emoji");
if (hasCopy) {
2023-08-07 12:38:00 +08:00
html += "<button class=\"keyboard__action\" data-action=\"copy\"><svg><use xlink:href=\"#iconCopy\"></use></svg></button>";
if (!protyle.disabled) {
2023-08-04 22:07:57 +08:00
html += `<button class="keyboard__action" data-action="cut"><svg><use xlink:href="#iconCut"></use></svg></button>
2023-08-07 12:38:00 +08:00
<button class="keyboard__action" data-action="delete"><svg><use xlink:href="#iconTrashcan"></use></svg></button>`;
}
}
if (!protyle.disabled) {
2023-08-04 22:07:57 +08:00
html += `<button class="keyboard__action" data-action="paste"><svg><use xlink:href="#iconPaste"></use></svg></button>
2023-08-07 12:38:00 +08:00
<button class="keyboard__action" data-action="select"><svg><use xlink:href="#iconSelect"></use></svg></button>`;
}
if (hasCopy || !protyle.disabled) {
2023-08-07 12:38:00 +08:00
html += "<button class=\"keyboard__action\" data-action=\"more\"><svg><use xlink:href=\"#iconMore\"></use></svg></button>";
}
this.subElement.innerHTML = `<div class="fn__flex">${html}</div>`;
this.subElement.lastElementChild.addEventListener("click", async (event) => {
2023-08-04 22:07:57 +08:00
const btnElemen = hasClosestByClassName(event.target as HTMLElement, "keyboard__action");
if (!btnElemen) {
2023-08-07 12:38:00 +08:00
return;
}
const action = btnElemen.getAttribute("data-action");
if (action === "copy") {
focusByRange(getEditorRange(nodeElement));
document.execCommand("copy");
this.subElement.classList.add("fn__none");
} else if (action === "cut") {
focusByRange(getEditorRange(nodeElement));
document.execCommand("cut");
this.subElement.classList.add("fn__none");
} else if (action === "delete") {
const currentRange = getEditorRange(nodeElement);
currentRange.insertNode(document.createElement("wbr"));
const oldHTML = nodeElement.outerHTML;
currentRange.extractContents();
focusByWbr(nodeElement, currentRange);
focusByRange(currentRange);
updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, oldHTML);
this.subElement.classList.add("fn__none");
} else if (action === "paste") {
if (document.queryCommandSupported("paste")) {
document.execCommand("paste");
} else {
try {
const clipText = await readText();
pasteText(protyle, clipText, nodeElement);
} catch (e) {
console.log(e);
}
}
this.subElement.classList.add("fn__none");
} else if (action === "select") {
selectAll(protyle, nodeElement, range);
this.subElement.classList.add("fn__none");
} else if (action === "copyPlainText") {
focusByRange(getEditorRange(nodeElement));
const cloneContents = getSelection().getRangeAt(0).cloneContents();
cloneContents.querySelectorAll('[data-type="backslash"]').forEach(item => {
item.firstElementChild.remove();
});
copyPlainText(cloneContents.textContent);
this.subElement.classList.add("fn__none");
} else if (action === "pasteAsPlainText") {
focusByRange(getEditorRange(nodeElement));
pasteAsPlainText(protyle);
this.subElement.classList.add("fn__none");
} else if (action === "pasteEscaped") {
pasteEscaped(protyle, nodeElement);
this.subElement.classList.add("fn__none");
} else if (action === "back") {
this.subElement.lastElementChild.innerHTML = html;
} else if (action === "more") {
2023-08-04 22:07:57 +08:00
this.subElement.lastElementChild.innerHTML = `<button class="keyboard__action${hasCopy ? "" : " fn__none"}" data-action="copyPlainText"><span>${window.siyuan.languages.copyPlainText}</span></button>
<div class="keyboard__split${hasCopy ? "" : " fn__none"}"></div>
<button class="keyboard__action${protyle.disabled ? " fn__none" : ""}" data-action="pasteAsPlainText"><span>${window.siyuan.languages.pasteAsPlainText}</span></button>
<div class="keyboard__split${protyle.disabled ? " fn__none" : ""}"></div>
<button class="keyboard__action${protyle.disabled ? " fn__none" : ""}" data-action="pasteEscaped"><span>${window.siyuan.languages.pasteEscaped}</span></button>
<div class="keyboard__split${protyle.disabled ? " fn__none" : ""}"></div>
2023-08-07 12:38:00 +08:00
<button class="keyboard__action" data-action="back"><svg><use xlink:href="#iconBack"></use></svg></button>`;
2023-08-04 22:07:57 +08:00
setPosition(this.subElement, rangePosition.left, rangePosition.top + 28, Constants.SIZE_TOOLBAR_HEIGHT);
}
});
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
this.element.classList.add("fn__none");
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top - 48, Constants.SIZE_TOOLBAR_HEIGHT);
}
}