Release v2.7.0
Publish Docker Image / build-and-push (./build/Dockerfile, git.lzstealth.com/lzstealth/pulse-signage-web, web) (push) Successful in 1m18s
Publish Docker Image / build-and-push (./build/Dockerfile.player, git.lzstealth.com/lzstealth/pulse-signage-player, player) (push) Successful in 33s

This commit is contained in:
2026-08-14 13:36:47 +01:00
parent 30f5ed11b8
commit e7ec276317
28 changed files with 872 additions and 198 deletions
+10 -1
View File
@@ -53,7 +53,16 @@ function buildThumbnailRegionStyle(region, canvasWidth, canvasHeight) {
}
function hasVisibleContent(html) {
return Boolean(String(html || '').replace(/<[^>]+>/g, '').trim());
var raw = String(html || '').trim();
if (!raw) {
return false;
}
if (/<img\b/i.test(raw)) {
return true;
}
return Boolean(raw.replace(/<[^>]+>/g, '').trim());
}
function buildTextRegionMarkup(region, regionContent) {
+10 -1
View File
@@ -83,7 +83,16 @@ function buildThumbnailRegionStyle(region, canvasWidth, canvasHeight) {
}
function hasVisibleContent(html) {
return Boolean(String(html || '').replace(/<[^>]+>/g, '').trim());
var raw = String(html || '').trim();
if (!raw) {
return false;
}
if (/<img\b/i.test(raw)) {
return true;
}
return Boolean(raw.replace(/<[^>]+>/g, '').trim());
}
function buildTextRegionMarkup(region, regionContent) {
+13 -13
View File
@@ -423,19 +423,6 @@
return true;
}
if (submitterValue === 'close' || submitterValue === 'new') {
var redirectUrl = submitterValue === 'close'
? String(response.url || form.dataset.asyncSaveCloseUrl || window.location.href)
: String(response.url || form.dataset.asyncSaveNewUrl || window.location.href);
window.location.replace(redirectUrl);
return true;
}
if (form.dataset && form.dataset.asyncSaveNewRedirect === 'response-url') {
window.location.replace(String(response.url || form.dataset.asyncSaveNewUrl || window.location.href));
return true;
}
var responseText = await response.text();
var responseDocument = null;
try {
@@ -456,6 +443,19 @@
clearFormDirty(form);
if (submitterValue === 'close' || submitterValue === 'new') {
var redirectUrl = submitterValue === 'close'
? String(response.url || form.dataset.asyncSaveCloseUrl || window.location.href)
: String(response.url || form.dataset.asyncSaveNewUrl || window.location.href);
window.location.replace(redirectUrl);
return true;
}
if (form.dataset && form.dataset.asyncSaveNewRedirect === 'response-url') {
window.location.replace(String(response.url || form.dataset.asyncSaveNewUrl || window.location.href));
return true;
}
var successMessage = typeof settings.getSuccessMessage === 'function'
? settings.getSuccessMessage({
form: form,
+105 -2
View File
@@ -28,11 +28,19 @@
var name = String(match[2] || '').toLowerCase();
var attrText = String(match[3] || '');
var selfClosing = Boolean(match[4]) || name === 'br' || name === 'hr';
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'];
var allowed = ['a', 'b', 'blockquote', 'br', 'code', 'col', 'colgroup', 'div', 'em', 'figure', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'];
if (allowed.indexOf(name) === -1) {
return '';
}
if (name === 'img') {
var srcMatch = String(attrText || '').match(/\bsrc\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>/=`]+))/i);
var srcValue = srcMatch ? String(srcMatch[2] !== undefined ? srcMatch[2] : srcMatch[3] !== undefined ? srcMatch[3] : srcMatch[4] !== undefined ? srcMatch[4] : '').trim() : '';
if (!srcValue || !/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/]|data:image\/)/i.test(srcValue)) {
return '';
}
}
if (closing) {
return '</' + name + '>';
}
@@ -42,7 +50,100 @@
}
function sanitizeRichText(html) {
return sanitizePreviewHtml(html);
var output = String(html || '');
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
output = output.replace(/<style[\s\S]*?<\/style>/gi, '');
return 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', 'col', 'colgroup', 'div', 'em', 'figure', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', '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 + sanitizeRichTextAttributes(name, attrText) + '>';
});
}
function sanitizeRichTextAttributes(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'],
img: ['src', 'alt', 'title', 'width', 'height', 'class', 'style', 'loading', 'decoding'],
col: ['class', 'style', 'span', 'width'],
colgroup: ['class', 'style', 'span'],
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 '';
}
if (tagName === 'img') {
var srcMatch = String(attrText || '').match(/\bsrc\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>/=`]+))/i);
var srcValue = srcMatch ? String(srcMatch[2] !== undefined ? srcMatch[2] : srcMatch[3] !== undefined ? srcMatch[3] : srcMatch[4] !== undefined ? srcMatch[4] : '').trim() : '';
if (!srcValue || !/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/]|data:image\/)/i.test(srcValue)) {
return '';
}
}
var attrs = [];
String(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 sanitizeTagAttributes(tagName, attrText) {
@@ -64,6 +165,7 @@
pre: ['class', 'style'],
span: ['class', 'style'],
table: ['class', 'style'],
img: ['src', 'alt', 'title', 'width', 'height', 'class', 'style', 'loading', 'decoding'],
td: ['class', 'style', 'colspan', 'rowspan'],
th: ['class', 'style', 'colspan', 'rowspan', 'scope'],
tr: ['class', 'style'],
@@ -844,6 +946,7 @@
escapeHtml: escapeHtml,
sanitizePreviewHtml: sanitizePreviewHtml,
sanitizeRichText: sanitizeRichText,
sanitizeRichTextAttributes: sanitizeRichTextAttributes,
sanitizeFontFamily: sanitizeFontFamily,
sanitizeTextColor: sanitizeTextColor,
normalizeAcceptList: normalizeAcceptList,
+4 -4
View File
@@ -9,8 +9,8 @@
return utils.escapeHtml ? utils.escapeHtml(value) : String(value === undefined || value === null ? '' : value);
}
function sanitizeRichText(html) {
return utils.sanitizeRichText ? utils.sanitizeRichText(html) : escapeHtml(html);
function sanitizePreviewHtml(html) {
return utils.sanitizePreviewHtml ? utils.sanitizePreviewHtml(html) : escapeHtml(html);
}
function sanitizeFontFamily(value) {
@@ -128,7 +128,7 @@
summary.push('<h3>' + escapeHtml(title) + '</h3>');
}
if (description) {
summary.push('<div>' + sanitizeRichText(description) + '</div>');
summary.push('<div>' + sanitizePreviewHtml(description) + '</div>');
}
if (!summary.length) {
return '';
@@ -183,7 +183,7 @@
if (!body) {
return '';
}
var renderedBody = sanitizeRichText(body);
var renderedBody = sanitizePreviewHtml(body);
return renderedBody ? '<div class="template-region api" style="width:100%;height:100%;overflow:hidden;font-family:' + escapeHtml(fontFamily) + ';font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';"><div class="template-region-text-scale" style="width:100%;height:100%;overflow:hidden;">' + renderedBody + '</div></div>' : '';
}
+2 -6
View File
@@ -17,10 +17,6 @@
return utils.sanitizePreviewHtml ? utils.sanitizePreviewHtml(html) : escapeHtml(html);
}
function sanitizeRichText(html) {
return utils.sanitizeRichText ? utils.sanitizeRichText(html) : sanitizePreviewHtml(html);
}
function sanitizeFontFamily(value) {
return utils.sanitizeFontFamily ? utils.sanitizeFontFamily(value) : String(value || '').trim();
}
@@ -198,7 +194,7 @@
summaryParts.push('<h3>' + escapeHtml(item.title) + '</h3>');
}
if (item.description) {
summaryParts.push('<div>' + sanitizeRichText(item.description) + '</div>');
summaryParts.push('<div>' + sanitizePreviewHtml(item.description) + '</div>');
}
body = summaryParts.join('');
}
@@ -206,7 +202,7 @@
if (!body) {
return '';
}
var renderedBody = sanitizeRichText(body);
var renderedBody = sanitizePreviewHtml(body);
return renderedBody ? '<div class="template-region rss" style="width:100%;height:100%;overflow:hidden;font-family:' + escapeHtml(fontFamily) + ';font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';"><div class="template-region-text-scale" style="width:100%;height:100%;overflow:hidden;">' + renderedBody + '</div></div>' : '';
}
@@ -15,10 +15,6 @@
return utils.escapeHtml ? utils.escapeHtml(value) : String(value === undefined || value === null ? '' : value);
}
function sanitizeRichText(html) {
return utils.sanitizeRichText ? utils.sanitizeRichText(html) : escapeHtml(html);
}
function getDefaultStyle() {
return {
font_family: DEFAULT_STYLE.font_family,
@@ -417,7 +413,7 @@
return '<div class="slide-preview-region timetable" style="width:100%;height:100%;overflow:hidden;' + (style.font_family ? 'font-family:' + escapeHtml(style.font_family) + ';' : '') + (style.font_size ? 'font-size:' + Math.max(1, Math.round(Number(style.font_size))) + 'px;' : '') + (style.font_color ? 'color:' + escapeHtml(style.font_color) + ';' : '') + '">' + entries.map(function (entry, index) {
var entryDate = entry && (entry.start_datetime || entry.end_datetime || entry.date || entry.time || '');
var timezoneValues = getTimezoneValues(group, entryDate);
return '<div class="timetable-region-entry" data-timetable-entry-index="' + index + '">' + sanitizeRichText(renderTemplate(value, Object.assign({}, entry || {}, {
return '<div class="timetable-region-entry" data-timetable-entry-index="' + index + '">' + (utils.sanitizeRichText ? utils.sanitizeRichText(renderTemplate(value, Object.assign({}, entry || {}, {
start: entry && entry.start_datetime !== undefined ? entry.start_datetime : '',
end: entry && entry.end_datetime !== undefined ? entry.end_datetime : '',
tz: timezoneValues.tz,
@@ -426,7 +422,16 @@
group: group || {},
entries: entries,
index: index + 1
}))) + '</div>';
}))) : escapeHtml(renderTemplate(value, Object.assign({}, entry || {}, {
start: entry && entry.start_datetime !== undefined ? entry.start_datetime : '',
end: entry && entry.end_datetime !== undefined ? entry.end_datetime : '',
tz: timezoneValues.tz,
tz_long: timezoneValues.tz_long,
timeZone: timezoneValues.tz_long,
group: group || {},
entries: entries,
index: index + 1
})))) + '</div>';
}).join('') + '</div>';
}
+226 -4
View File
@@ -11,6 +11,16 @@ export function createSlideFormEditorController(options) {
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 imageUploadAllowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'avif', 'tif', 'tiff'];
var imageUploadAllowedMimeTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/bmp', 'image/avif', 'image/tiff'];
var imageUploadFileTypes = imageUploadAllowedExtensions.join(',');
var editorImageUploadPaths = new Set();
var committedEditorImageUploadPaths = new Set();
var pendingEditorImageUploadCleanupPaths = new Set();
var getEditorBackgroundColor = typeof settings.getEditorBackgroundColor === 'function' ? settings.getEditorBackgroundColor : function () {
return '#111111';
};
@@ -29,6 +39,10 @@ export function createSlideFormEditorController(options) {
return true;
}
if (/<img\b/i.test(raw)) {
return false;
}
var stripped = raw
.replace(/<\s*br\s*\/?>/gi, '')
.replace(/<p[^>]*>(?:\s|&nbsp;|<br\s*\/?>)*<\/p>/gi, '')
@@ -209,6 +223,191 @@ export function createSlideFormEditorController(options) {
: 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, BMP, AVIF, or TIFF images.';
}
if (!mimeType && extension && imageUploadAllowedExtensions.indexOf(extension) === -1) {
return 'This editor accepts PNG, JPG, GIF, WebP, BMP, AVIF, or TIFF images.';
}
if (!mimeType && !extension) {
return 'This editor accepts PNG, JPG, GIF, WebP, BMP, AVIF, or TIFF 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;
@@ -305,16 +504,27 @@ export function createSlideFormEditorController(options) {
menubar: false,
branding: false,
promotion: false,
relative_urls: false,
remove_script_host: false,
convert_urls: true,
paste_data_images: false,
plugins: 'lists code advlist fullscreen table',
toolbar: 'undo redo | fontfamily fontsizeinput | forecolor backcolor bold italic underlineformats removeformat | align lineheight indent outdent bullist numlist table chip | fullscreen',
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',
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; width: 100%; } td, th { border: 1px solid currentColor; padding: 0.35em 0.5em; vertical-align: top; } th { font-weight: 700; }' + (editorContentStyle ? ' ' + editorContentStyle : ''),
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',
@@ -394,6 +604,7 @@ export function createSlideFormEditorController(options) {
if (editor.targetElm) {
editor.targetElm.value = editor.getContent({ format: 'html' });
}
markImageUploadsCommitted();
return editor;
}).catch(function (error) {
console.error('Failed to initialize TinyMCE.', error);
@@ -431,7 +642,10 @@ export function createSlideFormEditorController(options) {
return Promise.resolve();
});
return Promise.all(saves);
return Promise.all(saves).then(function (results) {
getCommittedImageUploadCleanupPaths();
return results;
});
}
function destroyEditors() {
@@ -469,6 +683,14 @@ export function createSlideFormEditorController(options) {
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();
+11
View File
@@ -41,6 +41,8 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
var uploadMaxLabel = '100 MB';
var uploadVideoMaxBytes = 1024 * 1024 * 1024;
var uploadVideoMaxLabel = '1 GB';
var wysiwygImageUploadMaxBytes = 2 * 1024 * 1024;
var wysiwygImageUploadLimitLabel = '2 MB';
var videoDurationCache = Object.create(null);
var previewRenderFrame = 0;
var previewPopupWindow = null;
@@ -106,6 +108,9 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
defaultFontSize: DEFAULT_FONT_SIZE,
fontFamilyFormats: slideEditorData.fontFamilyFormats || '',
fontStylesheetHref: fontStylesheetHref,
imageUploadMaxBytes: wysiwygImageUploadMaxBytes,
imageUploadLimitLabel: wysiwygImageUploadLimitLabel,
imageUploadContext: 'wysiwyg',
getRegionTypeModule: getRegionTypeModule,
getEditorBackgroundColor: function () {
var template = getTemplateById(templateSelect.value);
@@ -923,6 +928,12 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
if (regionMediaController) {
regionMediaController.queueUploadCleanup(regionMediaController.getPendingUploadCleanupPaths());
}
if (slideFormEditorController && typeof slideFormEditorController.getCommittedImageUploadCleanupPaths === 'function' && regionMediaController) {
regionMediaController.queueUploadCleanup(slideFormEditorController.getCommittedImageUploadCleanupPaths());
}
if (slideFormEditorController && typeof slideFormEditorController.getImageUploadCleanupPaths === 'function' && regionMediaController) {
regionMediaController.queueUploadCleanup(slideFormEditorController.getImageUploadCleanupPaths());
}
if (previewPopupWindow && !previewPopupWindow.closed) {
previewPopupWindow.close();
}
+29 -10
View File
@@ -33,6 +33,7 @@ module.exports = function registerContentRoutes(app, deps) {
const { buildDuplicateCanvasSizeName, buildDuplicateCanvasSize } = require('#src/web/routes/signage/canvas-sizes/duplicate');
const LIST_PAGE_SIZE = 25;
const WYSIWYG_IMAGE_UPLOAD_MAX_BYTES = 2 * 1024 * 1024;
const IMAGE_UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
const VIDEO_UPLOAD_MAX_BYTES = 1024 * 1024 * 1024;
@@ -104,9 +105,15 @@ module.exports = function registerContentRoutes(app, deps) {
next(error);
}
function getUploadedFileMediaType(file) {
function getUploadedFileMediaType(file, uploadContext) {
const mimeType = String(file && file.mimetype || '').trim().toLowerCase();
const extension = path.extname(String(file && file.originalname || '')).toLowerCase();
const isWysiwyg = String(uploadContext || '').trim().toLowerCase() === 'wysiwyg';
if (isWysiwyg && mimeType.indexOf('image/') !== 0 && ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.avif', '.tif', '.tiff'].indexOf(extension) === -1) {
return null;
}
if (mimeType.indexOf('video/') === 0 || ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv'].indexOf(extension) !== -1) {
return 'video';
}
@@ -116,12 +123,20 @@ module.exports = function registerContentRoutes(app, deps) {
return null;
}
function getUploadedFileLimitBytes(file) {
return getUploadedFileMediaType(file) === 'video' ? VIDEO_UPLOAD_MAX_BYTES : IMAGE_UPLOAD_MAX_BYTES;
function getUploadedFileLimitBytes(file, uploadContext) {
if (String(uploadContext || '').trim().toLowerCase() === 'wysiwyg') {
return WYSIWYG_IMAGE_UPLOAD_MAX_BYTES;
}
return getUploadedFileMediaType(file, uploadContext) === 'video' ? VIDEO_UPLOAD_MAX_BYTES : IMAGE_UPLOAD_MAX_BYTES;
}
function getUploadedFileLimitLabel(file) {
return getUploadedFileMediaType(file) === 'video' ? '1 GB' : '100 MB';
function getUploadedFileLimitLabel(file, uploadContext) {
if (String(uploadContext || '').trim().toLowerCase() === 'wysiwyg') {
return '10 MB';
}
return getUploadedFileMediaType(file, uploadContext) === 'video' ? '1 GB' : '100 MB';
}
async function removeUploadedFile(file) {
@@ -137,11 +152,11 @@ module.exports = function registerContentRoutes(app, deps) {
}
}
async function validateUploadedFiles(files) {
async function validateUploadedFiles(files, uploadContext) {
const list = Array.isArray(files) ? files.filter(Boolean) : [];
for (let i = 0; i < list.length; i += 1) {
const file = list[i];
const mediaType = getUploadedFileMediaType(file);
const mediaType = getUploadedFileMediaType(file, uploadContext);
if (!mediaType) {
await removeUploadedFile(file);
const error = new Error('Unsupported upload type.');
@@ -150,9 +165,11 @@ module.exports = function registerContentRoutes(app, deps) {
throw error;
}
if (Number(file.size || 0) > getUploadedFileLimitBytes(file)) {
if (Number(file.size || 0) > getUploadedFileLimitBytes(file, uploadContext)) {
await removeUploadedFile(file);
const error = new Error('File must be ' + getUploadedFileLimitLabel(file) + ' or smaller.');
const error = new Error(String(uploadContext || '').trim().toLowerCase() === 'wysiwyg'
? 'Image must be 2 MB or smaller. Larger images should use the dedicated Image region.'
: 'File must be ' + getUploadedFileLimitLabel(file, uploadContext) + ' or smaller.');
error.statusCode = 400;
error.expose = true;
throw error;
@@ -376,7 +393,9 @@ module.exports = function registerContentRoutes(app, deps) {
return res.status(400).json({ error: 'No file was uploaded.' });
}
await validateUploadedFiles([req.file]);
const uploadContext = String(req.get('X-Upload-Context') || req.query.context || '').trim().toLowerCase();
await validateUploadedFiles([req.file], uploadContext);
res.json({
path: '/media/uploads/' + req.file.filename,