diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index af80017..14f90f6 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -1130,6 +1130,12 @@ export function CustomFormBuilder() {

Click to insert at cursor.

+

+ Control case with |upper,{" "} + |lower, |title, or{" "} + |sentence. Example:{" "} + {`{{ownerName|upper}}`} +

diff --git a/src/lib/forms-shared.ts b/src/lib/forms-shared.ts index 61174a5..930bd46 100644 --- a/src/lib/forms-shared.ts +++ b/src/lib/forms-shared.ts @@ -134,6 +134,47 @@ export async function fetchCustomFieldDefs(): Promise { return (data ?? []) as CustomFieldVar[]; } +// Case modifiers usable in templates as {{var|modifier}}. +// Supported: upper, lower, title, sentence, capitalize (alias for sentence). +export type CaseModifier = "upper" | "lower" | "title" | "sentence" | "capitalize"; + +export function applyCase(value: string, modifier?: string | null): string { + if (!value || !modifier) return value ?? ""; + const m = modifier.toLowerCase().trim(); + switch (m) { + case "upper": + return value.toUpperCase(); + case "lower": + return value.toLowerCase(); + case "title": + // Capitalize each word; preserve internal punctuation/whitespace. + return value.toLowerCase().replace(/\b([a-z\u00C0-\u024F])/g, (c) => c.toUpperCase()); + case "sentence": + case "capitalize": { + // Lowercase the whole string, then capitalize the first letter of each sentence. + const lower = value.toLowerCase(); + return lower.replace(/(^\s*[a-z\u00C0-\u024F])|([.!?]\s+[a-z\u00C0-\u024F])/g, (s) => + s.toUpperCase(), + ); + } + default: + return value; + } +} + +// Replace any occurrence of {{name}} or {{name|modifier}} using the provided lookup. +function replaceWithModifier( + body: string, + pattern: RegExp, + resolver: (name: string) => string | undefined, +): string { + return body.replace(pattern, (_match, rawName: string, _pipe: string | undefined, mod: string | undefined) => { + const value = resolver(rawName.trim()); + if (value == null) return ""; + return applyCase(value, mod); + }); +} + export function applyVariables( body: string, ctx: { @@ -145,32 +186,40 @@ export function applyVariables( }, ): string { const today = format(new Date(), "MMMM d, yyyy"); - const replacements: Record = { - "{{clientName}}": ctx.client?.name ?? "", - "{{ownerName}}": ownerFullName(ctx.homeowner), - "{{propertyAddress}}": ctx.homeowner?.address ?? "", - "{{unitNumber}}": ctx.homeowner?.unit_number ?? "", - "{{accountNumber}}": ctx.homeowner?.unit_number ?? "", - "{{balance}}": (ctx.homeowner?.opening_balance ?? 0).toFixed(2), - "{{currentDate}}": today, - "{{firmName}}": ctx.firmName ?? "", + const systemValues: Record = { + clientName: ctx.client?.name ?? "", + ownerName: ownerFullName(ctx.homeowner), + propertyAddress: ctx.homeowner?.address ?? "", + unitNumber: ctx.homeowner?.unit_number ?? "", + accountNumber: ctx.homeowner?.unit_number ?? "", + balance: (ctx.homeowner?.opening_balance ?? 0).toFixed(2), + currentDate: today, + firmName: ctx.firmName ?? "", }; + let out = body; - for (const [k, v] of Object.entries(replacements)) { - out = out.split(k).join(v); - } - // Custom case-field variables like {{custom.mortgage_holder}} - if (ctx.customValues) { - for (const [k, v] of Object.entries(ctx.customValues)) { - out = out.split(`{{custom.${k}}}`).join(v ?? ""); - } - } - // Per-form fillable fields like {{field.tenant_name}} - if (ctx.fieldValues) { - for (const [k, v] of Object.entries(ctx.fieldValues)) { - out = out.split(`{{field.${k}}}`).join(v ?? ""); - } - } + + // System variables: {{name}} or {{name|modifier}} + out = replaceWithModifier( + out, + /\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g, + (name) => systemValues[name], + ); + + // Custom case-field variables: {{custom.key}} or {{custom.key|modifier}} + out = replaceWithModifier( + out, + /\{\{\s*custom\.([a-zA-Z0-9_]+)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g, + (name) => ctx.customValues?.[name], + ); + + // Per-form fillable fields: {{field.key}} or {{field.key|modifier}} + out = replaceWithModifier( + out, + /\{\{\s*field\.([a-zA-Z0-9_]+)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g, + (name) => ctx.fieldValues?.[name], + ); + return out; }