Release 2.8.6
This commit is contained in:
@@ -886,7 +886,7 @@
|
||||
var summaryLabel = isRoot ? (isArray ? 'Array' : 'Object') : String(key);
|
||||
var summaryMeta = isArray ? '[' + value.length + ']' : '{' + Object.keys(value).length + '}';
|
||||
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>' +
|
||||
'<div class="ms-3 ps-3 border-start">' + entries + '</div>' +
|
||||
'</details>';
|
||||
@@ -945,26 +945,30 @@
|
||||
detail.open = expanded;
|
||||
});
|
||||
|
||||
if (collapseButton) {
|
||||
collapseButton.disabled = !details.length || !expanded;
|
||||
}
|
||||
if (expandButton) {
|
||||
expandButton.disabled = !details.length || expanded;
|
||||
}
|
||||
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) {
|
||||
collapseButton.setAttribute('aria-pressed', 'false');
|
||||
collapseButton.disabled = !details.length || allCollapsed;
|
||||
collapseButton.setAttribute('aria-pressed', String(allCollapsed));
|
||||
}
|
||||
if (expandButton) {
|
||||
expandButton.setAttribute('aria-pressed', 'true');
|
||||
expandButton.disabled = !details.length || allExpanded;
|
||||
expandButton.setAttribute('aria-pressed', String(allExpanded));
|
||||
}
|
||||
}
|
||||
|
||||
output.innerHTML = formattedTree;
|
||||
setAllSectionsExpanded(false);
|
||||
var rootDetails = output.querySelector('details');
|
||||
if (rootDetails) {
|
||||
rootDetails.open = true;
|
||||
}
|
||||
syncButtons();
|
||||
setAllSectionsExpanded(true);
|
||||
|
||||
if (collapseButton) {
|
||||
collapseButton.addEventListener('click', function () {
|
||||
|
||||
@@ -1029,7 +1029,7 @@
|
||||
'</div>' +
|
||||
'<div class="card-body p-3 d-grid gap-2">' +
|
||||
'<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>' +
|
||||
'<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">' +
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
headerActions: '<span class="chip">Webpage</span>',
|
||||
bodyHtml: '' +
|
||||
'<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>' +
|
||||
'</div>'
|
||||
});
|
||||
|
||||
@@ -842,6 +842,15 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
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) {
|
||||
if (!window.webAsyncSaveForm || typeof window.webAsyncSaveForm.submit !== 'function') {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,98 @@
|
||||
// Shared browser helpers for web UI escaping and formatting.
|
||||
|
||||
(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) {
|
||||
return String(value ?? '')
|
||||
.replace(/&/g, '&')
|
||||
@@ -97,6 +189,7 @@
|
||||
}
|
||||
|
||||
window.escapeHtml = escapeHtml;
|
||||
initializeUrlValidation();
|
||||
|
||||
var defaultQrOptions = window.defaultQrOptions || {
|
||||
width: 1000,
|
||||
|
||||
Reference in New Issue
Block a user