/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { CollapseSettingTab: () => CollapseSettingTab, default: () => CanvasCollapsePlugin }); module.exports = __toCommonJS(src_exports); var import_obsidian4 = require("obsidian"); // src/ControlHeader.ts var import_obsidian = require("obsidian"); var CollapseControlHeader = class extends import_obsidian.Component { constructor(plugin, node) { super(); this.collapsed = false; this.content = ""; this.alias = ""; this.thumbnailUrl = ""; this.refreshed = false; this.containingNodes = []; this.oldFilePath = ""; this.plugin = plugin; this.node = node; this.collapsed = node.unknownData.collapsed === void 0 ? false : node.unknownData.collapsed; } onload() { this.initHeader(); this.initContent(); this.initTypeIcon(); this.updateNodesInGroup(); this.updateNode(); this.registerEvent(this.plugin.app.vault.on("rename", (file, oldPath) => { if (oldPath === this.oldFilePath) { this.titleEl.setText(file.name.split(".")[0]); this.oldFilePath = file.path; } })); return this.headerEl; } onunload() { super.onunload(); } unload() { super.unload(); this.headerEl.empty(); this.headerEl.detach(); } initHeader() { this.headerEl = createEl("div", { cls: "canvas-node-collapse-control" }); this.registerDomEvent(this.headerEl, "click", async (evt) => { evt.preventDefault(); evt.stopPropagation(); await this.toggleCollapsed(); }); this.collapsedIconEl = this.headerEl.createEl("div", { cls: "canvas-node-collapse-control-icon" }); this.typeIconEl = this.headerEl.createEl("div", { cls: "canvas-node-type-icon" }); this.titleEl = this.headerEl.createEl("span", { cls: "canvas-node-collapse-control-title" }); this.thumbnailEl = this.node.nodeEl.createEl("img", { cls: "canvas-node-collapse-control-thumbnail" }); this.aliasEl = this.headerEl.createEl("span", { cls: "canvas-node-collapse-control-alias" }); } checkNodeType() { return this.node.unknownData.type; } initTypeIcon() { this.setIconOrContent("setIcon"); } initContent() { var _a; this.setIconOrContent("setContent"); this.titleEl.setText((_a = this.content) == null ? void 0 : _a.replace(/^\#{1,} /g, "")); this.initAlias(); this.initThumbnail(); } initAlias() { var _a; if ((_a = this.node.unknownData) == null ? void 0 : _a.alias) { this.alias = this.node.unknownData.alias; } else { const fileNode = this.node; if (fileNode.file && this.plugin.app.metadataCache) { try { const meta = this.plugin.app.metadataCache.getFileCache(fileNode.file); if (meta == null ? void 0 : meta.frontmatter) { const aliases = (0, import_obsidian.parseFrontMatterAliases)(meta.frontmatter); if (aliases && aliases.length > 0) { this.alias = aliases[0]; } } } catch (e) { console.debug("Error getting alias:", e); } } } if (this.alias) { this.aliasEl.setText(this.alias); } else { this.aliasEl.detach(); } } updateNodeAlias(newAlias) { this.alias = newAlias; if (this.alias) { if (!this.aliasEl.parentElement) { this.headerEl.appendChild(this.aliasEl); } this.aliasEl.setText(this.alias); } else { this.aliasEl.detach(); } } initThumbnail() { var _a, _b; if ((_a = this.node.unknownData) == null ? void 0 : _a.thumbnail) { this.thumbnailUrl = this.node.unknownData.thumbnail; } else { const fileNode = this.node; if (fileNode.file && this.plugin.app.metadataCache) { try { const meta = this.plugin.app.metadataCache.getFileCache(fileNode.file); if ((_b = meta == null ? void 0 : meta.frontmatter) == null ? void 0 : _b.thumbnail) { this.thumbnailUrl = meta.frontmatter.thumbnail; } } catch (e) { console.debug("Error getting thumbnail:", e); } } } if (this.thumbnailUrl) { try { const url = this.thumbnailUrl.startsWith("http") ? this.thumbnailUrl : this.plugin.app.vault.adapter.getResourcePath(this.thumbnailUrl); this.thumbnailEl.src = url; } catch (e) { console.debug("Error setting thumbnail src:", e); this.thumbnailEl.detach(); } } else { this.thumbnailEl.detach(); } } updateNodeThumbnail(newThumbnailUrl) { this.thumbnailUrl = newThumbnailUrl; if (this.thumbnailUrl) { try { if (!this.thumbnailEl.parentElement) { this.headerEl.appendChild(this.thumbnailEl); } const url = this.thumbnailUrl.startsWith("http") ? this.thumbnailUrl : this.plugin.app.vault.adapter.getResourcePath(this.thumbnailUrl); this.thumbnailEl.src = url; } catch (e) { console.debug("Error setting thumbnail src:", e); this.thumbnailEl.detach(); } } else { this.thumbnailEl.detach(); } } setIconOrContent(action) { var _a, _b; const currentType = this.checkNodeType(); switch (currentType) { case "text": if (action === "setIcon") (0, import_obsidian.setIcon)(this.typeIconEl, "sticky-note"); if (action === "setContent") this.content = this.node.text.slice(0, 10) + (this.node.text.length > 10 ? "..." : ""); break; case "file": if (action === "setIcon") { if (this.node.file.name.split(".")[1].trim() === "md") (0, import_obsidian.setIcon)(this.typeIconEl, "file-text"); else (0, import_obsidian.setIcon)(this.typeIconEl, "file-image"); } if (action === "setContent") this.content = (_a = this.node.file) == null ? void 0 : _a.name.split(".")[0]; this.oldFilePath = (_b = this.node.file) == null ? void 0 : _b.path; break; case "group": if (action === "setIcon") (0, import_obsidian.setIcon)(this.typeIconEl, "create-group"); if (action === "setContent") this.content = ""; break; case "link": if (action === "setIcon") (0, import_obsidian.setIcon)(this.typeIconEl, "link"); if (action === "setContent") this.content = this.node.url; break; } if (action === "setIcon" && !this.node.unknownData.type) { (0, import_obsidian.setIcon)(this.typeIconEl, "sticky-note"); } } setCollapsed(collapsed) { if (this.node.canvas.readonly) return; if (this.collapsed === collapsed) return; this.collapsed = collapsed; this.node.unknownData.collapsed = collapsed; this.updateNodesInGroup(); this.updateNode(); this.updateEdges(); } refreshHistory() { if (this.refreshed) return; const history = this.node.canvas.history; if (!history || history.data.length === 0) return; for (const data of history.data) { for (const node of data.nodes) { if (node.id === this.node.id && (node == null ? void 0 : node.collapsed) === void 0) { node.collapsed = false; } } } this.refreshed = true; } async toggleCollapsed() { if (this.node.canvas.readonly) return; const wasCollapsed = this.collapsed; this.collapsed = !this.collapsed; this.node.unknownData.collapsed = !this.collapsed; this.updateNode(); setTimeout(() => { this.updateNodesInGroup(); this.updateEdges(); }, 50); this.node.canvas.requestSave(false, true); const canvasCurrentData = this.node.canvas.getData(); const nodeData = canvasCurrentData.nodes.find((node) => node.id === this.node.id); if (nodeData) { nodeData.collapsed = this.collapsed; if (wasCollapsed && !this.collapsed) { const nodeIndex = canvasCurrentData.nodes.findIndex((node) => node.id === this.node.id); if (nodeIndex !== -1) { const removedNode = canvasCurrentData.nodes.splice(nodeIndex, 1)[0]; canvasCurrentData.nodes.push(removedNode); } } this.refreshHistory(); } setTimeout(() => { this.node.canvas.setData(canvasCurrentData); this.node.canvas.requestSave(true); }, 300); } updateNode() { this.node.nodeEl.toggleClass("collapsed", this.collapsed); (0, import_obsidian.setIcon)(this.collapsedIconEl, "chevron-down"); this.collapsedIconEl.toggleClass(["collapsed", "collapse-handler"], this.collapsed); this.updateThumbnailVisibility(); this.updateAliasVisibility(); } updateThumbnailVisibility() { if (this.collapsed || this.plugin.settings.showThumbnailsAlways) { if (this.plugin.settings.showThumbnailsInCollapsedState && this.thumbnailUrl && this.thumbnailUrl !== this.node.unknownData.title) { this.thumbnailEl.toggleClass("collapsed-node-hidden", false); this.titleEl.toggleClass("collapsed-node-hidden", true); } else { this.thumbnailEl.toggleClass("collapsed-node-hidden", true); this.titleEl.toggleClass("collapsed-node-hidden", false); } } else { this.thumbnailEl.toggleClass("collapsed-node-hidden", true); } } updateAliasVisibility() { if (this.collapsed || this.plugin.settings.showAliasesAlways) { if (this.plugin.settings.showAliasesInCollapsedState && this.alias && this.alias !== this.node.unknownData.title) { this.aliasEl.toggleClass("collapsed-node-hidden", false); this.titleEl.toggleClass("collapsed-node-hidden", true); } else { this.aliasEl.toggleClass("collapsed-node-hidden", true); this.titleEl.toggleClass("collapsed-node-hidden", false); } } else { this.aliasEl.toggleClass("collapsed-node-hidden", true); } } updateEdges() { this.node.canvas.nodeInteractionLayer.interactionEl.detach(); this.node.canvas.nodeInteractionLayer.render(); const edges = this.node.canvas.getEdgesForNode(this.node); for (const edge of edges) { edge.render(); } } updateNodesInGroup(expandAll) { if (this.node.unknownData.type === "group" || this.node.label) { const nodes = this.node.canvas.getContainingNodes(this.node.getBBox(true)); if (expandAll) { this.collapsed = false; } this.node.nodeEl.toggleClass("animating", true); setTimeout(() => { this.node.nodeEl.toggleClass("animating", false); }, 300); if (this.collapsed) { const filteredNodes = nodes.filter((node) => node.id !== this.node.id); for (const node of filteredNodes) { this.containingNodes.push(node); node.nodeEl.toggleClass("node-transitioning", true); node.nodeEl.toggleClass("group-nodes-collapsed", this.collapsed); setTimeout(() => { node.nodeEl.toggleClass("node-transitioning", false); }, 300); this.updateEdgesInGroup(node); } } else { const otherGroupNodes = nodes.filter((node) => node.id !== this.node.id && node.unknownData.type === "group" && node.unknownData.collapsed); const ignoreNodes = []; for (const groupNode of otherGroupNodes) { const bbox = groupNode.getBBox(true); const nodesInGroup = this.node.canvas.getContainingNodes(bbox); for (const childNode of nodesInGroup) { if (childNode.id !== groupNode.id) { ignoreNodes.push(childNode); } } } const filteredContainingNodes = this.containingNodes.filter((t) => !ignoreNodes.find((k) => k.id === t.id)); for (const node of filteredContainingNodes) { node.nodeEl.toggleClass("node-transitioning", true); node.nodeEl.toggleClass("group-nodes-collapsed", this.collapsed); setTimeout(() => { node.nodeEl.toggleClass("node-transitioning", false); }, 300); this.updateEdgesInGroup(node, true); } for (const node of ignoreNodes) { this.updateEdgesInGroup(node, node.unknownData.collapsed); } this.containingNodes = []; } this.updateEdges(); } } updateEdgesInGroup(node, triggerCollapsed) { var _a, _b, _c, _d; const edges = this.node.canvas.getEdgesForNode(node); for (const edge of edges) { (_b = (_a = edge.labelElement) == null ? void 0 : _a.wrapperEl) == null ? void 0 : _b.classList.toggle("group-edges-collapsed", triggerCollapsed || this.collapsed); edge.lineGroupEl.classList.toggle("group-edges-collapsed", triggerCollapsed || this.collapsed); (_c = edge.lineEndGroupEl) == null ? void 0 : _c.classList.toggle("group-edges-collapsed", triggerCollapsed || this.collapsed); (_d = edge.lineStartGroupEl) == null ? void 0 : _d.classList.toggle("group-edges-collapsed", triggerCollapsed || this.collapsed); } } }; // src/utils.ts var import_obsidian2 = require("obsidian"); var getBoundingRect = (nodes) => { const bboxArray = nodes.map((t) => t.getBBox()); const minX = Math.min(...bboxArray.map((t) => t.minX)); const minY = Math.min(...bboxArray.map((t) => t.minY)); const maxX = Math.max(...bboxArray.map((t) => t.maxX)); const maxY = Math.max(...bboxArray.map((t) => t.maxY)); return { minX, minY, maxX, maxY }; }; var updateSelection = (canvas) => { if (canvas.menu.selection.bbox) { const selection = Array.from(canvas.selection); const currentNodesInSelection = canvas.getContainingNodes(canvas.menu.selection.bbox); if (currentNodesInSelection.length > 0) { const boundingRect = getBoundingRect(selection.length > currentNodesInSelection.length ? selection : currentNodesInSelection); if (boundingRect) { canvas.menu.selection.update(boundingRect); } } } }; var handleMultiNodes = (canvas, allNodes, collapse) => { var _a; const nodes = allNodes ? Array.from(canvas.nodes.values()) : Array.from(canvas.selection); const canvasData = canvas.getData(); if (nodes && nodes.length > 0) { for (const node of nodes) { if (node.unknownData.type === "group") { node.headerComponent.updateNodesInGroup(collapse); } (_a = node.headerComponent) == null ? void 0 : _a.setCollapsed(collapse); const nodeData = canvasData.nodes.find((t) => t.id === node.id); if (nodeData) nodeData.collapsed = collapse; } canvas.setData(canvasData); } canvas.requestSave(true, true); canvas.requestFrame(); updateSelection(canvas); }; var handleMultiNodesViaNodes = (canvas, nodes, collapse) => { var _a; const canvasData = canvas.getData(); if (nodes && nodes.length > 0) { for (const node of nodes) { if (node.unknownData.type === "group") { node.headerComponent.updateNodesInGroup(collapse); } (_a = node.headerComponent) == null ? void 0 : _a.setCollapsed(collapse); const nodeData = canvasData.nodes.find((t) => t.id === node.id); if (nodeData) nodeData.collapsed = collapse; } canvas.setData(canvasData); } canvas.requestSave(true, true); updateSelection(canvas); }; var handleSingleNode = (node, collapse) => { if (node.unknownData.type === "group") { node.headerComponent.updateNodesInGroup(); } const canvasData = node.canvas.getData(); const nodeData = canvasData.nodes.find((t) => t.id === node.id); if (nodeData) nodeData.collapsed = collapse; node.canvas.setData(canvasData); node.canvas.requestSave(true, true); updateSelection(node.canvas); }; var handleNodesViaCommands = (plugin, checking, allNodes, collapse) => { plugin.triggerByPlugin = true; const currentView = plugin.app.workspace.getActiveViewOfType(import_obsidian2.ItemView); if (currentView && currentView.getViewType() === "canvas") { if (!checking) { const canvasView = currentView; const canvas = canvasView.canvas; handleMultiNodes(canvas, allNodes, collapse); } return true; } }; var createHandleContextMenu = (section, callback) => { return (menu) => { menu.addItem((item) => { const subMenu = item.setSection(section).setTitle("Collapse node").setIcon("chevrons-left-right").setSubmenu(); handleCanvasMenu(subMenu, callback); }); }; }; var handleCanvasMenu = (subMenu, callback) => { return subMenu.addItem((item) => { item.setIcon("fold-vertical").setTitle("Fold selected nodes").onClick(async () => { await callback(true); }); }).addItem((item) => { item.setIcon("unfold-vertical").setTitle("Expand selected nodes").onClick(async () => { await callback(false); }); }); }; var handleSelectionContextMenu = (plugin, menu, canvas) => { plugin.triggerByPlugin = true; const callback = async (isFold) => { handleMultiNodes(canvas, false, isFold); }; createHandleContextMenu("action", callback)(menu); }; var handleNodeContextMenu = (plugin, menu, node) => { plugin.triggerByPlugin = true; const callback = async (isFold) => { handleSingleNode(node, isFold); }; createHandleContextMenu("canvas", callback)(menu); menu.addItem((item) => { item.setSection("canvas").setTitle("Set Node Alias").setIcon("text-cursor-input").onClick(async () => { await setNodeAlias(plugin, node); }); }); menu.addItem((item) => { item.setSection("canvas").setTitle("Set Node Thumbnail").setIcon("image").onClick(async () => { await setNodeThumbnail(plugin, node); }); }); if (node.unknownData.alias || node.unknownData.thumbnail) { menu.addItem((item) => { item.setSection("canvas").setTitle("Remove Node Customizations").setIcon("trash").onClick(async () => { await removeNodeCustomizations(plugin, node); }); }); } }; var setNodeAlias = async (plugin, node) => { const modal = new TextInputModal(plugin.app, "Enter alias for node", node.unknownData.alias || "", "Set Alias"); const alias = await modal.openAndGetValue(); if (alias !== null) { node.unknownData.alias = alias; if (node.headerComponent && node.headerComponent instanceof CollapseControlHeader) { const header = node.headerComponent; header.updateNodeAlias(alias); header.updateNode(); } node.canvas.requestSave(false, true); } }; var setNodeThumbnail = async (plugin, node) => { const modal = new ThumbnailSelectionModal(plugin.app); const thumbnailPath = await modal.openAndGetValue(); if (thumbnailPath) { node.unknownData.thumbnail = thumbnailPath; if (node.headerComponent && node.headerComponent instanceof CollapseControlHeader) { const header = node.headerComponent; header.updateNodeThumbnail(thumbnailPath); header.updateNode(); } node.canvas.requestSave(false, true); } }; var removeNodeCustomizations = async (plugin, node) => { delete node.unknownData.alias; delete node.unknownData.thumbnail; if (node.headerComponent && node.headerComponent instanceof CollapseControlHeader) { const header = node.headerComponent; header.updateNodeAlias(""); header.updateNodeThumbnail(""); header.updateNode(); } node.canvas.requestSave(false, true); }; var TextInputModal = class extends import_obsidian2.Modal { constructor(app, title, initialValue, buttonText) { super(app); this.title = title; this.value = initialValue; this.buttonText = buttonText; } onOpen() { const { contentEl } = this; contentEl.createEl("h2", { text: this.title }); this.inputEl = contentEl.createEl("input", { type: "text", value: this.value }); this.inputEl.style.width = "100%"; this.inputEl.style.marginBottom = "1em"; setTimeout(() => this.inputEl.focus(), 10); const buttonContainer = contentEl.createDiv(); buttonContainer.style.display = "flex"; buttonContainer.style.justifyContent = "flex-end"; buttonContainer.style.gap = "10px"; const cancelButton = buttonContainer.createEl("button", { text: "Cancel" }); const saveButton = buttonContainer.createEl("button", { text: this.buttonText, cls: "mod-cta" }); cancelButton.addEventListener("click", () => { this.resolvePromise(null); this.close(); }); saveButton.addEventListener("click", () => { this.resolvePromise(this.inputEl.value); this.close(); }); this.inputEl.addEventListener("keydown", (e) => { if (e.key === "Enter") { this.resolvePromise(this.inputEl.value); this.close(); } }); } onClose() { const { contentEl } = this; contentEl.empty(); } openAndGetValue() { return new Promise((resolve) => { this.resolvePromise = resolve; this.open(); }); } }; var ThumbnailSelectionModal = class extends import_obsidian2.Modal { constructor(app) { super(app); } onOpen() { const { contentEl } = this; contentEl.createEl("h2", { text: "Set Node Thumbnail" }); contentEl.createEl("p", { text: "Enter image URL or path to an attachment:" }); this.inputEl = contentEl.createEl("input", { type: "text", placeholder: "https://example.com/image.jpg or image.jpg" }); this.inputEl.style.width = "100%"; this.inputEl.style.marginBottom = "1em"; contentEl.createEl("p", { text: "Or select an image from your attachments:" }); const selectFileButton = contentEl.createEl("button", { text: "Browse vault files" }); selectFileButton.addEventListener("click", () => { this.openFilePicker(); }); contentEl.createEl("p", { text: "Preview:", cls: "thumbnail-preview-label" }); this.preview = contentEl.createEl("img", { cls: "thumbnail-preview" }); this.preview.style.maxWidth = "100%"; this.preview.style.maxHeight = "150px"; this.preview.style.display = "none"; this.inputEl.addEventListener("input", () => { this.updatePreview(); }); const buttonContainer = contentEl.createDiv(); buttonContainer.style.display = "flex"; buttonContainer.style.justifyContent = "flex-end"; buttonContainer.style.gap = "10px"; buttonContainer.style.marginTop = "1em"; const cancelButton = buttonContainer.createEl("button", { text: "Cancel" }); const saveButton = buttonContainer.createEl("button", { text: "Set Thumbnail", cls: "mod-cta" }); cancelButton.addEventListener("click", () => { this.resolvePromise(null); this.close(); }); saveButton.addEventListener("click", () => { this.resolvePromise(this.inputEl.value); this.close(); }); } openFilePicker() { const fileSuggestModal = new FileSuggestModal(this.app, (file) => { if (file) { this.inputEl.value = file.path; this.updatePreview(); } }); fileSuggestModal.open(); } updatePreview() { const url = this.inputEl.value; if (url) { try { if (url.startsWith("http")) { this.preview.src = url; this.preview.style.display = "block"; } else { this.preview.src = this.app.vault.adapter.getResourcePath(url); this.preview.style.display = "block"; } } catch (e) { this.preview.style.display = "none"; } } else { this.preview.style.display = "none"; } } onClose() { const { contentEl } = this; contentEl.empty(); } openAndGetValue() { return new Promise((resolve) => { this.resolvePromise = resolve; this.open(); }); } }; var FileSuggestModal = class extends import_obsidian2.SuggestModal { constructor(app, callback) { super(app); this.callback = callback; } getSuggestions(query) { const imageExtensions = [ "png", "jpg", "jpeg", "gif", "bmp", "svg", "webp" ]; const files = this.app.vault.getFiles().filter((file) => imageExtensions.includes(file.extension.toLowerCase()) && file.path.toLowerCase().includes(query.toLowerCase())); return files; } renderSuggestion(file, el) { el.createEl("div", { text: file.path }); } onChooseSuggestion(file, evt) { this.callback(file); } onClose() { if (this.callback) { this.callback(null); } } }; var refreshAllCanvasView = (app) => { const cavasLeaves = app.workspace.getLeavesOfType("canvas"); if (!cavasLeaves || cavasLeaves.length === 0) return; for (const leaf of cavasLeaves) { leaf.rebuildView(); } }; var getSelectionCoords = (dom) => { const domHTML = dom.outerHTML; const translateRegex = /translate\((-?\d+\.?\d*)px, (-?\d+\.?\d*)px\)/; const sizeRegex = /width: (\d+\.?\d*)px; height: (\d+\.?\d*)px;/; const translateMatches = domHTML.match(translateRegex); const sizeMatches = domHTML.match(sizeRegex); if (translateMatches && sizeMatches) { const x = parseFloat(translateMatches[1]); const y = parseFloat(translateMatches[2]); const width = parseFloat(sizeMatches[1]); const height = parseFloat(sizeMatches[2]); return { minX: x, minY: y, maxX: x + width, maxY: y + height }; } }; // src/index.ts var import_view = require("@codemirror/view"); // node_modules/monkey-around/mjs/index.js function around(obj, factories) { const removers = Object.keys(factories).map((key) => around1(obj, key, factories[key])); return removers.length === 1 ? removers[0] : function() { removers.forEach((r) => r()); }; } function around1(obj, method, createWrapper) { const original = obj[method], hadOwn = obj.hasOwnProperty(method); let current = createWrapper(original); if (original) Object.setPrototypeOf(current, original); Object.setPrototypeOf(wrapper, current); obj[method] = wrapper; return remove; function wrapper(...args) { if (current === original && obj[method] === wrapper) remove(); return current.apply(this, args); } function remove() { if (obj[method] === wrapper) { if (hadOwn) obj[method] = original; else delete obj[method]; } if (current === original) return; current = original; Object.setPrototypeOf(wrapper, original || Function); } } // src/patchUtils.ts var import_obsidian3 = require("obsidian"); var aroundCanvasMethods = (plugin) => { const patchCanvasMethod = (canvasView) => { const checkCoords = (e, t) => { return e.minX <= t.minX && e.minY <= t.minY && e.maxX >= t.maxX && e.maxY >= t.maxY; }; const checkTriggerByPlugin = () => { return plugin.triggerByPlugin; }; const toggleTriggerByPlugin = () => { plugin.triggerByPlugin = !plugin.triggerByPlugin; }; const patchCanvas = (canvas) => { const uninstaller2 = around(canvas.constructor.prototype, { getContainingNodes: (next) => function(e) { const result = next.call(this, e); const checkExistGroupNode = this.nodeIndex.search(e).find((t) => t.unknownData.type === "group" || t.label); if (!checkExistGroupNode) return result; const renewCoords = checkExistGroupNode == null ? void 0 : checkExistGroupNode.getBBox(true); if (renewCoords !== e && e.maxY - e.minY === 40) { const newResult = this.nodeIndex.search(renewCoords).filter((t) => { return checkCoords(renewCoords, t.getBBox(true)); }); if (newResult.length > result.length) { return newResult; } } return result; }, requestSave: (next) => function(args, triggerBySelf) { next.call(this, args); if (triggerBySelf) { if (args !== void 0) { this.data = this.getData(); args && this.requestPushHistory(this.data); } } }, pushHistory: (next) => function(args) { if (checkTriggerByPlugin()) { toggleTriggerByPlugin(); return; } return next.call(this, args); }, selectAll: (next) => function(e) { if (this.wrapperEl.querySelector(".canvas-selection")) { const domCoords = getSelectionCoords(this.wrapperEl.querySelector(".canvas-selection")); if (domCoords) { const newResult = Array.from(e).filter((t) => { if (!t.unknownData.collapsed) return true; if (t.nodeEl.hasClass("group-nodes-collapsed")) return false; return checkCoords(domCoords, t.getBBox()); }); if (newResult.length > 0) { const ne = new Set(newResult); return next.call(this, ne); } if (newResult.length === 0) { return; } } } return next.call(this, e); }, createTextNode: (next) => function(args) { var _a, _b, _c, _d; if (args.size === void 0 && args.pos) { return next.call(this, { ...args, pos: { x: args.pos.x, y: args.pos.y, width: ((_a = args == null ? void 0 : args.size) == null ? void 0 : _a.width) || 250, height: ((_b = args == null ? void 0 : args.size) == null ? void 0 : _b.height) || 140 }, size: { x: args.pos.x, y: args.pos.y, width: ((_c = args == null ? void 0 : args.size) == null ? void 0 : _c.width) || 250, height: ((_d = args == null ? void 0 : args.size) == null ? void 0 : _d.height) || 140 } }); } return next.call(this, args); }, createGroupNode: (next) => function(args) { var _a, _b, _c, _d; if (args.size !== void 0 && args.pos) { return next.call(this, { ...args, pos: { x: args.pos.x, y: args.pos.y - 30, width: (_a = args == null ? void 0 : args.size) == null ? void 0 : _a.width, height: ((_b = args == null ? void 0 : args.size) == null ? void 0 : _b.height) + 30 }, size: { x: args.pos.x, y: args.pos.y - 30, width: (_c = args == null ? void 0 : args.size) == null ? void 0 : _c.width, height: ((_d = args == null ? void 0 : args.size) == null ? void 0 : _d.height) + 30 } }); } return next.call(this, args); } }); plugin.register(uninstaller2); plugin.patchSucceed = true; console.log("Obsidian-Collapse-Node: canvas patched"); return true; }; patchCanvas(canvasView.canvas); }; const uninstaller = around(import_obsidian3.WorkspaceLeaf.prototype, { setViewState(next) { return function(state, ...rest) { var _a, _b, _c; if ((_a = state.state) == null ? void 0 : _a.file) { if (((_b = state.state) == null ? void 0 : _b.file) && ((_c = state.state) == null ? void 0 : _c.file).endsWith(".canvas")) { setTimeout(() => { if (this.view.canvas) { patchCanvasMethod(this.view); uninstaller(); } }, 400); } } return next.apply(this, [state, ...rest]); }; } }); plugin.register(uninstaller); }; var patchCanvasMenu = (plugin) => { const triggerPlugin = () => { plugin.triggerByPlugin = true; }; const patchMenu = async () => { const canvasLeaf = plugin.app.workspace.getLeavesOfType("canvas").first(); if (canvasLeaf == null ? void 0 : canvasLeaf.isDeferred) { await canvasLeaf.loadIfDeferred(); } const canvasView = canvasLeaf == null ? void 0 : canvasLeaf.view; if (!canvasView) return false; const menu = canvasView == null ? void 0 : canvasView.canvas.menu; if (!menu) return false; const selection = menu.selection; if (!selection) return false; const menuUninstaller = around(menu.constructor.prototype, { render: (next) => function(...args) { const result = next.call(this, ...args); if (this.menuEl.querySelector(".collapse-node-menu-item")) return result; const buttonEl = createEl("button", "clickable-icon collapse-node-menu-item"); (0, import_obsidian3.setTooltip)(buttonEl, "Fold selected nodes", { placement: "top" }); (0, import_obsidian3.setIcon)(buttonEl, "lucide-chevrons-left-right"); this.menuEl.appendChild(buttonEl); buttonEl.addEventListener("click", () => { const pos = buttonEl.getBoundingClientRect(); if (!buttonEl.hasClass("has-active-menu")) { buttonEl.toggleClass("has-active-menu", true); const menu2 = new import_obsidian3.Menu(); const containingNodes = this.canvas.getContainingNodes(this.selection.bbox); handleCanvasMenu(menu2, async (isFold) => { var _a; triggerPlugin(); const currentSelection = this.canvas.selection; containingNodes.length > 1 ? handleMultiNodesViaNodes(this.canvas, containingNodes, isFold) : currentSelection ? handleSingleNode((_a = Array.from(currentSelection)) == null ? void 0 : _a.first(), isFold) : ""; buttonEl.toggleClass("has-active-menu", false); }); menu2.setParentElement(this.menuEl).showAtPosition({ x: pos.x, y: pos.bottom, width: pos.width, overlap: true }); } }); return result; } }); plugin.register(menuUninstaller); plugin.app.workspace.trigger("collapse-node:patched-canvas"); console.log("Obsidian-Collapse-Node: canvas history patched"); return true; }; plugin.app.workspace.onLayoutReady(async () => { if (!await patchMenu()) { const evt = plugin.app.workspace.on("layout-change", async () => { await patchMenu() && plugin.app.workspace.offref(evt); }); plugin.registerEvent(evt); } }); }; var patchCanvasInteraction = (plugin) => { const patchInteraction = () => { var _a; const canvasView = (_a = plugin.app.workspace.getLeavesOfType("canvas").first()) == null ? void 0 : _a.view; if (!canvasView) return false; const canvas = canvasView == null ? void 0 : canvasView.canvas.nodeInteractionLayer; if (!canvas) return false; const uninstaller = around(canvas.constructor.prototype, { render: (next) => function(...args) { const result = next.call(this, ...args); if (!this.target) return result; const isCollapsed = this.target.nodeEl.hasClass("collapsed"); const isGroupNodesCollapsed = this.target.nodeEl.hasClass("group-nodes-collapsed"); if (this.target.unknownData) { this.interactionEl.toggleClass("collapsed-interaction", isCollapsed); } this.interactionEl.toggleClass("group-nodes-collapsed", isGroupNodesCollapsed); return result; } }); plugin.register(uninstaller); console.log("Obsidian-Collapse-Node: canvas history patched"); return true; }; plugin.app.workspace.onLayoutReady(() => { if (!patchInteraction()) { const evt = plugin.app.workspace.on("layout-change", () => { patchInteraction() && plugin.app.workspace.offref(evt); }); plugin.registerEvent(evt); } }); }; var initControlHeader = (plugin, node) => { return new CollapseControlHeader(plugin, node); }; var renderNodeWithHeader = (plugin, node) => { var _a, _b, _c, _d; if (node.headerComponent) return; const nodeType = node.unknownData.type; if (nodeType === "file" && ((_a = node.file) == null ? void 0 : _a.extension) === "md" && !plugin.settings.collapsableFileNode) return; if (nodeType === "file" && ((_b = node.file) == null ? void 0 : _b.extension) !== "md" && !plugin.settings.collapsableAttachmentNode) return; if (nodeType === "group" && !plugin.settings.collapsableGroupNode) return; if (nodeType === "link" && !plugin.settings.collapsableLinkNode) return; if (nodeType === "text" && !plugin.settings.collapsableTextNode) return; if (plugin.settings.minLineAmount > 0 && (nodeType === "text" || nodeType === "file")) { let lineCount = 0; if (nodeType === "text" && typeof node.text === "string") { lineCount = node.text.split("\n").length; } else if (nodeType === "file" && ((_c = node.file) == null ? void 0 : _c.extension) === "md" && node.child) { lineCount = ((_d = node.child.data) == null ? void 0 : _d.split("\n").length) || 0; } if (lineCount < plugin.settings.minLineAmount) return; } node.headerComponent = initControlHeader(plugin, node); node.nodeEl.setAttribute("data-node-type", nodeType); const addHeader = () => { if (!node.containerEl) { setTimeout(addHeader, 0); return; } node.containerEl.prepend(node.headerComponent.onload()); }; addHeader(); if (node.unknownData.collapsed) { node.nodeEl.toggleClass("collapsed", true); node.headerComponent.updateEdges(); } }; var updateAllNodeWithHeader = (plugin) => { var _a; const canvasLeaves = plugin.app.workspace.getLeavesOfType("canvas"); for (const canvasLeaf of canvasLeaves) { const canvas = (_a = canvasLeaf.view) == null ? void 0 : _a.canvas; if (!canvas) continue; const nodes = canvas.nodes.values(); for (const node of nodes) { renderNodeWithHeader(plugin, node); } } }; var patchCanvasNode = (plugin) => { const patchNode = () => { var _a, _b; const canvasView = (_a = plugin.app.workspace.getLeavesOfType("canvas").first()) == null ? void 0 : _a.view; if (!canvasView) return false; const canvas = canvasView == null ? void 0 : canvasView.canvas; if (!canvas) return false; const node = ((_b = plugin.app.workspace.getLeavesOfType("canvas").first()) == null ? void 0 : _b.view).canvas.nodes.values().next().value; if (!node) return false; let prototype = Object.getPrototypeOf(node); while (prototype && prototype !== Object.prototype) { prototype = Object.getPrototypeOf(prototype); if (prototype.renderZIndex) { break; } } console.log(prototype); if (!prototype) return false; const uninstaller = around(prototype, { render: (next) => function(...args) { const result = next.call(this, ...args); renderNodeWithHeader(plugin, this); return result; }, getBBox: (next) => function(containing) { const result = next.call(this); if (containing !== true && this.nodeEl.hasClass("collapsed")) { const x = this.x; const y = this.y; const width = this.width; const height = 40; return { minX: x, minY: y, maxX: x + width, maxY: y + height }; } return result; }, setData: (next) => function(data) { var _a2; if (data.collapsed !== void 0) { (_a2 = this.headerComponent) == null ? void 0 : _a2.setCollapsed(data.collapsed); } return next.call(this, data); } }); plugin.register(uninstaller); updateAllNodeWithHeader(plugin); console.log("Obsidian-Collapse-Node: canvas node patched"); return true; }; plugin.app.workspace.onLayoutReady(() => { if (!patchNode()) { const evt = plugin.app.workspace.on("layout-change", () => { patchNode() && plugin.app.workspace.offref(evt); }); plugin.registerEvent(evt); } }); }; // src/index.ts var DEFAULT_SETTINGS = { collapsableFileNode: true, collapsableAttachmentNode: true, collapsableGroupNode: true, collapsableLinkNode: true, collapsableTextNode: true, minLineAmount: 0, minimalControlHeader: false, showThumbnailsInCollapsedState: false, showAliasesInCollapsedState: false, showAliasesAlways: false, showThumbnailsAlways: false, hideDefaultNodeTitle: false }; var DynamicUpdateControlHeader = (plugin) => { return import_view.EditorView.updateListener.of((v) => { var _a; if (v.docChanged) { const editor = v.state.field(import_obsidian4.editorInfoField); const node = editor.node; if (node) { if (node.unknownData.type === "text" && !plugin.settings.collapsableTextNode) return; if (node.unknownData.type === "file" && !plugin.settings.collapsableFileNode) return; if (node.unknownData.type === "text" || node.unknownData.type === "file" && node.file.extension === "md") { const content = v.view.state.doc.toString(); if (node.headerComponent && plugin.settings.minLineAmount > 0 && content.split("\n").length < plugin.settings.minLineAmount) { (_a = node.headerComponent) == null ? void 0 : _a.unload(); node.headerComponent = void 0; return; } else if (!node.headerComponent && plugin.settings.minLineAmount > 0 && content.split("\n").length >= plugin.settings.minLineAmount) { node.headerComponent = new CollapseControlHeader(plugin, node); node.containerEl.prepend(node.headerComponent.onload()); node.headerComponent.updateNode(); } } } } }); }; var CanvasCollapsePlugin = class extends import_obsidian4.Plugin { constructor() { super(...arguments); this.triggerByPlugin = false; this.patchSucceed = false; this.headerComponents = {}; this.debounceReloadLeaves = (0, import_obsidian4.debounce)(() => { const leaves = this.app.workspace.getLeavesOfType("canvas"); leaves.forEach((leaf) => { leaf.rebuildView(); }); }, 1e3); } async onload() { await this.loadSettings(); this.addSettingTab(new CollapseSettingTab(this.app, this)); this.registerCommands(); this.registerCanvasEvents(); this.registerCustomIcons(); this.registerEditorExtension([DynamicUpdateControlHeader(this)]); this.initGlobalCss(); this.app.workspace.onLayoutReady(() => { aroundCanvasMethods(this); patchCanvasMenu(this); patchCanvasInteraction(this); patchCanvasNode(this); }); } onunload() { console.log("unloading plugin"); refreshAllCanvasView(this.app); } registerCommands() { this.addCommand({ id: "fold-all-nodes", name: "Fold all nodes", checkCallback: (checking) => handleNodesViaCommands(this, checking, true, true) }); this.addCommand({ id: "expand-all-nodes", name: "Expand all nodes", checkCallback: (checking) => handleNodesViaCommands(this, checking, true, false) }); this.addCommand({ id: "fold-selected-nodes", name: "Fold selected nodes", checkCallback: (checking) => handleNodesViaCommands(this, checking, false, true) }); this.addCommand({ id: "expand-selected-nodes", name: "Expand selected nodes", checkCallback: (checking) => handleNodesViaCommands(this, checking, false, false) }); } registerCanvasEvents() { this.registerEvent(this.app.workspace.on("collapse-node:patched-canvas", () => { refreshAllCanvasView(this.app); })); this.registerEvent(this.app.workspace.on("canvas:selection-menu", (menu, canvas) => { handleSelectionContextMenu(this, menu, canvas); })); this.registerEvent(this.app.workspace.on("canvas:node-menu", (menu, node) => { handleNodeContextMenu(this, menu, node); })); } registerCustomIcons() { (0, import_obsidian4.addIcon)("fold-vertical", ``); (0, import_obsidian4.addIcon)("unfold-vertical", ``); } initGlobalCss() { var _a, _b; document.body.toggleClass("minimal-control-header", (_a = this.settings) == null ? void 0 : _a.minimalControlHeader); document.body.toggleClass("hide-default-node-title", (_b = this.settings) == null ? void 0 : _b.hideDefaultNodeTitle); } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings() { await this.saveData(this.settings); this.debounceReloadLeaves(); } }; var CollapseSettingTab = class extends import_obsidian4.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); new import_obsidian4.Setting(containerEl).setHeading().setName("Enable nodes to be collapsable"); this.createCollapsableSetting(this.plugin, containerEl, "File node", "", "collapsableFileNode"); this.createCollapsableSetting(this.plugin, containerEl, "Attachment node", "", "collapsableAttachmentNode"); this.createCollapsableSetting(this.plugin, containerEl, "Group node", "", "collapsableGroupNode"); this.createCollapsableSetting(this.plugin, containerEl, "Link node", "", "collapsableLinkNode"); this.createCollapsableSetting(this.plugin, containerEl, "Text node", "", "collapsableTextNode"); new import_obsidian4.Setting(containerEl).setHeading().setName("Detail settings"); new import_obsidian4.Setting(containerEl).setName("Line amount the enable node to be collapsed").setDesc("The amount of lines that will be shown when the node is collapsed").addText((text) => { text.setValue(this.plugin.settings.minLineAmount.toString()).onChange(async (value) => { if (!isNaN(Number(value))) { this.plugin.settings.minLineAmount = Number(value); await this.plugin.saveSettings(); } }); }); new import_obsidian4.Setting(containerEl).setName("Minimal control header").setDesc("Hide the text and also icon in the control header of the node").addToggle((toggle) => { toggle.setValue(this.plugin.settings.minimalControlHeader).onChange(async (value) => { this.plugin.settings.minimalControlHeader = value; document.body.toggleClass("minimal-control-header", value); await this.plugin.saveSettings(); }); }); new import_obsidian4.Setting(containerEl).setName("Show thumbnails in collapsed state").setDesc("Show thumbnails in the collapsed state of the node").addToggle((toggle) => { toggle.setValue(this.plugin.settings.showThumbnailsInCollapsedState).onChange(async (value) => { this.plugin.settings.showThumbnailsInCollapsedState = value; await this.plugin.saveSettings(); }); }); new import_obsidian4.Setting(containerEl).setName("Show aliases in collapsed state").setDesc("Show aliases in the collapsed state of the node").addToggle((toggle) => { toggle.setValue(this.plugin.settings.showAliasesInCollapsedState).onChange(async (value) => { this.plugin.settings.showAliasesInCollapsedState = value; await this.plugin.saveSettings(); }); }); new import_obsidian4.Setting(containerEl).setName("Hide default node title").setDesc("Hide the default title of the node").addToggle((toggle) => { toggle.setValue(this.plugin.settings.hideDefaultNodeTitle).onChange(async (value) => { this.plugin.settings.hideDefaultNodeTitle = value; document.body.toggleClass("hide-default-node-title", value); await this.plugin.saveSettings(); }); }); new import_obsidian4.Setting(containerEl).setName("Show aliases always").setDesc("Show aliases always in the collapsed/expanded state of the node. Replace current title with alias.").addToggle((toggle) => { toggle.setValue(this.plugin.settings.showAliasesAlways).onChange(async (value) => { this.plugin.settings.showAliasesAlways = value; await this.plugin.saveSettings(); }); }); new import_obsidian4.Setting(containerEl).setName("Show thumbnails always").setDesc("Show thumbnails always in the collapsed state of the node").addToggle((toggle) => { toggle.setValue(this.plugin.settings.showThumbnailsAlways).onChange(async (value) => { this.plugin.settings.showThumbnailsAlways = value; await this.plugin.saveSettings(); }); }); } createCollapsableSetting(plugin, containerEl, name, desc, settingKey) { new import_obsidian4.Setting(containerEl).setName(name).setDesc(desc).addToggle((toggle) => { toggle.setValue(plugin.settings[settingKey]).onChange(async (value) => { plugin.settings[settingKey] = value; await plugin.saveSettings(); }); }); } };