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
+59 -16
View File
@@ -82,18 +82,42 @@ function registerActionHelpers(Handlebars) {
Handlebars.registerHelper('saveActionButtons', function (options) {
const hash = options && options.hash ? options.hash : {};
const formId = String(hash.formId || '').trim();
const saveLabel = String(hash.saveLabel || 'Save');
const saveButtonClass = String(hash.saveButtonClass || 'btn btn-success');
const saveActionName = String(hash.saveActionName || 'save_action');
const saveActionValue = String(hash.saveActionValue || 'save');
const closeLabel = String(hash.saveAndCloseLabel || 'Save and Close');
const closeValue = String(hash.saveAndCloseValue || 'close');
const newLabel = String(hash.saveAndNewLabel || 'Save and New');
const newValue = String(hash.saveAndNewValue || 'new');
const save = {
label: String(hash.saveLabel || 'Save'),
className: String(hash.saveButtonClass || 'btn btn-success'),
actionName: String(hash.saveActionName || 'save_action'),
actionValue: String(hash.saveActionValue || 'save'),
closeLabel: String(hash.saveAndCloseLabel || 'Save and Close'),
closeValue: String(hash.saveAndCloseValue || 'close'),
newLabel: String(hash.saveAndNewLabel || 'Save and New'),
newValue: String(hash.saveAndNewValue || 'new')
};
const showSaveAndClose = hash.showSaveAndClose === undefined ? true : String(hash.showSaveAndClose).toLowerCase() !== 'false';
const showSaveAndNew = hash.showSaveAndNew === undefined ? true : String(hash.showSaveAndNew).toLowerCase() !== 'false';
const ariaLabel = String(hash.ariaLabel || 'Save actions');
const showDelete = hash.showDelete === undefined ? true : String(hash.showDelete).toLowerCase() !== 'false';
const hasSecondaryActions = showSaveAndClose || showSaveAndNew;
const ariaLabel = String(hash.ariaLabel || 'Save actions');
const saveUrl = String(hash.saveUrl || '').trim();
const cancel = {
label: String(hash.cancelLabel || 'Cancel'),
className: String(hash.cancelClass || 'btn btn-warning'),
url: String(hash.cancelUrl || '').trim(),
confirmMessage: String(hash.cancelConfirmMessage || hash.cancelConfirm || 'You have unsaved changes. Leave this page?')
};
const deleteAction = {
label: String(hash.deleteLabel || 'Delete'),
className: String(hash.deleteClass || 'btn btn-danger'),
url: String(hash.deleteUrl || '').trim(),
confirmMessage: String(hash.deleteConfirmMessage || '').trim(),
disabled: hash.deleteDisabled === undefined ? false : String(hash.deleteDisabled).toLowerCase() !== 'false',
title: String(hash.deleteTitle || '')
};
const resolvedDeleteUrl = deleteAction.url || (!deleteAction.disabled && saveUrl ? saveUrl.replace(/\/+$/, '') + '/delete' : '');
if (!formId) {
return '';
@@ -102,16 +126,30 @@ function registerActionHelpers(Handlebars) {
const escape = Handlebars.escapeExpression;
const formAttr = ` form="${escape(formId)}"`;
return new Handlebars.SafeString([
const parts = [
'<div class="btn-group save-action-group" role="group">',
`<button type="submit" class="${escape(saveButtonClass)}"${formAttr} name="${escape(saveActionName)}" value="${escape(saveActionValue)}">${escape(saveLabel)}</button>`,
hasSecondaryActions ? `<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">${escape(ariaLabel)}</span></button>` : '',
`<button type="submit" class="${escape(save.className)}"${formAttr} name="${escape(save.actionName)}" value="${escape(save.actionValue)}">${escape(save.label)}</button>`,
hasSecondaryActions ? `<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split rounded-0" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">${escape(ariaLabel)}</span></button>` : '',
hasSecondaryActions ? '<div class="dropdown-menu dropdown-menu-end">' : '',
hasSecondaryActions && showSaveAndClose ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(closeValue)}">${escape(closeLabel)}</button>` : '',
hasSecondaryActions && showSaveAndNew ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(newValue)}">${escape(newLabel)}</button>` : '',
hasSecondaryActions && showSaveAndClose ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(save.actionName)}" value="${escape(save.closeValue)}">${escape(save.closeLabel)}</button>` : '',
hasSecondaryActions && showSaveAndNew ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(save.actionName)}" value="${escape(save.newValue)}">${escape(save.newLabel)}</button>` : '',
hasSecondaryActions ? '</div>' : '',
'</div>',
].join(''));
];
if (cancel.url) {
parts.push(`<a class="${escape(cancel.className)}" href="${escape(cancel.url)}" data-confirm-unsaved="${escape(cancel.confirmMessage)}">${escape(cancel.label)}</a>`);
}
if (showDelete && (deleteAction.disabled || resolvedDeleteUrl)) {
const deleteActionAttr = resolvedDeleteUrl ? ` data-delete-action-url="${escape(resolvedDeleteUrl)}"` : '';
const confirmAttr = deleteAction.confirmMessage ? ` data-confirm-message="${escape(deleteAction.confirmMessage)}"` : '';
const titleAttr = deleteAction.disabled && deleteAction.title ? ` title="${escape(deleteAction.title)}"` : '';
parts.push(`<button type="button" class="${escape(deleteAction.className)}"${deleteActionAttr}${confirmAttr}${deleteAction.disabled ? ' disabled' : ''}${titleAttr}>${escape(deleteAction.label)}</button>`);
}
parts.push('</div>');
return new Handlebars.SafeString(parts.join(''));
});
}
@@ -119,6 +157,11 @@ function registerActionHelpers(Handlebars) {
function registerPartials(Handlebars, viewsRoot) {
Handlebars.registerPartial('modal-shell', fs.readFileSync(path.join(viewsRoot, 'shared', 'modal-shell.hbs'), 'utf8'));
Handlebars.registerPartial('table-pagination', fs.readFileSync(path.join(viewsRoot, 'shared', 'table', 'table-pagination.hbs'), 'utf8'));
Handlebars.registerPartial('playlists/form', fs.readFileSync(path.join(viewsRoot, 'signage', 'playlists', 'form.hbs'), 'utf8'));
Handlebars.registerPartial('signage/playlists/slide-row', fs.readFileSync(path.join(viewsRoot, 'signage', 'playlists', 'slide-row.hbs'), 'utf8'));
Handlebars.registerPartial('signage/playlists/schedule-rule-card', fs.readFileSync(path.join(viewsRoot, 'signage', 'playlists', 'schedule-rule-card.hbs'), 'utf8'));
Handlebars.registerPartial('data-sources/api-sources/form', fs.readFileSync(path.join(viewsRoot, 'data-sources', 'api-sources', 'form.hbs'), 'utf8'));
Handlebars.registerPartial('data-sources/rss-feeds/form', fs.readFileSync(path.join(viewsRoot, 'data-sources', 'rss-feeds', 'form.hbs'), 'utf8'));
}
// One entry point keeps Handlebars bootstrap centralized.