常用函数

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 whenElementExistOrNull(selector, node, timeout = 5000, sleep = 0) {
    timeout = isNumber(timeout) ? parseInt(timeout) : 5000;
    sleep = isNumber(sleep) ? parseInt(sleep) : 0;
    return new Promise(resolve => {
        const startTime = Date.now();
        const check = async () => {
            const el = typeof selector === 'function'
                ? await selector()
                : (node || document).querySelector(selector);
            if (el || Date.now() - startTime >= timeout) {
                resolve(el || null);
                return;
            }
            sleep ? setTimeout(check, sleep) : requestAnimationFrame(check);
        };
        check();
    });
}

// 等待元素存在,带超时
function whenElementExist(selector, node, timeout = 5000, sleep = 0) {
    timeout = isNumber(timeout) ? parseInt(timeout) : 5000;
    sleep = isNumber(sleep) ? parseInt(sleep) : 0;
    return new Promise((resolve, reject) => {
        let isResolved = false;
        const check = () => {
            if(typeof node === 'string') node = document.querySelector(node);
            const el = typeof selector==='function'?selector():(node||document).querySelector(selector);
            if (el) {isResolved = true; resolve(el);} else if(!isResolved) sleep ? setTimeout(check, sleep) : requestAnimationFrame(check);
        };
        check();
        if(timeout > 0) {
            setTimeout(() => {
                if (!isResolved) {
                    reject(new Error(`Timeout: Element not found for selector "${selector}" within ${timeout}ms`));
                }
            }, timeout);
        }
    });
}

function whenElementExist(selector, node, timeout = 5000) {
    return new Promise((resolve, reject) => {
        let isResolved = false;
        const check = () => {
            const el = (typeof selector === 'function' ? selector() : (node || document).querySelector(selector));
            if (el) {
                isResolved = true;
                resolve(el);
            } else if (!isResolved) {
                setTimeout(check, 100); // 每 100ms 检查一次
            }
        };
        check();
        setTimeout(() => {
            if (!isResolved) {
                reject(new Error(`Timeout: Element not found for selector "${selector}" within ${timeout}ms`));
            }
        }, timeout);
    });
}

function whenElementExist(selector, node, timeout = 5000) {
    return new Promise((resolve, reject) => {
        const observer = new MutationObserver(() => {
            const el = (node || document).querySelector(selector);
            if (el) {
                observer.disconnect();
                resolve(el);
            }
        });
        observer.observe(document, { childList: true, subtree: true });
        // 设置超时
        setTimeout(() => {
            observer.disconnect();
            reject(new Error(`Timeout: Element not found for selector "${selector}" within ${timeout}ms`));
        }, timeout);
    });
}

// 等待元素或变量存在
function whenExist(selector, parentNode = null, timeout = 5000, errorMsg = '') {
    return new Promise((resolve, reject) => {
        let timeoutId;
        let isResolved = false;
        const check = () => {
            let element = null;
            if (typeof selector === 'function') {
                element = selector();
            } else {
                element = (parentNode||document).querySelector(selector);
            }
            if (element) {
                isResolved = true;
                if(timeoutId) clearTimeout(timeoutId);
                resolve(element);
            } else {
                requestAnimationFrame(check);
            }
        };
        check();
        // 设置超时兜底
        if(timeout > 0) timeoutId = setTimeout(() => {
            if(isResolved) return;
            console.error(errorMsg||`Element not found within ${timeout}ms`);
            reject(new Error(errorMsg||`Element not found within ${timeout}ms`));
        }, timeout);
    });
}

// 请求api 简版
// 调用 await requestApi('/api/block/getChildBlocks', {id: '20241208033813-onlvfmp'});
async function requestApi(url, data, method = 'POST') {
    return await (await fetch(url, {method: method, body: JSON.stringify(data||{})})).json();
}

// 请求api
async function fetchSyncPost(url, data, returnType = 'json') {
    const init = {
        method: "POST",
    };
    if (data) {
        if (data instanceof FormData) {
            init.body = data;
        } else {
            init.body = JSON.stringify(data);
        }
    }
    try {
        const res = await fetch(url, init);
        const res2 = returnType === 'json' ? await res.json() : await res.text();
        return res2;
    } catch(e) {
        console.log(e);
        return returnType === 'json' ? {code:e.code||1, msg: e.message||"", data: null} : "";
    }
}

// 等待元素出现(简版)
function whenElementExist(selector, node) {
    return new Promise(resolve => {
        const check = () => {
            const el = typeof selector==='function'?selector():(node||document).querySelector(selector);
            if (el) resolve(el); else requestAnimationFrame(check);
        };
        check();
    });
}

// 等待元素渲染完成后执行
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();
    });
}

// 等待多个元素渲染完成
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();
    });
}

// 等待元素渲染完成后执行(delay版)
function whenElementExist(selector, bySetTimeout = false, delay = 40, maxTime = 5000) {
    return new Promise(resolve => {
        let usedTime = 0;
        const checkForElement = () => {
            console.log('checkForElement', usedTime);
            let element = null;
            if (typeof selector === 'function') {
                element = selector();
            } else {
                element = document.querySelector(selector);
            }
            if (element) {
                resolve(element);
            } else {
                if (bySetTimeout) {
                    setTimeout(()=>{
                        usedTime += delay;
                        if(maxTime > usedTime) checkForElement();
                    }, delay);
                } else {
                    requestAnimationFrame(checkForElement);
                }
            }
        };
        checkForElement();
    });
}

// 查询SQL函数
async function query(sql) {
    const result = await fetchSyncPost('/api/query/sql', { "stmt": sql });
    if (result.code !== 0) {
        console.error("查询数据库出错", result.msg);
        return [];
    }
    return result.data;
}

// 添加style标签
function addStyle(css) {
    // 创建一个新的style元素
    const style = document.createElement('style');
    // 设置CSS规则
    style.innerHTML = css;
    // 将style元素添加到<head>中
    document.head.appendChild(style);
}

// 支持创建文件夹,当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", { // 写入js到本地
        method: "POST",
        body: formData,
    });
    const json = await result.json();
    return json;
}

// 存储文件
function putFile(storagePath, data) {
      const formData = new FormData();
      formData.append("path", storagePath);
      formData.append("file", new Blob([data]));
      return fetch("/api/file/putFile", {
          method: "POST",
          body: formData,
      }).then((response) => {
          if (response.ok) {
              //console.log("File saved successfully");
          }
          else {
              throw new Error("Failed to save file");
          }
      }).catch((error) => {
          console.error(error);
      });
  }

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

// 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 sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 发送消息
function showMessage(message, isError = false, delay = 7000) {
    return fetch('/api/notification/' + (isError ? 'pushErrMsg' : 'pushMsg'), {
        "method": "POST",
        "body": JSON.stringify({"msg": message, "timeout": delay})
    });
}

async function showMessageWithButton(message, isError = false, delay = 7000, text='',callback='', type='a') {
    const result = fetch('/api/notification/' + (isError ? 'pushErrMsg' : 'pushMsg'), {
        "method": "POST", "body": JSON.stringify({"msg": message, "timeout": delay})
    });
    if(text && callback) {
        if(typeof callback === 'string') {const link=callback; callback = async () => window.open(link);}
       const whenElementExist = (selector, node, timeout=5000) => { return new Promise(resolve => {
            const startTime = Date.now(); const check = () => {
                const el = typeof selector==='function'?selector():(node||document).querySelector(selector);
                if (el || Date.now() - startTime >= timeout) {resolve(el||null);return} requestAnimationFrame(check);
            }; check();
        });};
        const msgContent = await whenElementExist("#message .b3-snackbar__content");
        if(!msgContent) return; const br = document.createElement("br");
        const button = document.createElement(type);button.style.cursor='pointer';
        if(type === 'button') button.classList.add('b3-button', 'b3-button--white');
        button.textContent = text; if(typeof callback === 'function') button.onclick = callback;
        msgContent.appendChild(br); msgContent.appendChild(button);
    }
    return result;
}

// 状态栏输出
let statusMsg;
function showStatusMsg(params, append = false) {
    if(!statusMsg) statusMsg = document.querySelector('#status .status__msg');
    params = typeof params === 'string' ? params : JSON.stringify(params);
    let html = statusMsg.innerHTML;
    if(append) {
        html += params;
    } else {
        html = params;
    }
    html = html.trim();
    statusMsg.innerHTML = html;
}

// 自定义状态栏输出
function showMyStatusMsg(html, delay = 0, append = false) {
    let myStatus = document.querySelector('#status .my_status__msg');
    if(!myStatus) {
        const status = document.querySelector('#status');
        const statusMsg = status.querySelector('.status__msg');
        if(!statusMsg) return;
        const style = `
            color: var(--b3-theme-on-surface); white-space: nowrap;
            text-overflow: ellipsis; overflow: hidden;
            padding-left: 5px; padding-right: 5px; font-size: 12px;
        `;
        const myStatusHtml = `<div class="my_status__msg" style="${style}"></div>`;
        statusMsg.insertAdjacentHTML('afterend', myStatusHtml);
        myStatus = status.querySelector('.my_status__msg');
    }
    if(myStatus) append ? myStatus.innerHTML += html : myStatus.innerHTML = html;
    if(delay > 0) setTimeout(()=>myStatus.innerHTML = '', delay);
}

const setStyle = (() => {
  let styleElement = null; // 保存当前样式元素的引用

  return (css) => {
    // 如果已存在样式元素,先移除它
    if (styleElement) {
      styleElement.parentNode.removeChild(styleElement);
    }

    // 创建新的样式元素
    styleElement = document.createElement('style');
    styleElement.textContent = css;
    document.head.appendChild(styleElement);
  };
})();

function setStyle() {
    let styleElement = null; // 保存当前样式元素的引用
    return (css) => {
    // 如果已存在样式元素,先移除它
    if (styleElement) {
      styleElement.parentNode.removeChild(styleElement);
    }
    // 创建新的样式元素
    styleElement = document.createElement('style');
    styleElement.textContent = css;
    document.head.appendChild(styleElement);
  };
}

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

function getEditor() {
    return getProtyleEl()?.querySelector('.protyle-wysiwyg.protyle-wysiwyg--attr');
}

// 判断空文件
async function isEmptyFile(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);
}

// 判断是文件夹
async function isFolderFile(id) {
    const ret = await requestApi('/api/block/getDocInfo', {id});
    return ret && ret.code === 0 && ret.data && ret.data?.subFileCount > 0;
}

本地存储

see https://github.com/siyuan-note/siyuan/blob/1317020c1791edf440da7f836d366567e03dd843/app/src/protyle/util/compatibility.ts#L409

// 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];
    }
}

等待元素是否存在MutationObserver版

function waitForElement(selector, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    // 1. 先立即检查一次,避免无谓监听
    const element = document.querySelector(selector);
    if (element) {
      resolve(element);
      return;
    }

    // 2. 设置超时
    const timeout = setTimeout(() => {
      observer.disconnect();
      reject(new Error(`等待元素 "${selector}" 超时 (${timeoutMs}ms)`));
    }, timeoutMs);

    // 3. 创建监听器
    const observer = new MutationObserver((mutations) => {
      // 每次 DOM 变化时检查元素是否存在
      const targetElement = document.querySelector(selector);
      if (targetElement) {
        clearTimeout(timeout); // 清除超时
        observer.disconnect(); // 停止监听
        resolve(targetElement); // 返回元素
      }
    });

    // 4. 开始监听整个文档的子树变化
    observer.observe(document.body, {
      childList: true,    // 监听子节点增删
      subtree: true       // 监听所有后代节点
    });
  });
}

// 使用示例:等待 id="myButton" 的元素
try {
  const button = await waitForElement('#myButton', 3000);
  button.click(); // 元素存在后执行操作
} catch (error) {
  console.error(error.message);
}
image.png

留下你的脚步
推荐阅读