fix: 修复重载BUG

This commit is contained in:
eondrcode 2026-05-26 18:44:59 +08:00
parent ae0deaeb58
commit f666a20319
4 changed files with 369 additions and 363 deletions

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 zero
Copyright (c) 2026 zero
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",

View file

@ -6,10 +6,10 @@ import { ManagerModal } from './modal/manager-modal';
import Commands from './command';
import Agreement from 'src/agreement';
import { RepoResolver, ensureBpmTagExists, BPM_TAG_ID } from './repo-resolver';
import { Notice, Platform, requestUrl } from 'obsidian';
import { BetaSource, ManagerPlugin, BPM_IGNORE_TAG } from './data/types';
import { Notice, Platform, requestUrl } from 'obsidian';
import { BetaSource, ManagerPlugin, BPM_IGNORE_TAG } from './data/types';
import { runMigrations } from './migrations';
import { fetchReleaseVersions, installPluginFromGithub, installThemeFromGithub, ReleaseVersion, sanitizeRepo } from './github-install';
import { fetchReleaseVersions, installPluginFromGithub, installThemeFromGithub, ReleaseVersion, sanitizeRepo } from './github-install';
import { performSelfCheck } from './self-check';
import { SystemRibbonManager } from './manager/system-ribbon-manager';
import { RibbonItem } from './data/types';
@ -39,7 +39,7 @@ export default class Manager extends Plugin {
public agreement: Agreement;
public repoResolver: RepoResolver;
public systemRibbonManager: SystemRibbonManager;
public updateStatus: Record<string, UpdateStatus> = {};
public updateStatus: Record<string, UpdateStatus> = {};
private updateProgressNotice: Notice | null = null;
@ -61,37 +61,37 @@ export default class Manager extends Plugin {
this.settings.LANGUAGE = this.getAppLanguage();
this.settings.LANGUAGE_INITIALIZED = true;
await this.saveSettings();
}
// 初始化语言系统
this.translator = new Translator(this);
let builtinTagsChanged = false;
const tagCountBeforeBpmEnsure = this.settings.TAGS.length;
ensureBpmTagExists(this);
builtinTagsChanged = this.settings.TAGS.length !== tagCountBeforeBpmEnsure;
this.ensureBpmTagAndRecords();
this.ensureSelfPluginRecord();
// 确保 BPM Ignore 标签存在
if (!this.settings.TAGS.some(t => t.id === BPM_IGNORE_TAG)) {
}
// 初始化语言系统
this.translator = new Translator(this);
let builtinTagsChanged = false;
const tagCountBeforeBpmEnsure = this.settings.TAGS.length;
ensureBpmTagExists(this);
builtinTagsChanged = this.settings.TAGS.length !== tagCountBeforeBpmEnsure;
this.ensureBpmTagAndRecords();
this.ensureSelfPluginRecord();
// 确保 BPM Ignore 标签存在
if (!this.settings.TAGS.some(t => t.id === BPM_IGNORE_TAG)) {
this.settings.TAGS.push({
id: BPM_IGNORE_TAG,
name: this.translator.t("标签_BPM忽略_名称") || "BPM Ignored",
color: "#6c757d" // 灰色
});
builtinTagsChanged = true;
}
if (this.normalizeBuiltinTagNames() || builtinTagsChanged) await this.saveSettings();
this.repoResolver = new RepoResolver(this);
// 初始化 Ribbon 管理器。功能编排不写入 Obsidian workspace 配置,只用 BPM 数据驱动运行时样式。
this.systemRibbonManager = new SystemRibbonManager(this.app, this);
const savedRibbonItems = [...(this.settings.RIBBON_SETTINGS || [])]
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
const orderedRibbonIds = savedRibbonItems.map((item) => item.id);
const hiddenRibbonStatus: Record<string, boolean> = {};
savedRibbonItems.forEach((item) => hiddenRibbonStatus[item.id] = !item.visible);
await this.syncRibbonConfig(orderedRibbonIds, hiddenRibbonStatus);
name: this.translator.t("标签_BPM忽略_名称") || "BPM Ignored",
color: "#6c757d" // 灰色
});
builtinTagsChanged = true;
}
if (this.normalizeBuiltinTagNames() || builtinTagsChanged) await this.saveSettings();
this.repoResolver = new RepoResolver(this);
// 初始化 Ribbon 管理器。功能编排不写入 Obsidian workspace 配置,只用 BPM 数据驱动运行时样式。
this.systemRibbonManager = new SystemRibbonManager(this.app, this);
const savedRibbonItems = [...(this.settings.RIBBON_SETTINGS || [])]
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
const orderedRibbonIds = savedRibbonItems.map((item) => item.id);
const hiddenRibbonStatus: Record<string, boolean> = {};
savedRibbonItems.forEach((item) => hiddenRibbonStatus[item.id] = !item.visible);
await this.syncRibbonConfig(orderedRibbonIds, hiddenRibbonStatus);
// 初始化侧边栏图标
this.addRibbonIcon('folder-cog', this.translator.t('通用_管理器_文本'), () => { this.managerModal = new ManagerModal(this.app, this); this.managerModal.open(); });
@ -100,11 +100,11 @@ export default class Manager extends Plugin {
this.settings.DELAY ? this.enableDelay() : this.disableDelay();
Commands(this.app, this);
this.agreement = new Agreement(this);
this.startupCheckForUpdates();
this.startupMaintainBetaSources();
this.registerObsidianProtocolHandler("BPM-plugin-install", async (params: ObsidianProtocolData) => {
this.agreement = new Agreement(this);
this.startupCheckForUpdates();
this.startupMaintainBetaSources();
this.registerObsidianProtocolHandler("BPM-plugin-install", async (params: ObsidianProtocolData) => {
await this.agreement.parsePluginInstall(params);
});
this.registerObsidianProtocolHandler("BPM-plugin-github", async (params: ObsidianProtocolData) => {
@ -122,10 +122,10 @@ export default class Manager extends Plugin {
// 延迟启动自检,确保 Obsidian 初始化完成,避免自动接管被覆盖
setTimeout(() => {
this.cleanRibbonItems(); // 启动后清理一次
if (this.settings.DELAY) performSelfCheck(this);
}, 2000);
});
}
if (this.settings.DELAY) performSelfCheck(this);
}, 2000);
});
}
public async onunload() {
if (this.dragObserverCleanup) {
@ -134,14 +134,14 @@ export default class Manager extends Plugin {
}
if (this.settings.DELAY) this.disableDelaysForAllPlugins();
if (this.menuObserver) {
if (this.menuObserver) {
this.menuObserver.disconnect();
}
// 临走前再清理一次
this.cleanRibbonItems();
this.systemRibbonManager?.stopWatch();
// 临走前再清理一次
this.cleanRibbonItems();
this.systemRibbonManager?.stopWatch();
}
private setupDragToHideObserver() {
@ -235,51 +235,51 @@ export default class Manager extends Plugin {
// 保存设置 (这将更新 RIBBON_SETTINGS 到 data.json)
await this.saveSettings();
const orderedIds = this.settings.RIBBON_SETTINGS.map(i => i.id);
const hiddenStatus: Record<string, boolean> = {};
this.settings.RIBBON_SETTINGS.forEach(i => hiddenStatus[i.id] = !i.visible);
this.applyRibbonConfigToMemory(orderedIds, hiddenStatus);
// 更新 CSS 样式 (重要:这控制了实际的显隐)
this.updateRibbonStyles();
const orderedIds = this.settings.RIBBON_SETTINGS.map(i => i.id);
const hiddenStatus: Record<string, boolean> = {};
this.settings.RIBBON_SETTINGS.forEach(i => hiddenStatus[i.id] = !i.visible);
this.applyRibbonConfigToMemory(orderedIds, hiddenStatus);
// 更新 CSS 样式 (重要:这控制了实际的显隐)
this.updateRibbonStyles();
// 如果 BPM 设置面板打开着,尝试刷新它
this.reloadIfCurrentModal();
this.applyRibbonConfigToMemory(orderedIds, hiddenStatus);
this.updateRibbonStyles();
this.applyRibbonConfigToMemory(orderedIds, hiddenStatus);
this.updateRibbonStyles();
new Notice(this.translator.t("Ribbon_已隐藏_通知", { name: label }));
new Notice(this.translator.t("Ribbon_已隐藏_通知", { name: label }));
}
}
public async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); }
public async saveSettings() { await this.saveData(this.settings); }
// 保存单个插件配置。保留方法名以兼容旧调用点。
public async savePluginAndExport(pluginId: string) {
await this.saveSettings();
}
// 保存单个插件配置。保留方法名以兼容旧调用点。
public async savePluginAndExport(pluginId: string) {
await this.saveSettings();
}
public showUpdateProgress(total: number): { dispose: () => void; update: (processed: number, currentId?: string) => void; cancel: () => void; isCancelled: () => boolean } {
if (this.updateProgressNotice) this.updateProgressNotice.hide();
const baseText = this.translator.t("通知_检测更新中文案");
const notice = new Notice(baseText, 0);
const update = (p: number, currentId?: string) => {
notice.setMessage(`${baseText} ${Math.min(p, total)}/${total}${currentId ? ` · ${currentId}` : ""}`);
};
this.updateProgressNotice = notice;
update(0);
return {
public showUpdateProgress(total: number): { dispose: () => void; update: (processed: number, currentId?: string) => void; cancel: () => void; isCancelled: () => boolean } {
if (this.updateProgressNotice) this.updateProgressNotice.hide();
const baseText = this.translator.t("通知_检测更新中文案");
const notice = new Notice(baseText, 0);
const update = (p: number, currentId?: string) => {
notice.setMessage(`${baseText} ${Math.min(p, total)}/${total}${currentId ? ` · ${currentId}` : ""}`);
};
this.updateProgressNotice = notice;
update(0);
return {
dispose: () => {
notice.hide();
if (this.updateProgressNotice === notice) this.updateProgressNotice = null;
},
update,
cancel: () => undefined,
isCancelled: () => false
};
}
},
update,
cancel: () => undefined,
isCancelled: () => false
};
}
public async checkUpdatesWithNotice(): Promise<Record<string, UpdateStatus>> {
const manifests = Object.values(this.appPlugins.manifests).filter((pm: PluginManifest) => pm.id !== this.manifest.id) as PluginManifest[];
@ -299,9 +299,9 @@ export default class Manager extends Plugin {
}
}
private async startupCheckForUpdates() {
if (!this.settings.STARTUP_CHECK_UPDATES) return;
try {
private async startupCheckForUpdates() {
if (!this.settings.STARTUP_CHECK_UPDATES) return;
try {
const manifests = Object.values(this.appPlugins.manifests).filter((pm: PluginManifest) => pm.id !== this.manifest.id) as PluginManifest[];
const progress = this.showUpdateProgress(manifests.length);
let processed = 0;
@ -319,159 +319,159 @@ export default class Manager extends Plugin {
if (this.settings.DEBUG) console.error("[BPM] startup check updates failed", e);
if (!this.settings.GITHUB_TOKEN) {
new Notice(this.translator.t("通知_检查更新失败_建议Token"));
}
}
}
private sourceHasUpdate(source: BetaSource): boolean {
const localVersion = source.localVersion || "";
if (!source.latestVersion || !localVersion) return false;
return this.compareVersions(source.latestVersion, localVersion) > 0;
}
private pickBetaSourceVersion(source: BetaSource, versions: ReleaseVersion[]): string {
if (source.mode === "frozen") return source.frozenVersion || source.latestVersion || versions[0]?.version || "";
return versions.find(v => !v.prerelease)?.version || versions[0]?.version || "";
}
private async checkBetaSource(source: BetaSource): Promise<void> {
try {
const versions = await fetchReleaseVersions(this, source.repo);
const latestVersion = this.pickBetaSourceVersion(source, versions);
source.latestVersion = latestVersion || "";
source.lastChecked = Date.now();
source.error = "";
if (source.type === "plugin") {
const mappedId = Object.entries(this.settings.REPO_MAP || {})
.find(([, repo]) => sanitizeRepo(repo) === sanitizeRepo(source.repo))?.[0];
if (mappedId) source.id = mappedId;
const pluginId = mappedId || source.id;
source.localVersion = (this.appPlugins.manifests[pluginId] as PluginManifest | undefined)?.version || source.localVersion || "";
}
} catch (e) {
source.error = (e as Error)?.message || String(e);
source.lastChecked = Date.now();
if (this.settings.DEBUG) console.error("[BPM] beta source check failed", source.repo, e);
}
}
private async startupCheckBetaSources() {
if (!this.settings.SOURCE_STARTUP_CHECK_UPDATES) return;
const sources = (this.settings.BETA_SOURCES || []).filter(s => s.enabled);
if (sources.length === 0) return;
for (const source of sources) {
await this.checkBetaSource(source);
}
await this.saveSettings();
const count = sources.filter((source) => this.sourceHasUpdate(source)).length;
if (count > 0) {
new Notice(this.translator.t("通知_来源可更新数量", { count }), 5000);
}
}
private async startupMaintainBetaSources() {
await this.startupCheckBetaSources();
await this.startupUpdateBetaSources();
}
private async startupUpdateBetaSources() {
if (!this.settings.SOURCE_AUTO_UPDATE) return;
const sources = (this.settings.BETA_SOURCES || []).filter(s => s.enabled && s.autoUpdate);
if (sources.length === 0) return;
for (const source of sources) {
try {
const checkedRecently = source.lastChecked && Date.now() - source.lastChecked < 60_000;
let targetVersion = checkedRecently ? source.latestVersion || "" : "";
if (!targetVersion) {
const versions = await fetchReleaseVersions(this, source.repo);
targetVersion = this.pickBetaSourceVersion(source, versions);
source.latestVersion = targetVersion || "";
source.lastChecked = Date.now();
source.error = "";
}
if (!targetVersion) continue;
if (source.type === "plugin") {
const mappedId = Object.entries(this.settings.REPO_MAP || {})
.find(([, repo]) => sanitizeRepo(repo) === sanitizeRepo(source.repo))?.[0];
if (mappedId) source.id = mappedId;
const pluginId = mappedId || source.id;
const localVersion = (this.appPlugins.manifests[pluginId] as PluginManifest | undefined)?.version || source.localVersion || "";
source.localVersion = localVersion;
const needsUpdate = source.mode === "frozen"
? localVersion !== targetVersion
: this.compareVersions(targetVersion, localVersion || "0.0.0") > 0;
if (needsUpdate) {
const ok = await installPluginFromGithub(this, source.repo, targetVersion, true);
if (ok) {
const nextMappedId = Object.entries(this.settings.REPO_MAP || {})
.find(([, repo]) => sanitizeRepo(repo) === sanitizeRepo(source.repo))?.[0];
if (nextMappedId) source.id = nextMappedId;
const manifest = this.appPlugins.manifests[source.id] as PluginManifest | undefined;
source.localVersion = manifest?.version || targetVersion;
}
}
} else {
const needsUpdate = source.mode === "frozen"
? source.localVersion !== targetVersion
: !source.localVersion || this.compareVersions(targetVersion, source.localVersion) > 0;
if (needsUpdate) {
const ok = await installThemeFromGithub(this, source.repo, targetVersion);
if (ok) source.localVersion = targetVersion;
}
}
} catch (e) {
source.error = (e as Error)?.message || String(e);
source.lastChecked = Date.now();
if (this.settings.DEBUG) console.error("[BPM] beta source auto update failed", source.repo, e);
}
}
await this.saveSettings();
}
public ensureBpmTagAndRecords() {
ensureBpmTagExists(this);
// 确保 BPM 安装的插件拥有标签
this.settings.BPM_INSTALLED.forEach((id) => {
const mp = this.settings.Plugins.find(p => p.id === id);
if (mp && !mp.tags.includes(BPM_TAG_ID)) mp.tags.push(BPM_TAG_ID);
});
}
private normalizeBuiltinTagNames(): boolean {
let changed = false;
const normalize = (id: string, name: string, legacyNames: string[], color: string) => {
const tag = this.settings.TAGS.find((item) => item.id === id);
if (!tag) return;
if (!tag.name || legacyNames.includes(tag.name)) {
tag.name = name;
changed = true;
}
if (!tag.color) {
tag.color = color;
changed = true;
}
};
normalize(BPM_TAG_ID, this.translator.t("标签_BPM安装_名称") || "BPM 安装", [
"bpm install",
"bpm安装",
"BPM Install",
"BPM Installed",
], "#409EFF");
normalize(BPM_IGNORE_TAG, this.translator.t("标签_BPM忽略_名称") || "BPM 忽略", [
"BPM Ignore",
"BPM Ignored",
], "#6c757d");
return changed;
}
// 确保 BPM 自身也存在于插件记录中(用于面板显示)
}
}
}
private sourceHasUpdate(source: BetaSource): boolean {
const localVersion = source.localVersion || "";
if (!source.latestVersion || !localVersion) return false;
return this.compareVersions(source.latestVersion, localVersion) > 0;
}
private pickBetaSourceVersion(source: BetaSource, versions: ReleaseVersion[]): string {
if (source.mode === "frozen") return source.frozenVersion || source.latestVersion || versions[0]?.version || "";
return versions.find(v => !v.prerelease)?.version || versions[0]?.version || "";
}
private async checkBetaSource(source: BetaSource): Promise<void> {
try {
const versions = await fetchReleaseVersions(this, source.repo);
const latestVersion = this.pickBetaSourceVersion(source, versions);
source.latestVersion = latestVersion || "";
source.lastChecked = Date.now();
source.error = "";
if (source.type === "plugin") {
const mappedId = Object.entries(this.settings.REPO_MAP || {})
.find(([, repo]) => sanitizeRepo(repo) === sanitizeRepo(source.repo))?.[0];
if (mappedId) source.id = mappedId;
const pluginId = mappedId || source.id;
source.localVersion = (this.appPlugins.manifests[pluginId] as PluginManifest | undefined)?.version || source.localVersion || "";
}
} catch (e) {
source.error = (e as Error)?.message || String(e);
source.lastChecked = Date.now();
if (this.settings.DEBUG) console.error("[BPM] beta source check failed", source.repo, e);
}
}
private async startupCheckBetaSources() {
if (!this.settings.SOURCE_STARTUP_CHECK_UPDATES) return;
const sources = (this.settings.BETA_SOURCES || []).filter(s => s.enabled);
if (sources.length === 0) return;
for (const source of sources) {
await this.checkBetaSource(source);
}
await this.saveSettings();
const count = sources.filter((source) => this.sourceHasUpdate(source)).length;
if (count > 0) {
new Notice(this.translator.t("通知_来源可更新数量", { count }), 5000);
}
}
private async startupMaintainBetaSources() {
await this.startupCheckBetaSources();
await this.startupUpdateBetaSources();
}
private async startupUpdateBetaSources() {
if (!this.settings.SOURCE_AUTO_UPDATE) return;
const sources = (this.settings.BETA_SOURCES || []).filter(s => s.enabled && s.autoUpdate);
if (sources.length === 0) return;
for (const source of sources) {
try {
const checkedRecently = source.lastChecked && Date.now() - source.lastChecked < 60_000;
let targetVersion = checkedRecently ? source.latestVersion || "" : "";
if (!targetVersion) {
const versions = await fetchReleaseVersions(this, source.repo);
targetVersion = this.pickBetaSourceVersion(source, versions);
source.latestVersion = targetVersion || "";
source.lastChecked = Date.now();
source.error = "";
}
if (!targetVersion) continue;
if (source.type === "plugin") {
const mappedId = Object.entries(this.settings.REPO_MAP || {})
.find(([, repo]) => sanitizeRepo(repo) === sanitizeRepo(source.repo))?.[0];
if (mappedId) source.id = mappedId;
const pluginId = mappedId || source.id;
const localVersion = (this.appPlugins.manifests[pluginId] as PluginManifest | undefined)?.version || source.localVersion || "";
source.localVersion = localVersion;
const needsUpdate = source.mode === "frozen"
? localVersion !== targetVersion
: this.compareVersions(targetVersion, localVersion || "0.0.0") > 0;
if (needsUpdate) {
const ok = await installPluginFromGithub(this, source.repo, targetVersion, true);
if (ok) {
const nextMappedId = Object.entries(this.settings.REPO_MAP || {})
.find(([, repo]) => sanitizeRepo(repo) === sanitizeRepo(source.repo))?.[0];
if (nextMappedId) source.id = nextMappedId;
const manifest = this.appPlugins.manifests[source.id] as PluginManifest | undefined;
source.localVersion = manifest?.version || targetVersion;
}
}
} else {
const needsUpdate = source.mode === "frozen"
? source.localVersion !== targetVersion
: !source.localVersion || this.compareVersions(targetVersion, source.localVersion) > 0;
if (needsUpdate) {
const ok = await installThemeFromGithub(this, source.repo, targetVersion);
if (ok) source.localVersion = targetVersion;
}
}
} catch (e) {
source.error = (e as Error)?.message || String(e);
source.lastChecked = Date.now();
if (this.settings.DEBUG) console.error("[BPM] beta source auto update failed", source.repo, e);
}
}
await this.saveSettings();
}
public ensureBpmTagAndRecords() {
ensureBpmTagExists(this);
// 确保 BPM 安装的插件拥有标签
this.settings.BPM_INSTALLED.forEach((id) => {
const mp = this.settings.Plugins.find(p => p.id === id);
if (mp && !mp.tags.includes(BPM_TAG_ID)) mp.tags.push(BPM_TAG_ID);
});
}
private normalizeBuiltinTagNames(): boolean {
let changed = false;
const normalize = (id: string, name: string, legacyNames: string[], color: string) => {
const tag = this.settings.TAGS.find((item) => item.id === id);
if (!tag) return;
if (!tag.name || legacyNames.includes(tag.name)) {
tag.name = name;
changed = true;
}
if (!tag.color) {
tag.color = color;
changed = true;
}
};
normalize(BPM_TAG_ID, this.translator.t("标签_BPM安装_名称") || "BPM 安装", [
"bpm install",
"bpm安装",
"BPM Install",
"BPM Installed",
], "#409EFF");
normalize(BPM_IGNORE_TAG, this.translator.t("标签_BPM忽略_名称") || "BPM 忽略", [
"BPM Ignore",
"BPM Ignored",
], "#6c757d");
return changed;
}
// 确保 BPM 自身也存在于插件记录中(用于面板显示)
public ensureSelfPluginRecord() {
const id = this.manifest.id;
const existing = this.settings.Plugins.find(p => p.id === id);
@ -488,10 +488,10 @@ export default class Manager extends Plugin {
enabled: true,
delay: "",
note: "",
});
this.saveSettings();
return;
}
});
this.saveSettings();
return;
}
existing.name = existing.name || this.manifest.name;
existing.desc = existing.desc || this.manifest.description;
existing.enabled = true;
@ -533,21 +533,21 @@ export default class Manager extends Plugin {
}
// 开启延时 调用
private isBpmIgnoredPlugin(pluginId: string): boolean {
return Boolean(this.settings.Plugins.find(plugin => plugin.id === pluginId)?.tags.includes(BPM_IGNORE_TAG));
}
private getDelayManagedPluginManifests(): PluginManifest[] {
return Object.values(this.appPlugins.manifests)
.filter((pm: PluginManifest) => pm.id !== this.manifest.id && !this.isBpmIgnoredPlugin(pm.id)) as PluginManifest[];
}
public enableDelay() {
private isBpmIgnoredPlugin(pluginId: string): boolean {
return Boolean(this.settings.Plugins.find(plugin => plugin.id === pluginId)?.tags.includes(BPM_IGNORE_TAG));
}
private getDelayManagedPluginManifests(): PluginManifest[] {
return Object.values(this.appPlugins.manifests)
.filter((pm: PluginManifest) => pm.id !== this.manifest.id && !this.isBpmIgnoredPlugin(pm.id)) as PluginManifest[];
}
public enableDelay() {
const plugins = Object.values(this.appPlugins.manifests).filter((pm: PluginManifest) => pm.id !== this.manifest.id) as PluginManifest[];
// 同步插件
this.synchronizePlugins(plugins);
// 开始延时启动插件
this.getDelayManagedPluginManifests().forEach((plugin: PluginManifest) => this.startPluginWithDelay(plugin.id));
this.getDelayManagedPluginManifests().forEach((plugin: PluginManifest) => this.startPluginWithDelay(plugin.id));
}
// 为所有插件启动延迟
@ -557,7 +557,7 @@ export default class Manager extends Plugin {
// 同步插件
this.synchronizePlugins(plugins);
this.getDelayManagedPluginManifests().forEach(async (plugin: PluginManifest) => {
this.getDelayManagedPluginManifests().forEach(async (plugin: PluginManifest) => {
// 插件状态
const isEnabled = this.appPlugins.enabledPlugins.has(plugin.id);
if (isEnabled) {
@ -582,7 +582,7 @@ export default class Manager extends Plugin {
// 为所有插件关闭延迟
public disableDelaysForAllPlugins() {
const plugins = this.getDelayManagedPluginManifests();
const plugins = this.getDelayManagedPluginManifests();
plugins.forEach(async (pm: PluginManifest) => {
const plugin = this.settings.Plugins.find(p => p.id === pm.id)
if (plugin) {
@ -595,10 +595,10 @@ export default class Manager extends Plugin {
}
// 延时启动指定插件
private startPluginWithDelay(id: string) {
if (id === this.manifest.id) return;
if (this.isBpmIgnoredPlugin(id)) return;
const plugin = this.settings.Plugins.find(p => p.id === id);
private startPluginWithDelay(id: string) {
if (id === this.manifest.id) return;
if (this.isBpmIgnoredPlugin(id)) return;
const plugin = this.settings.Plugins.find(p => p.id === id);
if (plugin && plugin.enabled) {
const delay = this.settings.DELAYS.find(item => item.id === plugin.delay);
const time = delay ? delay.time : 0;
@ -608,17 +608,18 @@ export default class Manager extends Plugin {
// 同步插件到配置文件
public synchronizePlugins(p1: PluginManifest[]) {
const p2 = this.settings.Plugins;
p2.forEach(p2Item => {
if (p2Item.id === this.manifest.id) return;
if (!p1.some(p1Item => p1Item.id === p2Item.id)) {
this.settings.Plugins = this.settings.Plugins.filter(pm => pm.id !== p2Item.id);
}
const manifestIds = new Set(p1.map((plugin) => plugin.id));
const bpmInstalledIds = new Set(this.settings.BPM_INSTALLED || []);
const nextPlugins = this.settings.Plugins.filter((plugin) => {
return plugin.id === this.manifest.id || manifestIds.has(plugin.id);
});
const pluginSettingsById = new Map(nextPlugins.map((plugin) => [plugin.id, plugin]));
p1.forEach(p1Item => {
if (!p2.some(p2Item => p2Item.id === p1Item.id)) {
let mp = pluginSettingsById.get(p1Item.id);
if (!mp) {
const isEnabled = this.appPlugins.enabledPlugins.has(p1Item.id);
this.settings.Plugins.push({
mp = {
'id': p1Item.id,
'name': p1Item.name,
'desc': p1Item.description,
@ -627,18 +628,20 @@ export default class Manager extends Plugin {
'enabled': isEnabled,
'delay': '',
'note': ''
});
};
nextPlugins.push(mp);
pluginSettingsById.set(p1Item.id, mp);
}
const mp = this.settings.Plugins.find(pm => pm.id === p1Item.id);
if (mp && this.settings.BPM_INSTALLED.includes(p1Item.id) && !mp.tags.includes(BPM_TAG_ID)) {
if (bpmInstalledIds.has(p1Item.id) && !mp.tags.includes(BPM_TAG_ID)) {
mp.tags.push(BPM_TAG_ID);
}
});
this.settings.Plugins = nextPlugins;
// BPM 自身保持启用且不允许延迟
this.ensureSelfPluginRecord();
// 保存设置
this.saveSettings();
}
// 保存设置
this.saveSettings();
}
// 工具函数
public createTag(text: string, color: string, type: string) {
@ -736,52 +739,52 @@ export default class Manager extends Plugin {
const statusMap: Record<string, UpdateStatus> = {};
if (this.settings.DEBUG) console.log("[BPM] checkUpdates start, total manifests:", manifests.length);
for (const pm of manifests) {
if (opts?.isCancelled?.()) break;
const localVersion = pm.version || "0.0.0";
const st: UpdateStatus = { source: 'unknown', localVersion, checkedAt: Date.now() };
try {
// 1) 官方来源
const official = officialMap[pm.id];
if (official) {
st.source = 'official';
st.remoteVersion = official;
st.repo = await this.repoResolver.resolveRepo(pm.id);
if (st.repo) {
st.versions = await this.fetchGithubVersions(st.repo);
}
st.hasUpdate = this.compareVersions(official, localVersion) > 0;
if (this.settings.DEBUG) console.log("[BPM] update official match", pm.id, localVersion, "->", st.remoteVersion);
} else {
// 2) GitHubBPM 安装或有仓库映射 / 用户填写
let repo: string | null = this.settings.REPO_MAP[pm.id] || null;
if (!repo) {
try {
repo = await this.repoResolver.resolveRepo(pm.id);
} catch {
// ignore
}
}
if (repo) {
st.source = 'github';
st.repo = repo;
st.versions = await this.fetchGithubVersions(repo);
// 选择一个默认的远端版本:优先最新稳定,否则第一个
const pick = st.versions?.find(v => !v.prerelease) ?? st.versions?.[0] ?? null;
st.remoteVersion = pick?.version ?? await this.fetchGithubManifestVersion(repo);
st.hasUpdate = st.remoteVersion ? this.compareVersions(st.remoteVersion, localVersion) > 0 : false;
if (!st.remoteVersion) st.message = this.translator.t("更新_未获取到远端版本");
if (this.settings.DEBUG) console.log("[BPM] update github match", pm.id, repo, localVersion, "->", st.remoteVersion);
} else {
st.source = 'unknown';
st.message = this.translator.t("更新_无来源无法检测");
if (this.settings.DEBUG) console.log("[BPM] update unknown source", pm.id);
}
}
} catch (e) {
st.error = (e as Error)?.message || String(e);
console.error("[BPM] checkUpdates error", pm.id, e);
}
for (const pm of manifests) {
if (opts?.isCancelled?.()) break;
const localVersion = pm.version || "0.0.0";
const st: UpdateStatus = { source: 'unknown', localVersion, checkedAt: Date.now() };
try {
// 1) 官方来源
const official = officialMap[pm.id];
if (official) {
st.source = 'official';
st.remoteVersion = official;
st.repo = await this.repoResolver.resolveRepo(pm.id);
if (st.repo) {
st.versions = await this.fetchGithubVersions(st.repo);
}
st.hasUpdate = this.compareVersions(official, localVersion) > 0;
if (this.settings.DEBUG) console.log("[BPM] update official match", pm.id, localVersion, "->", st.remoteVersion);
} else {
// 2) GitHubBPM 安装或有仓库映射 / 用户填写
let repo: string | null = this.settings.REPO_MAP[pm.id] || null;
if (!repo) {
try {
repo = await this.repoResolver.resolveRepo(pm.id);
} catch {
// ignore
}
}
if (repo) {
st.source = 'github';
st.repo = repo;
st.versions = await this.fetchGithubVersions(repo);
// 选择一个默认的远端版本:优先最新稳定,否则第一个
const pick = st.versions?.find(v => !v.prerelease) ?? st.versions?.[0] ?? null;
st.remoteVersion = pick?.version ?? await this.fetchGithubManifestVersion(repo);
st.hasUpdate = st.remoteVersion ? this.compareVersions(st.remoteVersion, localVersion) > 0 : false;
if (!st.remoteVersion) st.message = this.translator.t("更新_未获取到远端版本");
if (this.settings.DEBUG) console.log("[BPM] update github match", pm.id, repo, localVersion, "->", st.remoteVersion);
} else {
st.source = 'unknown';
st.message = this.translator.t("更新_无来源无法检测");
if (this.settings.DEBUG) console.log("[BPM] update unknown source", pm.id);
}
}
} catch (e) {
st.error = (e as Error)?.message || String(e);
console.error("[BPM] checkUpdates error", pm.id, e);
}
statusMap[pm.id] = st;
opts?.onProgress?.(pm.id);
}
@ -823,11 +826,11 @@ export default class Manager extends Plugin {
const pick = st.versions?.find(v => !v.prerelease) ?? st.versions?.[0] ?? null;
st.remoteVersion = pick?.version ?? await this.fetchGithubManifestVersion(repo);
st.hasUpdate = st.remoteVersion ? this.compareVersions(st.remoteVersion, localVersion) > 0 : false;
if (!st.remoteVersion) st.message = this.translator.t("更新_未获取到远端版本");
if (!st.remoteVersion) st.message = this.translator.t("更新_未获取到远端版本");
if (this.settings.DEBUG) console.log("[BPM] single update github", pm.id, repo, localVersion, "->", st.remoteVersion);
} else {
st.source = "unknown";
st.message = this.translator.t("更新_无来源无法检测");
st.message = this.translator.t("更新_无来源无法检测");
if (this.settings.DEBUG) console.log("[BPM] single update unknown source", pm.id);
}
} catch (e) {
@ -1003,16 +1006,16 @@ export default class Manager extends Plugin {
baseSelector = `.side-dock-actions div.clickable-icon.side-dock-ribbon-action`;
}
const cssRules = items.map(item => {
if (!item.name) return "";
const order = Number.isFinite(item.order) ? item.order : 9999;
if (!item.visible) {
const selector = this.generateMultiLineAriaLabelSelector(baseSelector, item.name);
return `${selector} { order: ${order}; display: none !important; }`;
}
const selector = this.generateMultiLineAriaLabelSelector(baseSelector, item.name);
return `${selector} { order: ${order}; }`;
}).filter(rule => rule !== "").join("\n");
const cssRules = items.map(item => {
if (!item.name) return "";
const order = Number.isFinite(item.order) ? item.order : 9999;
if (!item.visible) {
const selector = this.generateMultiLineAriaLabelSelector(baseSelector, item.name);
return `${selector} { order: ${order}; display: none !important; }`;
}
const selector = this.generateMultiLineAriaLabelSelector(baseSelector, item.name);
return `${selector} { order: ${order}; }`;
}).filter(rule => rule !== "").join("\n");
styleEl.innerHTML = cssRules;
}
@ -1154,10 +1157,10 @@ export default class Manager extends Plugin {
// 功能编排只应用运行时样式,不写入 Obsidian workspace 配置或 Ribbon 内存状态。
applyRibbonConfigToMemory(orderedIds: string[], hiddenStatus: Record<string, boolean>) {
this.updateRibbonStyles();
}
// 功能编排只应用运行时样式,不写入 Obsidian workspace 配置或 Ribbon 内存状态。
applyRibbonConfigToMemory(orderedIds: string[], hiddenStatus: Record<string, boolean>) {
this.updateRibbonStyles();
}
public async syncRibbonConfig(orderedIds: string[], hiddenStatus: Record<string, boolean>) {
// 更新本地设置以匹配原生配置

View file

@ -1340,10 +1340,18 @@ export class ManagerModal extends Modal {
Object.values(manifestMap).forEach((mf: PluginManifest) => {
uniqMap.set(mf.id, mf);
});
const uniquePlugins = Array.from(uniqMap.values()).sort((a, b) => a.name.localeCompare(b.name));
const manifestById = new Map(uniquePlugins.map((plugin) => [plugin.id, plugin]));
const layoutItems = this.getPluginLayout(uniquePlugins);
const showSeparators = this.shouldRenderPluginLayoutSeparators();
const uniquePlugins = Array.from(uniqMap.values()).sort((a, b) => a.name.localeCompare(b.name));
const manifestById = new Map(uniquePlugins.map((plugin) => [plugin.id, plugin]));
const layoutItems = this.getPluginLayout(uniquePlugins);
const pluginSettingsById = new Map(this.manager.settings.Plugins.map((plugin) => [plugin.id, plugin]));
const groupSettingsById = new Map(this.settings.GROUPS.map((group) => [group.id, group]));
const tagSettingsById = new Map(this.settings.TAGS.map((tag) => [tag.id, tag]));
const delaySettingsById = new Map(this.settings.DELAYS.map((delay) => [delay.id, delay]));
const hiddenPluginIds = new Set(this.settings.HIDES || []);
const lowerSearchText = this.searchText.toLowerCase();
const getBasePath = (this.app.vault.adapter as any)?.getBasePath?.() as string | undefined;
const basePath = getBasePath ? normalizePath(getBasePath) : "";
const showSeparators = this.shouldRenderPluginLayoutSeparators();
let pendingSeparator: string | null = null;
if (this.settings.DEBUG) console.log("[BPM] render showData uniquePlugins:", uniquePlugins.map(p => p.id).join(","));
@ -1360,10 +1368,8 @@ export class ManagerModal extends Modal {
if (!plugin) continue;
if (renderedIds.has(plugin.id)) continue;
renderedIds.add(plugin.id);
const ManagerPlugin = this.manager.settings.Plugins.find((mp) => mp.id === plugin.id);
const ManagerPlugin = pluginSettingsById.get(plugin.id);
// 计算插件目录的绝对路径:基于 vault 根路径 + configDir + plugin.dir
const getBasePath = (this.app.vault.adapter as any)?.getBasePath?.() as string | undefined;
const basePath = getBasePath ? normalizePath(getBasePath) : "";
const cfgDir = this.app.vault.configDir; // 默认 .obsidian
const rawDir = plugin.dir || `plugins/${plugin.id}`;
const isAbsolute = new RegExp("^(?:[a-zA-Z]:[\\\\/]|[\\\\/])").test(rawDir);
@ -1428,9 +1434,9 @@ export class ManagerModal extends Modal {
if (this.delay !== "" && ManagerPlugin.delay !== this.delay) continue;
}
// [过滤] 搜索
if (this.searchText !== "" && ManagerPlugin.name.toLowerCase().indexOf(this.searchText.toLowerCase()) == -1 && ManagerPlugin.desc.toLowerCase().indexOf(this.searchText.toLowerCase()) == -1 && plugin.author.toLowerCase().indexOf(this.searchText.toLowerCase()) == -1) continue;
if (lowerSearchText !== "" && ManagerPlugin.name.toLowerCase().indexOf(lowerSearchText) == -1 && ManagerPlugin.desc.toLowerCase().indexOf(lowerSearchText) == -1 && (plugin.author || "").toLowerCase().indexOf(lowerSearchText) == -1) continue;
// [过滤] 隐藏
if (!isSelf && this.settings.HIDES.includes(plugin.id)) continue;
if (!isSelf && hiddenPluginIds.has(plugin.id)) continue;
if (pendingSeparator && this.displayPlugins.length > 0) {
this.renderPluginLayoutSeparator(pendingSeparator);
@ -1702,7 +1708,7 @@ export class ManagerModal extends Modal {
if (ManagerPlugin.group !== "") {
const group = createSpan({ cls: "manager-item__name-group", });
itemEl.nameEl.appendChild(group);
const item = this.settings.GROUPS.find((t) => t.id === ManagerPlugin.group);
const item = groupSettingsById.get(ManagerPlugin.group);
if (item) {
const tag = this.manager.createTag(item.name, item.color, this.settings.GROUP_STYLE);
if (this.editorMode) tag.onclick = () => { new GroupModal(this.app, this.manager, this, ManagerPlugin).open(); };
@ -1794,7 +1800,7 @@ export class ManagerModal extends Modal {
// [默认] 延迟
if (this.settings.DELAY && !this.editorMode && !isSelf && ManagerPlugin.delay !== "") {
const d = this.settings.DELAYS.find((item) => item.id === ManagerPlugin.delay);
const d = delaySettingsById.get(ManagerPlugin.delay);
if (d) {
const delay = createSpan({ text: `${d.time}s`, cls: ["manager-item__name-delay"], });
itemEl.nameEl.appendChild(delay);
@ -1824,7 +1830,7 @@ export class ManagerModal extends Modal {
itemEl.descEl.appendChild(tags);
let visibleTagCount = 0;
ManagerPlugin.tags.map((id: string) => {
const item = this.settings.TAGS.find((item) => item.id === id);
const item = tagSettingsById.get(id);
if (item) {
if ((item.id === BPM_TAG_ID || item.id === BPM_IGNORE_TAG) && this.settings.HIDE_BPM_TAG) {
// skip render
@ -2086,20 +2092,17 @@ export class ManagerModal extends Modal {
});
}
const openRepoButton = this.createConfiguredItemAction(itemEl.controlEl, "openRepo");
if (openRepoButton) {
openRepoButton.setIcon("github");
openRepoButton.setTooltip(this.manager.translator.t("管理器_仓库检测中_提示"));
openRepoButton.setDisabled(true);
const repo = await this.manager.repoResolver.resolveRepo(plugin.id);
if (!this.isRenderCurrent(renderGeneration, page)) return;
const repoState = this.resolvePluginRepoAction(plugin.id, repo);
openRepoButton.setTooltip(repoState.tooltip);
openRepoButton.setDisabled(repoState.disabled);
openRepoButton.onClick(async () => {
await this.openPluginRepo(plugin.id, repoState.repo);
});
}
const openRepoButton = this.createConfiguredItemAction(itemEl.controlEl, "openRepo");
if (openRepoButton) {
openRepoButton.setIcon("github");
const knownRepo = this.manager.settings.REPO_MAP?.[plugin.id] || null;
const repoState = this.resolvePluginRepoAction(plugin.id, knownRepo);
openRepoButton.setTooltip(knownRepo ? repoState.tooltip : this.manager.translator.t("管理器_打开仓库_标题"));
openRepoButton.setDisabled(false);
openRepoButton.onClick(async () => {
await this.openPluginRepo(plugin.id, knownRepo);
});
}
// [按钮] 打开设置
openPluginSetting = this.createConfiguredItemAction(itemEl.controlEl, "openSettings");
@ -2266,10 +2269,10 @@ export class ManagerModal extends Modal {
const cards = Array.from(this.contentEl.querySelectorAll(".manager-item"));
console.log("[BPM] render showData after loop, cards:", cards.length, "ids:", cards.map(el => el.getAttribute("data-plugin-id")).filter(Boolean).join(","));
}
// 计算页尾
this.updateStats();
}
}
}
// 计算页尾
this.updateStats();
}
private getCounts() {
let totalCount = 0;