/*
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();
});
});
}
};