AI 修改了 原本的插件 no-dupe-leaves 同一个文件只能打开一个窗口

修改的地方在于

  1. 对看板kanban生效
  2. 对目录列表生效
/*
Fixed "No Duplicate Leaves" - Solves Focus Issue
*/

var obsidian = require("obsidian");

class NoDuplicatePlugin extends obsidian.Plugin {
    async onload() {
        console.log("No Duplicate Leaves (Fixed Focus) loaded");

        // 1. 拦截 openLinkText (处理 [[双链]] 点击)
        this.register(
            around(obsidian.Workspace.prototype, {
                openLinkText: (next) => {
                    return function (linktext, sourcePath, newLeaf, openViewState) {
                        if (newLeaf) {
                            return next.call(this, linktext, sourcePath, newLeaf, openViewState);
                        }

                        const targetFile = app.metadataCache.getFirstLinkpathDest(
                            obsidian.getLinkpath(linktext),
                            sourcePath
                        );

                        if (targetFile) {
                            // 链接点击通常不需要延迟,但为了保险起见,也可以复用同样的逻辑
                            const leafFound = activateLeafByPath(targetFile.path, linktext);
                            if (leafFound) return; 
                        }

                        return next.call(this, linktext, sourcePath, newLeaf, openViewState);
                    };
                },
            })
        );

        // 2. 拦截 openFile (处理 文件列表/资源管理器 点击)
        this.register(
            around(obsidian.WorkspaceLeaf.prototype, {
                openFile: (next) => {
                    return function (file, openState) {
                        // 尝试查找并跳转,传入 delay=true
                        const leafFound = activateLeafByPath(file.path, null, this, true);
                        
                        if (leafFound) {
                            // 阻止当前标签页加载该文件,直接返回成功
                            return Promise.resolve(); 
                        }

                        return next.call(this, file, openState);
                    };
                },
            })
        );
    }

    onunload() {
        console.log("No Duplicate Leaves unloaded");
    }
}

// --- 核心辅助函数 ---

/**
 * 遍历所有标签页,寻找是否已打开指定路径
 * @param {string} path - 文件路径
 * @param {string} linktext - 链接文本
 * @param {WorkspaceLeaf} ignoreLeaf - 忽略的标签页(通常是当前正在操作的)
 * @param {boolean} delay - 是否延迟跳转(解决文件列表点击的焦点抢占问题)
 */
function activateLeafByPath(path, linktext = null, ignoreLeaf = null, delay = false) {
    let foundLeaf = null;
    
    app.workspace.iterateAllLeaves((leaf) => {
        if (foundLeaf) return;
        if (ignoreLeaf && leaf === ignoreLeaf) return;

        const viewState = leaf.getViewState();
        
        // 匹配 markdown 或 kanban
        const isMatch = 
            (viewState.type === "markdown" || viewState.type === "kanban") &&
            viewState.state &&
            viewState.state.file === path;

        if (isMatch) {
            foundLeaf = leaf;
        }
    });

    if (foundLeaf) {
        // --- 核心修复点 ---
        // 如果是点击文件列表触发的 (delay=true),我们需要通过 setTimeout
        // 让出主线程,等待 Obsidian 原生点击事件处理完焦点后,再把焦点抢过来。
        if (delay) {
            setTimeout(() => {
                app.workspace.setActiveLeaf(foundLeaf, { focus: true });
            }, 10); // 10ms 的延迟通常足以骗过原生逻辑
        } else {
            // 双链点击通常不需要延迟
            app.workspace.setActiveLeaf(foundLeaf, { focus: true });
        }

        // 处理滚动锚点 (只针对 markdown)
        if (linktext) {
             const viewState = foundLeaf.getViewState();
             if (viewState.type === "markdown") {
                 // 稍微延迟滚动以确保视图已激活
                 setTimeout(() => scrollToElement(linktext, foundLeaf), 20);
             }
        }
        
        return true;
    }

    return false;
}

/**
 * 滚动到锚点
 */
function scrollToElement(linktext, leaf) {
    const hashMatch = linktext.match(/(#|\^)(.*)$/);
    if (!hashMatch) return; 

    const view = leaf.view;
    if (!view || !view.editor) return;

    const cache = app.metadataCache.getFileCache(view.file);
    if (!cache) return;

    const selector = hashMatch[0];
    const type = hashMatch[1];
    const name = hashMatch[2];

    let line = -1;

    if (type === "#") {
        const heading = cache.headings?.find(h => h.heading === name);
        if (heading) line = heading.position.start.line;
    } else if (type === "^") {
        const block = cache.blocks?.[name];
        if (block) line = block.position.start.line;
    }

    if (line >= 0) {
        view.editor.setCursor({ line: line, ch: 0 });
        view.editor.scrollIntoView({ from: { line: line, ch: 0 }, to: { line: line, ch: 0 } }, true);
    }
}

// --- Monkey Around 工具 ---
function around(obj, factories) {
    const removers = Object.keys(factories).map((key) => {
        const prev = obj[key];
        const next = factories[key](prev);
        const original = prev;
        obj[key] = next;
        return () => {
            if (obj[key] === next) {
                obj[key] = original;
            }
        };
    });
    return {
        detach: () => {
            removers.forEach((r) => r());
        },
    };
}

module.exports = NoDuplicatePlugin;
  1. 如果有文件被锁定 也能正常跳转
/*
Ultra-Fast "No Duplicate Leaves" - Visual Glitch Reducer
*/

var obsidian = require("obsidian");

class NoDuplicatePlugin extends obsidian.Plugin {
    async onload() {
        console.log("No Duplicate Leaves (Optimized) loaded");

        // 1. 拦截 openLinkText
        this.register(
            around(obsidian.Workspace.prototype, {
                openLinkText: (next) => {
                    return function (linktext, sourcePath, newLeaf, openViewState) {
                        if (newLeaf) return next.call(this, linktext, sourcePath, newLeaf, openViewState);

                        const targetFile = app.metadataCache.getFirstLinkpathDest(
                            obsidian.getLinkpath(linktext),
                            sourcePath
                        );

                        if (targetFile) {
                            if (activateLeafByPath(targetFile.path, linktext)) return;
                        }
                        return next.call(this, linktext, sourcePath, newLeaf, openViewState);
                    };
                },
            })
        );

        // 2. 拦截 openFile
        this.register(
            around(obsidian.WorkspaceLeaf.prototype, {
                openFile: (next) => {
                    return function (file, openState) {
                        // 这里的 this 就是那个刚刚被 Obsidian 创建出来的、还没来得及加载内容的新标签页
                        
                        // 尝试跳转到旧标签页
                        const leafFound = activateLeafByPath(file.path, null, this, true);
                        
                        if (leafFound) {
                            // 检查当前 leaf 是否是新创建的空 leaf
                            const isNewEmptyLeaf = this.view && !this.view.file && this.view.getViewType() === "empty";
                            
                            if (isNewEmptyLeaf) {
                                // 【极速隐身】
                                // 在等待销毁的这几毫秒里,先把它藏起来,不让用户看到它渲染的过程
                                if (this.containerEl) {
                                    this.containerEl.style.display = "none";
                                }
                                
                                // 立即销毁
                                setTimeout(() => {
                                    this.detach(); 
                                }, 0);
                            }

                            return Promise.resolve(); 
                        }

                        return next.call(this, file, openState);
                    };
                },
            })
        );
    }

    onunload() {
        console.log("No Duplicate Leaves unloaded");
    }
}

// --- 辅助函数 ---

function activateLeafByPath(path, linktext = null, ignoreLeaf = null, delay = false) {
    let foundLeaf = null;
    
    app.workspace.iterateAllLeaves((leaf) => {
        if (foundLeaf) return;
        if (ignoreLeaf && leaf === ignoreLeaf) return;

        const viewState = leaf.getViewState();
        const isMatch = 
            (viewState.type === "markdown" || viewState.type === "kanban") &&
            viewState.state &&
            viewState.state.file === path;

        if (isMatch) foundLeaf = leaf;
    });

    if (foundLeaf) {
        // 这里的 10ms 延迟是为了解决焦点抢占,无法去掉,去掉就会跳转失败
        if (delay) {
            setTimeout(() => {
                app.workspace.setActiveLeaf(foundLeaf, { focus: true });
            }, 10);
        } else {
            app.workspace.setActiveLeaf(foundLeaf, { focus: true });
        }

        if (linktext) {
             const viewState = foundLeaf.getViewState();
             if (viewState.type === "markdown") {
                 setTimeout(() => scrollToElement(linktext, foundLeaf), 20);
             }
        }
        return true;
    }
    return false;
}

function scrollToElement(linktext, leaf) {
    const hashMatch = linktext.match(/(#|\^)(.*)$/);
    if (!hashMatch) return; 

    const view = leaf.view;
    if (!view || !view.editor) return;

    const cache = app.metadataCache.getFileCache(view.file);
    if (!cache) return;

    const selector = hashMatch[0];
    const type = hashMatch[1];
    const name = hashMatch[2];

    let line = -1;

    if (type === "#") {
        const heading = cache.headings?.find(h => h.heading === name);
        if (heading) line = heading.position.start.line;
    } else if (type === "^") {
        const block = cache.blocks?.[name];
        if (block) line = block.position.start.line;
    }

    if (line >= 0) {
        view.editor.setCursor({ line: line, ch: 0 });
        view.editor.scrollIntoView({ from: { line: line, ch: 0 }, to: { line: line, ch: 0 } }, true);
    }
}

function around(obj, factories) {
    const removers = Object.keys(factories).map((key) => {
        const prev = obj[key];
        const next = factories[key](prev);
        const original = prev;
        obj[key] = next;
        return () => {
            if (obj[key] === next) obj[key] = original;
        };
    });
    return { detach: () => removers.forEach((r) => r()) };
}

module.exports = NoDuplicatePlugin;