From ec3f1bc4044375ec3f9a5bbd3e90376fc0ae2e7a Mon Sep 17 00:00:00 2001 From: tolvitty Date: Fri, 13 Feb 2026 13:14:48 +0100 Subject: [PATCH] feat: add FormProcessor for note creation and frontmatter updates Co-Authored-By: Claude Opus 4.6 --- src/core/form-processor.ts | 135 +++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/core/form-processor.ts diff --git a/src/core/form-processor.ts b/src/core/form-processor.ts new file mode 100644 index 0000000..ab91538 --- /dev/null +++ b/src/core/form-processor.ts @@ -0,0 +1,135 @@ +import { App, TFile, TFolder, Notice, normalizePath } from 'obsidian'; +import { FormDefinition } from '../types'; +import { renderTemplate, sanitizeFileName } from '../utils/template-engine'; + +/** + * Processes a submitted form: creates a new note or updates frontmatter. + */ +export class FormProcessor { + constructor(private app: App) {} + + /** + * Process form submission based on mode. + * Returns the file that was created or updated. + */ + async process( + form: FormDefinition, + values: Record, + targetFile?: TFile, + ): Promise { + if (form.mode === 'create') { + return this.createNote(form, values); + } else { + return this.updateNote(form, values, targetFile); + } + } + + private async createNote( + form: FormDefinition, + values: Record, + ): Promise { + const stringValues = this.toStringValues(values); + + // Build file name + const rawName = form.fileNameTemplate + ? renderTemplate(form.fileNameTemplate, stringValues) + : `${stringValues['date'] ?? new Date().toISOString().slice(0, 10)}-untitled`; + const fileName = sanitizeFileName(rawName); + + // Ensure target folder exists + const folder = normalizePath(form.targetFolder ?? '/'); + await this.ensureFolder(folder); + + // Build file path (avoid duplicates) + let filePath = normalizePath(`${folder}/${fileName}.md`); + let counter = 1; + while (this.app.vault.getAbstractFileByPath(filePath)) { + filePath = normalizePath(`${folder}/${fileName}-${counter}.md`); + counter++; + } + + // Build content + const frontmatter = this.buildFrontmatter(form, values); + const body = form.bodyTemplate + ? renderTemplate(form.bodyTemplate, stringValues) + : ''; + const content = `---\n${frontmatter}---\n${body}`; + + const file = await this.app.vault.create(filePath, content); + new Notice(`Created: ${file.path}`); + return file; + } + + private async updateNote( + form: FormDefinition, + values: Record, + targetFile?: TFile, + ): Promise { + if (!targetFile) { + throw new Error('No target file specified for update.'); + } + + await this.app.fileManager.processFrontMatter(targetFile, (fm) => { + for (const field of form.fields) { + const value = values[field.id]; + if (value !== undefined) { + fm[field.id] = value; + } + } + }); + + new Notice(`Updated: ${targetFile.path}`); + return targetFile; + } + + private buildFrontmatter( + form: FormDefinition, + values: Record, + ): string { + let yaml = ''; + for (const field of form.fields) { + const value = values[field.id]; + if (value === undefined || value === null) continue; + + if (Array.isArray(value)) { + yaml += `${field.id}:\n`; + for (const item of value) { + yaml += ` - ${this.yamlEscape(String(item))}\n`; + } + } else if (typeof value === 'boolean') { + yaml += `${field.id}: ${value}\n`; + } else if (typeof value === 'number') { + yaml += `${field.id}: ${value}\n`; + } else { + yaml += `${field.id}: ${this.yamlEscape(String(value))}\n`; + } + } + return yaml; + } + + private yamlEscape(value: string): string { + if (/[:#{}[\],&*?|>!%@`]/.test(value) || value.includes('\n')) { + return `"${value.replace(/"/g, '\\"')}"`; + } + return value; + } + + private toStringValues(values: Record): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(values)) { + if (Array.isArray(value)) { + result[key] = value.join(', '); + } else { + result[key] = String(value ?? ''); + } + } + return result; + } + + private async ensureFolder(path: string): Promise { + if (path === '/' || path === '') return; + const existing = this.app.vault.getAbstractFileByPath(path); + if (existing instanceof TFolder) return; + await this.app.vault.createFolder(path); + } +}