Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325import {getIconByType} from "../editor/getIcon";import {isMobile} from "./functions";import {mathRender} from "../protyle/render/mathRender";import {unicode2Emoji} from "../emoji";import {Constants} from "../constants";import {escapeAriaLabel} from "./escape";import {hasClosestByTag} from "../protyle/util/hasClosest";
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, event: MouseEvent) => void; private toggleClick: (element: Element) => void; private shiftClick: (element: HTMLElement) => void; private altClick: (element: HTMLElement, event: MouseEvent) => 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, event: MouseEvent): void altClick?(element: HTMLElement, event: MouseEvent): 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 = `<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 & { folded?: boolean })[]) { let html = `<ul${data[0].depth === 0 ? " class='b3-list b3-list--background'" : ""}>`; data.forEach((item) => { let titleTip = ""; 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") { titleTip = ` aria-label="${escapeAriaLabel(item.hPath)}"`; iconHTML = `<svg class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.nodeType, item.subType)}"></use></svg>`; } else if (item.type === "outline") { titleTip = ` aria-label="${escapeAriaLabel(Lute.BlockDOM2Content(item.name))}"`; iconHTML = `<svg class="b3-list-item__graphic popover__block" data-id="${item.id}" style="height: 22px;width: 10px;"><use xlink:href="#${getIconByType(item.nodeType, item.subType)}"></use></svg>`; } let countHTML = ""; if (item.count) { countHTML = `<span class="counter">${item.count}</span>`; } 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 * 18) || 4}px;margin-right: 2px`; } const showArrow = hasChild || (item.type === "backlink" && !isMobile()); // data-id 需要添加 item.id,否则大纲更新时 name 不一致导致 https://github.com/siyuan-note/siyuan/issues/11843 html += `<li class="b3-list-item${isMobile() ? "" : " b3-list-item--hide-action"}" ${item.id ? 'data-node-id="' + item.id + '"' : ""} ${item.box ? 'data-notebook-id="' + item.box + '"' : ""} style="--file-toggle-width:${item.depth === 0 ? 22 : ((item.depth + 1) * 18)}px" data-treetype="${item.type}" data-type="${item.nodeType}" data-subtype="${item.subType}" ${item.label ? "data-label='" + item.label + "'" : ""}> <span style="${style}" class="b3-list-item__toggle${showArrow ? " b3-list-item__toggle--hl" : ""}${showArrow ? "" : " fn__hidden"}"> <svg data-id="${item.id || encodeURIComponent(item.name + item.depth)}" class="b3-list-item__arrow${(item.type === "outline" ? !item.folded : hasChild) ? " b3-list-item__arrow--open" : ""}"><use xlink:href="#iconRight"></use></svg> </span> ${iconHTML} <span class="b3-list-item__text ariaLabel" data-position="parentE"${titleTip}>${item.name}</span> ${this.topExtHTML || ""} ${countHTML}</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, item.type === "outline" ? !item.folded : 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; folded?: boolean ial?: { icon: string } }) => { let countHTML = ""; if (item.count) { countHTML = `<span class="counter">${item.count}</span>`; } let iconHTML; if (type === "outline") { iconHTML = `<svg data-showref="true" class="b3-list-item__graphic popover__block" data-id="${item.id}" style="height: 22px;width: 10px;"><use xlink:href="#${getIconByType(item.type, item.subType)}"></use></svg>`; } else { if (item.type === "NodeDocument") { iconHTML = `<span data-showref="true" class="b3-list-item__graphic popover__block" data-id="${item.id}">${unicode2Emoji(item.ial.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span>`; } else { iconHTML = `<svg data-showref="true" class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.type, item.subType)}"></use></svg>`; } } let style = ""; if (isMobile()) { if (item.depth > 0) { style = `padding-left: ${(item.depth - 1) * 20 + 24}px`; } } else { style = `padding-left: ${item.depth * 18 || 4}px;margin-right: 2px`; } html += `<li class="b3-list-item${isMobile() ? "" : " b3-list-item--hide-action"}" style="--file-toggle-width:${item.depth === 0 ? 22 : ((item.depth + 1) * 18)}px" 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}"> <span style="${style}" class="b3-list-item__toggle${item.children ? " b3-list-item__toggle--hl" : ""}${item.children ? "" : " fn__hidden"}"> <svg data-id="${item.id}" class="b3-list-item__arrow${(type === "outline" ? !item.folded : show) ? " b3-list-item__arrow--open" : ""}"><use xlink:href="#iconRight"></use></svg> </span> ${iconHTML} <span class="b3-list-item__text ariaLabel" data-position="parentE" ${type === "outline" ? ' aria-label="' + escapeAriaLabel(Lute.BlockDOM2Content(item.content)) + '"' : ""}>${item.content}</span> ${countHTML} ${this.blockExtHTML || ""}</li>`; if (item.children && item.children.length > 0) { html += this.genBlockHTML(item.children, type === "outline" ? !item.folded : false, type) + "</ul>"; } }); 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") && !window.siyuan.ctrlIsPressed && !window.siyuan.altIsPressed) { this.toggleBlocks(target.parentElement); this.setCurrent(target.parentElement); event.preventDefault(); break; } if (target.classList.contains("b3-list-item__action") && this.click) { // 移动端书签父节点删除按钮 const liElement = hasClosestByTag(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, event); } else if (this.altClick && window.siyuan.altIsPressed) { this.altClick(target, event); } 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.38"; 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[]) { if (!ids || ids.length === 0) { return; } 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"); } } }); }}