Release v2.4.2

This commit is contained in:
2026-08-03 12:45:27 +01:00
parent 2b9cabdab2
commit e95928f31c
141 changed files with 7694 additions and 5078 deletions
+99 -22
View File
@@ -8,7 +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 timetableGroups = Array.isArray(settings.timetableGroups) ? settings.timetableGroups : [];
var getRegionTypeModule = typeof settings.getRegionTypeModule === 'function' ? settings.getRegionTypeModule : function () { return null; };
var placeholderUtils = window.placeholderUtils || {};
var placeholderChips = window.placeholderChips || {};
@@ -460,10 +460,10 @@ export function createSlideFormRegionHelpers(options) {
};
}
function getCurrentScheduleConfig(region) {
function getCurrentTimetableConfig(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),
timetable_group_id: current.timetable_group_id === undefined || current.timetable_group_id === null || current.timetable_group_id === '' ? '' : Number(current.timetable_group_id),
display_mode: String(current.display_mode || 'upcoming').trim().toLowerCase() || 'upcoming',
max_items: Math.max(1, Number(current.max_items || 5))
};
@@ -480,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' || region.region_type === 'schedule' ? (existingContent[region.region_key] || {}) : getCurrentRegionValue(region),
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'time-date' || region.region_type === 'timetable' ? (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) : region.region_type === 'schedule' ? getCurrentScheduleConfig(region) : getCurrentRssConfig(region),
config: region.region_type === 'api' ? getCurrentApiConfig(region) : region.region_type === 'timetable' ? getCurrentTimetableConfig(region) : getCurrentRssConfig(region),
style: getCurrentTextStyle(region),
fontSize: getCurrentTextStyle(region).font_size,
feedOptions: rssFeeds.map(function (feed) {
@@ -503,8 +503,8 @@ export function createSlideFormRegionHelpers(options) {
}).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),
scheduleGroups: scheduleGroups
}, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'schedule' ? scheduleGroups : apiSources);
timetableGroups: timetableGroups
}, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'timetable' ? timetableGroups : apiSources);
}
var textStyle = getCurrentTextStyle(region);
@@ -521,7 +521,7 @@ export function createSlideFormRegionHelpers(options) {
uploadMaxLabel: uploadMaxLabel,
uploadVideoMaxLabel: uploadVideoMaxLabel,
disableAudio: currentContent.disable_audio,
config: region.region_type === 'api' ? apiConfig : region.region_type === 'schedule' ? getCurrentScheduleConfig(region) : rssConfig,
config: region.region_type === 'api' ? apiConfig : region.region_type === 'timetable' ? getCurrentTimetableConfig(region) : rssConfig,
style: textStyle,
fontSize: textStyle.font_size,
feedOptions: rssFeeds.map(function (feed) {
@@ -538,7 +538,7 @@ export function createSlideFormRegionHelpers(options) {
}).join('')
: buildLimitedPlaceholderChipMarkup(getApiFieldList(apiConfig.source_id, apiItemsPath), 'No JSON fields available.'),
sampleDataPreview: buildApiSampleDataMarkup(apiConfig.source_id, apiConfig.item_number, apiItemsPath),
scheduleGroups: scheduleGroups
timetableGroups: timetableGroups
};
}
@@ -548,13 +548,89 @@ export function createSlideFormRegionHelpers(options) {
return '';
}
return module.renderEditorCard(buildRegionEditorCardContext(region));
return makeRegionCardCollapsible(module.renderEditorCard(buildRegionEditorCardContext(region)), region);
}
function makeRegionCardCollapsible(html, region) {
if (typeof html !== 'string' || !html) {
return html;
}
if (html.indexOf('data-lte-toggle="card-collapse"') !== -1) {
return html;
}
var cardHtml = html.replace(
'class="card card-outline card-secondary admin-form-card template-field-card mb-3"',
'class="card card-widget card-outline card-secondary admin-form-card template-field-card mb-3"'
);
if (cardHtml === html) {
return html;
}
var bodyOpenMatch = cardHtml.match(/<div class="card-body([^"]*)">/);
if (bodyOpenMatch) {
var bodyClassTokens = String(bodyOpenMatch[1] || '')
.trim()
.split(/\s+/)
.filter(Boolean);
var wrapperGapTokens = bodyClassTokens.filter(function (token) {
return /^gap-\d+$/i.test(token);
});
var wrapperPaddingTokens = bodyClassTokens.filter(function (token) {
return /^(?:p|pt|pb|ps|pe|px|py)-\d+$/i.test(token);
});
var bodyClassTokensWithoutGrid = bodyClassTokens.filter(function (token) {
return token !== 'd-grid' && !/^gap-\d+$/i.test(token) && !/^(?:p|pt|pb|ps|pe|px|py)-\d+$/i.test(token);
});
var bodyClassAttribute = ' class="card-body p-0' + (bodyClassTokensWithoutGrid.length ? ' ' + bodyClassTokensWithoutGrid.join(' ') : '') + '"';
var wrapperClassTokens = ['template-field-body', 'd-grid']
.concat(wrapperPaddingTokens)
.concat(wrapperGapTokens);
var wrapperClassAttribute = ' class="' + wrapperClassTokens.filter(Boolean).join(' ') + '"';
cardHtml = cardHtml.replace(bodyOpenMatch[0], '<div' + bodyClassAttribute + '><div' + wrapperClassAttribute + '>');
var lastCloseIndex = cardHtml.lastIndexOf('</div>');
if (lastCloseIndex !== -1) {
var bodyCloseIndex = cardHtml.lastIndexOf('</div>', lastCloseIndex - 1);
if (bodyCloseIndex !== -1) {
cardHtml = cardHtml.slice(0, bodyCloseIndex) + '</div>' + cardHtml.slice(bodyCloseIndex);
}
}
}
var collapseLabel = region && region.label ? String(region.label) : 'region';
var collapseButton = '' +
'<button type="button" class="btn btn-tool p-0 ms-auto text-body-secondary template-field-collapse" data-lte-toggle="card-collapse" aria-label="Collapse ' + escapeHtml(collapseLabel) + '" title="Collapse ' + escapeHtml(collapseLabel) + '">' +
'<i class="bi bi-dash-lg" aria-hidden="true" data-lte-icon="collapse"></i>' +
'<i class="bi bi-plus-lg" aria-hidden="true" data-lte-icon="expand"></i>' +
'<span class="visually-hidden">Collapse ' + escapeHtml(collapseLabel) + '</span>' +
'</button>';
var bodyMarkerIndex = cardHtml.indexOf('<div class="card-body');
if (bodyMarkerIndex === -1) {
return cardHtml;
}
var actionsMarker = '<div class="template-field-actions">';
if (cardHtml.indexOf(actionsMarker) !== -1) {
return cardHtml.replace(/(<div class="template-field-actions">[\s\S]*?)<\/div>/, '$1' + collapseButton + '</div>');
}
var headerCloseIndex = cardHtml.lastIndexOf('</div>', bodyMarkerIndex);
if (headerCloseIndex === -1) {
return cardHtml;
}
return cardHtml.slice(0, headerCloseIndex) + collapseButton + cardHtml.slice(headerCloseIndex);
}
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 : region.region_type === 'schedule' ? scheduleGroups : apiSources);
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'timetable' ? timetableGroups : apiSources);
}
var current = existingContent[region.region_key];
@@ -604,14 +680,15 @@ 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 if (region.region_type === 'timetable') {
var timetableGroupInput = card.querySelector('select[name="region_timetable_group_id_' + region.id + '"]');
var timetableDisplayModeInput = card.querySelector('select[name="region_timetable_display_mode_' + region.id + '"]');
var timetableMaxItemsInput = card.querySelector('input[name="region_timetable_max_items_' + region.id + '"]');
var currentSchedule = getCurrentTimetableConfig(region);
content.value = hiddenInput ? hiddenInput.value : (content.value || currentSchedule.value || '');
content.timetable_group_id = timetableGroupInput ? timetableGroupInput.value : currentSchedule.timetable_group_id;
content.display_mode = timetableDisplayModeInput ? timetableDisplayModeInput.value : currentSchedule.display_mode;
content.max_items = timetableMaxItemsInput ? timetableMaxItemsInput.value : currentSchedule.max_items;
} else {
content.value = hiddenInput ? hiddenInput.value : (content.value || '');
}
@@ -622,7 +699,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 : region.region_type === 'schedule' ? scheduleGroups : apiSources);
return module.buildPreviewRenderContext(region, card, existingContent, region.region_type === 'rss' ? rssFeeds : region.region_type === 'timetable' ? timetableGroups : apiSources);
}
var textStyle = getCurrentTextStyle(region);
@@ -639,10 +716,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,
timetable_group_id: previewContent.timetable_group_id,
display_mode: previewContent.display_mode,
max_items: previewContent.max_items,
scheduleGroups: scheduleGroups,
timetableGroups: timetableGroups,
font_size: textStyle.font_size,
font_color: textStyle.font_color,
font_family: textStyle.font_family
+21 -83
View File
@@ -21,7 +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 timetableGroups = Array.isArray(slideEditorData.timetableGroups) ? slideEditorData.timetableGroups : [];
var fontStylesheetHref = String(slideEditorData.fontStylesheetHref || '').trim();
var existingTemplateId = slideEditorData.existingTemplateId !== undefined ? slideEditorData.existingTemplateId : null;
var existingContent = slideEditorData.existingContent || {};
@@ -37,7 +37,6 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
var slidePreviewEmpty = document.getElementById('slide-preview-empty');
var slidePreviewOverlay = document.getElementById('slide-preview-overlay');
var openPreviewPopupButton = document.getElementById('open-preview-popup');
var submitting = false;
var uploadMaxBytes = 100 * 1024 * 1024;
var uploadMaxLabel = '100 MB';
var uploadVideoMaxBytes = 1024 * 1024 * 1024;
@@ -120,7 +119,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
existingContent: existingContent,
rssFeeds: rssFeeds,
apiSources: apiSources,
scheduleGroups: scheduleGroups,
timetableGroups: timetableGroups,
defaultFontSize: DEFAULT_FONT_SIZE,
uploadMaxLabel: uploadMaxLabel,
uploadVideoMaxLabel: uploadVideoMaxLabel,
@@ -643,6 +642,12 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
return getRegionEditorCardHtml(region) || '<div class="muted">Region editor unavailable.</div>';
}).join('') : '<div class="muted">This template has no regions.</div>';
if (window.adminlte && typeof window.adminlte.CardWidget === 'function') {
templateFields.querySelectorAll('.card-widget[data-region-id]').forEach(function (card) {
window.adminlte.CardWidget.getOrCreateInstance(card);
});
}
templateFields.querySelectorAll('[data-region-media-type="video"]').forEach(function (card) {
var regionId = card.getAttribute('data-region-id');
var hidden = regionId ? card.querySelector('input[type="hidden"][name="existing_region_video_' + regionId + '"]') : null;
@@ -744,92 +749,25 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
requestPreviewRender();
}
slideForm.addEventListener('submit', async function (event) {
if (submitting) {
slideForm.addEventListener('submit', function (event) {
if (!window.webAsyncSaveForm || typeof window.webAsyncSaveForm.submit !== 'function') {
return;
}
event.preventDefault();
var slideFormUrl = new URL(slideForm.action, window.location.href);
var isCreateForm = slideFormUrl.pathname === '/slides';
var submitterValue = event.submitter && event.submitter.name === 'save_action'
? String(event.submitter.value || '').trim().toLowerCase()
: '';
submitting = true;
try {
if (hasPendingRegionImageUpload()) {
throw new Error('Wait for the image upload to finish before saving.');
}
await slideFormEditorController.syncEditors();
var formData = new FormData(slideForm);
if (event.submitter && event.submitter.name) {
formData.set(event.submitter.name, event.submitter.value || '');
}
var response = await fetch(slideForm.action, {
method: (slideForm.method || 'POST').toUpperCase(),
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'text/html, application/json, text/plain, */*'
},
body: formData,
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error(await response.text() || 'Unable to save slide.');
}
if (submitterValue === 'close' || submitterValue === 'new') {
var redirectUrl = submitterValue === 'close'
? String(slideForm.dataset.asyncSaveCloseUrl || response.url || slideForm.action)
: String(slideForm.dataset.asyncSaveNewUrl || response.url || slideForm.action);
window.location.replace(redirectUrl);
return;
}
if (isCreateForm) {
try {
if (response.url) {
if (regionMediaController) {
regionMediaController.clearPendingUploadCleanupPaths();
}
window.location.replace(response.url);
return;
}
} catch (_error) {
// Fall through and show a toast if the redirect URL cannot be resolved.
window.webAsyncSaveForm.submit(slideForm, event, {
beforeSubmit: async function () {
if (hasPendingRegionImageUpload()) {
throw new Error('Wait for the image upload to finish before saving.');
}
slideForm.dataset.dirty = 'false';
showToast('Saved slide.', 'success');
return;
}
var responseText = await response.text();
var savedMessage = '';
try {
var doc = new DOMParser().parseFromString(responseText || '', 'text/html');
var toastBody = doc.querySelector('.toast-body');
if (toastBody && toastBody.textContent) {
savedMessage = toastBody.textContent.trim();
await slideFormEditorController.syncEditors();
},
fallbackSuccessMessage: 'Saved slide.',
afterSuccess: function () {
if (regionMediaController) {
regionMediaController.clearPendingUploadCleanupPaths();
}
} catch (_error) {
savedMessage = '';
}
showToast(savedMessage || 'Saved slide.', 'success');
slideForm.dataset.dirty = 'false';
if (regionMediaController) {
regionMediaController.clearPendingUploadCleanupPaths();
}
} catch (error) {
if (typeof showToast === 'function') {
var variant = Number(error && error.status) >= 400 && Number(error && error.status) < 500 ? 'warning' : 'danger';
showToast(error && error.message ? error.message : 'Unable to save slide.', variant);
} else {
window.alert(error && error.message ? error.message : 'Unable to save slide.');
}
} finally {
submitting = false;
}
});
});
slideForm.addEventListener('input', function () {