IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    我是如何像Obsidian Publish那样在本地集成Graph View的

    茴香发表于 2024-08-04 15:59:31
    love 0

    Obsidian Publish 在笔记中集成了 Graph View,让浏览笔记的时候能够查看与当前笔记关联的知识图谱,帮助进行结构化地理解和思考。

    比较遗憾的是Obsidian App原生没有支持该能力——将graph view集成到笔记中进行预览,这篇文章就是介绍一下我是如何实现这个能力的,先看一下效果。

    0:00
    /0:10

    设计思路

    local graph是由Obsidian内部的引擎计算实现的,由canvas绘制图形(类似chartjs),我只需要将canvas元素插入到当前笔记的workspace leaf中即可:

    1. 通过local graph命令绘制canvas图形;
    2. 将canvas图形插入到当前笔记合适的位置;
    3. 去除canvas原先的view(window);

    一步一坑

    1. local graph绘制

    直接使用obsidian原生的local graph命令绘制当前笔记的关联图谱的view。

    await this.app.commands.executeCommandById("graph:open-local");
    

    在实验过程中发现,如果这样的话执行的话会创建split view,直接影响obsidian的使用体验。

    经过权衡,可以通过创建独立的window来解决这个问题:

    await this.app.commands.executeCommandById("graph:open-local");
    await new Promise((resolve) => setTimeout(resolve, 200));
    const graphLeaf = this.app.workspace.getLeavesOfType('localgraph')[0];
    this.app.workspace.moveLeafToPopout(graphLeaf);
    

    2. canvas插入当前笔记

    这一步骤属于基础的html query操作,通过view-content找到local graph的元素,然后将local graph的canvas插入到指定元素的位置(当前选择的是view-header元素)。

    const localgraph = graphLeaf.view.containerEl.getElementsByClassName('view-content')[0];
    const noteHeader = fileLeaf.containerEl.getElementsByClassName('view-header')[0];
    noteHeader.parentElement.insertAfter(localgraph, noteHeader);
    

    在实验过程中发现,发现了几个问题:

    • element占用空间太大了;
    • tab 切换的时候会出现空白的html element残留;

    通过unlink tab以及style自定义解决这些问题:

    const localgraph = graphLeaf.view.containerEl.getElementsByClassName('view-content')[0];
    localgraph.style.width = '80%';
    localgraph.style.height = '360px';
    localgraph.style.alignSelf = 'center';
    localgraph.addClass('embed-local-graph-with-personal-assistant');
    // unlink
    graphLeaf.tabHeaderStatusLinkEl.click();
    
    const noteHeader = fileLeaf.containerEl.getElementsByClassName('view-header')[0];
    noteHeader.parentElement.insertAfter(localgraph, noteHeader);
    

    3. tab切换问题

    obsidian tab切换的时候会发现几个问题:

    • 重复插入canvas;
    • graph view配置menu默认打开;
    • graph view没有继承颜色等配置;
    await this.app.commands.executeCommandById("personal-assistant:set-local-graph-view-colors");
    fileLeaf.view.containerEl.getElementsByClassName('graph-controls-button')[0].click();
    
    this.app.workspace.on('file-open', (file) => {
        // console.log(file);
        // let fileName = file ? file.basename : "--";
        // console.log(fileName);
        // if (this.app.workspace.activeLeaf.getDisplayText() !== fileName) {
        //     console.log("back"); return;
        // }
    
        const wins = BrowserWindow.getAllWindows();
        const graphWindow2Close = wins.find((win) => {
            //return win.getTitle().startsWith("Graph") && win.id === mainWinID;
            return !win.isVisible()
        },);
        if (graphWindow2Close) {
            console.log(graphWindow2Close.getTitle());
            graphWindow2Close.close();
        } else {
            fileLeaf.view.containerEl.getElementsByClassName('embed-local-graph-with-personal-assistant')[0]?.remove();
        }
    },);
    

    4. 功能触发

    通过dataview可以借助在任意笔记文件中执行js的能力实现该功能的触发,这样可以保证在打开笔记文件的时候就会触发local graph集成的。

    后续

    当前这样的实现的思路勉强可以满足需求,但是还有几个缺陷不足:

    • dataviewjs 触发执行的时机是lazy的,这会导致打开文件之后插入canvas的动作有滞后;
    • 由于electron window接口的问题,会导致editor/preview视图切换的时候出现显示问题;
    • 偶尔导致obsidian app闪退;

    对于这样的问题看是需要通过插件来实现,后续我打算把这个功能集成personal assistant中做到自动化开关和配置。

    附录

    // 注意在 obsidian 中通过 `dataviewjs` 来触发,也可以配置到模版中
    const { BrowserWindow } = require("@electron/remote");
    
    const fileLeaf = this.app.workspace.activeLeaf;
    if (fileLeaf.view.containerEl.getElementsByClassName('embed-local-graph-with-personal-assistant').length > 0) {
        console.log("already embedded");
        return;
    }
    const mainWin = BrowserWindow.getAllWindows();
    if (mainWin.length !== 1) {
        // new file tab to embed local graph
        for (let i = 0; i < mainWin.length; i++) {
            if (mainWin[i].getTitle().startsWith("Graph of")) {
                new Notice("closing embedded local graph windown");
                mainWin[i].close();
            }
        }
        return;
    }
    const mainWinID = mainWin[0].id;
    
    await this.app.commands.executeCommandById("graph:open-local");
    //await this.app.commands.executeCommandById("personal-assistant:local-graph");
    await new Promise((resolve) => setTimeout(resolve, 100));
    
    const graphLeaf = this.app.workspace.getLeavesOfType('localgraph')[0];
    this.app.workspace.moveLeafToPopout(graphLeaf);
    const winsFocus = BrowserWindow.getFocusedWindow();
    winsFocus.hide();
    
    // unlink
    graphLeaf.tabHeaderStatusLinkEl.click();
    
    const localgraph = graphLeaf.view.containerEl.getElementsByClassName('view-content')[0];
    localgraph.style.width = '80%';
    localgraph.style.height = '360px';
    localgraph.style.alignSelf = 'center';
    localgraph.addClass('embed-local-graph-with-personal-assistant');
    
    const noteHeader = fileLeaf.containerEl.getElementsByClassName('view-header')[0];
    noteHeader.parentElement.insertAfter(localgraph, noteHeader);
    
    await this.app.commands.executeCommandById("personal-assistant:set-local-graph-view-colors");
    fileLeaf.view.containerEl.getElementsByClassName('graph-controls-button')[0].click();
    
    this.app.workspace.on('file-open', (file) => {
        // console.log(file);
        // let fileName = file ? file.basename : "--";
        // console.log(fileName);
        // if (this.app.workspace.activeLeaf.getDisplayText() !== fileName) {
        //     console.log("back"); return;
        // }
    
        const wins = BrowserWindow.getAllWindows();
        const graphWindow2Close = wins.find((win) => {
            //return win.getTitle().startsWith("Graph") && win.id === mainWinID;
            return !win.isVisible()
        },);
        if (graphWindow2Close) {
            console.log(graphWindow2Close.getTitle());
            graphWindow2Close.close();
        } else {
            fileLeaf.view.containerEl.getElementsByClassName('embed-local-graph-with-personal-assistant')[0]?.remove();
        }
    },);
    

    References

    1. Notion Backlinks
    2. Obsidian Publish


沪ICP备19023445号-2号
友情链接