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 { DragbleEditor } from "dragble-react-editor";
const EDITOR_KEY = "your-editor-key";
const appearance = {
theme: "dark",
panels: {
tools: { dock: "left" },
},
};
const mergeTags = [
{ label: "First Name", value: "{{first_name}}" },
{ label: "Last Name", value: "{{last_name}}" },
{ label: "Email", value: "{{email}}" },
{ label: "Company", value: "{{company}}" },
{ label: "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 }}>
<DragbleEditor
ref={editorRef}
editorKey={EDITOR_KEY}
appearance={appearance}
mergeTags={mergeTags}
specialLinks={specialLinks}
customFonts={customFonts}
onImageUpload={onImageUpload}
onReady={onReady}
minHeight="100%"
/>
</div>
</div>
);
}
Feature summary
| Feature | How it's configured |
|---|---|
| Dark theme | appearance.theme: 'dark' |
| Merge tags | mergeTags array passed as prop |
| Special links | specialLinks array passed as prop |
| Custom fonts | customFonts with Google Fonts URLs |
| Image upload | onImageUpload callback posts to your server |
| Auto-save | setInterval calls exportJson every 30 s |
| Undo / Redo | editorRef.current.undo() / .redo() |