feat: add context snapshots for saving and replaying context recipes
Snapshots capture the file list, template, and formatting config from a history entry so the context can be regenerated from current vault state. Stored as JSON in .context-snapshots/ with no auto-cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
06a228847f
commit
40ce34c9b4
5 changed files with 537 additions and 0 deletions
|
|
@ -8,6 +8,7 @@ import {
|
|||
formatRelativeTime,
|
||||
DiffResult,
|
||||
} from './history';
|
||||
import { SaveSnapshotModal } from './snapshot-modal';
|
||||
|
||||
export class HistoryModal extends Modal {
|
||||
plugin: PromptfirePlugin;
|
||||
|
|
@ -324,6 +325,11 @@ class HistoryDetailModal extends Modal {
|
|||
new Notice('Copied to clipboard');
|
||||
});
|
||||
|
||||
const snapshotBtn = buttonContainer.createEl('button', { text: 'Save as Snapshot' });
|
||||
snapshotBtn.addEventListener('click', () => {
|
||||
new SaveSnapshotModal(this.app, this.plugin, this.entry).open();
|
||||
});
|
||||
|
||||
const closeBtn = buttonContainer.createEl('button', { text: 'Close' });
|
||||
closeBtn.addEventListener('click', () => {
|
||||
this.onUpdate();
|
||||
|
|
|
|||
149
src/main.ts
149
src/main.ts
|
|
@ -6,6 +6,8 @@ import { SourceRegistry, formatSourceOutput } from './sources';
|
|||
import { TemplateEngine } from './templates';
|
||||
import { HistoryManager, HistoryMetadata } from './history';
|
||||
import { HistoryModal } from './history-modal';
|
||||
import { SnapshotManager, ContextSnapshot } from './snapshots';
|
||||
import { SnapshotListModal } from './snapshot-modal';
|
||||
import { ContentSelector, FileSelection } from './content-selector';
|
||||
import { FileSelectorModal } from './file-selector-modal';
|
||||
import {
|
||||
|
|
@ -30,10 +32,12 @@ import { SmartContextModal } from './smart-context-modal';
|
|||
export default class PromptfirePlugin extends Plugin {
|
||||
settings: PromptfireSettings;
|
||||
historyManager: HistoryManager;
|
||||
snapshotManager: SnapshotManager;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.historyManager = new HistoryManager(this.app, this.settings.history);
|
||||
this.snapshotManager = new SnapshotManager(this.app);
|
||||
|
||||
// Ribbon icon
|
||||
this.addRibbonIcon('clipboard-copy', 'Copy Promptfire context', () => {
|
||||
|
|
@ -64,6 +68,12 @@ export default class PromptfirePlugin extends Plugin {
|
|||
callback: () => this.openHistory()
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'view-snapshots',
|
||||
name: 'View context snapshots',
|
||||
callback: () => this.openSnapshots()
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'copy-context-selective',
|
||||
name: 'Copy context (select sections)',
|
||||
|
|
@ -120,6 +130,145 @@ export default class PromptfirePlugin extends Plugin {
|
|||
new HistoryModal(this.app, this, this.historyManager).open();
|
||||
}
|
||||
|
||||
openSnapshots() {
|
||||
new SnapshotListModal(this.app, this).open();
|
||||
}
|
||||
|
||||
async replaySnapshot(snapshot: ContextSnapshot) {
|
||||
// Resolve additional sources
|
||||
const registry = new SourceRegistry();
|
||||
const enabledSources = this.settings.sources.filter(s => s.enabled);
|
||||
const resolvedSources = await registry.resolveAll(enabledSources);
|
||||
|
||||
const errors = resolvedSources.filter(r => r.error);
|
||||
if (errors.length > 0) {
|
||||
const errorNames = errors.map(e => e.source.name).join(', ');
|
||||
new Notice(`Some sources failed: ${errorNames}`, 5000);
|
||||
}
|
||||
|
||||
const prefixSources = resolvedSources.filter(r => r.source.position === 'prefix' && !r.error);
|
||||
const suffixSources = resolvedSources.filter(r => r.source.position === 'suffix' && !r.error);
|
||||
|
||||
// Build output parts
|
||||
const outputParts: string[] = [];
|
||||
|
||||
// Add prefix sources
|
||||
for (const resolved of prefixSources) {
|
||||
const formatted = formatSourceOutput(resolved, this.settings.showSourceLabels);
|
||||
if (formatted) outputParts.push(formatted);
|
||||
}
|
||||
|
||||
// Read files and track missing ones
|
||||
const missingFiles: string[] = [];
|
||||
const vaultParts: string[] = [];
|
||||
|
||||
for (const notePath of snapshot.notePaths) {
|
||||
// Strip (partial) suffix — replay always includes full file
|
||||
const cleanPath = notePath.replace(/ \(partial\)$/, '');
|
||||
const file = this.app.vault.getAbstractFileByPath(cleanPath);
|
||||
if (file instanceof TFile) {
|
||||
const content = await this.app.vault.read(file);
|
||||
if (snapshot.includeFilenames) {
|
||||
vaultParts.push(`# === ${file.name} ===\n\n${content}`);
|
||||
} else {
|
||||
vaultParts.push(content);
|
||||
}
|
||||
} else {
|
||||
missingFiles.push(cleanPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (vaultParts.length === 0 && !snapshot.activeNotePath) {
|
||||
new Notice('All files in this snapshot are missing');
|
||||
return;
|
||||
}
|
||||
|
||||
if (vaultParts.length > 0) {
|
||||
outputParts.push(vaultParts.join(`\n\n${snapshot.separator}\n\n`));
|
||||
}
|
||||
|
||||
// Add active note if set
|
||||
let activeNotePath: string | null = null;
|
||||
if (snapshot.activeNotePath) {
|
||||
const activeFile = this.app.vault.getAbstractFileByPath(snapshot.activeNotePath);
|
||||
if (activeFile instanceof TFile) {
|
||||
activeNotePath = activeFile.path;
|
||||
const content = await this.app.vault.read(activeFile);
|
||||
if (snapshot.includeFilenames) {
|
||||
outputParts.push(`# === ACTIVE: ${activeFile.name} ===\n\n${content}`);
|
||||
} else {
|
||||
outputParts.push(`--- ACTIVE NOTE ---\n\n${content}`);
|
||||
}
|
||||
} else {
|
||||
missingFiles.push(snapshot.activeNotePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Add suffix sources
|
||||
for (const resolved of suffixSources) {
|
||||
const formatted = formatSourceOutput(resolved, this.settings.showSourceLabels);
|
||||
if (formatted) outputParts.push(formatted);
|
||||
}
|
||||
|
||||
let combined = outputParts.join(`\n\n${snapshot.separator}\n\n`);
|
||||
|
||||
// Apply template if set
|
||||
let templateName: string | null = null;
|
||||
if (snapshot.templateId) {
|
||||
const template = this.settings.promptTemplates.find(t => t.id === snapshot.templateId);
|
||||
if (template) {
|
||||
const engine = new TemplateEngine(this.app);
|
||||
const context = await engine.buildContext(combined);
|
||||
combined = await engine.processTemplate(template.content, context);
|
||||
templateName = template.name;
|
||||
} else {
|
||||
new Notice('Snapshot template no longer exists, continuing without template', 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// Warn about missing files
|
||||
if (missingFiles.length > 0) {
|
||||
new Notice(`${missingFiles.length} file(s) missing, skipped:\n${missingFiles.join('\n')}`, 8000);
|
||||
}
|
||||
|
||||
// Prepare history metadata
|
||||
const includedFiles = snapshot.notePaths
|
||||
.map(p => p.replace(/ \(partial\)$/, ''))
|
||||
.filter(p => !missingFiles.includes(p));
|
||||
const historyMetadata: Omit<HistoryMetadata, 'charCount' | 'estimatedTokens'> = {
|
||||
templateId: snapshot.templateId,
|
||||
templateName,
|
||||
includedFiles,
|
||||
includedSources: [
|
||||
...prefixSources.map(r => r.source.name),
|
||||
...suffixSources.map(r => r.source.name),
|
||||
],
|
||||
activeNote: activeNotePath,
|
||||
userNote: `Replayed snapshot: ${snapshot.name}`,
|
||||
};
|
||||
|
||||
// Copy and save to history
|
||||
const copyAndSave = async () => {
|
||||
await navigator.clipboard.writeText(combined);
|
||||
|
||||
const fileCount = includedFiles.length + (activeNotePath ? 1 : 0);
|
||||
const sourceCount = prefixSources.length + suffixSources.length;
|
||||
let message = `Replayed snapshot "${snapshot.name}": ${fileCount} files`;
|
||||
if (sourceCount > 0) message += `, ${sourceCount} sources`;
|
||||
if (templateName) message += ` using "${templateName}"`;
|
||||
new Notice(message);
|
||||
|
||||
await this.historyManager.saveEntry(combined, historyMetadata);
|
||||
};
|
||||
|
||||
if (this.settings.showPreview) {
|
||||
const totalCount = includedFiles.length + (activeNotePath ? 1 : 0) + prefixSources.length + suffixSources.length;
|
||||
new PreviewModal(this.app, combined, totalCount, copyAndSave).open();
|
||||
} else {
|
||||
await copyAndSave();
|
||||
}
|
||||
}
|
||||
|
||||
async runHistoryCleanup(): Promise<number> {
|
||||
return await this.historyManager.cleanup();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -629,6 +629,19 @@ export class PromptfireSettingTab extends PluginSettingTab {
|
|||
new Notice(`Cleaned up ${deleted} old entries`);
|
||||
});
|
||||
}
|
||||
|
||||
// Snapshots subsection (independent of history toggle)
|
||||
const snapshotsSection = new CollapsibleSection(el, 'snapshots', 'Snapshots', this.plugin);
|
||||
const snc = snapshotsSection.contentEl;
|
||||
|
||||
new Setting(snc)
|
||||
.setName('View snapshots')
|
||||
.setDesc('Browse, replay, or delete saved context snapshots')
|
||||
.addButton(button => button
|
||||
.setButtonText('View Snapshots')
|
||||
.onClick(() => {
|
||||
this.plugin.openSnapshots();
|
||||
}));
|
||||
}
|
||||
|
||||
// === TAB: Intelligence ===
|
||||
|
|
|
|||
230
src/snapshot-modal.ts
Normal file
230
src/snapshot-modal.ts
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
import { App, Modal, Notice, Setting } from 'obsidian';
|
||||
import PromptfirePlugin from './main';
|
||||
import { HistoryEntry, formatRelativeTime } from './history';
|
||||
import { ContextSnapshot, SnapshotManager, generateSnapshotId } from './snapshots';
|
||||
|
||||
export class SaveSnapshotModal extends Modal {
|
||||
private plugin: PromptfirePlugin;
|
||||
private entry: HistoryEntry;
|
||||
private snapshotManager: SnapshotManager;
|
||||
private name = '';
|
||||
private description = '';
|
||||
|
||||
constructor(app: App, plugin: PromptfirePlugin, entry: HistoryEntry) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.entry = entry;
|
||||
this.snapshotManager = plugin.snapshotManager;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('promptfire-save-snapshot');
|
||||
|
||||
contentEl.createEl('h2', { text: 'Save as Snapshot' });
|
||||
|
||||
// Summary
|
||||
const summary = contentEl.createDiv();
|
||||
summary.style.marginBottom = '15px';
|
||||
summary.style.padding = '10px';
|
||||
summary.style.backgroundColor = 'var(--background-secondary)';
|
||||
summary.style.borderRadius = '4px';
|
||||
summary.style.fontSize = '12px';
|
||||
|
||||
const fileCount = this.entry.metadata.includedFiles.length;
|
||||
const templateName = this.entry.metadata.templateName || 'None';
|
||||
const activeNote = this.entry.metadata.activeNote || 'None';
|
||||
|
||||
summary.createEl('div', { text: `Files: ${fileCount}` });
|
||||
summary.createEl('div', { text: `Template: ${templateName}` });
|
||||
summary.createEl('div', { text: `Active note: ${activeNote}` });
|
||||
|
||||
// Name field
|
||||
let nameInput: HTMLInputElement;
|
||||
new Setting(contentEl)
|
||||
.setName('Name')
|
||||
.setDesc('A short name for this snapshot (required)')
|
||||
.addText(text => {
|
||||
nameInput = text.inputEl;
|
||||
text.setPlaceholder('e.g., "Auth refactor context"')
|
||||
.onChange(value => { this.name = value; });
|
||||
});
|
||||
|
||||
// Description field
|
||||
new Setting(contentEl)
|
||||
.setName('Description')
|
||||
.setDesc('Optional notes about when to use this snapshot')
|
||||
.addTextArea(text => {
|
||||
text.setPlaceholder('e.g., "Use when working on auth module"')
|
||||
.onChange(value => { this.description = value; });
|
||||
text.inputEl.rows = 2;
|
||||
text.inputEl.style.width = '100%';
|
||||
});
|
||||
|
||||
// Buttons
|
||||
const buttonContainer = contentEl.createDiv();
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'flex-end';
|
||||
buttonContainer.style.gap = '10px';
|
||||
buttonContainer.style.marginTop = '20px';
|
||||
|
||||
const saveBtn = buttonContainer.createEl('button', { text: 'Save Snapshot', cls: 'mod-cta' });
|
||||
saveBtn.addEventListener('click', async () => {
|
||||
if (!this.name.trim()) {
|
||||
new Notice('Name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot: ContextSnapshot = {
|
||||
id: generateSnapshotId(),
|
||||
name: this.name.trim(),
|
||||
description: this.description.trim() || undefined,
|
||||
createdAt: Date.now(),
|
||||
notePaths: this.entry.metadata.includedFiles,
|
||||
activeNotePath: this.entry.metadata.activeNote,
|
||||
templateId: this.entry.metadata.templateId,
|
||||
includeFilenames: this.plugin.settings.includeFilenames,
|
||||
separator: this.plugin.settings.separator,
|
||||
};
|
||||
|
||||
const success = await this.snapshotManager.saveSnapshot(snapshot);
|
||||
if (success) {
|
||||
new Notice(`Snapshot saved: ${snapshot.name}`);
|
||||
this.close();
|
||||
} else {
|
||||
new Notice('Failed to save snapshot');
|
||||
}
|
||||
});
|
||||
|
||||
const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' });
|
||||
cancelBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
export class SnapshotListModal extends Modal {
|
||||
private plugin: PromptfirePlugin;
|
||||
private snapshotManager: SnapshotManager;
|
||||
|
||||
constructor(app: App, plugin: PromptfirePlugin) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.snapshotManager = plugin.snapshotManager;
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
await this.loadAndRender();
|
||||
}
|
||||
|
||||
async loadAndRender() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('promptfire-snapshot-list');
|
||||
|
||||
contentEl.createEl('h2', { text: 'Context Snapshots' });
|
||||
|
||||
const snapshots = await this.snapshotManager.loadSnapshots();
|
||||
|
||||
if (snapshots.length === 0) {
|
||||
const notice = contentEl.createEl('p', {
|
||||
text: 'No snapshots yet. Open a history entry and click "Save as Snapshot" to create one.'
|
||||
});
|
||||
notice.style.fontStyle = 'italic';
|
||||
notice.style.color = 'var(--text-muted)';
|
||||
return;
|
||||
}
|
||||
|
||||
const countInfo = contentEl.createEl('p', {
|
||||
text: `${snapshots.length} snapshot${snapshots.length !== 1 ? 's' : ''}`
|
||||
});
|
||||
countInfo.style.color = 'var(--text-muted)';
|
||||
countInfo.style.marginBottom = '10px';
|
||||
|
||||
const list = contentEl.createDiv({ cls: 'snapshot-list' });
|
||||
list.style.maxHeight = '400px';
|
||||
list.style.overflow = 'auto';
|
||||
list.style.border = '1px solid var(--background-modifier-border)';
|
||||
list.style.borderRadius = '4px';
|
||||
|
||||
for (const snapshot of snapshots) {
|
||||
const row = list.createDiv({ cls: 'snapshot-row' });
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.padding = '10px 12px';
|
||||
row.style.borderBottom = '1px solid var(--background-modifier-border)';
|
||||
row.style.gap = '10px';
|
||||
|
||||
// Info
|
||||
const infoContainer = row.createDiv();
|
||||
infoContainer.style.flex = '1';
|
||||
|
||||
const header = infoContainer.createDiv();
|
||||
header.style.display = 'flex';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.gap = '8px';
|
||||
|
||||
const nameEl = header.createEl('span', { text: snapshot.name });
|
||||
nameEl.style.fontWeight = '500';
|
||||
|
||||
const timeEl = header.createEl('span', { text: formatRelativeTime(snapshot.createdAt) });
|
||||
timeEl.style.color = 'var(--text-muted)';
|
||||
timeEl.style.fontSize = '12px';
|
||||
|
||||
// Stats
|
||||
const stats = infoContainer.createDiv();
|
||||
stats.style.fontSize = '12px';
|
||||
stats.style.color = 'var(--text-muted)';
|
||||
stats.style.marginTop = '4px';
|
||||
|
||||
const fileCount = snapshot.notePaths.length;
|
||||
const templateName = snapshot.templateId
|
||||
? (this.plugin.settings.promptTemplates.find(t => t.id === snapshot.templateId)?.name || 'Unknown template')
|
||||
: 'No template';
|
||||
stats.setText(`${fileCount} files · ${templateName}`);
|
||||
|
||||
// Description
|
||||
if (snapshot.description) {
|
||||
const desc = infoContainer.createDiv({ text: snapshot.description });
|
||||
desc.style.fontSize = '12px';
|
||||
desc.style.fontStyle = 'italic';
|
||||
desc.style.marginTop = '4px';
|
||||
}
|
||||
|
||||
// Actions
|
||||
const actions = row.createDiv();
|
||||
actions.style.display = 'flex';
|
||||
actions.style.gap = '4px';
|
||||
|
||||
const replayBtn = actions.createEl('button', { text: '\u25B6' });
|
||||
replayBtn.title = 'Replay snapshot';
|
||||
replayBtn.style.padding = '4px 8px';
|
||||
replayBtn.addEventListener('click', async () => {
|
||||
this.close();
|
||||
await this.plugin.replaySnapshot(snapshot);
|
||||
});
|
||||
|
||||
const deleteBtn = actions.createEl('button', { text: '\u2715' });
|
||||
deleteBtn.title = 'Delete snapshot';
|
||||
deleteBtn.style.padding = '4px 8px';
|
||||
deleteBtn.addEventListener('click', async () => {
|
||||
await this.snapshotManager.deleteSnapshot(snapshot.id);
|
||||
new Notice('Snapshot deleted');
|
||||
await this.loadAndRender();
|
||||
});
|
||||
}
|
||||
|
||||
// Remove bottom border from last row
|
||||
const lastRow = list.lastElementChild as HTMLElement;
|
||||
if (lastRow) {
|
||||
lastRow.style.borderBottom = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
139
src/snapshots.ts
Normal file
139
src/snapshots.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { App, TFile, TFolder } from 'obsidian';
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface ContextSnapshot {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
createdAt: number;
|
||||
notePaths: string[];
|
||||
activeNotePath: string | null;
|
||||
templateId: string | null;
|
||||
includeFilenames: boolean;
|
||||
separator: string;
|
||||
}
|
||||
|
||||
// === ID Generation ===
|
||||
|
||||
export function generateSnapshotId(): string {
|
||||
return 'snap_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
|
||||
}
|
||||
|
||||
// === Snapshot Manager ===
|
||||
|
||||
const SNAPSHOT_FOLDER = '.context-snapshots';
|
||||
|
||||
export class SnapshotManager {
|
||||
private app: App;
|
||||
|
||||
constructor(app: App) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
private async ensureFolder(): Promise<TFolder | null> {
|
||||
const existing = this.app.vault.getAbstractFileByPath(SNAPSHOT_FOLDER);
|
||||
if (existing instanceof TFolder) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.app.vault.createFolder(SNAPSHOT_FOLDER);
|
||||
return this.app.vault.getAbstractFileByPath(SNAPSHOT_FOLDER) as TFolder;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private getSnapshotFilename(snapshot: ContextSnapshot): string {
|
||||
const date = new Date(snapshot.createdAt);
|
||||
const dateStr = date.toISOString().replace(/[:.]/g, '-').substring(0, 19);
|
||||
return `${dateStr}_${snapshot.id}.json`;
|
||||
}
|
||||
|
||||
async saveSnapshot(snapshot: ContextSnapshot): Promise<boolean> {
|
||||
const folder = await this.ensureFolder();
|
||||
if (!folder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const filename = this.getSnapshotFilename(snapshot);
|
||||
const filePath = `${SNAPSHOT_FOLDER}/${filename}`;
|
||||
|
||||
try {
|
||||
await this.app.vault.create(filePath, JSON.stringify(snapshot, null, 2));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async loadSnapshots(): Promise<ContextSnapshot[]> {
|
||||
const folder = this.app.vault.getAbstractFileByPath(SNAPSHOT_FOLDER);
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const snapshots: ContextSnapshot[] = [];
|
||||
|
||||
for (const file of folder.children) {
|
||||
if (file instanceof TFile && file.extension === 'json') {
|
||||
try {
|
||||
const content = await this.app.vault.read(file);
|
||||
const snapshot = JSON.parse(content) as ContextSnapshot;
|
||||
snapshots.push(snapshot);
|
||||
} catch {
|
||||
// Skip invalid files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snapshots.sort((a, b) => b.createdAt - a.createdAt);
|
||||
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
async deleteSnapshot(id: string): Promise<boolean> {
|
||||
const folder = this.app.vault.getAbstractFileByPath(SNAPSHOT_FOLDER);
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const file of folder.children) {
|
||||
if (file instanceof TFile && file.name.includes(id)) {
|
||||
try {
|
||||
await this.app.vault.delete(file);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async updateSnapshot(id: string, updates: Partial<Pick<ContextSnapshot, 'name' | 'description'>>): Promise<boolean> {
|
||||
const folder = this.app.vault.getAbstractFileByPath(SNAPSHOT_FOLDER);
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const file of folder.children) {
|
||||
if (file instanceof TFile && file.name.includes(id)) {
|
||||
try {
|
||||
const content = await this.app.vault.read(file);
|
||||
const snapshot = JSON.parse(content) as ContextSnapshot;
|
||||
if (updates.name !== undefined) snapshot.name = updates.name;
|
||||
if (updates.description !== undefined) snapshot.description = updates.description;
|
||||
await this.app.vault.modify(file, JSON.stringify(snapshot, null, 2));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue