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 markFormDirty = typeof settings.markFormDirty === 'function' ? settings.markFormDirty : 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 editorHeights = new Map(); var editorSizeControlsBound = false; var getRegionTypeModule = typeof settings.getRegionTypeModule === 'function' ? settings.getRegionTypeModule : function () { return null; }; var imageUploadUrl = String(settings.imageUploadUrl || '/slides/uploads').trim() || '/slides/uploads'; var imageUploadMaxBytes = Math.max(1, Number(settings.imageUploadMaxBytes || 2 * 1024 * 1024)); var imageUploadLimitLabel = String(settings.imageUploadLimitLabel || '').trim() || Math.max(1, Math.round(imageUploadMaxBytes / (1024 * 1024))) + ' MB'; var imageUploadContext = String(settings.imageUploadContext || 'wysiwyg').trim() || 'wysiwyg'; var configuredImageMimeTypes = Array.isArray(settings.uploadMimeTypes) ? settings.uploadMimeTypes.filter(function (mimeType) { return String(mimeType || '').indexOf('image/') === 0; }) : []; var imageUploadAllowedMimeTypes = configuredImageMimeTypes.length ? configuredImageMimeTypes : ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml']; var imageUploadAllowedExtensions = imageUploadAllowedMimeTypes.map(function (mimeType) { var subtype = String(mimeType).split('/')[1] || ''; return subtype === 'jpeg' ? 'jpg' : subtype === 'svg+xml' ? 'svg' : subtype; }); var imageUploadFileTypes = imageUploadAllowedExtensions.join(','); var wysiwygEditorHeightStep = Math.max(1, Number(settings.wysiwygEditorHeightStep || 80)); var wysiwygEditorHeightMin = Math.max(1, Number(settings.wysiwygEditorHeightMin || 240)); var wysiwygEditorHeightMax = Math.max(wysiwygEditorHeightMin, Number(settings.wysiwygEditorHeightMax || 960)); var wysiwygEditorHeightDefault = Math.max(wysiwygEditorHeightMin, Number(settings.wysiwygEditorHeightDefault || 360)); var editorImageUploadPaths = new Set(); var committedEditorImageUploadPaths = new Set(); var pendingEditorImageUploadCleanupPaths = new Set(); var getEditorBackgroundColor = typeof settings.getEditorBackgroundColor === 'function' ? settings.getEditorBackgroundColor : function () { return '#111111'; }; function normalizeEditorData(value) { return String(value || ''); } function isEmptyRichTextValue(value) { var raw = String(value === undefined || value === null ? '' : value).trim(); if (!raw) { return true; } if (//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 normalizeEditorHeightValue(value) { var numericValue = Number(value); if (!Number.isFinite(numericValue) || numericValue <= 0) { numericValue = wysiwygEditorHeightDefault; } return Math.min(wysiwygEditorHeightMax, Math.max(wysiwygEditorHeightMin, Math.round(numericValue))); } function getEditorHolder(regionId) { if (!templateFields) { return null; } return templateFields.querySelector('.editor-holder[data-region-id="' + regionId + '"]'); } function getEditorHeight(regionId) { var key = String(regionId || ''); var storedHeight = editorHeights.get(key); if (storedHeight !== undefined && storedHeight !== null) { return normalizeEditorHeightValue(storedHeight); } var holder = getEditorHolder(key); var holderHeight = holder && holder.dataset ? holder.dataset.editorHeight : ''; var normalizedHeight = normalizeEditorHeightValue(holderHeight || wysiwygEditorHeightDefault); editorHeights.set(key, normalizedHeight); return normalizedHeight; } function applyEditorHeight(regionId, height, options) { var key = String(regionId || ''); var normalizedHeight = normalizeEditorHeightValue(height); var holder = getEditorHolder(key); var editor = editorInstances.get(key); var container = editor && typeof editor.getContainer === 'function' ? editor.getContainer() : null; var shouldRequestPreview = !(options && options.silent); editorHeights.set(key, normalizedHeight); if (holder) { holder.dataset.editorHeight = String(normalizedHeight); holder.style.height = normalizedHeight + 'px'; holder.style.minHeight = normalizedHeight + 'px'; } if (container) { container.style.height = normalizedHeight + 'px'; container.style.minHeight = normalizedHeight + 'px'; } if (editor && typeof editor.dispatch === 'function') { editor.dispatch('ResizeEditor'); } if (shouldRequestPreview) { requestPreviewRender(); } } function adjustEditorHeight(regionId, delta) { applyEditorHeight(regionId, getEditorHeight(regionId) + Number(delta || 0)); } function bindEditorSizeControls() { if (editorSizeControlsBound || !templateFields) { return; } editorSizeControlsBound = true; templateFields.addEventListener('click', function (event) { var button = event.target && typeof event.target.closest === 'function' ? event.target.closest('[data-editor-size-action]') : null; if (!button) { return; } var action = String(button.getAttribute('data-editor-size-action') || '').trim(); if (action !== 'increase' && action !== 'decrease') { return; } var card = typeof button.closest === 'function' ? button.closest('[data-region-id]') : null; var regionId = card ? card.getAttribute('data-region-id') : ''; if (!regionId) { return; } event.preventDefault(); adjustEditorHeight(regionId, action === 'increase' ? wysiwygEditorHeightStep : -wysiwygEditorHeightStep); }); } 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 normalizeUploadPath(value) { return String(value || '').trim(); } function collectEditorImageUploadPaths(html) { var matches = String(html || '').match(/\/media\/uploads\/[^^\s"'<>]+/g); return matches ? Array.from(new Set(matches.map(normalizeUploadPath).filter(Boolean))) : []; } function collectCurrentEditorImageUploadPaths() { var currentPaths = new Set(); editorInstances.forEach(function (editor, regionId) { 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 : ''); collectEditorImageUploadPaths(getEditorContentSafely(editor, fallbackContent)).forEach(function (path) { currentPaths.add(path); }); }); return currentPaths; } function getImageUploadCleanupPaths() { var currentPaths = collectCurrentEditorImageUploadPaths(); return Array.from(editorImageUploadPaths).filter(function (path) { return !currentPaths.has(path); }); } function getCommittedImageUploadCleanupPaths() { var currentPaths = collectCurrentEditorImageUploadPaths(); return Array.from(committedEditorImageUploadPaths).filter(function (path) { return !currentPaths.has(path); }); } function getPendingImageUploadPaths() { return Array.from(editorImageUploadPaths).filter(function (path) { return !committedEditorImageUploadPaths.has(path); }); } function queueImageUploadCleanupPaths(paths) { Array.from(new Set((paths || []).map(normalizeUploadPath).filter(Boolean))).forEach(function (path) { pendingEditorImageUploadCleanupPaths.add(path); }); } function getPendingImageUploadCleanupPaths() { var currentPaths = collectCurrentEditorImageUploadPaths(); return Array.from(pendingEditorImageUploadCleanupPaths).filter(function (path) { return !currentPaths.has(path); }); } function markImageUploadsCommitted() { committedEditorImageUploadPaths = collectCurrentEditorImageUploadPaths(); } function getAllImageUploadPaths() { return Array.from(editorImageUploadPaths); } function clearImageUploadPaths() { editorImageUploadPaths.clear(); committedEditorImageUploadPaths.clear(); pendingEditorImageUploadCleanupPaths.clear(); } function getFileExtension(fileName) { var match = String(fileName || '').toLowerCase().match(/\.([a-z0-9]+)$/); return match ? String(match[1] || '') : ''; } function getImageUploadValidationMessage(blobInfo) { var blob = blobInfo && typeof blobInfo.blob === 'function' ? blobInfo.blob() : null; var fileName = blobInfo && typeof blobInfo.filename === 'function' ? String(blobInfo.filename() || '') : ''; var mimeType = blob && blob.type ? String(blob.type || '').trim().toLowerCase() : ''; var extension = getFileExtension(fileName); if (!blob) { return 'No image file was provided.'; } if (Number(blob.size || 0) > imageUploadMaxBytes) { return 'Image must be ' + imageUploadLimitLabel + ' or smaller. Larger images should use the dedicated Image region.'; } if (mimeType && imageUploadAllowedMimeTypes.indexOf(mimeType) === -1) { return 'This editor accepts PNG, JPG, GIF, WebP, or SVG images.'; } if (!mimeType && extension && imageUploadAllowedExtensions.indexOf(extension) === -1) { return 'This editor accepts PNG, JPG, GIF, WebP, or SVG images.'; } if (!mimeType && !extension) { return 'This editor accepts PNG, JPG, GIF, WebP, or SVG images.'; } return ''; } function uploadEditorImage(blobInfo, progress) { var validationError = getImageUploadValidationMessage(blobInfo); if (validationError) { return Promise.reject(new Error(validationError)); } return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); var formData = new FormData(); var blob = blobInfo.blob(); var fileName = typeof blobInfo.filename === 'function' ? String(blobInfo.filename() || 'image') : 'image'; formData.append('file', blob, fileName); xhr.open('POST', imageUploadUrl, true); xhr.responseType = 'text'; xhr.withCredentials = true; xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.setRequestHeader('Accept', 'application/json, text/plain, */*'); xhr.setRequestHeader('X-Upload-Context', imageUploadContext); xhr.upload.onprogress = function (event) { if (!progress) { return; } if (!event || !event.lengthComputable || !event.total) { progress(0); return; } progress(Math.round((event.loaded / event.total) * 100)); }; xhr.onload = function () { var responseText = String(xhr.responseText || ''); if (xhr.status < 200 || xhr.status >= 300) { reject(new Error(responseText || 'Unable to upload image.')); return; } if (responseText.trim().toLowerCase().indexOf('