快速打开自定义功能

// name 快速打开自定义功能
// see https://ld246.com/article/1745488922117
// version 0.0.3.1
// updateDesc 0.0.3.1 增加eruda调试工具
// updateDesc 0.0.3 增加 刷新页面,全屏,宽屏,断点调试,打开网页版等功能
// updateDesc 0.0.2 增加快捷键支持,把思源命令面板的命令移植过来
// updateUrl https://gitee.com/wish163/mysoft/raw/main/%E6%80%9D%E6%BA%90/%E5%BF%AB%E9%80%9F%E6%89%93%E5%BC%80%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8A%9F%E8%83%BD.js
// 使用帮助
// 建议把代码放到runjs插件的代码块中方便修改和添加菜单项(当然直接把该代码放到js代码片段中也行,代码片段修改后需要刷新页面)
// 然后,把下面的这行代码放到js代码片段中加载时运行即可(注意,外部调用runjs代码块,先要给块命名,然后保存为可调用的方法)
// runjs调用 runJs.plugin.call('quickOpen')
// runjs中修改代码后,只需点击「块菜单——>Run JS——> 运行代码」即可
//                  或将光标聚焦在代码块中,然后按 alt + F5 即可运行当前的代码块
// 如何添加新菜单?
// 只需要“自定义菜单区 开始”和“自定义菜单区 结束”直接添加 addMenu('菜单名', ()=>{})即可
// 比如:addMenu('demo1', (event, functions, option, selection)=>{alert('demo1')}, 'D', 'shortcut'); 这里第三个参数D代表当菜单出现时按D键直接选中demo1这个菜单
// 注意,弹出菜单可能使编辑器失去焦点,有些操作可能需要编辑器聚焦才有效,如果有问题,可以试试用setTimeout(()=>{},0)来延迟下。
(async (menus = [], pressKey = '' /* 👈修改快捷键可在这里修改pressKey,默认ctrl+; 修改后pressKey后需要刷新页面 */)=>{
    ///////////////////////////////// 自定义菜单区 开始 /////////////////////////////////
    // 打开本代码片编辑窗口
    addMenu('打开本代码片编辑窗口', (event, {getProtyleEl, showErrorMessage}) => {
        // 如果在runjs代码块中,设置这个代码块的可调用方法名,如果不在runjs中保持空或注释即可
        const runjsCallableName = 'quickOpen';
  
        // 如果在代码片段中,设置这个代码片段的名字,如果不在代码片段中保持空或注释即可
        const snippetName = '快速打开自定义功能';
  
        // 当二者都配置的话,runjs优先(代码片段修改代码后需要刷新页面,runjs只需要按alt+f5即可)
        if(typeof runjsCallableName !== 'undefined' && runjsCallableName) {
            const codeBlockId = window.siyuan.ws.app.plugins.find(item=>item.name==='sy-run-js')?.data["Callable.json"]?.quickOpen;
            if(!codeBlockId) showErrorMessage('没有找到'+runjsCallableName+'的代码块');
            window.open('siyuan://blocks/'+codeBlockId);
        } else {
            openAny.clicks('#barWorkspace', '[data-id="config"]', '[data-name="appearance"]', '#codeSnippet', '[data-key="dialog-setting"] svg.b3-dialog__close', '[data-type="js"]').input(typeof snippetName === 'undefined' ? '' : snippetName, '[data-action="search"][data-type="js"]');
        }
    }, 'E');

    // 移动当前文档到
    addMenu('移动当前文档到', (event, {getProtyleEl}) => {
        openAny.click('[data-type="doc"]', getProtyleEl()).click('[data-name="titleMenu"] button[data-id="move"]');
    }, 'M');
  
    // Bing搜索
    addMenu('Bing搜索', (event, {getSelectedText}) => {
        // 搜索引擎URL,%s% 是搜索关键词
        const searchUrl = 'https://cn.bing.com/search?q=%s%&form=QBRE';
        window.open(searchUrl.replace('%s%', getSelectedText()));
    }, 'B');
  
    // 问DeepSeek
    addMenu('问DeepSeek', (event, {getSelectedText}) => {
        // ai引擎URL,%s% 是查询关键词,支持deepseek-r1
        const aiUrl = 'https://chat.baidu.com/search?word=%s%';
        window.open(aiUrl.replace('%s%', getSelectedText()));
    }, 'D');
  
    // 打开翻译
    addMenu('打开翻译', (event, {getSelectedText}) => {
        // 翻译引擎URL,%s% 是翻译关键词
        const fanyiUrl = 'https://fanyi.baidu.com/mtpe-individual/multimodal?query=%s%';
        window.open(fanyiUrl.replace('%s%', getSelectedText()));
    }, 'T');
  
    // 打开查词
    addMenu('打开查词', (event, {getSelectedText}) => {
        const url = 'https://www.iciba.com/word?w=%s%';
        window.open(url.replace('%s%', getSelectedText()));
    });
  
    // 刷新页面
    addMenu('刷新页面', (event, {}) => {
        window.reload();
    }, 'R');

    // 全屏
    addMenu('全屏', (event, {}, menuItem) => {
        if(event?.isWillShow) {
            if (isFullScreen()) {
                menuItem.label = '退出全屏';
            } else {
                menuItem.label = '全屏';
            }
            return;
        }
        if(isFullScreen()){
            exitFullscreen();
        } else {
            requestFullScreen(document.querySelector('html'));
        }
    }, 'F', '', '', true);
  
    // 宽屏
    addMenu('宽屏', async (event, {showMsgBox}, menuItem) => {
        // 需要先安装宽屏代码片段后才行 https://ld246.com/article/1746079460404#%E4%BB%A3%E7%A0%81
        if(event?.isWillShow) {
            const editor = getEditor();
            if ((editor?.firstElementChild?.offsetWidth||0) + 40 >= (editor?.offsetWidth||0)) {
                menuItem.label = '取消宽屏';
            } else {
                menuItem.label = '宽屏';
            }
            return;
        }
        if(!document.querySelector('.dock__item[aria-label$="宽屏风格"]')) {
            await showMsgBox('请先安装宽屏代码片段!<a href="" target="_blank">点击这里安装</a>', '异常提醒', '', null);
            return;
        }
        openAny.click('.dock__item[aria-label$="宽屏风格"]');
    }, 'W', '', '', true);
  
    // 打开网页版
    addMenu('打开网页版', (event, {}) => {
        window.open(window.location.origin);
    });

    // 延迟断点
    addMenu('debugger', (event, {}) => {
        setTimeout('debugger', 5000);
    });

    // eruda see https://eruda.liriliri.io/zh/docs/
    addMenu('eruda console', (event, {}) => {
        const shouldLoad = event?.isLoading ? localStorage.getItem('eruda_running') === 'true' : !window.eruda;
        if(shouldLoad) {
            const src = 'https://cdn.bootcdn.net/ajax/libs/eruda/3.4.1/eruda.js';
            const script = document.createElement('script');
            script.onload = () => {
                window.eruda.init();
                window.eruda.position({x: window.innerWidth-40, y: window.innerHeight-80});
                localStorage.setItem('eruda_running', true);
            }
            script.src = src;
            document.head.appendChild(script);
        } else {
            if(event?.isLoading) return;
            if(window.eruda._isInit) {
                window.eruda.destroy();
                localStorage.setItem('eruda_running', false);
            } else {
                window.eruda.init();
                window.eruda.position({x: window.innerWidth-40, y: window.innerHeight-80});
                localStorage.setItem('eruda_running', true);
            }
        }
    }, '', '', '', '', true);

    // vConsole
    addMenu('vConsole', (event, {}) => {
        const shouldLoad = event?.isLoading ? localStorage.getItem('vconsole_running') === 'true' : !window.VConsole;
        if(shouldLoad) {
            const src = 'https://unpkg.com/vconsole@latest/dist/vconsole.min.js';
            const script = document.createElement('script');
            script.onload = () => {
                window.vConsole = new window.VConsole();
                localStorage.setItem('vconsole_running', true);
            }
            script.src = src;
            document.head.appendChild(script);
        } else {
            if(event?.isLoading) return;
            if(window?.vConsole?.isInited) {
                window.vConsole.destroy();
                localStorage.setItem('vconsole_running', false);
            } else {
                window.vConsole = new window.VConsole();
                localStorage.setItem('vconsole_running', true);
            }
        }
    }, '', '', '', '', true);
  
    // 打开设置
    addMenu('打开设置', (event, {}) => {
        openAny.pressByKeymap('config');
    });
  
    // 打开日记
    addMenu('打开日记', (event, {}) => {
        // 在这里输入你想在哪个笔记本中打开今日日记
        const noteBookName = '我的笔记';
        // 这里用wwhenExist先等待指定元素出现,否则需要在do内调用 await new OpenAny().whenExist()等待目标出现
        openAny.click('#barWorkspace').whenExist('[data-name="barWorkspace"]').do(()=>{
            const subMenuItems = [...openAny.queryElAll('[data-id="dailyNote"] .b3-menu__label')];
            const notebutton = subMenuItems.find(item => item.textContent === noteBookName)?.closest('button.b3-menu__item');
            // 返回链元素,供下一个链click调用,也可以直接在这里click,不用再次调用下一个链click
            return notebutton;
        }).click();
    });
  
    // 打开集市
    addMenu('打开集市', (event, {}) => {
        openAny.clicks('#barWorkspace', '[data-id="config"]', '[data-name="bazaar"]');
    });
  
    // 打开代码片段
    addMenu('打开代码片段', (event, {}) => {
        openAny.clicks('#barWorkspace', '[data-id="config"]', '[data-name="appearance"]', '#codeSnippet', '[data-key="dialog-setting"] svg.b3-dialog__close');
    }, 'S');
  
    // 打开快捷键设置
    addMenu('打开快捷键设置', (event, {}) => {
        openAny.clicks('#barWorkspace', '[data-id="config"]', '[data-name="keymap"]');
    });
  
    // 打开搜索
    addMenu('打开思源搜索', (event, {}) => {
        openAny.press('alt+p');
    });
  
    // 打开仅搜索文档
    addMenu('打开仅搜索文档', (event, {}) => {
        // 请参考 ctrl+shif+p 全局搜索仅搜文档,然后把这个快捷键填进来
        const pressKey = openAny.fn.isMac() ? 'meta+shift+p' : 'ctrl+shift+p'
        openAny.press(pressKey);
    }, 'P');

    // 模式切换
    addMenu('模式切换', (event, {}) => {
        const isPreview = document.querySelector('.protyle-preview:not(.fn__none) .protyle-preview__action');
        if(!isPreview) openAny.press('alt+meta+9'); else openAny.press('alt+meta+7');
    }, '7');
  
    // 打开链滴
    addMenu('打开链滴', (event, {}) => {
        window.open('https://ld246.com');
    });
  
    // 打开我的博客
    addMenu('打开我的博客', (event, {}) => {
        window.open('https://pipe.b3log.org/blogs/wilsons');
    });
  
    // 打开思源工作空间
    addMenu('打开思源工作空间', (event, {showFileInFolder}) => {
        showFileInFolder(window.siyuan.config.system.workspaceDir);
    });
  
    // 打开计算器
    addMenu('打开计算器', (event, {runCmd, isMac}) => {
        let cmd = `start calc`;
        if(isMac()) cmd = `open -a Calculator`;
        runCmd(cmd);
    }, 'C');
  
    // 字母大小写转换
    addMenu('字母大小写转换', (event, {getEditor}, option, {selectedText, selection, range}) => {
        let text = selectedText;
        if(/[a-z]/.test(text)) {
            text = text.toUpperCase();
        } else {
            text = text.toLowerCase();
        }
        // 替换选中的文本
        range.deleteContents();
        range.insertNode(document.createTextNode(text));

        // 恢复选中范围
        selection.removeAllRanges();
        selection.addRange(range);
        //const editor = getEditor();
        //openAny.sendText(text, editor);
    });

    ///////////////////////////////// 自定义菜单区 结束 /////////////////////////////////

    // 等待openAny加载完毕  
    await waitFor(() => typeof openAny !== 'undefined');

    // 生成菜单列表
    const handler = async (event, functions)=>{
        event.preventDefault();
        const selection = window.getSelection();
        const range = selection.getRangeAt(0);
        const selectedText = selection.toString();
        const lastSelected = await getStorageVal('local-quickopen-selected');console.log(lastSelected,2222);
        if(lastSelected) {
            const lastSelectedItem = menus.find(item=>item.selected);
            if(lastSelectedItem) lastSelectedItem.selected=false;
            const selectedItem = menus.find(item=>item.value===lastSelected);
            if(selectedItem) selectedItem.selected=true;
        }
        functions.whenElementExist('.open-any-menu-title:not([data-reward="true"])').then((title)=>{
            const search = title.querySelector('.open-any-menu-search');
            if(search) {
                search.style.width = 'calc(100% - 74px)';
                generateReward(title);
                title.dataset.reward = true;
            }
        });
        // 显示菜单前回调
        menus.forEach(menu => menu.runOnShow && menu.callback({isWillShow: true}, functions, menu, {selectedText, selection, range}));
        // 显示菜单      
        const selectedOption = await functions.showOptionsMenu(menus, {width:'min(800px, 100%)',maxWidth:'min(1000px, 100%)', height:'min(800px, calc(100% - 80px))', maxHeight:'min(800px, calc(100% - 80px))', search:true, menuItemStyle: 'text-align:left'});
        if (selectedOption !== null) {
            if(typeof selectedOption.callback === 'function') {
                selectedOption.callback(event, functions, selectedOption, {selectedText, selection, range});
                setStorageVal('local-quickopen-selected', selectedOption.value);console.log(selectedOption.value,1111);
            } else {
                alert(selectedOption.callback+' 不是有效的函数');
            }
        }
    };
    pressKey = pressKey || (openAny.fn.isMac() ? 'meta+;' : 'ctrl+;');
    openAny.removeKeymap(pressKey); // 注意,这里未提供callback时,会删除同名的所有监听
    openAny.addKeymap(pressKey, handler);

    // 把命令面板的命令移植过来
    // see https://github.com/siyuan-note/siyuan/blob/e47b8efc2f2611163beca9fad4ee5424001515ff/app/src/boot/globalEvent/command/panel.ts#L49
    Object.keys(window.siyuan.config.keymap.general).forEach((key) => {
        let keys;
        if (isMobile()) {
            keys = ["addToDatabase", "fileTree", "outline", "bookmark", "tag", "dailyNote", "inbox", "backlinks",
                "dataHistory", "editReadonly", "enter", "enterBack", "globalSearch", "lockScreen", "mainMenu", "move",
                "newFile", "recentDocs", "replace", "riffCard", "search", "selectOpen1", "syncNow"];
        } else {
            keys = ["addToDatabase", "fileTree", "outline", "bookmark", "tag", "dailyNote", "inbox", "backlinks",
                "graphView", "globalGraph", "closeAll", "closeLeft", "closeOthers", "closeRight", "closeTab",
                "closeUnmodified", "config", "dataHistory", "editReadonly", "enter", "enterBack", "globalSearch", "goBack",
                "goForward", "goToEditTabNext", "goToEditTabPrev", "goToTab1", "goToTab2", "goToTab3", "goToTab4",
                "goToTab5", "goToTab6", "goToTab7", "goToTab8", "goToTab9", "goToTabNext", "goToTabPrev", "lockScreen",
                "mainMenu", "move", "newFile", "recentDocs", "replace", "riffCard", "search", "selectOpen1", "syncNow",
                "splitLR", "splitMoveB", "splitMoveR", "splitTB", "tabToWindow", "stickSearch", "toggleDock", "unsplitAll",
                "unsplit"];
            if (!isBrowser()) {
                keys.push("toggleWin");
            }
        }
        if (keys.includes(key)) {
            addMenu(window.siyuan.languages[key], (event, functions, option, selection)=>runSiyuanCommand(key, '', event, functions, option, selection), '', !isMobile()?window.siyuan.config.keymap.general[key].custom:'', window.siyuan.languages[key]);
        }
    });
    Object.keys(window.siyuan.config.keymap.editor.general).forEach((key) => {
        if (["switchReadonly", "switchAdjust"].includes(key)) {
            addMenu(window.siyuan.languages[key], (event, functions, option, selection)=>runSiyuanCommand(key, '', event, functions, option, selection), '', !isMobile()?updateHotkeyTip(window.siyuan.config.keymap.editor.general[key].custom):'', window.siyuan.languages[key]);
        }
    });
    window.siyuan.ws.app.plugins.forEach(plugin => {
        plugin.commands.forEach(command => {
            addMenu(`${plugin.displayName}: ${command.langText || plugin.i18n[command.langKey]}`, (event, functions, option, selection)=>runSiyuanCommand(command, 'plugin', event, functions, option, selection), '', !isMobile()?updateHotkeyTip(command.customHotkey):'', `${plugin.displayName}: ${command.langText || plugin.i18n[command.langKey]}`);
        });
    });
  
    // 生成拼音和拼音首字母
    setTimeout(async ()=>{
        let pinyinCache = await getFile('/data/storage/quickopen_pinyin_catche.json') || '{}';
        pinyinCache = JSON.parse(pinyinCache);
        if(pinyinCache.code && pinyinCache.code !== 0) pinyinCache = {};
        let hasNewCache = false;
        for (const menu of menus) {
            try {
                const words = encodeURIComponent(menu.label);
                if(!pinyinCache[menu.label]) pinyinCache[menu.label] = {};
                // 生成汉字拼音
                let pinyin = pinyinCache[menu.label]?.pinyin;
                if(!pinyin) {
                    pinyin = await (await fetch('https://tools.getquicker.cn/api/Chinese/GetPinyin?source='+words+'&tone=false&forName=false')).text();
                    if(pinyin) {
                        pinyinCache[menu.label].pinyin = pinyin;
                        hasNewCache = true;
                    }
                }
                if(pinyin) menu.pinyin = pinyin;
                // 生成拼音首字母
                let pinyinFirst = pinyinCache[menu.label]?.pinyinFirst;
                if(!pinyinFirst) {
                    pinyinFirst = await (await fetch('https://tools.getquicker.cn/api/Chinese/GetFirstPinyin?source='+words)).text();
                    if(pinyinFirst) {
                        pinyinCache[menu.label].pinyinFirst = pinyinFirst;
                        hasNewCache = true;
                    }
                }
                if(pinyinFirst) menu.pinyinFirst = pinyinFirst;
            } catch(e) {
                console.warn(e);
                return;
            }
        }
        if(hasNewCache) {
            putFile('/data/storage/quickopen_pinyin_catche.json', JSON.stringify(pinyinCache));
        }
    }, 0);

    // 执行加载时执行的菜单
    setTimeout(async ()=>{
        menus.forEach(menu => menu.runOnLoad && menu.callback({isLoading: true}, openAny.fn, menu, {}));
    }, 0);

    // 添加菜单函数
    function addMenu(name, callback, key, shortcut, value, runOnShow = false, runOnLoad = false) {
        menus.push({ label: name, value: value||name, key: key || '', shortcut: shortcut, callback: callback, runOnShow, runOnLoad });
    }

    function runSiyuanCommand(command, type, event, functions, option, selection) {
        if(type === 'plugin') {
            if (command.callback) {
                command.callback();
            } else if (command.globalCallback) {
                command.globalCallback();
            }
        } else {
            // 这里有些操作可能需要编辑器聚焦才有效,因此使用setTimeout(()=>{},0)来延迟下
            setTimeout(()=>{
                openAny.click('#barCommand').click(`#commands [data-command="${command}"]`);
            }, 0);
        }
    }

    // see https://github.com/siyuan-note/siyuan/blob/1317020c1791edf440da7f836d366567e03dd843/app/src/protyle/util/compatibility.ts#L409
    async function setStorageVal(key, val, cb) {
        if (window.siyuan.config.readonly) {
            return;
        }
        const result = await fetchSyncPost("/api/storage/setLocalStorageVal", {
            app: window.siyuan.ws.app.appId,
            key,
            val,
        });
        if(result && result.code === 0) {
            if (cb) {
                cb();
            }
            return result;
        }
    }
  
    // see https://github.com/siyuan-note/siyuan/blob/e47b8efc2f2611163beca9fad4ee5424001515ff/app/src/protyle/util/compatibility.ts#L258
    async function getStorageVal(key) {
        const result = await fetchSyncPost("/api/storage/getLocalStorage");
        if(result && result.code === 0 && result.data) {
            return result.data[key];
        }
    }

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

    // 获取文件
    async function getFile(path) {
        return fetch("/api/file/getFile", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                path,
            }),
        }).then((response) => {
            if (response.ok) {
                return response.text();
            } else {
                throw new Error("Failed to get file content");
            }
        }).catch((error) => {
            console.error(error);
        });
    }

    // 存储文件,支持创建文件夹,当isDir true时创建文件夹,忽略文件
     async function putFile(path, content = '', isDir = false) {
        const formData = new FormData();
        formData.append("path", path);
        formData.append("isDir", isDir)
        formData.append("file", new Blob([content]));
        const result = await fetch("/api/file/putFile", {
            method: "POST",
            body: formData,
        });
        const json = await result.json();
        return json;
    }

    function waitFor(conditionFn, timeoutMs=5000) {
      return new Promise((resolve, reject) => {
        const start = Date.now();
        const check = () => {
          if(typeof conditionFn === 'string') 
              conditionFn = () => document.querySelector(conditionFn);
          const result = conditionFn();
          if (result) resolve(result);
          else if (Date.now() - start > timeoutMs) reject();
          else requestAnimationFrame(check); // 利用浏览器刷新周期
        };
        check();
      });
    }

    function isMobile() {
        return !!document.getElementById("sidebar");
    }

    function isBrowser() {
        return !navigator.userAgent.startsWith("SiYuan") ||
            navigator.userAgent.indexOf("iPad") > -1 ||
            (/Android/.test(navigator.userAgent) && !/(?:Mobile)/.test(navigator.userAgent));
    }

    function isMac() {
        return navigator.platform.indexOf("Mac") > -1;
    }

    function updateHotkeyTip(hotkey) {
        if (isMac()) {
            return hotkey;
        }
  
        const KEY_MAP = new Map(Object.entries({
            "⌘": "Ctrl",
            "⌃": "Ctrl",
            "⇧": "Shift",
            "⌥": "Alt",
            "⇥": "Tab",
            "⌫": "Backspace",
            "⌦": "Delete",
            "↩": "Enter",
        }));
  
        const keys = [];
  
        if ((hotkey.indexOf("⌘") > -1 || hotkey.indexOf("⌃") > -1)) keys.push(KEY_MAP.get("⌘"));
        if (hotkey.indexOf("⇧") > -1) keys.push(KEY_MAP.get("⇧"));
        if (hotkey.indexOf("⌥") > -1) keys.push(KEY_MAP.get("⌥"));
  
        // 不能去最后一个,需匹配 F2
        const lastKey = hotkey.replace(/⌘|⇧|⌥|⌃/g, "");
        if (lastKey) {
            keys.push(KEY_MAP.get(lastKey) || lastKey);
        }
  
        return keys.join("+");
    }

    function requestFullScreen(element) {
        if (element.requestFullscreen) {
            element.requestFullscreen();
        } else if (element.mozRequestFullScreen) { // Firefox
            element.mozRequestFullScreen();
        } else if (element.webkitRequestFullscreen) { // Chrome, Safari, Opera
            element.webkitRequestFullscreen();
        } else if (element.msRequestFullscreen) { // IE/Edge
            element.msRequestFullscreen();
        }
    }

    function exitFullscreen() {
      if (document.exitFullscreen) {
        document.exitFullscreen();
      } else if (document.webkitExitFullscreen) { /* Chrome, Safari, Opera */
        document.webkitExitFullscreen();
      } else if (document.mozCancelFullScreen) { /* Firefox */
        document.mozCancelFullScreen();
      } else if (document.msExitFullscreen) { /* IE/Edge Legacy */
        document.msExitFullscreen();
      }
    }

    function isFullScreen() {
      return !!document.fullscreenElement;
    }

    function getEditor() {
        return document.querySelector('[data-type="wnd"].layout__wnd--active .protyle:not(.fn__none) .protyle-wysiwyg.protyle-wysiwyg--attr')||document.querySelector('[data-type="wnd"] .protyle:not(.fn__none) .protyle-wysiwyg.protyle-wysiwyg--attr');
    }

    function generateReward(node) {
        const a = document.createElement('a');
        a.href = 'https://ld246.com/article/1745488922117#%E6%89%93%E8%B5%8F%E4%BD%9C%E8%80%85';
        a.textContent = '打赏作者';
        a.target = '_blank';
        a.style.position = 'absolute';
        a.style.right = '0';
        a.style.top = '22px';
        node.appendChild(a);
    }
})();

image.png

留下你的脚步
推荐阅读