diff --git a/src/settings.ts b/src/settings.ts index 23ce77c..4728b48 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -24,6 +24,7 @@ export interface ClaudeContextSettings { primaryTargetId: string | null; targetOutputFolder: string; lastSettingsTab: string; + collapsedSections: string[]; } export const DEFAULT_SETTINGS: ClaudeContextSettings = { @@ -42,6 +43,7 @@ export const DEFAULT_SETTINGS: ClaudeContextSettings = { primaryTargetId: null, targetOutputFolder: '_claude/outputs', lastSettingsTab: 'general', + collapsedSections: [], }; export type SettingsTabId = 'general' | 'sources' | 'templates' | 'output' | 'history'; @@ -54,6 +56,39 @@ const SETTINGS_TABS: { id: SettingsTabId; label: string }[] = [ { id: 'history', label: 'History' }, ]; +class CollapsibleSection { + contentEl: HTMLElement; + + constructor( + parent: HTMLElement, + id: string, + title: string, + plugin: ClaudeContextPlugin, + ) { + const isCollapsed = plugin.settings.collapsedSections.includes(id); + + const wrapper = parent.createDiv({ cls: `cc-section${isCollapsed ? ' is-collapsed' : ''}` }); + + const header = wrapper.createDiv({ cls: 'cc-section-header' }); + header.createEl('span', { text: title, cls: 'cc-section-title' }); + header.createEl('span', { text: '\u203A', cls: 'cc-section-chevron' }); + + this.contentEl = wrapper.createDiv({ cls: 'cc-section-content' }); + + header.addEventListener('click', async () => { + const nowCollapsed = wrapper.hasClass('is-collapsed'); + if (nowCollapsed) { + wrapper.removeClass('is-collapsed'); + plugin.settings.collapsedSections = plugin.settings.collapsedSections.filter(s => s !== id); + } else { + wrapper.addClass('is-collapsed'); + plugin.settings.collapsedSections.push(id); + } + await plugin.saveSettings(); + }); + } +} + export class ClaudeContextSettingTab extends PluginSettingTab { plugin: ClaudeContextPlugin; private activeTab: SettingsTabId; @@ -171,13 +206,11 @@ export class ClaudeContextSettingTab extends PluginSettingTab { // === TAB: Sources === private renderSourcesTab(el: HTMLElement) { - const desc = el.createEl('p', { - text: 'Add additional context sources like freetext, external files, or shell command output.', - cls: 'setting-item-description' - }); - desc.style.marginBottom = '10px'; + // Section: Configured sources + const sourcesSection = new CollapsibleSection(el, 'sources-list', 'Configured sources', this.plugin); + const sc = sourcesSection.contentEl; - const buttonContainer = el.createDiv({ cls: 'sources-button-container' }); + const buttonContainer = sc.createDiv({ cls: 'sources-button-container' }); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '8px'; buttonContainer.style.marginBottom = '15px'; @@ -197,10 +230,13 @@ export class ClaudeContextSettingTab extends PluginSettingTab { new SourceModal(this.app, this.plugin, 'shell', null, () => this.display()).open(); }); - const sourcesContainer = el.createDiv({ cls: 'sources-list-container' }); + const sourcesContainer = sc.createDiv({ cls: 'sources-list-container' }); this.renderSourcesList(sourcesContainer); - new Setting(el) + // Section: Options + const optionsSection = new CollapsibleSection(el, 'sources-options', 'Options', this.plugin); + + new Setting(optionsSection.contentEl) .setName('Show source labels') .setDesc('Add position and name labels to source output') .addToggle(toggle => toggle @@ -214,13 +250,11 @@ export class ClaudeContextSettingTab extends PluginSettingTab { // === TAB: Templates === private renderTemplatesTab(el: HTMLElement) { - const desc = el.createEl('p', { - text: 'Create reusable prompt templates that wrap around your context.', - cls: 'setting-item-description' - }); - desc.style.marginBottom = '10px'; + // Section: Manage templates + const manageSection = new CollapsibleSection(el, 'templates-list', 'Manage templates', this.plugin); + const mc = manageSection.contentEl; - const buttonContainer = el.createDiv({ cls: 'templates-button-container' }); + const buttonContainer = mc.createDiv({ cls: 'templates-button-container' }); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '8px'; buttonContainer.style.marginBottom = '15px'; @@ -243,7 +277,7 @@ export class ClaudeContextSettingTab extends PluginSettingTab { // Starter templates const hasStarterTemplates = this.plugin.settings.promptTemplates.some(t => t.isBuiltin); if (!hasStarterTemplates) { - const starterContainer = el.createDiv(); + const starterContainer = mc.createDiv(); starterContainer.style.padding = '10px'; starterContainer.style.backgroundColor = 'var(--background-secondary)'; starterContainer.style.borderRadius = '4px'; @@ -263,10 +297,13 @@ export class ClaudeContextSettingTab extends PluginSettingTab { }); } - const templatesContainer = el.createDiv({ cls: 'templates-list-container' }); + const templatesContainer = mc.createDiv({ cls: 'templates-list-container' }); this.renderTemplatesList(templatesContainer); - new Setting(el) + // Section: Defaults + const defaultsSection = new CollapsibleSection(el, 'templates-defaults', 'Defaults', this.plugin); + + new Setting(defaultsSection.contentEl) .setName('Default template') .setDesc('Template to use by default when copying context') .addDropdown(dropdown => { @@ -285,13 +322,11 @@ export class ClaudeContextSettingTab extends PluginSettingTab { // === TAB: Output === private renderOutputTab(el: HTMLElement) { - const desc = el.createEl('p', { - text: 'Configure multiple output formats for different LLMs. The primary target is copied to clipboard, secondary targets are saved as files.', - cls: 'setting-item-description' - }); - desc.style.marginBottom = '10px'; + // Section: Configured targets + const targetsSection = new CollapsibleSection(el, 'output-list', 'Configured targets', this.plugin); + const tc = targetsSection.contentEl; - const buttonContainer = el.createDiv({ cls: 'targets-button-container' }); + const buttonContainer = tc.createDiv({ cls: 'targets-button-container' }); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '8px'; buttonContainer.style.marginBottom = '15px'; @@ -316,10 +351,14 @@ export class ClaudeContextSettingTab extends PluginSettingTab { this.display(); }); - const targetsContainer = el.createDiv({ cls: 'targets-list-container' }); + const targetsContainer = tc.createDiv({ cls: 'targets-list-container' }); this.renderTargetsList(targetsContainer); - new Setting(el) + // Section: Output settings + const settingsSection = new CollapsibleSection(el, 'output-settings', 'Output settings', this.plugin); + const oc = settingsSection.contentEl; + + new Setting(oc) .setName('Primary target') .setDesc('This target\'s output is copied to clipboard') .addDropdown(dropdown => { @@ -334,7 +373,7 @@ export class ClaudeContextSettingTab extends PluginSettingTab { }); }); - new Setting(el) + new Setting(oc) .setName('Output folder') .setDesc('Folder for secondary target files') .addText(text => text diff --git a/styles.css b/styles.css index f18c57e..0d19e2a 100644 --- a/styles.css +++ b/styles.css @@ -31,3 +31,51 @@ border-bottom-color: var(--interactive-accent); font-weight: 600; } + +/* Collapsible sections */ +.cc-section { + margin-bottom: 8px; +} + +.cc-section-header { + display: flex; + align-items: center; + padding: 8px 4px; + cursor: pointer; + border-radius: 4px; + user-select: none; +} + +.cc-section-header:hover { + background: var(--background-modifier-hover); +} + +.cc-section-title { + flex: 1; + font-weight: 600; + font-size: 14px; +} + +.cc-section-chevron { + font-size: 16px; + transition: transform 0.2s ease; + transform: rotate(90deg); +} + +.cc-section.is-collapsed .cc-section-chevron { + transform: rotate(0deg); +} + +.cc-section-content { + max-height: 2000px; + overflow: hidden; + transition: max-height 0.25s ease, opacity 0.2s ease; + opacity: 1; + padding-top: 4px; +} + +.cc-section.is-collapsed .cc-section-content { + max-height: 0; + opacity: 0; + padding-top: 0; +}