Obsidian插件测评-BetterPluginManager-插件分组、标签管理,插件备注,延迟启动

插件简介

Better Plugin Manager (BPM)是一款 Obsidian 插件管理工具,功能包括:插件分组、标签管理、备注和延迟启动,提升插件管理效率和用户体验。

使用示例

插件分组&延迟启动

标签管理

插件备注

项目情况

是否上架官方市场::white_check_mark:

项目地址链接

使用教程

在OB插件市场中搜索安装,然后点击OB左侧的
image
图标即可启动管理页面。

在管理页面里可以完成以下工作:

  • 设置分组
  • 分配标签
  • 自定义插件名称与描述
  • 设置插件的启动延迟时间
  • 筛选插件:支持按分组、标签、名称筛选

管理页面各部件使用说明请参照下图:

因为插件的操作逻辑很简单,没必要再把图中的说明复述一遍,不过我要强调几个重点:

  1. 首次使用此插件时,建议复制当前库,以便在副本中测试插件功能,确保无误后再在主力库中启用(或者说首次使用任何插件时,都推荐这样做)
  2. 使用本插件管理 Obsidian 插件后,请避免使用 Obsidian 自带的插件管理器开关插件,以免引发未知错误
  3. 一键开关功能仅针对当前视图中的插件生效。通过分组或标签筛选,可以批量开关特定插件(也就是说如果你没有开启任何筛选功能的话,一键开关插件就是把你的OB中的所有插件一键开关,目前0.0.4版本的插件描述有误,请以本文为准)。

如果需要调整分组、标签和延迟,则需要前往BPM的设置页面中添加。(可选)点击取色工具来自定义元素的颜色,然后输入id(推荐使用纯英文)和名称,点击加号即可添加。

闲谈

有一段时间我一直想优化我的Obsidian启动速度,一个常用的方案就是让一部分插件在Obsidian启动后再启动。于是我尝试了很多方案,一开始我使用templater脚本来实现插件延迟启动,但是这种方案依赖于代码,不方便随时修改,然后我使用了 PluginGroupsLazyPlugins,前者已经停更两年,并且分组操作窗口小而不直观,后者专注于插件的延迟启动,但在我使用的时候有奇怪的bug。

直到我朋友02开发了这款插件,几乎能满足我的所有需求。直观的插件分类管理,可视化的操作,多组别延迟启动。因此我想把它推荐给你们。

5 个赞

前排提醒,这个插件一启动,所有的插件便马上处于关闭状态,哎感觉这点不太友好,吓得我马上把这插件给删了。

我观察它是安装后会把所有开启的插件关闭重开一次。应该是它会清空Obsidian自己的插件管理清单,然后接管了OB的插件启动,后续所有插件都应该走BPM启动。

太强了,是我想象中的插件管理。

1 个赞

简单查看了插件代码,似乎和以前的脚本一样,只是简单使用 setTimeout。除了界面更加用户友好外,不知之后是否愿意在这方面有所提升,发挥插件的独特优势?

举一个具体例子。1.7.2 官方启用延启视图 Deferred View 后,所有在启动时需要获取视图的插件都需要同步跟进,其中不乏插件在跟进时并不会考虑延迟启动的情况(毕竟这不是 Obsidian 默认的情况)。譬如,常用来增强内链查看的 Strange New Worlds 插件,如果只是普通的延迟启动,会一直重复开启一个新的侧边栏。

20250109_172953

我把问题反馈给插件作者了。

不过我第一次接触插件延迟启动的时候就有这个疑惑——如果被延迟启动的插件自身就依赖在Obsidian启动时加载,延迟加载可能会对这类插件产生破坏。

虽然我反馈了,但是我感觉他应该也修不了(笑),不过Strange New Worlds这个插件还好吧,只是多开了一个侧边栏,属于有错误但是能自己克服的…

我是担心某些插件的核心功能会被破坏掉,看未来会不会有人反馈吧。

我研究了一下 Strange New Worlds 延迟启动会多开侧边栏的问题。 发现有些奇怪的现象:

很多插件是这样设置侧边栏打开逻辑的(以 Strange New Worlds 为例):

// 注册 View
// ==>*位置1*
this.registerView(VIEW_TYPE_SNW, (leaf) => new SideBarPaneView(leaf, this));
// ==>*位置2*

// 判断当前是否存在该类型 View,如果没有就新开一个
this.app.workspace.onLayoutReady(async () => {
// ==>*位置3*
	if (!this.app.workspace.getLeavesOfType(VIEW_TYPE_SNW)?.length) {
		await this.app.workspace.getRightLeaf(false)?.setViewState({ type: VIEW_TYPE_SNW, active: false });
	}
	buildLinksAndReferences();
});

app.workspace.getLeavesOfType(XXX) 来查看各个位置当前侧边栏打开该 View 的数量。我保证在打开插件之前,侧边栏已经有 View 存在(也就是显示“插件XXX已不再活动”这种,上次未关闭的,其实仍然是该类型的View)

如果插件是默认情况下跟随 obsidian 启动, view的数量分别是:在位置1时数量为1,位置2时为0,位置3时为1。

也就是说启动插件时,registerView 操作会把已经存在的所有该类型的View 变成 empty view。因此位置2的该类型View数量会被计算为0。

但如果插件是手动启动(或者延时启动)的,在 位置1 时显示 view 为1个,在 位置2为 0 个。 但是在位置3也还是0个。这导致的结果就是后面插件判断是否要新打开一个侧边栏的时候,插件会以为当前没有该类型 View 存在,因此总会新打开一个。
而在插件启动结束后,那些 empty view 又会恢复为原来类型的 View。 总的结果就是旧的还在,又新打开了一个。

这有可能和 onLayoutReady 这个函数的行为有关系。 在 layout-ready 这个事件之前注册,则 callback 会在 layout-ready 触发时执行。 而如果在 layout-ready 事件已经发生后注册,则 callback 会立即执行。

// app.js
t.prototype.onLayoutReady = function(e) {
    null === this.onLayoutReadyCallbacks ? e() : this.onLayoutReadyCallbacks.push({
        pluginId: this.app.plugins.loadingPluginId,
        callback: e
    })
}

因此在手动启动/延迟启动插件时,是在 layout-ready 之后,因此 callback 是立即执行的,这可能早于 那些 empty view 恢复成 自定义View 的时刻,因此导致 view 数量 计算错误。

我分别尝试了用 Promise.resolve(0).thensetTimeout 来包裹这个 onLayoutReady 调用,结果是前者自定义View数量仍然计算为0,而后者计算正常。

总结一下就是:

  • 插件默认跟随 obsidian 启动时,layout-ready 事件是一个同步点,可以保证变成 empty view 的那些 View 已经恢复回来。
  • 而手动启动/延迟启动时,onLayoutReady的callback是立即执行的,empty view 还没有恢复回来,这时计算 自定义View 打开的数量时,结果是不对的,可能导致插件误判,新打开一个 View。

感觉这个问题还是只能由插件自己来修复,可能延迟启动插件很难从外部来纠正。不同插件打开侧边栏的逻辑不同:

  • 有的是随插件启动默认打开一个,这种可能就会导致上面的问题,重复打开多个 view。
  • 有的是不自动打开,需要手动点按钮或者用命令打开。这种插件可能比较依赖obsidian保存和恢复工作区布局的行为,从而不用每次启动obsidian都要重新手动打开。

因为第二种插件的存在,延迟启动插件不能主动把插件的 view 关掉,否则用户总是得手动启动 view。而这样的话,第一种插件就一定会在每次启动时新增一个 view,导致重复。

2 个赞

要是能像 Plugin Manager 通过命令切换插件开关就好了, 有些插件只有在用的时候需要加载,比如清理附件这种

1 个赞

@the_tree 哇,谢谢你呀。其实我提这个问题主要还是想说说插件的局限性,因为如果只是用 setTimeout 的话,早有现成的脚本,代码量也小。包括通过命令启停插件,也是已有现成的简单脚本。所以既然写了插件,还是想看能不能有这方面的优化,能比脚本有更好的效果。

然后,关于 Strange New Worlds 这个个例。当年有人提 1.7.2 issue 的时候我在 issue 里面回复过一个修复,那个是可以阻止这个情况的。当然,首先我确实不知道我修的对不对,其次可能我写的和那个 issue 具体情况不一样,最后作者也没意识到我这么写是要干嘛。后来作者自己跟进了之后我也不好说为了配合延迟启动让他改代码,所以可能也就这样了。

最后附上我目前每次更新 Strange New Worlds 之后对此做的更改。要重新对参数是因为我直接修改 main.js,不是通过 TS 编译的。注意:我确实不知道这样改对不对,所以不能有什么保证。

这种我一般是设置一个分组,然后可以根据分组批量开关,操作其实比用命令更直观

确实,如果增加更多的命令就好了。

命令提供更多可能性。

经测试,个人感觉应该是Ob官方提供的接口的问题

// 这样就会出现那种情况
await this.appPlugins.disablePlugin(plugin.id);
// 这样就不会,但如果使用这个接口就没办法保证插件管理器始终第一个启动了
await this.appPlugins.disablePluginAndSave(plugin.id);

PS:我不太懂源码,就只是测试了下提供的接口

1

@zero_two 作者大大,你回复的这个是我说的那个问题吗?这两个接口的差别就是一个只是临时关闭插件,另一个是把关闭的插件的信息写到了 community-plugins.json 中。

我应该没有理解错吧,看昨天那个动图会出现这个问题,但这两个接口确实会出现两种情况

附上源码

const toggleSwitch = new ToggleComponent(itemEl.controlEl)
                    toggleSwitch.setTooltip(t('管理器_切换状态_描述'))
                    toggleSwitch.setValue(ManagerPlugin.enabled)
                    toggleSwitch.onChange(async () => {
                        if (toggleSwitch.getValue()) {
                            if (this.settings.FADE_OUT_DISABLED_PLUGINS) itemEl.settingEl.removeClass('inactive');  // [淡化插件]
                            ManagerPlugin.enabled = true;
                            this.manager.saveSettings();
                            // await this.appPlugins.enablePlugin(plugin.id);
                            await this.appPlugins.enablePluginAndSave(plugin.id);
                        } else {
                            if (this.settings.FADE_OUT_DISABLED_PLUGINS) itemEl.settingEl.addClass('inactive');  // [淡化插件]
                            ManagerPlugin.enabled = false;
                            this.manager.saveSettings();
                            // await this.appPlugins.disablePlugin(plugin.id);
                            await this.appPlugins.disablePluginAndSave(plugin.id);
                        }
                        this.reloadShowData();
                    })

不是的。通过设置手动关闭插件时,可以触发插件的报销机制,所以可以正确关掉侧边栏。而延迟启动时重复出现侧边栏的情况与此不同,可参 #7。事实上,需要延迟启动插件做特殊处理的插件,基本上都是不能靠这样简单开启和关闭来测试的。我也快速地将 #15 放上来的源码补到插件的 main.js 中进行测试确认了这点。

20250110_123221-ezgif.com-video-to-gif-converter

似乎只是解决开关会出现那个问题(不太清楚你们那里会不会,我这里是开关插件也会出现这个情况),延时启动依旧不行,好像没什么太好的解决办法。

disablePlugindisablePluginAndSave 的区别是后者会多做一些事:

  • 前者:关闭插件
  • 后者:关闭插件、保存配置(下次ob启动时就不打开这个插件了)、关闭所有该插件打开的 view

所以如果用后者的话,不停开关插件时,每次关闭时都关掉了所有 view,所以就不会有重复了。 而前者不会关闭 view,所以每次打开插件就会再多一个。

之前那个插件的问题主要是关闭 obisidian,再打开的时候,插件启动时会重复添加一个 view。因为 obsidian 关闭的时候,并不会去调用 disablePlugin(AndSave) 这两个函数,所以之前打开的 view 没有被关闭。 而再打开 obsidian 的时候,插件启动过程中会再打开一个新的 view,导致重复。

感觉如果要靠延时启动插件修复的话,得修改 onLayoutReady 这个函数的行为,加一个 setTimeout。 但是不确定这样会不会导致其他依赖这个函数的代码出问题。

最新版本已修复此问题(不保证百分百有效)

0.0.6 版是通过关闭所有失效的视图然后等待插件开启新的视图,这样的问题是如果移动了侧边栏的位置,那么将不能够记住这个位置,而是始终默认在右侧最右边打开新的栏,恐怕不能解决这个问题。

没事,如果一时没有靠谱路径,不妨先完善插件其他部分,等到有合理的路径了,再着手去写,这样也能节省作者大大的精力。