js 代码片段实现,只聚焦当前文档所在的目录树

js 代码片段实现,只聚焦当前文档所在的目录树

需求

在使用“始终定位打开的文档”功能时,只展开当前文档的文档树,自动关闭(不展开)其它无关的文档树。

例如:

有笔记本 A,内有文档 A1,子文档 A2、子子文档 A3。

有笔记本 B,内有文档 B1,子文档 B2、子子文档 B3。

已将文档 A2、B2 打开,显示在上方页签栏中。

启用“始终定位打开的文档”功能。

点击 A2 页签,左侧文档树自动展开,并定位到 A-A1-A2;A3 列表不展开

点击 B2 页签,左侧文档树自动展开,定位到 B-B1-B2;同时关闭 A-A1-A2 文档树的展开状态,恢复到笔记本 A。也就是仅保留当前活动页签对应的文档树是展开状态,其它文档树分支全部关闭,包括当前活动文档的子文档树。

实现

// 加载时是否自动定位当前文档
const autoFocusTreeOnload = true;
// 等待标签页容器渲染完成后开始监听
whenElementExist('.layout__center').then(async element => {
    // 等待笔记列表加载完毕
    await sleep(40);
    // 监听页签切换事件
    observeTabChanged(element, (tab) => {
        // 折叠所有笔记,然后定位当前笔记
        collapseAllBooksThenFocusCurrentBook(element, tab);
    });
    // 加载时定位当前文档
    if(autoFocusTreeOnload) {
        await sleep(40);
        focusCurrentDocInTrees();
    }
});
// 折叠所有笔记,然后定位当前笔记
async function collapseAllBooksThenFocusCurrentBook(element, tab) {
    let currNodeId = "";
    // 等待激活文档加载完毕
    await whenElementExist(()=>{
        const content = element.querySelector('.layout-tab-container [data-id="'+tab.getAttribute("data-id")+'"]');
        // 获取当前文档的node-id
        currNodeId = document.querySelector('.layout-tab-container [data-id="'+tab.getAttribute("data-id")+'"] .protyle-title')?.getAttribute("data-node-id") || "";
        return content && content.getAttribute("data-loading") === "finished" && currNodeId;
    });
    // 获取当前文档所在的路径和当前笔记,并折叠当前文档以外的目录
    let currBoxDataUrl = "";
    if(currNodeId){
        // 定位当前文档
        focusCurrentDocInTrees();
        // 等待定位完成
        let currTreeItem = null;
        await whenElementExist(()=>{
            currTreeItem = document.querySelector("ul.b3-list[data-url] li[data-node-id='" + currNodeId + "']");
            return currTreeItem;
        });
        // 获取当前文档在目录树中的node-id
        if(currTreeItem) {
            // 获取当前笔记的data-url
            currBoxDataUrl = currTreeItem.closest('ul.b3-list[data-url]')?.getAttribute("data-url") || "";
            if(currBoxDataUrl){
                // 获取当前文档所在的路径
                currTreeItemPath = currTreeItem.getAttribute("data-path");
                // 获取所有展开的目录
                const trees = document.querySelectorAll("ul.b3-list[data-url='" + currBoxDataUrl + "'] span.b3-list-item__toggle svg.b3-list-item__arrow--open");
                trees.forEach(arrowBtn => {
                    // 如果是当前文档的上级目录则跳过
                    const itemPath = arrowBtn.closest('li.b3-list-item')?.getAttribute("data-path")?.replace(/\.sy$/i, '');
                    if(currTreeItemPath.startsWith(itemPath)){
                        return;
                    }
                    // 其他目录则折叠
                    if (arrowBtn.parentElement) {
                        arrowBtn.parentElement.click();
                    }
                });
            }
        }
    }
    // 折叠除当前笔记外的所有笔记
    document.querySelectorAll("ul.b3-list[data-url]").forEach(async book => {
        // 如果是当前笔记则跳过
        if(book.getAttribute("data-url") === currBoxDataUrl) {
            return;
        }
        // 折叠笔记
        const bookArrowBtn = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__toggle');
        if (bookArrowBtn && bookArrowBtn.firstElementChild.classList.contains("b3-list-item__arrow--open")) {
            bookArrowBtn.click();
        }
    });
}
// 在目录树中定位当前文档
async function focusCurrentDocInTrees() {
    // 定位当前文档
    document.querySelector(".layout-tab-container .block__icons span[data-type=focus]")?.click();
    // 等待定位完成
    await whenElementExist(()=>{
        return document.querySelector('ul.b3-list[data-url] li[data-type="navigation-file"].b3-list-item--focus');
    });
    // 处理官方定位,在未打开目录树时,左侧dock区目录树显示隐藏按钮样式会获取焦点的bug
    await sleep(40);
    const dockFileTreeBtn = document.querySelector('#dockLeft span[data-type="file"]');
    if(document.querySelector('#layouts .layout__dockl')?.style?.width === "0px"){
        if(dockFileTreeBtn.classList.contains("dock__item--active")) {
            dockFileTreeBtn.classList.remove("dock__item--active");
        }
        if(dockFileTreeBtn.classList.contains("dock__item--activefocus")) {
            dockFileTreeBtn.classList.remove("dock__item--activefocus");
        }
    }
}
// 监听页签切换事件
function observeTabChanged(parentNode, callback) {
    // 创建一个回调函数来处理观察到的变化
    const observerCallback = function(mutationsList, observer) {
        // 用常规方式遍历 mutationsList 中的每一个 mutation
        for (let mutation of mutationsList) {
            // 属性被修改
            if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                const element = mutation.target;
                if (element.tagName.toLowerCase() === 'li' && element.getAttribute('data-type') === 'tab-header' && element.classList.contains('item--focus')) {
                    if(typeof callback === 'function') callback(element);
                }
            }
            // 如果有新的子节点被添加
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'li') {
                        if (node.getAttribute('data-type') === 'tab-header' && node.classList.contains('item--focus')) {
                            if(typeof callback === 'function') callback(node);
                        }
                    }
                });
            }
        }
    };
    // 创建一个观察器实例并传入回调函数
    const observer = new MutationObserver(observerCallback);
    // 配置观察器:传递一个对象来指定观察器的行为
    const config = { attributes: true, attributeFilter: ['class'], childList: true, subtree: true };
    // 开始观察目标节点
    observer.observe(parentNode, config);
    // 返回一个函数,用于停止观察
    return function stopObserving() {
        observer.disconnect();
    };
}
// 延迟执行
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
// 等待元素渲染完成后执行
function whenElementExist(selector) {
    return new Promise(resolve => {
        const checkForElement = () => {
            let element = null;
            if (typeof selector === 'function') {
                element = selector();
            } else {
                element = document.querySelector(selector);
            }
            if (element) {
                resolve(element);
            } else {
                requestAnimationFrame(checkForElement);
            }
        };
        checkForElement();
    });
}

效果

t2

使用方法

设置 》外观 》代码片段 》js 中新增加代码片段,然后把上面的代码粘贴过去即可。

image-WGgDxi6

最后

由于刚接触思源,对思源 api 还不是很了解,所以选择用 js 代码片段实现,纯原生 js 实现,方法比较笨,勉强能用。

不足之处也请各界大佬们帮忙改进或多多批评指正!

文章来源:https://ld246.com/article/1717306355600

image.png

留下你的脚步
推荐阅读