Skip to main content

Full-Featured Editor

This page shows a React editor with every major feature enabled: dark theme, merge tags, special links, custom fonts, image upload, auto-save, and undo/redo.

src/FullFeaturedEditor.jsx
import React, { useRef, useCallback, useEffect } from 'react';
import { PexelizeEditor } from '@pexelize/react-editor';

const EDITOR_KEY = 'your-editor-key';

const appearance = {
theme: 'dark',
panels: {
tools: { dock: 'left' },
},
colors: {
primary: '#7c3aed',
background: '#1e1e2e',
foreground: '#cdd6f4',
},
};

const mergeTags = [
{ name: 'First Name', value: '{{first_name}}' },
{ name: 'Last Name', value: '{{last_name}}' },
{ name: 'Email', value: '{{email}}' },
{ name: 'Company', value: '{{company}}' },
{ name: 'Unsubscribe URL', value: '{{unsubscribe_url}}' },
];

const specialLinks = [
{ name: 'Unsubscribe', href: '{{unsubscribe_url}}', target: '_blank' },
{ name: 'View in Browser', href: '{{web_version_url}}', target: '_blank' },
{ name: 'Preferences', href: '{{preferences_url}}', target: '_blank' },
];

const customFonts = [
{
label: 'Inter',
value: "'Inter', sans-serif",
url: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap',
},
{
label: 'Space Grotesk',
value: "'Space Grotesk', sans-serif",
url: 'https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap',
},
];

export default function FullFeaturedEditor() {
const editorRef = useRef(null);
const autoSaveTimer = useRef(null);

// --- image upload handler ---
const onImageUpload = useCallback((file, done) => {
const formData = new FormData();
formData.append('file', file);

fetch('https://api.example.com/images/upload', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_TOKEN' },
body: formData,
})
.then((res) => res.json())
.then((data) => {
done({ progress: 100, url: data.url });
})
.catch((err) => {
console.error('Upload failed:', err);
done({ progress: 0 });
});
}, []);

// --- auto-save every 30 s ---
const startAutoSave = useCallback(() => {
autoSaveTimer.current = setInterval(() => {
editorRef.current?.exportJson((json) => {
localStorage.setItem('autosave-design', JSON.stringify(json));
console.log('Auto-saved at', new Date().toLocaleTimeString());
});
}, 30_000);
}, []);

useEffect(() => {
return () => clearInterval(autoSaveTimer.current);
}, []);

const onReady = useCallback(() => {
// restore last auto-save if present
const saved = localStorage.getItem('autosave-design');
if (saved) {
editorRef.current?.loadDesign(JSON.parse(saved));
}
startAutoSave();
}, [startAutoSave]);

// --- undo / redo ---
const undo = useCallback(() => editorRef.current?.undo(), []);
const redo = useCallback(() => editorRef.current?.redo(), []);

// --- export ---
const exportHtml = useCallback(() => {
editorRef.current?.exportHtml((data) => {
navigator.clipboard.writeText(data.html);
alert('HTML copied to clipboard');
});
}, []);

return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<div style={{ padding: 10, display: 'flex', gap: 8, background: '#181825', color: '#cdd6f4' }}>
<button onClick={undo}>Undo</button>
<button onClick={redo}>Redo</button>
<button onClick={exportHtml}>Export HTML</button>
</div>
<div style={{ flex: 1 }}>
<PexelizeEditor
ref={editorRef}
editorKey={EDITOR_KEY}
appearance={appearance}
mergeTags={mergeTags}
specialLinks={specialLinks}
customFonts={customFonts}
onImageUpload={onImageUpload}
onReady={onReady}
minHeight="100%"
/>
</div>
</div>
);
}

Feature summary

FeatureHow it's configured
Dark themeappearance.theme: 'dark' + custom colours
Merge tagsmergeTags array passed as prop
Special linksspecialLinks array passed as prop
Custom fontscustomFonts with Google Fonts URLs
Image uploadonImageUpload callback posts to your server
Auto-savesetInterval calls exportJson every 30 s
Undo / RedoeditorRef.current.undo() / .redo()