diff --git a/manifest.json b/manifest.json index 20159c6..51dd371 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,10 @@ { "id": "excalibrain", "name": "ExcaliBrain", - "version": "0.2.5", + "version": "0.2.6", "minAppVersion": "1.1.6", "description": "A clean, intuitive and editable graph view for Obsidian", "author": "Zsolt Viczian", "authorUrl": "https://zsolt.blog", - "fundingUrl": "https://ko-fi.com/zsolt", "isDesktopOnly": false } diff --git a/src/Components/AddToOntologyModal.ts b/src/Components/AddToOntologyModal.ts new file mode 100644 index 0000000..5093379 --- /dev/null +++ b/src/Components/AddToOntologyModal.ts @@ -0,0 +1,205 @@ +import { create } from "domain"; +import { App, Modal, Notice, Setting } from "obsidian"; +import { createBinaryOps } from "obsidian-dataview/lib/expression/binaryop"; +import ExcaliBrain from "src/excalibrain-main"; +import { t } from "src/lang/helpers"; + +export enum Ontology { + Parent = "parent", + Child = "child", + LeftFriend = "leftFriend", + RightFriend = "rightFriend", + Previous = "previous", + Next = "next", +} + +export class AddToOntologyModal extends Modal { + private ontology:Ontology|null = null; + private fieldName:string; + constructor( + app: App, + private plugin: ExcaliBrain, + ) { + super(app); + } + + private getCurrentOntology():Ontology|null { + const { settings } = this.plugin; + const field = this.fieldName; + + if(settings.hierarchy.parents.includes(field)) { + return Ontology.Parent; + } + if(settings.hierarchy.children.includes(field)) { + return Ontology.Child; + } + if(settings.hierarchy.leftFriends.includes(field)) { + return Ontology.LeftFriend; + } + if(settings.hierarchy.rightFriends.includes(field)) { + return Ontology.RightFriend; + } + if(settings.hierarchy.previous.includes(field)) { + return Ontology.Previous; + } + if(settings.hierarchy.next.includes(field)) { + return Ontology.Next; + } + + return null; + } + + private async setOntology(ontology:Ontology) { + if(this.ontology === ontology) return; + const { settings } = this.plugin; + const plugin = this.plugin; + + //remove from current ontology + switch(this.ontology) { + case Ontology.Parent: + settings.hierarchy.parents = settings.hierarchy.parents.filter(f=>f!==this.fieldName); + plugin.hierarchyLowerCase.parents = []; + settings.hierarchy.parents.forEach(f=>plugin.hierarchyLowerCase.parents.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.Child: + settings.hierarchy.children = settings.hierarchy.children.filter(f=>f!==this.fieldName); + plugin.hierarchyLowerCase.children = []; + settings.hierarchy.children.forEach(f=>plugin.hierarchyLowerCase.children.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.LeftFriend: + settings.hierarchy.leftFriends = settings.hierarchy.leftFriends.filter(f=>f!==this.fieldName); + plugin.hierarchyLowerCase.leftFriends = []; + settings.hierarchy.leftFriends.forEach(f=>plugin.hierarchyLowerCase.leftFriends.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.RightFriend: + settings.hierarchy.rightFriends = settings.hierarchy.rightFriends.filter(f=>f!==this.fieldName); + plugin.hierarchyLowerCase.rightFriends = []; + settings.hierarchy.rightFriends.forEach(f=>plugin.hierarchyLowerCase.rightFriends.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.Previous: + settings.hierarchy.previous = settings.hierarchy.previous.filter(f=>f!==this.fieldName); + plugin.hierarchyLowerCase.previous = []; + settings.hierarchy.previous.forEach(f=>plugin.hierarchyLowerCase.previous.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.Next: + settings.hierarchy.next = settings.hierarchy.next.filter(f=>f!==this.fieldName); + plugin.hierarchyLowerCase.next = []; + settings.hierarchy.next.forEach(f=>plugin.hierarchyLowerCase.next.push(f.toLowerCase().replaceAll(" ","-"))); + break; + } + + //add to new ontology + switch(ontology) { + case Ontology.Parent: + settings.hierarchy.parents.push(this.fieldName); + settings.hierarchy.parents = settings.hierarchy.parents.sort((a,b)=>a.toLowerCase()plugin.hierarchyLowerCase.parents.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.Child: + settings.hierarchy.children.push(this.fieldName); + settings.hierarchy.children = settings.hierarchy.children.sort((a,b)=>a.toLowerCase()plugin.hierarchyLowerCase.children.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.LeftFriend: + settings.hierarchy.leftFriends.push(this.fieldName); + settings.hierarchy.leftFriends = settings.hierarchy.leftFriends.sort((a,b)=>a.toLowerCase()plugin.hierarchyLowerCase.leftFriends.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.RightFriend: + settings.hierarchy.rightFriends.push(this.fieldName); + settings.hierarchy.rightFriends = settings.hierarchy.rightFriends.sort((a,b)=>a.toLowerCase()plugin.hierarchyLowerCase.rightFriends.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.Previous: + settings.hierarchy.previous.push(this.fieldName); + settings.hierarchy.previous = settings.hierarchy.previous.sort((a,b)=>a.toLowerCase()plugin.hierarchyLowerCase.previous.push(f.toLowerCase().replaceAll(" ","-"))); + break; + case Ontology.Next: + settings.hierarchy.next.push(this.fieldName); + settings.hierarchy.next = settings.hierarchy.next.sort((a,b)=>a.toLowerCase()plugin.hierarchyLowerCase.next.push(f.toLowerCase().replaceAll(" ","-"))); + break; + } + await this.plugin.saveSettings(); + if (plugin.scene && !plugin.scene.terminated) { + plugin.scene.vaultFileChanged = true; + } + new Notice(`Added ${this.fieldName} as ${ontology}`); + this.fieldName = null; + this.close(); + } + + async show( fieldName:string ) { + await this.plugin.loadSettings(); + this.fieldName = fieldName; + this.ontology = this.getCurrentOntology(); + this.open(); + } + + public async addFieldToOntology(ontology:Ontology, fieldName:string) { + await this.plugin.loadSettings(); + this.fieldName = fieldName; + this.ontology = this.getCurrentOntology(); + await this.setOntology(ontology); + this.fieldName = null; + } + + open () { + if(!this.fieldName) return; + const { contentEl, titleEl } = this; + titleEl.setText(this.fieldName); + contentEl.createEl("p", {text: t("ADD_TO_ONTOLOGY_MODAL_DESC")}); + const setting = new Setting(contentEl) + .addButton((b) => { + b.buttonEl.style.flex = "1 0 calc(33.33% - var(--size-4-2))"; + b.setButtonText(t("PARENTS_NAME")) + if(this.ontology === Ontology.Parent) b.setCta(); + b.onClick(()=>this.setOntology(Ontology.Parent)) + }) + .addButton((b) => { + b.buttonEl.style.flex = "1 0 calc(33.33% - var(--size-4-2))"; + b.setButtonText(t("CHILDREN_NAME")) + if(this.ontology === Ontology.Child) b.setCta(); + b.onClick(()=>this.setOntology(Ontology.Child)) + }) + .addButton((b) => { + b.buttonEl.style.flex = "1 0 calc(33.33% - var(--size-4-2))"; + b.setButtonText(t("LEFT_FRIENDS_NAME")) + if(this.ontology === Ontology.LeftFriend) b.setCta(); + b.onClick(()=>this.setOntology(Ontology.LeftFriend)) + }) + .addButton((b) => { + b.buttonEl.style.flex = "1 0 calc(33.33% - var(--size-4-2))"; + b.setButtonText(t("RIGHT_FRIENDS_NAME")) + if(this.ontology === Ontology.RightFriend) b.setCta(); + b.onClick(()=>this.setOntology(Ontology.RightFriend)) + }) + .addButton((b) => { + b.buttonEl.style.flex = "1 0 calc(33.33% - var(--size-4-2))"; + b.setButtonText(t("PREVIOUS_NAME")) + if(this.ontology === Ontology.Previous) b.setCta(); + b.onClick(()=>this.setOntology(Ontology.Previous)) + }) + .addButton((b) => { + b.buttonEl.style.flex = "1 0 calc(33.33% - var(--size-4-2))"; + b.setButtonText(t("NEXT_NAME")) + if(this.ontology === Ontology.Next) b.setCta(); + b.onClick(()=>this.setOntology(Ontology.Next)) + }); + setting.controlEl.style.flexWrap = "wrap"; + setting.controlEl.style.justifyContent = "space-between"; + super.open(); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/Components/ToolsPanel.ts b/src/Components/ToolsPanel.ts index 7da69f8..7221424 100644 --- a/src/Components/ToolsPanel.ts +++ b/src/Components/ToolsPanel.ts @@ -5,12 +5,12 @@ import ExcaliBrain from "src/excalibrain-main"; 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"; +import { addVerticalDivider } from "./VerticalDivider"; export class ToolsPanel { private wrapperDiv: HTMLDivElement; - private buttons: ToggleButton[] = []; + private buttons: (ToggleButton|HTMLElement)[] = []; public linkTagFilter: LinkTagFilter; public searchElement: HTMLInputElement; @@ -90,6 +90,62 @@ export class ToolsPanel { }, )); + addVerticalDivider(buttonsWrapperDiv); + + //------------ + //Navigate back + //------------ + this.buttons.push( + new ToggleButton( + this.plugin, + ()=>false, + (val:boolean)=>{}, + buttonsWrapperDiv, + { + display: "<", + icon: getIcon("lucide-arrow-big-left").outerHTML, + tooltip: t("NAVIGATE_BACK") + }, + false + ) + ) + + //------------ + //Navigate forward + //------------ + this.buttons.push( + new ToggleButton( + this.plugin, + ()=>false, + (val:boolean)=>{}, + buttonsWrapperDiv, + { + display: ">", + icon: getIcon("lucide-arrow-big-right").outerHTML, + tooltip: t("NAVIGATE_FORWARD") + }, + false + ) + ) + + //------------ + //Refresh view + //------------ + this.buttons.push( + new ToggleButton( + this.plugin, + ()=>false, + (val:boolean)=>this.plugin.scene.pinLeaf = val, + buttonsWrapperDiv, + { + display: "๐Ÿ”„", + icon: getIcon("lucide-refresh-cw").outerHTML, + tooltip: t("REFRESH_VIEW") + }, + false + ) + ) + //------------ //Pin Leaf //------------ @@ -108,6 +164,25 @@ export class ToolsPanel { ) ) + //------------ + //Automatically open central node in leaf + //------------ + this.buttons.push( + new ToggleButton( + this.plugin, + ()=>this.plugin.settings.autoOpenCentralDocument, + (val:boolean)=>this.plugin.settings.autoOpenCentralDocument = val, + buttonsWrapperDiv, + { + display: "๐Ÿ”Œ", + icon: ``, //getIcon("lucide-unplug").outerHTML, + tooltip: t("AUTO_OPEN_DOCUMENT") + }, + false + ) + ) + + addVerticalDivider(buttonsWrapperDiv); //------------ //Attachments //------------ @@ -290,7 +365,9 @@ export class ToolsPanel { } rerender() { - this.buttons.forEach(b=>b.setColor()); + this.buttons.forEach(b => { + if(b instanceof ToggleButton) b.setColor(); + }); this.linkTagFilter.render(); } diff --git a/src/Components/VerticalDivider.ts b/src/Components/VerticalDivider.ts new file mode 100644 index 0000000..6c83599 --- /dev/null +++ b/src/Components/VerticalDivider.ts @@ -0,0 +1,2 @@ +export const addVerticalDivider = (containerEl: HTMLElement):HTMLElement => containerEl.createDiv({ + cls: "excalibrain-toolspanel-divider"}); \ No newline at end of file diff --git a/src/Scene.ts b/src/Scene.ts index e97a7d2..87b17c6 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -102,7 +102,8 @@ export class Scene { const centralPage = this.plugin.pages.get(this.centralPagePath); if( centralPage?.file && - !(centralPage.isFolder || centralPage.isTag || centralPage.isVirtual) + !(centralPage.isFolder || centralPage.isTag || centralPage.isVirtual) && + !this.plugin.settings.embedCentralNode ) { if(!this.centralLeaf) { this.ea.openFileInNewOrAdjacentLeaf(centralPage.file); @@ -113,7 +114,7 @@ export class Scene { this.centralLeaf.openFile(centralPage.file, {active: false}); } } - await this.render(); + await this.render(this.plugin.settings.embedCentralNode); } private getCentralPage():Page { @@ -344,6 +345,7 @@ export class Scene { setTimeout(()=>api.zoomToFit(null, this.plugin.settings.maxZoom, 0.15),100); } ea.targetView.linksAlwaysOpenInANewPane = true; + ea.targetView.allowFrameButtonsInViewMode = true; await this.addEventHandler(); this.historyPanel = new HistoryPanel((this.leaf.view as TextFileView).contentEl.querySelector(".excalidraw"),this.plugin); new Notice("ExcaliBrain On"); @@ -542,11 +544,11 @@ export class Scene { this.layouts.push(lChildren); const friendOrigoX = isCompactView && isCenterEmbedded - ? centerEmbedWidth/2 + 1.5 * this.nodeWidth + ? centerEmbedWidth/2 + this.nodeWidth : Math.max( (((manyNextFriends?1:0)+Math.max(childrenCols,parentCols)+1.9)/2.4) * this.nodeWidth, // (manyChildren ? -3 : -2) * this.nodeWidth, isCenterEmbedded - ? centerEmbedWidth/2 + 1.5 * this.nodeWidth + ? centerEmbedWidth/2 + this.nodeWidth : 0 ); @@ -696,7 +698,7 @@ export class Scene { //------------------------------------------------------- // Render ea.style.opacity = 100; - await Promise.all(this.layouts.map(async (layout) => await layout.render(isCompactView))); + await Promise.all(this.layouts.map(async (layout) => await layout.render())); const nodeElements = ea.getElements(); this.links.render(Array.from(this.toolsPanel.linkTagFilter.selectedLinks)); @@ -713,9 +715,8 @@ export class Scene { ea.elementsDict = newImagesDict; ea.addElementsToView(false,false); - ea.targetView.clearDirty(); //hack to prevent excalidraw from saving the changes - excalidrawAPI.updateScene({appState: {viewBackgroundColor: settings.backgroundColor}}); + ea.targetView.clearDirty(); //hack to prevent excalidraw from saving the changes if(settings.allowAutozoom && !retainCentralNode) { setTimeout(()=>excalidrawAPI.zoomToFit(ea.getViewElements(),settings.maxZoom,0.15),100); } @@ -744,10 +745,17 @@ export class Scene { } private async brainEventHandler (leaf:WorkspaceLeaf, startup:boolean = false) { + const settings = this.plugin.settings; + + if(!this.ea.targetView?.file || this.ea.targetView.file.path !== settings.excalibrainFilepath) { + this.unloadScene(); + return; + } + if(this.disregardLeafChange) { return; } - const settings = this.plugin.settings; + if(!startup && settings.embedCentralNode) { return; } @@ -764,11 +772,6 @@ export class Scene { } if(this.pinLeaf && leaf !== this.centralLeaf) return; - - if(!this.ea.targetView?.file || this.ea.targetView.file.path !== settings.excalibrainFilepath) { - this.unloadScene(); - return; - } if(!(leaf?.view && (leaf.view instanceof FileView) && leaf.view.file)) { this.blockUpdateTimer = false; @@ -927,6 +930,10 @@ export class Scene { this.ea.targetView.linksAlwaysOpenInANewPane = false; } + if(this.ea.targetView && isBoolean(this.ea.targetView.allowFrameButtonsInViewMode)) { + this.ea.targetView.allowFrameButtonsInViewMode = false; + } + if(this.ea.targetView && this.ea.targetView.excalidrawAPI) { try { this.ea.targetView.semaphores.saving = false; diff --git a/src/Settings.ts b/src/Settings.ts index 0f2e224..d73740f 100644 --- a/src/Settings.ts +++ b/src/Settings.ts @@ -27,10 +27,12 @@ export interface ExcaliBrainSettings { hierarchy: Hierarchy; inferAllLinksAsFriends: boolean; inverseInfer: boolean; + inverseArrowDirection: boolean; renderAlias: boolean; nodeTitleScript: string; backgroundColor: string; excludeFilepaths: string[]; + autoOpenCentralDocument: boolean; showInferredNodes: boolean; showAttachments: boolean; showURLNodes: boolean; @@ -85,10 +87,12 @@ export const DEFAULT_SETTINGS: ExcaliBrainSettings = { hierarchy: DEFAULT_HIERARCHY_DEFINITION, inferAllLinksAsFriends: false, inverseInfer: false, + inverseArrowDirection: false, renderAlias: true, nodeTitleScript: "", backgroundColor: "#0c3e6aff", excludeFilepaths: [], + autoOpenCentralDocument: true, showInferredNodes: true, showAttachments: true, showURLNodes: true, @@ -1565,6 +1569,17 @@ export class ExcaliBrainSettingTab extends PluginSettingTab { }) ) + new Setting(containerEl) + .setName(t("INVERSE_ARROW_DIRECTION_NAME")) + .setDesc(fragWithHTML(t("INVERSE_ARROW_DIRECTION_DESC"))) + .addToggle(toggle => + toggle + .setValue(this.plugin.settings.inverseArrowDirection) + .onChange(value => { + this.plugin.settings.inverseArrowDirection = value; + this.dirty = true; + }) + ) let pSetting:Setting, cSetting:Setting, fSetting:Setting, gSetting: Setting, mSetting: Setting, bSetting: Setting; diff --git a/src/excalibrain-main.ts b/src/excalibrain-main.ts index b643b11..fcfc923 100644 --- a/src/excalibrain-main.ts +++ b/src/excalibrain-main.ts @@ -1,4 +1,4 @@ -import { App, MarkdownView, Notice, Plugin, PluginManifest, TextFileView, TFile, TFolder, WorkspaceLeaf } from 'obsidian'; +import { App, Editor, MarkdownView, Menu, MenuItem, Notice, Plugin, PluginManifest, TextFileView, TFile, TFolder, WorkspaceLeaf } from 'obsidian'; import { Page } from './graph/Page'; import { DEFAULT_SETTINGS, ExcaliBrainSettings, ExcaliBrainSettingTab } from './Settings'; import { errorlog, keepOnTop } from './utils/utils'; @@ -16,11 +16,13 @@ import { FieldSuggester } from './Suggesters/OntologySuggester'; import { Literal } from 'obsidian-dataview/lib/data-model/value'; import { isEmbedFileType } from './utils/fileUtils'; import { URLParser } from './graph/URLParser'; +import { AddToOntologyModal, Ontology } from './Components/AddToOntologyModal'; declare module "obsidian" { interface App { plugins: { disablePlugin(plugin: string):Promise; + plugins: { [key: string]: Plugin; }; }; } interface WorkspaceLeaf { @@ -58,6 +60,7 @@ export default class ExcaliBrain extends Plugin { public customNodeLabel: (dvPage: Literal, defaultName:string) => string public navigationHistory: string[] = []; public urlParser: URLParser; + private addToOntologyModal: AddToOntologyModal; constructor(app: App, manifest: PluginManifest) { super(app, manifest); @@ -69,6 +72,7 @@ export default class ExcaliBrain extends Plugin { "Initializing index, please wait" ) ] + this.addToOntologyModal = new AddToOntologyModal(app,this); } async onload() { @@ -76,6 +80,7 @@ export default class ExcaliBrain extends Plugin { this.navigationHistory = this.settings.navigationHistory; this.addSettingTab(new ExcaliBrainSettingTab(this.app, this)); this.registerEditorSuggest(new FieldSuggester(this)); + this.registerEvents(); this.urlParser = new URLParser(this); this.app.workspace.onLayoutReady(()=>{ this.urlParser.init(); @@ -138,6 +143,46 @@ export default class ExcaliBrain extends Plugin { }); } + private registerEvents() { + this.registerEvent( + this.app.workspace.on("editor-menu", this.handleEditorMenu, this) + ); + } + + private getFieldName(editor: Editor): string { + let line = editor.getLine(editor.getCursor().line).substring(0,editor.getCursor().ch); + const regex = /(?:^|[(\[])(?:==|\*\*|~~|\*|_|__)?([^\:\]\()]*?)(?:==|\*\*|~~|\*|_|__)?::/g; + let lastMatch = null; + let match; + + while ((match = regex.exec(line)) !== null) { + lastMatch = match; + } + + //default to the full line, maybe the user positioned the cursor in the middle of the field + if(!lastMatch) { + line = editor.getLine(editor.getCursor().line); + while ((match = regex.exec(line)) !== null) { + lastMatch = match; + } + } + return lastMatch !== null ? lastMatch[1] : null; + } + + private handleEditorMenu(menu: Menu, editor: Editor, view: MarkdownView) { + const field = this.getFieldName(editor); + if(field) { + menu.addItem((item: MenuItem) => { + item + .setTitle(`Add "${field}" to ExcaliBrain Ontology`) + .setIcon("plus") + .onClick(() => { + this.addToOntologyModal.show(field); + }); + }); + } + } + public lowercasePathMap: Map; public async createIndex() { @@ -177,16 +222,16 @@ export default class ExcaliBrain extends Plugin { if(f instanceof TFolder) { const child = new Page(this.pages,"folder:"+f.path, null, this, true, false, f.name); this.pages.add("folder:"+f.path,child); - child.addParent(parent,RelationType.DEFINED,LinkDirection.FROM,"file-tree"); - parent.addChild(child,RelationType.DEFINED,LinkDirection.TO,"file-tree"); + child.addParent(parent,RelationType.DEFINED,LinkDirection.TO,"file-tree"); + parent.addChild(child,RelationType.DEFINED,LinkDirection.FROM,"file-tree"); addFolderChildren(f,child); return; } else { this.lowercasePathMap.set(f.path.toLowerCase(),f.path); //path case sensitivity issue (see Pages.ts and Scene.ts for more) const child = new Page(this.pages,f.path,f as TFile,this); this.pages.add(f.path,child); - child.addParent(parent,RelationType.DEFINED,LinkDirection.FROM,"file-tree"); - parent.addChild(child,RelationType.DEFINED,LinkDirection.TO,"file-tree"); + child.addParent(parent,RelationType.DEFINED,LinkDirection.TO,"file-tree"); + parent.addChild(child,RelationType.DEFINED,LinkDirection.FROM,"file-tree"); } }) } @@ -269,7 +314,11 @@ export default class ExcaliBrain extends Plugin { } private excalidrawAvailable():boolean { - const ea = getEA(); + if(this.app.plugins.plugins["obsidian-excalidraw-plugin"] === this.EA.plugin) { + return true; + } + + const ea = getEA(this.scene?.leaf?.view); if(!ea) { this.EA = null; if(this.scene) { @@ -278,10 +327,8 @@ export default class ExcaliBrain extends Plugin { new Notice("ExcaliBrain: Please start Excalidraw and try again.",4000); return false; } - if(!this.EA !== ea) { - this.EA = ea; - this.registerExcalidrawAutomateHooks() - } + this.EA = ea; + this.registerExcalidrawAutomateHooks() return true; } @@ -304,132 +351,72 @@ export default class ExcaliBrain extends Plugin { searchElement?.focus(); } + private addFieldToOntology(field: string, direction: Ontology) { + this.addToOntologyModal.addFieldToOntology(direction, field); + } + private registerCommands() { - const addFieldToOntology = (checking: boolean, desc: string):boolean => { - const activeView = app.workspace.activeLeaf?.view; - if(checking) { - return (activeView instanceof MarkdownView) && activeView.getMode() === "source"; - } - if(!(activeView instanceof MarkdownView) || activeView.getMode() !== "source") { + const addFieldToOntology = (checking: boolean, direction: Ontology | "select"):boolean => { + const activeView = app.workspace.activeLeaf?.view; + if(!activeView || !(activeView instanceof MarkdownView) || activeView.getMode() !== "source") { return false; } - const cursor = activeView.editor.getCursor(); - const line = activeView.editor.getLine(cursor.line); - const field = line.match(/^([^\:]*)::/); + const field = this.getFieldName(activeView.editor); if(!field) { return false; } - - (async() => { - await this.loadSettings(); - - if(this.settings.hierarchy.parents.includes(field[1])) { - new Notice(`${field[1]} is already registered as a PARENT`); - return; - } - if(this.settings.hierarchy.children.includes(field[1])) { - new Notice(`${field[1]} is already registered as a CHILD`); - return; - } - if(this.settings.hierarchy.leftFriends.includes(field[1])) { - new Notice(`${field[1]} is already registered as a LEFT-SIDE FRIEND`); - return; - } - if(this.settings.hierarchy.rightFriends.includes(field[1])) { - new Notice(`${field[1]} is already registered as a RIGHT-SIDE FRIEND`); - return; - } - if(this.settings.hierarchy.previous.includes(field[1])) { - new Notice(`${field[1]} is already registered as a PREVIOUS (FRIEND)`); - return; - } - if(this.settings.hierarchy.next.includes(field[1])) { - new Notice(`${field[1]} is already registered as a NEXT (FRIEND)`); - return; - } - - switch (desc) { - case "parent": - this.settings.hierarchy.parents.push(field[1]); - this.settings.hierarchy.parents = this.settings.hierarchy.parents.sort((a,b)=>a.toLowerCase()this.hierarchyLowerCase.parents.push(f.toLowerCase().replaceAll(" ","-"))) - break; - case "child": - this.settings.hierarchy.children.push(field[1]); - this.settings.hierarchy.children = this.settings.hierarchy.children.sort((a,b)=>a.toLowerCase()this.hierarchyLowerCase.children.push(f.toLowerCase().replaceAll(" ","-"))) - break; - case "rightFriend": - this.settings.hierarchy.rightFriends.push(field[1]); - this.settings.hierarchy.rightFriends = this.settings.hierarchy.rightFriends.sort((a,b)=>a.toLowerCase()this.hierarchyLowerCase.rightFriends.push(f.toLowerCase().replaceAll(" ","-"))) - break; - case "previous": - this.settings.hierarchy.previous.push(field[1]); - this.settings.hierarchy.previous = this.settings.hierarchy.previous.sort((a,b)=>a.toLowerCase()this.hierarchyLowerCase.previous.push(f.toLowerCase().replaceAll(" ","-"))) - break; - case "next": - this.settings.hierarchy.next.push(field[1]); - this.settings.hierarchy.next = this.settings.hierarchy.next.sort((a,b)=>a.toLowerCase()this.hierarchyLowerCase.next.push(f.toLowerCase().replaceAll(" ","-"))) - break; - default: - this.settings.hierarchy.leftFriends.push(field[1]); - this.settings.hierarchy.leftFriends = this.settings.hierarchy.leftFriends.sort((a,b)=>a.toLowerCase()this.hierarchyLowerCase.leftFriends.push(f.toLowerCase().replaceAll(" ","-"))) - } - await this.saveSettings(); - if (this.scene && !this.scene.terminated) { - this.scene.vaultFileChanged = true; - } - new Notice(`Added ${field[1]} as ${desc}`); - })(); - + if(checking) { + return true; + } + if(direction === "select") { + this.addToOntologyModal.show(field); + return true; + } + this.addFieldToOntology(field,direction); return true; - } + } this.addCommand({ id: "excalibrain-addParentField", name: t("COMMAND_ADD_PARENT_FIELD"), - checkCallback: (checking: boolean) => addFieldToOntology(checking, "parent"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, Ontology.Parent), }); this.addCommand({ id: "excalibrain-addChildField", name: t("COMMAND_ADD_CHILD_FIELD"), - checkCallback: (checking: boolean) => addFieldToOntology(checking, "child"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, Ontology.Child), }); this.addCommand({ id: "excalibrain-addLeftFriendField", name: t("COMMAND_ADD_LEFT_FRIEND_FIELD"), - checkCallback: (checking: boolean) => addFieldToOntology(checking, "leftFriend"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, Ontology.LeftFriend), }); this.addCommand({ id: "excalibrain-addRightFriendField", name: t("COMMAND_ADD_RIGHT_FRIEND_FIELD"), - checkCallback: (checking: boolean) => addFieldToOntology(checking, "rightFriend"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, Ontology.RightFriend), }); this.addCommand({ id: "excalibrain-addPreviousField", name: t("COMMAND_ADD_PREVIOUS_FIELD"), - checkCallback: (checking: boolean) => addFieldToOntology(checking, "previous"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, Ontology.Previous), }); this.addCommand({ id: "excalibrain-addNextField", name: t("COMMAND_ADD_NEXT_FIELD"), - checkCallback: (checking: boolean) => addFieldToOntology(checking, "next"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, Ontology.Next), + }); + + this.addCommand({ + id: "excalibrain-selectOntology", + name: t("COMMAND_ADD_ONTOLOGY_MODAL"), + checkCallback: (checking: boolean) => addFieldToOntology(checking, "select"), }); this.addCommand({ @@ -961,4 +948,3 @@ export default class ExcaliBrain extends Plugin { this.focusSearchAfterInitiation = false; } } - diff --git a/src/graph/Layout.ts b/src/graph/Layout.ts index f37a225..72542d4 100644 --- a/src/graph/Layout.ts +++ b/src/graph/Layout.ts @@ -54,138 +54,7 @@ export class Layout { : getRowLayout(itemCount % columns).map(idx => idx ? sortedNodes[i*columns+idx-1]:null)); } - private setCompactPosition(center00: { x: number, y: number }) { - const fixOverlap = (center00: { x: number, y: number }) => { - - //I think if LayoutSpecification could add layoutPositionTag property: 'center' | 'children' | ... - //,this will be more simple and clear. - const layoutPosition = this.spec.origoX === 0 - ? (this.spec.top === null && this.spec.bottom === null) - ? 'center' - : this.spec.origoY < 0 ? 'top' : 'bottom' - : (this.spec.top === null && this.spec.bottom === null) - ? this.spec.origoX < 0 ? 'left' : 'right' - : 'sibling'; - - if (layoutPosition === 'center') { - return this.setPosition(center00); - } - if (layoutPosition === 'sibling') { - const maxNodeWidth = Math.max(...this.renderedNodes.map(row => { - return row.first().labelSize().width - })); - const gap = Math.abs(this.spec.origoX); - const offset = maxNodeWidth > gap - ? (maxNodeWidth - gap) + 0.5 * maxNodeWidth - : 0; - const newCenter00 = { - x: center00.x + offset, - y: center00.y, - } - return this.setPosition(newCenter00); - } - if (layoutPosition === 'left' || layoutPosition === 'right') { - const maxNodeWidth = Math.max(...this.renderedNodes.flat().map(node => node.labelSize().width)); - const offset = maxNodeWidth > this.spec.columnWidth - ? (maxNodeWidth - this.spec.columnWidth) / 2 + 0.2 * maxNodeWidth - : 0; - const newCenter00 = { - x: layoutPosition === 'left' - ? center00.x - offset - : center00.x + offset, - y: center00.y, - } - return this.setPosition(newCenter00); - - } - else { - const rowWidth = this.spec.columns * this.spec.columnWidth; - const nodeGap = rowWidth * 0.1; - const initialCenter = (spec: LayoutSpecification, width: number, row: number) => { - return width > spec.columnWidth - ? { - x: center00.x + (width - spec.columnWidth) / 2, - y: center00.y + row * spec.rowHeight, - } - : { - x: center00.x - (spec.columnWidth - width) / 2, - y: center00.y + row * spec.rowHeight, - } - } - const nodes = this.renderedNodes.flat().filter(node => !!node); - let stackWidth = 0; - let nodesInfo: { x: number, y: number, width: number }[][] = []; - const alignCenter = (nodeInfo: { x: number, y: number, width: number }[]) => { - const centerX = center00.x - this.spec.columnWidth/2 + rowWidth/2; - let l = 0; - let r = nodeInfo.length-1; - while (l<=r){ - const left = nodeInfo[l]; - const right = nodeInfo[r]; - const offset = centerX-(left.x + right.x)/2; - nodeInfo[l] = {...left,x:left.x + offset}; - nodeInfo[r] = {...right,x:right.x + offset}; - l +=1; - r -=1; - } - return nodeInfo; - } - - nodes.forEach((node, index) => { - const width = node.labelSize().width; - const row = nodesInfo.length - 1; - if (index === 0) { - const init = initialCenter(this.spec, width, 0); - stackWidth = width; - nodesInfo.push([{ ...init, width }]); - } - else if ((stackWidth + nodeGap + width) > rowWidth) { - nodesInfo[row] = alignCenter(nodesInfo[row]); - const newRow = row + 1; - const init = initialCenter(this.spec, width, newRow); - stackWidth = width; - nodesInfo.push([{ width, ...init }]); - } - else { - const prev_center = nodesInfo[row].last(); - nodesInfo[row].push({ - ...prev_center, - width, - x: prev_center.x + prev_center.width / 2 + nodeGap + width / 2, - }) - stackWidth = stackWidth + nodeGap + width; - } - - }); - - const nodePosition = nodesInfo.flatMap((nodes, row) => { - if (row === nodesInfo.length - 1) { - return alignCenter(nodes) - } - return nodes - }); - - return nodes.map((node, index) => { - const info = nodePosition[index]; - node?.setCenter({ ...info }); - return node; - }); - } - } - return fixOverlap(center00); - } - private setPosition(center00: { x: number, y: number }) { - return this.renderedNodes.map((nodes, row) => { - return nodes.map((node, idx) => { - node?.setCenter({ - x: center00.x + idx * this.spec.columnWidth, - y: center00.y + row * this.spec.rowHeight - }); - return node - }) - }).flat(); - } - async render(isCompactView:boolean) { + async render() { this.layout(); const rows = this.renderedNodes.length; const height = rows * this.spec.rowHeight; @@ -202,13 +71,15 @@ export class Layout { x: this.spec.origoX - (this.spec.columns === 1 ? 0 : (this.spec.columns-1)/2*this.spec.columnWidth), y: top }; - - const renderedNodesWithPosition = isCompactView - ? this.setCompactPosition(center00) - : this.setPosition(center00); - for (const [_, node] of renderedNodesWithPosition.entries()) { - if (node) { - await node.render(); + for (const [row, nodes] of this.renderedNodes.entries()) { + for (const [idx, node] of nodes.entries()) { + if(node) { + node.setCenter({ + x: center00.x + idx*this.spec.columnWidth, + y: center00.y + row*this.spec.rowHeight + }); + await node.render(); + } } } } diff --git a/src/graph/Links.ts b/src/graph/Links.ts index 728a495..f6c5083 100644 --- a/src/graph/Links.ts +++ b/src/graph/Links.ts @@ -30,13 +30,13 @@ export class Links { } const key2 = nodeB.page.path+SEPARATOR+nodeA.page.path; const link = new Link( - linkDirection===LinkDirection.FROM + linkDirection===(settings.inverseArrowDirection ? LinkDirection.TO : LinkDirection.FROM) ? nodeB : nodeA, - linkDirection===LinkDirection.FROM + linkDirection===(settings.inverseArrowDirection ? LinkDirection.TO : LinkDirection.FROM) ? nodeA : nodeB, - linkDirection===LinkDirection.FROM + linkDirection===(settings.inverseArrowDirection ? LinkDirection.TO : LinkDirection.FROM) ? nodeBRole === Role.LEFT || nodeBRole === Role.RIGHT ? nodeBRole === Role.LEFT ? Role.LEFT : Role.RIGHT : nodeBRole === Role.CHILD diff --git a/src/graph/Node.ts b/src/graph/Node.ts index 33f21ae..b9e2909 100644 --- a/src/graph/Node.ts +++ b/src/graph/Node.ts @@ -82,21 +82,6 @@ export class Node { : label; } - labelSize(){ - const ea = this.ea; - ea.style.fontSize = this.style.fontSize; - ea.style.fontFamily = this.style.fontFamily; - ea.style.fillStyle = this.style.fillStyle; - ea.style.roughness = this.style.roughness; - ea.style.strokeSharpness = this.style.strokeShaprness; - ea.style.strokeWidth = this.style.strokeWidth; - ea.style.strokeColor = this.style.textColor; - ea.style.backgroundColor = "transparent"; - const label = this.displayText(); - const labelSize = ea.measureText(`${label}`); - return {width:labelSize.width + 2*this.style.padding,height:labelSize.height} ; - } - setCenter(center:{x:number, y:number}) { this.center = center; } diff --git a/src/graph/Page.ts b/src/graph/Page.ts index ca3d233..17035fd 100644 --- a/src/graph/Page.ts +++ b/src/graph/Page.ts @@ -95,8 +95,8 @@ export class Page { return; } if(this.neighbours.has(path)) return; //https://github.com/zsviczian/excalibrain/issues/74 - child.addParent(this,RelationType.DEFINED,LinkDirection.FROM,"tag-tree"); - this.addChild(child,RelationType.DEFINED,LinkDirection.TO,"tag-tree"); + child.addParent(this,RelationType.DEFINED,LinkDirection.TO,"tag-tree"); + this.addChild(child,RelationType.DEFINED,LinkDirection.FROM,"tag-tree"); }); return; } @@ -129,8 +129,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.addParent(referencedPage,RelationType.DEFINED,LinkDirection.TO, item.field); - referencedPage.addChild(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addParent(referencedPage,RelationType.DEFINED,LinkDirection.FROM, item.field); + referencedPage.addChild(this,RelationType.DEFINED,LinkDirection.TO, item.field); }); const childFields = this.plugin.hierarchyLowerCase.children; @@ -141,8 +141,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.addChild(referencedPage,RelationType.DEFINED,LinkDirection.TO, item.field); - referencedPage.addParent(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addChild(referencedPage,RelationType.DEFINED,LinkDirection.FROM, item.field); + referencedPage.addParent(this,RelationType.DEFINED,LinkDirection.TO, item.field); }); const leftFriendFields = this.plugin.hierarchyLowerCase.leftFriends; @@ -153,8 +153,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.addLeftFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addLeftFriend(referencedPage,RelationType.DEFINED,LinkDirection.FROM,item.field); + referencedPage.addLeftFriend(this,RelationType.DEFINED,LinkDirection.TO, item.field); }); const rightFriendFields = this.plugin.hierarchyLowerCase.rightFriends; @@ -165,8 +165,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.addRightFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addRightFriend(referencedPage,RelationType.DEFINED,LinkDirection.FROM,item.field); + referencedPage.addRightFriend(this,RelationType.DEFINED,LinkDirection.TO, item.field); }); const previousFields = this.plugin.hierarchyLowerCase.previous; @@ -177,8 +177,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.addPreviousFriend(referencedPage,RelationType.DEFINED,LinkDirection.TO,item.field); - referencedPage.addNextFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addPreviousFriend(referencedPage,RelationType.DEFINED,LinkDirection.FROM,item.field); + referencedPage.addNextFriend(this,RelationType.DEFINED,LinkDirection.TO, item.field); }); const nextFields = this.plugin.hierarchyLowerCase.next; @@ -189,8 +189,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.addNextFriend(referencedPage,RelationType.DEFINED,LinkDirection.TO,item.field); - referencedPage.addPreviousFriend(this,RelationType.DEFINED,LinkDirection.FROM, item.field); + this.addNextFriend(referencedPage,RelationType.DEFINED,LinkDirection.FROM,item.field); + referencedPage.addPreviousFriend(this,RelationType.DEFINED,LinkDirection.TO, item.field); }); } diff --git a/src/graph/Pages.ts b/src/graph/Pages.ts index 7f036a3..4ca5c6b 100644 --- a/src/graph/Pages.ts +++ b/src/graph/Pages.ts @@ -51,15 +51,15 @@ export class Pages { private addInferredParentChild(parent: Page, child: Page) { if(this.plugin.settings.inferAllLinksAsFriends) { - child.addLeftFriend(parent,RelationType.INFERRED, LinkDirection.FROM); - parent.addLeftFriend(child,RelationType.INFERRED, LinkDirection.TO); + child.addLeftFriend(parent,RelationType.INFERRED, LinkDirection.TO); + parent.addLeftFriend(child,RelationType.INFERRED, LinkDirection.FROM); } else { if(this.plugin.settings.inverseInfer) { //https://github.com/zsviczian/excalibrain/issues/78 - child.addChild(parent,RelationType.INFERRED, LinkDirection.FROM); - parent.addParent(child,RelationType.INFERRED, LinkDirection.TO); + child.addChild(parent,RelationType.INFERRED, LinkDirection.TO); + parent.addParent(child,RelationType.INFERRED, LinkDirection.FROM); } else { - child.addParent(parent,RelationType.INFERRED, LinkDirection.FROM); - parent.addChild(child,RelationType.INFERRED, LinkDirection.TO); + child.addParent(parent,RelationType.INFERRED, LinkDirection.TO); + parent.addChild(child,RelationType.INFERRED, LinkDirection.FROM); } } } @@ -92,6 +92,9 @@ export class Pages { urlPage = new Page(this,url.url,null,this.plugin,false,false,url.alias,url.url); this.add(url.url, urlPage); } + if(url.alias !== "" && (urlPage.name === "" || urlPage.name === url.url)) { + urlPage.name = url.alias; + } this.addInferredParentChild(page,urlPage); const originPage = this.get(url.origin); if(originPage) { @@ -134,15 +137,15 @@ export class Pages { export const addUnresolvedPage = (childPath: string, parent: Page, plugin: ExcaliBrain, pages: Pages):Page => { const newPage = pages.get(childPath) ?? new Page(pages,childPath,null,plugin); if(plugin.settings.inferAllLinksAsFriends) { - newPage.addLeftFriend(parent,RelationType.INFERRED, LinkDirection.FROM); - parent.addLeftFriend(newPage,RelationType.INFERRED, LinkDirection.TO); + newPage.addLeftFriend(parent,RelationType.INFERRED, LinkDirection.TO); + parent.addLeftFriend(newPage,RelationType.INFERRED, LinkDirection.FROM); } else { if(plugin.settings.inverseInfer) { //https://github.com/zsviczian/excalibrain/issues/78 - newPage.addChild(parent,RelationType.INFERRED, LinkDirection.FROM); - parent.addParent(newPage,RelationType.INFERRED, LinkDirection.TO); + newPage.addChild(parent,RelationType.INFERRED, LinkDirection.TO); + parent.addParent(newPage,RelationType.INFERRED, LinkDirection.FROM); } else { - newPage.addParent(parent,RelationType.INFERRED, LinkDirection.FROM); - parent.addChild(newPage,RelationType.INFERRED, LinkDirection.TO); + newPage.addParent(parent,RelationType.INFERRED, LinkDirection.TO); + parent.addChild(newPage,RelationType.INFERRED, LinkDirection.FROM); } } pages.add(childPath,newPage); diff --git a/src/graph/URLParser.ts b/src/graph/URLParser.ts index 721e238..c729c0e 100644 --- a/src/graph/URLParser.ts +++ b/src/graph/URLParser.ts @@ -7,8 +7,8 @@ export interface FileURL { origin: string; } -export const linkRegex = /\[([^[\]]+)\]\((https?:\d*?\/\/[a-z0-9&#=.\/\-?_]+)\)/gi; // Matches links in markdown format [label](url) -export const plainLinkRegex = /(https?:\d*?\/\/[a-z0-9&#=.\/\-?_]+)/gi; // Matches plain links +// Matches links in markdown format [label](url) +export const linkRegex = /(?:\[([^[\]]+)\]\()((?:(?:ftp|https?|sftp|shttp|tftp):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>"']|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?ยซยปโ€œโ€โ€˜โ€™]))\)|\b()((?:(?:ftp|https?|sftp|shttp|tftp):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>"']|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?ยซยปโ€œโ€โ€˜โ€™]))\b/gi; export class URLParser { fileToUrlMap: Map = new Map(); @@ -52,7 +52,11 @@ export class URLParser { let match; while ((match = linkRegex.exec(content)) !== null) { - const [, alias, url] = match; + const alias = match[1] ?? match[3] ?? ""; + let url = match[2] ?? match[4] ?? ""; + if(!url.match(/^(?:ftp|https?|sftp|shttp|tftp):/)) { + url = "https://" + url; + } if (!links.has(url)) { const origin = this.getOrigin(url,file); links.set(url,{ url, alias, origin}); @@ -65,15 +69,6 @@ export class URLParser { } } - while ((match = plainLinkRegex.exec(content)) !== null) { - const url = match[0]; - if (!links.has(url)) { - const origin = this.getOrigin(url,file); - links.set(url, { url, alias: '', origin}); - this.updateInverseMap(url, file, origin); - } - } - const linkArray = Array.from(links.values()); if (linkArray.length > 0) { this.fileToUrlMap.set(file, linkArray); @@ -125,10 +120,6 @@ export class URLParser { this.plugin.registerEvent(this.app.vault.on('create', modifyEventHandler)); this.plugin.registerEvent(this.app.vault.on('modify', modifyEventHandler)); this.plugin.registerEvent(this.app.vault.on('delete', deleteEventHandler)); - /*this.plugin.registerEvent(this.app.vault.on('rename', async (file:TFile) => { - this.fileToUrlMap.delete(file); - await this.parseFileURLs(file); - }));*/ } } diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index d62841f..bfa70aa 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -28,6 +28,8 @@ export default { "
  • If files mutually link to each other, they are FRIENDS
  • ", REVERSE_NAME: "Reverse infer logic", REVERSE_DESC: "Toggle ON: Treat backlinks as children and forward links as parents.
    Toggle OFF: Treat backlinks as parents and forward links as children", + INVERSE_ARROW_DIRECTION_NAME: "Inverse arrow direction", + INVERSE_ARROW_DIRECTION_DESC: "Toggle ON: Display arrow heads in the opposite direction of the link direction.
    Toggle OFF: Display arrow heads in the same direction as the link direction", PARENTS_NAME: "Parents", CHILDREN_NAME: "Children", LEFT_FRIENDS_NAME: "Left-Side Friends", @@ -172,6 +174,7 @@ export default { COMMAND_ADD_RIGHT_FRIEND_FIELD: "Add dataview field to ontology as RIGHT-SIDE FRIEND", COMMAND_ADD_PREVIOUS_FIELD: "Add dataview field to ontology as PREVIOUS", COMMAND_ADD_NEXT_FIELD: "Add dataview field to ontology as NEXT", + COMMAND_ADD_ONTOLOGY_MODAL: "Add dataview field to ontology: Open Ontology Modal", COMMAND_START: "ExcaliBrain Normal", COMMAND_START_HOVER: "ExcaliBrain Hover-Editor", COMMAND_START_POPOUT: "ExcaliBrain Popout Window", @@ -191,5 +194,12 @@ export default { 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)", - PIN_LEAF: "Link ExcaliBrain to most recent active leaf" -} + PIN_LEAF: "Link ExcaliBrain to most recent active leaf", + NAVIGATE_BACK: "Navigate back", + NAVIGATE_FORWARD: "Navigate forward", + REFRESH_VIEW: "Refresh", + AUTO_OPEN_DOCUMENT: "Auto-open document in other leaf when navigating to graph-node and open graph-node when navigating to document in workspace leaf", + + //AddToOntologyModal + ADD_TO_ONTOLOGY_MODAL_DESC: "Select the direction of the ontology. If one of the buttons is highlighted, then the field is already part of the ontology in that direction.", +} \ No newline at end of file diff --git a/src/utils/dataview.ts b/src/utils/dataview.ts index cd0f8ad..330095a 100644 --- a/src/utils/dataview.ts +++ b/src/utils/dataview.ts @@ -1,7 +1,7 @@ import { App, TFile } from "obsidian"; import { Literal } from "obsidian-dataview/lib/data-model/value"; import ExcaliBrain from "src/excalibrain-main"; -import { plainLinkRegex } from "src/graph/URLParser"; +import { linkRegex } from "src/graph/URLParser"; import { ExcaliBrainSettings } from "src/Settings"; import { NodeStyle } from "src/types"; @@ -32,8 +32,8 @@ const readLinksFromString = (data: string, file:TFile):string[] => { } let match; - while ((match = plainLinkRegex.exec(data)) !== null) { - res.add(match[0]); + while ((match = linkRegex.exec(data)) !== null) { + res.add(match[2]??match[4]); } return Array.from(res); diff --git a/styles.css b/styles.css index 1880aeb..643e678 100644 --- a/styles.css +++ b/styles.css @@ -8,52 +8,80 @@ text-align: center; } -.excalibrain-settings-folding-L1 { - font-size: large; - font-weight: bold; - color: var(--text-title-h3); -} - -.excalibrain-settings-h1 { - color: var(--text-title-h1); -} - -.excalibrain-setting-style-section { - padding-left: 30px; - border-left: 10px solid var(--background-modifier-border); -} - -.excalibrain-settings-demoimg { - max-width: 400px; -} - -.excalibrain-setting-nameEl { - min-width: 10em; - max-width: 20em; -} - -.excalibrain-setting-descEl { - min-width: 10em; - max-width: 20em; -} - -.excalibrain-setting-controlEl { - width: 90%; +.excalibrain-contentEl div.Island, +.excalibrain-contentEl button.help-icon { + display:none; } .excalibrain-contentEl { + overflow: hidden !important; position: relative; } -.excalibrain-toolspanel-wrapper { - z-index: 3; - position: absolute; - top: 10px; - padding-left: 10px; - width: 100%; - padding-right: 10px; - pointer-events: none; -} +/* ----------- + TOOLS PANEL + ------------ */ + .excalibrain-toolspanel-divider { + width: 2px; + background-color: var(--default-border-color); + margin-left: 8px; + margin-right: 3px; + } + + .excalibrain-toolspanel-wrapper { + z-index: 3; + position: absolute; + top: 10px; + padding-left: 10px; + /* Set width to auto to fit its content */ + width: auto; + padding-right: 10px; + pointer-events: none; + } + + .excalibrain-dropdown-wrapper, + .excalibrain-searchinput { + /* Adjust flex properties to make them appear in separate rows */ + flex-wrap: wrap; + margin-bottom: 5px; /* Add a small margin to separate the rows */ + } + + .excalibrain-button { + vertical-align: middle; + padding-left: 5px; + padding-right: 5px; + margin-left: 5px !important; + margin-right: 0px !important; + width: 2.4em !important; + } + + .excalibrain-button.off { + background-color: var(--island-bg-color); + } + + .excalibrain-button.on { + background-color: var(--color-primary-darker); + } + + .excalibrain-searchinput { + width: 400px; + vertical-align: middle; + pointer-events: all; + } + + .excalibrain-buttons { + display: flex; /* Change display to flex */ + /* Adjust width to the content */ + width: auto; + margin-top: 5px; + pointer-events: all; + margin-left: -5px; + } + + +/* ----------- + HISTORY + ------------ */ .excalibrain-history-wrapper { z-index: 3; @@ -72,41 +100,55 @@ overflow-x: scroll; } -.excalibrain-button { - vertical-align: middle; - padding-left: 5px; - padding-right: 5px; - margin-left: 5px !important; - margin-right: 0px !important; - width: 2.4em !important; +.excalibrain-history-divider { + color: gold; + margin-left: 5px; + margin-right: 5px; + font-size: smaller; } -.excalibrain-button span { - text-align: center; +.excalibrain-history-item { + cursor: pointer; + color: silver; + font-size: smaller; } -.excalibrain-button.off { - background-color: var(--island-bg-color); +/* ----------- + SETTINGS + ------------ */ +.excalibrain-settings-folding-L1 { + font-size: large; + font-weight: bold; + color: var(--text-title-h3); } -.excalibrain-button.on { - background-color: var(--color-primary-darker); +.excalibrain-settings-h1 { + color: var(--text-title-h1); } -.excalibrain-searchinput { - width: 400px; - vertical-align: middle; - pointer-events: all; +.excalibrain-setting-style-section { + padding-left: 30px; + border-left: 10px solid var(--background-modifier-border); } -.excalibrain-buttons { - display: inline-flex; - width: 100%; - margin-top: 5px; - margin-left: -5px; - pointer-events: all; +.excalibrain-settings-demoimg { + max-width: 400px; +} + +.excalibrain-setting-nameEl { + min-width: 10em; + max-width: 20em; +} + +.excalibrain-setting-descEl { + min-width: 10em; + max-width: 20em; } +.excalibrain-setting-controlEl { + width: 90%; +} + .excalibrain-settings-colorlabel { padding-right: 5px; min-width: 3em; @@ -134,34 +176,13 @@ margin-right: 5px; } -.excalibrain-contentEl div.Island, -.excalibrain-contentEl button.help-icon { - display:none; -} - -.excalibrain-contentEl { - overflow: hidden !important; -} - -.excalibrain-history-divider { - color: gold; - margin-left: 5px; - margin-right: 5px; - font-size: smaller; -} - -.excalibrain-history-item { - cursor: pointer; - color: silver; - font-size: smaller; -} - .excalibrain-dropdown-wrapper { display: inline-flex; } -/*-----------*/ -/*MULTISELECT*/ +/* ----------- + MULTISELECT + ------------ */ .multiselect-container { padding-left: 5px; width:200px;