Release v2.2.0
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
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 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 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 getTinymce() {
|
||||
return window.tinymce || null;
|
||||
}
|
||||
|
||||
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 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;
|
||||
|
||||
function syncState() {
|
||||
var currentEditor = editorInstances.get(regionId);
|
||||
if (currentEditor !== editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof editor.save === 'function') {
|
||||
editor.save();
|
||||
}
|
||||
var content = editor.getContent({ format: 'html' });
|
||||
if (hidden) {
|
||||
hidden.value = content;
|
||||
}
|
||||
if (source) {
|
||||
source.value = content;
|
||||
}
|
||||
if (templateSelectorLock && typeof templateSelectorLock.markEdited === 'function') {
|
||||
templateSelectorLock.markEdited();
|
||||
}
|
||||
requestPreviewRender();
|
||||
}
|
||||
|
||||
editor.on('init', function () {
|
||||
var content = editor.getContent({ format: 'html' });
|
||||
if (hidden) {
|
||||
hidden.value = content;
|
||||
}
|
||||
if (source) {
|
||||
source.value = content;
|
||||
}
|
||||
syncEditorFontSizeHidden(regionId);
|
||||
syncState();
|
||||
});
|
||||
|
||||
['change', 'keyup', 'undo', 'redo', 'SetContent', 'Paste', 'input', 'NodeChange'].forEach(function (eventName) {
|
||||
editor.on(eventName, syncState);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return tinymce.init({
|
||||
target: source,
|
||||
menubar: false,
|
||||
branding: false,
|
||||
promotion: false,
|
||||
statusbar: true,
|
||||
resize: true,
|
||||
plugins: 'lists link code advlist fullscreen',
|
||||
toolbar: 'undo redo | fontfamily fontsizeinput | forecolor backcolor bold italic underline strikethrough subscript superscript removeformat | align lineheight indent outdent bullist numlist | 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; }' + (editorContentStyle ? ' ' + editorContentStyle : ''),
|
||||
font_family_formats: getFontFamilyFormats(),
|
||||
font_size_input_default_unit: 'px',
|
||||
forced_root_block: 'p',
|
||||
force_br_newlines: false,
|
||||
newline_behavior: 'default',
|
||||
setup: function (editor) {
|
||||
editorInstances.set(regionId, editor);
|
||||
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);
|
||||
var hidden = getEditorHiddenInput(regionId);
|
||||
|
||||
if (!editor || !hidden) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
hidden.value = editor.getContent({ format: 'html' });
|
||||
syncEditorFontSizeHidden(regionId);
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
export function createSlideFormPreviewHelpers(options) {
|
||||
var settings = options || {};
|
||||
var getRegionTypeModule = typeof settings.getRegionTypeModule === 'function' ? settings.getRegionTypeModule : function () { return null; };
|
||||
var escapeHtml = typeof settings.escapeHtml === 'function'
|
||||
? settings.escapeHtml
|
||||
: function (value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
|
||||
function sanitizeTagAttributes(tagName, attrText) {
|
||||
var allowedAttributes = {
|
||||
a: ['href', 'title', 'target', 'rel', 'class', 'style'],
|
||||
blockquote: ['class', 'style'],
|
||||
div: ['class', 'style'],
|
||||
figure: ['class', 'style'],
|
||||
figcaption: ['class', 'style'],
|
||||
h1: ['class', 'style'],
|
||||
h2: ['class', 'style'],
|
||||
h3: ['class', 'style'],
|
||||
h4: ['class', 'style'],
|
||||
h5: ['class', 'style'],
|
||||
h6: ['class', 'style'],
|
||||
li: ['class', 'style'],
|
||||
ol: ['class', 'style', 'start'],
|
||||
p: ['class', 'style'],
|
||||
pre: ['class', 'style'],
|
||||
span: ['class', 'style'],
|
||||
table: ['class', 'style'],
|
||||
td: ['class', 'style', 'colspan', 'rowspan'],
|
||||
th: ['class', 'style', 'colspan', 'rowspan', 'scope'],
|
||||
tr: ['class', 'style'],
|
||||
ul: ['class', 'style']
|
||||
};
|
||||
var allowed = allowedAttributes[tagName] || [];
|
||||
if (!allowed.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var attrs = [];
|
||||
attrText.replace(/([a-zA-Z0-9:-]+)(?:\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>/=`]+)))?/g, function (_full, key, _valuePart, doubleQuoted, singleQuoted, bareValue) {
|
||||
var lowerKey = String(key || '').toLowerCase();
|
||||
if (allowed.indexOf(lowerKey) === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
|
||||
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
|
||||
return '';
|
||||
}
|
||||
if (lowerKey === 'style' && /(?:expression\s*\(|javascript:|url\s*\()/i.test(String(value || ''))) {
|
||||
return '';
|
||||
}
|
||||
if (lowerKey === 'target') {
|
||||
var targetValue = String(value || '').trim();
|
||||
if (targetValue === '_blank') {
|
||||
attrs.push(' target="_blank"');
|
||||
if (attrs.indexOf(' rel="noreferrer noopener"') === -1) {
|
||||
attrs.push(' rel="noreferrer noopener"');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
attrs.push(' ' + lowerKey + '="' + escapeHtml(value) + '"');
|
||||
return '';
|
||||
});
|
||||
|
||||
return attrs.join('');
|
||||
}
|
||||
|
||||
function sanitizePreviewHtml(html) {
|
||||
var output = String(html || '');
|
||||
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
|
||||
output = output.replace(/<style[\s\S]*?<\/style>/gi, '');
|
||||
output = output.replace(/<[^>]+>/g, function (tag) {
|
||||
var match = tag.match(/^<\s*(\/?)\s*([a-z0-9]+)([\s\S]*?)(\/?)>$/i);
|
||||
if (!match) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var closing = Boolean(match[1]);
|
||||
var name = String(match[2] || '').toLowerCase();
|
||||
var attrText = String(match[3] || '');
|
||||
var allowed = ['a', 'b', 'blockquote', 'br', 'code', 'div', 'em', 'figure', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'];
|
||||
if (allowed.indexOf(name) === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (closing) {
|
||||
return '</' + name + '>';
|
||||
}
|
||||
|
||||
return '<' + name + sanitizeTagAttributes(name, attrText) + '>';
|
||||
});
|
||||
|
||||
return output.replace(/<p\b([^>]*)>/gi, function (tag, attrText) {
|
||||
if (/\bstyle\s*=\s*/i.test(attrText)) {
|
||||
return tag.replace(/\bstyle\s*=\s*("([^"]*)"|'([^']*)')/i, function (full, quoted, doubleQuoted, singleQuoted) {
|
||||
var existingStyle = String(doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : '');
|
||||
if (/\bmargin\s*:/i.test(existingStyle)) {
|
||||
return full;
|
||||
}
|
||||
return 'style="margin:1em 0;' + existingStyle + '"';
|
||||
});
|
||||
}
|
||||
|
||||
return '<p' + attrText + ' style="margin:1em 0;">';
|
||||
});
|
||||
}
|
||||
|
||||
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('') + '</' + tag + '>' : '';
|
||||
return '<li>' + sanitizePreviewHtml(content || '') + nested + '</li>';
|
||||
}
|
||||
|
||||
return '<li>' + sanitizePreviewHtml(item || '') + '</li>';
|
||||
}
|
||||
|
||||
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 '<tr>' + cells.map(function (cell) {
|
||||
return '<' + cellTag + cellAttrs + '>' + sanitizePreviewHtml(cell || '') + '</' + cellTag + '>';
|
||||
}).join('') + '</tr>';
|
||||
}).join('');
|
||||
return '<table class="slide-preview-table">' + body + '</table>';
|
||||
}
|
||||
|
||||
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 '<h' + level + '>' + sanitizePreviewHtml(block.data.text || '') + '</h' + level + '>';
|
||||
}
|
||||
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('') + '</' + tag + '>';
|
||||
}
|
||||
if (block.type === 'delimiter') {
|
||||
return '<hr />';
|
||||
}
|
||||
if (block.type === 'code') {
|
||||
return '<pre><code>' + sanitizePreviewHtml(block.data.code || '') + '</code></pre>';
|
||||
}
|
||||
if (block.type === 'table') {
|
||||
return renderPreviewTable(block.data);
|
||||
}
|
||||
if (block.type === 'paragraph') {
|
||||
return '<p>' + sanitizePreviewHtml(block.data.text || '') + '</p>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function renderPreviewText(value) {
|
||||
var rawValue = value;
|
||||
if (rawValue && typeof rawValue === 'object') {
|
||||
if (rawValue.value !== undefined) {
|
||||
rawValue = rawValue.value;
|
||||
} else if (rawValue.text !== undefined) {
|
||||
rawValue = rawValue.text;
|
||||
} else if (rawValue.html !== undefined) {
|
||||
rawValue = rawValue.html;
|
||||
} else {
|
||||
try {
|
||||
rawValue = JSON.stringify(rawValue);
|
||||
} catch (_error) {
|
||||
rawValue = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
var raw = String(rawValue || '');
|
||||
if (!raw) {
|
||||
return '<div class="slide-preview-placeholder">Empty text</div>';
|
||||
}
|
||||
return sanitizePreviewHtml(raw);
|
||||
}
|
||||
|
||||
function renderPreviewTextRegion(region, value, style, scale) {
|
||||
var module = getRegionTypeModule('text');
|
||||
if (module && typeof module.renderPreview === 'function') {
|
||||
return module.renderPreview(region, value, style, scale);
|
||||
}
|
||||
|
||||
var fontFamily = style.font_family ? 'font-family:' + escapeHtml(style.font_family) + ';' : '';
|
||||
var color = style.font_color ? 'color:' + escapeHtml(style.font_color) + ';' : '';
|
||||
var fontSize = style.font_size ? 'font-size:' + Math.max(1, Math.round(Number(style.font_size))) + 'px;' : '';
|
||||
var width = Math.max(1, Math.round(Number(region && region.width ? region.width : 0) || 1));
|
||||
var height = Math.max(1, Math.round(Number(region && region.height ? region.height : 0) || 1));
|
||||
var wrapperStyle = 'width:' + width + 'px;height:' + height + 'px;overflow:hidden;' + fontFamily + fontSize + color;
|
||||
return '<div class="slide-preview-text-content" style="' + wrapperStyle + '">' + renderPreviewText(value) + '</div>';
|
||||
}
|
||||
|
||||
function startPreviewVideoPlayback(root) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
var videos = root.querySelectorAll('video.slide-preview-video');
|
||||
Array.prototype.forEach.call(videos, function (video) {
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
|
||||
function tryPlay() {
|
||||
try {
|
||||
video.load();
|
||||
} catch (_error) {
|
||||
// Ignore load failures and try to play anyway.
|
||||
}
|
||||
|
||||
var playPromise = video.play && video.play();
|
||||
if (playPromise && typeof playPromise.catch === 'function') {
|
||||
playPromise.catch(function () {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (video.readyState >= 2) {
|
||||
tryPlay();
|
||||
return;
|
||||
}
|
||||
|
||||
video.addEventListener('canplay', tryPlay, { once: true });
|
||||
video.addEventListener('loadedmetadata', tryPlay, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizePreviewHtml: sanitizePreviewHtml,
|
||||
sanitizeTagAttributes: sanitizeTagAttributes,
|
||||
renderPreviewListItem: renderPreviewListItem,
|
||||
renderPreviewTable: renderPreviewTable,
|
||||
renderPreviewBlock: renderPreviewBlock,
|
||||
renderPreviewText: renderPreviewText,
|
||||
renderPreviewTextRegion: renderPreviewTextRegion,
|
||||
startPreviewVideoPlayback: startPreviewVideoPlayback
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
// Region-specific helpers for slide forms.
|
||||
|
||||
import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-preview.js';
|
||||
|
||||
export function createSlideFormRegionHelpers(options) {
|
||||
var settings = options || {};
|
||||
var templateFields = settings.templateFields || null;
|
||||
var existingContent = settings.existingContent || {};
|
||||
var rssFeeds = Array.isArray(settings.rssFeeds) ? settings.rssFeeds : [];
|
||||
var apiSources = Array.isArray(settings.apiSources) ? settings.apiSources : [];
|
||||
var getRegionTypeModule = typeof settings.getRegionTypeModule === 'function' ? settings.getRegionTypeModule : function () { return null; };
|
||||
var placeholderUtils = window.placeholderUtils || {};
|
||||
var placeholderChips = window.placeholderChips || {};
|
||||
var escapeHtml = typeof settings.escapeHtml === 'function'
|
||||
? settings.escapeHtml
|
||||
: function (value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
var previewHelpers = createSlideFormPreviewHelpers({
|
||||
getRegionTypeModule: getRegionTypeModule,
|
||||
escapeHtml: escapeHtml
|
||||
});
|
||||
var defaultFontSize = Math.max(1, Number(settings.defaultFontSize || 32));
|
||||
var uploadMaxLabel = String(settings.uploadMaxLabel || '100 MB');
|
||||
var uploadVideoMaxLabel = String(settings.uploadVideoMaxLabel || '1 GB');
|
||||
|
||||
function sanitizeFontSize(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 String(defaultFontSize);
|
||||
}
|
||||
|
||||
var sanitizePreviewHtml = previewHelpers.sanitizePreviewHtml;
|
||||
var sanitizeTagAttributes = previewHelpers.sanitizeTagAttributes;
|
||||
var renderPreviewListItem = previewHelpers.renderPreviewListItem;
|
||||
var renderPreviewTable = previewHelpers.renderPreviewTable;
|
||||
var renderPreviewBlock = previewHelpers.renderPreviewBlock;
|
||||
var renderPreviewText = previewHelpers.renderPreviewText;
|
||||
var renderPreviewTextRegion = previewHelpers.renderPreviewTextRegion;
|
||||
var startPreviewVideoPlayback = previewHelpers.startPreviewVideoPlayback;
|
||||
|
||||
function getCurrentTextStyle(region) {
|
||||
var current = existingContent[region.region_key] || {};
|
||||
var textRegion = getRegionTypeModule('text');
|
||||
var defaultStyle = window.pulseRegionTypes && typeof window.pulseRegionTypes.getDefaultRegionStyle === 'function'
|
||||
? window.pulseRegionTypes.getDefaultRegionStyle('text')
|
||||
: textRegion && typeof textRegion.getDefaultStyle === 'function'
|
||||
? textRegion.getDefaultStyle()
|
||||
: {
|
||||
font_family: 'Arial',
|
||||
font_size: defaultFontSize,
|
||||
font_color: '#000000'
|
||||
};
|
||||
return {
|
||||
font_family: String(current.font_family || region.font_family || defaultStyle.font_family || 'Arial').trim() || 'Arial',
|
||||
font_size: Math.max(8, Number(current.font_size || defaultStyle.font_size || defaultFontSize)),
|
||||
font_color: String(current.font_color || region.font_color || defaultStyle.font_color || '#000000').trim() || '#000000'
|
||||
};
|
||||
}
|
||||
|
||||
function getRssFeedById(feedId) {
|
||||
var normalizedId = Number(feedId || 0);
|
||||
return rssFeeds.find(function (feed) {
|
||||
return Number(feed.id) === normalizedId;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function getRssFieldList(feedId) {
|
||||
var module = getRegionTypeModule('rss');
|
||||
if (module && typeof module.getFieldList === 'function') {
|
||||
return module.getFieldList(feedId, rssFeeds);
|
||||
}
|
||||
|
||||
var feed = getRssFeedById(feedId);
|
||||
var sampleItem = feed && Array.isArray(feed.items) ? feed.items[0] : null;
|
||||
var sampleJson = sampleItem && sampleItem.itemJson && typeof sampleItem.itemJson === 'object' ? sampleItem.itemJson : null;
|
||||
|
||||
function walkFields(value, prefix, output) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
if (key === 'rawXml') {
|
||||
return;
|
||||
}
|
||||
|
||||
var nextPath = prefix ? prefix + '.' + key : key;
|
||||
var nextValue = value[key];
|
||||
if (nextValue && typeof nextValue === 'object' && !Array.isArray(nextValue)) {
|
||||
walkFields(nextValue, nextPath, output);
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.indexOf(nextPath) === -1) {
|
||||
output.push(nextPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var fields = [];
|
||||
walkFields(sampleJson, '', fields);
|
||||
return fields;
|
||||
}
|
||||
|
||||
function buildLimitedPlaceholderChipMarkup(fieldList, emptyLabel) {
|
||||
var fields = Array.isArray(fieldList) ? fieldList : [];
|
||||
var visibleFields = fields.slice(0, 8);
|
||||
var overflowFields = fields.slice(visibleFields.length);
|
||||
|
||||
if (!fields.length) {
|
||||
return '<span class="muted slide-image-file">' + escapeHtml(emptyLabel) + '</span>';
|
||||
}
|
||||
|
||||
var visibleMarkup = visibleFields.map(function (field) {
|
||||
return typeof placeholderChips.renderChip === 'function'
|
||||
? placeholderChips.renderChip(field)
|
||||
: '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('');
|
||||
|
||||
if (!overflowFields.length) {
|
||||
return visibleMarkup;
|
||||
}
|
||||
|
||||
return '' +
|
||||
visibleMarkup +
|
||||
'<details class="w-100 mt-2" data-placeholder-chips-more>' +
|
||||
'<summary class="small text-body-secondary">Show ' + escapeHtml(overflowFields.length) + ' more fields</summary>' +
|
||||
'<div class="d-flex flex-wrap gap-2 mt-2">' + overflowFields.map(function (field) {
|
||||
return typeof placeholderChips.renderChip === 'function'
|
||||
? placeholderChips.renderChip(field)
|
||||
: '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('') + '</div>' +
|
||||
'</details>';
|
||||
}
|
||||
|
||||
function truncateJsonPreview(value, maxLength) {
|
||||
var text = '';
|
||||
try {
|
||||
text = JSON.stringify(value, null, 2);
|
||||
} catch (_error) {
|
||||
text = String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var limit = Math.max(200, Number(maxLength || 1100));
|
||||
if (text.length <= limit) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.slice(0, limit).replace(/\s+$/, '') + '\n...';
|
||||
}
|
||||
|
||||
function buildApiSampleDataMarkup(sourceId, itemNumber, itemsPathOverride) {
|
||||
var source = getApiSourceById(sourceId);
|
||||
if (!source) {
|
||||
return '<div class="api-region-sample-meta">Select an API source to preview sample data.</div>';
|
||||
}
|
||||
|
||||
var item = getApiSourceItems(sourceId, itemsPathOverride)[Math.max(0, Math.max(1, Number(itemNumber || 1)) - 1)] || null;
|
||||
if (!item) {
|
||||
return '<div class="api-region-sample-meta">No sample item is available for the current selection.</div>';
|
||||
}
|
||||
|
||||
return '<pre class="api-region-sample-preview" data-api-sample-data-output>' + escapeHtml(truncateJsonPreview(item, 1100)) + '</pre>';
|
||||
}
|
||||
|
||||
function updatePlaceholderChipList(card, fieldList, emptyLabel) {
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
var container = card.querySelector('[data-placeholder-chips]');
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chips = Array.isArray(fieldList) ? fieldList.map(function (field) {
|
||||
return typeof placeholderChips.renderChip === 'function'
|
||||
? placeholderChips.renderChip(field)
|
||||
: '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('') : '';
|
||||
|
||||
container.innerHTML = chips || '<span class="muted slide-image-file">' + escapeHtml(emptyLabel) + '</span>';
|
||||
}
|
||||
|
||||
function updateRssPlaceholderChips(regionId, feedId) {
|
||||
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
|
||||
updatePlaceholderChipList(card, getRssFieldList(feedId), 'No RSS fields available.');
|
||||
}
|
||||
|
||||
function getCurrentRssConfig(region) {
|
||||
var module = getRegionTypeModule('rss');
|
||||
if (module && typeof module.getCurrentConfig === 'function') {
|
||||
return module.getCurrentConfig(region, existingContent);
|
||||
}
|
||||
|
||||
var current = existingContent[region.region_key] || {};
|
||||
var parsedItemNumber = Math.max(1, Number(current.item_number || 1));
|
||||
return {
|
||||
feed_id: current.feed_id === undefined || current.feed_id === null || current.feed_id === '' ? '' : Number(current.feed_id),
|
||||
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
|
||||
value: current.value !== undefined ? current.value : ''
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePath(value, path) {
|
||||
var current = value;
|
||||
if (!path) {
|
||||
return current;
|
||||
}
|
||||
String(path).split('.').forEach(function (segment) {
|
||||
if (current === undefined || current === null) {
|
||||
current = '';
|
||||
return;
|
||||
}
|
||||
current = current[segment];
|
||||
});
|
||||
return current === undefined || current === null ? '' : current;
|
||||
}
|
||||
|
||||
function substituteRssVariables(html, item) {
|
||||
var source = String(html || '');
|
||||
return source.replace(/\{\{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_.]+))?\s*\}\}/g, function (_match, tokenName, tokenPath) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return '';
|
||||
}
|
||||
var key = tokenName === 'item' && tokenPath ? tokenPath : tokenName;
|
||||
return escapeHtml(resolvePath(item, key || ''));
|
||||
});
|
||||
}
|
||||
|
||||
function getRssItem(feedId, itemNumber) {
|
||||
var module = getRegionTypeModule('rss');
|
||||
if (module && typeof module.getItem === 'function') {
|
||||
return module.getItem(feedId, itemNumber, rssFeeds);
|
||||
}
|
||||
|
||||
var feed = getRssFeedById(feedId);
|
||||
var items = feed && Array.isArray(feed.items) ? feed.items : [];
|
||||
var parsedItemNumber = Math.max(1, Number(itemNumber || 1));
|
||||
var index = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
|
||||
return items[index] || null;
|
||||
}
|
||||
|
||||
function getRssPreviewFallback(item) {
|
||||
var module = getRegionTypeModule('rss');
|
||||
if (module && typeof module.getPreviewFallback === 'function') {
|
||||
return module.getPreviewFallback(item);
|
||||
}
|
||||
|
||||
if (!item || typeof item !== 'object') {
|
||||
return '<div class="slide-preview-placeholder">RSS item</div>';
|
||||
}
|
||||
|
||||
var title = String(item.title || '').trim();
|
||||
var description = String(item.description || '').trim();
|
||||
var summary = [];
|
||||
if (title) {
|
||||
summary.push('<h3>' + escapeHtml(title) + '</h3>');
|
||||
}
|
||||
if (description) {
|
||||
summary.push('<p>' + sanitizePreviewHtml(description) + '</p>');
|
||||
}
|
||||
if (!summary.length) {
|
||||
return '<div class="slide-preview-placeholder">RSS item</div>';
|
||||
}
|
||||
return summary.join('');
|
||||
}
|
||||
|
||||
function getApiSourceById(sourceId) {
|
||||
var normalizedId = Number(sourceId || 0);
|
||||
return apiSources.find(function (source) {
|
||||
return Number(source.id) === normalizedId;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function getApiSourceItemsPath(source, overridePath) {
|
||||
if (overridePath === undefined || overridePath === null) {
|
||||
return String(source && (source.items_path || source.itemsPath) || '').trim();
|
||||
}
|
||||
|
||||
return String(overridePath || '').trim();
|
||||
}
|
||||
|
||||
function getApiSourceItems(sourceId, itemsPathOverride) {
|
||||
var source = getApiSourceById(sourceId);
|
||||
var module = getRegionTypeModule('api');
|
||||
if (module && typeof module.getItems === 'function') {
|
||||
return module.getItems(sourceId, apiSources, itemsPathOverride);
|
||||
}
|
||||
|
||||
var responseJson = source && source.responseJson && typeof source.responseJson === 'object' ? source.responseJson : null;
|
||||
var itemsPath = getApiSourceItemsPath(source, itemsPathOverride);
|
||||
if (itemsPath !== undefined && itemsPath !== null && itemsPath !== '') {
|
||||
var current = responseJson;
|
||||
String(itemsPath).split('.').forEach(function (segment) {
|
||||
if (current === undefined || current === null) {
|
||||
current = '';
|
||||
return;
|
||||
}
|
||||
current = current[segment];
|
||||
});
|
||||
return Array.isArray(current) ? current : [];
|
||||
}
|
||||
if (Array.isArray(responseJson)) {
|
||||
return responseJson;
|
||||
}
|
||||
if (responseJson && Array.isArray(responseJson.items)) {
|
||||
return responseJson.items;
|
||||
}
|
||||
if (responseJson && Array.isArray(responseJson.results)) {
|
||||
return responseJson.results;
|
||||
}
|
||||
if (responseJson && Array.isArray(responseJson.data)) {
|
||||
return responseJson.data;
|
||||
}
|
||||
return responseJson ? [responseJson] : [];
|
||||
}
|
||||
|
||||
function getApiItemCount(sourceId, itemsPathOverride) {
|
||||
var module = getRegionTypeModule('api');
|
||||
if (module && typeof module.getItemCount === 'function') {
|
||||
return module.getItemCount(sourceId, apiSources, itemsPathOverride);
|
||||
}
|
||||
|
||||
return getApiSourceItems(sourceId, itemsPathOverride).length;
|
||||
}
|
||||
|
||||
function getApiFieldList(sourceId, itemsPathOverride) {
|
||||
var sampleItem = getApiSourceItems(sourceId, itemsPathOverride)[0] || null;
|
||||
if (typeof placeholderUtils.collectPlaceholderFieldPaths === 'function') {
|
||||
return placeholderUtils.collectPlaceholderFieldPaths(sampleItem && typeof sampleItem === 'object' ? sampleItem : null);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function updateApiPlaceholderChips(regionId, sourceId, itemsPathOverride) {
|
||||
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
var container = card.querySelector('[data-placeholder-chips]');
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = buildLimitedPlaceholderChipMarkup(getApiFieldList(sourceId, itemsPathOverride), 'No JSON fields available.');
|
||||
}
|
||||
|
||||
function updateApiSampleDataPanel(regionId, sourceId, itemNumber, itemsPathOverride) {
|
||||
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
var container = card.querySelector('[data-api-sample-data-panel]');
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = buildApiSampleDataMarkup(sourceId, itemNumber, itemsPathOverride);
|
||||
}
|
||||
|
||||
function updateApiItemNumberLimit(regionId, sourceId, itemsPathOverride) {
|
||||
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
var input = card.querySelector('input[name="region_api_item_number_' + regionId + '"]');
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
var itemCount = Math.max(1, Number(getApiItemCount(sourceId, itemsPathOverride) || 1));
|
||||
input.setAttribute('max', String(itemCount));
|
||||
if (Number(input.value || 0) > itemCount) {
|
||||
input.value = String(itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
function clampApiItemNumber(regionId, sourceId, itemNumber, itemsPathOverride) {
|
||||
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
|
||||
if (!card) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var input = card.querySelector('input[name="region_api_item_number_' + regionId + '"]');
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var itemCount = Math.max(1, Number(getApiItemCount(sourceId, itemsPathOverride) || 1));
|
||||
var parsedValue = Math.max(1, Number(itemNumber || 1));
|
||||
var clampedValue = Math.min(itemCount, Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : 1);
|
||||
input.setAttribute('max', String(itemCount));
|
||||
input.value = String(clampedValue);
|
||||
return String(clampedValue);
|
||||
}
|
||||
|
||||
function getCurrentApiConfig(region) {
|
||||
var module = getRegionTypeModule('api');
|
||||
if (module && typeof module.getCurrentConfig === 'function') {
|
||||
return module.getCurrentConfig(region, existingContent, apiSources);
|
||||
}
|
||||
|
||||
var current = existingContent[region.region_key] || {};
|
||||
var source = getApiSourceById(current.source_id);
|
||||
var parsedItemNumber = Math.max(1, Number(current.item_number || 1));
|
||||
return {
|
||||
source_id: current.source_id === undefined || current.source_id === null || current.source_id === '' ? '' : Number(current.source_id),
|
||||
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
|
||||
items_path: Object.prototype.hasOwnProperty.call(current, 'items_path') ? String(current.items_path === undefined || current.items_path === null ? '' : current.items_path) : undefined,
|
||||
default_items_path: String(source && (source.items_path || source.itemsPath) || '').trim(),
|
||||
value: current.value !== undefined ? current.value : ''
|
||||
};
|
||||
}
|
||||
|
||||
function reduceAspectRatio(width, height) {
|
||||
var a = Math.max(1, Math.round(Number(width || 0) || 1));
|
||||
var b = Math.max(1, Math.round(Number(height || 0) || 1));
|
||||
var x = a;
|
||||
var y = b;
|
||||
|
||||
while (y !== 0) {
|
||||
var remainder = x % y;
|
||||
x = y;
|
||||
y = remainder;
|
||||
}
|
||||
|
||||
return Math.max(1, Math.round(a / x)) + ':' + Math.max(1, Math.round(b / x));
|
||||
}
|
||||
|
||||
function getCurrentRegionValue(region) {
|
||||
var current = existingContent[region.region_key];
|
||||
return current && current.value !== undefined ? current.value : '';
|
||||
}
|
||||
|
||||
function getCurrentTimeDateConfig(region) {
|
||||
var current = existingContent[region.region_key] || {};
|
||||
return {
|
||||
value: current.value !== undefined ? current.value : '',
|
||||
timezone: current.timezone !== undefined ? current.timezone : (current.time_zone !== undefined ? current.time_zone : '')
|
||||
};
|
||||
}
|
||||
|
||||
function getCurrentRegionVideoDuration(region) {
|
||||
var current = existingContent[region.region_key] || {};
|
||||
var duration = Math.round(Number(current.duration_seconds || 0) * 1000) / 1000;
|
||||
return Number.isFinite(duration) && duration > 0 ? String(duration) : '';
|
||||
}
|
||||
|
||||
function buildRegionEditorCardContext(region) {
|
||||
var module = getRegionTypeModule(region.region_type);
|
||||
if (module && typeof module.buildEditorCardContext === 'function') {
|
||||
return module.buildEditorCardContext({
|
||||
region: region,
|
||||
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'time-date' ? (existingContent[region.region_key] || {}) : getCurrentRegionValue(region),
|
||||
currentDuration: getCurrentRegionVideoDuration(region),
|
||||
regionRatio: reduceAspectRatio(region.width, region.height),
|
||||
uploadMaxLabel: uploadMaxLabel,
|
||||
uploadVideoMaxLabel: uploadVideoMaxLabel,
|
||||
disableAudio: (existingContent[region.region_key] || {}).disable_audio,
|
||||
config: region.region_type === 'api' ? getCurrentApiConfig(region) : getCurrentRssConfig(region),
|
||||
style: getCurrentTextStyle(region),
|
||||
fontSize: getCurrentTextStyle(region).font_size,
|
||||
feedOptions: rssFeeds.map(function (feed) {
|
||||
var selected = Number(feed.id) === Number(getCurrentRssConfig(region).feed_id) ? ' selected' : '';
|
||||
return '<option value="' + escapeHtml(feed.id) + '"' + selected + '>' + escapeHtml(feed.name || ('Feed ' + feed.id)) + '</option>';
|
||||
}).join(''),
|
||||
sourceOptions: apiSources.map(function (source) {
|
||||
var selected = Number(source.id) === Number(getCurrentApiConfig(region).source_id) ? ' selected' : '';
|
||||
return '<option value="' + escapeHtml(source.id) + '"' + selected + '>' + escapeHtml(source.name || ('Source ' + source.id)) + '</option>';
|
||||
}).join(''),
|
||||
placeholderChips: region.region_type === 'rss'
|
||||
? getRssFieldList(getCurrentRssConfig(region).feed_id).map(function (field) {
|
||||
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('')
|
||||
: buildLimitedPlaceholderChipMarkup(getApiFieldList(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).items_path), 'No JSON fields available.'),
|
||||
sampleDataPreview: buildApiSampleDataMarkup(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).item_number, getCurrentApiConfig(region).items_path)
|
||||
}, existingContent, region.region_type === 'rss' ? rssFeeds : apiSources);
|
||||
}
|
||||
|
||||
var textStyle = getCurrentTextStyle(region);
|
||||
var rssConfig = getCurrentRssConfig(region);
|
||||
var apiConfig = getCurrentApiConfig(region);
|
||||
var currentContent = existingContent[region.region_key] || {};
|
||||
var apiItemsPath = apiConfig.items_path !== undefined ? apiConfig.items_path : apiConfig.default_items_path;
|
||||
|
||||
return {
|
||||
region: region,
|
||||
current: region.region_type === 'rss' || region.region_type === 'api' ? currentContent : getCurrentRegionValue(region),
|
||||
currentDuration: getCurrentRegionVideoDuration(region),
|
||||
regionRatio: reduceAspectRatio(region.width, region.height),
|
||||
uploadMaxLabel: uploadMaxLabel,
|
||||
uploadVideoMaxLabel: uploadVideoMaxLabel,
|
||||
disableAudio: currentContent.disable_audio,
|
||||
config: region.region_type === 'api' ? apiConfig : rssConfig,
|
||||
style: textStyle,
|
||||
fontSize: textStyle.font_size,
|
||||
feedOptions: rssFeeds.map(function (feed) {
|
||||
var selected = Number(feed.id) === Number(rssConfig.feed_id) ? ' selected' : '';
|
||||
return '<option value="' + escapeHtml(feed.id) + '"' + selected + '>' + escapeHtml(feed.name || ('Feed ' + feed.id)) + '</option>';
|
||||
}).join(''),
|
||||
sourceOptions: apiSources.map(function (source) {
|
||||
var selected = Number(source.id) === Number(apiConfig.source_id) ? ' selected' : '';
|
||||
return '<option value="' + escapeHtml(source.id) + '"' + selected + '>' + escapeHtml(source.name || ('Source ' + source.id)) + '</option>';
|
||||
}).join(''),
|
||||
placeholderChips: region.region_type === 'rss'
|
||||
? getRssFieldList(rssConfig.feed_id).map(function (field) {
|
||||
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('')
|
||||
: buildLimitedPlaceholderChipMarkup(getApiFieldList(apiConfig.source_id, apiItemsPath), 'No JSON fields available.'),
|
||||
sampleDataPreview: buildApiSampleDataMarkup(apiConfig.source_id, apiConfig.item_number, apiItemsPath)
|
||||
};
|
||||
}
|
||||
|
||||
function getRegionEditorCardHtml(region) {
|
||||
var module = getRegionTypeModule(region.region_type);
|
||||
if (!module || typeof module.renderEditorCard !== 'function') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return module.renderEditorCard(buildRegionEditorCardContext(region));
|
||||
}
|
||||
|
||||
function getPreviewRegionContent(card, region) {
|
||||
var module = getRegionTypeModule(region.region_type);
|
||||
if (module && typeof module.buildPreviewRenderContext === 'function') {
|
||||
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : apiSources);
|
||||
}
|
||||
|
||||
var current = existingContent[region.region_key];
|
||||
var content = current && typeof current === 'object' ? Object.assign({}, current) : {};
|
||||
if (!card) {
|
||||
return content;
|
||||
}
|
||||
|
||||
var hiddenInput = card.querySelector('input[type="hidden"][name="region_text_' + region.id + '"]');
|
||||
var textAreaInput = card.querySelector('textarea[name="region_text_' + region.id + '"]');
|
||||
var htmlInput = card.querySelector('textarea[name="region_html_' + region.id + '"]');
|
||||
var timeDateTimezoneInput = card.querySelector('input[name="region_timezone_' + region.id + '"]');
|
||||
var imageHidden = card.querySelector('input[type="hidden"][name="existing_region_image_' + region.id + '"]');
|
||||
var imageFileInput = card.querySelector('input[type="file"][name="region_image_' + region.id + '"]');
|
||||
var videoHidden = card.querySelector('input[type="hidden"][name="existing_region_video_' + region.id + '"]');
|
||||
var videoFileInput = card.querySelector('input[type="file"][name="region_video_' + region.id + '"]');
|
||||
var webpageInput = card.querySelector('input[type="url"][name="region_webpage_' + region.id + '"]');
|
||||
var rtmpInput = card.querySelector('input[type="url"][name="region_rtmp_' + region.id + '"]');
|
||||
var disableAudioInput = card.querySelector('input[type="checkbox"][name="region_disable_audio_' + region.id + '"]');
|
||||
var rssFeedInput = card.querySelector('select[name="region_rss_feed_id_' + region.id + '"]');
|
||||
var rssItemInput = card.querySelector('input[name="region_rss_item_number_' + region.id + '"]');
|
||||
var apiSourceInput = card.querySelector('select[name="region_api_source_id_' + region.id + '"]');
|
||||
var apiItemInput = card.querySelector('input[name="region_api_item_number_' + region.id + '"]');
|
||||
var apiItemsPathInput = card.querySelector('input[name="region_api_items_path_' + region.id + '"]');
|
||||
|
||||
if (region.region_type === 'image') {
|
||||
content.value = (imageFileInput && imageFileInput.dataset.previewUrl) ? imageFileInput.dataset.previewUrl : (imageHidden ? imageHidden.value : content.value || '');
|
||||
} else if (region.region_type === 'video') {
|
||||
content.value = (videoFileInput && videoFileInput.dataset.previewUrl) ? videoFileInput.dataset.previewUrl : (videoHidden ? videoHidden.value : content.value || '');
|
||||
} else if (region.region_type === 'webpage') {
|
||||
content.value = webpageInput ? webpageInput.value : (content.value || '');
|
||||
} else if (region.region_type === 'rtmp') {
|
||||
content.value = rtmpInput ? rtmpInput.value : (content.value || '');
|
||||
content.disable_audio = disableAudioInput ? disableAudioInput.checked : (content.disable_audio === undefined ? true : Boolean(content.disable_audio));
|
||||
} else if (region.region_type === 'html') {
|
||||
content.value = htmlInput ? htmlInput.value : (content.value || '');
|
||||
} else if (region.region_type === 'time-date') {
|
||||
var timeDateCurrent = getCurrentTimeDateConfig(region);
|
||||
content.value = textAreaInput ? textAreaInput.value : (timeDateCurrent.value || '');
|
||||
content.timezone = timeDateTimezoneInput ? timeDateTimezoneInput.value : (timeDateCurrent.timezone || '');
|
||||
} else if (region.region_type === 'rss') {
|
||||
content.value = hiddenInput ? hiddenInput.value : (content.value || '');
|
||||
content.feed_id = rssFeedInput ? rssFeedInput.value : content.feed_id;
|
||||
content.item_number = rssItemInput ? rssItemInput.value : content.item_number;
|
||||
} else if (region.region_type === 'api') {
|
||||
content.value = hiddenInput ? hiddenInput.value : (content.value || '');
|
||||
content.source_id = apiSourceInput ? apiSourceInput.value : content.source_id;
|
||||
content.item_number = apiItemInput ? apiItemInput.value : content.item_number;
|
||||
content.items_path = apiItemsPathInput ? apiItemsPathInput.value : content.items_path;
|
||||
} else {
|
||||
content.value = hiddenInput ? hiddenInput.value : (content.value || '');
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function buildPreviewRenderContext(region, card) {
|
||||
var module = getRegionTypeModule(region.region_type);
|
||||
if (module && typeof module.buildPreviewRenderContext === 'function') {
|
||||
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : apiSources);
|
||||
}
|
||||
|
||||
var textStyle = getCurrentTextStyle(region);
|
||||
var previewContent = getPreviewRegionContent(card, region);
|
||||
return {
|
||||
value: previewContent.value,
|
||||
timezone: previewContent.timezone,
|
||||
style: textStyle,
|
||||
existingContent: existingContent,
|
||||
feeds: rssFeeds,
|
||||
sources: apiSources,
|
||||
disable_audio: previewContent.disable_audio === undefined ? true : Boolean(previewContent.disable_audio),
|
||||
feed_id: previewContent.feed_id,
|
||||
item_number: previewContent.item_number,
|
||||
source_id: previewContent.source_id,
|
||||
items_path: previewContent.items_path,
|
||||
font_size: textStyle.font_size,
|
||||
font_color: textStyle.font_color,
|
||||
font_family: textStyle.font_family
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizePreviewHtml: sanitizePreviewHtml,
|
||||
sanitizeTagAttributes: sanitizeTagAttributes,
|
||||
getCurrentTextStyle: getCurrentTextStyle,
|
||||
getRssFieldList: getRssFieldList,
|
||||
updateRssPlaceholderChips: updateRssPlaceholderChips,
|
||||
getCurrentRssConfig: getCurrentRssConfig,
|
||||
getApiFieldList: getApiFieldList,
|
||||
updateApiPlaceholderChips: updateApiPlaceholderChips,
|
||||
updateApiSampleDataPanel: updateApiSampleDataPanel,
|
||||
updateApiItemNumberLimit: updateApiItemNumberLimit,
|
||||
getCurrentApiConfig: getCurrentApiConfig,
|
||||
reduceAspectRatio: reduceAspectRatio,
|
||||
renderPreviewTextRegion: renderPreviewTextRegion,
|
||||
startPreviewVideoPlayback: startPreviewVideoPlayback,
|
||||
getRegionEditorCardHtml: getRegionEditorCardHtml,
|
||||
buildRegionEditorCardContext: buildRegionEditorCardContext,
|
||||
getPreviewRegionContent: getPreviewRegionContent,
|
||||
buildPreviewRenderContext: buildPreviewRenderContext,
|
||||
getCurrentRegionValue: getCurrentRegionValue,
|
||||
getCurrentRegionVideoDuration: getCurrentRegionVideoDuration
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// Template selection lock controller for slide forms.
|
||||
|
||||
export function createTemplateSelectorLockController(templateSelect) {
|
||||
var locked = false;
|
||||
var armed = false;
|
||||
|
||||
+264
-1787
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
// Slide image cropper modal wiring and file replacement helpers.
|
||||
|
||||
(function () {
|
||||
var templateFields = document.getElementById('template-fields');
|
||||
var modal = document.getElementById('slide-image-cropper-modal');
|
||||
|
||||
Reference in New Issue
Block a user