/** * Media editor - file upload, URL entry, media library browser, and save UI * * Reads (via globals): * SFE.Context - .activeEditor (r/w), .activeMode (r/w), * .draftEditState, .actionBar, .buttonManager * SFE.PositionManager - .positionFloatingElements * SFE.FocusManager - .createFocusManager * SFE.Api - .apiCall * SFE.MediaHelper - (MediaHelper global) * SFE.MediaLibraryCache - (mediaLibraryCache global) * SFE.OverlayManager * SFE.LifecycleHelpers - .createFadeHandler, .setupDraftPreviewLifecycle * SFE.handleInlineSave - set by SaveManager * SFE.closeDraftPreview - set by DraftManager * SFE.ManagerData - .iconLibraryUrl, .mediaLibraryUrl, .restBase, * .restUrl, .nonce, .postId * * Exposes: SFE.MediaEditor { startMediaEditing, startSchemaComponentEditing } */ (function() { 'use strict'; window.MWP = window.MWP || {}; window.MWP.SFE = window.MWP.SFE || {}; const SFE = window.MWP.SFE; SFE.ManagerData = SFE.ManagerData || {}; function showToolbar(toolbar) { if (!toolbar) return; if (toolbar._mwpSwitchHideTimeout) { clearTimeout(toolbar._mwpSwitchHideTimeout); delete toolbar._mwpSwitchHideTimeout; } toolbar.classList.remove('mwp-sfe-closing'); toolbar.style.display = ''; } function ensureToolbarContainer(editorState) { if (!editorState) return null; let toolbarContainer = editorState.toolbarContainer || null; if (!toolbarContainer) { toolbarContainer = document.createElement('div'); toolbarContainer.className = 'mwp-sfe-inline-toolbar mwp-sfe-inline-editor'; toolbarContainer.setAttribute('data-mwp-sfe-control', 'true'); toolbarContainer.style.pointerEvents = 'auto'; toolbarContainer.style.display = 'none'; document.body.appendChild(toolbarContainer); editorState.toolbarContainer = toolbarContainer; } return toolbarContainer; } function bindInlineEditButtons(editorState) { const actionsContainer = editorState?.actionsContainer || null; if (!actionsContainer) return; if (actionsContainer._saveBtn) { actionsContainer._saveBtn.addEventListener('click', (event) => { event.preventDefault(); event.stopImmediatePropagation(); SFE.handleInlineSave(editorState); }); } if (actionsContainer._cancelBtn) { actionsContainer._cancelBtn.addEventListener('click', (event) => { event.preventDefault(); event.stopImmediatePropagation(); SFE.closeInPlaceEditor(editorState, true); }); } } function restoreInlineEditButtons(editorState) { const ctx = SFE.Context; const actionBar = ctx?.actionBar; const buttonManager = ctx?.buttonManager; const actionsContainer = editorState?.actionsContainer || null; if (!actionBar || !buttonManager || !actionsContainer) return; actionBar.updateState({ bar: actionsContainer, element: editorState.element, state: 'edit', content: buttonManager.getEditButtons(), }); bindInlineEditButtons(editorState); } function cloneAttributeChanges(attributeChanges) { return attributeChanges && typeof attributeChanges === 'object' ? { ...attributeChanges } : {}; } function createMediaToolbarHost(config) { const { editorState, component, toolbarContainer, formats, getMediaElement, setMediaElement, reposition, serializeState, applySerializedState, showMediaReplaceUI, } = config; const historyApi = typeof editorState?.getSessionHistoryApi === 'function' ? editorState.getSessionHistoryApi('media') : null; const host = { element: getMediaElement(), formats: Array.isArray(formats) ? formats : [], options: { ...(component?.editorOptions && typeof component.editorOptions === 'object' ? component.editorOptions : {}), toolbarContainer, blockRootElement: editorState?.element || null, }, attributeChanges: editorState.attributeChanges && typeof editorState.attributeChanges === 'object' ? editorState.attributeChanges : (editorState.attributeChanges = {}), toolbarManager: null, attachToolbarManager(manager) { this.toolbarManager = manager; }, detachToolbarManager(manager) { if (this.toolbarManager === manager) { this.toolbarManager = null; } }, isSelectionInEditor() { return false; }, getCurrentListItem() { return null; }, getParentList() { return null; }, canIndentListItem() { return false; }, canOutdentListItem() { return false; }, canUndo() { return !!historyApi?.canUndo?.(); }, canRedo() { return !!historyApi?.canRedo?.(); }, updateToolbarState() { if (this.toolbarManager && typeof this.toolbarManager.updateToolbarState === 'function') { this.toolbarManager.updateToolbarState(); } }, updateUndoRedoButtons() { if (this.toolbarManager && typeof this.toolbarManager.updateUndoRedoButtons === 'function') { this.toolbarManager.updateUndoRedoButtons(); } }, saveToHistory() { historyApi?.saveToHistory?.(); }, undo() { historyApi?.undo?.(); }, redo() { historyApi?.redo?.(); }, setElement(element) { this.element = element; setMediaElement(element); }, showMediaReplaceUI() { if (typeof showMediaReplaceUI === 'function') { showMediaReplaceUI(); } }, getBlockRootElement() { return editorState?.element || null; }, scheduleFloatingElementsPositionAfterLayout() { requestAnimationFrame(() => { requestAnimationFrame(() => { reposition(); }); }); } }; return SFE.SchemaEditorHost.attachHostContract(host); } // ─── Pure / stateless helpers ───────────────────────────────────────────── /** * Reposition the action bar once a media element has finished loading, or * immediately when it is already in a ready state. * Handles both (.complete) and / (.readyState >= 2). */ function repositionAfterMediaLoad(mediaEl, element, toolbarContainer, actionsContainer, positionFloatingElements) { const reposition = () => { if (!element) return; positionFloatingElements(element, toolbarContainer || null, actionsContainer); }; const tagName = mediaEl?.tagName ? mediaEl.tagName.toUpperCase() : ''; const waitsForLoad = (tagName === 'IMG' || tagName === 'VIDEO' || tagName === 'AUDIO'); if (!mediaEl || !waitsForLoad || mediaEl.complete || mediaEl.readyState >= 2) { reposition(); } else { mediaEl.addEventListener('load', reposition, { once: true }); mediaEl.addEventListener('loadeddata', reposition, { once: true }); } } /** Return whether a MIME type is acceptable for the given block media type. */ function validateFileType(mimeType, expectedMediaType) { if (!mimeType) return true; // unknown type – let the server decide const IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml', 'image/bmp']; const AUDIO_TYPES = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/aac', 'audio/flac', 'audio/m4a']; const VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime', 'video/x-msvideo', 'video/avi']; const mime = mimeType.toLowerCase(); switch (expectedMediaType) { case 'image': return IMAGE_TYPES.includes(mime); case 'audio': return AUDIO_TYPES.includes(mime) || mime.startsWith('audio/'); case 'video': return VIDEO_TYPES.includes(mime) || mime.startsWith('video/'); case 'image_or_video': return IMAGE_TYPES.includes(mime) || VIDEO_TYPES.includes(mime) || mime.startsWith('image/') || mime.startsWith('video/'); case 'file': return true; default: return true; } } /** * Pause, detach src, and reload all / elements inside a * container. Called before clearing innerHTML to abort in-flight network * requests and release decode/buffer memory. */ function releaseMediaElements(container) { container.querySelectorAll('video, audio').forEach(m => { try { m.pause(); m.removeAttribute('src'); m.load(); } catch (e) { /* ignore */ } }); } /** Map the internal mediaType key to a human-readable display name. */ function getMediaTypeName(mediaType) { const names = { image: 'Image', video: 'Video', audio: 'Audio', file: 'File', image_or_video: 'Image or Video', icon: 'Icon' }; return names[mediaType] || (mediaType.charAt(0).toUpperCase() + mediaType.slice(1)); } // ─── UI builders ───────────────────────────────────────────────────────── /** * Build the drop-zone widget (SVG upload icon + helper text label). * * Returns { dropZone, iconWrap, textWrap } so callers can re-append * iconWrap/textWrap when resetting an error state without rebuilding the * whole widget from scratch. * * @param {string} mediaTypeName - Display name, e.g. "Image" * @returns {{ dropZone: HTMLElement, iconWrap: HTMLElement, textWrap: HTMLElement }} */ function buildDropZone(mediaTypeName) { const dropZone = document.createElement('div'); dropZone.className = 'mwp-sfe-media-upload-drop-zone'; dropZone.setAttribute('role', 'button'); dropZone.setAttribute('aria-label', `Upload ${mediaTypeName}, drag and drop or click to select`); const iconWrap = document.createElement('div'); iconWrap.className = 'mwp-sfe-upload-icon'; iconWrap.innerHTML = ` Upload `; const textWrap = document.createElement('div'); textWrap.className = 'mwp-sfe-upload-text'; textWrap.innerHTML = `Drag & Drop ${mediaTypeName} hereor Click to Upload`; dropZone.appendChild(iconWrap); dropZone.appendChild(textWrap); return { dropZone, iconWrap, textWrap }; } /** * Build the "input" panel: URL field, drop zone, and Upload / Browse / Cancel buttons. * * @param {object} config * @param {object} config.state - Shared mutable { url, file, attachmentId } * @param {string} config.mediaType * @param {string} config.mediaTypeName * @param {object} config.mediaDescriptor - Schema media descriptor for the active component. * @param {function} config.onUploadSuccess - (url: string, attachmentId: number|null) => void * @param {function} config.onBrowse - () => void * @param {function} config.onCancel - () => void * @param {function} config.maintainFocus - () => void keeps action bar focused * @param {boolean} config.isAttrsLoading - true when block attrs are still resolving * @returns {HTMLElement} */ function buildInputUI({ state, mediaType, mediaTypeName, mediaDescriptor, onUploadSuccess, onBrowse, onCancel, maintainFocus, isAttrsLoading = false }) { const container = document.createElement('div'); container.className = 'mwp-sfe-inline-media-editor'; container.addEventListener('click', e => e.stopPropagation()); // ── URL input ── const input = document.createElement('input'); input.type = 'text'; input.id = 'mwp-sfe-media-upload'; input.className = 'mwp-sfe-text-entry mwp-sfe-link-url-entry'; input.placeholder = `Enter ${mediaTypeName} URL...`; input.value = state.url; // ── Drop zone ── const { dropZone, iconWrap, textWrap } = buildDropZone(mediaTypeName); const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = SFE.MediaHelper.getAcceptTypes(mediaDescriptor); fileInput.style.display = 'none'; // ── Buttons ── const uploadBtn = document.createElement('button'); uploadBtn.className = 'mwp-sfe-btn mwp-sfe-btn-primary-inline'; uploadBtn.textContent = 'Upload'; uploadBtn.disabled = !state.url; uploadBtn.setAttribute('data-url-gated', 'true'); if (isAttrsLoading) { uploadBtn.disabled = true; uploadBtn.setAttribute('data-loading-attrs', 'true'); uploadBtn.setAttribute('mwp-sfe-btn-loading', 'true'); } const browseBtn = document.createElement('button'); browseBtn.className = 'mwp-sfe-btn mwp-sfe-btn-secondary-inline'; browseBtn.textContent = 'Browse Library'; const cancelBtn = document.createElement('button'); cancelBtn.className = 'mwp-sfe-btn mwp-sfe-btn-secondary-inline'; cancelBtn.textContent = 'Cancel'; const btnRow = document.createElement('div'); btnRow.className = 'mwp-sfe-inline-media-upload-actions'; btnRow.appendChild(uploadBtn); btnRow.appendChild(browseBtn); btnRow.appendChild(cancelBtn); container.appendChild(input); container.appendChild(dropZone); container.appendChild(fileInput); container.appendChild(btnRow); // ── Error display ── const handleUploadError = (msg) => { dropZone.classList.remove('mwp-sfe-is-uploading'); dropZone.textContent = 'Error: ' + msg; dropZone.style.borderColor = '#dc3232'; dropZone.style.borderStyle = 'solid'; input.disabled = false; input.focus(); setTimeout(() => { dropZone.innerHTML = ''; dropZone.appendChild(iconWrap); dropZone.appendChild(textWrap); dropZone.style.backgroundColor = ''; dropZone.style.borderColor = ''; dropZone.style.borderStyle = ''; dropZone.style.pointerEvents = ''; uploadBtn.disabled = !state.url; }, 3000); }; // Set the drop zone into "uploading" visual state const setUploadingState = () => { dropZone.classList.remove('mwp-sfe-is-dragging'); dropZone.classList.add('mwp-sfe-is-uploading'); dropZone.textContent = 'Uploading...'; dropZone.style.pointerEvents = 'none'; uploadBtn.disabled = true; input.disabled = true; }; // ── File handler ── const handleFile = async (file, fileName = null) => { if (!file) return; if (!validateFileType(file.type || '', mediaType)) { const expected = { image: 'an image', audio: 'an audio file', video: 'a video', image_or_video: 'an image or video' }[mediaType] || 'a valid file'; handleUploadError( `Please select ${expected}. The file you selected doesn't match the expected type.` ); dropZone.classList.remove('mwp-sfe-is-dragging'); uploadBtn.disabled = !state.url; return; } setUploadingState(); try { const formData = new FormData(); if (fileName) { formData.append('file', file, fileName); } else { formData.append('file', file); } const mediaLibraryUrl = String(SFE.ManagerData.mediaLibraryUrl || '').trim(); if (!mediaLibraryUrl) { throw new Error('Media Library URL is not configured.'); } const response = await fetch(mediaLibraryUrl, { method: 'POST', headers: { 'X-WP-Nonce': SFE.ManagerData.nonce }, body: formData }); if (!response.ok) throw new Error('Upload failed'); const media = await response.json(); dropZone.classList.remove('mwp-sfe-is-uploading'); state.url = media.source_url; state.attachmentId = media.id || null; input.value = state.url; dropZone.textContent = 'Upload Successful!'; dropZone.style.borderColor = '#00a32a'; dropZone.style.borderStyle = 'solid'; uploadBtn.disabled = false; SFE.MediaLibraryCache.invalidate(mediaType); input.disabled = false; maintainFocus(); setTimeout(() => onUploadSuccess(state.url, state.attachmentId), 500); } catch (err) { handleUploadError(err.message); } }; // ── URL-to-blob upload ── const handleUrlUpload = async () => { if (!state.url) return; setUploadingState(); try { const res = await fetch(state.url); if (!res.ok) throw new Error('Could not fetch media from URL'); const blob = await res.blob(); let filename = state.url.split('/').pop().split('?')[0]; if (!filename || !filename.includes('.')) { const ext = { image: 'jpg', audio: 'mp3', video: 'mp4', image_or_video: 'jpg' }[mediaType] || 'file'; filename = `${mediaType}-upload.${ext}`; } handleFile(blob, filename); } catch (err) { handleUploadError(err.message); } }; // ── Event bindings ── input.addEventListener('input', (e) => { state.url = e.target.value; if (uploadBtn.hasAttribute('data-loading-attrs')) { uploadBtn.disabled = true; return; } uploadBtn.disabled = !state.url.trim(); }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && state.url.trim()) { e.preventDefault(); handleUrlUpload(); } }); dropZone.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', (e) => handleFile(e.target.files[0])); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('mwp-sfe-is-dragging'); }); dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('mwp-sfe-is-dragging'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.style.background = '#444'; dropZone.style.borderColor = '#2196F3'; handleFile(e.dataTransfer.files[0]); }); uploadBtn.addEventListener('click', (e) => { e.preventDefault(); if (uploadBtn.hasAttribute('data-loading-attrs')) return; handleUrlUpload(); }); browseBtn.addEventListener('click', (e) => { e.preventDefault(); onBrowse(); }); cancelBtn.addEventListener('click', (e) => { e.preventDefault(); onCancel(); }); return container; } /** * Append media library thumbnail items to a grid element. * Called for each page load; does not clear existing items. * * @param {HTMLElement} grid * @param {Array} items - Array of media item objects * @param {string} mediaType * @param {function} onSelect - (url: string, attachmentId: number|null) => void * @param {function} maintainFocus */ function appendMediaItems(grid, items, mediaType, onSelect, maintainFocus) { const typeIcon = (mimeType) => { if (!mimeType) return '📄'; if (mimeType.startsWith('image/')) return '🖼️'; if (mimeType.startsWith('video/')) return '🎬'; if (mimeType.startsWith('audio/')) return '🎵'; return '📄'; }; items.forEach(item => { const itemEl = document.createElement('div'); itemEl.className = 'mwp-sfe-media-library-item'; itemEl.setAttribute('role', 'button'); itemEl.setAttribute('tabindex', '0'); itemEl.setAttribute('aria-label', `Select ${item.title || 'Untitled'}`); // Preview wrapper: icon placeholder sits underneath; thumbnail fades in on top. // The grid renders instantly with icons; thumbnails appear progressively. const previewWrap = document.createElement('div'); previewWrap.style.cssText = 'position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;' + 'background:#222;display:flex;align-items:center;justify-content:center;'; const iconLayer = document.createElement('div'); iconLayer.className = 'mwp-sfe-media-library-placeholder'; iconLayer.innerHTML = `${typeIcon(item.type)}`; previewWrap.appendChild(iconLayer); if (item.thumb) { const img = document.createElement('img'); img.alt = item.title || ''; img.loading = 'lazy'; // skip off-screen items, no wasted requests img.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;' + 'object-fit:cover;opacity:0;transition:opacity 0.2s ease;'; // Handlers set before src to never miss a synchronous cache-hit load img.onload = () => { img.style.opacity = '1'; iconLayer.style.transition = 'opacity 0.2s ease'; iconLayer.style.opacity = '0'; }; img.onerror = () => img.remove(); // icon stays visible on broken thumb img.src = item.thumb; previewWrap.appendChild(img); } itemEl.appendChild(previewWrap); if (item.title) { const title = document.createElement('div'); title.className = 'mwp-sfe-media-library-item-title'; title.textContent = item.title; itemEl.appendChild(title); } const selectMedia = () => { maintainFocus(); onSelect(item.url, item.id || null); }; itemEl.addEventListener('click', selectMedia); itemEl.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectMedia(); } }); grid.appendChild(itemEl); }); } /** * Build and show the media library browser inside actionsContainer. * Supports infinite scroll: loads 45 items at a time, fetching the next * page automatically as the user scrolls to the bottom of the grid. * * @param {object} config * @param {HTMLElement} config.actionsContainer * @param {string} config.mediaType * @param {string} config.mediaTypeName * @param {number} config.postId * @param {function} config.onBack * @param {function} config.onSelect - (url, attachmentId) => void * @param {function} config.reposition - () => void * @param {function} config.maintainFocus - () => void */ async function showMediaLibrary( { actionsContainer, mediaType, mediaTypeName, postId, onBack, onSelect, reposition, maintainFocus } ) { const { apiCall } = SFE.Api; const mediaLibraryCache = SFE.MediaLibraryCache; const PER_PAGE = 45; releaseMediaElements(actionsContainer); actionsContainer.innerHTML = ''; const container = document.createElement('div'); container.className = 'mwp-sfe-inline-media-library'; container.addEventListener('click', e => e.stopPropagation()); const header = document.createElement('div'); header.className = 'mwp-sfe-media-library-header'; header.textContent = `Select ${mediaTypeName} from the Library`; const grid = document.createElement('div'); grid.className = 'mwp-sfe-media-library-grid'; grid.innerHTML = 'Loading media library...'; // Sentinel sits at the very bottom of the grid (inside the scroll container). // IntersectionObserver fires when it is within 150px of the visible area, // giving the next page time to arrive before the user actually hits the edge. const sentinel = document.createElement('div'); sentinel.className = 'mwp-sfe-media-library-sentinel'; sentinel.style.cssText = 'height:1px;width:100%;grid-column:1/-1;'; // Loading indicator row rendered inside the grid while a fetch is in flight const loadingRow = document.createElement('div'); loadingRow.className = 'mwp-sfe-media-library-loading-row'; loadingRow.style.cssText = 'grid-column:1/-1;text-align:center;padding:12px 0;display:none;color:var(--mwp-sfe-text-muted,#aaa);font-size:12px;'; loadingRow.textContent = 'Loading more...'; const btnRow = document.createElement('div'); btnRow.className = 'mwp-sfe-inline-media-upload-actions'; const backBtn = document.createElement('button'); backBtn.className = 'mwp-sfe-btn mwp-sfe-btn-secondary-inline'; backBtn.textContent = 'Back'; backBtn.addEventListener('click', (e) => { e.preventDefault(); onBack(); }); btnRow.appendChild(backBtn); container.appendChild(header); container.appendChild(grid); container.appendChild(btnRow); actionsContainer.appendChild(container); maintainFocus(); requestAnimationFrame(reposition); // ── Cache structure stored per mediaType ────────────────────────── // { items: [...all items fetched so far], page: number, hasMore: boolean } // Re-opening the library restores the full previously-scrolled state // instantly from cache without hitting the server again. // invalidate() (called after an upload) wipes this so the next open // fetches fresh data. const cached = mediaLibraryCache.get(mediaType); let currentPage = cached ? cached.page : 0; let hasMore = cached ? cached.hasMore : true; let isLoading = false; let observer = null; // allItems is the single source of truth - every page appends to it, // and it is always written to cache in full so re-opens restore correctly. let allItems = cached ? cached.items.slice() : []; const saveCache = () => { mediaLibraryCache.set(mediaType, { items: allItems, page: currentPage, hasMore }); }; const disconnectObserver = () => { if (observer) { observer.disconnect(); observer = null; } }; // ── Restore from cache (no network request) ─────────────────────── const restoreFromCache = (cachedItems) => { grid.innerHTML = ''; grid.appendChild(loadingRow); grid.appendChild(sentinel); if (cachedItems.length === 0) { const msg = document.createElement('div'); msg.className = 'mwp-sfe-media-library-message'; msg.textContent = 'No media found in library.'; grid.insertBefore(msg, loadingRow); } else { const frag = document.createDocumentFragment(); appendMediaItems(frag, cachedItems, mediaType, onSelect, maintainFocus); grid.insertBefore(frag, loadingRow); } }; // ── Fetch the next page from the server ─────────────────────────── const loadNextPage = async () => { if (isLoading || !hasMore) return; isLoading = true; loadingRow.style.display = ''; try { currentPage++; const result = await apiCall('/get-media-library', { post_id: postId, media_type: mediaType, page: currentPage, per_page: PER_PAGE }); // First page fetch: swap out the "Loading..." placeholder if (currentPage === 1) { grid.innerHTML = ''; grid.appendChild(loadingRow); grid.appendChild(sentinel); } if (!result.items || result.items.length === 0) { if (currentPage === 1) { const msg = document.createElement('div'); msg.className = 'mwp-sfe-media-library-message'; msg.textContent = 'No media found in library.'; grid.insertBefore(msg, loadingRow); } hasMore = false; disconnectObserver(); saveCache(); return; } // Append new items above the sentinel/loading row const frag = document.createDocumentFragment(); appendMediaItems(frag, result.items, mediaType, onSelect, maintainFocus); grid.insertBefore(frag, loadingRow); // Accumulate into the running list and persist to cache allItems.push(...result.items); if (currentPage >= result.total_pages) { hasMore = false; disconnectObserver(); } saveCache(); } catch (error) { console.error('Failed to load media library:', error); if (currentPage === 1) { grid.innerHTML = ` Error loading media library: ${error.message}`; } else { loadingRow.textContent = 'Error loading more items.'; } hasMore = false; disconnectObserver(); } finally { isLoading = false; loadingRow.style.display = 'none'; } }; // ── IntersectionObserver - scoped to the grid scroll container ──── // rootMargin bottom of 150px means the observer fires when the sentinel // is within 150px of the bottom edge of the grid, giving the fetch time // to complete before the user reaches the very end. const attachObserver = () => { observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) loadNextPage(); }, { root: grid, rootMargin: '0px 0px 150px 0px', threshold: 0 } ); observer.observe(sentinel); }; // ── Bootstrap: restore cache instantly, then set up observer ───── if (cached) { restoreFromCache(cached.items); if (hasMore) attachObserver(); } else { // No cache - add sentinel now (loadNextPage will clear the placeholder) attachObserver(); await loadNextPage(); } } /** * Show the WordPress core Icon Library for schema media components that use * the `icon` media type. Icons are REST entities, not media attachments. * * @param {object} config Icon-library UI configuration. * @returns {Promise} */ async function showIconLibrary({ actionsContainer, onSelect, onBack, reposition, maintainFocus }) { const mediaLibraryCache = SFE.MediaLibraryCache; releaseMediaElements(actionsContainer); actionsContainer.innerHTML = ''; const container = document.createElement('div'); container.className = 'mwp-sfe-inline-media-library'; container.addEventListener('click', event => event.stopPropagation()); const header = document.createElement('div'); header.className = 'mwp-sfe-media-library-header'; header.textContent = 'Select Icon from the Library'; const grid = document.createElement('div'); grid.className = 'mwp-sfe-media-library-grid'; grid.innerHTML = 'Loading icon library...'; const btnRow = document.createElement('div'); btnRow.className = 'mwp-sfe-inline-media-upload-actions'; const backBtn = document.createElement('button'); backBtn.className = 'mwp-sfe-btn mwp-sfe-btn-secondary-inline'; backBtn.textContent = 'Back'; backBtn.addEventListener('click', event => { event.preventDefault(); onBack(); }); btnRow.appendChild(backBtn); container.append(header, grid, btnRow); actionsContainer.appendChild(container); maintainFocus(); requestAnimationFrame(reposition); const renderIcons = (icons) => { grid.innerHTML = ''; if (!Array.isArray(icons) || !icons.length) { grid.innerHTML = 'No icons found in the Icon Library.'; return; } icons.forEach(icon => { const name = String(icon?.name || '').trim(); if (!name) return; const item = document.createElement('button'); item.type = 'button'; item.className = 'mwp-sfe-media-library-item'; item.setAttribute('aria-label', `Select ${String(icon?.label || name)}`); const preview = document.createElement('div'); preview.style.cssText = 'position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;background:#fff;display:flex;align-items:center;justify-content:center;'; preview.innerHTML = String(icon?.content || ''); const title = document.createElement('div'); title.className = 'mwp-sfe-media-library-item-title'; title.textContent = String(icon?.label || name); item.append(preview, title); item.addEventListener('click', () => { maintainFocus(); onSelect(name, null, String(icon?.content || '')); }); grid.appendChild(item); }); }; const cached = mediaLibraryCache?.get('icon'); if (cached?.items) { renderIcons(cached.items); return; } try { const iconLibraryUrl = String(SFE.ManagerData.iconLibraryUrl || '').trim(); if (!iconLibraryUrl) { throw new Error('Icon Library URL is not configured.'); } const iconLibraryRequestUrl = new URL(iconLibraryUrl, window.location.href); iconLibraryRequestUrl.searchParams.set('per_page', '100'); iconLibraryRequestUrl.searchParams.set('context', 'view'); const response = await fetch(iconLibraryRequestUrl.toString(), { headers: { 'X-WP-Nonce': SFE.ManagerData.nonce }, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const icons = await response.json(); mediaLibraryCache?.set('icon', { items: icons }); renderIcons(icons); } catch (error) { console.error('FrontEdit: failed to load the Icon Library', error); grid.innerHTML = `Unable to load the Icon Library: ${error.message}`; } } // ─── Main entry point ──────────────────────────────────────────────────── /** * Build the explicit schema media descriptor consumed by MediaHelper and the * schema media editing UI. * * @param {object|null} component Editable schema component definition. * @returns {object|null} Normalized schema media descriptor, or null when invalid. */ function buildSchemaMediaDescriptor(component) { const mediaDescriptor = component?.mediaDescriptor && typeof component.mediaDescriptor === 'object' ? component.mediaDescriptor : null; const targetSource = component?.target && typeof component.target === 'object' ? component.target : null; const descriptor = mediaDescriptor || { componentId: component?.id || '', scopeSelector: component?.selector || '', targetSelector: targetSource?.selector || '', attribute: targetSource?.attribute || '', mediaType: targetSource?.mediaType || '', }; const MediaHelper = SFE.MediaHelper || null; return MediaHelper?.isSchemaMediaDescriptor?.(descriptor) ? descriptor : null; } /** * Resolve every schema target element that should receive the current media value. * * @param {HTMLElement|null} mediaEl Active schema component root element. * @param {object|null} mediaDescriptor Schema media descriptor. * @returns {HTMLElement[]} Ordered list of target elements within the component. */ function resolveComponentMediaTargets(mediaEl, mediaDescriptor) { const targetSelector = typeof mediaDescriptor?.targetSelector === 'string' ? mediaDescriptor.targetSelector.trim() : ''; if (!mediaEl || !targetSelector) return []; if (targetSelector === ':self') { return [mediaEl]; } const targets = []; if (mediaEl.matches && mediaEl.matches(targetSelector)) { targets.push(mediaEl); } if (typeof mediaEl.querySelectorAll === 'function') { mediaEl.querySelectorAll(targetSelector).forEach(node => { if (!targets.includes(node)) { targets.push(node); } }); } return targets; } /** * Read the currently displayed media URL from a schema media component. * * @param {HTMLElement|null} mediaEl Active schema component root element. * @param {object|null} mediaDescriptor Schema media descriptor. * @param {object|null} MediaHelper Shared media helper utilities. * @returns {string} Current media URL for the component. */ function readComponentMediaUrl(mediaEl, mediaDescriptor, MediaHelper) { if (!mediaEl) return ''; const targets = resolveComponentMediaTargets(mediaEl, mediaDescriptor); if (!targets.length) return ''; const primary = targets[0] || mediaEl; if (MediaHelper?.isCssBackgroundElement(primary)) { const bg = primary.style?.backgroundImage || ''; const match = bg.match(/url\((['"]?)(.*?)\1\)/i); return match && match[2] ? match[2] : ''; } const attr = String(mediaDescriptor?.attribute || '').toLowerCase(); if (!attr) return ''; if (attr === 'href') { return primary.getAttribute('href') || primary.href || ''; } if (attr === 'src') { return primary.getAttribute('src') || primary.src || ''; } return primary.getAttribute(attr) || ''; } /** * Apply a new media URL to a schema media component in the live DOM preview. * * @param {HTMLElement|null} mediaEl Active schema component root element. * @param {object|null} mediaDescriptor Schema media descriptor. * @param {string} url New media URL. * @param {object|null} MediaHelper Shared media helper utilities. * @returns {HTMLElement|null} Updated component root element reference. */ function writeComponentMediaUrl(mediaEl, mediaDescriptor, url, MediaHelper) { if (!mediaEl) return mediaEl; const targets = resolveComponentMediaTargets(mediaEl, mediaDescriptor); if (!targets.length) return mediaEl; const attr = String(mediaDescriptor?.attribute || '').toLowerCase(); if (!attr) return mediaEl; const updatedTargets = targets.map(target => { if (!target) return target; if (MediaHelper?.isCssBackgroundElement(target)) { target.style.backgroundImage = `url(${url})`; return target; } const tagName = target.tagName ? target.tagName.toUpperCase() : ''; if (attr === 'src' && (tagName === 'IMG' || tagName === 'VIDEO')) { return MediaHelper?.swapIfNeeded ? (MediaHelper.swapIfNeeded(target, url) || target) : target; } target.setAttribute(attr, url); if (attr === 'href' && 'href' in target) { target.href = url; } return target; }); if (targets[0] === mediaEl) { return updatedTargets[0] || mediaEl; } return mediaEl; } /** * Replace an icon component's rendered SVG preview with the trusted markup * returned by WordPress' Icon Library endpoint. * * @param {HTMLElement|null} mediaEl Current icon SVG element. * @param {string} markup Icon SVG markup from `/wp/v2/icons`. * @returns {HTMLElement|null} Updated SVG element, or the original element. */ function writeIconPreview(mediaEl, markup) { if (!mediaEl || typeof markup !== 'string' || !markup.trim()) { return mediaEl; } const template = document.createElement('template'); template.innerHTML = markup.trim(); const replacement = template.content.firstElementChild; if (!replacement || replacement.tagName?.toLowerCase() !== 'svg' || !mediaEl.parentNode) { return mediaEl; } mediaEl.parentNode.replaceChild(replacement, mediaEl); return replacement; } /** * Schema mixed-component media editing path. * Runs inside an existing text editor session and must not refetch block attrs. */ function startSchemaComponentEditing(editorState, component, options = {}) { const ctx = SFE.Context; const actionBar = ctx?.actionBar; const buttonManager = ctx?.buttonManager; const { positionFloatingElements } = SFE.PositionManager; const MediaHelper = SFE.MediaHelper; const ToolbarManager = SFE.ToolbarManager || null; const postId = SFE.ManagerData.postId; const actionsContainer = editorState?.actionsContainer || null; const blockEditSession = editorState?.blockEditSession || null; const getRootElement = () => editorState?.element || null; if (!editorState || !component?.element || !getRootElement() || !actionsContainer || !actionBar || !buttonManager) { return { cleanup: () => {} }; } const toolbarContainer = ensureToolbarContainer(editorState); if (!editorState.attributeChanges || typeof editorState.attributeChanges !== 'object') { editorState.attributeChanges = {}; } let mediaEl = component.element; const mediaDescriptor = buildSchemaMediaDescriptor(component); const declaredType = SFE.MediaHelper?.getMediaType?.(mediaDescriptor) || ''; if (!mediaDescriptor || !declaredType) { return { cleanup: () => {} }; } const isCssBg = MediaHelper?.isCssBackgroundElement?.(mediaEl); const mediaType = (isCssBg && declaredType === 'image_or_video') ? 'image' : declaredType; const mediaTypeName = getMediaTypeName(mediaType); const baselineUrl = readComponentMediaUrl(mediaEl, mediaDescriptor, MediaHelper); const baselineOuterHTML = mediaEl?.outerHTML || ''; const baselineChanges = ( mediaEl._mwpMediaChanges && typeof mediaEl._mwpMediaChanges === 'object' ) ? { ...mediaEl._mwpMediaChanges } : null; const uploadState = { url: baselineChanges?.url || baselineUrl, file: null, attachmentId: baselineChanges?.id ?? null, }; let disposed = false; let currentState = 'idle'; const toolbarFormats = ToolbarManager && typeof ToolbarManager.resolveFormats === 'function' ? ToolbarManager.resolveFormats(component?.editorOptions || {}, mediaEl) : []; let toolbarHost = null; let toolbarManager = null; const maintainActionBarFocus = () => { requestAnimationFrame(() => { if (!disposed && actionsContainer && document.body.contains(actionsContainer)) { actionsContainer.focus({ preventScroll: true }); } }); }; const reposition = () => { const rootElement = getRootElement(); if (!disposed && rootElement) { if (toolbarHost?.options && typeof toolbarHost.options === 'object') { toolbarHost.options.blockRootElement = rootElement; } positionFloatingElements(rootElement, editorState.toolbarContainer || null, actionsContainer); } }; const syncRootMediaChanges = (changes) => { const rootElement = getRootElement(); if (!rootElement) { return; } if (changes && typeof changes === 'object') { rootElement._mwpMediaChanges = { ...changes }; } else { delete rootElement._mwpMediaChanges; } }; const serializeToolbarState = (host) => ({ componentId: String(component?.id || '').trim(), align: typeof host.getBlockAlignState === 'function' ? host.getBlockAlignState() : 'none', attributeChanges: cloneAttributeChanges(host.attributeChanges), mediaUrl: readComponentMediaUrl(mediaEl, mediaDescriptor, MediaHelper), mediaMarkup: mediaEl?._mwpMediaChanges?.markup || '', mediaChanges: mediaEl?._mwpMediaChanges && typeof mediaEl._mwpMediaChanges === 'object' ? { ...mediaEl._mwpMediaChanges } : null, }); const applyToolbarState = (host, state) => { host.attributeChanges = cloneAttributeChanges(state?.attributeChanges); editorState.attributeChanges = host.attributeChanges; const targetUrl = typeof state?.mediaUrl === 'string' ? state.mediaUrl : baselineUrl; mediaEl = writeComponentMediaUrl(mediaEl, mediaDescriptor, targetUrl, MediaHelper); if (declaredType === 'icon') { mediaEl = writeIconPreview(mediaEl, state?.mediaMarkup || baselineOuterHTML); } component.element = mediaEl; component.mediaDescriptor = mediaDescriptor; if (state?.mediaChanges && typeof state.mediaChanges === 'object') { mediaEl._mwpMediaChanges = { ...state.mediaChanges }; syncRootMediaChanges(mediaEl._mwpMediaChanges); } else { delete mediaEl._mwpMediaChanges; syncRootMediaChanges(null); } markComponentElement(); const nextAlign = typeof state?.align === 'string' && state.align.trim() ? state.align.trim().toLowerCase() : 'none'; host.changeBlockAlign(nextAlign, { target: 'block', targetKey: 'align', operation: typeof host.getBlockAlignOperation === 'function' ? host.getBlockAlignOperation() : null, }); }; const markComponentElement = () => { if (!mediaEl) return; component.element = mediaEl; component.mediaDescriptor = mediaDescriptor; if (toolbarHost && typeof toolbarHost.setElement === 'function') { toolbarHost.setElement(mediaEl); if (toolbarHost.options && typeof toolbarHost.options === 'object') { toolbarHost.options.blockRootElement = getRootElement(); } } mediaEl.classList.add('mwp-sfe-editable-component', 'mwp-sfe-component-active'); mediaEl.dataset.mwpSfeEditableComponent = component.id; mediaEl.dataset.mwpSfeActiveComponent = component.id; }; if (toolbarContainer && toolbarFormats.length && ToolbarManager) { toolbarHost = createMediaToolbarHost({ editorState, component, toolbarContainer, formats: toolbarFormats, getMediaElement: () => mediaEl, setMediaElement: (nextElement) => { mediaEl = nextElement; }, reposition, serializeState: serializeToolbarState, applySerializedState: applyToolbarState, showMediaReplaceUI: () => showInputUI(), }); toolbarManager = new ToolbarManager(toolbarHost); toolbarManager.createToolbar(); showToolbar(toolbarContainer); if (blockEditSession && typeof blockEditSession.registerSnapshotHost === 'function') { const textEditorApi = SFE.TextEditor || null; blockEditSession.registerSnapshotHost(toolbarHost, { editorState, scopeId: 'media', attachHistoryApi: true, seedInitialHistory: true, captureSnapshot: ({ editorState: activeEditorState, host }) => ( textEditorApi && typeof textEditorApi.captureBlockSessionSnapshot === 'function' ) ? textEditorApi.captureBlockSessionSnapshot(activeEditorState) : serializeToolbarState(host), captureSelection: ({ editorState: activeEditorState }) => ( textEditorApi && typeof textEditorApi.captureBlockSessionSelection === 'function' ) ? textEditorApi.captureBlockSessionSelection(activeEditorState) : null, restoreSnapshot: ({ editorState: activeEditorState, host, snapshot, selectionToRestore }) => { if ( textEditorApi && typeof textEditorApi.restoreBlockSessionSnapshot === 'function' && snapshot && typeof snapshot === 'object' && Object.prototype.hasOwnProperty.call(snapshot, 'rootOuterHTML') ) { textEditorApi.restoreBlockSessionSnapshot(activeEditorState, snapshot, selectionToRestore || null); return; } applyToolbarState(host, snapshot); }, }); } toolbarHost.updateToolbarState(); } editorState.activeSchemaHost = toolbarHost || null; const restoreBaselineMedia = () => { mediaEl = declaredType === 'icon' ? writeIconPreview(mediaEl, baselineOuterHTML) : writeComponentMediaUrl(mediaEl, mediaDescriptor, baselineUrl, MediaHelper); markComponentElement(); if (baselineChanges) { mediaEl._mwpMediaChanges = { ...baselineChanges }; syncRootMediaChanges(mediaEl._mwpMediaChanges); } else { delete mediaEl._mwpMediaChanges; syncRootMediaChanges(null); } if (toolbarHost) { toolbarHost.updateToolbarState(); } repositionAfterMediaLoad(mediaEl, getRootElement(), toolbarContainer, actionsContainer, positionFloatingElements); }; const applyMediaChange = (url, attachmentId, markup = '') => { uploadState.url = url; uploadState.attachmentId = attachmentId; mediaEl = declaredType === 'icon' ? writeIconPreview(mediaEl, markup) : writeComponentMediaUrl(mediaEl, mediaDescriptor, url, MediaHelper); markComponentElement(); const mediaChanges = { url }; if (markup) mediaChanges.markup = markup; if (attachmentId != null) mediaChanges.id = attachmentId; mediaEl._mwpMediaChanges = mediaChanges; syncRootMediaChanges(mediaChanges); if (attachmentId != null) { SFE.Api.queueResolvedMediaAttributes(editorState, { mediaElement: mediaEl, syncRootChanges: syncRootMediaChanges }).catch(error => { console.warn('FrontEdit: failed to resolve schema media attributes', error); }); } if (toolbarHost) { toolbarHost.saveToHistory(); toolbarHost.updateToolbarState(); } repositionAfterMediaLoad(mediaEl, getRootElement(), toolbarContainer, actionsContainer, positionFloatingElements); }; if (toolbarHost) { toolbarHost.applyMediaSelection = (url, attachmentId, options = {}) => { const normalizedUrl = String(url || '').trim(); if (!normalizedUrl) { return false; } applyMediaChange(normalizedUrl, attachmentId); restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); return true; }; } const stepBack = () => { if (disposed) return false; if (currentState === 'library') { if (mediaType === 'icon') { restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); return true; } showInputUI({ force: true }); return true; } if (currentState === 'input') { restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); return true; } return false; }; const showMediaLibraryUI = () => { if (disposed) return; currentState = 'library'; if (mediaType === 'icon') { showIconLibrary({ actionsContainer, onBack: stepBack, onSelect: (iconName, unusedAttachmentId, iconMarkup) => { applyMediaChange(iconName, null, iconMarkup); restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); }, reposition, maintainFocus: maintainActionBarFocus, }); return; } showMediaLibrary({ actionsContainer, mediaType, mediaTypeName, postId, onBack: stepBack, onSelect: (url, attachmentId) => { applyMediaChange(url, attachmentId); restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); }, reposition, maintainFocus: maintainActionBarFocus }); }; const showInputUI = (options = {}) => { if (disposed) return false; if (mediaType === 'icon') { showMediaLibraryUI(); return true; } const force = options.force === true; // Replace Media UI is already open. Do not rebuild it. if (!force && (currentState === 'input' || currentState === 'library')) { maintainActionBarFocus(); return false; } currentState = 'input'; uploadState.url = ''; uploadState.file = null; uploadState.attachmentId = null; const isAttrsLoading = !!actionsContainer.querySelector( '.mwp-sfe-btn-primary-inline[data-loading-attrs]' ); releaseMediaElements(actionsContainer); actionsContainer.innerHTML = ''; actionsContainer.appendChild(buildInputUI({ state: uploadState, mediaType, mediaTypeName, mediaDescriptor, isAttrsLoading, onUploadSuccess: (url, attachmentId) => { applyMediaChange(url, attachmentId); restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); }, onBrowse: showMediaLibraryUI, onCancel: () => { restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); }, maintainFocus: maintainActionBarFocus })); maintainActionBarFocus(); requestAnimationFrame(reposition); return true; }; restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(() => { requestAnimationFrame(() => { reposition(); }); }); return { handleEscape: stepBack, getEditorHost: () => toolbarHost, applySelection: (url, attachmentId) => { if (disposed || !url) return false; applyMediaChange(url, attachmentId); restoreInlineEditButtons(editorState); currentState = 'idle'; requestAnimationFrame(reposition); return true; }, showInputUI: () => showInputUI(), showMediaLibraryUI: () => { if (disposed) return false; showMediaLibraryUI(); return true; }, cleanup: (cleanupOptions = {}) => { const preserveChanges = cleanupOptions.preserveChanges !== false; const preserveToolbarDom = cleanupOptions.preserveToolbarDom === true; disposed = true; releaseMediaElements(actionsContainer); if (editorState.activeSchemaHost === toolbarHost) { editorState.activeSchemaHost = null; } if (toolbarManager && typeof toolbarManager.destroy === 'function') { toolbarManager.destroy({ removeToolbar: !preserveToolbarDom }); } toolbarManager = null; toolbarHost = null; if (!preserveChanges) { restoreBaselineMedia(); // Full teardown path (explicit discard): clear panel content now. actionsContainer.innerHTML = ''; } // Preserve-path cleanup is used by component switches and editor close. // Keep existing action-bar DOM until the next state render / close // animation to avoid flashing an empty wrapper mid-transition. } }; } function findScopedComponentElement(rootElement, selector) { if (!rootElement || !selector) return null; if (rootElement.matches(selector)) { const owner = rootElement.closest('[data-mwp-sfe-uuid]'); if (!owner || owner === rootElement) return rootElement; } const matches = rootElement.querySelectorAll(selector); for (const candidate of matches) { const owner = candidate.closest('[data-mwp-sfe-uuid]'); if (!owner || owner === rootElement) { return candidate; } } return null; } function buildStandaloneSchemaMediaComponent(rootElement, handler) { const configured = Array.isArray(handler?.client_config?.editableComponents) ? handler.client_config.editableComponents : []; if (!configured.length) return null; const fileComponents = configured .filter(component => ( component && typeof component === 'object' && typeof component.type === 'string' && component.type.trim().toLowerCase() === 'file' )) .sort((a, b) => Number(!!b.default) - Number(!!a.default)); if (!fileComponents.length) return null; for (let i = 0; i < fileComponents.length; i++) { const component = fileComponents[i]; const selector = typeof component.selector === 'string' ? component.selector.trim() : ''; const mediaDescriptor = buildSchemaMediaDescriptor(component); if (!selector || !mediaDescriptor) { continue; } const element = findScopedComponentElement(rootElement, selector); if (!element) { continue; } const id = typeof component.id === 'string' && component.id.trim() ? component.id.trim() : `schema_media_component_${i + 1}`; return { id, label: typeof component.label === 'string' && component.label.trim() ? component.label.trim() : id, type: 'file', selector, default: !!component.default, element, editorOptions: component.editor && typeof component.editor === 'object' ? { ...component.editor } : {}, target: { selector: mediaDescriptor.targetSelector, attribute: mediaDescriptor.attribute, mediaType: mediaDescriptor.mediaType, }, mediaDescriptor: { ...mediaDescriptor, componentId: id, scopeSelector: selector, }, urlBindingPath: typeof component.urlBindingPath === 'string' ? component.urlBindingPath.trim() : '', idBindingPath: typeof component.idBindingPath === 'string' ? component.idBindingPath.trim() : '', }; } return null; } /** * Content-type-specific: Media Editing * Receives common state, adds media-specific resources, returns complete editor state. */ function startMediaEditing(commonState) { const ctx = SFE.Context; const { debouncedPosition } = SFE.PositionManager; const { element, handler, actionsContainer } = commonState; ctx.activeMode = 'media'; const schemaComponent = buildStandaloneSchemaMediaComponent(element, handler); if (!schemaComponent) { console.error('FrontEdit: startMediaEditing requires a schema file component', handler?.id || ''); alert('No media element found to edit.'); return commonState; } const schemaEditorState = { ...commonState, editableComponents: [schemaComponent], activeEditableComponent: schemaComponent, activeComponentId: schemaComponent.id, }; ensureToolbarContainer(schemaEditorState); const mediaSession = startSchemaComponentEditing(schemaEditorState, schemaComponent, { onCancel: () => { const activeState = SFE.Context.activeEditor || schemaEditorState; SFE.closeInPlaceEditor(activeState, true); }, }); const updatePositions = () => debouncedPosition(element, schemaEditorState.toolbarContainer || null, actionsContainer); window.addEventListener('scroll', updatePositions, true); const resizeObserver = new ResizeObserver(updatePositions); resizeObserver.observe(element); const escapeHandler = (e) => { if (e.key !== 'Escape') return; e.preventDefault(); const activeState = SFE.Context.activeEditor || schemaEditorState; const activeSession = activeState?._mwpSchemaMediaSession || mediaSession; if ( activeSession && typeof activeSession.handleEscape === 'function' && activeSession.handleEscape() ) { return; } SFE.closeInPlaceEditor(activeState, true); }; document.addEventListener('keydown', escapeHandler); schemaEditorState._mwpSchemaMediaSession = mediaSession; schemaEditorState._mwpActiveComponentType = 'file'; schemaEditorState.updatePositions = updatePositions; schemaEditorState.resizeObserver = resizeObserver; schemaEditorState.escapeHandler = escapeHandler; schemaEditorState.isMediaEditor = true; return schemaEditorState; } SFE.MediaEditor = { startMediaEditing, startSchemaComponentEditing }; })(); import { __ } from '@wordpress/i18n'; export const Newsmode = () => ( {__('Newsmode Icon', 'extendify-local')} ); "use strict";(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[926],{2522:(t,e,n)=>{n.d(e,{D:()=>o});var r=n(32091),i=n.n(r);function o(t,{dateRangeLength:e}){i()(Array.isArray(t),"report must be an array to partition."),i()(Number.isInteger(e)&&e>0,"dateRangeLength must be a positive integer.");const n=-1*e;return{currentRange:t.slice(n),compareRange:t.slice(2*n,n)}}},8143:(t,e,n)=>{n.d(e,{VZ:()=>o,dc:()=>a,pH:()=>i,r0:()=>s});var r=n(84024);function i(t){try{return new URL(t).pathname}catch{}return null}function o(t,e){try{return new URL(e,t).href}catch{}return("string"==typeof t?t:"")+("string"==typeof e?e:"")}function a(t){return"string"!=typeof t?t:t.replace(/^https?:\/\/(www\.)?/i,"").replace(/\/$/,"")}function s(t,e){if(!(0,r.m)(t))return t;if(t.length<=e)return t;const n=new URL(t),i=t.replace(n.origin,"");if(i.length{n.d(e,{tt:()=>$,Jg:()=>T,Gp:()=>b,GH:()=>v,r0:()=>D,Du:()=>S,Zf:()=>H,Cn:()=>j,G7:()=>h,vH:()=>p,N_:()=>P,zh:()=>V,mK:()=>l.mK,Ql:()=>A,vY:()=>x,sq:()=>E,VZ:()=>F.VZ,JK:()=>l.JK,IS:()=>C,pH:()=>F.pH,kf:()=>q,O5:()=>I,Qr:()=>N,x6:()=>U,K5:()=>l.K5,S_:()=>m,dc:()=>F.dc,Eo:()=>l.Eo,jq:()=>l.jq,DK:()=>G.D,N9:()=>K,p9:()=>o.p,XH:()=>L,Zm:()=>c,sx:()=>i.sx,BI:()=>i.BI,CZ:()=>o.C,BG:()=>J});var r=n(17243),i=n(89318),o=n(82046),a=n(10523),s=n.n(a);function c(t){return s()(JSON.stringify(u(t)))}function u(t){const e={};return Object.keys(t).sort().forEach(n=>{let r=t[n];r&&"object"==typeof r&&!Array.isArray(r)&&(r=u(r)),e[n]=r}),e}var l=n(79829);function g(t){return t.replace(new RegExp("\\[([^\\]]+)\\]\\((https?://[^/]+\\.\\w+/?.*?)\\)","gi"),'$1')}function d(t){return`${t.replace(/\n{2,}/g,"")}`}function f(t){return t.replace(/\n/gi,"")}function m(t){const e=[g,d,f];let n=t;for(const t of e)n=t(n);return n}function p(t){return t=parseFloat(t),isNaN(t)||0===t?[0,0,0,0]:[Math.floor(t/60/60),Math.floor(t/60%60),Math.floor(t%60),Math.floor(1e3*t)-1e3*Math.floor(t)]}function h(t){const e=t&&!Number.isInteger(t)?new Date(t).getTime():t;return isNaN(e)||!e?0:e}var y=n(32091),k=n.n(y),_=n(82871);const w="Date param must construct to a valid date instance or be a valid date instance itself.",v="Invalid dateString parameter, it must be a string.",b='Invalid date range, it must be a string with the format "last-x-days".',D=60,T=60*D,$=24*T,S=7*$;function A(){function t(t){return(0,_.sprintf)(/* translators: %s: number of days */ /* translators: %s: number of days */ (0,_._n)("Last %s day","Last %s days",t,"google-site-kit"),t)}return{"last-7-days":{slug:"last-7-days",label:t(7),days:7},"last-14-days":{slug:"last-14-days",label:t(14),days:14},"last-28-days":{slug:"last-28-days",label:t(28),days:28},"last-90-days":{slug:"last-90-days",label:t(90),days:90}}}function N(t=""){if(!(0,r.isString)(t))return!1;if(3!==t.split("-").length)return!1;const e=new Date(t);return(0,r.isDate)(e)&&!isNaN(e)}function E(t){k()((0,r.isDate)(t)&&!isNaN(t),w);const e=`${t.getMonth()+1}`,n=`${t.getDate()}`;return[t.getFullYear(),e.length<2?`0${e}`:e,n.length<2?`0${n}`:n].join("-")}function L(t){k()(N(t),v);const[e,n,r]=t.split("-");return new Date(e,n-1,r)}function C(t,e){return E(P(t,e*$))}function I(t){const e=t.split("-");return 3===e.length&&"last"===e[0]&&!Number.isNaN(e[1])&&!Number.isNaN(parseFloat(e[1]))&&"days"===e[2]}function P(t,e){k()(N(t)||(0,r.isDate)(t)&&!isNaN(t),v);const n=N(t)?Date.parse(t):t.getTime();return new Date(n-1e3*e)}var M=n(69743),R=n(94552),O=n(62540);function x(t,e={}){if(Number.isNaN(Number(t)))return"";const{invertColor:n=!1}=e;return(0,M.Ay)((0,O.jsx)(R.A,{direction:t>0?"up":"down",invertColor:n}))}function j(t,e){return t>0&&e>0?t/e-1:t>0?1:e>0?-1:0}var F=n(8143);function U(t){const e=parseFloat(t)||0;return!!Number.isInteger(e)&&e>0}function q(t){if("number"==typeof t)return!0;const e=(t||"").toString();return!!e&&!isNaN(e)}function K(t){return Array.isArray(t)?[...t].sort():t}var G=n(2522);function H(t,e){function n(t){return"0"===t||0===t}if(n(t)&&n(e))return 0;if(n(t)||Number.isNaN(t))return null;const r=(e-t)/t;return Number.isNaN(r)||!Number.isFinite(r)?null:r}function J(t){try{return JSON.parse(t)&&!!t}catch(t){return!1}}function V(t){if(!t)return"";const e=t.replace(/(\d+);/g,(t,e)=>String.fromCharCode(e)).replace(/(\\)/g,"");return(0,r.unescape)(e)}},15210:(t,e,n)=>{n.d(e,{O:()=>i});var r=n(31234);const i=n.n(r)()(n.g)},21134:(t,e,n)=>{n.d(e,{Gq:()=>g,IL:()=>p,LD:()=>f,SO:()=>d,a2:()=>i,xD:()=>m});var r=n(12850);const i="googlesitekit_",o=`${i}1.164.0_${n.g._googlesitekitBaseData.storagePrefix}_`,a=["sessionStorage","localStorage"];let s,c=[...a];async function u(t){const e=n.g[t];if(!e)return!1;try{const t="__storage_test__";return e.setItem(t,t),e.removeItem(t),!0}catch(t){return t instanceof DOMException&&(22===t.code||1014===t.code||"QuotaExceededError"===t.name||"NS_ERROR_DOM_QUOTA_REACHED"===t.name)&&0!==e.length}}async function l(){if(void 0!==s)return s;for(const t of c)s||await u(t)&&(s=n.g[t]);return void 0===s&&(s=null),s}async function g(t){const e=await l();if(e){const n=e.getItem(`${o}${t}`);if(n){const t=JSON.parse(n),{timestamp:e,ttl:r,value:i,isError:o}=t;if(e&&(!r||Math.round(Date.now()/1e3)-e{n.d(e,{A:()=>l});var r=n(19371),i=n(31638);const o=function(t){const e=Object.keys(t).reduce((e,n)=>(e[(0,i.getStablePath)(n)]=t[n],e),{});let n=!1;return(t,r)=>{if(n)return r(t);setTimeout(()=>{n=!0},3e3);const{parse:o=!0}=t,a=t.path;if("string"==typeof t.path){const n=t.method?.toUpperCase()||"GET",r=(0,i.getStablePath)(a);if(o&&"GET"===n&&e[r]){const t=Promise.resolve(e[r].body);return delete e[r],t}if("OPTIONS"===n&&e[n]&&e[n][r]){const t=Promise.resolve(e[n][r]);return delete e[n][r],t}}return r(t)}},{nonce:a,nonceEndpoint:s,preloadedData:c,rootURL:u}=n.g._googlesitekitAPIFetchData||{};r.default.nonceEndpoint=s,r.default.nonceMiddleware=r.default.createNonceMiddleware(a),r.default.rootURLMiddleware=r.default.createRootURLMiddleware(u),r.default.preloadingMiddleware=o(c),r.default.use(r.default.nonceMiddleware),r.default.use(r.default.mediaUploadMiddleware),r.default.use(r.default.rootURLMiddleware),r.default.use(r.default.preloadingMiddleware);const l=r.default},50618:(t,e,n)=>{var r=n(32091),i=n.n(r),o=n(42926),a=n(44451),s=n(21134),c=n(12850),u=n(67150);const l=["fetch_error"],g=[];var d=n(58116);let f=!0;function m(t,e,n,r={}){const i=[t,e,n].filter(t=>!!t&&t.length);return 3===i.length&&r&&r.constructor===Object&&Object.keys(r).length&&i.push((0,c.Zm)(r)),i.join("::")}function p(t){const e=n.g.googlesitekit?.data?.dispatch?.(d.oR);e&&((0,u.G)(t)?e.setPermissionScopeError(t):(0,u.HY)(t)&&e.setAuthError(t))}async function h(t,e,r,{bodyParams:u,cacheTTL:d=c.Jg,method:f="GET",queryParams:h,useCache:k,signal:_}={}){i()(t,"`type` argument for requests is required."),i()(e,"`identifier` argument for requests is required."),i()(r,"`datapoint` argument for requests is required.");const w="GET"===f&&(void 0!==k?k:y()),v=m(t,e,r,h);if(w){const{cacheHit:t,value:e,isError:n}=await(0,s.Gq)(v);if(n)throw p(e),e;if(t)return e}try{const n=await(0,o.A)({data:u,method:f,signal:_,path:(0,a.F)(`/google-site-kit/v1/${t}/${e}/data/${r}`,h)});return w&&await(0,s.SO)(v,n,{ttl:d}),n}catch(i){if(_?.aborted)throw i;throw i?.data?.cacheTTL&&await(0,s.SO)(v,i,{ttl:i.data.cacheTTL,isError:!0}),async function(t){const{method:e,type:n,identifier:r,datapoint:i,error:o}=t,a=`${n}/${r}/data/${i}`;if(g.includes(a))return;if(!o||l.includes(o?.code))return;let s=`code: ${o.code}`;o.data?.reason&&(s+=`, reason: ${o.data.reason}`),await(0,c.sx)("api_error",`${e}:${n}/${r}/data/${i}`,`${o.message} (${s})`,o.data?.status||o.code)}({method:f,datapoint:r,type:t,identifier:e,error:i}),p(i),n.g.console.error("Google Site Kit API Error",`method:${f}`,`datapoint:${r}`,`type:${t}`,`identifier:${e}`,`error:"${i.message}"`),i}}function y(){return f}async function k(t,e,n){const r=m(t,e,n);(await(0,s.xD)()).forEach(t=>{new RegExp(`^${s.a2}([^_]+_){2}${r}`).test(t)&&(0,s.LD)(t)})}const _={invalidateCache:k,get:function(t,e,n,r,{cacheTTL:i=c.Jg,useCache:o,signal:a}={}){return h(t,e,n,{cacheTTL:i,queryParams:r,useCache:o,signal:a})},set:async function(t,e,n,r,{method:i="POST",queryParams:o={},signal:a}={}){const s=await h(t,e,n,{bodyParams:{data:r},method:i,queryParams:o,useCache:!1,signal:a});return await k(t,e,n),s},setUsingCache:function(t){return f=!!t,f},usingCache:y};void 0===n.g.googlesitekit&&(n.g.googlesitekit={}),void 0===n.g.googlesitekit.api&&(n.g.googlesitekit.api=_)},58116:(t,e,n)=>{n.d(e,{$8:()=>a,$Q:()=>f,BT:()=>P,CQ:()=>S,DF:()=>V,GM:()=>$,GT:()=>k,HA:()=>x,HD:()=>d,HP:()=>I,J5:()=>F,JF:()=>N,JK:()=>h,Ml:()=>p,SS:()=>M,UF:()=>l,UY:()=>G,Vl:()=>R,W6:()=>J,Xq:()=>A,YQ:()=>E,Yw:()=>K,dV:()=>C,dX:()=>T,ej:()=>u,em:()=>o,ep:()=>b,fu:()=>w,gC:()=>_,hz:()=>m,jx:()=>g,lV:()=>c,nH:()=>j,oR:()=>r,od:()=>s,p3:()=>y,pG:()=>D,qv:()=>i,qy:()=>L,t1:()=>H,t7:()=>q,tB:()=>v,tK:()=>U,u_:()=>O});const r="core/user",i="connected_url_mismatch",o="__global",a="temporary_persist_permission_error",s="adblocker_active",c=["weekly","monthly","quarterly"],u="googlesitekit_authenticate",l="googlesitekit_setup",g="googlesitekit_view_dashboard",d="googlesitekit_manage_options",f="googlesitekit_read_shared_module_data",m="googlesitekit_manage_module_sharing_options",p="googlesitekit_delegate_module_sharing_management",h="googlesitekit_update_plugins",y="kmAnalyticsAdSenseTopEarningContent",k="kmAnalyticsEngagedTrafficSource",_="kmAnalyticsLeastEngagingPages",w="kmAnalyticsNewVisitors",v="kmAnalyticsPopularAuthors",b="kmAnalyticsPopularContent",D="kmAnalyticsPopularProducts",T="kmAnalyticsReturningVisitors",$="kmAnalyticsTopCities",S="kmAnalyticsTopCitiesDrivingLeads",A="kmAnalyticsTopCitiesDrivingAddToCart",N="kmAnalyticsTopCitiesDrivingPurchases",E="kmAnalyticsTopDeviceDrivingPurchases",L="kmAnalyticsTopConvertingTrafficSource",C="kmAnalyticsTopCountries",I="kmAnalyticsTopPagesDrivingLeads",P="kmAnalyticsTopRecentTrendingPages",M="kmAnalyticsTopTrafficSource",R="kmAnalyticsTopTrafficSourceDrivingAddToCart",O="kmAnalyticsTopTrafficSourceDrivingLeads",x="kmAnalyticsTopTrafficSourceDrivingPurchases",j="kmAnalyticsPagesPerVisit",F="kmAnalyticsVisitLength",U="kmAnalyticsTopReturningVisitorPages",q="kmSearchConsolePopularKeywords",K="kmAnalyticsVisitsPerVisitor",G="kmAnalyticsMostEngagingPages",H="kmAnalyticsTopCategories",J=[y,k,_,w,v,b,D,T,H,$,S,A,N,E,L,C,P,M,R,j,F,U,K,G,H],V=[...J,q]},65214:(t,e,n)=>{n.d(e,{G:()=>i,t:()=>r});const r=new Set(n.g?._googlesitekitBaseData?.enabledFeatures||[]);function i(t,e=r){return e instanceof Set&&e.has(t)}},67150:(t,e,n)=>{n.d(e,{G:()=>u,HY:()=>g,SG:()=>l,db:()=>i,e4:()=>f,vl:()=>d});n(17243);var r=n(82871);const i="missing_required_scopes",o="insufficientPermissions",a="forbidden",s="internal_server_error",c="invalid_json";function u(t){return t?.code===i}function l(t){return[o,a].includes(t?.data?.reason)}function g(t){return!!t?.data?.reconnectURL}function d(t,e){return!(!e?.storeName||l(t)||u(t)||g(t))}function f(t){return t?.code===s?(0,r.__)("There was a critical error on this website while fetching data","google-site-kit"):t?.code===c?(0,r.__)("The server provided an invalid response","google-site-kit"):t?.message}},79829:(t,e,n)=>{n.d(e,{Eo:()=>g,JK:()=>p,K5:()=>m,jq:()=>f,mK:()=>l});var r=n(17243),i=n(50532),o=n.n(i),a=n(82871);function s(t,e={}){const{formatUnit:n,formatDecimal:r}=function(t,e={}){const{hours:n,minutes:r,seconds:i}=c(t);return{hours:n,minutes:r,seconds:i,formatUnit(){const{unitDisplay:o="short",...s}=e,c={unitDisplay:o,...s,style:"unit"};return 0===t?f(i,{...c,unit:"second"}):(0,a.sprintf)(/* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */ /* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */ (0,a._x)("%3$s %2$s %1$s","duration of time: hh mm ss","google-site-kit"),i?f(i,{...c,unit:"second"}):"",r?f(r,{...c,unit:"minute"}):"",n?f(n,{...c,unit:"hour"}):"").trim()},formatDecimal(){const e=(0,a.sprintf)( // translators: %s: number of seconds with "s" as the abbreviated unit. // translators: %s: number of seconds with "s" as the abbreviated unit. (0,a.__)("%ds","google-site-kit"),i);if(0===t)return e;const o=(0,a.sprintf)( // translators: %s: number of minutes with "m" as the abbreviated unit. // translators: %s: number of minutes with "m" as the abbreviated unit. (0,a.__)("%dm","google-site-kit"),r),s=(0,a.sprintf)( // translators: %s: number of hours with "h" as the abbreviated unit. // translators: %s: number of hours with "h" as the abbreviated unit. (0,a.__)("%dh","google-site-kit"),n);return(0,a.sprintf)(/* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */ /* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */ (0,a._x)("%3$s %2$s %1$s","duration of time: hh mm ss","google-site-kit"),i?e:"",r?o:"",n?s:"").trim()}}}(t,e);try{return n()}catch{return r()}}function c(t){t=parseInt(t,10),Number.isNaN(t)&&(t=0);return{hours:Math.floor(t/60/60),minutes:Math.floor(t/60%60),seconds:Math.floor(t%60)}}function u(t){return 1e6<=t?Math.round(t/1e5)/10:1e4<=t?Math.round(t/1e3):1e3<=t?Math.round(t/100)/10:t}function l(t){let e={};return"%"===t?e={style:"percent",maximumFractionDigits:2}:"s"===t?e={style:"duration",unitDisplay:"narrow"}:t&&"string"==typeof t?e={style:"currency",currency:t}:(0,r.isPlainObject)(t)&&(e={...t}),e}function g(t,e={}){t=(0,r.isFinite)(t)?t:Number(t),(0,r.isFinite)(t)||(console.warn("Invalid number",t,typeof t),t=0);const n=l(e),{style:i="metric"}=n;return"metric"===i?function(t){const e={minimumFractionDigits:1,maximumFractionDigits:1};return 1e6<=t?(0,a.sprintf)( // translators: %s: an abbreviated number in millions. // translators: %s: an abbreviated number in millions. (0,a.__)("%sM","google-site-kit"),f(u(t),t%10==0?{}:e)):1e4<=t?(0,a.sprintf)( // translators: %s: an abbreviated number in thousands. // translators: %s: an abbreviated number in thousands. (0,a.__)("%sK","google-site-kit"),f(u(t))):1e3<=t?(0,a.sprintf)( // translators: %s: an abbreviated number in thousands. // translators: %s: an abbreviated number in thousands. (0,a.__)("%sK","google-site-kit"),f(u(t),t%10==0?{}:e)):f(t,{signDisplay:"never",maximumFractionDigits:1})}(t):"duration"===i?s(t,n):"durationISO"===i?function(t){let{hours:e,minutes:n,seconds:r}=c(t);return r=("0"+r).slice(-2),n=("0"+n).slice(-2),e=("0"+e).slice(-2),"00"===e?`${n}:${r}`:`${e}:${n}:${r}`}(t):f(t,n)}const d=o()(console.warn);function f(t,e={}){const{locale:n=p(),...r}=e;try{return new Intl.NumberFormat(n,r).format(t)}catch(e){d(`Site Kit numberFormat error: Intl.NumberFormat( ${JSON.stringify(n)}, ${JSON.stringify(r)} ).format( ${typeof t} )`,e.message)}const i={currencyDisplay:"narrow",currencySign:"accounting",style:"unit"},o=["signDisplay","compactDisplay"],a={};for(const[t,e]of Object.entries(r))i[t]&&e===i[t]||o.includes(t)||(a[t]=e);try{return new Intl.NumberFormat(n,a).format(t)}catch{return new Intl.NumberFormat(n).format(t)}}function m(t,e={}){const{locale:n=p(),style:r="long",type:i="conjunction"}=e;if(Intl.ListFormat){return new Intl.ListFormat(n,{style:r,type:i}).format(t)} /* translators: used between list items, there is a space after the comma. */const o=(0,a.__)(", ","google-site-kit");return t.join(o)}function p(t=n.g){const e=(0,r.get)(t,["_googlesitekitLegacyData","locale"]);if(e){const t=e.match(/^(\w{2})?(_)?(\w{2})/);if(t&&t[0])return t[0].replace(/_/g,"-")}return t.navigator.language}},82046:(t,e,n)=>{n.d(e,{C:()=>o,p:()=>i});var r=n(15210);function i(t,e={}){return{__html:r.O.sanitize(t,e)}}function o(t){const e="object"==typeof t?t.toString():t;return e?.replace?.(/\/+$/,"")}},82871:t=>{t.exports=googlesitekit.i18n},89318:(t,e,n)=>{n.d(e,{M9:()=>$,sx:()=>D,BI:()=>T});var r=n(17243);const i="_googlesitekitDataLayer",o="data-googlesitekit-gtag";function a(t){return function(){t[i]=t[i]||[],t[i].push(arguments)}}var s=n(65214);const c={activeModules:[],isAuthenticated:!1,referenceSiteURL:"",trackingEnabled:!1,trackingID:"",userIDHash:"",userRoles:[]};const{activeModules:u=[],isSiteKitScreen:l,trackingEnabled:g,trackingID:d,referenceSiteURL:f,userIDHash:m,isAuthenticated:p,userRoles:h}=n.g._googlesitekitTrackingData||{},{GOOGLESITEKIT_VERSION:y}=n.g,k={activeModules:u,trackingEnabled:g,trackingID:d,referenceSiteURL:f,userIDHash:m,isSiteKitScreen:l,userRoles:h,isAuthenticated:p,pluginVersion:y},{enableTracking:_,disableTracking:w,isTrackingEnabled:v,initializeSnippet:b,trackEvent:D,trackEventOnce:T}=function(t,e=n.g,u=n.g){const l={...c,...t};l.referenceSiteURL&&(l.referenceSiteURL=l.referenceSiteURL.toString().replace(/\/+$/,""));const g=function(t,e){const r=a(e);let c;const{activeModules:u,referenceSiteURL:l,userIDHash:g,userRoles:d=[],isAuthenticated:f,pluginVersion:m}=t;return function(){const{document:e}=n.g;if(void 0===c&&(c=!!e.querySelector(`script[${o}]`)),c)return!1;c=!0;const a=d?.length?d.join(","):"";r("js",new Date),r("config",t.trackingID,{groups:"site_kit",send_page_view:t.isSiteKitScreen,domain:l,plugin_version:m||"",enabled_features:Array.from(s.t).join(","),active_modules:u.join(","),authenticated:f?"1":"0",user_properties:{user_roles:a,user_identifier:g}});const p=e.createElement("script");return p.setAttribute(o,""),p.async=!0,p.src=`https://www.googletagmanager.com/gtag/js?id=${t.trackingID}&l=${i}`,e.head.appendChild(p),{scriptTagSrc:`https://www.googletagmanager.com/gtag/js?id=${t.trackingID}&l=${i}`}}}(l,e),d=function(t,e,n,r){const i=a(e);return async function(e,o,a,s){const{trackingEnabled:c}=t;if(!c)return null;n();const u={send_to:"site_kit",event_category:e,event_label:a,value:s};return new Promise(t=>{const n=setTimeout(function(){r.console.warn(`Tracking event "${o}" (category "${e}") took too long to fire.`),t()},1e3);function a(){clearTimeout(n),t()}i("event",o,{...u,event_callback:a}),r._gaUserPrefs?.ioo?.()&&a()})}}(l,e,g,u),f={};return{enableTracking:function(){l.trackingEnabled=!0},disableTracking:function(){l.trackingEnabled=!1},initializeSnippet:g,isTrackingEnabled:function(){return!!l.trackingEnabled},trackEvent:d,trackEventOnce:function(...t){const e=JSON.stringify(t);f[e]||(f[e]=(0,r.once)(d)),f[e](...t)}}}(k);function $(t){t?_():w()}l&&g&&b()},94552:(t,e,n)=>{n.d(e,{A:()=>c});var r=n(62688),i=n.n(r),o=n(4452),a=n.n(o),s=n(62540);function ChangeArrow({direction:t,invertColor:e,width:n,height:r}){return(0,s.jsx)("svg",{className:a()("googlesitekit-change-arrow",`googlesitekit-change-arrow--${t}`,{"googlesitekit-change-arrow--inverted-color":e}),width:n,height:r,viewBox:"0 0 10 10",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:(0,s.jsx)("path",{d:"M5.625 10L5.625 2.375L9.125 5.875L10 5L5 -1.76555e-07L-2.7055e-07 5L0.875 5.875L4.375 2.375L4.375 10L5.625 10Z",fill:"currentColor"})})}ChangeArrow.propTypes={direction:i().string,invertColor:i().bool,width:i().number,height:i().number},ChangeArrow.defaultProps={direction:"up",invertColor:!1,width:9,height:9};const c=ChangeArrow}},t=>{t.O(0,[660],()=>{return e=50618,t(t.s=e);var e});t.O()}]);{"translation-revision-date":"2023-10-11 19:32:01+0000","generator":"WP-CLI\/2.12.0","source":"src\/HelpCenter\/tours\/plugin-management.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"fr","plural-forms":"nplurals=2; plural=n > 1;"},"Click here to add another plugin to your site.":["Cliquez ici pour ajouter un autre plugin sur votre site."],"Add another":["Ajouter un autre"],"Under each plugin you can activate or deactivate it.":["Sous chaque plugin, vous avez la possibilit\u00e9 de l'activer et le d\u00e9sactiver."],"Deactivate\/activate option":["Option d\u00e9sactiver\/activer"],"See all plugins installed on your site. This includes plugins that are active and deactivated.":["Consultez tous les plugins install\u00e9s sur votre site. Cela inclut les plugins actifs et d\u00e9sactiv\u00e9s."],"Installed plugins":["Plugins install\u00e9s"],"Click this menu to see and manage the plugins you have installed.":["Cliquez sur ce menu pour consulter et g\u00e9rer les plugins que vous avez install\u00e9s."],"Installed Plugins menu":["Menu Plugins install\u00e9s"],"Plugin management":["Gestion des extensions"]}}} /*! For license information please see assets-manager.min.js.LICENSE.txt */ (()=>{var r={9535:(r,u,c)=>{var p=c(89736);function _regenerator(){var u,c,l="function"==typeof Symbol?Symbol:{},_=l.iterator||"@@iterator",y=l.toStringTag||"@@toStringTag";function i(r,l,_,y){var x=l&&l.prototype instanceof Generator?l:Generator,m=Object.create(x.prototype);return p(m,"_invoke",function(r,p,l){var _,y,x,m=0,h=l||[],b=!1,g={p:0,n:0,v:u,a:d,f:d.bind(u,4),d:function d(r,c){return _=r,y=0,x=u,g.n=c,v}};function d(r,p){for(y=r,x=p,c=0;!b&&m&&!l&&c3?(l=k===p)&&(x=_[(y=_[4])?5:(y=3,3)],_[4]=_[5]=u):_[0]<=w&&((l=r<2&&w<_[1])?(y=0,g.v=p,g.n=_[1]):wp||p>k)&&(_[4]=r,_[5]=p,g.n=k,y=0))}if(l||r>1)return v;throw b=!0,p}return function(l,h,w){if(m>1)throw TypeError("Generator is already running");for(b&&1===h&&d(h,w),y=h,x=w;(c=y<2?u:x)||!b;){_||(y?y<3?(y>1&&(g.n=-1),d(y,x)):g.n=x:g.v=x);try{if(m=2,_){if(y||(l="next"),c=_[l]){if(!(c=c.call(_,x)))throw TypeError("iterator result is not an object");if(!c.done)return c;x=c.value,y<2&&(y=0)}else 1===y&&(c=_.return)&&c.call(_),y<2&&(x=TypeError("The iterator does not provide a '"+l+"' method"),y=1);_=u}else if((c=(b=g.n<0)?x:r.call(p,g))!==v)break}catch(r){_=u,y=1,x=r}finally{m=1}}return{value:c,done:b}}}(r,_,y),!0),m}var v={};function Generator(){}function GeneratorFunction(){}function GeneratorFunctionPrototype(){}c=Object.getPrototypeOf;var x=[][_]?c(c([][_]())):(p(c={},_,function(){return this}),c),m=GeneratorFunctionPrototype.prototype=Generator.prototype=Object.create(x);function f(r){return Object.setPrototypeOf?Object.setPrototypeOf(r,GeneratorFunctionPrototype):(r.__proto__=GeneratorFunctionPrototype,p(r,y,"GeneratorFunction")),r.prototype=Object.create(m),r}return GeneratorFunction.prototype=GeneratorFunctionPrototype,p(m,"constructor",GeneratorFunctionPrototype),p(GeneratorFunctionPrototype,"constructor",GeneratorFunction),GeneratorFunction.displayName="GeneratorFunction",p(GeneratorFunctionPrototype,y,"GeneratorFunction"),p(m),p(m,y,"Generator"),p(m,_,function(){return this}),p(m,"toString",function(){return"[object Generator]"}),(r.exports=_regenerator=function _regenerator(){return{w:i,m:f}},r.exports.__esModule=!0,r.exports.default=r.exports)()}r.exports=_regenerator,r.exports.__esModule=!0,r.exports.default=r.exports},10564:r=>{function _typeof(u){return r.exports=_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},r.exports.__esModule=!0,r.exports.default=r.exports,_typeof(u)}r.exports=_typeof,r.exports.__esModule=!0,r.exports.default=r.exports},33929:(r,u,c)=>{var p=c(67114),l=c(89736);r.exports=function AsyncIterator(r,u){function n(c,l,_,y){try{var v=r[c](l),x=v.value;return x instanceof p?u.resolve(x.v).then(function(r){n("next",r,_,y)},function(r){n("throw",r,_,y)}):u.resolve(x).then(function(r){v.value=r,_(v)},function(r){return n("throw",r,_,y)})}catch(r){y(r)}}var c;this.next||(l(AsyncIterator.prototype),l(AsyncIterator.prototype,"function"==typeof Symbol&&Symbol.asyncIterator||"@asyncIterator",function(){return this})),l(this,"_invoke",function(r,p,l){function f(){return new u(function(u,c){n(r,l,u,c)})}return c=c?c.then(f,f):f()},!0)},r.exports.__esModule=!0,r.exports.default=r.exports},46313:(r,u,c)=>{var p=c(9535),l=c(33929);r.exports=function _regeneratorAsyncGen(r,u,c,_,y){return new l(p().w(r,u,c,_),y||Promise)},r.exports.__esModule=!0,r.exports.default=r.exports},53051:(r,u,c)=>{var p=c(67114),l=c(9535),_=c(62507),y=c(46313),v=c(33929),x=c(95315),m=c(66961);function _regeneratorRuntime(){"use strict";var u=l(),c=u.m(_regeneratorRuntime),h=(Object.getPrototypeOf?Object.getPrototypeOf(c):c.__proto__).constructor;function n(r){var u="function"==typeof r&&r.constructor;return!!u&&(u===h||"GeneratorFunction"===(u.displayName||u.name))}var b={throw:1,return:2,break:3,continue:3};function a(r){var u,c;return function(p){u||(u={stop:function stop(){return c(p.a,2)},catch:function _catch(){return p.v},abrupt:function abrupt(r,u){return c(p.a,b[r],u)},delegateYield:function delegateYield(r,l,_){return u.resultName=l,c(p.d,m(r),_)},finish:function finish(r){return c(p.f,r)}},c=function t(r,c,l){p.p=u.prev,p.n=u.next;try{return r(c,l)}finally{u.next=p.n}}),u.resultName&&(u[u.resultName]=p.v,u.resultName=void 0),u.sent=p.v,u.next=p.n;try{return r.call(this,u)}finally{p.p=u.prev,p.n=u.next}}}return(r.exports=_regeneratorRuntime=function _regeneratorRuntime(){return{wrap:function wrap(r,c,p,l){return u.w(a(r),c,p,l&&l.reverse())},isGeneratorFunction:n,mark:u.m,awrap:function awrap(r,u){return new p(r,u)},AsyncIterator:v,async:function async(r,u,c,p,l){return(n(u)?y:_)(a(r),u,c,p,l)},keys:x,values:m}},r.exports.__esModule=!0,r.exports.default=r.exports)()}r.exports=_regeneratorRuntime,r.exports.__esModule=!0,r.exports.default=r.exports},58155:r=>{function asyncGeneratorStep(r,u,c,p,l,_,y){try{var v=r[_](y),x=v.value}catch(r){return void c(r)}v.done?u(x):Promise.resolve(x).then(p,l)}r.exports=function _asyncToGenerator(r){return function(){var u=this,c=arguments;return new Promise(function(p,l){var _=r.apply(u,c);function _next(r){asyncGeneratorStep(_,p,l,_next,_throw,"next",r)}function _throw(r){asyncGeneratorStep(_,p,l,_next,_throw,"throw",r)}_next(void 0)})}},r.exports.__esModule=!0,r.exports.default=r.exports},61790:(r,u,c)=>{var p=c(53051)();r.exports=p;try{regeneratorRuntime=p}catch(r){"object"==typeof globalThis?globalThis.regeneratorRuntime=p:Function("r","regeneratorRuntime = r")(p)}},62507:(r,u,c)=>{var p=c(46313);r.exports=function _regeneratorAsync(r,u,c,l,_){var y=p(r,u,c,l,_);return y.next().then(function(r){return r.done?r.value:y.next()})},r.exports.__esModule=!0,r.exports.default=r.exports},66961:(r,u,c)=>{var p=c(10564).default;r.exports=function _regeneratorValues(r){if(null!=r){var u=r["function"==typeof Symbol&&Symbol.iterator||"@@iterator"],c=0;if(u)return u.call(r);if("function"==typeof r.next)return r;if(!isNaN(r.length))return{next:function next(){return r&&c>=r.length&&(r=void 0),{value:r&&r[c++],done:!r}}}}throw new TypeError(p(r)+" is not iterable")},r.exports.__esModule=!0,r.exports.default=r.exports},67114:r=>{r.exports=function _OverloadYield(r,u){this.v=r,this.k=u},r.exports.__esModule=!0,r.exports.default=r.exports},73903:(r,u)=>{"use strict";Object.defineProperty(u,"__esModule",{value:!0}),u.appendCss=function appendCss(r,u){var p=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(c.has(r))return c.get(r);if(document.getElementById(cssElementId(r))){var l=Promise.resolve();return c.set(r,l),l}var _=new Promise(function(c,l){var _,y=document.createElement("link");y.id=cssElementId(r),y.rel="stylesheet",y.href=u,y.media=null!==(_=p.media)&&void 0!==_?_:"all",y.onload=c,y.onerror=l,document.head.appendChild(y)});return c.set(r,_),_},u.appendJs=function appendJs(r,u){var c=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(p.has(r))return p.get(r);if(document.getElementById(jsElementId(r))){var l=Promise.resolve();return p.set(r,l),l}var _=new Promise(function(p,l){var _,y=document.createElement("script");y.id=jsElementId(r),y.src=u,y.type="text/javascript",y.async=null===(_=c.async)||void 0===_||_,y.onload=p,y.onerror=l,document.body.appendChild(y)});return p.set(r,_),_};var c=new Map,p=new Map;function jsElementId(r){return"".concat(r,"-js")}function cssElementId(r){return"".concat(r,"-css")}},89736:r=>{function _regeneratorDefine(u,c,p,l){var _=Object.defineProperty;try{_({},"",{})}catch(u){_=0}r.exports=_regeneratorDefine=function regeneratorDefine(r,u,c,p){function o(u,c){_regeneratorDefine(r,u,function(r){return this._invoke(u,c,r)})}u?_?_(r,u,{value:c,enumerable:!p,configurable:!p,writable:!p}):r[u]=c:(o("next",0),o("throw",1),o("return",2))},r.exports.__esModule=!0,r.exports.default=r.exports,_regeneratorDefine(u,c,p,l)}r.exports=_regeneratorDefine,r.exports.__esModule=!0,r.exports.default=r.exports},95315:r=>{r.exports=function _regeneratorKeys(r){var u=Object(r),c=[];for(var p in u)c.unshift(p);return function e(){for(;c.length;)if((p=c.pop())in u)return e.value=p,e.done=!1,e;return e.done=!0,e}},r.exports.__esModule=!0,r.exports.default=r.exports},96784:r=>{r.exports=function _interopRequireDefault(r){return r&&r.__esModule?r:{default:r}},r.exports.__esModule=!0,r.exports.default=r.exports}},u={};function __webpack_require__(c){var p=u[c];if(void 0!==p)return p.exports;var l=u[c]={exports:{}};return r[c](l,l.exports,__webpack_require__),l.exports}(()=>{"use strict";var r,u=__webpack_require__(96784),c=u(__webpack_require__(61790)),p=u(__webpack_require__(58155)),l=__webpack_require__(73903);function _createForOfIteratorHelper(r,u){var c="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(!c){if(Array.isArray(r)||(c=function _unsupportedIterableToArray(r,u){if(r){if("string"==typeof r)return _arrayLikeToArray(r,u);var c={}.toString.call(r).slice(8,-1);return"Object"===c&&r.constructor&&(c=r.constructor.name),"Map"===c||"Set"===c?Array.from(r):"Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c)?_arrayLikeToArray(r,u):void 0}}(r))||u&&r&&"number"==typeof r.length){c&&(r=c);var p=0,l=function F(){};return{s:l,n:function n(){return p>=r.length?{done:!0}:{done:!1,value:r[p++]}},e:function e(r){throw r},f:l}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var _,y=!0,v=!1;return{s:function s(){c=c.call(r)},n:function n(){var r=c.next();return y=r.done,r},e:function e(r){v=!0,_=r},f:function f(){try{y||null==c.return||c.return()}finally{if(v)throw _}}}}function _arrayLikeToArray(r,u){(null==u||u>r.length)&&(u=r.length);for(var c=0,p=Array(u);c
${t.replace(/\n{2,}/g,"
")}