// Shared media library picker for editor forms. (function () { var modal = document.getElementById('media-picker-modal'); if (!modal) { return; } var activeButton = null; var items = Array.prototype.slice.call(modal.querySelectorAll('[data-media-picker-item]')); var grid = modal.querySelector('[data-media-picker-grid]'); var search = modal.querySelector('[data-media-picker-search]'); var sort = modal.querySelector('[data-media-picker-sort]'); var noResults = modal.querySelector('[data-media-picker-no-results]'); var loadMoreWrap = modal.querySelector('[data-media-picker-load-more-wrap]'); var loadMoreButton = modal.querySelector('[data-media-picker-load-more]'); var confirmButton = modal.querySelector('[data-media-picker-confirm]'); var cancelButton = modal.querySelector('[data-media-picker-cancel]'); var uploadButton = modal.querySelector('[data-media-picker-upload]'); var currentPath = ''; var pendingPath = ''; var pendingType = ''; var visibleRowLimit = 4; var loadedAssets = []; var mediaOffset = 0; var mediaHasMore = false; var refreshSequence = 0; function getColumnsPerRow() { return window.innerWidth >= 992 ? 4 : window.innerWidth >= 768 ? 3 : 2; } function renderItems(assets) { if (!grid) { return; } grid.innerHTML = (Array.isArray(assets) ? assets : []).map(function (asset) { var type = String(asset.media_type || ''); var path = escapeHtml(asset.media_path); var name = escapeHtml(asset.original_name); var createdAt = escapeHtml(asset.created_at); var preview = type === 'video' ? '' : ''; return '
' + '
'; }).join(''); items = Array.prototype.slice.call(modal.querySelectorAll('[data-media-picker-item]')); } function getQueryParams(offset, limit) { var params = new URLSearchParams(); var type = activeButton ? activeButton.getAttribute('data-media-picker-type') || 'image' : 'image'; params.set('offset', String(offset)); params.set('limit', String(limit)); params.set('search', String(search && search.value || '').trim()); params.set('type', type); params.set('sort', sort && sort.value || 'date-desc'); return params.toString(); } async function refreshItems() { var requestSequence = ++refreshSequence; try { var response = await fetch('/media-library/assets?' + getQueryParams(0, 4 * getColumnsPerRow()), { credentials: 'same-origin', headers: { Accept: 'application/json' } }); if (!response.ok) { return; } var payload = await response.json(); if (requestSequence !== refreshSequence) { return; } loadedAssets = Array.isArray(payload.assets) ? payload.assets : []; mediaOffset = Number(payload.nextOffset || loadedAssets.length); mediaHasMore = Boolean(payload.hasMore); renderItems(loadedAssets); updateSelection(); applyFilterAndSort(); } catch (_error) { // Keep the server-rendered list if refreshing is unavailable. } } function escapeHtml(value) { return String(value || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } function showModal() { if (window.pulseModal && typeof window.pulseModal.show === 'function') { window.pulseModal.show(modal); } else if (window.bootstrap && window.bootstrap.Modal) { window.bootstrap.Modal.getOrCreateInstance(modal).show(); } } function hideModal() { if (window.pulseModal && typeof window.pulseModal.hide === 'function') { window.pulseModal.hide(modal); } else if (window.bootstrap && window.bootstrap.Modal) { window.bootstrap.Modal.getOrCreateInstance(modal).hide(); } } function applyFilterAndSort() { var visibleItems = items.slice(); var columnsPerRow = getColumnsPerRow(); var visibleItemLimit = visibleRowLimit * columnsPerRow; items.forEach(function (item) { item.hidden = true; }); visibleItems.slice(0, visibleItemLimit).forEach(function (item) { item.hidden = false; if (grid) grid.appendChild(item); }); if (noResults) noResults.hidden = visibleItems.length > 0; if (loadMoreWrap) loadMoreWrap.hidden = !mediaHasMore || visibleItems.length === 0; } function resetVisibleItemLimit() { visibleRowLimit = 4; if (activeButton) { refreshItems(); } } function updateSelection() { items.forEach(function (item) { var itemButton = item.querySelector('[data-media-picker-path]'); var path = itemButton && itemButton.getAttribute('data-media-picker-path'); var current = Boolean(currentPath && path === currentPath); var selected = Boolean(pendingPath && path === pendingPath); item.classList.toggle('media-picker-item-current-card', current); item.classList.toggle('media-picker-item-selected-card', selected); if (itemButton) { itemButton.setAttribute('aria-pressed', selected ? 'true' : 'false'); var currentLabel = itemButton.querySelector('[data-media-picker-current]'); if (currentLabel) currentLabel.hidden = !current; var selectedLabel = itemButton.querySelector('[data-media-picker-selected]'); if (selectedLabel) selectedLabel.hidden = !selected; } }); } function getActiveHidden() { var hiddenKey = activeButton && activeButton.getAttribute('data-media-picker-hidden'); return hiddenKey && (document.getElementById(hiddenKey) || document.querySelector('[name="' + hiddenKey + '"]')); } function closePicker() { activeButton = null; currentPath = ''; pendingPath = ''; pendingType = ''; hideModal(); } function getActiveFileInput() { if (!activeButton) { return null; } var hiddenKey = activeButton.getAttribute('data-media-picker-hidden'); if (hiddenKey) { var inputFromHidden = document.getElementById(hiddenKey.replace(/^existing_/, '')); if (inputFromHidden) { return inputFromHidden; } } var inputId = activeButton.getAttribute('for'); if (inputId) { return document.getElementById(inputId); } var region = activeButton.closest ? activeButton.closest('[data-region-id]') : null; return region ? region.querySelector('input[type="file"]') : null; } function updatePreview(hidden, path, type) { var region = hidden.closest ? hidden.closest('[data-region-id]') : null; if (region) { var previewBox = region.querySelector('.slide-image-region-preview-box'); if (previewBox) { var safePath = escapeHtml(path); previewBox.innerHTML = type === 'video' ? '
' : '
Selected media preview
'; } return; } var backgroundPreview = document.getElementById('background-preview'); var backgroundEmpty = document.getElementById('background-empty'); if (backgroundPreview && path) { backgroundPreview.src = path; backgroundPreview.style.display = 'block'; if (backgroundEmpty) backgroundEmpty.style.display = 'none'; } } document.addEventListener('click', function (event) { var uploadTrigger = event.target && event.target.closest ? event.target.closest('[data-media-picker-upload]') : null; if (uploadTrigger) { event.preventDefault(); var fileInput = getActiveFileInput(); if (fileInput) { fileInput.addEventListener('click', function (inputEvent) { inputEvent.stopPropagation(); }, { once: true }); fileInput.click(); closePicker(); } return; } var button = event.target && event.target.closest ? event.target.closest('[data-media-picker]') : null; if (button) { event.preventDefault(); activeButton = button; var hidden = getActiveHidden(); currentPath = hidden ? String(hidden.value || '') : ''; pendingPath = currentPath; pendingType = button.getAttribute('data-media-picker-type') || 'image'; if (search) search.value = ''; if (sort) sort.value = 'date-desc'; visibleRowLimit = 4; updateSelection(); applyFilterAndSort(); showModal(); refreshItems(); return; } var itemButton = event.target && event.target.closest ? event.target.closest('[data-media-picker-path]') : null; if (!itemButton || !activeButton) { return; } pendingPath = itemButton.getAttribute('data-media-picker-path') || ''; pendingType = itemButton.getAttribute('data-media-picker-type') || activeButton.getAttribute('data-media-picker-type') || 'image'; updateSelection(); }); if (search) search.addEventListener('input', resetVisibleItemLimit); if (sort) sort.addEventListener('change', resetVisibleItemLimit); if (loadMoreButton) loadMoreButton.addEventListener('click', async function () { loadMoreButton.disabled = true; try { var response = await fetch('/media-library/assets?' + getQueryParams(mediaOffset, 4 * getColumnsPerRow()), { credentials: 'same-origin', headers: { Accept: 'application/json' } }); if (!response.ok) { return; } var payload = await response.json(); loadedAssets = loadedAssets.concat(Array.isArray(payload.assets) ? payload.assets : []); mediaOffset = Number(payload.nextOffset || mediaOffset); mediaHasMore = Boolean(payload.hasMore); renderItems(loadedAssets); visibleRowLimit += 4; updateSelection(); applyFilterAndSort(); } finally { loadMoreButton.disabled = false; } }); if (confirmButton) confirmButton.addEventListener('click', function () { var hidden = getActiveHidden(); if (!hidden || !pendingPath) { closePicker(); return; } hidden.value = pendingPath; updatePreview(hidden, pendingPath, pendingType); hidden.dispatchEvent(new Event('change', { bubbles: true })); closePicker(); }); if (cancelButton) cancelButton.addEventListener('click', closePicker); modal.addEventListener('hidden.bs.modal', function () { activeButton = null; currentPath = ''; pendingPath = ''; pendingType = ''; }); })();