feat: add multi-target export profiles for one-click multi-format export
Export Profiles bundle targets into named groups, enabling instant multi-format export (e.g. XML for Claude + Markdown for ChatGPT + Plain for Gemini) via a single "Export context (multi-target)" command. - Add ExportProfile interface, generateProfileId, BUILTIN_PROFILES - Add per-target outputPath override for file output location - Add export-multi-target command with active profile resolution - Add ExportProfileModal for creating/editing profiles - Add profile management UI in Settings > Output - Add profile dropdown in generator modal for target pre-selection - Clean up stale profile references on target deletion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
40ce34c9b4
commit
45a0de063e
6 changed files with 467 additions and 9 deletions
213
src/export-profile-modal.ts
Normal file
213
src/export-profile-modal.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import { App, Modal, Notice, Setting } from 'obsidian';
|
||||
import PromptfirePlugin from './main';
|
||||
import {
|
||||
ExportProfile,
|
||||
generateProfileId,
|
||||
getTargetIcon,
|
||||
} from './targets';
|
||||
|
||||
export class ExportProfileModal extends Modal {
|
||||
plugin: PromptfirePlugin;
|
||||
profile: ExportProfile | null;
|
||||
isNew: boolean;
|
||||
onSave: () => void;
|
||||
|
||||
// Form state
|
||||
name: string = '';
|
||||
description: string = '';
|
||||
selectedTargetIds: Set<string> = new Set();
|
||||
primaryTargetId: string | null = null;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: PromptfirePlugin,
|
||||
profile: ExportProfile | null,
|
||||
onSave: () => void
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.profile = profile;
|
||||
this.isNew = profile === null;
|
||||
this.onSave = onSave;
|
||||
|
||||
if (profile) {
|
||||
this.name = profile.name;
|
||||
this.description = profile.description || '';
|
||||
this.selectedTargetIds = new Set(profile.targetIds);
|
||||
this.primaryTargetId = profile.primaryTargetId;
|
||||
}
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('export-profile-modal');
|
||||
|
||||
contentEl.createEl('h2', { text: this.isNew ? 'Add Export Profile' : 'Edit Export Profile' });
|
||||
|
||||
// Name
|
||||
new Setting(contentEl)
|
||||
.setName('Name')
|
||||
.setDesc('Display name for this profile')
|
||||
.addText(text => text
|
||||
.setPlaceholder('e.g. Full LLM Export')
|
||||
.setValue(this.name)
|
||||
.onChange(v => this.name = v));
|
||||
|
||||
// Description
|
||||
new Setting(contentEl)
|
||||
.setName('Description')
|
||||
.setDesc('Optional description')
|
||||
.addTextArea(text => {
|
||||
text.setPlaceholder('Describe what this profile does...')
|
||||
.setValue(this.description)
|
||||
.onChange(v => this.description = v);
|
||||
text.inputEl.rows = 2;
|
||||
text.inputEl.style.width = '100%';
|
||||
});
|
||||
|
||||
// Target checklist
|
||||
contentEl.createEl('h3', { text: 'Targets' });
|
||||
|
||||
const targets = this.plugin.settings.targets;
|
||||
if (targets.length === 0) {
|
||||
contentEl.createEl('p', {
|
||||
text: 'No targets configured. Create targets first in Settings > Output.',
|
||||
cls: 'setting-item-description',
|
||||
});
|
||||
} else {
|
||||
const checklistContainer = contentEl.createDiv({ cls: 'profile-targets-checklist' });
|
||||
checklistContainer.style.display = 'flex';
|
||||
checklistContainer.style.flexDirection = 'column';
|
||||
checklistContainer.style.gap = '8px';
|
||||
checklistContainer.style.padding = '10px';
|
||||
checklistContainer.style.backgroundColor = 'var(--background-secondary)';
|
||||
checklistContainer.style.borderRadius = '4px';
|
||||
checklistContainer.style.marginBottom = '15px';
|
||||
|
||||
for (const target of targets) {
|
||||
const row = checklistContainer.createDiv();
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '10px';
|
||||
|
||||
const checkbox = row.createEl('input', { type: 'checkbox' });
|
||||
checkbox.checked = this.selectedTargetIds.has(target.id);
|
||||
checkbox.addEventListener('change', () => {
|
||||
if (checkbox.checked) {
|
||||
this.selectedTargetIds.add(target.id);
|
||||
} else {
|
||||
this.selectedTargetIds.delete(target.id);
|
||||
if (this.primaryTargetId === target.id) {
|
||||
this.primaryTargetId = null;
|
||||
}
|
||||
}
|
||||
this.refreshPrimaryDropdown();
|
||||
});
|
||||
|
||||
row.createEl('span', { text: getTargetIcon(target.format) });
|
||||
|
||||
const label = row.createEl('span', { text: target.name });
|
||||
label.style.flex = '1';
|
||||
|
||||
const tokenInfo = row.createEl('span', {
|
||||
text: `${target.maxTokens.toLocaleString()} tokens · ${target.format}`
|
||||
});
|
||||
tokenInfo.style.fontSize = '11px';
|
||||
tokenInfo.style.color = 'var(--text-muted)';
|
||||
}
|
||||
}
|
||||
|
||||
// Primary target dropdown
|
||||
this.primaryDropdownContainer = contentEl.createDiv();
|
||||
this.refreshPrimaryDropdown();
|
||||
|
||||
// Buttons
|
||||
const buttonContainer = contentEl.createDiv();
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'flex-end';
|
||||
buttonContainer.style.gap = '10px';
|
||||
buttonContainer.style.marginTop = '20px';
|
||||
|
||||
const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' });
|
||||
cancelBtn.addEventListener('click', () => this.close());
|
||||
|
||||
const saveBtn = buttonContainer.createEl('button', { text: 'Save', cls: 'mod-cta' });
|
||||
saveBtn.addEventListener('click', () => this.save());
|
||||
}
|
||||
|
||||
private primaryDropdownContainer: HTMLElement | null = null;
|
||||
|
||||
private refreshPrimaryDropdown() {
|
||||
if (!this.primaryDropdownContainer) return;
|
||||
this.primaryDropdownContainer.empty();
|
||||
|
||||
const selectedTargets = this.plugin.settings.targets.filter(
|
||||
t => this.selectedTargetIds.has(t.id)
|
||||
);
|
||||
|
||||
if (selectedTargets.length === 0) return;
|
||||
|
||||
// Validate current primary is still in selection
|
||||
if (this.primaryTargetId && !this.selectedTargetIds.has(this.primaryTargetId)) {
|
||||
this.primaryTargetId = null;
|
||||
}
|
||||
|
||||
new Setting(this.primaryDropdownContainer)
|
||||
.setName('Primary target')
|
||||
.setDesc('This target\'s output goes to clipboard; others are saved as files')
|
||||
.addDropdown(dropdown => {
|
||||
dropdown.addOption('', 'First selected target');
|
||||
for (const target of selectedTargets) {
|
||||
dropdown.addOption(target.id, target.name);
|
||||
}
|
||||
dropdown.setValue(this.primaryTargetId || '');
|
||||
dropdown.onChange(v => this.primaryTargetId = v || null);
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (!this.name.trim()) {
|
||||
new Notice('Name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTargetIds.size === 0) {
|
||||
new Notice('Select at least one target');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate primary is in selection
|
||||
if (this.primaryTargetId && !this.selectedTargetIds.has(this.primaryTargetId)) {
|
||||
this.primaryTargetId = null;
|
||||
}
|
||||
|
||||
const profileData: ExportProfile = {
|
||||
id: this.profile?.id || generateProfileId(),
|
||||
name: this.name.trim(),
|
||||
description: this.description.trim() || undefined,
|
||||
targetIds: Array.from(this.selectedTargetIds),
|
||||
primaryTargetId: this.primaryTargetId,
|
||||
};
|
||||
|
||||
if (this.isNew) {
|
||||
this.plugin.settings.exportProfiles.push(profileData);
|
||||
} else {
|
||||
const index = this.plugin.settings.exportProfiles.findIndex(p => p.id === this.profile!.id);
|
||||
if (index >= 0) {
|
||||
this.plugin.settings.exportProfiles[index] = profileData;
|
||||
}
|
||||
}
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(this.isNew ? 'Profile added' : 'Profile updated');
|
||||
this.onSave();
|
||||
this.close();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
this.primaryDropdownContainer = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { App, Modal, Notice, Setting, TFile, TFolder } from 'obsidian';
|
|||
import PromptfirePlugin from './main';
|
||||
import { createFreetextSource, SourcePosition } from './sources';
|
||||
import { estimateTokens } from './history';
|
||||
import { OutputTarget, OutputFormat, getTargetIcon, formatTokenCount } from './targets';
|
||||
import { OutputTarget, ExportProfile, OutputFormat, getTargetIcon, formatTokenCount } from './targets';
|
||||
|
||||
interface FolderConfig {
|
||||
name: string;
|
||||
|
|
@ -425,6 +425,29 @@ export class ContextGeneratorModal extends Modal {
|
|||
if (this.plugin.settings.targets.length > 0) {
|
||||
this.targetsHeadingEl = configZone.createEl('h3', { text: 'Output Targets' });
|
||||
|
||||
// Profile dropdown (only if profiles exist)
|
||||
if (this.plugin.settings.exportProfiles.length > 0) {
|
||||
new Setting(configZone)
|
||||
.setName('Profile')
|
||||
.setDesc('Pre-select targets from an export profile')
|
||||
.addDropdown(dropdown => {
|
||||
dropdown.addOption('', 'Manual selection');
|
||||
for (const profile of this.plugin.settings.exportProfiles) {
|
||||
dropdown.addOption(profile.id, profile.name);
|
||||
}
|
||||
dropdown.setValue('');
|
||||
dropdown.onChange(v => {
|
||||
if (v) {
|
||||
const profile = this.plugin.settings.exportProfiles.find(p => p.id === v);
|
||||
if (profile) {
|
||||
this.selectedTargetIds = new Set(profile.targetIds);
|
||||
this.onOpen();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const targetsContainer = configZone.createDiv({ cls: 'targets-checkboxes' });
|
||||
targetsContainer.style.display = 'flex';
|
||||
targetsContainer.style.flexDirection = 'column';
|
||||
|
|
|
|||
54
src/main.ts
54
src/main.ts
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from './presets';
|
||||
import {
|
||||
OutputTarget,
|
||||
ExportProfile,
|
||||
TargetExecutor,
|
||||
TargetResult,
|
||||
saveTargetToFile,
|
||||
|
|
@ -92,6 +93,12 @@ export default class PromptfirePlugin extends Plugin {
|
|||
callback: () => this.copySmartContext()
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'export-multi-target',
|
||||
name: 'Export context (multi-target)',
|
||||
callback: () => this.exportMultiTarget()
|
||||
});
|
||||
|
||||
this.addSettingTab(new PromptfireSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +123,12 @@ export default class PromptfirePlugin extends Plugin {
|
|||
);
|
||||
}
|
||||
}
|
||||
if (!loaded?.exportProfiles) {
|
||||
this.settings.exportProfiles = [];
|
||||
}
|
||||
if (loaded?.activeProfileId === undefined) {
|
||||
this.settings.activeProfileId = null;
|
||||
}
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
@ -277,7 +290,8 @@ export default class PromptfirePlugin extends Plugin {
|
|||
forceIncludeNote = false,
|
||||
temporaryFreetext?: string,
|
||||
templateId?: string | null,
|
||||
targets?: OutputTarget[]
|
||||
targets?: OutputTarget[],
|
||||
profilePrimaryTargetId?: string | null
|
||||
) {
|
||||
const folder = this.app.vault.getAbstractFileByPath(this.settings.contextFolder);
|
||||
|
||||
|
|
@ -412,7 +426,7 @@ export default class PromptfirePlugin extends Plugin {
|
|||
|
||||
// Process targets if provided
|
||||
if (targets && targets.length > 0) {
|
||||
await this.processTargets(combined, targets, historyMetadata, fileCount, sourceCount, templateName);
|
||||
await this.processTargets(combined, targets, historyMetadata, fileCount, sourceCount, templateName, profilePrimaryTargetId);
|
||||
} else {
|
||||
// Copy and save to history (legacy mode)
|
||||
const copyAndSave = async () => {
|
||||
|
|
@ -437,7 +451,8 @@ export default class PromptfirePlugin extends Plugin {
|
|||
historyMetadata: Omit<HistoryMetadata, 'charCount' | 'estimatedTokens'>,
|
||||
fileCount: number,
|
||||
sourceCount: number,
|
||||
templateName: string | null
|
||||
templateName: string | null,
|
||||
overridePrimaryId?: string | null
|
||||
) {
|
||||
const executor = new TargetExecutor();
|
||||
const results: TargetResult[] = [];
|
||||
|
|
@ -449,7 +464,8 @@ export default class PromptfirePlugin extends Plugin {
|
|||
}
|
||||
|
||||
// Determine primary target
|
||||
const primaryId = this.settings.primaryTargetId ||
|
||||
const primaryId = overridePrimaryId ??
|
||||
this.settings.primaryTargetId ??
|
||||
(targets.find(t => t.enabled)?.id ?? targets[0]?.id);
|
||||
const primaryResult = results.find(r => r.target.id === primaryId) || results[0];
|
||||
|
||||
|
|
@ -626,6 +642,36 @@ export default class PromptfirePlugin extends Plugin {
|
|||
return checkAll(selection.headings);
|
||||
}
|
||||
|
||||
async exportMultiTarget() {
|
||||
const profileId = this.settings.activeProfileId;
|
||||
if (!profileId) {
|
||||
new Notice('No active export profile. Set one in Settings > Output > Export Profiles.');
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = this.settings.exportProfiles.find(p => p.id === profileId);
|
||||
if (!profile) {
|
||||
new Notice('Active export profile not found. Check Settings > Output > Export Profiles.');
|
||||
return;
|
||||
}
|
||||
|
||||
const targets = profile.targetIds
|
||||
.map(id => this.settings.targets.find(t => t.id === id))
|
||||
.filter((t): t is OutputTarget => t != null);
|
||||
|
||||
if (targets.length === 0) {
|
||||
new Notice(`Profile "${profile.name}" has no valid targets.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.length < profile.targetIds.length) {
|
||||
const missing = profile.targetIds.length - targets.length;
|
||||
new Notice(`${missing} target(s) in profile no longer exist and were skipped.`, 5000);
|
||||
}
|
||||
|
||||
await this.copyContextToClipboard(false, undefined, undefined, targets, profile.primaryTargetId);
|
||||
}
|
||||
|
||||
async copySmartContext() {
|
||||
if (!this.settings.intelligence.enabled) {
|
||||
new Notice('Context intelligence is disabled. Enable it in Settings > Intelligence.');
|
||||
|
|
|
|||
142
src/settings.ts
142
src/settings.ts
|
|
@ -5,8 +5,9 @@ import { SourceModal } from './source-modal';
|
|||
import { PromptTemplate, STARTER_TEMPLATES } from './templates';
|
||||
import { TemplateModal, TemplateImportExportModal } from './template-modal';
|
||||
import { HistorySettings, DEFAULT_HISTORY_SETTINGS } from './history';
|
||||
import { OutputTarget, BUILTIN_TARGETS, getTargetIcon } from './targets';
|
||||
import { OutputTarget, ExportProfile, BUILTIN_TARGETS, BUILTIN_PROFILES, getTargetIcon } from './targets';
|
||||
import { TargetModal } from './target-modal';
|
||||
import { ExportProfileModal } from './export-profile-modal';
|
||||
import { IntelligenceSettings, DEFAULT_INTELLIGENCE_SETTINGS } from './context-intelligence';
|
||||
|
||||
export interface PromptfireSettings {
|
||||
|
|
@ -24,6 +25,8 @@ export interface PromptfireSettings {
|
|||
targets: OutputTarget[];
|
||||
primaryTargetId: string | null;
|
||||
targetOutputFolder: string;
|
||||
exportProfiles: ExportProfile[];
|
||||
activeProfileId: string | null;
|
||||
lastSettingsTab: string;
|
||||
collapsedSections: string[];
|
||||
generatorPreviewOpen: boolean;
|
||||
|
|
@ -45,6 +48,8 @@ export const DEFAULT_SETTINGS: PromptfireSettings = {
|
|||
targets: [],
|
||||
primaryTargetId: null,
|
||||
targetOutputFolder: '_context/outputs',
|
||||
exportProfiles: [],
|
||||
activeProfileId: null,
|
||||
lastSettingsTab: 'general',
|
||||
collapsedSections: [],
|
||||
generatorPreviewOpen: false,
|
||||
|
|
@ -530,6 +535,21 @@ export class PromptfireSettingTab extends PluginSettingTab {
|
|||
this.setDynamicDesc(primaryTargetSetting, 'This target\'s output is copied to clipboard',
|
||||
totalTargets > 0 ? `${enabledTargets} of ${totalTargets} enabled` : '');
|
||||
|
||||
new Setting(oc)
|
||||
.setName('Active export profile')
|
||||
.setDesc('Profile used by the "Export context (multi-target)" command')
|
||||
.addDropdown(dropdown => {
|
||||
dropdown.addOption('', 'None');
|
||||
for (const profile of this.plugin.settings.exportProfiles) {
|
||||
dropdown.addOption(profile.id, profile.name);
|
||||
}
|
||||
dropdown.setValue(this.plugin.settings.activeProfileId || '');
|
||||
dropdown.onChange(async (value) => {
|
||||
this.plugin.settings.activeProfileId = value || null;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
const outputFolderSetting = new Setting(oc)
|
||||
.setName('Output folder')
|
||||
.addText(text => text
|
||||
|
|
@ -541,6 +561,37 @@ export class PromptfireSettingTab extends PluginSettingTab {
|
|||
this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder);
|
||||
}));
|
||||
this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder);
|
||||
|
||||
// Section: Export Profiles
|
||||
const profilesSection = new CollapsibleSection(el, 'output-profiles', 'Export profiles', this.plugin);
|
||||
const pc = profilesSection.contentEl;
|
||||
|
||||
const profileButtonContainer = pc.createDiv({ cls: 'profiles-button-container' });
|
||||
profileButtonContainer.style.display = 'flex';
|
||||
profileButtonContainer.style.gap = '8px';
|
||||
profileButtonContainer.style.marginBottom = '15px';
|
||||
|
||||
const addProfileBtn = profileButtonContainer.createEl('button', { text: '+ New Profile' });
|
||||
addProfileBtn.addEventListener('click', () => {
|
||||
new ExportProfileModal(this.app, this.plugin, null, () => this.display()).open();
|
||||
});
|
||||
|
||||
const addBuiltinProfilesBtn = profileButtonContainer.createEl('button', { text: 'Add Built-in Profiles' });
|
||||
addBuiltinProfilesBtn.addEventListener('click', async () => {
|
||||
const existingIds = this.plugin.settings.exportProfiles.map(p => p.id);
|
||||
const newProfiles = BUILTIN_PROFILES.filter(p => !existingIds.includes(p.id));
|
||||
if (newProfiles.length === 0) {
|
||||
new Notice('All built-in profiles already added');
|
||||
return;
|
||||
}
|
||||
this.plugin.settings.exportProfiles.push(...newProfiles);
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(`Added ${newProfiles.length} built-in profile(s)`);
|
||||
this.display();
|
||||
});
|
||||
|
||||
const profilesContainer = pc.createDiv({ cls: 'profiles-list-container' });
|
||||
this.renderProfilesList(profilesContainer);
|
||||
}
|
||||
|
||||
// === TAB: History ===
|
||||
|
|
@ -1100,6 +1151,13 @@ export class PromptfireSettingTab extends PluginSettingTab {
|
|||
if (this.plugin.settings.primaryTargetId === target.id) {
|
||||
this.plugin.settings.primaryTargetId = null;
|
||||
}
|
||||
// Clean up profile references
|
||||
for (const profile of this.plugin.settings.exportProfiles) {
|
||||
profile.targetIds = profile.targetIds.filter(id => id !== target.id);
|
||||
if (profile.primaryTargetId === target.id) {
|
||||
profile.primaryTargetId = null;
|
||||
}
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
|
|
@ -1111,4 +1169,86 @@ export class PromptfireSettingTab extends PluginSettingTab {
|
|||
lastTargetRow.style.borderBottom = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
private renderProfilesList(container: HTMLElement) {
|
||||
container.empty();
|
||||
|
||||
if (this.plugin.settings.exportProfiles.length === 0) {
|
||||
const emptyMsg = container.createEl('p', {
|
||||
text: 'No export profiles configured yet.',
|
||||
cls: 'setting-item-description'
|
||||
});
|
||||
emptyMsg.style.fontStyle = 'italic';
|
||||
return;
|
||||
}
|
||||
|
||||
const list = container.createDiv({ cls: 'profiles-list' });
|
||||
list.style.border = '1px solid var(--background-modifier-border)';
|
||||
list.style.borderRadius = '4px';
|
||||
list.style.marginBottom = '15px';
|
||||
|
||||
for (const profile of this.plugin.settings.exportProfiles) {
|
||||
const row = list.createDiv({ cls: 'profile-row' });
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.padding = '8px 12px';
|
||||
row.style.borderBottom = '1px solid var(--background-modifier-border)';
|
||||
row.style.gap = '10px';
|
||||
|
||||
// Name and info
|
||||
const textContainer = row.createDiv();
|
||||
textContainer.style.flex = '1';
|
||||
|
||||
const name = textContainer.createEl('span', { text: profile.name });
|
||||
name.style.fontWeight = '500';
|
||||
|
||||
// Show target names
|
||||
const targetNames = profile.targetIds
|
||||
.map(id => this.plugin.settings.targets.find(t => t.id === id)?.name)
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
if (targetNames) {
|
||||
const info = textContainer.createEl('div', { text: targetNames });
|
||||
info.style.fontSize = '11px';
|
||||
info.style.color = 'var(--text-muted)';
|
||||
}
|
||||
|
||||
// Active badge
|
||||
if (this.plugin.settings.activeProfileId === profile.id) {
|
||||
const badge = row.createEl('span', { text: 'active' });
|
||||
badge.style.padding = '2px 6px';
|
||||
badge.style.borderRadius = '3px';
|
||||
badge.style.fontSize = '11px';
|
||||
badge.style.backgroundColor = 'var(--interactive-accent)';
|
||||
badge.style.color = 'var(--text-on-accent)';
|
||||
}
|
||||
|
||||
// Edit button
|
||||
const editBtn = row.createEl('button', { text: '✎' });
|
||||
editBtn.style.padding = '2px 8px';
|
||||
editBtn.addEventListener('click', () => {
|
||||
new ExportProfileModal(this.app, this.plugin, profile, () => this.display()).open();
|
||||
});
|
||||
|
||||
// Delete button
|
||||
const deleteBtn = row.createEl('button', { text: '✕' });
|
||||
deleteBtn.style.padding = '2px 8px';
|
||||
deleteBtn.addEventListener('click', async () => {
|
||||
this.plugin.settings.exportProfiles = this.plugin.settings.exportProfiles.filter(
|
||||
p => p.id !== profile.id
|
||||
);
|
||||
if (this.plugin.settings.activeProfileId === profile.id) {
|
||||
this.plugin.settings.activeProfileId = null;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
}
|
||||
|
||||
// Remove bottom border from last item
|
||||
const lastProfileRow = list.lastElementChild as HTMLElement;
|
||||
if (lastProfileRow) {
|
||||
lastProfileRow.style.borderBottom = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export class TargetModal extends Modal {
|
|||
wrapperPrefix: string = '';
|
||||
wrapperSuffix: string = '';
|
||||
separator: string = '\n\n---\n\n';
|
||||
outputPath: string = '';
|
||||
enabled: boolean = true;
|
||||
|
||||
constructor(
|
||||
|
|
@ -44,6 +45,7 @@ export class TargetModal extends Modal {
|
|||
this.wrapperPrefix = target.wrapper?.prefix || '';
|
||||
this.wrapperSuffix = target.wrapper?.suffix || '';
|
||||
this.separator = target.separator || '\n\n---\n\n';
|
||||
this.outputPath = target.outputPath || '';
|
||||
this.enabled = target.enabled;
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +140,15 @@ export class TargetModal extends Modal {
|
|||
text.inputEl.style.width = '100%';
|
||||
});
|
||||
|
||||
// Output path
|
||||
new Setting(contentEl)
|
||||
.setName('Output path')
|
||||
.setDesc('Custom folder for this target\'s file output (leave empty for default)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('_context/outputs')
|
||||
.setValue(this.outputPath)
|
||||
.onChange(v => this.outputPath = v));
|
||||
|
||||
// Enabled toggle
|
||||
new Setting(contentEl)
|
||||
.setName('Enabled')
|
||||
|
|
@ -180,6 +191,7 @@ export class TargetModal extends Modal {
|
|||
format: this.format,
|
||||
strategy: this.strategy,
|
||||
separator: this.separator,
|
||||
outputPath: this.outputPath.trim() || undefined,
|
||||
enabled: this.enabled,
|
||||
isBuiltin: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export interface OutputTarget {
|
|||
suffix?: string;
|
||||
};
|
||||
separator?: string;
|
||||
outputPath?: string;
|
||||
enabled: boolean;
|
||||
isBuiltin?: boolean;
|
||||
}
|
||||
|
|
@ -29,12 +30,24 @@ export interface TargetResult {
|
|||
sectionsDropped: number;
|
||||
}
|
||||
|
||||
export interface ExportProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
targetIds: string[];
|
||||
primaryTargetId: string | null;
|
||||
}
|
||||
|
||||
// === ID Generation ===
|
||||
|
||||
export function generateTargetId(): string {
|
||||
return 'target_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
|
||||
}
|
||||
|
||||
export function generateProfileId(): string {
|
||||
return 'profile_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
|
||||
}
|
||||
|
||||
// === Built-in Targets ===
|
||||
|
||||
export const BUILTIN_TARGETS: OutputTarget[] = [
|
||||
|
|
@ -74,6 +87,16 @@ export const BUILTIN_TARGETS: OutputTarget[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const BUILTIN_PROFILES: ExportProfile[] = [
|
||||
{
|
||||
id: 'builtin_profile_full',
|
||||
name: 'Full LLM Export',
|
||||
description: 'All built-in targets: XML for Claude, Markdown for GPT-4o, Plain for compact',
|
||||
targetIds: ['builtin_promptfire', 'builtin_gpt4', 'builtin_compact'],
|
||||
primaryTargetId: 'builtin_promptfire',
|
||||
},
|
||||
];
|
||||
|
||||
// === Content Transformer ===
|
||||
|
||||
export class ContentTransformer {
|
||||
|
|
@ -480,14 +503,15 @@ export async function saveTargetToFile(
|
|||
result: TargetResult,
|
||||
outputFolder: string
|
||||
): Promise<TFile | null> {
|
||||
const effectiveFolder = result.target.outputPath || outputFolder;
|
||||
const filename = `context-${result.target.name.toLowerCase().replace(/\s+/g, '-')}.md`;
|
||||
const path = `${outputFolder}/${filename}`;
|
||||
const path = `${effectiveFolder}/${filename}`;
|
||||
|
||||
try {
|
||||
// Ensure folder exists
|
||||
const folder = app.vault.getAbstractFileByPath(outputFolder);
|
||||
const folder = app.vault.getAbstractFileByPath(effectiveFolder);
|
||||
if (!folder) {
|
||||
await app.vault.createFolder(outputFolder);
|
||||
await app.vault.createFolder(effectiveFolder);
|
||||
}
|
||||
|
||||
// Create or update file
|
||||
|
|
|
|||
Loading…
Reference in a new issue