Implement schedule WYSIWYG and UTC dates

This commit is contained in:
2026-08-02 14:04:41 +01:00
parent a9d1d45d78
commit 2b9cabdab2
31 changed files with 1585 additions and 52 deletions
@@ -0,0 +1,35 @@
// Minimal row editor for schedule groups.
(function () {
var form = document.getElementById('schedule-group-form');
var body = document.querySelector('[data-schedule-entries-body]');
var addButton = document.querySelector('[data-add-schedule-entry]');
var template = document.getElementById('schedule-entry-row-template');
if (!form || !body || !addButton || !template) {
return;
}
function bindRemove(row) {
var button = row.querySelector('[data-remove-schedule-entry]');
if (!button) {
return;
}
button.addEventListener('click', function () {
row.remove();
});
}
function addRow() {
var fragment = template.content.cloneNode(true);
var row = fragment.querySelector('[data-schedule-entry-row]');
if (!row) {
return;
}
bindRemove(row);
body.appendChild(fragment);
}
body.querySelectorAll('[data-schedule-entry-row]').forEach(bindRemove);
addButton.addEventListener('click', addRow);
}());
+338
View File
@@ -0,0 +1,338 @@
// Schedule region helpers for editor previews and defaults.
(function () {
var registry = window.pulseRegionTypes;
var utils = window.pulseRegionUtils || {};
var placeholderChips = window.placeholderChips || {};
var placeholderUtils = window.placeholderUtils || {};
var DEFAULT_STYLE = {
font_family: 'Arial',
font_size: 28,
font_color: '#000000'
};
function escapeHtml(value) {
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,
font_size: DEFAULT_STYLE.font_size,
font_color: DEFAULT_STYLE.font_color
};
}
function getScheduleGroups() {
return Array.isArray(window.initialData && window.initialData.scheduleGroups) ? window.initialData.scheduleGroups : [];
}
function getGroupById(groupId, groups) {
var normalizedId = Number(groupId || 0);
return (Array.isArray(groups) ? groups : getScheduleGroups()).find(function (group) {
return Number(group.id) === normalizedId;
}) || null;
}
function getEntries(groupId, groups) {
var group = getGroupById(groupId, groups);
return group && Array.isArray(group.entries) ? group.entries : [];
}
function toDate(value) {
if (!value) {
return null;
}
var date = value instanceof Date ? value : new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
}
function isUpcoming(entry, now) {
var start = toDate(entry && entry.start_datetime);
return Boolean(start && now < start);
}
function isLive(entry, now) {
var start = toDate(entry && entry.start_datetime);
var end = toDate(entry && entry.end_datetime);
return Boolean(start && end && now >= start && now < end);
}
function getVisibleEntries(groupId, displayMode, maxItems, groups) {
var now = new Date();
var entries = getEntries(groupId, groups).slice().sort(function (left, right) {
var leftStart = toDate(left && left.start_datetime);
var rightStart = toDate(right && right.start_datetime);
return (leftStart ? leftStart.getTime() : 0) - (rightStart ? rightStart.getTime() : 0) || Number(left.id || 0) - Number(right.id || 0);
});
var mode = String(displayMode || 'upcoming').trim().toLowerCase();
entries = entries.filter(function (entry) {
if (mode === 'current') {
return isLive(entry, now);
}
if (mode === 'both') {
return isUpcoming(entry, now) || isLive(entry, now);
}
return isUpcoming(entry, now);
});
return entries.slice(0, Math.max(1, Number(maxItems || 5)));
}
function renderTemplate(template, context) {
var source = String(template || '');
if (!source) {
return '';
}
if (placeholderUtils && typeof placeholderUtils.resolvePlaceholderExpression === 'function' && typeof placeholderUtils.formatPlaceholderValue === 'function') {
return source.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function (_match, expression) {
return escapeHtml(placeholderUtils.formatPlaceholderValue(placeholderUtils.resolvePlaceholderExpression(context, expression)));
});
}
return source.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function (_match, expression) {
var value = context;
String(expression || '').trim().split('.').forEach(function (segment) {
if (value === undefined || value === null) {
value = '';
return;
}
value = value[segment];
});
return escapeHtml(value);
});
}
function getCurrentConfig(region, existingContent) {
var current = existingContent[region.region_key] || {};
return {
schedule_group_id: current.schedule_group_id === undefined || current.schedule_group_id === null || current.schedule_group_id === '' ? '' : Number(current.schedule_group_id),
display_mode: String(current.display_mode || 'upcoming').trim().toLowerCase() || 'upcoming',
value: String(current.value !== undefined ? current.value : current.text !== undefined ? current.text : ''),
max_items: Math.max(1, Number(current.max_items || 5)),
font_family: current.font_family || region.font_family || getDefaultStyle().font_family,
font_size: current.font_size || region.font_size || getDefaultStyle().font_size,
font_color: current.font_color || region.font_color || getDefaultStyle().font_color
};
}
function getSchedulePlaceholderTokens() {
return ['title', 'short_description', 'start', 'end'];
}
function renderSchedulePlaceholderChips(group, entries) {
var tokens = getSchedulePlaceholderTokens(group, entries);
if (placeholderChips && typeof placeholderChips.renderChips === 'function') {
return placeholderChips.renderChips(tokens);
}
return tokens.map(function (token) {
return '<span class="chip">{{' + escapeHtml(token) + '}}</span>';
}).join('');
}
function getCurrentSelection(regionId, card, existingContent, scheduleGroups) {
var current = existingContent[regionId] || {};
var scheduleGroupInput = card && card.querySelector ? card.querySelector('select[name="region_schedule_group_id_' + regionId + '"]') : null;
var displayModeInput = card && card.querySelector ? card.querySelector('select[name="region_schedule_display_mode_' + regionId + '"]') : null;
var maxItemsInput = card && card.querySelector ? card.querySelector('input[name="region_schedule_max_items_' + regionId + '"]') : null;
var groupId = scheduleGroupInput ? scheduleGroupInput.value : (current.schedule_group_id === undefined || current.schedule_group_id === null || current.schedule_group_id === '' ? '' : Number(current.schedule_group_id));
var displayMode = displayModeInput ? displayModeInput.value : String(current.display_mode || 'upcoming').trim().toLowerCase() || 'upcoming';
var maxItems = Math.max(1, Number(maxItemsInput ? maxItemsInput.value : current.max_items || 5));
var group = getGroupById(groupId, scheduleGroups);
var entries = getVisibleEntries(groupId, displayMode, maxItems, scheduleGroups);
if (!entries.length) {
entries = getEntries(groupId, scheduleGroups).slice(0, maxItems);
}
return {
group: group,
entries: entries,
displayMode: displayMode,
maxItems: maxItems
};
}
function renderEditorCard(context) {
var region = context.region;
var current = context.current || {};
var scheduleGroups = Array.isArray(context.scheduleGroups) ? context.scheduleGroups : [];
var currentValue = String(current.value !== undefined ? current.value : current.text !== undefined ? current.text : '');
var currentGroupId = current.schedule_group_id === undefined || current.schedule_group_id === null || current.schedule_group_id === '' ? '' : Number(current.schedule_group_id);
var currentDisplayMode = String(current.display_mode || 'upcoming').trim().toLowerCase() || 'upcoming';
var currentMaxItems = Math.max(1, Number(current.max_items || 5));
var currentGroup = getGroupById(currentGroupId, scheduleGroups);
var currentEntries = getVisibleEntries(currentGroupId, currentDisplayMode, currentMaxItems, scheduleGroups);
if (!currentEntries.length) {
currentEntries = getEntries(currentGroupId, scheduleGroups).slice(0, currentMaxItems);
}
var groupOptions = scheduleGroups.map(function (group) {
var selected = Number(group.id) === Number(currentGroupId) ? ' selected' : '';
return '<option value="' + escapeHtml(group.id) + '"' + selected + '>' + escapeHtml(group.name || ('Group ' + group.id)) + '</option>';
}).join('');
return '' +
'<div class="card card-outline card-secondary admin-form-card template-field-card mb-3" data-region-id="' + region.id + '" style="position:relative;overflow:visible !important;">' +
'<div class="card-header template-field-head">' +
'<strong>' + escapeHtml(region.label) + '</strong>' +
'<div class="template-field-actions"><span class="chip">Schedule</span></div>' +
'</div>' +
'<div class="card-body p-3 d-grid gap-3">' +
'<div class="editor-holder" data-region-id="' + region.id + '">' +
'<textarea class="editor-source" rows="10">' + escapeHtml(currentValue) + '</textarea>' +
'<input type="hidden" name="region_text_' + region.id + '" value="' + escapeHtml(currentValue) + '" />' +
'</div>' +
'<div class="row g-3">' +
'<div class="col-12 col-lg-6">' +
'<label class="form-label" for="region_schedule_group_id_' + region.id + '">Schedule group</label>' +
'<select id="region_schedule_group_id_' + region.id + '" name="region_schedule_group_id_' + region.id + '" class="form-select">' +
'<option value="">Select a group</option>' +
groupOptions +
'</select>' +
'</div>' +
'<div class="col-6 col-lg-3">' +
'<label class="form-label" for="region_schedule_display_mode_' + region.id + '">Display mode</label>' +
'<select id="region_schedule_display_mode_' + region.id + '" name="region_schedule_display_mode_' + region.id + '" class="form-select">' +
'<option value="upcoming"' + (currentDisplayMode === 'upcoming' ? ' selected' : '') + '>Upcoming</option>' +
'<option value="current"' + (currentDisplayMode === 'current' ? ' selected' : '') + '>Current</option>' +
'<option value="both"' + (currentDisplayMode === 'both' ? ' selected' : '') + '>Both</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="row g-3 align-items-end">' +
'<div class="col-6 col-lg-3">' +
'<label class="form-label" for="region_schedule_max_items_' + region.id + '">Max items</label>' +
'<input id="region_schedule_max_items_' + region.id + '" type="number" min="1" step="1" name="region_schedule_max_items_' + region.id + '" class="form-control" value="' + escapeHtml(currentMaxItems || 5) + '" />' +
'</div>' +
'<div class="col-12 col-lg-9 text-body-secondary small">Use the selected group and display mode to choose which schedule entry fields are available.</div>' +
'</div>' +
'<div class="api-region-placeholder-section schedule-placeholder-section" data-schedule-placeholder-chips>' +
'<div class="api-region-placeholder-title">Available placeholders</div>' +
'<div class="d-flex flex-wrap gap-2">' + renderSchedulePlaceholderChips(currentGroup, currentEntries) + '</div>' +
'<div class="text-body-secondary small mt-2">Format start and end values with expressions like <code>{{start.format("MMM D, YYYY h:mm A")}}</code> and <code>{{end.format("MMM D, YYYY h:mm A")}}</code>.</div>' +
'</div>' +
'</div>' +
'</div>';
}
function getTextStyle(region, current) {
var defaultStyle = getDefaultStyle();
return {
font_family: String(current.font_family || region.font_family || defaultStyle.font_family || 'Arial').trim() || 'Arial',
font_size: Math.max(8, Number(current.font_size || region.font_size || defaultStyle.font_size || 28)),
font_color: String(current.font_color || region.font_color || defaultStyle.font_color || '#000000').trim() || '#000000'
};
}
function updateSchedulePlaceholderPanel(card) {
if (!card) {
return;
}
var regionId = card.getAttribute('data-region-id');
var panel = card.querySelector('[data-schedule-placeholder-chips] .d-flex.flex-wrap.gap-2');
if (!regionId || !panel) {
return;
}
var groups = getScheduleGroups();
var current = getCurrentSelection(regionId, card, {}, groups);
panel.innerHTML = renderSchedulePlaceholderChips(current.group, current.entries);
}
if (!window.__scheduleRegionPlaceholderRefreshInstalled) {
window.__scheduleRegionPlaceholderRefreshInstalled = true;
document.addEventListener('change', function (event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('select[name^="region_schedule_group_id_"], select[name^="region_schedule_display_mode_"], input[name^="region_schedule_max_items_"]')) {
return;
}
var card = target.closest('[data-region-id]');
if (card) {
updateSchedulePlaceholderPanel(card);
}
}, true);
}
function buildEditorCardContext(context) {
return {
region: context.region,
current: context.current || {},
scheduleGroups: Array.isArray(context.scheduleGroups) ? context.scheduleGroups : []
};
}
function buildPreviewRenderContext(region, card, existingContent, scheduleGroups) {
var current = getCurrentConfig(region, existingContent || {});
var scheduleGroupInput = card && card.querySelector ? card.querySelector('select[name="region_schedule_group_id_' + region.id + '"]') : null;
var displayModeInput = card && card.querySelector ? card.querySelector('select[name="region_schedule_display_mode_' + region.id + '"]') : null;
var maxItemsInput = card && card.querySelector ? card.querySelector('input[name="region_schedule_max_items_' + region.id + '"]') : null;
var textAreaInput = card && card.querySelector ? card.querySelector('textarea.editor-source') : null;
var hiddenInput = card && card.querySelector ? card.querySelector('input[type="hidden"][name="region_text_' + region.id + '"]') : null;
var editor = window.tinymce && typeof window.tinymce.get === 'function' ? window.tinymce.get('slide-editor-region-' + region.id) : null;
var value = String(editor ? editor.getContent({ format: 'html' }) : (hiddenInput && hiddenInput.value !== undefined ? hiddenInput.value : (textAreaInput && textAreaInput.value !== undefined ? textAreaInput.value : current.value)));
return {
value: value,
style: getTextStyle(region, current),
schedule_group_id: scheduleGroupInput ? scheduleGroupInput.value || current.schedule_group_id : current.schedule_group_id,
display_mode: displayModeInput ? displayModeInput.value || current.display_mode : current.display_mode,
max_items: maxItemsInput ? maxItemsInput.value || current.max_items : current.max_items,
existingContent: existingContent || {},
scheduleGroups: Array.isArray(scheduleGroups) ? scheduleGroups : []
};
}
function renderPreview(region, regionContent, context) {
var groups = context && context.scheduleGroups ? context.scheduleGroups : [];
var style = regionContent && regionContent.style ? regionContent.style : getTextStyle(region, regionContent || {});
var value = String(regionContent && (regionContent.value !== undefined ? regionContent.value : regionContent.text !== undefined ? regionContent.text : '') || '').trim();
var groupId = regionContent && regionContent.schedule_group_id !== undefined ? regionContent.schedule_group_id : '';
var displayMode = regionContent && regionContent.display_mode ? regionContent.display_mode : 'upcoming';
var maxItems = regionContent && regionContent.max_items !== undefined ? regionContent.max_items : 5;
var group = getGroupById(groupId, groups);
var entries = getVisibleEntries(groupId, displayMode, maxItems, groups);
if (!entries.length) {
entries = getEntries(groupId, groups).slice(0, Math.max(1, Number(maxItems || 5)));
}
if (!value) {
return '<div class="slide-preview-region schedule" 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) + ';' : '') + '"></div>';
}
return '<div class="slide-preview-region schedule" 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) {
return '<div class="schedule-region-entry" data-schedule-entry-index="' + index + '">' + sanitizeRichText(renderTemplate(value, Object.assign({}, entry || {}, {
start: entry && entry.start_datetime !== undefined ? entry.start_datetime : '',
end: entry && entry.end_datetime !== undefined ? entry.end_datetime : '',
group: group || {},
entries: entries,
index: index + 1
}))) + '</div>';
}).join('') + '</div>';
}
registry.register('schedule', {
label: 'Schedule',
getDefaultStyle: getDefaultStyle,
getDefaultRegionSize: function () {
return { width: 520, height: 280 };
},
getCurrentConfig: getCurrentConfig,
renderPreview: renderPreview,
renderEditorCard: renderEditorCard,
buildEditorCardContext: buildEditorCardContext,
buildPreviewRenderContext: buildPreviewRenderContext
});
}());
+81 -6
View File
@@ -2,7 +2,12 @@
(function () {
var root = window;
var transformPattern = /^(upper|lower|title)\(\)$/i;
var transformPattern = /^([a-z_][a-z0-9_]*)\((.*)\)$/i;
var monthNamesShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var monthNamesLong = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var dayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var dayNamesLong = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
function resolvePath(value, path) {
var current = value;
@@ -29,11 +34,15 @@
while (segments.length) {
var candidate = String(segments[segments.length - 1] || '').trim();
if (!transformPattern.test(candidate)) {
var match = candidate.match(transformPattern);
if (!match) {
break;
}
transforms.unshift(candidate.replace(/\(\)$/g, '').toLowerCase());
transforms.unshift({
name: String(match[1] || '').trim().toLowerCase(),
args: match[2] ? splitTransformArgs(match[2]) : []
});
segments.pop();
}
@@ -43,23 +52,89 @@
};
}
function splitTransformArgs(value) {
var source = String(value || '').trim();
if (!source) {
return [];
}
if ((source[0] === '"' && source[source.length - 1] === '"') || (source[0] === '\'' && source[source.length - 1] === '\'')) {
return [source.slice(1, -1)];
}
return source.split(',').map(function (item) {
return String(item || '').trim();
}).filter(Boolean);
}
function padNumber(value, size) {
var text = String(Math.abs(Number(value || 0)));
while (text.length < size) {
text = '0' + text;
}
return text;
}
function formatDateValue(value, pattern) {
var date = value instanceof Date ? value : new Date(value);
if (Number.isNaN(date.getTime())) {
return '';
}
var format = String(pattern || 'YYYY-MM-DD HH:mm').trim() || 'YYYY-MM-DD HH:mm';
var hours24 = date.getHours();
var hours12 = hours24 % 12 || 12;
var tokenMap = {
YYYY: String(date.getFullYear()),
YY: String(date.getFullYear()).slice(-2),
MMMM: monthNamesLong[date.getMonth()],
MMM: monthNamesShort[date.getMonth()],
MM: padNumber(date.getMonth() + 1, 2),
M: String(date.getMonth() + 1),
DD: padNumber(date.getDate(), 2),
D: String(date.getDate()),
dddd: dayNamesLong[date.getDay()],
ddd: dayNamesShort[date.getDay()],
HH: padNumber(hours24, 2),
H: String(hours24),
hh: padNumber(hours12, 2),
h: String(hours12),
mm: padNumber(date.getMinutes(), 2),
m: String(date.getMinutes()),
ss: padNumber(date.getSeconds(), 2),
s: String(date.getSeconds()),
A: hours24 >= 12 ? 'PM' : 'AM',
a: hours24 >= 12 ? 'pm' : 'am'
};
return format.replace(/\[([^\]]+)\]|YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g, function (match, literal) {
return literal || tokenMap[match] || match;
});
}
function applyTransform(value, transform) {
var text = String(value === undefined || value === null ? '' : value);
var name = String(transform && transform.name || '').trim().toLowerCase();
var args = Array.isArray(transform && transform.args) ? transform.args : [];
if (transform === 'lower') {
if (name === 'lower') {
return text.toLowerCase();
}
if (transform === 'upper') {
if (name === 'upper') {
return text.toUpperCase();
}
if (transform === 'title') {
if (name === 'title') {
return text.toLowerCase().replace(/\b([a-z])/g, function (match, letter) {
return String(letter || '').toUpperCase();
});
}
if (name === 'format' || name === 'date' || name === 'datetime' || name === 'time') {
return formatDateValue(value, args[0] || (name === 'time' ? 'h:mm A' : name === 'date' ? 'MMM D, YYYY' : 'MMM D, YYYY h:mm A'));
}
return text;
}
+22 -3
View File
@@ -23,6 +23,22 @@ export function createSlideFormEditorController(options) {
return String(value === undefined || value === null ? '' : value).trim();
}
function isEmptyRichTextValue(value) {
var raw = String(value === undefined || value === null ? '' : value).trim();
if (!raw) {
return true;
}
var stripped = raw
.replace(/<\s*br\s*\/?>/gi, '')
.replace(/<p[^>]*>(?:\s|&nbsp;|<br\s*\/?>)*<\/p>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/gi, '')
.trim();
return !stripped;
}
function normalizeFontSizeValue(value) {
var raw = String(value || '').trim().toLowerCase();
if (/^\d+(?:\.\d+)?px$/.test(raw)) {
@@ -156,6 +172,7 @@ export function createSlideFormEditorController(options) {
editor.save();
}
var content = editor.getContent({ format: 'html' });
content = isEmptyRichTextValue(content) ? '' : content;
if (hidden) {
hidden.value = content;
}
@@ -170,6 +187,7 @@ export function createSlideFormEditorController(options) {
editor.on('init', function () {
var content = editor.getContent({ format: 'html' });
content = isEmptyRichTextValue(content) ? '' : content;
if (hidden) {
hidden.value = content;
}
@@ -215,20 +233,21 @@ export function createSlideFormEditorController(options) {
promotion: false,
statusbar: true,
resize: true,
plugins: 'lists link code advlist fullscreen',
toolbar: 'undo redo | fontfamily fontsizeinput | forecolor backcolor bold italic underline strikethrough subscript superscript removeformat | align lineheight indent outdent bullist numlist | fullscreen',
plugins: 'lists link code advlist fullscreen table',
toolbar: 'undo redo | fontfamily fontsizeinput | forecolor backcolor bold italic underline strikethrough subscript superscript removeformat | align lineheight indent outdent bullist numlist table | fullscreen',
toolbar_mode: 'sliding',
license_key: 'gpl',
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; }' + (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; width: 100%; } td, th { border: 1px solid currentColor; padding: 0.35em 0.5em; vertical-align: top; } th { font-weight: 700; }' + (editorContentStyle ? ' ' + editorContentStyle : ''),
font_family_formats: getFontFamilyFormats(),
font_size_input_default_unit: 'px',
forced_root_block: 'p',
force_br_newlines: false,
newline_behavior: 'default',
placeholder: String(source.getAttribute('placeholder') || '').trim(),
setup: function (editor) {
editorInstances.set(regionId, editor);
attachEditorEvents(regionId, editor);
+32 -8
View File
@@ -8,6 +8,7 @@ export function createSlideFormRegionHelpers(options) {
var existingContent = settings.existingContent || {};
var rssFeeds = Array.isArray(settings.rssFeeds) ? settings.rssFeeds : [];
var apiSources = Array.isArray(settings.apiSources) ? settings.apiSources : [];
var scheduleGroups = Array.isArray(settings.scheduleGroups) ? settings.scheduleGroups : [];
var getRegionTypeModule = typeof settings.getRegionTypeModule === 'function' ? settings.getRegionTypeModule : function () { return null; };
var placeholderUtils = window.placeholderUtils || {};
var placeholderChips = window.placeholderChips || {};
@@ -459,6 +460,15 @@ export function createSlideFormRegionHelpers(options) {
};
}
function getCurrentScheduleConfig(region) {
var current = existingContent[region.region_key] || {};
return {
schedule_group_id: current.schedule_group_id === undefined || current.schedule_group_id === null || current.schedule_group_id === '' ? '' : Number(current.schedule_group_id),
display_mode: String(current.display_mode || 'upcoming').trim().toLowerCase() || 'upcoming',
max_items: Math.max(1, Number(current.max_items || 5))
};
}
function getCurrentRegionVideoDuration(region) {
var current = existingContent[region.region_key] || {};
var duration = Math.round(Number(current.duration_seconds || 0) * 1000) / 1000;
@@ -470,13 +480,13 @@ export function createSlideFormRegionHelpers(options) {
if (module && typeof module.buildEditorCardContext === 'function') {
return module.buildEditorCardContext({
region: region,
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'time-date' ? (existingContent[region.region_key] || {}) : getCurrentRegionValue(region),
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'time-date' || region.region_type === 'schedule' ? (existingContent[region.region_key] || {}) : getCurrentRegionValue(region),
currentDuration: getCurrentRegionVideoDuration(region),
regionRatio: reduceAspectRatio(region.width, region.height),
uploadMaxLabel: uploadMaxLabel,
uploadVideoMaxLabel: uploadVideoMaxLabel,
disableAudio: (existingContent[region.region_key] || {}).disable_audio,
config: region.region_type === 'api' ? getCurrentApiConfig(region) : getCurrentRssConfig(region),
config: region.region_type === 'api' ? getCurrentApiConfig(region) : region.region_type === 'schedule' ? getCurrentScheduleConfig(region) : getCurrentRssConfig(region),
style: getCurrentTextStyle(region),
fontSize: getCurrentTextStyle(region).font_size,
feedOptions: rssFeeds.map(function (feed) {
@@ -492,8 +502,9 @@ export function createSlideFormRegionHelpers(options) {
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
}).join('')
: buildLimitedPlaceholderChipMarkup(getApiFieldList(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).items_path), 'No JSON fields available.'),
sampleDataPreview: buildApiSampleDataMarkup(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).item_number, getCurrentApiConfig(region).items_path)
}, existingContent, region.region_type === 'rss' ? rssFeeds : apiSources);
sampleDataPreview: buildApiSampleDataMarkup(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).item_number, getCurrentApiConfig(region).items_path),
scheduleGroups: scheduleGroups
}, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'schedule' ? scheduleGroups : apiSources);
}
var textStyle = getCurrentTextStyle(region);
@@ -510,7 +521,7 @@ export function createSlideFormRegionHelpers(options) {
uploadMaxLabel: uploadMaxLabel,
uploadVideoMaxLabel: uploadVideoMaxLabel,
disableAudio: currentContent.disable_audio,
config: region.region_type === 'api' ? apiConfig : rssConfig,
config: region.region_type === 'api' ? apiConfig : region.region_type === 'schedule' ? getCurrentScheduleConfig(region) : rssConfig,
style: textStyle,
fontSize: textStyle.font_size,
feedOptions: rssFeeds.map(function (feed) {
@@ -526,7 +537,8 @@ export function createSlideFormRegionHelpers(options) {
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
}).join('')
: buildLimitedPlaceholderChipMarkup(getApiFieldList(apiConfig.source_id, apiItemsPath), 'No JSON fields available.'),
sampleDataPreview: buildApiSampleDataMarkup(apiConfig.source_id, apiConfig.item_number, apiItemsPath)
sampleDataPreview: buildApiSampleDataMarkup(apiConfig.source_id, apiConfig.item_number, apiItemsPath),
scheduleGroups: scheduleGroups
};
}
@@ -542,7 +554,7 @@ export function createSlideFormRegionHelpers(options) {
function getPreviewRegionContent(card, region) {
var module = getRegionTypeModule(region.region_type);
if (module && typeof module.buildPreviewRenderContext === 'function') {
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : apiSources);
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'schedule' ? scheduleGroups : apiSources);
}
var current = existingContent[region.region_key];
@@ -592,6 +604,14 @@ export function createSlideFormRegionHelpers(options) {
content.source_id = apiSourceInput ? apiSourceInput.value : content.source_id;
content.item_number = apiItemInput ? apiItemInput.value : content.item_number;
content.items_path = apiItemsPathInput ? apiItemsPathInput.value : content.items_path;
} else if (region.region_type === 'schedule') {
var scheduleGroupInput = card.querySelector('select[name="region_schedule_group_id_' + region.id + '"]');
var scheduleDisplayModeInput = card.querySelector('select[name="region_schedule_display_mode_' + region.id + '"]');
var scheduleMaxItemsInput = card.querySelector('input[name="region_schedule_max_items_' + region.id + '"]');
var currentSchedule = getCurrentScheduleConfig(region);
content.schedule_group_id = scheduleGroupInput ? scheduleGroupInput.value : currentSchedule.schedule_group_id;
content.display_mode = scheduleDisplayModeInput ? scheduleDisplayModeInput.value : currentSchedule.display_mode;
content.max_items = scheduleMaxItemsInput ? scheduleMaxItemsInput.value : currentSchedule.max_items;
} else {
content.value = hiddenInput ? hiddenInput.value : (content.value || '');
}
@@ -602,7 +622,7 @@ export function createSlideFormRegionHelpers(options) {
function buildPreviewRenderContext(region, card) {
var module = getRegionTypeModule(region.region_type);
if (module && typeof module.buildPreviewRenderContext === 'function') {
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : apiSources);
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'schedule' ? scheduleGroups : apiSources);
}
var textStyle = getCurrentTextStyle(region);
@@ -619,6 +639,10 @@ export function createSlideFormRegionHelpers(options) {
item_number: previewContent.item_number,
source_id: previewContent.source_id,
items_path: previewContent.items_path,
schedule_group_id: previewContent.schedule_group_id,
display_mode: previewContent.display_mode,
max_items: previewContent.max_items,
scheduleGroups: scheduleGroups,
font_size: textStyle.font_size,
font_color: textStyle.font_color,
font_family: textStyle.font_family
+6 -1
View File
@@ -21,6 +21,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
var templates = Array.isArray(slideEditorData.templates) ? slideEditorData.templates : [];
var rssFeeds = Array.isArray(slideEditorData.rssFeeds) ? slideEditorData.rssFeeds : [];
var apiSources = Array.isArray(slideEditorData.apiSources) ? slideEditorData.apiSources : [];
var scheduleGroups = Array.isArray(slideEditorData.scheduleGroups) ? slideEditorData.scheduleGroups : [];
var fontStylesheetHref = String(slideEditorData.fontStylesheetHref || '').trim();
var existingTemplateId = slideEditorData.existingTemplateId !== undefined ? slideEditorData.existingTemplateId : null;
var existingContent = slideEditorData.existingContent || {};
@@ -49,7 +50,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
var currentPreviewCanvasWidth = 0;
var currentPreviewCanvasHeight = 0;
var sidebarSyncFrame = 0;
var sidebarTopOffset = 16;
var sidebarBaseOffset = 16;
var templateSelectorLock = createTemplateSelectorLockController(templateSelect);
var regionTypes = window.pulseRegionTypes || {};
@@ -119,6 +120,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
existingContent: existingContent,
rssFeeds: rssFeeds,
apiSources: apiSources,
scheduleGroups: scheduleGroups,
defaultFontSize: DEFAULT_FONT_SIZE,
uploadMaxLabel: uploadMaxLabel,
uploadVideoMaxLabel: uploadVideoMaxLabel,
@@ -487,6 +489,9 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
return;
}
var stickyHeader = document.querySelector('.app-header');
var stickyHeaderHeight = stickyHeader ? Math.max(0, Math.round(stickyHeader.getBoundingClientRect().height || stickyHeader.offsetHeight || 0)) : 0;
var sidebarTopOffset = sidebarBaseOffset + stickyHeaderHeight + 12;
var shellRect = slideEditorShell.getBoundingClientRect();
var sidebarHeight = slideEditorSidebar.offsetHeight;
var shellTop = window.scrollY + shellRect.top;