import { walkAndUpdateImageDetails } from '@agent/lib/blocks';
import { useQuickEditStore } from '@quick-edit/state/store';
import {
addCustomMediaViewsCss,
removeCustomMediaViewsCss,
} from '@shared/lib/media-views';
import { registerCoreBlocks } from '@wordpress/block-library';
import { getBlockTypes } from '@wordpress/blocks';
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { MediaUpload } from '@wordpress/media-utils';
const openButton = __('Open Media Library', 'extendify-local');
const normalizeImageUrl = (url) => {
if (!url) return '';
const base = url.split('?')[0].split('#')[0];
try {
return decodeURIComponent(base);
} catch {
return base;
}
};
// The rendered src often diverges from inputs.url — a srcset candidate, a
// re-encoded comma, or extra CDN query params — so we match on the normalized
// URL and, failing that, fall back to the sole image (a single-image block is
// unambiguous) rather than missing the swap entirely.
const findLiveImage = (blockId, url) => {
if (!blockId) return null;
const scope = document.querySelector(
`[data-extendify-agent-block-id="${blockId}"]`,
);
if (!scope) return null;
const images = [...scope.querySelectorAll('img')];
if (!images.length) return null;
const target = normalizeImageUrl(url);
const match = images.find(
(img) => normalizeImageUrl(img.getAttribute('src')) === target,
);
return match ?? (images.length === 1 ? images[0] : null);
};
export const UpdateImageConfirm = ({ inputs, onConfirm, onCancel }) => {
const [showConfirmation, setShowConfirmation] = useState(false);
const block = useQuickEditStore((s) => s.agentBlock);
// The selected attachment plus the live
{sprintf(
__(
'The agent has requested the media library. Press "%s" to upload or select an image.',
'extendify-local',
),
openButton,
)}
we swapped and its original
// src/srcset, so the preview reverts to exactly what was there on cancel.
const preview = useRef(null);
const resetImagePreview = useCallback(() => {
const swapped = preview.current;
if (!swapped?.el) return;
swapped.el.src = swapped.src;
if (swapped.srcset) {
swapped.el.srcset = swapped.srcset;
} else {
swapped.el.removeAttribute('srcset');
}
}, []);
const confirmed = useRef(false);
useEffect(
() => () => {
if (!confirmed.current) resetImagePreview();
},
[resetImagePreview],
);
const previewImage = (image) => {
const liveImage = findLiveImage(block?.id, inputs.url);
preview.current = {
image,
el: liveImage,
src: liveImage?.getAttribute('src') ?? '',
srcset: liveImage?.getAttribute('srcset') ?? '',
};
if (liveImage) {
liveImage.srcset = '';
liveImage.src = image.url;
}
// Advance as soon as an image is chosen. The picker's single click
// auto-confirms (the frame closes itself), and QuickEdit's QE_INTERIOR
// guard keeps that click from canceling the workflow — so the frame is
// torn down cleanly before this unmounts the picker. The live swap is
// best-effort; the real change is applied server-side on confirm.
setShowConfirmation(true);
};
const handleConfirm = async () => {
const image = preview.current?.image;
if (!image) return;
confirmed.current = true;
await onConfirm({
data: {
previousContent: inputs.previousContent,
newContent: walkAndUpdateImageDetails(inputs, image),
},
shouldRefreshPage: true,
});
};
useEffect(() => {
if (getBlockTypes().length !== 0) return;
registerCoreBlocks();
}, []);
useEffect(() => {
// Put modal above the Agent
const style = document.createElement('style');
style.textContent = `.media-modal {
z-index: 999999 !important;
}`;
document.head.appendChild(style);
return () => style.remove();
}, []);
useEffect(() => {
addCustomMediaViewsCss();
return () => removeCustomMediaViewsCss();
}, []);
if (showConfirmation) {
return (
{__( 'The agent has made the changes in the browser. Please review and confirm.', 'extendify-local', )}