mirror of
https://github.com/eondrcode/obsidian-manager.git
synced 2026-07-22 11:40:24 +00:00
fix: 优化命令
This commit is contained in:
parent
1e20ae6519
commit
76875b7352
11 changed files with 901 additions and 155 deletions
|
|
@ -264,7 +264,7 @@ BPM settings are split into focused pages:
|
|||
|
||||
| Page | What you can configure |
|
||||
|------|------------------------|
|
||||
| **Basic** | Language, persistent filters, delayed startup, auto takeover, startup update checks, source update checks, source auto-update, BPM tag visibility, ribbon order, commands, debug mode, and GitHub token |
|
||||
| **Basic** | Language, persistent filters, delayed startup, auto takeover, startup update checks, source update checks, source auto-update, BPM tag visibility, ribbon order, command registration, debug mode, and GitHub token |
|
||||
| **Main Page Actions** | Choose which plugin actions appear directly on plugin cards and which stay in the right-click menu |
|
||||
| **Style** | Plugin list layout, item display style, group/tag styles, and disabled-plugin fading |
|
||||
| **Groups** | Create, rename, recolor, and delete plugin groups |
|
||||
|
|
@ -278,9 +278,14 @@ BPM settings are split into focused pages:
|
|||
| Command | Availability | Description |
|
||||
|---------|--------------|-------------|
|
||||
| **Open the plugin manager** | Always available | Opens the BPM main interface |
|
||||
| **Control a plugin** | Always available | Search a plugin, then enable, disable, single-start, restart, open settings, open folder, open repository, or copy ID |
|
||||
| **Save current plugin state as profile** | Always available | Saves the current enabled/disabled state as a reusable command profile |
|
||||
| **Restore previous plugin state** | Available after a BPM command changes plugin state | Restores the snapshot captured before the last command-driven state change |
|
||||
| **Troubleshoot plugin conflicts** | Always available | Starts the conflict diagnosis workflow |
|
||||
| **Enable/Disable selected plugin** | Optional setting | Registers one command per plugin for direct toggling |
|
||||
| **One-click Enable/Disable selected group** | Optional setting | Registers group-level commands for batch toggling |
|
||||
| **One-click Enable/Disable selected tag** | Optional setting | Registers tag-level commands for batch toggling |
|
||||
| **Apply selected profile** | Optional setting | Registers one command per saved plugin profile |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ BPM 设置按功能拆分为多个页面:
|
|||
|
||||
| 页面 | 可配置内容 |
|
||||
|------|------------|
|
||||
| **Basic** | 语言、筛选持久化、延迟启动、自动接管、启动检查更新、来源检查更新、来源自动更新、BPM 标签显示、Ribbon 编排、命令、调试模式和 GitHub Token |
|
||||
| **Basic** | 语言、筛选持久化、延迟启动、自动接管、启动检查更新、来源检查更新、来源自动更新、BPM 标签显示、Ribbon 编排、命令注册、调试模式和 GitHub Token |
|
||||
| **Main Page Actions** | 选择哪些插件操作直接显示在插件卡片上,哪些收纳到右键菜单 |
|
||||
| **Style** | 插件列表布局、项目显示样式、分组/标签样式和禁用插件淡化 |
|
||||
| **Groups** | 创建、重命名、重新着色和删除插件分组 |
|
||||
|
|
@ -276,9 +276,14 @@ BPM 设置按功能拆分为多个页面:
|
|||
| 命令 | 可用性 | 说明 |
|
||||
|------|--------|------|
|
||||
| **Open the plugin manager** | 始终可用 | 打开 BPM 主界面 |
|
||||
| **Control a plugin** | 始终可用 | 搜索插件后执行启用、禁用、单次启动、重启、打开设置、打开目录、打开仓库或复制 ID |
|
||||
| **Save current plugin state as profile** | 始终可用 | 将当前插件启用/禁用状态保存为可复用 Profile |
|
||||
| **Restore previous plugin state** | BPM 命令改变插件状态后可用 | 恢复最近一次命令操作前保存的状态快照 |
|
||||
| **Troubleshoot plugin conflicts** | 始终可用 | 启动冲突诊断流程 |
|
||||
| **Enable/Disable [Plugin Name]** | 可选设置 | 为每个插件注册独立启用/禁用命令 |
|
||||
| **One-click Enable/Disable [Group Name]** | 可选设置 | 为分组注册批量切换命令 |
|
||||
| **One-click Enable/Disable [Tag Name]** | 可选设置 | 为标签注册批量切换命令 |
|
||||
| **Apply [Profile Name]** | 可选设置 | 为已保存的插件 Profile 注册一键应用命令 |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "better-plugins-manager",
|
||||
"version": "1.0.12",
|
||||
"version": "1.0.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "better-plugins-manager",
|
||||
"version": "1.0.12",
|
||||
"version": "1.0.13",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json package-lock.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "zero",
|
||||
|
|
|
|||
850
src/command.ts
850
src/command.ts
|
|
@ -1,146 +1,704 @@
|
|||
import { App, PluginManifest } from "obsidian";
|
||||
import Manager from "./main";
|
||||
import { ManagerModal } from "./modal/manager-modal";
|
||||
import { TroubleshootModal } from "./troubleshoot/troubleshoot-modal";
|
||||
|
||||
const Commands = (app: App, manager: Manager) => {
|
||||
manager.addCommand({
|
||||
id: 'manager-view',
|
||||
name: manager.translator.t('命令_管理面板_描述'),
|
||||
callback: () => { new ManagerModal(app, manager).open() }
|
||||
});
|
||||
|
||||
// 排查冲突命令
|
||||
manager.addCommand({
|
||||
id: 'troubleshoot-conflicts',
|
||||
name: manager.translator.t('排查_按钮_描述'),
|
||||
callback: () => { new TroubleshootModal(app, manager).open() }
|
||||
});
|
||||
|
||||
if (manager.settings.DELAY) {
|
||||
// 单行命令
|
||||
if (manager.settings.COMMAND_ITEM) {
|
||||
const plugins: PluginManifest[] = Object.values(manager.appPlugins.manifests).filter((pm: PluginManifest) => pm.id !== manager.manifest.id);
|
||||
plugins.forEach(plugin => {
|
||||
const mp = manager.settings.Plugins.find(mp => mp.id === plugin.id);
|
||||
if (mp) {
|
||||
manager.addCommand({
|
||||
id: `manager-${mp.id}`,
|
||||
name: `${mp.enabled ? manager.translator.t('通用_关闭_文本') : manager.translator.t('通用_开启_文本')} ${mp.name} `,
|
||||
callback: async () => {
|
||||
if (mp.enabled) {
|
||||
mp.enabled = false;
|
||||
await manager.savePluginAndExport(mp.id);
|
||||
await manager.appPlugins.disablePlugin(plugin.id);
|
||||
Commands(app, manager);
|
||||
} else {
|
||||
mp.enabled = true;
|
||||
await manager.savePluginAndExport(mp.id);
|
||||
await manager.appPlugins.enablePlugin(plugin.id);
|
||||
Commands(app, manager);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// 分组命令
|
||||
if (manager.settings.COMMAND_GROUP) {
|
||||
manager.settings.GROUPS.forEach((group) => {
|
||||
manager.addCommand({
|
||||
id: `manager-${group.id}-enabled`,
|
||||
name: `${manager.translator.t('命令行_一键启用_文本')} ${group.name}`,
|
||||
callback: async () => {
|
||||
const filteredPlugins = manager.settings.Plugins.filter(plugin => plugin.group === group.id);
|
||||
for (const plugin of filteredPlugins) {
|
||||
if (plugin && !plugin.enabled) {
|
||||
await manager.appPlugins.enablePlugin(plugin.id);
|
||||
plugin.enabled = true;
|
||||
await manager.savePluginAndExport(plugin.id);
|
||||
}
|
||||
}
|
||||
Commands(app, manager);
|
||||
}
|
||||
});
|
||||
manager.addCommand({
|
||||
id: `manager-${group.id}-disable`,
|
||||
name: `${manager.translator.t('命令行_一键禁用_文本')} ${group.name}`,
|
||||
callback: async () => {
|
||||
const filteredPlugins = manager.settings.Plugins.filter(plugin => plugin.group === group.id);
|
||||
for (const plugin of filteredPlugins) {
|
||||
if (plugin && plugin.enabled) {
|
||||
await manager.appPlugins.disablePlugin(plugin.id);
|
||||
plugin.enabled = false;
|
||||
await manager.savePluginAndExport(plugin.id);
|
||||
}
|
||||
}
|
||||
Commands(app, manager);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 单行命令
|
||||
if (manager.settings.COMMAND_ITEM) {
|
||||
const plugins: PluginManifest[] = Object.values(manager.appPlugins.manifests).filter((pm: PluginManifest) => pm.id !== manager.manifest.id);
|
||||
plugins.forEach(plugin => {
|
||||
const enabled = manager.appPlugins.enabledPlugins.has(plugin.id);
|
||||
manager.addCommand({
|
||||
id: `manager-${plugin.id}`,
|
||||
name: `${enabled ? manager.translator.t('命令行_禁用_文本') : manager.translator.t('命令行_启用_文本')} ${plugin.name} `,
|
||||
callback: async () => {
|
||||
if (enabled) {
|
||||
await manager.appPlugins.disablePluginAndSave(plugin.id);
|
||||
const mp = manager.settings.Plugins.find(p => p.id === plugin.id);
|
||||
if (mp) mp.enabled = false;
|
||||
await manager.savePluginAndExport(plugin.id);
|
||||
Commands(app, manager);
|
||||
} else {
|
||||
await manager.appPlugins.enablePluginAndSave(plugin.id);
|
||||
const mp = manager.settings.Plugins.find(p => p.id === plugin.id);
|
||||
if (mp) mp.enabled = true;
|
||||
await manager.savePluginAndExport(plugin.id);
|
||||
Commands(app, manager);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
// 分组命令
|
||||
if (manager.settings.COMMAND_GROUP) {
|
||||
manager.settings.GROUPS.forEach((group) => {
|
||||
manager.addCommand({
|
||||
id: `manager-${group.id}-enabled`,
|
||||
name: `${manager.translator.t('命令行_一键启用_文本')} ${group.name} ${manager.translator.t('命令行_分组_文本')}`,
|
||||
callback: async () => {
|
||||
const filteredPlugins = manager.settings.Plugins.filter(plugin => plugin.group === group.id);
|
||||
for (const plugin of filteredPlugins) {
|
||||
await manager.appPlugins.enablePluginAndSave(plugin.id);
|
||||
const mp = manager.settings.Plugins.find(p => p.id === plugin.id);
|
||||
if (mp) mp.enabled = true;
|
||||
await manager.savePluginAndExport(plugin.id);
|
||||
}
|
||||
Commands(app, manager);
|
||||
}
|
||||
});
|
||||
manager.addCommand({
|
||||
id: `manager-${group.id}-disable`,
|
||||
name: `${manager.translator.t('命令行_一键禁用_文本')} ${group.name} ${manager.translator.t('命令行_分组_文本')}`,
|
||||
callback: async () => {
|
||||
const filteredPlugins = manager.settings.Plugins.filter(plugin => plugin.group === group.id);
|
||||
for (const plugin of filteredPlugins) {
|
||||
await manager.appPlugins.disablePluginAndSave(plugin.id);
|
||||
const mp = manager.settings.Plugins.find(p => p.id === plugin.id);
|
||||
if (mp) mp.enabled = false;
|
||||
await manager.savePluginAndExport(plugin.id);
|
||||
}
|
||||
Commands(app, manager);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Commands
|
||||
import {
|
||||
App,
|
||||
Command,
|
||||
Notice,
|
||||
PluginManifest,
|
||||
SuggestModal,
|
||||
normalizePath,
|
||||
setIcon,
|
||||
} from "obsidian";
|
||||
import Manager from "./main";
|
||||
import { ManagerModal } from "./modal/manager-modal";
|
||||
import { TroubleshootModal } from "./troubleshoot/troubleshoot-modal";
|
||||
import { BPM_IGNORE_TAG, ManagerPlugin } from "./data/types";
|
||||
import { managerOpen } from "./utils";
|
||||
import { ObsidianAppWithInternals, VaultAdapterWithBasePath } from "./obsidian-internals";
|
||||
import { PluginCommandProfile, PluginCommandState } from "./settings/data";
|
||||
|
||||
type CommandManagerLike = {
|
||||
commands?: Record<string, Command>;
|
||||
removeCommand?: (id: string) => void;
|
||||
};
|
||||
|
||||
type PluginActionId =
|
||||
| "toggle"
|
||||
| "enable"
|
||||
| "disable"
|
||||
| "single-start"
|
||||
| "restart"
|
||||
| "open-settings"
|
||||
| "open-dir"
|
||||
| "open-repo"
|
||||
| "copy-id";
|
||||
|
||||
type PluginAction = {
|
||||
id: PluginActionId;
|
||||
label: string;
|
||||
icon: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const commandServices = new WeakMap<Manager, ManagerCommandService>();
|
||||
|
||||
class ManagerCommandService {
|
||||
private readonly app: App;
|
||||
private readonly manager: Manager;
|
||||
private staticCommandIds = new Set<string>();
|
||||
private staticLanguage = "";
|
||||
private dynamicCommandIds = new Set<string>();
|
||||
private running = new Set<string>();
|
||||
|
||||
constructor(app: App, manager: Manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.refreshStaticCommands();
|
||||
this.refreshDynamicCommands();
|
||||
}
|
||||
|
||||
openPluginControl() {
|
||||
new PluginControlModal(this.app, this).open();
|
||||
}
|
||||
|
||||
openProfileNameModal() {
|
||||
new ProfileNameModal(this.app, this).open();
|
||||
}
|
||||
|
||||
getTranslator() {
|
||||
return this.manager.translator;
|
||||
}
|
||||
|
||||
getPluginManifests(): PluginManifest[] {
|
||||
return Object.values(this.manager.appPlugins.manifests || {})
|
||||
.filter((plugin) => plugin.id !== this.manager.manifest.id)
|
||||
.sort((a, b) => (a.name || a.id).localeCompare(b.name || b.id));
|
||||
}
|
||||
|
||||
getManagerPlugin(pluginId: string): ManagerPlugin | undefined {
|
||||
return this.manager.settings.Plugins.find((plugin) => plugin.id === pluginId);
|
||||
}
|
||||
|
||||
isPluginEnabled(pluginId: string): boolean {
|
||||
const managerPlugin = this.getManagerPlugin(pluginId);
|
||||
if (this.manager.settings.DELAY && managerPlugin && !managerPlugin.tags.includes(BPM_IGNORE_TAG)) {
|
||||
return managerPlugin.enabled;
|
||||
}
|
||||
return this.manager.appPlugins.enabledPlugins.has(pluginId);
|
||||
}
|
||||
|
||||
isActionablePlugin(pluginId: string): boolean {
|
||||
if (pluginId === this.manager.manifest.id) return false;
|
||||
if (!this.manager.appPlugins.manifests[pluginId]) return false;
|
||||
const managerPlugin = this.getManagerPlugin(pluginId);
|
||||
return !managerPlugin?.tags?.includes(BPM_IGNORE_TAG);
|
||||
}
|
||||
|
||||
getPluginActions(pluginId: string): PluginAction[] {
|
||||
const enabled = this.isPluginEnabled(pluginId);
|
||||
const isActionable = this.isActionablePlugin(pluginId);
|
||||
return [
|
||||
{
|
||||
id: "toggle",
|
||||
label: enabled
|
||||
? this.t("command_action_disable")
|
||||
: this.t("command_action_enable"),
|
||||
icon: enabled ? "power-off" : "power",
|
||||
disabled: !isActionable,
|
||||
},
|
||||
{ id: "enable", label: this.t("command_action_enable"), icon: "power", disabled: !isActionable || enabled },
|
||||
{ id: "disable", label: this.t("command_action_disable"), icon: "power-off", disabled: !isActionable || !enabled },
|
||||
{
|
||||
id: "single-start",
|
||||
label: this.t("command_action_single_start"),
|
||||
icon: "repeat-1",
|
||||
disabled: this.manager.settings.DELAY || !isActionable || enabled,
|
||||
},
|
||||
{
|
||||
id: "restart",
|
||||
label: this.t("command_action_restart"),
|
||||
icon: "refresh-ccw",
|
||||
disabled: this.manager.settings.DELAY || !isActionable || !enabled,
|
||||
},
|
||||
{ id: "open-settings", label: this.t("command_action_open_settings"), icon: "settings", disabled: !enabled },
|
||||
{ id: "open-dir", label: this.t("command_action_open_dir"), icon: "folder-open" },
|
||||
{ id: "open-repo", label: this.t("command_action_open_repo"), icon: "github" },
|
||||
{ id: "copy-id", label: this.t("command_action_copy_id"), icon: "copy" },
|
||||
];
|
||||
}
|
||||
|
||||
async runPluginAction(pluginId: string, actionId: PluginActionId) {
|
||||
const manifest = this.manager.appPlugins.manifests[pluginId];
|
||||
if (!manifest) {
|
||||
new Notice(this.t("command_notice_missing_plugin", { id: pluginId }));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (actionId) {
|
||||
case "toggle":
|
||||
await this.togglePlugin(pluginId);
|
||||
break;
|
||||
case "enable":
|
||||
await this.setPluginEnabled(pluginId, true);
|
||||
break;
|
||||
case "disable":
|
||||
await this.setPluginEnabled(pluginId, false);
|
||||
break;
|
||||
case "single-start":
|
||||
await this.singleStartPlugin(pluginId);
|
||||
break;
|
||||
case "restart":
|
||||
await this.restartPlugin(pluginId);
|
||||
break;
|
||||
case "open-settings":
|
||||
await this.openPluginSettings(pluginId);
|
||||
break;
|
||||
case "open-dir":
|
||||
this.openPluginDir(manifest);
|
||||
break;
|
||||
case "open-repo":
|
||||
await this.openPluginRepo(pluginId);
|
||||
break;
|
||||
case "copy-id":
|
||||
this.copyPluginId(pluginId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async togglePlugin(pluginId: string) {
|
||||
await this.setPluginEnabled(pluginId, !this.isPluginEnabled(pluginId));
|
||||
}
|
||||
|
||||
async setPluginEnabled(pluginId: string, targetEnabled: boolean) {
|
||||
if (!this.isActionablePlugin(pluginId)) {
|
||||
new Notice(this.t("command_notice_not_actionable"));
|
||||
return;
|
||||
}
|
||||
await this.runLocked(`plugin:${pluginId}`, async () => {
|
||||
this.capturePreviousState(this.t("command_snapshot_plugin", { name: this.getPluginName(pluginId) }));
|
||||
const changed = await this.setPluginEnabledInternal(pluginId, targetEnabled);
|
||||
await this.manager.saveSettings();
|
||||
if (changed) {
|
||||
this.refreshAfterStatusChange([pluginId]);
|
||||
new Notice(targetEnabled
|
||||
? this.t("command_notice_plugin_enabled", { name: this.getPluginName(pluginId) })
|
||||
: this.t("command_notice_plugin_disabled", { name: this.getPluginName(pluginId) }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async applyGroup(groupId: string, targetEnabled: boolean) {
|
||||
const group = this.manager.settings.GROUPS.find((item) => item.id === groupId);
|
||||
const label = group?.name || groupId;
|
||||
const plugins = this.getActionableManagerPlugins((plugin) => plugin.group === groupId);
|
||||
await this.applyPluginsEnabled(plugins, targetEnabled, this.t("command_snapshot_group", { name: label }));
|
||||
}
|
||||
|
||||
async applyTag(tagId: string, targetEnabled: boolean) {
|
||||
const tag = this.manager.settings.TAGS.find((item) => item.id === tagId);
|
||||
const label = tag?.name || tagId;
|
||||
const plugins = this.getActionableManagerPlugins((plugin) => plugin.tags.includes(tagId));
|
||||
await this.applyPluginsEnabled(plugins, targetEnabled, this.t("command_snapshot_tag", { name: label }));
|
||||
}
|
||||
|
||||
async saveCurrentProfile(name: string) {
|
||||
const profileName = name.trim();
|
||||
if (!profileName) {
|
||||
new Notice(this.t("command_notice_profile_name_required"));
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const existing = this.manager.settings.COMMAND_PROFILES.find((profile) => profile.name === profileName);
|
||||
const profile: PluginCommandProfile = existing || {
|
||||
id: `${this.slugify(profileName)}-${now}`,
|
||||
name: profileName,
|
||||
pluginStates: {},
|
||||
createdAt: now,
|
||||
};
|
||||
profile.name = profileName;
|
||||
profile.pluginStates = this.captureCurrentState();
|
||||
profile.updatedAt = now;
|
||||
|
||||
if (!existing) this.manager.settings.COMMAND_PROFILES.push(profile);
|
||||
await this.manager.saveSettings();
|
||||
this.refresh();
|
||||
new Notice(this.t("command_notice_profile_saved", { name: profile.name }));
|
||||
}
|
||||
|
||||
async applyProfile(profileId: string) {
|
||||
const profile = this.manager.settings.COMMAND_PROFILES.find((item) => item.id === profileId);
|
||||
if (!profile) {
|
||||
new Notice(this.t("command_notice_profile_missing"));
|
||||
return;
|
||||
}
|
||||
await this.applyPluginStateMap(profile.pluginStates, this.t("command_snapshot_profile", { name: profile.name }));
|
||||
}
|
||||
|
||||
async restorePreviousState() {
|
||||
const snapshot = this.manager.settings.COMMAND_LAST_STATE;
|
||||
if (!snapshot) {
|
||||
new Notice(this.t("command_notice_no_snapshot"));
|
||||
return;
|
||||
}
|
||||
await this.applyPluginStateMap(snapshot.pluginStates, this.t("command_snapshot_restore"), true);
|
||||
}
|
||||
|
||||
private refreshStaticCommands() {
|
||||
const language = this.manager.settings.LANGUAGE || "";
|
||||
if (this.staticCommandIds.size > 0 && this.staticLanguage === language) return;
|
||||
this.removeStaticCommands();
|
||||
this.staticLanguage = language;
|
||||
this.registerStaticCommands();
|
||||
}
|
||||
|
||||
private registerStaticCommands() {
|
||||
this.addStaticCommand({
|
||||
id: "manager-view",
|
||||
name: this.manager.translator.t("命令_管理面板_描述"),
|
||||
callback: () => {
|
||||
this.manager.managerModal = new ManagerModal(this.app, this.manager);
|
||||
this.manager.managerModal.open();
|
||||
},
|
||||
});
|
||||
|
||||
this.addStaticCommand({
|
||||
id: "control-plugin",
|
||||
name: this.t("command_control_plugin"),
|
||||
callback: () => this.openPluginControl(),
|
||||
});
|
||||
|
||||
this.addStaticCommand({
|
||||
id: "save-command-profile",
|
||||
name: this.t("command_save_profile"),
|
||||
callback: () => this.openProfileNameModal(),
|
||||
});
|
||||
|
||||
this.addStaticCommand({
|
||||
id: "restore-previous-command-state",
|
||||
name: this.t("command_restore_previous_state"),
|
||||
checkCallback: (checking) => {
|
||||
const canRestore = Boolean(this.manager.settings.COMMAND_LAST_STATE);
|
||||
if (checking) return canRestore;
|
||||
if (canRestore) void this.restorePreviousState();
|
||||
return canRestore;
|
||||
},
|
||||
});
|
||||
|
||||
this.addStaticCommand({
|
||||
id: "troubleshoot-conflicts",
|
||||
name: this.manager.translator.t("排查_按钮_描述"),
|
||||
callback: () => { new TroubleshootModal(this.app, this.manager).open(); },
|
||||
});
|
||||
}
|
||||
|
||||
private refreshDynamicCommands() {
|
||||
this.removeDynamicCommands();
|
||||
if (this.manager.settings.COMMAND_ITEM) this.registerPluginCommands();
|
||||
if (this.manager.settings.COMMAND_GROUP) this.registerGroupCommands();
|
||||
if (this.manager.settings.COMMAND_TAG) this.registerTagCommands();
|
||||
if (this.manager.settings.COMMAND_PROFILE) this.registerProfileCommands();
|
||||
}
|
||||
|
||||
private registerPluginCommands() {
|
||||
this.getPluginManifests()
|
||||
.filter((plugin) => this.isActionablePlugin(plugin.id))
|
||||
.forEach((plugin) => {
|
||||
const enabled = this.isPluginEnabled(plugin.id);
|
||||
this.addDynamicCommand({
|
||||
id: `manager-${plugin.id}`,
|
||||
name: `${enabled ? this.manager.translator.t("命令行_禁用_文本") : this.manager.translator.t("命令行_启用_文本")} ${plugin.name || plugin.id}`,
|
||||
callback: () => { void this.togglePlugin(plugin.id); },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private registerGroupCommands() {
|
||||
this.manager.settings.GROUPS.forEach((group) => {
|
||||
const name = group.name || group.id;
|
||||
this.addDynamicCommand({
|
||||
id: `manager-${group.id}-enabled`,
|
||||
name: `${this.manager.translator.t("命令行_一键启用_文本")} ${name} ${this.manager.translator.t("命令行_分组_文本")}`,
|
||||
callback: () => { void this.applyGroup(group.id, true); },
|
||||
});
|
||||
this.addDynamicCommand({
|
||||
id: `manager-${group.id}-disable`,
|
||||
name: `${this.manager.translator.t("命令行_一键禁用_文本")} ${name} ${this.manager.translator.t("命令行_分组_文本")}`,
|
||||
callback: () => { void this.applyGroup(group.id, false); },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private registerTagCommands() {
|
||||
this.manager.settings.TAGS
|
||||
.filter((tag) => tag.id !== BPM_IGNORE_TAG)
|
||||
.forEach((tag) => {
|
||||
const name = tag.name || tag.id;
|
||||
const commandId = this.safeCommandPart(tag.id);
|
||||
this.addDynamicCommand({
|
||||
id: `manager-tag-${commandId}-enabled`,
|
||||
name: `${this.t("command_enable_tag")} ${name}`,
|
||||
callback: () => { void this.applyTag(tag.id, true); },
|
||||
});
|
||||
this.addDynamicCommand({
|
||||
id: `manager-tag-${commandId}-disable`,
|
||||
name: `${this.t("command_disable_tag")} ${name}`,
|
||||
callback: () => { void this.applyTag(tag.id, false); },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private registerProfileCommands() {
|
||||
this.manager.settings.COMMAND_PROFILES.forEach((profile) => {
|
||||
this.addDynamicCommand({
|
||||
id: `manager-profile-${this.safeCommandPart(profile.id)}-apply`,
|
||||
name: `${this.t("command_apply_profile")} ${profile.name}`,
|
||||
callback: () => { void this.applyProfile(profile.id); },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private addDynamicCommand(command: Command) {
|
||||
const registered = this.manager.addCommand(command);
|
||||
this.dynamicCommandIds.add(registered.id);
|
||||
this.dynamicCommandIds.add(this.fullCommandId(command.id));
|
||||
}
|
||||
|
||||
private addStaticCommand(command: Command) {
|
||||
const registered = this.manager.addCommand(command);
|
||||
this.staticCommandIds.add(registered.id);
|
||||
this.staticCommandIds.add(this.fullCommandId(command.id));
|
||||
}
|
||||
|
||||
private removeStaticCommands() {
|
||||
this.removeCommands(this.staticCommandIds);
|
||||
}
|
||||
|
||||
private removeDynamicCommands() {
|
||||
this.removeCommands(this.dynamicCommandIds);
|
||||
}
|
||||
|
||||
private removeCommands(commandIds: Set<string>) {
|
||||
const commandManager = (this.app as unknown as { commands?: CommandManagerLike }).commands;
|
||||
commandIds.forEach((id) => {
|
||||
try {
|
||||
commandManager?.removeCommand?.(id);
|
||||
} catch {
|
||||
// ignore unsupported internal command manager variants
|
||||
}
|
||||
if (commandManager?.commands) delete commandManager.commands[id];
|
||||
});
|
||||
commandIds.clear();
|
||||
}
|
||||
|
||||
private async applyPluginsEnabled(plugins: ManagerPlugin[], targetEnabled: boolean, label: string) {
|
||||
if (plugins.length === 0) {
|
||||
new Notice(this.t("command_notice_no_actionable_plugins"));
|
||||
return;
|
||||
}
|
||||
const states: PluginCommandState = {};
|
||||
plugins.forEach((plugin) => { states[plugin.id] = targetEnabled; });
|
||||
await this.applyPluginStateMap(states, label);
|
||||
}
|
||||
|
||||
private async applyPluginStateMap(states: PluginCommandState, label: string, restoring = false) {
|
||||
const entries = Object.entries(states).filter(([pluginId]) => this.isActionablePlugin(pluginId));
|
||||
if (entries.length === 0) {
|
||||
new Notice(this.t("command_notice_no_actionable_plugins"));
|
||||
return;
|
||||
}
|
||||
|
||||
await this.runLocked(`bulk:${label}`, async () => {
|
||||
this.capturePreviousState(restoring ? this.t("command_snapshot_before_restore") : label);
|
||||
const progress = new Notice(`${this.t("command_notice_applying")} 0/${entries.length}`, 0);
|
||||
let changedCount = 0;
|
||||
let processed = 0;
|
||||
const changedIds: string[] = [];
|
||||
try {
|
||||
for (const [pluginId, targetEnabled] of entries) {
|
||||
const changed = await this.setPluginEnabledInternal(pluginId, targetEnabled);
|
||||
if (changed) {
|
||||
changedCount++;
|
||||
changedIds.push(pluginId);
|
||||
}
|
||||
processed++;
|
||||
progress.setMessage(`${this.t("command_notice_applying")} ${processed}/${entries.length} · ${pluginId}`);
|
||||
}
|
||||
} finally {
|
||||
progress.hide();
|
||||
}
|
||||
await this.manager.saveSettings();
|
||||
this.refreshAfterStatusChange(changedIds);
|
||||
new Notice(this.t("command_notice_bulk_done", { count: changedCount }));
|
||||
});
|
||||
}
|
||||
|
||||
private async setPluginEnabledInternal(pluginId: string, targetEnabled: boolean): Promise<boolean> {
|
||||
const current = this.isPluginEnabled(pluginId);
|
||||
const managerPlugin = this.getManagerPlugin(pluginId);
|
||||
if (current === targetEnabled) {
|
||||
if (managerPlugin) managerPlugin.enabled = targetEnabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.manager.settings.DELAY && managerPlugin && !managerPlugin.tags.includes(BPM_IGNORE_TAG)) {
|
||||
managerPlugin.enabled = targetEnabled;
|
||||
if (targetEnabled) await this.manager.appPlugins.enablePlugin(pluginId);
|
||||
else await this.manager.appPlugins.disablePlugin(pluginId);
|
||||
} else {
|
||||
if (targetEnabled) await this.manager.appPlugins.enablePluginAndSave(pluginId);
|
||||
else await this.manager.appPlugins.disablePluginAndSave(pluginId);
|
||||
if (managerPlugin) managerPlugin.enabled = targetEnabled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async singleStartPlugin(pluginId: string) {
|
||||
if (this.manager.settings.DELAY || !this.isActionablePlugin(pluginId) || this.isPluginEnabled(pluginId)) return;
|
||||
await this.runLocked(`single:${pluginId}`, async () => {
|
||||
this.capturePreviousState(this.t("command_snapshot_single_start", { name: this.getPluginName(pluginId) }));
|
||||
new Notice(this.manager.translator.t("管理器_单次启动中_提示"));
|
||||
await this.manager.appPlugins.enablePlugin(pluginId);
|
||||
this.refreshAfterStatusChange([pluginId]);
|
||||
});
|
||||
}
|
||||
|
||||
private async restartPlugin(pluginId: string) {
|
||||
if (this.manager.settings.DELAY || !this.isActionablePlugin(pluginId) || !this.isPluginEnabled(pluginId)) return;
|
||||
await this.runLocked(`restart:${pluginId}`, async () => {
|
||||
this.capturePreviousState(this.t("command_snapshot_restart", { name: this.getPluginName(pluginId) }));
|
||||
new Notice(this.manager.translator.t("管理器_重启中_提示"));
|
||||
await this.manager.appPlugins.disablePluginAndSave(pluginId);
|
||||
await this.manager.appPlugins.enablePluginAndSave(pluginId);
|
||||
this.refreshAfterStatusChange([pluginId]);
|
||||
});
|
||||
}
|
||||
|
||||
private async openPluginSettings(pluginId: string) {
|
||||
if (!this.isPluginEnabled(pluginId)) {
|
||||
new Notice(this.t("command_notice_enable_before_settings"));
|
||||
return;
|
||||
}
|
||||
const appSetting = (this.app as ObsidianAppWithInternals).setting;
|
||||
await appSetting.open();
|
||||
await appSetting.openTabById(pluginId);
|
||||
}
|
||||
|
||||
private openPluginDir(plugin: PluginManifest) {
|
||||
const getBasePath = (this.app.vault.adapter as VaultAdapterWithBasePath).getBasePath?.();
|
||||
const basePath = getBasePath ? normalizePath(getBasePath) : "";
|
||||
const cfgDir = this.app.vault.configDir;
|
||||
const rawDir = plugin.dir || `plugins/${plugin.id}`;
|
||||
const isAbsolute = new RegExp("^(?:[a-zA-Z]:[\\\\/]|[\\\\/])").test(rawDir);
|
||||
let pluginDir: string;
|
||||
if (isAbsolute) {
|
||||
pluginDir = normalizePath(rawDir);
|
||||
} else if (rawDir.startsWith(cfgDir) || rawDir.startsWith(".") || rawDir.startsWith("/")) {
|
||||
pluginDir = normalizePath(`${basePath}/${rawDir}`);
|
||||
} else {
|
||||
pluginDir = normalizePath(`${basePath}/${cfgDir}/${rawDir}`);
|
||||
}
|
||||
managerOpen(pluginDir, this.manager);
|
||||
}
|
||||
|
||||
private async openPluginRepo(pluginId: string) {
|
||||
const repo = this.manager.settings.REPO_MAP?.[pluginId] || await this.manager.repoResolver.resolveRepo(pluginId);
|
||||
if (repo) {
|
||||
window.open(`https://github.com/${repo}`);
|
||||
return;
|
||||
}
|
||||
const isBpmInstall = this.manager.settings.BPM_INSTALLED.includes(pluginId);
|
||||
new Notice(isBpmInstall
|
||||
? this.manager.translator.t("管理器_仓库未记录_提示")
|
||||
: this.manager.translator.t("管理器_仓库需手动添加_提示"));
|
||||
}
|
||||
|
||||
private copyPluginId(pluginId: string) {
|
||||
void navigator.clipboard.writeText(pluginId);
|
||||
new Notice(this.manager.translator.t("通知_ID已复制"));
|
||||
}
|
||||
|
||||
private captureCurrentState(): PluginCommandState {
|
||||
const state: PluginCommandState = {};
|
||||
this.getPluginManifests()
|
||||
.filter((plugin) => this.isActionablePlugin(plugin.id))
|
||||
.forEach((plugin) => { state[plugin.id] = this.isPluginEnabled(plugin.id); });
|
||||
return state;
|
||||
}
|
||||
|
||||
private capturePreviousState(label: string) {
|
||||
this.manager.settings.COMMAND_LAST_STATE = {
|
||||
pluginStates: this.captureCurrentState(),
|
||||
createdAt: Date.now(),
|
||||
label,
|
||||
};
|
||||
}
|
||||
|
||||
private getActionableManagerPlugins(predicate: (plugin: ManagerPlugin) => boolean): ManagerPlugin[] {
|
||||
return this.manager.settings.Plugins
|
||||
.filter((plugin) => predicate(plugin))
|
||||
.filter((plugin) => this.isActionablePlugin(plugin.id));
|
||||
}
|
||||
|
||||
private refreshAfterStatusChange(pluginIds: string[]) {
|
||||
this.refresh();
|
||||
try {
|
||||
if (pluginIds.length === 0) {
|
||||
void this.manager.managerModal?.reloadShowData();
|
||||
return;
|
||||
}
|
||||
pluginIds.forEach((pluginId) => this.manager.managerModal?.refreshPluginCard(pluginId, { allowReload: true }));
|
||||
} catch {
|
||||
// UI may not be open
|
||||
}
|
||||
}
|
||||
|
||||
private async runLocked(key: string, task: () => Promise<void>) {
|
||||
if (this.running.has(key)) return;
|
||||
this.running.add(key);
|
||||
try {
|
||||
await task();
|
||||
} catch (error) {
|
||||
console.error("[BPM] command failed", key, error);
|
||||
new Notice(this.t("command_notice_failed"));
|
||||
} finally {
|
||||
this.running.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
private getPluginName(pluginId: string): string {
|
||||
const manifest = this.manager.appPlugins.manifests[pluginId];
|
||||
const managerPlugin = this.getManagerPlugin(pluginId);
|
||||
return managerPlugin?.name || manifest?.name || pluginId;
|
||||
}
|
||||
|
||||
private fullCommandId(id: string): string {
|
||||
return `${this.manager.manifest.id}:${id}`;
|
||||
}
|
||||
|
||||
private safeCommandPart(id: string): string {
|
||||
return id.replace(/[^a-zA-Z0-9_-]/g, "-");
|
||||
}
|
||||
|
||||
private slugify(name: string): string {
|
||||
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
return slug || "profile";
|
||||
}
|
||||
|
||||
private t(key: string, vars?: Record<string, string | number | boolean | null | undefined>): string {
|
||||
return this.manager.translator.t(key, vars);
|
||||
}
|
||||
}
|
||||
|
||||
class PluginControlModal extends SuggestModal<PluginManifest> {
|
||||
private readonly service: ManagerCommandService;
|
||||
|
||||
constructor(app: App, service: ManagerCommandService) {
|
||||
super(app);
|
||||
this.service = service;
|
||||
this.setPlaceholder(service.getTranslator().t("command_control_placeholder"));
|
||||
this.emptyStateText = service.getTranslator().t("command_control_empty");
|
||||
}
|
||||
|
||||
getSuggestions(query: string): PluginManifest[] {
|
||||
const lower = query.trim().toLowerCase();
|
||||
return this.service.getPluginManifests()
|
||||
.filter((plugin) => {
|
||||
if (!lower) return true;
|
||||
return `${plugin.name} ${plugin.id} ${plugin.description || ""}`.toLowerCase().includes(lower);
|
||||
})
|
||||
.slice(0, 50);
|
||||
}
|
||||
|
||||
renderSuggestion(plugin: PluginManifest, el: HTMLElement) {
|
||||
const row = el.createDiv({ cls: "manager-command-suggestion" });
|
||||
const icon = row.createSpan({ cls: "manager-command-suggestion__icon" });
|
||||
setIcon(icon, this.service.isPluginEnabled(plugin.id) ? "toggle-right" : "toggle-left");
|
||||
const text = row.createDiv({ cls: "manager-command-suggestion__text" });
|
||||
text.createDiv({ cls: "manager-command-suggestion__title", text: plugin.name || plugin.id });
|
||||
text.createDiv({ cls: "manager-command-suggestion__meta", text: plugin.id });
|
||||
}
|
||||
|
||||
onChooseSuggestion(plugin: PluginManifest) {
|
||||
new PluginActionModal(this.app, this.service, plugin).open();
|
||||
}
|
||||
}
|
||||
|
||||
class PluginActionModal extends SuggestModal<PluginAction> {
|
||||
private readonly service: ManagerCommandService;
|
||||
private readonly plugin: PluginManifest;
|
||||
|
||||
constructor(app: App, service: ManagerCommandService, plugin: PluginManifest) {
|
||||
super(app);
|
||||
this.service = service;
|
||||
this.plugin = plugin;
|
||||
this.setPlaceholder(service.getTranslator().t("command_action_placeholder", { name: plugin.name || plugin.id }));
|
||||
this.emptyStateText = service.getTranslator().t("command_control_empty");
|
||||
}
|
||||
|
||||
getSuggestions(query: string): PluginAction[] {
|
||||
const lower = query.trim().toLowerCase();
|
||||
return this.service.getPluginActions(this.plugin.id)
|
||||
.filter((action) => !lower || action.label.toLowerCase().includes(lower));
|
||||
}
|
||||
|
||||
renderSuggestion(action: PluginAction, el: HTMLElement) {
|
||||
const row = el.createDiv({ cls: "manager-command-suggestion" });
|
||||
const icon = row.createSpan({ cls: "manager-command-suggestion__icon" });
|
||||
setIcon(icon, action.icon);
|
||||
const text = row.createDiv({ cls: "manager-command-suggestion__text" });
|
||||
text.createDiv({ cls: "manager-command-suggestion__title", text: action.label });
|
||||
text.createDiv({
|
||||
cls: "manager-command-suggestion__meta",
|
||||
text: action.disabled
|
||||
? this.service.getTranslator().t("command_action_disabled")
|
||||
: this.plugin.id,
|
||||
});
|
||||
if (action.disabled) row.addClass("is-disabled");
|
||||
}
|
||||
|
||||
onChooseSuggestion(action: PluginAction) {
|
||||
if (action.disabled) {
|
||||
new Notice(this.service.getTranslator().t("command_action_disabled"));
|
||||
return;
|
||||
}
|
||||
void this.service.runPluginAction(this.plugin.id, action.id);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileNameModal extends SuggestModal<string> {
|
||||
private readonly service: ManagerCommandService;
|
||||
|
||||
constructor(app: App, service: ManagerCommandService) {
|
||||
super(app);
|
||||
this.service = service;
|
||||
this.setPlaceholder(service.getTranslator().t("command_profile_name_placeholder"));
|
||||
this.emptyStateText = service.getTranslator().t("command_profile_name_empty");
|
||||
}
|
||||
|
||||
getSuggestions(query: string): string[] {
|
||||
const value = query.trim();
|
||||
if (!value) return [];
|
||||
return [value];
|
||||
}
|
||||
|
||||
renderSuggestion(value: string, el: HTMLElement) {
|
||||
el.createDiv({ text: this.service.getTranslator().t("command_profile_save_as", { name: value }) });
|
||||
}
|
||||
|
||||
onChooseSuggestion(value: string) {
|
||||
void this.service.saveCurrentProfile(value);
|
||||
}
|
||||
}
|
||||
|
||||
const Commands = (app: App, manager: Manager) => {
|
||||
let service = commandServices.get(manager);
|
||||
if (!service) {
|
||||
service = new ManagerCommandService(app, manager);
|
||||
commandServices.set(manager, service);
|
||||
}
|
||||
service.refresh();
|
||||
};
|
||||
|
||||
export default Commands;
|
||||
|
|
|
|||
|
|
@ -264,9 +264,13 @@ export default {
|
|||
设置_基础设置_筛选持久化_描述: 'After enabling, you will see the same plugin list every time you open the manager.',
|
||||
|
||||
设置_基础设置_单独命令_标题: 'Control Plugin Commands Separately',
|
||||
设置_基础设置_单独命令_描述: 'Enable this option to control the enabled and disabled state of each plugin separately. (Restart Obsidian to take effect)',
|
||||
设置_基础设置_单独命令_描述: 'Enable direct command palette toggles for individual plugins.',
|
||||
设置_基础设置_分组命令_标题: 'Control Plugin Commands by Group',
|
||||
设置_基础设置_分组命令_描述: 'Enable this option to enable or disable all plugins in a specified group with one click. (Restart Obsidian to take effect)',
|
||||
设置_基础设置_分组命令_描述: 'Enable direct command palette actions for enabling or disabling a whole group.',
|
||||
command_setting_tag_title: 'Control Plugin Commands by Tag',
|
||||
command_setting_tag_desc: 'Enable direct command palette actions for enabling or disabling plugins by tag.',
|
||||
command_setting_profile_title: 'Plugin Profile Commands',
|
||||
command_setting_profile_desc: 'Register direct apply commands for saved plugin state profiles.',
|
||||
|
||||
设置_主页面功能_标题: 'Main Page Actions',
|
||||
设置_主页面功能_描述: 'Choose whether each plugin action on the manager page is shown directly on the item or kept in the context menu.',
|
||||
|
|
@ -312,6 +316,48 @@ export default {
|
|||
通知_获取版本中文案: 'Fetching remote version info…',
|
||||
|
||||
命令_管理面板_描述: 'Open the plugin manager',
|
||||
command_control_plugin: 'Control a plugin',
|
||||
command_control_placeholder: 'Search plugin to control...',
|
||||
command_control_empty: 'No matching plugin',
|
||||
command_action_placeholder: 'Choose action for {name}',
|
||||
command_action_enable: 'Enable',
|
||||
command_action_disable: 'Disable',
|
||||
command_action_single_start: 'Single start',
|
||||
command_action_restart: 'Restart plugin',
|
||||
command_action_open_settings: 'Open settings',
|
||||
command_action_open_dir: 'Open plugin folder',
|
||||
command_action_open_repo: 'Open repository',
|
||||
command_action_copy_id: 'Copy plugin ID',
|
||||
command_action_disabled: 'Unavailable for the current plugin state',
|
||||
command_enable_tag: 'Enable tag',
|
||||
command_disable_tag: 'Disable tag',
|
||||
command_save_profile: 'Save current plugin state as profile',
|
||||
command_restore_previous_state: 'Restore previous plugin state',
|
||||
command_apply_profile: 'Apply plugin profile',
|
||||
command_profile_name_placeholder: 'Profile name...',
|
||||
command_profile_name_empty: 'Type a profile name',
|
||||
command_profile_save_as: 'Save profile: {name}',
|
||||
command_notice_profile_saved: 'Saved plugin profile: {name}',
|
||||
command_notice_profile_missing: 'Plugin profile no longer exists',
|
||||
command_notice_profile_name_required: 'Profile name is required',
|
||||
command_notice_no_snapshot: 'No previous plugin state to restore',
|
||||
command_notice_missing_plugin: 'Plugin not found: {id}',
|
||||
command_notice_not_actionable: 'This plugin is not controlled by BPM',
|
||||
command_notice_no_actionable_plugins: 'No actionable plugins found',
|
||||
command_notice_applying: 'Applying plugin changes',
|
||||
command_notice_bulk_done: 'Updated {count} plugins',
|
||||
command_notice_plugin_enabled: 'Enabled {name}',
|
||||
command_notice_plugin_disabled: 'Disabled {name}',
|
||||
command_notice_enable_before_settings: 'Enable this plugin before opening its settings',
|
||||
command_notice_failed: 'Command failed. Check the console for details.',
|
||||
command_snapshot_plugin: 'Plugin command: {name}',
|
||||
command_snapshot_group: 'Group command: {name}',
|
||||
command_snapshot_tag: 'Tag command: {name}',
|
||||
command_snapshot_profile: 'Profile command: {name}',
|
||||
command_snapshot_restore: 'Restore previous plugin state',
|
||||
command_snapshot_before_restore: 'Before restore',
|
||||
command_snapshot_single_start: 'Single start: {name}',
|
||||
command_snapshot_restart: 'Restart: {name}',
|
||||
标签_BPM安装_名称: 'bpm install',
|
||||
标签_Eondr插件_名称: 'Eondr plugin',
|
||||
管理器_下载更新_描述: 'Download an update (choose version, incl. pre-release)',
|
||||
|
|
|
|||
|
|
@ -266,9 +266,13 @@ export default {
|
|||
设置_基础设置_筛选持久化_描述: '启用后,您将在每次打开管理器时看到相同的插件列表。',
|
||||
|
||||
设置_基础设置_单独命令_标题: '单独控制插件命令',
|
||||
设置_基础设置_单独命令_描述: '启用此选项可以单独控制每个插件的启用和禁用状态。(重启Obsidian生效)',
|
||||
设置_基础设置_单独命令_描述: '在命令面板中为每个插件注册独立启用/禁用命令。',
|
||||
设置_基础设置_分组命令_标题: '分组控制插件命令',
|
||||
设置_基础设置_分组命令_描述: '启用此选项可以一键启用或禁用指定分组中的所有插件。(重启Obsidian生效)',
|
||||
设置_基础设置_分组命令_描述: '在命令面板中为每个分组注册一键启用/禁用命令。',
|
||||
command_setting_tag_title: '标签控制插件命令',
|
||||
command_setting_tag_desc: '在命令面板中按标签一键启用或禁用插件。',
|
||||
command_setting_profile_title: '插件 Profile 命令',
|
||||
command_setting_profile_desc: '为已保存的插件状态方案注册一键应用命令。',
|
||||
标签_BPM安装_名称: 'BPM 安装',
|
||||
标签_Eondr插件_名称: 'Eondr 出品',
|
||||
|
||||
|
|
@ -316,6 +320,48 @@ export default {
|
|||
通知_获取版本中文案: '正在获取云端版本信息…',
|
||||
|
||||
命令_管理面板_描述: '开启插件管理器',
|
||||
command_control_plugin: '控制插件',
|
||||
command_control_placeholder: '搜索要控制的插件...',
|
||||
command_control_empty: '没有匹配的插件',
|
||||
command_action_placeholder: '选择 {name} 的操作',
|
||||
command_action_enable: '启用',
|
||||
command_action_disable: '禁用',
|
||||
command_action_single_start: '单次启动',
|
||||
command_action_restart: '重启插件',
|
||||
command_action_open_settings: '打开设置',
|
||||
command_action_open_dir: '打开插件目录',
|
||||
command_action_open_repo: '打开仓库',
|
||||
command_action_copy_id: '复制插件 ID',
|
||||
command_action_disabled: '当前插件状态不可用',
|
||||
command_enable_tag: '启用标签',
|
||||
command_disable_tag: '禁用标签',
|
||||
command_save_profile: '保存当前插件状态为 Profile',
|
||||
command_restore_previous_state: '恢复上一次插件状态',
|
||||
command_apply_profile: '应用插件 Profile',
|
||||
command_profile_name_placeholder: 'Profile 名称...',
|
||||
command_profile_name_empty: '输入一个 Profile 名称',
|
||||
command_profile_save_as: '保存 Profile:{name}',
|
||||
command_notice_profile_saved: '已保存插件 Profile:{name}',
|
||||
command_notice_profile_missing: '插件 Profile 不存在',
|
||||
command_notice_profile_name_required: '请输入 Profile 名称',
|
||||
command_notice_no_snapshot: '没有可恢复的上一次插件状态',
|
||||
command_notice_missing_plugin: '未找到插件:{id}',
|
||||
command_notice_not_actionable: '此插件不由 BPM 控制',
|
||||
command_notice_no_actionable_plugins: '没有可操作的插件',
|
||||
command_notice_applying: '正在应用插件变更',
|
||||
command_notice_bulk_done: '已更新 {count} 个插件',
|
||||
command_notice_plugin_enabled: '已启用 {name}',
|
||||
command_notice_plugin_disabled: '已禁用 {name}',
|
||||
command_notice_enable_before_settings: '请先启用此插件再打开设置',
|
||||
command_notice_failed: '命令执行失败,请查看控制台。',
|
||||
command_snapshot_plugin: '插件命令:{name}',
|
||||
command_snapshot_group: '分组命令:{name}',
|
||||
command_snapshot_tag: '标签命令:{name}',
|
||||
command_snapshot_profile: 'Profile 命令:{name}',
|
||||
command_snapshot_restore: '恢复上一次插件状态',
|
||||
command_snapshot_before_restore: '恢复前状态',
|
||||
command_snapshot_single_start: '单次启动:{name}',
|
||||
command_snapshot_restart: '重启:{name}',
|
||||
管理器_下载更新_描述: '下载指定版本更新(支持预发布)',
|
||||
安装_成功_提示: '已安装/更新插件:{name}',
|
||||
安装_错误_限速: 'GitHub 请求受限(403),请配置 GitHub Token 后重试。',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,21 @@ export type TagFilterOperator = FilterOperator;
|
|||
export type PluginOverviewLayout = "list" | "two-column";
|
||||
export type PluginUpdateCheckMode = NonNullable<BetaSource["updateCheckMode"]>;
|
||||
export type ReleaseCompatibilityMode = NonNullable<BetaSource["compatibilityMode"]>;
|
||||
export type PluginCommandState = Record<string, boolean>;
|
||||
|
||||
export interface PluginCommandSnapshot {
|
||||
pluginStates: PluginCommandState;
|
||||
createdAt: number;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface PluginCommandProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
pluginStates: PluginCommandState;
|
||||
createdAt: number;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_MAIN_PAGE_ACTION_PLACEMENT: Record<MainPageActionId, MainPageActionPlacement> = {
|
||||
checkUpdate: "menu",
|
||||
|
|
@ -76,6 +91,14 @@ export interface ManagerSettings {
|
|||
COMMAND_ITEM: boolean;
|
||||
/** 是否为每个分组注册批量启用/禁用命令。 */
|
||||
COMMAND_GROUP: boolean;
|
||||
/** 是否为每个标签注册批量启用/禁用命令。 */
|
||||
COMMAND_TAG: boolean;
|
||||
/** 是否为保存的插件状态方案注册一键应用命令。 */
|
||||
COMMAND_PROFILE: boolean;
|
||||
/** 命令面板保存的插件状态方案。 */
|
||||
COMMAND_PROFILES: PluginCommandProfile[];
|
||||
/** 命令面板批量操作前的上一份状态快照,用于撤销。 */
|
||||
COMMAND_LAST_STATE?: PluginCommandSnapshot;
|
||||
/** 是否在插件启动后自动检查插件更新。 */
|
||||
STARTUP_CHECK_UPDATES: boolean;
|
||||
/** 是否在插件启动后自动检查 GitHub 来源订阅的远程版本。 */
|
||||
|
|
@ -187,6 +210,9 @@ export const DEFAULT_SETTINGS: ManagerSettings = {
|
|||
SELF_CHECK_IGNORED: false,
|
||||
COMMAND_ITEM: false,
|
||||
COMMAND_GROUP: false,
|
||||
COMMAND_TAG: false,
|
||||
COMMAND_PROFILE: false,
|
||||
COMMAND_PROFILES: [],
|
||||
STARTUP_CHECK_UPDATES: false,
|
||||
SOURCE_STARTUP_CHECK_UPDATES: false,
|
||||
SOURCE_AUTO_UPDATE: true,
|
||||
|
|
|
|||
|
|
@ -179,6 +179,28 @@ export default class ManagerBasis extends BaseSetting {
|
|||
Commands(this.app, this.manager);
|
||||
});
|
||||
|
||||
const CommandTagBar = new Setting(this.containerEl)
|
||||
.setName(this.manager.translator.t("command_setting_tag_title"))
|
||||
.setDesc(this.manager.translator.t("command_setting_tag_desc"));
|
||||
const CommandTagToggle = new ToggleComponent(CommandTagBar.controlEl);
|
||||
CommandTagToggle.setValue(this.settings.COMMAND_TAG);
|
||||
CommandTagToggle.onChange((value) => {
|
||||
this.settings.COMMAND_TAG = value;
|
||||
void this.manager.saveSettings();
|
||||
Commands(this.app, this.manager);
|
||||
});
|
||||
|
||||
const CommandProfileBar = new Setting(this.containerEl)
|
||||
.setName(this.manager.translator.t("command_setting_profile_title"))
|
||||
.setDesc(this.manager.translator.t("command_setting_profile_desc"));
|
||||
const CommandProfileToggle = new ToggleComponent(CommandProfileBar.controlEl);
|
||||
CommandProfileToggle.setValue(this.settings.COMMAND_PROFILE);
|
||||
CommandProfileToggle.onChange((value) => {
|
||||
this.settings.COMMAND_PROFILE = value;
|
||||
void this.manager.saveSettings();
|
||||
Commands(this.app, this.manager);
|
||||
});
|
||||
|
||||
heading("设置_基础设置_分组_开发网络");
|
||||
|
||||
const debugBar = new Setting(this.containerEl)
|
||||
|
|
|
|||
33
styles.css
33
styles.css
|
|
@ -12171,3 +12171,36 @@ body.theme-dark .manager-transfer-segment[data-state="active"]:hover {
|
|||
.manager-repo-page .manager-install__segment {
|
||||
padding: 4px 8px !important;
|
||||
}
|
||||
|
||||
.manager-command-suggestion {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.manager-command-suggestion.is-disabled {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.manager-command-suggestion__icon {
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.manager-command-suggestion__text {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manager-command-suggestion__title,
|
||||
.manager-command-suggestion__meta {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.manager-command-suggestion__meta {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-smaller);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,8 @@ writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
|
|||
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
|
||||
versions[targetVersion] = minAppVersion;
|
||||
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
|
||||
|
||||
let packageLock = JSON.parse(readFileSync("package-lock.json", "utf8"));
|
||||
packageLock.version = targetVersion;
|
||||
if (packageLock.packages?.[""]) packageLock.packages[""].version = targetVersion;
|
||||
writeFileSync("package-lock.json", JSON.stringify(packageLock, null, 2));
|
||||
|
|
|
|||
Loading…
Reference in a new issue