Release 2.8.6

This commit is contained in:
2026-08-18 02:23:42 +01:00
parent f071c21219
commit e1e759f64e
10 changed files with 131 additions and 17 deletions
+8
View File
@@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 2.8.6 - 2026-08-18
### Fixed
- Added live URL validation with inline feedback and explicit HTTP or HTTPS scheme enforcement for URL fields.
- Prevented Enter in slide editor inputs from implicitly saving the slide.
- Collapsed nested API response JSON sections by default while keeping the root response visible.
## 2.8.5 - 2026-08-17 ## 2.8.5 - 2026-08-17
### Fixed ### Fixed
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pulse-signage-player", "name": "pulse-signage-player",
"version": "2.8.5", "version": "2.8.6",
"private": false, "private": false,
"description": "Pulse Signage player application bundle", "description": "Pulse Signage player application bundle",
"engines": { "engines": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pulse-signage-web", "name": "pulse-signage-web",
"version": "2.8.5", "version": "2.8.6",
"private": false, "private": false,
"description": "Pulse Signage web and bridge application bundle", "description": "Pulse Signage web and bridge application bundle",
"engines": { "engines": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "pulse-signage", "name": "pulse-signage",
"version": "2.8.5", "version": "2.8.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pulse-signage", "name": "pulse-signage",
"version": "2.8.5", "version": "2.8.6",
"dependencies": { "dependencies": {
"@sparticuz/chromium": "^149.0.0", "@sparticuz/chromium": "^149.0.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pulse-signage", "name": "pulse-signage",
"version": "2.8.5", "version": "2.8.6",
"private": false, "private": false,
"description": "Pulse Signage application with MySQL and media storage", "description": "Pulse Signage application with MySQL and media storage",
"engines": { "engines": {
+14 -10
View File
@@ -886,7 +886,7 @@
var summaryLabel = isRoot ? (isArray ? 'Array' : 'Object') : String(key); var summaryLabel = isRoot ? (isArray ? 'Array' : 'Object') : String(key);
var summaryMeta = isArray ? '[' + value.length + ']' : '{' + Object.keys(value).length + '}'; var summaryMeta = isArray ? '[' + value.length + ']' : '{' + Object.keys(value).length + '}';
return '' + return '' +
'<details class="json-tree-node" open>' + '<details class="json-tree-node"' + (isRoot ? ' open' : '') + '>' +
'<summary><span class="json-tree-key">' + escapeHtml(summaryLabel) + '</span><span class="mx-1">:</span><span class="text-body-secondary">' + escapeHtml(summaryMeta) + '</span></summary>' + '<summary><span class="json-tree-key">' + escapeHtml(summaryLabel) + '</span><span class="mx-1">:</span><span class="text-body-secondary">' + escapeHtml(summaryMeta) + '</span></summary>' +
'<div class="ms-3 ps-3 border-start">' + entries + '</div>' + '<div class="ms-3 ps-3 border-start">' + entries + '</div>' +
'</details>'; '</details>';
@@ -945,26 +945,30 @@
detail.open = expanded; detail.open = expanded;
}); });
if (collapseButton) { syncButtons();
collapseButton.disabled = !details.length || !expanded;
}
if (expandButton) {
expandButton.disabled = !details.length || expanded;
}
} }
function syncButtons() { function syncButtons() {
var details = output.querySelectorAll('details');
var allExpanded = details.length > 0 && Array.prototype.every.call(details, function (detail) { return detail.open; });
var allCollapsed = details.length > 0 && Array.prototype.every.call(details, function (detail) { return !detail.open; });
if (collapseButton) { if (collapseButton) {
collapseButton.setAttribute('aria-pressed', 'false'); collapseButton.disabled = !details.length || allCollapsed;
collapseButton.setAttribute('aria-pressed', String(allCollapsed));
} }
if (expandButton) { if (expandButton) {
expandButton.setAttribute('aria-pressed', 'true'); expandButton.disabled = !details.length || allExpanded;
expandButton.setAttribute('aria-pressed', String(allExpanded));
} }
} }
output.innerHTML = formattedTree; output.innerHTML = formattedTree;
setAllSectionsExpanded(false);
var rootDetails = output.querySelector('details');
if (rootDetails) {
rootDetails.open = true;
}
syncButtons(); syncButtons();
setAllSectionsExpanded(true);
if (collapseButton) { if (collapseButton) {
collapseButton.addEventListener('click', function () { collapseButton.addEventListener('click', function () {
+1 -1
View File
@@ -1029,7 +1029,7 @@
'</div>' + '</div>' +
'<div class="card-body p-3 d-grid gap-2">' + '<div class="card-body p-3 d-grid gap-2">' +
'<label class="form-label mb-1" for="region_qr_code_' + region.id + '">URL</label>' + '<label class="form-label mb-1" for="region_qr_code_' + region.id + '">URL</label>' +
'<input id="region_qr_code_' + region.id + '" type="url" name="region_qr_code_' + region.id + '" class="form-control" value="' + escapeHtml(current) + '" placeholder="https://example.com" />' + '<input id="region_qr_code_' + region.id + '" type="url" name="region_qr_code_' + region.id + '" class="form-control" value="' + escapeHtml(current) + '" placeholder="https://example.com" required />' +
'<textarea name="region_qr_svg_' + region.id + '" class="visually-hidden" hidden>' + escapeHtml(currentSvg) + '</textarea>' + '<textarea name="region_qr_svg_' + region.id + '" class="visually-hidden" hidden>' + escapeHtml(currentSvg) + '</textarea>' +
'<input type="hidden" name="region_qr_preview_' + region.id + '" value="' + escapeHtml(String(context.qr_preview || '')) + '" />' + '<input type="hidden" name="region_qr_preview_' + region.id + '" value="' + escapeHtml(String(context.qr_preview || '')) + '" />' +
'<div class="card card-outline card-secondary overflow-hidden mt-2">' + '<div class="card card-outline card-secondary overflow-hidden mt-2">' +
+1 -1
View File
@@ -50,7 +50,7 @@
headerActions: '<span class="chip">Webpage</span>', headerActions: '<span class="chip">Webpage</span>',
bodyHtml: '' + bodyHtml: '' +
'<div class="input-group flex-nowrap">' + '<div class="input-group flex-nowrap">' +
'<input type="url" name="region_webpage_' + region.id + '" class="form-control" value="' + escapeHtml(current) + '" placeholder="https://example.com" />' + '<input type="url" name="region_webpage_' + region.id + '" class="form-control" value="' + escapeHtml(current) + '" placeholder="https://example.com" required />' +
'<button type="button" class="btn btn-outline-secondary text-nowrap" data-webpage-preview-update data-region-id="' + region.id + '">Update</button>' + '<button type="button" class="btn btn-outline-secondary text-nowrap" data-webpage-preview-update data-region-id="' + region.id + '">Update</button>' +
'</div>' '</div>'
}); });
+9
View File
@@ -842,6 +842,15 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
requestPreviewRender(); requestPreviewRender();
} }
slideForm.addEventListener('keydown', function (event) {
var target = event.target;
if (event.key !== 'Enter' || !target || !target.matches || !target.matches('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select')) {
return;
}
event.preventDefault();
});
slideForm.addEventListener('submit', function (event) { slideForm.addEventListener('submit', function (event) {
if (!window.webAsyncSaveForm || typeof window.webAsyncSaveForm.submit !== 'function') { if (!window.webAsyncSaveForm || typeof window.webAsyncSaveForm.submit !== 'function') {
return; return;
+93
View File
@@ -1,6 +1,98 @@
// Shared browser helpers for web UI escaping and formatting. // Shared browser helpers for web UI escaping and formatting.
(function () { (function () {
var urlValidationMessage = 'Please enter a URL.';
function isUrlInput(input) {
return input && input.matches && input.matches('input[type="url"]');
}
function isValidHttpUrl(input) {
input.setCustomValidity('');
var value = String(input.value || '').trim();
var hasValidNativeUrl = input.validity.valid;
var hasExplicitHttpScheme = /^https?:\/\/[^\s]+$/i.test(value);
var isValid = hasValidNativeUrl && hasExplicitHttpScheme;
if (!isValid) {
input.setCustomValidity(urlValidationMessage);
}
return isValid;
}
function getUrlFeedback(input) {
if (input._urlFeedback && input._urlFeedback.parentNode) {
return input._urlFeedback;
}
var feedbackId = input.id ? input.id + '-url-feedback' : '';
var feedback = feedbackId ? document.getElementById(feedbackId) : null;
if (feedback) {
var inputGroup = input.closest ? input.closest('.input-group') : null;
var feedbackAnchor = inputGroup || input;
feedbackAnchor.parentNode.insertBefore(feedback, feedbackAnchor.nextSibling);
input._urlFeedback = feedback;
return feedback;
}
feedback = document.createElement('div');
if (feedbackId) {
feedback.id = feedbackId;
}
feedback.className = 'invalid-feedback';
feedback.setAttribute('role', 'alert');
feedback.textContent = urlValidationMessage;
var inputGroup = input.closest ? input.closest('.input-group') : null;
var feedbackAnchor = inputGroup || input;
feedbackAnchor.parentNode.insertBefore(feedback, feedbackAnchor.nextSibling);
if (input.id) {
input.setAttribute('aria-describedby', feedback.id);
}
input._urlFeedback = feedback;
return feedback;
}
function updateUrlValidation(input, showFeedback) {
if (!isUrlInput(input)) {
return true;
}
var isValid = isValidHttpUrl(input);
if (!isValid && showFeedback) {
var feedback = getUrlFeedback(input);
feedback.hidden = false;
feedback.classList.add('d-block');
input.classList.add('is-invalid');
} else if (isValid) {
if (input.id) {
document.querySelectorAll('[id="' + input.id + '-url-feedback"]').forEach(function (feedback) {
feedback.hidden = true;
feedback.classList.remove('d-block');
});
} else if (input._urlFeedback) {
input._urlFeedback.hidden = true;
input._urlFeedback.classList.remove('d-block');
}
input.classList.remove('is-invalid');
}
return isValid;
}
function initializeUrlValidation() {
document.addEventListener('input', function (event) {
if (isUrlInput(event.target)) {
updateUrlValidation(event.target, true);
}
});
document.addEventListener('invalid', function (event) {
if (isUrlInput(event.target)) {
event.preventDefault();
updateUrlValidation(event.target, true);
}
}, true);
}
function escapeHtml(value) { function escapeHtml(value) {
return String(value ?? '') return String(value ?? '')
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
@@ -97,6 +189,7 @@
} }
window.escapeHtml = escapeHtml; window.escapeHtml = escapeHtml;
initializeUrlValidation();
var defaultQrOptions = window.defaultQrOptions || { var defaultQrOptions = window.defaultQrOptions || {
width: 1000, width: 1000,