Release v2.2.0
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
(function () {
|
||||
var ANNOUNCEMENT_TYPES = ['lower-third', 'fullscreen', 'top-banner'];
|
||||
var ANNOUNCEMENT_ICONS = Array.isArray(window.pulseAnnouncementIconKeys) ? window.pulseAnnouncementIconKeys : [];
|
||||
var DEFAULT_ANNOUNCEMENT_ICON = String(window.pulseAnnouncementDefaultIconKey || ANNOUNCEMENT_ICONS[0] || '').trim().toLowerCase();
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function normalizeAnnouncementType(value) {
|
||||
var normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === 'center-card') {
|
||||
return 'fullscreen';
|
||||
}
|
||||
return ANNOUNCEMENT_TYPES.indexOf(normalized) !== -1 ? normalized : 'lower-third';
|
||||
}
|
||||
|
||||
function normalizeAnnouncementColor(value) {
|
||||
var normalized = String(value || '').trim().toLowerCase();
|
||||
if (['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'dark', 'light'].indexOf(normalized) !== -1) {
|
||||
return normalized;
|
||||
}
|
||||
return 'primary';
|
||||
}
|
||||
|
||||
function normalizeAnnouncementIcon(value) {
|
||||
var normalized = String(value || '').trim().toLowerCase();
|
||||
return ANNOUNCEMENT_ICONS.indexOf(normalized) !== -1 ? normalized : DEFAULT_ANNOUNCEMENT_ICON;
|
||||
}
|
||||
|
||||
function getAnnouncementColorTokens(colorKey) {
|
||||
var tokens = {
|
||||
primary: { accent: '#0d6efd', foreground: '#ffffff', glow: 'rgba(13, 110, 253, 0.32)' },
|
||||
secondary: { accent: '#6c757d', foreground: '#ffffff', glow: 'rgba(108, 117, 125, 0.30)' },
|
||||
success: { accent: '#198754', foreground: '#ffffff', glow: 'rgba(25, 135, 84, 0.34)' },
|
||||
info: { accent: '#0dcaf0', foreground: '#052c36', glow: 'rgba(13, 202, 240, 0.28)' },
|
||||
warning: { accent: '#ffc107', foreground: '#201400', glow: 'rgba(255, 193, 7, 0.30)' },
|
||||
danger: { accent: '#dc3545', foreground: '#ffffff', glow: 'rgba(220, 53, 69, 0.34)' },
|
||||
dark: { accent: '#212529', foreground: '#ffffff', glow: 'rgba(33, 37, 41, 0.34)' },
|
||||
light: { accent: '#f8f9fa', foreground: '#1f2937', glow: 'rgba(248, 249, 250, 0.34)' }
|
||||
};
|
||||
|
||||
return tokens[colorKey] || tokens.primary;
|
||||
}
|
||||
|
||||
function replaceTemplate(template, replacements) {
|
||||
return String(template || '').replace(/{{([A-Z0-9_]+)}}/g, function (_match, key) {
|
||||
return Object.prototype.hasOwnProperty.call(replacements, key) ? String(replacements[key]) : '';
|
||||
});
|
||||
}
|
||||
|
||||
function renderPlayerAnnouncementMarkup(announcement) {
|
||||
var normalizedType = normalizeAnnouncementType(announcement && announcement.announcement_type);
|
||||
var colorKey = normalizeAnnouncementColor(announcement && announcement.color_key);
|
||||
var colorTokens = getAnnouncementColorTokens(colorKey);
|
||||
var iconKey = normalizeAnnouncementIcon(announcement && announcement.icon_key);
|
||||
var text = escapeHtml(String(announcement && announcement.message || '').trim()).replace(/\n/g, '<br />');
|
||||
var titleText = escapeHtml(String(announcement && announcement.short_label || 'Announcement').trim() || 'Announcement');
|
||||
var templates = window.playerAnnouncementTemplates || {};
|
||||
var templateName = normalizedType === 'fullscreen' ? 'fullscreen' : normalizedType === 'top-banner' ? 'topBanner' : 'lowerThird';
|
||||
var template = templates[templateName] || '';
|
||||
|
||||
return replaceTemplate(template, {
|
||||
ACCENT: colorTokens.accent,
|
||||
FOREGROUND: colorTokens.foreground,
|
||||
GLOW: colorTokens.glow,
|
||||
ICON: iconKey,
|
||||
COLOR_KEY: colorKey,
|
||||
TITLE: titleText,
|
||||
MESSAGE: text
|
||||
});
|
||||
}
|
||||
|
||||
window.renderPlayerAnnouncementMarkup = renderPlayerAnnouncementMarkup;
|
||||
})();
|
||||
@@ -106,9 +106,45 @@ function clearSlideTransitionTimer() {
|
||||
}
|
||||
}
|
||||
|
||||
function getPlayerRegionModules() {
|
||||
if (!window.pulsePlayerRegionTypes || typeof window.pulsePlayerRegionTypes.list !== 'function') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return window.pulsePlayerRegionTypes.list();
|
||||
}
|
||||
|
||||
function runRegionLifecycle(root, lifecycleName) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
getPlayerRegionModules().forEach(function (entry) {
|
||||
var module = entry && entry.definition ? entry.definition : null;
|
||||
if (!module || typeof module[lifecycleName] !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
module[lifecycleName](root);
|
||||
} catch (_error) {
|
||||
// Keep slide rendering resilient if a region lifecycle hook fails.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function destroyRegionInstances(root) {
|
||||
runRegionLifecycle(root, 'destroyRegion');
|
||||
}
|
||||
|
||||
function initializeRegionInstances(root) {
|
||||
runRegionLifecycle(root, 'initRegion');
|
||||
}
|
||||
|
||||
// Swap slide markup with optional fade animation.
|
||||
function renderSlideMarkup(markup, shouldFade) {
|
||||
clearSlideTransitionTimer();
|
||||
destroyRegionInstances(app);
|
||||
if (typeof destroyRtmpRegions === 'function') {
|
||||
destroyRtmpRegions(app);
|
||||
}
|
||||
@@ -172,6 +208,7 @@ function renderSlideMarkup(markup, shouldFade) {
|
||||
if (typeof syncRtmpRegions === 'function') {
|
||||
syncRtmpRegions(app);
|
||||
}
|
||||
initializeRegionInstances(app);
|
||||
initializeRenderedVideoPlayback(app);
|
||||
return app.firstElementChild;
|
||||
}
|
||||
@@ -224,6 +261,7 @@ function renderSlideMarkup(markup, shouldFade) {
|
||||
if (typeof syncRtmpRegions === 'function') {
|
||||
syncRtmpRegions(nextShell);
|
||||
}
|
||||
initializeRegionInstances(nextShell);
|
||||
initializeRenderedVideoPlayback(nextShell, slideFadeDurationMs / 2);
|
||||
return nextShell;
|
||||
}
|
||||
@@ -245,6 +283,8 @@ function renderSlideMarkup(markup, shouldFade) {
|
||||
syncRtmpRegions(nextShell);
|
||||
}
|
||||
|
||||
initializeRegionInstances(nextShell);
|
||||
|
||||
initializeRenderedVideoPlayback(nextShell, slideFadeDurationMs / 2);
|
||||
|
||||
slideTransitionTimer = window.setTimeout(function () {
|
||||
@@ -389,6 +429,11 @@ function handleCommandMessage(rawMessage) {
|
||||
case 'reload':
|
||||
window.location.reload();
|
||||
return;
|
||||
case 'announcement-refresh':
|
||||
if (typeof refreshAnnouncementOverlay === 'function') {
|
||||
refreshAnnouncementOverlay();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,17 @@ function fitCanvasSize(canvasWidth, canvasHeight, maxWidth, maxHeight) {
|
||||
};
|
||||
}
|
||||
|
||||
function setPlayerCanvasDimensions(canvasWidth, canvasHeight) {
|
||||
if (!document || !document.documentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
var width = Math.max(1, Math.round(Number(canvasWidth) || 0) || 0);
|
||||
var height = Math.max(1, Math.round(Number(canvasHeight) || 0) || 0);
|
||||
document.documentElement.style.setProperty('--player-canvas-width', width + 'px');
|
||||
document.documentElement.style.setProperty('--player-canvas-height', height + 'px');
|
||||
}
|
||||
|
||||
const ALLOWED_RICH_TEXT_TAGS = ['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'];
|
||||
|
||||
function sanitizeRichTextAttributes(tagName, attrText) {
|
||||
@@ -114,7 +125,7 @@ function sanitizeRichTextAttributes(tagName, attrText) {
|
||||
return attrs.join('');
|
||||
}
|
||||
|
||||
// Remove unsafe markup while preserving richer CKEditor formatting.
|
||||
// Remove unsafe markup while preserving richer rich-text formatting.
|
||||
function sanitizeRichText(html) {
|
||||
var output = String(html || '');
|
||||
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
|
||||
@@ -202,6 +213,17 @@ function renderEditorJsTable(data) {
|
||||
return '<table class="ck-content-table">' + tableRows + '</table>';
|
||||
}
|
||||
|
||||
function wrapRichTextParagraph(html) {
|
||||
var raw = String(html || '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
if (/^<\s*(?:p|div|h[1-6]|ul|ol|pre|table|blockquote|figure|hr|li)\b/i.test(raw)) {
|
||||
return raw;
|
||||
}
|
||||
return '<p>' + raw + '</p>';
|
||||
}
|
||||
|
||||
// Render Editor.js JSON or plain content safely.
|
||||
function renderEditorJsContent(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
@@ -221,7 +243,7 @@ function renderEditorJsContent(value) {
|
||||
} catch (_error) {
|
||||
// fall through to legacy HTML rendering
|
||||
}
|
||||
return sanitizeRichText(raw);
|
||||
return wrapRichTextParagraph(sanitizeRichText(raw));
|
||||
}
|
||||
|
||||
// Parse string values that look like JSON.
|
||||
@@ -415,15 +437,14 @@ function getTemplateLayout(template) {
|
||||
function buildBackdropStyle(backgroundColor, backgroundImagePath) {
|
||||
var color = String(backgroundColor || '#111111').trim() || '#111111';
|
||||
var style = 'background-color:' + escapeHtml(color) + ';';
|
||||
var gradient = 'linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42))';
|
||||
|
||||
if (backgroundImagePath) {
|
||||
style += 'background-image:' + gradient + ',url("' + escapeHtml(backgroundImagePath) + '");';
|
||||
style += 'background-position:center,center;background-size:100% 100%,cover;background-repeat:no-repeat,no-repeat;';
|
||||
style += 'background-image:url("' + escapeHtml(backgroundImagePath) + '");';
|
||||
style += 'background-position:center;background-size:contain;background-repeat:no-repeat;';
|
||||
return style;
|
||||
}
|
||||
|
||||
style += 'background-image:' + gradient + ';background-position:center;background-size:100% 100%;background-repeat:no-repeat;';
|
||||
style += 'background-image:none;background-position:center;background-size:cover;background-repeat:no-repeat;';
|
||||
return style;
|
||||
}
|
||||
|
||||
@@ -446,9 +467,17 @@ function getTemplateRenderPlan(template) {
|
||||
}
|
||||
|
||||
var layout = getTemplateLayout(template);
|
||||
function getPlayerRegionModule(regionType) {
|
||||
return window.pulsePlayerRegionTypes && typeof window.pulsePlayerRegionTypes.get === 'function' ? window.pulsePlayerRegionTypes.get(regionType) : null;
|
||||
}
|
||||
|
||||
var plan = {
|
||||
layout: layout,
|
||||
renderRegion: function (region, regionContent) {
|
||||
var regionModule = getPlayerRegionModule(region.regionType);
|
||||
if (regionModule && typeof regionModule.renderRegion === 'function') {
|
||||
return regionModule.renderRegion(region, regionContent);
|
||||
}
|
||||
if (region.regionType === 'image') {
|
||||
return renderImageRegion(region, regionContent);
|
||||
}
|
||||
@@ -489,6 +518,9 @@ function renderTemplateSlideMarkup(slide) {
|
||||
return plan.renderRegion(region, regionContent);
|
||||
}).join('') : '';
|
||||
const stageStyle = layout ? buildBackdropStyle(layout.backgroundColor || '#111111', template.background_image_path) : '';
|
||||
if (layout) {
|
||||
setPlayerCanvasDimensions(layout.canvasWidth, layout.canvasHeight);
|
||||
}
|
||||
return renderSlideShell(slide, '', (layout ? layout.canvasWidth : 0) + 'px', (layout ? layout.canvasHeight : 0) + 'px', '<div class="template-stage" style="' + stageStyle + '">' + (layout ? layout.background : '') + regions + '</div>');
|
||||
}
|
||||
|
||||
@@ -506,6 +538,7 @@ function renderMediaSlideMarkup(slide) {
|
||||
const canvasSize = fitCanvasSize(16, 9, window.innerWidth, window.innerHeight);
|
||||
const media = renderMediaSlideContent(slide);
|
||||
const canvasStyle = slide.kind === 'image' ? buildBackdropStyle('#111111', slide.media_url) : '';
|
||||
setPlayerCanvasDimensions(canvasSize.width, canvasSize.height);
|
||||
return renderSlideShell(slide, 'slide-media', canvasSize.width + 'px', canvasSize.height + 'px', media, canvasStyle);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user