思源笔记自动移动大纲JS

结城 思源笔记 4 次阅读 612 字 发布于 2026-05-18 预计阅读时间: 3 分钟


分享一个思源笔记社区大佬贡献的自动移动大纲位置JS扩展

// 滚动时自动定位大纲位置
// 暂不支持手机和预览
// see https://ld246.com/article/1757773937694
(()=>{
    if(isMobile()) return;
    eventBusOn('loaded-protyle-static', (event) => {
        const protyle = event?.detail?.protyle;
        const protyleContent = protyle?.element?.querySelector('.protyle-content');
        const wysiwyg = protyle?.element?.querySelector('.protyle-wysiwyg');
        // 滚动时执行
        let ticking = false;
        if(!protyleContent.scrollEventOutline) {
            protyleContent.scrollEventOutline = true;
            protyleContent?.addEventListener('scroll', () => {
            if (!ticking) {
                requestAnimationFrame(() => {
                  openCursorHeading('scroll', wysiwyg);
                  ticking = false;
                });
                ticking = true;
              }
            });
        }
        // 文档切换事件
        eventBusOn('switch-protyle', () => {
            setTimeout(()=>openCursorHeading('load', wysiwyg), 200);
        });
        // 页面加载时执行
        setTimeout(()=>openCursorHeading('load', wysiwyg), 800);
    });
    // 大纲加载时执行
    setTimeout(()=>{
        whenOutlineExist((outline, isExist) => {
            if(isExist) return; // 已存在返回
            const wysiwyg = getProtyle()?.querySelector('.protyle-wysiwyg');
            setTimeout(()=>openCursorHeading('load', wysiwyg), 200);
        });
    }, 2000);
    function getTopestHead(by = 'scroll', parentNode) {
        return [...(parentNode || document).querySelectorAll('.h1,.h2,.h3,.h4,.h5,.h6')].find(h => {
            const top = h.getBoundingClientRect().top;
            return by === 'scroll' ? top > 80 && top < 160 : top > 80;
        });
    }
    function openCursorHeading(by, parentNode) {
        //获取是否在heading中
        const heading = getTopestHead(by, parentNode);
        if(!heading) return;
        // 展开光标处的标题
        headingNodeId = heading.dataset.nodeId;
        node = document.querySelector('.sy__outline [data-node-id="'+headingNodeId+'"]');
        if(node && ['scroll', 'load'].includes(by)) {
            // 滚动时,设置大纲选中状态
            document.querySelector('.sy__outline li.b3-list-item.b3-list-item--focus')?.classList?.remove('b3-list-item--focus');
            node?.classList?.add('b3-list-item--focus');
            node?.scrollIntoView({block: 'center'});
        }
    }
    function eventBusOn(eventName, callback) {
        const pluginName = 'my-custom-plugin';
        if(window.siyuan.ws.app.plugins?.length === 0) {
            console.log('绑定事件'+eventName+'失败,请至少安装一个插件');
            return false;
        }
        let myPlugin = window.siyuan.ws.app.plugins.find(item=>item.name === pluginName);
        if(!myPlugin) {
            const Plguin = Object.getPrototypeOf(window.siyuan.ws.app.plugins[0].constructor);
            const MyPlugin = class extends Plguin{};
            myPlugin = new MyPlugin({app:window.siyuan.ws.app.appId, name:pluginName, displayName:pluginName});
            myPlugin.openSetting = null; // 防止顶部插件按钮添加设置菜单
            window.siyuan.ws.app.plugins.push(myPlugin);
        }
        myPlugin.eventBus.on(eventName, callback);
        return true;
    }
    function whenOutlineExist(callback) {
        // 确保回调函数有效
        if (typeof callback !== 'function') {
            console.error('onOutlineExist: 参数必须是函数');
            return;
        }
        // 获取已存在的容器(.layout__dockr 或 .layout__dockl)
        const containers = document.querySelectorAll('.layout__dockr, .layout__dockl');
        if(!containers || containers.length === 0){
            console.error('[onOutlineExist] 未找到 .layout__dockr 或 .layout__dockl');
            return;
        }
        // 已存在直接返回
        const outline = document.querySelector('.sy__outline');
        if(outline) {
            callback(outline, true);
            return;
        }
        // 创建 MutationObserver 监听后代中 .sy__outline 的添加
        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== Node.ELEMENT_NODE) return;
                        // 检查节点自身是否是 .sy__outline
                        if (node.matches('.sy__outline')) {
                            callback(node);
                            observer.disconnect();
                        }
                    });
                }
            }
        });
        // 开始监听容器的子树变化
        observer.observe(containers[0], { childList: true, subtree: true});
        observer.observe(containers[1], { childList: true, subtree: true});
    }
    function getProtyle() {
        return document.querySelector('#editor') || document.querySelector(`.protyle[data-id="${[...document.querySelectorAll('.layout-tab-bar [data-type="tab-header"]')]
          .reduce((max, tab) => Number(tab?.dataset?.activetime) > Number(max?.dataset?.activetime || -1) ? tab : max, null)?.dataset?.id}"]`);
    }
    function isMobile() {
        return !!document.getElementById("sidebar");
    }
})();
给时光以生命,给岁月以文明
最后更新于 2026-06-15