两个quick add脚本,刷新文件、随机市场主题/本地主题

刷新文件

/**
 * 刷新文件 - QuickAdd 用户脚本 (全官方原生 API + 无感无闪烁版)
 * 
 * 功能:
 * 1. 强制对所有视图执行 leaf.rebuildView() 销毁重建,彻底消除渲染与 DOM 残余 Bug。
 * 2. 使用 Obsidian 官方原生的 Ephemeral State API 保存/还原滚动位置(scroll)与光标选择状态(cursor)。
 * 3. 采用透明度遮罩机制掩盖重建和恢复过程中的首帧视觉跳动。
 * 4. 零外部插件依赖,无需 window.__scrollMemory 等变量。
 */

module.exports = async (params) => {
    // 优先从 Obsidian 上下文或全局获取 app 与 Notice
    const app = params?.app || window.app;
    const Notice = window.Notice || require("obsidian").Notice;

    // ── 获取当前激活的视图与 DOM 容器 ──
    const leaf = app?.workspace?.activeLeaf;
    if (!leaf?.view) {
        new Notice("没有激活的视图");
        return;
    }

    const view = leaf.view;
    const viewType = view.getViewType?.() || "未知";
    const containerEl = leaf.containerEl;

    try {
        // 1. 针对延迟初始化的视图进行加载
        if (typeof leaf.loadIfDeferred === "function") {
            await leaf.loadIfDeferred();
        }

        // 2. 若有未保存的修改,优先写入磁盘(防止 rebuildView 丢失)
        if (typeof view.save === "function" && (view.isDirty?.() || view.dirty)) {
            await view.save();
        }

        // 3. 【官方原生 API】获取刷新前的临时状态(包含 scroll 滚动位置、cursor 光标与选区等)
        const ephemeralState = leaf.getEphemeralState();

        // 4. 开启透明度遮罩:遮蔽 rebuildView 瞬间 DOM 重构与归零的视觉跳动
        if (containerEl) {
            containerEl.style.transition = "none";
            containerEl.style.opacity = "0";
        }

        // 5. 执行销毁与重建
        if (typeof leaf.rebuildView === "function") {
            await leaf.rebuildView();
        } else {
            // 降级方案:使用 setViewState 触发重新挂载
            const state = leaf.getViewState?.();
            if (state) await leaf.setViewState(state);
        }

        // 6. 【官方原生 API】将保存的临时状态还原回新创建的视图
        if (ephemeralState && Object.keys(ephemeralState).length > 0) {
            await leaf.setEphemeralState(ephemeralState);
        }

        new Notice(`已重建 ${viewType} 视图 ✓`, 2000);
    } catch (e) {
        console.error("[刷新文件] 执行失败:", e);
        new Notice("刷新失败,请查看控制台");
    } finally {
        // 7. 延迟恢复显示:确保 CodeMirror 6 / Canvas 异步计算完布局与滚动位置后再接触遮罩
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                if (containerEl) {
                    containerEl.style.opacity = "1";
                }
            });
        });
    }
};

随机主题

会跳选项,是本地随机,还是市场随机,市场随机会下载主题

module.exports = async (params) => {
    // ================= 自定义配置 =================
    const CONFIG = {
        // 运行模式:"ask" (弹窗选择) | "local" (本地随机) | "market" (市场随机) | "favorite" (收藏夹随机) | "toggle" (收藏当前)
        MODE: "ask",

        // 配置文件相对路径(相对于库根目录)
        // 示例 1(保存在 QuickAdd 插件目录): `${app.vault.configDir}/plugins/quickadd/theme-tracker.json`
        // 示例 2(保存在库内指定文件夹)  : "插件配置文件夹/QuickAdd/theme-tracker.json"
        DATA_PATH: `${app.vault.configDir}/plugins/quickadd/theme-tracker.json`,

        // GitHub 加速镜像前缀(留空不开启,如:"https://ghfast.top/")
        GITHUB_PROXY: "",

        // GitHub Personal Access Token(留空不开启)
        GITHUB_TOKEN: "",
    };
    // ==============================================

    const { Notice, requestUrl } = window;
    const customCss = app.customCss;
    const adapter = app.vault.adapter;

    // 读取持久化数据(相对路径)
    const getData = async () => {
        try {
            if (await adapter.exists(CONFIG.DATA_PATH)) {
                const content = await adapter.read(CONFIG.DATA_PATH);
                return JSON.parse(content);
            }
        } catch (e) {
            console.error("读取主题跟踪数据失败", e);
        }
        return { favorites: [], downloaded: [] };
    };

    // 保存持久化数据(自动递归创建上级目录)
    const saveData = async (data) => {
        try {
            const dir = CONFIG.DATA_PATH.substring(0, CONFIG.DATA_PATH.lastIndexOf("/"));
            if (dir && !(await adapter.exists(dir))) {
                await adapter.mkdir(dir);
            }
            await adapter.write(CONFIG.DATA_PATH, JSON.stringify(data, null, 2));
        } catch (e) {
            console.error("保存主题跟踪数据失败", e);
        }
    };

    // 1. 收藏 / 取消收藏当前主题
    const toggleFavoriteCurrentTheme = async () => {
        const currentTheme = customCss.theme;
        if (!currentTheme) {
            new Notice("⚠️ 当前使用的是 Obsidian 默认主题,无需收藏。");
            return;
        }

        const data = await getData();
        const index = data.favorites.indexOf(currentTheme);

        if (index > -1) {
            data.favorites.splice(index, 1);
            await saveData(data);
            new Notice(`💔 已将【${currentTheme}】从收藏夹中移除`);
        } else {
            data.favorites.push(currentTheme);
            await saveData(data);
            new Notice(`❤️ 已将【${currentTheme}】加入收藏夹!`);
        }
    };

    // 2. 收藏夹主题随机切换
    const runFavoriteRandom = async () => {
        const data = await getData();
        const installedThemes = Object.keys(customCss.themes || {});
        const favInstalled = data.favorites.filter(t => installedThemes.includes(t));

        if (favInstalled.length === 0) {
            new Notice("❌ 收藏夹为空,或收藏的主题未在本地安装!");
            return;
        }

        const currentTheme = customCss.theme;
        const pool = favInstalled.filter(t => t !== currentTheme);
        const targetPool = pool.length > 0 ? pool : favInstalled;

        const randomTheme = targetPool[Math.floor(Math.random() * targetPool.length)];
        customCss.setTheme(randomTheme);
        new Notice(`❤️ 已随机切换至收藏主题:${randomTheme}`);
    };

    // 3. 本地已安装主题随机切换
    const runLocalRandom = () => {
        const installedThemes = Object.keys(customCss.themes || {});
        if (installedThemes.length === 0) {
            new Notice("❌ 未检测到已安装的自定义主题!");
            return;
        }

        const currentTheme = customCss.theme;
        const pool = installedThemes.filter(t => t !== currentTheme);
        const targetPool = pool.length > 0 ? pool : installedThemes;

        const randomTheme = targetPool[Math.floor(Math.random() * targetPool.length)];
        customCss.setTheme(randomTheme);
        new Notice(`🎨 已随机切换至本地主题:${randomTheme}`);
    };

    // 4. 市场主题随机下载并切换
    const runMarketRandom = async () => {
        const formatUrl = (rawUrl) => {
            if (!CONFIG.GITHUB_PROXY || !CONFIG.GITHUB_PROXY.trim()) return rawUrl;
            const proxy = CONFIG.GITHUB_PROXY.trim().replace(/\/+$/, "") + "/";
            return `${proxy}${rawUrl}`;
        };

        const getHeaders = () => {
            const headers = {};
            if (CONFIG.GITHUB_TOKEN && CONFIG.GITHUB_TOKEN.trim()) {
                headers["Authorization"] = `token ${CONFIG.GITHUB_TOKEN.trim()}`;
            }
            return headers;
        };

        new Notice("🎲 正在获取主题市场列表...");

        try {
            const marketUrl = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/community-css-themes.json";
            const res = await requestUrl({
                url: formatUrl(marketUrl),
                headers: getHeaders()
            });
            const onlineThemes = res.json;

            if (!onlineThemes || onlineThemes.length === 0) {
                new Notice("❌ 获取主题市场列表失败");
                return;
            }

            const randomTheme = onlineThemes[Math.floor(Math.random() * onlineThemes.length)];
            const themeName = randomTheme.name;
            const repo = randomTheme.repo;

            const installedThemes = Object.keys(customCss.themes || {});
            if (installedThemes.includes(themeName)) {
                customCss.setTheme(themeName);
                new Notice(`🎨 本地已存在,已切换至:${themeName}`);
                return;
            }

            new Notice(`📥 抽中新主题【${themeName}】,正在下载...`);

            const cssUrl = `https://raw.githubusercontent.com/${repo}/HEAD/theme.css`;
            const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;

            const [cssRes, manifestRes] = await Promise.all([
                requestUrl({ url: formatUrl(cssUrl), headers: getHeaders() }),
                requestUrl({ url: formatUrl(manifestUrl), headers: getHeaders() })
            ]);

            const themeDir = `${app.vault.configDir}/themes/${themeName}`;
            if (!await adapter.exists(themeDir)) {
                await adapter.mkdir(themeDir);
            }

            await adapter.write(`${themeDir}/theme.css`, cssRes.text);
            await adapter.write(`${themeDir}/manifest.json`, manifestRes.text);

            // 记录在线下载历史
            const data = await getData();
            if (!data.downloaded.includes(themeName)) {
                data.downloaded.push(themeName);
                await saveData(data);
            }

            if (typeof customCss.checkForUpdates === "function") {
                await customCss.checkForUpdates();
            }
            customCss.setTheme(themeName);

            new Notice(`✨ 下载完成,已切换至新主题:${themeName}`);

        } catch (err) {
            console.error("随机切换市场主题失败:", err);
            new Notice("❌ 切换失败,请检查网络连接或代理配置");
        }
    };

    // 5. 执行入口判断
    let selectedMode = CONFIG.MODE;

    if (selectedMode === "ask") {
        const data = await getData();
        const currentTheme = customCss.theme || "默认主题";
        const isFav = data.favorites.includes(currentTheme);
        const favText = isFav ? `💔 取消收藏当前主题 (${currentTheme})` : `❤️ 收藏当前主题 (${currentTheme})`;

        if (params?.quickAddApi?.suggester) {
            selectedMode = await params.quickAddApi.suggester(
                [
                    favText,
                    "🎨 本地已安装主题(随机切换)",
                    `❤️ 收藏夹主题(随机切换 - 共 ${data.favorites.length} 个)`,
                    "🌐 在线主题市场(随机下载并切换)",
                    `📊 查看记录(已下载: ${data.downloaded.length} 个)`
                ],
                ["toggle", "local", "favorite", "market", "info"]
            );
        } else {
            selectedMode = "local";
        }
    }

    if (selectedMode === "toggle") {
        await toggleFavoriteCurrentTheme();
    } else if (selectedMode === "favorite") {
        await runFavoriteRandom();
    } else if (selectedMode === "local") {
        runLocalRandom();
    } else if (selectedMode === "market") {
        await runMarketRandom();
    } else if (selectedMode === "info") {
        const data = await getData();
        new Notice(`❤️ 收藏主题: ${data.favorites.join(", ") || "暂无"}\n📥 随机下载主题: ${data.downloaded.join(", ") || "暂无"}`, 8000);
    }
};