2026-01-19 15:47:23 +01:00
import { App , Notice , PluginSettingTab , Setting , TFile } from "obsidian" ;
2026-01-15 11:34:29 +01:00
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" } ) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 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" ,
} ) ;
2026-01-15 11:34:29 +01:00
// Edge vocabulary path
new Setting ( containerEl )
. setName ( "Edge vocabulary path" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Pfad zur Edge-Vokabular-Datei (Markdown). Definiert verfügbare Edge-Typen und deren Beschreibungen. Wird für die semantische Mapping-Erstellung verwendet."
)
2026-01-15 11:34:29 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Pfad zur Graph-Schema-Datei (Markdown). Definiert typische und verbotene Edge-Typen für verschiedene Quelltypen. Wird für Empfehlungen beim Mapping verwendet."
)
2026-01-15 11:34:29 +01:00
. 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 ( ) ;
} )
2026-01-17 07:42:17 +01:00
)
. 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 } ` ) ;
}
} )
2026-01-15 11:34:29 +01:00
) ;
2026-01-17 10:54:32 +01:00
// Interview config path
2026-01-15 11:34:29 +01:00
new Setting ( containerEl )
2026-01-17 10:54:32 +01:00
. 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 )
2026-01-15 11:34:29 +01:00
. onChange ( async ( value ) = > {
2026-01-17 10:54:32 +01:00
this . plugin . settings . interviewConfigPath = value ;
2026-01-15 11:34:29 +01:00
await this . plugin . saveSettings ( ) ;
} )
2026-01-17 10:54:32 +01:00
)
. 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 } ` ) ;
}
} )
2026-01-15 11:34:29 +01:00
) ;
2026-01-18 14:50:17 +01:00
// 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 } ` ) ;
}
} )
) ;
2026-01-19 15:47:23 +01:00
// 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 } ` ) ;
}
} )
) ;
2026-01-18 21:46:20 +01:00
// 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 ( ) ;
} )
) ;
2026-01-19 15:47:23 +01:00
// 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 ( ) ;
}
} )
) ;
2026-02-05 11:41:15 +01:00
// Max matches per template (default)
new Setting ( containerEl )
. setName ( "Max matches per template (default)" )
. setDesc (
2026-02-06 11:40:44 +01:00
"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."
2026-02-05 11:41:15 +01:00
)
. addText ( ( text ) = >
text
. setPlaceholder ( "2" )
. setValue ( String ( this . plugin . settings . maxMatchesPerTemplateDefault ) )
. onChange ( async ( value ) = > {
const numValue = parseInt ( value , 10 ) ;
2026-02-06 11:40:44 +01:00
if ( ! isNaN ( numValue ) && numValue >= 0 && numValue <= 10000 ) {
2026-02-05 11:41:15 +01:00
this . plugin . settings . maxMatchesPerTemplateDefault = numValue ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
2026-02-06 11:40:44 +01:00
// 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 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
2026-01-15 11:34:29 +01:00
new Setting ( containerEl )
. setName ( "Max hops" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Maximale Anzahl von Hops (Schritten) bei der Graph-Durchquerung. Bestimmt, wie tief der Graph durchsucht wird."
)
2026-01-15 11:34:29 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-15 12:27:16 +01:00
2026-01-17 10:54:32 +01:00
// Chain direction
2026-01-15 12:27:16 +01:00
new Setting ( containerEl )
. setName ( "Chain direction" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Richtung für Chain-Traversal: 'Forward' (vorwärts), 'Backward' (rückwärts) oder 'Both' (beide Richtungen)."
)
2026-01-15 12:27:16 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-16 12:27:44 +01:00
2026-01-17 10:54:32 +01:00
// Strict mode
2026-01-16 12:27:44 +01:00
new Setting ( containerEl )
2026-01-17 10:54:32 +01:00
. setName ( "Strict mode" )
. setDesc (
"Aktiviert den strikten Validierungsmodus. Erzwingt strengere Regeln bei der Lint-Validierung."
)
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . strictMode )
2026-01-16 12:27:44 +01:00
. onChange ( async ( value ) = > {
2026-01-17 10:54:32 +01:00
this . plugin . settings . strictMode = value ;
2026-01-16 12:27:44 +01:00
await this . plugin . saveSettings ( ) ;
} )
2026-01-17 10:54:32 +01:00
) ;
// 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."
2026-01-16 12:27:44 +01:00
)
2026-01-17 10:54:32 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . showCanonicalHints )
. onChange ( async ( value ) = > {
this . plugin . settings . showCanonicalHints = value ;
await this . plugin . saveSettings ( ) ;
2026-01-16 12:27:44 +01:00
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 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
2026-01-16 12:27:44 +01:00
new Setting ( containerEl )
. setName ( "Auto-start interview on create" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Startet automatisch den Interview-Wizard, wenn eine neue Notiz über ein Profil erstellt wird."
)
2026-01-16 12:27:44 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . autoStartInterviewOnCreate )
. onChange ( async ( value ) = > {
this . plugin . settings . autoStartInterviewOnCreate = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 10:54:32 +01:00
// 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
2026-01-16 12:27:44 +01:00
new Setting ( containerEl )
. setName ( "Intercept unresolved link clicks" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Aktiviert die Abfangen von Klicks auf nicht aufgelöste interne Links. Öffnet die Profilauswahl, wenn auf einen nicht existierenden Link geklickt wird."
)
2026-01-16 12:27:44 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . interceptUnresolvedLinkClicks )
. onChange ( async ( value ) = > {
this . plugin . settings . interceptUnresolvedLinkClicks = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 07:27:11 +01:00
2026-01-17 10:54:32 +01:00
// Auto-start interview on unresolved click
2026-01-17 10:07:07 +01:00
new Setting ( containerEl )
. setName ( "Auto-start interview on unresolved link click" )
2026-01-17 10:54:32 +01:00
. 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."
)
2026-01-17 10:07:07 +01:00
. 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)" )
2026-01-17 10:54:32 +01:00
. 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)."
)
2026-01-17 10:07:07 +01:00
. 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)" )
2026-01-17 10:54:32 +01:00
. 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."
)
2026-01-17 10:07:07 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 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" ,
} ) ;
2026-01-17 10:07:07 +01:00
// Wait for first modify after create
new Setting ( containerEl )
. setName ( "Wait for first modify after create" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Wartet, bis Templater die Datei modifiziert hat, bevor der Wizard gestartet wird. Empfohlen, wenn Templater verwendet wird, um Konflikte zu vermeiden."
)
2026-01-17 10:07:07 +01:00
. 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)" )
2026-01-17 10:54:32 +01:00
. 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."
)
2026-01-17 10:07:07 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 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" ,
} ) ;
2026-01-17 10:07:07 +01:00
// Adopt new notes in editor
new Setting ( containerEl )
. setName ( "Adopt new notes in editor" )
2026-01-17 10:54:32 +01:00
. 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."
)
2026-01-17 10:07:07 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Maximale Inhaltslänge (in Zeichen), um eine Notiz als Übernahme-Kandidat zu betrachten. Leere oder sehr kurze Notizen werden bevorzugt übernommen."
)
2026-01-17 10:07:07 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-17 10:11:30 +01:00
// Adopt confirm mode
new Setting ( containerEl )
. setName ( "Adopt confirm mode" )
2026-01-17 10:54:32 +01:00
. 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)."
)
2026-01-17 10:11:30 +01:00
. 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)" )
2026-01-17 10:54:32 +01:00
. 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."
)
2026-01-17 10:11:30 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 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" ,
} ) ;
2026-01-17 07:27:11 +01:00
// Wrapper callout type
new Setting ( containerEl )
. setName ( "Mapping wrapper callout type" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Callout-Typ für den Mapping-Wrapper (z.B. 'abstract', 'info', 'note'). Bestimmt das visuelle Erscheinungsbild des Mapping-Blocks."
)
2026-01-17 07:27:11 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Titel-Text für den Mapping-Wrapper-Callout. Wird als Überschrift des Mapping-Blocks angezeigt."
)
2026-01-17 07:27:11 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Startet mit dem Mapping-Wrapper-Callout eingeklappt (collapsed). Reduziert visuellen Lärm in der Notiz."
)
2026-01-17 07:27:11 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. 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)."
)
2026-01-17 07:27:11 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// 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 ( ) ;
} )
) ;
2026-01-17 07:27:11 +01:00
// Allow overwrite existing mappings
new Setting ( containerEl )
. setName ( "Allow overwrite existing mappings" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Wenn aktiviert, wird vor dem Überschreiben bestehender Edge-Typ-Zuordnungen eine Bestätigung angefordert. Wenn deaktiviert, werden bestehende Mappings immer beibehalten."
)
2026-01-17 07:27:11 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . allowOverwriteExistingMappings )
. onChange ( async ( value ) = > {
this . plugin . settings . allowOverwriteExistingMappings = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 08:01:04 +01:00
2026-01-17 11:54:14 +01:00
// 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
2026-02-05 11:41:15 +01:00
// 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
2026-01-17 10:54:32 +01:00
// ============================================
containerEl . createEl ( "h3" , { text : "🐛 Debug & Development" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für Debugging und Entwicklung." ,
cls : "setting-item-description" ,
} ) ;
// Debug logging
2026-01-17 08:01:04 +01:00
new Setting ( containerEl )
2026-01-17 10:54:32 +01:00
. 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 )
2026-01-17 08:01:04 +01:00
. onChange ( async ( value ) = > {
2026-01-17 10:54:32 +01:00
this . plugin . settings . debugLogging = value ;
2026-01-17 08:01:04 +01:00
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-18 13:45:38 +01:00
// ============================================
2026-02-05 11:41:15 +01:00
// 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 . module LogLevels ) {
this . plugin . settings . module LogLevels = { } ;
}
// 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 . module LogLevels [ 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 . module LogLevels [ module .name ] ;
} else {
this . plugin . settings . module LogLevels [ module .name ] = value ;
}
await this . plugin . saveSettings ( ) ;
// Update logger registry immediately
const { initializeLogging } = await import ( "../utils/logger" ) ;
initializeLogging ( this . plugin . settings . module LogLevels ) ;
}
} ) ;
} ) ;
}
// 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 . module LogLevels = { } ;
await this . plugin . saveSettings ( ) ;
// Update logger registry
const { initializeLogging } = await import ( "../utils/logger" ) ;
initializeLogging ( { } ) ;
// Refresh settings UI
this . display ( ) ;
} )
) ;
// ============================================
// 10. Export
2026-01-18 13:45:38 +01:00
// ============================================
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 ) ;
}
} )
) ;
2026-01-18 21:10:33 +01:00
// ============================================
2026-02-05 11:41:15 +01:00
// 11. Fix Actions Settings
2026-01-18 21:10:33 +01:00
// ============================================
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 ( ) ;
} )
) ;
2026-01-15 11:34:29 +01:00
}
}