2022-05-26 15:18:53 +08:00
|
|
|
import {Divider} from "./Divider";
|
|
|
|
|
import {Font} from "./Font";
|
|
|
|
|
import {ToolbarItem} from "./ToolbarItem";
|
|
|
|
|
import {
|
|
|
|
|
focusByRange,
|
|
|
|
|
focusByWbr,
|
|
|
|
|
focusSideBlock,
|
|
|
|
|
getEditorRange,
|
|
|
|
|
getSelectionOffset,
|
2022-07-25 11:17:28 +08:00
|
|
|
getSelectionPosition,
|
|
|
|
|
setFirstNodeRange,
|
|
|
|
|
setLastNodeRange
|
2022-05-26 15:18:53 +08:00
|
|
|
} from "../util/selection";
|
|
|
|
|
import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest";
|
|
|
|
|
import {Link} from "./Link";
|
|
|
|
|
import {setPosition} from "../../util/setPosition";
|
|
|
|
|
import {updateTransaction} from "../wysiwyg/transaction";
|
|
|
|
|
import {Constants} from "../../constants";
|
|
|
|
|
import {mathRender} from "../markdown/mathRender";
|
|
|
|
|
import {getEventName} from "../util/compatibility";
|
|
|
|
|
import {upDownHint} from "../../util/upDownHint";
|
|
|
|
|
import {highlightRender} from "../markdown/highlightRender";
|
2022-07-25 11:17:28 +08:00
|
|
|
import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock";
|
2022-05-26 15:18:53 +08:00
|
|
|
import {processRender} from "../util/processCode";
|
|
|
|
|
import {BlockRef} from "./BlockRef";
|
|
|
|
|
import {hintMoveBlock, hintRef, hintRenderAssets, hintRenderTemplate, hintRenderWidget} from "../hint/extend";
|
|
|
|
|
import {blockRender} from "../markdown/blockRender";
|
|
|
|
|
/// #if !BROWSER
|
|
|
|
|
import {clipboard, nativeImage, NativeImage} from "electron";
|
|
|
|
|
import {getCurrentWindow} from "@electron/remote";
|
|
|
|
|
/// #endif
|
|
|
|
|
import {fetchPost} from "../../util/fetch";
|
|
|
|
|
import {isBrowser, isMobile} from "../../util/functions";
|
|
|
|
|
import * as dayjs from "dayjs";
|
|
|
|
|
import {insertEmptyBlock} from "../../block/util";
|
|
|
|
|
import {matchHotKey} from "../util/hotKey";
|
|
|
|
|
import {unicode2Emoji} from "../../emoji";
|
|
|
|
|
import {escapeHtml} from "../../util/escape";
|
|
|
|
|
import {hideElements} from "../ui/hideElements";
|
2022-06-01 23:52:22 +08:00
|
|
|
import {linkMenu} from "../../menus/protyle";
|
2022-07-16 23:20:50 +08:00
|
|
|
import {renderAssetsPreview} from "../../asset/renderAssets";
|
2022-08-15 10:24:37 +08:00
|
|
|
import {electronUndo} from "../undo";
|
2022-08-18 20:07:58 +08:00
|
|
|
import {previewTemplate} from "./util";
|
2022-08-28 10:28:47 +08:00
|
|
|
import {showMessage} from "../../dialog/message";
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
export class Toolbar {
|
|
|
|
|
public element: HTMLElement;
|
|
|
|
|
public subElement: HTMLElement;
|
|
|
|
|
public range: Range;
|
|
|
|
|
public isNewEmptyInline: boolean;
|
|
|
|
|
private toolbarHeight: number;
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
this.subElement.className = "protyle-util fn__none";
|
|
|
|
|
this.toolbarHeight = 29;
|
|
|
|
|
|
|
|
|
|
options.toolbar.forEach((menuItem: IMenuItem) => {
|
|
|
|
|
const itemElement = this.genItem(protyle, menuItem);
|
|
|
|
|
this.element.appendChild(itemElement);
|
|
|
|
|
});
|
|
|
|
|
this.isNewEmptyInline = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public render(protyle: IProtyle, range: Range, event?: KeyboardEvent) {
|
|
|
|
|
this.range = range;
|
|
|
|
|
const nodeElement = hasClosestBlock(range.startContainer);
|
|
|
|
|
if (!nodeElement || protyle.disabled) {
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-06-11 00:50:51 +08:00
|
|
|
// 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;
|
2022-06-11 00:50:51 +08:00
|
|
|
}
|
|
|
|
|
} else if (!(item as HTMLElement).classList.contains("img")) {
|
2022-06-11 00:51:42 +08:00
|
|
|
hasImg = false;
|
|
|
|
|
return true;
|
2022-06-11 00:50:51 +08:00
|
|
|
}
|
2022-06-11 00:51:42 +08:00
|
|
|
});
|
2022-06-11 00:50:51 +08:00
|
|
|
if (hasImg && noText) {
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
// 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 {
|
|
|
|
|
this.range = setFirstNodeRange(getContenteditableElement(endElement), range);
|
|
|
|
|
}
|
|
|
|
|
} 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;
|
2022-08-03 18:08:28 +08:00
|
|
|
this.element.setAttribute("data-inity", y + Constants.ZWSP + protyle.contentElement.scrollTop.toString());
|
|
|
|
|
setPosition(this.element, rangePosition.left - 52, y);
|
2022-05-26 15:18:53 +08:00
|
|
|
this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => {
|
|
|
|
|
item.classList.remove("protyle-toolbar__item--current");
|
|
|
|
|
});
|
|
|
|
|
const types = this.getCurrentType();
|
|
|
|
|
types.forEach(item => {
|
|
|
|
|
if (item === "blockRef") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.element.querySelector(`[data-type="${item}"]`).classList.add("protyle-toolbar__item--current");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getCurrentType(range = this.range) {
|
2022-09-13 10:19:13 +08:00
|
|
|
let types: string[] = [];
|
2022-05-26 15:18:53 +08:00
|
|
|
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 [];
|
|
|
|
|
}
|
2022-09-13 10:19:13 +08:00
|
|
|
if (!["DIV", "TD", "TH"].includes(startElement.tagName)) {
|
|
|
|
|
types = startElement.getAttribute("data-type").split(" ");
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
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 [];
|
|
|
|
|
}
|
2022-09-13 10:19:13 +08:00
|
|
|
if (!["DIV", "TD", "TH"].includes(endElement.tagName)) {
|
|
|
|
|
types = types.concat(endElement.getAttribute("data-type").split(" "));
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
if (range.startOffset === range.startContainer.textContent.length) {
|
|
|
|
|
const nextSibling = hasNextSibling(range.startContainer as Element);
|
|
|
|
|
if (nextSibling && nextSibling.nodeType !== 3 && (nextSibling as Element).getAttribute("data-type") === "inline-math") {
|
|
|
|
|
types.push("inline-math");
|
|
|
|
|
}
|
|
|
|
|
} else if (range.endOffset === 0) {
|
|
|
|
|
const previousSibling = hasPreviousSibling(range.startContainer as Element);
|
|
|
|
|
if (previousSibling && previousSibling.nodeType !== 3 && (previousSibling as Element).getAttribute("data-type") === "inline-math") {
|
|
|
|
|
types.push("inline-math");
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-13 10:19:13 +08:00
|
|
|
range.cloneContents().childNodes.forEach((item:HTMLElement) => {
|
|
|
|
|
if (item.nodeType !== 3) {
|
2022-09-13 10:26:15 +08:00
|
|
|
types = types.concat(item.getAttribute("data-type").split(" "));
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
2022-09-13 10:19:13 +08:00
|
|
|
});
|
|
|
|
|
types = [...new Set(types)];
|
2022-05-26 15:18:53 +08:00
|
|
|
return types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private genItem(protyle: IProtyle, menuItem: IMenuItem) {
|
|
|
|
|
let menuItemObj;
|
|
|
|
|
switch (menuItem.name) {
|
2022-09-13 10:19:13 +08:00
|
|
|
case "strong":
|
|
|
|
|
case "em":
|
|
|
|
|
case "s":
|
|
|
|
|
case "code":
|
2022-05-26 15:18:53 +08:00
|
|
|
case "mark":
|
|
|
|
|
case "tag":
|
2022-09-13 10:19:13 +08:00
|
|
|
case "u":
|
2022-05-26 15:18:53 +08:00
|
|
|
case "sup":
|
|
|
|
|
case "sub":
|
|
|
|
|
case "kbd":
|
|
|
|
|
case "inline-math":
|
|
|
|
|
menuItemObj = new ToolbarItem(protyle, menuItem);
|
|
|
|
|
break;
|
2022-09-13 10:19:13 +08:00
|
|
|
case "block-ref":
|
2022-05-26 15:18:53 +08:00
|
|
|
menuItemObj = new BlockRef(protyle, menuItem);
|
|
|
|
|
break;
|
|
|
|
|
case "|":
|
|
|
|
|
menuItemObj = new Divider();
|
|
|
|
|
break;
|
2022-09-13 10:19:13 +08:00
|
|
|
case "text":
|
2022-05-26 15:18:53 +08:00
|
|
|
menuItemObj = new Font(protyle, menuItem);
|
|
|
|
|
break;
|
2022-09-13 10:19:13 +08:00
|
|
|
case "a":
|
2022-05-26 15:18:53 +08:00
|
|
|
menuItemObj = new Link(protyle, menuItem);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!menuItemObj) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
return menuItemObj.element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private pushNode(newNodes: Node[], element: Element | DocumentFragment) {
|
|
|
|
|
element.childNodes.forEach((item: Element) => {
|
|
|
|
|
if (item.nodeType !== 3 && (
|
|
|
|
|
(item.getAttribute("data-type") === "inline-math" && item.textContent !== "") ||
|
|
|
|
|
item.tagName === "BR" || item.getAttribute("data-type") === "backslash"
|
|
|
|
|
)) {
|
|
|
|
|
// 软换行、数学公式、转移符不能消失
|
|
|
|
|
newNodes.push(item.cloneNode(true));
|
|
|
|
|
} else {
|
|
|
|
|
if (item.textContent === "") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
newNodes.push(document.createTextNode(item.textContent));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-13 10:19:13 +08:00
|
|
|
public setInlineMark(protyle: IProtyle, type: string, action: "remove" | "add" | "range" | "toolbar", focusAdd = false) {
|
|
|
|
|
const nodeElement = hasClosestBlock(this.range.startContainer);
|
|
|
|
|
if (!nodeElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const wbrElement = document.createElement("wbr");
|
|
|
|
|
const html = nodeElement.outerHTML;
|
|
|
|
|
|
|
|
|
|
if (this.range.startOffset === 0 && !hasPreviousSibling(this.range.startContainer)) {
|
|
|
|
|
this.range.setStartBefore(this.range.startContainer.parentElement)
|
|
|
|
|
}
|
|
|
|
|
if (this.range.endOffset === this.range.endContainer.textContent.length && !hasNextSibling(this.range.endContainer)) {
|
|
|
|
|
this.range.setEndAfter(this.range.endContainer.parentElement)
|
|
|
|
|
}
|
|
|
|
|
const actionBtn = action === "toolbar" ? this.element.querySelector(`[data-type="${type}"]`) : undefined;
|
|
|
|
|
const contents = this.range.extractContents();
|
|
|
|
|
this.range.insertNode(wbrElement);
|
|
|
|
|
const newNodes: Node[] = [];
|
|
|
|
|
contents.childNodes.forEach((item: HTMLElement) => {
|
|
|
|
|
if (item.nodeType === 3) {
|
|
|
|
|
if (item.textContent !== "") {
|
|
|
|
|
const inlineElement = document.createElement("span");
|
|
|
|
|
inlineElement.setAttribute("data-type", type);
|
|
|
|
|
inlineElement.appendChild(item);
|
|
|
|
|
newNodes.push(inlineElement);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const types = (item.getAttribute("data-type") || "").split(" ");
|
|
|
|
|
types.push(type);
|
|
|
|
|
item.setAttribute("data-type", types.join(" "));
|
|
|
|
|
newNodes.push(item);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
newNodes.forEach((item, index) => {
|
|
|
|
|
this.range.insertNode(item);
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
this.range.setStart(item.firstChild, 0);
|
|
|
|
|
}
|
|
|
|
|
if (index === newNodes.length - 1) {
|
|
|
|
|
this.range.setEnd(item.lastChild, item.lastChild.textContent.length);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
|
|
|
|
|
updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, html);
|
|
|
|
|
wbrElement.remove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async setInlineMark1(protyle: IProtyle, type: string, action: "remove" | "add" | "range" | "toolbar", focusAdd = false) {
|
2022-05-26 15:18:53 +08:00
|
|
|
const nodeElement = hasClosestBlock(this.range.startContainer);
|
|
|
|
|
if (!nodeElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const types = this.getCurrentType();
|
|
|
|
|
if (action === "add" && types.length > 0 && types.includes(type) && !focusAdd) {
|
|
|
|
|
if (type === "link") {
|
2022-06-01 23:52:22 +08:00
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
linkMenu(protyle, this.range.startContainer.parentElement);
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 对已有字体样式的文字再次添加字体样式
|
2022-09-13 10:19:13 +08:00
|
|
|
if (focusAdd && action === "add" && types.includes("text") && this.range.startContainer.nodeType === 3 &&
|
2022-05-30 09:51:16 +08:00
|
|
|
this.range.startContainer.parentNode.isSameNode(this.range.endContainer.parentNode)) {
|
2022-05-26 15:18:53 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let startElement = this.range.startContainer as Element;
|
|
|
|
|
if (this.range.startContainer.nodeType === 3) {
|
|
|
|
|
startElement = this.range.startContainer.parentElement;
|
|
|
|
|
if (startElement.getAttribute("data-type") === "virtual-block-ref" && !["DIV", "TD", "TH"].includes(startElement.parentElement.tagName)) {
|
|
|
|
|
startElement = startElement.parentElement;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// table 选中处理
|
|
|
|
|
const tableElement = hasClosestByAttribute(startElement, "data-type", "NodeTable");
|
|
|
|
|
if (this.range.toString() !== "" && tableElement && this.range.commonAncestorContainer.nodeType !== 3) {
|
|
|
|
|
const parentTag = (this.range.commonAncestorContainer as Element).tagName;
|
|
|
|
|
if (parentTag !== "TH" && parentTag !== "TD") {
|
|
|
|
|
const startCellElement = hasClosestByMatchTag(startElement, "TD") || hasClosestByMatchTag(startElement, "TH");
|
|
|
|
|
const endCellElement = hasClosestByMatchTag(this.range.endContainer, "TD") || hasClosestByMatchTag(this.range.endContainer, "TH");
|
|
|
|
|
if (!startCellElement && !endCellElement) {
|
|
|
|
|
const cellElement = tableElement.querySelector("th") || tableElement.querySelector("td");
|
|
|
|
|
this.range.setStartBefore(cellElement.firstChild);
|
|
|
|
|
this.range.setEndAfter(cellElement.lastChild);
|
|
|
|
|
startElement = cellElement;
|
|
|
|
|
} else if (startCellElement &&
|
|
|
|
|
// 不能包含自身元素,否则对 cell 中的部分文字两次高亮后就会选中整个 cell。 https://github.com/siyuan-note/siyuan/issues/3649 第二点
|
|
|
|
|
!startCellElement.contains(this.range.endContainer)) {
|
|
|
|
|
const cloneRange = this.range.cloneRange();
|
|
|
|
|
this.range.setEndAfter(startCellElement.lastChild);
|
|
|
|
|
if (this.range.toString() === "" && endCellElement) {
|
|
|
|
|
this.range.setEnd(cloneRange.endContainer, cloneRange.endOffset);
|
|
|
|
|
this.range.setStartBefore(endCellElement.lastChild);
|
|
|
|
|
}
|
|
|
|
|
if (this.range.toString() === "") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.range.toString() === "" && action === "range" && getSelectionOffset(startElement, protyle.wysiwyg.element).end === startElement.textContent.length &&
|
|
|
|
|
this.range.startContainer.nodeType === 3 && !this.range.startContainer.parentElement.getAttribute("contenteditable") &&
|
|
|
|
|
types.length > 0) {
|
|
|
|
|
// 跳出行内元素
|
|
|
|
|
const textNode = document.createTextNode(Constants.ZWSP);
|
|
|
|
|
this.range.startContainer.parentElement.after(textNode);
|
|
|
|
|
this.range.selectNodeContents(textNode);
|
|
|
|
|
this.range.collapse(false);
|
|
|
|
|
if (types.includes(type)) {
|
|
|
|
|
// 如果不是同一种行内元素,需进行后续的渲染操作
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (types.length > 0 && types.includes("link") && action === "range") {
|
|
|
|
|
// 链接快捷键不应取消,应该显示链接信息
|
2022-06-01 23:52:22 +08:00
|
|
|
linkMenu(protyle, this.range.startContainer.parentElement);
|
2022-05-26 15:18:53 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const wbrElement = document.createElement("wbr");
|
|
|
|
|
this.range.insertNode(wbrElement);
|
|
|
|
|
this.range.setStartAfter(wbrElement);
|
|
|
|
|
const html = nodeElement.outerHTML;
|
|
|
|
|
const actionBtn = action === "toolbar" ? this.element.querySelector(`[data-type="${type}"]`) : undefined;
|
|
|
|
|
// 光标前标签移除
|
|
|
|
|
const newNodes: Node[] = [];
|
|
|
|
|
let startText = "";
|
|
|
|
|
if (!["DIV", "TD", "TH"].includes(startElement.tagName)) {
|
|
|
|
|
startText = startElement.textContent;
|
|
|
|
|
this.pushNode(newNodes, startElement);
|
|
|
|
|
startElement.remove();
|
|
|
|
|
}
|
|
|
|
|
// 光标后标签移除
|
|
|
|
|
let endText = "";
|
|
|
|
|
let endClone;
|
|
|
|
|
if (!this.range.startContainer.isSameNode(this.range.endContainer)) {
|
|
|
|
|
let endElement = this.range.endContainer as HTMLElement;
|
|
|
|
|
if (this.range.endContainer.nodeType === 3) {
|
|
|
|
|
endElement = this.range.endContainer.parentElement;
|
|
|
|
|
}
|
|
|
|
|
if (endElement.getAttribute("data-type") === "virtual-block-ref" && !["DIV", "TD", "TH"].includes(endElement.parentElement.tagName)) {
|
|
|
|
|
endElement = endElement.parentElement;
|
|
|
|
|
}
|
|
|
|
|
if (!["DIV", "TD", "TH"].includes(endElement.tagName)) {
|
|
|
|
|
endClone = endElement;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const selectContents = this.range.extractContents();
|
|
|
|
|
this.pushNode(newNodes, selectContents);
|
|
|
|
|
if (endClone) {
|
|
|
|
|
endText = endClone.textContent;
|
|
|
|
|
this.pushNode(newNodes, endClone);
|
|
|
|
|
endClone.remove();
|
|
|
|
|
}
|
|
|
|
|
if ((action === "toolbar" && actionBtn.classList.contains("protyle-toolbar__item--current")) ||
|
|
|
|
|
action === "remove" || (action === "range" && types.length > 0 && types.includes(type))) {
|
|
|
|
|
// 移除
|
|
|
|
|
if (type === "inline-math" && newNodes.length === 1) {
|
|
|
|
|
const textNode = document.createTextNode(newNodes[0].textContent);
|
|
|
|
|
this.range.insertNode(textNode);
|
|
|
|
|
this.range.selectNodeContents(textNode);
|
|
|
|
|
} else {
|
|
|
|
|
newNodes.forEach((item, index) => {
|
|
|
|
|
this.range.insertNode(item);
|
|
|
|
|
if (index !== newNodes.length - 1) {
|
|
|
|
|
this.range.collapse(false);
|
|
|
|
|
} else {
|
|
|
|
|
this.range.setEnd(item, item.textContent.length);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (newNodes.length > 0) {
|
|
|
|
|
this.range.setStart(newNodes[0], 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => {
|
|
|
|
|
item.classList.remove("protyle-toolbar__item--current");
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
if (newNodes.length === 0) {
|
|
|
|
|
newNodes.push(document.createTextNode(Constants.ZWSP));
|
|
|
|
|
}
|
|
|
|
|
// 添加
|
|
|
|
|
let newElement: Element;
|
|
|
|
|
const refText = startText + selectContents.textContent + endText;
|
|
|
|
|
const refNode = document.createTextNode(refText);
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "bold":
|
|
|
|
|
newElement = document.createElement("strong");
|
|
|
|
|
break;
|
|
|
|
|
case "underline":
|
|
|
|
|
newElement = document.createElement("u");
|
|
|
|
|
break;
|
|
|
|
|
case "italic":
|
|
|
|
|
newElement = document.createElement("em");
|
|
|
|
|
break;
|
|
|
|
|
case "strike":
|
|
|
|
|
newElement = document.createElement("s");
|
|
|
|
|
break;
|
|
|
|
|
case "inline-code":
|
|
|
|
|
newElement = document.createElement("code");
|
|
|
|
|
break;
|
|
|
|
|
case "mark":
|
|
|
|
|
newElement = document.createElement("mark");
|
|
|
|
|
break;
|
|
|
|
|
case "sup":
|
|
|
|
|
newElement = document.createElement("sup");
|
|
|
|
|
break;
|
|
|
|
|
case "sub":
|
|
|
|
|
newElement = document.createElement("sub");
|
|
|
|
|
break;
|
|
|
|
|
case "kbd":
|
|
|
|
|
newElement = document.createElement("kbd");
|
|
|
|
|
break;
|
|
|
|
|
case "tag":
|
|
|
|
|
newElement = document.createElement("span");
|
|
|
|
|
newElement.setAttribute("data-type", "tag");
|
|
|
|
|
break;
|
|
|
|
|
case "link":
|
|
|
|
|
newElement = document.createElement("span");
|
|
|
|
|
newElement.setAttribute("data-type", "a");
|
|
|
|
|
break;
|
|
|
|
|
case "blockRef":
|
|
|
|
|
if (refText === "") {
|
|
|
|
|
wbrElement.remove();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.range.insertNode(refNode);
|
|
|
|
|
this.range.selectNodeContents(refNode);
|
|
|
|
|
hintRef(refText, protyle, true);
|
|
|
|
|
break;
|
|
|
|
|
case "inline-math":
|
|
|
|
|
newElement = document.createElement("span");
|
|
|
|
|
newElement.className = "render-node";
|
|
|
|
|
newElement.setAttribute("contenteditable", "false");
|
|
|
|
|
newElement.setAttribute("data-type", "inline-math");
|
|
|
|
|
newElement.setAttribute("data-subtype", "math");
|
|
|
|
|
newElement.setAttribute("data-content", startText + selectContents.textContent + endText);
|
|
|
|
|
mathRender(newElement);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (newElement) {
|
|
|
|
|
this.range.insertNode(newElement);
|
|
|
|
|
}
|
|
|
|
|
if (type === "inline-math") {
|
|
|
|
|
this.range.setStartAfter(newElement);
|
|
|
|
|
this.range.collapse(true);
|
|
|
|
|
if (startText + selectContents.textContent + endText === "") {
|
|
|
|
|
this.showRender(protyle, newElement as HTMLElement);
|
|
|
|
|
} else {
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
} else if (type !== "blockRef") {
|
|
|
|
|
newNodes.forEach(item => {
|
|
|
|
|
newElement.append(item);
|
|
|
|
|
});
|
|
|
|
|
if (newElement.textContent === Constants.ZWSP) {
|
|
|
|
|
this.isNewEmptyInline = true;
|
|
|
|
|
this.range.setStart(newElement.firstChild, 1);
|
|
|
|
|
this.range.collapse(true);
|
|
|
|
|
} else {
|
|
|
|
|
if (!hasPreviousSibling(newElement)) {
|
|
|
|
|
// 列表内斜体后的最后一个字符无法选中 https://ld246.com/article/1629787455575
|
|
|
|
|
const nextSibling = hasNextSibling(newElement);
|
|
|
|
|
if (nextSibling && nextSibling.nodeType === 3) {
|
|
|
|
|
const textContent = nextSibling.textContent;
|
|
|
|
|
nextSibling.textContent = "";
|
|
|
|
|
nextSibling.textContent = textContent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.range.setStart(newElement.firstChild, 0);
|
|
|
|
|
this.range.setEnd(newElement.lastChild, newElement.lastChild.textContent.length);
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
|
|
|
|
if (type === "link") {
|
|
|
|
|
let needShowLink = true;
|
|
|
|
|
let focusText = false;
|
|
|
|
|
try {
|
|
|
|
|
const clipText = await navigator.clipboard.readText();
|
|
|
|
|
// 选中链接时需忽略剪切板内容 https://ld246.com/article/1643035329737
|
|
|
|
|
if (protyle.lute.IsValidLinkDest(this.range.toString().trim())) {
|
|
|
|
|
(newElement as HTMLElement).setAttribute("data-href", this.range.toString().trim());
|
|
|
|
|
needShowLink = false;
|
|
|
|
|
} else if (protyle.lute.IsValidLinkDest(clipText)) {
|
|
|
|
|
(newElement as HTMLElement).setAttribute("data-href", clipText);
|
|
|
|
|
if (newElement.textContent.replace(Constants.ZWSP, "") !== "") {
|
|
|
|
|
needShowLink = false;
|
|
|
|
|
}
|
|
|
|
|
focusText = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(e);
|
|
|
|
|
}
|
|
|
|
|
if (needShowLink) {
|
2022-06-01 23:52:22 +08:00
|
|
|
linkMenu(protyle, newElement as HTMLElement, focusText);
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (actionBtn) {
|
|
|
|
|
this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => {
|
|
|
|
|
item.classList.remove("protyle-toolbar__item--current");
|
|
|
|
|
});
|
|
|
|
|
actionBtn.classList.add("protyle-toolbar__item--current");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
|
|
|
|
|
updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, html);
|
|
|
|
|
wbrElement.remove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showFileAnnotationRef(protyle: IProtyle, refElement: HTMLElement) {
|
|
|
|
|
const nodeElement = hasClosestBlock(refElement);
|
|
|
|
|
if (!nodeElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const id = nodeElement.getAttribute("data-node-id");
|
|
|
|
|
let html = nodeElement.outerHTML;
|
|
|
|
|
this.subElement.style.width = isMobile() ? "80vw" : Math.min(480, window.innerWidth) + "px";
|
|
|
|
|
this.subElement.style.padding = "";
|
|
|
|
|
this.subElement.innerHTML = `<div class="b3-form__space--small">
|
|
|
|
|
<label class="fn__flex">
|
|
|
|
|
<span class="ft__on-surface fn__flex-center" style="width: 64px">ID</span>
|
|
|
|
|
<div class="fn__space"></div>
|
|
|
|
|
<input data-type="id" value="${refElement.getAttribute("data-id") || ""}" class="b3-text-field fn__block" readonly />
|
|
|
|
|
</label>
|
|
|
|
|
<div class="fn__hr"></div>
|
|
|
|
|
<label class="fn__flex">
|
|
|
|
|
<span class="ft__on-surface fn__flex-center" style="width: 64px">${window.siyuan.languages.anchor}</span>
|
|
|
|
|
<div class="fn__space"></div>
|
|
|
|
|
<input data-type="anchor" class="b3-text-field fn__block" placeholder="${window.siyuan.languages.anchor}" />
|
|
|
|
|
</label>
|
|
|
|
|
<div class="fn__hr"></div>
|
|
|
|
|
<div class="fn__hr"></div>
|
|
|
|
|
<div class="fn__flex"><span class="fn__flex-1"></span>
|
|
|
|
|
<button class="b3-button b3-button--cancel">${window.siyuan.languages.remove}</button>
|
|
|
|
|
</div></div>`;
|
|
|
|
|
this.subElement.querySelector(".b3-button--cancel").addEventListener(getEventName(), () => {
|
|
|
|
|
refElement.insertAdjacentHTML("afterend", "<wbr>");
|
|
|
|
|
const oldHTML = nodeElement.outerHTML;
|
|
|
|
|
refElement.outerHTML = refElement.textContent;
|
|
|
|
|
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
|
|
|
|
|
updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML);
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
|
|
|
|
focusByWbr(nodeElement, this.range);
|
|
|
|
|
});
|
|
|
|
|
const anchorElement = this.subElement.querySelector('[data-type="anchor"]') as HTMLInputElement;
|
|
|
|
|
if (refElement.getAttribute("data-subtype") === "s") {
|
|
|
|
|
anchorElement.value = refElement.textContent;
|
|
|
|
|
}
|
|
|
|
|
anchorElement.addEventListener("change", (event) => {
|
|
|
|
|
refElement.after(document.createElement("wbr"));
|
|
|
|
|
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
|
|
|
|
|
updateTransaction(protyle, id, nodeElement.outerHTML, html);
|
|
|
|
|
html = nodeElement.outerHTML;
|
|
|
|
|
nodeElement.querySelector("wbr").remove();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
});
|
|
|
|
|
anchorElement.addEventListener("input", (event) => {
|
|
|
|
|
const target = event.target as HTMLInputElement;
|
|
|
|
|
if (target.value) {
|
|
|
|
|
refElement.innerHTML = Lute.EscapeHTMLStr(target.value);
|
|
|
|
|
} else {
|
|
|
|
|
refElement.innerHTML = "*";
|
|
|
|
|
}
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
});
|
|
|
|
|
anchorElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (event.key === "Enter" || event.key === "Escape") {
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
|
|
|
|
this.range.setStart(refElement.firstChild, 0);
|
|
|
|
|
this.range.setEnd(refElement.lastChild, refElement.lastChild.textContent.length);
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.subElement.classList.remove("fn__none");
|
|
|
|
|
const nodeRect = refElement.getBoundingClientRect();
|
|
|
|
|
setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height + 4);
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
anchorElement.select();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showRender(protyle: IProtyle, renderElement: Element) {
|
|
|
|
|
const nodeElement = hasClosestBlock(renderElement);
|
|
|
|
|
if (!nodeElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const id = nodeElement.getAttribute("data-node-id");
|
|
|
|
|
const type = renderElement.getAttribute("data-type");
|
2022-08-19 10:37:26 +08:00
|
|
|
let html = protyle.lute.SpinBlockDOM(nodeElement.outerHTML);
|
2022-05-26 15:18:53 +08:00
|
|
|
let title = "HTML";
|
|
|
|
|
let placeholder = "";
|
|
|
|
|
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 (type === "NodeMathBlock") {
|
|
|
|
|
title = window.siyuan.languages.math;
|
|
|
|
|
} else {
|
|
|
|
|
title = window.siyuan.languages["inline-math"];
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (type === "NodeBlockQueryEmbed") {
|
|
|
|
|
title = window.siyuan.languages.blockEmbed;
|
|
|
|
|
}
|
2022-06-20 21:47:12 +08:00
|
|
|
const isPin = this.subElement.querySelector('[data-type="pin"]')?.classList.contains("block__icon--active");
|
2022-05-26 15:18:53 +08:00
|
|
|
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 {
|
2022-09-03 11:20:36 +08:00
|
|
|
this.subElement.style.width = isMobile() ? "100vw" : "";
|
2022-05-26 15:18:53 +08:00
|
|
|
this.subElement.style.padding = "0";
|
|
|
|
|
}
|
|
|
|
|
this.subElement.innerHTML = `<div ${(isPin && this.subElement.firstElementChild.getAttribute("data-drag") === "true") ? 'data-drag="true"' : ""} class="block__popover--move"><div class="block__icons block__icons--border fn__flex">
|
|
|
|
|
${title}
|
|
|
|
|
<span class="fn__flex-1"></span>
|
|
|
|
|
<label aria-label="${window.siyuan.languages.hideHeadingBelowBlocks}" style="overflow:inherit;" class="b3-tooltips b3-tooltips__nw${type !== "NodeBlockQueryEmbed" ? " fn__none" : ""}">
|
|
|
|
|
<input type="checkbox" class="b3-switch">
|
|
|
|
|
<span class="fn__space"></span>
|
|
|
|
|
</label>
|
2022-06-20 21:47:12 +08:00
|
|
|
<button data-type="refresh" class="block__icon b3-tooltips b3-tooltips__nw${(isPin && !this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active")) ? "" : " block__icon--active"}${type === "NodeBlockQueryEmbed" ? " fn__none" : ""}" aria-label="${window.siyuan.languages.refresh}"><svg><use xlink:href="#iconRefresh"></use></svg></button>
|
2022-05-26 15:18:53 +08:00
|
|
|
<span class="fn__space"></span>
|
|
|
|
|
<button data-type="before" class="block__icon b3-tooltips b3-tooltips__nw" aria-label="${window.siyuan.languages["insert-before"]}"><svg><use xlink:href="#iconBefore"></use></svg></button>
|
|
|
|
|
<span class="fn__space"></span>
|
|
|
|
|
<button data-type="after" class="block__icon b3-tooltips b3-tooltips__nw" aria-label="${window.siyuan.languages["insert-after"]}"><svg><use xlink:href="#iconAfter"></use></svg></button>
|
|
|
|
|
<span class="fn__space"></span>
|
|
|
|
|
<button data-type="copy" class="block__icon b3-tooltips b3-tooltips__nw${isBrowser() ? " fn__none" : ""}" aria-label="${window.siyuan.languages.copy} PNG"><svg><use xlink:href="#iconCopy"></use></svg></button>
|
|
|
|
|
<span class="fn__space"></span>
|
2022-06-20 21:47:12 +08:00
|
|
|
<button data-type="pin" class="block__icon b3-tooltips b3-tooltips__nw${isPin ? " block__icon--active" : ""}" aria-label="${window.siyuan.languages.pin}"><svg><use xlink:href="#iconPin"></use></svg></button>
|
2022-05-26 15:18:53 +08:00
|
|
|
<span class="fn__space"></span>
|
|
|
|
|
<button data-type="close" class="block__icon b3-tooltips b3-tooltips__nw" aria-label="${window.siyuan.languages.close}"><svg style="width: 10px"><use xlink:href="#iconClose"></use></svg></button>
|
|
|
|
|
</div>
|
2022-09-03 11:22:59 +08:00
|
|
|
<textarea spellcheck="false" class="b3-text-field b3-text-field--text fn__block" placeholder="${placeholder}" style="width:${isMobile() ? "100vw" : Math.max(480, renderElement.clientWidth * 0.7) + "px"};max-height:50vh"></textarea></div>`;
|
2022-05-26 15:18:53 +08:00
|
|
|
const autoHeight = () => {
|
|
|
|
|
textElement.style.height = textElement.scrollHeight + "px";
|
2022-09-03 11:20:36 +08:00
|
|
|
if (isMobile()) {
|
|
|
|
|
setPosition(this.subElement, 0, 0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (this.subElement.clientHeight <= window.innerHeight - nodeRect.bottom || this.subElement.clientHeight <= nodeRect.top) {
|
|
|
|
|
if (type === "inline-math") {
|
|
|
|
|
setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height);
|
|
|
|
|
} else {
|
|
|
|
|
setPosition(this.subElement, nodeRect.left + (nodeRect.width - this.subElement.clientWidth) / 2, nodeRect.bottom, nodeRect.height);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setPosition(this.subElement, nodeRect.right, nodeRect.bottom);
|
|
|
|
|
}
|
|
|
|
|
};
|
2022-08-21 17:38:09 +08:00
|
|
|
const headerElement = this.subElement.querySelector(".block__icons");
|
2022-08-19 14:39:19 +08:00
|
|
|
headerElement.addEventListener("click", (event: MouseEvent) => {
|
2022-05-26 15:18:53 +08:00
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
const btnElement = hasClosestByClassName(target, "b3-tooltips");
|
|
|
|
|
if (!btnElement) {
|
2022-08-19 14:39:19 +08:00
|
|
|
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();
|
2022-08-19 17:34:47 +08:00
|
|
|
event.stopPropagation();
|
2022-08-19 14:39:19 +08:00
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
switch (btnElement.getAttribute("data-type")) {
|
|
|
|
|
case "close":
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
2022-06-20 21:47:12 +08:00
|
|
|
this.subElement.querySelector('[data-type="pin"]').classList.remove("block__icon--active");
|
2022-05-26 15:18:53 +08:00
|
|
|
break;
|
|
|
|
|
case "pin":
|
2022-08-19 14:39:19 +08:00
|
|
|
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);
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
break;
|
|
|
|
|
case "refresh":
|
2022-06-20 21:47:12 +08:00
|
|
|
btnElement.classList.toggle("block__icon--active");
|
2022-05-26 15:18:53 +08:00
|
|
|
break;
|
|
|
|
|
case "before":
|
|
|
|
|
insertEmptyBlock(protyle, "beforebegin", id);
|
|
|
|
|
hideElements(["util"], protyle);
|
|
|
|
|
break;
|
|
|
|
|
case "after":
|
|
|
|
|
insertEmptyBlock(protyle, "afterend", id);
|
|
|
|
|
hideElements(["util"], protyle);
|
|
|
|
|
break;
|
|
|
|
|
case "copy":
|
|
|
|
|
/// #if !BROWSER
|
|
|
|
|
hideElements(["util"], protyle);
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const rect = renderElement.getBoundingClientRect();
|
|
|
|
|
getCurrentWindow().webContents.capturePage({
|
|
|
|
|
x: Math.floor(rect.x),
|
|
|
|
|
y: Math.floor(rect.y) - 4, // 行内数学公式头部截不到
|
|
|
|
|
width: Math.floor(rect.width),
|
|
|
|
|
height: Math.floor(rect.height) + 4
|
|
|
|
|
}).then((image: NativeImage) => {
|
|
|
|
|
clipboard.writeImage(nativeImage.createFromBuffer(image.toPNG()));
|
|
|
|
|
});
|
|
|
|
|
}, 100);
|
|
|
|
|
/// #endif
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-08-19 14:39:19 +08:00
|
|
|
headerElement.addEventListener("mousedown", (event: MouseEvent) => {
|
2022-05-26 15:18:53 +08:00
|
|
|
if (hasClosestByClassName(event.target as HTMLElement, "block__icon")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const documentSelf = document;
|
|
|
|
|
this.subElement.style.userSelect = "none";
|
|
|
|
|
const x = event.clientX - parseInt(this.subElement.style.left);
|
|
|
|
|
const y = event.clientY - parseInt(this.subElement.style.top);
|
2022-08-19 17:34:47 +08:00
|
|
|
documentSelf.onmousemove = (moveEvent: MouseEvent) => {
|
|
|
|
|
let positionX = moveEvent.clientX - x;
|
|
|
|
|
let positionY = moveEvent.clientY - y;
|
|
|
|
|
if (positionX > window.innerWidth - this.subElement.clientWidth) {
|
|
|
|
|
positionX = window.innerWidth - this.subElement.clientWidth;
|
|
|
|
|
}
|
|
|
|
|
if (positionY > window.innerHeight - this.subElement.clientHeight) {
|
|
|
|
|
positionY = window.innerHeight - this.subElement.clientHeight;
|
|
|
|
|
}
|
|
|
|
|
this.subElement.style.left = Math.max(positionX, 0) + "px";
|
|
|
|
|
this.subElement.style.top = Math.max(positionY, Constants.SIZE_TOOLBAR_HEIGHT) + "px";
|
|
|
|
|
this.subElement.firstElementChild.setAttribute("data-drag", "true");
|
|
|
|
|
};
|
|
|
|
|
documentSelf.onmouseup = () => {
|
|
|
|
|
this.subElement.style.userSelect = "auto";
|
|
|
|
|
documentSelf.onmousemove = null;
|
|
|
|
|
documentSelf.onmouseup = null;
|
|
|
|
|
documentSelf.ondragstart = null;
|
|
|
|
|
documentSelf.onselectstart = null;
|
|
|
|
|
documentSelf.onselect = null;
|
|
|
|
|
};
|
2022-05-26 15:18:53 +08:00
|
|
|
return;
|
|
|
|
|
});
|
|
|
|
|
const textElement = this.subElement.querySelector(".b3-text-field") as HTMLTextAreaElement;
|
|
|
|
|
if (type === "NodeHTMLBlock") {
|
|
|
|
|
textElement.value = Lute.UnEscapeHTMLStr(renderElement.querySelector("protyle-html").getAttribute("data-content") || "");
|
|
|
|
|
} else {
|
|
|
|
|
const switchElement = this.subElement.querySelector(".b3-switch") as HTMLInputElement;
|
|
|
|
|
if (nodeElement.getAttribute("custom-heading-mode") === "1") {
|
|
|
|
|
switchElement.checked = true;
|
|
|
|
|
}
|
|
|
|
|
switchElement.addEventListener("change", () => {
|
|
|
|
|
hideElements(["util"], protyle);
|
|
|
|
|
nodeElement.setAttribute("custom-heading-mode", switchElement.checked ? "1" : "0");
|
|
|
|
|
fetchPost("/api/attr/setBlockAttrs", {
|
|
|
|
|
id,
|
|
|
|
|
attrs: {"custom-heading-mode": switchElement.checked ? "1" : "0"}
|
|
|
|
|
});
|
|
|
|
|
renderElement.removeAttribute("data-render");
|
|
|
|
|
blockRender(protyle, renderElement);
|
|
|
|
|
});
|
|
|
|
|
textElement.value = Lute.UnEscapeHTMLStr(renderElement.getAttribute("data-content") || "");
|
|
|
|
|
}
|
2022-06-18 09:50:41 +08:00
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
textElement.addEventListener("input", (event) => {
|
|
|
|
|
if (!renderElement.parentElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (textElement.clientHeight !== textElement.scrollHeight) {
|
|
|
|
|
autoHeight();
|
|
|
|
|
}
|
2022-06-20 21:47:12 +08:00
|
|
|
if (!this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active")) {
|
2022-05-26 15:18:53 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const target = event.target as HTMLTextAreaElement;
|
|
|
|
|
if (type === "NodeHTMLBlock") {
|
|
|
|
|
renderElement.querySelector("protyle-html").setAttribute("data-content", Lute.EscapeHTMLStr(target.value));
|
|
|
|
|
} else {
|
|
|
|
|
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(target.value));
|
|
|
|
|
renderElement.removeAttribute("data-render");
|
|
|
|
|
}
|
|
|
|
|
if (!["NodeBlockQueryEmbed", "NodeHTMLBlock"].includes(type)) {
|
|
|
|
|
processRender(renderElement);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
});
|
|
|
|
|
textElement.addEventListener("change", (event) => {
|
|
|
|
|
if (!renderElement.parentElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-28 10:28:47 +08:00
|
|
|
const target = event.target as HTMLTextAreaElement;
|
2022-06-20 21:47:12 +08:00
|
|
|
if (!this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active")) {
|
2022-05-26 15:18:53 +08:00
|
|
|
if (type === "NodeHTMLBlock") {
|
|
|
|
|
renderElement.querySelector("protyle-html").setAttribute("data-content", Lute.EscapeHTMLStr(target.value));
|
|
|
|
|
} else {
|
|
|
|
|
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(target.value));
|
|
|
|
|
renderElement.removeAttribute("data-render");
|
|
|
|
|
}
|
|
|
|
|
if (!["NodeBlockQueryEmbed", "NodeHTMLBlock"].includes(type)) {
|
|
|
|
|
processRender(renderElement);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (type === "NodeBlockQueryEmbed") {
|
|
|
|
|
blockRender(protyle, renderElement);
|
|
|
|
|
}
|
|
|
|
|
if (this.range) {
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
|
|
|
|
if (type === "inline-math") {
|
|
|
|
|
// 行内数学公式不允许换行 https://github.com/siyuan-note/siyuan/issues/2187
|
|
|
|
|
renderElement.setAttribute("data-content", renderElement.getAttribute("data-content").replace(/\n/g, ""));
|
|
|
|
|
}
|
|
|
|
|
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
|
2022-08-21 17:38:09 +08:00
|
|
|
const newHTML = protyle.lute.SpinBlockDOM(nodeElement.outerHTML);
|
2022-08-28 10:28:47 +08:00
|
|
|
// HTML 块中包含多个 <pre> 时只能保存第一个 https://github.com/siyuan-note/siyuan/issues/5732
|
|
|
|
|
if (type === "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);
|
2022-08-28 10:28:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
2022-08-19 10:37:26 +08:00
|
|
|
updateTransaction(protyle, id, newHTML, html);
|
|
|
|
|
html = newHTML;
|
2022-05-26 15:18:53 +08:00
|
|
|
event.stopPropagation();
|
|
|
|
|
});
|
|
|
|
|
textElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
2022-07-30 17:07:09 +08:00
|
|
|
// 阻止 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;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-06-18 20:19:31 +08:00
|
|
|
if (event.key === "Escape" || matchHotKey("⌘↩", event)) {
|
2022-05-26 15:18:53 +08:00
|
|
|
this.subElement.classList.add("fn__none");
|
2022-06-20 21:47:12 +08:00
|
|
|
this.subElement.querySelector('[data-type="pin"]').classList.remove("block__icon--active");
|
2022-05-26 15:18:53 +08:00
|
|
|
if (renderElement.tagName === "SPAN") {
|
|
|
|
|
const range = getEditorRange(renderElement);
|
|
|
|
|
range.setStartAfter(renderElement);
|
|
|
|
|
range.collapse(true);
|
|
|
|
|
focusByRange(range);
|
|
|
|
|
} else {
|
|
|
|
|
focusSideBlock(renderElement);
|
|
|
|
|
}
|
|
|
|
|
} else if (event.key === "Tab") {
|
2022-06-23 11:22:36 +08:00
|
|
|
// https://github.com/siyuan-note/siyuan/issues/5270
|
|
|
|
|
document.execCommand("insertText", false, "\t");
|
2022-05-26 15:18:53 +08:00
|
|
|
event.preventDefault();
|
2022-08-15 11:44:11 +08:00
|
|
|
} else if (electronUndo(event)) {
|
|
|
|
|
return;
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
textElement.select();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showCodeLanguage(protyle: IProtyle, languageElement: HTMLElement) {
|
|
|
|
|
const nodeElement = hasClosestBlock(languageElement);
|
|
|
|
|
if (!nodeElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.range = getEditorRange(nodeElement);
|
|
|
|
|
const id = nodeElement.getAttribute("data-node-id");
|
|
|
|
|
let oldHtml = nodeElement.outerHTML;
|
|
|
|
|
let html = "";
|
|
|
|
|
Constants.CODE_LANGUAGES.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: 4px 8px 8px 8px" class="b3-text-field"/>
|
|
|
|
|
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
|
|
const inputElement = this.subElement.querySelector("input");
|
|
|
|
|
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
upDownHint(this.subElement.lastElementChild.lastElementChild as HTMLElement, event);
|
|
|
|
|
if (event.key === "Enter") {
|
|
|
|
|
languageElement.textContent = this.subElement.querySelector(".b3-list-item--focus").textContent;
|
|
|
|
|
localStorage.setItem(Constants.LOCAL_CODELANG, languageElement.textContent);
|
|
|
|
|
const editElement = getContenteditableElement(nodeElement);
|
2022-05-29 10:06:02 +08:00
|
|
|
const lineNumber = nodeElement.getAttribute("linenumber");
|
2022-05-26 15:18:53 +08:00
|
|
|
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 mathLanguages: string[] = [];
|
|
|
|
|
Constants.CODE_LANGUAGES.forEach((item) => {
|
|
|
|
|
if (item.indexOf(inputElement.value.toLowerCase()) > -1) {
|
|
|
|
|
mathLanguages.push(item);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
let html = "";
|
|
|
|
|
// sort
|
|
|
|
|
mathLanguages.sort((a, b) => {
|
|
|
|
|
if (a.startsWith(inputElement.value.toLowerCase()) && b.startsWith(inputElement.value.toLowerCase())) {
|
|
|
|
|
if (a.length < b.length) {
|
|
|
|
|
return -1;
|
|
|
|
|
} else if (a.length === b.length) {
|
|
|
|
|
return 0;
|
|
|
|
|
} else {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
} else if (a.startsWith(inputElement.value.toLowerCase())) {
|
|
|
|
|
return -1;
|
|
|
|
|
} else if (b.startsWith(inputElement.value.toLowerCase())) {
|
|
|
|
|
return 1;
|
|
|
|
|
} else {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}).forEach((item) => {
|
|
|
|
|
html += `<div class="b3-list-item">${item.replace(inputElement.value.toLowerCase(), "<b>" + inputElement.value.toLowerCase() + "</b>")}</div>`;
|
|
|
|
|
});
|
|
|
|
|
this.subElement.firstElementChild.lastElementChild.innerHTML = html;
|
|
|
|
|
if (html) {
|
|
|
|
|
this.subElement.firstElementChild.lastElementChild.firstElementChild.classList.add("b3-list-item--focus");
|
|
|
|
|
}
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
});
|
|
|
|
|
this.subElement.lastElementChild.lastElementChild.addEventListener("click", (event) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
const listElement = hasClosestByClassName(target, "b3-list-item");
|
|
|
|
|
if (!listElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
languageElement.textContent = listElement.textContent;
|
|
|
|
|
localStorage.setItem(Constants.LOCAL_CODELANG, languageElement.textContent);
|
|
|
|
|
const nodeElement = hasClosestBlock(languageElement);
|
|
|
|
|
if (nodeElement) {
|
|
|
|
|
const editElement = getContenteditableElement(nodeElement);
|
2022-05-29 10:06:02 +08:00
|
|
|
const lineNumber = nodeElement.getAttribute("linenumber");
|
2022-05-26 15:18:53 +08:00
|
|
|
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.classList.remove("fn__none");
|
|
|
|
|
const nodeRect = languageElement.getBoundingClientRect();
|
|
|
|
|
setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height);
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
inputElement.select();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showTpl(protyle: IProtyle, nodeElement: HTMLElement, range: Range) {
|
|
|
|
|
this.range = range;
|
|
|
|
|
fetchPost("/api/search/searchTemplate", {
|
|
|
|
|
k: "",
|
|
|
|
|
}, (response) => {
|
2022-08-18 18:20:49 +08:00
|
|
|
let html = "";
|
2022-05-26 15:18:53 +08:00
|
|
|
response.data.blocks.forEach((item: { path: string, content: string }, index: number) => {
|
|
|
|
|
html += `<div data-value="${item.path}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item.content}</div>`;
|
|
|
|
|
});
|
2022-08-18 18:20:49 +08:00
|
|
|
if (html === "") {
|
|
|
|
|
html = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
this.subElement.style.width = "";
|
|
|
|
|
this.subElement.style.padding = "";
|
2022-08-18 20:07:58 +08:00
|
|
|
this.subElement.innerHTML = `<div style="max-height:50vh" class="fn__flex">
|
|
|
|
|
<div class="fn__flex-column" style="min-width: 260px;max-width: 100vw">
|
|
|
|
|
<input style="margin: 4px 8px 8px 8px" class="b3-text-field"/>
|
|
|
|
|
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
|
|
|
|
|
</div>
|
2022-08-18 23:44:45 +08:00
|
|
|
<div style="width: 520px;${isMobile() ? "display:none" : ""};overflow: auto;"></div>
|
2022-05-26 15:18:53 +08:00
|
|
|
</div>`;
|
2022-08-18 20:07:58 +08:00
|
|
|
const listElement = this.subElement.querySelector(".b3-list");
|
|
|
|
|
const previewElement = this.subElement.firstElementChild.lastElementChild;
|
2022-08-18 23:47:22 +08:00
|
|
|
previewTemplate(listElement.firstElementChild.getAttribute("data-value"), previewElement, protyle.block.parentID);
|
2022-08-18 20:07:58 +08:00
|
|
|
listElement.addEventListener("mouseover", (event) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
const hoverItemElement = hasClosestByClassName(target, "b3-list-item");
|
|
|
|
|
if (!hoverItemElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-18 23:47:22 +08:00
|
|
|
previewTemplate(hoverItemElement.getAttribute("data-value"), previewElement, protyle.block.parentID);
|
2022-08-18 20:07:58 +08:00
|
|
|
});
|
2022-05-26 15:18:53 +08:00
|
|
|
const inputElement = this.subElement.querySelector("input");
|
|
|
|
|
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-18 23:47:22 +08:00
|
|
|
const isEmpty = !this.subElement.querySelector(".b3-list-item");
|
2022-08-18 16:59:12 +08:00
|
|
|
if (!isEmpty) {
|
2022-08-18 20:07:58 +08:00
|
|
|
const currentElement = upDownHint(listElement, event);
|
|
|
|
|
if (currentElement) {
|
2022-08-18 23:47:22 +08:00
|
|
|
previewTemplate(currentElement.getAttribute("data-value"), previewElement, protyle.block.parentID);
|
2022-08-18 20:07:58 +08:00
|
|
|
}
|
2022-08-18 16:59:12 +08:00
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
if (event.key === "Enter") {
|
2022-08-18 16:59:12 +08:00
|
|
|
if (!isEmpty) {
|
|
|
|
|
hintRenderTemplate(decodeURIComponent(this.subElement.querySelector(".b3-list-item--focus").getAttribute("data-value")), protyle, nodeElement);
|
|
|
|
|
} else {
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
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) => {
|
2022-08-18 18:20:49 +08:00
|
|
|
let searchHTML = "";
|
2022-05-26 15:18:53 +08:00
|
|
|
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>`;
|
|
|
|
|
});
|
2022-08-18 20:07:58 +08:00
|
|
|
listElement.innerHTML = searchHTML || `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
|
2022-08-18 23:44:45 +08:00
|
|
|
previewTemplate(response.data.blocks[0]?.path, previewElement, protyle.block.parentID);
|
2022-05-26 15:18:53 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
this.subElement.lastElementChild.addEventListener("click", (event) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
2022-08-18 16:59:12 +08:00
|
|
|
if (target.classList.contains("b3-list--empty")) {
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
const listElement = hasClosestByClassName(target, "b3-list-item");
|
|
|
|
|
if (!listElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
hintRenderTemplate(decodeURIComponent(listElement.getAttribute("data-value")), protyle, nodeElement);
|
|
|
|
|
});
|
|
|
|
|
const rangePosition = getSelectionPosition(nodeElement, range);
|
|
|
|
|
this.subElement.classList.remove("fn__none");
|
|
|
|
|
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
inputElement.select();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showWidget(protyle: IProtyle, nodeElement: HTMLElement, range: Range) {
|
|
|
|
|
this.range = range;
|
|
|
|
|
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.style.width = "";
|
|
|
|
|
this.subElement.style.padding = "";
|
|
|
|
|
this.subElement.innerHTML = `<div class="fn__flex-column" style="max-height:50vh"><input style="margin: 4px 8px 8px 8px" class="b3-text-field"/>
|
|
|
|
|
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
|
|
const inputElement = this.subElement.querySelector("input");
|
|
|
|
|
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
upDownHint(this.subElement.lastElementChild.lastElementChild as HTMLElement, 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>`;
|
|
|
|
|
});
|
|
|
|
|
this.subElement.firstElementChild.lastElementChild.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);
|
|
|
|
|
});
|
|
|
|
|
const rangePosition = getSelectionPosition(nodeElement, range);
|
|
|
|
|
this.subElement.classList.remove("fn__none");
|
|
|
|
|
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
inputElement.select();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showAssets(protyle: IProtyle, nodeElement: HTMLElement, range: Range) {
|
|
|
|
|
this.range = range;
|
|
|
|
|
fetchPost("/api/search/searchAsset", {
|
|
|
|
|
k: "",
|
|
|
|
|
}, (response) => {
|
|
|
|
|
let html = "";
|
|
|
|
|
response.data.forEach((item: { hName: string, path: string }, index: number) => {
|
2022-07-16 23:20:50 +08:00
|
|
|
html += `<div data-value="${item.path}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item.hName}</div>`;
|
2022-05-26 15:18:53 +08:00
|
|
|
});
|
2022-08-18 20:07:58 +08:00
|
|
|
if (html === "") {
|
|
|
|
|
html = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
this.subElement.style.width = "";
|
|
|
|
|
this.subElement.style.padding = "";
|
2022-07-16 23:20:50 +08:00
|
|
|
this.subElement.innerHTML = `<div style="max-height:50vh" class="fn__flex">
|
2022-07-26 10:48:39 +08:00
|
|
|
<div class="fn__flex-column" style="min-width: 260px;max-width: 100vw">
|
2022-07-16 23:20:50 +08:00
|
|
|
<input style="margin: 4px 8px 8px 8px" class="b3-text-field"/>
|
|
|
|
|
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
|
|
|
|
|
</div>
|
2022-07-26 10:48:39 +08:00
|
|
|
<div style="width: 260px;display: ${isMobile() ? "none" : "flex"};padding: 8px;overflow: auto;justify-content: center;align-items: center;"></div>
|
2022-05-26 15:18:53 +08:00
|
|
|
</div>`;
|
2022-07-16 23:20:50 +08:00
|
|
|
const listElement = this.subElement.querySelector(".b3-list");
|
|
|
|
|
listElement.addEventListener("mouseover", (event) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
const hoverItemElement = hasClosestByClassName(target, "b3-list-item");
|
|
|
|
|
if (!hoverItemElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-07-17 11:06:57 +08:00
|
|
|
previewElement.innerHTML = renderAssetsPreview(hoverItemElement.getAttribute("data-value"));
|
|
|
|
|
});
|
|
|
|
|
const previewElement = this.subElement.firstElementChild.lastElementChild;
|
2022-07-16 23:20:50 +08:00
|
|
|
previewElement.innerHTML = renderAssetsPreview(listElement.firstElementChild.getAttribute("data-value"));
|
2022-05-26 15:18:53 +08:00
|
|
|
const inputElement = this.subElement.querySelector("input");
|
|
|
|
|
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-18 23:47:22 +08:00
|
|
|
const isEmpty = !this.subElement.querySelector(".b3-list-item");
|
2022-08-18 20:07:58 +08:00
|
|
|
if (!isEmpty) {
|
|
|
|
|
const currentElement = upDownHint(listElement, event);
|
|
|
|
|
if (currentElement) {
|
|
|
|
|
previewElement.innerHTML = renderAssetsPreview(currentElement.getAttribute("data-value"));
|
|
|
|
|
}
|
2022-07-16 23:20:50 +08:00
|
|
|
}
|
2022-08-18 20:07:58 +08:00
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
if (event.key === "Enter") {
|
2022-08-18 20:07:58 +08:00
|
|
|
if (!isEmpty) {
|
|
|
|
|
hintRenderAssets(this.subElement.querySelector(".b3-list-item--focus").getAttribute("data-value"), protyle);
|
|
|
|
|
} else {
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
2022-07-16 16:22:02 +08:00
|
|
|
// 空行处插入 mp3 会多一个空的 mp3 块
|
|
|
|
|
event.preventDefault();
|
2022-05-26 15:18:53 +08:00
|
|
|
} else if (event.key === "Escape") {
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
inputElement.addEventListener("input", (event) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
fetchPost("/api/search/searchAsset", {
|
|
|
|
|
k: inputElement.value,
|
|
|
|
|
}, (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" : ""}">${item.hName}</div>`;
|
|
|
|
|
});
|
2022-08-18 20:07:58 +08:00
|
|
|
listElement.innerHTML = searchHTML || `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
|
2022-07-16 23:20:50 +08:00
|
|
|
previewElement.innerHTML = renderAssetsPreview(listElement.firstElementChild.getAttribute("data-value"));
|
2022-05-26 15:18:53 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
this.subElement.lastElementChild.addEventListener("click", (event) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
2022-08-18 20:07:58 +08:00
|
|
|
if (target.classList.contains("b3-list--empty")) {
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-07-16 23:20:50 +08:00
|
|
|
const listItemElement = hasClosestByClassName(target, "b3-list-item");
|
|
|
|
|
if (!listItemElement) {
|
2022-05-26 15:18:53 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2022-07-16 23:20:50 +08:00
|
|
|
hintRenderAssets(listItemElement.getAttribute("data-value"), protyle);
|
2022-05-26 15:18:53 +08:00
|
|
|
});
|
|
|
|
|
const rangePosition = getSelectionPosition(nodeElement, range);
|
|
|
|
|
this.subElement.classList.remove("fn__none");
|
|
|
|
|
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
inputElement.select();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public showFile(protyle: IProtyle, nodeElements: Element[], range: Range) {
|
|
|
|
|
this.range = range;
|
|
|
|
|
fetchPost("/api/filetree/searchDocs", {
|
|
|
|
|
k: "",
|
|
|
|
|
}, (response) => {
|
|
|
|
|
let html = "";
|
|
|
|
|
response.data.forEach((item: { boxIcon: string, box: string, hPath: string, path: string }) => {
|
|
|
|
|
if (item.path === "/") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
html += `<div class="b3-list-item${html === "" ? " b3-list-item--focus" : ""}" data-path="${item.path}" data-box="${item.box}">
|
|
|
|
|
${item.boxIcon ? ('<span class="b3-list-item__icon">' + unicode2Emoji(item.boxIcon) + "</span>") : ""}
|
|
|
|
|
<span class="b3-list-item__text">${escapeHtml(item.hPath)}</span>
|
|
|
|
|
</div>`;
|
|
|
|
|
});
|
|
|
|
|
this.subElement.style.width = "";
|
|
|
|
|
this.subElement.style.padding = "";
|
|
|
|
|
this.subElement.innerHTML = `<div class="fn__flex-column" style="max-height:50vh"><input style="margin: 4px 8px 8px 8px" class="b3-text-field"/>
|
|
|
|
|
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
|
|
const inputElement = this.subElement.querySelector("input");
|
|
|
|
|
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
upDownHint(this.subElement.lastElementChild.lastElementChild as HTMLElement, event);
|
|
|
|
|
if (event.key === "Enter") {
|
|
|
|
|
hintMoveBlock(this.subElement.querySelector(".b3-list-item--focus").getAttribute("data-path"), nodeElements, protyle);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
} else if (event.key === "Escape") {
|
|
|
|
|
this.subElement.classList.add("fn__none");
|
|
|
|
|
focusByRange(this.range);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
inputElement.addEventListener("input", (event) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
fetchPost("/api/filetree/searchDocs", {
|
|
|
|
|
k: inputElement.value,
|
|
|
|
|
}, (response) => {
|
|
|
|
|
let searchHTML = "";
|
|
|
|
|
response.data.forEach((item: { boxIcon: string, box: string, hPath: string, path: string }) => {
|
|
|
|
|
if (item.path === "/") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
searchHTML += `<div class="b3-list-item${searchHTML === "" ? " b3-list-item--focus" : ""}" data-path="${item.path}" data-box="${item.box}">
|
|
|
|
|
${item.boxIcon ? ('<span class="b3-list-item__icon">' + unicode2Emoji(item.boxIcon) + "</span>") : ""}
|
|
|
|
|
<span class="b3-list-item__text">${escapeHtml(item.hPath)}</span>
|
|
|
|
|
</div>`;
|
|
|
|
|
});
|
|
|
|
|
this.subElement.firstElementChild.lastElementChild.innerHTML = searchHTML;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
this.subElement.lastElementChild.addEventListener("click", (event) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
const listElement = hasClosestByClassName(target, "b3-list-item");
|
|
|
|
|
if (!listElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
hintMoveBlock(listElement.getAttribute("data-path"), nodeElements, protyle);
|
|
|
|
|
});
|
|
|
|
|
const rangePosition = getSelectionPosition(nodeElements[0], range);
|
|
|
|
|
this.subElement.classList.remove("fn__none");
|
|
|
|
|
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
|
|
|
|
|
this.element.classList.add("fn__none");
|
|
|
|
|
inputElement.select();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|