思源折叠大纲改进版

思源折叠大纲改进版

see https://ld246.com/article/1729605574188

image

// see https://ld246.com/article/1729605574188
(async ()=>{
    whenElementExist('.sy__outline > .fn__flex-1').then(async el => {
        //let clicking = false;
        // 监听大纲标题被添加
        observeChildAddition(el, node => {
            return node.tagName.toLowerCase() === 'ul' &&
                        node.classList.contains('b3-list') && 
                        node.querySelector('.b3-list-item')
        }, uls => {
            // 获取大纲列表
            const ul = uls[0];
            // 遍历大纲第一级子元素
            Array.from(ul.children).forEach(item => {
                // 初始时,仅打开第一级
                if(item.tagName === 'LI') {
                    const toggleBtn = item.querySelector('.b3-list-item__toggle');
                    const svg = toggleBtn?.querySelector('svg.b3-list-item__arrow');
                    if(!svg.classList.contains('b3-list-item__arrow--open')) {
                        svg.classList.add('b3-list-item__arrow--open');
                    }
                }
                if(item.tagName === 'UL') {
                    if(item.classList.contains('fn__none')) {
                        item.remove('fn__none');
                    }
                    // 初始时,隐藏第一级下面的后代元素
                    itemsShow(item, false);
                }
                // 监听大纲鼠标移入事件
                const ul = item.tagName === 'LI' ? item.nextElementSibling : item;
                item.addEventListener('mouseenter', (event) => {
                    if(!ul || ul?.tagName !== 'UL') return;
                    // 鼠标移入显示第一级后面的后代元素
                    itemsShow(ul, true);
                })
                // 监听大纲鼠标移出事件
                item.addEventListener('mouseleave', (event) => {
                    //if(clicking) {
                        //clicking = false;
                        //return;
                    //}
                    if(!ul || ul?.tagName !== 'UL') return;
                    // 鼠标移出隐藏第一级后面的后代元素
                    itemsShow(ul, false);

                    // 始终定位光标处的标题
                    const heading = isInHeading();
                    if(heading){
                        headingNodeId = heading.dataset.nodeId;
                        const outlineNode = document.querySelector('.sy__outline [data-node-id="'+headingNodeId+'"]');
                        openCursorHeading(outlineNode);
                    }
                });
                // 监听大纲点击事件
                // item.addEventListener('click', (event) => {
                //     clicking = true;
                // });
            });
        });

        // 添加光标被移动位置事件
        document.addEventListener("selectionchange", () => {
            //获取是否在heading中
            const heading = isInHeading();
            if(!heading) return;

            // 关闭其他大纲展开
            Array.from(el.firstElementChild.children).forEach(item => {
                itemsShow(item, false);
            });

            // 展开光标处的标题
            headingNodeId = heading.dataset.nodeId;
            const outlineNode = document.querySelector('.sy__outline [data-node-id="'+headingNodeId+'"]');
            openCursorHeading(outlineNode);
        }, false);
    });
    function isInHeading() {
        const el = getCursorElement();
        let heading = el?.closest('[data-type="NodeHeading"]');
        if(heading) {
            return heading;
        }
        const Sibling = el?.closest('[data-node-index]');
        heading = findPreviousNodeHeading(Sibling);
        return heading;
    }
    function findPreviousNodeHeading(el) {
        // 从当前元素开始向前查找兄弟节点
        let sibling = el?.previousElementSibling;
  
        while (sibling) {
            // 检查是否具有 data-type="NodeHeading" 的属性
            if (sibling.getAttribute('data-type') === 'NodeHeading') {
                return sibling; // 找到了,返回这个节点
            }
            // 继续向前查找
            sibling = sibling.previousElementSibling;
        }
  
        // 如果没有找到符合条件的兄弟节点,返回null
        return null;
    }
    function openCursorHeading(node) {
        // 遍历节点的祖先节点
        while (node && !node.classList.contains('b3-list')) {
            if (node.tagName === 'UL' && node.classList.contains('fn__none')) {
                // 展开箭头
                const li = node.previousElementSibling;
                if(li){
                    const arrowSvg = li.querySelector('.b3-list-item__toggle svg.b3-list-item__arrow:not(.b3-list-item__arrow--open)');
                    if(arrowSvg) arrowSvg.classList.add('b3-list-item__arrow--open');
                }
                // 移除.fn__none类
                node.classList.remove('fn__none');
            }
            // 向上查找父节点
            node = node.parentElement;
        }
    }
    function getCursorElement() {
        const selection = window.getSelection();
        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            // 获取选择范围的起始位置所在的节点
            const startContainer = range.startContainer;
            // 如果起始位置是文本节点,返回其父元素节点
            const cursorElement = startContainer.nodeType === Node.TEXT_NODE
                ? startContainer.parentElement
                : startContainer;
  
            return cursorElement;
        }
        return null;
    }
    // 动态显示隐藏子标题
    function itemsShow(ul, isOpen) {
        if(isOpen){
           const svgs = ul.querySelectorAll('span.b3-list-item__toggle svg:not(.b3-list-item__arrow--open)');
            svgs.forEach(item => {
                item.classList.add('b3-list-item__arrow--open');
            });
            const uls = ul.querySelectorAll('ul.fn__none');
            uls.forEach(item => {
                item.classList.remove('fn__none');
            });
        } else {
            const svgs = ul.querySelectorAll('span.b3-list-item__toggle svg.b3-list-item__arrow--open');
            svgs.forEach(item => {
                item.classList.remove('b3-list-item__arrow--open');
            });
            const uls = ul.querySelectorAll('ul:not(.fn__none)');
            uls.forEach(item => {
                item.classList.add('fn__none');
            });
        }
    }
    function observeChildAddition(el, filter, handler) {
        // 配置观察器选项
        const config = { attributes: false, childList: true, subtree: false };
        // 定义回调函数
        const callback = function(mutationsList, observer) {
            // 遍历 mutation 列表
            for (let mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    // 查找新增加的具有类名 'b3-list' 的 ul 元素
                    const newULs = Array.from(mutation.addedNodes).filter(node => node.nodeType === Node.ELEMENT_NODE && filter(node));
                    // 如果有新的 ul 元素被添加,则调用处理函数
                    if(newULs.length > 0) {
                        handler(newULs);
                    }
                }
            }
        };
        // 创建一个新的 MutationObserver 实例
        const observer = new MutationObserver(callback);
        // 开始观察目标节点
        observer.observe(el, config);
        // 返回一个函数来停止观察
        return () => {
            observer.disconnect();
        };
    }
    // 等待元素渲染完成后执行
    function whenElementExist(selector, bySetTimeout = false, delay = 40) {
        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 {
                    if (bySetTimeout) {
                        setTimeout(checkForElement, delay);
                    } else {
                        requestAnimationFrame(checkForElement);
                    }
                }
            };
            checkForElement();
        });
    }
})();
image.png

留下你的脚步
推荐阅读