import apiFetch from '@wordpress/api-fetch'; import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useWorkflowStore } from '@agent/state/workflows'; const dynamicClasses = ['is-style-outline']; export const UpdateBlockConfirm = ({ inputs, onConfirm, onCancel }) => { const { block } = useWorkflowStore(); const [loading, setLoading] = useState(true); const detachedEl = useRef(null); const handleConfirm = async () => { await onConfirm({ data: inputs }); setTimeout(() => { // Reload the page after a short delay to let the DOM update window.location.reload(); }, 1500); }; const handleCancel = useCallback(() => { // remove the new block we added const el = document.querySelector('[data-extendify-temp-replacement]'); // unhide the block if (detachedEl.current) { el?.parentNode?.insertBefore(detachedEl.current, el); detachedEl.current = null; } if (el) el.remove(); onCancel(); }, [onCancel]); useEffect(() => { apiFetch({ path: '/extendify/v1/agent/get-block-html', method: 'POST', data: { blockCode: inputs.newContent }, }).then(({ content }) => { // Remove the highlighter window.dispatchEvent(new Event('extendify-agent:remove-block-highlight')); // hide the block const el = document.querySelector( `[data-extendify-agent-block-id="${block.id}"]`, ); // TODO: work out a way to propagate an error here if (!el) return onCancel(); if (detachedEl.current) return; // already done detachedEl.current = el; const patched = patchVariantClasses( content, el.cloneNode(true), dynamicClasses, ); const template = document.createElement('template'); template.innerHTML = patched || '
'; const newEl = template.content.firstElementChild; if (!newEl) return onCancel(); newEl.setAttribute('data-extendify-temp-replacement', true); el.parentNode.insertBefore(newEl, el.nextSibling); el.parentNode?.removeChild(el); setLoading(false); }); }, [block, inputs, onCancel]); if (loading) return ( {__('Loading...', 'extendify-local')} ); return (

{__( 'The agent has made the changes in the browser. Please review and confirm.', 'extendify-local', )}

); }; const Wrapper = ({ children }) => (
{children}
); const Content = ({ children }) => (
{children}
); // escape for regex building const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const getVariantNumbersInTree = (rootEl, base) => { if (!rootEl) return []; const re = new RegExp(`^${esc(base)}--(\\d+)$`, 'i'); const out = []; const seen = new Set(); // include root + descendants const all = [rootEl, ...rootEl.querySelectorAll(`[class*="${base}--"]`)]; for (const el of all) { for (const cls of el.classList) { const m = cls.match(re); if (m) { const n = Number(m[1]); if (!seen.has(n)) { seen.add(n); out.push(n); } } } } return out; // e.g., [3,4] }; const getVariantNumbersInHtml = (html, base) => { const wrapper = document.createElement('div'); wrapper.innerHTML = html; return getVariantNumbersInTree(wrapper, base); // e.g., [1,2] }; const applyVariantNumberMapToHtml = (html, base, numberMap) => { if (!numberMap || !numberMap.size) return html; const wrapper = document.createElement('div'); wrapper.innerHTML = html; const reToken = new RegExp(`^${esc(base)}--(\\d+)$`, 'i'); const ensureBase = base; // e.g. "is-style-outline" // target every element that *could* contain the class const els = wrapper.querySelectorAll(`[class*="${base}--"]`); els.forEach((el) => { const classes = Array.from(el.classList); let changed = false; for (let i = 0; i < classes.length; i++) { const m = classes[i].match(reToken); if (!m) continue; const oldN = Number(m[1]); if (numberMap.has(oldN)) { const newN = numberMap.get(oldN); const nextCls = `${base}--${newN}`; if (nextCls !== classes[i]) { classes[i] = nextCls; changed = true; } } } // keep the base style class too (e.g. "is-style-outline") if (!classes.includes(ensureBase)) { classes.push(ensureBase); changed = true; } if (changed) el.className = classes.join(' '); }); return wrapper.innerHTML; }; /** * This takes classes like "is-style-outline--N" and patches them to match the current DOM structure, since they are reordered by WP when parsing blocks. */ const patchVariantClasses = (html, el, bases) => { let out = html; bases.forEach((base) => { const targetNums = getVariantNumbersInTree(el, base); // e.g., [3,4] const currentNums = getVariantNumbersInHtml(out, base); // e.g., [1,2] if (!targetNums.length || !currentNums.length) return; // order-preserving mapping: 1->3, 2->4, ... const count = Math.min(targetNums.length, currentNums.length); const map = new Map(); for (let i = 0; i < count; i++) map.set(currentNums[i], targetNums[i]); out = applyVariantNumberMapToHtml(out, base, map); }); return out; };