/* 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/main.ts var main_exports = {}; __export(main_exports, { default: () => AdvancedCanvasPlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian19 = require("obsidian"); // src/utils/icons-helper.ts var import_obsidian = require("obsidian"); var CUSTOM_ICONS = { "shape-pill": ``, "shape-parallelogram": ``, "shape-predefined-process": ` `, "shape-document": ``, "shape-database": ` `, "border-solid": ``, "border-dashed": ``, "border-dotted": ``, "path-solid": ``, "path-dotted": ``, "path-short-dashed": ``, "path-long-dashed": ``, "arrow-triangle": ``, "arrow-triangle-outline": ``, "arrow-thin-triangle": ``, "arrow-halved-triangle": ``, "arrow-diamond": ``, "arrow-diamond-outline": ``, "arrow-circle": ``, "arrow-circle-outline": ``, "pathfinding-method-bezier": ``, "pathfinding-method-square": ``, "arrows-selected": ` `, "arrow-right-selected": ` `, "arrow-left-selected": ` ` }; var IconsHelper = class { static addIcons() { for (const [id, svg] of Object.entries(CUSTOM_ICONS)) { (0, import_obsidian.addIcon)(id, svg); } } }; // src/utils/debug-helper.ts var DebugHelper = class { constructor(plugin) { this.logging = true; this.nodeAddedCount = 0; this.nodeChangedCount = 0; this.edgeAddedCount = 0; this.edgeChangedCount = 0; this.plugin = plugin; this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (_canvas) => { this.nodeAddedCount = 0; this.nodeChangedCount = 0; this.edgeAddedCount = 0; this.edgeChangedCount = 0; } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-added", (_canvas, _node) => { if (this.logging) console.count("\u{1F7E2} NodeAdded"); this.nodeAddedCount++; } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-changed", (_canvas, _node) => { if (this.logging) console.count("\u{1F7E1} NodeChanged"); this.nodeChangedCount++; } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-added", (_canvas, _edge) => { if (this.logging) console.count("\u{1F7E2} EdgeAdded"); this.edgeAddedCount++; } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-changed", (_canvas, _edge) => { if (this.logging) console.count("\u{1F7E1} EdgeChanged"); this.edgeChangedCount++; } )); } resetEfficiency() { this.nodeAddedCount = 0; this.nodeChangedCount = 0; this.edgeAddedCount = 0; this.edgeChangedCount = 0; } logEfficiency() { const canvas = this.plugin.getCurrentCanvas(); if (!canvas) return; console.log("NodeAdded Efficiency:", this.nodeAddedCount / canvas.nodes.size); console.log("NodeChanged Efficiency:", this.nodeChangedCount / canvas.nodes.size); console.log("EdgeAdded Efficiency:", this.edgeAddedCount / canvas.edges.size); console.log("EdgeChanged Efficiency:", this.edgeChangedCount / canvas.edges.size); } static markBBox(canvas, bbox, duration = -1) { const node = canvas.createTextNode({ pos: { x: bbox.minX, y: bbox.minY }, size: { width: bbox.maxX - bbox.minX, height: bbox.maxY - bbox.minY }, text: "", focus: false }); node.setData({ ...node.getData(), id: "debug-bbox", color: "1", styleAttributes: { border: "invisible" } }); if (duration >= 0) { setTimeout(() => { canvas.removeNode(node); }, duration); } } }; // src/settings.ts var import_obsidian4 = require("obsidian"); // src/utils/bbox-helper.ts var BBoxHelper = class { static combineBBoxes(bboxes) { let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const bbox of bboxes) { minX = Math.min(minX, bbox.minX); minY = Math.min(minY, bbox.minY); maxX = Math.max(maxX, bbox.maxX); maxY = Math.max(maxY, bbox.maxY); } return { minX, minY, maxX, maxY }; } static scaleBBox(bbox, scale) { const diffX = (scale - 1) * (bbox.maxX - bbox.minX); const diffY = (scale - 1) * (bbox.maxY - bbox.minY); return { minX: bbox.minX - diffX / 2, maxX: bbox.maxX + diffX / 2, minY: bbox.minY - diffY / 2, maxY: bbox.maxY + diffY / 2 }; } static isColliding(bbox1, bbox2) { return bbox1.minX <= bbox2.maxX && bbox1.maxX >= bbox2.minX && bbox1.minY <= bbox2.maxY && bbox1.maxY >= bbox2.minY; } static insideBBox(position, bbox, canTouchEdge) { if ("x" in position) { const x = position.x, y = position.y; return canTouchEdge ? x >= bbox.minX && x <= bbox.maxX && y >= bbox.minY && y <= bbox.maxY : x > bbox.minX && x < bbox.maxX && y > bbox.minY && y < bbox.maxY; } return canTouchEdge ? position.minX >= bbox.minX && position.maxX <= bbox.maxX && position.minY >= bbox.minY && position.maxY <= bbox.maxY : position.minX > bbox.minX && position.maxX < bbox.maxX && position.minY > bbox.minY && position.maxY < bbox.maxY; } static enlargeBBox(bbox, padding) { return { minX: bbox.minX - padding, minY: bbox.minY - padding, maxX: bbox.maxX + padding, maxY: bbox.maxY + padding }; } static moveInDirection(position, side, distance) { switch (side) { case "top": return { x: position.x, y: position.y - distance }; case "right": return { x: position.x + distance, y: position.y }; case "bottom": return { x: position.x, y: position.y + distance }; case "left": return { x: position.x - distance, y: position.y }; } } static getCenterOfBBoxSide(bbox, side) { switch (side) { case "top": return { x: (bbox.minX + bbox.maxX) / 2, y: bbox.minY }; case "right": return { x: bbox.maxX, y: (bbox.minY + bbox.maxY) / 2 }; case "bottom": return { x: (bbox.minX + bbox.maxX) / 2, y: bbox.maxY }; case "left": return { x: bbox.minX, y: (bbox.minY + bbox.maxY) / 2 }; } } static getSideVector(side) { switch (side) { case "top": return { x: 0, y: 1 }; case "right": return { x: 1, y: 0 }; case "bottom": return { x: 0, y: -1 }; case "left": return { x: -1, y: 0 }; default: return { x: 0, y: 0 }; } } static getOppositeSide(side) { switch (side) { case "top": return "bottom"; case "right": return "left"; case "bottom": return "top"; case "left": return "right"; } } static isHorizontal(side) { return side === "left" || side === "right"; } static direction(side) { return side === "right" || side === "bottom" ? 1 : -1; } }; // src/utils/canvas-helper.ts var import_obsidian2 = require("obsidian"); var _CanvasHelper = class _CanvasHelper { static canvasCommand(plugin, check, run) { return (checking) => { const canvas = plugin.getCurrentCanvas(); if (checking) return canvas !== null && check(canvas); if (canvas) run(canvas); return true; }; } static createControlMenuButton(menuOption) { const quickSetting = document.createElement("div"); if (menuOption.id) quickSetting.id = menuOption.id; quickSetting.classList.add("canvas-control-item"); (0, import_obsidian2.setIcon)(quickSetting, menuOption.icon); (0, import_obsidian2.setTooltip)(quickSetting, menuOption.label, { placement: "left" }); quickSetting.addEventListener("click", () => { var _a; return (_a = menuOption.callback) == null ? void 0 : _a.call(menuOption); }); return quickSetting; } static addControlMenuButton(controlGroup, element) { var _a; if (element.id) (_a = controlGroup.querySelector(`#${element.id}`)) == null ? void 0 : _a.remove(); controlGroup.appendChild(element); } static createCardMenuOption(canvas, menuOption, previewNodeSize, onPlaced) { const menuOptionElement = document.createElement("div"); if (menuOption.id) menuOptionElement.id = menuOption.id; menuOptionElement.classList.add("canvas-card-menu-button"); menuOptionElement.classList.add("mod-draggable"); (0, import_obsidian2.setIcon)(menuOptionElement, menuOption.icon); (0, import_obsidian2.setTooltip)(menuOptionElement, menuOption.label, { placement: "top" }); menuOptionElement.addEventListener("click", (_e) => { onPlaced(canvas, this.getCenterCoordinates(canvas, previewNodeSize())); }); menuOptionElement.addEventListener("pointerdown", (e) => { canvas.dragTempNode(e, previewNodeSize(), (pos) => { canvas.deselectAll(); onPlaced(canvas, pos); }); }); return menuOptionElement; } static addCardMenuOption(canvas, element) { var _a; if (element.id) (_a = canvas == null ? void 0 : canvas.cardMenuEl.querySelector(`#${element.id}`)) == null ? void 0 : _a.remove(); canvas == null ? void 0 : canvas.cardMenuEl.appendChild(element); } static createPopupMenuOption(menuOption) { const menuOptionElement = document.createElement("button"); if (menuOption.id) menuOptionElement.id = menuOption.id; menuOptionElement.classList.add("clickable-icon"); (0, import_obsidian2.setIcon)(menuOptionElement, menuOption.icon); (0, import_obsidian2.setTooltip)(menuOptionElement, menuOption.label, { placement: "top" }); menuOptionElement.addEventListener("click", () => { var _a; return (_a = menuOption.callback) == null ? void 0 : _a.call(menuOption); }); return menuOptionElement; } static createExpandablePopupMenuOption(menuOption, subMenuOptions) { const menuOptionElement = this.createPopupMenuOption({ ...menuOption, callback: () => { var _a, _b, _c; const submenuId = `${menuOption.id}-submenu`; if (menuOptionElement.classList.contains("is-active")) { menuOptionElement.classList.remove("is-active"); (_b = (_a = menuOptionElement.parentElement) == null ? void 0 : _a.querySelector(`#${submenuId}`)) == null ? void 0 : _b.remove(); return; } menuOptionElement.classList.add("is-active"); const submenu = document.createElement("div"); submenu.id = submenuId; submenu.classList.add("canvas-submenu"); for (const subMenuOption of subMenuOptions) { const subMenuOptionElement = this.createPopupMenuOption(subMenuOption); submenu.appendChild(subMenuOptionElement); } (_c = menuOptionElement.parentElement) == null ? void 0 : _c.appendChild(submenu); } }); return menuOptionElement; } static addPopupMenuOption(canvas, element, index = -1) { var _a; const popupMenuEl = (_a = canvas == null ? void 0 : canvas.menu) == null ? void 0 : _a.menuEl; if (!popupMenuEl) return; if (element.id) { const optionToReplace = popupMenuEl.querySelector(`#${element.id}`); if (optionToReplace && index === -1) index = Array.from(popupMenuEl.children).indexOf(optionToReplace) - 1; optionToReplace == null ? void 0 : optionToReplace.remove(); } const sisterElement = index >= 0 ? popupMenuEl.children[index] : popupMenuEl.children[popupMenuEl.children.length + index]; popupMenuEl.insertAfter(element, sisterElement); } static getCenterCoordinates(canvas, nodeSize) { const viewBounds = canvas.getViewportBBox(); return { x: (viewBounds.minX + viewBounds.maxX) / 2 - nodeSize.width / 2, y: (viewBounds.minY + viewBounds.maxY) / 2 - nodeSize.height / 2 }; } static getBBox(canvasElements) { const bBoxes = canvasElements.map((element) => { if (element.getBBox) return element.getBBox(); const nodeData = element; if (nodeData.x !== void 0 && nodeData.y !== void 0 && nodeData.width !== void 0 && nodeData.height !== void 0) return { minX: nodeData.x, minY: nodeData.y, maxX: nodeData.x + nodeData.width, maxY: nodeData.y + nodeData.height }; return null; }).filter((bbox) => bbox !== null); return BBoxHelper.combineBBoxes(bBoxes); } static getSmallestAllowedZoomBBox(canvas, bbox) { if (canvas.screenshotting) return bbox; if (canvas.canvasRect.width === 0 || canvas.canvasRect.height === 0) return bbox; const widthZoom = canvas.canvasRect.width / (bbox.maxX - bbox.minX); const heightZoom = canvas.canvasRect.height / (bbox.maxY - bbox.minY); const requiredZoom = Math.min(widthZoom, heightZoom); if (requiredZoom > _CanvasHelper.MAX_ALLOWED_ZOOM) { const scaleFactor = requiredZoom / _CanvasHelper.MAX_ALLOWED_ZOOM; return BBoxHelper.scaleBBox(bbox, scaleFactor); } return bbox; } static addStyleAttributesToPopup(plugin, canvas, styleAttributes, currentStyleAttributes, setStyleAttribute) { if (!plugin.settings.getSetting("combineCustomStylesInDropdown")) this.addStyleAttributesButtons(canvas, styleAttributes, currentStyleAttributes, setStyleAttribute); else this.addStyleAttributesDropdownMenu(canvas, styleAttributes, currentStyleAttributes, setStyleAttribute); } static addStyleAttributesButtons(canvas, stylableAttributes, currentStyleAttributes, setStyleAttribute) { var _a; for (const stylableAttribute of stylableAttributes) { const selectedStyle = (_a = stylableAttribute.options.find((option) => currentStyleAttributes[stylableAttribute.key] === option.value)) != null ? _a : stylableAttribute.options.find((value) => value.value === null); if (!selectedStyle) { console.warn(`No "null" style option found for stylable attribute "${stylableAttribute.key}"`); continue; } const menuOption = _CanvasHelper.createExpandablePopupMenuOption({ id: `menu-option-${stylableAttribute.key}`, label: stylableAttribute.label, icon: selectedStyle.icon }, stylableAttribute.options.map((styleOption) => ({ label: styleOption.label, icon: styleOption.icon, callback: () => { setStyleAttribute(stylableAttribute, styleOption.value); currentStyleAttributes[stylableAttribute.key] = styleOption.value; (0, import_obsidian2.setIcon)(menuOption, styleOption.icon); menuOption.dispatchEvent(new Event("click")); } }))); _CanvasHelper.addPopupMenuOption(canvas, menuOption); } } static addStyleAttributesDropdownMenu(canvas, stylableAttributes, currentStyleAttributes, setStyleAttribute) { var _a, _b; const STYLE_MENU_ID = "style-menu"; const STYLE_MENU_DROPDOWN_ID = "style-menu-dropdown"; const STYLE_MENU_DROPDOWN_SUBMENU_ID = "style-menu-dropdown-submenu"; const popupMenuElement = (_a = canvas == null ? void 0 : canvas.menu) == null ? void 0 : _a.menuEl; if (!popupMenuElement) return; (_b = popupMenuElement.querySelector(`#${STYLE_MENU_ID}`)) == null ? void 0 : _b.remove(); const styleMenuButtonElement = document.createElement("button"); styleMenuButtonElement.id = STYLE_MENU_ID; styleMenuButtonElement.classList.add("clickable-icon"); (0, import_obsidian2.setIcon)(styleMenuButtonElement, "paintbrush"); (0, import_obsidian2.setTooltip)(styleMenuButtonElement, "Style", { placement: "top" }); popupMenuElement.appendChild(styleMenuButtonElement); styleMenuButtonElement.addEventListener("click", () => { var _a2, _b2, _c; const isOpen = styleMenuButtonElement.classList.toggle("has-active-menu"); if (!isOpen) { (_a2 = popupMenuElement.querySelector(`#${STYLE_MENU_DROPDOWN_ID}`)) == null ? void 0 : _a2.remove(); (_b2 = popupMenuElement.querySelector(`#${STYLE_MENU_DROPDOWN_SUBMENU_ID}`)) == null ? void 0 : _b2.remove(); return; } const styleMenuDropdownElement = document.createElement("div"); styleMenuDropdownElement.id = STYLE_MENU_DROPDOWN_ID; styleMenuDropdownElement.classList.add("menu"); styleMenuDropdownElement.style.position = "absolute"; styleMenuDropdownElement.style.maxHeight = "initial"; styleMenuDropdownElement.style.top = `${popupMenuElement.getBoundingClientRect().height}px`; const canvasWrapperCenterX = canvas.wrapperEl.getBoundingClientRect().left + canvas.wrapperEl.getBoundingClientRect().width / 2; const leftPosition = styleMenuButtonElement.getBoundingClientRect().left - popupMenuElement.getBoundingClientRect().left; const rightPosition = popupMenuElement.getBoundingClientRect().right - styleMenuButtonElement.getBoundingClientRect().right; if (popupMenuElement.getBoundingClientRect().left + leftPosition < canvasWrapperCenterX) styleMenuDropdownElement.style.left = `${leftPosition}px`; else styleMenuDropdownElement.style.right = `${rightPosition}px`; for (const stylableAttribute of stylableAttributes) { const stylableAttributeElement = document.createElement("div"); stylableAttributeElement.classList.add("menu-item"); stylableAttributeElement.classList.add("tappable"); const iconElement = document.createElement("div"); iconElement.classList.add("menu-item-icon"); let selectedStyle = (_c = stylableAttribute.options.find((option) => currentStyleAttributes[stylableAttribute.key] === option.value)) != null ? _c : stylableAttribute.options.find((value) => value.value === null); if (!selectedStyle) continue; (0, import_obsidian2.setIcon)(iconElement, selectedStyle.icon); stylableAttributeElement.appendChild(iconElement); const labelElement = document.createElement("div"); labelElement.classList.add("menu-item-title"); labelElement.textContent = stylableAttribute.label; stylableAttributeElement.appendChild(labelElement); const expandIconElement = document.createElement("div"); expandIconElement.classList.add("menu-item-icon"); (0, import_obsidian2.setIcon)(expandIconElement, "chevron-right"); stylableAttributeElement.appendChild(expandIconElement); styleMenuDropdownElement.appendChild(stylableAttributeElement); stylableAttributeElement.addEventListener("pointerenter", () => { stylableAttributeElement.classList.add("selected"); }); stylableAttributeElement.addEventListener("pointerleave", () => { stylableAttributeElement.classList.remove("selected"); }); stylableAttributeElement.addEventListener("click", () => { var _a3; (_a3 = popupMenuElement.querySelector(`#${STYLE_MENU_DROPDOWN_SUBMENU_ID}`)) == null ? void 0 : _a3.remove(); const styleMenuDropdownSubmenuElement = document.createElement("div"); styleMenuDropdownSubmenuElement.id = STYLE_MENU_DROPDOWN_SUBMENU_ID; styleMenuDropdownSubmenuElement.classList.add("menu"); styleMenuDropdownSubmenuElement.style.position = "absolute"; styleMenuDropdownSubmenuElement.style.maxHeight = "initial"; const topOffset = parseFloat(window.getComputedStyle(styleMenuDropdownElement).getPropertyValue("padding-top")) + (styleMenuDropdownElement.offsetHeight - styleMenuDropdownElement.clientHeight) / 2; styleMenuDropdownSubmenuElement.style.top = `${stylableAttributeElement.getBoundingClientRect().top - topOffset - popupMenuElement.getBoundingClientRect().top}px`; const leftPosition2 = styleMenuDropdownElement.getBoundingClientRect().right - popupMenuElement.getBoundingClientRect().left; const rightPosition2 = popupMenuElement.getBoundingClientRect().right - styleMenuDropdownElement.getBoundingClientRect().left; if (popupMenuElement.getBoundingClientRect().left + leftPosition2 < canvasWrapperCenterX) styleMenuDropdownSubmenuElement.style.left = `${leftPosition2}px`; else styleMenuDropdownSubmenuElement.style.right = `${rightPosition2}px`; for (const styleOption of stylableAttribute.options) { const styleMenuDropdownSubmenuOptionElement = this.createDropdownOptionElement({ label: styleOption.label, icon: styleOption.icon, callback: () => { setStyleAttribute(stylableAttribute, styleOption.value); currentStyleAttributes[stylableAttribute.key] = styleOption.value; selectedStyle = styleOption; (0, import_obsidian2.setIcon)(iconElement, styleOption.icon); styleMenuDropdownSubmenuElement.remove(); } }); if (selectedStyle === styleOption) { styleMenuDropdownSubmenuOptionElement.classList.add("mod-selected"); const selectedIconElement = document.createElement("div"); selectedIconElement.classList.add("menu-item-icon"); selectedIconElement.classList.add("mod-selected"); (0, import_obsidian2.setIcon)(selectedIconElement, "check"); styleMenuDropdownSubmenuOptionElement.appendChild(selectedIconElement); } styleMenuDropdownSubmenuElement.appendChild(styleMenuDropdownSubmenuOptionElement); } popupMenuElement.appendChild(styleMenuDropdownSubmenuElement); }); } popupMenuElement.appendChild(styleMenuDropdownElement); }); } static createDropdownOptionElement(menuOption) { const menuDropdownOptionElement = document.createElement("div"); menuDropdownOptionElement.classList.add("menu-item"); menuDropdownOptionElement.classList.add("tappable"); const iconElement = document.createElement("div"); iconElement.classList.add("menu-item-icon"); (0, import_obsidian2.setIcon)(iconElement, menuOption.icon); menuDropdownOptionElement.appendChild(iconElement); const labelElement = document.createElement("div"); labelElement.classList.add("menu-item-title"); labelElement.textContent = menuOption.label; menuDropdownOptionElement.appendChild(labelElement); menuDropdownOptionElement.addEventListener("pointerenter", () => { menuDropdownOptionElement.classList.add("selected"); }); menuDropdownOptionElement.addEventListener("pointerleave", () => { menuDropdownOptionElement.classList.remove("selected"); }); menuDropdownOptionElement.addEventListener("click", () => { var _a; (_a = menuOption.callback) == null ? void 0 : _a.call(menuOption); }); return menuDropdownOptionElement; } static createDropdownSeparatorElement() { const separatorElement = document.createElement("div"); separatorElement.classList.add("menu-separator"); return separatorElement; } static alignToGrid(value, gridSize = this.GRID_SIZE) { return Math.round(value / gridSize) * gridSize; } static getBestSideForFloatingEdge(sourcePos, target) { const targetBBox = target.getBBox(); const possibleSides = ["top", "right", "bottom", "left"]; const possibleTargetPos = possibleSides.map((side) => [side, BBoxHelper.getCenterOfBBoxSide(targetBBox, side)]); let bestSide = null; let bestDistance = Infinity; for (const [side, pos] of possibleTargetPos) { const distance = Math.sqrt(Math.pow(sourcePos.x - pos.x, 2) + Math.pow(sourcePos.y - pos.y, 2)); if (distance < bestDistance) { bestDistance = distance; bestSide = side; } } return bestSide; } static selectEdgesForNodes(canvas, direction) { const selection = canvas.getSelectionData(); if (selection.nodes.length === 0) return; const edges = /* @__PURE__ */ new Set(); for (const nodeData of selection.nodes) { const node = canvas.nodes.get(nodeData.id); if (!node) continue; for (const edge of canvas.getEdgesForNode(node)) { switch (direction) { case "connected": edges.add(edge); break; case "incoming": if (edge.to.node === node) edges.add(edge); break; case "outgoing": if (edge.from.node === node) edges.add(edge); break; } } } canvas.updateSelection(() => { canvas.selection = edges; }); } }; _CanvasHelper.GRID_SIZE = 20; _CanvasHelper.MAX_ALLOWED_ZOOM = 1; var CanvasHelper = _CanvasHelper; // src/canvas-extensions/canvas-extension.ts var CanvasExtension = class { constructor(plugin) { this.plugin = plugin; const isEnabled = this.isEnabled(); if (!(isEnabled === true || this.plugin.settings.getSetting(isEnabled))) return; this.init(); } }; // src/utils/svg-path-helper.ts var SvgPathHelper = class { static smoothenPathArray(positions, tension) { let newPositions = [...positions]; if (positions.length <= 2) return newPositions; newPositions = [positions[0]]; for (let i = 1; i < positions.length - 2; i++) { const p1 = positions[i]; const p2 = positions[i + 1]; const p3 = positions[i + 2]; const t1 = (1 - tension) / 2; const t2 = 1 - t1; const x = t2 * t2 * t2 * p1.x + 3 * t2 * t2 * t1 * p2.x + 3 * t2 * t1 * t1 * p3.x + t1 * t1 * t1 * p2.x; const y = t2 * t2 * t2 * p1.y + 3 * t2 * t2 * t1 * p2.y + 3 * t2 * t1 * t1 * p3.y + t1 * t1 * t1 * p2.y; newPositions.push({ x, y }); } const lastPoint = positions[positions.length - 1]; newPositions.push(lastPoint); return newPositions; } static pathArrayToSvgPath(positions) { for (let i = 0; i < positions.length - 2; i++) { const p1 = positions[i]; const p2 = positions[i + 1]; const p3 = positions[i + 2]; const currentDirection = { x: p2.x - p1.x, y: p2.y - p1.y }; const nextDirection = { x: p3.x - p2.x, y: p3.y - p2.y }; if (currentDirection.x !== nextDirection.x && currentDirection.y !== nextDirection.y) continue; positions.splice(i + 1, 1); i--; } return positions.map( (position, index) => `${index === 0 ? "M" : "L"} ${position.x} ${position.y}` ).join(" "); } static pathArrayToRoundedSvgPath(pathArray, targetRadius) { if (pathArray.length < 3) return this.pathArrayToSvgPath(pathArray); pathArray = pathArray.filter((position, index) => { if (index === 0) return true; const previous = pathArray[index - 1]; return !(position.x === previous.x && position.y === previous.y); }); const commands = []; commands.push(`M ${pathArray[0].x} ${pathArray[0].y}`); for (let i = 1; i < pathArray.length - 1; i++) { const previous = pathArray[i - 1]; const current = pathArray[i]; const next = pathArray[i + 1]; const prevDelta = { x: current.x - previous.x, y: current.y - previous.y }; const nextDelta = { x: next.x - current.x, y: next.y - current.y }; const prevLen = Math.sqrt(prevDelta.x * prevDelta.x + prevDelta.y * prevDelta.y); const nextLen = Math.sqrt(nextDelta.x * nextDelta.x + nextDelta.y * nextDelta.y); const prevUnit = prevLen ? { x: prevDelta.x / prevLen, y: prevDelta.y / prevLen } : { x: 0, y: 0 }; const nextUnit = nextLen ? { x: nextDelta.x / nextLen, y: nextDelta.y / nextLen } : { x: 0, y: 0 }; let dot = prevUnit.x * nextUnit.x + prevUnit.y * nextUnit.y; dot = Math.max(-1, Math.min(1, dot)); const angle = Math.acos(dot); if (angle < 0.01 || Math.abs(Math.PI - angle) < 0.01) { commands.push(`L ${current.x} ${current.y}`); continue; } const desiredOffset = targetRadius * Math.tan(angle / 2); const d = Math.min(desiredOffset, prevLen / 2, nextLen / 2); const effectiveRadius = d / Math.tan(angle / 2); const firstAnchor = { x: current.x - prevUnit.x * d, y: current.y - prevUnit.y * d }; const secondAnchor = { x: current.x + nextUnit.x * d, y: current.y + nextUnit.y * d }; commands.push(`L ${firstAnchor.x} ${firstAnchor.y}`); const cross = prevDelta.x * nextDelta.y - prevDelta.y * nextDelta.x; const sweepFlag = cross < 0 ? 0 : 1; commands.push(`A ${effectiveRadius} ${effectiveRadius} 0 0 ${sweepFlag} ${secondAnchor.x} ${secondAnchor.y}`); } const last = pathArray[pathArray.length - 1]; commands.push(`L ${last.x} ${last.y}`); return commands.join(" "); } }; // src/canvas-extensions/advanced-styles/edge-pathfinding-methods/edge-pathfinding-method.ts var EdgePathfindingMethod = class { constructor(plugin, canvas, fromNodeBBox, fromPos, fromBBoxSidePos, fromSide, toNodeBBox, toPos, toBBoxSidePos, toSide) { this.plugin = plugin; this.canvas = canvas; this.fromNodeBBox = fromNodeBBox; this.fromPos = fromPos; this.fromBBoxSidePos = fromBBoxSidePos; this.fromSide = fromSide; this.toNodeBBox = toNodeBBox; this.toPos = toPos; this.toBBoxSidePos = toBBoxSidePos; this.toSide = toSide; } }; // src/canvas-extensions/advanced-styles/edge-pathfinding-methods/pathfinding-a-star.ts var MAX_MS_CALCULATION = 100; var BASIC_DIRECTIONS = [ { dx: 1, dy: 0 }, { dx: -1, dy: 0 }, { dx: 0, dy: 1 }, { dx: 0, dy: -1 } ]; var DIAGONAL_DIRECTIONS = [ { dx: 1, dy: 1 }, { dx: -1, dy: 1 }, { dx: 1, dy: -1 }, { dx: -1, dy: -1 } ]; var DIAGONAL_COST = Math.sqrt(2); var ROUND_PATH_RADIUS = 5; var SMOOTHEN_PATH_TENSION = 0.2; var Node = class { constructor(x, y) { this.x = x; this.y = y; this.gCost = 0; this.hCost = 0; this.fCost = 0; this.parent = null; } // Only check for x and y, not gCost, hCost, fCost, or parent inList(nodes) { return nodes.some((n) => n.x === this.x && n.y === this.y); } }; var EdgePathfindingAStar = class extends EdgePathfindingMethod { getPath() { const nodeBBoxes = [...this.canvas.nodes.values()].filter((node) => { const nodeData = node.getData(); if (nodeData.portal === true) return false; const nodeBBox = node.getBBox(); const nodeContainsFromPos = BBoxHelper.insideBBox(this.fromPos, nodeBBox, true); const nodeContainsToPos = BBoxHelper.insideBBox(this.toPos, nodeBBox, true); return !nodeContainsFromPos && !nodeContainsToPos; }).map((node) => node.getBBox()); const fromPosWithMargin = BBoxHelper.moveInDirection(this.fromPos, this.fromSide, 10); const toPosWithMargin = BBoxHelper.moveInDirection(this.toPos, this.toSide, 10); const allowDiagonal = this.plugin.settings.getSetting("edgeStylePathfinderAllowDiagonal"); const pathArray = this.aStarAlgorithm(fromPosWithMargin, toPosWithMargin, nodeBBoxes, CanvasHelper.GRID_SIZE / 2, allowDiagonal); if (!pathArray) return null; pathArray.splice(0, 0, this.fromPos); pathArray.splice(pathArray.length, 0, this.toPos); let svgPath; const roundPath = this.plugin.settings.getSetting("edgeStylePathfinderPathRounded"); if (roundPath) { if (allowDiagonal) svgPath = SvgPathHelper.pathArrayToSvgPath(SvgPathHelper.smoothenPathArray(pathArray, SMOOTHEN_PATH_TENSION)); else svgPath = SvgPathHelper.pathArrayToRoundedSvgPath(pathArray, ROUND_PATH_RADIUS); } else svgPath = SvgPathHelper.pathArrayToSvgPath(pathArray); return { svgPath, center: pathArray[Math.floor(pathArray.length / 2)], rotateArrows: false }; } aStarAlgorithm(fromPos, toPos, obstacles, gridResolution, allowDiagonal) { const start = new Node( Math.floor(fromPos.x / gridResolution) * gridResolution, Math.floor(fromPos.y / gridResolution) * gridResolution ); if (this.fromSide === "right" && fromPos.x !== start.x) start.x += gridResolution; if (this.fromSide === "bottom" && fromPos.y !== start.y) start.y += gridResolution; const end = new Node( Math.floor(toPos.x / gridResolution) * gridResolution, Math.floor(toPos.y / gridResolution) * gridResolution ); if (this.toSide === "right" && toPos.x !== end.x) end.x += gridResolution; if (this.toSide === "bottom" && toPos.y !== end.y) end.y += gridResolution; if (this.isInsideObstacle(start, obstacles) || this.isInsideObstacle(end, obstacles)) return null; const openSet = [start]; const closedSet = []; const startTimestamp = performance.now(); while (openSet.length > 0) { let current = null; let lowestFCost = Infinity; for (const node of openSet) { if (node.fCost < lowestFCost) { current = node; lowestFCost = node.fCost; } } if (performance.now() - startTimestamp > MAX_MS_CALCULATION) return null; if (!current) return null; openSet.splice(openSet.indexOf(current), 1); closedSet.push(current); if (current.x === end.x && current.y === end.y) return [fromPos, ...this.reconstructPath(current), toPos].map((node) => ({ x: node.x, y: node.y })); if (!(current.x === start.x && current.y === start.y) && this.isTouchingObstacle(current, obstacles)) continue; for (const neighbor of this.getPossibleNeighbors(current, obstacles, gridResolution, allowDiagonal)) { if (neighbor.inList(closedSet)) continue; const tentativeGCost = current.gCost + (allowDiagonal ? this.getMovementCost({ dx: neighbor.x - current.x, dy: neighbor.y - current.y }) : 1); if (!neighbor.inList(openSet) || tentativeGCost < neighbor.gCost) { neighbor.parent = current; neighbor.gCost = tentativeGCost; neighbor.hCost = this.heuristic(neighbor, end); neighbor.fCost = neighbor.gCost + neighbor.hCost; openSet.push(neighbor); } } } return null; } // Manhattan distance heuristic(node, end) { return Math.abs(node.x - end.x) + Math.abs(node.y - end.y); } // Define a function to check if a position isn't inside any obstacle isTouchingObstacle(node, obstacles) { return obstacles.some((obstacle) => BBoxHelper.insideBBox(node, obstacle, true)); } isInsideObstacle(node, obstacles) { return obstacles.some((obstacle) => BBoxHelper.insideBBox(node, obstacle, false)); } // Define a function to calculate movement cost based on direction getMovementCost(direction) { return direction.dx !== 0 && direction.dy !== 0 ? DIAGONAL_COST : 1; } getPossibleNeighbors(node, obstacles, gridResolution, allowDiagonal) { const neighbors = []; const availableDirections = allowDiagonal ? [...BASIC_DIRECTIONS, ...DIAGONAL_DIRECTIONS] : BASIC_DIRECTIONS; for (const direction of availableDirections) { const neighbor = new Node( node.x + direction.dx * gridResolution, node.y + direction.dy * gridResolution ); neighbor.gCost = node.gCost + this.getMovementCost(direction); if (this.isInsideObstacle(neighbor, obstacles)) continue; neighbors.push(neighbor); } return neighbors; } reconstructPath(node) { const path = []; while (node) { path.push(node); node = node.parent; } return path.reverse(); } }; // src/canvas-extensions/advanced-styles/edge-pathfinding-methods/pathfinding-direct.ts var EdgePathfindingDirect = class extends EdgePathfindingMethod { getPath() { return { svgPath: SvgPathHelper.pathArrayToSvgPath([this.fromPos, this.toPos]), center: { x: (this.fromPos.x + this.toPos.x) / 2, y: (this.fromPos.y + this.toPos.y) / 2 }, rotateArrows: true }; } }; // src/canvas-extensions/advanced-styles/edge-pathfinding-methods/pathfinding-square.ts var ROUNDED_EDGE_RADIUS = 5; var EdgePathfindingSquare = class extends EdgePathfindingMethod { getPath() { const pathArray = []; let center = { x: (this.fromPos.x + this.toPos.x) / 2, y: (this.fromPos.y + this.toPos.y) / 2 }; const idealCenter = BBoxHelper.isHorizontal(this.fromSide) ? { x: this.toBBoxSidePos.x, y: this.fromBBoxSidePos.y } : { x: this.fromBBoxSidePos.x, y: this.toBBoxSidePos.y }; const isPathCollidingAtFrom = this.fromSide === "top" && idealCenter.y > this.fromPos.y || this.fromSide === "bottom" && idealCenter.y < this.fromPos.y || this.fromSide === "left" && idealCenter.x > this.fromPos.x || this.fromSide === "right" && idealCenter.x < this.fromPos.x; const isPathCollidingAtTo = this.toSide === "top" && idealCenter.y > this.toPos.y || this.toSide === "bottom" && idealCenter.y < this.toPos.y || this.toSide === "left" && idealCenter.x > this.toPos.x || this.toSide === "right" && idealCenter.x < this.toPos.x; if (this.fromSide === this.toSide) { const uPath = this.getUPath(this.fromPos, this.toPos, this.fromSide, this.toSide); pathArray.push(...uPath.pathArray); center = uPath.center; } else if (BBoxHelper.isHorizontal(this.fromSide) === BBoxHelper.isHorizontal(this.toSide)) { let zPath; if (!isPathCollidingAtFrom || !isPathCollidingAtTo) { zPath = this.getZPath(this.fromPos, this.toPos, this.fromSide, this.toSide); pathArray.push(...zPath.pathArray); } else { const fromDirection = BBoxHelper.direction(this.fromSide); const firstFromDetourPoint = BBoxHelper.isHorizontal(this.fromSide) ? { x: CanvasHelper.alignToGrid(this.fromBBoxSidePos.x + fromDirection * CanvasHelper.GRID_SIZE), y: this.fromBBoxSidePos.y } : { x: this.fromBBoxSidePos.x, y: CanvasHelper.alignToGrid(this.fromBBoxSidePos.y + fromDirection * CanvasHelper.GRID_SIZE) }; const toDirection = BBoxHelper.direction(this.toSide); const firstToDetourPoint = BBoxHelper.isHorizontal(this.toSide) ? { x: CanvasHelper.alignToGrid(this.toBBoxSidePos.x + toDirection * CanvasHelper.GRID_SIZE), y: this.toBBoxSidePos.y } : { x: this.toBBoxSidePos.x, y: CanvasHelper.alignToGrid(this.toBBoxSidePos.y + toDirection * CanvasHelper.GRID_SIZE) }; const newFromSide = BBoxHelper.isHorizontal(this.fromSide) ? firstFromDetourPoint.y < this.fromPos.y ? "top" : "bottom" : firstFromDetourPoint.x < firstToDetourPoint.x ? "right" : "left"; zPath = this.getZPath(firstFromDetourPoint, firstToDetourPoint, newFromSide, BBoxHelper.getOppositeSide(newFromSide)); pathArray.push(this.fromPos); pathArray.push(...zPath.pathArray); pathArray.push(this.toPos); } center = zPath.center; } else { if (isPathCollidingAtFrom || isPathCollidingAtTo) { if (isPathCollidingAtFrom && isPathCollidingAtTo) { const direction = BBoxHelper.direction(this.fromSide); let firstFromDetourPoint; let secondFromDetourPoint; if (BBoxHelper.isHorizontal(this.fromSide)) { const combinedBBoxes = BBoxHelper.combineBBoxes([this.fromNodeBBox, this.toNodeBBox]); firstFromDetourPoint = { x: CanvasHelper.alignToGrid((direction > 0 ? combinedBBoxes.maxX : combinedBBoxes.minX) + direction * CanvasHelper.GRID_SIZE), y: this.fromBBoxSidePos.y }; secondFromDetourPoint = { x: firstFromDetourPoint.x, y: BBoxHelper.getCenterOfBBoxSide(this.fromNodeBBox, this.toSide).y }; } else { const combinedBBoxes = BBoxHelper.combineBBoxes([this.fromNodeBBox, this.toNodeBBox]); firstFromDetourPoint = { x: this.fromBBoxSidePos.x, y: CanvasHelper.alignToGrid((direction > 0 ? combinedBBoxes.maxY : combinedBBoxes.minY) + direction * CanvasHelper.GRID_SIZE) }; secondFromDetourPoint = { x: BBoxHelper.getCenterOfBBoxSide(this.fromNodeBBox, this.toSide).x, y: firstFromDetourPoint.y }; } const uPath = this.getUPath(secondFromDetourPoint, this.toPos, this.toSide, this.toSide); pathArray.push(this.fromPos); pathArray.push(firstFromDetourPoint); pathArray.push(...uPath.pathArray); center = pathArray[Math.floor(pathArray.length / 2)]; } else { if (isPathCollidingAtFrom) { const direction = BBoxHelper.direction(this.fromSide); const firstFromDetourPoint = BBoxHelper.isHorizontal(this.fromSide) ? { x: CanvasHelper.alignToGrid(this.fromBBoxSidePos.x + direction * CanvasHelper.GRID_SIZE), y: this.fromBBoxSidePos.y } : { x: this.fromBBoxSidePos.x, y: CanvasHelper.alignToGrid(this.fromBBoxSidePos.y + direction * CanvasHelper.GRID_SIZE) }; const useUPath = BBoxHelper.isHorizontal(this.fromSide) ? this.toPos.y > BBoxHelper.getCenterOfBBoxSide(this.fromNodeBBox, BBoxHelper.getOppositeSide(this.toSide)).y === BBoxHelper.direction(this.toSide) > 0 : this.toPos.x > BBoxHelper.getCenterOfBBoxSide(this.fromNodeBBox, BBoxHelper.getOppositeSide(this.toSide)).x === BBoxHelper.direction(this.toSide) > 0; const connectionSide = useUPath ? this.toSide : BBoxHelper.getOppositeSide(this.toSide); const secondFromDetourPoint = BBoxHelper.isHorizontal(this.fromSide) ? { x: firstFromDetourPoint.x, y: BBoxHelper.getCenterOfBBoxSide(this.fromNodeBBox, connectionSide).y } : { x: BBoxHelper.getCenterOfBBoxSide(this.fromNodeBBox, connectionSide).x, y: firstFromDetourPoint.y }; const path = useUPath ? this.getUPath(secondFromDetourPoint, this.toPos, this.toSide, this.toSide) : this.getZPath(secondFromDetourPoint, this.toPos, this.toSide, this.toSide); pathArray.push(this.fromPos); pathArray.push(firstFromDetourPoint); pathArray.push(...path.pathArray); center = path.center; } if (isPathCollidingAtTo) { const direction = BBoxHelper.direction(this.toSide); const firstToDetourPoint = BBoxHelper.isHorizontal(this.toSide) ? { x: CanvasHelper.alignToGrid(this.toBBoxSidePos.x + direction * CanvasHelper.GRID_SIZE), y: this.toBBoxSidePos.y } : { x: this.toBBoxSidePos.x, y: CanvasHelper.alignToGrid(this.toBBoxSidePos.y + direction * CanvasHelper.GRID_SIZE) }; const useUPath = BBoxHelper.isHorizontal(this.toSide) ? this.fromPos.y > BBoxHelper.getCenterOfBBoxSide(this.toNodeBBox, BBoxHelper.getOppositeSide(this.fromSide)).y === BBoxHelper.direction(this.fromSide) > 0 : this.fromPos.x > BBoxHelper.getCenterOfBBoxSide(this.toNodeBBox, BBoxHelper.getOppositeSide(this.fromSide)).x === BBoxHelper.direction(this.fromSide) > 0; const connectionSide = useUPath ? this.fromSide : BBoxHelper.getOppositeSide(this.fromSide); const secondToDetourPoint = BBoxHelper.isHorizontal(this.toSide) ? { x: firstToDetourPoint.x, y: BBoxHelper.getCenterOfBBoxSide(this.toNodeBBox, connectionSide).y } : { x: BBoxHelper.getCenterOfBBoxSide(this.toNodeBBox, connectionSide).x, y: firstToDetourPoint.y }; const path = useUPath ? this.getUPath(this.fromPos, secondToDetourPoint, this.fromSide, this.fromSide) : this.getZPath(this.fromPos, secondToDetourPoint, this.fromSide, this.fromSide); pathArray.push(...path.pathArray); pathArray.push(secondToDetourPoint); pathArray.push(firstToDetourPoint); pathArray.push(this.toPos); center = path.center; } } } else { pathArray.push( this.fromPos, idealCenter, this.toPos ); center = { x: pathArray[1].x, y: pathArray[1].y }; } } const svgPath = this.plugin.settings.getSetting("edgeStyleSquarePathRounded") ? SvgPathHelper.pathArrayToRoundedSvgPath(pathArray, ROUNDED_EDGE_RADIUS) : SvgPathHelper.pathArrayToSvgPath(pathArray); return { svgPath, center, rotateArrows: false }; } getUPath(fromPos, toPos, fromSide, toSide) { const direction = BBoxHelper.direction(fromSide); if (BBoxHelper.isHorizontal(fromSide)) { const xExtremum = direction > 0 ? Math.max(fromPos.x, toPos.x) : Math.min(fromPos.x, toPos.x); const x = CanvasHelper.alignToGrid(xExtremum + direction * CanvasHelper.GRID_SIZE); return { pathArray: [ fromPos, { x, y: fromPos.y }, { x, y: toPos.y }, toPos ], center: { x, y: (fromPos.y + toPos.y) / 2 } }; } else { const yExtremum = direction > 0 ? Math.max(fromPos.y, toPos.y) : Math.min(fromPos.y, toPos.y); const y = CanvasHelper.alignToGrid(yExtremum + direction * CanvasHelper.GRID_SIZE); return { pathArray: [ fromPos, { x: fromPos.x, y }, { x: toPos.x, y }, toPos ], center: { x: (fromPos.x + toPos.x) / 2, y } }; } } getZPath(fromPos, toPos, fromSide, toSide) { if (BBoxHelper.isHorizontal(fromSide)) { const midX = fromPos.x + (toPos.x - fromPos.x) / 2; return { pathArray: [ fromPos, { x: midX, y: fromPos.y }, { x: midX, y: toPos.y }, toPos ], center: { x: midX, y: (fromPos.y + toPos.y) / 2 } }; } else { const midY = fromPos.y + (toPos.y - fromPos.y) / 2; return { pathArray: [ fromPos, { x: fromPos.x, y: midY }, { x: toPos.x, y: midY }, toPos ], center: { x: (fromPos.x + toPos.x) / 2, y: midY } }; } } }; // src/utils/text-helper.ts var TextHelper = class { static toCamelCase(str) { return str.replace(/-./g, (x) => x[1].toUpperCase()); } static toTitleCase(str) { return str.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" "); } }; // src/canvas-extensions/advanced-styles/style-config.ts function styleAttributeValidator(json) { var _a; const hasKey = json.key !== void 0; const hasLabel = json.label !== void 0; const hasOptions = Array.isArray(json.options); if (!hasKey) console.error('Style attribute is missing the "key" property'); if (!hasLabel) console.error('Style attribute is missing the "label" property'); if (!hasOptions) console.error('Style attribute is missing the "options" property or it is not an array'); json.key = TextHelper.toCamelCase(json.key); let optionsValid = true; let hasDefault = false; for (const option of json.options) { const hasIcon = option.icon !== void 0; const hasLabel2 = option.label !== void 0; const hasValue = option.value !== void 0; if (!hasIcon) console.error(`Style attribute option (${(_a = option.value) != null ? _a : option.label}) is missing the "icon" property`); if (!hasLabel2) console.error(`Style attribute option (${option.value}) is missing the "label" property`); if (!hasValue) console.error(`Style attribute option (${option.label}) is missing the "value" property`); if (!hasIcon || !hasLabel2 || !hasValue) optionsValid = false; if (option.value === null) hasDefault = true; } if (!hasDefault) console.error('Style attribute is missing a default option (option with a "value" of null)'); const isValid = hasKey && hasLabel && hasOptions && optionsValid && hasDefault; return isValid ? json : null; } var BUILTIN_NODE_STYLE_ATTRIBUTES = [ { key: "textAlign", label: "Text Alignment", nodeTypes: ["text"], options: [ { icon: "align-left", label: "Left", value: null }, { icon: "align-center", label: "Center", value: "center" }, { icon: "align-right", label: "Right", value: "right" } ] }, { key: "shape", label: "Shape", nodeTypes: ["text"], options: [ { icon: "rectangle-horizontal", label: "Round Rectangle", value: null }, { icon: "shape-pill", label: "Pill", value: "pill" }, { icon: "diamond", label: "Diamond", value: "diamond" }, { icon: "shape-parallelogram", label: "Parallelogram", value: "parallelogram" }, { icon: "circle", label: "Circle", value: "circle" }, { icon: "shape-predefined-process", label: "Predefined Process", value: "predefined-process" }, { icon: "shape-document", label: "Document", value: "document" }, { icon: "shape-database", label: "Database", value: "database" } ] }, { key: "border", label: "Border", options: [ { icon: "border-solid", label: "Solid", value: null }, { icon: "border-dashed", label: "Dashed", value: "dashed" }, { icon: "border-dotted", label: "Dotted", value: "dotted" }, { icon: "eye-off", label: "Invisible", value: "invisible" } ] } ]; var BUILTIN_EDGE_STYLE_ATTRIBUTES = [ { key: "path", label: "Path Style", options: [ { icon: "path-solid", label: "Solid", value: null }, { icon: "path-dotted", label: "Dotted", value: "dotted" }, { icon: "path-short-dashed", label: "Short Dashed", value: "short-dashed" }, { icon: "path-long-dashed", label: "Long Dashed", value: "long-dashed" } ] }, { key: "arrow", label: "Arrow Style", options: [ { icon: "arrow-triangle", label: "Triangle", value: null }, { icon: "arrow-triangle-outline", label: "Triangle Outline", value: "triangle-outline" }, { icon: "arrow-thin-triangle", label: "Thin Triangle", value: "thin-triangle" }, { icon: "arrow-halved-triangle", label: "Halved Triangle", value: "halved-triangle" }, { icon: "arrow-diamond", label: "Diamond", value: "diamond" }, { icon: "arrow-diamond-outline", label: "Diamond Outline", value: "diamond-outline" }, { icon: "arrow-circle", label: "Circle", value: "circle" }, { icon: "arrow-circle-outline", label: "Circle Outline", value: "circle-outline" }, { icon: "tally-1", label: "Blunt", value: "blunt" } ] }, { key: "pathfindingMethod", label: "Pathfinding Method", options: [ { icon: "pathfinding-method-bezier", label: "Bezier", value: null }, { icon: "slash", label: "Direct", value: "direct" }, { icon: "pathfinding-method-square", label: "Square", value: "square" }, { icon: "map", label: "A*", value: "a-star" } ] } ]; // src/managers/css-styles-config-manager.ts var import_obsidian3 = require("obsidian"); var CssStylesConfigManager = class { constructor(plugin, trigger, validate) { this.plugin = plugin; this.validate = validate; this.cachedConfig = null; this.configRegex = new RegExp(`\\/\\*\\s*@${trigger}\\s*\\n([\\s\\S]*?)\\*\\/`, "g"); this.plugin.registerEvent(this.plugin.app.workspace.on( "css-change", () => { this.cachedConfig = null; } )); } getStyles() { if (this.cachedConfig) return this.cachedConfig; this.cachedConfig = []; const styleSheets = document.styleSheets; for (let i = 0; i < styleSheets.length; i++) { const sheet = styleSheets.item(i); if (!sheet) continue; const styleSheetConfigs = this.parseStyleConfigsFromCSS(sheet); for (const config of styleSheetConfigs) { const validConfig = this.validate(config); if (!validConfig) continue; this.cachedConfig.push(validConfig); } } return this.cachedConfig; } parseStyleConfigsFromCSS(sheet) { var _a, _b; const textContent = (_b = (_a = sheet == null ? void 0 : sheet.ownerNode) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim(); if (!textContent) return []; const configs = []; const matches = textContent.matchAll(this.configRegex); for (const match of matches) { const yamlString = match[1]; const configYaml = (0, import_obsidian3.parseYaml)(yamlString); configs.push(configYaml); } return configs; } }; // src/canvas-extensions/advanced-styles/edge-styles.ts var GET_EDGE_CSS_STYLES_MANAGER = (plugin) => new CssStylesConfigManager(plugin, "advanced-canvas-edge-style", styleAttributeValidator); var EDGE_PATHFINDING_METHODS = { "direct": EdgePathfindingDirect, "square": EdgePathfindingSquare, "a-star": EdgePathfindingAStar }; var MAX_LIVE_UPDATE_SELECTION_SIZE = 5; var EdgeStylesExtension = class extends CanvasExtension { isEnabled() { return "edgesStylingFeatureEnabled"; } init() { this.cssStylesManager = GET_EDGE_CSS_STYLES_MANAGER(this.plugin); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenuCreated(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-changed", (canvas, edge) => this.onEdgeChanged(canvas, edge) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-center-requested", (canvas, edge, center) => this.onEdgeCenterRequested(canvas, edge, center) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-added", (canvas, node) => { if (canvas.dirty.size > 1 && !canvas.isPasting) return; this.updateAllEdgesInArea(canvas, node.getBBox()); } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-moved", // Only update edges this way if a node got moved with the arrow keys (canvas, node, keyboard) => node.initialized && keyboard ? this.updateAllEdgesInArea(canvas, node.getBBox()) : void 0 )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-removed", (canvas, node) => this.updateAllEdgesInArea(canvas, node.getBBox()) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:dragging-state-changed", (canvas, isDragging) => { if (isDragging) return; const selectedNodes = canvas.getSelectionData().nodes.map((nodeData) => canvas.nodes.get(nodeData.id)).filter((node) => node !== void 0); const selectedNodeBBoxes = selectedNodes.map((node) => node.getBBox()); const selectedNodeBBox = BBoxHelper.combineBBoxes(selectedNodeBBoxes); this.updateAllEdgesInArea(canvas, selectedNodeBBox); } )); } // Skip if isDragging and setting isn't enabled and not connecting an edge shouldUpdateEdge(canvas) { return !canvas.isDragging || this.plugin.settings.getSetting("edgeStyleUpdateWhileDragging") || canvas.canvasEl.hasClass("is-connecting"); } onPopupMenuCreated(canvas) { var _a; const selectedEdges = [...canvas.selection].filter((item) => item.path !== void 0); if (canvas.readonly || selectedEdges.length === 0 || selectedEdges.length !== canvas.selection.size) return; CanvasHelper.addStyleAttributesToPopup( this.plugin, canvas, [ ...BUILTIN_EDGE_STYLE_ATTRIBUTES, /* Legacy */ ...this.plugin.settings.getSetting("customEdgeStyleAttributes"), ...this.cssStylesManager.getStyles() ], (_a = selectedEdges[0].getData().styleAttributes) != null ? _a : {}, (attribute, value) => this.setStyleAttributeForSelection(canvas, attribute, value) ); } setStyleAttributeForSelection(canvas, attribute, value) { const selectedEdges = [...canvas.selection].filter((item) => item.path !== void 0); for (const edge of selectedEdges) { const edgeData = edge.getData(); edge.setData({ ...edgeData, styleAttributes: { ...edgeData.styleAttributes, [attribute.key]: value } }); } canvas.pushHistory(canvas.getData()); } updateAllEdgesInArea(canvas, bbox) { if (!this.shouldUpdateEdge(canvas)) return; for (const edge of canvas.edges.values()) { if (!BBoxHelper.isColliding(edge.getBBox(), bbox)) continue; canvas.markDirty(edge); } } onEdgeChanged(canvas, edge) { var _a, _b, _c, _d, _e, _f, _g; if (!canvas.dirty.has(edge) && !canvas.selection.has(edge)) return; if (!this.shouldUpdateEdge(canvas)) { const tooManySelected = canvas.selection.size > MAX_LIVE_UPDATE_SELECTION_SIZE; if (tooManySelected) return; const groupNodesSelected = [...canvas.selection].some((item) => { var _a2; return ((_a2 = item.getData()) == null ? void 0 : _a2.type) === "group"; }); if (groupNodesSelected) return; } const edgeData = edge.getData(); if (!edge.bezier) return; edge.center = void 0; edge.updatePath(); const pathfindingMethod = (_a = edgeData.styleAttributes) == null ? void 0 : _a.pathfindingMethod; if (pathfindingMethod && pathfindingMethod in EDGE_PATHFINDING_METHODS) { const fromNodeBBox = edge.from.node.getBBox(); const fromBBoxSidePos = BBoxHelper.getCenterOfBBoxSide(fromNodeBBox, edge.from.side); const fromPos = edge.from.end === "none" ? fromBBoxSidePos : edge.bezier.from; const toNodeBBox = edge.to.node.getBBox(); const toBBoxSidePos = BBoxHelper.getCenterOfBBoxSide(toNodeBBox, edge.to.side); const toPos = edge.to.end === "none" ? toBBoxSidePos : edge.bezier.to; const path = new EDGE_PATHFINDING_METHODS[pathfindingMethod]( this.plugin, canvas, fromNodeBBox, fromPos, fromBBoxSidePos, edge.from.side, toNodeBBox, toPos, toBBoxSidePos, edge.to.side ).getPath(); if (!path) return; edge.center = path.center; edge.path.interaction.setAttr("d", path == null ? void 0 : path.svgPath); edge.path.display.setAttr("d", path == null ? void 0 : path.svgPath); } (_b = edge.labelElement) == null ? void 0 : _b.render(); const arrowPolygonPoints = this.getArrowPolygonPoints((_c = edgeData.styleAttributes) == null ? void 0 : _c.arrow); if ((_d = edge.fromLineEnd) == null ? void 0 : _d.el) (_e = edge.fromLineEnd.el.querySelector("polygon")) == null ? void 0 : _e.setAttribute("points", arrowPolygonPoints); if ((_f = edge.toLineEnd) == null ? void 0 : _f.el) (_g = edge.toLineEnd.el.querySelector("polygon")) == null ? void 0 : _g.setAttribute("points", arrowPolygonPoints); } onEdgeCenterRequested(_canvas, edge, center) { var _a, _b, _c, _d; center.x = (_b = (_a = edge.center) == null ? void 0 : _a.x) != null ? _b : center.x; center.y = (_d = (_c = edge.center) == null ? void 0 : _c.y) != null ? _d : center.y; } getArrowPolygonPoints(arrowStyle) { if (arrowStyle === "halved-triangle") return `-2,0 7.5,12 -2,12`; else if (arrowStyle === "thin-triangle") return `0,0 7,10 0,0 0,10 0,0 -7,10`; else if (arrowStyle === "diamond" || arrowStyle === "diamond-outline") return `0,0 5,10 0,20 -5,10`; else if (arrowStyle === "circle" || arrowStyle === "circle-outline") return `0 0, 4.95 1.8, 7.5 6.45, 6.6 11.7, 2.7 15, -2.7 15, -6.6 11.7, -7.5 6.45, -4.95 1.8`; else if (arrowStyle === "blunt") return `-10,8 10,8 10,6 -10,6`; else return `0,0 6.5,10.4 -6.5,10.4`; } }; // src/canvas-extensions/advanced-styles/node-styles.ts var GET_NODE_CSS_STYLES_MANAGER = (plugin) => new CssStylesConfigManager(plugin, "advanced-canvas-node-style", styleAttributeValidator); var NodeStylesExtension = class extends CanvasExtension { isEnabled() { return "nodeStylingFeatureEnabled"; } init() { this.cssStylesManager = GET_NODE_CSS_STYLES_MANAGER(this.plugin); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenuCreated(canvas) )); } onPopupMenuCreated(canvas) { var _a; const selectionNodeData = canvas.getSelectionData().nodes; if (canvas.readonly || selectionNodeData.length === 0 || selectionNodeData.length !== canvas.selection.size) return; const selectedNodeTypes = new Set(selectionNodeData.map((node) => node.type)); const availableNodeStyles = [ ...BUILTIN_NODE_STYLE_ATTRIBUTES, /* Legacy */ ...this.plugin.settings.getSetting("customNodeStyleAttributes"), ...this.cssStylesManager.getStyles() ].filter((style) => !style.nodeTypes || style.nodeTypes.some((type) => selectedNodeTypes.has(type))); CanvasHelper.addStyleAttributesToPopup( this.plugin, canvas, availableNodeStyles, (_a = selectionNodeData[0].styleAttributes) != null ? _a : {}, (attribute, value) => this.setStyleAttributeForSelection(canvas, attribute, value) ); } setStyleAttributeForSelection(canvas, attribute, value) { const selectionNodeData = canvas.getSelectionData().nodes; for (const nodeData of selectionNodeData) { const node = canvas.nodes.get(nodeData.id); if (!node) continue; if (attribute.nodeTypes && !attribute.nodeTypes.includes(nodeData.type)) continue; node.setData({ ...nodeData, styleAttributes: { ...nodeData.styleAttributes, [attribute.key]: value } }); } canvas.pushHistory(canvas.getData()); } }; // src/canvas-extensions/variable-breakpoint-canvas-extension.ts var VARIABLE_BREAKPOINT_CSS_VAR = "--variable-breakpoint"; var VariableBreakpointCanvasExtension = class extends CanvasExtension { isEnabled() { return "variableBreakpointFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-breakpoint-changed", (canvas, node, breakpointRef) => this.onNodeBreakpointChanged(canvas, node, breakpointRef) )); } onNodeBreakpointChanged(canvas, node, breakpointRef) { if (!node.initialized) return; if (node.breakpoint === void 0) { const computedStyle = window.getComputedStyle(node.nodeEl); const variableBreakpointString = computedStyle.getPropertyValue(VARIABLE_BREAKPOINT_CSS_VAR); let numberBreakpoint; if (variableBreakpointString.length > 0 && !isNaN(numberBreakpoint = parseFloat(variableBreakpointString))) node.breakpoint = numberBreakpoint; else node.breakpoint = null; } if (node.breakpoint === null) return; breakpointRef.value = canvas.zoom > node.breakpoint; } }; // src/settings.ts var README_URL = "https://github.com/Developer-Mike/obsidian-advanced-canvas?tab=readme-ov-file"; var SPENT_HOURS = 250; var RECEIVED_DONATIONS = 606; var HOURLY_RATE_GOAL = 15; var KOFI_PAGE_URL = "https://ko-fi.com/X8X27IA08"; var KOFI_BADGE_URI = "data:image/webp;base64,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"; var DEFAULT_SETTINGS_VALUES = { nodeTypeOnDoubleClick: "text", alignNewNodesToGrid: true, defaultTextNodeDimensions: [260, 60], defaultFileNodeDimensions: [400, 400], minNodeSize: 60, maxNodeWidth: -1, disableFontSizeRelativeToZoom: false, canvasMetadataCompatibilityEnabled: true, enableSingleNodeLinks: true, combineCustomStylesInDropdown: false, nodeStylingFeatureEnabled: true, customNodeStyleAttributes: [], defaultTextNodeColor: 0, defaultTextNodeStyleAttributes: {}, edgesStylingFeatureEnabled: true, customEdgeStyleAttributes: [], inheritEdgeColorFromNode: false, defaultEdgeColor: 0, defaultEdgeLineDirection: "unidirectional", defaultEdgeStyleAttributes: {}, edgeStyleUpdateWhileDragging: false, edgeStyleSquarePathRounded: true, edgeStylePathfinderAllowDiagonal: false, edgeStylePathfinderPathRounded: true, variableBreakpointFeatureEnabled: false, zOrderingControlFeatureEnabled: false, zOrderingControlShowOneLayerShiftOptions: false, aspectRatioControlFeatureEnabled: false, commandsFeatureEnabled: true, zoomToClonedNode: true, cloneNodeMargin: 20, expandNodeStepSize: 20, nativeFileSearchEnabled: true, floatingEdgeFeatureEnabled: true, allowFloatingEdgeCreation: false, newEdgeFromSideFloating: false, flipEdgeFeatureEnabled: true, betterExportFeatureEnabled: true, betterReadonlyEnabled: false, hideBackgroundGridWhenInReadonly: true, disableNodePopup: false, disableZoom: false, disablePan: false, autoResizeNodeFeatureEnabled: false, autoResizeNodeEnabledByDefault: false, autoResizeNodeMaxHeight: -1, autoResizeNodeSnapToGrid: true, collapsibleGroupsFeatureEnabled: true, collapsedGroupPreviewOnDrag: true, focusModeFeatureEnabled: false, presentationFeatureEnabled: true, showSetStartNodeInPopup: false, defaultSlideDimensions: [1200, 675], wrapInSlidePadding: 20, resetViewportOnPresentationEnd: true, useArrowKeysToChangeSlides: true, usePgUpPgDownKeysToChangeSlides: true, zoomToSlideWithoutPadding: true, useUnclampedZoomWhilePresenting: false, fullscreenPresentationEnabled: true, slideTransitionAnimationDuration: 0.5, slideTransitionAnimationIntensity: 1.25, canvasEncapsulationEnabled: false, portalsFeatureEnabled: true, showEdgesIntoDisabledPortals: true, autoFileNodeEdgesFeatureEnabled: false, autoFileNodeEdgesFrontmatterKey: "canvas-edges", edgeHighlightEnabled: false, highlightIncomingEdges: false, edgeSelectionEnabled: false, selectEdgeByDirection: false }; var SETTINGS = { general: { label: "General", description: "General settings of the Advanced Canvas plugin.", disableToggle: true, children: { nodeTypeOnDoubleClick: { label: "Node type on double click", description: "The type of node that will be created when double clicking on the canvas.", type: "dropdown", options: { "text": "Text", "file": "File" } }, alignNewNodesToGrid: { label: "Always align new nodes to grid", description: "When enabled, new nodes will be aligned to the grid.", type: "boolean" }, defaultTextNodeDimensions: { label: "Default text node dimensions", description: "The default dimensions of a text node.", type: "dimension", parse: (value) => { const width = Math.max(1, parseInt(value[0]) || 0); const height = Math.max(1, parseInt(value[1]) || 0); return [width, height]; } }, defaultFileNodeDimensions: { label: "Default file node dimensions", description: "The default dimensions of a file node.", type: "dimension", parse: (value) => { const width = Math.max(1, parseInt(value[0]) || 0); const height = Math.max(1, parseInt(value[1]) || 0); return [width, height]; } }, minNodeSize: { label: "Minimum node size", description: "The minimum size (either width or height) of a node.", type: "number", parse: (value) => Math.max(1, parseInt(value) || 0) }, maxNodeWidth: { label: "Maximum node width", description: "The maximum width of a node. Set to -1 for no limit.", type: "number", parse: (value) => Math.max(-1, parseInt(value) || 0) }, disableFontSizeRelativeToZoom: { label: "Disable font size relative to zoom", description: "When enabled, the font size of e.g. group node titles and edge labels will not increase when zooming out.", type: "boolean" } } }, commandsFeatureEnabled: { label: "Extended commands", description: "Add more commands to the canvas.", infoSection: "canvas-commands", children: { zoomToClonedNode: { label: "Zoom to cloned node", description: "When enabled, the canvas will zoom to the cloned node.", type: "boolean" }, cloneNodeMargin: { label: "Clone node margin", description: "The margin between the cloned node and the source node.", type: "number", parse: (value) => Math.max(0, parseInt(value) || 0) }, expandNodeStepSize: { label: "Expand node step size", description: "The step size for expanding the node.", type: "number", parse: (value) => Math.max(1, parseInt(value) || 0) } } }, canvasMetadataCompatibilityEnabled: { label: "Enable .canvas metadata cache compatibility", description: "Make .canvas files compatible with the backlinks and outgoing links feature and show the connections in the graph view.", infoSection: "full-metadata-cache-support", children: { enableSingleNodeLinks: { label: "Enable support for linking to a node using a [[wikilink]]", description: "When enabled, you can link and embed a node using [[canvas-file#node-id]].", type: "boolean" } } }, nativeFileSearchEnabled: { label: "Native-like file search", description: "When enabled, the file search will be done using the native Obsidian file search.", infoSection: "native-like-file-search", children: {} }, autoFileNodeEdgesFeatureEnabled: { label: "Auto file node edges", description: "Automatically create edges between file nodes based their frontmatter links.", infoSection: "auto-file-node-edges", children: { autoFileNodeEdgesFrontmatterKey: { label: "Frontmatter key name", description: "The frontmatter key to fetch the outgoing edges from. (Keep the default to ensure best compatibility.)", type: "text", parse: (value) => value.trim() || "canvas-edges" } } }, portalsFeatureEnabled: { label: "Portals", description: "Create portals to other canvases.", infoSection: "portals", children: { showEdgesIntoDisabledPortals: { label: "Show edges into disabled portals", description: "When enabled, edges into disabled portals will be shown.", type: "boolean" } } }, collapsibleGroupsFeatureEnabled: { label: "Collapsible groups", description: "Group nodes can be collapsed and expanded to keep the canvas organized.", infoSection: "collapsible-groups", children: { collapsedGroupPreviewOnDrag: { label: "Collapsed group preview on drag", description: "When enabled, a group that is collapsed show its border while dragging a node.", type: "boolean" } } }, combineCustomStylesInDropdown: { label: "Combine custom styles", description: "Combine all style attributes of Advanced Canvas in a single dropdown.", children: {} }, nodeStylingFeatureEnabled: { label: "Node styling", description: "Style your nodes with different shapes and borders.", infoSection: "node-styles", children: { customNodeStyleAttributes: { label: "Custom node style settings", description: "Add custom style settings for nodes. (Go to GitHub for more information)", type: "button", onClick: () => window.open("https://github.com/Developer-Mike/obsidian-advanced-canvas/blob/main/README.md#custom-styles") }, defaultTextNodeColor: { label: "Default text node color", description: "The default color of a text node. The default range is from 0 to 6, where 0 is no color. The range can be extended by using the Custom Colors feature of Advanced Canvas.", type: "number", parse: (value) => Math.max(0, parseInt(value) || 0) }, defaultTextNodeStyleAttributes: { label: "Default text node style attributes", type: "styles", getParameters(settingsManager) { return [ ...BUILTIN_NODE_STYLE_ATTRIBUTES, /* BUILTINS */ ...settingsManager.nodeCssStylesManager.getStyles(), /* CUSTOM CSS STYLES */ ...settingsManager.getSetting("customNodeStyleAttributes") /* LEGACY CUSTOM STYLES */ ].filter((setting) => { var _a; return setting.nodeTypes === void 0 || ((_a = setting.nodeTypes) == null ? void 0 : _a.includes("text")); }); } } } }, edgesStylingFeatureEnabled: { label: "Edges styling", description: "Style your edges with different path styles.", infoSection: "edge-styles", children: { customEdgeStyleAttributes: { label: "Custom edge style settings", description: "Add custom style settings for edges. (Go to GitHub for more information)", type: "button", onClick: () => window.open("https://github.com/Developer-Mike/obsidian-advanced-canvas/blob/main/README.md#custom-styles") }, inheritEdgeColorFromNode: { label: "Inherit edge color from node", description: "When creating a new edge by dragging from a node, the edge will inherit the color of the node it is dragged from.", type: "boolean" }, defaultEdgeColor: { label: "Default edge color", description: "The default color of an edge. The default range is from 0 to 6, where 0 is no color. The range can be extended by using the Custom Colors feature of Advanced Canvas.", type: "number", parse: (value) => Math.max(0, parseInt(value) || 0) }, defaultEdgeLineDirection: { label: "Default edge line direction", description: "The default line direction of an edge.", type: "dropdown", options: { "nondirectional": "Nondirectional", "unidirectional": "Unidirectional", "bidirectional": "Bidirectional" } }, defaultEdgeStyleAttributes: { label: "Default edge style attributes", type: "styles", getParameters(settingsManager) { return [ ...BUILTIN_EDGE_STYLE_ATTRIBUTES, /* BUILTINS */ ...settingsManager.edgeCssStylesManager.getStyles(), /* CUSTOM CSS STYLES */ ...settingsManager.getSetting("customEdgeStyleAttributes") /* LEGACY CUSTOM STYLES */ ]; } }, edgeStyleUpdateWhileDragging: { label: "Update edge style while dragging (Can be very slow)", description: "When enabled, the edge style will be updated while dragging an edge.", type: "boolean" }, edgeStyleSquarePathRounded: { label: "Square path rounded", description: "When enabled, the square path's corners will be rounded.", type: "boolean" }, edgeStylePathfinderAllowDiagonal: { label: "A* allow diagonal", description: "When enabled, the A* path style will allow diagonal paths.", type: "boolean" }, edgeStylePathfinderPathRounded: { label: "A* rounded path", description: "When enabled, the A* path style will be rounded.", type: "boolean" } } }, floatingEdgeFeatureEnabled: { label: "Floating edges (auto edge side)", description: "Floating edges are automatically placed on the most suitable side of the node.", infoSection: "floating-edges-automatic-edge-side", children: { allowFloatingEdgeCreation: { label: "Allow floating edges creation", description: "Allow floating edges creation by dragging the edge over the target node without placing it over a specific side connection point. (If disabled, floating edges can only be created and used by other Advanced Canvas features.)", type: "boolean" }, newEdgeFromSideFloating: { label: "New edge from side floating", description: 'When enabled, the "from" side of the edge will always be floating.', type: "boolean" } } }, flipEdgeFeatureEnabled: { label: "Flip edges", description: "Flip the direction of edges using the popup menu.", infoSection: "flip-edge", children: {} }, presentationFeatureEnabled: { label: "Presentations", description: "Create a presentation from your canvas.", infoSection: "presentation-mode", children: { showSetStartNodeInPopup: { label: 'Show "Set Start Node" in node popup', description: "If turned off, you can still set the start node using the corresponding command.", type: "boolean" }, defaultSlideDimensions: { label: "Default slide dimensions", description: "The default dimensions of a slide.", type: "dimension", parse: (value) => { const width = Math.max(1, parseInt(value[0]) || 0); const height = Math.max(1, parseInt(value[1]) || 0); return [width, height]; } }, wrapInSlidePadding: { label: "Wrap in slide padding", description: "The padding of the slide when wrapping the canvas in a slide.", type: "number", parse: (value) => Math.max(0, parseInt(value) || 0) }, resetViewportOnPresentationEnd: { label: "Reset viewport on presentation end", description: "When enabled, the viewport will be reset to the original position after the presentation ends.", type: "boolean" }, useArrowKeysToChangeSlides: { label: "Use arrow keys to change slides", description: "When enabled, you can use the arrow keys to change slides in presentation mode.", type: "boolean" }, usePgUpPgDownKeysToChangeSlides: { label: "Use PgUp/PgDown keys to change slides", description: "When enabled, you can use the PgUp/PgDown keys to change slides in presentation mode (Makes the presentation mode compatible with most presentation remotes).", type: "boolean" }, zoomToSlideWithoutPadding: { label: "Zoom to slide without padding", description: "When enabled, the canvas will zoom to the slide without padding.", type: "boolean" }, useUnclampedZoomWhilePresenting: { label: "Use unclamped zoom while presenting", description: "When enabled, the zoom will not be clamped while presenting.", type: "boolean" }, fullscreenPresentationEnabled: { label: "Enter fullscreen while presenting", description: "When enabled, presentations automatically request fullscreen. Disable to keep Obsidian windowed during presentations.", type: "boolean" }, slideTransitionAnimationDuration: { label: "Slide transition animation duration", description: "The duration of the slide transition animation in seconds. Set to 0 to disable the animation.", type: "number", parse: (value) => Math.max(0, parseFloat(value) || 0) }, slideTransitionAnimationIntensity: { label: "Slide transition animation intensity", description: "The intensity of the slide transition animation. The higher the value, the more the canvas will zoom out before zooming in on the next slide.", type: "number", parse: (value) => Math.max(0, parseFloat(value) || 0) } } }, zOrderingControlFeatureEnabled: { label: "Z ordering controls", description: "Change the persistent z-index of nodes using the context menu.", children: { zOrderingControlShowOneLayerShiftOptions: { label: "Show one layer shift options", description: "When enabled, you can move nodes one layer forward or backward.", type: "boolean" } } }, aspectRatioControlFeatureEnabled: { label: "Aspect ratio control", description: "Change the aspect ratio of nodes using the context menu.", children: {} }, variableBreakpointFeatureEnabled: { label: "Variable breakpoint", description: `Change the zoom breakpoint (the zoom level at which the nodes won't render their content anymore) on a per-node basis using the ${VARIABLE_BREAKPOINT_CSS_VAR} CSS variable.`, infoSection: "variable-breakpoints", children: {} }, autoResizeNodeFeatureEnabled: { label: "Auto resize node", description: "Automatically resize the height of a node to fit the content.", infoSection: "auto-node-resizing", children: { autoResizeNodeEnabledByDefault: { label: "Enable auto resize by default", description: "When enabled, the auto resize feature will be enabled by default for all nodes.", type: "boolean" }, autoResizeNodeMaxHeight: { label: "Max height", description: "The maximum height of the node when auto resizing (-1 for unlimited).", type: "number", parse: (value) => { var _a; return Math.max(-1, (_a = parseInt(value)) != null ? _a : -1); } }, autoResizeNodeSnapToGrid: { label: "Snap to grid", description: "When enabled, the height of the node will snap to the grid.", type: "boolean" } } }, canvasEncapsulationEnabled: { label: "Canvas encapsulation", description: "Encapsulate a selection of nodes and edges into a new canvas using the context menu.", infoSection: "encapsulate-selection", children: {} }, betterReadonlyEnabled: { label: "Better readonly", description: "Improve the readonly mode.", infoSection: "better-readonly", children: { hideBackgroundGridWhenInReadonly: { label: "Hide background grid when in readonly", description: "When enabled, the background grid will be hidden when in readonly mode.", type: "boolean" } } }, edgeHighlightEnabled: { label: "Edge highlight", description: "Highlight outgoing (and optionally incoming) edges of a selected node.", infoSection: "edge-highlight", children: { highlightIncomingEdges: { label: "Highlight incoming edges", description: "When enabled, incoming edges will also be highlighted.", type: "boolean" } } }, edgeSelectionEnabled: { label: "Edge selection", description: "Select edges connected to the selected node(s) using the popup menu.", infoSection: "edge-selection", children: { selectEdgeByDirection: { label: "Select edge by direction", description: "Select incoming or outgoing edges using separate popup menu items.", type: "boolean" } } }, focusModeFeatureEnabled: { label: "Focus mode", description: "Focus on a single node and blur all other nodes.", infoSection: "focus-mode", children: {} } }; var SettingsManager = class { constructor(plugin) { this.plugin = plugin; this.nodeCssStylesManager = GET_NODE_CSS_STYLES_MANAGER(plugin); this.edgeCssStylesManager = GET_EDGE_CSS_STYLES_MANAGER(plugin); } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS_VALUES, await this.plugin.loadData()); this.plugin.app.workspace.trigger("advanced-canvas:settings-changed"); } async saveSettings() { await this.plugin.saveData(this.settings); } getSetting(key) { return this.settings[key]; } async setSetting(data) { this.settings = Object.assign(this.settings, data); await this.saveSettings(); this.plugin.app.workspace.trigger("advanced-canvas:settings-changed"); } addSettingsTab() { this.settingsTab = new AdvancedCanvasPluginSettingTab(this.plugin, this); this.plugin.addSettingTab(this.settingsTab); } }; var AdvancedCanvasPluginSettingTab = class extends import_obsidian4.PluginSettingTab { constructor(plugin, settingsManager) { super(plugin.app, plugin); this.settingsManager = settingsManager; } display() { const { containerEl } = this; containerEl.empty(); this.createKofiBanner(containerEl); for (const [headingId, heading] of Object.entries(SETTINGS)) { this.createFeatureHeading( containerEl, heading.label, heading.description, heading.infoSection, heading.disableToggle ? null : headingId ); const settingsHeaderChildrenContainerEl = document.createElement("div"); settingsHeaderChildrenContainerEl.classList.add("settings-header-children"); settingsHeaderChildrenContainerEl.appendChild(document.createElement("span")); containerEl.appendChild(settingsHeaderChildrenContainerEl); for (const [settingId, setting] of Object.entries(heading.children)) { if (!(settingId in DEFAULT_SETTINGS_VALUES)) continue; switch (setting.type) { case "text": this.createTextSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; case "number": this.createNumberSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; case "dimension": this.createDimensionSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; case "boolean": this.createBooleanSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; case "dropdown": this.createDropdownSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; case "button": this.createButtonSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; case "styles": this.createStylesSetting(settingsHeaderChildrenContainerEl, settingId, setting); break; } } } } createFeatureHeading(containerEl, label, description, infoSection, settingsKey) { const setting = new import_obsidian4.Setting(containerEl).setHeading().setClass("ac-settings-heading").setName(label).setDesc(description); if (infoSection !== void 0) { setting.addExtraButton( (button) => button.setTooltip("Open github documentation").setIcon("info").onClick(async () => { window.open(`${README_URL}#${infoSection}`); }) ); } if (settingsKey !== null) { setting.addToggle( (toggle) => toggle.setTooltip("Requires a reload to take effect.").setValue(this.settingsManager.getSetting(settingsKey)).onChange(async (value) => { await this.settingsManager.setSetting({ [settingsKey]: value }); new import_obsidian4.Notice("Reload obsidian to apply the changes."); }) ); } return setting; } createTextSetting(containerEl, settingId, setting) { new import_obsidian4.Setting(containerEl).setName(setting.label).setDesc(setting.description).addText( (text) => text.setValue(this.settingsManager.getSetting(settingId)).onChange(async (value) => { await this.settingsManager.setSetting({ [settingId]: setting.parse ? setting.parse(value) : value }); }) ); } createNumberSetting(containerEl, settingId, setting) { new import_obsidian4.Setting(containerEl).setName(setting.label).setDesc(setting.description).addText( (text) => text.setValue(this.settingsManager.getSetting(settingId).toString()).onChange(async (value) => { await this.settingsManager.setSetting({ [settingId]: setting.parse(value) }); }) ); } createDimensionSetting(containerEl, settingId, setting) { let text1; let text2; new import_obsidian4.Setting(containerEl).setName(setting.label).setDesc(setting.description).addText((text) => { text1 = text.setValue(this.settingsManager.getSetting(settingId)[0].toString()).onChange(async (value) => await this.settingsManager.setSetting({ [settingId]: setting.parse([value, text2.getValue()]) })); }).addText((text) => { text2 = text.setValue(this.settingsManager.getSetting(settingId)[1].toString()).onChange(async (value) => await this.settingsManager.setSetting({ [settingId]: setting.parse([text1.getValue(), value]) })); }); } createBooleanSetting(containerEl, settingId, setting) { new import_obsidian4.Setting(containerEl).setName(setting.label).setDesc(setting.description).addToggle( (toggle) => toggle.setValue(this.settingsManager.getSetting(settingId)).onChange(async (value) => { await this.settingsManager.setSetting({ [settingId]: value }); }) ); } createDropdownSetting(containerEl, settingId, setting) { new import_obsidian4.Setting(containerEl).setName(setting.label).setDesc(setting.description).addDropdown( (dropdown) => dropdown.addOptions(setting.options).setValue(this.settingsManager.getSetting(settingId)).onChange(async (value) => { await this.settingsManager.setSetting({ [settingId]: value }); }) ); } createButtonSetting(containerEl, settingId, setting) { new import_obsidian4.Setting(containerEl).setName(setting.label).setDesc(setting.description).addButton( (button) => button.setButtonText("Open").onClick(() => setting.onClick()) ); } createStylesSetting(containerEl, settingId, setting) { const nestedContainerEl = document.createElement("details"); nestedContainerEl.classList.add("setting-item"); containerEl.appendChild(nestedContainerEl); const summaryEl = document.createElement("summary"); summaryEl.textContent = setting.label; nestedContainerEl.appendChild(summaryEl); for (const styleAttribute of setting.getParameters(this.settingsManager)) { new import_obsidian4.Setting(nestedContainerEl).setName(styleAttribute.label).addDropdown( (dropdown) => { var _a; return dropdown.addOptions(Object.fromEntries(styleAttribute.options.map((option) => [option.value, option.value === null ? `${option.label} (default)` : option.label]))).setValue((_a = this.settingsManager.getSetting(settingId)[styleAttribute.key]) != null ? _a : "null").onChange(async (value) => { const newValue = this.settingsManager.getSetting(settingId); if (value === "null") delete newValue[styleAttribute.key]; else newValue[styleAttribute.key] = value; await this.settingsManager.setSetting({ [settingId]: newValue }); }); } ); } } async createKofiBanner(containerEl) { const banner = document.createElement("div"); banner.classList.add("kofi-banner"); const title = document.createElement("h1"); title.textContent = "Enjoying the plugin?"; banner.appendChild(title); const description = document.createElement("p"); description.innerHTML = ` Currently, Advanced Canvas has received about ${RECEIVED_DONATIONS}\xA3 in donations with a total of about ${SPENT_HOURS} hours spent on development.

Please help me develop this plugin further by reaching the goal of ${HOURLY_RATE_GOAL}\xA3/hour \u2764\uFE0F `; banner.appendChild(description); const progressContainer = document.createElement("div"); progressContainer.classList.add("progress-container"); const progressbar = document.createElement("progress"); progressbar.value = RECEIVED_DONATIONS / SPENT_HOURS; progressbar.max = HOURLY_RATE_GOAL; progressContainer.appendChild(progressbar); const hourlyRate = document.createElement("span"); hourlyRate.classList.add("hourly-rate"); hourlyRate.textContent = `${(RECEIVED_DONATIONS / SPENT_HOURS).toString()}\xA3/h`; progressContainer.appendChild(hourlyRate); banner.appendChild(progressContainer); const koFiButton = document.createElement("a"); koFiButton.classList.add("ac-kofi-button"); koFiButton.href = KOFI_PAGE_URL; koFiButton.target = "_blank"; const koFiImage = document.createElement("img"); koFiImage.src = KOFI_BADGE_URI; koFiButton.appendChild(koFiImage); banner.appendChild(koFiButton); containerEl.appendChild(banner); } }; // src/managers/windows-manager.ts var WindowsManager = class { constructor(plugin) { this.windows = [window]; this.plugin = plugin; this.plugin.registerEvent(this.plugin.app.workspace.on( "window-open", (_win, window2) => this.windows.push(window2) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "window-close", (_win, window2) => this.windows = this.windows.filter((w) => w !== window2) )); } }; // src/patchers/canvas-patcher.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/patchers/canvas-patcher.ts var import_obsidian5 = require("obsidian"); // node_modules/tiny-jsonc/dist/index.js var stringOrCommentRe = /("(?:\\?[^])*?")|(\/\/.*)|(\/\*[^]*?\*\/)/g; var stringOrTrailingCommaRe = /("(?:\\?[^])*?")|(,\s*)(?=]|})/g; var JSONC = { parse: (text) => { text = String(text); try { return JSON.parse(text); } catch (e) { return JSON.parse(text.replace(stringOrCommentRe, "$1").replace(stringOrTrailingCommaRe, "$1")); } } }; var dist_default = JSONC; // src/patchers/patcher.ts var Patcher = class _Patcher { constructor(plugin) { this.plugin = plugin; this.patch(); } static async waitForMapValueLookup(map, viewType, patch) { return new Promise((resolve) => { const uninstaller = around(map, { [viewType]: (next) => function(...args) { const view = next.call(this, ...args); patch(view); const patchedView = next.call(this, ...args); uninstaller(); resolve(patchedView); return patchedView; } }); }); } static async waitForViewRequest(plugin, viewType, patch) { return this.waitForMapValueLookup(plugin.app.viewRegistry.viewByType, viewType, patch); } static OverrideExisting(fn) { return Object.assign(fn, { __overrideExisting: true }); } static patchThisAndPrototype(plugin, object, patches, uninstallers) { _Patcher.patch(plugin, object, patches, false, uninstallers); return _Patcher.patchPrototype(plugin, object, patches, uninstallers); } static patchPrototype(plugin, target, patches, uninstallers) { return _Patcher.patch(plugin, target, patches, true, uninstallers); } static patch(plugin, object, patches, prototype = false, uninstallers) { if (!object) return null; const target = prototype ? object.constructor.prototype : object; for (const key of Object.keys(patches)) { const patch = patches[key]; if (patch == null ? void 0 : patch.__overrideExisting) { if (typeof target[key] !== "function") throw new Error(`Method ${String(key)} does not exist on target`); } } const uninstaller = around(target, patches); if (uninstallers) uninstallers.push(uninstaller); plugin.register(uninstaller); return object; } static async patchOnce(plugin, object, patches) { const uninstallers = []; const value = await new Promise( (resolve) => this.patch(plugin, object, patches(resolve), false, uninstallers) ); for (const uninstall of uninstallers) uninstall(); return value; } static tryPatchWorkspacePrototype(plugin, getTarget, patches, uninstallers) { return new Promise((resolve) => { const result = _Patcher.patchPrototype(plugin, getTarget(), patches, uninstallers); if (result) { resolve(result); return; } const listener = plugin.app.workspace.on("layout-change", () => { const result2 = _Patcher.patchPrototype(plugin, getTarget(), patches, uninstallers); if (result2) { plugin.app.workspace.offref(listener); resolve(result2); } }); plugin.registerEvent(listener); }); } }; // src/utils/migration-helper.ts var CURRENT_SPEC_VERSION = "1.0-1.0"; var _MigrationHelper = class _MigrationHelper { static needsMigration(canvas) { var _a; return ((_a = canvas.metadata) == null ? void 0 : _a.version) !== CURRENT_SPEC_VERSION; } static migrate(canvas) { var _a, _b; let version = (_b = (_a = canvas.metadata) == null ? void 0 : _a.version) != null ? _b : "undefined"; if (version === CURRENT_SPEC_VERSION) return canvas; while (version !== CURRENT_SPEC_VERSION) { const migrationFunction = _MigrationHelper.MIGRATIONS[version]; if (!migrationFunction) { console.error(`No migration function found for version ${version}. Critical error!`); break; } const { version: newVersion, canvas: migratedCanvas } = migrationFunction(canvas); version = newVersion; canvas = migratedCanvas; if (!canvas.metadata) canvas.metadata = { version, frontmatter: {} }; else canvas.metadata.version = version; } return canvas; } }; _MigrationHelper.MIGRATIONS = { undefined: (canvas) => { var _a, _b, _c; const TARGET_SPEC_VERSION = "1.0-1.0"; let startNode; const globalInterdimensionalEdges = {}; for (const node of (_a = canvas.nodes) != null ? _a : []) { node.dynamicHeight = node.autoResizeHeight; delete node.autoResizeHeight; node.ratio = node.sideRatio; delete node.sideRatio; node.collapsed = node.isCollapsed; delete node.isCollapsed; if (node.portalToFile) { node.portal = true; delete node.portalToFile; } if (node.isStartNode) { startNode = node.id; delete node.isStartNode; } if (node.edgesToNodeFromPortal) { const edgesToNodeFromPortal = node.edgesToNodeFromPortal; for (const [portalId, edges] of Object.entries(edgesToNodeFromPortal)) { if (!(portalId in globalInterdimensionalEdges)) globalInterdimensionalEdges[portalId] = []; for (const edge of edges) { if (edge.fromNode !== node.id) edge.fromNode = `${portalId}-${edge.fromNode}`; if (edge.toNode !== node.id) edge.toNode = `${portalId}-${edge.toNode}`; } globalInterdimensionalEdges[portalId].push(...edges); } delete node.edgesToNodeFromPortal; } } for (const node of (_b = canvas.nodes) != null ? _b : []) { if (!(node.id in globalInterdimensionalEdges)) continue; node.interdimensionalEdges = globalInterdimensionalEdges[node.id]; } (_c = canvas.metadata) != null ? _c : canvas.metadata = { version: TARGET_SPEC_VERSION, frontmatter: {}, startNode }; return { version: TARGET_SPEC_VERSION, canvas }; } }; var MigrationHelper = _MigrationHelper; // src/patchers/canvas-patcher.ts var CanvasPatcher = class extends Patcher { async patch() { const loadedCanvasViewLeafs = this.plugin.app.workspace.getLeavesOfType("canvas").filter((leaf) => !(0, import_obsidian5.requireApiVersion)("1.7.2") || !leaf.isDeferred); if (loadedCanvasViewLeafs.length > 0) { console.debug(`Patching and reloading loaded canvas views (Count: ${loadedCanvasViewLeafs.length})`); this.patchCanvas(loadedCanvasViewLeafs.first().view); for (const leaf of loadedCanvasViewLeafs) leaf.rebuildView(); } else { await Patcher.waitForViewRequest(this.plugin, "canvas", (view) => this.patchCanvas(view)); console.debug(`Patched canvas view on first request`); } } patchCanvas(view) { const that = this; Patcher.patchPrototype(this.plugin, view, { setEphemeralState: Patcher.OverrideExisting((next) => function(state) { var _a, _b, _c; if (state == null ? void 0 : state.subpath) { const nodeId = state.subpath.replace(/^#/, ""); const node = this.canvas.nodes.get(nodeId); if (node) { this.canvas.selectOnly(node); this.canvas.zoomToSelection(); return; } } if (((_b = (_a = state.match) == null ? void 0 : _a.matches) == null ? void 0 : _b[0]) && !((_c = state.match) == null ? void 0 : _c.nodeId)) { const match = state.match.matches[0]; const elementType = match[0] === 0 ? "nodes" : "edges"; const elementIndex = match[1]; const element = elementType === "nodes" ? Array.from(this.canvas.nodes.values())[elementIndex] : Array.from(this.canvas.edges.values())[elementIndex]; if (element) { this.canvas.selectOnly(element); this.canvas.zoomToSelection(); return; } } return next.call(this, state); }), setViewData: Patcher.OverrideExisting((next) => function(json, ...args) { json = json !== "" ? json : '{"nodes": [], "edges": []}'; try { const canvasData = dist_default.parse(json); if (MigrationHelper.needsMigration(canvasData)) { if (this.file) that.plugin.createFileSnapshot(this.file.path, json); json = JSON.stringify(MigrationHelper.migrate(canvasData)); } } catch (e) { console.error("Failed to migrate canvas data:", e); } let result; try { result = next.call(this, json, ...args); } catch (e) { console.error("Invalid JSON, repairing through Advanced Canvas:", e); if (this.file) that.plugin.createFileSnapshot(this.file.path, json); json = JSON.stringify(dist_default.parse(json), null, 2); result = next.call(this, json, ...args); } that.plugin.app.workspace.trigger("advanced-canvas:canvas-changed", this.canvas); return result; }), close: Patcher.OverrideExisting((next) => function(...args) { that.plugin.app.workspace.trigger("advanced-canvas:canvas-view-unloaded:before", this); return next.call(this, ...args); }) }); Patcher.patchPrototype(this.plugin, view.canvas, { markViewportChanged: Patcher.OverrideExisting((next) => function(...args) { that.plugin.app.workspace.trigger("advanced-canvas:viewport-changed:before", this); const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:viewport-changed:after", this); return result; }), markMoved: Patcher.OverrideExisting((next) => function(node) { const result = next.call(this, node); if (!this.viewportChanged) { if (node.prevX !== node.x || node.prevY !== node.y) that.plugin.app.workspace.trigger("advanced-canvas:node-moved", this, node, !this.isDragging); if (node.prevWidth !== node.width || node.prevHeight !== node.height) that.plugin.app.workspace.trigger("advanced-canvas:node-resized", this, node); } node.prevX = node.x; node.prevY = node.y; node.prevWidth = node.width; node.prevHeight = node.height; return result; }), onDoubleClick: Patcher.OverrideExisting((next) => function(event) { const preventDefault = { value: false }; that.plugin.app.workspace.trigger("advanced-canvas:double-click", this, event, preventDefault); if (!preventDefault.value) next.call(this, event); }), setDragging: Patcher.OverrideExisting((next) => function(dragging) { const result = next.call(this, dragging); that.plugin.app.workspace.trigger("advanced-canvas:dragging-state-changed", this, dragging); return result; }), // OBSIDIAN-FIX cloneData: Patcher.OverrideExisting((next) => function(elements, shift) { const result = next.call(this, elements, shift); elements.nodes = elements.nodes.map((nodeData) => JSON.parse(JSON.stringify(nodeData))); elements.edges = elements.edges.map((edgeData) => JSON.parse(JSON.stringify(edgeData))); return result; }), getContainingNodes: Patcher.OverrideExisting((next) => function(bbox) { const result = next.call(this, bbox); that.plugin.app.workspace.trigger("advanced-canvas:containing-nodes-requested", this, bbox, result); return result; }), updateSelection: Patcher.OverrideExisting((next) => function(update) { const oldSelection = new Set(this.selection); const result = next.call(this, update); that.plugin.app.workspace.trigger("advanced-canvas:selection-changed", this, oldSelection, (update2) => next.call(this, update2)); return result; }), createTextNode: Patcher.OverrideExisting((next) => function(...args) { const node = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-created", this, node); return node; }), createFileNode: Patcher.OverrideExisting((next) => function(...args) { const node = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-created", this, node); return node; }), createFileNodes: Patcher.OverrideExisting((next) => function(...args) { const nodes = next.call(this, ...args); nodes.forEach((node) => that.plugin.app.workspace.trigger("advanced-canvas:node-created", this, node)); return nodes; }), createGroupNode: Patcher.OverrideExisting((next) => function(...args) { const node = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-created", this, node); return node; }), createLinkNode: Patcher.OverrideExisting((next) => function(...args) { const node = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-created", this, node); return node; }), addNode: Patcher.OverrideExisting((next) => function(node) { that.patchNode(node); return next.call(this, node); }), addEdge: Patcher.OverrideExisting((next) => function(edge) { that.patchEdge(edge); if (!this.viewportChanged) that.plugin.app.workspace.trigger("advanced-canvas:edge-created", this, edge); return next.call(this, edge); }), removeNode: Patcher.OverrideExisting((next) => function(node) { const result = next.call(this, node); if (!this.isClearing) that.plugin.app.workspace.trigger("advanced-canvas:node-removed", this, node); return result; }), removeEdge: Patcher.OverrideExisting((next) => function(edge) { const result = next.call(this, edge); if (!this.isClearing) that.plugin.app.workspace.trigger("advanced-canvas:edge-removed", this, edge); return result; }), handleCopy: Patcher.OverrideExisting((next) => function(...args) { this.isCopying = true; const result = next.call(this, ...args); this.isCopying = false; return result; }), handlePaste: Patcher.OverrideExisting((next) => function(...args) { this.isPasting = true; const result = next.call(this, ...args); this.isPasting = false; return result; }), getSelectionData: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); if (this.isCopying) that.plugin.app.workspace.trigger("advanced-canvas:copy", this, result); return result; }), zoomToBbox: Patcher.OverrideExisting((next) => function(bbox) { that.plugin.app.workspace.trigger("advanced-canvas:zoom-to-bbox:before", this, bbox); const result = next.call(this, bbox); that.plugin.app.workspace.trigger("advanced-canvas:zoom-to-bbox:after", this, bbox); return result; }), // Custom zoomToRealBbox: (_next) => function(bbox) { if (this.canvasRect.width === 0 || this.canvasRect.height === 0) return; that.plugin.app.workspace.trigger("advanced-canvas:zoom-to-bbox:before", this, bbox); const widthZoom = this.canvasRect.width / (bbox.maxX - bbox.minX); const heightZoom = this.canvasRect.height / (bbox.maxY - bbox.minY); const zoom = this.screenshotting ? Math.min(widthZoom, heightZoom) : Math.clamp(Math.min(widthZoom, heightZoom), -4, 1); this.tZoom = Math.log2(zoom); this.zoomCenter = null; this.tx = (bbox.minX + bbox.maxX) / 2; this.ty = (bbox.minY + bbox.maxY) / 2; this.markViewportChanged(); that.plugin.app.workspace.trigger("advanced-canvas:zoom-to-bbox:after", this, bbox); }, setReadonly: Patcher.OverrideExisting((next) => function(readonly) { const result = next.call(this, readonly); that.plugin.app.workspace.trigger("advanced-canvas:readonly-changed", this, readonly); return result; }), undo: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); this.importData(this.getData(), true); that.plugin.app.workspace.trigger("advanced-canvas:undo", this); return result; }), redo: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); this.importData(this.getData(), true); that.plugin.app.workspace.trigger("advanced-canvas:redo", this); return result; }), clear: Patcher.OverrideExisting((next) => function(...args) { this.isClearing = true; const result = next.call(this, ...args); this.isClearing = false; return result; }), /*setData: Patcher.OverrideExisting(next => function (...args: any): void { // const result = next.call(this, ...args) // return result }),*/ getData: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:data-requested", this, result); return result; }), importData: Patcher.OverrideExisting((next) => function(data, clearCanvas, silent) { var _a; const targetFilePath = (_a = this.view.file) == null ? void 0 : _a.path; const setData = (data2) => { if (!this.view.file || this.view.file.path !== targetFilePath) return; this.importData(data2, true, true); }; if (!silent) that.plugin.app.workspace.trigger("advanced-canvas:data-loaded:before", this, data, setData); const result = next.call(this, data, clearCanvas); if (!silent) that.plugin.app.workspace.trigger("advanced-canvas:data-loaded:after", this, data, setData); return result; }), requestSave: Patcher.OverrideExisting((next) => function(...args) { that.plugin.app.workspace.trigger("advanced-canvas:canvas-saved:before", this); const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:canvas-saved:after", this); return result; }) }); Patcher.patchPrototype(this.plugin, view.canvas.menu, { render: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:popup-menu-created", this.canvas); next.call(this); return result; }) }); Patcher.patchPrototype(this.plugin, view.canvas.nodeInteractionLayer, { setTarget: Patcher.OverrideExisting((next) => function(node) { const result = next.call(this, node); that.plugin.app.workspace.trigger("advanced-canvas:node-interaction", this.canvas, node); return result; }) }); this.plugin.registerEditorExtension([import_view.EditorView.updateListener.of((update) => { if (!update.docChanged) return; const editor = update.state.field(import_obsidian5.editorInfoField); const node = editor.node; if (!node) return; that.plugin.app.workspace.trigger("advanced-canvas:node-text-content-changed", node.canvas, node, update); })]); } patchNode(node) { const that = this; Patcher.patch(this.plugin, node, { setData: Patcher.OverrideExisting((next) => function(data, addHistory) { const result = next.call(this, data); if (node.initialized && !node.isDirty) { node.isDirty = true; that.plugin.app.workspace.trigger("advanced-canvas:node-changed", this.canvas, node); delete node.isDirty; } this.canvas.data = this.canvas.getData(); if (this.initialized) this.canvas.view.requestSave(); if (addHistory) this.canvas.pushHistory(this.canvas.data); return result; }), setZIndex: (_next) => function(value) { this.setData({ ...this.getData(), zIndex: value }, true); this.updateZIndex(); }, updateZIndex: Patcher.OverrideExisting((next) => function() { const persistentZIndex = this.getData().zIndex; if (persistentZIndex === void 0) return next.call(this); this.canvas.zIndexCounter = Math.max(this.canvas.zIndexCounter, persistentZIndex); this.renderZIndex(); }), renderZIndex: Patcher.OverrideExisting((next) => function() { const persistentZIndex = this.getData().zIndex; if (persistentZIndex === void 0) return next.call(this); this.zIndex = persistentZIndex; if (this.canvas.selection.size === 1 && this.canvas.selection.has(this)) this.zIndex = this.canvas.zIndexCounter + 1; this.nodeEl.style.zIndex = this.zIndex.toString(); }), setIsEditing: Patcher.OverrideExisting((next) => function(editing, ...args) { const result = next.call(this, editing, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-editing-state-changed", this.canvas, node, editing); return result; }), updateBreakpoint: Patcher.OverrideExisting((next) => function(breakpoint) { const breakpointRef = { value: breakpoint }; that.plugin.app.workspace.trigger("advanced-canvas:node-breakpoint-changed", this.canvas, node, breakpointRef); return next.call(this, breakpointRef.value); }), getBBox: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-bbox-requested", this.canvas, node, result); return result; }), onConnectionPointerdown: Patcher.OverrideExisting((next) => function(e, side) { const addEdgeEventRef = that.plugin.app.workspace.on("advanced-canvas:edge-added", (_canvas, edge) => { that.plugin.app.workspace.trigger("advanced-canvas:edge-connection-dragging:before", this.canvas, edge, e, true, "to"); that.plugin.app.workspace.offref(addEdgeEventRef); document.addEventListener("pointerup", (e2) => { that.plugin.app.workspace.trigger("advanced-canvas:edge-connection-dragging:after", this.canvas, edge, e2, true, "to"); }, { once: true }); }); const result = next.call(this, e, side); return result; }), // File nodes setFile: (next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-changed", this.canvas, this); return result; }, setFilePath: (next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:node-changed", this.canvas, this); return result; } }); this.runAfterInitialized(node, () => { this.plugin.app.workspace.trigger("advanced-canvas:node-added", node.canvas, node); this.plugin.app.workspace.trigger("advanced-canvas:node-changed", node.canvas, node); }); } patchEdge(edge) { const that = this; Patcher.patch(this.plugin, edge, { setData: Patcher.OverrideExisting((next) => function(data, addHistory) { const result = next.call(this, data); if (this.initialized && !this.isDirty) { this.isDirty = true; that.plugin.app.workspace.trigger("advanced-canvas:edge-changed", this.canvas, this); delete this.isDirty; } this.canvas.data = this.canvas.getData(); if (this.initialized) this.canvas.view.requestSave(); if (addHistory) this.canvas.pushHistory(this.canvas.getData()); return result; }), render: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:edge-changed", this.canvas, this); return result; }), getCenter: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); that.plugin.app.workspace.trigger("advanced-canvas:edge-center-requested", this.canvas, this, result); return result; }), onConnectionPointerdown: Patcher.OverrideExisting((next) => function(e) { const cancelRef = { value: false }; that.plugin.app.workspace.trigger("advanced-canvas:edge-connection-try-dragging:before", this.canvas, this, e, cancelRef); if (cancelRef.value) return; const previousEnds = { from: this.from, to: this.to }; const result = next.call(this, e); const eventPos = this.canvas.posFromEvt(e); const fromPos = BBoxHelper.getCenterOfBBoxSide(this.from.node.getBBox(), this.from.side); const toPos = BBoxHelper.getCenterOfBBoxSide(this.to.node.getBBox(), this.to.side); const draggingSide = Math.hypot(eventPos.x - fromPos.x, eventPos.y - fromPos.y) > Math.hypot(eventPos.x - toPos.x, eventPos.y - toPos.y) ? "to" : "from"; that.plugin.app.workspace.trigger("advanced-canvas:edge-connection-dragging:before", this.canvas, this, e, false, draggingSide, previousEnds); document.addEventListener("pointerup", (e2) => { that.plugin.app.workspace.trigger("advanced-canvas:edge-connection-dragging:after", this.canvas, this, e2, false, draggingSide, previousEnds); }, { once: true }); return result; }) }); this.runAfterInitialized(edge, () => { this.plugin.app.workspace.trigger("advanced-canvas:edge-added", edge.canvas, edge); }); } runAfterInitialized(canvasElement, onReady) { if (canvasElement.initialized) { onReady(); return; } const that = this; const uninstall = around(canvasElement, { initialize: (next) => function(...args) { const result = next.call(this, ...args); onReady(); uninstall(); return result; } }); that.plugin.register(uninstall); } }; // src/patchers/link-suggestions-patcher.ts var import_obsidian6 = require("obsidian"); var LinkSuggestionsPatcher = class extends Patcher { async patch() { var _a; if (!this.plugin.settings.getSetting("enableSingleNodeLinks")) return; const suggestManager = (_a = this.plugin.app.workspace.editorSuggest.suggests.find((s) => s.suggestManager)) == null ? void 0 : _a.suggestManager; if (!suggestManager) return console.warn("LinkSuggestionsPatcher: No suggest manager found."); Patcher.patchThisAndPrototype(this.plugin, suggestManager, { getHeadingSuggestions: Patcher.OverrideExisting((next) => async function(context, path, subpath) { const result = await next.call(this, context, path, subpath); if (!path.endsWith(".canvas")) return result; const currentFilePath = this.getSourcePath(); const targetFile = this.app.metadataCache.getFirstLinkpathDest(path, currentFilePath); if (!targetFile) return result; if (!(targetFile instanceof import_obsidian6.TFile) || targetFile.extension !== "canvas") return result; const fileCache = this.app.metadataCache.getFileCache(targetFile); if (!fileCache) return result; const canvasNodeCaches = fileCache.nodes; if (!canvasNodeCaches) return result; for (const nodeId of Object.keys(canvasNodeCaches)) { if (nodeId === subpath) continue; const suggestion = { file: targetFile, heading: nodeId, level: 1, matches: [], path, subpath: `#${nodeId}`, score: 0, type: "heading" }; result.push(suggestion); } return result; }) }); } }; // src/advanced-canvas-embed.ts var import_obsidian7 = require("obsidian"); var AdvancedCanvasEmbed = class extends import_obsidian7.Component { constructor(context, file, subpath) { super(); this.onModifyCallback = (file) => { if (file.path !== this.file.path) return; this.loadFile(); }; this.context = context; this.file = file; this.subpath = subpath; if (!subpath) console.warn("AdvancedCanvasEmbed: No subpath provided. This embed will not work as expected."); } onload() { this.context.app.vault.on("modify", this.onModifyCallback); } onunload() { this.context.app.vault.off("modify", this.onModifyCallback); } async loadFile() { if (!this.subpath) return; const nodeId = this.subpath.replace(/^#/, ""); const canvasContent = await this.context.app.vault.cachedRead(this.file); if (!canvasContent) return console.warn("AdvancedCanvasEmbed: No canvas content found."); const canvasJson = JSON.parse(canvasContent); const canvasNode = canvasJson.nodes.find((node) => node.id === nodeId); if (!canvasNode) { this.context.containerEl.classList.add("mod-empty"); this.context.containerEl.textContent = "Node not found"; return; } let nodeContent = ""; if (canvasNode.type === "text") nodeContent = canvasNode.text; else if (canvasNode.type === "group") nodeContent = `**Group Node:** ${canvasNode.label}`; else if (canvasNode.type === "file") nodeContent = `**File Node:** ${canvasNode.file}`; this.context.containerEl.classList.add("markdown-embed"); this.context.containerEl.empty(); import_obsidian7.MarkdownRenderer.render(this.context.app, nodeContent, this.context.containerEl, this.file.path, this); } }; // src/patchers/embed-patcher.ts var EmbedPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("enableSingleNodeLinks")) return; Patcher.patch(this.plugin, this.plugin.app.embedRegistry.embedByExtension, { canvas: (next) => function(context, file, subpath) { if (subpath) return new AdvancedCanvasEmbed(context, file, subpath); return next.call(this, context, file, subpath); } }); } }; // src/patchers/metadata-cache-patcher.ts var import_obsidian8 = require("obsidian"); // src/utils/filepath-helper.ts var FilepathHelper = class { static extension(path) { var _a; return (path == null ? void 0 : path.includes(".")) ? (_a = path == null ? void 0 : path.split(".")) == null ? void 0 : _a.pop() : void 0; } }; // src/utils/hash-helper.ts var HashHelper = class _HashHelper { static async getBytesHash(bytes) { const cryptoBytes = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)); return _HashHelper.arrayBufferToHexString(cryptoBytes); } static arrayBufferToHexString(buffer) { const uint8Array = new Uint8Array(buffer); const hexArray = []; for (const byte of uint8Array) { hexArray.push((byte >>> 4).toString(16)); hexArray.push((byte & 15).toString(16)); } return hexArray.join(""); } }; // src/utils/task-queue.ts var TaskQueue = class { constructor() { this.running = false; this.queue = []; this.onFinished = () => { }; } async add(task) { return new Promise((resolve) => { this.queue.push([resolve, task]); if (!this.running) this.run(); }); } setOnFinished(callback) { this.onFinished = callback; } async run() { var _a; this.running = true; while (this.queue.length > 0) { const [resolver, task] = (_a = this.queue.shift()) != null ? _a : [void 0, void 0]; await (task == null ? void 0 : task()); resolver == null ? void 0 : resolver(); } this.running = false; this.onFinished(); } }; // src/patchers/metadata-cache-patcher.ts var MetadataCachePatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; Patcher.patchPrototype(this.plugin, this.plugin.app.metadataCache, { getCache: Patcher.OverrideExisting((next) => function(filepath, ...args) { if (FilepathHelper.extension(filepath) === "canvas") { if (!this.fileCache.hasOwnProperty(filepath)) return null; const hash = this.fileCache[filepath].hash; return this.metadataCache[hash] || null; } return next.call(this, filepath, ...args); }), computeFileMetadataAsync: Patcher.OverrideExisting((next) => async function(file, ...args) { if (file instanceof import_obsidian8.TFile && (file == null ? void 0 : file.extension) === "canvas") return CanvasMetadataHandler.computeCanvasFileMetadataAsync.call(this, file); return next.call(this, file, ...args); }), resolveLinks: Patcher.OverrideExisting((next) => async function(filepath) { const result = next.call(this, filepath); if (FilepathHelper.extension(filepath) === "canvas") CanvasMetadataHandler.resolveCanvasLinks.call(this, filepath); return result; }) }); } }; var _CanvasMetadataHandler = class _CanvasMetadataHandler { static async computeCanvasFileMetadataAsync(file) { this.uniqueFileLookup.add(file.name.toLowerCase(), file); let isStale = true; if (!this.fileCache.hasOwnProperty(file.path)) this.saveFileCache(file.path, { mtime: 0, size: 0, hash: "" }); else { const cache2 = this.fileCache[file.path]; const unchanged = cache2.mtime === file.stat.mtime && cache2.size === file.stat.size; const hasMetadataCache = cache2.hash && this.metadataCache.hasOwnProperty(cache2.hash); if (unchanged && hasMetadataCache) isStale = false; } if (isStale) { _CanvasMetadataHandler.linkResolveQueue.setOnFinished(() => this.trigger("finished")); await _CanvasMetadataHandler.metadataQueue.add( () => _CanvasMetadataHandler.updateMetadataCache.call(this, file) ); } _CanvasMetadataHandler.linkResolveQueue.setOnFinished(() => this.trigger("resolved")); await _CanvasMetadataHandler.linkResolveQueue.add( () => _CanvasMetadataHandler.resolveCanvasLinks.call(this, file.path) ); } static async updateMetadataCache(file) { const bytes = await this.vault.readBinary(file); const data = new TextDecoder().decode(new Uint8Array(bytes)); const hash = await HashHelper.getBytesHash(bytes); const cache2 = { mtime: file.stat.mtime, size: file.stat.size, hash }; this.saveFileCache(file.path, cache2); let metadata = this.metadataCache[cache2.hash]; if (metadata) return this.trigger( "changed", file, data, metadata ); const slowIndexingTimeout = setTimeout(() => { new import_obsidian8.Notice(`Canvas indexing taking a long time for file ${file.path}`); }, 1e4); try { metadata = await _CanvasMetadataHandler.computeCanvasMetadataAsync.call(this, data); } finally { clearTimeout(slowIndexingTimeout); } if (metadata) { this.saveMetaCache(hash, metadata); this.trigger("changed", file, data, metadata); } else { console.log("Canvas metadata failed to parse", file); } } static async computeCanvasMetadataAsync(data) { var _a, _b, _c, _d, _e; const content = JSON.parse(data || "{}"); const metadata = { v: 1 }; const frontmatter = (_a = content.metadata) == null ? void 0 : _a.frontmatter; metadata.frontmatterPosition = { start: { line: 0, col: 0, offset: 0 }, end: { line: 0, col: 0, offset: 0 } }; metadata.frontmatter = frontmatter; metadata.frontmatterLinks = []; for (const [key, value] of Object.entries(frontmatter != null ? frontmatter : {})) { const getLinks = (value2) => value2.map((v) => { if (!v.startsWith("[[") || !v.endsWith("]]")) return null; const [link, ...aliases] = v.slice(2, -2).split("|"); return { key, displayText: aliases.length > 0 ? aliases.join("|") : link, link, original: v }; }).filter((v) => v !== null); if (typeof value === "string") (_b = metadata.frontmatterLinks) == null ? void 0 : _b.push(...getLinks([value])); else if (Array.isArray(value)) (_c = metadata.frontmatterLinks) == null ? void 0 : _c.push(...getLinks(value)); } metadata.nodes = {}; metadata.links = []; metadata.embeds = []; await Promise.all(((_d = content.nodes) != null ? _d : []).map(async (node, index) => { var _a2, _b2; if (node.type !== "text") return; const text = node.text; const buffer = new TextEncoder().encode(text).buffer; const nodeMetadata = await this.computeMetadataAsync(buffer); if (!nodeMetadata) return; metadata.nodes[node.id] = nodeMetadata; metadata.links.push(...((_a2 = nodeMetadata.links) != null ? _a2 : []).map((link) => ({ ...link, position: { nodeId: node.id, start: { line: 0, col: 1, offset: 0 }, // 0 for node end: { line: 0, col: 1, offset: index } // index of node } }))); metadata.embeds.push(...((_b2 = nodeMetadata.embeds) != null ? _b2 : []).map((embed2) => ({ ...embed2, position: { nodeId: node.id, start: { line: 0, col: 1, offset: 0 }, // 0 for node end: { line: 0, col: 1, offset: index } // index of node } }))); })); for (const [index, node] of ((_e = content.nodes) != null ? _e : []).entries()) { if (node.type !== "file") continue; const file = node.file; if (!file) continue; metadata.embeds.push({ link: file, original: file, displayText: file, position: { start: { line: 0, col: 1, offset: 0 }, // 0 for nodes end: { line: 0, col: 1, offset: index } // index of node } }); } return metadata; } static async resolveCanvasLinks(filepath) { var _a, _b, _c, _d, _e; const file = this.vault.getAbstractFileByPath(filepath); if (!(file instanceof import_obsidian8.TFile)) return; const metadata = this.getFileCache(file); const references = [...(_a = metadata == null ? void 0 : metadata.links) != null ? _a : [], ...(_b = metadata == null ? void 0 : metadata.embeds) != null ? _b : []]; const referenceLinks = references.map((ref) => ref.link).sort(); const resolvedLinks = {}; const unresolvedLinks = {}; for (const link of referenceLinks) { const resolved = this.getFirstLinkpathDest(link, filepath); if (resolved) { (_d = resolvedLinks[_c = resolved.path]) != null ? _d : resolvedLinks[_c] = 0; resolvedLinks[resolved.path]++; } else { const strippedLink = link.endsWith(".md") ? link.slice(0, -3) : link; (_e = unresolvedLinks[strippedLink]) != null ? _e : unresolvedLinks[strippedLink] = 0; unresolvedLinks[strippedLink]++; } } this.resolvedLinks[filepath] = resolvedLinks; this.unresolvedLinks[filepath] = unresolvedLinks; await sleep(1); this.trigger("resolve", file); } }; _CanvasMetadataHandler.metadataQueue = new TaskQueue(); _CanvasMetadataHandler.linkResolveQueue = new TaskQueue(); var CanvasMetadataHandler = _CanvasMetadataHandler; // src/patchers/backlinks-patcher.ts var import_obsidian9 = require("obsidian"); var BacklinksPatcher = class extends Patcher { constructor() { super(...arguments); this.isRecomputingBacklinks = false; } async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; const that = this; await Patcher.waitForViewRequest(this.plugin, "backlink", (view) => { Patcher.patchPrototype(this.plugin, view.backlink, { recomputeBacklink: Patcher.OverrideExisting((next) => function(file, ...args) { that.isRecomputingBacklinks = true; const result = next.call(this, file, ...args); that.isRecomputingBacklinks = false; return result; }) }); }); Patcher.patchPrototype(this.plugin, this.plugin.app.vault, { recurseChildrenAC: (_next) => function(origin, traverse) { for (let stack = [origin]; stack.length > 0; ) { const current = stack.pop(); if (current) { traverse(current); if (current instanceof import_obsidian9.TFolder) stack = stack.concat(current.children); } } }, getMarkdownFiles: Patcher.OverrideExisting((next) => function(...args) { if (!that.isRecomputingBacklinks) return next.call(this, ...args); const files = []; const root = this.getRoot(); this.recurseChildrenAC(root, (child) => { if (child instanceof import_obsidian9.TFile && (child.extension === "md" || child.extension === "canvas")) { files.push(child); } }); return files; }) }); } }; // src/patchers/outgoing-links-patcher.ts var OutgoingLinksPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; await Patcher.waitForViewRequest(this.plugin, "outgoing-link", (view) => { Patcher.patchPrototype(this.plugin, view.outgoingLink, { recomputeLinks: Patcher.OverrideExisting((next) => function(...args) { var _a; const isCanvas = ((_a = this.file) == null ? void 0 : _a.extension) === "canvas"; if (isCanvas) this.file.extension = "md"; const result = next.call(this, ...args); if (isCanvas) this.file.extension = "canvas"; return result; }), recomputeUnlinked: Patcher.OverrideExisting((next) => function(...args) { var _a; const isCanvas = ((_a = this.file) == null ? void 0 : _a.extension) === "canvas"; if (isCanvas) this.file.extension = "md"; const result = next.call(this, ...args); if (isCanvas) this.file.extension = "canvas"; return result; }) }); }); } }; // src/patchers/file-manager-patcher.ts var FileManagerPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; const that = this; Patcher.patch(this.plugin, this.plugin.app.fileManager, { processFrontMatter: Patcher.OverrideExisting((next) => async function(file, fn, options) { if ((file == null ? void 0 : file.extension) === "canvas") { that.plugin.app.vault.process(file, (data) => { const content = JSON.parse(data); fn(content.metadata.frontmatter); return JSON.stringify(content, null, 2); }); return; } return next.call(this, file, fn, options); }) }); } }; // src/patchers/properties-patcher.ts var PropertiesPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; if (!this.plugin.app.viewRegistry.viewByType["file-properties"]) return; await Patcher.waitForViewRequest(this.plugin, "file-properties", (view) => { Patcher.patchPrototype(this.plugin, view, { isSupportedFile: Patcher.OverrideExisting((next) => function(file) { if ((file == null ? void 0 : file.extension) === "canvas") return true; return next.call(this, file); }), updateFrontmatter: Patcher.OverrideExisting((next) => function(file, content) { var _a, _b, _c; if ((file == null ? void 0 : file.extension) === "canvas") { let frontmatter; try { frontmatter = (_c = (_b = (_a = JSON.parse(content)) == null ? void 0 : _a.metadata) == null ? void 0 : _b.frontmatter) != null ? _c : {}; } catch (e) { frontmatter = {}; } this.rawFrontmatter = JSON.stringify(frontmatter, null, 2); this.frontmatter = frontmatter; return frontmatter; } return next.call(this, file, content); }), saveFrontmatter: Patcher.OverrideExisting((next) => function(frontmatter) { var _a; if (((_a = this.file) == null ? void 0 : _a.extension) === "canvas") { if (this.file !== this.modifyingFile) return; this.app.vault.process(this.file, (data) => { const content = JSON.parse(data); if (content == null ? void 0 : content.metadata) content.metadata.frontmatter = frontmatter; return JSON.stringify(content, null, 2); }); return; } return next.call(this, frontmatter); }) }); }); } }; // src/patchers/search-patcher.ts var SearchPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; const that = this; await Patcher.waitForViewRequest(this.plugin, "search", (view) => { const uninstallers = []; Patcher.patchThisAndPrototype(this.plugin, view, { startSearch: (next) => function(...args) { const result = next.call(this, ...args); if (this.searchQuery) { that.patchSearchQuery(this.searchQuery); uninstallers.forEach((uninstall) => uninstall()); } return result; } }, uninstallers); }); } patchSearchQuery(searchQuery) { Patcher.patchThisAndPrototype(this.plugin, searchQuery, { _match: Patcher.OverrideExisting((next) => function(data) { var _a, _b; const isCanvas = (_b = (_a = data.strings.filepath) == null ? void 0 : _a.endsWith(".canvas")) != null ? _b : false; if (isCanvas && !data.cache) data.cache = this.app.metadataCache.getCache(data.strings.filepath); return next.call(this, data); }) }); } }; // src/patchers/search-command-patcher.ts var import_obsidian10 = require("obsidian"); var SearchCommandPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("nativeFileSearchEnabled")) return; const that = this; Patcher.patch(this.plugin, this.plugin.app.commands.commands["editor:open-search"], { checkCallback: Patcher.OverrideExisting((next) => function(checking) { if (that.plugin.app.workspace.activeEditor) return next.call(this, checking); const activeCanvasView = that.plugin.getCurrentCanvasView(); if (!activeCanvasView) return next.call(this, checking); if (checking) return true; if (!activeCanvasView.canvas.searchEl) new CanvasSearchView(activeCanvasView); return true; }) }); } }; var CanvasSearchView = class { constructor(view) { this.searchMatches = []; this.matchIndex = 0; this.view = view; this.createSearchView(); } createSearchView() { this.containerEl = document.createElement("div"); this.containerEl.className = "document-search-container"; const documentSearch = document.createElement("div"); documentSearch.className = "document-search"; this.containerEl.appendChild(documentSearch); const searchInputContainer = document.createElement("div"); searchInputContainer.className = "search-input-container document-search-input"; documentSearch.appendChild(searchInputContainer); this.searchInput = document.createElement("input"); this.searchInput.type = "text"; this.searchInput.placeholder = "Find..."; this.searchInput.addEventListener("keydown", (e) => this.onKeyDown(e)); this.searchInput.addEventListener("input", () => this.onInput()); searchInputContainer.appendChild(this.searchInput); this.searchCount = document.createElement("div"); this.searchCount.className = "document-search-count"; this.searchCount.style.display = "none"; this.searchCount.textContent = "0 / 0"; searchInputContainer.appendChild(this.searchCount); const documentSearchButtons = document.createElement("div"); documentSearchButtons.className = "document-search-buttons"; documentSearch.appendChild(documentSearchButtons); const previousButton = document.createElement("button"); previousButton.className = "clickable-icon document-search-button"; previousButton.setAttribute("aria-label", "Previous\nShift + F3"); previousButton.setAttribute("data-tooltip-position", "top"); (0, import_obsidian10.setIcon)(previousButton, "arrow-up"); previousButton.addEventListener("click", () => this.changeMatch(this.matchIndex - 1)); documentSearchButtons.appendChild(previousButton); const nextButton = document.createElement("button"); nextButton.className = "clickable-icon document-search-button"; nextButton.setAttribute("aria-label", "Next\nF3"); nextButton.setAttribute("data-tooltip-position", "top"); (0, import_obsidian10.setIcon)(nextButton, "arrow-down"); nextButton.addEventListener("click", () => this.changeMatch(this.matchIndex + 1)); documentSearchButtons.appendChild(nextButton); const closeButton = document.createElement("button"); closeButton.className = "clickable-icon document-search-close-button"; closeButton.setAttribute("aria-label", "Exit search"); closeButton.setAttribute("data-tooltip-position", "top"); (0, import_obsidian10.setIcon)(closeButton, "x"); closeButton.addEventListener("click", () => this.close()); documentSearch.appendChild(closeButton); this.view.canvas.wrapperEl.appendChild(this.containerEl); this.view.canvas.searchEl = this.containerEl; this.searchInput.focus(); } onKeyDown(e) { if (e.key === "Enter" || e.key === "F3") this.changeMatch(this.matchIndex + (e.shiftKey ? -1 : 1)); else if (e.key === "Escape") this.close(); } onInput() { const hasQuery = this.searchInput.value.length > 0; this.searchCount.style.display = hasQuery ? "block" : "none"; if (!hasQuery) this.searchMatches = []; else { this.searchMatches = Array.from(this.view.canvas.nodes.values()).map((node) => { const nodeData = node.getData(); let content = void 0; if (nodeData.type === "text") content = nodeData.text; else if (nodeData.type === "group") content = nodeData.label; else if (nodeData.type === "file") content = node.child.data; if (!content) return null; const matches = []; const regex = new RegExp(this.searchInput.value, "gi"); let match; while ((match = regex.exec(content)) !== null) { matches.push([match.index, match.index + match[0].length]); } return { nodeId: node.id, content, matches }; }).filter((match) => match && match.matches.length > 0); } this.changeMatch(0); } changeMatch(index) { if (this.searchMatches.length === 0) this.matchIndex = -1; else { if (index < 0) index += this.searchMatches.length; this.matchIndex = index % this.searchMatches.length; } const match = this.searchMatches[this.matchIndex]; if (match) this.goToMatch(match); this.searchCount.textContent = `${this.matchIndex + 1} / ${this.searchMatches.length}`; } goToMatch(match) { this.view.setEphemeralState({ match }); } close() { this.containerEl.remove(); this.view.canvas.searchEl = void 0; } }; // src/canvas-extensions/metadata-canvas-extension.ts var import_obsidian11 = require("obsidian"); var MetadataCanvasExtension = class extends CanvasExtension { constructor() { super(...arguments); this.canvasCssclassesCache = /* @__PURE__ */ new Map(); } isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.onCanvasChanged(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-metadata-changed", (canvas) => this.onMetadataChanged(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-view-unloaded:before", (view) => this.onCanvasViewUnloaded(view) )); } onCanvasChanged(canvas) { var _a; const metadata = (_a = canvas.data) == null ? void 0 : _a.metadata; if (!metadata || metadata.version !== CURRENT_SPEC_VERSION) return new import_obsidian11.Notice("Metadata node not found or version mismatch. Should have been migrated (but wasn't)."); const that = this; const validator = { get(target, key) { if (typeof target[key] === "object" && target[key] !== null) return new Proxy(target[key], validator); else return target[key]; }, set(target, key, value) { target[key] = value; that.plugin.app.workspace.trigger("advanced-canvas:canvas-metadata-changed", canvas); canvas.requestSave(); return true; } }; canvas.metadata = new Proxy(metadata, validator); this.plugin.app.workspace.trigger("advanced-canvas:canvas-metadata-changed", canvas); } onMetadataChanged(canvas) { var _a, _b, _c; const oldCssClasses = this.canvasCssclassesCache.get(canvas.view); if (oldCssClasses) canvas.wrapperEl.classList.remove(...oldCssClasses); const currentClasses = (_c = (_b = (_a = canvas.metadata) == null ? void 0 : _a.frontmatter) == null ? void 0 : _b.cssclasses) != null ? _c : []; this.canvasCssclassesCache.set(canvas.view, currentClasses); if (currentClasses.length > 0) canvas.wrapperEl.classList.add(...currentClasses); } onCanvasViewUnloaded(view) { this.canvasCssclassesCache.delete(view); } }; // src/utils/modal-helper.ts var import_obsidian12 = require("obsidian"); var AbstractSelectionModal = class extends import_obsidian12.FuzzySuggestModal { constructor(app, placeholder, suggestions) { super(app); this.suggestions = suggestions; this.setPlaceholder(placeholder); this.setInstructions([{ command: "\u2191\u2193", purpose: "to navigate" }, { command: "esc", purpose: "to dismiss" }]); } getItems() { return this.suggestions; } getItemText(item) { return item; } onChooseItem(item, evt) { } awaitInput() { return new Promise((resolve, _reject) => { this.onChooseItem = (item) => { resolve(item); }; this.open(); }); } }; var FileNameModal = class extends import_obsidian12.SuggestModal { constructor(app, parentPath, fileExtension) { super(app); this.parentPath = parentPath.replace(/^\//, "").replace(/\/$/, ""); this.fileExtension = fileExtension; } getSuggestions(query) { const queryWithoutExtension = query.replace(new RegExp(`\\.${this.fileExtension}$`), ""); if (queryWithoutExtension === "") return []; const queryWithExtension = queryWithoutExtension + "." + this.fileExtension; const suggestions = [queryWithExtension]; if (this.parentPath.length > 0) suggestions.splice(0, 0, `${this.parentPath}/${queryWithExtension}`); return suggestions.filter((s) => this.app.vault.getAbstractFileByPath(s) === null); } renderSuggestion(text, el) { el.setText(text); } onChooseSuggestion(_text, _evt) { } awaitInput() { return new Promise((resolve, _reject) => { this.onChooseSuggestion = (text) => { resolve(text); }; this.open(); }); } }; var FileSelectModal = class extends import_obsidian12.SuggestModal { constructor(app, extensionsRegex, suggestNewFile = false) { super(app); this.files = this.app.vault.getFiles().map((file) => file.path).filter((path) => { var _a; return (_a = FilepathHelper.extension(path)) == null ? void 0 : _a.match(extensionsRegex != null ? extensionsRegex : /.*/); }); this.suggestNewFile = suggestNewFile; this.setPlaceholder("Type to search..."); this.setInstructions([{ command: "\u2191\u2193", purpose: "to navigate" }, { command: "\u21B5", purpose: "to open" }, { command: "shift \u21B5", purpose: "to create" }, { command: "esc", purpose: "to dismiss" }]); this.scope.register(["Shift"], "Enter", (e) => { this.onChooseSuggestion(this.inputEl.value, e); this.close(); }); } getSuggestions(query) { const suggestions = this.files.filter((path) => path.toLowerCase().includes(query.toLowerCase())); if (suggestions.length === 0 && this.suggestNewFile) suggestions.push(query); return suggestions; } renderSuggestion(path, el) { const simplifiedPath = path.replace(/\.md$/, ""); el.setText(simplifiedPath); } onChooseSuggestion(_path, _evt) { } awaitInput() { return new Promise((resolve, _reject) => { this.onChooseSuggestion = (path, _evt) => { const file = this.app.vault.getAbstractFileByPath(path); if (file instanceof import_obsidian12.TFile) return resolve(file); if (!this.suggestNewFile) return; if (FilepathHelper.extension(path) === void 0) path += ".md"; const newFile = this.app.vault.create(path, ""); resolve(newFile); }; this.open(); }); } }; // src/canvas-extensions/node-ratio-canvas-extension.ts var NodeRatioCanvasExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "canvas:node-menu", (menu, node) => this.onNodeMenu(menu, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-resized", (canvas, node) => this.onNodeResized(canvas, node) )); } onNodeMenu(menu, node) { if (!this.plugin.settings.getSetting("aspectRatioControlFeatureEnabled")) return; menu.addItem((item) => { item.setTitle("Set Aspect Ratio").setIcon("aspect-ratio").onClick(async () => { const NO_RATIO = "No ratio enforcement"; const newRatioString = await new AbstractSelectionModal(this.plugin.app, "Enter aspect ratio (width:height)", ["16:9", "4:3", "3:2", "1:1", NO_RATIO]).awaitInput(); const nodeData = node.getData(); if (newRatioString === NO_RATIO) { node.setData({ ...nodeData, ratio: void 0 }); return; } const [width, height] = newRatioString.split(":").map(Number); if (width && height) { node.setData({ ...nodeData, ratio: width / height }); node.setData({ ...node.getData(), width: nodeData.height * (width / height) }); } }); }); } onNodeResized(_canvas, node) { const nodeData = node.getData(); if (!nodeData.ratio) return; const nodeBBox = node.getBBox(); const nodeSize = { width: nodeBBox.maxX - nodeBBox.minX, height: nodeBBox.maxY - nodeBBox.minY }; const nodeAspectRatio = nodeSize.width / nodeSize.height; if (nodeAspectRatio < nodeData.ratio) nodeSize.width = nodeSize.height * nodeData.ratio; else nodeSize.height = nodeSize.width / nodeData.ratio; node.setData({ ...nodeData, width: nodeSize.width, height: nodeSize.height }); } }; // src/canvas-extensions/group-canvas-extension.ts var GROUP_NODE_SIZE = { width: 300, height: 300 }; var GROUP_NODE_PADDING = 20; var GroupCanvasExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.addCommand({ id: "create-group-around-selection", name: "Group selected nodes", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size > 0, (canvas) => this.createGroupAroundSelection(canvas) ) }); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => { CanvasHelper.addCardMenuOption( canvas, CanvasHelper.createCardMenuOption( canvas, { id: "create-group", label: "Drag to add group", icon: "group" }, () => GROUP_NODE_SIZE, (canvas2, pos) => { canvas2.createGroupNode({ pos, size: GROUP_NODE_SIZE }); } ) ); } )); } createGroupAroundSelection(canvas) { const bbox = BBoxHelper.combineBBoxes( Array.from(canvas.selection.values()).map((e) => e.getBBox()) ); const paddedBBox = BBoxHelper.enlargeBBox(bbox, GROUP_NODE_PADDING); canvas.createGroupNode({ pos: { x: paddedBBox.minX, y: paddedBBox.minY }, size: { width: paddedBBox.maxX - paddedBBox.minX, height: paddedBBox.maxY - paddedBBox.minY } }); } }; // src/canvas-extensions/presentation-canvas-extension.ts var import_obsidian13 = require("obsidian"); var START_SLIDE_NAME = "Start Slide"; var DEFAULT_SLIDE_NAME = "New Slide"; var PresentationCanvasExtension = class extends CanvasExtension { constructor() { super(...arguments); this.savedViewport = null; this.isPresentationMode = false; this.visitedNodeIds = []; this.fullscreenModalObserver = null; this.presentationUsesFullscreen = false; } isEnabled() { return "presentationFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "canvas:selection-menu", (menu, canvas) => { menu.addItem( (item) => item.setTitle("Wrap in slide").setIcon("gallery-vertical").onClick(() => this.addSlide( canvas, void 0, BBoxHelper.enlargeBBox(BBoxHelper.combineBBoxes( [...canvas.selection.values()].map((element) => element.getBBox()) ), this.plugin.settings.getSetting("wrapInSlidePadding")) )) ); } )); this.plugin.addCommand({ id: "create-new-slide", name: "Create new slide", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && !this.isPresentationMode, (canvas) => this.addSlide(canvas) ) }); this.plugin.addCommand({ id: "set-start-node", name: "Set start node", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && !this.isPresentationMode && canvas.getSelectionData().nodes.length === 1, (canvas) => this.setStartNode(canvas, canvas.nodes.get(canvas.getSelectionData().nodes[0].id)) ) }); this.plugin.addCommand({ id: "start-presentation", name: "Start presentation", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => !this.isPresentationMode, (canvas) => this.startPresentation(canvas) ) }); this.plugin.addCommand({ id: "continue-presentation", name: "Continue presentation", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => !this.isPresentationMode, (canvas) => this.startPresentation(canvas, true) ) }); this.plugin.addCommand({ id: "end-presentation", name: "End presentation", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => this.isPresentationMode, (canvas) => this.endPresentation(canvas) ) }); this.plugin.addCommand({ id: "previous-node", name: "Previous node", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => this.isPresentationMode, (canvas) => this.previousNode(canvas) ) }); this.plugin.addCommand({ id: "next-node", name: "Next node", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => this.isPresentationMode, (canvas) => this.nextNode(canvas) ) }); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.onCanvasChanged(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenuCreated(canvas) )); } onCanvasChanged(canvas) { CanvasHelper.addCardMenuOption( canvas, CanvasHelper.createCardMenuOption( canvas, { id: "new-slide", label: "Drag to add slide", icon: "gallery-vertical" }, () => this.getDefaultSlideSize(), (canvas2, pos) => this.addSlide(canvas2, pos) ) ); } onPopupMenuCreated(canvas) { if (!this.plugin.settings.getSetting("showSetStartNodeInPopup")) return; const selectedNodesData = canvas.getSelectionData().nodes; if (canvas.readonly || selectedNodesData.length !== 1 || canvas.selection.size > 1) return; const selectedNode = canvas.nodes.get(selectedNodesData[0].id); if (!selectedNode) return; CanvasHelper.addPopupMenuOption( canvas, CanvasHelper.createPopupMenuOption({ id: "start-node", label: "Set as start slide", icon: "play", callback: () => this.setStartNode(canvas, selectedNode) }) ); } setStartNode(canvas, node) { if (!node) return; canvas.metadata["startNode"] = node.getData().id; } getDefaultSlideSize() { const slideSize = this.plugin.settings.getSetting("defaultSlideDimensions"); return { width: slideSize[0], height: slideSize[1] }; } getSlideAspectRatio() { const slideSize = this.getDefaultSlideSize(); return slideSize.width / slideSize.height; } addSlide(canvas, pos, bbox) { const isStartNode = canvas.metadata["startNode"] === void 0; const slideSize = this.getDefaultSlideSize(); const slideAspectRatio = this.getSlideAspectRatio(); if (bbox) { const bboxWidth = bbox.maxX - bbox.minX; const bboxHeight = bbox.maxY - bbox.minY; if (bboxWidth / bboxHeight > slideAspectRatio) { slideSize.width = bboxWidth; slideSize.height = bboxWidth / slideAspectRatio; } else { slideSize.height = bboxHeight; slideSize.width = bboxHeight * slideAspectRatio; } pos = { x: bbox.minX, y: bbox.minY }; } if (!pos) pos = CanvasHelper.getCenterCoordinates(canvas, this.getDefaultSlideSize()); const groupNode = canvas.createGroupNode({ pos, size: slideSize, label: isStartNode ? START_SLIDE_NAME : DEFAULT_SLIDE_NAME, focus: false }); groupNode.setData({ ...groupNode.getData(), ratio: slideAspectRatio }); if (isStartNode) canvas.metadata["startNode"] = groupNode.getData().id; } async animateNodeTransition(canvas, fromNode, toNode) { const removePadding = this.plugin.settings.getSetting("zoomToSlideWithoutPadding"); const animationDurationMs = this.plugin.settings.getSetting("slideTransitionAnimationDuration") * 1e3; const toNodeBBox = CanvasHelper.getSmallestAllowedZoomBBox(canvas, toNode.getBBox()); const toNodeBBoxPadded = removePadding ? toNodeBBox : BBoxHelper.enlargeBBox(toNodeBBox, 50); console.log({ toNodeBBox, toNodeBBoxPadded }); if (animationDurationMs > 0 && fromNode) { const animationIntensity = this.plugin.settings.getSetting("slideTransitionAnimationIntensity"); const fromNodeBBox = CanvasHelper.getSmallestAllowedZoomBBox(canvas, fromNode.getBBox()); const fromNodeBBoxPadded = removePadding ? fromNodeBBox : BBoxHelper.enlargeBBox(fromNodeBBox, 50); const currentNodeBBoxEnlarged = BBoxHelper.scaleBBox(fromNodeBBoxPadded, animationIntensity); canvas.zoomToRealBbox(currentNodeBBoxEnlarged); await sleep(animationDurationMs / 2); if (fromNode.getData().id !== toNode.getData().id) { const nextNodeBBoxEnlarged = BBoxHelper.scaleBBox(toNodeBBoxPadded, animationIntensity); canvas.zoomToRealBbox(nextNodeBBoxEnlarged); await sleep(animationDurationMs / 2); } } canvas.zoomToRealBbox(toNodeBBoxPadded); } async startPresentation(canvas, tryContinue = false) { if (!tryContinue || this.visitedNodeIds.length === 0) { const startNode2 = canvas.metadata["startNode"] && canvas.nodes.get(canvas.metadata["startNode"]); if (!startNode2) { new import_obsidian13.Notice("No start node found. Please mark a node as a start node trough the popup menu."); return; } this.visitedNodeIds = [startNode2.getData().id]; } this.savedViewport = { x: canvas.tx, y: canvas.ty, zoom: canvas.tZoom }; const shouldEnterFullscreen = this.plugin.settings.getSetting("fullscreenPresentationEnabled"); this.presentationUsesFullscreen = shouldEnterFullscreen; canvas.wrapperEl.focus(); canvas.wrapperEl.classList.add("presentation-mode"); if (shouldEnterFullscreen) { try { await canvas.wrapperEl.requestFullscreen(); } catch (e) { this.presentationUsesFullscreen = false; } } canvas.setReadonly(true); if (this.plugin.settings.getSetting("useUnclampedZoomWhilePresenting")) canvas.screenshotting = true; canvas.wrapperEl.onkeydown = (e) => { if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); this.endPresentation(canvas); return; } if (this.plugin.settings.getSetting("useArrowKeysToChangeSlides")) { if (e.key === "ArrowRight") this.nextNode(canvas); else if (e.key === "ArrowLeft") this.previousNode(canvas); } if (this.plugin.settings.getSetting("usePgUpPgDownKeysToChangeSlides")) { if (e.key === "PageDown") this.nextNode(canvas); else if (e.key === "PageUp") this.previousNode(canvas); } }; if (this.presentationUsesFullscreen) { this.fullscreenModalObserver = new MutationObserver((mutationRecords) => { mutationRecords.forEach((mutationRecord) => { mutationRecord.addedNodes.forEach((node) => { var _a; document.body.removeChild(node); (_a = document.fullscreenElement) == null ? void 0 : _a.appendChild(node); }); }); const inputField = document.querySelector(".prompt-input"); if (inputField) inputField.focus(); }); this.fullscreenModalObserver.observe(document.body, { childList: true }); canvas.wrapperEl.onfullscreenchange = (_e) => { if (document.fullscreenElement) return; this.endPresentation(canvas); }; } this.isPresentationMode = true; if (this.presentationUsesFullscreen) await sleep(500); const startNodeId = this.visitedNodeIds.first(); if (!startNodeId) return; const startNode = canvas.nodes.get(startNodeId); if (!startNode) return; this.animateNodeTransition(canvas, void 0, startNode); } endPresentation(canvas) { var _a; if (!this.isPresentationMode) return; if (this.presentationUsesFullscreen) { (_a = this.fullscreenModalObserver) == null ? void 0 : _a.disconnect(); this.fullscreenModalObserver = null; canvas.wrapperEl.onfullscreenchange = null; if (document.fullscreenElement) document.exitFullscreen(); } canvas.wrapperEl.onkeydown = null; canvas.setReadonly(false); if (this.plugin.settings.getSetting("useUnclampedZoomWhilePresenting")) canvas.screenshotting = false; canvas.wrapperEl.classList.remove("presentation-mode"); if (this.plugin.settings.getSetting("resetViewportOnPresentationEnd")) canvas.setViewport(this.savedViewport.x, this.savedViewport.y, this.savedViewport.zoom); this.isPresentationMode = false; this.presentationUsesFullscreen = false; } nextNode(canvas) { var _a; const fromNodeId = this.visitedNodeIds.last(); if (!fromNodeId) return; const fromNode = canvas.nodes.get(fromNodeId); if (!fromNode) return; const outgoingEdges = canvas.getEdgesForNode(fromNode).filter((edge) => edge.from.node.getData().id === fromNodeId); let toNode = (_a = outgoingEdges.first()) == null ? void 0 : _a.to.node; if (outgoingEdges.length > 1) { const sortedEdges = outgoingEdges.sort((a, b) => { if (!a.label) return 1; if (!b.label) return -1; return a.label.localeCompare(b.label); }); const traversedEdgesCount = this.visitedNodeIds.filter((visitedNodeId) => visitedNodeId === fromNodeId).length - 1; const nextEdge = sortedEdges[traversedEdgesCount]; toNode = nextEdge.to.node; } if (toNode) { this.visitedNodeIds.push(toNode.getData().id); this.animateNodeTransition(canvas, fromNode, toNode); } else { this.animateNodeTransition(canvas, fromNode, fromNode); } } previousNode(canvas) { const fromNodeId = this.visitedNodeIds.pop(); if (!fromNodeId) return; const fromNode = canvas.nodes.get(fromNodeId); if (!fromNode) return; const toNodeId = this.visitedNodeIds.last(); let toNode = toNodeId ? canvas.nodes.get(toNodeId) : null; if (!toNode) { toNode = fromNode; this.visitedNodeIds.push(fromNodeId); } this.animateNodeTransition(canvas, fromNode, toNode); } }; // src/canvas-extensions/z-ordering-canvas-extension.ts var ZOrderingCanvasExtension = class extends CanvasExtension { isEnabled() { return "zOrderingControlFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "canvas:node-menu", (menu, node) => this.nodeContextMenu(node, menu) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "canvas:selection-menu", (menu, canvas) => this.selectionContextMenu(canvas, menu) )); } nodeContextMenu(node, menu) { this.addZOrderingContextMenuItems(node.canvas, [node], menu); } selectionContextMenu(canvas, menu) { const selectedNodes = canvas.getSelectionData().nodes.map((nodeData) => canvas.nodes.get(nodeData.id)).filter((node) => node !== void 0); this.addZOrderingContextMenuItems(canvas, selectedNodes, menu); } addZOrderingContextMenuItems(canvas, nodes, menu) { menu.addSeparator(); if (this.plugin.settings.getSetting("zOrderingControlShowOneLayerShiftOptions") && nodes.length === 1) { menu.addItem((item) => { item.setTitle("Move one layer forward"); item.setIcon("arrow-up"); item.onClick(() => this.moveOneLayer(canvas, nodes.first(), true)); }); menu.addItem((item) => { item.setTitle("Move one layer backward"); item.setIcon("arrow-down"); item.onClick(() => this.moveOneLayer(canvas, nodes.first(), false)); }); } menu.addItem((item) => { item.setTitle("Bring to Front"); item.setIcon("bring-to-front"); item.onClick(() => this.moveMaxLayers(canvas, nodes, true)); }); menu.addItem((item) => { item.setTitle("Send to Back"); item.setIcon("send-to-back"); item.onClick(() => this.moveMaxLayers(canvas, nodes, false)); }); if (nodes.some((node) => node.getData().zIndex !== void 0)) { menu.addItem((item) => { item.setTitle("Remove persistent z-index"); item.setIcon("pin-off"); item.onClick(() => this.removePersistentZIndexes(canvas, nodes)); }); } menu.addSeparator(); } moveOneLayer(canvas, selectedNode, forward) { const selectedNodeBBox = selectedNode.getBBox(); const collidingNodes = [...canvas.nodes.values()].filter((node) => BBoxHelper.isColliding(selectedNodeBBox, node.getBBox())).filter((node) => node !== selectedNode); const nearestZIndexNode = collidingNodes.sort((a, b) => forward ? a.zIndex - b.zIndex : b.zIndex - a.zIndex).filter((node) => forward ? node.zIndex > selectedNode.zIndex : node.zIndex < selectedNode.zIndex).first(); if (nearestZIndexNode === void 0) return; const targetZIndex = nearestZIndexNode.zIndex; this.setNodesZIndex([nearestZIndexNode], selectedNode.zIndex); this.setNodesZIndex([selectedNode], targetZIndex); } moveMaxLayers(canvas, selectedNodes, forward) { const targetZIndex = forward ? Math.max(...this.getAllZIndexes(canvas)) + 1 : Math.min(...this.getAllZIndexes(canvas)) - selectedNodes.length; this.setNodesZIndex(selectedNodes, targetZIndex); } removePersistentZIndexes(_canvas, nodes) { for (const node of nodes) node.setZIndex(void 0); } setNodesZIndex(nodes, zIndex) { const sortedNodes = nodes.sort((a, b) => a.zIndex - b.zIndex); for (let i = 0; i < sortedNodes.length; i++) { const node = sortedNodes[i]; const finalZIndex = zIndex + i; node.setZIndex(finalZIndex); } } getAllZIndexes(canvas) { return [...canvas.nodes.values()].map((n) => n.zIndex); } }; // src/canvas-extensions/better-readonly-canvas-extension.ts var BetterReadonlyCanvasExtension = class extends CanvasExtension { constructor() { super(...arguments); this.isMovingToBBox = false; } isEnabled() { return "betterReadonlyEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.updatePopupMenu(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:viewport-changed:before", (canvas) => this.onBeforeViewPortChanged(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:zoom-to-bbox:before", () => this.isMovingToBBox = true )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:readonly-changed", (canvas, _readonly) => { this.updatePopupMenu(canvas); this.updateLockedZoom(canvas); this.updateLockedPan(canvas); } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.addQuickSettings(canvas) )); } onBeforeViewPortChanged(canvas) { var _a, _b, _c, _d, _e, _f; if (this.isMovingToBBox) { this.isMovingToBBox = false; this.updateLockedZoom(canvas); this.updateLockedPan(canvas); return; } if (!canvas.readonly) return; if (this.plugin.settings.getSetting("disableZoom")) { canvas.zoom = (_a = canvas.lockedZoom) != null ? _a : canvas.zoom; canvas.tZoom = (_b = canvas.lockedZoom) != null ? _b : canvas.tZoom; } if (this.plugin.settings.getSetting("disablePan")) { canvas.x = (_c = canvas.lockedX) != null ? _c : canvas.x; canvas.tx = (_d = canvas.lockedX) != null ? _d : canvas.tx; canvas.y = (_e = canvas.lockedY) != null ? _e : canvas.y; canvas.ty = (_f = canvas.lockedY) != null ? _f : canvas.ty; } } addQuickSettings(canvas) { var _a; const settingsContainer = (_a = canvas.quickSettingsButton) == null ? void 0 : _a.parentElement; if (!settingsContainer) return; CanvasHelper.addControlMenuButton( settingsContainer, this.createToggle({ id: "disable-node-popup", label: "Disable node popup", icon: "arrow-up-right-from-circle", callback: () => this.updatePopupMenu(canvas) }, "disableNodePopup") ); CanvasHelper.addControlMenuButton( settingsContainer, this.createToggle({ id: "disable-zoom", label: "Disable zoom", icon: "zoom-in", callback: () => this.updateLockedZoom(canvas) }, "disableZoom") ); CanvasHelper.addControlMenuButton( settingsContainer, this.createToggle({ id: "disable-pan", label: "Disable pan", icon: "move", callback: () => this.updateLockedPan(canvas) }, "disablePan") ); } createToggle(menuOption, settingKey) { const toggle = CanvasHelper.createControlMenuButton({ ...menuOption, callback: () => (async () => { var _a; const newValue = !this.plugin.settings.getSetting(settingKey); await this.plugin.settings.setSetting({ [settingKey]: newValue }); toggle.dataset.toggled = this.plugin.settings.getSetting(settingKey).toString(); (_a = menuOption.callback) == null ? void 0 : _a.call(this); })() }); toggle.classList.add("show-while-readonly"); toggle.dataset.toggled = this.plugin.settings.getSetting(settingKey).toString(); return toggle; } updatePopupMenu(canvas) { const hidden = canvas.readonly && this.plugin.settings.getSetting("disableNodePopup"); canvas.menu.menuEl.style.visibility = hidden ? "hidden" : "visible"; } updateLockedZoom(canvas) { canvas.lockedZoom = canvas.tZoom; } updateLockedPan(canvas) { canvas.lockedX = canvas.tx; canvas.lockedY = canvas.ty; } }; // src/canvas-extensions/encapsulate-canvas-extension.ts var ENCAPSULATED_FILE_NODE_SIZE = { width: 300, height: 300 }; var EncapsulateCanvasExtension = class extends CanvasExtension { isEnabled() { return "canvasEncapsulationEnabled"; } init() { this.plugin.addCommand({ id: "encapsulate-selection", name: "Encapsulate selection", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && canvas.selection.size > 0, (canvas) => this.encapsulateSelection(canvas) ) }); this.plugin.registerEvent(this.plugin.app.workspace.on( "canvas:selection-menu", (menu, canvas) => { menu.addItem( (item) => item.setTitle("Encapsulate").setIcon("file-plus").onClick(() => this.encapsulateSelection(canvas)) ); } )); } async encapsulateSelection(canvas) { var _a, _b, _c, _d; const selection = canvas.getSelectionData(); const canvasSettings = this.plugin.app.internalPlugins.plugins.canvas.instance.options; const defaultNewCanvasLocation = canvasSettings.newFileLocation; let targetFolderPath = this.plugin.app.vault.getRoot().path; if (defaultNewCanvasLocation === "current") targetFolderPath = (_c = (_b = (_a = canvas.view.file) == null ? void 0 : _a.parent) == null ? void 0 : _b.path) != null ? _c : targetFolderPath; else if (defaultNewCanvasLocation === "folder") targetFolderPath = (_d = canvasSettings.newFileFolderPath) != null ? _d : targetFolderPath; const targetFilePath = await new FileNameModal( this.plugin.app, targetFolderPath, "canvas" ).awaitInput(); const newFileData = { nodes: selection.nodes, edges: selection.edges }; const file = await this.plugin.app.vault.create(targetFilePath, JSON.stringify(newFileData, null, 2)); for (const nodeData of selection.nodes) { const node = canvas.nodes.get(nodeData.id); if (node) canvas.removeNode(node); } canvas.createFileNode({ pos: { x: selection.center.x - ENCAPSULATED_FILE_NODE_SIZE.width / 2, y: selection.center.y - ENCAPSULATED_FILE_NODE_SIZE.height / 2 }, size: ENCAPSULATED_FILE_NODE_SIZE, file }); } }; // src/canvas-extensions/commands-canvas-extension.ts var import_obsidian14 = require("obsidian"); var DIRECTIONS = ["up", "down", "left", "right"]; var CommandsCanvasExtension = class extends CanvasExtension { isEnabled() { return "commandsFeatureEnabled"; } init() { this.plugin.addCommand({ id: "toggle-readonly", name: "Toggle readonly", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => true, (canvas) => canvas.setReadonly(!canvas.readonly) ) }); this.plugin.addCommand({ id: "create-text-node", name: "Create text node", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly, (canvas) => this.createTextNode(canvas) ) }); this.plugin.addCommand({ id: "create-file-node", name: "Create file node", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly, (canvas) => this.createFileNode(canvas) ) }); this.plugin.addCommand({ id: "select-all-edges", name: "Select all edges", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => true, (canvas) => canvas.updateSelection( () => canvas.selection = new Set(canvas.edges.values()) ) ) }); this.plugin.addCommand({ id: "zoom-to-selection", name: "Zoom to selection", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size > 0, (canvas) => canvas.zoomToSelection() ) }); this.plugin.addCommand({ id: "zoom-to-fit", name: "Zoom to fit", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => true, (canvas) => canvas.zoomToFit() ) }); for (const direction of DIRECTIONS) { this.plugin.addCommand({ id: `clone-node-${direction}`, name: `Clone node ${direction}`, checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => { var _a; return !canvas.readonly && canvas.selection.size === 1 && ((_a = canvas.selection.values().next().value) == null ? void 0 : _a.getData().type) === "text"; }, (canvas) => this.cloneNode(canvas, direction) ) }); this.plugin.addCommand({ id: `expand-node-${direction}`, name: `Expand node ${direction}`, checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && canvas.selection.size === 1, (canvas) => this.expandNode(canvas, direction) ) }); this.plugin.addCommand({ id: `navigate-${direction}`, name: `Navigate ${direction}`, checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.getSelectionData().nodes.length === 1, (canvas) => this.navigate(canvas, direction) ) }); } this.plugin.addCommand({ id: "flip-selection-horizontally", name: "Flip selection horizontally", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && canvas.selection.size > 0, (canvas) => this.flipSelection( canvas, true ) ) }); this.plugin.addCommand({ id: "flip-selection-vertically", name: "Flip selection vertically", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && canvas.selection.size > 0, (canvas) => this.flipSelection(canvas, false) ) }); this.plugin.addCommand({ id: "select-connected-edges", name: "Select connected edges", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size > 0, (canvas) => CanvasHelper.selectEdgesForNodes(canvas, "connected") ) }); this.plugin.addCommand({ id: "select-incoming-edges", name: "Select incoming edges", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size > 0, (canvas) => CanvasHelper.selectEdgesForNodes(canvas, "incoming") ) }); this.plugin.addCommand({ id: "select-outgoing-edges", name: "Select outgoing edges", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size > 0, (canvas) => CanvasHelper.selectEdgesForNodes(canvas, "outgoing") ) }); this.plugin.addCommand({ id: "swap-nodes", name: "Swap nodes", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && canvas.getSelectionData().nodes.length === 2, (canvas) => { const selectedNodes = canvas.getSelectionData().nodes.map((nodeData) => canvas.nodes.get(nodeData.id)).filter((node) => node !== void 0); if (selectedNodes.length !== 2) return; const [nodeA, nodeB] = selectedNodes; const nodeAData = nodeA.getData(); const nodeBData = nodeB.getData(); nodeA.setData({ ...nodeAData, x: nodeBData.x, y: nodeBData.y, width: nodeBData.width, height: nodeBData.height }); nodeB.setData({ ...nodeBData, x: nodeAData.x, y: nodeAData.y, width: nodeAData.width, height: nodeAData.height }); canvas.pushHistory(canvas.getData()); } ) }); this.plugin.addCommand({ id: "copy-wikilink-to-node", name: "Copy wikilink to node", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly && canvas.selection.size === 1, (canvas) => { const file = canvas.view.file; if (!file) return; const nodeData = canvas.getSelectionData().nodes[0]; if (!nodeData) return; const wikilink = `[[${file.path}#${nodeData.id}|${file.name} (${TextHelper.toTitleCase(nodeData.type)} node)]]`; navigator.clipboard.writeText(wikilink); new import_obsidian14.Notice("Copied wikilink to node to clipboard.", 2e3); } ) }); this.plugin.addCommand({ id: "pull-outgoing-links-to-canvas", name: "Pull outgoing links to canvas", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly, (canvas) => { var _a, _b, _c; const canvasFile = canvas.view.file; if (!canvasFile) return; let selectedNodeIds = canvas.getSelectionData().nodes.map((node) => node.id); if (selectedNodeIds.length === 0) selectedNodeIds = [...canvas.nodes.keys()]; const metadata = this.plugin.app.metadataCache.getFileCache(canvasFile); if (!metadata) return; const outgoingLinks = /* @__PURE__ */ new Set(); for (const nodeId of selectedNodeIds) { let relativeFile = canvasFile; let nodeOutgoingLinks = (_b = (_a = metadata.nodes) == null ? void 0 : _a[nodeId]) == null ? void 0 : _b.links; if (!nodeOutgoingLinks) { const file = (_c = canvas.nodes.get(nodeId)) == null ? void 0 : _c.file; if (!file) continue; const fileMetadata = this.plugin.app.metadataCache.getFileCache(file); nodeOutgoingLinks = fileMetadata == null ? void 0 : fileMetadata.links; relativeFile = file; } if (!nodeOutgoingLinks) continue; for (const nodeOutgoingLink of nodeOutgoingLinks) { const resolvedLink = this.plugin.app.metadataCache.getFirstLinkpathDest(nodeOutgoingLink.link, relativeFile.path); if (!(resolvedLink instanceof import_obsidian14.TFile)) continue; outgoingLinks.add(resolvedLink); } } const existingFileNodes = /* @__PURE__ */ new Set([canvas.view.file]); for (const node of canvas.nodes.values()) { if (node.getData().type !== "file" || !node.file) continue; existingFileNodes.add(node.file); } for (const outgoingLink of outgoingLinks) { if (existingFileNodes.has(outgoingLink)) continue; this.createFileNode(canvas, outgoingLink); } } ) }); this.plugin.addCommand({ id: "pull-backlinks-to-canvas", name: "Pull backlinks to canvas", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => !canvas.readonly, (canvas) => { const canvasFile = canvas.view.file; if (!canvasFile) return; const selectedNodesData = canvas.getSelectionData().nodes.map((node) => node); const backlinks = /* @__PURE__ */ new Set(); if (selectedNodesData.length > 0) { for (const nodeData of selectedNodesData) { if (nodeData.type !== "file" || !nodeData.file) continue; const file = this.plugin.app.vault.getFileByPath(nodeData.file); if (!file) continue; const nodeBacklinks = this.plugin.app.metadataCache.getBacklinksForFile(file); if (!nodeBacklinks) continue; for (const nodeBacklink of nodeBacklinks.data.keys()) { const resolvedLink = this.plugin.app.metadataCache.getFirstLinkpathDest(nodeBacklink, file.path); if (!(resolvedLink instanceof import_obsidian14.TFile)) continue; backlinks.add(resolvedLink); } } } else { const canvasBacklinks = this.plugin.app.metadataCache.getBacklinksForFile(canvasFile); if (!canvasBacklinks) return; for (const canvasBacklink of canvasBacklinks.data.keys()) { const resolvedLink = this.plugin.app.metadataCache.getFirstLinkpathDest(canvasBacklink, canvasFile.path); if (!(resolvedLink instanceof import_obsidian14.TFile)) continue; backlinks.add(resolvedLink); } } const existingFileNodes = /* @__PURE__ */ new Set([canvas.view.file]); for (const node of canvas.nodes.values()) { if (node.getData().type !== "file" || !node.file) continue; existingFileNodes.add(node.file); } for (const backlink of backlinks) { if (existingFileNodes.has(backlink)) continue; this.createFileNode(canvas, backlink); } } ) }); } createTextNode(canvas) { const size = canvas.config.defaultTextNodeDimensions; const pos = CanvasHelper.getCenterCoordinates(canvas, size); canvas.createTextNode({ pos, size }); } async createFileNode(canvas, file) { const size = canvas.config.defaultFileNodeDimensions; const pos = CanvasHelper.getCenterCoordinates(canvas, size); file != null ? file : file = await new FileSelectModal(this.plugin.app, void 0, true).awaitInput(); canvas.createFileNode({ pos, size, file }); } cloneNode(canvas, cloneDirection) { const sourceNode = canvas.selection.values().next().value; if (!sourceNode) return; const sourceNodeData = sourceNode.getData(); const nodeMargin = this.plugin.settings.getSetting("cloneNodeMargin"); const offset = { x: (sourceNode.width + nodeMargin) * (cloneDirection === "left" ? -1 : cloneDirection === "right" ? 1 : 0), y: (sourceNode.height + nodeMargin) * (cloneDirection === "up" ? -1 : cloneDirection === "down" ? 1 : 0) }; const clonedNode = canvas.createTextNode({ pos: { x: sourceNode.x + offset.x, y: sourceNode.y + offset.y }, size: { width: sourceNode.width, height: sourceNode.height } }); clonedNode.setData({ ...clonedNode.getData(), color: sourceNodeData.color, styleAttributes: sourceNodeData.styleAttributes }); if (this.plugin.settings.getSetting("zoomToClonedNode")) canvas.zoomToBbox(clonedNode.getBBox()); } expandNode(canvas, expandDirection) { const node = canvas.selection.values().next().value; if (!node) return; const expandNodeStepSize = this.plugin.settings.getSetting("expandNodeStepSize"); const expand = { x: expandDirection === "left" ? -1 : expandDirection === "right" ? 1 : 0, y: expandDirection === "up" ? -1 : expandDirection === "down" ? 1 : 0 }; node.setData({ ...node.getData(), width: node.width + expand.x * expandNodeStepSize, height: node.height + expand.y * expandNodeStepSize }); } flipSelection(canvas, horizontally) { const selectionData = canvas.getSelectionData(); if (selectionData.nodes.length === 0) return; const nodeIds = /* @__PURE__ */ new Set(); for (const nodeData of selectionData.nodes) { nodeIds.add(nodeData.id); const node = canvas.nodes.get(nodeData.id); if (!node) continue; const newX = horizontally ? 2 * selectionData.center.x - nodeData.x - nodeData.width : nodeData.x; const newY = horizontally ? nodeData.y : 2 * selectionData.center.y - nodeData.y - nodeData.height; node.setData({ ...nodeData, x: newX, y: newY }); } for (const edge of canvas.edges.values()) { const edgeData = edge.getData(); let newFromSide = edgeData.fromSide; if (nodeIds.has(edgeData.fromNode) && BBoxHelper.isHorizontal(edgeData.fromSide) === horizontally) newFromSide = BBoxHelper.getOppositeSide(edgeData.fromSide); let newToSide = edgeData.toSide; if (nodeIds.has(edgeData.toNode) && BBoxHelper.isHorizontal(edgeData.toSide) === horizontally) newToSide = BBoxHelper.getOppositeSide(edgeData.toSide); edge.setData({ ...edgeData, fromSide: newFromSide, toSide: newToSide }); } canvas.pushHistory(canvas.getData()); } navigate(canvas, direction) { const node = this.getNextNode(canvas, direction); if (!node) return; canvas.updateSelection(() => { canvas.selection = /* @__PURE__ */ new Set([node]); }); } getNextNode(canvas, direction) { var _a; const selectedNodeData = (_a = canvas.getSelectionData().nodes) == null ? void 0 : _a.first(); if (!selectedNodeData) return; const selectedNodeBBox = { minX: selectedNodeData.x, minY: selectedNodeData.y, maxX: selectedNodeData.x + selectedNodeData.width, maxY: selectedNodeData.y + selectedNodeData.height }; const possibleTargetNodes = Array.from(canvas.nodes.values()).filter((node) => { const nodeData = node.getData(); return nodeData.id !== selectedNodeData.id && (nodeData.type === "text" || nodeData.type === "file"); }); const closestNode = possibleTargetNodes.reduce((closestNode2, node) => { const nodeBBox = node.getBBox(); const isInVerticalRange = selectedNodeBBox.minY <= nodeBBox.maxY && selectedNodeBBox.maxY >= nodeBBox.minY; const isInHorizontalRange = selectedNodeBBox.minX <= nodeBBox.maxX && selectedNodeBBox.maxX >= nodeBBox.minX; if (["up", "down"].includes(direction) && !isInHorizontalRange) return closestNode2; if (["left", "right"].includes(direction) && !isInVerticalRange) return closestNode2; let distance = -1; switch (direction) { case "up": distance = selectedNodeBBox.minY - nodeBBox.maxY; break; case "down": distance = nodeBBox.minY - selectedNodeBBox.maxY; break; case "left": distance = selectedNodeBBox.minX - nodeBBox.maxX; break; case "right": distance = nodeBBox.minX - selectedNodeBBox.maxX; break; } if (distance < 0) return closestNode2; if (!closestNode2) return { node, distance }; if (distance < closestNode2.distance) return { node, distance }; if (distance === closestNode2.distance) { const selectedNodeCenter = { x: selectedNodeData.x + selectedNodeData.width / 2, y: selectedNodeData.y + selectedNodeData.height / 2 }; const closestNodeCenter = { x: closestNode2.node.x + closestNode2.node.width / 2, y: closestNode2.node.y + closestNode2.node.height / 2 }; const nodeCenter = { x: node.x + node.width / 2, y: node.y + node.height / 2 }; const closestNodeDistance = Math.sqrt( Math.pow(selectedNodeCenter.x - closestNodeCenter.x, 2) + Math.pow(selectedNodeCenter.y - closestNodeCenter.y, 2) ); const nodeDistance = Math.sqrt( Math.pow(selectedNodeCenter.x - nodeCenter.x, 2) + Math.pow(selectedNodeCenter.y - nodeCenter.y, 2) ); if (nodeDistance < closestNodeDistance) return { node, distance }; } return closestNode2; }, null); return closestNode == null ? void 0 : closestNode.node; } }; // src/canvas-extensions/auto-resize-node-canvas-extension.ts var AutoResizeNodeCanvasExtension = class extends CanvasExtension { isEnabled() { return "autoResizeNodeFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-created", (canvas, node) => this.onNodeCreated(canvas, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenuCreated(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-editing-state-changed", (canvas, node, editing) => this.onNodeEditingStateChanged(canvas, node, editing) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-text-content-changed", (canvas, node, viewUpdate) => this.onNodeTextContentChanged(canvas, node, viewUpdate.view.dom) )); } isValidNodeType(nodeData) { return nodeData.type === "text" || nodeData.type === "file" && nodeData.file.endsWith(".md"); } onNodeCreated(_canvas, node) { const autoResizeNodeEnabledByDefault = this.plugin.settings.getSetting("autoResizeNodeEnabledByDefault"); if (!autoResizeNodeEnabledByDefault) return; const nodeData = node.getData(); if (nodeData.type !== "text" && nodeData.type !== "file") return; node.setData({ ...node.getData(), dynamicHeight: true }); } onPopupMenuCreated(canvas) { if (canvas.readonly) return; const selectedNodes = canvas.getSelectionData().nodes.filter((nodeData) => this.isValidNodeType(nodeData)).map((nodeData) => canvas.nodes.get(nodeData.id)).filter((node) => node !== void 0); if (selectedNodes.length === 0) return; const autoResizeHeightEnabled = selectedNodes.some((node) => node.getData().dynamicHeight); CanvasHelper.addPopupMenuOption( canvas, CanvasHelper.createPopupMenuOption({ id: "auto-resize-height", label: autoResizeHeightEnabled ? "Disable auto-resize" : "Enable auto-resize", icon: autoResizeHeightEnabled ? "scan-text" : "lock", callback: () => this.toggleAutoResizeHeightEnabled(canvas, selectedNodes, autoResizeHeightEnabled) }) ); } toggleAutoResizeHeightEnabled(canvas, nodes, autoResizeHeight) { nodes.forEach((node) => node.setData({ ...node.getData(), dynamicHeight: !autoResizeHeight })); this.onPopupMenuCreated(canvas); } canBeResized(node) { const nodeData = node.getData(); return nodeData.dynamicHeight; } async onNodeEditingStateChanged(_canvas, node, editing) { if (!this.isValidNodeType(node.getData())) return; if (!this.canBeResized(node)) return; await sleep(10); if (editing) { this.onNodeTextContentChanged(_canvas, node, node.child.editMode.cm.dom); return; } const renderedMarkdownContainer = node.nodeEl.querySelector(".markdown-preview-view.markdown-rendered"); if (!renderedMarkdownContainer) return; renderedMarkdownContainer.style.height = "min-content"; const newHeight = renderedMarkdownContainer.clientHeight; renderedMarkdownContainer.style.removeProperty("height"); this.setNodeHeight(node, newHeight); } async onNodeTextContentChanged(_canvas, node, dom) { if (!this.isValidNodeType(node.getData())) return; if (!this.canBeResized(node)) return; const cmScroller = dom.querySelector(".cm-scroller"); if (!cmScroller) return; cmScroller.style.height = "min-content"; const newHeight = cmScroller.scrollHeight; cmScroller.style.removeProperty("height"); this.setNodeHeight(node, newHeight); } setNodeHeight(node, height) { if (height === 0) return; const maxHeight = this.plugin.settings.getSetting("autoResizeNodeMaxHeight"); if (maxHeight != -1 && height > maxHeight) height = maxHeight; const nodeData = node.getData(); height = Math.max(height, node.canvas.config.minContainerDimension); if (this.plugin.settings.getSetting("autoResizeNodeSnapToGrid")) height = Math.ceil(height / CanvasHelper.GRID_SIZE) * CanvasHelper.GRID_SIZE; node.setData({ ...nodeData, height }); } }; // src/canvas-extensions/portals-canvas-extension.ts var import_obsidian15 = require("obsidian"); var PORTAL_ID_DELIMITER = "||"; var PORTAL_ID_PREFIX = `acportal${PORTAL_ID_DELIMITER}`; var PORTAL_PADDING = 50; var MIN_OPEN_PORTAL_SIZE = { width: 200, height: 200 }; var PortalsCanvasExtension = class _PortalsCanvasExtension extends CanvasExtension { isEnabled() { return "portalsFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.vault.on("modify", (file) => { for (const canvas of this.plugin.getCanvases()) this.onFileModified(canvas, file); })); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenu(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-removed", (canvas, node) => this.onNodeRemoved(canvas, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-moved", (canvas, node, _keyboard) => this.onNodeMoved(canvas, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-resized", (canvas, node) => this.onNodeResized(canvas, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:dragging-state-changed", (canvas, startedDragging) => this.onDraggingStateChanged(canvas, startedDragging) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:containing-nodes-requested", (canvas, bbox, nodes) => this.onContainingNodesRequested(canvas, bbox, nodes) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-connection-try-dragging:before", (canvas, edge, event, cancelRef) => this.onEdgeConnectionTryDraggingBefore(canvas, edge, event, cancelRef) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-connection-dragging:after", (canvas, edge, event, newEdge, side, previousEnds) => this.onEdgeConnectionDraggingAfter(canvas, edge, event, newEdge, side, previousEnds) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:selection-changed", (canvas, oldSelection, updateSelection) => this.onSelectionChanged(canvas, oldSelection, updateSelection) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:data-requested", (canvas, data) => this.onGetData(canvas, data) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:data-loaded:before", (canvas, data, setData) => { this.onSetData(canvas, data).then((newData) => { if (newData.nodes.length === data.nodes.length && newData.edges.length === data.edges.length) return; setData(newData); }); } )); } onFileModified(canvas, file) { const isAffected = Object.values(canvas.nodes).filter( (nodeData) => nodeData.getData().type === "file" && nodeData.currentPortalFile === file.path ).length > 0; if (!isAffected) return; canvas.setData(canvas.getData()); canvas.history.current--; canvas.history.data.pop(); } onContainingNodesRequested(_canvas, _bbox, nodes) { const filteredNodes = nodes.filter((node) => !_PortalsCanvasExtension.isPortalElement(node.id)); nodes.splice(0, nodes.length, ...filteredNodes); } onSelectionChanged(canvas, _oldSelection, updateSelection) { updateSelection(() => { const updatedSelection = Array.from(canvas.selection).filter((canvasElement) => !_PortalsCanvasExtension.isPortalElement(canvasElement.id)); canvas.selection = new Set(updatedSelection); }); } onDraggingStateChanged(canvas, startedDragging) { if (!startedDragging) return; if (!canvas.getSelectionData().nodes.some((node) => node.type === "file" && node.portal)) return; const objectSnappingEnabled = canvas.options.snapToObjects; if (!objectSnappingEnabled) return; canvas.toggleObjectSnapping(false); const dragEndEventRef = this.plugin.app.workspace.on( "advanced-canvas:dragging-state-changed", (canvas2, startedDragging2) => { if (startedDragging2) return; canvas2.toggleObjectSnapping(objectSnappingEnabled); this.plugin.app.workspace.offref(dragEndEventRef); } ); this.plugin.registerEvent(dragEndEventRef); } onNodeMoved(canvas, portalNode) { const portalNodeData = portalNode.getData(); if (portalNodeData.type !== "file" || !portalNodeData.isPortalLoaded) return; const nestedNodes = this.getContainingNodes(canvas, portalNode); const containingNodesBBox = CanvasHelper.getBBox(nestedNodes); const portalOffset = { x: portalNodeData.x - containingNodesBBox.minX + PORTAL_PADDING, y: portalNodeData.y - containingNodesBBox.minY + PORTAL_PADDING }; for (const nestedNode of nestedNodes) { const nestedNodeData = nestedNode.getData(); nestedNode.setData({ ...nestedNodeData, x: nestedNodeData.x + portalOffset.x, y: nestedNodeData.y + portalOffset.y }); } } onNodeResized(_canvas, portalNode) { const portalNodeData = portalNode.getData(); if (portalNodeData.type !== "file" || !portalNodeData.isPortalLoaded) return; portalNode.setData({ ...portalNodeData, x: portalNode.prevX ? portalNode.prevX : portalNodeData.x, y: portalNode.prevY ? portalNode.prevY : portalNodeData.y, width: portalNode.prevWidth ? portalNode.prevWidth : portalNodeData.width, height: portalNode.prevHeight ? portalNode.prevHeight : portalNodeData.height }); } onNodeRemoved(canvas, portalNode) { const portalNodeData = portalNode.getData(); if (portalNodeData.type !== "file" || !portalNodeData.portal) return; for (const node of this.getContainingNodes(canvas, portalNode, false)) canvas.removeNode(node); for (const edge of this.getContainingEdges(canvas, portalNode, false)) canvas.removeEdge(edge); } onEdgeConnectionTryDraggingBefore(_canvas, edge, _event, cancelRef) { if (!_PortalsCanvasExtension.isPortalElement(edge.id)) return; cancelRef.value = true; new import_obsidian15.Notice("Updating edges from portals is not supported yet."); } onEdgeConnectionDraggingAfter(canvas, edge, _event, _newEdge, _side, _previousEnds) { if (_PortalsCanvasExtension.isPortalElement(edge.id)) return; if (!_PortalsCanvasExtension.isPortalElement(edge.from.node.id) || !_PortalsCanvasExtension.isPortalElement(edge.to.node.id)) return; canvas.removeEdge(edge); new import_obsidian15.Notice("Creating edges with both ends in portals are not supported yet."); } onPopupMenu(canvas) { if (canvas.readonly) return; const selectedFileNodes = canvas.getSelectionData().nodes.map((nodeData) => { var _a; const node = canvas.nodes.get(nodeData.id); if (!node) return null; if (nodeData.type !== "file") return null; if (((_a = node.file) == null ? void 0 : _a.extension) === "canvas") return node; if (nodeData.portal) this.setPortalOpen(canvas, node, false); return null; }).filter((node) => node !== null); if (selectedFileNodes.length !== 1) return; const portalNode = selectedFileNodes.first(); const portalNodeData = portalNode.getData(); if (portalNodeData.portal && portalNodeData.file !== portalNode.currentPortalFile) this.setPortalOpen(canvas, portalNode, true); CanvasHelper.addPopupMenuOption( canvas, CanvasHelper.createPopupMenuOption({ id: "toggle-portal", label: portalNodeData.portal ? "Close portal" : "Open portal", icon: portalNodeData.portal ? "door-open" : "door-closed", callback: () => { this.setPortalOpen(canvas, portalNode, !portalNodeData.portal); this.onPopupMenu(canvas); } }) ); } setPortalOpen(canvas, portalNode, open) { const portalNodeData = portalNode.getData(); portalNode.setData({ ...portalNodeData, portal: open }); portalNode.currentPortalFile = open ? portalNodeData.file : void 0; canvas.setData(canvas.getData()); } // Remove all edges and nodes from portals onGetData(_canvas, data) { data.nodes = data.nodes.filter((nodeData) => !_PortalsCanvasExtension.isPortalElement(nodeData.id)); for (const nodeData of data.nodes) delete nodeData.isPortalLoaded; const portalsIdMap = new Map( data.nodes.filter((nodeData) => nodeData.portal).map((nodeData) => [nodeData.id, nodeData]) ); data.edges = data.edges.filter((edgeData) => { var _a; if (_PortalsCanvasExtension.isPortalElement(edgeData.id)) return false; const isFromNodeFromPortal = _PortalsCanvasExtension.getNestedIds(edgeData.fromNode).length > 1; const isToNodeFromPortal = _PortalsCanvasExtension.getNestedIds(edgeData.toNode).length > 1; if (!isFromNodeFromPortal && !isToNodeFromPortal) return true; if (isFromNodeFromPortal && isToNodeFromPortal) return false; const targetPortalId = this.getParentPortalId(isFromNodeFromPortal ? edgeData.fromNode : edgeData.toNode); const targetPortalData = portalsIdMap.get(targetPortalId); if (!targetPortalData) return false; (_a = targetPortalData.interdimensionalEdges) != null ? _a : targetPortalData.interdimensionalEdges = []; targetPortalData.interdimensionalEdges.push(edgeData); return false; }); } // Add all edges and nodes from portals async onSetData(canvas, dataRef) { if (!(dataRef == null ? void 0 : dataRef.nodes)) return dataRef; const data = JSON.parse(JSON.stringify(dataRef)); const addedData = await Promise.all(data.nodes.map((nodeData) => this.tryOpenPortal(canvas, nodeData))); for (const newData of addedData) { data.nodes.push(...newData.nodes); data.edges.push(...newData.edges); } for (const nodeData of data.nodes) { if (nodeData.type !== "file" || !nodeData.isPortalLoaded) continue; const interdimensionalEdges = nodeData.interdimensionalEdges; if (!interdimensionalEdges) continue; for (const edge of interdimensionalEdges) data.edges.push(edge); delete nodeData.interdimensionalEdges; } return data; } async tryOpenPortal(canvas, portalNodeData, nestedPortalFiles = /* @__PURE__ */ new Set()) { const addedData = { nodes: [], edges: [] }; if (portalNodeData.type !== "file" || !portalNodeData.portal) return addedData; if (portalNodeData.file === canvas.view.file.path) return addedData; if (nestedPortalFiles.has(portalNodeData.file)) return addedData; nestedPortalFiles.add(portalNodeData.file); const portalFile = this.plugin.app.vault.getAbstractFileByPath(portalNodeData.file); if (!(portalFile instanceof import_obsidian15.TFile) || portalFile.extension !== "canvas") return addedData; const portalFileDataString = await this.plugin.app.vault.cachedRead(portalFile); if (portalFileDataString === "") return addedData; const portalFileData = JSON.parse(portalFileDataString); if (!portalFileData) return addedData; portalNodeData.isPortalLoaded = true; const sourceMinCoordinates = CanvasHelper.getBBox(portalFileData.nodes); const portalOffset = { x: portalNodeData.x - sourceMinCoordinates.minX + PORTAL_PADDING, y: portalNodeData.y - sourceMinCoordinates.minY + PORTAL_PADDING }; for (const nodeDataFromPortal of portalFileData.nodes) { let newNodeId = `${portalNodeData.id}${PORTAL_ID_DELIMITER}${nodeDataFromPortal.id}`; if (!newNodeId.startsWith(PORTAL_ID_PREFIX)) newNodeId = PORTAL_ID_PREFIX + newNodeId; const addedNode = { ...nodeDataFromPortal, id: newNodeId, x: nodeDataFromPortal.x + portalOffset.x, y: nodeDataFromPortal.y + portalOffset.y }; addedData.nodes.push(addedNode); const nestedNodes = await this.tryOpenPortal(canvas, addedNode, nestedPortalFiles); addedData.nodes.push(...nestedNodes.nodes); addedData.edges.push(...nestedNodes.edges); } for (const edgeDataFromPortal of portalFileData.edges) { let newEdgeId = `${portalNodeData.id}${PORTAL_ID_DELIMITER}${edgeDataFromPortal.id}`; if (!newEdgeId.startsWith(PORTAL_ID_PREFIX)) newEdgeId = PORTAL_ID_PREFIX + newEdgeId; const fromNodeId = `${portalNodeData.id}${PORTAL_ID_DELIMITER}${edgeDataFromPortal.fromNode}`; const toNodeId = `${portalNodeData.id}${PORTAL_ID_DELIMITER}${edgeDataFromPortal.toNode}`; addedData.edges.push({ ...edgeDataFromPortal, id: newEdgeId, fromNode: fromNodeId, toNode: toNodeId }); } const targetSize = this.getPortalSize(CanvasHelper.getBBox(addedData.nodes)); portalNodeData.width = targetSize.width; portalNodeData.height = targetSize.height; return addedData; } // Helper functions getPortalSize(sourceBBox) { const sourceSize = { width: sourceBBox.maxX - sourceBBox.minX, height: sourceBBox.maxY - sourceBBox.minY }; const targetSize = { width: Math.max(sourceSize.width + PORTAL_PADDING * 2, MIN_OPEN_PORTAL_SIZE.width), height: Math.max(sourceSize.height + PORTAL_PADDING * 2, MIN_OPEN_PORTAL_SIZE.height) }; if (!Number.isFinite(targetSize.width)) targetSize.width = MIN_OPEN_PORTAL_SIZE.width; if (!Number.isFinite(targetSize.height)) targetSize.height = MIN_OPEN_PORTAL_SIZE.height; return targetSize; } getContainingNodes(canvas, portalNode, directChildren = true) { return Array.from(canvas.nodes.values()).filter((node) => this.isChildOfPortal(portalNode.getData(), node.getData(), directChildren)); } getContainingEdges(canvas, portalNode, directChildren = true) { return Array.from(canvas.edges.values()).filter((edge) => this.isChildOfPortal(portalNode.getData(), edge.getData(), directChildren)); } getParentPortalId(elementId) { const nestedIds = _PortalsCanvasExtension.getNestedIds(elementId); if (nestedIds.length < 2) return void 0; return nestedIds.slice(0, -1).join(PORTAL_ID_DELIMITER); } static getNestedIds(id) { if (!this.isPortalElement(id)) return [id]; const trimmedId = id.substring(PORTAL_ID_PREFIX.length); return trimmedId.split(PORTAL_ID_DELIMITER); } static isPortalElement(id) { return id.startsWith(PORTAL_ID_PREFIX); } isChildOfPortal(portal, canvasElement, directChild = true) { const nestedIds = _PortalsCanvasExtension.getNestedIds(canvasElement.id); if (nestedIds.length < 2) return false; return canvasElement.id !== portal.id && // Not the portal itself nestedIds.contains(portal.id) && // Is a child of the portal (!directChild || nestedIds[nestedIds.length - 2] === portal.id); } }; // src/canvas-extensions/frontmatter-control-button-canvas-extension.ts var import_obsidian16 = require("obsidian"); var FrontmatterControlButtonCanvasExtension = class extends CanvasExtension { isEnabled() { return "canvasMetadataCompatibilityEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.addQuickSettings(canvas) )); } addQuickSettings(canvas) { var _a; if (!canvas) return; const settingsContainer = (_a = canvas.quickSettingsButton) == null ? void 0 : _a.parentElement; if (!settingsContainer) return; CanvasHelper.addControlMenuButton( settingsContainer, CanvasHelper.createControlMenuButton({ id: "properties-button", icon: "info", label: "Properties", callback: () => { var _a2; const propertiesPlugin = this.plugin.app.internalPlugins.plugins["properties"]; if (!(propertiesPlugin == null ? void 0 : propertiesPlugin._loaded)) { new import_obsidian16.Notice(`Core plugin "Properties view" was not found or isn't enabled. Enable it and restart Obsidian.`); return; } let propertiesLeaf = (_a2 = this.plugin.app.workspace.getLeavesOfType("file-properties").first()) != null ? _a2 : null; if (!propertiesLeaf) { propertiesLeaf = this.plugin.app.workspace.getRightLeaf(false); propertiesLeaf == null ? void 0 : propertiesLeaf.setViewState({ type: "file-properties" }); } if (propertiesLeaf) this.plugin.app.workspace.revealLeaf(propertiesLeaf); } }) ); } }; // src/canvas-extensions/better-default-settings-canvas-extension.ts var BetterDefaultSettingsCanvasExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.modifyCanvasSettings(this.plugin.getCurrentCanvas()); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:settings-changed", () => this.modifyCanvasSettings(this.plugin.getCurrentCanvas()) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.modifyCanvasSettings(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:double-click", (canvas, event, preventDefault) => this.onDoubleClick(canvas, event, preventDefault) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-created", (canvas, node) => { this.enforceNodeGridAlignment(canvas, node); this.applyDefaultNodeStyles(canvas, node); } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-created", (canvas, edge) => this.applyDefaultEdgeStyles(canvas, edge) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-resized", (canvas, node) => this.enforceMaxNodeWidth(canvas, node) )); } modifyCanvasSettings(canvas) { if (!canvas) return; const defaultTextNodeDimensionsArray = this.plugin.settings.getSetting("defaultTextNodeDimensions"); canvas.config.defaultTextNodeDimensions = { width: defaultTextNodeDimensionsArray[0], height: defaultTextNodeDimensionsArray[1] }; const defaultFileNodeDimensionsArray = this.plugin.settings.getSetting("defaultFileNodeDimensions"); canvas.config.defaultFileNodeDimensions = { width: defaultFileNodeDimensionsArray[0], height: defaultFileNodeDimensionsArray[1] }; canvas.config.minContainerDimension = this.plugin.settings.getSetting("minNodeSize"); } async onDoubleClick(canvas, event, preventDefault) { if (event.defaultPrevented || event.target !== canvas.wrapperEl || canvas.isDragging || canvas.readonly) return; preventDefault.value = true; const pos = canvas.posFromEvt(event); switch (this.plugin.settings.getSetting("nodeTypeOnDoubleClick")) { case "file": { const file = await new FileSelectModal(this.plugin.app, void 0, true).awaitInput(); canvas.createFileNode({ pos, position: "center", file }); break; } default: canvas.createTextNode({ pos, position: "center" }); break; } } enforceNodeGridAlignment(_canvas, node) { if (!this.plugin.settings.getSetting("alignNewNodesToGrid")) return; const nodeData = node.getData(); node.setData({ ...nodeData, x: CanvasHelper.alignToGrid(nodeData.x), y: CanvasHelper.alignToGrid(nodeData.y) }); } applyDefaultNodeStyles(_canvas, node) { const nodeData = node.getData(); if (nodeData.type !== "text") return; let color = this.plugin.settings.getSetting("defaultTextNodeColor").toString(); if (color === "0") color = void 0; node.setData({ ...nodeData, color, styleAttributes: { ...nodeData.styleAttributes, ...this.plugin.settings.getSetting("defaultTextNodeStyleAttributes") } }); } async applyDefaultEdgeStyles(canvas, edge) { var _a; const edgeData = edge.getData(); let color = this.plugin.settings.getSetting("defaultEdgeColor").toString(); if (this.plugin.settings.getSetting("inheritEdgeColorFromNode")) color = (_a = edge.from.node.getData().color) != null ? _a : color; if (color === "0") color = void 0; edge.setData({ ...edgeData, color, styleAttributes: { ...edgeData.styleAttributes, ...this.plugin.settings.getSetting("defaultEdgeStyleAttributes") } }); if (canvas.canvasEl.hasClass("is-connecting")) { await new Promise((resolve) => { new MutationObserver(() => { if (!canvas.canvasEl.hasClass("is-connecting")) resolve(); }).observe(canvas.canvasEl, { attributes: true, attributeFilter: ["class"] }); }); } const lineDirection = this.plugin.settings.getSetting("defaultEdgeLineDirection"); edge.setData({ ...edge.getData(), fromEnd: lineDirection === "bidirectional" ? "arrow" : "none", toEnd: lineDirection === "nondirectional" ? "none" : "arrow" }); } enforceMaxNodeWidth(_canvas, node) { const maxNodeWidth = this.plugin.settings.getSetting("maxNodeWidth"); if (maxNodeWidth <= 0) return; const nodeData = node.getData(); if (nodeData.type !== "text" && nodeData.type !== "file" || nodeData.portal) return; if (nodeData.width <= maxNodeWidth) return; node.setData({ ...nodeData, x: node.prevX !== void 0 ? node.prevX : nodeData.x, // Reset the position to the previous value width: maxNodeWidth }); } }; // src/canvas-extensions/color-palette-canvas-extension.ts var DEFAULT_COLORS_COUNT = 6; var CUSTOM_COLORS_MOD_STYLES_ID = "mod-custom-colors"; var ColorPaletteCanvasExtension = class extends CanvasExtension { constructor() { super(...arguments); this.observer = null; } isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "window-open", (_win, _window) => this.updateCustomColorModStyleClasses() )); this.plugin.registerEvent(this.plugin.app.workspace.on( "css-change", () => this.updateCustomColorModStyleClasses() )); this.updateCustomColorModStyleClasses(); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.patchColorSelection(canvas) )); this.plugin.register(() => { var _a; return (_a = this.observer) == null ? void 0 : _a.disconnect(); }); } updateCustomColorModStyleClasses() { var _a; const customCss = this.getCustomColors().map((colorId) => ` .mod-canvas-color-${colorId} { --canvas-color: var(--canvas-color-${colorId}); } `).join(""); for (const win of this.plugin.windowsManager.windows) { const doc = win.document; (_a = doc.getElementById(CUSTOM_COLORS_MOD_STYLES_ID)) == null ? void 0 : _a.remove(); const customColorModStyle = doc.createElement("style"); customColorModStyle.id = CUSTOM_COLORS_MOD_STYLES_ID; doc.head.appendChild(customColorModStyle); customColorModStyle.textContent = customCss; } } patchColorSelection(canvas) { if (this.observer) this.observer.disconnect(); this.observer = new MutationObserver((mutations) => { const colorMenuOpened = mutations.some( (mutation) => Object.values(mutation.addedNodes).some( (node) => node instanceof HTMLElement && node.classList.contains("canvas-submenu") && Object.values(node.childNodes).some( (node2) => node2 instanceof HTMLElement && node2.classList.contains("canvas-color-picker-item") ) ) ); if (!colorMenuOpened) return; const submenu = canvas.menu.menuEl.querySelector(".canvas-submenu"); if (!submenu) return; const currentNodeColor = canvas.getSelectionData().nodes.map((node) => node.color).last(); for (const colorId of this.getCustomColors()) { const customColorMenuItem = this.createColorMenuItem(canvas, colorId); if (currentNodeColor === colorId) customColorMenuItem.classList.add("is-active"); submenu.insertBefore(customColorMenuItem, submenu.lastChild); } }); this.observer.observe(canvas.menu.menuEl, { childList: true }); } createColorMenuItem(canvas, colorId) { const menuItem = document.createElement("div"); menuItem.classList.add("canvas-color-picker-item"); menuItem.classList.add(`mod-canvas-color-${colorId}`); menuItem.addEventListener("click", () => { menuItem.classList.add("is-active"); for (const item of canvas.selection) { item.setColor(colorId); } canvas.requestSave(); }); return menuItem; } getCustomColors() { const colors = []; const style = getComputedStyle(document.body); let colorIndex = DEFAULT_COLORS_COUNT + 1; while (style.getPropertyValue(`--canvas-color-${colorIndex}`)) { colors.push(colorIndex.toString()); colorIndex++; } return colors; } }; // src/canvas-extensions/collapsible-groups-canvas-extension.ts var import_obsidian17 = require("obsidian"); var CollapsibleGroupsCanvasExtension = class extends CanvasExtension { isEnabled() { return "collapsibleGroupsFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-changed", (canvas, node) => this.onNodeChanged(canvas, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-bbox-requested", (canvas, node, bbox) => this.onNodeBBoxRequested(canvas, node, bbox) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:copy", (canvas, selectionData) => this.onCopy(canvas, selectionData) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:data-requested", (_canvas, data) => this.expandNodes(data) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:data-loaded:before", (_canvas, data, _setData) => this.collapseNodes(data) )); this.plugin.addCommand({ id: "toggle-collapse-group", name: "Toggle collapse group", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size === 1 && canvas.selection.values().next().value.getData().type === "group", (canvas) => this.toggleCollapseGroup(canvas, canvas.selection.values().next().value) ) }); } toggleCollapseGroup(canvas, node) { const data = node.getData(); this.setCollapsed(node.canvas, node, data.collapsed ? void 0 : true); canvas.markMoved(node); } onNodeChanged(canvas, groupNode) { var _a, _b; const groupNodeData = groupNode.getData(); if (groupNodeData.type !== "group") return; (_a = groupNode.collapseEl) == null ? void 0 : _a.remove(); const collapseEl = document.createElement("span"); collapseEl.className = "collapse-button"; (0, import_obsidian17.setIcon)(collapseEl, groupNodeData.collapsed ? "plus-circle" : "minus-circle"); collapseEl.onclick = () => this.toggleCollapseGroup(canvas, groupNode); groupNode.collapseEl = collapseEl; (_b = groupNode.labelEl) == null ? void 0 : _b.insertAdjacentElement("afterend", collapseEl); } onCopy(_canvas, selectionData) { for (const collapsedGroupData of selectionData.nodes) { if (collapsedGroupData.type !== "group" || !collapsedGroupData.collapsed || !collapsedGroupData.collapsedData) continue; selectionData.nodes.push(...collapsedGroupData.collapsedData.nodes.map((nodeData) => ({ ...nodeData, // Restore the relative position of the node to the group x: nodeData.x + collapsedGroupData.x, y: nodeData.y + collapsedGroupData.y }))); selectionData.edges.push(...collapsedGroupData.collapsedData.edges); } } setCollapsed(canvas, groupNode, collapsed) { groupNode.setData({ ...groupNode.getData(), collapsed }); canvas.setData(canvas.getData()); canvas.history.current--; canvas.pushHistory(canvas.getData()); canvas.requestSave(); } onNodeBBoxRequested(canvas, node, bbox) { var _a, _b; const nodeData = node.getData(); if (nodeData.type !== "group" || !nodeData.collapsed) return; const collapseElBBox = (_a = node.collapseEl) == null ? void 0 : _a.getBoundingClientRect(); if (!collapseElBBox) return; const labelElBBox = (_b = node.labelEl) == null ? void 0 : _b.getBoundingClientRect(); if (!labelElBBox) return; const minPos = canvas.posFromClient({ x: collapseElBBox.left, y: collapseElBBox.top }); const maxPos = canvas.posFromClient({ x: labelElBBox.right, y: collapseElBBox.bottom }); bbox.minX = minPos.x; bbox.minY = minPos.y; bbox.maxX = maxPos.x; bbox.maxY = maxPos.y; } expandNodes(data) { var _a; if (!data) return; data.nodes = (_a = data.nodes) == null ? void 0 : _a.flatMap((groupNodeData) => { const collapsedData = groupNodeData.collapsedData; if (collapsedData === void 0) return [groupNodeData]; delete groupNodeData.collapsedData; data.edges.push(...collapsedData.edges); return [groupNodeData, ...collapsedData.nodes.map((nodeData) => ({ ...nodeData, // Restore the relative position of the node to the group x: nodeData.x + groupNodeData.x, y: nodeData.y + groupNodeData.y }))]; }); } collapseNodes(data) { var _a; (_a = data == null ? void 0 : data.nodes) == null ? void 0 : _a.forEach((groupNodeData) => { var _a2, _b, _c, _d; if (!groupNodeData.collapsed) return; const groupNodeBBox = CanvasHelper.getBBox([groupNodeData]); const containedNodesData = data.nodes.filter( (nodeData) => nodeData.id !== groupNodeData.id && BBoxHelper.insideBBox(CanvasHelper.getBBox([nodeData]), groupNodeBBox, false) ); const containedEdgesData = data.edges.filter((edgeData) => { return containedNodesData.some((nodeData) => nodeData.id === edgeData.fromNode) || containedNodesData.some((nodeData) => nodeData.id === edgeData.toNode); }); data.nodes = data.nodes.filter((nodeData) => !containedNodesData.includes(nodeData)); data.edges = data.edges.filter((edgeData) => !containedEdgesData.includes(edgeData)); const newContainedNodesData = containedNodesData.filter((nodeData) => { var _a3, _b2, _c2; return !((_c2 = (_b2 = (_a3 = groupNodeData.collapsedData) == null ? void 0 : _a3.nodes) == null ? void 0 : _b2.some((e) => e.id === nodeData.id)) != null ? _c2 : false); }); const newContainedEdgesData = containedEdgesData.filter((edgeData) => { var _a3, _b2, _c2; return !((_c2 = (_b2 = (_a3 = groupNodeData.collapsedData) == null ? void 0 : _a3.edges) == null ? void 0 : _b2.some((n) => n.id === edgeData.id)) != null ? _c2 : false); }); groupNodeData.collapsedData = { nodes: [ ...(_b = (_a2 = groupNodeData.collapsedData) == null ? void 0 : _a2.nodes) != null ? _b : [], ...newContainedNodesData.map((nodeData) => ({ ...nodeData, // Store the relative position of the node to the group x: nodeData.x - groupNodeData.x, y: nodeData.y - groupNodeData.y })) ], edges: [ ...(_d = (_c = groupNodeData.collapsedData) == null ? void 0 : _c.edges) != null ? _d : [], ...newContainedEdgesData ] }; }); } }; // src/canvas-extensions/focus-mode-canvas-extension.ts var CONTROL_MENU_FOCUS_TOGGLE_ID = "focus-mode-toggle"; var FocusModeCanvasExtension = class extends CanvasExtension { isEnabled() { return "focusModeFeatureEnabled"; } init() { this.plugin.addCommand({ id: "toggle-focus-mode", name: "Toggle Focus Mode", checkCallback: CanvasHelper.canvasCommand( this.plugin, (_canvas) => true, (canvas) => this.toggleFocusMode(canvas) ) }); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.addControlMenuToggle(canvas) )); } addControlMenuToggle(canvas) { var _a; const settingsContainer = (_a = canvas.quickSettingsButton) == null ? void 0 : _a.parentElement; if (!settingsContainer) return; const controlMenuFocusToggle = CanvasHelper.createControlMenuButton({ id: CONTROL_MENU_FOCUS_TOGGLE_ID, label: "Focus Mode", icon: "focus", callback: () => this.toggleFocusMode(canvas) }); CanvasHelper.addControlMenuButton(settingsContainer, controlMenuFocusToggle); } toggleFocusMode(canvas) { var _a, _b; const controlMenuFocusToggle = (_b = (_a = canvas.quickSettingsButton) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector(`#${CONTROL_MENU_FOCUS_TOGGLE_ID}`); if (!controlMenuFocusToggle) return; const newValue = controlMenuFocusToggle.dataset.toggled !== "true"; canvas.wrapperEl.dataset.focusModeEnabled = newValue.toString(); controlMenuFocusToggle.dataset.toggled = newValue.toString(); } }; // src/canvas-extensions/auto-file-node-edges-canvas-extension.ts var AUTO_EDGE_ID_PREFIX = "afe"; var AutoFileNodeEdgesCanvasExtension = class extends CanvasExtension { isEnabled() { return "autoFileNodeEdgesFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.metadataCache.on("changed", (file) => { for (const canvas of this.plugin.getCanvases()) this.onMetadataChanged(canvas, file); })); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-added", (canvas, node) => this.onNodeChanged(canvas, node) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-changed", (canvas, node) => this.onNodeChanged(canvas, node) )); } onMetadataChanged(canvas, file) { var _a; for (const node of canvas.nodes.values()) { if (node.getData().type !== "file" || ((_a = node.file) == null ? void 0 : _a.path) !== file.path) continue; this.updateFileNodeEdges(canvas, node); } } onNodeChanged(canvas, node) { if (node.getData().type !== "file") return; for (const node2 of canvas.nodes.values()) { if (node2.getData().type !== "file") continue; this.updateFileNodeEdges(canvas, node2); } } updateFileNodeEdges(canvas, node) { const edges = this.getFileNodeEdges(canvas, node); const newEdges = Array.from(edges.values()).filter((edge) => !canvas.edges.has(edge.id)); canvas.importData({ nodes: [], edges: newEdges }, false, false); for (const edge of canvas.edges.values()) { if (edge.id.startsWith(`${AUTO_EDGE_ID_PREFIX}${node.id}`) && !edges.has(edge.id)) canvas.removeEdge(edge); } } getFileNodeEdges(canvas, node) { var _a, _b; const canvasFile = canvas.view.file; if (!canvasFile || !node.file) return /* @__PURE__ */ new Map(); const fileMetadata = this.plugin.app.metadataCache.getFileCache(node.file); if (!fileMetadata) return /* @__PURE__ */ new Map(); const linkedFilesFrontmatterKey = this.plugin.settings.getSetting("autoFileNodeEdgesFrontmatterKey"); const fileLinksToBeLinkedTo = (_b = (_a = fileMetadata.frontmatterLinks) == null ? void 0 : _a.filter((link) => link.key.split(".")[0] === linkedFilesFrontmatterKey)) != null ? _b : []; const filepathsToBeLinkedTo = fileLinksToBeLinkedTo.map((link) => this.plugin.app.metadataCache.getFirstLinkpathDest(link.link, canvasFile.path)).map((file) => file == null ? void 0 : file.path).filter((path) => path !== null); const nodesToBeLinkedTo = Array.from(canvas.nodes.values()).filter((otherNode) => { var _a2; return otherNode.id !== node.id && filepathsToBeLinkedTo.includes((_a2 = otherNode.file) == null ? void 0 : _a2.path); }); const newEdges = /* @__PURE__ */ new Map(); for (const otherNode of nodesToBeLinkedTo) { const edgeId = `${AUTO_EDGE_ID_PREFIX}${node.id}${otherNode.id}`; const bestFromSide = CanvasHelper.getBestSideForFloatingEdge(BBoxHelper.getCenterOfBBoxSide(otherNode.getBBox(), "right"), node); const bestToSide = CanvasHelper.getBestSideForFloatingEdge(BBoxHelper.getCenterOfBBoxSide(node.getBBox(), "left"), otherNode); newEdges.set(edgeId, { id: edgeId, fromNode: node.id, fromSide: bestFromSide, fromFloating: true, toNode: otherNode.id, toSide: bestToSide, toFloating: true }); } return newEdges; } }; // src/canvas-extensions/flip-edge-canvas-extension.ts var FlipEdgeCanvasExtension = class extends CanvasExtension { isEnabled() { return "flipEdgeFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenuCreated(canvas) )); } onPopupMenuCreated(canvas) { var _a, _b; const popupMenuEl = (_a = canvas == null ? void 0 : canvas.menu) == null ? void 0 : _a.menuEl; if (!popupMenuEl) return; const POSSIBLE_ICONS = ["lucide-arrow-right", "lucide-move-horizontal", "line-horizontal"]; let edgeDirectionButton = null; for (const icon of POSSIBLE_ICONS) { edgeDirectionButton = (_b = popupMenuEl.querySelector(`button:not([id]) > .svg-icon.${icon}`)) == null ? void 0 : _b.parentElement; if (edgeDirectionButton) break; } if (!edgeDirectionButton) return; edgeDirectionButton.addEventListener("click", () => this.onEdgeDirectionDropdownCreated(canvas)); } onEdgeDirectionDropdownCreated(canvas) { const dropdownEl = document.body.querySelector("div.menu"); if (!dropdownEl) return; const separatorEl = CanvasHelper.createDropdownSeparatorElement(); dropdownEl.appendChild(separatorEl); const flipEdgeButton = CanvasHelper.createDropdownOptionElement({ icon: "flip-horizontal-2", label: "Flip Edge", callback: () => this.flipEdge(canvas) }); dropdownEl.appendChild(flipEdgeButton); } flipEdge(canvas) { const selectedEdges = [...canvas.selection].filter((item) => item.path !== void 0); if (selectedEdges.length === 0) return; for (const edge of selectedEdges) { edge.update({ ...edge.from, node: edge.to.node, side: edge.to.side }, { ...edge.to, node: edge.from.node, side: edge.from.side }); } canvas.pushHistory(canvas.getData()); } }; // src/canvas-extensions/edge-selection-canvas-extension.ts var DIRECTION_MENU_MAP = { connected: { id: "select-connected-edges", icon: "arrows-selected", label: "Select Connected Edges" }, outgoing: { id: "select-outgoing-edges", icon: "arrow-right-selected", label: "Select Outgoing Edges" }, incoming: { id: "select-incoming-edges", icon: "arrow-left-selected", label: "Select Incoming Edges" } }; var EdgeSelectionCanvasExtension = class extends CanvasExtension { isEnabled() { return "edgeSelectionEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:popup-menu-created", (canvas) => this.onPopupMenuCreated(canvas) )); } onPopupMenuCreated(canvas) { var _a; const popupMenuEl = (_a = canvas == null ? void 0 : canvas.menu) == null ? void 0 : _a.menuEl; if (!popupMenuEl) return; const selectionNodeData = canvas.getSelectionData().nodes; if (canvas.readonly || selectionNodeData.length === 0) return; const selectEdgeByDirection = this.plugin.settings.getSetting("selectEdgeByDirection"); const menuDirectionSet = /* @__PURE__ */ new Set(["connected"]); if (selectionNodeData.length === 1) { const node = canvas.nodes.get(selectionNodeData[0].id); if (!node) return; const edges = canvas.getEdgesForNode(node); if (edges.length === 0) return; if (selectEdgeByDirection) { edges.forEach((edge) => { if (edge.from.node === node) { menuDirectionSet.add("outgoing"); } else if (edge.to.node === node) { menuDirectionSet.add("incoming"); } }); } } else if (selectEdgeByDirection) { menuDirectionSet.add("outgoing"); menuDirectionSet.add("incoming"); } menuDirectionSet.forEach((direction) => { const config = DIRECTION_MENU_MAP[direction]; CanvasHelper.addPopupMenuOption(canvas, CanvasHelper.createPopupMenuOption({ ...config, callback: () => CanvasHelper.selectEdgesForNodes(canvas, direction) })); }); } }; // node_modules/html-to-image/es/util.js function resolveUrl(url, baseUrl) { if (url.match(/^[a-z]+:\/\//i)) { return url; } if (url.match(/^\/\//)) { return window.location.protocol + url; } if (url.match(/^[a-z]+:/i)) { return url; } const doc = document.implementation.createHTMLDocument(); const base = doc.createElement("base"); const a = doc.createElement("a"); doc.head.appendChild(base); doc.body.appendChild(a); if (baseUrl) { base.href = baseUrl; } a.href = url; return a.href; } var uuid = /* @__PURE__ */ (() => { let counter = 0; const random = () => ( // eslint-disable-next-line no-bitwise `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4) ); return () => { counter += 1; return `u${random()}${counter}`; }; })(); function toArray(arrayLike) { const arr = []; for (let i = 0, l = arrayLike.length; i < l; i++) { arr.push(arrayLike[i]); } return arr; } function px(node, styleProperty) { const win = node.ownerDocument.defaultView || window; const val = win.getComputedStyle(node).getPropertyValue(styleProperty); return val ? parseFloat(val.replace("px", "")) : 0; } function getNodeWidth(node) { const leftBorder = px(node, "border-left-width"); const rightBorder = px(node, "border-right-width"); return node.clientWidth + leftBorder + rightBorder; } function getNodeHeight(node) { const topBorder = px(node, "border-top-width"); const bottomBorder = px(node, "border-bottom-width"); return node.clientHeight + topBorder + bottomBorder; } function getImageSize(targetNode, options = {}) { const width = options.width || getNodeWidth(targetNode); const height = options.height || getNodeHeight(targetNode); return { width, height }; } function getPixelRatio() { let ratio; let FINAL_PROCESS; try { FINAL_PROCESS = process; } catch (e) { } const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null; if (val) { ratio = parseInt(val, 10); if (Number.isNaN(ratio)) { ratio = 1; } } return ratio || window.devicePixelRatio || 1; } var canvasDimensionLimit = 16384; function checkCanvasDimensions(canvas) { if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) { if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) { if (canvas.width > canvas.height) { canvas.height *= canvasDimensionLimit / canvas.width; canvas.width = canvasDimensionLimit; } else { canvas.width *= canvasDimensionLimit / canvas.height; canvas.height = canvasDimensionLimit; } } else if (canvas.width > canvasDimensionLimit) { canvas.height *= canvasDimensionLimit / canvas.width; canvas.width = canvasDimensionLimit; } else { canvas.width *= canvasDimensionLimit / canvas.height; canvas.height = canvasDimensionLimit; } } } function createImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.decode = () => resolve(img); img.onload = () => resolve(img); img.onerror = reject; img.crossOrigin = "anonymous"; img.decoding = "async"; img.src = url; }); } async function svgToDataURL(svg) { return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`); } async function nodeToDataURL(node, width, height) { const xmlns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(xmlns, "svg"); const foreignObject = document.createElementNS(xmlns, "foreignObject"); svg.setAttribute("width", `${width}`); svg.setAttribute("height", `${height}`); svg.setAttribute("viewBox", `0 0 ${width} ${height}`); foreignObject.setAttribute("width", "100%"); foreignObject.setAttribute("height", "100%"); foreignObject.setAttribute("x", "0"); foreignObject.setAttribute("y", "0"); foreignObject.setAttribute("externalResourcesRequired", "true"); svg.appendChild(foreignObject); foreignObject.appendChild(node); return svgToDataURL(svg); } var isInstanceOfElement = (node, instance) => { if (node instanceof instance) return true; const nodePrototype = Object.getPrototypeOf(node); if (nodePrototype === null) return false; return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance); }; // node_modules/html-to-image/es/clone-pseudos.js function formatCSSText(style) { const content = style.getPropertyValue("content"); return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`; } function formatCSSProperties(style) { return toArray(style).map((name) => { const value = style.getPropertyValue(name); const priority = style.getPropertyPriority(name); return `${name}: ${value}${priority ? " !important" : ""};`; }).join(" "); } function getPseudoElementStyle(className, pseudo, style) { const selector = `.${className}:${pseudo}`; const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style); return document.createTextNode(`${selector}{${cssText}}`); } function clonePseudoElement(nativeNode, clonedNode, pseudo) { const style = window.getComputedStyle(nativeNode, pseudo); const content = style.getPropertyValue("content"); if (content === "" || content === "none") { return; } const className = uuid(); try { clonedNode.className = `${clonedNode.className} ${className}`; } catch (err) { return; } const styleElement = document.createElement("style"); styleElement.appendChild(getPseudoElementStyle(className, pseudo, style)); clonedNode.appendChild(styleElement); } function clonePseudoElements(nativeNode, clonedNode) { clonePseudoElement(nativeNode, clonedNode, ":before"); clonePseudoElement(nativeNode, clonedNode, ":after"); } // node_modules/html-to-image/es/mimes.js var WOFF = "application/font-woff"; var JPEG = "image/jpeg"; var mimes = { woff: WOFF, woff2: WOFF, ttf: "application/font-truetype", eot: "application/vnd.ms-fontobject", png: "image/png", jpg: JPEG, jpeg: JPEG, gif: "image/gif", tiff: "image/tiff", svg: "image/svg+xml", webp: "image/webp" }; function getExtension(url) { const match = /\.([^./]*?)$/g.exec(url); return match ? match[1] : ""; } function getMimeType(url) { const extension = getExtension(url).toLowerCase(); return mimes[extension] || ""; } // node_modules/html-to-image/es/dataurl.js function getContentFromDataUrl(dataURL) { return dataURL.split(/,/)[1]; } function isDataUrl(url) { return url.search(/^(data:)/) !== -1; } function makeDataUrl(content, mimeType) { return `data:${mimeType};base64,${content}`; } async function fetchAsDataURL(url, init, process2) { const res = await fetch(url, init); if (res.status === 404) { throw new Error(`Resource "${res.url}" not found`); } const blob = await res.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onloadend = () => { try { resolve(process2({ res, result: reader.result })); } catch (error) { reject(error); } }; reader.readAsDataURL(blob); }); } var cache = {}; function getCacheKey(url, contentType, includeQueryParams) { let key = url.replace(/\?.*/, ""); if (includeQueryParams) { key = url; } if (/ttf|otf|eot|woff2?/i.test(key)) { key = key.replace(/.*\//, ""); } return contentType ? `[${contentType}]${key}` : key; } async function resourceToDataURL(resourceUrl, contentType, options) { const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams); if (cache[cacheKey] != null) { return cache[cacheKey]; } if (options.cacheBust) { resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime(); } let dataURL; try { const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => { if (!contentType) { contentType = res.headers.get("Content-Type") || ""; } return getContentFromDataUrl(result); }); dataURL = makeDataUrl(content, contentType); } catch (error) { dataURL = options.imagePlaceholder || ""; let msg = `Failed to fetch resource: ${resourceUrl}`; if (error) { msg = typeof error === "string" ? error : error.message; } if (msg) { console.warn(msg); } } cache[cacheKey] = dataURL; return dataURL; } // node_modules/html-to-image/es/clone-node.js async function cloneCanvasElement(canvas) { const dataURL = canvas.toDataURL(); if (dataURL === "data:,") { return canvas.cloneNode(false); } return createImage(dataURL); } async function cloneVideoElement(video, options) { if (video.currentSrc) { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = video.clientWidth; canvas.height = video.clientHeight; ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const dataURL2 = canvas.toDataURL(); return createImage(dataURL2); } const poster = video.poster; const contentType = getMimeType(poster); const dataURL = await resourceToDataURL(poster, contentType, options); return createImage(dataURL); } async function cloneIFrameElement(iframe) { var _a; try { if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) { return await cloneNode(iframe.contentDocument.body, {}, true); } } catch (_b) { } return iframe.cloneNode(false); } async function cloneSingleNode(node, options) { if (isInstanceOfElement(node, HTMLCanvasElement)) { return cloneCanvasElement(node); } if (isInstanceOfElement(node, HTMLVideoElement)) { return cloneVideoElement(node, options); } if (isInstanceOfElement(node, HTMLIFrameElement)) { return cloneIFrameElement(node); } return node.cloneNode(false); } var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT"; async function cloneChildren(nativeNode, clonedNode, options) { var _a, _b; let children = []; if (isSlotElement(nativeNode) && nativeNode.assignedNodes) { children = toArray(nativeNode.assignedNodes()); } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) { children = toArray(nativeNode.contentDocument.body.childNodes); } else { children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes); } if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) { return clonedNode; } await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => { if (clonedChild) { clonedNode.appendChild(clonedChild); } }), Promise.resolve()); return clonedNode; } function cloneCSSStyle(nativeNode, clonedNode) { const targetStyle = clonedNode.style; if (!targetStyle) { return; } const sourceStyle = window.getComputedStyle(nativeNode); if (sourceStyle.cssText) { targetStyle.cssText = sourceStyle.cssText; targetStyle.transformOrigin = sourceStyle.transformOrigin; } else { toArray(sourceStyle).forEach((name) => { let value = sourceStyle.getPropertyValue(name); if (name === "font-size" && value.endsWith("px")) { const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1; value = `${reducedFont}px`; } if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") { value = "block"; } if (name === "d" && clonedNode.getAttribute("d")) { value = `path(${clonedNode.getAttribute("d")})`; } targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name)); }); } } function cloneInputValue(nativeNode, clonedNode) { if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) { clonedNode.innerHTML = nativeNode.value; } if (isInstanceOfElement(nativeNode, HTMLInputElement)) { clonedNode.setAttribute("value", nativeNode.value); } } function cloneSelectValue(nativeNode, clonedNode) { if (isInstanceOfElement(nativeNode, HTMLSelectElement)) { const clonedSelect = clonedNode; const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value")); if (selectedOption) { selectedOption.setAttribute("selected", ""); } } } function decorate(nativeNode, clonedNode) { if (isInstanceOfElement(clonedNode, Element)) { cloneCSSStyle(nativeNode, clonedNode); clonePseudoElements(nativeNode, clonedNode); cloneInputValue(nativeNode, clonedNode); cloneSelectValue(nativeNode, clonedNode); } return clonedNode; } async function ensureSVGSymbols(clone, options) { const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : []; if (uses.length === 0) { return clone; } const processedDefs = {}; for (let i = 0; i < uses.length; i++) { const use = uses[i]; const id = use.getAttribute("xlink:href"); if (id) { const exist = clone.querySelector(id); const definition = document.querySelector(id); if (!exist && definition && !processedDefs[id]) { processedDefs[id] = await cloneNode(definition, options, true); } } } const nodes = Object.values(processedDefs); if (nodes.length) { const ns = "http://www.w3.org/1999/xhtml"; const svg = document.createElementNS(ns, "svg"); svg.setAttribute("xmlns", ns); svg.style.position = "absolute"; svg.style.width = "0"; svg.style.height = "0"; svg.style.overflow = "hidden"; svg.style.display = "none"; const defs = document.createElementNS(ns, "defs"); svg.appendChild(defs); for (let i = 0; i < nodes.length; i++) { defs.appendChild(nodes[i]); } clone.appendChild(svg); } return clone; } async function cloneNode(node, options, isRoot) { if (!isRoot && options.filter && !options.filter(node)) { return null; } return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode)).then((clonedNode) => ensureSVGSymbols(clonedNode, options)); } // node_modules/html-to-image/es/embed-resources.js var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g; var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g; var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g; function toRegex(url) { const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1"); return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g"); } function parseURLs(cssText) { const urls = []; cssText.replace(URL_REGEX, (raw, quotation, url) => { urls.push(url); return raw; }); return urls.filter((url) => !isDataUrl(url)); } async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) { try { const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL; const contentType = getMimeType(resourceURL); let dataURL; if (getContentFromUrl) { const content = await getContentFromUrl(resolvedURL); dataURL = makeDataUrl(content, contentType); } else { dataURL = await resourceToDataURL(resolvedURL, contentType, options); } return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`); } catch (error) { } return cssText; } function filterPreferredFontFormat(str, { preferredFontFormat }) { return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => { while (true) { const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || []; if (!format) { return ""; } if (format === preferredFontFormat) { return `src: ${src};`; } } }); } function shouldEmbed(url) { return url.search(URL_REGEX) !== -1; } async function embedResources(cssText, baseUrl, options) { if (!shouldEmbed(cssText)) { return cssText; } const filteredCSSText = filterPreferredFontFormat(cssText, options); const urls = parseURLs(filteredCSSText); return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText)); } // node_modules/html-to-image/es/embed-images.js async function embedProp(propName, node, options) { var _a; const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName); if (propValue) { const cssString = await embedResources(propValue, null, options); node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName)); return true; } return false; } async function embedBackground(clonedNode, options) { if (!await embedProp("background", clonedNode, options)) { await embedProp("background-image", clonedNode, options); } if (!await embedProp("mask", clonedNode, options)) { await embedProp("mask-image", clonedNode, options); } } async function embedImageNode(clonedNode, options) { const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement); if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) { return; } const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal; const dataURL = await resourceToDataURL(url, getMimeType(url), options); await new Promise((resolve, reject) => { clonedNode.onload = resolve; clonedNode.onerror = reject; const image = clonedNode; if (image.decode) { image.decode = resolve; } if (image.loading === "lazy") { image.loading = "eager"; } if (isImageElement) { clonedNode.srcset = ""; clonedNode.src = dataURL; } else { clonedNode.href.baseVal = dataURL; } }); } async function embedChildren(clonedNode, options) { const children = toArray(clonedNode.childNodes); const deferreds = children.map((child) => embedImages(child, options)); await Promise.all(deferreds).then(() => clonedNode); } async function embedImages(clonedNode, options) { if (isInstanceOfElement(clonedNode, Element)) { await embedBackground(clonedNode, options); await embedImageNode(clonedNode, options); await embedChildren(clonedNode, options); } } // node_modules/html-to-image/es/apply-style.js function applyStyle(node, options) { const { style } = node; if (options.backgroundColor) { style.backgroundColor = options.backgroundColor; } if (options.width) { style.width = `${options.width}px`; } if (options.height) { style.height = `${options.height}px`; } const manual = options.style; if (manual != null) { Object.keys(manual).forEach((key) => { style[key] = manual[key]; }); } return node; } // node_modules/html-to-image/es/embed-webfonts.js var cssFetchCache = {}; async function fetchCSS(url) { let cache2 = cssFetchCache[url]; if (cache2 != null) { return cache2; } const res = await fetch(url); const cssText = await res.text(); cache2 = { url, cssText }; cssFetchCache[url] = cache2; return cache2; } async function embedFonts(data, options) { let cssText = data.cssText; const regexUrl = /url\(["']?([^"')]+)["']?\)/g; const fontLocs = cssText.match(/url\([^)]+\)/g) || []; const loadFonts = fontLocs.map(async (loc) => { let url = loc.replace(regexUrl, "$1"); if (!url.startsWith("https://")) { url = new URL(url, data.url).href; } return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => { cssText = cssText.replace(loc, `url(${result})`); return [loc, result]; }); }); return Promise.all(loadFonts).then(() => cssText); } function parseCSS(source) { if (source == null) { return []; } const result = []; const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi; let cssText = source.replace(commentsRegex, ""); const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi"); while (true) { const matches = keyframesRegex.exec(cssText); if (matches === null) { break; } result.push(matches[0]); } cssText = cssText.replace(keyframesRegex, ""); const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi; const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})"; const unifiedRegex = new RegExp(combinedCSSRegex, "gi"); while (true) { let matches = importRegex.exec(cssText); if (matches === null) { matches = unifiedRegex.exec(cssText); if (matches === null) { break; } else { importRegex.lastIndex = unifiedRegex.lastIndex; } } else { unifiedRegex.lastIndex = importRegex.lastIndex; } result.push(matches[0]); } return result; } async function getCSSRules(styleSheets, options) { const ret = []; const deferreds = []; styleSheets.forEach((sheet) => { if ("cssRules" in sheet) { try { toArray(sheet.cssRules || []).forEach((item, index) => { if (item.type === CSSRule.IMPORT_RULE) { let importIndex = index + 1; const url = item.href; const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => { try { sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length); } catch (error) { console.error("Error inserting rule from remote css", { rule, error }); } })).catch((e) => { console.error("Error loading remote css", e.toString()); }); deferreds.push(deferred); } }); } catch (e) { const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0]; if (sheet.href != null) { deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => { inline.insertRule(rule, sheet.cssRules.length); })).catch((err) => { console.error("Error loading remote stylesheet", err); })); } console.error("Error inlining remote css file", e); } } }); return Promise.all(deferreds).then(() => { styleSheets.forEach((sheet) => { if ("cssRules" in sheet) { try { toArray(sheet.cssRules || []).forEach((item) => { ret.push(item); }); } catch (e) { console.error(`Error while reading CSS rules from ${sheet.href}`, e); } } }); return ret; }); } function getWebFontRules(cssRules) { return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src"))); } async function parseWebFontRules(node, options) { if (node.ownerDocument == null) { throw new Error("Provided element is not within a Document"); } const styleSheets = toArray(node.ownerDocument.styleSheets); const cssRules = await getCSSRules(styleSheets, options); return getWebFontRules(cssRules); } async function getWebFontCSS(node, options) { const rules = await parseWebFontRules(node, options); const cssTexts = await Promise.all(rules.map((rule) => { const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null; return embedResources(rule.cssText, baseUrl, options); })); return cssTexts.join("\n"); } async function embedWebFonts(clonedNode, options) { const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options); if (cssText) { const styleNode = document.createElement("style"); const sytleContent = document.createTextNode(cssText); styleNode.appendChild(sytleContent); if (clonedNode.firstChild) { clonedNode.insertBefore(styleNode, clonedNode.firstChild); } else { clonedNode.appendChild(styleNode); } } } // node_modules/html-to-image/es/index.js async function toSvg(node, options = {}) { const { width, height } = getImageSize(node, options); const clonedNode = await cloneNode(node, options, true); await embedWebFonts(clonedNode, options); await embedImages(clonedNode, options); applyStyle(clonedNode, options); const datauri = await nodeToDataURL(clonedNode, width, height); return datauri; } async function toCanvas(node, options = {}) { const { width, height } = getImageSize(node, options); const svg = await toSvg(node, options); const img = await createImage(svg); const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const ratio = options.pixelRatio || getPixelRatio(); const canvasWidth = options.canvasWidth || width; const canvasHeight = options.canvasHeight || height; canvas.width = canvasWidth * ratio; canvas.height = canvasHeight * ratio; if (!options.skipAutoScale) { checkCanvasDimensions(canvas); } canvas.style.width = `${canvasWidth}`; canvas.style.height = `${canvasHeight}`; if (options.backgroundColor) { context.fillStyle = options.backgroundColor; context.fillRect(0, 0, canvas.width, canvas.height); } context.drawImage(img, 0, 0, canvas.width, canvas.height); return canvas; } async function toPng(node, options = {}) { const canvas = await toCanvas(node, options); return canvas.toDataURL(); } // src/canvas-extensions/export-canvas-extension.ts var import_obsidian18 = require("obsidian"); var MAX_ALLOWED_LOADING_TIME = 1e4; var ExportCanvasExtension = class extends CanvasExtension { isEnabled() { return "betterExportFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-breakpoint-changed", (canvas, node, breakpointRef) => { if (canvas.screenshotting) breakpointRef.value = true; } )); this.plugin.addCommand({ id: "export-all-as-image", name: "Export canvas as image", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.nodes.size > 0, (canvas) => this.showExportImageSettingsModal(canvas, null) ) }); this.plugin.addCommand({ id: "export-selected-as-image", name: "Export selected nodes as image", checkCallback: CanvasHelper.canvasCommand( this.plugin, (canvas) => canvas.selection.size > 0, (canvas) => this.showExportImageSettingsModal( canvas, canvas.getSelectionData().nodes.map((nodeData) => canvas.nodes.get(nodeData.id)).filter((node) => node !== void 0) ) ) }); } async showExportImageSettingsModal(canvas, nodesToExport) { const modal = new import_obsidian18.Modal(this.plugin.app); modal.setTitle("Export image settings"); let pixelRatioSetting = null; let noFontExportSetting = null; let transparentBackgroundSetting = null; const updateDynamicSettings = () => { var _a, _b, _c, _d, _e, _f; if (svg) { (_a = pixelRatioSetting == null ? void 0 : pixelRatioSetting.settingEl) == null ? void 0 : _a.hide(); (_b = noFontExportSetting == null ? void 0 : noFontExportSetting.settingEl) == null ? void 0 : _b.show(); (_c = transparentBackgroundSetting == null ? void 0 : transparentBackgroundSetting.settingEl) == null ? void 0 : _c.hide(); } else { (_d = pixelRatioSetting == null ? void 0 : pixelRatioSetting.settingEl) == null ? void 0 : _d.show(); (_e = noFontExportSetting == null ? void 0 : noFontExportSetting.settingEl) == null ? void 0 : _e.hide(); (_f = transparentBackgroundSetting == null ? void 0 : transparentBackgroundSetting.settingEl) == null ? void 0 : _f.show(); } }; let svg = false; new import_obsidian18.Setting(modal.contentEl).setName("Export file format").setDesc("Choose the file format to export the canvas as.").addDropdown( (dropdown) => dropdown.addOptions({ png: "PNG", svg: "SVG" }).setValue(svg ? "svg" : "png").onChange((value) => { svg = value === "svg"; updateDynamicSettings(); }) ); let pixelRatioFactor = 1; pixelRatioSetting = new import_obsidian18.Setting(modal.contentEl).setName("Pixel ratio").setDesc("Higher pixel ratios result in higher resolution images but also larger file sizes.").addSlider( (slider) => slider.setDynamicTooltip().setLimits(0.2, 5, 0.1).setValue(pixelRatioFactor).onChange((value) => pixelRatioFactor = value) ); let noFontExport = true; noFontExportSetting = new import_obsidian18.Setting(modal.contentEl).setName("Skip font export").setDesc("This will not include the fonts in the exported SVG. This will make the SVG file smaller.").addToggle( (toggle) => toggle.setValue(noFontExport).onChange((value) => noFontExport = value) ); let theme = document.body.classList.contains("theme-dark") ? "dark" : "light"; new import_obsidian18.Setting(modal.contentEl).setName("Theme").setDesc("The theme used for the export.").addDropdown( (dropdown) => dropdown.addOptions({ light: "Light", dark: "Dark" }).setValue(theme).onChange((value) => theme = value) ); let watermark = false; new import_obsidian18.Setting(modal.contentEl).setName("Show logo").setDesc("This will add an Obsidian + Advanced Canvas logo to the bottom left.").addToggle( (toggle) => toggle.setValue(watermark).onChange((value) => watermark = value) ); let garbledText = false; new import_obsidian18.Setting(modal.contentEl).setName("Privacy mode").setDesc("This will obscure any text on your canvas.").addToggle( (toggle) => toggle.setValue(garbledText).onChange((value) => garbledText = value) ); let transparentBackground = false; transparentBackgroundSetting = new import_obsidian18.Setting(modal.contentEl).setName("Transparent background").setDesc("This will make the background of the image transparent.").addToggle( (toggle) => toggle.setValue(transparentBackground).onChange((value) => transparentBackground = value) ); new import_obsidian18.Setting(modal.contentEl).addButton( (button) => button.setButtonText("Save").setCta().onClick(async () => { modal.close(); this.exportImage( canvas, nodesToExport, svg, svg ? 1 : pixelRatioFactor, svg ? noFontExport : false, theme, watermark, garbledText, svg ? true : transparentBackground ); }) ); updateDynamicSettings(); modal.open(); } async exportImage(canvas, nodesToExport, svg, pixelRatioFactor, noFontExport, theme, watermark, garbledText, transparentBackground) { var _a, _b, _c; const cachedTheme = document.body.classList.contains("theme-dark") ? "dark" : "light"; if (theme !== cachedTheme) { document.body.classList.toggle("theme-dark", theme === "dark"); document.body.classList.toggle("theme-light", theme === "light"); } const isWholeCanvas = nodesToExport === null; if (!nodesToExport) nodesToExport = [...canvas.nodes.values()]; const nodesToExportIds = nodesToExport.map((node) => node.getData().id); const edgesToExport = [...canvas.edges.values()].filter((edge) => { const edgeData = edge.getData(); return nodesToExportIds.includes(edgeData.fromNode) && nodesToExportIds.includes(edgeData.toNode); }); const backgroundColor = transparentBackground ? void 0 : window.getComputedStyle(canvas.canvasEl).getPropertyValue("--canvas-background"); new import_obsidian18.Notice("Exporting the canvas. Please wait..."); const interactionBlocker = this.getInteractionBlocker(); document.body.appendChild(interactionBlocker); canvas.screenshotting = true; canvas.canvasEl.classList.add("is-exporting"); if (garbledText) canvas.canvasEl.classList.add("is-text-garbled"); let watermarkEl = null; const cachedSelection = new Set(canvas.selection); canvas.deselectAll(); const cachedViewport = { x: canvas.x, y: canvas.y, zoom: canvas.zoom }; try { const targetBoundingBox = CanvasHelper.getBBox([...nodesToExport, ...edgesToExport]); let enlargedTargetBoundingBox = BBoxHelper.scaleBBox(targetBoundingBox, 1.1); const enlargedTargetBoundingBoxSize = { width: enlargedTargetBoundingBox.maxX - enlargedTargetBoundingBox.minX, height: enlargedTargetBoundingBox.maxY - enlargedTargetBoundingBox.minY }; const canvasElSize = { width: canvas.canvasEl.clientWidth, height: canvas.canvasEl.clientHeight }; const requiredPixelRatio = Math.max(enlargedTargetBoundingBoxSize.width / canvasElSize.width, enlargedTargetBoundingBoxSize.height / canvasElSize.height); const pixelRatio = svg ? void 0 : Math.round(requiredPixelRatio * pixelRatioFactor); watermarkEl = watermark ? this.getWatermark(enlargedTargetBoundingBox) : null; if (watermarkEl) canvas.canvasEl.appendChild(watermarkEl); const actualAspectRatio = canvas.canvasRect.width / canvas.canvasRect.height; const targetAspectRatio = (enlargedTargetBoundingBox.maxX - enlargedTargetBoundingBox.minX) / (enlargedTargetBoundingBox.maxY - enlargedTargetBoundingBox.minY); let adjustedBoundingBox = { ...enlargedTargetBoundingBox }; if (actualAspectRatio > targetAspectRatio) { const targetHeight = enlargedTargetBoundingBox.maxY - enlargedTargetBoundingBox.minY; const actualWidth = targetHeight * actualAspectRatio; adjustedBoundingBox.maxX = enlargedTargetBoundingBox.minX + actualWidth; } else { const targetWidth = enlargedTargetBoundingBox.maxX - enlargedTargetBoundingBox.minX; const actualHeight = targetWidth / actualAspectRatio; adjustedBoundingBox.maxY = enlargedTargetBoundingBox.minY + actualHeight; } canvas.zoomToRealBbox(adjustedBoundingBox); canvas.setViewport(canvas.tx, canvas.ty, canvas.tZoom); await sleep(10); let canvasScale = parseFloat(((_a = canvas.canvasEl.style.transform.match(/scale\((\d+(\.\d+)?)\)/)) == null ? void 0 : _a[1]) || "1"); const edgePathsBBox = BBoxHelper.combineBBoxes(edgesToExport.map((edge) => { const edgeCenter = edge.getCenter(); const labelWidth = edge.labelElement ? edge.labelElement.wrapperEl.getBoundingClientRect().width / canvasScale : 0; return { minX: edgeCenter.x - labelWidth / 2, minY: edgeCenter.y, maxX: edgeCenter.x + labelWidth / 2, maxY: edgeCenter.y }; })); const enlargedEdgePathsBBox = BBoxHelper.enlargeBBox(edgePathsBBox, 1.1); enlargedTargetBoundingBox = BBoxHelper.combineBBoxes([enlargedTargetBoundingBox, enlargedEdgePathsBBox]); adjustedBoundingBox = BBoxHelper.combineBBoxes([adjustedBoundingBox, enlargedEdgePathsBBox]); canvas.zoomToRealBbox(adjustedBoundingBox); canvas.setViewport(canvas.tx, canvas.ty, canvas.tZoom); await sleep(10); const canvasViewportBBox = canvas.getViewportBBox(); canvasScale = parseFloat(((_b = canvas.canvasEl.style.transform.match(/scale\((\d+(\.\d+)?)\)/)) == null ? void 0 : _b[1]) || "1"); let width = (canvasViewportBBox.maxX - canvasViewportBBox.minX) * canvasScale; let height = (canvasViewportBBox.maxY - canvasViewportBBox.minY) * canvasScale; if (actualAspectRatio > targetAspectRatio) width = height * targetAspectRatio; else height = width / targetAspectRatio; let unloadedNodes = nodesToExport.filter((node) => node.initialized === false || node.isContentMounted === false); const startTimestamp = performance.now(); while (unloadedNodes.length > 0 && performance.now() - startTimestamp < MAX_ALLOWED_LOADING_TIME) { await sleep(10); unloadedNodes = nodesToExport.filter((node) => node.initialized === false || node.isContentMounted === false); console.info(`Waiting for ${unloadedNodes.length} nodes to finish loading...`); } if (unloadedNodes.length === 0) { const nodeElements = nodesToExport.map((node) => node.nodeEl); const edgePathAndArrowElements = edgesToExport.map((edge) => [edge.lineGroupEl, edge.lineEndGroupEl]).flat(); const edgeLabelElements = edgesToExport.map((edge) => { var _a2; return (_a2 = edge.labelElement) == null ? void 0 : _a2.wrapperEl; }).filter((labelElement) => labelElement !== void 0); const filter = (element) => { var _a2, _b2, _c2, _d; if (((_a2 = element.classList) == null ? void 0 : _a2.contains("canvas-node")) && !nodeElements.includes(element)) return false; if (((_c2 = (_b2 = element.parentElement) == null ? void 0 : _b2.classList) == null ? void 0 : _c2.contains("canvas-edges")) && !edgePathAndArrowElements.includes(element)) return false; if (((_d = element.classList) == null ? void 0 : _d.contains("canvas-path-label-wrapper")) && !edgeLabelElements.includes(element)) return false; return true; }; const options = { pixelRatio, backgroundColor, height, width, filter }; if (noFontExport) options.fontEmbedCSS = ""; let imageDataUri = svg ? await toSvg(canvas.canvasEl, options) : await toPng(canvas.canvasEl, options); if (svg) { const header = ``; imageDataUri = imageDataUri.replace( encodeURIComponent(" canvas.selection = cachedSelection); canvas.setViewport(cachedViewport.x, cachedViewport.y, cachedViewport.zoom); interactionBlocker.remove(); if (theme !== cachedTheme) { document.body.classList.toggle("theme-dark", cachedTheme === "dark"); document.body.classList.toggle("theme-light", cachedTheme === "light"); } } } getInteractionBlocker() { const interactionBlocker = document.createElement("div"); interactionBlocker.classList.add("progress-bar-container"); const progressBar = document.createElement("div"); progressBar.classList.add("progress-bar"); interactionBlocker.appendChild(progressBar); const progressBarMessage = document.createElement("div"); progressBarMessage.classList.add("progress-bar-message", "u-center-text"); progressBarMessage.innerText = "Generating image..."; progressBar.appendChild(progressBarMessage); const progressBarIndicator = document.createElement("div"); progressBarIndicator.classList.add("progress-bar-indicator"); progressBar.appendChild(progressBarIndicator); const progressBarLine = document.createElement("div"); progressBarLine.classList.add("progress-bar-line"); progressBarIndicator.appendChild(progressBarLine); const progressBarSublineIncrease = document.createElement("div"); progressBarSublineIncrease.classList.add("progress-bar-subline", "mod-increase"); progressBarIndicator.appendChild(progressBarSublineIncrease); const progressBarSublineDecrease = document.createElement("div"); progressBarSublineDecrease.classList.add("progress-bar-subline", "mod-decrease"); progressBarIndicator.appendChild(progressBarSublineDecrease); return interactionBlocker; } getWatermark(bbox) { const bboxWidth = bbox.maxX - bbox.minX; const width = Math.max(200, bboxWidth * 0.3); const WATERMARK_SIZE = { width: 215, height: 25 }; const height = WATERMARK_SIZE.height / WATERMARK_SIZE.width * width; const watermarkPadding = { x: bboxWidth * 0.02, y: bboxWidth * 0.014 }; bbox.maxY += height + watermarkPadding.y; const watermarkEl = document.createElementNS("http://www.w3.org/2000/svg", "svg"); watermarkEl.id = "watermark-ac"; watermarkEl.style.transform = `translate(${bbox.minX + watermarkPadding.x}px, ${bbox.maxY - height - watermarkPadding.y}px)`; watermarkEl.setAttrs({ viewBox: `0 0 ${WATERMARK_SIZE.width} ${WATERMARK_SIZE.height}`, width: width.toString(), fill: "currentColor" }); watermarkEl.innerHTML = ''; return watermarkEl; } }; // src/canvas-extensions/floating-edge-canvas-extension.ts var FloatingEdgeCanvasExtension = class extends CanvasExtension { isEnabled() { return "floatingEdgeFeatureEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:data-loaded:after", (canvas, data, setData) => this.onLoadData(canvas, data) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-moved", (canvas, node) => this.onNodeMoved(canvas, node) )); if (this.plugin.settings.getSetting("allowFloatingEdgeCreation")) { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-connection-dragging:before", (canvas, edge, event, newEdge, side) => this.onEdgeStartedDragging(canvas, edge, event, newEdge, side) )); } this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-connection-dragging:after", (canvas, edge, event, newEdge, side) => this.onEdgeStoppedDragging(canvas, edge, event, newEdge, side) )); } onLoadData(canvas, data) { for (const edgeData of data.edges) { const edge = canvas.edges.get(edgeData.id); if (!edge) return console.warn("Imported edge is not yet loaded :("); this.updateEdgeConnectionSide(edge); } } onNodeMoved(canvas, node) { const affectedEdges = canvas.getEdgesForNode(node); for (const edge of affectedEdges) this.updateEdgeConnectionSide(edge); } updateEdgeConnectionSide(edge) { const edgeData = edge.getData(); if (edgeData.fromFloating) { const fixedNodeConnectionPoint = BBoxHelper.getCenterOfBBoxSide(edge.to.node.getBBox(), edge.to.side); const bestSide = CanvasHelper.getBestSideForFloatingEdge(fixedNodeConnectionPoint, edge.from.node); if (bestSide !== edge.from.side) { edge.setData({ ...edgeData, fromSide: bestSide }); } } if (edgeData.toFloating) { const fixedNodeConnectionPoint = BBoxHelper.getCenterOfBBoxSide(edge.from.node.getBBox(), edge.from.side); const bestSide = CanvasHelper.getBestSideForFloatingEdge(fixedNodeConnectionPoint, edge.to.node); if (bestSide !== edge.to.side) { edge.setData({ ...edgeData, toSide: bestSide }); } } } onEdgeStartedDragging(canvas, edge, _event, newEdge, _side) { if (newEdge && this.plugin.settings.getSetting("newEdgeFromSideFloating")) edge.setData({ ...edge.getData(), fromFloating: true // New edges can only get dragged from the "from" side }); let cachedViewportNodes = null; let hasNaNFloatingEdgeDropZones = false; this.onPointerMove = (event) => { if (cachedViewportNodes === null || hasNaNFloatingEdgeDropZones || canvas.viewportChanged) { hasNaNFloatingEdgeDropZones = false; cachedViewportNodes = canvas.getViewportNodes().map((node) => { const nodeFloatingEdgeDropZone = this.getFloatingEdgeDropZoneForNode(node); if (isNaN(nodeFloatingEdgeDropZone.minX) || isNaN(nodeFloatingEdgeDropZone.minY) || isNaN(nodeFloatingEdgeDropZone.maxX) || isNaN(nodeFloatingEdgeDropZone.maxY)) hasNaNFloatingEdgeDropZones = true; return [node, nodeFloatingEdgeDropZone]; }); } for (const [node, nodeFloatingEdgeDropZoneClientRect] of cachedViewportNodes) { const hovering = BBoxHelper.insideBBox({ x: event.clientX, y: event.clientY }, nodeFloatingEdgeDropZoneClientRect, true); node.nodeEl.classList.toggle("hovering-floating-edge-zone", hovering); } }; document.addEventListener("pointermove", this.onPointerMove); } onEdgeStoppedDragging(_canvas, edge, event, _newEdge, side) { document.removeEventListener("pointermove", this.onPointerMove); const dropZoneNode = side === "from" ? edge.from.node : edge.to.node; const floatingEdgeDropZone = this.getFloatingEdgeDropZoneForNode(dropZoneNode); const wasDroppedInFloatingEdgeDropZone = this.plugin.settings.getSetting("allowFloatingEdgeCreation") ? BBoxHelper.insideBBox({ x: event.clientX, y: event.clientY }, floatingEdgeDropZone, true) : false; const edgeData = edge.getData(); if (side === "from" && wasDroppedInFloatingEdgeDropZone == edgeData.fromFloating) return; if (side === "to" && wasDroppedInFloatingEdgeDropZone == edgeData.toFloating) return; if (side === "from") edgeData.fromFloating = wasDroppedInFloatingEdgeDropZone; else edgeData.toFloating = wasDroppedInFloatingEdgeDropZone; edge.setData(edgeData); this.updateEdgeConnectionSide(edge); } getFloatingEdgeDropZoneForNode(node) { const nodeElClientBoundingRect = node.nodeEl.getBoundingClientRect(); const nodeFloatingEdgeDropZoneElStyle = window.getComputedStyle(node.nodeEl, ":after"); const nodeFloatingEdgeDropZoneSize = { width: parseFloat(nodeFloatingEdgeDropZoneElStyle.getPropertyValue("width")), height: parseFloat(nodeFloatingEdgeDropZoneElStyle.getPropertyValue("height")) }; return { minX: nodeElClientBoundingRect.left + (nodeElClientBoundingRect.width - nodeFloatingEdgeDropZoneSize.width) / 2, minY: nodeElClientBoundingRect.top + (nodeElClientBoundingRect.height - nodeFloatingEdgeDropZoneSize.height) / 2, maxX: nodeElClientBoundingRect.right - (nodeElClientBoundingRect.width - nodeFloatingEdgeDropZoneSize.width) / 2, maxY: nodeElClientBoundingRect.bottom - (nodeElClientBoundingRect.height - nodeFloatingEdgeDropZoneSize.height) / 2 }; } }; // src/canvas-extensions/edge-highlight-canvas-extension.ts var EdgeHighlightCanvasExtension = class extends CanvasExtension { isEnabled() { return "edgeHighlightEnabled"; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:selection-changed", (canvas, oldSelection, updateSelection) => this.onSelectionChanged(canvas, oldSelection) )); } onSelectionChanged(canvas, oldSelection) { var _a, _b, _c, _d, _e; const connectedEdgesToBeHighlighted = new Set(canvas.getSelectionData().nodes.flatMap((nodeData) => { var _a2, _b2; return [ ...(_a2 = canvas.edgeFrom.get(canvas.nodes.get(nodeData.id))) != null ? _a2 : [], ...this.plugin.settings.getSetting("highlightIncomingEdges") ? (_b2 = canvas.edgeTo.get(canvas.nodes.get(nodeData.id))) != null ? _b2 : [] : [] ]; })); for (const edge of canvas.edges.values()) { const isFocused = canvas.selection.has(edge) || connectedEdgesToBeHighlighted.has(edge); edge.lineGroupEl.classList.toggle("is-focused", isFocused); (_b = (_a = edge.lineEndGroupEl) == null ? void 0 : _a.classList) == null ? void 0 : _b.toggle("is-focused", isFocused); (_e = (_d = (_c = edge.labelElement) == null ? void 0 : _c.textareaEl) == null ? void 0 : _d.classList) == null ? void 0 : _e.toggle("is-focused", isFocused); } } }; // src/canvas-extensions/dataset-exposers/canvas-metadata-exposer.ts var CanvasMetadataExposerExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-metadata-changed", (canvas) => this.updateExposedSettings(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.updateExposedSettings(canvas) )); } updateExposedSettings(canvas) { const startNodeId = canvas.metadata["startNode"]; for (const [nodeId, node] of canvas.nodes) { if (nodeId === startNodeId) node.nodeEl.dataset.isStartNode = "true"; else delete node.nodeEl.dataset.isStartNode; } } }; // src/canvas-extensions/dataset-exposers/node-exposer.ts var CANVAS_NODE_IFRAME_BODY_CLASS = "canvas-node-iframe-body"; function getExposedNodeData(settings) { const exposedData = []; if (settings.getSetting("nodeStylingFeatureEnabled")) exposedData.push("styleAttributes"); if (settings.getSetting("collapsibleGroupsFeatureEnabled")) exposedData.push("collapsed"); if (settings.getSetting("portalsFeatureEnabled")) exposedData.push("isPortalLoaded"); return exposedData; } var NodeExposerExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-changed", (_canvas, node) => { var _a, _b; const nodeData = node == null ? void 0 : node.getData(); if (!nodeData) return; this.setDataAttributes(node.nodeEl, nodeData); const iframe = (_b = (_a = node.nodeEl.querySelector("iframe")) == null ? void 0 : _a.contentDocument) == null ? void 0 : _b.body; if (iframe) this.setDataAttributes(iframe, nodeData); } )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-editing-state-changed", (_canvas, node, editing) => { var _a, _b; if (!editing) return; const nodeData = node.getData(); if (!nodeData) return; const iframe = (_b = (_a = node.nodeEl.querySelector("iframe")) == null ? void 0 : _a.contentDocument) == null ? void 0 : _b.body; if (!iframe) return; iframe.classList.add(CANVAS_NODE_IFRAME_BODY_CLASS); new MutationObserver(() => iframe.classList.toggle(CANVAS_NODE_IFRAME_BODY_CLASS, true)).observe(iframe, { attributes: true, attributeFilter: ["class"] }); this.setDataAttributes(iframe, nodeData); } )); } setDataAttributes(element, nodeData) { for (const exposedDataKey of getExposedNodeData(this.plugin.settings)) { const datasetPairs = nodeData[exposedDataKey] && typeof nodeData[exposedDataKey] === "object" ? Object.entries(nodeData[exposedDataKey]) : [[exposedDataKey, nodeData[exposedDataKey]]]; for (const [key, value] of datasetPairs) { if (!value) delete element.dataset[key]; else element.dataset[key] = value; } } } }; // src/canvas-extensions/dataset-exposers/node-interaction-exposer.ts var TARGET_NODE_DATASET_PREFIX = "target"; var NodeInteractionExposerExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:node-interaction", (canvas, node) => { const nodeData = node == null ? void 0 : node.getData(); if (!nodeData) return; const interactionEl = canvas.nodeInteractionLayer.interactionEl; if (!interactionEl) return; for (const exposedDataKey of getExposedNodeData(this.plugin.settings)) { const datasetPairs = nodeData[exposedDataKey] instanceof Object ? Object.entries(nodeData[exposedDataKey]) : [[exposedDataKey, nodeData[exposedDataKey]]]; for (const [key, value] of datasetPairs) { const modifiedKey = TARGET_NODE_DATASET_PREFIX + key.toString().charAt(0).toUpperCase() + key.toString().slice(1); if (!value) delete interactionEl.dataset[modifiedKey]; else interactionEl.dataset[modifiedKey] = value; } } if (PortalsCanvasExtension.isPortalElement(node.id)) interactionEl.dataset.isFromPortal = "true"; else delete interactionEl.dataset.isFromPortal; } )); } }; // src/canvas-extensions/dataset-exposers/edge-exposer.ts function getExposedEdgeData(settings) { const exposedData = []; if (settings.getSetting("edgesStylingFeatureEnabled")) exposedData.push("styleAttributes"); return exposedData; } var EdgeExposerExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:edge-changed", (_canvas, edge) => { var _a, _b, _c, _d; const edgeData = edge == null ? void 0 : edge.getData(); if (!edgeData) return; for (const exposedDataKey of getExposedEdgeData(this.plugin.settings)) { const datasetPairs = edgeData[exposedDataKey] && typeof edgeData[exposedDataKey] === "object" ? Object.entries(edgeData[exposedDataKey]) : [[exposedDataKey, edgeData[exposedDataKey]]]; for (const [key, value] of datasetPairs) { const stringifiedKey = key == null ? void 0 : key.toString(); if (!stringifiedKey) continue; if (!value) { delete edge.path.display.dataset[stringifiedKey]; if ((_a = edge.fromLineEnd) == null ? void 0 : _a.el) delete edge.fromLineEnd.el.dataset[stringifiedKey]; if ((_b = edge.toLineEnd) == null ? void 0 : _b.el) delete edge.toLineEnd.el.dataset[stringifiedKey]; } else { edge.path.display.dataset[stringifiedKey] = value.toString(); if ((_c = edge.fromLineEnd) == null ? void 0 : _c.el) edge.fromLineEnd.el.dataset[stringifiedKey] = value.toString(); if ((_d = edge.toLineEnd) == null ? void 0 : _d.el) edge.toLineEnd.el.dataset[stringifiedKey] = value.toString(); } } } } )); } }; // src/canvas-extensions/dataset-exposers/canvas-wrapper-exposer.ts var EXPOSED_SETTINGS = [ "disableFontSizeRelativeToZoom", "hideBackgroundGridWhenInReadonly", "collapsibleGroupsFeatureEnabled", "collapsedGroupPreviewOnDrag", "allowFloatingEdgeCreation" ]; var CanvasWrapperExposerExtension = class extends CanvasExtension { isEnabled() { return true; } init() { this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:settings-changed", () => this.updateExposedSettings(this.plugin.getCurrentCanvas()) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:canvas-changed", (canvas) => this.updateExposedSettings(canvas) )); this.plugin.registerEvent(this.plugin.app.workspace.on( "advanced-canvas:dragging-state-changed", (canvas, dragging) => { if (dragging) canvas.wrapperEl.dataset.isDragging = "true"; else delete canvas.wrapperEl.dataset.isDragging; } )); } updateExposedSettings(canvas) { if (!canvas) return; for (const setting of EXPOSED_SETTINGS) { canvas.wrapperEl.dataset[setting] = this.plugin.settings.getSetting(setting).toString(); } } }; // src/patchers/bases-table-view-patcher.ts var BasesTableViewPatcher = class extends Patcher { async patch() { if (!this.plugin.settings.getSetting("canvasMetadataCompatibilityEnabled")) return; const bases = this.plugin.app.internalPlugins.getEnabledPluginById("bases"); if (!bases) return; this.patchViewFactory(bases); } async patchViewFactory(bases) { const that = this; await Patcher.patchOnce(this.plugin, bases.registrations.table, (resolve) => ({ factory: Patcher.OverrideExisting((next) => function(...args) { const view = next.call(this, ...args); that.patchTableView(view); resolve(view); return view; }) })); } async patchTableView(basesView) { const that = this; await Patcher.patchOnce(this.plugin, basesView, (resolve) => ({ updateVirtualDisplay: Patcher.OverrideExisting((next) => function(...args) { const result = next.call(this, ...args); if (this.rows.length > 0) { const row = this.rows.first(); that.patchTableRow(row); resolve(row); } return result; }) })); } async patchTableRow(row) { const that = this; await Patcher.patchOnce(this.plugin, row, (resolve) => ({ render: Patcher.OverrideExisting((next) => function(...args) { let result = next.call(this, ...args); if (this.cells.length > 0) { const cell = this.cells.first(); that.patchTableCell(cell); resolve(cell); result = next.call(this, ...args); } return result; }) })); } async patchTableCell(cell) { Patcher.patchPrototype(this.plugin, cell, { render: Patcher.OverrideExisting((next) => function(ctx) { var _a; const isCanvas = ((_a = ctx.file) == null ? void 0 : _a.extension) === "canvas"; if (isCanvas) ctx.file.extension = "md"; const result = next.call(this, ctx); if (isCanvas) ctx.file.extension = "canvas"; return result; }) }); } }; // src/main.ts var PATCHERS = [ // Core canvas patchers CanvasPatcher, SearchCommandPatcher, // Core metadata patchers MetadataCachePatcher, FileManagerPatcher, // Direct metadata dependant patchers PropertiesPatcher, !(0, import_obsidian19.requireApiVersion)("1.12.0") && BacklinksPatcher, OutgoingLinksPatcher, // Metadata dependant patchers (0, import_obsidian19.requireApiVersion)("1.9.0") && BasesTableViewPatcher, LinkSuggestionsPatcher, EmbedPatcher, SearchPatcher ]; var CANVAS_EXTENSIONS = [ // Advanced JSON Canvas Extensions MetadataCanvasExtension, NodeStylesExtension, EdgeStylesExtension, NodeRatioCanvasExtension, FloatingEdgeCanvasExtension, AutoResizeNodeCanvasExtension, CollapsibleGroupsCanvasExtension, ColorPaletteCanvasExtension, PresentationCanvasExtension, PortalsCanvasExtension, // UI Extensions (Non-savable data) CanvasMetadataExposerExtension, CanvasWrapperExposerExtension, NodeExposerExtension, EdgeExposerExtension, NodeInteractionExposerExtension, FrontmatterControlButtonCanvasExtension, BetterDefaultSettingsCanvasExtension, CommandsCanvasExtension, BetterReadonlyCanvasExtension, GroupCanvasExtension, VariableBreakpointCanvasExtension, EdgeHighlightCanvasExtension, AutoFileNodeEdgesCanvasExtension, FlipEdgeCanvasExtension, ZOrderingCanvasExtension, ExportCanvasExtension, FocusModeCanvasExtension, EncapsulateCanvasExtension, EdgeSelectionCanvasExtension ]; var AdvancedCanvasPlugin = class extends import_obsidian19.Plugin { async onload() { IconsHelper.addIcons(); this.settings = new SettingsManager(this); await this.settings.loadSettings(); this.settings.addSettingsTab(); this.windowsManager = new WindowsManager(this); this.patchers = PATCHERS.map((Patcher2) => { if (!Patcher2) return; try { return new Patcher2(this); } catch (e) { console.error(`Error initializing patcher ${Patcher2.name}:`, e); } }); this.canvasExtensions = CANVAS_EXTENSIONS.map((Extension) => { try { return new Extension(this); } catch (e) { console.error(`Error initializing ac-extension ${Extension.name}:`, e); } }); } onunload() { } getCanvases() { return this.app.workspace.getLeavesOfType("canvas").map((leaf) => { var _a; return (_a = leaf.view) == null ? void 0 : _a.canvas; }).filter((canvas) => canvas); } getCurrentCanvasView() { const canvasView = this.app.workspace.getActiveViewOfType(import_obsidian19.ItemView); if ((canvasView == null ? void 0 : canvasView.getViewType()) !== "canvas") return null; return canvasView; } getCurrentCanvas() { var _a; return ((_a = this.getCurrentCanvasView()) == null ? void 0 : _a.canvas) || null; } createFileSnapshot(path, content) { var _a; const fileRecoveryPlugin = (_a = this.app.internalPlugins.plugins["file-recovery"]) == null ? void 0 : _a.instance; if (!fileRecoveryPlugin) return; fileRecoveryPlugin.forceAdd(path, content); } // this.app.plugins.plugins["advanced-canvas"].enableDebugMode() enableDebugMode() { if (this.debugHelper) return; this.debugHelper = new DebugHelper(this); } }; /* nosourcemap */