给编辑器增加标尺

给编辑器增加标尺

// 功能:给编辑器增加标尺
// see https://ld246.com/article/1725849206361
(()=>{
    //////////// 配置区 ////////////////////

    // 标尺的间隔大小,默认50 (50代表50px高度)
    const rulerGaps = 50;

    //////////// 主逻辑区 ////////////////////
  
    addRulerStyle();
    whenElementExist('.layout__center').then(async element => {
        // 等待笔记列表加载完毕
        await sleep(40);
        // 防止死循环
        let locking = false;
        // 监听编辑器加载事件
        observeDomChange(element, async (mutation) => {
            // 添加标尺本身则不监听(暂无用到)
            // if(mutation.addedNodes && Array.from(mutation.addedNodes)?.some(item=>item.classList?.contains("ruler"))){
            //     return;
            // }
            if (mutation.target.classList?.contains("protyle-wysiwyg")) {
               const editor = mutation.target;
                if(editor && editor.closest){
                    // 等待编辑器加载完毕
                    const protyle = editor.closest(".protyle");
                    if(protyle.dataset.loading !== 'finished'){
                        await whenElementExist(()=>editor?.closest(".protyle") === 'finished');
                    }
                    // 编辑器加载完毕(包括编辑器内容被修改)
                    //console.log('editor loaded');
                    // TODO 这里写你要进行的操作
                    if(locking) return;
                    locking = true;
                    //console.log('runed');
                    //添加标尺
                    addRuler(editor, protyle);
                    setTimeout(()=>{
                        locking = false;
                        // 加载时,防止编辑器未完全加载,再执行一次
                        if(!document.querySelector('#ruler-'+protyle.dataset.id)) addRuler(editor, protyle);
                    }, 1000)
                }
            }
        });
    });
  
    //////////// 函数辑区 ////////////////////
  
    async function addRuler(editor, protyle) {
        let ruler = document.querySelector('#ruler-'+protyle.dataset.id);
        const first = !ruler;
        let containner;
        if(!ruler) {
            containner = document.createElement('div');
            containner.className = 'protyle-ruler';
            ruler = document.createElement('div');
            ruler.id = 'ruler-'+protyle.dataset.id;
            ruler.className = 'ruler';
            ruler.contentEditable = false;
        }
        if(first) await sleep(500);
        // 根据 editor 的高度计算需要多少个刻度线
        const numberOfTicks = Math.floor(editor.clientHeight / rulerGaps);
        //ruler.style.height = (rulerGaps * numberOfTicks) + 'px';
        if(ruler.children.length > numberOfTicks) {
            while (ruler.children.length > numberOfTicks) {
                ruler.removeChild(ruler.children[ruler.children.length-1]);
            }
            return;
        }
        for (let i = ruler.children.length; i <= numberOfTicks; i++) {
            const tick = document.createElement('div');
            tick.className = 'tick';
          
            const tickNumber = document.createElement('span');
            tickNumber.className = 'tick-number';
            tickNumber.textContent = i; // 显示间隔数
          
            const tickLine = document.createElement('div');
            tickLine.className = 'tick-line';
          
            tick.appendChild(tickNumber);
            tick.appendChild(tickLine);
          
            ruler.appendChild(tick);
        }
  
        if(first) {
            //editor.prepend(ruler);
            containner.appendChild(ruler);
            editor.parentElement.insertBefore(containner, editor);
        }
    }
    function addRulerStyle() {
        if(document.querySelector("#rulerStyle")) return;
        const style = document.createElement('style');
        style.id = 'rulerStyle';
        style.textContent = `
            .protyle-ruler {
                position:relative;
          
                .ruler {
                    position: absolute;
                    left: 0;
                    top: 0;
                    width: 30px; /* 宽度包括数字和短横线 */
                    display: flex;
                    flex-direction: column;
                    justify-content: flex-start;
                    align-items: flex-start; /* 让内容靠左对齐 */
                    border-right: 0.5px solid var(--b3-border-color);
                    font-size:13px;
                    cursor: default;
                    user-select: none;
                    /*overflow:hidden;*/
                }
              
                .tick {
                    position: relative;
                    width: 100%;
                    height: ${rulerGaps||50}px; /* 每个刻度线的高度 */
                    display: flex;
                    justify-content: flex-end;
                    align-items: center;
                }
              
                .tick-line {
                    width: 8px;
                    height: 1px;
                    /*background-color: var(--b3-theme-on-surface-light);*/
                    background-color: var(--b3-border-color);
                }
              
                .tick-number {
                    writing-mode: vertical-rl; /* 文字垂直书写 */
                    transform: rotate(180deg); /* 旋转文字 */
                    margin-right: 1px; /* 与数字之间留点空隙 */
                    color: var(--b3-theme-on-surface-light);
                }
            }
        `
        document.head.appendChild(style);
    }
    // 观察元素被添加
    function observeDomChange(selector, callback) {
        // 定义一个回调函数,当DOM发生变化时调用
        const onChange = function(mutationsList, observer) {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    callback(mutation);
                }
            }
        };
        // 创建一个观察器实例,并传入回调函数
        const observer = new MutationObserver(onChange);
        // 配置观察选项:指定需要观察哪些变动
        const config = { attributes: false, childList: true, subtree: true };
        // 获取目标节点
        const targetNode = typeof selector === 'string' ? document.querySelector(selector) : selector;
        // 如果目标节点存在,则开始观察
        if (targetNode) {
            observer.observe(targetNode, config);
        }
        // 返回一个函数,用于停止观察
        return () => {
            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();
        });
    }
})();
image.png

留下你的脚步
推荐阅读