(function () { var dataElement = document.getElementById('slide-editor-data'); if (!dataElement) { return; } var slideEditorData = {}; try { slideEditorData = JSON.parse(dataElement.getAttribute('data-json') || dataElement.textContent || '{}') || {}; } catch (_error) { slideEditorData = {}; } var templates = Array.isArray(slideEditorData.templates) ? slideEditorData.templates : []; var existingTemplateId = slideEditorData.existingTemplateId !== undefined ? slideEditorData.existingTemplateId : null; var existingContent = slideEditorData.existingContent || {}; var templateSelect = document.getElementById('template-select'); var templateFields = document.getElementById('template-fields'); var slideForm = document.getElementById('slide-form'); var slidePreviewMeta = document.getElementById('slide-preview-meta'); var slidePreviewStage = document.getElementById('slide-preview-stage'); var slidePreviewBackground = document.getElementById('slide-preview-background'); var slidePreviewEmpty = document.getElementById('slide-preview-empty'); var slidePreviewOverlay = document.getElementById('slide-preview-overlay'); var editorInstances = new Map(); var submitting = false; function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getTemplateById(id) { return templates.find(function (item) { return Number(item.id) === Number(id); }) || null; } function getCurrentRegionValue(region) { var current = existingContent[region.region_key]; return current && current.value !== undefined ? current.value : ''; } function normalizeEditorData(value) { var raw = String(value || ''); if (!raw) { return { time: Date.now(), blocks: [] }; } try { var parsed = JSON.parse(raw); if (parsed && Array.isArray(parsed.blocks)) { return parsed; } } catch (_error) { // fall through to legacy HTML/plain text } return { time: Date.now(), blocks: [{ type: 'paragraph', data: { text: raw } }] }; } function sanitizePreviewHtml(html) { var output = String(html || ''); output = output.replace(//gi, ''); output = output.replace(//gi, ''); return output.replace(/<[^>]+>/g, function (tag) { var match = tag.match(/^<\s*(\/?)\s*([a-z0-9]+)(?:\s[^>]*)?>$/i); if (!match) { return ''; } var closing = Boolean(match[1]); var name = String(match[2] || '').toLowerCase(); var allowed = ['b', 'strong', 'i', 'em', 'u', 'br', 'p', 'div', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'code', 'table', 'thead', 'tbody', 'tr', 'th', 'td']; if (allowed.indexOf(name) === -1) { return ''; } if (name === 'br') { return '
'; } return closing ? '' : '<' + name + '>'; }); } function renderPreviewListItem(item, tag) { if (item && typeof item === 'object') { var content = item.content !== undefined ? item.content : (item.text !== undefined ? item.text : item.value !== undefined ? item.value : ''); var children = Array.isArray(item.items) ? item.items : Array.isArray(item.subItems) ? item.subItems : Array.isArray(item.children) ? item.children : []; var nested = children.length ? '<' + tag + ' style="list-style-type:' + (tag === 'ol' ? 'decimal' : 'disc') + ';padding-left:1.4em;">' + children.map(function (child) { return renderPreviewListItem(child, tag); }).join('') + '' : ''; return '
  • ' + sanitizePreviewHtml(content || '') + nested + '
  • '; } return '
  • ' + sanitizePreviewHtml(item || '') + '
  • '; } function renderPreviewTable(data) { var rows = Array.isArray(data.content) ? data.content : Array.isArray(data.rows) ? data.rows : []; if (!rows.length) { return ''; } var hasHeadings = Boolean(data.withHeadings); var body = rows.map(function (row, rowIndex) { var cells = Array.isArray(row) ? row : []; var cellTag = hasHeadings && rowIndex === 0 ? 'th' : 'td'; var cellAttrs = cellTag === 'th' ? ' scope="col"' : ''; return '' + cells.map(function (cell) { return '<' + cellTag + cellAttrs + '>' + sanitizePreviewHtml(cell || '') + ''; }).join('') + ''; }).join(''); return '' + body + '
    '; } function renderPreviewBlock(block) { if (!block || !block.type || !block.data) { return ''; } if (block.type === 'header') { var level = Math.max(1, Math.min(6, Number(block.data.level || 2))); return '' + sanitizePreviewHtml(block.data.text || '') + ''; } if (block.type === 'list') { var tag = block.data.style === 'ordered' ? 'ol' : 'ul'; var items = Array.isArray(block.data.items) ? block.data.items : []; return '<' + tag + ' style="list-style-type:' + (tag === 'ol' ? 'decimal' : 'disc') + ';padding-left:1.4em;">' + items.map(function (item) { return renderPreviewListItem(item, tag); }).join('') + ''; } if (block.type === 'delimiter') { return '
    '; } if (block.type === 'code') { return '
    ' + sanitizePreviewHtml(block.data.code || '') + '
    '; } if (block.type === 'table') { return renderPreviewTable(block.data); } if (block.type === 'paragraph') { return '

    ' + sanitizePreviewHtml(block.data.text || '') + '

    '; } return ''; } function renderPreviewText(value) { var raw = String(value || ''); if (!raw) { return '
    Empty text
    '; } try { var parsed = JSON.parse(raw); if (parsed && Array.isArray(parsed.blocks)) { return parsed.blocks.map(renderPreviewBlock).join(''); } } catch (_error) { // fall through to raw text } return '

    ' + sanitizePreviewHtml(raw) + '

    '; } function renderPreviewTextRegion(region, value, style, scale) { var fontFamily = style.font_family ? 'font-family:' + escapeHtml(style.font_family) + ';' : ''; var fontSize = style.font_size ? 'font-size:' + Math.max(1, Math.round(Number(style.font_size) * scale)) + 'px;' : ''; var color = style.font_color ? 'color:' + escapeHtml(style.font_color) + ';' : ''; return '
    ' + renderPreviewText(value) + '
    '; } function renderPreviewWebpageRegion(value) { var src = String(value || '').trim(); if (!src) { return '
    Webpage
    '; } return ''; } function renderPreviewHtmlRegion(value) { var html = String(value || '').trim(); if (!html) { return '
    HTML
    '; } return ''; } function destroyEditors() { editorInstances.forEach(function (editor) { if (editor && typeof editor.destroy === 'function') { editor.destroy(); } }); editorInstances.clear(); } function getEditorHiddenInput(regionId) { var card = templateFields.querySelector('[data-region-id="' + regionId + '"]'); return card ? card.querySelector('input[type="hidden"][name="region_text_' + regionId + '"]') : null; } function getCurrentTextStyle(region) { var current = existingContent[region.region_key] || {}; return { font_family: String(current.font_family || region.font_family || 'Arial').trim() || 'Arial', font_size: Math.max(8, Number(current.font_size || 24)), font_color: String(current.font_color || region.font_color || '#000000').trim() || '#000000' }; } function getTextStyleFromCard(card, region) { var familyInput = card.querySelector('[name="region_font_family_' + region.id + '"]'); var sizeInput = card.querySelector('[name="region_font_size_' + region.id + '"]'); var colorInput = card.querySelector('[name="region_font_color_' + region.id + '"]'); var current = getCurrentTextStyle(region); return { font_family: String(familyInput ? familyInput.value : current.font_family).trim() || current.font_family, font_size: Math.max(8, Number(sizeInput ? sizeInput.value : current.font_size)), font_color: String(colorInput ? colorInput.value : current.font_color).trim() || current.font_color }; } function getSelectedTemplate() { return templates.find(function (item) { return Number(item.id) === Number(templateSelect.value); }) || null; } function clearImagePreviewUrls() { templateFields.querySelectorAll('input[type="file"][name^="region_image_"]').forEach(function (input) { if (input.dataset.previewUrl) { URL.revokeObjectURL(input.dataset.previewUrl); delete input.dataset.previewUrl; } }); } function getRegionBox(region, stageRect, canvasWidth, canvasHeight) { return { left: (Number(region.x || 0) / canvasWidth) * stageRect.width, top: (Number(region.y || 0) / canvasHeight) * stageRect.height, width: (Number(region.width || 0) / canvasWidth) * stageRect.width, height: (Number(region.height || 0) / canvasHeight) * stageRect.height }; } function renderPreview() { var template = getSelectedTemplate(); if (!template) { slidePreviewMeta.textContent = 'Select a template to preview the slide.'; slidePreviewBackground.style.display = 'none'; slidePreviewEmpty.style.display = 'flex'; slidePreviewEmpty.textContent = 'Select a template to preview the slide.'; slidePreviewOverlay.innerHTML = ''; slidePreviewStage.style.aspectRatio = '16 / 9'; return; } var canvasWidth = Math.max(1, Number(template.canvas_size_width || 1920)); var canvasHeight = Math.max(1, Number(template.canvas_size_height || 1080)); slidePreviewStage.style.aspectRatio = canvasWidth + ' / ' + canvasHeight; slidePreviewMeta.textContent = template.name + ' • ' + canvasWidth + 'x' + canvasHeight; if (!(template.regions || []).length) { slidePreviewEmpty.style.display = 'flex'; slidePreviewEmpty.textContent = 'This template has no regions.'; slidePreviewBackground.style.display = template.background_image_path ? 'block' : 'none'; slidePreviewBackground.src = template.background_image_path || ''; slidePreviewOverlay.innerHTML = ''; return; } slidePreviewEmpty.style.display = 'none'; slidePreviewBackground.style.display = template.background_image_path ? 'block' : 'none'; slidePreviewBackground.src = template.background_image_path || ''; var stageRect = slidePreviewStage.getBoundingClientRect(); var scale = Math.min(stageRect.width / canvasWidth, stageRect.height / canvasHeight) || 1; var regions = template.regions || []; slidePreviewOverlay.innerHTML = regions.map(function (region) { var box = getRegionBox(region, stageRect, canvasWidth, canvasHeight); var card = templateFields.querySelector('[data-region-id="' + region.id + '"]'); var hiddenInput = card && card.querySelector('input[type="hidden"][name="region_text_' + region.id + '"]'); var htmlInput = card && card.querySelector('textarea[name="region_html_' + region.id + '"]'); var imageHidden = card && card.querySelector('input[type="hidden"][name="existing_region_image_' + region.id + '"]'); var imageFileInput = card && card.querySelector('input[type="file"][name="region_image_' + region.id + '"]'); var webpageInput = card && card.querySelector('input[type="url"][name="region_webpage_' + region.id + '"]'); var value = region.region_type === 'image' ? ((imageFileInput && imageFileInput.dataset.previewUrl) ? imageFileInput.dataset.previewUrl : (imageHidden ? imageHidden.value : '')) : region.region_type === 'webpage' ? (webpageInput ? webpageInput.value : '') : region.region_type === 'html' ? (htmlInput ? htmlInput.value : '') : (hiddenInput ? hiddenInput.value : ''); var style = region.region_type === 'image' ? null : region.region_type === 'webpage' ? null : region.region_type === 'html' ? null : getTextStyleFromCard(card, region); var content = region.region_type === 'image' ? (value ? '' : '
    Image
    ') : region.region_type === 'webpage' ? renderPreviewWebpageRegion(value) : region.region_type === 'html' ? renderPreviewHtmlRegion(value) : renderPreviewTextRegion(region, value, style, scale); var selected = region.region_type === 'image' ? ' slide-preview-image-region' : region.region_type === 'webpage' ? ' slide-preview-webpage-region' : region.region_type === 'html' ? ' slide-preview-html-region' : ' slide-preview-text-region'; return '
    ' + content + '
    '; }).join(''); } function renderTextRegion(region) { var current = getCurrentRegionValue(region); var style = getCurrentTextStyle(region); var fontChoices = [ 'Arial', 'Courier New', 'Garamond', 'Georgia', 'Helvetica', 'Impact', 'Lucida Console', 'Palatino', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana' ]; return '' + '
    ' + '
    ' + '' + escapeHtml(region.label) + '' + 'Text' + '
    ' + '
    ' + '' + '' + '' + '
    ' + '
    ' + '' + '
    '; } function renderWebpageRegion(region) { var current = getCurrentRegionValue(region); return '' + '
    ' + '
    ' + '' + escapeHtml(region.label) + '' + 'Webpage' + '
    ' + '' + '
    The webpage will be loaded in an iframe.
    ' + '
    '; } function renderHtmlRegion(region) { var current = getCurrentRegionValue(region); return '' + '
    ' + '
    ' + '' + escapeHtml(region.label) + '' + 'HTML' + '
    ' + '' + '
    HTML is rendered in a sandboxed iframe preview.
    ' + '
    '; } function renderImageRegion(region) { var current = getCurrentRegionValue(region); return '' + '
    ' + '
    ' + '' + escapeHtml(region.label) + '' + 'Image' + '
    ' + '' + '' + (current ? '' : '
    No image selected.
    ') + '
    '; } function renderTemplate() { clearImagePreviewUrls(); destroyEditors(); var template = getTemplateById(templateSelect.value); if (!template) { templateFields.innerHTML = ''; renderPreview(); return; } templateFields.innerHTML = template.regions.length ? template.regions.map(function (region) { if (region.region_type === 'image') { return renderImageRegion(region); } if (region.region_type === 'webpage') { return renderWebpageRegion(region); } if (region.region_type === 'html') { return renderHtmlRegion(region); } return renderTextRegion(region); }).join('') : '
    This template has no regions.
    '; templateFields.querySelectorAll('.editorjs-holder').forEach(function (holder) { var regionId = holder.getAttribute('data-region-id'); var hidden = getEditorHiddenInput(regionId); var editor = new EditorJS({ holder: holder.id, minHeight: 120, placeholder: 'Write content...', inlineToolbar: true, tools: { header: { class: Header, config: { placeholder: 'Enter a header', levels: [1, 2, 3, 4], defaultLevel: 3 } }, list: { class: EditorjsList, inlineToolbar: false, config: { defaultStyle: 'unordered' } }, table: { class: Table, inlineToolbar: true, config: { rows: 2, cols: 3, maxRows: 5, maxCols: 5 } }, marker: { class: Marker } }, data: normalizeEditorData(hidden ? hidden.value : ''), onChange: function () { var instance = editorInstances.get(regionId); if (!instance) { return; } instance.save().then(function (output) { if (hidden) { hidden.value = JSON.stringify(output); } renderPreview(); }); } }); editorInstances.set(regionId, editor); }); templateFields.querySelectorAll('input[type="file"][name^="region_image_"]').forEach(function (input) { input.addEventListener('change', function () { if (input.dataset.previewUrl) { URL.revokeObjectURL(input.dataset.previewUrl); delete input.dataset.previewUrl; } if (input.files && input.files.length) { input.dataset.previewUrl = URL.createObjectURL(input.files[0]); } renderPreview(); }); }); renderPreview(); } slideForm.addEventListener('submit', async function (event) { if (submitting) { return; } event.preventDefault(); var saves = Array.prototype.map.call(templateFields.querySelectorAll('.editorjs-holder'), function (holder) { var regionId = holder.getAttribute('data-region-id'); var editor = editorInstances.get(regionId); var hidden = getEditorHiddenInput(regionId); if (!editor || !hidden) { return Promise.resolve(); } return editor.save().then(function (output) { hidden.value = JSON.stringify(output); }); }); submitting = true; try { await Promise.all(saves); var response = await fetch(slideForm.action, { method: (slideForm.method || 'POST').toUpperCase(), headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html, application/json, text/plain, */*' }, body: new FormData(slideForm), credentials: 'same-origin' }); if (!response.ok) { throw new Error(await response.text() || 'Unable to save slide.'); } var responseText = await response.text(); var savedMessage = ''; try { var doc = new DOMParser().parseFromString(responseText || '', 'text/html'); var toastBody = doc.querySelector('.toast-body'); if (toastBody && toastBody.textContent) { savedMessage = toastBody.textContent.trim(); } } catch (_error) { savedMessage = ''; } showToast(savedMessage || 'Saved slide.'); window.location.href = response.url || slideForm.action; } finally { submitting = false; } }); templateSelect.addEventListener('change', renderTemplate); templateFields.addEventListener('change', renderPreview); templateFields.addEventListener('input', renderPreview); if (existingTemplateId) { templateSelect.value = String(existingTemplateId); } renderTemplate(); window.addEventListener('resize', renderPreview); })();