import {getIconByType} from "../editor/getIcon"; import {hasClosestByMatchTag, hasClosestByTag} from "../protyle/util/hasClosest"; import {isMobile} from "./functions"; import {mathRender} from "../protyle/render/mathRender"; import {unicode2Emoji} from "../emoji"; import {Constants} from "../constants"; import {escapeAriaLabel} from "./escape"; export class Tree { public element: HTMLElement; private data: IBlockTree[]; private blockExtHTML: string; private topExtHTML: string; public click: (element: Element, event?: MouseEvent) => void; private ctrlClick: (element: HTMLElement) => void; private toggleClick: (element: Element) => void; 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, topExtHTML?: string, click?(element: HTMLElement, event: MouseEvent): void ctrlClick?(element: HTMLElement): void altClick?(element: HTMLElement): void shiftClick?(element: HTMLElement): void toggleClick?(element: HTMLElement): void 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; this.toggleClick = options.toggleClick; this.element = options.element; this.blockExtHTML = options.blockExtHTML; this.topExtHTML = options.topExtHTML; this.updateData(options.data); this.bindEvent(); } public updateData(data: IBlockTree[]) { this.data = data; if (!this.data || this.data.length === 0) { this.element.innerHTML = ``; } else { this.element.innerHTML = this.genHTML(this.data); mathRender(this.element); } } private genHTML(data: IBlockTree[]) { let html = ``; data.forEach((item) => { let titleTip = ""; let iconHTML = ''; if (item.type === "bookmark") { iconHTML = ''; } else if (item.type === "tag") { iconHTML = ''; } else if (item.type === "backlink") { titleTip = ` aria-label="${escapeAriaLabel(item.hPath)}"`; iconHTML = ``; } else if (item.type === "outline") { titleTip = ` aria-label="${escapeAriaLabel(Lute.BlockDOM2Content(item.name))}"`; iconHTML = ``; } let countHTML = ""; if (item.count) { countHTML = `${item.count}`; } const hasChild = (item.children && item.children.length > 0) || (item.blocks && item.blocks.length > 0); let style = ""; if (isMobile()) { if (item.depth > 0) { style = `padding-left: ${(item.depth - 1) * 20 + 24}px`; } } else { style = `padding-left: ${(item.depth - 1) * 18 + 22}px;margin-right: 2px`; } const showArrow = hasChild || (item.type === "backlink" && !isMobile()); html += `
  • ${iconHTML} ${item.name} ${this.topExtHTML || ""} ${countHTML}
  • `; if (item.children && item.children.length > 0) { html += this.genHTML(item.children) + ""; } if (item.blocks && item.blocks.length > 0) { html += this.genBlockHTML(item.blocks, true, item.type) + ""; } }); return html; } private genBlockHTML(data: IBlock[], show = false, type: string) { let html = `"; } }); return html; } public toggleBlocks(liElement: Element) { if (this.toggleClick) { this.toggleClick(liElement); return; } 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)) { if (target.classList.contains("b3-list-item__toggle") && !target.classList.contains("fn__hidden")) { this.toggleBlocks(target.parentElement); this.setCurrent(target.parentElement); event.preventDefault(); break; } if (target.classList.contains("b3-list-item__action") && this.click) { // 移动端书签父节点删除按钮 const liElement = hasClosestByMatchTag(target, "LI"); if (liElement) { this.click(liElement, event); } event.preventDefault(); event.stopPropagation(); break; } else if (target.tagName === "LI") { 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); // 设置了的话 drop 就无法监听 alt event.dataTransfer.dropEffect = "move"; 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"); }); } public collapseAll() { this.element.querySelectorAll("ul").forEach(item => { if (!item.classList.contains("b3-list")) { item.classList.add("fn__none"); } }); this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => { item.classList.remove("b3-list-item__arrow--open"); }); } 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"); } } }); } }