Release 1.5.3

This commit is contained in:
2026-07-25 15:11:41 +01:00
parent 9ee938fd2f
commit 0e89892c94
89 changed files with 7660 additions and 542 deletions
+5
View File
@@ -434,6 +434,11 @@
window.location.replace(response.url);
return;
}
if (form.hasAttribute('data-async-save-reload-on-success')) {
clearFormDirty(form);
window.location.replace(response.url || window.location.href);
return;
}
clearFormDirty(form);
return response.text().then(function (text) {
var savedMessage = '';
+351
View File
@@ -234,6 +234,12 @@
if (typeof window.initLocalDateTimes === 'function') {
window.initLocalDateTimes(targetElement);
}
if (typeof window.initTableSearches === 'function') {
window.initTableSearches(targetElement);
}
if (typeof window.initTablePaginations === 'function') {
window.initTablePaginations(targetElement);
}
}
function replaceRefreshTargetFromPage(refreshTargetSelector, sequenceId) {
@@ -434,6 +440,11 @@
window.location.replace(response.url);
return;
}
if (form.hasAttribute('data-async-save-reload-on-success')) {
clearFormDirty(form);
window.location.replace(response.url || window.location.href);
return;
}
clearFormDirty(form);
return response.text().then(function (text) {
var savedMessage = '';
@@ -577,6 +588,332 @@
});
}
function initTablePaginations(root) {
var scope = root && root.querySelectorAll ? root : document;
Array.prototype.forEach.call(scope.querySelectorAll('[data-table-pagination="auto"]'), function (container) {
if (container.getAttribute('data-table-pagination-bound') === 'true') {
return;
}
var table = container.querySelector('[data-table-searchable]');
var cardHeader = container.querySelector('.card-header');
var cardBody = container.querySelector('.card-body.table-responsive') || container.querySelector('.card-body');
var footer = container.querySelector('[data-table-pagination-controls]');
var footerSummary = null;
var footerList = null;
var syncFrame = 0;
var state = {
currentPage: 1,
pageSize: 1,
totalPages: 1
};
if (!table || !cardBody || !table.tBodies.length) {
return;
}
container.setAttribute('data-table-pagination-bound', 'true');
function getRows() {
return Array.prototype.slice.call(table.querySelectorAll('tbody tr[data-table-search-row]'));
}
function isSearchMatched(row) {
return String(row.getAttribute('data-table-search-match') || 'true') !== 'false';
}
function ensureFooter() {
if (footer) {
footerSummary = footer.querySelector('[data-table-pagination-summary]');
footerList = footer.querySelector('[data-table-pagination-list]');
return footer;
}
footer = document.createElement('div');
footer.className = 'card-footer d-none';
footer.setAttribute('data-table-pagination-controls', 'true');
footer.innerHTML = '' +
'<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">' +
'<div class="text-muted small" data-table-pagination-summary></div>' +
'<nav aria-label="Table pages">' +
'<ul class="pagination pagination-sm mb-0" data-table-pagination-list></ul>' +
'</nav>' +
'</div>';
cardBody.parentNode.insertBefore(footer, cardBody.nextSibling);
footerSummary = footer.querySelector('[data-table-pagination-summary]');
footerList = footer.querySelector('[data-table-pagination-list]');
return footer;
}
function setRowVisible(row, visible) {
row.hidden = !visible;
row.classList.toggle('d-none', !visible);
}
function getAvailableHeight() {
var containerRect = container.getBoundingClientRect();
var headerHeight = cardHeader ? cardHeader.getBoundingClientRect().height : 0;
var footerEstimate = footer && !footer.classList.contains('d-none') ? footer.getBoundingClientRect().height : 56;
return Math.max(0, window.innerHeight - Math.max(0, containerRect.top) - headerHeight - footerEstimate - 24);
}
function renderFooter(totalItems) {
var pageButtons = [];
ensureFooter();
if (state.totalPages <= 1 || totalItems <= 0) {
footer.classList.add('d-none');
footerSummary.textContent = '';
footerList.innerHTML = '';
return;
}
footer.classList.remove('d-none');
footerSummary.textContent = 'Showing ' + (((state.currentPage - 1) * state.pageSize) + 1) + '-' + Math.min(totalItems, state.currentPage * state.pageSize) + ' of ' + totalItems;
pageButtons.push('<li class="page-item' + (state.currentPage <= 1 ? ' disabled' : '') + '"><a class="page-link" href="#" data-table-pagination-page="' + (state.currentPage - 1) + '" aria-label="Previous page">Previous</a></li>');
for (var pageNumber = 1; pageNumber <= state.totalPages; pageNumber += 1) {
pageButtons.push('<li class="page-item' + (pageNumber === state.currentPage ? ' active' : '') + '"><a class="page-link" href="#" data-table-pagination-page="' + pageNumber + '">' + pageNumber + '</a></li>');
}
pageButtons.push('<li class="page-item' + (state.currentPage >= state.totalPages ? ' disabled' : '') + '"><a class="page-link" href="#" data-table-pagination-page="' + (state.currentPage + 1) + '" aria-label="Next page">Next</a></li>');
footerList.innerHTML = pageButtons.join('');
}
function applyPagination(resetPage) {
var rows = getRows();
var matchedRows = rows.filter(isSearchMatched);
var totalItems = matchedRows.length;
var rowHeight = 48;
var pageSize;
var startIndex;
var endIndex;
if (resetPage) {
state.currentPage = 1;
}
rows.forEach(function (row) {
if (!isSearchMatched(row)) {
setRowVisible(row, false);
}
});
if (!totalItems) {
state.pageSize = 1;
state.totalPages = 1;
state.currentPage = 1;
renderFooter(0);
return;
}
matchedRows.forEach(function (row) {
setRowVisible(row, true);
});
if (matchedRows[0]) {
rowHeight = Math.max(24, matchedRows[0].getBoundingClientRect().height || matchedRows[0].offsetHeight || 48);
}
pageSize = Math.max(1, Math.floor(getAvailableHeight() / rowHeight));
if (pageSize >= totalItems) {
state.pageSize = totalItems;
state.totalPages = 1;
state.currentPage = 1;
matchedRows.forEach(function (row) {
setRowVisible(row, true);
});
renderFooter(totalItems);
return;
}
state.pageSize = pageSize;
state.totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
state.currentPage = Math.min(Math.max(1, state.currentPage), state.totalPages);
startIndex = (state.currentPage - 1) * pageSize;
endIndex = startIndex + pageSize;
matchedRows.forEach(function (row, index) {
setRowVisible(row, index >= startIndex && index < endIndex);
});
renderFooter(totalItems);
}
function scheduleSync(options) {
if (syncFrame) {
window.cancelAnimationFrame(syncFrame);
}
syncFrame = window.requestAnimationFrame(function () {
syncFrame = 0;
applyPagination(Boolean(options && options.resetPage));
});
}
ensureFooter();
if (footer) {
footer.addEventListener('click', function (event) {
var target = event.target && event.target.closest ? event.target.closest('[data-table-pagination-page]') : null;
var pageValue;
if (!target || target.closest('.disabled')) {
return;
}
event.preventDefault();
pageValue = Math.max(1, Math.floor(Number(target.getAttribute('data-table-pagination-page')) || 1));
state.currentPage = pageValue;
scheduleSync();
});
}
window.addEventListener('resize', function () {
scheduleSync();
});
if (typeof ResizeObserver === 'function') {
try {
new ResizeObserver(function () {
scheduleSync();
}).observe(container);
} catch (_error) {
// Ignore observer setup failures and rely on window resize.
}
}
container.syncTablePagination = scheduleSync;
scheduleSync({ resetPage: true });
});
}
function initTableSearches(root) {
var scope = root && root.querySelectorAll ? root : document;
Array.prototype.forEach.call(scope.querySelectorAll('[data-table-search]'), function (input) {
if (input.getAttribute('data-table-search-bound') === 'true') {
return;
}
var container = input.closest('[data-table-search-container]') || input.closest('.card') || document;
var table = container.querySelector('[data-table-searchable]');
var emptyRow = null;
if (!table) {
return;
}
input.setAttribute('data-table-search-bound', 'true');
function getSearchableRows() {
return Array.prototype.slice.call(table.querySelectorAll('tbody tr[data-table-search-row]'));
}
function getDefaultEmptyRow() {
return table.querySelector('tbody tr[data-table-search-empty-default]');
}
function removeGeneratedEmptyRow() {
if (emptyRow && emptyRow.parentNode) {
emptyRow.parentNode.removeChild(emptyRow);
}
emptyRow = null;
}
function ensureGeneratedEmptyRow(message) {
var tbody = table.tBodies[0] || table.querySelector('tbody');
var columnCount = 1;
if (!tbody) {
return;
}
if (!emptyRow) {
emptyRow = document.createElement('tr');
emptyRow.setAttribute('data-table-search-empty-row', 'true');
var cell = document.createElement('td');
cell.className = 'empty';
cell.setAttribute('data-table-search-empty-cell', 'true');
emptyRow.appendChild(cell);
}
columnCount = table.tHead && table.tHead.rows && table.tHead.rows[0] ? table.tHead.rows[0].cells.length : (getSearchableRows()[0] ? getSearchableRows()[0].cells.length : 1);
emptyRow.firstElementChild.colSpan = Math.max(1, columnCount);
emptyRow.firstElementChild.textContent = message;
if (!emptyRow.parentNode) {
tbody.appendChild(emptyRow);
}
}
function syncSearch() {
var query = String(input.value || '').trim().toLowerCase();
var rows = getSearchableRows();
var visibleCount = 0;
var defaultEmptyRow = getDefaultEmptyRow();
removeGeneratedEmptyRow();
rows.forEach(function (row) {
var haystack = String(row.getAttribute('data-search-text') || row.textContent || '').toLowerCase();
var visible = !query || haystack.indexOf(query) !== -1;
row.setAttribute('data-table-search-match', visible ? 'true' : 'false');
row.classList.toggle('d-none', !visible);
row.hidden = !visible;
if (visible) {
visibleCount += 1;
}
});
if (defaultEmptyRow) {
defaultEmptyRow.classList.toggle('d-none', Boolean(query));
defaultEmptyRow.hidden = Boolean(query);
}
if (query && visibleCount === 0) {
ensureGeneratedEmptyRow('No results match your search.');
}
if (typeof window.syncTablePagination === 'function') {
window.syncTablePagination(container, { resetPage: true });
}
}
input.addEventListener('input', syncSearch);
syncSearch();
});
}
function createSlideThumbPlaceholder() {
var placeholder = document.createElement('span');
var icon = document.createElement('i');
placeholder.className = 'playlist-slide-thumb-placeholder';
icon.className = 'bi bi-image';
icon.setAttribute('aria-hidden', 'true');
placeholder.appendChild(icon);
return placeholder;
}
function attachSlideThumbFallbacks(root) {
var scope = root && root.querySelectorAll ? root : document;
Array.prototype.forEach.call(scope.querySelectorAll('.playlist-slide-thumb-image'), function (image) {
if (image.getAttribute('data-slide-thumb-fallback-bound') === 'true') {
return;
}
image.setAttribute('data-slide-thumb-fallback-bound', 'true');
image.addEventListener('error', function () {
if (image.parentNode) {
image.parentNode.replaceChild(createSlideThumbPlaceholder(), image);
}
});
});
}
if (window.initSortableTables) {
window.initSortableTables();
}
@@ -589,6 +926,20 @@
initSubmitOnChange();
initJsonTogglePanels();
initLocalDateTimes();
initTableSearches();
initTablePaginations();
attachSlideThumbFallbacks(document);
window.initJsonTogglePanels = initJsonTogglePanels;
window.initLocalDateTimes = initLocalDateTimes;
window.initTableSearches = initTableSearches;
window.initTablePaginations = initTablePaginations;
window.syncTablePagination = function (container, options) {
if (!container || !container.syncTablePagination) {
return;
}
container.syncTablePagination(options);
};
window.createSlideThumbPlaceholder = createSlideThumbPlaceholder;
window.attachSlideThumbFallbacks = attachSlideThumbFallbacks;
}());
+32
View File
@@ -0,0 +1,32 @@
(function () {
function getOrCreate(modalElement) {
if (!modalElement || !window.bootstrap || !window.bootstrap.Modal) {
return null;
}
return window.bootstrap.Modal.getOrCreateInstance(modalElement);
}
function show(modalElement) {
var modal = getOrCreate(modalElement);
if (modal) {
modal.show();
return true;
}
return false;
}
function hide(modalElement) {
var modal = getOrCreate(modalElement);
if (modal) {
modal.hide();
return true;
}
return false;
}
window.pulseModal = {
getOrCreate: getOrCreate,
show: show,
hide: hide
};
}());
+291 -72
View File
@@ -305,16 +305,239 @@
function initPlaylistEditStaging() {
var tbody = document.getElementById('playlist-items-body');
var form = document.getElementById('playlist-edit-form');
var addSection = document.getElementById('playlist-add-section');
var addSlideForm = document.getElementById('playlist-add-slide-form');
var addSlideButton = document.getElementById('playlist-add-slide-button');
var addSlideSelect = document.getElementById('playlist-add-slide-select');
var addSlideEmpty = document.getElementById('playlist-add-slide-empty');
var addSlideModal = document.getElementById('playlist-add-slide-modal');
var addSlideGrid = document.getElementById('playlist-slide-picker-grid');
var addSlideSearch = document.getElementById('playlist-slide-picker-search');
var addSlideEmpty = document.getElementById('playlist-slide-picker-empty');
var addSlideConfirm = document.getElementById('playlist-confirm-add-slides');
var addSlideCount = document.getElementById('playlist-slide-picker-selected-count');
var addSlideData = document.getElementById('playlist-add-slide-data');
var addSlideOpenButton = document.getElementById('playlist-open-slide-modal');
var slidePickerCards = [];
var slidePickerSelection = new Set();
var slidePickerSlides = [];
if (!tbody || !form) {
return;
}
if (addSlideData) {
try {
var parsedSlides = JSON.parse(addSlideData.textContent || '[]');
slidePickerSlides = Array.isArray(parsedSlides) ? parsedSlides : [];
} catch (_error) {
slidePickerSlides = [];
}
}
function getModalInstance() {
if (!window.pulseModal) {
return null;
}
return window.pulseModal.getOrCreate(addSlideModal);
}
function setCardSelected(card, selected) {
if (!card) {
return;
}
card.classList.toggle('is-selected', Boolean(selected));
card.setAttribute('aria-pressed', Boolean(selected) ? 'true' : 'false');
}
function setCardVisible(card, visible) {
if (!card) {
return;
}
card.classList.toggle('is-hidden', !visible);
}
function clearModalSelection() {
slidePickerSelection.clear();
slidePickerCards.forEach(function (card) {
setCardSelected(card, false);
});
if (addSlideSearch) {
addSlideSearch.value = '';
}
syncSlidePickerState();
}
function attachSlideThumbFallbacks(root) {
if (typeof window.attachSlideThumbFallbacks === 'function') {
window.attachSlideThumbFallbacks(root || tbody);
}
}
function createPickerThumb(slide) {
function createPlaceholder() {
var placeholder = document.createElement('div');
var icon = document.createElement('i');
var label = document.createElement('span');
placeholder.className = 'playlist-slide-picker-thumb-placeholder';
icon.className = 'bi bi-image playlist-slide-picker-thumb-placeholder-icon';
icon.setAttribute('aria-hidden', 'true');
label.className = 'playlist-slide-picker-thumb-placeholder-label';
label.textContent = 'No thumbnail';
placeholder.appendChild(icon);
placeholder.appendChild(label);
return placeholder;
}
if (slide && slide.thumbnail_path) {
var image = document.createElement('img');
image.className = 'playlist-slide-picker-thumb-image';
image.loading = 'lazy';
image.alt = String(slide.title || 'Slide');
image.src = String(slide.thumbnail_path || '');
image.addEventListener('error', function () {
if (image.parentNode) {
image.parentNode.replaceChild(createPlaceholder(), image);
}
});
return image;
}
return createPlaceholder();
}
function createPickerCard(slide) {
var button = document.createElement('button');
var media = document.createElement('div');
var title = document.createElement('div');
var check = document.createElement('span');
var searchText = String(slide && slide.title ? slide.title : '').toLowerCase();
button.type = 'button';
button.className = 'playlist-slide-picker-card';
button.setAttribute('aria-pressed', 'false');
button.setAttribute('data-slide-id', String(slide.id || ''));
button.setAttribute('data-search-text', searchText);
if (slide && slide.isAssigned) {
button.disabled = true;
button.classList.add('is-assigned');
}
media.className = 'playlist-slide-picker-media';
media.appendChild(createPickerThumb(slide));
check.className = 'playlist-slide-picker-check bi bi-check2-circle';
check.setAttribute('aria-hidden', 'true');
media.appendChild(check);
title.className = 'playlist-slide-picker-title';
title.textContent = String(slide && slide.title ? slide.title : 'Slide');
button.appendChild(media);
button.appendChild(title);
return button;
}
function renderSlidePicker() {
if (!addSlideGrid) {
return;
}
addSlideGrid.innerHTML = '';
slidePickerCards = slidePickerSlides.map(function (slide) {
var card = createPickerCard(slide);
addSlideGrid.appendChild(card);
return card;
});
syncSlidePickerState();
}
function syncSlidePickerState() {
var query = String(addSlideSearch && addSlideSearch.value ? addSlideSearch.value : '').trim().toLowerCase();
var visibleCount = 0;
slidePickerCards.forEach(function (card) {
var searchText = String(card.getAttribute('data-search-text') || '');
var matches = !query || searchText.indexOf(query) !== -1;
setCardVisible(card, matches);
if (matches) {
visibleCount += 1;
}
});
if (addSlideEmpty) {
addSlideEmpty.textContent = query ? 'No slides match your search.' : 'No slides are currently available for this playlist.';
addSlideEmpty.classList.toggle('is-hidden', visibleCount !== 0);
}
if (addSlideCount) {
addSlideCount.textContent = String(slidePickerSelection.size);
}
if (addSlideConfirm) {
addSlideConfirm.disabled = slidePickerSelection.size === 0;
}
}
function toggleCardSelection(card) {
var slideId = String(card && card.getAttribute('data-slide-id') ? card.getAttribute('data-slide-id') : '');
var selected;
if (!slideId || card.disabled) {
return;
}
selected = !slidePickerSelection.has(slideId);
if (selected) {
slidePickerSelection.add(slideId);
} else {
slidePickerSelection.delete(slideId);
}
setCardSelected(card, selected);
syncSlidePickerState();
}
function addSelectedSlides() {
var selectedSlides = slidePickerSlides.filter(function (slide) {
return slidePickerSelection.has(String(slide.id || '')) && !slide.isAssigned;
});
if (!selectedSlides.length) {
return;
}
if (tbody.querySelector('.playlist-empty-row')) {
tbody.querySelector('.playlist-empty-row').remove();
}
selectedSlides.forEach(function (slide) {
var row = createRow({
row_key: 'new-' + Date.now() + '-' + slide.id,
slide_id: String(slide.id),
thumbnail_path: String(slide.thumbnail_path || ''),
canvas_signature: String(slide.canvasSignature || ''),
title: slide.title || 'Slide',
duration_seconds: 10,
schedule_mode: 'always',
schedule_start_datetime: '',
schedule_end_datetime: '',
schedule_start_time: '',
schedule_end_time: '',
schedule_days_json: '[]',
summary: 'Always visible'
});
tbody.appendChild(row);
});
attachSlideThumbFallbacks(tbody);
updateRowOrder(true);
if (!window.pulseModal || !window.pulseModal.hide(addSlideModal)) {
addSlideModal.classList.remove('show');
addSlideModal.setAttribute('aria-hidden', 'true');
}
}
attachSlideThumbFallbacks(tbody);
function getRows() {
return Array.prototype.slice.call(tbody.querySelectorAll('tr[data-playlist-slide-row]'));
}
@@ -375,9 +598,6 @@
}
function syncAddSlideOptions() {
if (!addSlideSelect) {
return;
}
var activeSlideIds = {};
var activeCanvasSignatures = {};
getRows().forEach(function (row) {
@@ -397,51 +617,37 @@
allowedCanvasSignature = canvasKeys[0];
}
Array.prototype.forEach.call(addSlideSelect.options, function (option) {
if (!option.value) {
return;
slidePickerSlides.forEach(function (slide) {
var slideId = String(slide.id || '');
var card = slidePickerCards.find(function (item) {
return String(item.getAttribute('data-slide-id') || '') === slideId;
});
var canvasSignature = String(slide.canvasSignature || '');
var isAssigned = Boolean(activeSlideIds[slideId]);
var isCanvasMismatch = Boolean(allowedCanvasSignature && canvasSignature && canvasSignature !== allowedCanvasSignature);
if (card) {
card.disabled = isAssigned || isCanvasMismatch;
card.classList.toggle('is-assigned', isAssigned);
card.classList.toggle('is-mismatch', isCanvasMismatch);
card.classList.toggle('is-hidden', false);
card.setAttribute('aria-disabled', card.disabled ? 'true' : 'false');
}
var optionCanvasSignature = String(option.getAttribute('data-canvas-signature') || '');
var isActive = Boolean(activeSlideIds[String(option.value)]);
var isCanvasMismatch = Boolean(allowedCanvasSignature && optionCanvasSignature && optionCanvasSignature !== allowedCanvasSignature);
option.disabled = isActive || isCanvasMismatch;
option.hidden = isActive || isCanvasMismatch;
});
if (
addSlideSelect.options[addSlideSelect.selectedIndex]
&& (addSlideSelect.options[addSlideSelect.selectedIndex].disabled || addSlideSelect.options[addSlideSelect.selectedIndex].hidden)
) {
for (var i = 0; i < addSlideSelect.options.length; i += 1) {
if (!addSlideSelect.options[i].disabled && !addSlideSelect.options[i].hidden) {
addSlideSelect.selectedIndex = i;
break;
if (isAssigned) {
slidePickerSelection.delete(slideId);
if (card) {
setCardSelected(card, false);
}
}
}
var availableCount = 0;
Array.prototype.forEach.call(addSlideSelect.options, function (option) {
if (!option.value) {
return;
}
if (!option.disabled && !option.hidden) {
availableCount += 1;
}
});
if (addSlideButton) {
addSlideButton.disabled = availableCount === 0;
}
if (addSlideSelect) {
addSlideSelect.disabled = availableCount === 0;
}
if (addSlideEmpty) {
addSlideEmpty.classList.toggle('is-hidden', availableCount !== 0);
}
if (addSection && addSlideForm && addSlideSelect && addSlideButton) {
var controlsVisible = availableCount > 0;
addSlideForm.classList.toggle('is-hidden', !controlsVisible);
if (addSlideOpenButton) {
addSlideOpenButton.disabled = slidePickerCards.filter(function (card) {
return !card.disabled;
}).length === 0;
}
syncSlidePickerState();
}
function updateRowOrder(markDirty) {
@@ -530,6 +736,9 @@
var row = document.createElement('tr');
var rowKey = values.row_key || ('new-' + Date.now() + '-' + Math.random().toString(36).slice(2));
var playlistId = tbody.getAttribute('data-playlist-id') || '';
var thumbnailMarkup = values.thumbnail_path
? '<img class="playlist-slide-thumb-image" src="' + values.thumbnail_path + '" alt="" loading="lazy" />'
: '<span class="playlist-slide-thumb-placeholder"><i class="bi bi-image" aria-hidden="true"></i></span>';
row.setAttribute('data-playlist-slide-row', '');
row.setAttribute('data-row-key', rowKey);
row.setAttribute('data-slide-id', String(values.slide_id));
@@ -541,7 +750,12 @@
'<span class="playlist-order-number"></span>' +
'</div>' +
'</td>' +
'<td>' + values.title + '<input type="hidden" name="slide_id[]" value="' + values.slide_id + '" form="playlist-edit-form" /></td>' +
'<td data-label="Slide">' +
'<div class="playlist-slide-cell">' +
'<div class="playlist-slide-thumb" aria-hidden="true">' + thumbnailMarkup + '</div>' +
'<div class="playlist-slide-cell-content"><span class="playlist-slide-title">' + values.title + '</span></div>' +
'</div>' +
'<input type="hidden" name="slide_id[]" value="' + values.slide_id + '" form="playlist-edit-form" /></td>' +
'<td>' +
'<div class="playlist-schedule-summary">' + values.summary + '</div>' +
'<input type="hidden" name="schedule_mode[]" value="' + values.schedule_mode + '" form="playlist-edit-form" />' +
@@ -551,7 +765,7 @@
'<input type="hidden" name="schedule_end_time[]" value="' + values.schedule_end_time + '" form="playlist-edit-form" />' +
'<input type="hidden" name="schedule_days_json[]" value="' + values.schedule_days_json + '" form="playlist-edit-form" />' +
'</td>' +
'<td><input name="duration_seconds[]" type="number" min="1" value="' + values.duration_seconds + '" required form="playlist-edit-form" /></td>' +
'<td><input name="duration_seconds[]" type="number" min="1" value="' + values.duration_seconds + '" required form="playlist-edit-form" class="form-control form-control-sm playlist-duration-input text-end" /></td>' +
'<td><div class="actions playlist-item-actions">' +
'<button type="button" class="btn btn-sm btn-primary" data-schedule-config="/playlists/' + encodeURIComponent(playlistId) + '/slides/0/config" data-schedule-config-row="' + rowKey + '">Schedule</button>' +
'<button type="button" class="btn btn-sm btn-danger" data-playlist-remove-row>Remove</button>' +
@@ -609,32 +823,37 @@
});
}
if (addSlideButton && addSlideSelect) {
addSlideButton.addEventListener('click', function () {
var selectedOption = addSlideSelect.options[addSlideSelect.selectedIndex];
if (!selectedOption || selectedOption.disabled) {
if (addSlideModal && addSlideGrid) {
renderSlidePicker();
addSlideGrid.addEventListener('click', function (event) {
var card = event.target.closest('.playlist-slide-picker-card');
if (!card) {
return;
}
var placeholder = tbody.querySelector('.playlist-empty-row');
if (placeholder) {
placeholder.remove();
}
var row = createRow({
row_key: 'new-' + Date.now(),
slide_id: String(selectedOption.value),
canvas_signature: String(selectedOption.getAttribute('data-canvas-signature') || ''),
title: selectedOption.textContent || 'Slide',
duration_seconds: 10,
schedule_mode: 'always',
schedule_start_datetime: '',
schedule_end_datetime: '',
schedule_start_time: '',
schedule_end_time: '',
schedule_days_json: '[]',
summary: 'Always visible'
event.preventDefault();
toggleCardSelection(card);
});
if (addSlideSearch) {
addSlideSearch.addEventListener('input', syncSlidePickerState);
}
if (addSlideConfirm) {
addSlideConfirm.addEventListener('click', function () {
addSelectedSlides();
});
tbody.appendChild(row);
updateRowOrder(true);
}
addSlideModal.addEventListener('shown.bs.modal', function () {
syncSlidePickerState();
if (addSlideSearch) {
addSlideSearch.focus();
}
});
addSlideModal.addEventListener('hidden.bs.modal', function () {
clearModalSelection();
});
}
@@ -8,6 +8,133 @@
return;
}
function getPageUrl(href) {
try {
return new URL(href, window.location.href);
} catch (_error) {
return null;
}
}
function updateLocalDateTimes(root) {
if (typeof window.initLocalDateTimes === 'function') {
window.initLocalDateTimes(root || document);
}
}
function replaceSectionFromDocument(sectionName, responseDocument) {
var currentCard = document.querySelector('[data-pagination-card="' + sectionName + '"]');
var nextCard = responseDocument.querySelector('[data-pagination-card="' + sectionName + '"]');
if (!currentCard || !nextCard) {
return false;
}
currentCard.outerHTML = nextCard.outerHTML;
updateLocalDateTimes(document.querySelector('[data-pagination-card="' + sectionName + '"]'));
return true;
}
function replaceSummaryFromDocument(responseDocument) {
var currentSummary = document.getElementById('background-tasks-summary');
var nextSummary = responseDocument.getElementById('background-tasks-summary');
if (!currentSummary || !nextSummary) {
return false;
}
currentSummary.outerHTML = nextSummary.outerHTML;
return true;
}
function replaceFiltersFromDocument(responseDocument) {
var currentFilters = document.getElementById('background-tasks-filters');
var nextFilters = responseDocument.getElementById('background-tasks-filters');
if (!currentFilters || !nextFilters) {
return false;
}
currentFilters.outerHTML = nextFilters.outerHTML;
return true;
}
function replaceStateFromDocument(responseDocument) {
var currentState = document.getElementById('background-tasks-state');
var nextState = responseDocument.getElementById('background-tasks-state');
if (!currentState || !nextState) {
return false;
}
currentState.outerHTML = nextState.outerHTML;
stateNode = document.getElementById('background-tasks-state');
currentVersion = stateNode ? String(stateNode.getAttribute('data-state-version') || '') : currentVersion;
return true;
}
function applyPageResponse(responseDocument, sectionNames) {
var updated = false;
if (replaceStateFromDocument(responseDocument)) {
updated = true;
}
if (replaceSummaryFromDocument(responseDocument)) {
updated = true;
}
if (replaceFiltersFromDocument(responseDocument)) {
updated = true;
}
sectionNames.forEach(function (sectionName) {
if (replaceSectionFromDocument(sectionName, responseDocument)) {
updated = true;
}
});
return updated;
}
function loadPage(nextUrl, options) {
var url = getPageUrl(nextUrl);
var sectionNames = options && options.sectionNames ? options.sectionNames : [];
var replaceHistory = Boolean(options && options.replaceHistory);
if (!url) {
return;
}
fetch(url.toString(), {
method: 'GET',
headers: {
'Accept': 'text/html, application/xhtml+xml',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin',
cache: 'no-store'
}).then(function (response) {
if (!response.ok) {
throw new Error('Unable to load background tasks page.');
}
return response.text();
}).then(function (text) {
var responseDocument = new DOMParser().parseFromString(text || '', 'text/html');
if (!applyPageResponse(responseDocument, sectionNames.length ? sectionNames : ['tasks', 'recurring'])) {
return;
}
if (replaceHistory) {
window.history.replaceState({}, document.title, url.pathname + url.search + url.hash);
} else {
window.history.pushState({}, document.title, url.pathname + url.search + url.hash);
}
}).catch(function (_error) {
window.location.assign(url.pathname + url.search + url.hash);
});
}
function stripMessageParameter() {
try {
var url = new URL(window.location.href);
@@ -21,6 +148,100 @@
}
}
function findPaginationLink(target) {
if (!target || !target.closest) {
return null;
}
var link = target.closest('.pagination .page-link');
if (!link) {
return null;
}
var href = String(link.getAttribute('href') || '').trim();
if (!href || href === '#') {
return null;
}
var card = link.closest('[data-pagination-card]');
if (!card) {
return null;
}
return {
href: href,
sectionName: String(card.getAttribute('data-pagination-card') || '').trim()
};
}
function findFilterLink(target) {
if (!target || !target.closest) {
return null;
}
var link = target.closest('a[data-background-tasks-nav]');
if (!link) {
return null;
}
var href = String(link.getAttribute('href') || '').trim();
if (!href || href === '#') {
return null;
}
return {
href: href
};
}
function initPaginationNavigation() {
document.addEventListener('click', function (event) {
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
var paginationLink = findPaginationLink(event.target);
if (paginationLink && paginationLink.sectionName) {
var currentUrl = getPageUrl(window.location.href);
var nextUrl = getPageUrl(paginationLink.href);
if (!currentUrl || !nextUrl) {
return;
}
if (currentUrl.pathname === nextUrl.pathname && currentUrl.search === nextUrl.search && currentUrl.hash === nextUrl.hash) {
event.preventDefault();
return;
}
event.preventDefault();
loadPage(nextUrl.toString(), { sectionNames: [paginationLink.sectionName] });
return;
}
var filterLink = findFilterLink(event.target);
if (!filterLink) {
return;
}
var filterUrl = getPageUrl(filterLink.href);
if (!filterUrl) {
return;
}
if (filterUrl.pathname === window.location.pathname && filterUrl.search === window.location.search && filterUrl.hash === window.location.hash) {
event.preventDefault();
return;
}
event.preventDefault();
loadPage(filterUrl.toString(), { sectionNames: ['tasks'] });
}, true);
window.addEventListener('popstate', function () {
loadPage(window.location.href, { replaceHistory: true });
});
}
function refreshPage() {
if (document.visibilityState !== 'visible') {
return;
@@ -59,5 +280,6 @@
}
stripMessageParameter();
initPaginationNavigation();
window.setInterval(refreshPage, REFRESH_INTERVAL_MS);
}());
+2 -2
View File
@@ -543,7 +543,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
if (!src) {
return '<div class="slide-preview-placeholder">Webpage</div>';
}
return '<iframe class="slide-preview-webpage-frame" src="' + escapeHtml(src) + '" title="Webpage preview" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe>';
return '<iframe class="slide-preview-webpage-frame" src="' + escapeHtml(src) + '" title="Webpage preview" loading="eager" referrerpolicy="no-referrer" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"></iframe>';
}
function renderPreviewHtmlRegion(value) {
@@ -551,7 +551,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
if (!html) {
return '<div class="slide-preview-placeholder">HTML</div>';
}
return '<iframe class="slide-preview-html-frame" sandbox="" scrolling="no" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="HTML preview" loading="eager"></iframe>';
return '<iframe class="slide-preview-html-frame" sandbox="" scrolling="no" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="HTML preview" loading="eager" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe>';
}
function destroyEditors() {
+34 -6
View File
@@ -67,6 +67,34 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
var sidebarTopOffset = 16;
var templateSelectorLock = createTemplateSelectorLockController(templateSelect);
function applyBackdropStyle(element, backgroundColor, backgroundImagePath) {
if (!element) {
return;
}
var color = String(backgroundColor || '#111111').trim() || '#111111';
element.style.backgroundColor = color;
element.style.backgroundImage = backgroundImagePath
? 'linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42)), url("' + encodeURI(String(backgroundImagePath)) + '")'
: 'linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42))';
element.style.backgroundPosition = backgroundImagePath ? 'center, center' : 'center';
element.style.backgroundSize = backgroundImagePath ? '100% 100%, cover' : '100% 100%';
element.style.backgroundRepeat = backgroundImagePath ? 'no-repeat, no-repeat' : 'no-repeat';
}
function applyPopupBackdropStyle(element, backgroundColor) {
if (!element) {
return;
}
var color = String(backgroundColor || '#111111').trim() || '#111111';
element.style.backgroundColor = color;
element.style.backgroundImage = 'linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42))';
element.style.backgroundPosition = 'center';
element.style.backgroundSize = '100% 100%';
element.style.backgroundRepeat = 'no-repeat';
}
class FontSizeInputUI extends Plugin {
static get pluginName() {
return 'FontSizeInputUI';
@@ -408,7 +436,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
if (!src) {
return '<div class="slide-preview-placeholder">Webpage</div>';
}
return '<iframe class="slide-preview-webpage-frame" src="' + escapeHtml(src) + '" title="Webpage preview" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe>';
return '<iframe class="slide-preview-webpage-frame" src="' + escapeHtml(src) + '" title="Webpage preview" loading="eager" referrerpolicy="no-referrer" scrolling="no" style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"></iframe>';
}
function renderPreviewRtmpRegion(value, disableAudio) {
@@ -425,7 +453,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
if (!html) {
return '<div class="slide-preview-placeholder">HTML</div>';
}
return '<iframe class="slide-preview-html-frame" sandbox="" scrolling="no" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="HTML preview" loading="eager"></iframe>';
return '<iframe class="slide-preview-html-frame" sandbox="" scrolling="no" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="HTML preview" loading="eager" style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"></iframe>';
}
function destroyEditors() {
@@ -814,7 +842,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
}
if (!template) {
popupStage.style.backgroundColor = '#111111';
applyBackdropStyle(popupCanvas, '#111111');
popupCanvas.style.width = '';
popupCanvas.style.height = '';
popupCanvas.style.transform = '';
@@ -837,7 +865,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
var canvasWidth = currentPreviewCanvasWidth || Math.max(1, Number(template.canvas_size_width || 1920));
var canvasHeight = currentPreviewCanvasHeight || Math.max(1, Number(template.canvas_size_height || 1080));
popupStage.style.backgroundColor = template.background_color || '#111111';
applyBackdropStyle(popupCanvas, template.background_color || '#111111', template.background_image_path);
popupCanvas.style.width = canvasWidth + 'px';
popupCanvas.style.height = canvasHeight + 'px';
popupCanvas.innerHTML = slidePreviewCanvas.innerHTML;
@@ -949,7 +977,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
slidePreviewOverlay.style.height = '';
slidePreviewOverlay.style.transform = '';
slidePreviewStage.style.aspectRatio = '16 / 9';
slidePreviewStage.style.backgroundColor = '#111111';
applyBackdropStyle(slidePreviewStage, '#111111');
return;
}
@@ -959,7 +987,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
currentPreviewCanvasWidth = canvasWidth;
currentPreviewCanvasHeight = canvasHeight;
slidePreviewStage.style.aspectRatio = canvasWidth + ' / ' + canvasHeight;
slidePreviewStage.style.backgroundColor = template.background_color || '#111111';
applyBackdropStyle(slidePreviewStage, template.background_color || '#111111', template.background_image_path);
slidePreviewMeta.textContent = canvasWidth + 'x' + canvasHeight;
if (!(template.regions || []).length) {
slidePreviewEmpty.style.display = 'flex';
@@ -528,10 +528,10 @@
}
function openAddRegionModal() {
if (!regionAddModal || !window.bootstrap || !window.bootstrap.Modal) {
if (!window.pulseModal) {
return;
}
window.bootstrap.Modal.getOrCreateInstance(regionAddModal).show();
window.pulseModal.show(regionAddModal);
}
function createDefaultRegion(type) {
@@ -701,8 +701,8 @@
button.addEventListener('click', function () {
var regionType = button.getAttribute('data-add-region-type');
addRegion(createDefaultRegion(regionType));
if (window.bootstrap && window.bootstrap.Modal) {
window.bootstrap.Modal.getOrCreateInstance(regionAddModal).hide();
if (window.pulseModal) {
window.pulseModal.hide(regionAddModal);
}
});
});