- Updated `parseEdgesFromCallouts.ts` to support edge extraction from both callout blocks and plain lines, allowing for more flexible edge definitions. - Introduced new settings in `settings.ts` for maximum assignments collected per template, enhancing loop protection during edge processing. - Enhanced documentation in `Entwicklerhandbuch.md` to reflect changes in edge parsing and settings. - Improved UI components to utilize the new settings for better user experience in the Chain Workbench and related features.
1186 lines
46 KiB
TypeScript
1186 lines
46 KiB
TypeScript
import { App, Notice, PluginSettingTab, Setting, TFile } from "obsidian";
|
|
import type MindnetCausalAssistantPlugin from "../main";
|
|
import { VocabularyLoader } from "../vocab/VocabularyLoader";
|
|
|
|
export class MindnetSettingTab extends PluginSettingTab {
|
|
plugin: MindnetCausalAssistantPlugin;
|
|
|
|
constructor(app: App, plugin: MindnetCausalAssistantPlugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
display(): void {
|
|
const { containerEl } = this;
|
|
|
|
containerEl.empty();
|
|
|
|
containerEl.createEl("h2", { text: "Mindnet Settings" });
|
|
|
|
// ============================================
|
|
// 1. Dictionary & Schema Configuration
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "📚 Dictionary & Schema" });
|
|
containerEl.createEl("p", {
|
|
text: "Konfigurationsdateien für Edge-Vokabular, Graph-Schema und Interview-Profile.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Edge vocabulary path
|
|
new Setting(containerEl)
|
|
.setName("Edge vocabulary path")
|
|
.setDesc(
|
|
"Pfad zur Edge-Vokabular-Datei (Markdown). Definiert verfügbare Edge-Typen und deren Beschreibungen. Wird für die semantische Mapping-Erstellung verwendet."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/dictionary/edge_vocabulary.md")
|
|
.setValue(this.plugin.settings.edgeVocabularyPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.edgeVocabularyPath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Validate")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
const text = await VocabularyLoader.loadText(
|
|
this.app,
|
|
this.plugin.settings.edgeVocabularyPath
|
|
);
|
|
new Notice(
|
|
`Edge vocabulary file found (${text.length} characters)`
|
|
);
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(
|
|
`Failed to load edge vocabulary: ${msg}`
|
|
);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Graph schema path
|
|
new Setting(containerEl)
|
|
.setName("Graph schema path")
|
|
.setDesc(
|
|
"Pfad zur Graph-Schema-Datei (Markdown). Definiert typische und verbotene Edge-Typen für verschiedene Quelltypen. Wird für Empfehlungen beim Mapping verwendet."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/dictionary/graph_schema.md")
|
|
.setValue(this.plugin.settings.graphSchemaPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.graphSchemaPath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Validate")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
const { GraphSchemaLoader } = await import("../schema/GraphSchemaLoader");
|
|
const isValid = await GraphSchemaLoader.validate(
|
|
this.app,
|
|
this.plugin.settings.graphSchemaPath
|
|
);
|
|
if (isValid) {
|
|
const schema = await GraphSchemaLoader.load(
|
|
this.app,
|
|
this.plugin.settings.graphSchemaPath
|
|
);
|
|
const ruleCount = schema.schema.size;
|
|
new Notice(
|
|
`Graph schema file found (${ruleCount} source types)`
|
|
);
|
|
} else {
|
|
new Notice("Graph schema file not found or invalid");
|
|
}
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(`Failed to validate graph schema: ${msg}`);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Interview config path
|
|
new Setting(containerEl)
|
|
.setName("Interview config path")
|
|
.setDesc(
|
|
"Pfad zur Interview-Konfigurationsdatei (YAML). Definiert verfügbare Interview-Profile mit ihren Schritten und Einstellungen."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/dictionary/interview_config.yaml")
|
|
.setValue(this.plugin.settings.interviewConfigPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.interviewConfigPath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Validate")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
const { InterviewConfigLoader } = await import("../interview/InterviewConfigLoader");
|
|
const result = await InterviewConfigLoader.loadConfig(
|
|
this.app,
|
|
this.plugin.settings.interviewConfigPath
|
|
);
|
|
if (result.errors.length > 0) {
|
|
new Notice(
|
|
`Interview config loaded with ${result.errors.length} error(s). Check console.`
|
|
);
|
|
console.warn("Interview config errors:", result.errors);
|
|
} else {
|
|
new Notice(
|
|
`Interview config file found (${result.config.profiles.length} profile(s))`
|
|
);
|
|
}
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(`Failed to load interview config: ${msg}`);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Chain roles path
|
|
new Setting(containerEl)
|
|
.setName("Chain roles path")
|
|
.setDesc(
|
|
"Pfad zur Chain-Roles-Konfigurationsdatei (YAML). Definiert Rollen für Chain-Intelligence mit ihren Edge-Typen."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/dictionary/chain_roles.yaml")
|
|
.setValue(this.plugin.settings.chainRolesPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.chainRolesPath = value;
|
|
await this.plugin.saveSettings();
|
|
// Trigger reload if path changes
|
|
if (this.plugin.settings.chainRolesPath) {
|
|
// Reload will happen on next file modify or can be triggered manually
|
|
}
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Validate")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
const { ChainRolesLoader } = await import("../dictionary/ChainRolesLoader");
|
|
const result = await ChainRolesLoader.load(
|
|
this.app,
|
|
this.plugin.settings.chainRolesPath
|
|
);
|
|
if (result.errors.length > 0) {
|
|
new Notice(
|
|
`Chain roles loaded with ${result.errors.length} error(s). Check console.`
|
|
);
|
|
console.warn("Chain roles errors:", result.errors);
|
|
} else {
|
|
const roleCount = Object.keys(result.data?.roles || {}).length;
|
|
new Notice(`Chain roles file found (${roleCount} role(s))`);
|
|
}
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(`Failed to load chain roles: ${msg}`);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Chain templates path
|
|
new Setting(containerEl)
|
|
.setName("Chain templates path")
|
|
.setDesc(
|
|
"Pfad zur Chain-Templates-Konfigurationsdatei (YAML). Definiert Templates für Chain-Intelligence mit Slots und Constraints."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/dictionary/chain_templates.yaml")
|
|
.setValue(this.plugin.settings.chainTemplatesPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.chainTemplatesPath = value;
|
|
await this.plugin.saveSettings();
|
|
// Trigger reload if path changes
|
|
if (this.plugin.settings.chainTemplatesPath) {
|
|
// Reload will happen on next file modify or can be triggered manually
|
|
}
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Validate")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
const { ChainTemplatesLoader } = await import("../dictionary/ChainTemplatesLoader");
|
|
const result = await ChainTemplatesLoader.load(
|
|
this.app,
|
|
this.plugin.settings.chainTemplatesPath
|
|
);
|
|
if (result.errors.length > 0) {
|
|
new Notice(
|
|
`Chain templates loaded with ${result.errors.length} error(s). Check console.`
|
|
);
|
|
console.warn("Chain templates errors:", result.errors);
|
|
} else {
|
|
const templateCount = result.data?.templates?.length || 0;
|
|
new Notice(`Chain templates file found (${templateCount} template(s))`);
|
|
}
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(`Failed to load chain templates: ${msg}`);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Analysis policies path
|
|
new Setting(containerEl)
|
|
.setName("Analysis policies path")
|
|
.setDesc(
|
|
"Pfad zur Analysis-Policies-Konfigurationsdatei (YAML). Definiert Severity-Policies für verschiedene Profile (discovery, decisioning, audit) und Finding-Codes."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/dictionary/analysis_policies.yaml")
|
|
.setValue(this.plugin.settings.analysisPoliciesPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.analysisPoliciesPath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Validate")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
const { parse } = await import("yaml");
|
|
const file = this.app.vault.getAbstractFileByPath(this.plugin.settings.analysisPoliciesPath);
|
|
if (!file || !("extension" in file) || file.extension !== "yaml") {
|
|
new Notice("Analysis policies file not found");
|
|
return;
|
|
}
|
|
const text = await this.app.vault.read(file as TFile);
|
|
const parsed = parse(text);
|
|
if (parsed && parsed.profiles) {
|
|
const profileCount = Object.keys(parsed.profiles || {}).length;
|
|
new Notice(`Analysis policies file found (${profileCount} profile(s))`);
|
|
} else {
|
|
new Notice("Analysis policies file found but invalid format");
|
|
}
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(`Failed to load analysis policies: ${msg}`);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Template matching profile
|
|
new Setting(containerEl)
|
|
.setName("Template matching profile")
|
|
.setDesc(
|
|
"Profil für Template Matching: 'discovery' (weniger strikt, mehr Findings) oder 'decisioning' (strikter, weniger Findings). Wird aus chain_templates.yaml defaults.profiles geladen."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("discovery", "Discovery")
|
|
.addOption("decisioning", "Decisioning")
|
|
.setValue(this.plugin.settings.templateMatchingProfile)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.templateMatchingProfile = value as "discovery" | "decisioning";
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Chain Inspector: Include candidates
|
|
new Setting(containerEl)
|
|
.setName("Chain Inspector: Include candidates")
|
|
.setDesc(
|
|
"Wenn aktiviert, werden Candidate-Edges (nicht explizit zugeordnete Links) in die Chain-Analyse einbezogen. Standard: deaktiviert (nur explizite Edges werden analysiert)."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.chainInspectorIncludeCandidates)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.chainInspectorIncludeCandidates = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Chain Inspector: Max template matches
|
|
new Setting(containerEl)
|
|
.setName("Chain Inspector: Max template matches")
|
|
.setDesc(
|
|
"Maximale Anzahl der Template-Matches, die im Report angezeigt werden. Standard: 3. Höhere Werte zeigen mehr Matches, können aber den Report unübersichtlich machen."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("3")
|
|
.setValue(String(this.plugin.settings.chainInspectorMaxTemplateMatches))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.chainInspectorMaxTemplateMatches = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Max matches per template (default)
|
|
new Setting(containerEl)
|
|
.setName("Max matches per template (default)")
|
|
.setDesc(
|
|
"Standard: Wie viele verschiedene Zuordnungen pro Template maximal ausgegeben werden (z. B. intra-note + cross-note). 0 = kein Limit. Wird durch chain_templates.yaml (defaults.matching.max_matches_per_template) überschrieben."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("2")
|
|
.setValue(String(this.plugin.settings.maxMatchesPerTemplateDefault))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue >= 0 && numValue <= 10000) {
|
|
this.plugin.settings.maxMatchesPerTemplateDefault = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Max assignments collected (loop protection)
|
|
new Setting(containerEl)
|
|
.setName("Max assignments collected (loop protection)")
|
|
.setDesc(
|
|
"Schleifenschutz: Max. Anzahl gesammelter Zuordnungen pro Template beim Backtracking. Nur zur Absicherung gegen Endlosschleifen; höhere Werte = mehr Kanten können erkannt werden. Überschreibbar durch chain_templates.yaml (defaults.matching.max_assignments_collected)."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("1000")
|
|
.setValue(String(this.plugin.settings.maxAssignmentsCollectedDefault))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0 && numValue <= 100000) {
|
|
this.plugin.settings.maxAssignmentsCollectedDefault = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 2. Graph Traversal & Linting
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "🔍 Graph Traversal & Linting" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für Graph-Durchquerung und Lint-Validierung.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Max hops
|
|
new Setting(containerEl)
|
|
.setName("Max hops")
|
|
.setDesc(
|
|
"Maximale Anzahl von Hops (Schritten) bei der Graph-Durchquerung. Bestimmt, wie tief der Graph durchsucht wird."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("3")
|
|
.setValue(String(this.plugin.settings.maxHops))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.maxHops = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Chain direction
|
|
new Setting(containerEl)
|
|
.setName("Chain direction")
|
|
.setDesc(
|
|
"Richtung für Chain-Traversal: 'Forward' (vorwärts), 'Backward' (rückwärts) oder 'Both' (beide Richtungen)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("forward", "Forward")
|
|
.addOption("backward", "Backward")
|
|
.addOption("both", "Both")
|
|
.setValue(this.plugin.settings.chainDirection)
|
|
.onChange(async (value) => {
|
|
if (value === "forward" || value === "backward" || value === "both") {
|
|
this.plugin.settings.chainDirection = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Strict mode
|
|
new Setting(containerEl)
|
|
.setName("Strict mode")
|
|
.setDesc(
|
|
"Aktiviert den strikten Validierungsmodus. Erzwingt strengere Regeln bei der Lint-Validierung."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.strictMode)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.strictMode = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Show canonical hints
|
|
new Setting(containerEl)
|
|
.setName("Show canonical hints")
|
|
.setDesc(
|
|
"Zeigt INFO-Hinweise mit kanonischer Edge-Typ-Auflösung in den Lint-Ergebnissen an. Hilfreich für die Debugging von Edge-Typ-Zuordnungen."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.showCanonicalHints)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.showCanonicalHints = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 3. Interview & Note Creation
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "📝 Interview & Note Creation" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für Interview-Wizard und Notiz-Erstellung.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Auto-start interview on create
|
|
new Setting(containerEl)
|
|
.setName("Auto-start interview on create")
|
|
.setDesc(
|
|
"Startet automatisch den Interview-Wizard, wenn eine neue Notiz über ein Profil erstellt wird."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.autoStartInterviewOnCreate)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.autoStartInterviewOnCreate = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Default notes folder
|
|
new Setting(containerEl)
|
|
.setName("Default notes folder")
|
|
.setDesc(
|
|
"Standard-Ordner für neue Notizen (vault-relativer Pfad, leer für Root). Profil-spezifische Defaults haben Vorrang."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("")
|
|
.setValue(this.plugin.settings.defaultNotesFolder)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.defaultNotesFolder = value || "";
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 4. Unresolved Link Handling
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "🔗 Unresolved Link Handling" });
|
|
containerEl.createEl("p", {
|
|
text: "Konfiguration für das Verhalten bei Klicks auf nicht aufgelöste Links.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Intercept unresolved links
|
|
new Setting(containerEl)
|
|
.setName("Intercept unresolved link clicks")
|
|
.setDesc(
|
|
"Aktiviert die Abfangen von Klicks auf nicht aufgelöste interne Links. Öffnet die Profilauswahl, wenn auf einen nicht existierenden Link geklickt wird."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.interceptUnresolvedLinkClicks)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.interceptUnresolvedLinkClicks = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Auto-start interview on unresolved click
|
|
new Setting(containerEl)
|
|
.setName("Auto-start interview on unresolved link click")
|
|
.setDesc(
|
|
"Startet automatisch den Interview-Wizard, wenn eine Notiz aus einem nicht aufgelösten Link erstellt wird. Erfordert, dass 'Intercept unresolved link clicks' aktiviert ist."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.autoStartOnUnresolvedClick)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.autoStartOnUnresolvedClick = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Bypass modifier (Reading View)
|
|
new Setting(containerEl)
|
|
.setName("Bypass modifier (Reading View)")
|
|
.setDesc(
|
|
"Modifier-Taste zum Umgehen des Link-Intercepts in der Leseansicht. Wenn diese Taste gedrückt wird, wird der Standard-Link-Klick ausgeführt (z.B. zum Erstellen einer leeren Notiz)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("Alt", "Alt")
|
|
.addOption("Ctrl", "Ctrl/Cmd")
|
|
.addOption("Shift", "Shift")
|
|
.addOption("None", "None")
|
|
.setValue(this.plugin.settings.bypassModifier)
|
|
.onChange(async (value) => {
|
|
if (value === "Alt" || value === "Ctrl" || value === "Shift" || value === "None") {
|
|
this.plugin.settings.bypassModifier = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Editor follow modifier (Live Preview/Source)
|
|
new Setting(containerEl)
|
|
.setName("Editor follow modifier (Live Preview/Source)")
|
|
.setDesc(
|
|
"Modifier-Taste, die im Editor (Live Preview/Source) gedrückt werden muss, um den Link-Intercept zu aktivieren. Standard: Ctrl/Cmd. Verhindert versehentliche Intercepts beim normalen Bearbeiten."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("Alt", "Alt")
|
|
.addOption("Ctrl", "Ctrl/Cmd")
|
|
.addOption("Shift", "Shift")
|
|
.addOption("None", "None")
|
|
.setValue(this.plugin.settings.editorFollowModifier)
|
|
.onChange(async (value) => {
|
|
if (value === "Alt" || value === "Ctrl" || value === "Shift" || value === "None") {
|
|
this.plugin.settings.editorFollowModifier = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 5. Templater Compatibility
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "⚙️ Templater Compatibility" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für die Kompatibilität mit dem Templater-Plugin.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Wait for first modify after create
|
|
new Setting(containerEl)
|
|
.setName("Wait for first modify after create")
|
|
.setDesc(
|
|
"Wartet, bis Templater die Datei modifiziert hat, bevor der Wizard gestartet wird. Empfohlen, wenn Templater verwendet wird, um Konflikte zu vermeiden."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.waitForFirstModifyAfterCreate)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.waitForFirstModifyAfterCreate = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Modify timeout
|
|
new Setting(containerEl)
|
|
.setName("Modify timeout (ms)")
|
|
.setDesc(
|
|
"Timeout in Millisekunden für das Warten auf ein File-Modify-Event. Wenn Templater nicht innerhalb dieser Zeit reagiert, wird der Wizard trotzdem gestartet."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("1200")
|
|
.setValue(String(this.plugin.settings.waitForModifyTimeoutMs))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.waitForModifyTimeoutMs = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 6. Note Adoption
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "🔄 Note Adoption" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für die automatische Übernahme neu erstellter Notizen im Editor.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Adopt new notes in editor
|
|
new Setting(containerEl)
|
|
.setName("Adopt new notes in editor")
|
|
.setDesc(
|
|
"Übernimmt automatisch neu erstellte Notizen im Editor und konvertiert sie in das Mindnet-Format (mit Frontmatter-ID). Nützlich, wenn Obsidian eine Notiz direkt erstellt, bevor unser Intercept greift."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.adoptNewNotesInEditor)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.adoptNewNotesInEditor = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Adopt max chars
|
|
new Setting(containerEl)
|
|
.setName("Adopt max chars")
|
|
.setDesc(
|
|
"Maximale Inhaltslänge (in Zeichen), um eine Notiz als Übernahme-Kandidat zu betrachten. Leere oder sehr kurze Notizen werden bevorzugt übernommen."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("200")
|
|
.setValue(String(this.plugin.settings.adoptMaxChars))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.adoptMaxChars = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Adopt confirm mode
|
|
new Setting(containerEl)
|
|
.setName("Adopt confirm mode")
|
|
.setDesc(
|
|
"Wann die Übernahme-Bestätigung angezeigt wird: 'Always ask' (immer fragen), 'Only for low confidence' (nur bei niedriger Konfidenz, überspringt bei hoher Konfidenz), oder 'Never ask' (nie fragen, automatisch übernehmen)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("always", "Always ask")
|
|
.addOption("onlyLowConfidence", "Only for low confidence")
|
|
.addOption("never", "Never ask")
|
|
.setValue(this.plugin.settings.adoptConfirmMode)
|
|
.onChange(async (value) => {
|
|
if (value === "always" || value === "onlyLowConfidence" || value === "never") {
|
|
this.plugin.settings.adoptConfirmMode = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// High confidence window
|
|
new Setting(containerEl)
|
|
.setName("High confidence window (ms)")
|
|
.setDesc(
|
|
"Zeitfenster in Millisekunden für die hohe Konfidenz-Übernahme. Notizen, die innerhalb dieses Zeitfensters nach einem Klick erstellt wurden, werden mit hoher Konfidenz als Übernahme-Kandidaten betrachtet."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("3000")
|
|
.setValue(String(this.plugin.settings.highConfidenceWindowMs))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.highConfidenceWindowMs = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 7. Semantic Mapping Builder
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "🕸️ Semantic Mapping Builder" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für den Semantic Mapping Builder, der automatisch Mapping-Blöcke in Notizen erstellt.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Wrapper callout type
|
|
new Setting(containerEl)
|
|
.setName("Mapping wrapper callout type")
|
|
.setDesc(
|
|
"Callout-Typ für den Mapping-Wrapper (z.B. 'abstract', 'info', 'note'). Bestimmt das visuelle Erscheinungsbild des Mapping-Blocks."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("abstract")
|
|
.setValue(this.plugin.settings.mappingWrapperCalloutType)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.mappingWrapperCalloutType = value || "abstract";
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Wrapper title
|
|
new Setting(containerEl)
|
|
.setName("Mapping wrapper title")
|
|
.setDesc(
|
|
"Titel-Text für den Mapping-Wrapper-Callout. Wird als Überschrift des Mapping-Blocks angezeigt."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("🕸️ Semantic Mapping")
|
|
.setValue(this.plugin.settings.mappingWrapperTitle)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.mappingWrapperTitle = value || "🕸️ Semantic Mapping";
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Wrapper folded
|
|
new Setting(containerEl)
|
|
.setName("Mapping wrapper folded")
|
|
.setDesc(
|
|
"Startet mit dem Mapping-Wrapper-Callout eingeklappt (collapsed). Reduziert visuellen Lärm in der Notiz."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.mappingWrapperFolded)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.mappingWrapperFolded = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Unassigned handling
|
|
new Setting(containerEl)
|
|
.setName("Unassigned handling")
|
|
.setDesc(
|
|
"Wie Links ohne bestehende Mappings behandelt werden: 'Prompt (interactive)' (interaktive Auswahl), 'None (skip unmapped)' (überspringen), 'Use default edge type' (Standard-Typ verwenden), oder 'Use advisor' (zukünftig: E1-Engine)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("prompt", "Prompt (interactive)")
|
|
.addOption("none", "None (skip unmapped)")
|
|
.addOption("defaultType", "Use default edge type")
|
|
.addOption("advisor", "Use advisor (future: E1 engine)")
|
|
.setValue(this.plugin.settings.unassignedHandling)
|
|
.onChange(async (value) => {
|
|
if (value === "prompt" || value === "none" || value === "defaultType" || value === "advisor") {
|
|
this.plugin.settings.unassignedHandling = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Default edge type
|
|
new Setting(containerEl)
|
|
.setName("Default edge type")
|
|
.setDesc(
|
|
"Standard-Edge-Typ für nicht zugeordnete Links. Wird nur verwendet, wenn 'Unassigned handling' auf 'Use default edge type' gesetzt ist."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("")
|
|
.setValue(this.plugin.settings.defaultEdgeType)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.defaultEdgeType = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Allow overwrite existing mappings
|
|
new Setting(containerEl)
|
|
.setName("Allow overwrite existing mappings")
|
|
.setDesc(
|
|
"Wenn aktiviert, wird vor dem Überschreiben bestehender Edge-Typ-Zuordnungen eine Bestätigung angefordert. Wenn deaktiviert, werden bestehende Mappings immer beibehalten."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.allowOverwriteExistingMappings)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.allowOverwriteExistingMappings = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Inline micro edge suggester
|
|
new Setting(containerEl)
|
|
.setName("Inline micro edge suggester enabled")
|
|
.setDesc(
|
|
"Aktiviert den Inline-Micro-Edge-Suggester. Zeigt nach dem Einfügen eines Links über den Entity Picker sofort eine Edge-Typ-Auswahl an (nur wenn Profil edging.mode=inline_micro)."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.inlineMicroEnabled)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.inlineMicroEnabled = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Inline max alternatives
|
|
new Setting(containerEl)
|
|
.setName("Inline max alternatives")
|
|
.setDesc(
|
|
"Maximale Anzahl von alternativen Edge-Typen, die im Inline-Micro-Modal angezeigt werden (Standard: 6)."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("6")
|
|
.setValue(String(this.plugin.settings.inlineMaxAlternatives))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.inlineMaxAlternatives = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 8. Backend Logging Configuration
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "📝 Backend Logging" });
|
|
containerEl.createEl("p", {
|
|
text: "Konfiguration für das Backend-Logging. Steuert Log-Level, Dateigröße und Rotation. Diese Einstellungen werden beim Backend-Start verwendet.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Log level
|
|
new Setting(containerEl)
|
|
.setName("Log level")
|
|
.setDesc(
|
|
"Log-Level für das Backend: 'INFO' (Standard, weniger Zeilen), 'WARNING' (nur Warnungen/Fehler), 'ERROR' (nur Fehler), oder 'DEBUG' (sehr ausführlich, viele Zeilen)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("INFO", "INFO")
|
|
.addOption("WARNING", "WARNING")
|
|
.addOption("ERROR", "ERROR")
|
|
.addOption("DEBUG", "DEBUG")
|
|
.setValue(this.plugin.settings.logLevel)
|
|
.onChange(async (value) => {
|
|
if (value === "DEBUG" || value === "INFO" || value === "WARNING" || value === "ERROR") {
|
|
this.plugin.settings.logLevel = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Log max bytes
|
|
new Setting(containerEl)
|
|
.setName("Log max bytes")
|
|
.setDesc(
|
|
"Maximale Größe einer Log-Datei in Bytes vor Rotation. Standard: 1048576 (1 MB). Kleinere Werte führen zu häufigerer Rotation und kürzeren Einzeldateien. Empfohlen: 524288 (512 KB) für sehr große Logs."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("1048576")
|
|
.setValue(String(this.plugin.settings.logMaxBytes))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
this.plugin.settings.logMaxBytes = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Log backup count
|
|
new Setting(containerEl)
|
|
.setName("Log backup count")
|
|
.setDesc(
|
|
"Anzahl rotierter Backup-Dateien für logs/mindnet.log. Standard: 2. Bei Erreichen von 'Log max bytes' wird die aktuelle Datei rotiert (mindnet.log.1, mindnet.log.2, etc.)."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("2")
|
|
.setValue(String(this.plugin.settings.logBackupCount))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue >= 0) {
|
|
this.plugin.settings.logBackupCount = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 9. Debug & Development
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "🐛 Debug & Development" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für Debugging und Entwicklung.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Debug logging
|
|
new Setting(containerEl)
|
|
.setName("Debug logging")
|
|
.setDesc(
|
|
"Aktiviert ausführliches Debug-Logging für das Unresolved-Link-Handling. Logs erscheinen in der Browser-Konsole (F12). Nützlich für die Fehlersuche."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.debugLogging)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.debugLogging = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 9.1. Module-specific Logging
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "📊 Modulspezifisches Logging" });
|
|
containerEl.createEl("p", {
|
|
text: "Konfigurieren Sie individuelle Log-Level für verschiedene Module. Nützlich zum gezielten Debugging von Chain-Matching und Edge-Erkennung. Logs erscheinen in der Browser-Konsole (F12).",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Ensure moduleLogLevels exists
|
|
if (!this.plugin.settings.moduleLogLevels) {
|
|
this.plugin.settings.moduleLogLevels = {};
|
|
}
|
|
|
|
// Define important modules for chain matching
|
|
const importantModules = [
|
|
{ name: "templateMatching", description: "Template-Matching: Erkennt Chain-Templates und ordnet Slots zu" },
|
|
{ name: "chainInspector", description: "Chain Inspector: Analysiert Chains und findet fehlende Kanten" },
|
|
{ name: "todoGenerator", description: "Todo Generator: Generiert Todos für fehlende Slots und Links" },
|
|
{ name: "graphIndex", description: "Graph Index: Baut den Graph-Index auf und verwaltet Edges" },
|
|
{ name: "workbenchBuilder", description: "Workbench Builder: Baut Workbench-Matches auf" },
|
|
{ name: "chainWorkbenchCommand", description: "Chain Workbench Command: Hauptkommando für Chain-Workbench" },
|
|
];
|
|
|
|
// Create settings for each module
|
|
for (const module of importantModules) {
|
|
const currentLevel = this.plugin.settings.moduleLogLevels[module.name] || "INFO";
|
|
|
|
new Setting(containerEl)
|
|
.setName(`${module.name}`)
|
|
.setDesc(module.description)
|
|
.addDropdown((dropdown) => {
|
|
dropdown
|
|
.addOption("NONE", "NONE (keine Logs)")
|
|
.addOption("ERROR", "ERROR (nur Fehler)")
|
|
.addOption("WARN", "WARN (Warnungen und Fehler)")
|
|
.addOption("INFO", "INFO (Standard)")
|
|
.addOption("DEBUG", "DEBUG (sehr ausführlich)")
|
|
.setValue(currentLevel)
|
|
.onChange(async (value) => {
|
|
if (value === "NONE" || value === "ERROR" || value === "WARN" || value === "INFO" || value === "DEBUG") {
|
|
if (value === "INFO") {
|
|
// Remove from config if set to default
|
|
delete this.plugin.settings.moduleLogLevels[module.name];
|
|
} else {
|
|
this.plugin.settings.moduleLogLevels[module.name] = value;
|
|
}
|
|
await this.plugin.saveSettings();
|
|
|
|
// Update logger registry immediately
|
|
const { initializeLogging } = await import("../utils/logger");
|
|
initializeLogging(this.plugin.settings.moduleLogLevels);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Add button to reset all to defaults
|
|
new Setting(containerEl)
|
|
.setName("Alle auf Standard zurücksetzen")
|
|
.setDesc("Setzt alle Modul-Log-Level auf INFO (Standard) zurück.")
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Zurücksetzen")
|
|
.onClick(async () => {
|
|
this.plugin.settings.moduleLogLevels = {};
|
|
await this.plugin.saveSettings();
|
|
|
|
// Update logger registry
|
|
const { initializeLogging } = await import("../utils/logger");
|
|
initializeLogging({});
|
|
|
|
// Refresh settings UI
|
|
this.display();
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 10. Export
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "📤 Export" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für den Graph-Export.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Export path
|
|
new Setting(containerEl)
|
|
.setName("Export path")
|
|
.setDesc(
|
|
"Pfad für die exportierte Graph-JSON-Datei. Die Datei enthält alle Knoten (Nodes) und Kanten (Edges) aus dem Vault."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("_system/exports/graph_export.json")
|
|
.setValue(this.plugin.settings.exportPath)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.exportPath = value || "_system/exports/graph_export.json";
|
|
await this.plugin.saveSettings();
|
|
})
|
|
)
|
|
.addButton((button) =>
|
|
button
|
|
.setButtonText("Export now")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
try {
|
|
// Load vocabulary using the same method as the command
|
|
const { VocabularyLoader } = await import("../vocab/VocabularyLoader");
|
|
const { parseEdgeVocabulary } = await import("../vocab/parseEdgeVocabulary");
|
|
const { Vocabulary } = await import("../vocab/Vocabulary");
|
|
|
|
const text = await VocabularyLoader.loadText(
|
|
this.app,
|
|
this.plugin.settings.edgeVocabularyPath
|
|
);
|
|
|
|
const parsed = parseEdgeVocabulary(text);
|
|
const vocabulary = new Vocabulary(parsed);
|
|
|
|
const { exportGraph } = await import("../export/exportGraph");
|
|
await exportGraph(this.app, vocabulary, this.plugin.settings.exportPath);
|
|
|
|
new Notice(`Graph exported to ${this.plugin.settings.exportPath}`);
|
|
console.log(`Graph exported: ${this.plugin.settings.exportPath}`);
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
new Notice(`Failed to export graph: ${msg}`);
|
|
console.error(e);
|
|
}
|
|
})
|
|
);
|
|
|
|
// ============================================
|
|
// 11. Fix Actions Settings
|
|
// ============================================
|
|
containerEl.createEl("h3", { text: "🔧 Fix Actions" });
|
|
containerEl.createEl("p", {
|
|
text: "Einstellungen für automatische Fix-Aktionen bei Chain Inspector Findings.",
|
|
cls: "setting-item-description",
|
|
});
|
|
|
|
// Create missing note mode
|
|
new Setting(containerEl)
|
|
.setName("Create missing note mode")
|
|
.setDesc(
|
|
"Verhalten beim Erstellen fehlender Noten: 'Skeleton only' (nur Frontmatter), 'Create and open profile picker' (mit Profil-Auswahl), 'Create and start wizard' (mit Wizard)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("skeleton_only", "Skeleton only")
|
|
.addOption("create_and_open_profile_picker", "Create and open profile picker")
|
|
.addOption("create_and_start_wizard", "Create and start wizard")
|
|
.setValue(this.plugin.settings.fixActions.createMissingNote.mode)
|
|
.onChange(async (value) => {
|
|
if (
|
|
value === "skeleton_only" ||
|
|
value === "create_and_open_profile_picker" ||
|
|
value === "create_and_start_wizard"
|
|
) {
|
|
this.plugin.settings.fixActions.createMissingNote.mode = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Default type strategy
|
|
new Setting(containerEl)
|
|
.setName("Default type strategy")
|
|
.setDesc(
|
|
"Strategie für Note-Typ-Zuweisung: 'Profile picker' (immer Picker zeigen), 'Inference then picker' (heuristische Vorauswahl), 'Default concept no prompt' (Standard 'concept' ohne Prompt)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("profile_picker", "Profile picker")
|
|
.addOption("inference_then_picker", "Inference then picker")
|
|
.addOption("default_concept_no_prompt", "Default concept no prompt")
|
|
.setValue(this.plugin.settings.fixActions.createMissingNote.defaultTypeStrategy)
|
|
.onChange(async (value) => {
|
|
if (
|
|
value === "profile_picker" ||
|
|
value === "inference_then_picker" ||
|
|
value === "default_concept_no_prompt"
|
|
) {
|
|
this.plugin.settings.fixActions.createMissingNote.defaultTypeStrategy = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Include zones
|
|
new Setting(containerEl)
|
|
.setName("Include zones")
|
|
.setDesc(
|
|
"Welche Zonen in neu erstellten Noten einfügen: 'None' (keine), 'Note links only' (nur Note-Verbindungen), 'Candidates only' (nur Kandidaten), 'Both' (beide)."
|
|
)
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOption("none", "None")
|
|
.addOption("note_links_only", "Note links only")
|
|
.addOption("candidates_only", "Candidates only")
|
|
.addOption("both", "Both")
|
|
.setValue(this.plugin.settings.fixActions.createMissingNote.includeZones)
|
|
.onChange(async (value) => {
|
|
if (
|
|
value === "none" ||
|
|
value === "note_links_only" ||
|
|
value === "candidates_only" ||
|
|
value === "both"
|
|
) {
|
|
this.plugin.settings.fixActions.createMissingNote.includeZones = value;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Create missing heading level
|
|
new Setting(containerEl)
|
|
.setName("Create missing heading level")
|
|
.setDesc(
|
|
"Heading-Level für neu erstellte Headings (1-6). Standard: 2 (H2)."
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("2")
|
|
.setValue(String(this.plugin.settings.fixActions.createMissingHeading.level))
|
|
.onChange(async (value) => {
|
|
const numValue = parseInt(value, 10);
|
|
if (!isNaN(numValue) && numValue >= 1 && numValue <= 6) {
|
|
this.plugin.settings.fixActions.createMissingHeading.level = numValue;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
// Promote candidate keep original
|
|
new Setting(containerEl)
|
|
.setName("Promote candidate: Keep original")
|
|
.setDesc(
|
|
"Wenn aktiviert, bleibt das ursprüngliche Candidate-Edge im Kandidaten-Bereich erhalten, wenn es zu einem expliziten Edge befördert wird."
|
|
)
|
|
.addToggle((toggle) =>
|
|
toggle
|
|
.setValue(this.plugin.settings.fixActions.promoteCandidate.keepOriginal)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.fixActions.promoteCandidate.keepOriginal = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
}
|
|
}
|