UnaTools

自用公用脚本

Цей скрипт не слід встановлювати безпосередньо. Це - бібліотека для інших скриптів для включення в мета директиву // @require https://update.greasyfork.org/scripts/493023/1363403/UnaTools.js

const log = console.log.bind(console, "%c[Tools]:", "font-weight:bold;background-color:#00A0D8;color:#fff;padding: 3px 3px;margin: 5px 0px;border-radius:2px;");
const toastStyle = `.link-toast{position:fixed !important;padding:12px 24px;font-size:14px;border-radius:8px;white-space:nowrap;color:#fff;-webkit-transition:-webkit-transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);transition:-webkit-transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);transition:transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);transition:transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98),-webkit-transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);-webkit-animation:link-msg-move-in-top cubic-bezier(0.22,0.58,0.12,0.98) 0.4s;animation:link-msg-move-in-top cubic-bezier(0.22,0.58,0.12,0.98) 0.4s;z-index:10100;text-align:center;height:auto;line-height:auto}.link-toast.mobile{text-align:center;-webkit-box-shadow:none !important;box-shadow:none !important;width:auto;background-color:transparent !important;position:fixed;top:50vh;left:0;right:0}.link-toast.mobile .toast-text{background-color:rgba(0,0,0,0.8);padding:8px;border-radius:8px;display:inline-block;min-width:50px;word-break:break-all;white-space:pre-wrap}.link-toast.fixed{position:fixed}.link-toast.success{background-color:#47d279;-webkit-box-shadow:0 0.2em 0.1em 0.1em rgba(71,210,121,0.2);box-shadow:0 0.2em 0.1em 0.1em rgba(71,210,121,0.2)}.link-toast.caution{background-color:#ffb243;-webkit-box-shadow:0 0.2em 0.1em 0.1em rgba(255,190,68,0.2);box-shadow:0 0.2em 0.1em 0.1em rgba(255,190,68,0.2)}.link-toast.error{background-color:#ff6464;-webkit-box-shadow:0 0.2em 1em 0.1em rgba(255,100,100,0.2);box-shadow:0 0.2em 1em 0.1em rgba(255,100,100,0.2)}.link-toast.info{background-color:#48bbf8;-webkit-box-shadow:0 0.2em 0.1em 0.1em rgba(72,187,248,0.2);box-shadow:0 0.2em 0.1em 0.1em rgba(72,187,248,0.2)}.link-toast.out-fade{-webkit-animation:link-msg-fade-out cubic-bezier(0.22,0.58,0.12,0.98) 0.4s;animation:link-msg-fade-out cubic-bezier(0.22,0.58,0.12,0.98) 0.4s}@-webkit-keyframes link-msg-move-in-top{from{opacity:0;-webkit-transform:translate(0,5em);transform:translate(0,5em)}to{opacity:1;-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes link-msg-move-in-top{from{opacity:0;-webkit-transform:translate(0,5em);transform:translate(0,5em)}to{opacity:1;-webkit-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes link-msg-fade-out{from{opacity:1}to{opacity:0}}@keyframes link-msg-fade-out{from{opacity:1}to{opacity:0}}.el-fade-enter,.el-fade-leave{opacity:0;animation-timing-function:linear;animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.el-fade-enter.el-fade-enter-active{animation-name:el-fade-in;animation-play-state:running}.el-fade-leave.el-fade-leave-active{animation-name:el-fade-out;animation-play-state:running;pointer-events:none}.el-fade-enter-active,.el-fade-leave-active{transition:opacity .3s}@keyframes el-fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes el-fade-in{0%{opacity:0}to{opacity:1}}@keyframes el-fade-out{0%{opacity:1}to{opacity:0}}@-webkit-keyframes el-fade-out{0%{opacity:1}to{opacity:0}}`;
const aniStyle = `@keyframes aniLeftToRight{0%{transform:translateX(-32px);opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@keyframes aniBottomToTop{0%{transform:translateY(32px);opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@-webkit-keyframes aniTopToBottom{0%{transform:translateY(-32px);opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@-webkit-keyframes aniHideToShow{0%{display:none;opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@-webkit-keyframes aniShowToHide{0%{display:none;opacity:1}20%{opacity:0.8}30%{opacity:0.5}100%{opacity:0.3}}.aniDelete{transition:all 0.15s cubic-bezier(0.4,0,1,1);opacity:0.1}@keyframes spin{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}`;

/**
 * ContentType 映射表
 * @type {{download: string, form: string, json: string}}
 */
const ContentType = {
    json: 'application/json;charset=UTF-8', // json数据格式
    form: 'application/x-www-form-urlencoded; charset=UTF-8', // 表单数据格式
    download: 'application/octet-stream' // 二进制文件流格式,用于download
};
/**
 * 消息提示框类型
 * @type {{warn: string, success: string, error: string, info: string}}
 */
const ToastType = {
    success: 'success', error: 'error', info: 'info', warn: 'caution'
};

/**
 * 消息提示弹窗
 * @param message
 * @param type
 * @param wait
 * @param target
 */
function toast(message = '成功', type = ToastType.info, wait = 3000, target = document.body) { // 签到提示
    const toast = document.createElement("div"); // 创建标签
    toast.className = `link-toast ${type}`;
    toast.style.cssText = "top: 70px; right: 25px;";
    toast.innerHTML = `<span class="toast-text">${message}</span>`;
    target.append(toast); // 添加提示到页面上
    setTimeout(() => {
        fadeOutRemove(toast);
    }, wait);
}
/**
 * 解析boolean
 * @param val
 * @returns { boolean }
 */
function parseBoolean(val) {
    if (["yes", "y", "true", "1", "on"].indexOf(String(val).toLowerCase()) !== -1) return true;
    if (["no", "n", "false", "0", "off"].indexOf(String(val).toLowerCase()) !== -1) return false;
    return Boolean(val);
}

/**
 * 淡入显示
 * @param element
 */
function fadeInShow(element) {
    element.classList.remove("el-hidden");
    element.classList.add("el-fade-enter", "el-fade-enter-active");
    const f = () => {
        element.classList.remove("el-fade-enter", "el-fade-enter-active");
        element.removeEventListener("animationend", f);
        element.removeEventListener("webkitAnimationEnd", f);
    };
    element.addEventListener("animationend", f);
    element.addEventListener("webkitAnimationEnd", f);
}

/**
 * 淡出隐藏
 * @param element
 */
function fadeOutHide(element) {
    element.classList.add("el-fade-leave", "el-fade-leave-active");
    const f = () => {
        element.classList.remove("el-fade-leave", "el-fade-leave-active");
        element.classList.add("el-hidden");
        element.removeEventListener("animationend", f);
        element.removeEventListener("webkitAnimationEnd", f);
    };
    element.addEventListener("animationend", f);
    element.addEventListener("webkitAnimationEnd", f);
}

/**
 * 淡出删除
 * @param element
 */
function fadeOutRemove(element) {
    element.classList.add("el-fade-leave", "el-fade-leave-active");
    const f = () => {
        aniRemove(element, false, 0);
        element.removeEventListener("animationend", f);
        element.removeEventListener("webkitAnimationEnd", f);
    };
    element.addEventListener("animationend", f);
    element.addEventListener("webkitAnimationEnd", f);
}
/**
 * 单选选择器
 * @param selector
 * @param { Document } element
 * @returns { * }
 */
function $one(selector, element = document) {
    return element.querySelector(selector);
}
/**
 * 多选选择器
 * @param selector
 * @param element
 * @returns { NodeListOf<*> }
 */
function $all(selector, element = document) {
    return element.querySelectorAll(selector);
}

/**
 * 安全删除
 * @param { string } selector
 * @param { boolean } withAni
 * @param { number } wait
 * @returns { void }
 */
function safeRemove(selector, withAni = false, wait = 200) {
    safeFunction(() => {
        let removeNodes = document.querySelectorAll(selector);
        for (let i = 0; i < removeNodes.length; i++) {
            aniRemove(removeNodes[i], withAni, wait);
        }
    });
}

/**
 * 动画删除element
 * @param { Element } node
 * @param { boolean } withAni
 * @param { number } wait
 * @returns { void }
 */
function aniRemove(node, withAni = false, wait = 200) {
    if (withAni) {
        node.classList.add('aniDelete');
        setTimeout(() => {
            node.remove();
        }, wait);
    } else {
        node.remove();
    }
}

/**
 * 安全运行方法
 * @param func
 * @param failCb
 * @returns { void }
 */
function safeFunction(func, failCb) {
    try {
        func();
    } catch (e) {
        failCb && failCb(e);
    }
}

/**
 *
 * @param callback 回调函数, 需要返回是否, True: 结束、False: 相当于定时器
 * callback return:
 *     true  = 倒计时
 *     false = 计时器
 * @param { number } period 周期,如: 200ms
 * @param { boolean }  now 立即执行
 * @param { number } count 次数, -1: Infinity
 * @returns { void }
 */
function retryInterval(callback, period = 50, now = false, count = -1) {
    if (now && count-- !== 0) {
        if (callback()) return;
    }
    const inter = setInterval(() => {
        if (count-- === 0) {
            return clearInterval(inter);
        }
        callback() && clearInterval(inter);
    }, period);
}

/**
 * 等待选择器运行
 * @param selector
 * @param callbackFunc
 * @param time
 * @param notClear
 * @returns { void }
 */
function safeWaitFunc(selector, callbackFunc, time = 60, notClear = false) {
    notClear = notClear || false;
    let doClear = !notClear;
    retryInterval(() => {
        if ((typeof (selector) === "string" && document.querySelector(selector) != null)) {
            callbackFunc(document.querySelector(selector));
            if (doClear) return true;
        } else if (typeof (selector) === "function" && (selector() != null || (selector() || []).length > 0)) {
            callbackFunc(selector()[0]);
            if (doClear) return true;
        }
    }, time, true);
}

/**
 * 生成n位数字字母混合字符串
 * @param n
 * @returns { string }
 */
function generateMixed(n) {
    const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    let res = "";
    for (let i = 0; i < n; i++) {
        const id = Math.floor(Math.random() * chars.length);
        res += chars[id];
    }
    return res;
}

/**
 * 睡眠
 * @param milliseconds
 * @returns { Promise<void> }
 */
function sleep(milliseconds) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, milliseconds);
    });
}

/**
 * 是否是数组
 * @param arr
 * @returns { boolean }
 */
function isArray(arr) {
    const toString = Object.prototype.toString;
    const isArray = Array.isArray || function (arg) {
        return toString.call(arg) === '[object Array]';
    };
    return isArray(arr);
}

/**
 * 是否是对象
 * @param val
 * @returns { boolean }
 */
function isObject(val) {
    return typeof val === 'object' && val !== null && Array.isArray(val) === false;
}

/**
 * 过滤空字段
 * @param obj
 * @returns { {} }
 */
function filterNullUndefined(obj) {
    if (!isObject(obj)) {
        return {};
    }
    return Object.entries(obj).reduce((acc, [key, value]) => {
        if (value !== null && value !== undefined && value !== '') {
            acc[key] = value;
        }
        return acc;
    }, {});
}

/**
 * 递归获取
 * @param key
 * @param source
 * @returns { {} | * }
 */
function acquireChain(key = [], source = {}) {
    if (!isArray(key)) {
        return {};
    }
    return key.reduce((res, k) => {
        if (!res) return {};
        return res[k];
    }, source);
}

/**
 * 是否时期已过
 * @param timestamp
 * @param time
 * @returns { boolean }
 */
function isExpired(timestamp, time) {
    const now = Date.now();
    return now - timestamp * 1000 > time;
}

/**
 * 请求
 * @param { string } path
 * @param { string } method
 * @param { {} } data
 * @param { {} } headers
 * @returns { Promise<any> }
 */
function request(path, method = 'GET', data = {}, headers = {}) {

    /**
     * 转化请求参数
     * @param data
     * @returns { string }
     */
    function transform(data = {}) {
        const esc = encodeURIComponent;
        // 转换参数
        return Object.keys(data)
            .map(k => `${esc(k)}=${esc(data[k])}`)
            .join('&');
    }

    method = method.toLocaleUpperCase();
    const config = {
        method, mode: 'cors', // 跨域
        credentials: 'include', // 允许携带cookie
        headers: Object.assign({
            'Content-type': ContentType.form
        }, headers)
    };

    // 如果是get请求
    if (method === 'GET') {
        // 转换拼接get参数
        let param = transform(data);
        if (param) path += `?${param}`;
    } else if (config.headers["Content-type"] === ContentType.form) {
        config.body = transform(data);
    } else {
        // 其他请求 放入body里面
        config.body = JSON.stringify(data);
    }
    return fetch(path, config).then(response => response.json());
}

/**
 * 开关按钮切换
 * @param e
 */
function arcoSwitchChange(e) {
    const $ev = e || window.event;
    const target = $ev.target || $ev.srcElement;
    const btn = target.closest(".arco-switch");
    if (!btn) return;
    if (btn.classList.contains("arco-switch-checked")) {
        btn.classList.remove("arco-switch-checked");
        btn.setAttribute("aria-checked", false);
    } else {
        btn.classList.add("arco-switch-checked");
        btn.setAttribute("aria-checked", true);
    }
    btn.dispatchEvent(new Event('change'));
}

/**
 * 内部检查
 * @param e
 * @param selector
 * @returns { * }
 */
function insideCheck(e, selector) {
    const $ev = e || window.event;
    const target = $ev.target || $ev.srcElement;
    return target.closest(selector);
}