feat: restructure generator modal into two-zone layout
Split the single-column generator modal into a Selection zone (what to include) and Configuration zone (how to output) using CSS grid, with a full-width footer for action buttons. Adds note count indicator and token estimate from existing context files. Responsive: side-by-side on wide viewports, stacked on narrow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a6cc173293
commit
08b8a180ca
2 changed files with 261 additions and 149 deletions
339
src/generator.ts
339
src/generator.ts
|
|
@ -1,6 +1,7 @@
|
||||||
import { App, Modal, Notice, Setting, TFolder } from 'obsidian';
|
import { App, Modal, Notice, Setting, TFile, TFolder } from 'obsidian';
|
||||||
import ClaudeContextPlugin from './main';
|
import ClaudeContextPlugin from './main';
|
||||||
import { createFreetextSource, SourcePosition } from './sources';
|
import { createFreetextSource, SourcePosition } from './sources';
|
||||||
|
import { estimateTokens } from './history';
|
||||||
import { OutputTarget, getTargetIcon, formatTokenCount } from './targets';
|
import { OutputTarget, getTargetIcon, formatTokenCount } from './targets';
|
||||||
|
|
||||||
interface FolderConfig {
|
interface FolderConfig {
|
||||||
|
|
@ -103,77 +104,66 @@ export class ContextGeneratorModal extends Modal {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
contentEl.addClass('claude-context-generator');
|
contentEl.addClass('claude-context-generator');
|
||||||
contentEl.style.maxHeight = '80vh';
|
this.modalEl.addClass('cc-gen-modal');
|
||||||
contentEl.style.overflow = 'auto';
|
|
||||||
|
|
||||||
contentEl.createEl('h2', { text: 'Context Generator' });
|
contentEl.createEl('h2', { text: 'Context Generator' });
|
||||||
|
|
||||||
// === BASIC SECTION ===
|
// === TWO-ZONE GRID LAYOUT ===
|
||||||
contentEl.createEl('h3', { text: 'General' });
|
const layout = contentEl.createDiv({ cls: 'cc-gen-layout' });
|
||||||
|
const selectionZone = layout.createDiv({ cls: 'cc-gen-zone cc-gen-selection' });
|
||||||
|
const configZone = layout.createDiv({ cls: 'cc-gen-zone cc-gen-configuration' });
|
||||||
|
|
||||||
new Setting(contentEl)
|
// =============================================
|
||||||
.setName('Vault description')
|
// SELECTION ZONE (left) – "What to include"
|
||||||
.setDesc('What is this vault used for?')
|
// =============================================
|
||||||
.addTextArea(text => {
|
|
||||||
text.setPlaceholder('e.g. Personal Zettelkasten for development and knowledge management')
|
|
||||||
.setValue(this.config.vaultDescription)
|
|
||||||
.onChange(v => this.config.vaultDescription = v);
|
|
||||||
text.inputEl.rows = 2;
|
|
||||||
text.inputEl.style.width = '100%';
|
|
||||||
});
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
// === FILES TO GENERATE ===
|
||||||
.setName('Language')
|
selectionZone.createEl('h3', { text: 'Files to generate' });
|
||||||
.addDropdown(dropdown => dropdown
|
|
||||||
.addOption('english', 'English')
|
|
||||||
.addOption('german', 'Deutsch')
|
|
||||||
.setValue(this.config.language)
|
|
||||||
.onChange(v => this.config.language = v));
|
|
||||||
|
|
||||||
// === FORMATTING SECTION ===
|
new Setting(selectionZone)
|
||||||
contentEl.createEl('h3', { text: 'Formatting' });
|
.setName('conventions.md')
|
||||||
|
.addToggle(toggle => toggle
|
||||||
|
.setValue(this.config.generateFiles.conventions)
|
||||||
|
.onChange(v => this.config.generateFiles.conventions = v));
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('File naming')
|
.setName('structure.md')
|
||||||
.addDropdown(dropdown => dropdown
|
.addToggle(toggle => toggle
|
||||||
.addOption('kebab-case', 'kebab-case')
|
.setValue(this.config.generateFiles.structure)
|
||||||
.addOption('snake_case', 'snake_case')
|
.onChange(v => this.config.generateFiles.structure = v));
|
||||||
.addOption('camelCase', 'camelCase')
|
|
||||||
.addOption('free', 'Free / no convention')
|
|
||||||
.setValue(this.config.fileNaming)
|
|
||||||
.onChange(v => this.config.fileNaming = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('Link style')
|
.setName('workflows.md')
|
||||||
.addDropdown(dropdown => dropdown
|
.addToggle(toggle => toggle
|
||||||
.addOption('wikilinks', '[[Wikilinks]]')
|
.setValue(this.config.generateFiles.workflows)
|
||||||
.addOption('markdown', '[Markdown](links)')
|
.onChange(v => this.config.generateFiles.workflows = v));
|
||||||
.setValue(this.config.linkStyle)
|
|
||||||
.onChange(v => this.config.linkStyle = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('Heading depth')
|
.setName('templates.md')
|
||||||
.addDropdown(dropdown => dropdown
|
.addToggle(toggle => toggle
|
||||||
.addOption('h2', 'H1 - H2')
|
.setValue(this.config.generateFiles.templates)
|
||||||
.addOption('h3', 'H1 - H3')
|
.onChange(v => this.config.generateFiles.templates = v));
|
||||||
.addOption('h4', 'H1 - H4')
|
|
||||||
.addOption('h6', 'Unlimited')
|
|
||||||
.setValue(this.config.headingDepth)
|
|
||||||
.onChange(v => this.config.headingDepth = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('Date format')
|
.setName('examples.md')
|
||||||
.addDropdown(dropdown => dropdown
|
.addToggle(toggle => toggle
|
||||||
.addOption('YYYY-MM-DD', 'YYYY-MM-DD (ISO)')
|
.setValue(this.config.generateFiles.examples)
|
||||||
.addOption('DD.MM.YYYY', 'DD.MM.YYYY')
|
.onChange(v => this.config.generateFiles.examples = v));
|
||||||
.addOption('MM/DD/YYYY', 'MM/DD/YYYY')
|
|
||||||
.setValue(this.config.dateFormat)
|
// === FOLDER STRUCTURE ===
|
||||||
.onChange(v => this.config.dateFormat = v));
|
selectionZone.createEl('h3', { text: 'Folder structure' });
|
||||||
|
selectionZone.createEl('p', {
|
||||||
|
text: 'Describe the purpose of your folders:',
|
||||||
|
cls: 'setting-item-description'
|
||||||
|
});
|
||||||
|
|
||||||
|
const foldersContainer = selectionZone.createDiv({ cls: 'folders-container' });
|
||||||
|
this.renderFolders(foldersContainer);
|
||||||
|
|
||||||
// === TAGS SECTION ===
|
// === TAGS SECTION ===
|
||||||
contentEl.createEl('h3', { text: 'Tags' });
|
selectionZone.createEl('h3', { text: 'Tags' });
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('Tag style')
|
.setName('Tag style')
|
||||||
.addDropdown(dropdown => dropdown
|
.addDropdown(dropdown => dropdown
|
||||||
.addOption('hierarchical', 'Hierarchical (#area/tag)')
|
.addOption('hierarchical', 'Hierarchical (#area/tag)')
|
||||||
|
|
@ -182,7 +172,7 @@ export class ContextGeneratorModal extends Modal {
|
||||||
.setValue(this.config.tagsStyle)
|
.setValue(this.config.tagsStyle)
|
||||||
.onChange(v => this.config.tagsStyle = v));
|
.onChange(v => this.config.tagsStyle = v));
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('Predefined tags')
|
.setName('Predefined tags')
|
||||||
.setDesc('Comma-separated (e.g. status/active, status/done, project)')
|
.setDesc('Comma-separated (e.g. status/active, status/done, project)')
|
||||||
.addText(text => text
|
.addText(text => text
|
||||||
|
|
@ -193,9 +183,9 @@ export class ContextGeneratorModal extends Modal {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// === FRONTMATTER SECTION ===
|
// === FRONTMATTER SECTION ===
|
||||||
contentEl.createEl('h3', { text: 'Frontmatter' });
|
selectionZone.createEl('h3', { text: 'Frontmatter' });
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(selectionZone)
|
||||||
.setName('Frontmatter fields')
|
.setName('Frontmatter fields')
|
||||||
.setDesc('Comma-separated (e.g. date, tags, aliases, status)')
|
.setDesc('Comma-separated (e.g. date, tags, aliases, status)')
|
||||||
.addText(text => text
|
.addText(text => text
|
||||||
|
|
@ -204,10 +194,98 @@ export class ContextGeneratorModal extends Modal {
|
||||||
this.config.frontmatterFields = v.split(',').map(s => s.trim()).filter(s => s);
|
this.config.frontmatterFields = v.split(',').map(s => s.trim()).filter(s => s);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// === RULES SECTION ===
|
// === NOTE TEMPLATES ===
|
||||||
contentEl.createEl('h3', { text: 'Rules' });
|
selectionZone.createEl('h3', { text: 'Note templates' });
|
||||||
|
|
||||||
new Setting(contentEl)
|
const templatesContainer = selectionZone.createDiv({ cls: 'templates-container' });
|
||||||
|
this.renderTemplates(templatesContainer);
|
||||||
|
|
||||||
|
new Setting(selectionZone)
|
||||||
|
.addButton(btn => btn
|
||||||
|
.setButtonText('+ Add template')
|
||||||
|
.onClick(() => {
|
||||||
|
this.config.templates.push({ name: '', folder: '', tag: '' });
|
||||||
|
this.renderTemplates(templatesContainer);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// === NOTE COUNT INDICATOR ===
|
||||||
|
const fileCount = this.app.vault.getFiles().length;
|
||||||
|
const folderCount = this.config.folders.length;
|
||||||
|
selectionZone.createDiv({
|
||||||
|
cls: 'cc-gen-stat',
|
||||||
|
text: `${folderCount} folders, ${fileCount} total files in vault`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// CONFIGURATION ZONE (right) – "How to output"
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
// === GENERAL ===
|
||||||
|
configZone.createEl('h3', { text: 'General' });
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
|
.setName('Vault description')
|
||||||
|
.setDesc('What is this vault used for?')
|
||||||
|
.addTextArea(text => {
|
||||||
|
text.setPlaceholder('e.g. Personal Zettelkasten for development and knowledge management')
|
||||||
|
.setValue(this.config.vaultDescription)
|
||||||
|
.onChange(v => this.config.vaultDescription = v);
|
||||||
|
text.inputEl.rows = 2;
|
||||||
|
text.inputEl.style.width = '100%';
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
|
.setName('Language')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
|
.addOption('english', 'English')
|
||||||
|
.addOption('german', 'Deutsch')
|
||||||
|
.setValue(this.config.language)
|
||||||
|
.onChange(v => this.config.language = v));
|
||||||
|
|
||||||
|
// === FORMATTING ===
|
||||||
|
configZone.createEl('h3', { text: 'Formatting' });
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
|
.setName('File naming')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
|
.addOption('kebab-case', 'kebab-case')
|
||||||
|
.addOption('snake_case', 'snake_case')
|
||||||
|
.addOption('camelCase', 'camelCase')
|
||||||
|
.addOption('free', 'Free / no convention')
|
||||||
|
.setValue(this.config.fileNaming)
|
||||||
|
.onChange(v => this.config.fileNaming = v));
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
|
.setName('Link style')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
|
.addOption('wikilinks', '[[Wikilinks]]')
|
||||||
|
.addOption('markdown', '[Markdown](links)')
|
||||||
|
.setValue(this.config.linkStyle)
|
||||||
|
.onChange(v => this.config.linkStyle = v));
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
|
.setName('Heading depth')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
|
.addOption('h2', 'H1 - H2')
|
||||||
|
.addOption('h3', 'H1 - H3')
|
||||||
|
.addOption('h4', 'H1 - H4')
|
||||||
|
.addOption('h6', 'Unlimited')
|
||||||
|
.setValue(this.config.headingDepth)
|
||||||
|
.onChange(v => this.config.headingDepth = v));
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
|
.setName('Date format')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
|
.addOption('YYYY-MM-DD', 'YYYY-MM-DD (ISO)')
|
||||||
|
.addOption('DD.MM.YYYY', 'DD.MM.YYYY')
|
||||||
|
.addOption('MM/DD/YYYY', 'MM/DD/YYYY')
|
||||||
|
.setValue(this.config.dateFormat)
|
||||||
|
.onChange(v => this.config.dateFormat = v));
|
||||||
|
|
||||||
|
// === RULES ===
|
||||||
|
configZone.createEl('h3', { text: 'Rules' });
|
||||||
|
|
||||||
|
new Setting(configZone)
|
||||||
.setName('Custom rules')
|
.setName('Custom rules')
|
||||||
.setDesc('One rule per line')
|
.setDesc('One rule per line')
|
||||||
.addTextArea(text => {
|
.addTextArea(text => {
|
||||||
|
|
@ -220,7 +298,7 @@ export class ContextGeneratorModal extends Modal {
|
||||||
text.inputEl.style.width = '100%';
|
text.inputEl.style.width = '100%';
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(configZone)
|
||||||
.setName('Forbidden actions')
|
.setName('Forbidden actions')
|
||||||
.setDesc('Comma-separated (e.g. .obsidian/, certain folders)')
|
.setDesc('Comma-separated (e.g. .obsidian/, certain folders)')
|
||||||
.addText(text => text
|
.addText(text => text
|
||||||
|
|
@ -229,67 +307,10 @@ export class ContextGeneratorModal extends Modal {
|
||||||
this.config.forbiddenActions = v.split(',').map(s => s.trim()).filter(s => s);
|
this.config.forbiddenActions = v.split(',').map(s => s.trim()).filter(s => s);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// === STRUCTURE SECTION ===
|
// === ADDITIONAL CONTEXT ===
|
||||||
contentEl.createEl('h3', { text: 'Folder structure' });
|
configZone.createEl('h3', { text: 'Additional Context (this session)' });
|
||||||
contentEl.createEl('p', {
|
|
||||||
text: 'Describe the purpose of your folders:',
|
|
||||||
cls: 'setting-item-description'
|
|
||||||
});
|
|
||||||
|
|
||||||
const foldersContainer = contentEl.createDiv({ cls: 'folders-container' });
|
new Setting(configZone)
|
||||||
this.renderFolders(foldersContainer);
|
|
||||||
|
|
||||||
// === TEMPLATES SECTION ===
|
|
||||||
contentEl.createEl('h3', { text: 'Note templates' });
|
|
||||||
|
|
||||||
const templatesContainer = contentEl.createDiv({ cls: 'templates-container' });
|
|
||||||
this.renderTemplates(templatesContainer);
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.addButton(btn => btn
|
|
||||||
.setButtonText('+ Add template')
|
|
||||||
.onClick(() => {
|
|
||||||
this.config.templates.push({ name: '', folder: '', tag: '' });
|
|
||||||
this.renderTemplates(templatesContainer);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// === FILES TO GENERATE ===
|
|
||||||
contentEl.createEl('h3', { text: 'Files to generate' });
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.setName('conventions.md')
|
|
||||||
.addToggle(toggle => toggle
|
|
||||||
.setValue(this.config.generateFiles.conventions)
|
|
||||||
.onChange(v => this.config.generateFiles.conventions = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.setName('structure.md')
|
|
||||||
.addToggle(toggle => toggle
|
|
||||||
.setValue(this.config.generateFiles.structure)
|
|
||||||
.onChange(v => this.config.generateFiles.structure = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.setName('workflows.md')
|
|
||||||
.addToggle(toggle => toggle
|
|
||||||
.setValue(this.config.generateFiles.workflows)
|
|
||||||
.onChange(v => this.config.generateFiles.workflows = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.setName('templates.md')
|
|
||||||
.addToggle(toggle => toggle
|
|
||||||
.setValue(this.config.generateFiles.templates)
|
|
||||||
.onChange(v => this.config.generateFiles.templates = v));
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.setName('examples.md')
|
|
||||||
.addToggle(toggle => toggle
|
|
||||||
.setValue(this.config.generateFiles.examples)
|
|
||||||
.onChange(v => this.config.generateFiles.examples = v));
|
|
||||||
|
|
||||||
// === ADDITIONAL CONTEXT SECTION ===
|
|
||||||
contentEl.createEl('h3', { text: 'Additional Context (this session)' });
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
|
||||||
.setName('Temporary freetext')
|
.setName('Temporary freetext')
|
||||||
.setDesc('Add context for this session only')
|
.setDesc('Add context for this session only')
|
||||||
.addTextArea(text => {
|
.addTextArea(text => {
|
||||||
|
|
@ -300,7 +321,7 @@ export class ContextGeneratorModal extends Modal {
|
||||||
text.inputEl.style.width = '100%';
|
text.inputEl.style.width = '100%';
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(configZone)
|
||||||
.setName('Position')
|
.setName('Position')
|
||||||
.addDropdown(dropdown => dropdown
|
.addDropdown(dropdown => dropdown
|
||||||
.addOption('prefix', 'Prefix (before vault content)')
|
.addOption('prefix', 'Prefix (before vault content)')
|
||||||
|
|
@ -308,25 +329,24 @@ export class ContextGeneratorModal extends Modal {
|
||||||
.setValue(this.temporaryPosition)
|
.setValue(this.temporaryPosition)
|
||||||
.onChange(v => this.temporaryPosition = v as SourcePosition));
|
.onChange(v => this.temporaryPosition = v as SourcePosition));
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(configZone)
|
||||||
.setName('Save as default')
|
.setName('Save as default')
|
||||||
.setDesc('Save this freetext as a permanent source')
|
.setDesc('Save this freetext as a permanent source')
|
||||||
.addToggle(toggle => toggle
|
.addToggle(toggle => toggle
|
||||||
.setValue(this.saveAsDefault)
|
.setValue(this.saveAsDefault)
|
||||||
.onChange(v => this.saveAsDefault = v));
|
.onChange(v => this.saveAsDefault = v));
|
||||||
|
|
||||||
// Show saved sources count
|
|
||||||
const enabledCount = this.plugin.settings.sources.filter(s => s.enabled).length;
|
const enabledCount = this.plugin.settings.sources.filter(s => s.enabled).length;
|
||||||
const sourcesInfo = contentEl.createEl('p', {
|
const sourcesInfo = configZone.createEl('p', {
|
||||||
text: `Saved sources: ${enabledCount} enabled (manage in settings)`,
|
text: `Saved sources: ${enabledCount} enabled (manage in settings)`,
|
||||||
cls: 'setting-item-description'
|
cls: 'setting-item-description'
|
||||||
});
|
});
|
||||||
sourcesInfo.style.marginTop = '10px';
|
sourcesInfo.style.marginTop = '10px';
|
||||||
|
|
||||||
// === PROMPT TEMPLATE SECTION ===
|
// === PROMPT TEMPLATE ===
|
||||||
contentEl.createEl('h3', { text: 'Prompt Template' });
|
configZone.createEl('h3', { text: 'Prompt Template' });
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(configZone)
|
||||||
.setName('Template')
|
.setName('Template')
|
||||||
.setDesc('Wrap context with a prompt template')
|
.setDesc('Wrap context with a prompt template')
|
||||||
.addDropdown(dropdown => {
|
.addDropdown(dropdown => {
|
||||||
|
|
@ -340,19 +360,18 @@ export class ContextGeneratorModal extends Modal {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show template count
|
|
||||||
const templateCount = this.plugin.settings.promptTemplates.length;
|
const templateCount = this.plugin.settings.promptTemplates.length;
|
||||||
const templateInfo = contentEl.createEl('p', {
|
const templateInfo = configZone.createEl('p', {
|
||||||
text: `${templateCount} template(s) available (manage in settings)`,
|
text: `${templateCount} template(s) available (manage in settings)`,
|
||||||
cls: 'setting-item-description'
|
cls: 'setting-item-description'
|
||||||
});
|
});
|
||||||
templateInfo.style.marginTop = '5px';
|
templateInfo.style.marginTop = '5px';
|
||||||
|
|
||||||
// === OUTPUT TARGETS SECTION ===
|
// === OUTPUT TARGETS ===
|
||||||
if (this.plugin.settings.targets.length > 0) {
|
if (this.plugin.settings.targets.length > 0) {
|
||||||
contentEl.createEl('h3', { text: 'Output Targets' });
|
configZone.createEl('h3', { text: 'Output Targets' });
|
||||||
|
|
||||||
const targetsContainer = contentEl.createDiv({ cls: 'targets-checkboxes' });
|
const targetsContainer = configZone.createDiv({ cls: 'targets-checkboxes' });
|
||||||
targetsContainer.style.display = 'flex';
|
targetsContainer.style.display = 'flex';
|
||||||
targetsContainer.style.flexDirection = 'column';
|
targetsContainer.style.flexDirection = 'column';
|
||||||
targetsContainer.style.gap = '8px';
|
targetsContainer.style.gap = '8px';
|
||||||
|
|
@ -385,7 +404,6 @@ export class ContextGeneratorModal extends Modal {
|
||||||
const label = row.createEl('span', { text: target.name });
|
const label = row.createEl('span', { text: target.name });
|
||||||
label.style.flex = '1';
|
label.style.flex = '1';
|
||||||
|
|
||||||
// Primary indicator
|
|
||||||
if (target.id === primaryId) {
|
if (target.id === primaryId) {
|
||||||
const badge = row.createEl('span', { text: 'clipboard' });
|
const badge = row.createEl('span', { text: 'clipboard' });
|
||||||
badge.style.padding = '2px 6px';
|
badge.style.padding = '2px 6px';
|
||||||
|
|
@ -408,24 +426,29 @@ export class ContextGeneratorModal extends Modal {
|
||||||
tokenInfo.style.color = 'var(--text-muted)';
|
tokenInfo.style.color = 'var(--text-muted)';
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetsInfo = contentEl.createEl('p', {
|
const targetsInfo = configZone.createEl('p', {
|
||||||
text: 'Primary target is copied to clipboard. Secondary targets are saved as files in the output folder.',
|
text: 'Primary target is copied to clipboard. Secondary targets are saved as files in the output folder.',
|
||||||
cls: 'setting-item-description'
|
cls: 'setting-item-description'
|
||||||
});
|
});
|
||||||
targetsInfo.style.marginTop = '5px';
|
targetsInfo.style.marginTop = '5px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy buttons
|
// === TOKEN ESTIMATE ===
|
||||||
const copyButtonContainer = contentEl.createDiv();
|
this.renderTokenEstimate(configZone);
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// FOOTER (full-width, below both zones)
|
||||||
|
// =============================================
|
||||||
|
const footer = contentEl.createDiv({ cls: 'cc-gen-footer' });
|
||||||
|
|
||||||
|
const copyButtonContainer = footer.createDiv();
|
||||||
copyButtonContainer.style.display = 'flex';
|
copyButtonContainer.style.display = 'flex';
|
||||||
copyButtonContainer.style.gap = '10px';
|
copyButtonContainer.style.gap = '10px';
|
||||||
copyButtonContainer.style.marginTop = '10px';
|
|
||||||
|
|
||||||
new Setting(copyButtonContainer)
|
new Setting(copyButtonContainer)
|
||||||
.addButton(btn => btn
|
.addButton(btn => btn
|
||||||
.setButtonText('Copy Context Now')
|
.setButtonText('Copy Context Now')
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
// Save freetext if requested
|
|
||||||
if (this.saveAsDefault && this.temporaryFreetext.trim()) {
|
if (this.saveAsDefault && this.temporaryFreetext.trim()) {
|
||||||
const source = createFreetextSource(
|
const source = createFreetextSource(
|
||||||
'Generator Context',
|
'Generator Context',
|
||||||
|
|
@ -437,12 +460,10 @@ export class ContextGeneratorModal extends Modal {
|
||||||
new Notice('Freetext saved as default source');
|
new Notice('Freetext saved as default source');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get selected targets
|
|
||||||
const selectedTargets = this.plugin.settings.targets.filter(
|
const selectedTargets = this.plugin.settings.targets.filter(
|
||||||
t => this.selectedTargetIds.has(t.id)
|
t => this.selectedTargetIds.has(t.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copy context with selected template and targets
|
|
||||||
await this.plugin.copyContextToClipboard(
|
await this.plugin.copyContextToClipboard(
|
||||||
false,
|
false,
|
||||||
this.temporaryFreetext,
|
this.temporaryFreetext,
|
||||||
|
|
@ -458,10 +479,9 @@ export class ContextGeneratorModal extends Modal {
|
||||||
this.plugin.openFileSelector();
|
this.plugin.openFileSelector();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// === GENERATE BUTTON ===
|
footer.createEl('hr');
|
||||||
contentEl.createEl('hr');
|
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(footer)
|
||||||
.addButton(btn => btn
|
.addButton(btn => btn
|
||||||
.setButtonText('Generate')
|
.setButtonText('Generate')
|
||||||
.setCta()
|
.setCta()
|
||||||
|
|
@ -532,6 +552,27 @@ export class ContextGeneratorModal extends Modal {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async renderTokenEstimate(container: HTMLElement) {
|
||||||
|
const contextFolder = this.plugin.settings.contextFolder;
|
||||||
|
const folder = this.app.vault.getAbstractFileByPath(contextFolder);
|
||||||
|
let totalChars = 0;
|
||||||
|
|
||||||
|
if (folder instanceof TFolder) {
|
||||||
|
for (const child of folder.children) {
|
||||||
|
if (child instanceof TFile && child.extension === 'md') {
|
||||||
|
const content = await this.app.vault.cachedRead(child);
|
||||||
|
totalChars += content.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = Math.ceil(totalChars / 4);
|
||||||
|
container.createDiv({
|
||||||
|
cls: 'cc-gen-stat',
|
||||||
|
text: `Estimated tokens: ~${tokens.toLocaleString()} (from existing context files)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
scanVaultStructure(): string[] {
|
scanVaultStructure(): string[] {
|
||||||
const root = this.app.vault.getRoot();
|
const root = this.app.vault.getRoot();
|
||||||
const folders: string[] = [];
|
const folders: string[] = [];
|
||||||
|
|
|
||||||
71
styles.css
71
styles.css
|
|
@ -79,3 +79,74 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Generator modal */
|
||||||
|
.cc-gen-modal {
|
||||||
|
width: 70vw;
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-modal .modal-content {
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-zone {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 60vh;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-selection {
|
||||||
|
border-right: 1px solid var(--background-modifier-border);
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-zone h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-footer {
|
||||||
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
|
padding-top: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-gen-stat {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--background-secondary);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stack on narrow viewports */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cc-gen-modal {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
.cc-gen-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.cc-gen-selection {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
.cc-gen-zone {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue