diff --git a/manifest.json b/manifest.json index 56d22e2..e65b2aa 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "excalibrain", "name": "ExcaliBrain", - "version": "0.2.2", + "version": "0.2.3", "minAppVersion": "1.1.6", "description": "A clean, intuitive and editable graph view for Obsidian", "author": "Zsolt Viczian", diff --git a/src/Components/ToggleButton.ts b/src/Components/ToggleButton.ts index 40c9b7a..6b1ad62 100644 --- a/src/Components/ToggleButton.ts +++ b/src/Components/ToggleButton.ts @@ -10,7 +10,8 @@ export class ToggleButton { setVal: (val:boolean)=>void, wrapper: HTMLElement, options: { - display: string, + display?: string, + icon?: string, tooltip: string }, private updateIndex: boolean = true @@ -18,7 +19,11 @@ export class ToggleButton { this.button = wrapper.createEl("button", { cls: "excalibrain-button", }); - this.button.createSpan({text: options.display}) + if(options.icon) { + this.button.innerHTML = options.icon; + } else { + this.button.createSpan({text: options.display??""}) + } this.button.ariaLabel = options.tooltip; this.setColor(); diff --git a/src/Components/ToolsPanel.ts b/src/Components/ToolsPanel.ts index eec0f04..7bd06be 100644 --- a/src/Components/ToolsPanel.ts +++ b/src/Components/ToolsPanel.ts @@ -6,6 +6,7 @@ import { splitFolderAndFilename } from "src/utils/fileUtils"; import { PageSuggest } from "../Suggesters/PageSuggester"; import { LinkTagFilter } from "./LinkTagFilter"; import { keepOnTop } from "src/utils/utils"; +import { getIcon } from "obsidian"; export class ToolsPanel { private wrapperDiv: HTMLDivElement; @@ -64,24 +65,48 @@ export class ToolsPanel { //------------ //Edit drawing //------------ - const saveAsDrawingButton = buttonsWrapperDiv.createEl("button", { - cls: "excalibrain-button", - text: "โœ" - }); - saveAsDrawingButton.ariaLabel = t("OPEN_DRAWING"); - saveAsDrawingButton.onclick = () => { - const elements = this.plugin.EA.getExcalidrawAPI().getSceneElements() as ExcalidrawElement[]; - const appState = this.plugin.EA.getExcalidrawAPI().getAppState(); - const ea = this.plugin.EA; //window.ExcalidrawAutomate; - ea.reset(); - ea.canvas.viewBackgroundColor = appState.viewBackgroundColor; - ea.canvas.theme = "light"; - elements.forEach(el=>ea.elementsDict[el.id] = el); - ea.create({ - filename: `ExcaliBrain Snapshot - ${splitFolderAndFilename(this.plugin.scene.centralPagePath).basename}`, - onNewPane: true - }); - } + this.buttons.push( + new ToggleButton( + this.plugin, + ()=>false, + (val:boolean)=>{ + const elements = this.plugin.EA.getExcalidrawAPI().getSceneElements() as ExcalidrawElement[]; + const appState = this.plugin.EA.getExcalidrawAPI().getAppState(); + const ea = this.plugin.EA; //window.ExcalidrawAutomate; + ea.reset(); + ea.canvas.viewBackgroundColor = appState.viewBackgroundColor; + ea.canvas.theme = "light"; + elements.forEach(el=>ea.elementsDict[el.id] = el); + ea.create({ + filename: `ExcaliBrain Snapshot - ${splitFolderAndFilename(this.plugin.scene.centralPagePath).basename}`, + onNewPane: true + }); + }, + buttonsWrapperDiv, + { + display: "โœ", + icon: getIcon("lucide-pencil").outerHTML, + tooltip: t("OPEN_DRAWING"), + }, + )); + + //------------ + //Pin Leaf + //------------ + this.buttons.push( + new ToggleButton( + this.plugin, + ()=>this.plugin.scene.pinLeaf, + (val:boolean)=>this.plugin.scene.pinLeaf = val, + buttonsWrapperDiv, + { + display: "๐Ÿ“Œ", + icon: getIcon("lucide-pin").outerHTML, + tooltip: t("PIN_LEAF") + }, + false + ) + ) //------------ //Attachments @@ -94,6 +119,7 @@ export class ToolsPanel { buttonsWrapperDiv, { display: "๐Ÿ“Ž", + icon: getIcon("lucide-paperclip").outerHTML, tooltip: t("SHOW_HIDE_ATTACHMENTS") } ) @@ -110,6 +136,7 @@ export class ToolsPanel { buttonsWrapperDiv, { display: "โˆ…", + icon: getIcon("lucide-minus-circle").outerHTML, tooltip: t("SHOW_HIDE_VIRTUAL") }, false @@ -127,6 +154,7 @@ export class ToolsPanel { buttonsWrapperDiv, { display: "๐Ÿค”", + icon: getIcon("lucide-git-pull-request-draft").outerHTML, tooltip: t("SHOW_HIDE_INFERRED") } ) @@ -143,79 +171,83 @@ export class ToolsPanel { buttonsWrapperDiv, { display: "๐Ÿ“„", + icon: getIcon("lucide-file-text").outerHTML, tooltip: t("SHOW_HIDE_PAGES") } ) ) //------------ - //Folder + //Alias //------------ this.buttons.push( new ToggleButton( this.plugin, - ()=>this.plugin.settings.showFolderNodes, - (val:boolean)=>this.plugin.settings.showFolderNodes = val, + ()=>this.plugin.settings.renderAlias, + (val:boolean)=>this.plugin.settings.renderAlias = val, buttonsWrapperDiv, { - display: "๐Ÿ“‚", - tooltip: t("SHOW_HIDE_FOLDER") + display: "๐Ÿงฅ", + icon: getIcon("lucide-venetian-mask").outerHTML, + tooltip: t("SHOW_HIDE_ALIAS") }, - true + false ) ) //------------ - //Tag + //Folder //------------ this.buttons.push( new ToggleButton( this.plugin, - ()=>this.plugin.settings.showTagNodes, - (val:boolean)=>this.plugin.settings.showTagNodes = val, + ()=>this.plugin.settings.showFolderNodes, + (val:boolean)=>this.plugin.settings.showFolderNodes = val, buttonsWrapperDiv, { - display: "#", - tooltip: t("SHOW_HIDE_TAG") + display: "๐Ÿ“‚", + icon: getIcon("lucide-folder").outerHTML, + tooltip: t("SHOW_HIDE_FOLDER") }, - false + true ) ) - //------------ - //Alias + //Tag //------------ this.buttons.push( new ToggleButton( this.plugin, - ()=>this.plugin.settings.renderAlias, - (val:boolean)=>this.plugin.settings.renderAlias = val, + ()=>this.plugin.settings.showTagNodes, + (val:boolean)=>this.plugin.settings.showTagNodes = val, buttonsWrapperDiv, { - display: "๐Ÿงฅ", - tooltip: t("SHOW_HIDE_ALIAS") + display: "#", + icon: getIcon("lucide-tag").outerHTML, + tooltip: t("SHOW_HIDE_TAG") }, false ) ) //------------ - //Pin Leaf + // Render weblinks in page //------------ - this.buttons.push( + /*this.buttons.push( new ToggleButton( this.plugin, - ()=>this.plugin.scene.pinLeaf, - (val:boolean)=>this.plugin.scene.pinLeaf = val, + ()=>this.plugin.settings.showURLs, + (val:boolean)=>this.plugin.settings.showURLs = val, buttonsWrapperDiv, { - display: "๐Ÿ“Œ", - tooltip: t("PIN_LEAF") + display: "๐ŸŒ", + icon: getIcon("lucide-globe").outerHTML, + tooltip: t("SHOW_HIDE_URLS") }, false ) - ) + )*/ //------------ //Display siblings @@ -228,6 +260,7 @@ export class ToolsPanel { buttonsWrapperDiv, { display: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + icon: getIcon("lucide-grip").outerHTML, tooltip: t("SHOW_HIDE_SIBLINGS") }, false @@ -245,6 +278,7 @@ export class ToolsPanel { buttonsWrapperDiv, { display: "โน๏ธ", + icon: getIcon("lucide-code").outerHTML, tooltip: t("SHOW_HIDE_EMBEDDEDCENTRAL") }, false diff --git a/src/Scene.ts b/src/Scene.ts index c8dfc98..af143be 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -10,9 +10,11 @@ import { ToolsPanel } from "./Components/ToolsPanel"; import { Mutable, Neighbour, RelationType, Role } from "./types"; import { HistoryPanel } from "./Components/HistoryPanel"; import { WarningPrompt } from "./utils/Prompts"; -import { debug, keepOnTop } from "./utils/utils"; +import { keepOnTop } from "./utils/utils"; import { ExcalidrawElement } from "obsidian-excalidraw-plugin"; import { isEmbedFileType } from "./utils/fileUtils"; +import { Page } from "./graph/Page"; +import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types" export class Scene { ea: ExcalidrawAutomate; @@ -20,6 +22,7 @@ export class Scene { app: App; leaf: WorkspaceLeaf; centralPagePath: string; //path of the page in the center of the graph + centralPageFile: TFile; public centralLeaf: WorkspaceLeaf; //workspace leaf containing the central page textSize: {width:number, height:number}; nodeWidth: number; @@ -64,7 +67,7 @@ export class Scene { this.focusSearchAfterInitiation = focusSearchAfterInitiation; await this.plugin.loadSettings(); if(!this.leaf?.view) return; - this.toolsPanel = new ToolsPanel((this.leaf.view as TextFileView).contentEl,this.plugin); + this.toolsPanel = new ToolsPanel((this.leaf.view as TextFileView).contentEl.querySelector(".excalidraw"),this.plugin); this.initializeScene(); } @@ -108,11 +111,21 @@ export class Scene { this.centralLeaf.view?.file?.path !== centralPage.file.path ) { this.centralLeaf.openFile(centralPage.file, {active: false}); - //app.workspace.revealLeaf(this.centralLeaf); } } await this.render(); - //this.toolsPanel.rerender(); //this is also there at the end of render. Seems duplicate. + } + + private getCentralPage():Page { + //centralPagePath might no longer be valid in case the user changed the filename of the central page + //this is relevant only when the central page is embedded, since if the file is in another leaf the leaf.view.file will + //have the right new path + let centralPage = this.plugin.pages.get(this.centralPagePath) + if(!centralPage && this.centralPageFile) { + this.centralPagePath = this.centralPageFile.path; + centralPage = this.plugin.pages.get(this.centralPageFile.path); + } + return centralPage; } /** @@ -126,7 +139,7 @@ export class Scene { } this.blockUpdateTimer = true; //blocks the updateTimer - + const settings = this.plugin.settings; const page = this.plugin.pages.get(path); if(!page) { this.blockUpdateTimer = false; @@ -141,7 +154,7 @@ export class Scene { } //abort excalibrain if the file in the Obsidian view has changed - if(!this.ea.targetView?.file || this.ea.targetView.file.path !== this.plugin.settings.excalibrainFilepath) { + if(!this.ea.targetView?.file || this.ea.targetView.file.path !== settings.excalibrainFilepath) { this.unloadScene(); return; } @@ -154,8 +167,8 @@ export class Scene { keepOnTop(this.plugin.EA); - const centralPage = this.plugin.pages.get(this.centralPagePath) - const isSameFileAsCurrent = centralPage && centralPage.path === path && isFile + const centralPage = this.getCentralPage(); + const isSameFileAsCurrent = centralPage && isFile && centralPage.file === page.file // if the file hasn't changed don't update the graph if(isSameFileAsCurrent && page.file.stat.mtime === centralPage.mtime) { @@ -163,7 +176,7 @@ export class Scene { return; //don't reload the file if it has not changed } - if(isFile && shouldOpenFile && !this.plugin.settings.embedCentralNode) { + if(isFile && shouldOpenFile && !settings.embedCentralNode) { const centralLeaf = this.getCentralLeaf(); //@ts-ignore if(!centralLeaf || !app.workspace.getLeafById(centralLeaf.id)) { @@ -176,18 +189,18 @@ export class Scene { this.addToHistory(page.path); } - if(page.isFolder && !this.plugin.settings.showFolderNodes) { - this.plugin.settings.showFolderNodes = true; + if(page.isFolder && !settings.showFolderNodes) { + settings.showFolderNodes = true; this.toolsPanel.rerender(); } - if(page.isTag && !this.plugin.settings.showTagNodes) { - this.plugin.settings.showTagNodes = true; + if(page.isTag && !settings.showTagNodes) { + settings.showTagNodes = true; this.toolsPanel.rerender(); } this.centralPagePath = path; - //await this.plugin.createIndex(); + this.centralPageFile = page.file; await this.render(isSameFileAsCurrent); } @@ -259,10 +272,19 @@ export class Scene { public async initializeScene() { this.disregardLeafChange = false; const ea = this.ea; - const style = this.plugin.settings.baseNodeStyle; + const settings = this.plugin.settings; + const style = { + ...settings.baseNodeStyle, + ...settings.centralNodeStyle, + }; + let counter = 0; ea.clear(); - ea.setView(this.leaf.view as any) + ea.setView(this.leaf.view as any); + //delete existing elements from view. The actual delete will happen when addElementsToView is called + //I delete them this way to avoid the splash screen flashing up when the scene is cleared + ea.copyViewElementsToEAforEditing(ea.getViewElements()); + ea.getElements().forEach((el: Mutable)=>el.isDeleted=true); while(!ea.targetView.excalidrawAPI && counter++<10) { await sleep(50); @@ -278,8 +300,8 @@ export class Scene { api.setMobileModeAllowed(false); //disable mobile view https://github.com/zsviczian/excalibrain/issues/9 ea.style.fontFamily = style.fontFamily; ea.style.fontSize = style.fontSize; - this.textSize = ea.measureText("m".repeat(style.maxLabelLength+3)); - this.nodeWidth = this.textSize.width + 3 * style.padding; + this.textSize = ea.measureText("m".repeat(style.maxLabelLength)); + this.nodeWidth = this.textSize.width + 2 * style.padding; if(this.plugin.settings.compactView) { this.nodeWidth = this.nodeWidth * 0.6; } @@ -296,8 +318,7 @@ export class Scene { }, theme: "light", viewBackgroundColor: this.plugin.settings.backgroundColor - }, - elements: [] + } }); } const frame2 = () => { @@ -311,13 +332,14 @@ export class Scene { ea.targetView.clearDirty(); //hack to prevent excalidraw from saving the changes } const frame3 = async () => { - if(this.plugin.settings.allowAutozoom) api.zoomToFit(null, 5, 0.15); + if(this.plugin.settings.allowAutozoom) { + api.zoomToFit(null, 5, 0.15); + } ea.targetView.linksAlwaysOpenInANewPane = true; await this.addEventHandler(); - this.historyPanel = new HistoryPanel((this.leaf.view as TextFileView).contentEl,this.plugin); + this.historyPanel = new HistoryPanel((this.leaf.view as TextFileView).contentEl.querySelector(".excalidraw"),this.plugin); new Notice("ExcaliBrain On"); } - frame1(); frame2(); frame3(); @@ -358,64 +380,66 @@ export class Scene { this.historyPanel.rerender() } if(!this.centralPagePath) return; + const settings = this.plugin.settings; + const isCompactView = settings.compactView; let centralPage = this.plugin.pages.get(this.centralPagePath); if(!centralPage) { //path case sensitivity issue this.centralPagePath = this.plugin.lowercasePathMap.get(this.centralPagePath.toLowerCase()); centralPage = this.plugin.pages.get(this.centralPagePath); if(!centralPage) return; + this.centralPageFile = centralPage.file; } const ea = this.ea; - retainCentralNode = retainCentralNode && Boolean(this.rootNode) && isEmbedFileType(centralPage.file,ea); + retainCentralNode = + retainCentralNode && Boolean(this.rootNode) && + settings.embedCentralNode && isEmbedFileType(centralPage.file,ea); this.zoomToFitOnNextBrainLeafActivate = !ea.targetView.containerEl.isShown(); ea.clear(); - const excalidrawAPI = ea.getExcalidrawAPI(); - if(!retainCentralNode) { - ea.copyViewElementsToEAforEditing(ea.getViewElements()); - ea.getElements().forEach((el: Mutable)=>el.isDeleted=true); - excalidrawAPI.updateScene({ - elements: ea.getElements(), - }); - ea.clear(); - } else { - excalidrawAPI.updateScene({ - elements:excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>this.rootNode.embeddedElementIds.includes(el.id)) - }); - } + const excalidrawAPI = ea.getExcalidrawAPI() as ExcalidrawImperativeAPI; + ea.copyViewElementsToEAforEditing(ea.getViewElements()); + //delete existing elements from view. The actual delete will happen when addElementsToView is called + //I delete them this way to avoid the splash screen flashing up when the scene is cleared + //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1248#event-9940972555 + ea.getElements() + .filter((el: ExcalidrawElement)=>!retainCentralNode || !this.rootNode.embeddedElementIds.includes(el.id)) + .forEach((el: Mutable)=>el.isDeleted=true); ea.style.verticalAlign = "middle"; + //Extract URLs as child nodes + //List nodes for the graph const parents = centralPage.getParents() .filter(x => (x.page.path !== centralPage.path) && - !this.plugin.settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && + !settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && (!x.page.primaryStyleTag || !this.toolsPanel.linkTagFilter.selectedTags.has(x.page.primaryStyleTag))) - .slice(0,this.plugin.settings.maxItemCount); + .slice(0,settings.maxItemCount); const parentPaths = parents.map(x=>x.page.path); const children =centralPage.getChildren() .filter(x => (x.page.path !== centralPage.path) && - !this.plugin.settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && + !settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && (!x.page.primaryStyleTag || !this.toolsPanel.linkTagFilter.selectedTags.has(x.page.primaryStyleTag))) - .slice(0,this.plugin.settings.maxItemCount); + .slice(0,settings.maxItemCount); - const friends = centralPage.getLeftFriends() + const friends = centralPage.getLeftFriends().concat(centralPage.getPreviousFriends()) .filter(x => (x.page.path !== centralPage.path) && - !this.plugin.settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && + !settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && (!x.page.primaryStyleTag || !this.toolsPanel.linkTagFilter.selectedTags.has(x.page.primaryStyleTag))) - .slice(0,this.plugin.settings.maxItemCount); + .slice(0,settings.maxItemCount); - const nextFriends = centralPage.getRightFriends() + const nextFriends = centralPage.getRightFriends().concat(centralPage.getNextFriends()) .filter(x => (x.page.path !== centralPage.path) && - !this.plugin.settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && + !settings.excludeFilepaths.some(p => x.page.path.startsWith(p)) && (!x.page.primaryStyleTag || !this.toolsPanel.linkTagFilter.selectedTags.has(x.page.primaryStyleTag))) - .slice(0,this.plugin.settings.maxItemCount); + .slice(0,settings.maxItemCount); const rawSiblings = centralPage .getSiblings() @@ -426,7 +450,7 @@ export class Scene { friends.some(f=>f.page.path === s.page.path) || nextFriends.some(f=>f.page.path === s.page.path) || //or not exluded via folder path in settings - this.plugin.settings.excludeFilepaths.some(p => s.page.path.startsWith(p)) + settings.excludeFilepaths.some(p => s.page.path.startsWith(p)) ) && //it is not the current central page (s.page.path !== centralPage.path)); @@ -438,7 +462,7 @@ export class Scene { s.page.getParents().map(x=>x.page.path).some(y=>parentPaths.includes(y)) && //filter based on primary tag (!s.page.primaryStyleTag || !this.toolsPanel.linkTagFilter.selectedTags.has(s.page.primaryStyleTag))) - .slice(0,this.plugin.settings.maxItemCount); + .slice(0,settings.maxItemCount); //------------------------------------------------------- // Generate layout and nodes @@ -447,20 +471,20 @@ export class Scene { this.layouts = []; const manyFriends = friends.length >= 10; const manyNextFriends = nextFriends.length >= 10; - const baseStyle = this.plugin.settings.baseNodeStyle; + const baseStyle = settings.baseNodeStyle; const siblingsCols = siblings.length >= 20 ? 3 : siblings.length >= 10 ? 2 : 1; - const childrenCols = this.plugin.settings.compactView + const childrenCols = isCompactView ? (children.length <= 12 ? [1, 1, 2, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2][children.length] : 3) : (children.length <= 12 ? [1, 1, 2, 3, 3, 3, 3, 4, 4, 5, 5, 4, 4][children.length] : 5); - const parentCols = this.plugin.settings.compactView + const parentCols = isCompactView ? (parents.length < 2 ? 1 : 2) @@ -470,12 +494,12 @@ export class Scene { const isCenterEmbedded = - this.plugin.settings.embedCentralNode && + settings.embedCentralNode && !centralPage.isVirtual && !centralPage.isFolder && !centralPage.isTag; - const centerEmbedWidth = this.plugin.settings.centerEmbedWidth; - const centerEmbedHeight = this.plugin.settings.centerEmbedHeight; + const centerEmbedWidth = settings.centerEmbedWidth; + const centerEmbedHeight = settings.centerEmbedHeight; const lCenter = new Layout({ origoX: 0, @@ -509,7 +533,6 @@ export class Scene { }); this.layouts.push(lChildren); - const isCompactView = this.plugin.settings.compactView; const friendOrigoX = isCompactView && isCenterEmbedded ? centerEmbedWidth/2 + 1.5 * this.nodeWidth : Math.max( @@ -556,7 +579,7 @@ export class Scene { }); this.layouts.push(lParents); - const siblingsStyle = this.plugin.settings.siblingNodeStyle; + const siblingsStyle = settings.siblingNodeStyle; const siblingsPadding = siblingsStyle.padding??baseStyle.padding; const siblingsLabelLength = siblingsStyle.maxLabelLength??baseStyle.maxLabelLength; ea.style.fontFamily = siblingsStyle.fontFamily; @@ -622,7 +645,7 @@ export class Scene { friendGateOnLeft: true }); - if(this.plugin.settings.renderSiblings) { + if(settings.renderSiblings) { this.addNodes({ neighbours: siblings, layout: lSiblings, @@ -648,7 +671,7 @@ export class Scene { neighbour.typeDefinition, neighbour.linkDirection, ea, - this.plugin.settings + settings ) }) } @@ -656,8 +679,10 @@ export class Scene { Array.from(this.nodesMap.values()).forEach(nodeA => { addLinks(nodeA, nodeA.page.getChildren(),Role.CHILD); addLinks(nodeA, nodeA.page.getParents(),Role.PARENT); - addLinks(nodeA, nodeA.page.getLeftFriends(),Role.FRIEND); - addLinks(nodeA, nodeA.page.getRightFriends(),Role.NEXT); + addLinks(nodeA, nodeA.page.getLeftFriends(),Role.LEFT); + addLinks(nodeA, nodeA.page.getPreviousFriends(),Role.LEFT); + addLinks(nodeA, nodeA.page.getRightFriends(),Role.RIGHT); + addLinks(nodeA, nodeA.page.getNextFriends(),Role.RIGHT); }); //------------------------------------------------------- @@ -682,11 +707,13 @@ export class Scene { ea.addElementsToView(false,false); ea.targetView.clearDirty(); //hack to prevent excalidraw from saving the changes - ea.getExcalidrawAPI().updateScene({appState: {viewBackgroundColor: this.plugin.settings.backgroundColor}}); - if(this.plugin.settings.allowAutozoom) ea.getExcalidrawAPI().zoomToFit(null,5,0.15); + excalidrawAPI.updateScene({appState: {viewBackgroundColor: settings.backgroundColor}}); + if(settings.allowAutozoom) { + setTimeout(()=>excalidrawAPI.zoomToFit(ea.getViewElements(),5,0.15)); + } this.toolsPanel.rerender(); - if(this.focusSearchAfterInitiation && this.plugin.settings.allowAutofocuOnSearch) { + if(this.focusSearchAfterInitiation && settings.allowAutofocuOnSearch) { this.toolsPanel.searchElement.focus(); this.focusSearchAfterInitiation = false; } @@ -695,103 +722,105 @@ export class Scene { } public isCentralLeafStillThere():boolean { + const settings = this.plugin.settings; //@ts-ignore const noCentralLeaf = app.workspace.getLeafById(this.centralLeaf.id) === null ; if(noCentralLeaf) { return false; } //@ts-ignore - if (this.centralLeaf.view?.file?.path === this.plugin.settings.excalibrainFilepath) { + if (this.centralLeaf.view?.file?.path === settings.excalibrainFilepath) { return false; } return true; } - private async addEventHandler() { - const self = this; - - const brainEventHandler = async (leaf:WorkspaceLeaf, startup:boolean = false) => { - if(this.disregardLeafChange) { - return; - } - if(!startup && self.plugin.settings.embedCentralNode) { - return; - } + private async brainEventHandler (leaf:WorkspaceLeaf, startup:boolean = false) { + if(this.disregardLeafChange) { + return; + } + const settings = this.plugin.settings; + if(!startup && settings.embedCentralNode) { + return; + } - self.blockUpdateTimer = true; - //await self.plugin.createIndex(); - await sleep(100); + this.blockUpdateTimer = true; + await sleep(100); - //------------------------------------------------------- - //terminate event handler if view no longer exists or file has changed + //------------------------------------------------------- + //terminate event handler if view no longer exists or file has changed - if(this.pinLeaf && !this.isCentralLeafStillThere()) { - this.pinLeaf = false; - this.toolsPanel.rerender(); - } + if(this.pinLeaf && !this.isCentralLeafStillThere()) { + this.pinLeaf = false; + this.toolsPanel.rerender(); + } - if(this.pinLeaf && leaf !== this.centralLeaf) return; + if(this.pinLeaf && leaf !== this.centralLeaf) return; - if(!self.ea.targetView?.file || self.ea.targetView.file.path !== self.plugin.settings.excalibrainFilepath) { - self.unloadScene(); - return; - } - - if(!(leaf?.view && (leaf.view instanceof FileView) && leaf.view.file)) { - self.blockUpdateTimer = false; - return; - } - - const rootFile = leaf.view.file; - - if (rootFile.path === self.ea.targetView.file.path) { //brainview drawing is the active leaf - if(this.vaultFileChanged) { - this.zoomToFitOnNextBrainLeafActivate = false; - await this.reRender(true); - } - if(this.zoomToFitOnNextBrainLeafActivate) { - this.zoomToFitOnNextBrainLeafActivate = false; - if(self.plugin.settings.allowAutozoom) self.ea.getExcalidrawAPI().zoomToFit(null, 5, 0.15); - } - self.blockUpdateTimer = false; - return; - } + if(!this.ea.targetView?.file || this.ea.targetView.file.path !== settings.excalibrainFilepath) { + this.unloadScene(); + return; + } - const centralPage = self.plugin.pages.get(self.centralPagePath); - if( - centralPage && - centralPage.path === rootFile.path && - rootFile.stat.mtime === centralPage.mtime - ) { - self.blockUpdateTimer = false; - return; //don't reload the file if it has not changed - } + if(!(leaf?.view && (leaf.view instanceof FileView) && leaf.view.file)) { + this.blockUpdateTimer = false; + return; + } - if(!self.plugin.pages.get(rootFile.path)) { - await self.plugin.createIndex(); + const rootFile = leaf.view.file; + + if (rootFile.path === this.ea.targetView.file.path) { //brainview drawing is the active leaf + if(this.vaultFileChanged) { + this.zoomToFitOnNextBrainLeafActivate = false; + await this.reRender(true); + } + if(this.zoomToFitOnNextBrainLeafActivate) { + this.zoomToFitOnNextBrainLeafActivate = false; + if(settings.allowAutozoom) { + this.ea.getExcalidrawAPI().zoomToFit(null, 5, 0.15); + } } + this.blockUpdateTimer = false; + return; + } - this.addToHistory(rootFile.path); - self.centralPagePath = rootFile.path; - self.centralLeaf = leaf; + const centralPage = this.getCentralPage(); + if( + centralPage && + centralPage.path === rootFile.path && + rootFile.stat.mtime === centralPage.mtime + ) { + this.blockUpdateTimer = false; + return; //don't reload the file if it has not changed + } - self.render(); + if(!this.plugin.pages.get(rootFile.path)) { + await this.plugin.createIndex(); } + this.addToHistory(rootFile.path); + this.centralPagePath = rootFile.path; + this.centralPageFile = rootFile; + this.centralLeaf = leaf; + this.render(); + } + + private async addEventHandler() { const fileChangeHandler = () => { this.vaultFileChanged = true; } - app.workspace.on("active-leaf-change", brainEventHandler); - this.removeEH = () => app.workspace.off("active-leaf-change",brainEventHandler); + const beh = (leaf:WorkspaceLeaf)=>this.brainEventHandler(leaf); + this.app.workspace.on("active-leaf-change", beh); + this.removeEH = () => app.workspace.off("active-leaf-change",beh); this.setTimer(); - app.vault.on("rename",fileChangeHandler); + this.app.vault.on("rename",fileChangeHandler); this.removeOnRename = () => app.vault.off("rename",fileChangeHandler) - app.vault.on("modify",fileChangeHandler); + this.app.vault.on("modify",fileChangeHandler); this.removeOnModify = () => app.vault.off("modify",fileChangeHandler) - app.vault.on("create",fileChangeHandler); + this.app.vault.on("create",fileChangeHandler); this.removeOnCreate = () => app.vault.off("create",fileChangeHandler) - app.vault.on("delete",fileChangeHandler); + this.app.vault.on("delete",fileChangeHandler); this.removeOnDelete = () => app.vault.off("delete",fileChangeHandler) const leaves: WorkspaceLeaf[] = []; @@ -813,7 +842,7 @@ export class Scene { } } keepOnTop(this.plugin.EA); - brainEventHandler(leafToOpen, true); + this.brainEventHandler(leafToOpen, true); } else { if(this.plugin.navigationHistory.length>0) { const lastFilePath = this.plugin.navigationHistory.last(); @@ -831,11 +860,13 @@ export class Scene { this.vaultFileChanged = false; await this.plugin.createIndex(); if(this.centralPagePath) { - if(!this.plugin.pages.get(this.centralPagePath)) { + const centralPage = this.getCentralPage(); + if(!centralPage) { //@ts-ignore if(this.centralLeaf && this.centralLeaf.view && this.centralLeaf.view.file) { //@ts-ignore - this.centralPagePath = this.centralLeaf.view.file.path; + this.centralPageFile = this.centralLeaf.view.file; + this.centralPagePath = this.centralPageFile.path; } } } @@ -919,6 +950,7 @@ export class Scene { this.leaf = undefined; this.centralLeaf = undefined; this.centralPagePath = undefined; + this.centralPageFile = undefined; this.terminated = true; //@ts-ignore if(!this.app.plugins.plugins["obsidian-excalidraw-plugin"]) { diff --git a/src/Settings.ts b/src/Settings.ts index b6312ca..1a75e39 100644 --- a/src/Settings.ts +++ b/src/Settings.ts @@ -33,6 +33,7 @@ export interface ExcaliBrainSettings { excludeFilepaths: string[]; showInferredNodes: boolean; showAttachments: boolean; + showURLs: boolean; showVirtualNodes: boolean; showFolderNodes: boolean; showTagNodes: boolean; @@ -88,6 +89,7 @@ export const DEFAULT_SETTINGS: ExcaliBrainSettings = { excludeFilepaths: [], showInferredNodes: true, showAttachments: true, + showURLs: true, showVirtualNodes: true, showFolderNodes: false, showTagNodes: false, @@ -282,10 +284,10 @@ export class ExcaliBrainSettingTab extends PluginSettingTab { let role:Role = Role.CHILD; if(hierarchy.leftFriends.contains(this.demoLinkStyle.display)) { demoNode2.setCenter({x:-300,y:0}); - role = Role.FRIEND; + role = Role.LEFT; } else if(hierarchy.rightFriends.contains(this.demoLinkStyle.display)) { demoNode2.setCenter({x:300,y:0}); - role = Role.NEXT; + role = Role.RIGHT; } else if(hierarchy.parents.contains(this.demoLinkStyle.display)) { demoNode2.setCenter({x:0,y:-150}); role = Role.PARENT diff --git a/src/Suggesters/Suggest.ts b/src/Suggesters/Suggest.ts index a92d3cf..9f4e514 100644 --- a/src/Suggesters/Suggest.ts +++ b/src/Suggesters/Suggest.ts @@ -124,6 +124,7 @@ export abstract class TextInputSuggest implements ISuggestOwner { this.scope = new Scope(); this.suggestEl = containerEl.createDiv("suggestion-container"); + this.suggestEl.style.left = "-1000px"; const suggestion = this.suggestEl.createDiv("suggestion"); this.suggest = new Suggest(this, suggestion, this.scope); diff --git a/src/Types.ts b/src/Types.ts index 8ba9f15..2cb3ee9 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,5 +1,5 @@ import { FillStyle, StrokeRoundness, StrokeStyle } from "obsidian-excalidraw-plugin"; -import { Arrowhead as ExcalidrawArrowHead} from "@zsviczian/excalidraw/types/element/types"; +import { Arrowhead as ExcalidrawArrowHead } from "@zsviczian/excalidraw/types/element/types"; import { Page } from "./graph/Page"; export enum RelationType { @@ -10,8 +10,8 @@ export enum RelationType { export enum Role { PARENT, CHILD, - FRIEND, - NEXT, + LEFT, + RIGHT, } export enum LinkDirection { @@ -32,11 +32,17 @@ export type Relation = { childType?: RelationType; childTypeDefinition?: string; isLeftFriend: boolean; - friendType?: RelationType; - friendTypeDefinition?: string; + leftFriendType?: RelationType; + leftFriendTypeDefinition?: string; isRightFriend: boolean; + rightFriendType?: RelationType; + rightFriendTypeDefinition?: string; + isNextFriend: boolean; nextFriendType?: RelationType; nextFriendTypeDefinition?: string; + isPreviousFriend: boolean; + previousFriendType?: RelationType; + previousFriendTypeDefinition?: string; } export type Hierarchy = { diff --git a/src/graph/Link.ts b/src/graph/Link.ts index 4ef7e42..071cca6 100644 --- a/src/graph/Link.ts +++ b/src/graph/Link.ts @@ -75,7 +75,7 @@ export class Link { gateAId = this.nodeA.parentGateId; gateBId = this.nodeB.childGateId; break; - case Role.NEXT: + case Role.RIGHT: gateAId = this.nodeA.nextFriendGateId; gateBId = this.nodeB.nextFriendGateId; break; diff --git a/src/graph/Links.ts b/src/graph/Links.ts index e7f5303..728a495 100644 --- a/src/graph/Links.ts +++ b/src/graph/Links.ts @@ -37,8 +37,8 @@ export class Links { ? nodeA : nodeB, linkDirection===LinkDirection.FROM - ? nodeBRole === Role.FRIEND || nodeBRole === Role.NEXT - ? nodeBRole === Role.FRIEND ? Role.FRIEND : Role.NEXT + ? nodeBRole === Role.LEFT || nodeBRole === Role.RIGHT + ? nodeBRole === Role.LEFT ? Role.LEFT : Role.RIGHT : nodeBRole === Role.CHILD ? Role.PARENT : Role.CHILD diff --git a/src/graph/Node.ts b/src/graph/Node.ts index e0b8a5c..eb9f649 100644 --- a/src/graph/Node.ts +++ b/src/graph/Node.ts @@ -179,6 +179,7 @@ export class Node { async render() { const ea = this.ea; + const settings = this.settings; const gateDiameter = this.style.gateRadius*2; ea.style.fontSize = this.style.fontSize; @@ -202,9 +203,14 @@ export class Node { ea.style.strokeColor = this.style.gateStrokeColor; ea.style.strokeStyle = "solid"; - const leftFriendCount = this.isCentral - ? this.page.leftFriendCount() - : this.page.leftFriendCount() + this.page.rightFriendCount(); + const previousFriendCount = this.friendGateOnLeft + ? this.page.previousFriendCount() + : this.page.nextFriendCount(); + const nextFriendCount = this.friendGateOnLeft + ? this.page.nextFriendCount() + : this.page.previousFriendCount(); + + const leftFriendCount = this.page.leftFriendCount() + previousFriendCount; ea.style.backgroundColor = leftFriendCount > 0 ? this.style.gateBackgroundColor : "transparent"; @@ -218,7 +224,7 @@ export class Node { ); const neighborCountLabelIds = []; - if(this.settings.showNeighborCount && leftFriendCount>0) { + if(settings.showNeighborCount && leftFriendCount>0) { ea.style.fontSize = gateDiameter; neighborCountLabelIds.push(ea.addText( this.friendGateOnLeft @@ -233,35 +239,35 @@ export class Node { )); } - if(this.isCentral) { - const rightFriendCount = this.page.rightFriendCount(); - ea.style.backgroundColor = rightFriendCount > 0 - ? this.style.gateBackgroundColor - : "transparent"; - this.nextFriendGateId = ea.addEllipse( + const rightFriendCount = this.page.rightFriendCount() + nextFriendCount; + ea.style.backgroundColor = rightFriendCount > 0 + ? this.style.gateBackgroundColor + : "transparent"; + this.nextFriendGateId = ea.addEllipse( + !this.friendGateOnLeft + ? this.center.x - gateDiameter - this.style.padding - labelSize.width / 2 + : this.center.x + this.style.padding + labelSize.width / 2, + this.center.y - this.style.gateRadius, + gateDiameter, + gateDiameter + ); + + if(settings.showNeighborCount && rightFriendCount>0) { + ea.style.fontSize = gateDiameter; + neighborCountLabelIds.push(ea.addText( !this.friendGateOnLeft - ? this.center.x - gateDiameter - this.style.padding - labelSize.width / 2 - : this.center.x + this.style.padding + labelSize.width / 2, - this.center.y - this.style.gateRadius, - gateDiameter, - gateDiameter - ); + ? rightFriendCount>9 + ? this.center.x - 2*gateDiameter - this.style.padding - labelSize.width / 2 + : this.center.x - gateDiameter - this.style.padding - labelSize.width / 2 + : this.center.x + this.style.padding + labelSize.width / 2, + !this.friendGateOnLeft + ? this.center.y - 2*gateDiameter + : this.center.y - this.style.gateRadius + gateDiameter, + rightFriendCount.toString() + )); + } - if(this.settings.showNeighborCount && rightFriendCount>0) { - ea.style.fontSize = gateDiameter; - neighborCountLabelIds.push(ea.addText( - !this.friendGateOnLeft - ? rightFriendCount>9 - ? this.center.x - 2*gateDiameter - this.style.padding - labelSize.width / 2 - : this.center.x - gateDiameter - this.style.padding - labelSize.width / 2 - : this.center.x + this.style.padding + labelSize.width / 2, - !this.friendGateOnLeft - ? this.center.y - 2*gateDiameter - : this.center.y - this.style.gateRadius + gateDiameter, - rightFriendCount.toString() - )); - } - } else { + if(!this.isCentral) { this.nextFriendGateId = this.friendGateId; } @@ -275,7 +281,7 @@ export class Node { gateDiameter, gateDiameter ); - if(this.settings.showNeighborCount && parentCount>0) { + if(settings.showNeighborCount && parentCount>0) { ea.style.fontSize = gateDiameter; neighborCountLabelIds.push(ea.addText( this.center.x + gateDiameter - this.style.gateOffset, @@ -294,7 +300,7 @@ export class Node { gateDiameter, gateDiameter ); - if(this.settings.showNeighborCount && childrenCount>0) { + if(settings.showNeighborCount && childrenCount>0) { ea.style.fontSize = gateDiameter; neighborCountLabelIds.push(ea.addText( this.center.x + gateDiameter + this.style.gateOffset, diff --git a/src/graph/Page.ts b/src/graph/Page.ts index a6df40a..2525b82 100644 --- a/src/graph/Page.ts +++ b/src/graph/Page.ts @@ -13,6 +13,8 @@ const DEFAULT_RELATION:Relation = { isChild: false, isLeftFriend: false, isRightFriend: false, + isNextFriend: false, + isPreviousFriend: false, direction: null } @@ -174,8 +176,8 @@ export class Page { //log(`Unexpected: ${this.file.path} references ${item.link} in DV, but it was not found in app.metadataCache. The page was skipped.`); //return; } - this.addLeftFriend(referencedPage,RelationType.DEFINED,LinkDirection.TO,item.field); - referencedPage.addRightFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addPreviousFriend(referencedPage,RelationType.DEFINED,LinkDirection.TO,item.field); + referencedPage.addNextFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); }); const nextFields = this.plugin.hierarchyLowerCase.next; @@ -186,8 +188,8 @@ export class Page { //log(`Unexpected: ${this.file.path} references ${item.link} in DV, but it was not found in app.metadataCache. The page was skipped.`); //return; } - this.addRightFriend(referencedPage,RelationType.DEFINED,LinkDirection.TO,item.field); - referencedPage.addLeftFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addNextFriend(referencedPage,RelationType.DEFINED,LinkDirection.TO,item.field); + referencedPage.addPreviousFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); }); } @@ -231,6 +233,8 @@ export class Page { cd: boolean, lfd: boolean,//left friend defined rfd: boolean //right friend defined + pfd: boolean //previous defined + nfd: boolean //next defined } { return { pi: r.isParent && r.parentType === RelationType.INFERRED, @@ -239,7 +243,9 @@ export class Page { cd: r.isChild && r.childType === RelationType.DEFINED, lfd: (!this.plugin.settings.inferAllLinksAsFriends && r.isLeftFriend) || (this.plugin.settings.inferAllLinksAsFriends && r.isLeftFriend && !(r.parentType === RelationType.DEFINED || r.childType === RelationType.DEFINED)), - rfd: r.isRightFriend && r.nextFriendType === RelationType.DEFINED, + rfd: r.isRightFriend && (r.rightFriendType === RelationType.DEFINED), + pfd: r.isPreviousFriend && (r.previousFriendType === RelationType.DEFINED), + nfd: r.isNextFriend && (r.nextFriendType === RelationType.DEFINED), } } @@ -328,9 +334,9 @@ export class Page { const neighbour = this.neighbours.get(page.path); if(neighbour) { neighbour.isLeftFriend = true; - neighbour.friendType = relationTypeToSet(neighbour.friendType,relationType); - if(definition && !neighbour.friendTypeDefinition?.contains(definition)) { - neighbour.friendTypeDefinition = concat(definition,neighbour.friendTypeDefinition); + neighbour.leftFriendType = relationTypeToSet(neighbour.leftFriendType,relationType); + if(definition && !neighbour.leftFriendTypeDefinition?.contains(definition)) { + neighbour.leftFriendTypeDefinition = concat(definition,neighbour.leftFriendTypeDefinition); } neighbour.direction = directionToSet(neighbour.direction, direction); return; @@ -339,8 +345,8 @@ export class Page { ...DEFAULT_RELATION, target: page, isLeftFriend: true, - friendType: relationType, - friendTypeDefinition: definition, + leftFriendType: relationType, + leftFriendTypeDefinition: definition, direction }); } @@ -352,6 +358,30 @@ export class Page { const neighbour = this.neighbours.get(page.path); if(neighbour) { neighbour.isRightFriend = true; + neighbour.rightFriendType = relationTypeToSet(neighbour.rightFriendType,relationType); + if(definition && !neighbour.rightFriendTypeDefinition?.contains(definition)) { + neighbour.rightFriendTypeDefinition = concat(definition,neighbour.rightFriendTypeDefinition); + } + neighbour.direction = directionToSet(neighbour.direction, direction); + return; + } + this.neighbours.set(page.path, { + ...DEFAULT_RELATION, + target: page, + isRightFriend: true, + rightFriendType: relationType, + rightFriendTypeDefinition: definition, + direction + }); + } + + addNextFriend(page: Page, relationType:RelationType, direction: LinkDirection, definition?: string) { + if(page.path === this.plugin.settings.excalibrainFilepath || page.path === this.path) { + return; + }; + const neighbour = this.neighbours.get(page.path); + if(neighbour) { + neighbour.isNextFriend = true; neighbour.nextFriendType = relationTypeToSet(neighbour.nextFriendType,relationType); if(definition && !neighbour.nextFriendTypeDefinition?.contains(definition)) { neighbour.nextFriendTypeDefinition = concat(definition,neighbour.nextFriendTypeDefinition); @@ -362,12 +392,36 @@ export class Page { this.neighbours.set(page.path, { ...DEFAULT_RELATION, target: page, - isRightFriend: true, + isNextFriend: true, nextFriendType: relationType, nextFriendTypeDefinition: definition, direction }); } + + addPreviousFriend(page: Page, relationType:RelationType, direction: LinkDirection, definition?: string) { + if(page.path === this.plugin.settings.excalibrainFilepath || page.path === this.path) { + return; + }; + const neighbour = this.neighbours.get(page.path); + if(neighbour) { + neighbour.isPreviousFriend = true; + neighbour.previousFriendType = relationTypeToSet(neighbour.previousFriendType,relationType); + if(definition && !neighbour.previousFriendTypeDefinition?.contains(definition)) { + neighbour.previousFriendTypeDefinition = concat(definition,neighbour.previousFriendTypeDefinition); + } + neighbour.direction = directionToSet(neighbour.direction, direction); + return; + } + this.neighbours.set(page.path, { + ...DEFAULT_RELATION, + target: page, + isPreviousFriend: true, + previousFriendType: relationType, + previousFriendTypeDefinition: definition, + direction + }); + } unlinkNeighbour(pagePath: string) { this.neighbours.delete(pagePath); @@ -376,11 +430,11 @@ export class Page { //----------------------------------------------- //see: getRelationLogic.excalidraw //----------------------------------------------- - isChild = (relation: Relation):RelationType => { - const {pi,pd,ci,cd,lfd, rfd} = this.getRelationVector(relation); - return (cd && !pd && !lfd && !rfd) + isChild (relation: Relation):RelationType { + const { pi,pd,ci,cd,lfd, rfd, nfd, pfd } = this.getRelationVector(relation); + return (cd && !pd && !lfd && !rfd && !nfd && !pfd) ? RelationType.DEFINED - : (!pi && !pd && ci && !cd && !lfd && !rfd) + : (!pi && !pd && ci && !cd && !lfd && !rfd && !nfd && !pfd) ? RelationType.INFERRED : null; }; @@ -417,10 +471,10 @@ export class Page { } isParent (relation: Relation):RelationType { - const {pi,pd,ci,cd,lfd, rfd} = this.getRelationVector(relation); - return (!cd && pd && !lfd && !rfd) + const { pi,pd,ci,cd,lfd, rfd, nfd, pfd } = this.getRelationVector(relation); + return (!cd && pd && !lfd && !rfd && !nfd && !pfd) ? RelationType.DEFINED - : (pi && !pd && !ci && !cd && !lfd && !rfd) + : (pi && !pd && !ci && !cd && !lfd && !rfd && !nfd && !pfd) ? RelationType.INFERRED : null; } @@ -458,10 +512,11 @@ export class Page { } isLeftFriend (relation: Relation):RelationType { - const {pi,pd,ci,cd,lfd, rfd} = this.getRelationVector(relation); + const { pi,pd,ci,cd,lfd, rfd, nfd, pfd } = this.getRelationVector(relation); let res = lfd ? RelationType.DEFINED - : (pi && !pd && ci && !cd && !lfd && !rfd) + : (pi && !pd && ci && !cd && !lfd && !rfd && !nfd && !pfd) || + [pd, cd, lfd, rfd, nfd, pfd].filter(Boolean).length >= 2 ? RelationType.INFERRED : null; return res; @@ -494,13 +549,13 @@ export class Page { .map(x => { return { page: x[1].target, - relationType: x[1].friendType ?? + relationType: x[1].leftFriendType ?? ((x[1].parentType === RelationType.DEFINED && x[1].childType === RelationType.DEFINED) //case H ? RelationType.DEFINED //case I : RelationType.INFERRED), - typeDefinition: x[1].friendTypeDefinition, + typeDefinition: x[1].leftFriendTypeDefinition, linkDirection: x[1].direction } });//.sort @@ -508,8 +563,8 @@ export class Page { } isRightFriend (relation: Relation):RelationType { - const {pi,pd,ci,cd,lfd, rfd} = this.getRelationVector(relation); - return rfd + const { pd,cd,lfd, rfd, nfd, pfd } = this.getRelationVector(relation); + return !pd && !cd && !lfd && rfd && !nfd && !pfd ? RelationType.DEFINED : null; } @@ -540,48 +595,94 @@ export class Page { return { page: x[1].target, relationType: RelationType.DEFINED, - typeDefinition: x[1].nextFriendTypeDefinition, + typeDefinition: x[1].rightFriendTypeDefinition, linkDirection: x[1].direction } });//.sort } - - getRelationToPage(otherPage:Page):null|{ - type: "nextFriend" | "leftFriend" | "parent" | "child", - relationType: RelationType; - typeDefinition: string, - } { - const relation = this.neighbours.get(otherPage.path) - if(!relation) { - return null; - } - if(this.isChild(relation)) { - return { - type: "child", - relationType: relation.childType, - typeDefinition: relation.childTypeDefinition - } - } - if(this.isParent(relation)) { + isPreviousFriend (relation: Relation):RelationType { + const { pd,cd,lfd, rfd, nfd, pfd } = this.getRelationVector(relation); + let res = !pd && !cd && !lfd && !rfd && pfd && !nfd + ? RelationType.DEFINED + : null; + return res; + } + + previousFriendCount():number { + const count = this.getNeighbours() + .reduce((prev,x) => { + const rt = this.isPreviousFriend(x[1]); + return prev + ((rt && this.plugin.settings.showInferredNodes) || (rt === RelationType.DEFINED) ? 1:0); + },0); + return count; + } + + hasPreviousFriends():boolean { + const hasPreviousFriends = this.getNeighbours() + .some(x => { + const rt = this.isPreviousFriend(x[1]); + return (rt && this.plugin.settings.showInferredNodes) || (rt === RelationType.DEFINED); + }) + return hasPreviousFriends; + } + + getPreviousFriends():Neighbour[] { + return this.getNeighbours() + .filter(x => { + const rt = this.isPreviousFriend(x[1]); + return (rt && this.plugin.settings.showInferredNodes) || (rt === RelationType.DEFINED); + }) + .map(x => { return { - type: "parent", - relationType: relation.parentType, - typeDefinition: relation.parentTypeDefinition + page: x[1].target, + relationType: RelationType.DEFINED, + typeDefinition: x[1].previousFriendTypeDefinition, + linkDirection: x[1].direction } - } - if(this.isRightFriend(relation)) { + });//.sort + } + + isNextFriend (relation: Relation):RelationType { + const { pd,cd,lfd, rfd, nfd, pfd } = this.getRelationVector(relation); + let res = !pd && !cd && !lfd && !rfd && !pfd && nfd + ? RelationType.DEFINED + : null; + return res; + } + + nextFriendCount():number { + const count = this.getNeighbours() + .reduce((prev,x) => { + const rt = this.isNextFriend(x[1]); + return prev + ((rt && this.plugin.settings.showInferredNodes) || (rt === RelationType.DEFINED) ? 1:0); + },0); + return count; + } + + hasNextFriends():boolean { + const hasPreviousFriends = this.getNeighbours() + .some(x => { + const rt = this.isNextFriend(x[1]); + return (rt && this.plugin.settings.showInferredNodes) || (rt === RelationType.DEFINED); + }) + return hasPreviousFriends; + } + + getNextFriends():Neighbour[] { + return this.getNeighbours() + .filter(x => { + const rt = this.isNextFriend(x[1]); + return (rt && this.plugin.settings.showInferredNodes) || (rt === RelationType.DEFINED); + }) + .map(x => { return { - type: "nextFriend", - relationType: relation.nextFriendType, - typeDefinition: relation.nextFriendTypeDefinition + page: x[1].target, + relationType: RelationType.DEFINED, + typeDefinition: x[1].nextFriendTypeDefinition, + linkDirection: x[1].direction } - } - return { - type: "leftFriend", - relationType: relation.friendType, - typeDefinition: relation.friendTypeDefinition - } + });//.sort } getSiblings():Neighbour[] { diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 2ea12a2..f735570 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -184,6 +184,7 @@ export default { SHOW_HIDE_ALIAS: "Show/Hide document alias", SHOW_HIDE_SIBLINGS: "Show/Hide siblings", SHOW_HIDE_EMBEDDEDCENTRAL: "Display central node as embedded frame", + SHOW_HIDE_URLS: "Show/Hide URLs in central notes as graph nodes", SHOW_HIDE_FOLDER: "Show/Hide folder nodes", SHOW_HIDE_TAG: "Show/Hide tag nodes", SHOW_HIDE_PAGES: "Show/Hide page nodes (incl. defined, inferred, virtual and attachments)", diff --git a/src/utils/ParseURLs.ts b/src/utils/ParseURLs.ts new file mode 100644 index 0000000..f369530 --- /dev/null +++ b/src/utils/ParseURLs.ts @@ -0,0 +1,23 @@ +export function extractURLs(input: string): { url: string; alias: string }[] { + const urlRegex = /\[([^\]]+)\]\((https?:\/\/\S+)\)/g; // [alias](url) markdown link format + const plainTextUrlRegex = /(?:^|\s)(https?:\/\/\S+)/g; // Plain text URL format + + const urls: { url: string; alias: string }[] = []; + + // Extract URLs from [alias](url) markdown links + let match; + while ((match = urlRegex.exec(input))) { + const alias = match[1]; + const url = match[2]; + urls.push({ url, alias }); + } + + // Extract plain text URLs + while ((match = plainTextUrlRegex.exec(input))) { + const alias = match[1]; // Alias is the URL itself for plain text URLs + const url = match[1]; + urls.push({ url, alias }); + } + + return urls; +} \ No newline at end of file diff --git a/styles.css b/styles.css index 4f3acdb..be8e9b7 100644 --- a/styles.css +++ b/styles.css @@ -86,11 +86,11 @@ } .excalibrain-button.off { - background-color: var(--interactive-normal); + background-color: var(--island-bg-color); } .excalibrain-button.on { - background-color: var(--interactive-accent); + background-color: var(--color-primary-darker); } .excalibrain-searchinput { @@ -181,7 +181,7 @@ position:relative; width:100%; height:30px; - background:var(--background-modifier-form-field); + background: var(--island-bg-color); /*var(--background-modifier-form-field);*/ border:1px solid var(--background-modifier-border); display:flex; align-items:center;