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 }) => (
);
// 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;
};