Skip to main content
Version: Latest

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

FeatureHow it's configured
Dark themeappearance.theme: 'dark'
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()