diff --git a/LICENSE b/LICENSE index b2a3bb2..e1d0747 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/package-lock.json b/package-lock.json index 45efe2e..1203a79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/main.ts b/src/main.ts index ff8e4e5..a094e33 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 = {}; + public updateStatus: Record = {}; 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 = {}; - 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 = {}; + 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 = {}; - 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 = {}; + 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> { 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 { - 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 { + 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 = {}; 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) GitHub:BPM 安装或有仓库映射 / 用户填写 - 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) GitHub:BPM 安装或有仓库映射 / 用户填写 + 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) { - this.updateRibbonStyles(); - } + // 功能编排只应用运行时样式,不写入 Obsidian workspace 配置或 Ribbon 内存状态。 + applyRibbonConfigToMemory(orderedIds: string[], hiddenStatus: Record) { + this.updateRibbonStyles(); + } public async syncRibbonConfig(orderedIds: string[], hiddenStatus: Record) { // 更新本地设置以匹配原生配置 diff --git a/src/modal/manager-modal.ts b/src/modal/manager-modal.ts index 228efc9..aafb43f 100644 --- a/src/modal/manager-modal.ts +++ b/src/modal/manager-modal.ts @@ -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;