816 lines
28 KiB
JavaScript
816 lines
28 KiB
JavaScript
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 (/<img\b/i.test(raw)) {
|
|
return false;
|
|
}
|
|
|
|
var stripped = raw
|
|
.replace(/<\s*br\s*\/?>/gi, '')
|
|
.replace(/<p[^>]*>(?:\s| |<br\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('<!doctype html') === 0 || responseText.toLowerCase().indexOf('<html') !== -1) {
|
|
reject(new Error('Upload redirected to an HTML page. Please sign in again and retry.'));
|
|
return;
|
|
}
|
|
|
|
var payload = {};
|
|
try {
|
|
payload = JSON.parse(responseText || '{}') || {};
|
|
} catch (_error) {
|
|
reject(new Error('Unable to parse the upload response.'));
|
|
return;
|
|
}
|
|
|
|
if (!payload.path) {
|
|
reject(new Error('Unable to upload image.'));
|
|
return;
|
|
}
|
|
|
|
if (progress) {
|
|
progress(100);
|
|
}
|
|
|
|
editorImageUploadPaths.add(String(payload.path || '').trim());
|
|
|
|
resolve(String(payload.path || ''));
|
|
};
|
|
|
|
xhr.onerror = function () {
|
|
reject(new Error('Unable to upload image.'));
|
|
};
|
|
|
|
xhr.ontimeout = function () {
|
|
reject(new Error('Upload timed out. Please try again.'));
|
|
};
|
|
|
|
xhr.send(formData);
|
|
});
|
|
}
|
|
|
|
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);
|
|
if (hydrated) {
|
|
markFormDirty();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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 = normalizeEditorData(source && source.value !== undefined && source.value !== null ? source.value : (hidden ? hidden.value : ''));
|
|
|
|
if (!source.id) {
|
|
source.id = 'slide-editor-region-' + regionId;
|
|
}
|
|
|
|
applyEditorHeight(regionId, getEditorHeight(regionId), { silent: true });
|
|
|
|
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,
|
|
relative_urls: false,
|
|
remove_script_host: false,
|
|
convert_urls: true,
|
|
paste_data_images: false,
|
|
automatic_uploads: true,
|
|
images_file_types: imageUploadFileTypes,
|
|
images_upload_handler: uploadEditorImage,
|
|
plugins: 'lists code advlist fullscreen table image',
|
|
toolbar: 'undo redo | fontfamily fontsizeinput | forecolor backcolor bold italic underlineformats removeformat | align lineheight indent outdent bullist numlist table image chip | fullscreen',
|
|
toolbar_mode: 'sliding',
|
|
license_key: 'gpl',
|
|
height: getEditorHeight(regionId),
|
|
min_height: wysiwygEditorHeightMin,
|
|
table_default_attributes: {
|
|
border: '1',
|
|
cellpadding: '0',
|
|
cellspacing: '0'
|
|
},
|
|
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; border-spacing: 0; width: 100%; } td, th { border: 1px solid currentColor; padding: 0; 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);
|
|
applyEditorHeight(regionId, getEditorHeight(regionId), { silent: true });
|
|
if (editor.targetElm) {
|
|
editor.targetElm.value = editor.getContent({ format: 'html' });
|
|
}
|
|
markImageUploadsCommitted();
|
|
return editor;
|
|
}).catch(function (error) {
|
|
console.error('Failed to initialize TinyMCE.', error);
|
|
return null;
|
|
});
|
|
}
|
|
|
|
function renderEditors() {
|
|
if (!templateFields) {
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
bindEditorSizeControls();
|
|
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).then(function (results) {
|
|
getCommittedImageUploadCleanupPaths();
|
|
return results;
|
|
});
|
|
}
|
|
|
|
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,
|
|
getImageUploadCleanupPaths: getImageUploadCleanupPaths,
|
|
getCommittedImageUploadCleanupPaths: getCommittedImageUploadCleanupPaths,
|
|
getPendingImageUploadPaths: getPendingImageUploadPaths,
|
|
getPendingImageUploadCleanupPaths: getPendingImageUploadCleanupPaths,
|
|
getAllImageUploadPaths: getAllImageUploadPaths,
|
|
queueImageUploadCleanupPaths: queueImageUploadCleanupPaths,
|
|
markImageUploadsCommitted: markImageUploadsCommitted,
|
|
clearImageUploadPaths: clearImageUploadPaths,
|
|
destroyEditors: function () {
|
|
if (themeObserver) {
|
|
themeObserver.disconnect();
|
|
themeObserver = null;
|
|
}
|
|
destroyEditors();
|
|
},
|
|
watchThemeChanges: watchThemeChanges
|
|
};
|
|
} |