257 lines
7.3 KiB
HTML
257 lines
7.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Email Editor</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" />
|
|
<style>
|
|
html, body {
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
background: #f5f5f5;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
}
|
|
#gjs { height: 100%; border: none; }
|
|
.gjs-one-bg { background-color: #f8f9fa; }
|
|
.gjs-two-color { color: #333; }
|
|
.gjs-three-bg { background-color: #009195; color: #fff; }
|
|
.gjs-four-color,
|
|
.gjs-four-color-h:hover { color: #009195; }
|
|
.gjs-pn-btn.gjs-pn-active,
|
|
.gjs-pn-btn:hover { color: #009195; }
|
|
.gjs-block { min-height: 54px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="gjs"></div>
|
|
|
|
<script src="https://unpkg.com/grapesjs"></script>
|
|
<script src="https://unpkg.com/grapesjs-preset-newsletter"></script>
|
|
<script>
|
|
let editor = null;
|
|
let pendingHtml = null;
|
|
let changeTimer = null;
|
|
let lastExportedHtml = '';
|
|
let destroyed = false;
|
|
|
|
function postToParent(payload) {
|
|
if (destroyed) return;
|
|
if (window.parent && window.parent !== window) {
|
|
window.parent.postMessage(JSON.stringify(payload), '*');
|
|
}
|
|
}
|
|
|
|
function extractBodyContent(html) {
|
|
if (!html) return '';
|
|
const match = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
return match ? match[1].trim() : html;
|
|
}
|
|
|
|
function wrapAsEmailDocument(innerHtml) {
|
|
return '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8">\n<meta name="viewport" content="width=device-width, initial-scale=1">\n</head>\n<body style="margin:0;padding:0;">' + innerHtml + '\n</body>\n</html>';
|
|
}
|
|
|
|
function exportFullHtml() {
|
|
if (!editor) return '';
|
|
try {
|
|
const inlined = editor.runCommand('gjs-get-inlined-html');
|
|
return wrapAsEmailDocument(inlined || '');
|
|
} catch (e) {
|
|
const html = editor.getHtml() || '';
|
|
const css = editor.getCss() || '';
|
|
return wrapAsEmailDocument('<style>' + css + '</style>' + html);
|
|
}
|
|
}
|
|
|
|
function scheduleChange() {
|
|
if (destroyed) return;
|
|
clearTimeout(changeTimer);
|
|
changeTimer = setTimeout(function () {
|
|
if (destroyed || !editor) return;
|
|
const html = exportFullHtml();
|
|
if (!html || html === lastExportedHtml) return;
|
|
lastExportedHtml = html;
|
|
postToParent({ source: 'nhance-email-editor', action: 'change', html: html });
|
|
}, 800);
|
|
}
|
|
|
|
function loadHtml(fullHtml) {
|
|
if (destroyed || !editor) {
|
|
pendingHtml = fullHtml;
|
|
return;
|
|
}
|
|
const content = extractBodyContent(fullHtml);
|
|
editor.setComponents(content);
|
|
makePlaceholderTokensEditable();
|
|
editor.refresh();
|
|
lastExportedHtml = exportFullHtml();
|
|
}
|
|
|
|
function makePlaceholderTokensEditable() {
|
|
if (destroyed || !editor) return;
|
|
const wrapper = editor.getWrapper();
|
|
if (!wrapper) return;
|
|
|
|
const visit = function (cmp) {
|
|
if (!cmp || typeof cmp.get !== 'function') return;
|
|
|
|
const content = (cmp.get('content') || '').toString();
|
|
if (/\{\{[^{}]+\}\}/.test(content)) {
|
|
cmp.set('editable', true);
|
|
cmp.set('selectable', true);
|
|
cmp.set('hoverable', true);
|
|
cmp.set('copyable', true);
|
|
}
|
|
|
|
const children = cmp.components && cmp.components();
|
|
if (children && typeof children.each === 'function') {
|
|
children.each(function (child) {
|
|
visit(child);
|
|
});
|
|
}
|
|
};
|
|
|
|
visit(wrapper);
|
|
|
|
// Ensure rendered canvas nodes containing placeholders are editable too.
|
|
// Some newsletter/table components keep internal text nodes read-only unless
|
|
// contenteditable is explicitly set on the live DOM element.
|
|
const doc = editor.Canvas && editor.Canvas.getDocument
|
|
? editor.Canvas.getDocument()
|
|
: null;
|
|
if (!doc) return;
|
|
|
|
const nodes = doc.querySelectorAll('*');
|
|
nodes.forEach(function (el) {
|
|
if (!el || !el.innerHTML) return;
|
|
if (/\{\{[^{}]+\}\}/.test(el.innerHTML)) {
|
|
el.setAttribute('contenteditable', 'true');
|
|
}
|
|
});
|
|
}
|
|
|
|
function insertToken(token) {
|
|
if (destroyed || !editor) return;
|
|
const selected = editor.getSelected();
|
|
const cmp = selected && selected.get('type') !== 'wrapper'
|
|
? selected
|
|
: editor.getWrapper().components().at(0);
|
|
if (cmp && cmp.append) {
|
|
cmp.append({
|
|
type: 'text',
|
|
content: token,
|
|
editable: true,
|
|
selectable: true,
|
|
hoverable: true,
|
|
copyable: true,
|
|
});
|
|
} else {
|
|
editor.addComponents({
|
|
type: 'text',
|
|
content: token,
|
|
editable: true,
|
|
selectable: true,
|
|
hoverable: true,
|
|
copyable: true,
|
|
});
|
|
}
|
|
makePlaceholderTokensEditable();
|
|
scheduleChange();
|
|
}
|
|
|
|
function destroyEditor() {
|
|
destroyed = true;
|
|
clearTimeout(changeTimer);
|
|
changeTimer = null;
|
|
try {
|
|
if (editor) editor.destroy();
|
|
} catch (e) {}
|
|
editor = null;
|
|
}
|
|
|
|
function initEditor() {
|
|
try {
|
|
editor = grapesjs.init({
|
|
container: '#gjs',
|
|
height: '100%',
|
|
width: 'auto',
|
|
fromElement: false,
|
|
storageManager: false,
|
|
noticeOnUnload: false,
|
|
canvas: {
|
|
styles: ['https://fonts.googleapis.com/css?family=Roboto:400,500,700'],
|
|
},
|
|
deviceManager: {
|
|
devices: [
|
|
{ name: 'Desktop', width: '' },
|
|
{ name: 'Mobile', width: '320px', widthMedia: '480px' },
|
|
],
|
|
},
|
|
plugins: ['grapesjs-preset-newsletter'],
|
|
pluginsOpts: {
|
|
'grapesjs-preset-newsletter': {
|
|
modalLabelImport: 'Paste your HTML',
|
|
modalLabelExport: 'Copy HTML',
|
|
codeViewerTheme: 'material',
|
|
},
|
|
},
|
|
});
|
|
|
|
editor.on('update', scheduleChange);
|
|
editor.on('component:selected', function () {
|
|
makePlaceholderTokensEditable();
|
|
});
|
|
|
|
if (pendingHtml) {
|
|
loadHtml(pendingHtml);
|
|
pendingHtml = null;
|
|
}
|
|
|
|
postToParent({ source: 'nhance-email-editor', action: 'ready' });
|
|
} catch (e) {
|
|
postToParent({ source: 'nhance-email-editor', action: 'error', message: String(e) });
|
|
}
|
|
}
|
|
|
|
window.addEventListener('message', function (event) {
|
|
let data = event.data || {};
|
|
if (typeof data === 'string') {
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (_) {
|
|
return;
|
|
}
|
|
}
|
|
if (data.source !== 'nhance-email-editor-host') return;
|
|
|
|
switch (data.action) {
|
|
case 'load':
|
|
loadHtml(data.html || '');
|
|
break;
|
|
case 'export':
|
|
postToParent({
|
|
source: 'nhance-email-editor',
|
|
action: 'html',
|
|
requestId: data.requestId,
|
|
html: exportFullHtml(),
|
|
});
|
|
break;
|
|
case 'insertToken':
|
|
insertToken(data.token || '');
|
|
break;
|
|
case 'destroy':
|
|
destroyEditor();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
|
|
initEditor();
|
|
</script>
|
|
</body>
|
|
</html>
|