Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15dc6eb7f2 | ||
|
|
75b5cb5a6b |
@@ -2,6 +2,19 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 2.7.2 - 2026-08-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- HTML and webpage region previews now normalize object-shaped content before rendering, so the player, thumbnails, and popup preview show the intended iframe content instead of leaking raw objects.
|
||||
- HTML and webpage preview iframes now size explicitly to the full region bounds in the player, thumbnails, and popup preview.
|
||||
|
||||
## 2.7.1 - 2026-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- The player page now supports keyboard navigation with arrow keys to move between slides.
|
||||
|
||||
## 2.7.0 - 2026-08-14
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-player",
|
||||
"version": "2.7.0",
|
||||
"version": "2.7.2",
|
||||
"private": false,
|
||||
"description": "Pulse Signage player application bundle",
|
||||
"main": "src/common.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-web",
|
||||
"version": "2.7.0",
|
||||
"version": "2.7.2",
|
||||
"private": false,
|
||||
"description": "Pulse Signage web and bridge application bundle",
|
||||
"main": "src/common.js",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.7.0",
|
||||
"version": "2.7.2",
|
||||
"private": false,
|
||||
"description": "Pulse Signage application with MySQL and media storage",
|
||||
"repository": {
|
||||
|
||||
@@ -427,6 +427,42 @@ function normalizeBoolean(value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isEditableTarget(target) {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.isContentEditable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var tagName = String(target.tagName || '').toUpperCase();
|
||||
return ['INPUT', 'TEXTAREA', 'SELECT', 'OPTION'].indexOf(tagName) !== -1;
|
||||
}
|
||||
|
||||
function handlePlayerKeydown(event) {
|
||||
if (!event || event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEditableTarget(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
navigateSlides(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
navigateSlides(1);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handlePlayerKeydown);
|
||||
|
||||
// Move to the previous or next active slide.
|
||||
function navigateSlides(offset) {
|
||||
const manualSlides = getCurrentActiveSlides();
|
||||
|
||||
@@ -2,16 +2,49 @@
|
||||
|
||||
var registry = window.pulsePlayerRegionTypes;
|
||||
|
||||
function normalizeRenderableValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizeRenderableValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizeRenderableValue(value.text);
|
||||
}
|
||||
if (value.html !== undefined) {
|
||||
return normalizeRenderableValue(value.html);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizeRenderableValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function buildHtmlDocument(html) {
|
||||
var raw = String(html || '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (/^<!doctype\b/i.test(raw) || /^<html\b/i.test(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return '<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + raw + '</body></html>';
|
||||
}
|
||||
|
||||
function renderHtmlRegionContent(value) {
|
||||
var html = String(value || '').trim();
|
||||
var html = normalizeRenderableValue(value).trim();
|
||||
if (!html) {
|
||||
return '';
|
||||
}
|
||||
return '<iframe class="template-region-html-frame" sandbox="" scrolling="no" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="HTML region" loading="eager"></iframe>';
|
||||
return '<iframe class="template-region-html-frame" sandbox="" scrolling="no" srcdoc="' + escapeHtml(buildHtmlDocument(html)) + '" title="HTML region" loading="eager" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe>';
|
||||
}
|
||||
|
||||
function renderHtmlRegion(region, regionContent) {
|
||||
return '<div class="template-region html" style="' + region.baseStyle + '">' + renderHtmlRegionContent(regionContent.value || '') + '</div>';
|
||||
return '<div class="template-region html" style="' + region.baseStyle + '">' + renderHtmlRegionContent(regionContent && regionContent.value !== undefined ? regionContent.value : '') + '</div>';
|
||||
}
|
||||
|
||||
registry.register('html', {
|
||||
|
||||
@@ -2,12 +2,38 @@
|
||||
|
||||
var registry = window.pulsePlayerRegionTypes;
|
||||
|
||||
function normalizeRenderableValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizeRenderableValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizeRenderableValue(value.text);
|
||||
}
|
||||
if (value.url !== undefined) {
|
||||
return normalizeRenderableValue(value.url);
|
||||
}
|
||||
if (value.href !== undefined) {
|
||||
return normalizeRenderableValue(value.href);
|
||||
}
|
||||
if (value.src !== undefined) {
|
||||
return normalizeRenderableValue(value.src);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizeRenderableValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function renderWebpageRegion(region, regionContent) {
|
||||
var url = String(regionContent.value || '').trim();
|
||||
var url = normalizeRenderableValue(regionContent && regionContent.value !== undefined ? regionContent.value : '').trim();
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
return '<div class="template-region webpage" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(url) + '" title="' + escapeHtml(region.label) + '" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe></div>';
|
||||
return '<div class="template-region webpage" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(url) + '" title="' + escapeHtml(region.label) + '" loading="eager" referrerpolicy="no-referrer" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"></iframe></div>';
|
||||
}
|
||||
|
||||
registry.register('webpage', {
|
||||
|
||||
@@ -279,12 +279,45 @@ function renderEditorJsContent(value) {
|
||||
return wrapRichTextParagraph(sanitizeRichText(raw));
|
||||
}
|
||||
|
||||
function buildHtmlDocument(html) {
|
||||
const raw = String(html || '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (/^<!doctype\b/i.test(raw) || /^<html\b/i.test(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return '<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + raw + '</body></html>';
|
||||
}
|
||||
|
||||
function renderHtmlRegionContent(value) {
|
||||
const html = String(value || '').trim();
|
||||
const html = normalizeRenderableValue(value).trim();
|
||||
if (!html) {
|
||||
return '<div class="template-region-placeholder">HTML</div>';
|
||||
}
|
||||
return '<iframe class="template-region-html-frame" sandbox="" srcdoc="' + escapeHtml(html) + '" title="HTML region" loading="eager"></iframe>';
|
||||
return '<iframe class="template-region-html-frame" sandbox="" srcdoc="' + escapeHtml(buildHtmlDocument(html)) + '" title="HTML region" loading="eager" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe>';
|
||||
}
|
||||
|
||||
function normalizeRenderableValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizeRenderableValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizeRenderableValue(value.text);
|
||||
}
|
||||
if (value.html !== undefined) {
|
||||
return normalizeRenderableValue(value.html);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizeRenderableValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function fitCanvasSize(canvasWidth, canvasHeight, maxWidth, maxHeight) {
|
||||
|
||||
@@ -30,6 +30,35 @@ function resolveAssetUrl(baseUrl, value) {
|
||||
return normalizedBaseUrl + '/' + raw.replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
function normalizeRenderableValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizeRenderableValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizeRenderableValue(value.text);
|
||||
}
|
||||
if (value.html !== undefined) {
|
||||
return normalizeRenderableValue(value.html);
|
||||
}
|
||||
if (value.url !== undefined) {
|
||||
return normalizeRenderableValue(value.url);
|
||||
}
|
||||
if (value.href !== undefined) {
|
||||
return normalizeRenderableValue(value.href);
|
||||
}
|
||||
if (value.src !== undefined) {
|
||||
return normalizeRenderableValue(value.src);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizeRenderableValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function getThumbnailCanvasSize(slide) {
|
||||
const template = slide && slide.template ? slide.template : null;
|
||||
return {
|
||||
@@ -79,7 +108,7 @@ function buildTextRegionMarkup(region, regionContent) {
|
||||
|
||||
function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
||||
const regionType = String(regionContent.type || region.region_type || 'text').trim().toLowerCase();
|
||||
const rawValue = regionContent && regionContent.value !== undefined ? regionContent.value : '';
|
||||
const rawValue = normalizeRenderableValue(regionContent && regionContent.value !== undefined ? regionContent.value : '');
|
||||
|
||||
if (regionType === 'image') {
|
||||
const src = resolveAssetUrl(baseUrl, rawValue);
|
||||
@@ -98,7 +127,7 @@ function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
||||
if (regionType === 'webpage') {
|
||||
const src = resolveAssetUrl(baseUrl, rawValue);
|
||||
return src
|
||||
? '<div class="slide-preview-region slide-preview-webpage-region" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(src) + '" title="' + escapeHtml(region.label || region.region_key || 'webpage') + '" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe></div>'
|
||||
? '<div class="slide-preview-region slide-preview-webpage-region" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(src) + '" title="' + escapeHtml(region.label || region.region_key || 'webpage') + '" loading="eager" referrerpolicy="no-referrer" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"></iframe></div>'
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -114,7 +143,7 @@ function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
||||
if (regionType === 'html') {
|
||||
const html = String(rawValue || '').trim();
|
||||
return html
|
||||
? '<div class="slide-preview-region slide-preview-html-region" style="' + region.baseStyle + '"><iframe sandbox="" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="' + escapeHtml(region.label || region.region_key || 'html') + '" loading="eager" scrolling="no"></iframe></div>'
|
||||
? '<div class="slide-preview-region slide-preview-html-region" style="' + region.baseStyle + '"><iframe sandbox="" allowtransparency="true" srcdoc="' + escapeHtml('<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + html + '</body></html>') + '" title="' + escapeHtml(region.label || region.region_key || 'html') + '" loading="eager" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe></div>'
|
||||
: '';
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,35 @@ function resolveAssetUrl(baseUrl, value) {
|
||||
return normalizedBaseUrl + '/' + raw.replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
function normalizeRenderableValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizeRenderableValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizeRenderableValue(value.text);
|
||||
}
|
||||
if (value.html !== undefined) {
|
||||
return normalizeRenderableValue(value.html);
|
||||
}
|
||||
if (value.url !== undefined) {
|
||||
return normalizeRenderableValue(value.url);
|
||||
}
|
||||
if (value.href !== undefined) {
|
||||
return normalizeRenderableValue(value.href);
|
||||
}
|
||||
if (value.src !== undefined) {
|
||||
return normalizeRenderableValue(value.src);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizeRenderableValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function getCanvasSize(slide) {
|
||||
const template = slide && slide.template ? slide.template : null;
|
||||
return {
|
||||
@@ -109,7 +138,7 @@ function buildTextRegionMarkup(region, regionContent) {
|
||||
|
||||
function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
||||
const regionType = String(regionContent.type || region.region_type || 'text').trim().toLowerCase();
|
||||
const rawValue = regionContent && regionContent.value !== undefined ? regionContent.value : '';
|
||||
const rawValue = normalizeRenderableValue(regionContent && regionContent.value !== undefined ? regionContent.value : '');
|
||||
|
||||
if (regionType === 'image') {
|
||||
const src = resolveAssetUrl(baseUrl, rawValue);
|
||||
@@ -128,7 +157,7 @@ function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
||||
if (regionType === 'webpage') {
|
||||
const src = resolveAssetUrl(baseUrl, rawValue);
|
||||
return src
|
||||
? '<div class="slide-preview-region slide-preview-webpage-region" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(src) + '" title="' + escapeHtml(region.label || region.region_key || 'webpage') + '" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe></div>'
|
||||
? '<div class="slide-preview-region slide-preview-webpage-region" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(src) + '" title="' + escapeHtml(region.label || region.region_key || 'webpage') + '" loading="eager" referrerpolicy="no-referrer" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"></iframe></div>'
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -144,7 +173,7 @@ function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
||||
if (regionType === 'html') {
|
||||
const html = String(rawValue || '').trim();
|
||||
return html
|
||||
? '<div class="slide-preview-region slide-preview-html-region" style="' + region.baseStyle + '"><iframe sandbox="" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="' + escapeHtml(region.label || region.region_key || 'html') + '" loading="eager" scrolling="no"></iframe></div>'
|
||||
? '<div class="slide-preview-region slide-preview-html-region" style="' + region.baseStyle + '"><iframe sandbox="" allowtransparency="true" srcdoc="' + escapeHtml('<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + html + '</body></html>') + '" title="' + escapeHtml(region.label || region.region_key || 'html') + '" loading="eager" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe></div>'
|
||||
: '';
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,49 @@
|
||||
return utils.escapeHtml ? utils.escapeHtml(value) : String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function renderPreview(value) {
|
||||
var content = value && typeof value === 'object' && value.value !== undefined ? value : null;
|
||||
var html = String(content ? content.value : value || '').trim();
|
||||
function normalizePreviewValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizePreviewValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizePreviewValue(value.text);
|
||||
}
|
||||
if (value.html !== undefined) {
|
||||
return normalizePreviewValue(value.html);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizePreviewValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function buildHtmlDocument(html) {
|
||||
var raw = String(html || '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (/^<!doctype\b/i.test(raw) || /^<html\b/i.test(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return '<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + raw + '</body></html>';
|
||||
}
|
||||
|
||||
function renderPreview(region, value) {
|
||||
var html = normalizePreviewValue(value !== undefined ? value : region).trim();
|
||||
if (!html) {
|
||||
return '<div class="slide-preview-placeholder">HTML</div>';
|
||||
}
|
||||
return '<iframe class="slide-preview-html-frame" sandbox="" scrolling="no" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="HTML preview" loading="eager" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe>';
|
||||
if (/^<!doctype\b/i.test(html) || /^<html\b/i.test(html)) {
|
||||
return '<iframe class="slide-preview-html-frame" sandbox="" allowtransparency="true" scrolling="no" srcdoc="' + escapeHtml(buildHtmlDocument(html)) + '" title="HTML preview" loading="eager" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe>';
|
||||
}
|
||||
|
||||
return '<div class="slide-preview-html-frame" style="width:100%;height:100%;overflow:hidden;background:transparent;">' + html + '</div>';
|
||||
}
|
||||
|
||||
function renderEditorCard(context) {
|
||||
|
||||
@@ -8,9 +8,34 @@
|
||||
return utils.escapeHtml ? utils.escapeHtml(value) : String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function renderPreview(value) {
|
||||
var content = value && typeof value === 'object' && value.value !== undefined ? value : null;
|
||||
var src = String(content ? content.value : value || '').trim();
|
||||
function normalizePreviewValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
return normalizePreviewValue(value.value);
|
||||
}
|
||||
if (value.text !== undefined) {
|
||||
return normalizePreviewValue(value.text);
|
||||
}
|
||||
if (value.url !== undefined) {
|
||||
return normalizePreviewValue(value.url);
|
||||
}
|
||||
if (value.href !== undefined) {
|
||||
return normalizePreviewValue(value.href);
|
||||
}
|
||||
if (value.src !== undefined) {
|
||||
return normalizePreviewValue(value.src);
|
||||
}
|
||||
if (value.content !== undefined) {
|
||||
return normalizePreviewValue(value.content);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
|
||||
function renderPreview(region, value) {
|
||||
var src = normalizePreviewValue(value !== undefined ? value : region).trim();
|
||||
if (!src) {
|
||||
return '<div class="slide-preview-placeholder">Webpage</div>';
|
||||
}
|
||||
@@ -24,7 +49,10 @@
|
||||
region: region,
|
||||
headerActions: '<span class="chip">Webpage</span>',
|
||||
bodyHtml: '' +
|
||||
'<input type="url" name="region_webpage_' + region.id + '" class="form-control" value="' + escapeHtml(current) + '" placeholder="https://example.com" />'
|
||||
'<div class="input-group flex-nowrap">' +
|
||||
'<input type="url" name="region_webpage_' + region.id + '" class="form-control" value="' + escapeHtml(current) + '" placeholder="https://example.com" />' +
|
||||
'<button type="button" class="btn btn-outline-secondary text-nowrap" data-webpage-preview-update data-region-id="' + region.id + '">Update</button>' +
|
||||
'</div>'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -825,6 +825,16 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
});
|
||||
});
|
||||
|
||||
templateFields.querySelectorAll('input[type="url"][name^="region_webpage_"]').forEach(function (input) {
|
||||
input.addEventListener('input', function () {
|
||||
templateSelectorLock.markEdited();
|
||||
});
|
||||
|
||||
input.addEventListener('change', function () {
|
||||
templateSelectorLock.markEdited();
|
||||
});
|
||||
});
|
||||
|
||||
if (existingTemplateId && hasExistingSlideContent()) {
|
||||
templateSelectorLock.arm();
|
||||
templateSelectorLock.markEdited();
|
||||
@@ -880,18 +890,42 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
|
||||
templateSelect.addEventListener('change', renderTemplate);
|
||||
templateFields.addEventListener('change', function () {
|
||||
var webpageInput = event.target && typeof event.target.matches === 'function' && event.target.matches('input[type="url"][name^="region_webpage_"]');
|
||||
if (webpageInput) {
|
||||
templateSelectorLock.arm();
|
||||
templateSelectorLock.markEdited();
|
||||
return;
|
||||
}
|
||||
|
||||
templateSelectorLock.arm();
|
||||
templateSelectorLock.markEdited();
|
||||
requestPreviewRender();
|
||||
});
|
||||
templateFields.addEventListener('input', function () {
|
||||
var webpageInput = event.target && typeof event.target.matches === 'function' && event.target.matches('input[type="url"][name^="region_webpage_"]');
|
||||
if (webpageInput) {
|
||||
templateSelectorLock.arm();
|
||||
templateSelectorLock.markEdited();
|
||||
return;
|
||||
}
|
||||
|
||||
templateSelectorLock.arm();
|
||||
templateSelectorLock.markEdited();
|
||||
requestPreviewRender();
|
||||
});
|
||||
templateFields.addEventListener('click', function (event) {
|
||||
var button = event.target && typeof event.target.closest === 'function' ? event.target.closest('[data-api-items-path-reset]') : null;
|
||||
var webpageUpdateButton = event.target && typeof event.target.closest === 'function' ? event.target.closest('[data-webpage-preview-update]') : null;
|
||||
if (!button) {
|
||||
if (!webpageUpdateButton) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (webpageUpdateButton) {
|
||||
templateSelectorLock.arm();
|
||||
templateSelectorLock.markEdited();
|
||||
requestPreviewRender();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,22 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
#popup-preview-canvas iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#popup-preview-canvas .slide-preview-webpage-region iframe {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#popup-preview-canvas .slide-preview-html-region iframe {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.slide-preview-text-content {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ function createSandbox() {
|
||||
playRegionAnimations() {
|
||||
calls.playRegionAnimations += 1;
|
||||
},
|
||||
addEventListener() {},
|
||||
pulsePlayerRegionTypes: {
|
||||
list() {
|
||||
return [
|
||||
|
||||
@@ -68,6 +68,7 @@ test('playlist refresh queues updates until the next slide transition', async ()
|
||||
scheduleRefreshRetry() {},
|
||||
syncWebpagePreloads() {},
|
||||
syncRtmpWarmups() {},
|
||||
addEventListener() {},
|
||||
showCurrent() {
|
||||
calls.showCurrent += 1;
|
||||
},
|
||||
@@ -209,6 +210,7 @@ test('single-slide playlists re-render the active slide instead of refreshing af
|
||||
scheduleRefreshRetry() {},
|
||||
syncWebpagePreloads() {},
|
||||
syncRtmpWarmups() {},
|
||||
addEventListener() {},
|
||||
clearActiveSlidesCache() {},
|
||||
showCurrent() {},
|
||||
sendCommandState() {},
|
||||
@@ -347,6 +349,7 @@ test('deferred playlist updates preserve the current slide index', async () => {
|
||||
scheduleRefreshRetry() {},
|
||||
syncWebpagePreloads() {},
|
||||
syncRtmpWarmups() {},
|
||||
addEventListener() {},
|
||||
clearActiveSlidesCache() {},
|
||||
showCurrent() {},
|
||||
sendCommandState() {},
|
||||
@@ -406,6 +409,87 @@ test('deferred playlist updates preserve the current slide index', async () => {
|
||||
assert.equal(sandbox.index, 1);
|
||||
});
|
||||
|
||||
test('player page arrow keys move between slides', async () => {
|
||||
const handlers = Object.create(null);
|
||||
const calls = [];
|
||||
|
||||
const sandbox = {
|
||||
window: null,
|
||||
console,
|
||||
Array,
|
||||
Object,
|
||||
String,
|
||||
Boolean,
|
||||
Number,
|
||||
Math,
|
||||
Date,
|
||||
JSON,
|
||||
Promise,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
location: { origin: 'http://localhost', href: 'http://localhost/screen/test2' },
|
||||
addEventListener(type, handler) {
|
||||
handlers[type] = handler;
|
||||
},
|
||||
slides: [{ id: 1 }, { id: 2 }, { id: 3 }],
|
||||
lastRenderedSlide: { id: 2 },
|
||||
index: 1,
|
||||
timer: null,
|
||||
slideOutroTimers: [],
|
||||
getCurrentActiveSlides() {
|
||||
return sandbox.slides;
|
||||
},
|
||||
clearSlideTimer() {},
|
||||
applyPendingPlaylistUpdate() {},
|
||||
renderSlideAtIndex(_sourceSlides, targetIndex) {
|
||||
calls.push(targetIndex);
|
||||
}
|
||||
};
|
||||
sandbox.window = sandbox;
|
||||
|
||||
const scriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-commands.js');
|
||||
const script = fs.readFileSync(scriptPath, 'utf8');
|
||||
vm.runInNewContext(script, sandbox, { filename: scriptPath });
|
||||
|
||||
assert.equal(typeof handlers.keydown, 'function');
|
||||
|
||||
let prevented = false;
|
||||
handlers.keydown({
|
||||
key: 'ArrowLeft',
|
||||
target: {},
|
||||
preventDefault() {
|
||||
prevented = true;
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, [0]);
|
||||
assert.equal(prevented, true);
|
||||
|
||||
sandbox.lastRenderedSlide = { id: 2 };
|
||||
sandbox.index = 1;
|
||||
prevented = false;
|
||||
handlers.keydown({
|
||||
key: 'ArrowRight',
|
||||
target: {},
|
||||
preventDefault() {
|
||||
prevented = true;
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, [0, 2]);
|
||||
assert.equal(prevented, true);
|
||||
|
||||
handlers.keydown({
|
||||
key: 'ArrowLeft',
|
||||
target: { tagName: 'INPUT' },
|
||||
preventDefault() {
|
||||
throw new Error('should not be called for editable targets');
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, [0, 2]);
|
||||
});
|
||||
|
||||
test('removing the currently visible slide from a two-slide playlist applies the one-slide update immediately', async () => {
|
||||
const calls = {
|
||||
showCurrent: 0,
|
||||
@@ -464,6 +548,7 @@ test('removing the currently visible slide from a two-slide playlist applies the
|
||||
scheduleRefreshRetry() {},
|
||||
syncWebpagePreloads() {},
|
||||
syncRtmpWarmups() {},
|
||||
addEventListener() {},
|
||||
clearActiveSlidesCache() {},
|
||||
showCurrent() {
|
||||
calls.showCurrent += 1;
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
mediaKind,
|
||||
normalizeSlide,
|
||||
renderEditorJsContent,
|
||||
renderHtmlRegionContent,
|
||||
sanitizeRichText
|
||||
} = require('../src/player/render-helpers');
|
||||
|
||||
@@ -86,4 +87,27 @@ test('renderEditorJsContent sanitizes editor blocks and wraps legacy text', () =
|
||||
test('timetable region registers the timetable type', () => {
|
||||
assert.ok(timetableRegionSource.includes("registry.register('timetable'"));
|
||||
assert.ok(timetableRegionSource.includes("sanitizeRichText(substituteTimetableVariables(value"));
|
||||
});
|
||||
|
||||
test('shared iframe renderers size preview content explicitly', () => {
|
||||
const playerHtmlRegionSource = fs.readFileSync(require.resolve('../src/player/regions/html.js'), 'utf8');
|
||||
const playerWebpageRegionSource = fs.readFileSync(require.resolve('../src/player/regions/webpage.js'), 'utf8');
|
||||
const playerRenderHelpersSource = fs.readFileSync(require.resolve('../src/player/render-helpers.js'), 'utf8');
|
||||
const slideThumbnailPreviewSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnail-preview.js'), 'utf8');
|
||||
const slideThumbnailsSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnails.js'), 'utf8');
|
||||
|
||||
assert.ok(playerHtmlRegionSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"'));
|
||||
assert.ok(playerWebpageRegionSource.includes('style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"'));
|
||||
assert.ok(playerRenderHelpersSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"'));
|
||||
assert.ok(slideThumbnailPreviewSource.includes('style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"'));
|
||||
assert.ok(slideThumbnailPreviewSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"'));
|
||||
assert.ok(slideThumbnailsSource.includes('style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"'));
|
||||
assert.ok(slideThumbnailsSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"'));
|
||||
});
|
||||
|
||||
test('html region helpers unwrap object-shaped values before rendering', () => {
|
||||
const rendered = renderHtmlRegionContent({ html: '<div>Hi</div>' });
|
||||
|
||||
assert.match(rendered, /srcdoc="<!doctype html><html><head><style>html,body\{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;\}<\/style><\/head><body><div>Hi<\/div><\/body><\/html>"/);
|
||||
assert.doesNotMatch(rendered, /\[object Object\]/);
|
||||
});
|
||||
@@ -6,6 +6,8 @@ const slideFormEditorSource = fs.readFileSync(require.resolve('../src/web/public
|
||||
const slideFormSource = fs.readFileSync(require.resolve('../src/web/public/js/slides/slide-form.js'), 'utf8');
|
||||
const slideThumbnailPreviewSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnail-preview.js'), 'utf8');
|
||||
const slideThumbnailsSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnails.js'), 'utf8');
|
||||
const slideHtmlRegionSource = fs.readFileSync(require.resolve('../src/web/public/js/regions/type/html.js'), 'utf8');
|
||||
const slideWebpageRegionSource = fs.readFileSync(require.resolve('../src/web/public/js/regions/type/webpage.js'), 'utf8');
|
||||
|
||||
test('slide editor disables pasted data images in TinyMCE', () => {
|
||||
assert.ok(slideFormEditorSource.includes('paste_data_images: false'));
|
||||
@@ -56,4 +58,24 @@ test('slide form queues editor image cleanup on save and close', () => {
|
||||
test('slide thumbnail previews treat image-only text as visible content', () => {
|
||||
assert.ok(slideThumbnailPreviewSource.includes('/<img\\b/i.test(raw)'));
|
||||
assert.ok(slideThumbnailsSource.includes('/<img\\b/i.test(raw)'));
|
||||
});
|
||||
|
||||
test('popup preview falls back to iframe sizing rules', () => {
|
||||
const popupPreviewSource = fs.readFileSync(require.resolve('../src/web/views/signage/slides/popup-preview.hbs'), 'utf8');
|
||||
|
||||
assert.ok(popupPreviewSource.includes('#popup-preview-canvas iframe'));
|
||||
assert.ok(popupPreviewSource.includes('#popup-preview-canvas .slide-preview-webpage-region iframe'));
|
||||
assert.ok(popupPreviewSource.includes('#popup-preview-canvas .slide-preview-html-region iframe'));
|
||||
});
|
||||
|
||||
test('browser preview helpers normalize object-shaped HTML and webpage values', () => {
|
||||
assert.ok(slideHtmlRegionSource.includes('normalizePreviewValue'));
|
||||
assert.ok(slideWebpageRegionSource.includes('normalizePreviewValue'));
|
||||
});
|
||||
|
||||
test('webpage preview updates only through the explicit button', () => {
|
||||
assert.ok(slideWebpageRegionSource.includes('input-group flex-nowrap'));
|
||||
assert.ok(slideWebpageRegionSource.includes('data-webpage-preview-update'));
|
||||
assert.ok(slideFormSource.includes('input[type="url"][name^="region_webpage_"]'));
|
||||
assert.ok(slideFormSource.includes('requestPreviewRender();'));
|
||||
});
|
||||
Reference in New Issue
Block a user