2022-05-26 15:18:53 +08:00
|
|
|
import {getIconByType} from "../editor/getIcon";
|
2022-07-31 11:55:15 +08:00
|
|
|
import {hasClosestByMatchTag, hasClosestByTag} from "../protyle/util/hasClosest";
|
2022-05-26 15:18:53 +08:00
|
|
|
import {isMobile} from "./functions";
|
|
|
|
|
import {mathRender} from "../protyle/markdown/mathRender";
|
2022-05-30 11:51:22 +08:00
|
|
|
import {unicode2Emoji} from "../emoji";
|
|
|
|
|
import {Constants} from "../constants";
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
export class Tree {
|
|
|
|
|
public element: HTMLElement;
|
|
|
|
|
private data: IBlockTree[];
|
|
|
|
|
private blockExtHTML: string;
|
2022-07-31 11:55:15 +08:00
|
|
|
private topExtHTML: string;
|
2022-05-26 15:18:53 +08:00
|
|
|
|
2022-12-07 18:27:24 +08:00
|
|
|
public click: (element: Element, event?: MouseEvent) => void;
|
2022-05-26 15:18:53 +08:00
|
|
|
private ctrlClick: (element: HTMLElement) => void;
|
2022-12-07 18:27:24 +08:00
|
|
|
private toggleClick: (element: Element) => void;
|
2022-05-26 15:18:53 +08:00
|
|
|
private shiftClick: (element: HTMLElement) => void;
|
|
|
|
|
private altClick: (element: HTMLElement) => void;
|
|
|
|
|
private rightClick: (element: HTMLElement, event: MouseEvent) => void;
|
|
|
|
|
|
|
|
|
|
constructor(options: {
|
|
|
|
|
element: HTMLElement,
|
|
|
|
|
data: IBlockTree[],
|
|
|
|
|
blockExtHTML?: string,
|
2022-07-31 11:55:15 +08:00
|
|
|
topExtHTML?: string,
|
2022-05-26 15:18:53 +08:00
|
|
|
click?(element: HTMLElement, event: MouseEvent): void
|
|
|
|
|
ctrlClick?(element: HTMLElement): void
|
|
|
|
|
altClick?(element: HTMLElement): void
|
|
|
|
|
shiftClick?(element: HTMLElement): void
|
2022-09-29 12:48:25 +08:00
|
|
|
toggleClick?(element: HTMLElement): void
|
2022-05-26 15:18:53 +08:00
|
|
|
rightClick?(element: HTMLElement, event: MouseEvent): void
|
|
|
|
|
}) {
|
|
|
|
|
this.click = options.click;
|
|
|
|
|
this.ctrlClick = options.ctrlClick;
|
|
|
|
|
this.altClick = options.altClick;
|
|
|
|
|
this.shiftClick = options.shiftClick;
|
|
|
|
|
this.rightClick = options.rightClick;
|
2022-09-29 12:48:25 +08:00
|
|
|
this.toggleClick = options.toggleClick;
|
2022-05-26 15:18:53 +08:00
|
|
|
this.element = options.element;
|
|
|
|
|
this.blockExtHTML = options.blockExtHTML;
|
2022-07-31 11:55:15 +08:00
|
|
|
this.topExtHTML = options.topExtHTML;
|
2022-05-26 15:18:53 +08:00
|
|
|
this.updateData(options.data);
|
|
|
|
|
this.bindEvent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateData(data: IBlockTree[]) {
|
|
|
|
|
this.data = data;
|
|
|
|
|
if (!this.data || this.data.length === 0) {
|
|
|
|
|
this.element.innerHTML = `<ul class="b3-list b3-list--background"><li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li></ul>`;
|
|
|
|
|
} else {
|
|
|
|
|
this.element.innerHTML = this.genHTML(this.data);
|
|
|
|
|
mathRender(this.element);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private genHTML(data: IBlockTree[]) {
|
|
|
|
|
let html = `<ul${data[0].depth === 0 ? " class='b3-list b3-list--background'" : ""}>`;
|
|
|
|
|
data.forEach((item) => {
|
2022-10-05 21:29:56 +08:00
|
|
|
let titleTip = "";
|
2022-05-26 15:18:53 +08:00
|
|
|
let iconHTML = '<svg class="b3-list-item__graphic"><use xlink:href="#iconFolder"></use></svg>';
|
|
|
|
|
if (item.type === "bookmark") {
|
|
|
|
|
iconHTML = '<svg class="b3-list-item__graphic"><use xlink:href="#iconBookmark"></use></svg>';
|
|
|
|
|
} else if (item.type === "tag") {
|
|
|
|
|
iconHTML = '<svg class="b3-list-item__graphic"><use xlink:href="#iconTags"></use></svg>';
|
|
|
|
|
} else if (item.type === "backlink") {
|
2022-10-18 22:58:46 +08:00
|
|
|
titleTip = ` title="${item.hPath}"`;
|
2022-05-26 15:18:53 +08:00
|
|
|
iconHTML = `<svg class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.nodeType, item.subType)}"></use></svg>`;
|
2022-06-03 11:01:41 +08:00
|
|
|
} else if (item.type === "outline") {
|
2022-10-18 22:58:46 +08:00
|
|
|
titleTip = ` title="${Lute.EscapeHTMLStr(Lute.BlockDOM2Content(item.name))}"`;
|
2022-06-03 11:01:41 +08:00
|
|
|
iconHTML = `<svg class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.nodeType, item.subType)}"></use></svg>`;
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
let countHTML = "";
|
|
|
|
|
if (item.count) {
|
|
|
|
|
countHTML = `<span class="counter">${item.count}</span>`;
|
|
|
|
|
}
|
2022-10-19 21:47:47 +08:00
|
|
|
const hasChild = (item.children && item.children.length > 0) || (item.blocks && item.blocks.length > 0);
|
2023-03-25 16:56:56 +08:00
|
|
|
let style = "";
|
|
|
|
|
if (isMobile()) {
|
|
|
|
|
if (item.depth > 0) {
|
|
|
|
|
style = `padding-left: ${(item.depth - 1) * 29 + 40}px`
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
style = `padding-left: ${(item.depth - 1) * 18 + 22}px;margin-right: 2px`
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
html += `<li class="b3-list-item"
|
2022-12-02 17:37:29 +08:00
|
|
|
${(item.nodeType !== "NodeDocument" && item.type === "backlink") ? 'draggable="true" ' : ""}
|
2022-05-26 15:18:53 +08:00
|
|
|
${item.id ? 'data-node-id="' + item.id + '"' : ""}
|
2022-12-02 17:37:29 +08:00
|
|
|
${item.box ? 'data-notebook-id="' + item.box + '"' : ""}
|
2022-05-26 15:18:53 +08:00
|
|
|
data-treetype="${item.type}"
|
|
|
|
|
data-type="${item.nodeType}"
|
|
|
|
|
data-subtype="${item.subType}"
|
|
|
|
|
${item.label ? "data-label='" + item.label + "'" : ""}>
|
2023-03-25 16:56:56 +08:00
|
|
|
<span style="${style}" class="b3-list-item__toggle${(item.type === "backlink" || hasChild) ? " b3-list-item__toggle--hl" : ""}${hasChild || item.type === "backlink" ? "" : " fn__hidden"}">
|
2022-12-07 18:27:24 +08:00
|
|
|
<svg data-id="${encodeURIComponent(item.name + item.depth)}" class="b3-list-item__arrow${hasChild ? " b3-list-item__arrow--open" : ""}"><use xlink:href="#iconRight"></use></svg>
|
2022-05-26 15:18:53 +08:00
|
|
|
</span>
|
|
|
|
|
${iconHTML}
|
2022-10-05 21:29:56 +08:00
|
|
|
<span class="b3-list-item__text"${titleTip}>${item.name}</span>
|
2022-05-26 15:18:53 +08:00
|
|
|
${countHTML}
|
2022-07-31 11:55:15 +08:00
|
|
|
${this.topExtHTML || ""}
|
2022-05-26 15:18:53 +08:00
|
|
|
</li>`;
|
|
|
|
|
if (item.children && item.children.length > 0) {
|
|
|
|
|
html += this.genHTML(item.children) + "</ul>";
|
|
|
|
|
}
|
|
|
|
|
if (item.blocks && item.blocks.length > 0) {
|
|
|
|
|
html += this.genBlockHTML(item.blocks, true, item.type) + "</ul>";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private genBlockHTML(data: IBlock[], show = false, type: string) {
|
|
|
|
|
let html = `<ul class="${!show ? "fn__none" : ""}">`;
|
|
|
|
|
data.forEach((item: IBlock & {
|
|
|
|
|
subType: string;
|
|
|
|
|
count: string;
|
2022-05-30 11:51:22 +08:00
|
|
|
ial?: {
|
|
|
|
|
icon: string
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}) => {
|
|
|
|
|
let countHTML = "";
|
|
|
|
|
if (item.count) {
|
|
|
|
|
countHTML = `<span class="counter">${item.count}</span>`;
|
|
|
|
|
}
|
2022-05-30 11:51:22 +08:00
|
|
|
let iconHTML;
|
|
|
|
|
if (item.type === "NodeDocument") {
|
2022-05-31 15:51:17 +08:00
|
|
|
iconHTML = `<span data-defids='["${item.defID}"]' class="b3-list-item__graphic popover__block" data-id="${item.id}">${unicode2Emoji(item.ial.icon || Constants.SIYUAN_IMAGE_FILE)}</span>`;
|
2022-05-30 11:51:22 +08:00
|
|
|
} else {
|
2022-05-31 15:51:17 +08:00
|
|
|
iconHTML = `<svg data-defids='["${item.defID}"]' class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.type, item.subType)}"></use></svg>`;
|
2022-05-30 11:51:22 +08:00
|
|
|
}
|
2023-03-25 16:56:56 +08:00
|
|
|
let style = "";
|
|
|
|
|
if (isMobile()) {
|
|
|
|
|
if (item.depth > 0) {
|
|
|
|
|
style = `padding-left: ${(item.depth - 1) * 29 + 40}px`
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
style = `padding-left: ${(item.depth - 1) * 18 + 22}px;margin-right: 2px`
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
html += `<li ${type === "backlink" ? 'draggable="true"' : ""}
|
|
|
|
|
class="b3-list-item ${isMobile() ? "" : "b3-list-item--hide-action"}"
|
|
|
|
|
data-node-id="${item.id}"
|
|
|
|
|
data-ref-text="${encodeURIComponent(item.refText)}"
|
|
|
|
|
data-def-id="${item.defID}"
|
|
|
|
|
data-type="${item.type}"
|
|
|
|
|
data-subtype="${item.subType}"
|
|
|
|
|
data-treetype="${type}"
|
|
|
|
|
data-def-path="${item.defPath}">
|
2023-03-25 16:56:56 +08:00
|
|
|
<span style="${style}" class="b3-list-item__toggle${item.children ? " b3-list-item__toggle--hl" : ""}${item.children ? "" : " fn__hidden"}">
|
2022-12-07 18:27:24 +08:00
|
|
|
<svg data-id="${item.id}" class="b3-list-item__arrow"><use xlink:href="#iconRight"></use></svg>
|
2022-05-26 15:18:53 +08:00
|
|
|
</span>
|
2022-05-30 11:51:22 +08:00
|
|
|
${iconHTML}
|
2022-06-21 11:15:36 +08:00
|
|
|
<span class="b3-list-item__text" ${type === "outline" ? ' title="' + Lute.EscapeHTMLStr(Lute.BlockDOM2Content(item.content)) + '"' : ""}>${item.content}</span>
|
2022-05-26 15:18:53 +08:00
|
|
|
${countHTML}
|
|
|
|
|
${this.blockExtHTML || ""}
|
|
|
|
|
</li>`;
|
|
|
|
|
if (item.children && item.children.length > 0) {
|
|
|
|
|
html += this.genBlockHTML(item.children, false, type) + "</ul>";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return html;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 18:27:24 +08:00
|
|
|
public toggleBlocks(liElement: Element) {
|
2022-09-29 12:48:25 +08:00
|
|
|
if (this.toggleClick) {
|
2022-09-30 00:13:07 +08:00
|
|
|
this.toggleClick(liElement);
|
2022-09-29 12:48:25 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
if (!liElement.nextElementSibling) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const svgElement = liElement.firstElementChild.firstElementChild;
|
|
|
|
|
if (svgElement.classList.contains("b3-list-item__arrow--open")) {
|
|
|
|
|
svgElement.classList.remove("b3-list-item__arrow--open");
|
|
|
|
|
liElement.nextElementSibling.classList.add("fn__none");
|
|
|
|
|
if (liElement.nextElementSibling.nextElementSibling && liElement.nextElementSibling.nextElementSibling.tagName === "UL") {
|
|
|
|
|
liElement.nextElementSibling.nextElementSibling.classList.add("fn__none");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
svgElement.classList.add("b3-list-item__arrow--open");
|
|
|
|
|
liElement.nextElementSibling.classList.remove("fn__none");
|
|
|
|
|
if (liElement.nextElementSibling.nextElementSibling && liElement.nextElementSibling.nextElementSibling.tagName === "UL") {
|
|
|
|
|
liElement.nextElementSibling.nextElementSibling.classList.remove("fn__none");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setCurrent(target: HTMLElement) {
|
|
|
|
|
if (target.classList.contains("b3-list--empty")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.element.querySelectorAll("li").forEach((liItem) => {
|
|
|
|
|
liItem.classList.remove("b3-list-item--focus");
|
|
|
|
|
});
|
|
|
|
|
target.classList.add("b3-list-item--focus");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bindEvent() {
|
|
|
|
|
this.element.addEventListener("contextmenu", (event) => {
|
|
|
|
|
let target = event.target as HTMLElement;
|
|
|
|
|
while (target && !target.isEqualNode(this.element)) {
|
|
|
|
|
if (target.tagName === "LI" && this.rightClick) {
|
|
|
|
|
this.rightClick(target, event);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
target = target.parentElement;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
|
|
|
|
|
let target = event.target as HTMLElement;
|
|
|
|
|
while (target && !target.isEqualNode(this.element)) {
|
2022-12-07 18:27:24 +08:00
|
|
|
if (target.classList.contains("b3-list-item__toggle") && !target.classList.contains("fn__hidden")) {
|
2022-05-26 15:18:53 +08:00
|
|
|
this.toggleBlocks(target.parentElement);
|
|
|
|
|
this.setCurrent(target.parentElement);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-31 11:55:15 +08:00
|
|
|
if (target.classList.contains("b3-list-item__action") && this.click) {
|
|
|
|
|
// 移动端书签父节点删除按钮
|
2022-07-31 18:22:03 +08:00
|
|
|
const liElement = hasClosestByMatchTag(target, "LI");
|
2022-07-31 11:55:15 +08:00
|
|
|
if (liElement) {
|
|
|
|
|
this.click(liElement, event);
|
|
|
|
|
}
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
break;
|
|
|
|
|
} else if (target.tagName === "LI") {
|
2022-05-26 15:18:53 +08:00
|
|
|
this.setCurrent(target);
|
|
|
|
|
if (target.getAttribute("data-node-id") || target.getAttribute("data-treetype") === "tag") {
|
|
|
|
|
if (this.ctrlClick && window.siyuan.ctrlIsPressed) {
|
|
|
|
|
this.ctrlClick(target);
|
|
|
|
|
} else if (this.altClick && window.siyuan.altIsPressed) {
|
|
|
|
|
this.altClick(target);
|
|
|
|
|
} else if (this.shiftClick && window.siyuan.shiftIsPressed) {
|
|
|
|
|
this.shiftClick(target);
|
|
|
|
|
} else if (this.click) {
|
|
|
|
|
this.click(target, event);
|
|
|
|
|
}
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
} else {
|
|
|
|
|
this.toggleBlocks(target);
|
|
|
|
|
}
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
target = target.parentElement;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.element.addEventListener("dragstart", (event: DragEvent & { target: HTMLElement }) => {
|
|
|
|
|
const liElement = hasClosestByTag(event.target, "LI");
|
|
|
|
|
if (liElement) {
|
|
|
|
|
event.dataTransfer.setData("text/html", liElement.outerHTML);
|
2022-09-01 20:24:06 +08:00
|
|
|
// 设置了的话 drop 就无法监听 alt event.dataTransfer.dropEffect = "move";
|
2022-05-26 15:18:53 +08:00
|
|
|
liElement.style.opacity = "0.1";
|
|
|
|
|
window.siyuan.dragElement = liElement;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.element.addEventListener("dragend", (event: DragEvent & { target: HTMLElement }) => {
|
|
|
|
|
const liElement = hasClosestByTag(event.target, "LI");
|
|
|
|
|
if (liElement) {
|
|
|
|
|
liElement.style.opacity = "1";
|
|
|
|
|
}
|
|
|
|
|
window.siyuan.dragElement = undefined;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public expandAll() {
|
|
|
|
|
this.element.querySelectorAll("ul").forEach(item => {
|
|
|
|
|
if (!item.classList.contains("b3-list")) {
|
|
|
|
|
item.classList.remove("fn__none");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => {
|
|
|
|
|
item.classList.add("b3-list-item__arrow--open");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-28 22:43:27 +08:00
|
|
|
public collapseAll() {
|
2022-05-26 15:18:53 +08:00
|
|
|
this.element.querySelectorAll("ul").forEach(item => {
|
|
|
|
|
if (!item.classList.contains("b3-list")) {
|
2022-05-28 22:43:27 +08:00
|
|
|
item.classList.add("fn__none");
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => {
|
2022-05-28 22:43:27 +08:00
|
|
|
item.classList.remove("b3-list-item__arrow--open");
|
2022-05-26 15:18:53 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getExpandIds() {
|
|
|
|
|
const ids: string[] = [];
|
|
|
|
|
this.element.querySelectorAll(".b3-list-item__arrow--open").forEach(item => {
|
|
|
|
|
ids.push(item.getAttribute("data-id"));
|
|
|
|
|
});
|
|
|
|
|
return ids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public setExpandIds(ids: string[]) {
|
|
|
|
|
this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => {
|
|
|
|
|
if (ids.includes(item.getAttribute("data-id"))) {
|
|
|
|
|
item.classList.add("b3-list-item__arrow--open");
|
|
|
|
|
if (item.parentElement.parentElement.nextElementSibling) {
|
|
|
|
|
item.parentElement.parentElement.nextElementSibling.classList.remove("fn__none");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
item.classList.remove("b3-list-item__arrow--open");
|
|
|
|
|
if (item.parentElement.parentElement.nextElementSibling && item.parentElement.parentElement.nextElementSibling.tagName === "UL") {
|
|
|
|
|
item.parentElement.parentElement.nextElementSibling.classList.add("fn__none");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|