Save worktree changes

This commit is contained in:
2026-07-25 02:29:19 +01:00
parent 8d3b7d557b
commit db9d718cd8
170 changed files with 11719 additions and 3414 deletions
+97
View File
@@ -0,0 +1,97 @@
function getApiSourceById(sourceId) {
var sources = Array.isArray(initialData && initialData.apiSources) ? initialData.apiSources : [];
var normalizedId = Number(sourceId || 0);
return sources.find(function (source) {
return Number(source.id) === normalizedId;
}) || null;
}
function getApiSourceItems(sourceId) {
var source = getApiSourceById(sourceId);
var responseJson = source && source.responseJson && typeof source.responseJson === 'object' ? source.responseJson : null;
if (Array.isArray(responseJson)) {
return responseJson;
}
if (responseJson && Array.isArray(responseJson.items)) {
return responseJson.items;
}
if (responseJson && Array.isArray(responseJson.results)) {
return responseJson.results;
}
if (responseJson && Array.isArray(responseJson.data)) {
return responseJson.data;
}
return responseJson ? [responseJson] : [];
}
function getApiItem(sourceId, itemNumber) {
var items = getApiSourceItems(sourceId);
var parsedItemNumber = Math.max(1, Number(itemNumber || 1));
var index = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
return items[index] || null;
}
function resolveApiPath(value, path) {
var current = value;
if (!path) {
return current;
}
String(path).split('.').forEach(function (segment) {
if (current === undefined || current === null) {
current = '';
return;
}
current = current[segment];
});
return current === undefined || current === null ? '' : current;
}
function substituteApiVariables(html, item) {
var source = String(html || '');
return source.replace(/\{\{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_.]+))?\s*\}\}/g, function (_match, tokenName, tokenPath) {
if (!item || typeof item !== 'object') {
return '';
}
var key = tokenName === 'item' && tokenPath ? tokenPath : tokenName;
return escapeHtml(resolveApiPath(item, key || ''));
});
}
function getApiPreviewFallback(item) {
if (!item || typeof item !== 'object') {
return '<div class="template-region-placeholder">API item</div>';
}
var title = String(item.title || item.name || '').trim();
var description = String(item.description || item.summary || item.text || '').trim();
var summary = [];
if (title) {
summary.push('<h3>' + escapeHtml(title) + '</h3>');
}
if (description) {
summary.push('<div>' + sanitizeRichText(description) + '</div>');
}
if (!summary.length) {
return '<div class="template-region-placeholder">API item</div>';
}
return summary.join('');
}
function renderApiRegion(region, regionContent) {
var content = regionContent && regionContent.value !== undefined ? regionContent.value : '';
var sourceId = regionContent && regionContent.source_id !== undefined ? regionContent.source_id : null;
var itemNumber = regionContent && regionContent.item_number !== undefined ? regionContent.item_number : 1;
var fontFamily = sanitizeFontFamily(regionContent.font_family || region.fontFamily);
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize);
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor);
var item = getApiItem(sourceId, itemNumber);
var body = item ? substituteApiVariables(content, item) : '';
if (!body && item) {
body = getApiPreviewFallback(item);
}
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
var renderedBody = body ? renderEditorJsContent(body) : '<div class="template-region-placeholder">API item</div>';
return '<div class="template-region api" style="' + region.baseStyle + '"><div class="template-region-text-scale" style="' + contentStyle + '">' + renderedBody + '</div></div>';
}
+11
View File
@@ -0,0 +1,11 @@
function renderHtmlRegionContent(value) {
var html = String(value || '').trim();
if (!html) {
return '<div class="template-region-placeholder">HTML</div>';
}
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>';
}
function renderHtmlRegion(region, regionContent) {
return '<div class="template-region html" style="' + region.baseStyle + '">' + renderHtmlRegionContent(regionContent.value || '') + '</div>';
}
+4
View File
@@ -0,0 +1,4 @@
function renderImageRegion(region, regionContent) {
var src = regionContent.value || '';
return '<div class="template-region image" style="' + region.baseStyle + '"><img src="' + escapeHtml(src) + '" alt="' + escapeHtml(region.label) + '" /></div>';
}
+67
View File
@@ -0,0 +1,67 @@
function getRssFeedById(feedId) {
var feeds = Array.isArray(initialData && initialData.rssFeeds) ? initialData.rssFeeds : [];
var normalizedId = Number(feedId || 0);
return feeds.find(function (feed) {
return Number(feed.id) === normalizedId;
}) || null;
}
function getRssFeedItem(feedId, itemNumber) {
var feed = getRssFeedById(feedId);
var items = feed && Array.isArray(feed.items) ? feed.items : [];
var parsedItemNumber = Math.max(1, Number(itemNumber || 1));
var index = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
return items[index] || null;
}
function resolveRssPath(value, path) {
var current = value;
if (!path) {
return current;
}
String(path).split('.').forEach(function (segment) {
if (current === undefined || current === null) {
current = '';
return;
}
current = current[segment];
});
return current === undefined || current === null ? '' : current;
}
function substituteRssVariables(html, item) {
var source = String(html || '');
return source.replace(/\{\{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_.]+))?\s*\}\}/g, function (_match, tokenName, tokenPath) {
if (!item || typeof item !== 'object') {
return '';
}
var key = tokenName === 'item' && tokenPath ? tokenPath : tokenName;
return escapeHtml(resolveRssPath(item, key || ''));
});
}
function renderRssRegion(region, regionContent) {
var content = regionContent && regionContent.value !== undefined ? regionContent.value : '';
var feedId = regionContent && regionContent.feed_id !== undefined ? regionContent.feed_id : null;
var itemNumber = regionContent && regionContent.item_number !== undefined ? regionContent.item_number : 1;
var fontFamily = sanitizeFontFamily(regionContent.font_family || region.fontFamily);
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize);
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor);
var item = getRssFeedItem(feedId, itemNumber);
var body = item ? substituteRssVariables(content, item) : '';
if (!body && item) {
var summaryParts = [];
if (item.title) {
summaryParts.push('<h3>' + escapeHtml(item.title) + '</h3>');
}
if (item.description) {
summaryParts.push('<div>' + sanitizeRichText(item.description) + '</div>');
}
body = summaryParts.join('');
}
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
var renderedBody = body ? renderEditorJsContent(body) : '<div class="template-region-placeholder">RSS item</div>';
return '<div class="template-region rss" style="' + region.baseStyle + '"><div class="template-region-text-scale" style="' + contentStyle + '">' + renderedBody + '</div></div>';
}
+227
View File
@@ -0,0 +1,227 @@
function renderRtmpRegion(region, regionContent) {
var url = String(regionContent.value || '').trim();
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
if (!url) {
return '<div class="template-region rtmp" style="' + region.baseStyle + '"><div class="template-region-placeholder">RTMP stream</div></div>';
}
return '<div class="template-region rtmp" style="' + region.baseStyle + '"><video class="template-region-rtmp-video" data-rtmp-source="' + escapeHtml(url) + '" data-rtmp-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay playsinline preload="auto" tabindex="-1" disablepictureinpicture></video><div class="template-region-placeholder template-region-rtmp-placeholder">Loading RTMP stream...</div></div>';
}
function getRtmpSessionUrl(sourceUrl, disableAudio) {
return '/api/rtmp/session?source=' + encodeURIComponent(sourceUrl) + '&disableAudio=' + (disableAudio ? '1' : '0');
}
var rtmpPreloadSignature = '';
function getRtmpPreloadSlides(sourceSlides, targetIndex) {
var availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
if (!availableSlides.length) {
return [];
}
var normalizedIndex = Number(targetIndex || 0);
if (!Number.isFinite(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
normalizedIndex = 0;
}
var preloadSlides = [];
var currentSlide = availableSlides[normalizedIndex];
var nextSlide = availableSlides[normalizedIndex + 1];
if (currentSlide) {
preloadSlides.push(currentSlide);
}
if (nextSlide && nextSlide !== currentSlide) {
preloadSlides.push(nextSlide);
}
return preloadSlides;
}
function getRtmpPreloadEntries(sourceSlides) {
var entries = [];
var seen = Object.create(null);
(Array.isArray(sourceSlides) ? sourceSlides : []).forEach(function (slide) {
var content = slide && slide.content ? slide.content : {};
var regions = slide && slide.template && Array.isArray(slide.template.regions) ? slide.template.regions : [];
regions.forEach(function (region) {
if (region.region_type !== 'rtmp') {
return;
}
var regionContent = content[region.region_key] || {};
var url = String(regionContent.value || '').trim();
if (!url) {
return;
}
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
var key = url + '\n' + (disableAudio ? '1' : '0');
if (seen[key]) {
return;
}
seen[key] = true;
entries.push({
url: url,
disableAudio: disableAudio
});
});
});
return entries;
}
function syncRtmpPreloads(sourceSlides, targetIndex) {
var entries = getRtmpPreloadEntries(getRtmpPreloadSlides(sourceSlides, targetIndex));
var signature = entries.map(function (entry) {
return entry.url + '\n' + (entry.disableAudio ? '1' : '0');
}).join('\n');
if (signature === rtmpPreloadSignature) {
return;
}
rtmpPreloadSignature = signature;
if (!entries.length) {
return;
}
entries.forEach(function (entry) {
fetch(getRtmpSessionUrl(entry.url, entry.disableAudio), {
credentials: 'same-origin'
}).catch(function () {
return null;
});
});
}
function syncRtmpRegions(root) {
if (!root) {
return;
}
var videos = root.querySelectorAll('video[data-rtmp-source]');
Array.prototype.forEach.call(videos, function (video) {
if (!video || video.dataset.rtmpInitialized === '1') {
return;
}
var sourceUrl = String(video.dataset.rtmpSource || '').trim();
var disableAudio = String(video.dataset.rtmpDisableAudio || '1') !== '0';
var region = video.parentNode;
var placeholder = region ? region.querySelector('.template-region-rtmp-placeholder') : null;
if (!sourceUrl) {
if (placeholder) {
placeholder.textContent = 'RTMP stream';
}
return;
}
video.dataset.rtmpInitialized = '1';
video.muted = disableAudio;
video.controls = false;
video.playsInline = true;
video.autoplay = true;
var markReady = function () {
if (placeholder) {
placeholder.style.display = 'none';
}
};
var tryPlay = function () {
if (video && typeof video.play === 'function') {
video.play().catch(function () {
return null;
});
}
};
video.addEventListener('canplay', markReady, { once: true });
video.addEventListener('playing', markReady, { once: true });
fetch(getRtmpSessionUrl(sourceUrl, disableAudio), {
credentials: 'same-origin'
}).then(function (response) {
if (!response.ok) {
throw new Error('Unable to initialize RTMP stream.');
}
return response.json();
}).then(function (payload) {
var playlistUrl = payload && payload.playlistUrl ? String(payload.playlistUrl).trim() : '';
if (!playlistUrl) {
throw new Error('RTMP playlist URL was not returned.');
}
if (window.Hls && window.Hls.isSupported && window.Hls.isSupported()) {
var hls = new window.Hls({
enableWorker: true,
lowLatencyMode: true,
liveSyncDurationCount: 4,
liveMaxLatencyDurationCount: 8,
maxBufferLength: 20,
maxLiveSyncPlaybackRate: 1,
backBufferLength: 30
});
video.__rtmpHls = hls;
hls.attachMedia(video);
hls.on(window.Hls.Events.MEDIA_ATTACHED, function () {
hls.loadSource(playlistUrl);
});
hls.on(window.Hls.Events.MANIFEST_PARSED, function () {
tryPlay();
});
hls.on(window.Hls.Events.ERROR, function (_event, data) {
if (data && data.fatal) {
try {
hls.destroy();
} catch (_error) {
// ignore cleanup errors
}
video.__rtmpHls = null;
if (placeholder) {
placeholder.style.display = 'flex';
placeholder.textContent = 'RTMP playback failed.';
}
}
});
return;
}
if (video.canPlayType && video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = playlistUrl;
tryPlay();
return;
}
if (placeholder) {
placeholder.style.display = 'flex';
placeholder.textContent = 'RTMP playback is not supported in this browser.';
}
}).catch(function (_error) {
if (placeholder) {
placeholder.style.display = 'flex';
placeholder.textContent = 'Unable to load RTMP stream.';
}
});
});
}
function destroyRtmpRegions(root) {
if (!root) {
return;
}
var videos = root.querySelectorAll('video[data-rtmp-source]');
Array.prototype.forEach.call(videos, function (video) {
if (video.__rtmpHls) {
try {
video.__rtmpHls.destroy();
} catch (_error) {
// ignore cleanup errors
}
video.__rtmpHls = null;
}
});
}
+7
View File
@@ -0,0 +1,7 @@
function renderTextRegion(region, regionContent) {
var fontFamily = sanitizeFontFamily(regionContent.font_family || region.fontFamily);
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize);
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor);
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
return '<div class="template-region text" style="' + region.baseStyle + '"><div class="template-region-text-scale" style="' + contentStyle + '">' + renderEditorJsContent(regionContent.value || '') + '</div></div>';
}
+5
View File
@@ -0,0 +1,5 @@
function renderWebpageRegion(region, regionContent) {
var url = String(regionContent.value || '').trim();
var iframe = url ? '<iframe src="' + escapeHtml(url) + '" title="' + escapeHtml(region.label) + '" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe>' : '<div class="template-region-placeholder">Webpage</div>';
return '<div class="template-region webpage" style="' + region.baseStyle + '">' + iframe + '</div>';
}