js 代码,左键展开文档树,中键打开文档

js 代码,左键展开文档树,中键打开文档

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

代码

// 左侧文件夹中键打开和点击展开
// pc版 中键打开,单击展开
// 触屏版 长按打开 点击展开
// see https://ld246.com/article/1736401552973
(()=>{
    // 是否更改空文件夹图标
    const isUpdateFolderIconWhenItEmpty = false;

    // 打开文件夹的方式 midclick 中键 dblclick 双击
    const openFolderBy = 'midclick';
  
    // 空文件夹图标代码 📂 1f4c2  📁 1f4c1
    const emptyFolderIconCode = '1f4c2';

    // 思源默认图标,首先读取用户自定义的默认图标,没有用官方默认图标,也可在这里写死
    const defaultIconCode = siyuan?.storage["local-images"]?.folder || '1f4d1';
  
    whenElementsExist(':is(.file-tree, [data-type="sidebar-file"]) .b3-list.b3-list--background').then((trees) => {
        trees.forEach(tree => {
            //////// pc版 中键打开,单击展开 ///////////
            if(!isTouchDevice()) {
                // 绑定鼠标单击
                tree.addEventListener('click', async (event) => {
                    const {toggleBtn, li} = isTreeFolder(event.target);
                    if(!toggleBtn) return;
                    if (event.target.classList.contains("b3-list-item__text")){
                        event.stopPropagation();
                        event.preventDefault();
                        toggleBtn.click();

                        // 添加图标,文件夹的文件内容为空,修改为指定的图标
                        if(isUpdateFolderIconWhenItEmpty) addIcon(li);
                    }
                });
      
                // 绑定中键单击,无论文件夹或文件都打开
                if(openFolderBy === 'midclick') {
                    tree.addEventListener('mousedown', (event) => {
                        if (event.button === 1) {
                            event.preventDefault();
                            //const {li} = isTreeFolder(event.target);
                            const li = event.target.closest('li[data-type="navigation-file"]:not([data-type="navigation-root"])');
                            if(!li) return;
                            li.click();
                        }
                    });
                }
                // 绑定双击事件,无论文件夹或文件都打开
                if(openFolderBy === 'dblclick') {
                    tree.addEventListener('dblclick', (event) => {
                        event.preventDefault();
                        //const {li} = isTreeFolder(event.target);
                        const li = event.target.closest('li[data-type="navigation-file"]:not([data-type="navigation-root"])');
                        if(!li) return;
                        li.click();
                    });
                }
            }

            //////// 触屏版 长按打开 点击展开 ///////////
            if(isTouchDevice()) {
                let pressTimer;
  
                // 点击事件
                function handleTap(event) {
                    const {toggleBtn, li} = isTreeFolder(event.target);
                    if(!toggleBtn) return;
                    if (event.target.classList.contains("b3-list-item__text")||event.target.classList.contains("b3-list-item__icon")){
                        event.stopPropagation();
                        event.preventDefault();
                        toggleBtn.click();

                        // 添加图标,文件夹的文件内容为空,修改为指定的图标
                        if(isUpdateFolderIconWhenItEmpty) addIcon(li);
                    }
                }
  
                // 长按事件
                function handleLongPress(event) {
                    const {li} = isTreeFolder(event.target);
                    if(!li) return;
                    li.click();
                }
              
                tree.addEventListener('touchstart', (event) => {
                    pressTimer = setTimeout(() => {
                        handleLongPress(event);
                    }, 500);
                });
              
                tree.addEventListener('touchend', (event) => {
                    if (pressTimer) {
                        clearTimeout(pressTimer);
                        handleTap(event);
                    }
                });
              
                tree.addEventListener('touchmove', (event) => {
                    if (pressTimer) {
                        clearTimeout(pressTimer);
                        pressTimer = null;
                    }
                });
            }
        });
    });

    async function addIcon(li) {
        const isFolderFileEmpty = await isFileEmpty(li.dataset.nodeId);
        if(isFolderFileEmpty) {
            const icon = li.querySelector('.b3-list-item__icon');
            const defaultIcon = unicode2Emoji(defaultIconCode);
            // 用户已自定义图标了返回
            if(icon?.innerHTML?.trim() !== defaultIcon) return;
            const newIcon = unicode2Emoji(emptyFolderIconCode);
            // 空文件图标不等于现有图标则修改
            if(newIcon !==  icon?.innerHTML?.trim()) {
                const result = await requestApi('/api/attr/setBlockAttrs', {
                    "id": li.dataset.nodeId,
                    "attrs": { "icon": emptyFolderIconCode }
                });
                if(result.code === 0) {
                    icon.innerHTML = newIcon;
                }
            }
        }
    }

    async function isFileEmpty(id) {
        const ret = await requestApi('api/block/getTreeStat', {id});
        return ret && ret.code === 0 && ret.data && (ret.data?.runeCount === 0 || ret.data?.stat?.runeCount === 0) || false;
    }

    async function requestApi(url, data, method = 'POST') {
        return await (await fetch(url, {method: method, body: JSON.stringify(data||{})})).json();
    }

    function isTreeFolder(element) {
        // 判断目标元素是否是 .sy__file li[data-type="navigation-file"]
        const li = element.closest('li[data-type="navigation-file"]:not([data-type="navigation-root"])');
        if(!li) return false;
        // 非文件夹返回
        const toggleBtn = li.querySelector(':is(.b3-list-item__toggle--hl,.b3-list-item__toggle):not(.fn__hidden)');
        if(!toggleBtn) return false;
        return {li, toggleBtn};
    }

    function isTouchDevice() {
        return ("ontouchstart" in window) && navigator.maxTouchPoints > 1;
    }

    // unicode转emoji
    // 使用示例:unicode2Emoji('1f4c4');
    // see https://ld246.com/article/1726920727424
    function unicode2Emoji(unicode, className = "", needSpan = false, lazy = false) {
        if (!unicode) {
            return "";
        }
        let emoji = "";
        if (unicode.indexOf(".") > -1) {
            emoji = `<img class="${className}" ${lazy ? "data-" : ""}src="/emojis/${unicode}"/>`;
        } else {
            try {
                unicode.split("-").forEach(item => {
                    if (item.length < 5) {
                        emoji += String.fromCodePoint(parseInt("0" + item, 16));
                    } else {
                        emoji += String.fromCodePoint(parseInt(item, 16));
                    }
                });
                if (needSpan) {
                    emoji = `<span class="${className}">${emoji}</span>`;
                }
            } catch (e) {
                // 自定义表情搜索报错 https://github.com/siyuan-note/siyuan/issues/5883
                // 这里忽略错误不做处理
            }
        }
        return emoji;
    }

    // 等待多个元素渲染完成
    function whenElementsExist(selector) {
        return new Promise(resolve => {
            const checkForElement = () => {
                let elements = null;
                if (typeof selector === 'function') {
                    elements = selector();
                } else {
                    elements = document.querySelectorAll(selector);
                }
                if (elements && elements.length > 0) {
                    resolve(elements);
                } else {
                    requestAnimationFrame(checkForElement);
                }
            };
            checkForElement();
        });
    }
})();
image.png

留下你的脚步
推荐阅读