// ==UserScript== // @name NodeSeek X // @namespace http://www.nodeseek.com/ // @version 1.0.0 // @description 用于增强 NodeSeek/DeepFlood 论坛体验的用户脚本:提供自动签到、下拉加载、快速评论、内容过滤、等级标记、浏览历史、Callout 渲染、图片预览、快捷键等功能,并带可视化设置面板可自由开关配置。 // @author dabao // @match *://www.nodeseek.com/* // @match *://www.deepflood.com/* // @require https://s4.zstatic.net/ajax/libs/layui/2.10.3/layui.min.js // @resource highlightStyle https://s4.zstatic.net/ajax/libs/highlight.js/11.9.0/styles/atom-one-light.min.css // @resource highlightStyle_dark https://s4.zstatic.net/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getResourceURL // @grant GM_addElement // @grant GM_addStyle // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-idle // @license GPL-3.0 // @supportURL https://www.nodeseek.com/post-36263-1 // @homepageURL https://www.nodeseek.com/post-36263-1 // @downloadURL none // ==/UserScript== (function () { 'use strict'; // NSX Core - 核心 // 环境 + DOM + 网络 + 存储 + 模块管理 const SITES = [ { host: "www.nodeseek.com", code: "ns", name: "NodeSeek" }, { host: "www.deepflood.com", code: "df", name: "DeepFlood" } ]; const info = GM_info?.script || {}; const site = SITES.find(s => s.host === location.host); let debug = false; try { debug = GM_getValue("settings", {})?.debug?.enabled; } catch { } // ===== 环境 ===== const env = { info, site, BASE_URL: location.origin, log: (...a) => debug && console.log(`[NSX]`, ...a), warn: (...a) => debug && console.warn(`[NSX]`, ...a), error: (...a) => console.error(`[NSX]`, ...a) }; // ===== DOM ===== const $ = (s, r = document) => r?.querySelector(s); const $$ = (s, r = document) => [...(r?.querySelectorAll(s) || [])]; function addStyle(id, val) { if (document.getElementById(id)) return; const isUrl = /^(https?:)?\/\//.test(val); const el = document.createElement(isUrl ? "link" : "style"); el.id = id; isUrl ? (el.rel = "stylesheet", el.href = val) : (el.textContent = val); document.head?.appendChild(el); } function addScript(id, val) { if (document.getElementById(id)) return; const el = document.createElement("script"); el.id = id; /^(https?:)?\/\//.test(val) ? (el.src = val) : (el.textContent = val); document.body?.appendChild(el); } const debounce = (fn, ms) => { let t; const d = (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; d.cancel = () => clearTimeout(t); return d; }; const throttle = (fn, ms) => { let last = 0; return (...a) => { const now = Date.now(); if (now - last >= ms) { last = now; fn(...a); } }; }; // ===== 存储 ===== const cfgFragments = new Map(), metaFragments = new Map(); let cfgCache = null; const isObj = v => v && typeof v === "object" && !Array.isArray(v); const merge = (t, s) => { for (const k in s) isObj(s[k]) ? (isObj(t[k]) || (t[k] = {}), merge(t[k], s[k])) : t[k] === undefined && (t[k] = s[k]); }; const getPath = (o, p) => p.split(".").reduce((a, k) => a?.[k], o); const setPath = (o, p, v) => { const ks = p.split("."), l = ks.pop(); ks.reduce((a, k) => a[k] ??= {}, o)[l] = v; }; const store = { reg(id, cfg, meta) { cfg && cfgFragments.set(id, cfg); meta && metaFragments.set(id, meta); }, getDefaults() { const d = { version: info.version, debug: { enabled: false } }; cfgFragments.forEach(f => merge(d, f)); return d; }, getMeta() { const m = {}; metaFragments.forEach(f => merge(m, f)); return m; }, init() { if (cfgCache) return cfgCache; const def = this.getDefaults(); cfgCache = GM_getValue("settings", null) || {}; merge(cfgCache, def); cfgCache.version = def.version; GM_setValue("settings", cfgCache); return cfgCache; }, get(p, fb) { const v = getPath(this.init(), p); return v === undefined ? fb : v; }, set(p, v) { setPath(this.init(), p, v); GM_setValue("settings", cfgCache); } }; // ===== 网络 ===== const net = { async fetch(url, { method = "GET", data, headers = {}, type = "json" } = {}) { const r = await fetch(url.startsWith("http") ? url : env.BASE_URL + url, { method, credentials: "include", headers: { ...(data ? { "Content-Type": "application/json" } : {}), ...headers }, body: data ? JSON.stringify(data) : undefined }); return r[type]().catch(() => null); }, get: (u, h, t) => net.fetch(u, { headers: h, type: t }), post: (u, d, h, t) => net.fetch(u, { method: "POST", data: d, headers: h, type: t }) }; // ===== 模块管理 ===== const modules = new Map(); function define(cfg) { if (!cfg?.id) throw new Error("id required"); cfg.deps ??= []; cfg.order ??= 100; modules.set(cfg.id, cfg); cfg.cfg && store.reg(cfg.id, cfg.cfg, cfg.meta); return cfg; } function boot(ctx) { store.init(); // 拓扑排序 const list = [...modules.values()]; const indeg = new Map(list.map(m => [m.id, 0])); const edges = new Map(list.map(m => [m.id, []])); list.forEach(m => m.deps.forEach(d => { if (modules.has(d)) { edges.get(d).push(m.id); indeg.set(m.id, indeg.get(m.id) + 1); } })); const q = list.filter(m => indeg.get(m.id) === 0).sort((a, b) => a.order - b.order); const sorted = []; while (q.length) { const cur = q.shift(); sorted.push(cur); edges.get(cur.id).forEach(n => { indeg.set(n, indeg.get(n) - 1); if (!indeg.get(n)) q.push(modules.get(n)); }); q.sort((a, b) => a.order - b.order); } // 初始化 sorted.forEach(m => { try { if (m.match?.(ctx) !== false) m.init?.(ctx); } catch (e) { env.error(m.id, e); } }); // watch if (ctx.watch) sorted.forEach(m => { const w = typeof m.watch === "function" ? m.watch(ctx) : m.watch; [].concat(w || []).filter(Boolean).forEach(i => ctx.watch(i.sel, i.fn, i.opts)); }); } // 自动跳转外部链接 const autoJump = { id: "autoJump", order: 60, cfg: { auto_jump_external_links: { enabled: true } }, meta: { auto_jump_external_links: { label: "自动跳转外部链接", group: "基本设置" } }, match: ctx => ctx.store.get("auto_jump_external_links.enabled", true), init(ctx) { $$('a[href*="/jump?to="]').forEach(a => { try { const to = new URL(a.href).searchParams.get("to"); if (to) a.href = decodeURIComponent(to); } catch { } }); if (/^\/jump/.test(location.pathname)) ctx.$(".btn")?.click(); } }; const __vite_glob_0_0 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: autoJump }, Symbol.toStringTag, { value: 'Module' })); // 下拉加载翻页 const PROFILES = { list: { path: /^\/(categories\/|page|award|search|$)/, threshold: 1500, next: ".nsk-pager a.pager-next", list: "ul.post-list:not(.topic-carousel-panel)", pagerTop: "div.nsk-pager.pager-top", pagerBot: "div.nsk-pager.pager-bottom" }, post: { path: /^\/post-/, threshold: 690, next: ".nsk-pager a.pager-next", list: "ul.comments", pagerTop: "div.nsk-pager.post-top-pager", pagerBot: "div.nsk-pager.post-bottom-pager" } }; const autoLoading = { id: "autoLoading", order: 100, cfg: { loading_post: { enabled: true }, loading_comment: { enabled: true } }, meta: { loading_post: { label: "加载帖子", group: "内容设置" }, loading_comment: { label: "加载评论", group: "内容设置" } }, match: ctx => ctx.store.get("loading_post.enabled", true) || ctx.store.get("loading_comment.enabled", true), init(ctx) { const profile = ctx.isList ? PROFILES.list : ctx.isPost ? PROFILES.post : null; if (!profile) return; let busy = false, prevY = scrollY; const blockByLevel = (doc) => { const lv = ctx.user?.rank || 0; doc.querySelectorAll('.post-list-item use[href="#lock"]').forEach(el => { const n = +(el.closest("span")?.textContent?.match(/\d+/)?.[0] || 0); if (n > lv) el.closest(".post-list-item")?.classList.add("blocked-post"); }); }; const load = async () => { if (busy) return; const atBottom = document.documentElement.scrollHeight <= innerHeight + scrollY + profile.threshold; if (!atBottom) return; const nextUrl = ctx.$(profile.next)?.href; if (!nextUrl) return; busy = true; try { const html = await net.get(nextUrl, {}, "text"); const doc = new DOMParser().parseFromString(html, "text/html"); blockByLevel(doc); // 评论数据同步 if (ctx.isPost) { const json = doc.getElementById("temp-script")?.textContent; if (json) try { const cfg = JSON.parse(decodeURIComponent(atob(json).split("").map(c => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")).join(""))); if (cfg?.postData?.comments) ctx.uw.__config__.postData.comments.push(...cfg.postData.comments); } catch { } } const src = doc.querySelector(profile.list), dst = document.querySelector(profile.list); if (src && dst) dst.append(...src.children); [profile.pagerTop, profile.pagerBot].forEach(sel => { const s = doc.querySelector(sel), d = document.querySelector(sel); if (s && d) d.innerHTML = s.innerHTML; }); history.pushState(null, null, nextUrl); } catch (e) { ctx.env.error("autoLoading", e); } busy = false; }; const deb = debounce(load, 300); addEventListener("scroll", throttle(() => { if (scrollY > prevY) deb(); prevY = scrollY; }, 200), { passive: true }); } }; const __vite_glob_0_1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: autoLoading }, Symbol.toStringTag, { value: 'Module' })); // 屏蔽用户 const blockMembers = { id: "blockMembers", deps: ["ui"], order: 240, cfg: { block_members: { enabled: true } }, meta: { block_members: { label: "屏蔽用户", group: "过滤设置" } }, match: ctx => ctx.loggedIn && ctx.store.get("block_members.enabled", true), init(ctx) { addStyle("nsx-block", ".usercard-button-group .btn{padding:0 .8rem}"); const block = name => net.post("/api/block-list/add", { block_member_name: name }) .then(r => ctx.ui.alert?.("提示", r?.success ? `屏蔽【${name}】成功` : `屏蔽失败:${r?.message || ""}`)); document.querySelectorAll(".post-list .post-list-item,.content-item").forEach(item => { const avatar = item.querySelector(".avatar-normal"); if (!avatar) return; avatar.addEventListener("click", () => { const check = setInterval(() => { const card = document.querySelector("div.user-card.hover-user-card"); const pm = card?.querySelector("a.btn"); if (!card || !pm) return; clearInterval(check); const name = card.querySelector("a.Username")?.textContent; if (!name || card.querySelector(".nsx-block-btn")) return; const btn = pm.cloneNode(false); btn.className = "btn nsx-block-btn"; btn.textContent = "屏蔽"; btn.style.cssText = "float:left;background-color:rgba(0,0,0,.3)!important"; btn.onclick = e => { e.preventDefault(); ctx.ui.confirm?.(`屏蔽"${name}"?`, "可在设置=>屏蔽用户解除", () => block(name)); }; pm.after(btn); }, 50); }); }); } }; const __vite_glob_0_2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: blockMembers }, Symbol.toStringTag, { value: 'Module' })); // 屏蔽帖子(关键词) const mark$3 = new WeakSet(); const run$1 = (els, ctx) => { const kws = (ctx.store.get("block_posts.keywords", []) || []).map(k => String(k).trim().toLowerCase()).filter(Boolean); els.forEach(item => { if (mark$3.has(item)) return; mark$3.add(item); const title = item.querySelector(".post-title>a")?.textContent?.toLowerCase() || ""; if (kws.some(k => title.includes(k))) item.classList.add("blocked-post"); }); }; const blockPosts = { id: "blockPosts", order: 220, cfg: { block_posts: { enabled: true, keywords: [] } }, meta: { block_posts: { label: "屏蔽帖子", group: "过滤设置", fields: { keywords: { type: "TEXTAREA", label: "关键词", placeholder: "每行一个", valueType: "array" } } } }, match: ctx => ctx.isList && ctx.store.get("block_posts.enabled", true), init(ctx) { run$1($$(".post-list-item"), ctx); }, watch: ctx => ({ sel: ".post-list-item", fn: els => run$1(els, ctx), opts: { debounce: 80 } }) }; const __vite_glob_0_3 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: blockPosts }, Symbol.toStringTag, { value: 'Module' })); // 屏蔽低等级可见帖子 const mark$2 = new WeakSet(); const run = (els, ctx) => { const lv = ctx.user?.rank || 0; els.forEach(el => { const item = el.closest(".post-list-item"); if (!item || mark$2.has(item)) return; mark$2.add(item); const n = +(el.closest("span")?.textContent?.match(/\d+/)?.[0] || 0); if (n > lv) item.classList.add("blocked-post"); }); }; const blockViewLevel = { id: "blockViewLevel", order: 222, match: ctx => ctx.isList, init(ctx) { run($$('.post-list-item use[href="#lock"]'), ctx); }, watch: ctx => ({ sel: '.post-list-item use[href="#lock"]', fn: els => run(els, ctx), opts: { debounce: 80 } }) }; const __vite_glob_0_4 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: blockViewLevel }, Symbol.toStringTag, { value: 'Module' })); // Callout 支持 + 编辑器插入菜单 const CSS_BASE = `.post-content blockquote{border-left:none;border-radius:4px;margin:1em 0;box-shadow:inset 4px 0 0 0 rgba(0,0,0,.1)}.callout{--c:8,109,221;overflow:hidden;border-radius:4px;margin:1em 0;padding:12px 12px 12px 24px!important;box-shadow:inset 4px 0 0 0 rgba(var(--c),.5)}.callout.is-collapsible .callout-title{cursor:pointer}.callout-title{display:flex;gap:4px;color:rgb(var(--c));line-height:1.3;align-items:flex-start}.callout-content{overflow-x:auto}.callout-icon{flex:0 0 auto;display:flex;align-items:center}.callout-icon .svg-icon,.callout-fold .svg-icon{color:rgb(var(--c));height:18px;width:18px}.callout-title-inner{font-weight:600}.callout-fold{display:flex;align-items:center;padding-inline-end:8px}.callout-fold .svg-icon{transition:transform .1s}.callout-fold.is-collapsed .svg-icon{transform:rotate(-90deg)}.callout.is-collapsed .callout-content{display:none}.callout[data-callout="abstract"],.callout[data-callout="summary"],.callout[data-callout="tldr"]{--c:83,223,221}.callout[data-callout="info"],.callout[data-callout="todo"]{--c:8,109,221}.callout[data-callout="tip"],.callout[data-callout="hint"],.callout[data-callout="important"]{--c:83,223,221}.callout[data-callout="success"],.callout[data-callout="check"],.callout[data-callout="done"]{--c:68,207,110}.callout[data-callout="question"],.callout[data-callout="help"],.callout[data-callout="faq"]{--c:236,117,0}.callout[data-callout="warning"],.callout[data-callout="caution"],.callout[data-callout="attention"]{--c:236,117,0}.callout[data-callout="failure"],.callout[data-callout="fail"],.callout[data-callout="missing"]{--c:233,49,71}.callout[data-callout="danger"],.callout[data-callout="error"]{--c:233,49,71}.callout[data-callout="bug"]{--c:233,49,71}.callout[data-callout="example"]{--c:120,82,238}.callout[data-callout="quote"],.callout[data-callout="cite"]{--c:158,158,158}.callout-inserter-wrapper{position:relative;display:inline-flex;align-items:center}.callout-inserter-btn{padding:0;border:none;background:0 0;cursor:pointer;display:flex;color:currentColor}.callout-inserter-btn:hover{opacity:.7}.callout-inserter-dropdown{position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:8px;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.15);z-index:1000;min-width:160px;display:none;overflow:auto;max-height:240px}.callout-inserter-dropdown.show{display:block}.callout-inserter-item{padding:8px 12px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:13px;transition:background .15s}.callout-inserter-item:hover{background:#f5f5f5}.callout-inserter-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}`; const CSS_COLORFUL = `.callout{background:rgba(var(--c),.1)}`; const ICONS = { note: "M21.17 6.81a1 1 0 0 0-3.99-3.99L3.84 16.17a2 2 0 0 0-.5.83l-1.32 4.35a.5.5 0 0 0 .62.62l4.35-1.32a2 2 0 0 0 .83-.5zm-6.17-1.81 4 4", abstract: "M8 2h8v4H8zM16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2M12 11h4M12 16h4M8 11h.01M8 16h.01", info: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 14v-4m0-4h.01", tip: "M12 3q1 4 4 6.5t3 5.5a1 1 0 0 1-14 0 5 5 0 0 1 1-3 1 1 0 0 0 5 0c0-2-1.5-3-1.5-5q0-2 2.5-4", success: "M20 6 9 17l-5-5", question: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01", warning: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4m0 4h.01", failure: "M18 6 6 18M6 6l12 12", danger: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z", bug: "M12 20v-9m2-6a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4zM14.12 3.88 16 2M8 2l1.88 1.88M9 7.13V6a3 3 0 1 1 6 0v1.13", example: "M3 5h.01M3 12h.01M3 19h.01M8 5h13M8 12h13M8 19h13", quote: "M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2zM5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z", fold: "m6 9 6 6 6-6" }; const TYPE_MAP = { summary: "abstract", tldr: "abstract", hint: "tip", important: "tip", check: "success", done: "success", help: "question", faq: "question", caution: "warning", attention: "warning", fail: "failure", missing: "failure", error: "danger", cite: "quote" }; const MENUS = [{ k: "note", n: "笔记", c: "8,109,221" }, { k: "info", n: "信息", c: "8,109,221" }, { k: "tip", n: "提示", c: "83,223,221" }, { k: "warning", n: "警告", c: "236,117,0" }, { k: "danger", n: "危险", c: "233,49,71" }, { k: "success", n: "成功", c: "68,207,110" }, { k: "question", n: "问题", c: "236,117,0" }, { k: "example", n: "示例", c: "120,82,238" }]; const svg = d => ``; const RE = /^\[!(\w+)\]([+-])?(?:\s+([^<\n]+))?(?:)?([\s\S]*)$/i; const render = (els) => { els.forEach(bq => { if (bq.classList.contains("oc-done") || bq.closest("blockquote.oc-done")) return; bq.classList.add("oc-done"); const p = bq.querySelector(":scope > p"); const m = (p?.innerHTML?.trim() || "").match(RE); if (!m) return; const [, type, fold, title, content] = m; const t = type.toLowerCase(), base = TYPE_MAP[t] || t, icon = ICONS[base] || ICONS.note; const isColl = fold === "+" || fold === "-", isCol = fold === "-"; const wrap = document.createElement("div"); wrap.className = `callout${isColl ? " is-collapsible" : ""}${isCol ? " is-collapsed" : ""}`; wrap.dataset.callout = t; const titleEl = document.createElement("div"); titleEl.className = "callout-title"; titleEl.innerHTML = `
${svg(icon)}
${title?.trim() || type[0].toUpperCase() + type.slice(1)}
`; if (isColl) { const foldEl = document.createElement("div"); foldEl.className = `callout-fold${isCol ? " is-collapsed" : ""}`; foldEl.innerHTML = svg(ICONS.fold); titleEl.appendChild(foldEl); titleEl.onclick = () => { wrap.classList.toggle("is-collapsed"); foldEl.classList.toggle("is-collapsed"); }; } wrap.appendChild(titleEl); const cont = document.createElement("div"); cont.className = "callout-content"; if (content?.trim()) { const pp = document.createElement("p"); pp.innerHTML = content.trim(); cont.appendChild(pp); } let sib = p.nextSibling; while (sib) { const next = sib.nextSibling; cont.appendChild(sib); sib = next; } if (cont.childNodes.length) wrap.appendChild(cont); bq.replaceWith(wrap); }); }; const insertCallout = (editor, type) => { const cm = editor.querySelector(".CodeMirror")?.CodeMirror; if (!cm) return; const doc = cm.getDoc(); let cur = doc.getCursor(); const lvl = (doc.getLine(cur.line).match(/^(>\s*)+/)?.[0].match(/>/g) || []).length; if (lvl > 0) { let last = cur.line; for (let i = cur.line + 1; i < doc.lineCount(); i++) { if (doc.getLine(i).match(/^>\s*/)) last = i; else break; } cur = { line: last, ch: doc.getLine(last).length }; } const pre = lvl > 0 ? ">".repeat(lvl + 1) + " " : "> "; doc.replaceRange((lvl > 0 ? "\n" : "") + `${pre}[!${type}] \n${pre}`, cur); doc.setCursor({ line: cur.line + (lvl > 0 ? 1 : 0), ch: `${pre}[!${type}] `.length }); cm.focus(); }; let clickBound = false; const createInserter = () => { const editor = $(".md-editor"); const bar = editor?.querySelector(".mde-toolbar"); if (!editor || !bar || bar.querySelector(".callout-inserter-wrapper")) return; const vAttr = [...(bar.querySelector(".toolbar-item")?.attributes || [])].find(a => a.name.startsWith("data-v-"))?.name; const setV = el => vAttr && el.setAttribute(vAttr, ""); const wrap = document.createElement("span"); wrap.className = "callout-inserter-wrapper toolbar-item"; wrap.title = "Callout - NodeSeek X"; setV(wrap); const btn = document.createElement("span"); btn.className = "callout-inserter-btn i-icon"; btn.innerHTML = ``; setV(btn); const drop = document.createElement("div"); drop.className = "callout-inserter-dropdown"; MENUS.forEach(t => { const item = document.createElement("div"); item.className = "callout-inserter-item"; item.innerHTML = `${t.n}[${t.k}]`; item.onclick = e => { e.stopPropagation(); insertCallout(editor, t.k); drop.classList.remove("show"); }; drop.appendChild(item); }); btn.onclick = e => { e.stopPropagation(); drop.classList.toggle("show"); }; if (!clickBound) { document.addEventListener("click", () => $$(".callout-inserter-dropdown.show").forEach(d => d.classList.remove("show"))); clickBound = true; } const sep = document.createElement("div"); sep.className = "sep"; setV(sep); wrap.append(btn, drop); bar.append(sep, wrap); }; const callout = { id: "callout", order: 360, cfg: { callout: { enabled: true, style: "colorful" } }, meta: { callout: { label: "Callout 支持", group: "内容设置", fields: { style: { type: "RADIO", label: "风格", options: [{ value: "colorful", text: "绚丽" }, { value: "clean", text: "清新" }] } } } }, match: ctx => (ctx.isPost || /^\/new-discussion/.test(location.pathname)) && ctx.store.get("callout.enabled", true), init(ctx) { const style = ctx.store.get("callout.style", "colorful"); addStyle("nsx-callout", CSS_BASE + (style === "colorful" ? CSS_COLORFUL : "")); render($$(".post-content blockquote")); createInserter(); document.addEventListener("click", e => { if (e.target?.closest?.(".md-editor")) requestAnimationFrame(createInserter); }); }, watch: () => [{ sel: ".post-content blockquote", fn: render, opts: { debounce: 80 } }, { sel: ".mde-toolbar", fn: createInserter, opts: { debounce: 80 } }] }; const __vite_glob_0_5 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: callout }, Symbol.toStringTag, { value: 'Module' })); // 代码高亮 + 复制按钮 const CSS$4 = `.post-content pre{position:relative}.post-content pre span.copy-code{position:absolute;right:.5em;top:.5em;cursor:pointer;color:#c1c7cd}.post-content pre .iconpark-icon{width:16px;height:16px;margin:3px}.post-content pre .iconpark-icon:hover{color:var(--link-hover-color)}.dark-layout .post-content pre code.hljs{padding:1em!important}`; const mark$1 = new WeakSet(); const addCopyBtn = (els, ctx) => { els.forEach(code => { if (mark$1.has(code)) return; mark$1.add(code); const btn = document.createElement("span"); btn.className = "copy-code"; btn.title = "复制代码"; btn.innerHTML = ``; btn.onclick = () => { const sel = getSelection(), range = document.createRange(); range.selectNodeContents(code); sel.removeAllRanges(); sel.addRange(range); document.execCommand("copy"); sel.removeAllRanges(); btn.querySelector("use")?.setAttribute("href", "#check"); setTimeout(() => btn.querySelector("use")?.setAttribute("href", "#copy"), 1000); ctx.ui.tips?.("复制成功", btn, { tips: 4, time: 1000 }); }; code.after(btn); }); }; const codeHighlight = { id: "codeHighlight", deps: ["ui"], order: 140, cfg: { code_highlight: { enabled: true } }, meta: { code_highlight: { label: "代码高亮", group: "内容设置" } }, match: ctx => ctx.store.get("code_highlight.enabled", true), init(ctx) { addStyle("nsx-hl-css", CSS$4); addCopyBtn($$(".post-content pre code"), ctx); }, watch: ctx => ({ sel: ".post-content pre code", fn: els => addCopyBtn(els, ctx), opts: { debounce: 80 } }) }; const __vite_glob_0_6 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: codeHighlight }, Symbol.toStringTag, { value: 'Module' })); // 快捷键发送评论 (Ctrl+Enter) const commentShortcut = { id: "commentShortcut", order: 135, cfg: { comment_shortcut: { enabled: true } }, meta: { comment_shortcut: { label: "快捷键发帖", group: "内容设置" } }, match: ctx => ctx.isPost && ctx.store.get("comment_shortcut.enabled", true), init(ctx) { const getBtn = () => $(".md-editor button.submit.btn.focus-visible"); $$(".CodeMirror").forEach(cmEl => { const cm = cmEl?.CodeMirror; if (!cm || cm.__nsx) return; cm.__nsx = true; const bind = () => { const btn = getBtn(); if (btn && !/Ctrl\+Enter/i.test(btn.textContent)) btn.textContent += "(Ctrl+Enter)"; if (btn && !cm.__nsxMap) { cm.__nsxMap = { "Ctrl-Enter": () => getBtn()?.click() }; cm.addKeyMap(cm.__nsxMap); } else if (!btn && cm.__nsxMap) { cm.removeKeyMap(cm.__nsxMap); cm.__nsxMap = null; } }; bind(); cmEl.addEventListener("focusin", bind, true); cmEl.addEventListener("focusout", bind, true); }); } }; const __vite_glob_0_7 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: commentShortcut }, Symbol.toStringTag, { value: 'Module' })); // 暗色模式样式切换 const darkMode = { id: "darkMode", order: 180, init(ctx) { const body = document.body; if (!body) return; const lightHl = GM_getResourceURL("highlightStyle"); const darkHl = GM_getResourceURL("highlightStyle_dark"); const apply = () => { const dark = body.classList.contains("dark-layout"); // 为 html 添加/移除 .dark 类以触发 layui 深色主题 document.documentElement.classList.toggle("dark", dark); // 切换 highlight.js 样式 document.getElementById("nsx-hl")?.remove(); addStyle("nsx-hl", dark ? darkHl : lightHl); }; apply(); new MutationObserver(() => apply()).observe(body, { attributes: true, attributeFilter: ["class"] }); } }; const __vite_glob_0_8 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: darkMode }, Symbol.toStringTag, { value: 'Module' })); // 浏览历史 const CSS$3 = `#nsx-history-panel{position:fixed;right:12px;top:56px;width:min(380px,94vw);height:70vh;background:#fff;border:1px solid #e4e4e4;border-radius:12px;box-shadow:0 16px 32px rgba(0,0,0,.12);z-index:99999;display:none;flex-direction:column;font-size:13px;color:#1f1f1f;box-sizing:border-box;font-family:"Segoe UI","Microsoft YaHei",sans-serif}#nsx-history-panel.show{display:flex}.nsx-history-header{display:flex;align-items:center;justify-content:space-between;padding:12px 12px 6px}.nsx-history-title{font-size:15px;font-weight:600}.nsx-history-action{border:0;background:0;color:#666;cursor:pointer;font-size:12px;padding:4px 8px;border-radius:6px}.nsx-history-action:hover{background:#f2f3f5}.nsx-history-search{display:flex;align-items:center;gap:6px;margin:0 12px 8px;border:1px solid #e1e1e1;border-radius:8px;padding:6px 8px}.nsx-history-search input{border:0;background:0;outline:0;width:100%;font-size:13px}.nsx-history-tabs{display:flex;gap:16px;padding:0 12px 6px;border-bottom:1px solid #f0f0f0}.nsx-history-tab{border:0;background:0;cursor:pointer;color:#6b6b6b;font-size:12px;padding:6px 0;font-weight:600;border-bottom:2px solid transparent}.nsx-history-tab.is-active{color:#0a62ff;border-bottom-color:#0a62ff}.nsx-history-list{flex:1;overflow-y:auto;padding:6px 8px 12px}.nsx-history-group{margin-bottom:10px}.nsx-history-group-title{display:flex;align-items:center;justify-content:space-between;padding:4px;color:#666;font-size:12px}.nsx-history-items{list-style:none;margin:0;padding:0}.nsx-history-item{display:flex;align-items:center;gap:8px;padding:6px;border-radius:8px}.nsx-history-item:hover{background:#f5f7fb}.nsx-history-link{display:flex;align-items:center;gap:8px;flex:1;min-width:0;text-decoration:none;color:inherit}.nsx-history-icon{width:20px;height:20px;border-radius:50%;background:#f0f0f0;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0}.nsx-history-icon img{width:100%;height:100%;object-fit:cover}.nsx-history-item-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nsx-history-time{color:#9a9a9a;font-size:12px;margin-left:auto}.nsx-history-empty{padding:10px 6px;color:#999;font-size:12px}.nsx-history-close,.nsx-history-restore{border:0;background:0;cursor:pointer;font-size:12px;padding:2px 4px;border-radius:6px;display:none}.nsx-history-close{color:#999}.nsx-history-restore{color:#0a62ff}.nsx-history-item:hover .nsx-history-time{display:none}.nsx-history-item:hover .nsx-history-close,.nsx-history-item:hover .nsx-history-restore{display:block}.nsx-history-group-title .nsx-history-close{display:block;opacity:.9}.nsx-history-close:hover{color:#ff4d4f}.nsx-history-restore:hover{background:#eef3ff}.dark-layout #nsx-history-panel{background:#1e1e1e;border-color:#3a3a3a;color:#e0e0e0}.dark-layout .nsx-history-action{color:#999}.dark-layout .nsx-history-action:hover{background:#2a2a2a}.dark-layout .nsx-history-search{border-color:#3a3a3a}.dark-layout .nsx-history-search input{color:#e0e0e0}.dark-layout .nsx-history-tabs{border-bottom-color:#3a3a3a}.dark-layout .nsx-history-tab{color:#999}.dark-layout .nsx-history-group-title{color:#888}.dark-layout .nsx-history-item:hover{background:#2a2a2a}.dark-layout .nsx-history-icon{background:#3a3a3a}.dark-layout .nsx-history-time{color:#666}.dark-layout .nsx-history-empty{color:#666}`; const HKEY = "nsx_browsing_history", RKEY = "nsx_recently_closed"; const pad = n => String(n).padStart(2, "0"); const fmtDate = d => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; const fmtTime = d => `${pad(d.getHours())}:${pad(d.getMinutes())}`; const now = () => new Date().toISOString(); const esc = s => String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]); const WEEK = ["日", "一", "二", "三", "四", "五", "六"]; const history$1 = { id: "history", order: 300, cfg: { history: { enabled: true, limit: 100, days: 7 } }, meta: { history: { label: "浏览历史", group: "显示设置", fields: { limit: { type: "NUMBER", label: "保存上限", valueType: "number" }, days: { type: "NUMBER", label: "保存天数", valueType: "number" } } } }, match: ctx => (ctx.isPost || ctx.isList) && ctx.store.get("history.enabled", true), init(ctx) { const maxItems = ctx.store.get("history.limit", 100) || 100; const maxAge = (ctx.store.get("history.days", 7) || 7) * 864e5; const prune = arr => { const t = Date.now(); return (arr || []).filter(i => t - new Date(i.time).getTime() < maxAge).sort((a, b) => new Date(a.time) - new Date(b.time)).slice(-maxItems); }; const load = k => { try { const r = JSON.parse(localStorage.getItem(k) || "[]"); const n = prune(r); if (n.length !== r.length) localStorage.setItem(k, JSON.stringify(n)); return n; } catch { return []; } }; const save = (k, a) => localStorage.setItem(k, JSON.stringify(prune(a))); const getH = () => load(HKEY), saveH = a => save(HKEY, a); const getR = () => load(RKEY), saveR = a => save(RKEY, a); // 使用 postData 获取帖子信息 const add = (pd, list, saveFn) => { if (!pd?.postId) return; const id = pd.postId; const h = list(), i = h.findIndex(x => x.postId === id); const e = { postId: id, title: pd.title || document.title, time: now(), uid: pd.op?.uid || null, author: pd.op?.name || null }; i > -1 ? Object.assign(h[i], e) : h.push(e); saveFn(h); }; addStyle("nsx-hist", CSS$3); let panel = null, trigger = null, state = { open: false, tab: "all", kw: "" }; const orig = $("#nsk-head .color-theme-switcher"); if (!orig) return; trigger = orig.cloneNode(false); trigger.classList.replace("color-theme-switcher", "history-dropdown-on"); trigger.title = "历史记录"; trigger.innerHTML = ``; orig.before(trigger); const fmtDayTitle = day => { const d = new Date(`${day}T00:00:00`); const title = `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 星期${WEEK[d.getDay()]}`; return day === fmtDate(new Date()) ? `今天 - ${title}` : title; }; const open = () => { if (!panel) { panel = document.createElement("div"); panel.id = "nsx-history-panel"; panel.innerHTML = `
历史记录
`; document.body.appendChild(panel); panel.querySelector("input").oninput = e => { state.kw = e.target.value.toLowerCase(); render(); }; panel.onclick = e => { e.stopPropagation(); const t = e.target.closest("[data-t]"); if (t) { state.tab = t.dataset.t; render(); return; } const a = e.target.closest("[data-a]"); if (!a) return; const act = a.dataset.a, id = a.dataset.id; if (act === "clear") ctx.ui.confirm("确认", "确定要清空所有记录吗?", () => { localStorage.removeItem(state.tab === "recent" ? RKEY : HKEY); render(); }); if (act === "del") { state.tab === "recent" ? saveR(getR().filter(x => x.postId != id)) : saveH(getH().filter(x => x.postId != id)); render(); } if (act === "clear-day") { const key = state.tab === "recent" ? RKEY : HKEY; save(key, load(key).filter(i => fmtDate(new Date(i.time)) !== a.dataset.day)); render(); } if (act === "restore") window.open(`/post-${id}-1`, "_blank"); }; document.addEventListener("click", e => { if (state.open && !panel.contains(e.target) && !trigger.contains(e.target)) close(); }); document.addEventListener("keydown", e => { if (state.open && e.key === "Escape") close(); }); } const r = trigger.getBoundingClientRect(); panel.style.top = `${r.bottom + 8}px`; panel.style.height = `${innerHeight - r.bottom - 16}px`; render(); panel.classList.add("show"); state.open = true; }; const close = () => { panel?.classList.remove("show"); state.open = false; }; const toggle = () => state.open ? close() : open(); const render = () => { let list = (state.tab === "recent" ? getR() : getH()).sort((a, b) => new Date(b.time) - new Date(a.time)); if (state.kw) list = list.filter(i => (i.title || "").toLowerCase().includes(state.kw)); panel.querySelectorAll(".nsx-history-tab").forEach(b => b.classList.toggle("is-active", b.dataset.t === state.tab)); const lEl = panel.querySelector(".nsx-history-list"); if (!list.length) { lEl.innerHTML = `
暂无记录
`; return; } const g = {}; list.forEach(i => { const d = fmtDate(new Date(i.time)); (g[d] ||= []).push(i); }); lEl.innerHTML = Object.entries(g).map(([day, items]) => { const itemsHtml = items.map(i => { if (!i.postId) return ""; const url = `/post-${i.postId}-1`; const avatar = i.uid ? `` : ""; const restore = state.tab === "recent" ? `` : ""; return `
  • ${avatar}${esc((i.title || "").slice(0, 32))}${fmtTime(new Date(i.time))}${restore}
  • `; }).join(""); return `
    ${fmtDayTitle(day)}
    `; }).join(""); }; trigger.onclick = e => { e.preventDefault(); e.stopPropagation(); toggle(); }; // 记录当前页面 const pd = ctx.uw?.__config__?.postData; if (pd) add(pd, getH, saveH); // 监听页面关闭 addEventListener("beforeunload", () => { const pd = ctx.uw?.__config__?.postData; if (pd) add(pd, getR, saveR); }, { capture: true }); } }; const __vite_glob_0_9 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: history$1 }, Symbol.toStringTag, { value: 'Module' })); // 图片预览 const mark = new WeakSet(); const bind = (els, ctx) => { els.forEach(img => { const post = img.closest("article.post-content"); if (!post || mark.has(img)) return; mark.add(img); const newImg = img.cloneNode(true); img.replaceWith(newImg); mark.add(newImg); newImg.addEventListener("click", e => { e.preventDefault(); const imgs = [...post.querySelectorAll("img:not(.sticker)")]; const data = imgs.map((x, i) => ({ alt: x.alt, pid: i + 1, src: x.src })); ctx.ui.layer?.photos({ photos: { title: "图片预览", start: imgs.indexOf(newImg), data } }); }, true); }); }; const imageSlide = { id: "imageSlide", deps: ["ui"], order: 160, cfg: { image_slide: { enabled: true } }, meta: { image_slide: { label: "图片预览", group: "内容设置" } }, match: ctx => ctx.isPost && ctx.store.get("image_slide.enabled", true), init(ctx) { bind($$("article.post-content img:not(.sticker)"), ctx); }, watch: ctx => ({ sel: "article.post-content img:not(.sticker)", fn: els => bind(els, ctx), opts: { debounce: 80 } }) }; const __vite_glob_0_10 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: imageSlide }, Symbol.toStringTag, { value: 'Module' })); // 悬停预加载 const instantPage = { id: "instantPage", order: 320, cfg: { instant_page: { enabled: true } }, meta: { instant_page: { label: "网页预加载", group: "内容设置" } }, match: ctx => ctx.store.get("instant_page.enabled", true), init(ctx) { const done = new Set(); const link = document.createElement("link"); link.rel = "prefetch"; document.body.addEventListener("mouseover", e => { const a = e.target.closest("a"); if (!a?.href?.startsWith(`${location.origin}/post-`) || done.has(a.href)) return; setTimeout(() => { if (a.matches(":hover")) { link.href = a.href; document.head.appendChild(link); done.add(a.href); } }, 65); }, { passive: true }); } }; const __vite_glob_0_11 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: instantPage }, Symbol.toStringTag, { value: 'Module' })); // 等级标签 const CSS$2 = `.role-tag.user-level{color:#fafafa}.user-lv0{background:#c7c2c2;border-color:#c7c2c2}.user-lv1{background:#ffb74d;border-color:#ffb74d}.user-lv2{background:#ff9400;border-color:#ff9400}.user-lv3{background:#ff5252;border-color:#ff5252}.user-lv4{background:#e53935;border-color:#e53935}.user-lv5{background:#ab47bc;border-color:#ab47bc}.user-lv6{background:#8e24aa;border-color:#8e24aa}.user-lv7{background:#42a5f5;border-color:#42a5f5}.user-lv8{background:#1e88e5;border-color:#1e88e5}.user-lv9{background:#66bb6a;border-color:#66bb6a}.user-lv10{background:#2e7d32;border-color:#2e7d32}.user-lv11{background:#ffca28;border-color:#ffca28}.user-lv12{background:#ffb300;border-color:#ffb300}.user-lv13{background:#b388ff;border-color:#b388ff}.user-lv14{background:#7c4dff;border-color:#7c4dff}.user-lv15{background:#000;border-color:#000;color:#ffd700}`; const levelTag = { id: "levelTag", deps: ["ui"], order: 260, cfg: { level_tag: { enabled: true, low_lv_alarm: true, low_lv_max_days: 30 } }, meta: { level_tag: { label: "等级标签", group: "显示设置", fields: { low_lv_alarm: { type: "SWITCH", label: "低等级警告" }, low_lv_max_days: { type: "NUMBER", label: "注册天数", valueType: "number" } } } }, match: ctx => ctx.loggedIn && ctx.isPost && ctx.store.get("level_tag.enabled", true), async init(ctx) { addStyle("nsx-lv", CSS$2); const opUid = ctx.uw?.__config__?.postData?.op?.uid; if (!opUid) return; let user; try { const r = await net.get(`/api/account/getInfo/${opUid}`); if (!r?.success) return; user = r.detail; } catch { return; } const days = Math.floor((Date.now() - new Date(user.created_at)) / 864e5); const alarm = ctx.store.get("level_tag.low_lv_alarm") && days < (ctx.store.get("level_tag.low_lv_max_days", 30) || 30) ? "⚠️" : ""; const rank = Math.floor(Math.sqrt(user.coin || 0) / 10); const span = document.createElement("span"); span.className = `nsk-badge role-tag user-level user-lv${rank}`; span.innerHTML = `${alarm}Lv ${rank}`; span.onmouseenter = () => ctx.ui.tips?.(`注册 ${days} 天;帖子 ${user.nPost};评论 ${user.nComment}`, span, { tips: 3, time: 0 }); span.onmouseleave = () => ctx.ui.layer?.closeAll?.(); ctx.$('#nsk-body .nsk-post .nsk-content-meta-info .author-info>a')?.after(span); } }; const __vite_glob_0_12 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: levelTag }, Symbol.toStringTag, { value: 'Module' })); // 菜单系统(油猴菜单 + 高级设置面板) const CSS$1 = `#nsx-config-menu{height:100%;overflow-y:visible;border-right:1px solid #eee}#nsx-config-content{height:100%;overflow-y:auto;padding:0 15px;background:#f8f8f8}.nsx-config-card{margin-bottom:20px}.nsx-config-card .layui-card-header{display:flex;align-items:center;justify-content:space-between;font-weight:700}.nsx-config-card .header-checkbox{position:absolute;right:15px;top:50%;transform:translateY(-50%)}.nsx-config-card .layui-form-switch{margin-top:0!important}.nsx-config-card .layui-card-body:empty{padding-top:0;padding-bottom:0}.dark-layout #nsx-config-menu{border-right-color:#3a3a3a}.dark-layout #nsx-config-content{background:#1e1e1e}`; const el = (t, c, p, s) => { const e = document.createElement(t); if (c) e.className = c; if (s) e.style.cssText = s; if (p) p.appendChild(e); return e; }; const menus = { id: "menus", deps: ["ui"], order: 30, cfg: { open_post_in_new_tab: { enabled: false } }, meta: { open_post_in_new_tab: { label: "新标签页打开帖子", group: "内容设置" } }, match: () => true, init(ctx) { const uw = ctx.uw, code = ctx.site?.code || "ns"; const ids = []; const txt = (m, v) => `${m.text}: ${m.states[v].s1} ${m.states[v].s2}`; const regMenus = () => { ids.splice(0).forEach(i => GM_unregisterMenuCommand(i)); menus.forEach(m => { let lbl = m.text; if (m.states.length > 0) { let v = 0; if (m.name === "sign_in") v = store.get(`sign_in.${code}.method`, 0); else v = store.get(`${m.name}.enabled`, true) === false ? 0 : 1; lbl = txt(m, v); } const id = GM_registerMenuCommand(lbl, () => m.cb(m.name, m.states), { autoClose: m.autoClose ?? true }); ids.push(id || lbl); }); }; const switchState = (n, states) => { if (n === "sign_in") { if (!ctx.site) return; let cur = store.get(`sign_in.${code}.method`, 0); cur = (cur + 1) % states.length; store.set(`sign_in.${code}.enabled`, cur !== 0); store.set(`sign_in.${code}.method`, cur || 1); } else if (n === "loading_post") { const next = !store.get("loading_post.enabled", true); store.set("loading_post.enabled", next); store.set("loading_comment.enabled", next); } else { store.set(`${n}.enabled`, !store.get(`${n}.enabled`, true)); } regMenus(); }; const reSign = () => { if (!ctx.loggedIn || store.get(`sign_in.${code}.enabled`, true) === false) return ctx.ui.alert("提示", "签到已关闭"); store.set(`sign_in.${code}.last_date`, "1753/1/1"); location.reload(); }; const switchNewTab = () => { const next = !store.get("open_post_in_new_tab.enabled", false); try { uw.indexedDB.open("ns-preference-db").onsuccess = e => { const db = e.target.result; const s = db.transaction("ns-preference-store", "readwrite").objectStore("ns-preference-store"); s.get("configuration").onsuccess = e2 => { const c = e2.target.result || {}; c.openPostInNewPage = next; s.put(c, "configuration"); store.set("open_post_in_new_tab.enabled", next); regMenus(); ctx.ui.alert("", `已${next ? "开启" : "关闭"}新标签页打开链接`); }; }; } catch { } }; const advSettings = () => { if (!ctx.ui.layer || !window.layui) return; addStyle("nsx-cfg", CSS$1); // 获取所有模块的 cfg 和 meta const defs = store.getDefaults(), metas = store.getMeta(); const ignore = new Set(["version", "debug", "ui"]); const entries = Object.entries(metas).filter(([k]) => defs[k] && !ignore.has(k)).map(([k, m]) => ({ key: k, meta: m })); const groups = {}; entries.forEach(e => { const g = e.meta.group || "其他设置"; (groups[g] ||= []).push(e); }); const cont = document.createElement("div"); cont.className = "layui-row"; cont.style.cssText = "display:flex;height:100%"; const menuDiv = el("div", "layui-panel layui-col-xs3", cont); menuDiv.id = "nsx-config-menu"; const menuList = el("ul", "layui-menu", menuDiv); const wrapper = el("div", "layui-col-xs9", cont); wrapper.id = "nsx-config-content"; const isObj = v => v && typeof v === "object" && !Array.isArray(v); const inferType = (v, m) => m?.type || (Array.isArray(v) ? "TEXTAREA" : typeof v === "boolean" ? "SWITCH" : typeof v === "number" ? "NUMBER" : "TEXT"); const inferVT = (v, m) => m?.valueType || (Array.isArray(v) ? "array" : typeof v === "number" ? "number" : typeof v === "boolean" ? "boolean" : "string"); const makeField = (f, path, val, defaultCol = 12) => { const col = f.col ?? defaultCol; const w = el("div", `layui-col-md${col}`), item = el("div", "layui-form-item", w); const lbl = el("label", "layui-form-label", item); lbl.textContent = f.label || f.key; const blk = el("div", "layui-input-block", item); let inp; if (f.type === "SWITCH") { inp = el("input", "", blk); inp.type = "checkbox"; if (val) inp.setAttribute("checked", ""); inp.setAttribute("lay-skin", "switch"); inp.setAttribute("lay-text", "开启|关闭"); inp.name = path; } else if (f.type === "TEXTAREA") { inp = el("textarea", "layui-textarea", blk); inp.setAttribute("placeholder", f.placeholder || ""); inp.textContent = Array.isArray(val) ? val.join("\n") : (val ?? ""); inp.name = path; } else if (f.type === "RADIO" && f.options) { f.options.forEach(opt => { const r = el("input", "", blk); r.type = "radio"; r.name = path; r.setAttribute("value", opt.value); r.dataset.valueType = f.valueType || ""; if (String(val) === String(opt.value)) r.setAttribute("checked", ""); r.setAttribute("title", opt.text); }); inp = blk.querySelector("input"); } else if (f.type === "COLOR") { const inpWrap = el("div", "layui-input-inline", blk); inpWrap.style.width = "100px"; inp = el("input", "layui-input", inpWrap); inp.type = "text"; inp.name = path; inp.value = val ?? ""; inp.readOnly = true; inp.style.cssText = `background:${val || "#fff"};cursor:pointer;color:transparent`; const cpWrap = el("div", "layui-inline", blk); cpWrap.style.left = "-11px"; const wrap = el("div", "", cpWrap); wrap.dataset.colorPath = path; wrap.dataset.colorVal = val ?? ""; wrap.dataset.colorInp = inp.name; wrap.dataset.colorDefault = f.defaultVal ?? ""; } else { inp = el("input", "layui-input", blk); inp.type = f.type === "NUMBER" ? "number" : "text"; inp.setAttribute("value", val ?? ""); inp.name = path; } if (inp) inp.dataset.valueType = f.valueType || ""; return w; }; const makeCard = (entry, siteCode) => { const m = entry.meta || {}; let base = entry.key, cfg = defs[entry.key]; if (entry.key === "sign_in") { cfg = defs.sign_in?.[siteCode] || defs.sign_in?.ns || {}; base = `sign_in.${siteCode}`; } if (!isObj(cfg)) return null; const card = el("div", "layui-card layui-form nsx-config-card"); card.setAttribute("lay-filter", `nsx-${entry.key}`); const hdr = el("div", "layui-card-header", card); hdr.textContent = m.label || entry.key; if (typeof cfg.enabled === "boolean") { const cbW = el("div", "header-checkbox", hdr), cb = el("input", "", cbW); cb.type = "checkbox"; cb.name = `${base}.enabled`; if (store.get(`${base}.enabled`, cfg.enabled)) cb.setAttribute("checked", ""); cb.setAttribute("lay-skin", "switch"); cb.setAttribute("lay-text", "开启|关闭"); cb.setAttribute("lay-filter", "nsx-main-switch"); } const body = el("div", "layui-card-body layui-row layui-col-space10", card); const fields = m.fields || {}, hidden = new Set(m.hidden || []); const cols = m.cols || 1, defaultCol = Math.floor(12 / cols); Object.keys(cfg).filter(k => k !== "enabled" && !isObj(cfg[k]) && !hidden.has(k)).forEach(k => { const fm = fields[k] || {}; const f = { key: k, label: fm.label || k, type: inferType(cfg[k], fm), options: fm.options, placeholder: fm.placeholder, valueType: inferVT(cfg[k], fm), col: fm.col, defaultVal: cfg[k] }; const cur = store.get(`${base}.${k}`, cfg[k]); const fe = makeField(f, `${base}.${k}`, cur, defaultCol); if (fe) body.appendChild(fe); }); return card; }; Object.entries(groups).forEach(([g, list], i) => { const fs = el("fieldset", "layui-elem-field layui-field-title", wrapper); fs.id = `group-${i}`; const lg = el("legend", "", fs); lg.textContent = g; const fd = el("div", "layui-form", wrapper); list.forEach(e => { const c = makeCard(e, code); if (c) fd.appendChild(c); }); const mi = el("li", "", menuList); if (i === 0) mi.classList.add("layui-menu-item-checked"); const mb = el("div", "layui-menu-body-title", mi), a = el("a", "", mb); a.href = `#group-${i}`; a.textContent = g; }); // 底部提示 const endFs = el("fieldset", "layui-elem-field layui-field-title", wrapper, "text-align:center"); const endLg = el("legend", "", endFs, "font-size:0.8em;opacity:0.5"); endLg.textContent = "到底了"; const w = window.layui.device().mobile ? "100%" : "620px"; ctx.ui.layer.open({ type: 1, offset: "r", anim: "slideLeft", area: [w, "100%"], scrollbar: false, shade: 0.1, shadeClose: false, btn: ["保存设置", "取消"], btnAlign: "r", title: "NodeSeek X 设置", id: "setting-layer-direction-r", content: cont.outerHTML, success: ly => { const r = ly?.[0] || ly; try { window.layui.form?.render(); } catch { } // 滚动同步:右侧滚动时高亮左侧菜单 const content = r?.querySelector?.("#nsx-config-content"); const menu = r?.querySelector?.("#nsx-config-menu"); if (content && menu) { const items = menu.querySelectorAll("li"); content.addEventListener("scroll", () => { const groups = content.querySelectorAll("fieldset[id^='group-']"); let activeIdx = 0; groups.forEach((g, i) => { if (g.offsetTop - content.scrollTop <= 50) activeIdx = i; }); items.forEach((li, i) => li.classList.toggle("layui-menu-item-checked", i === activeIdx)); }, { passive: true }); } // 主开关联动 const toggleCard = (card, on) => { card.querySelectorAll(".layui-card-body input,.layui-card-body select,.layui-card-body textarea").forEach(el => { el.disabled = !on; el.closest(".layui-form-item")?.classList.toggle("layui-disabled", !on); }); window.layui.form?.render(null, card.getAttribute("lay-filter")); }; // 初始 + 监听 r?.querySelectorAll?.(".header-checkbox input").forEach(cb => !cb.checked && toggleCard(cb.closest(".nsx-config-card"), false)); window.layui.form?.on("switch(nsx-main-switch)", d => toggleCard(d.elem.closest(".nsx-config-card"), d.elem.checked)); window.layui.use("colorpicker", () => { const cp = window.layui.colorpicker; r?.querySelectorAll?.("[data-color-path]").forEach(wrap => { const inp = r.querySelector(`input[name="${wrap.dataset.colorInp}"]`), init = wrap.dataset.colorVal || "", def = wrap.dataset.colorDefault || ""; const setBg = c => { if (inp) inp.style.background = c || ""; }; const render = color => cp.render({ elem: wrap, color, alpha: true, predefine: true, format: "rgb", change: setBg, done(c) { if (inp) inp.value = c || def; if (!c && def) { render(def); setBg(def); } }, cancel: setBg }); render(init); }); }); }, yes: (idx, ly) => { const r = ly?.[0] || ly, sc = r?.querySelector ? r : document; sc.querySelectorAll("input,select,textarea").forEach(el => { if (!el.name) return; // radio 只保存选中的那个 if (el.type === "radio" && !el.checked) return; let v; const vt = el.dataset.valueType; if (el.type === "checkbox") v = el.checked; else if (el.type === "radio") v = vt === "number" ? Number(el.value) : el.value; else if (el.tagName === "TEXTAREA") v = vt === "array" ? el.value.split("\n").map(s => s.trim()).filter(Boolean) : el.value; else if (el.type === "number" || vt === "number") { const n = Number(el.value); v = Number.isFinite(n) ? n : 0; } else v = el.value; if (v !== undefined && v !== "") store.set(el.name, v); }); ctx.ui.layer.msg("设置已保存,刷新生效"); ctx.ui.layer.close(idx); } }); }; const menus = [ { name: "sign_in", cb: switchState, text: "自动签到", states: [{ s1: "❌", s2: "关闭" }, { s1: "🎲", s2: "随机🍗" }, { s1: "📌", s2: "5个🍗" }] }, { name: "re_sign", cb: reSign, text: "🔂 重试签到", states: [] }, { name: "loading_post", cb: switchState, text: "下拉加载翻页", states: [{ s1: "❌", s2: "关闭" }, { s1: "✅", s2: "开启" }] }, { name: "open_post_in_new_tab", cb: switchNewTab, text: "新标签页打开帖子", states: [{ s1: "❌", s2: "关闭" }, { s1: "✅", s2: "开启" }] }, { name: "advanced_settings", cb: advSettings, text: "⚙️ 高级设置", states: [] }, { name: "feedback", cb: () => GM_openInTab("https://greasyfork.org/zh-CN/scripts/479426/feedback", { active: true, insert: true, setParent: true }), text: "💬 反馈 & 建议", states: [] } ]; regMenus(); } }; const __vite_glob_0_13 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: menus }, Symbol.toStringTag, { value: 'Module' })); // 快捷评论 const quickComment = { id: "quickComment", order: 120, cfg: { quick_comment: { enabled: true } }, meta: { quick_comment: { label: "快捷评论", group: "内容设置" } }, match: ctx => ctx.loggedIn && ctx.isPost && ctx.store.get("loading_comment.enabled", true) && ctx.store.get("quick_comment.enabled", true), init(ctx) { const editor = $(".md-editor"), parent = $("#back-to-parent"), group = $("#fast-nav-button-group"); if (!editor || !parent || !group) return; let open = false; const show = e => { if (open) return; e?.preventDefault?.(); editor.style.cssText = `position:fixed;bottom:0;margin:0;width:100%;max-width:${editor.clientWidth || 720}px;z-index:999`; addClose(); open = true; }; const btn = parent.cloneNode(true); btn.id = "back-to-comment"; btn.innerHTML = ``; btn.onclick = show; parent.before(btn); $$(".nsk-post .comment-menu,.comment-container .comments").forEach(el => el.addEventListener("click", e => { if (["引用", "回复", "编辑"].includes(e.target?.textContent)) show(e); }, true)); function addClose() { const tb = $("#editor-body .window_header > :last-child"); if (!tb || $(".nsx-close-editor")) return; const cb = tb.cloneNode(true); cb.classList.add("nsx-close-editor"); cb.title = "关闭"; const sp = cb.querySelector("span"); if (sp) { sp.classList.replace("i-icon-full-screen-one", "i-icon-close"); sp.innerHTML = ``; } cb.onclick = () => { editor.style.cssText = ""; cb.remove(); open = false; }; tb.after(cb); } } }; const __vite_glob_0_14 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: quickComment }, Symbol.toStringTag, { value: 'Module' })); // 自动签到 const signIn = { id: "signIn", deps: ["ui"], order: 80, cfg: { sign_in: { ns: { enabled: true, method: 1, last_date: "" }, df: { enabled: true, method: 1, last_date: "" } } }, meta: { sign_in: { label: "自动签到", group: "基本设置", fields: { method: { type: "RADIO", label: "签到方式", valueType: "number", options: [{ value: 1, text: "随机🍗" }, { value: 2, text: "5个🍗" }] } }, hidden: ["last_date"] } }, match: ctx => ctx.site && ctx.loggedIn && ctx.store.get(`sign_in.${ctx.site.code}.enabled`, true), async init(ctx) { const code = ctx.site.code; const method = ctx.store.get(`sign_in.${code}.method`, 0); const now = (() => { const off = new Date().getTimezoneOffset() + 480; const bj = new Date(Date.now() + off * 60000); return `${bj.getFullYear()}/${bj.getMonth() + 1}/${bj.getDate()}`; })(); if (ctx.store.get(`sign_in.${code}.last_date`) === now) return; ctx.store.set(`sign_in.${code}.last_date`, now); try { const r = await net.post(`/api/attendance?random=${method === 1}`); r?.success ? ctx.ui.success?.(`签到成功!+${r.gain}🍗,共${r.current}🍗`) : ctx.ui.info?.(r?.message || "签到失败"); } catch (e) { ctx.ui.info?.(e?.message || "签到错误"); } } }; const __vite_glob_0_15 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: signIn }, Symbol.toStringTag, { value: 'Module' })); // 签到提示 const CSS = `.nsplus-tip{background:rgba(255,217,0,.8);padding:3px;text-align:center;animation:blink 5s ease infinite}.nsplus-tip p,.nsplus-tip p a{color:#f00}.nsplus-tip p a:hover{color:#0ff}`; const signinTips = { id: "signinTips", deps: ["ui"], order: 82, cfg: { signin_tips: { enabled: true } }, meta: { signin_tips: { label: "签到提示", group: "基本设置" } }, match(ctx) { if (!ctx.site || !ctx.loggedIn || !ctx.store.get("signin_tips.enabled", true)) return false; return ctx.store.get(`sign_in.${ctx.site.code}.enabled`, true) === false; }, init(ctx) { addStyle("nsx-signtip", CSS); const code = ctx.site.code; const now = (() => { const d = new Date(Date.now() + (new Date().getTimezoneOffset() + 480) * 6e4); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`; })(); if (now === ctx.store.get(`sign_in.${code}.ignore_date`) || now === ctx.store.get(`sign_in.${code}.last_date`)) return; const header = $("header"); if (!header) return; const tip = document.createElement("div"); tip.className = "nsplus-tip"; tip.innerHTML = `

    今天还没签到!【随机🍗】【5个🍗】【今天不提示

    `; header.appendChild(tip); $$(".nsx-sign", tip).forEach(a => a.onclick = async e => { e.preventDefault(); try { const r = await net.post(`/api/attendance?random=${a.dataset.r === "1"}`); r?.success ? ctx.ui.success?.(`签到成功!+${r.gain}🍗`) : ctx.ui.info?.(r?.message || "签到失败"); } catch (e) { ctx.ui.warning?.(e?.message || "失败"); } tip.remove(); ctx.store.set(`sign_in.${code}.last_date`, now); }); $(".nsx-ign", tip).onclick = e => { e.preventDefault(); tip.remove(); ctx.store.set(`sign_in.${code}.ignore_date`, now); }; } }; const __vite_glob_0_16 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: signinTips }, Symbol.toStringTag, { value: 'Module' })); // 平滑滚动 const smoothScroll = { id: "smoothScroll", order: 340, cfg: { smooth_scroll: { enabled: true } }, meta: { smooth_scroll: { label: "平滑滚动", group: "显示设置" } }, match: ctx => ctx.store.get("smooth_scroll.enabled", true), init() { addStyle("nsx-smooth", "html{scroll-behavior:smooth}"); } }; const __vite_glob_0_17 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: smoothScroll }, Symbol.toStringTag, { value: 'Module' })); // 用户卡片扩展 - 跨标签页同步未读消息 class Broadcast { static ins = new Map(); constructor(name) { if (Broadcast.ins.has(name)) return Broadcast.ins.get(name); this.myId = `${Date.now()}-${Math.random()}`; this.recv = []; this.KEY = `nsx_tab_${name}`; try { this.ch = new BroadcastChannel(name); this.ch.onmessage = e => this.recv.forEach(f => f(e.data)); } catch { this.ch = null; } addEventListener("storage", e => { if (e.key === this.KEY) { e.newValue || localStorage.setItem(this.KEY, this.myId); this._up(); } }); addEventListener("beforeunload", () => { if (this.active) localStorage.removeItem(this.KEY); }); localStorage.setItem(this.KEY, this.myId); this._up(); Broadcast.ins.set(name, this); } _up() { this.active = localStorage.getItem(this.KEY) === this.myId; } on(fn) { this.recv.push(fn); } send(data) { if (!this.ch) return; const m = { sender: this.myId, data }; this.ch.postMessage(m); this.recv.forEach(f => f(m)); } task(fn, ms) { setInterval(async () => { if (!this.active) return; try { const d = await fn(); if (d !== undefined) this.send(d); } catch { } }, ms); } } const userCardExt = { id: "userCardExt", order: 200, cfg: { user_card_ext: { enabled: true } }, meta: { user_card_ext: { label: "用户卡片扩展", group: "显示设置" } }, match: ctx => ctx.loggedIn && (ctx.isPost || ctx.isList) && ctx.store.get("user_card_ext.enabled", true), async init(ctx) { const bn = new Broadcast("nsx_notify"); const card = $(".user-card .user-stat"); const last = card?.querySelector(".stat-block:first-child > :last-child"); if (!card || !last) return; const atEl = last.cloneNode(true), msgEl = last.cloneNode(true); last.after(atEl); card.querySelector(".stat-block:last-child")?.append(msgEl); const up = (el, href, icon, text, cnt) => { const a = el.querySelector("a"); if (!a) return; a.href = href; el.querySelector("a svg use")?.setAttribute("href", icon); const t = el.querySelector("a > :nth-child(2)"); if (t) t.textContent = `${text} `; const c = el.querySelector("a > :last-child"); if (c) { c.textContent = cnt; c.classList.toggle("notify-count", cnt > 0); } }; const upAll = c => { up(atEl, "/notification#/atMe", "#at-sign", "我", c.atMe); up(msgEl, "/notification#/message?mode=list", "#envelope-one", "私信", c.message); up(last, "/notification#/reply", "#remind-6nce9p47", "回复", c.reply); }; bn.on(({ data }) => { if (data?.type === "unreadCount" && data.counts) upAll(data.counts); }); bn.send({ type: "unreadCount", counts: ctx.user?.unViewedCount || {}, timestamp: Date.now() }); bn.task(async () => { const r = await fetch("/api/notification/unread-count", { credentials: "include" }); if (!r.ok) throw 0; const d = await r.json(); if (d?.success && d.unreadCount) return { type: "unreadCount", counts: d.unreadCount, timestamp: Date.now() }; throw 0; }, 5000); } }; const __vite_glob_0_18 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: userCardExt }, Symbol.toStringTag, { value: 'Module' })); // 已访问链接颜色 const DEFAULT_LIGHT = "#afb9c1"; const DEFAULT_DARK = "#393f4e"; const visitedColor = { id: "visitedColor", order: 350, cfg: { visited_color: { enabled: true, light: DEFAULT_LIGHT, dark: DEFAULT_DARK } }, meta: { visited_color: { label: "已访问颜色", group: "显示设置", // cols: 2, fields: { light: { type: "COLOR", label: "浅色模式" }, dark: { type: "COLOR", label: "深色模式" } } } }, match: ctx => ctx.isList && ctx.store.get("visited_color.enabled", true), init(ctx) { const light = ctx.store.get("visited_color.light", DEFAULT_LIGHT); const dark = ctx.store.get("visited_color.dark", DEFAULT_DARK); addStyle("nsx-visited-color", `.post-list .post-title a:visited{color:${light}}body.dark-layout .post-list .post-title a:visited{color:${dark}}`); } }; const __vite_glob_0_19 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, default: visitedColor }, Symbol.toStringTag, { value: 'Module' })); // ===== SVG 图标 ===== const SVG_SPRITE = ` `; // ===== 基础 CSS ===== const BASE_CSS = `.blocked-post{display:none!important}#back-to-comment{display:flex}#fast-nav-button-group .nav-item-btn:nth-last-child(4){bottom:120px}header div.history-dropdown-on{color:var(--link-hover-color);cursor:pointer;padding:0 5px;position:absolute;right:50px}.msc-overlay{background-color:var(--bg-sub-color)}`; // ===== Observer ===== class Observer { constructor() { this.listeners = []; this.mo = null; } watch(sel, fn, opts = {}) { this.listeners.push({ sel, fn, opts }); if (!this.mo) { this.mo = new MutationObserver(debounce(() => this._run(), 50)); this.mo.observe(document.body, { childList: true, subtree: true }); } } _run() { this.listeners.forEach(({ sel, fn, opts }) => { const els = $$(sel); if (els.length) fn(els, opts); }); } } // ===== 创建 ctx ===== function createCtx(obs) { const uw = typeof unsafeWindow !== "undefined" ? unsafeWindow : window; return { env, $, $$, addStyle, store, net, uw, get loggedIn() { return !!uw?.__config__?.user; }, get user() { return uw?.__config__?.user; }, get uid() { return uw?.__config__?.user?.member_id; }, site: env.site, isPost: /^\/post-/.test(location.pathname), isList: /^\/(categories\/|page|award|search|$)/.test(location.pathname), watch: obs.watch.bind(obs), ui: {} }; } // ===== 启动 ===== function start() { // 注入资源 document.body?.insertAdjacentHTML("beforeend", SVG_SPRITE); addStyle("nsx-base", BASE_CSS); // layui CSS addStyle("nsx-layui-css", "https://s.cfn.pp.ua/layui/2.10.3/css/layui.css"); addStyle("nsx-layui-dark", "https://s.cfn.pp.ua/layui/theme-dark/2.10.3/css/layui-theme-dark-selector.css"); // highlight.js 脚本 addScript("nsx-hljs-script", "https://s4.zstatic.net/ajax/libs/highlight.js/11.9.0/highlight.min.js"); // highlight.js 样式 addStyle("hightlight-style", GM_getResourceURL("highlightStyle")); // hljs 初始化 addScript("nsx-hljs-onload", `(()=>{const r=()=>{if(window.hljs&&typeof hljs.highlightAll==="function")hljs.highlightAll()};document.readyState==="complete"?r():window.addEventListener("load",r,{once:true})})()`); // 加载模块 const mods = /* #__PURE__ */ Object.assign({"./features/autoJump.js": __vite_glob_0_0,"./features/autoLoading.js": __vite_glob_0_1,"./features/blockMembers.js": __vite_glob_0_2,"./features/blockPosts.js": __vite_glob_0_3,"./features/blockViewLevel.js": __vite_glob_0_4,"./features/callout.js": __vite_glob_0_5,"./features/codeHighlight.js": __vite_glob_0_6,"./features/commentShortcut.js": __vite_glob_0_7,"./features/darkMode.js": __vite_glob_0_8,"./features/history.js": __vite_glob_0_9,"./features/imageSlide.js": __vite_glob_0_10,"./features/instantPage.js": __vite_glob_0_11,"./features/levelTag.js": __vite_glob_0_12,"./features/menus.js": __vite_glob_0_13,"./features/quickComment.js": __vite_glob_0_14,"./features/signIn.js": __vite_glob_0_15,"./features/signinTips.js": __vite_glob_0_16,"./features/smoothScroll.js": __vite_glob_0_17,"./features/userCardExt.js": __vite_glob_0_18,"./features/visitedColor.js": __vite_glob_0_19}); Object.values(mods).forEach(m => m.default && define(m.default)); // 创建 Observer & ctx const obs = new Observer(); const ctx = createCtx(obs); // 初始化 UI (依赖 layui) const initUI = () => { if (!window.layui?.layer) return (ctx.ui = {}); const layer = window.layui.layer, uw = ctx.uw; ctx.ui = { layer, toast: (text, style) => { const idx = layer.msg(text, { offset: 't', area: ['100%', 'auto'], anim: 'slideDown' }); layer.style(idx, Object.assign({ opacity: 0.9 }, style)); return idx; }, info: msg => ctx.ui.toast(msg, { "background-color": "#4D82D6" }), success: msg => ctx.ui.toast(msg, { "background-color": "#57BF57" }), warning: msg => ctx.ui.toast(msg, { "background-color": "#D6A14D" }), error: msg => ctx.ui.toast(msg, { "background-color": "#E1715B" }), alert: (t, c, fn) => uw?.mscAlert ? (c === undefined ? uw.mscAlert(t) : uw.mscAlert(t, c)) : layer.alert(c, { title: t, icon: 0, btn: ["确定"] }, fn), confirm: (t, c, y, n) => uw?.mscConfirm ? uw.mscConfirm(t, c, y, n) : layer.confirm(c, { title: t, icon: 0, btn: ["确定", "取消"] }, y, n), tips: (msg, el, opts) => layer.tips(msg, el, opts) }; }; initUI(); if (!ctx.ui.layer) { const timer = setInterval(() => { if (window.layui?.layer) { initUI(); clearInterval(timer); } }, 100); setTimeout(() => clearInterval(timer), 5000); } // 启动所有模块 boot(ctx); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", start); } else { start(); } })();