feat(templates): add support for HTML templates

This commit is contained in:
Marc Sportiello
2026-07-19 10:10:31 +02:00
committed by Linus Rath
parent 0b62afb0f8
commit 0a30b2fb3a
6 changed files with 17 additions and 4 deletions
+1 -1
View File
@@ -1092,7 +1092,7 @@ export function EmailComposer({
: template.body;
// In plain text mode, use template body as-is; otherwise convert to HTML
const bodyContent = plainTextMode
const bodyContent = plainTextMode || template.isHTML
? filledBody
: `<p>${filledBody.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>')}</p>`;
@@ -94,7 +94,7 @@ export function PlaceholderFillModal({
<div className="mt-4 pt-4 border-t border-border">
<p className="text-xs font-medium text-muted-foreground mb-2">{t('preview')}</p>
<div className="text-sm text-foreground whitespace-pre-wrap p-3 rounded-md bg-muted/50 border border-border max-h-32 overflow-y-auto">
{preview}
{template.isHTML ? <div dangerouslySetInnerHTML={{ __html: preview }}></div> : preview}
</div>
</div>
)}
+11 -1
View File
@@ -4,7 +4,7 @@ import { useState, useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Star, Plus } from 'lucide-react';
import { Star, Plus, Square, CheckSquare } from 'lucide-react';
import { cn } from '@/lib/utils';
import { validateTemplateName } from '@/lib/template-utils';
import { BUILT_IN_PLACEHOLDERS } from '@/lib/template-types';
@@ -38,6 +38,7 @@ export function TemplateForm({ template, initialData, onSave, onCancel }: Templa
const [category, setCategory] = useState(template?.category || '');
const [subject, setSubject] = useState(template?.subject || initialData?.subject || '');
const [body, setBody] = useState(template?.body || initialData?.body || '');
const [isHTML, setIsHTML] = useState(template?.isHTML || false);
const [toRecipients, setToRecipients] = useState(
template?.defaultRecipients?.to?.join(', ') || initialData?.to?.join(', ') || ''
);
@@ -76,6 +77,7 @@ export function TemplateForm({ template, initialData, onSave, onCancel }: Templa
name: name.trim(),
subject,
body,
isHTML,
category: category.trim(),
defaultRecipients: to.length || cc.length || bcc.length
? { to: to.length ? to : undefined, cc: cc.length ? cc : undefined, bcc: bcc.length ? bcc : undefined }
@@ -190,6 +192,14 @@ export function TemplateForm({ template, initialData, onSave, onCancel }: Templa
rows={6}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary resize-y"
/>
<button
type="button"
onClick={() => setIsHTML(!isHTML)}
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
{isHTML ? <CheckSquare className={cn('w-4 h-4')}></CheckSquare> : <Square className={cn('w-4 h-4')}></Square>}
HTML
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
+1
View File
@@ -20,6 +20,7 @@ function makeTemplate(overrides: Partial<EmailTemplate> = {}): EmailTemplate {
name: 'Test Template',
subject: '',
body: '',
isHTML: false,
category: '',
isFavorite: false,
createdAt: '2026-01-01T00:00:00Z',
+1
View File
@@ -3,6 +3,7 @@ export interface EmailTemplate {
name: string;
subject: string;
body: string;
isHTML?: boolean;
category: string;
defaultRecipients?: {
to?: string[];
+2 -1
View File
@@ -173,7 +173,8 @@ export function importTemplates(json: string): ImportResult {
id: generateUUID(),
name: sanitizeText(t.name),
subject: sanitizeText(t.subject),
body: sanitizeText(t.body),
body: t.isHTML ? String(t.body || '') : sanitizeText(t.body),
isHTML: Boolean(t.isHTML),
category: sanitizeText(t.category),
defaultRecipients: recipients && typeof recipients === 'object'
? {