Release v2.17.4: add API region sorting and indexed placeholders
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m17s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 1m10s

This commit is contained in:
lzstealth committed 2026-10-23 20:54:15 +01:00
1 parent bc8075379a
commit 46636939de
18 files changed
+709 -232

No files matched your search

+64 -28
View File
@@ -3716,47 +3716,81 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
.api-region-placeholder-section {
display: grid;
gap: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.5rem;
gap: 0.4rem;
padding-top: 0.125rem;
padding-bottom: 0.25rem;
margin-bottom: 0;
}
.api-region-placeholder-title {
color: var(--bs-primary);
font-size: 0.8rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--bs-secondary-color);
font-size: 0.78rem;
font-weight: 600;
}
.api-region-placeholder-title-help {
font-size: 0.9rem;
font-weight: 800;
font-size: 0.8rem;
font-weight: 600;
letter-spacing: normal;
text-transform: none;
}
.api-region-sample-accordion {
padding: 0.85rem 1rem 1rem;
margin-bottom: 0.75rem;
.api-region-placeholder-section .chip {
padding: 0.2rem 0.45rem;
border: 1px solid var(--bs-border-color);
border-radius: 0.85rem;
background: var(--bs-secondary-bg);
border-radius: 0.3rem;
background: transparent;
color: var(--bs-secondary-color);
font-family: var(--bs-font-monospace);
font-size: 0.75rem;
}
.api-region-sample-accordion > summary {
.api-region-placeholder-section .chip:hover {
border-color: var(--bs-secondary-color);
background: var(--bs-tertiary-bg);
}
.api-region-placeholder-section .chip:focus-visible {
outline: 2px solid var(--bs-primary);
outline-offset: 2px;
}
.api-region-sample-accordion {
margin-bottom: 0.75rem;
}
.api-region-sample-header {
display: flex;
align-items: center;
gap: 0.5rem;
}
.api-region-sample-toggle {
display: flex;
flex: 1 1 auto;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
box-sizing: border-box;
height: 2.5rem;
padding: 0.375rem 0.75rem;
cursor: pointer;
list-style: none;
color: var(--bs-primary);
font-size: 0.8rem;
font-size: 0.875rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
text-align: left;
}
.api-region-sample-accordion > summary::-webkit-details-marker {
display: none;
.api-region-sample-toggle:focus-visible {
outline: 2px solid var(--bs-primary);
outline-offset: 2px;
}
.api-region-sample-chevron {
transition: transform 120ms ease;
}
.api-region-sample-accordion.is-open .api-region-sample-chevron {
transform: rotate(180deg);
}
.api-region-sample-body {
@@ -3765,19 +3799,21 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
margin-top: 0.75rem;
}
.api-region-sample-body[hidden] {
display: none;
}
.api-region-sample-meta {
color: var(--bs-secondary-color);
font-size: 0.78rem;
}
.api-region-sample-preview {
max-height: 12rem;
overflow: auto;
margin: 0;
padding: 0.75rem;
border-radius: 0.65rem;
background: var(--bs-body-bg);
border: 1px solid var(--bs-border-color-translucent);
border: 0;
border-radius: 0.375rem;
background: var(--bs-secondary-bg);
white-space: pre-wrap;
word-break: break-word;
font-size: 0.78rem;
+59 -31
View File
@@ -47,15 +47,16 @@
return String(overridePath || '').trim();
}
function getItems(sourceId, sources, itemsPathOverride) {
function getItems(sourceId, sources, itemsPathOverride, sortBy, sortDirection) {
var source = getSourceById(sourceId, sources);
var responseJson = source && source.responseJson && typeof source.responseJson === 'object' ? source.responseJson : null;
var itemsPath = getSourceItemsPath(source, itemsPathOverride);
return placeholderUtils.resolveApiItems(responseJson, itemsPath);
var items = placeholderUtils.resolveApiItems(responseJson, itemsPath);
return typeof placeholderUtils.sortApiItems === 'function' ? placeholderUtils.sortApiItems(items, sortBy, sortDirection) : items;
}
function getItemCount(sourceId, sources, itemsPathOverride) {
return getItems(sourceId, sources, itemsPathOverride).length;
function getItemCount(sourceId, sources, itemsPathOverride, sortBy, sortDirection) {
return getItems(sourceId, sources, itemsPathOverride, sortBy, sortDirection).length;
}
function getCurrentConfig(region, existingContent, sources) {
@@ -66,15 +67,21 @@
source_id: current.source_id === undefined || current.source_id === null || current.source_id === '' ? '' : Number(current.source_id),
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
items_path: Object.prototype.hasOwnProperty.call(current, 'items_path') ? String(current.items_path === undefined || current.items_path === null ? '' : current.items_path) : undefined,
sort_by: String(current.sort_by || ''),
sort_direction: String(current.sort_direction || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc',
default_items_path: String(source && (source.items_path || source.itemsPath) || '').trim(),
value: current.value !== undefined ? current.value : ''
};
}
function substituteVariables(html, item, options) {
function substituteVariables(html, item, options, items) {
var source = String(html || '');
return source.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function (_match, expression) {
if (!item || typeof item !== 'object') {
var indexedMatch = String(expression || '').match(/^\s*(\d+)\.(.+)$/);
var resultIndex = indexedMatch ? Number(indexedMatch[1]) : 0;
var expressionItem = indexedMatch ? (Array.isArray(items) ? items[resultIndex] : null) : item;
var resolvedExpression = indexedMatch ? indexedMatch[2] : expression;
if (!expressionItem || typeof expressionItem !== 'object') {
return '';
}
@@ -82,16 +89,16 @@
return '';
}
var resolved = placeholderUtils.resolvePlaceholderExpression(item, expression);
if (typeof placeholderUtils.isProgressPlaceholderExpression === 'function' && placeholderUtils.isProgressPlaceholderExpression(expression)) {
return placeholderUtils.renderProgressPlaceholder(item, expression);
var resolved = placeholderUtils.resolvePlaceholderExpression(expressionItem, resolvedExpression);
if (typeof placeholderUtils.isProgressPlaceholderExpression === 'function' && placeholderUtils.isProgressPlaceholderExpression(resolvedExpression)) {
return placeholderUtils.renderProgressPlaceholder(expressionItem, resolvedExpression);
}
if (typeof placeholderUtils.isImagePlaceholderExpression === 'function' && placeholderUtils.isImagePlaceholderExpression(expression)) {
if (typeof placeholderUtils.isImagePlaceholderExpression === 'function' && placeholderUtils.isImagePlaceholderExpression(resolvedExpression)) {
var imageSource = placeholderUtils.formatPlaceholderValue(resolved);
if (!/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/])/i.test(imageSource)) {
return '';
}
var imageConfig = typeof placeholderUtils.getImagePlaceholderConfig === 'function' ? placeholderUtils.getImagePlaceholderConfig(expression) : {};
var imageConfig = typeof placeholderUtils.getImagePlaceholderConfig === 'function' ? placeholderUtils.getImagePlaceholderConfig(resolvedExpression) : {};
var imageStyle = options && options.preview && imageConfig.width && imageConfig.height
? 'display:block;width:100%;height:100%;object-fit:contain;'
: 'display:block;width:auto;height:auto;' + (imageConfig.width ? 'max-width:' + imageConfig.width + 'px;' : '') + (imageConfig.height ? 'max-height:' + imageConfig.height + 'px;' : '');
@@ -99,13 +106,14 @@
var hasPreviewBounds = options && options.preview && imageConfig.width && imageConfig.height;
if (hasPreviewBounds || imageConfig.verticalAlign) {
var wrapperStyle = 'display:inline-flex;align-items:center;justify-content:center;';
var wrapperClass = hasPreviewBounds ? ' class="api-image-placeholder-bounds"' : '';
if (hasPreviewBounds) {
wrapperStyle += 'box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;border:1px dashed rgba(120,120,120,.75);';
}
if (imageConfig.verticalAlign) {
wrapperStyle += 'vertical-align:' + imageConfig.verticalAlign + ';';
}
return '<span style="' + wrapperStyle + '">' + image + '</span>';
return '<span' + wrapperClass + ' style="' + wrapperStyle + '">' + image + '</span>';
}
return image;
}
@@ -113,8 +121,8 @@
});
}
function getItem(sourceId, itemNumber, sources, itemsPathOverride) {
var items = getItems(sourceId, sources, itemsPathOverride);
function getItem(sourceId, itemNumber, sources, itemsPathOverride, sortBy, sortDirection) {
var items = getItems(sourceId, sources, itemsPathOverride, sortBy, sortDirection);
var parsedItemNumber = Math.max(1, Number(itemNumber || 1));
var index = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
return items[index] || null;
@@ -149,8 +157,8 @@
'<h2 class="offcanvas-title fs-5" id="' + offcanvasId + '-label">API placeholder transforms</h2>' +
'<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>' +
'</div>' +
'<div class="offcanvas-body">' +
'<p class="small text-body-secondary">Placeholders read values from the selected API item. Nested fields use dots.</p>' +
'<div class="offcanvas-body">' +
'<p class="small text-body-secondary">Use placeholders such as <code>{{name}}</code> for the selected result, or <code>{{0.name}}</code> and <code>{{1.name}}</code> to read sorted results by index. Indices start at 0. Nested fields use dots.</p>' +
(window.placeholderInfo ? window.placeholderInfo.renderTextTransforms() : '') +
(window.placeholderInfo ? window.placeholderInfo.renderMathTransforms() : '') +
(window.placeholderInfo ? window.placeholderInfo.renderDateFormatTokens() : '') +
@@ -189,14 +197,15 @@
var sourceId = regionContent && regionContent.source_id !== undefined ? regionContent.source_id : config.source_id;
var itemNumber = regionContent && regionContent.item_number !== undefined ? regionContent.item_number : config.item_number;
var itemsPath = regionContent && regionContent.items_path !== undefined ? regionContent.items_path : (config.items_path !== undefined ? config.items_path : config.default_items_path);
var itemCount = getItemCount(sourceId, sources, itemsPath);
var itemNumberMax = Math.max(1, Number(itemCount || 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 = getItem(sourceId, itemNumber, sources, itemsPath);
var items = getItems(sourceId, sources, itemsPath, regionContent.sort_by, regionContent.sort_direction);
var parsedItemNumber = Math.max(1, Number(itemNumber || 1));
var itemIndex = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
var item = items[itemIndex] || null;
var hasSource = String(sourceId === undefined || sourceId === null ? '' : sourceId).trim() !== '';
var body = hasSource ? (item ? substituteVariables(content, item, { preview: true }) : '') : content;
var body = hasSource ? (item || items.length ? substituteVariables(content, item, { preview: true }, items) : '') : content;
body = String(body || '')
.replace(/<pre[^>]*class="[^"]*api-region-sample-preview[^"]*"[^>]*>[\s\S]*?<\/pre>/gi, '')
@@ -239,17 +248,13 @@
'<textarea class="editor-source" rows="10">' + escapeHtml(current.value !== undefined ? current.value : '') + '</textarea>' +
'</div>' +
'<div class="row g-3 align-items-end">' +
'<div class="col-12 col-lg-5">' +
'<div class="col-12 col-lg-4">' +
'<label class="form-label" for="region_api_source_id_' + region.id + '">API source</label>' +
'<select id="region_api_source_id_' + region.id + '" name="region_api_source_id_' + region.id + '" class="form-select">' +
'<option value="">Select a source</option>' +
sourceOptions +
'</select>' +
'</div>' +
'<div class="col-6 col-lg-2">' +
'<label class="form-label" for="region_api_item_number_' + region.id + '">Item number</label>' +
'<input id="region_api_item_number_' + region.id + '" type="number" min="1" step="1" name="region_api_item_number_' + region.id + '" class="form-control" value="' + escapeHtml(config.item_number) + '" />' +
'</div>' +
'<div class="col-12 col-lg-5">' +
'<label class="form-label" for="region_api_items_path_' + region.id + '">Items path override</label>' +
'<div class="input-group flex-nowrap">' +
@@ -257,17 +262,36 @@
'<button type="button" class="btn btn-outline-secondary text-nowrap" data-api-items-path-reset data-api-default-items-path="' + escapeHtml(config.default_items_path || '') + '">Reset</button>' +
'</div>' +
'</div>' +
'<div class="col-12 col-lg-3 d-flex align-items-end">' +
'<button type="button" class="btn btn-outline-secondary w-100" data-api-advanced-toggle aria-expanded="false" aria-controls="api-region-advanced-' + region.id + '"><i class="bi bi-sliders me-2" aria-hidden="true"></i>Advanced options</button>' +
'</div>' +
'</div>' +
'<div id="api-region-advanced-' + region.id + '" class="row g-3 align-items-end api-region-advanced-options" data-api-advanced-options hidden>' +
'<div class="col-12 col-md-4">' +
'<label class="form-label" for="region_api_sort_mode_' + region.id + '">Order results</label>' +
'<select id="region_api_sort_mode_' + region.id + '" name="region_api_sort_mode_' + region.id + '" class="form-select"><option value="original"' + (config.sort_by ? '' : ' selected') + '>API order</option><option value="field"' + (config.sort_by ? ' selected' : '') + '>Sort by field</option></select>' +
'</div>' +
'<div class="col-12 col-md-5" data-api-sort-field-wrap' + (config.sort_by ? '' : ' hidden') + '>' +
'<label class="form-label" for="region_api_sort_by_' + region.id + '">Sort field</label>' +
'<input id="region_api_sort_by_' + region.id + '" type="text" name="region_api_sort_by_' + region.id + '" class="form-control" value="' + escapeHtml(config.sort_by || '') + '" placeholder="Enter a field path" />' +
'</div>' +
'<div class="col-12 col-md-3" data-api-sort-direction-wrap' + (config.sort_by ? '' : ' hidden') + '>' +
'<label class="form-label" for="region_api_sort_direction_' + region.id + '">Direction</label>' +
'<select id="region_api_sort_direction_' + region.id + '" name="region_api_sort_direction_' + region.id + '" class="form-select"><option value="asc"' + (config.sort_direction === 'asc' ? ' selected' : '') + '>Ascending</option><option value="desc"' + (config.sort_direction !== 'asc' ? ' selected' : '') + '>Descending</option></select>' +
'</div>' +
'</div>' +
'<input type="hidden" name="region_font_size_' + region.id + '" value="' + escapeHtml(context.fontSize || '') + '" />' +
'<input type="hidden" name="region_text_' + region.id + '" value="' + escapeHtml(current.value !== undefined ? current.value : '') + '" />' +
'<div class="api-region-placeholder-section">' +
'<div class="api-region-placeholder-title api-region-placeholder-title-help d-flex align-items-center gap-2">Available placeholders ' + renderTransformInfo(region.id) + '</div>' +
'<div class="api-region-placeholder-title api-region-placeholder-title-help d-flex align-items-center gap-2">Available fields ' + renderTransformInfo(region.id) + '</div>' +
'<div class="d-flex flex-wrap gap-2" data-placeholder-chips>' + ((placeholderFields.length && window.placeholderChips && typeof window.placeholderChips.renderChips === 'function') ? window.placeholderChips.renderChips(placeholderFields) : placeholderChips || '<span class="muted slide-image-file">No JSON fields available.</span>') + '</div>' +
'</div>' +
'<details class="api-region-sample-accordion" data-api-sample-data-accordion>' +
'<summary>Data</summary>' +
'<div class="api-region-sample-body" data-api-sample-data-panel>' + sampleDataPreview + '</div>' +
'</details>' +
'<div class="api-region-sample-accordion" data-api-sample-data-accordion>' +
'<div class="api-region-sample-header">' +
'<button type="button" class="btn btn-outline-primary api-region-sample-toggle" data-api-sample-toggle aria-expanded="false" aria-controls="api-region-sample-panel-' + region.id + '"><span><i class="bi bi-eye me-2" aria-hidden="true"></i>View results</span><i class="bi bi-chevron-down api-region-sample-chevron" aria-hidden="true"></i></button>' +
'</div>' +
'<div id="api-region-sample-panel-' + region.id + '" class="api-region-sample-body" data-api-sample-data-panel hidden>' + sampleDataPreview + '</div>' +
'</div>' +
'</div>' +
'</div>';
}
@@ -314,7 +338,9 @@
}())),
source_id: card.querySelector('select[name="region_api_source_id_' + region.id + '"]') ? card.querySelector('select[name="region_api_source_id_' + region.id + '"]').value : current.source_id,
item_number: (card.querySelector('input[name="region_api_item_number_' + region.id + '"]') || {}).value || current.item_number,
items_path: card.querySelector('input[name="region_api_items_path_' + region.id + '"]') ? (card.querySelector('input[name="region_api_items_path_' + region.id + '"]') || {}).value : current.items_path
items_path: card.querySelector('input[name="region_api_items_path_' + region.id + '"]') ? (card.querySelector('input[name="region_api_items_path_' + region.id + '"]') || {}).value : current.items_path,
sort_by: card.querySelector('select[name="region_api_sort_mode_' + region.id + '"]') && card.querySelector('select[name="region_api_sort_mode_' + region.id + '"]').value === 'field' ? (card.querySelector('input[name="region_api_sort_by_' + region.id + '"]') || {}).value || '' : '',
sort_direction: (card.querySelector('select[name="region_api_sort_direction_' + region.id + '"]') || {}).value || current.sort_direction
} : current;
return {
@@ -322,6 +348,8 @@
source_id: content.source_id,
item_number: content.item_number,
items_path: content.items_path,
sort_by: content.sort_by,
sort_direction: content.sort_direction,
existingContent: existingContent || {},
sources: Array.isArray(sources) ? sources : []
};
@@ -67,6 +67,44 @@
return value && typeof value === 'object' ? [value] : [];
}
function sortApiItems(items, sortBy, direction) {
var path = String(sortBy || '').trim().replace(/\[([0-9]+)\]/g, '.$1');
if (!Array.isArray(items)) {
return [];
}
if (!path) {
return items.slice();
}
var descending = String(direction || '').toLowerCase() === 'desc';
function valueFor(item) {
return resolvePath(item, path);
}
function isMissing(value) {
return value === undefined || value === null || value === '';
}
function compare(left, right) {
if (isMissing(left)) return isMissing(right) ? 0 : 1;
if (isMissing(right)) return -1;
var leftNumber = typeof left === 'number' ? left : (typeof left === 'string' && left.trim() !== '' ? Number(left) : NaN);
var rightNumber = typeof right === 'number' ? right : (typeof right === 'string' && right.trim() !== '' ? Number(right) : NaN);
if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
return leftNumber < rightNumber ? -1 : leftNumber > rightNumber ? 1 : 0;
}
return String(left).localeCompare(String(right), undefined, { numeric: true, sensitivity: 'base' });
}
return items.map(function (item, index) {
return { item: item, index: index, value: valueFor(item) };
}).sort(function (left, right) {
var result = compare(left.value, right.value);
if (result && descending && !isMissing(left.value) && !isMissing(right.value)) return -result;
return result || left.index - right.index;
}).map(function (entry) {
return entry.item;
});
}
function parsePlaceholderExpression(expression) {
var raw = String(expression || '').trim();
var segments = raw ? splitExpressionSegments(raw) : [];
@@ -604,6 +642,7 @@
return {
resolvePath: resolvePath,
resolveApiItems: resolveApiItems,
sortApiItems: sortApiItems,
parsePlaceholderExpression: parsePlaceholderExpression,
resolvePlaceholderExpression: resolvePlaceholderExpression,
isImagePlaceholderExpression: isImagePlaceholderExpression,
+64 -56
View File
@@ -178,18 +178,39 @@ export function createSlideFormRegionHelpers(options) {
return text.slice(0, limit).replace(/\s+$/, '') + '\n...';
}
function buildApiSampleDataMarkup(sourceId, itemNumber, itemsPathOverride) {
function buildApiSampleDataMarkup(sourceId, itemsPathOverride, sortBy, sortDirection) {
var source = getApiSourceById(sourceId);
if (!source) {
return '<div class="api-region-sample-meta">Select an API source to preview sample data.</div>';
}
var item = getApiSourceItems(sourceId, itemsPathOverride)[Math.max(0, Math.max(1, Number(itemNumber || 1)) - 1)] || null;
if (!item) {
var items = getApiSourceItems(sourceId, itemsPathOverride, sortBy, sortDirection);
if (!items.length) {
return '<div class="api-region-sample-meta">No sample item is available for the current selection.</div>';
}
return '<pre class="api-region-sample-preview" data-api-sample-data-output>' + escapeHtml(truncateJsonPreview(item, 1100)) + '</pre>';
var firstItemJson = '';
try {
firstItemJson = JSON.stringify(items[0], null, 2);
} catch (_error) {
firstItemJson = String(items[0] === undefined || items[0] === null ? '' : items[0]);
}
var label = apiItemsPathIsArray(source, itemsPathOverride) ? 'Example result (first item)' : 'Example result';
var jsonSource = JSON.stringify(firstItemJson).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026');
return '' +
'<div class="api-region-sample-example">' +
'<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-1">' +
'<div class="api-region-sample-meta">' + label + '</div>' +
'<div class="btn-group btn-group-sm" role="group" aria-label="Example JSON controls">' +
'<button type="button" class="btn btn-outline-secondary" data-json-toggle-collapse-all><i class="bi bi-arrows-collapse me-1" aria-hidden="true"></i>Collapse all</button>' +
'<button type="button" class="btn btn-outline-secondary" data-json-toggle-expand-all><i class="bi bi-arrows-expand me-1" aria-hidden="true"></i>Expand all</button>' +
'</div>' +
'</div>' +
'<div data-json-toggle-panel>' +
'<script type="application/json" data-json-toggle-source>' + jsonSource + '</script>' +
'<div class="api-region-sample-preview font-monospace" data-json-toggle-output data-api-sample-data-output>' + escapeHtml(firstItemJson) + '</div>' +
'</div>' +
'</div>';
}
function updatePlaceholderChipList(card, fieldList, emptyLabel) {
@@ -356,25 +377,31 @@ export function createSlideFormRegionHelpers(options) {
return String(overridePath || '').trim();
}
function getApiSourceItems(sourceId, itemsPathOverride) {
function getApiSourceItems(sourceId, itemsPathOverride, sortBy, sortDirection) {
var source = getApiSourceById(sourceId);
var module = getRegionTypeModule('api');
if (module && typeof module.getItems === 'function') {
return module.getItems(sourceId, apiSources, itemsPathOverride);
return module.getItems(sourceId, apiSources, itemsPathOverride, sortBy, sortDirection);
}
var responseJson = source && source.responseJson && typeof source.responseJson === 'object' ? source.responseJson : null;
var itemsPath = getApiSourceItemsPath(source, itemsPathOverride);
return window.placeholderUtils.resolveApiItems(responseJson, itemsPath);
var items = window.placeholderUtils.resolveApiItems(responseJson, itemsPath);
return typeof window.placeholderUtils.sortApiItems === 'function' ? window.placeholderUtils.sortApiItems(items, sortBy, sortDirection) : items;
}
function getApiItemCount(sourceId, itemsPathOverride) {
var module = getRegionTypeModule('api');
if (module && typeof module.getItemCount === 'function') {
return module.getItemCount(sourceId, apiSources, itemsPathOverride);
function apiItemsPathIsArray(source, itemsPathOverride) {
var response = source && source.responseJson;
var itemsPath = getApiSourceItemsPath(source, itemsPathOverride);
if (itemsPath) {
return Array.isArray(window.placeholderUtils.resolvePath(response, itemsPath));
}
return getApiSourceItems(sourceId, itemsPathOverride).length;
if (Array.isArray(response)) {
return true;
}
return Boolean(response && ['items', 'results', 'data'].some(function (key) {
return Array.isArray(response[key]);
}));
}
function getApiFieldList(sourceId, itemsPathOverride) {
@@ -399,7 +426,18 @@ export function createSlideFormRegionHelpers(options) {
container.innerHTML = buildLimitedPlaceholderChipMarkup(getApiFieldList(sourceId, itemsPathOverride), 'No JSON fields available.', false);
}
function updateApiSampleDataPanel(regionId, sourceId, itemNumber, itemsPathOverride) {
function updateApiItemsPathReset(regionId, sourceId) {
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
var button = card ? card.querySelector('[data-api-items-path-reset]') : null;
if (!button) {
return;
}
var source = getApiSourceById(sourceId);
button.dataset.apiDefaultItemsPath = String(source && (source.items_path || source.itemsPath) || '').trim();
}
function updateApiSampleDataPanel(regionId, sourceId, itemsPathOverride, sortBy, sortDirection) {
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
if (!card) {
return;
@@ -410,44 +448,10 @@ export function createSlideFormRegionHelpers(options) {
return;
}
container.innerHTML = buildApiSampleDataMarkup(sourceId, itemNumber, itemsPathOverride);
}
function updateApiItemNumberLimit(regionId, sourceId, itemsPathOverride) {
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
if (!card) {
return;
container.innerHTML = buildApiSampleDataMarkup(sourceId, itemsPathOverride, sortBy, sortDirection);
if (typeof window.initJsonTogglePanels === 'function') {
window.initJsonTogglePanels(container);
}
var input = card.querySelector('input[name="region_api_item_number_' + regionId + '"]');
if (!input) {
return;
}
var itemCount = Math.max(1, Number(getApiItemCount(sourceId, itemsPathOverride) || 1));
input.setAttribute('max', String(itemCount));
if (Number(input.value || 0) > itemCount) {
input.value = String(itemCount);
}
}
function clampApiItemNumber(regionId, sourceId, itemNumber, itemsPathOverride) {
var card = templateFields ? templateFields.querySelector('[data-region-id="' + regionId + '"]') : null;
if (!card) {
return '';
}
var input = card.querySelector('input[name="region_api_item_number_' + regionId + '"]');
if (!input) {
return '';
}
var itemCount = Math.max(1, Number(getApiItemCount(sourceId, itemsPathOverride) || 1));
var parsedValue = Math.max(1, Number(itemNumber || 1));
var clampedValue = Math.min(itemCount, Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : 1);
input.setAttribute('max', String(itemCount));
input.value = String(clampedValue);
return String(clampedValue);
}
function getCurrentApiConfig(region) {
@@ -546,7 +550,7 @@ export function createSlideFormRegionHelpers(options) {
: region.region_type === 'rss'
? getRssFieldList(getCurrentRssConfig(region).feed_id)
: [],
sampleDataPreview: buildApiSampleDataMarkup(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).item_number, getCurrentApiConfig(region).items_path),
sampleDataPreview: buildApiSampleDataMarkup(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).items_path, getCurrentApiConfig(region).sort_by, getCurrentApiConfig(region).sort_direction),
timetableGroups: timetableGroups,
weatherLocations: weatherLocations,
mediaAssets: mediaAssets
@@ -589,7 +593,7 @@ export function createSlideFormRegionHelpers(options) {
: region.region_type === 'rss'
? getRssFieldList(rssConfig.feed_id)
: [],
sampleDataPreview: buildApiSampleDataMarkup(apiConfig.source_id, apiConfig.item_number, apiItemsPath),
sampleDataPreview: buildApiSampleDataMarkup(apiConfig.source_id, apiItemsPath, apiConfig.sort_by, apiConfig.sort_direction),
timetableGroups: timetableGroups,
weatherLocations: weatherLocations,
mediaAssets: mediaAssets
@@ -754,7 +758,6 @@ export function createSlideFormRegionHelpers(options) {
var rssFeedInput = card.querySelector('select[name="region_rss_feed_id_' + region.id + '"]');
var rssItemInput = card.querySelector('input[name="region_rss_item_number_' + region.id + '"]');
var apiSourceInput = card.querySelector('select[name="region_api_source_id_' + region.id + '"]');
var apiItemInput = card.querySelector('input[name="region_api_item_number_' + region.id + '"]');
var apiItemsPathInput = card.querySelector('input[name="region_api_items_path_' + region.id + '"]');
if (region.region_type === 'image') {
@@ -779,8 +782,12 @@ export function createSlideFormRegionHelpers(options) {
} else if (region.region_type === 'api') {
content.value = hiddenInput ? hiddenInput.value : (content.value || '');
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;
var apiSortModeInput = card.querySelector('select[name="region_api_sort_mode_' + region.id + '"]');
var apiSortByInput = card.querySelector('input[name="region_api_sort_by_' + region.id + '"]');
var apiSortDirectionInput = card.querySelector('select[name="region_api_sort_direction_' + region.id + '"]');
content.sort_by = apiSortModeInput && apiSortModeInput.value === 'field' && apiSortByInput ? apiSortByInput.value : '';
content.sort_direction = apiSortDirectionInput ? apiSortDirectionInput.value : content.sort_direction;
} 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 + '"]');
@@ -835,9 +842,10 @@ export function createSlideFormRegionHelpers(options) {
updateRssPlaceholderChips: updateRssPlaceholderChips,
getCurrentRssConfig: getCurrentRssConfig,
getApiFieldList: getApiFieldList,
buildApiSampleDataMarkup: buildApiSampleDataMarkup,
updateApiPlaceholderChips: updateApiPlaceholderChips,
updateApiItemsPathReset: updateApiItemsPathReset,
updateApiSampleDataPanel: updateApiSampleDataPanel,
updateApiItemNumberLimit: updateApiItemNumberLimit,
updateRssItemNumberLimit: updateRssItemNumberLimit,
getCurrentApiConfig: getCurrentApiConfig,
reduceAspectRatio: reduceAspectRatio,
+57 -18
View File
@@ -748,8 +748,8 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
var getCurrentRssConfig = slideRegionHelpers.getCurrentRssConfig;
var getApiFieldList = slideRegionHelpers.getApiFieldList;
var updateApiPlaceholderChips = slideRegionHelpers.updateApiPlaceholderChips;
var updateApiItemsPathReset = slideRegionHelpers.updateApiItemsPathReset;
var updateApiSampleDataPanel = slideRegionHelpers.updateApiSampleDataPanel;
var updateApiItemNumberLimit = slideRegionHelpers.updateApiItemNumberLimit;
var getCurrentApiConfig = slideRegionHelpers.getCurrentApiConfig;
var updateRssItemNumberLimit = slideRegionHelpers.updateRssItemNumberLimit;
@@ -768,6 +768,9 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
templateFields.innerHTML = template.regions.length ? template.regions.map(function (region) {
return getRegionEditorCardHtml(region) || '<div class="muted">Region editor unavailable.</div>';
}).join('') : '<div class="muted">This template has no regions.</div>';
if (typeof window.initJsonTogglePanels === 'function') {
window.initJsonTogglePanels(templateFields);
}
if (window.adminlte && typeof window.adminlte.CardWidget === 'function') {
templateFields.querySelectorAll('.card-widget[data-region-id]').forEach(function (card) {
@@ -787,14 +790,6 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
window.requestAnimationFrame(function () {
bindRegionCards();
});
templateFields.querySelectorAll('input[name^="region_api_item_number_"]').forEach(function (input) {
var regionId = input.name.replace(/^region_api_item_number_/, '');
var sourceInput = templateFields.querySelector('select[name="region_api_source_id_' + regionId + '"]');
var pathInput = templateFields.querySelector('input[name="region_api_items_path_' + regionId + '"]');
if (sourceInput) {
updateApiItemNumberLimit(regionId, sourceInput.value, pathInput ? pathInput.value : '');
}
});
templateFields.querySelectorAll('input[name^="region_rss_item_number_"]').forEach(function (input) {
var regionId = input.name.replace(/^region_rss_item_number_/, '');
var feedInput = templateFields.querySelector('select[name="region_rss_feed_id_' + regionId + '"]');
@@ -852,39 +847,59 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
}
});
}
templateFields.querySelectorAll('select[name^="region_rss_feed_id_"], input[name^="region_rss_item_number_"], select[name^="region_api_source_id_"], input[name^="region_api_item_number_"], input[name^="region_api_items_path_"]').forEach(function (input) {
templateFields.querySelectorAll('select[name^="region_rss_feed_id_"], input[name^="region_rss_item_number_"], select[name^="region_api_source_id_"], input[name^="region_api_items_path_"], select[name^="region_api_sort_mode_"], input[name^="region_api_sort_by_"], select[name^="region_api_sort_direction_"]').forEach(function (input) {
input.addEventListener('input', function () {
var regionId = input.name.replace(/^region_(?:rss_feed_id|rss_item_number|api_source_id|api_item_number|api_items_path)_/, '');
var regionId = input.name.replace(/^region_(?:rss_feed_id|rss_item_number|api_source_id|api_items_path|api_sort_mode|api_sort_by|api_sort_direction)_/, '');
if (input.name.indexOf('region_rss_') === 0) {
var rssFeed = input.name.indexOf('region_rss_feed_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_rss_feed_id_' + regionId + '"]') || {}).value;
updateRssPlaceholderChips(regionId, rssFeed);
updateRssItemNumberLimit(regionId, rssFeed);
}
if (input.name.indexOf('region_api_') === 0) {
var apiSortMode = templateFields.querySelector('select[name="region_api_sort_mode_' + regionId + '"]');
var apiSortByInput = templateFields.querySelector('input[name="region_api_sort_by_' + regionId + '"]');
var apiSortFieldWrap = apiSortByInput && apiSortByInput.closest('[data-api-sort-field-wrap]');
var apiSortDirection = templateFields.querySelector('select[name="region_api_sort_direction_' + regionId + '"]');
var apiSortDirectionWrap = apiSortDirection && apiSortDirection.closest('[data-api-sort-direction-wrap]');
var apiSortEnabled = apiSortMode && apiSortMode.value === 'field';
if (apiSortFieldWrap) apiSortFieldWrap.hidden = !apiSortEnabled;
if (apiSortDirectionWrap) apiSortDirectionWrap.hidden = !apiSortEnabled;
if (apiSortMode && !apiSortEnabled && apiSortByInput) apiSortByInput.value = '';
var apiSource = input.name.indexOf('region_api_source_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_api_source_id_' + regionId + '"]') || {}).value;
var apiItemNumber = input.name.indexOf('region_api_item_number_') === 0 ? input.value : (templateFields.querySelector('input[name="region_api_item_number_' + regionId + '"]') || {}).value;
var apiItemsPath = input.name.indexOf('region_api_items_path_') === 0 ? input.value : (templateFields.querySelector('input[name="region_api_items_path_' + regionId + '"]') || {}).value;
updateApiItemNumberLimit(regionId, apiSource, apiItemsPath);
var apiSortBy = apiSortEnabled && apiSortByInput ? apiSortByInput.value : '';
var apiSortDirectionValue = apiSortDirection ? apiSortDirection.value : 'asc';
updateApiItemsPathReset(regionId, apiSource);
updateApiPlaceholderChips(regionId, apiSource, apiItemsPath);
updateApiSampleDataPanel(regionId, apiSource, apiItemNumber, apiItemsPath);
updateApiSampleDataPanel(regionId, apiSource, apiItemsPath, apiSortBy, apiSortDirectionValue);
}
templateSelectorLock.markEdited();
requestPreviewRender();
});
input.addEventListener('change', function () {
var regionId = input.name.replace(/^region_(?:rss_feed_id|rss_item_number|api_source_id|api_item_number|api_items_path)_/, '');
var regionId = input.name.replace(/^region_(?:rss_feed_id|rss_item_number|api_source_id|api_items_path|api_sort_mode|api_sort_by|api_sort_direction)_/, '');
if (input.name.indexOf('region_rss_') === 0) {
var rssFeed = input.name.indexOf('region_rss_feed_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_rss_feed_id_' + regionId + '"]') || {}).value;
updateRssPlaceholderChips(regionId, rssFeed);
updateRssItemNumberLimit(regionId, rssFeed);
}
if (input.name.indexOf('region_api_') === 0) {
var apiSortMode = templateFields.querySelector('select[name="region_api_sort_mode_' + regionId + '"]');
var apiSortByInput = templateFields.querySelector('input[name="region_api_sort_by_' + regionId + '"]');
var apiSortFieldWrap = apiSortByInput && apiSortByInput.closest('[data-api-sort-field-wrap]');
var apiSortDirection = templateFields.querySelector('select[name="region_api_sort_direction_' + regionId + '"]');
var apiSortDirectionWrap = apiSortDirection && apiSortDirection.closest('[data-api-sort-direction-wrap]');
var apiSortEnabled = apiSortMode && apiSortMode.value === 'field';
if (apiSortFieldWrap) apiSortFieldWrap.hidden = !apiSortEnabled;
if (apiSortDirectionWrap) apiSortDirectionWrap.hidden = !apiSortEnabled;
if (apiSortMode && !apiSortEnabled && apiSortByInput) apiSortByInput.value = '';
var apiSource = input.name.indexOf('region_api_source_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_api_source_id_' + regionId + '"]') || {}).value;
var apiItemNumber = input.name.indexOf('region_api_item_number_') === 0 ? input.value : (templateFields.querySelector('input[name="region_api_item_number_' + regionId + '"]') || {}).value;
var apiItemsPath = input.name.indexOf('region_api_items_path_') === 0 ? input.value : (templateFields.querySelector('input[name="region_api_items_path_' + regionId + '"]') || {}).value;
updateApiItemNumberLimit(regionId, apiSource, apiItemsPath);
var apiSortBy = apiSortEnabled && apiSortByInput ? apiSortByInput.value : '';
var apiSortDirectionValue = apiSortDirection ? apiSortDirection.value : 'asc';
updateApiItemsPathReset(regionId, apiSource);
updateApiPlaceholderChips(regionId, apiSource, apiItemsPath);
updateApiSampleDataPanel(regionId, apiSource, apiItemNumber, apiItemsPath);
updateApiSampleDataPanel(regionId, apiSource, apiItemsPath, apiSortBy, apiSortDirectionValue);
}
templateSelectorLock.markEdited();
requestPreviewRender();
@@ -989,6 +1004,30 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
requestPreviewRender();
});
templateFields.addEventListener('click', function (event) {
var advancedToggle = event.target && typeof event.target.closest === 'function' ? event.target.closest('[data-api-advanced-toggle]') : null;
if (advancedToggle) {
var advancedPanel = templateFields.querySelector('#' + advancedToggle.getAttribute('aria-controls'));
if (advancedPanel) {
var isExpanded = advancedToggle.getAttribute('aria-expanded') === 'true';
advancedToggle.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
advancedPanel.hidden = isExpanded;
advancedToggle.classList.toggle('btn-outline-secondary', isExpanded);
advancedToggle.classList.toggle('btn-secondary', !isExpanded);
}
return;
}
var sampleToggle = event.target && typeof event.target.closest === 'function' ? event.target.closest('[data-api-sample-toggle]') : null;
if (sampleToggle) {
var sampleAccordion = sampleToggle.closest('[data-api-sample-data-accordion]');
var samplePanel = sampleAccordion && sampleAccordion.querySelector('[data-api-sample-data-panel]');
if (sampleAccordion && samplePanel) {
var isExpanded = sampleToggle.getAttribute('aria-expanded') === 'true';
sampleToggle.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
sampleAccordion.classList.toggle('is-open', !isExpanded);
samplePanel.hidden = isExpanded;
}
return;
}
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) {