(function () { var webUiHelpers = window.webUiHelpers || {}; var escapeHtml = webUiHelpers.escapeHtml; var formatDashboardDate = webUiHelpers.formatDashboardDate; var getClientRowKey = webUiHelpers.getClientRowKey; var getClientDisplayName = webUiHelpers.getClientDisplayName; var setButtonVariant = webUiHelpers.setButtonVariant; var normalizeDisplayIp = webUiHelpers.normalizeDisplayIp; function initConfirmForms() { document.addEventListener('submit', function (event) { var form = event.target; if (!form || !form.getAttribute) { return; } if (form.hasAttribute && form.hasAttribute('data-async-command')) { return; } var message = form.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { event.preventDefault(); return; } var actionPath = ''; try { actionPath = new URL(form.action, window.location.href).pathname; } catch (_error) { actionPath = String(form.action || ''); } if (!/\/delete(?:\/|$)/.test(actionPath)) { return; } if (form.dataset && form.dataset.busy === 'true') { event.preventDefault(); return; } event.preventDefault(); form.dataset.busy = 'true'; fetch(form.action, { method: (form.method || 'POST').toUpperCase(), headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' }, credentials: 'same-origin' }).then(function (response) { if (!response.ok) { return response.text().then(function (text) { var error = new Error(text || 'Unable to delete item.'); error.status = response.status; throw error; }); } if (isLoginRedirect(response)) { window.location.replace(response.url || '/login'); return; } window.location.replace(response.url || window.location.href); }).catch(function (error) { if (typeof showToast === 'function') { var status = Number(error && error.status); var variant = status === 403 ? 'warning' : status >= 400 && status < 500 ? 'warning' : 'danger'; showToast(error.message || 'Unable to delete item.', variant); return; } window.alert(error.message || 'Unable to delete item.'); }).finally(function () { delete form.dataset.busy; }); }); } function updateDataSourceToggle(form, response) { var toggleButton = document.querySelector('button[form="' + form.id + '"][data-async-data-source-toggle]'); if (!toggleButton) { return; } var willEnable = toggleButton.getAttribute('data-enabled') !== 'true'; toggleButton.setAttribute('data-enabled', willEnable ? 'true' : 'false'); toggleButton.className = toggleButton.className.replace(/btn-(danger|success)/g, willEnable ? 'btn-danger' : 'btn-success'); toggleButton.innerHTML = '' + (willEnable ? 'Disable' : 'Enable'); var message = ''; try { message = new URL(response.url, window.location.href).searchParams.get('message') || ''; } catch (_error) { message = ''; } if (typeof showToast === 'function') { showToast(message || (willEnable ? 'Data source enabled.' : 'Data source disabled.'), 'success'); } } function initDeleteButtons() { document.addEventListener('click', function (event) { var deleteButton = event.target.closest('[data-delete-action-url]'); if (!deleteButton) { return; } if (deleteButton.disabled || (deleteButton.dataset && deleteButton.dataset.busy === 'true')) { event.preventDefault(); return; } var actionUrl = deleteButton.getAttribute('data-delete-action-url'); if (!actionUrl) { return; } var message = deleteButton.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { event.preventDefault(); return; } event.preventDefault(); deleteButton.dataset.busy = 'true'; fetch(actionUrl, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' }, credentials: 'same-origin' }).then(function (response) { if (!response.ok) { return response.text().then(function (text) { var error = new Error(text || 'Unable to delete item.'); error.status = response.status; throw error; }); } if (isLoginRedirect(response)) { window.location.replace(response.url || '/login'); return; } window.location.replace(response.url || window.location.href); }).catch(function (error) { if (typeof showToast === 'function') { var status = Number(error && error.status); var variant = status === 403 ? 'warning' : status >= 400 && status < 500 ? 'warning' : 'danger'; showToast(error.message || 'Unable to delete item.', variant); return; } window.alert(error.message || 'Unable to delete item.'); }).finally(function () { delete deleteButton.dataset.busy; }); }); } function markFormDirty(form) { if (!form || !form.hasAttribute || form.hasAttribute('data-clean-on-load')) { return; } form.dataset.dirty = 'true'; } function clearFormDirty(form) { if (!form) { return; } form.dataset.dirty = 'false'; } function isFormDirty(form) { return Boolean(form && form.dataset && form.dataset.dirty === 'true'); } function isRegionEditorField(target) { return Boolean(target && typeof target.closest === 'function' && target.closest('.region-item')); } function initDirtyTracking() { document.addEventListener('input', function (event) { var target = event.target; if (!target || !target.form || isRegionEditorField(target)) { return; } markFormDirty(target.form); }, true); document.addEventListener('change', function (event) { var target = event.target; if (!target || !target.form || isRegionEditorField(target)) { return; } markFormDirty(target.form); }, true); } function clampLimitedInputValue(input) { if (!input || !input.matches || !input.matches('input[data-limit-input-value]')) { return; } var minAttr = input.getAttribute('min'); var maxAttr = input.getAttribute('max'); var minValue = minAttr === null || minAttr === '' ? null : Number(minAttr); var maxValue = maxAttr === null || maxAttr === '' ? null : Number(maxAttr); var rawValue = String(input.value || '').trim(); if (!rawValue) { return; } var numericValue = Number(rawValue); if (!Number.isFinite(numericValue)) { return; } if (Number.isFinite(minValue) && numericValue < minValue) { input.value = String(minValue); return; } if (Number.isFinite(maxValue) && numericValue > maxValue) { input.value = String(maxValue); } } function initLimitedInputValues() { document.addEventListener('input', function (event) { clampLimitedInputValue(event.target); }, true); document.addEventListener('change', function (event) { clampLimitedInputValue(event.target); }, true); document.addEventListener('paste', function (event) { var target = event.target; if (!target || !target.matches || !target.matches('input[data-limit-input-value]')) { return; } window.setTimeout(function () { clampLimitedInputValue(target); }, 0); }, true); } function clampLimitedTextValue(input) { if (!input || !input.matches || !input.matches('input[data-limit-text-length], textarea[data-limit-text-length]')) { return; } var maxLength = Number(input.getAttribute('maxlength')); if (!Number.isFinite(maxLength) || maxLength < 1) { return; } var value = String(input.value || ''); if (value.length > maxLength) { input.value = value.slice(0, maxLength); } } function initLimitedTextInputValues() { document.addEventListener('input', function (event) { clampLimitedTextValue(event.target); }, true); document.addEventListener('change', function (event) { clampLimitedTextValue(event.target); }, true); document.addEventListener('paste', function (event) { var target = event.target; if (!target || !target.matches || !target.matches('input[data-limit-text-length], textarea[data-limit-text-length]')) { return; } window.setTimeout(function () { clampLimitedTextValue(target); }, 0); }, true); } function initCancelConfirm() { document.addEventListener('click', function (event) { var cancelTarget = event.target.closest('[data-confirm-unsaved]'); if (!cancelTarget) { return; } var form = cancelTarget.form || cancelTarget.closest('form') || document.querySelector('form[data-dirty="true"]'); if (!isFormDirty(form)) { return; } var message = cancelTarget.getAttribute('data-confirm-unsaved') || 'You have unsaved changes. Leave this page?'; if (!window.confirm(message)) { event.preventDefault(); event.stopPropagation(); } }, true); } function parseAsyncSaveResponseMessage(responseText) { var savedMessage = ''; try { var responseDocument = new DOMParser().parseFromString(responseText || '', 'text/html'); var toastBody = responseDocument.querySelector('.toast-body'); if (toastBody && toastBody.textContent) { savedMessage = toastBody.textContent.trim(); } } catch (_error) { savedMessage = ''; } return savedMessage; } function parseAsyncSaveErrorMessage(responseText) { var errorMessage = parseAsyncSaveResponseMessage(responseText); if (errorMessage) { return errorMessage; } return String(responseText || '').trim(); } function isWarningSaveError(error) { var status = Number(error && error.status); if (status === 400 || status === 422) { return true; } return /region names must be unique/i.test(String(error && error.message || '')); } function getAsyncSaveActionField(form) { if (!form || !form.querySelector) { return null; } return form.querySelector('input[type="hidden"][name="save_action"]') || form.querySelector('input[type="hidden"][name="action"]'); } function isLoginRedirect(response) { if (!response || !response.redirected || !response.url) { return false; } try { return new URL(response.url, window.location.href).pathname === '/login'; } catch (_error) { return /\/login(?:[?#]|$)/.test(String(response.url || '')); } } async function submitAsyncSaveForm(form, event, options) { var settings = options || {}; if (!form || !form.hasAttribute || !form.hasAttribute('data-async-save')) { return false; } if (form.dataset && form.dataset.busy === 'true') { if (event) { event.preventDefault(); } return false; } var message = form.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { if (event) { event.preventDefault(); } return false; } if (event) { event.preventDefault(); } form.dataset.busy = 'true'; try { if (typeof settings.beforeSubmit === 'function') { var beforeResult = await settings.beforeSubmit(form, event); if (beforeResult === false) { return false; } } var hiddenSaveAction = getAsyncSaveActionField(form); var formData = new FormData(form); var submitterValue = String((hiddenSaveAction && hiddenSaveAction.value) || form.dataset.submitterValue || '').trim().toLowerCase(); if (event && event.submitter && event.submitter.name) { submitterValue = String(event.submitter.value || '').trim().toLowerCase(); formData.set(event.submitter.name, event.submitter.value || ''); } var hasFileValue = false; formData.forEach(function (value) { if (value && typeof value === 'object' && typeof value.name === 'string') { hasFileValue = true; } }); var isMultipart = hasFileValue || String(form.enctype || '').toLowerCase() === 'multipart/form-data'; var body = isMultipart ? formData : new URLSearchParams(); if (!isMultipart) { formData.forEach(function (value, key) { body.append(key, value); }); } var response = await fetch(form.action, { method: (form.method || 'POST').toUpperCase(), headers: Object.assign({ 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html, application/json, text/plain, */*' }, isMultipart ? {} : { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }), body: isMultipart ? body : body.toString(), credentials: 'same-origin' }); if (!response.ok) { var responseText = await response.text(); var error = new Error(parseAsyncSaveErrorMessage(responseText) || (settings.errorMessage || 'Unable to save changes.')); error.status = response.status; throw error; } if (isLoginRedirect(response)) { window.location.replace(response.url || '/login'); return true; } var responseText = await response.text(); var responseDocument = null; try { responseDocument = new DOMParser().parseFromString(responseText || '', 'text/html'); } catch (_error) { responseDocument = null; } if (typeof settings.afterSuccess === 'function') { await settings.afterSuccess({ form: form, response: response, responseText: responseText, responseDocument: responseDocument, submitterValue: submitterValue }); } try { form.dispatchEvent(new CustomEvent('web-async-save:success', { bubbles: true, detail: { form: form, response: response, responseText: responseText, responseDocument: responseDocument, submitterValue: submitterValue } })); } catch (_error) { // Ignore event dispatch failures and continue the save flow. } clearFormDirty(form); if (submitterValue === 'close' || submitterValue === 'new') { var redirectUrl = submitterValue === 'close' ? String(response.url || form.dataset.asyncSaveCloseUrl || window.location.href) : String(response.url || form.dataset.asyncSaveNewUrl || window.location.href); window.location.replace(redirectUrl); return true; } if (form.dataset && form.dataset.asyncSaveNewRedirect === 'response-url') { window.location.replace(String(response.url || form.dataset.asyncSaveNewUrl || window.location.href)); return true; } var successMessage = typeof settings.getSuccessMessage === 'function' ? settings.getSuccessMessage({ form: form, response: response, responseText: responseText, responseDocument: responseDocument, submitterValue: submitterValue }) : parseAsyncSaveResponseMessage(responseText); var resolvedSuccessMessage = successMessage || String(settings.fallbackSuccessMessage || 'Saved.'); if (form.dataset && form.dataset.asyncSaveRefreshPage === 'true') { if (typeof window.queueToastAfterRefresh === 'function') { window.queueToastAfterRefresh(resolvedSuccessMessage, 'success'); } else if (typeof showToast === 'function') { showToast(resolvedSuccessMessage, 'success'); } window.setTimeout(function () { window.location.reload(); }, 1000); return true; } showToast(resolvedSuccessMessage, 'success'); return true; } catch (error) { if (typeof showToast === 'function') { var variant = isWarningSaveError(error) ? 'warning' : 'danger'; showToast(error && error.message ? error.message : (settings.errorMessage || 'Unable to save changes.'), variant); } else { window.alert(error && error.message ? error.message : (settings.errorMessage || 'Unable to save changes.')); } return false; } finally { delete form.dataset.busy; delete form.dataset.submitterValue; if (typeof settings.afterFinally === 'function') { settings.afterFinally(form); } var hiddenInput = getAsyncSaveActionField(form); if (hiddenInput) { hiddenInput.value = ''; } } } window.webAsyncSaveForm = window.webAsyncSaveForm || {}; window.webAsyncSaveForm.submit = submitAsyncSaveForm; window.webAsyncSaveForm.parseResponseMessage = parseAsyncSaveResponseMessage; function initAsyncCommandForms() { function getAnnouncementActionState(actionPath) { var isPlay = /\/play$/.test(actionPath); return { nextActionPath: actionPath.replace(/\/(?:play|stop)$/, isPlay ? '/stop' : '/play'), nextLabel: isPlay ? 'Stop' : 'Play', nextIcon: isPlay ? 'bi-stop-fill' : 'bi-play-fill', nextClassName: isPlay ? 'btn-outline-warning' : 'btn-outline-success', nextConfirmMessage: isPlay ? 'Stop this announcement on the selected screens now?' : 'Send this announcement to the selected screens now?' }; } function updateAnnouncementActionButton(form, actionPath) { if (!form) { return false; } var state = getAnnouncementActionState(actionPath); var visibleButton = form.querySelector('button[type="submit"]') || (form.id ? document.querySelector('button[form="' + form.id + '"]') : null); if (!visibleButton) { return false; } form.action = state.nextActionPath; form.setAttribute('data-confirm-message', state.nextConfirmMessage); visibleButton.className = visibleButton.className.replace(/btn-outline-(success|warning)/g, state.nextClassName); visibleButton.innerHTML = '' + state.nextLabel; return true; } function updateFontToggleRow(form) { if (!form) { return false; } var row = form.closest ? form.closest('tr[data-font-toggle-row]') : null; if (!row) { return false; } var enabledInput = form.querySelector('input[name="enabled"]'); var toggleButton = form.querySelector('button[type="submit"]'); var statusBadge = row.querySelector('[data-font-status-badge]'); var isCurrentlyEnabled = String(row.getAttribute('data-font-enabled') || '').trim() === 'true'; var willEnable = !isCurrentlyEnabled; if (enabledInput) { enabledInput.value = isCurrentlyEnabled ? '0' : '1'; } if (toggleButton) { toggleButton.textContent = willEnable ? 'Disable' : 'Enable'; } if (statusBadge) { statusBadge.className = statusBadge.className.replace(/text-bg-(success|secondary)/g, willEnable ? 'text-bg-success' : 'text-bg-secondary'); statusBadge.textContent = willEnable ? 'Enabled' : 'Disabled'; } row.setAttribute('data-font-enabled', willEnable ? 'true' : 'false'); return true; } document.addEventListener('submit', function (event) { var form = event.target; if (!form || !form.hasAttribute || !form.hasAttribute('data-async-command')) { return; } if (form.dataset && form.dataset.busy === 'true') { event.preventDefault(); return; } var message = form.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { event.preventDefault(); return; } var actionPath = ''; try { actionPath = new URL(form.action, window.location.href).pathname; } catch (_error) { actionPath = String(form.action || ''); } var shouldReloadAfterSuccess = /^\/announcements\/\d+\/(?:play|stop)$/.test(actionPath); event.preventDefault(); form.dataset.busy = 'true'; var formData = new FormData(form); var body = new URLSearchParams(); formData.forEach(function (value, key) { body.append(key, value); }); fetch(form.action, { method: (form.method || 'POST').toUpperCase(), headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' }, body: body.toString(), credentials: 'same-origin' }).then(function (response) { if (!response || Number(response.status) >= 400) { return; } if (/^\/settings\/fonts\/[^/]+\/toggle$/.test(actionPath)) { updateFontToggleRow(form); return; } if (/^\/data-sources\/(?:api-sources|rss-feeds|weather)\/\d+$/.test(actionPath)) { updateDataSourceToggle(form, response); return; } if (shouldReloadAfterSuccess && response && response.ok) { window.location.reload(); return; } }).finally(function () { delete form.dataset.busy; }); }, true); } function initAsyncSaveForms() { var refreshSequence = 0; function setSaveActionValue(form, value) { if (!form) { return; } var hiddenInput = getAsyncSaveActionField(form); if (!hiddenInput) { hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = 'save_action'; form.appendChild(hiddenInput); } hiddenInput.value = String(value || '').trim().toLowerCase(); } function getResponseQueryValue(responseUrl, key) { try { var url = new URL(responseUrl, window.location.href); return String(url.searchParams.get(key) || '').trim(); } catch (_error) { return ''; } } function rebindRefreshTarget(targetElement) { if (!targetElement) { return; } if (typeof window.initJsonTogglePanels === 'function') { window.initJsonTogglePanels(targetElement); } if (typeof window.initLocalDateTimes === 'function') { window.initLocalDateTimes(targetElement); } if (typeof window.initTableSearches === 'function') { window.initTableSearches(targetElement); } if (targetElement.id === 'playlist-items-body' && typeof window.rebindPlaylistTableDrag === 'function') { window.rebindPlaylistTableDrag(targetElement); } } function replaceRefreshTargetFromPage(refreshTargetSelector, sequenceId) { if (sequenceId !== refreshSequence) { return; } fetch(window.location.href, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html, application/xhtml+xml' }, credentials: 'same-origin', cache: 'no-store' }).then(function (response) { if (!response.ok) { throw new Error('Unable to refresh saved data.'); } return response.text(); }).then(function (text) { if (sequenceId !== refreshSequence) { return; } var responseDocument = new DOMParser().parseFromString(text || '', 'text/html'); var currentTarget = document.querySelector(refreshTargetSelector); var nextTarget = responseDocument.querySelector(refreshTargetSelector); if (!currentTarget || !nextTarget) { return; } currentTarget.outerHTML = nextTarget.outerHTML; rebindRefreshTarget(document.querySelector(refreshTargetSelector)); }).catch(function () { // Ignore refresh replacement failures and leave the existing content in place. }); } function watchRefreshTask(refreshStateUrl, refreshTaskId, refreshTargetSelector, sequenceId) { var pollDelayMs = 1000; var maxAttempts = 60; function poll(attempt) { if (sequenceId !== refreshSequence) { return; } var stateUrl; try { stateUrl = new URL(refreshStateUrl, window.location.href); } catch (_error) { return; } stateUrl.searchParams.set('refresh_task_id', refreshTaskId); fetch(stateUrl.toString(), { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' }, credentials: 'same-origin', cache: 'no-store' }).then(function (response) { if (!response.ok) { throw new Error('Unable to check refresh status.'); } return response.json(); }).then(function (payload) { if (sequenceId !== refreshSequence) { return; } var status = String(payload && payload.status || '').trim().toLowerCase(); if (status === 'queued' || status === 'running') { if (attempt < maxAttempts) { window.setTimeout(function () { poll(attempt + 1); }, pollDelayMs); } return; } replaceRefreshTargetFromPage(refreshTargetSelector, sequenceId); }).catch(function () { if (attempt < maxAttempts) { window.setTimeout(function () { poll(attempt + 1); }, pollDelayMs); } }); } poll(0); } document.addEventListener('click', function (event) { var target = event.target; if (!target || !target.closest) { return; } var refreshButton = target.closest('[data-manual-refresh-url]'); if (refreshButton) { event.preventDefault(); if (refreshButton.disabled) { return; } refreshButton.disabled = true; fetch(refreshButton.getAttribute('data-manual-refresh-url'), { method: 'POST', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(function (response) { if (!response.ok) { throw new Error('Refresh failed'); } window.location.assign(response.url); }).catch(function () { refreshButton.disabled = false; }); return; } var button = target.closest('button[name="save_action"], button[name="action"]'); if (!button) { return; } var form = button.form || button.closest('form'); if (!form || !form.hasAttribute || !form.hasAttribute('data-async-save')) { return; } setSaveActionValue(form, button.value || ''); form.dataset.submitterValue = String(button.value || '').trim().toLowerCase(); }, true); document.addEventListener('submit', function (event) { var form = event.target; if (!form || !form.hasAttribute || !form.hasAttribute('data-async-save')) { return; } submitAsyncSaveForm(form, event, { fallbackSuccessMessage: 'Saved.', afterSuccess: function (context) { var refreshTargetSelector = String(form.getAttribute('data-async-save-refresh-target') || '').trim(); var refreshStateUrl = String(form.getAttribute('data-async-save-refresh-state-url') || '').trim(); var refreshTaskId = getResponseQueryValue(context.response.url || '', 'refresh_task_id'); if (refreshTargetSelector && refreshStateUrl && refreshTaskId) { refreshSequence += 1; watchRefreshTask(refreshStateUrl, refreshTaskId, refreshTargetSelector, refreshSequence); } else if (refreshTargetSelector && context.responseDocument) { var currentTarget = document.querySelector(refreshTargetSelector); var nextTarget = context.responseDocument.querySelector(refreshTargetSelector); if (currentTarget && nextTarget) { currentTarget.outerHTML = nextTarget.outerHTML; rebindRefreshTarget(document.querySelector(refreshTargetSelector)); } } } }); }, true); } function initSubmitOnChange() { var fields = document.querySelectorAll('[data-submit-on-change]'); Array.prototype.forEach.call(fields, function (field) { field.addEventListener('change', function () { var form = field.form || field.closest('form'); if (form) { form.submit(); } }); }); } function renderJsonPrimitive(value) { if (value === null) { return 'null'; } if (value === true || value === false) { return '' + String(value) + ''; } if (typeof value === 'number') { return '' + escapeHtml(String(value)) + ''; } return '"' + escapeHtml(String(value)) + '"'; } function renderJsonNode(key, value, isRoot) { var isArray = Array.isArray(value); var isObject = value && typeof value === 'object' && !isArray; if (!isArray && !isObject) { var primitiveLabel = isRoot ? 'value' : String(key); return '
' + escapeHtml(primitiveLabel) + ':' + renderJsonPrimitive(value) + '
'; } var entries = isArray ? value.map(function (entry, index) { return renderJsonNode('[' + index + ']', entry, false); }).join('') : Object.keys(value).map(function (childKey) { return renderJsonNode(childKey, value[childKey], false); }).join(''); var summaryLabel = isRoot ? (isArray ? 'Array' : 'Object') : String(key); var summaryMeta = isArray ? '[' + value.length + ']' : '{' + Object.keys(value).length + '}'; return '' + '
' + '' + escapeHtml(summaryLabel) + ':' + escapeHtml(summaryMeta) + '' + '
' + entries + '
' + '
'; } function renderJsonTree(value) { if (Array.isArray(value) || (value && typeof value === 'object')) { return renderJsonNode('', value, true); } return renderJsonNode('value', value, true); } function initJsonTogglePanels(root) { var scope = root && root.querySelectorAll ? root : document; var panels = scope.querySelectorAll('[data-json-toggle-panel]'); Array.prototype.forEach.call(panels, function (panel) { var output = panel.querySelector('[data-json-toggle-output]'); if (!output) { return; } var card = panel.closest ? panel.closest('.card') : null; var collapseButton = card ? card.querySelector('[data-json-toggle-collapse-all]') : null; var expandButton = card ? card.querySelector('[data-json-toggle-expand-all]') : null; var sourceNode = panel.querySelector('[data-json-toggle-source]'); var rawJson = ''; try { rawJson = JSON.parse(String(sourceNode ? sourceNode.textContent : '""')); } catch (_error) { rawJson = ''; } if (typeof rawJson !== 'string' || !rawJson.trim()) { if (collapseButton) { collapseButton.classList.add('d-none'); } if (expandButton) { expandButton.classList.add('d-none'); } return; } var parsedJson; try { parsedJson = JSON.parse(rawJson); } catch (_error) { if (collapseButton) { collapseButton.classList.add('d-none'); } if (expandButton) { expandButton.classList.add('d-none'); } return; } var formattedJson = JSON.stringify(parsedJson, null, 2); var formattedTree = renderJsonTree(parsedJson); function setAllSectionsExpanded(expanded) { var details = output.querySelectorAll('details'); Array.prototype.forEach.call(details, function (detail) { detail.open = 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.disabled = !details.length || allCollapsed; collapseButton.setAttribute('aria-pressed', String(allCollapsed)); } if (expandButton) { 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(); if (collapseButton) { collapseButton.addEventListener('click', function () { setAllSectionsExpanded(false); syncButtons(); }); } if (expandButton) { expandButton.addEventListener('click', function () { setAllSectionsExpanded(true); syncButtons(); }); } }); } function initLocalDateTimes(root) { var scope = root && root.querySelectorAll ? root : document; var elements = scope.querySelectorAll('[data-local-datetime]'); Array.prototype.forEach.call(elements, function (element) { var rawValue = String(element.getAttribute('datetime') || element.getAttribute('data-local-datetime') || element.textContent || '').trim(); if (!rawValue) { return; } var date = new Date(rawValue); if (Number.isNaN(date.getTime())) { return; } element.textContent = formatDashboardDate(date, element.getAttribute('data-local-datetime-format') === '24h'); }); } function initTableSearches(root) { if (typeof window.initTableSearches === 'function') { window.initTableSearches(root); } } 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); } }); }); } initConfirmForms(); initDeleteButtons(); initDirtyTracking(); initLimitedInputValues(); initLimitedTextInputValues(); initCancelConfirm(); initAsyncCommandForms(); initAsyncSaveForms(); initSubmitOnChange(); initJsonTogglePanels(); initLocalDateTimes(); initTableSearches(); attachSlideThumbFallbacks(document); window.initJsonTogglePanels = initJsonTogglePanels; window.initLocalDateTimes = initLocalDateTimes; window.clampLimitedInputValue = clampLimitedInputValue; window.initLimitedInputValues = initLimitedInputValues; window.clampLimitedTextValue = clampLimitedTextValue; window.initLimitedTextInputValues = initLimitedTextInputValues; window.createSlideThumbPlaceholder = createSlideThumbPlaceholder; window.attachSlideThumbFallbacks = attachSlideThumbFallbacks; }());