刷新文件
/**
* 刷新文件 - 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);
}
};