Files
pulse-signage/src/web/public/js/regions/region-utils.js
T
lzstealth e7ec276317
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
Release v2.7.0
2026-08-14 13:36:47 +01:00

957 lines
31 KiB
JavaScript

// Shared browser utilities for region helper modules.
(function () {
function escapeHtml(value) {
if (window.webUiHelpers && typeof window.webUiHelpers.escapeHtml === 'function') {
return window.webUiHelpers.escapeHtml(value);
}
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function 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 selfClosing = Boolean(match[4]) || name === 'br' || name === 'hr';
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 + '>';
}
return '<' + name + sanitizeTagAttributes(name, attrText) + '>';
});
}
function sanitizeRichText(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) {
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'],
img: ['src', 'alt', 'title', 'width', 'height', 'class', 'style', 'loading', 'decoding'],
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 sanitizeFontFamily(value) {
return String(value || '').trim();
}
function sanitizeTextColor(value) {
return String(value || '').trim() || '#000000';
}
function normalizeAcceptList(value) {
return Array.isArray(value) ? value : String(value || '').split(',').map(function (item) {
return String(item || '').trim();
}).filter(Boolean);
}
function fileMatchesAccept(file, acceptValue) {
if (!file) {
return false;
}
var acceptList = normalizeAcceptList(acceptValue).map(function (item) {
return item.toLowerCase();
});
if (!acceptList.length) {
return true;
}
var fileName = String(file.name || '').toLowerCase();
var fileType = String(file.type || '').toLowerCase();
return acceptList.some(function (rule) {
if (rule === '*/*') {
return true;
}
if (rule.charAt(0) === '.') {
return fileName.endsWith(rule);
}
if (rule.endsWith('/*')) {
return fileType.indexOf(rule.slice(0, -1)) === 0;
}
return fileType === rule;
});
}
function bindRegionMediaRemoveControls(templateFields, options) {
var callbacks = options || {};
if (!templateFields || templateFields.dataset.regionMediaRemoveBound === '1') {
return;
}
templateFields.dataset.regionMediaRemoveBound = '1';
function getCard(regionId) {
if (typeof callbacks.getCardByRegionId === 'function') {
return callbacks.getCardByRegionId(regionId);
}
return regionId ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
}
function getMediaType(button, card) {
if (typeof callbacks.getMediaType === 'function') {
return callbacks.getMediaType(card, button);
}
if (button && button.hasAttribute('data-remove-region-video')) {
return 'video';
}
if (button && button.hasAttribute('data-remove-region-qr-image')) {
return 'qr-image';
}
if (button && button.hasAttribute('data-remove-region-image')) {
return 'image';
}
if (card && card.dataset && String(card.dataset.regionMediaType || '').trim()) {
var regionMediaType = String(card.dataset.regionMediaType || '').trim();
if (regionMediaType === 'video') {
return 'video';
}
if (regionMediaType === 'qr-image') {
return 'qr-image';
}
return 'image';
}
return 'image';
}
function getExistingPrefix(mediaType) {
if (typeof callbacks.getExistingMediaPrefix === 'function') {
return callbacks.getExistingMediaPrefix(mediaType);
}
if (String(mediaType || '').trim() === 'video') {
return 'existing_region_video_';
}
if (String(mediaType || '').trim() === 'qr-image') {
return 'existing_region_qr_image_';
}
return 'existing_region_image_';
}
function getMediaPrefix(mediaType) {
if (typeof callbacks.getMediaPrefix === 'function') {
return callbacks.getMediaPrefix(mediaType);
}
if (String(mediaType || '').trim() === 'video') {
return 'region_video_';
}
if (String(mediaType || '').trim() === 'qr-image') {
return 'region_qr_image_';
}
return 'region_image_';
}
function getVideoDurationHiddenInput(regionId) {
if (typeof callbacks.getVideoDurationHiddenInput === 'function') {
return callbacks.getVideoDurationHiddenInput(regionId);
}
return null;
}
function handleRemove(button) {
var regionId = button.getAttribute('data-remove-region-image') || button.getAttribute('data-remove-region-video') || button.getAttribute('data-remove-region-qr-image');
var card = getCard(regionId);
var mediaType = getMediaType(button, card);
var hiddenName = getExistingPrefix(mediaType) + regionId;
var inputName = getMediaPrefix(mediaType) + regionId;
var hidden = card && card.querySelector('input[type="hidden"][name="' + hiddenName + '"]');
var input = card && card.querySelector('input[type="file"][name="' + inputName + '"]');
if (!hidden && regionId) {
hidden = templateFields.querySelector('input[type="hidden"][name="' + hiddenName + '"]');
}
if (!input && regionId) {
input = templateFields.querySelector('input[type="file"][name="' + inputName + '"]');
}
var uploadPath = input ? String(input.dataset.uploadedPath || '').trim() : '';
var hasPendingUpload = input && input.dataset && String(input.dataset.uploadToken || '').trim();
if (input && input.dataset.previewUrl) {
URL.revokeObjectURL(input.dataset.previewUrl);
delete input.dataset.previewUrl;
}
if (input && hasPendingUpload) {
input.dataset.uploadCancelRequested = '1';
}
if (input && input.dataset.uploadedNeedsCleanup === '1' && uploadPath && typeof callbacks.queueUploadCleanup === 'function') {
callbacks.queueUploadCleanup([uploadPath]);
}
if (input) {
input.value = '';
delete input.dataset.uploadedPath;
delete input.dataset.uploadedNeedsCleanup;
}
if (hidden) {
hidden.value = '';
hidden.dispatchEvent(new Event('change', { bubbles: true }));
}
var durationHidden = getVideoDurationHiddenInput(regionId);
if (durationHidden) {
durationHidden.value = '';
}
if (typeof callbacks.renderPreview === 'function') {
callbacks.renderPreview(card, '');
}
if (typeof callbacks.markEdited === 'function') {
callbacks.markEdited();
}
if (typeof callbacks.requestPreviewRender === 'function') {
callbacks.requestPreviewRender();
}
}
templateFields.addEventListener('click', function (event) {
var button = event.target && event.target.closest ? event.target.closest('[data-remove-region-image], [data-remove-region-video], [data-remove-region-qr-image]') : null;
if (!button || !templateFields.contains(button)) {
return;
}
handleRemove(button);
});
templateFields.addEventListener('keydown', function (event) {
if (event.key !== 'Enter' && event.key !== ' ') {
return;
}
var button = event.target && event.target.closest ? event.target.closest('[data-remove-region-image], [data-remove-region-video], [data-remove-region-qr-image]') : null;
if (!button || !templateFields.contains(button)) {
return;
}
event.preventDefault();
handleRemove(button);
});
}
function createRegionMediaUploadController(templateFields, options) {
var callbacks = options || {};
if (!templateFields) {
return null;
}
function getCard(regionId) {
if (typeof callbacks.getCardByRegionId === 'function') {
return callbacks.getCardByRegionId(regionId);
}
return regionId ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
}
function getMediaTypeFromInput(input) {
if (typeof callbacks.getMediaTypeFromInput === 'function') {
return callbacks.getMediaTypeFromInput(input);
}
if (input && input.name && input.name.indexOf('region_video_') === 0) {
return 'video';
}
if (input && input.name && input.name.indexOf('region_qr_image_') === 0) {
return 'qr-image';
}
return 'image';
}
function getMediaPrefix(mediaType) {
if (typeof callbacks.getMediaPrefix === 'function') {
return callbacks.getMediaPrefix(mediaType);
}
if (String(mediaType || '').trim() === 'video') {
return 'region_video_';
}
if (String(mediaType || '').trim() === 'qr-image') {
return 'region_qr_image_';
}
return 'region_image_';
}
function getExistingMediaPrefix(mediaType) {
if (typeof callbacks.getExistingMediaPrefix === 'function') {
return callbacks.getExistingMediaPrefix(mediaType);
}
if (String(mediaType || '').trim() === 'video') {
return 'existing_region_video_';
}
if (String(mediaType || '').trim() === 'qr-image') {
return 'existing_region_qr_image_';
}
return 'existing_region_image_';
}
function getUploadZone(input) {
if (typeof callbacks.getUploadZone === 'function') {
return callbacks.getUploadZone(input);
}
return input ? input.closest('[data-region-upload-zone]') : null;
}
function getUploadProgressNode(zone) {
return zone ? zone.querySelector('[data-region-upload-progress]') : null;
}
function getUploadProgressBar(zone) {
return zone ? zone.querySelector('[data-region-upload-progress-bar]') : null;
}
function getUploadProgressText(zone) {
return zone ? zone.querySelector('[data-region-upload-progress-text]') : null;
}
function setUploadProgress(zone, percent, text, isIndeterminate) {
var progress = getUploadProgressNode(zone);
var bar = getUploadProgressBar(zone);
var progressText = getUploadProgressText(zone);
var normalizedPercent = Math.max(0, Math.min(100, Math.round(Number(percent || 0))));
if (!progress || !bar) {
return;
}
if (progressText) {
progressText.textContent = text || 'Uploading...';
}
progress.hidden = false;
bar.style.width = (isIndeterminate ? 100 : normalizedPercent) + '%';
bar.textContent = isIndeterminate ? 'Uploading...' : normalizedPercent + '%';
bar.setAttribute('aria-valuenow', String(isIndeterminate ? 100 : normalizedPercent));
bar.classList.toggle('progress-bar-striped', Boolean(isIndeterminate));
bar.classList.toggle('progress-bar-animated', Boolean(isIndeterminate));
}
function setUploadState(input, isUploading, percent, text, isIndeterminate) {
var zone = getUploadZone(input);
var progress = getUploadProgressNode(zone);
if (!zone || !progress) {
return;
}
zone.classList.toggle('is-uploading', Boolean(isUploading));
zone.setAttribute('aria-busy', isUploading ? 'true' : 'false');
input.disabled = Boolean(isUploading);
if (isUploading) {
setUploadProgress(zone, percent, text, isIndeterminate);
return;
}
progress.hidden = true;
if (input) {
input.disabled = false;
}
}
function setInputFile(input, file) {
if (!input || !file) {
return;
}
var dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
input.files = dataTransfer.files;
input.dispatchEvent(new Event('change', { bubbles: true }));
}
function queueUploadCleanup(uploadPaths) {
var normalizedPaths = Array.from(new Set((uploadPaths || []).map(function (value) {
return String(value || '').trim();
}).filter(Boolean)));
if (!normalizedPaths.length) {
return;
}
if (typeof callbacks.queueUploadCleanup === 'function') {
callbacks.queueUploadCleanup(normalizedPaths);
return;
}
var payload = JSON.stringify({ uploadPaths: normalizedPaths });
var url = String(callbacks.cleanupUrl || '/slides/uploads/cleanup');
if (navigator.sendBeacon) {
try {
navigator.sendBeacon(url, new Blob([payload], { type: 'application/json' }));
return;
} catch (_error) {
// Fall back to fetch below.
}
}
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: payload,
credentials: 'same-origin',
keepalive: true
}).catch(function () {
// Best-effort cleanup only.
});
}
function getPendingUploadCleanupPaths() {
return Array.prototype.slice.call(templateFields.querySelectorAll('input[type="file"][name^="region_image_"], input[type="file"][name^="region_video_"], input[type="file"][name^="region_qr_image_"]')).map(function (input) {
if (!input || String(input.dataset.uploadedNeedsCleanup || '') !== '1') {
return null;
}
return String(input.dataset.uploadedPath || '').trim();
}).filter(Boolean);
}
function clearPendingUploadCleanupPaths() {
Array.prototype.slice.call(templateFields.querySelectorAll('input[type="file"][name^="region_image_"], input[type="file"][name^="region_video_"], input[type="file"][name^="region_qr_image_"]')).forEach(function (input) {
if (!input) {
return;
}
delete input.dataset.uploadedNeedsCleanup;
});
}
function uploadFile(input, file) {
if (!input || !file) {
return Promise.resolve(null);
}
var mediaType = getMediaTypeFromInput(input);
var regionId = input.name.replace(/^region_(?:image|video|qr_image)_/, '');
var card = getCard(regionId);
var hidden = card && card.querySelector('input[type="hidden"][name="' + getExistingMediaPrefix(mediaType) + regionId + '"]');
var zone = getUploadZone(input);
var uploadToken = String(Date.now()) + ':' + Math.random().toString(16).slice(2);
var previousUploadedPath = String(input.dataset.uploadedPath || '').trim();
var previousNeedsCleanup = String(input.dataset.uploadedNeedsCleanup || '') === '1';
var uploadUrl = String(callbacks.uploadUrl || '/slides/uploads');
var uploadTimeoutMs = Math.max(1, Number(callbacks.uploadTimeoutMs || 30000));
delete input.dataset.uploadCancelRequested;
input.dataset.uploadToken = uploadToken;
setUploadState(input, true, 0, 'Uploading...', true);
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('file', file, file.name || 'upload');
xhr.open('POST', uploadUrl, true);
xhr.responseType = 'text';
xhr.withCredentials = true;
xhr.timeout = uploadTimeoutMs;
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
xhr.upload.onprogress = function (event) {
if (input.dataset.uploadToken !== uploadToken) {
return;
}
if (!event || !event.lengthComputable || !event.total) {
setUploadProgress(zone, 100, 'Uploading...', true);
return;
}
var percent = Math.round((event.loaded / event.total) * 100);
setUploadProgress(zone, percent, 'Uploading ' + percent + '%', false);
};
xhr.onload = function () {
if (input.dataset.uploadToken !== uploadToken) {
resolve(null);
return;
}
var response = {
ok: xhr.status >= 200 && xhr.status < 300,
status: xhr.status,
statusText: xhr.statusText,
responseText: xhr.responseText || ''
};
if (!response.ok) {
var error = new Error(response.responseText || 'Unable to upload media.');
error.status = response.status;
error.responseText = response.responseText;
reject(error);
return;
}
if (String(response.responseText || '').trim().toLowerCase().indexOf('<!doctype html') === 0 || String(response.responseText || '').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(response.responseText || '{}') || {};
} catch (_error) {
reject(new Error('Unable to parse the upload response.'));
return;
}
var uploadedPath = String(payload.path || '').trim();
if (!uploadedPath) {
reject(new Error('Unable to upload media.'));
return;
}
if (input.dataset.previewUrl) {
URL.revokeObjectURL(input.dataset.previewUrl);
delete input.dataset.previewUrl;
}
if (String(input.dataset.uploadCancelRequested || '') === '1') {
if (uploadedPath) {
queueUploadCleanup([uploadedPath]);
}
setUploadState(input, false);
resolve(payload);
return;
}
if (hidden) {
hidden.value = uploadedPath;
hidden.dispatchEvent(new Event('change', { bubbles: true }));
}
input.dataset.uploadedPath = uploadedPath;
input.dataset.uploadedNeedsCleanup = '1';
input.value = '';
input.setCustomValidity('');
if (typeof callbacks.renderPreview === 'function') {
callbacks.renderPreview(card, uploadedPath);
}
if (typeof callbacks.syncDuration === 'function') {
callbacks.syncDuration(card, uploadedPath);
}
if (typeof callbacks.markEdited === 'function') {
callbacks.markEdited();
}
if (typeof callbacks.requestPreviewRender === 'function') {
callbacks.requestPreviewRender();
}
if (previousNeedsCleanup && previousUploadedPath && previousUploadedPath !== uploadedPath) {
queueUploadCleanup([previousUploadedPath]);
}
setUploadState(input, false);
resolve(payload);
};
xhr.onerror = function () {
if (input.dataset.uploadToken !== uploadToken) {
resolve(null);
return;
}
reject(new Error('Unable to upload media.'));
};
xhr.onabort = function () {
if (input.dataset.uploadToken !== uploadToken) {
resolve(null);
return;
}
reject(new Error('Unable to upload media.'));
};
xhr.ontimeout = function () {
if (input.dataset.uploadToken !== uploadToken) {
resolve(null);
return;
}
reject(new Error('Upload timed out. Please try again.'));
};
xhr.onloadend = function () {
if (input.dataset.uploadToken === uploadToken) {
delete input.dataset.uploadToken;
delete input.dataset.uploadCancelRequested;
setUploadState(input, false);
}
};
xhr.send(formData);
}).catch(function (error) {
if (input.dataset.uploadToken === uploadToken) {
delete input.dataset.uploadToken;
setUploadState(input, false);
}
throw error;
});
}
function handleSelection(input, file) {
if (typeof callbacks.handleMediaSelection !== 'function') {
return false;
}
return callbacks.handleMediaSelection(input, file, {
setInputFile: setInputFile,
uploadFile: uploadFile,
getMediaTypeFromInput: getMediaTypeFromInput,
getExistingMediaPrefix: getExistingMediaPrefix,
getMediaPrefix: getMediaPrefix,
getUploadZone: getUploadZone,
queueUploadCleanup: queueUploadCleanup,
getPendingUploadCleanupPaths: getPendingUploadCleanupPaths,
clearPendingUploadCleanupPaths: clearPendingUploadCleanupPaths
});
}
if (!templateFields.dataset.regionMediaUploadBound) {
templateFields.dataset.regionMediaUploadBound = '1';
templateFields.addEventListener('change', function (event) {
var input = event.target && event.target.matches && event.target.matches('input[type="file"][name^="region_image_"], input[type="file"][name^="region_video_"], input[type="file"][name^="region_qr_image_"]') ? event.target : null;
if (!input || !templateFields.contains(input)) {
return;
}
var file = input.files && input.files.length ? input.files[0] : null;
var hookResult = handleSelection(input, file);
if (hookResult === false) {
return;
}
if (hookResult && typeof hookResult.then === 'function') {
hookResult.catch(function (error) {
if (input.dataset.previewUrl) {
URL.revokeObjectURL(input.dataset.previewUrl);
delete input.dataset.previewUrl;
}
input.value = '';
if (typeof callbacks.onUploadError === 'function') {
callbacks.onUploadError(error, input);
}
});
return;
}
if (file) {
uploadFile(input, file).catch(function (error) {
if (input.dataset.previewUrl) {
URL.revokeObjectURL(input.dataset.previewUrl);
delete input.dataset.previewUrl;
}
input.value = '';
if (typeof callbacks.onUploadError === 'function') {
callbacks.onUploadError(error, input);
}
});
}
});
templateFields.addEventListener('dragover', function (event) {
var zone = event.target && event.target.closest ? event.target.closest('[data-region-upload-zone]') : null;
if (!zone || !templateFields.contains(zone)) {
return;
}
event.preventDefault();
zone.classList.add('is-dragover');
});
templateFields.addEventListener('dragleave', function (event) {
var zone = event.target && event.target.closest ? event.target.closest('[data-region-upload-zone]') : null;
if (!zone || !templateFields.contains(zone)) {
return;
}
zone.classList.remove('is-dragover');
});
templateFields.addEventListener('drop', function (event) {
var zone = event.target && event.target.closest ? event.target.closest('[data-region-upload-zone]') : null;
if (!zone || !templateFields.contains(zone)) {
return;
}
event.preventDefault();
zone.classList.remove('is-dragover');
var input = zone.querySelector('input[type="file"]');
var files = event.dataTransfer && event.dataTransfer.files ? event.dataTransfer.files : null;
var file = files && files.length ? files[0] : null;
if (!input || !file) {
return;
}
var hookResult = handleSelection(input, file);
if (hookResult === false) {
return;
}
if (hookResult && typeof hookResult.then === 'function') {
hookResult.catch(function (error) {
if (input.dataset.previewUrl) {
URL.revokeObjectURL(input.dataset.previewUrl);
delete input.dataset.previewUrl;
}
input.value = '';
if (typeof callbacks.onUploadError === 'function') {
callbacks.onUploadError(error, input);
}
});
return;
}
setInputFile(input, file);
});
}
return {
uploadFile: uploadFile,
queueUploadCleanup: queueUploadCleanup,
getPendingUploadCleanupPaths: getPendingUploadCleanupPaths,
clearPendingUploadCleanupPaths: clearPendingUploadCleanupPaths,
setInputFile: setInputFile
};
}
window.pulseRegionUtils = {
escapeHtml: escapeHtml,
sanitizePreviewHtml: sanitizePreviewHtml,
sanitizeRichText: sanitizeRichText,
sanitizeRichTextAttributes: sanitizeRichTextAttributes,
sanitizeFontFamily: sanitizeFontFamily,
sanitizeTextColor: sanitizeTextColor,
normalizeAcceptList: normalizeAcceptList,
fileMatchesAccept: fileMatchesAccept,
bindRegionMediaRemoveControls: bindRegionMediaRemoveControls,
createRegionMediaUploadController: createRegionMediaUploadController
};
}());