feat: add dynamic descriptions to settings reflecting current state

Eight settings now show live contextual info (file counts, entry usage,
folder existence) that updates as values change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca G. Oelfke 2026-02-06 12:22:42 +01:00
parent 94c2822340
commit 72f64bcbd9
No known key found for this signature in database
GPG key ID: E22BABF67200F864
2 changed files with 78 additions and 17 deletions

View file

@ -1,4 +1,4 @@
import { App, Notice, PluginSettingTab, Setting } from 'obsidian'; import { App, Notice, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian';
import ClaudeContextPlugin from './main'; import ClaudeContextPlugin from './main';
import { ContextSource, getSourceIcon, SourceRegistry } from './sources'; import { ContextSource, getSourceIcon, SourceRegistry } from './sources';
import { SourceModal } from './source-modal'; import { SourceModal } from './source-modal';
@ -238,19 +238,54 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
} }
} }
private setDynamicDesc(setting: Setting, base: string, dynamic: string): void {
setting.descEl.empty();
setting.descEl.appendText(base);
if (dynamic) {
setting.descEl.createSpan({ text: ` (${dynamic})`, cls: 'cc-dynamic-desc' });
}
}
private describeContextFolder(setting: Setting, path: string): void {
const folder = this.app.vault.getAbstractFileByPath(path);
let count = 0;
if (folder instanceof TFolder) {
for (const child of folder.children) {
if (child instanceof TFile && child.extension === 'md') count++;
}
}
this.setDynamicDesc(setting, 'Folder containing your context files',
count > 0 ? `${count} .md file${count !== 1 ? 's' : ''}` : '');
}
private describeOutputFolder(setting: Setting, path: string): void {
const exists = this.app.vault.getAbstractFileByPath(path) instanceof TFolder;
this.setDynamicDesc(setting, 'Folder for secondary target files',
exists ? 'folder exists' : '');
}
private countHistoryEntries(): number {
const folder = this.app.vault.getAbstractFileByPath(this.plugin.settings.history.storageFolder);
if (folder instanceof TFolder) {
return folder.children.filter(c => c instanceof TFile).length;
}
return 0;
}
// === TAB: General === // === TAB: General ===
private renderGeneralTab(el: HTMLElement) { private renderGeneralTab(el: HTMLElement) {
new Setting(el) const contextFolderSetting = new Setting(el)
.setName('Context folder') .setName('Context folder')
.setDesc('Folder containing your context files')
.addText(text => text .addText(text => text
.setPlaceholder('_claude') .setPlaceholder('_claude')
.setValue(this.plugin.settings.contextFolder) .setValue(this.plugin.settings.contextFolder)
.onChange(async (value) => { .onChange(async (value) => {
this.plugin.settings.contextFolder = value; this.plugin.settings.contextFolder = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
this.describeContextFolder(contextFolderSetting, value);
})); }));
this.describeContextFolder(contextFolderSetting, this.plugin.settings.contextFolder);
new Setting(el) new Setting(el)
.setName('Separator') .setName('Separator')
@ -293,9 +328,8 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
new Setting(el) const excludedSetting = new Setting(el)
.setName('Excluded files') .setName('Excluded files')
.setDesc('Comma-separated filenames to exclude (e.g. "examples.md, drafts.md")')
.addText(text => text .addText(text => text
.setPlaceholder('file1.md, file2.md') .setPlaceholder('file1.md, file2.md')
.setValue(this.plugin.settings.excludedFiles.join(', ')) .setValue(this.plugin.settings.excludedFiles.join(', '))
@ -305,7 +339,13 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
.map(s => s.trim()) .map(s => s.trim())
.filter(s => s.length > 0); .filter(s => s.length > 0);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
const n = this.plugin.settings.excludedFiles.length;
this.setDynamicDesc(excludedSetting, 'Comma-separated filenames to exclude',
n > 0 ? `${n} excluded` : '');
})); }));
const excludedCount = this.plugin.settings.excludedFiles.length;
this.setDynamicDesc(excludedSetting, 'Comma-separated filenames to exclude',
excludedCount > 0 ? `${excludedCount} excluded` : '');
} }
// === TAB: Sources === // === TAB: Sources ===
@ -341,15 +381,17 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
// Section: Options // Section: Options
const optionsSection = new CollapsibleSection(el, 'sources-options', 'Options', this.plugin); const optionsSection = new CollapsibleSection(el, 'sources-options', 'Options', this.plugin);
new Setting(optionsSection.contentEl) const sourceLabelsSetting = new Setting(optionsSection.contentEl)
.setName('Show source labels') .setName('Show source labels')
.setDesc('Add position and name labels to source output')
.addToggle(toggle => toggle .addToggle(toggle => toggle
.setValue(this.plugin.settings.showSourceLabels) .setValue(this.plugin.settings.showSourceLabels)
.onChange(async (value) => { .onChange(async (value) => {
this.plugin.settings.showSourceLabels = value; this.plugin.settings.showSourceLabels = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
const enabledSources = this.plugin.settings.sources.filter(s => s.enabled).length;
this.setDynamicDesc(sourceLabelsSetting, 'Add position and name labels to source output',
`${enabledSources} source${enabledSources !== 1 ? 's' : ''} enabled`);
} }
// === TAB: Templates === // === TAB: Templates ===
@ -408,9 +450,8 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
// Section: Defaults // Section: Defaults
const defaultsSection = new CollapsibleSection(el, 'templates-defaults', 'Defaults', this.plugin); const defaultsSection = new CollapsibleSection(el, 'templates-defaults', 'Defaults', this.plugin);
new Setting(defaultsSection.contentEl) const defaultTemplateSetting = new Setting(defaultsSection.contentEl)
.setName('Default template') .setName('Default template')
.setDesc('Template to use by default when copying context')
.addDropdown(dropdown => { .addDropdown(dropdown => {
dropdown.addOption('', 'None (plain context)'); dropdown.addOption('', 'None (plain context)');
for (const template of this.plugin.settings.promptTemplates) { for (const template of this.plugin.settings.promptTemplates) {
@ -422,6 +463,8 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}); });
this.setDynamicDesc(defaultTemplateSetting, 'Template to use by default when copying context',
`${this.plugin.settings.promptTemplates.length} available`);
} }
// === TAB: Output === // === TAB: Output ===
@ -463,9 +506,8 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
const settingsSection = new CollapsibleSection(el, 'output-settings', 'Output settings', this.plugin); const settingsSection = new CollapsibleSection(el, 'output-settings', 'Output settings', this.plugin);
const oc = settingsSection.contentEl; const oc = settingsSection.contentEl;
new Setting(oc) const primaryTargetSetting = new Setting(oc)
.setName('Primary target') .setName('Primary target')
.setDesc('This target\'s output is copied to clipboard')
.addDropdown(dropdown => { .addDropdown(dropdown => {
dropdown.addOption('', 'First enabled target'); dropdown.addOption('', 'First enabled target');
for (const target of this.plugin.settings.targets) { for (const target of this.plugin.settings.targets) {
@ -477,17 +519,22 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}); });
const enabledTargets = this.plugin.settings.targets.filter(t => t.enabled).length;
const totalTargets = this.plugin.settings.targets.length;
this.setDynamicDesc(primaryTargetSetting, 'This target\'s output is copied to clipboard',
totalTargets > 0 ? `${enabledTargets} of ${totalTargets} enabled` : '');
new Setting(oc) const outputFolderSetting = new Setting(oc)
.setName('Output folder') .setName('Output folder')
.setDesc('Folder for secondary target files')
.addText(text => text .addText(text => text
.setPlaceholder('_claude/outputs') .setPlaceholder('_claude/outputs')
.setValue(this.plugin.settings.targetOutputFolder) .setValue(this.plugin.settings.targetOutputFolder)
.onChange(async (value) => { .onChange(async (value) => {
this.plugin.settings.targetOutputFolder = value || '_claude/outputs'; this.plugin.settings.targetOutputFolder = value || '_claude/outputs';
await this.plugin.saveSettings(); await this.plugin.saveSettings();
this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder);
})); }));
this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder);
} }
// === TAB: History === // === TAB: History ===
@ -499,9 +546,8 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
}); });
desc.style.marginBottom = '10px'; desc.style.marginBottom = '10px';
new Setting(el) const enableHistorySetting = new Setting(el)
.setName('Enable history') .setName('Enable history')
.setDesc('Save generated contexts for later review and comparison')
.addToggle(toggle => toggle .addToggle(toggle => toggle
.setValue(this.plugin.settings.history.enabled) .setValue(this.plugin.settings.history.enabled)
.onChange(async (value) => { .onChange(async (value) => {
@ -509,6 +555,9 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
this.display(); this.display();
})); }));
const entryCount = this.countHistoryEntries();
this.setDynamicDesc(enableHistorySetting, 'Save generated contexts for later review and comparison',
this.plugin.settings.history.enabled && entryCount > 0 ? `${entryCount} entries stored` : '');
if (this.plugin.settings.history.enabled) { if (this.plugin.settings.history.enabled) {
new Setting(el) new Setting(el)
@ -522,9 +571,8 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
new Setting(el) const maxEntriesSetting = new Setting(el)
.setName('Maximum entries') .setName('Maximum entries')
.setDesc('Oldest entries will be deleted when limit is exceeded')
.addText(text => text .addText(text => text
.setPlaceholder('50') .setPlaceholder('50')
.setValue(String(this.plugin.settings.history.maxEntries)) .setValue(String(this.plugin.settings.history.maxEntries))
@ -533,8 +581,17 @@ export class ClaudeContextSettingTab extends PluginSettingTab {
if (!isNaN(num) && num > 0) { if (!isNaN(num) && num > 0) {
this.plugin.settings.history.maxEntries = num; this.plugin.settings.history.maxEntries = num;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
const used = this.countHistoryEntries();
this.setDynamicDesc(maxEntriesSetting, 'Oldest entries will be deleted when limit is exceeded',
`${used} of ${num} used`);
} }
})); }));
{
const used = this.countHistoryEntries();
const max = this.plugin.settings.history.maxEntries;
this.setDynamicDesc(maxEntriesSetting, 'Oldest entries will be deleted when limit is exceeded',
`${used} of ${max} used`);
}
new Setting(el) new Setting(el)
.setName('Auto-cleanup (days)') .setName('Auto-cleanup (days)')

View file

@ -41,6 +41,10 @@
padding: 20px 0; padding: 20px 0;
} }
.cc-dynamic-desc {
color: var(--text-faint);
}
/* Tab navigation for settings */ /* Tab navigation for settings */
.cc-settings-tabs { .cc-settings-tabs {
display: flex; display: flex;