export function createSlideFormEditorController(options) { var settings = options || {}; var templateFields = settings.templateFields || null; var templateSelectorLock = settings.templateSelectorLock || null; var requestPreviewRender = typeof settings.requestPreviewRender === 'function' ? settings.requestPreviewRender : function () {}; var defaultFontSize = Math.max(1, Number(settings.defaultFontSize || 32)); var fontFamilyFormats = String(settings.fontFamilyFormats || '').trim(); var fontStylesheetHref = String(settings.fontStylesheetHref || '').trim(); var defaultEditorFontFamily = 'Arial, Helvetica, sans-serif'; var editorInstances = new Map(); var getRegionTypeModule = typeof settings.getRegionTypeModule === 'function' ? settings.getRegionTypeModule : function () { return null; }; var getEditorBackgroundColor = typeof settings.getEditorBackgroundColor === 'function' ? settings.getEditorBackgroundColor : function () { return '#111111'; }; function normalizeEditorData(value) { return String(value || ''); } function normalizeEditorMarkup(value) { return String(value === undefined || value === null ? '' : value).trim(); } function isEmptyRichTextValue(value) { var raw = String(value === undefined || value === null ? '' : value).trim(); if (!raw) { return true; } var stripped = raw .replace(/<\s*br\s*\/?>/gi, '') .replace(/]*>(?:\s| |)*<\/p>/gi, '') .replace(/<[^>]+>/g, '') .replace(/ /gi, '') .trim(); return !stripped; } function normalizeFontSizeValue(value) { var raw = String(value || '').trim().toLowerCase(); if (/^\d+(?:\.\d+)?px$/.test(raw)) { return String(Math.max(1, Math.round(Number(raw.replace(/px$/, ''))))); } if (/^\d+(?:\.\d+)?$/.test(raw)) { return String(Math.max(1, Math.round(Number(raw)))); } return ''; } function getEditorHiddenInput(regionId) { if (!templateFields) { return null; } var card = templateFields.querySelector('[data-region-id="' + regionId + '"]'); return card ? card.querySelector('input[type="hidden"][name="region_text_' + regionId + '"]') : null; } function getEditorFontSizeHiddenInput(regionId) { if (!templateFields) { return null; } var card = templateFields.querySelector('[data-region-id="' + regionId + '"]'); return card ? card.querySelector('input[name="region_font_size_' + regionId + '"]') : null; } function getTinymce() { return window.tinymce || null; } function getEditorContentSafely(editor, fallbackValue) { if (!editor || typeof editor.getContent !== 'function') { return String(fallbackValue || ''); } try { return String(editor.getContent({ format: 'html' })); } catch (_error) { return String(fallbackValue || ''); } } function getThemeName() { var theme = String(document.documentElement && document.documentElement.dataset && document.documentElement.dataset.bsTheme || 'light').trim().toLowerCase(); if (theme === 'auto') { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } return theme === 'dark' ? 'dark' : 'light'; } function getTinyMceThemeAssets() { var themeName = getThemeName(); var skinName = themeName === 'dark' ? 'oxide-dark' : 'oxide'; var skinBase = '/assets/vendor/tinymce/skins/ui/' + skinName; return { themeName: themeName, skinName: skinName, skinUrl: skinBase, contentCss: skinBase + '/content.css', bodyClass: themeName === 'dark' ? 'tinymce-theme-dark' : 'tinymce-theme-light' }; } function syncEditorState(regionId, editor, shouldLock, hydrated) { var hidden = getEditorHiddenInput(regionId); var sourceElm = editor && editor.targetElm ? editor.targetElm : null; var fallbackContent = hidden && hidden.value !== undefined ? hidden.value : (sourceElm && sourceElm.value !== undefined ? sourceElm.value : ''); if (editor && typeof editor.save === 'function') { if (typeof editor.blur === 'function') { editor.blur(); } editor.save(); } var content = sourceElm && sourceElm.value !== undefined ? String(sourceElm.value || '') : getEditorContentSafely(editor, fallbackContent); content = isEmptyRichTextValue(content) ? '' : content; if (hidden) { hidden.value = content; } if (sourceElm) { sourceElm.value = content; } if (typeof editor.nodeChanged === 'function') { editor.nodeChanged(); } if (typeof editor.setDirty === 'function') { editor.setDirty(true); } if (shouldLock && templateSelectorLock && typeof templateSelectorLock.arm === 'function') { templateSelectorLock.arm(); } if (shouldLock && templateSelectorLock && typeof templateSelectorLock.markEdited === 'function') { templateSelectorLock.markEdited(); } if (hydrated) { requestPreviewRender(); } } function getEditorRegionType(card) { if (!card) { return ''; } if (card.dataset && String(card.dataset.regionMediaType || '').trim()) { return String(card.dataset.regionMediaType || '').trim(); } var regionTypeInput = card.querySelector ? card.querySelector('input[type="hidden"][name="region_type[]"]') : null; return String(regionTypeInput && regionTypeInput.value || '').trim(); } function getEditorContentStyle(regionType) { var module = getRegionTypeModule(regionType); if (module && typeof module.getEditorContentStyle === 'function') { return String(module.getEditorContentStyle() || '').trim(); } return ''; } function syncEditorFontSizeHidden(regionId) { var fontSizeHidden = getEditorFontSizeHiddenInput(regionId); if (!fontSizeHidden) { return; } fontSizeHidden.value = normalizeFontSizeValue(fontSizeHidden.value) || String(defaultFontSize); } function getEditorBackgroundColorValue() { var value = String(getEditorBackgroundColor() || '').trim(); return value || '#111111'; } function getFontFamilyFormats() { if (fontFamilyFormats) { return fontFamilyFormats; } return [ 'Default=inherit', 'Arial=Arial,Helvetica,sans-serif', 'Comic Sans MS=Comic Sans MS,cursive,sans-serif', 'Courier New=Courier New,Courier,monospace', 'Georgia=Georgia,serif', 'Helvetica=Helvetica,Arial,sans-serif', 'Impact=Impact,Charcoal,sans-serif', 'Lucida Sans Unicode=Lucida Sans Unicode,Lucida Grande,sans-serif', 'Palatino Linotype=Palatino Linotype,Book Antiqua,Palatino,serif', 'Tahoma=Tahoma,Geneva,sans-serif', 'Times New Roman=Times New Roman,Times,serif', 'Trebuchet MS=Trebuchet MS,Helvetica,sans-serif', 'Verdana=Verdana,Geneva,sans-serif' ].join(';'); } function getContentCss() { var themeAssets = getTinyMceThemeAssets(); return fontStylesheetHref ? [themeAssets.contentCss, fontStylesheetHref] : themeAssets.contentCss; } function attachEditorEvents(regionId, editor) { var hidden = getEditorHiddenInput(regionId); var source = editor && editor.targetElm ? editor.targetElm : null; var hydrated = false; function syncState(shouldLock) { var currentEditor = editorInstances.get(regionId); if (currentEditor !== editor) { return; } syncEditorState(regionId, editor, shouldLock, hydrated); if (!hydrated) { requestPreviewRender(); return; } } editor.on('init', function () { var content = getEditorContentSafely(editor, hidden && hidden.value !== undefined ? hidden.value : (source && source.value !== undefined ? source.value : '')); content = isEmptyRichTextValue(content) ? '' : content; if (hidden) { hidden.value = content; } if (source) { source.value = content; } syncEditorFontSizeHidden(regionId); hydrated = true; requestPreviewRender(); }); ['change', 'keyup', 'undo', 'redo', 'Paste', 'input'].forEach(function (eventName) { editor.on(eventName, function () { syncState(true); }); }); } function createEditorForHolder(holder) { if (!holder) { return Promise.resolve(null); } var regionId = holder.getAttribute('data-region-id'); var card = holder.closest ? holder.closest('[data-region-id]') : null; var source = holder.querySelector('.editor-source'); var hidden = getEditorHiddenInput(regionId); var tinymce = getTinymce(); var themeAssets = getTinyMceThemeAssets(); var editorContentStyle = getEditorContentStyle(getEditorRegionType(card)); if (!source || !tinymce || typeof tinymce.init !== 'function') { return Promise.resolve(null); } source.value = normalizeEditorMarkup(normalizeEditorData(source && source.value !== undefined && source.value !== null ? source.value : (hidden ? hidden.value : ''))); if (!source.id) { source.id = 'slide-editor-region-' + regionId; } function registerInlineFormat(editor, formatName, styles) { editor.formatter.register(formatName, { inline: 'span', styles: styles }); } function applyInlineFormat(regionId, editor, formatName) { if (editor && editor.undoManager && typeof editor.undoManager.transact === 'function') { editor.undoManager.transact(function () { editor.formatter.apply(formatName); }); } else { editor.formatter.apply(formatName); } syncEditorState(regionId, editor, false, true); } function applyEditorCommand(regionId, editor, commandName) { if (editor && editor.undoManager && typeof editor.undoManager.transact === 'function') { editor.undoManager.transact(function () { editor.execCommand(commandName); }); } else { editor.execCommand(commandName); } syncEditorState(regionId, editor, false, true); } return tinymce.init({ target: source, menubar: false, branding: false, promotion: false, paste_data_images: false, plugins: 'lists code advlist fullscreen table', toolbar: 'undo redo | fontfamily fontsizeinput | forecolor backcolor bold italic underlineformats removeformat | align lineheight indent outdent bullist numlist table chip | fullscreen', toolbar_mode: 'sliding', license_key: 'gpl', skin: themeAssets.skinName, skin_url: themeAssets.skinUrl, content_css: getContentCss(), body_class: themeAssets.bodyClass, content_style: 'body { font-family: ' + defaultEditorFontFamily + '; font-size: 32px; line-height: 1.5; background-color: ' + getEditorBackgroundColorValue() + '; } p { margin: 1em 0; } p:first-child { margin-top: 0; } p:last-child { margin-bottom: 1em; } table { border-collapse: collapse; width: 100%; } td, th { border: 1px solid currentColor; padding: 0.35em 0.5em; vertical-align: top; } th { font-weight: 700; }' + (editorContentStyle ? ' ' + editorContentStyle : ''), font_family_formats: getFontFamilyFormats(), font_size_input_default_unit: 'px', invalid_elements: 'a', forced_root_block: 'p', force_br_newlines: false, newline_behavior: 'default', placeholder: String(source.getAttribute('placeholder') || '').trim(), setup: function (editor) { editorInstances.set(regionId, editor); editor.ui.registry.addButton('chip', { icon: 'addtag', tooltip: 'Chip style', onAction: function () { applyInlineFormat(regionId, editor, 'chip'); } }); editor.ui.registry.addSplitButton('underlineformats', { icon: 'underline', tooltip: 'Text formats', onAction: function () { applyEditorCommand(regionId, editor, 'Underline'); }, onItemAction: function (_api, value) { if (value === 'strikethrough') { applyEditorCommand(regionId, editor, 'Strikethrough'); return; } if (value === 'subscript') { applyEditorCommand(regionId, editor, 'Subscript'); return; } if (value === 'superscript') { applyEditorCommand(regionId, editor, 'Superscript'); } }, fetch: function (callback) { callback([ { type: 'choiceitem', value: 'strikethrough', icon: 'strikethrough', text: 'Strikethrough', }, { type: 'choiceitem', value: 'subscript', icon: 'subscript', text: 'Subscript', }, { type: 'choiceitem', value: 'superscript', icon: 'superscript', text: 'Superscript', } ]); } }); editor.on('init', function () { registerInlineFormat(editor, 'chip', { display: 'inline-block', padding: '0.2em 0.6em', borderRadius: '999px', fontWeight: '700', letterSpacing: '0.02em' }); }); attachEditorEvents(regionId, editor); } }).then(function (editors) { var editor = Array.isArray(editors) && editors.length ? editors[0] : tinymce.get(source.id); if (!editor) { return null; } editorInstances.set(regionId, editor); if (editor.targetElm) { editor.targetElm.value = editor.getContent({ format: 'html' }); } return editor; }).catch(function (error) { console.error('Failed to initialize TinyMCE.', error); return null; }); } function renderEditors() { if (!templateFields) { return Promise.resolve([]); } watchThemeChanges(); var holders = Array.prototype.slice.call(templateFields.querySelectorAll('.editor-holder')); return Promise.all(holders.map(function (holder) { return createEditorForHolder(holder); })); } function syncEditors() { if (!templateFields) { return Promise.resolve([]); } var saves = Array.prototype.map.call(templateFields.querySelectorAll('.editor-holder'), function (holder) { var regionId = holder.getAttribute('data-region-id'); var editor = editorInstances.get(regionId); if (!editor) { return Promise.resolve(); } syncEditorState(regionId, editor, false, true); return Promise.resolve(); }); return Promise.all(saves); } function destroyEditors() { editorInstances.forEach(function (editor) { if (editor && typeof editor.remove === 'function') { editor.remove(); } }); editorInstances.clear(); var tinymce = getTinymce(); if (tinymce && typeof tinymce.remove === 'function') { tinymce.remove(); } } var themeObserver = null; function watchThemeChanges() { if (themeObserver || !window.MutationObserver || !templateFields) { return; } themeObserver = new MutationObserver(function () { destroyEditors(); renderEditors(); }); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-bs-theme'] }); } return { renderEditors: renderEditors, syncEditors: syncEditors, destroyEditors: function () { if (themeObserver) { themeObserver.disconnect(); themeObserver = null; } destroyEditors(); }, watchThemeChanges: watchThemeChanges }; }