mirror of
https://github.com/uglyboy-tl/obsidian-ink-player.git
synced 2026-07-22 05:42:02 +00:00
feat(plugins): add plugin dependency resolution system
- Centralize plugin imports in utils/deps.ts with dependency graph - Cascade dependencies when toggling plugins (e.g., auto-save requires memory) - Rename plugin setting keys from snake_case to kebab-case to match plugin IDs - Update settings toggle to use resolve() for dependency-aware behavior - Fix Object.assign to avoid mutating DEFAULT_SETTINGS
This commit is contained in:
parent
a3ddbeeb66
commit
3b1a19cd0c
5 changed files with 183 additions and 53 deletions
26
src/main.ts
26
src/main.ts
|
|
@ -1,22 +1,11 @@
|
|||
import { Plugins } from "@inkweave/core";
|
||||
import {
|
||||
audioPlugin,
|
||||
autoButtonPlugin,
|
||||
autoRestorePlugin,
|
||||
cdButtonPlugin,
|
||||
classTagPlugin,
|
||||
fadeEffectPlugin,
|
||||
imagePlugin,
|
||||
linkOpenPlugin,
|
||||
memoryPlugin,
|
||||
scrollAfterChoicePlugin,
|
||||
} from "@inkweave/plugins";
|
||||
|
||||
import { Platform, Plugin, type TAbstractFile, type WorkspaceLeaf } from "obsidian";
|
||||
import { setupCommands } from "./commands";
|
||||
import { I18n, type TransItemType } from "./locales/i18n";
|
||||
import { SettingsTab } from "./settings";
|
||||
import { DEFAULT_SETTINGS, type Settings } from "./types";
|
||||
import { plugins } from "./utils/deps";
|
||||
import { StoryView, VIEW_TYPE } from "./view";
|
||||
|
||||
export default class InkWeavePlugin extends Plugin {
|
||||
|
|
@ -42,21 +31,12 @@ export default class InkWeavePlugin extends Plugin {
|
|||
this.registerExtensions(["ink"], "markdown");
|
||||
|
||||
setupCommands(this);
|
||||
Plugins.register(imagePlugin);
|
||||
Plugins.register(audioPlugin);
|
||||
Plugins.register(autoRestorePlugin);
|
||||
Plugins.register(fadeEffectPlugin);
|
||||
Plugins.register(scrollAfterChoicePlugin);
|
||||
Plugins.register(linkOpenPlugin);
|
||||
Plugins.register(memoryPlugin);
|
||||
Plugins.register(autoButtonPlugin);
|
||||
Plugins.register(cdButtonPlugin);
|
||||
Plugins.register(classTagPlugin);
|
||||
for (const p of plugins) Plugins.register(p);
|
||||
Plugins.setPluginsEnabled(this.getPluginSettings());
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(DEFAULT_SETTINGS, (await this.loadData()) ?? {});
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, (await this.loadData()) ?? {});
|
||||
Plugins.setPluginsEnabled(this.getPluginSettings());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { type App, PluginSettingTab, Setting } from "obsidian";
|
||||
import type { TransItemType } from "./locales/i18n";
|
||||
import type InkWeavePlugin from "./main";
|
||||
import type { PluginSettings, Settings } from "./types";
|
||||
import type { PluginSettings } from "./types";
|
||||
import { DEFAULT_SETTINGS } from "./types";
|
||||
import { resolve } from "./utils/deps";
|
||||
|
||||
export class SettingsTab extends PluginSettingTab {
|
||||
plugin: InkWeavePlugin;
|
||||
|
|
@ -25,16 +26,21 @@ export class SettingsTab extends PluginSettingTab {
|
|||
.setName(this.t(titleKey))
|
||||
.setDesc(this.t(descKey))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings[key])
|
||||
.onChange(
|
||||
async (value) =>
|
||||
await this.plugin.updateSettings({ [key]: value } as Partial<Settings>),
|
||||
),
|
||||
toggle.setValue(this.plugin.settings[key]).onChange(async (value) => {
|
||||
// 获取当前插件设置(排除 linedelay 和 debug)
|
||||
const { linedelay, debug, ...currentPluginSettings } = this.plugin.settings;
|
||||
|
||||
// 计算包含依赖关系的新设置
|
||||
const newPluginSettings = resolve(currentPluginSettings, key, value);
|
||||
|
||||
// 更新所有相关设置
|
||||
await this.plugin.updateSettings(newPluginSettings);
|
||||
this.display();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
display(): void {
|
||||
override display(): void {
|
||||
this.containerEl.empty();
|
||||
|
||||
new Setting(this.containerEl).setName(this.t("settings_options")).setHeading();
|
||||
|
|
@ -56,24 +62,23 @@ export class SettingsTab extends PluginSettingTab {
|
|||
|
||||
new Setting(this.containerEl).setName(this.t("settings_plugins")).setHeading();
|
||||
|
||||
// Fixed plugin settings
|
||||
this.addPluginToggle("audio", "settings_audio_title", "settings_audio_desc");
|
||||
this.addPluginToggle("image", "settings_image_title", "settings_image_desc");
|
||||
this.addPluginToggle("linkopen", "settings_linkopen_title", "settings_linkopen_desc");
|
||||
this.addPluginToggle("link-open", "settings_linkopen_title", "settings_linkopen_desc");
|
||||
this.addPluginToggle("memory", "settings_memory_title", "settings_memory_desc");
|
||||
this.addPluginToggle(
|
||||
"auto_restore",
|
||||
"auto-restore",
|
||||
"settings_auto_restore_title",
|
||||
"settings_auto_restore_desc",
|
||||
);
|
||||
this.addPluginToggle(
|
||||
"scrollafterchoice",
|
||||
"scroll-after-choice",
|
||||
"settings_scrollafterchoice_title",
|
||||
"settings_scrollafterchoice_desc",
|
||||
);
|
||||
this.addPluginToggle("fadeforline", "settings_fadeforline_title", "settings_fadeforline_desc");
|
||||
this.addPluginToggle("cd_button", "settings_cdbutton_title", "settings_cdbutton_desc");
|
||||
this.addPluginToggle("auto_button", "settings_autobutton_title", "settings_autobutton_desc");
|
||||
this.addPluginToggle("auto_save", "settings_autosave_title", "settings_autosave_desc");
|
||||
this.addPluginToggle("fade-effect", "settings_fadeforline_title", "settings_fadeforline_desc");
|
||||
this.addPluginToggle("cd-button", "settings_cdbutton_title", "settings_cdbutton_desc");
|
||||
this.addPluginToggle("auto-button", "settings_autobutton_title", "settings_autobutton_desc");
|
||||
this.addPluginToggle("auto-save", "settings_autosave_title", "settings_autosave_desc");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/types.ts
28
src/types.ts
|
|
@ -1,14 +1,14 @@
|
|||
export interface PluginSettings {
|
||||
audio: boolean;
|
||||
image: boolean;
|
||||
linkopen: boolean;
|
||||
"link-open": boolean;
|
||||
memory: boolean;
|
||||
auto_restore: boolean;
|
||||
scrollafterchoice: boolean;
|
||||
fadeforline: boolean;
|
||||
cd_button: boolean;
|
||||
auto_button: boolean;
|
||||
auto_save: boolean;
|
||||
"auto-restore": boolean;
|
||||
"scroll-after-choice": boolean;
|
||||
"fade-effect": boolean;
|
||||
"cd-button": boolean;
|
||||
"auto-button": boolean;
|
||||
"auto-save": boolean;
|
||||
}
|
||||
|
||||
export interface Settings extends PluginSettings {
|
||||
|
|
@ -19,14 +19,14 @@ export interface Settings extends PluginSettings {
|
|||
export const DEFAULT_SETTINGS: Settings = {
|
||||
audio: true,
|
||||
image: true,
|
||||
linkopen: false,
|
||||
"link-open": false,
|
||||
memory: true,
|
||||
auto_restore: true,
|
||||
scrollafterchoice: true,
|
||||
fadeforline: true,
|
||||
cd_button: false,
|
||||
auto_button: false,
|
||||
auto_save: false,
|
||||
"auto-restore": true,
|
||||
"scroll-after-choice": true,
|
||||
"fade-effect": true,
|
||||
"cd-button": false,
|
||||
"auto-button": false,
|
||||
"auto-save": false,
|
||||
linedelay: 0.1,
|
||||
debug: false,
|
||||
};
|
||||
|
|
|
|||
68
src/utils/__tests__/deps.test.ts
Normal file
68
src/utils/__tests__/deps.test.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import { resolve } from "../deps";
|
||||
|
||||
describe("pluginDependencies", () => {
|
||||
it("should enable memory when enabling auto-save", () => {
|
||||
const currentSettings = {
|
||||
audio: true,
|
||||
image: true,
|
||||
"link-open": false,
|
||||
memory: false,
|
||||
"auto-restore": false,
|
||||
"scroll-after-choice": true,
|
||||
"fade-effect": true,
|
||||
"cd-button": false,
|
||||
"auto-button": false,
|
||||
"auto-save": false,
|
||||
};
|
||||
|
||||
const newSettings = resolve(currentSettings, "auto-save", true);
|
||||
|
||||
expect(newSettings["auto-save"]).toBe(true);
|
||||
expect(newSettings.memory).toBe(true); // memory should be enabled as dependency
|
||||
});
|
||||
|
||||
it("should disable auto-save and auto-restore when disabling memory", () => {
|
||||
const currentSettings = {
|
||||
audio: true,
|
||||
image: true,
|
||||
"link-open": false,
|
||||
memory: true,
|
||||
"auto-restore": true,
|
||||
"scroll-after-choice": true,
|
||||
"fade-effect": true,
|
||||
"cd-button": false,
|
||||
"auto-button": false,
|
||||
"auto-save": true,
|
||||
};
|
||||
|
||||
const newSettings = resolve(currentSettings, "memory", false);
|
||||
|
||||
expect(newSettings.memory).toBe(false);
|
||||
expect(newSettings["auto-save"]).toBe(false); // auto-save depends on memory
|
||||
expect(newSettings["auto-restore"]).toBe(false); // auto-restore depends on memory
|
||||
});
|
||||
|
||||
it("should not affect unrelated plugins", () => {
|
||||
const currentSettings = {
|
||||
audio: true,
|
||||
image: true,
|
||||
"link-open": false,
|
||||
memory: true,
|
||||
"auto-restore": true,
|
||||
"scroll-after-choice": true,
|
||||
"fade-effect": true,
|
||||
"cd-button": false,
|
||||
"auto-button": false,
|
||||
"auto-save": true,
|
||||
};
|
||||
|
||||
const newSettings = resolve(currentSettings, "audio", false);
|
||||
|
||||
expect(newSettings.audio).toBe(false);
|
||||
// Other plugins should remain unchanged
|
||||
expect(newSettings.image).toBe(true);
|
||||
expect(newSettings.memory).toBe(true);
|
||||
expect(newSettings["auto-save"]).toBe(true);
|
||||
});
|
||||
});
|
||||
77
src/utils/deps.ts
Normal file
77
src/utils/deps.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import type { Plugin } from "@inkweave/core";
|
||||
import {
|
||||
audioPlugin,
|
||||
autoButtonPlugin,
|
||||
autoRestorePlugin,
|
||||
autoSavePlugin,
|
||||
cdButtonPlugin,
|
||||
classTagPlugin,
|
||||
fadeEffectPlugin,
|
||||
imagePlugin,
|
||||
linkOpenPlugin,
|
||||
memoryPlugin,
|
||||
scrollAfterChoicePlugin,
|
||||
} from "@inkweave/plugins";
|
||||
import type { PluginSettings } from "../types";
|
||||
|
||||
export const plugins: Plugin[] = [
|
||||
audioPlugin,
|
||||
autoButtonPlugin,
|
||||
autoRestorePlugin,
|
||||
autoSavePlugin,
|
||||
cdButtonPlugin,
|
||||
classTagPlugin,
|
||||
fadeEffectPlugin,
|
||||
imagePlugin,
|
||||
linkOpenPlugin,
|
||||
memoryPlugin,
|
||||
scrollAfterChoicePlugin,
|
||||
];
|
||||
|
||||
const deps: Record<string, string[]> = {};
|
||||
const rdeps: Record<string, string[]> = {};
|
||||
for (const p of plugins) {
|
||||
deps[p.id] = p.dependencies || [];
|
||||
rdeps[p.id] = [];
|
||||
}
|
||||
for (const p of plugins) {
|
||||
for (const d of p.dependencies || []) {
|
||||
if (rdeps[d]) rdeps[d].push(p.id);
|
||||
}
|
||||
}
|
||||
|
||||
const reachable = (start: string, edges: Record<string, string[]>): Set<string> => {
|
||||
const found = new Set<string>();
|
||||
const seen = new Set<string>();
|
||||
const queue = [start];
|
||||
let head = 0;
|
||||
|
||||
while (head < queue.length) {
|
||||
const id = queue[head++];
|
||||
if (id === undefined || seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
for (const next of edges[id] || []) {
|
||||
if (!found.has(next)) {
|
||||
found.add(next);
|
||||
queue.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
|
||||
export const resolve = (
|
||||
settings: Record<string, boolean>,
|
||||
id: string,
|
||||
on: boolean,
|
||||
): Partial<PluginSettings> => {
|
||||
const next = { ...settings };
|
||||
if (on) {
|
||||
for (const dep of reachable(id, deps)) next[dep] = true;
|
||||
next[id] = true;
|
||||
} else {
|
||||
for (const dep of reachable(id, rdeps)) next[dep] = false;
|
||||
next[id] = false;
|
||||
}
|
||||
return next;
|
||||
};
|
||||
Loading…
Reference in a new issue