| @@ -1,5 +1,7 @@ | ||
| 1 | 1 | import { usePortal } from '@agent/hooks/usePortal'; |
| 2 | +import { blockCodeQueryArgs } from '@agent/lib/block-code'; | |
| 3 | +import { findBlockEl, scopeOf } from '@agent/lib/block-el'; | |
| 2 | 4 | import { useWorkflowStore } from '@agent/state/workflows'; |
| 3 | 5 | import { whenAnimationsSettle } from '@quick-edit/lib/after-animations'; |
| 4 | 6 | import { MEDIA_RING, needsContrastRing } from '@quick-edit/lib/over-media'; |
| 5 | 7 | import { useQuickEditStore } from '@quick-edit/state/store'; |
| @@ -22,9 +24,14 @@ | ||
| 22 | 24 | // Render-only after the selector unification: `agentBlock` is set |
| 23 | 25 | // by Quick Edit's Ask AI flow (or future workflows), and this component |
| 24 | 26 | // draws the outline + X-close indicator. Hover-bar owns hover + click |
| 25 | 27 | // selection on the live page; DOMHighlighter no longer listens for either. |
| 26 | -export const DOMHighlighter = ({ busy = false, working = false }) => { | |
| 28 | +export const DOMHighlighter = ({ | |
| 29 | + busy = false, | |
| 30 | + working = false, | |
| 31 | + waiting = false, | |
| 32 | + taskActive = false, | |
| 33 | +}) => { | |
| 27 | 34 | const [rect, setRect] = useState(null); |
| 28 | 35 | const [ringNeeded, setRingNeeded] = useState(false); |
| 29 | 36 | const mountNode = usePortal('extendify-agent-dom-mount'); |
| 30 | 37 | const el = useRef(null); |
| @@ -42,14 +49,13 @@ | ||
| 42 | 49 | |
| 43 | 50 | useEffect(() => { |
| 44 | 51 | if (!block?.id) return; |
| 45 | 52 | const ac = new AbortController(); |
| 46 | - const postId = window.extAgentData?.context?.postId; | |
| 47 | - if (!postId) return; | |
| 48 | - const queryArgs = { | |
| 49 | - postId: String(postId), | |
| 50 | - blockId: String(block.id), | |
| 51 | - }; | |
| 53 | + const queryArgs = blockCodeQueryArgs( | |
| 54 | + block, | |
| 55 | + window.extAgentData?.context?.postId, | |
| 56 | + ); | |
| 57 | + if (!queryArgs) return; | |
| 52 | 58 | |
| 53 | 59 | const isAlive = { current: true }; |
| 54 | 60 | (async () => { |
| 55 | 61 | const res = await apiFetch({ |
| @@ -68,12 +74,9 @@ | ||
| 68 | 74 | // Re-syncs the rect for programmatic block changes (e.g. Ask AI) |
| 69 | 75 | // and after the wp-site-blocks open/close transform settles. |
| 70 | 76 | useEffect(() => { |
| 71 | 77 | if (!block?.id) return; |
| 72 | - const attr = block.target || 'data-extendify-agent-block-id'; | |
| 73 | - const match = document.querySelector( | |
| 74 | - `[${attr}="${CSS.escape(String(block.id))}"]`, | |
| 75 | - ); | |
| 78 | + const match = findBlockEl(block.id, document, scopeOf(block)); | |
| 76 | 79 | if (!match) return; |
| 77 | 80 | el.current = match; |
| 78 | 81 | setRingNeeded(needsContrastRing(match)); |
| 79 | 82 | |
| @@ -124,13 +127,10 @@ | ||
| 124 | 127 | }, []); |
| 125 | 128 | |
| 126 | 129 | useEffect(() => { |
| 127 | 130 | if (!block?.id) return; |
| 128 | - const attr = block.target || 'data-extendify-agent-block-id'; | |
| 129 | 131 | const handle = () => { |
| 130 | - const match = document.querySelector( | |
| 131 | - `[${attr}="${CSS.escape(String(block.id))}"]`, | |
| 132 | - ); | |
| 132 | + const match = findBlockEl(block.id, document, scopeOf(block)); | |
| 133 | 133 | // Clearing only the rect leaves the chip counting a gone node. |
| 134 | 134 | if (!match) { |
| 135 | 135 | clearBlock(); |
| 136 | 136 | return; |
| @@ -189,20 +189,14 @@ | ||
| 189 | 189 | resizeObserver.disconnect(); |
| 190 | 190 | }; |
| 191 | 191 | }, [el.current]); |
| 192 | 192 | |
| 193 | - // Workflows can mutate the page while the outline is up: a tool that | |
| 194 | - // re-renders the block produces a new DOM node with the same | |
| 195 | - // data-extendify-agent-block-id, and ancestor reflows can shift the | |
| 196 | - // element without changing its own size (ResizeObserver misses both). | |
| 197 | - // Re-query and re-measure on any wp-site-blocks subtree mutation, | |
| 198 | - // rAF-debounced so a burst of mutations costs one measurement. | |
| 193 | + // ResizeObserver misses a replaced node and an ancestor reflow, so the | |
| 194 | + // outline stays on the old box. | |
| 199 | 195 | useEffect(() => { |
| 200 | 196 | if (!block?.id) return; |
| 201 | 197 | const root = document.querySelector('.wp-site-blocks'); |
| 202 | 198 | if (!root) return; |
| 203 | - const attr = block.target || 'data-extendify-agent-block-id'; | |
| 204 | - const sel = `[${attr}="${CSS.escape(String(block.id))}"]`; | |
| 205 | 199 | |
| 206 | 200 | let rafId = 0; |
| 207 | 201 | const observer = new MutationObserver(() => { |
| 208 | 202 | if (rafId) return; |
| @@ -207,9 +201,9 @@ | ||
| 207 | 201 | const observer = new MutationObserver(() => { |
| 208 | 202 | if (rafId) return; |
| 209 | 203 | rafId = window.requestAnimationFrame(() => { |
| 210 | 204 | rafId = 0; |
| 211 | - const match = document.querySelector(sel); | |
| 205 | + const match = findBlockEl(block.id, document, scopeOf(block)); | |
| 212 | 206 | if (!match) return; |
| 213 | 207 | el.current = match; |
| 214 | 208 | const r = match.getBoundingClientRect(); |
| 215 | 209 | if (r.width <= 0 || r.height <= 0) return; |
| @@ -246,8 +240,24 @@ | ||
| 246 | 240 | if (!root) return; |
| 247 | 241 | root.classList.add('extendify-agent-busy'); |
| 248 | 242 | return () => root.classList.remove('extendify-agent-busy'); |
| 249 | 243 | }, [busy]); |
| 244 | + | |
| 245 | + useEffect(() => { | |
| 246 | + if (!taskActive) return; | |
| 247 | + const root = document.querySelector('.wp-site-blocks'); | |
| 248 | + if (!root) return; | |
| 249 | + root.classList.add('extendify-agent-task'); | |
| 250 | + return () => root.classList.remove('extendify-agent-task'); | |
| 251 | + }, [taskActive]); | |
| 252 | + | |
| 253 | + useEffect(() => { | |
| 254 | + if (!waiting) return; | |
| 255 | + const root = document.querySelector('.wp-site-blocks'); | |
| 256 | + if (!root) return; | |
| 257 | + root.classList.add('extendify-agent-waiting'); | |
| 258 | + return () => root.classList.remove('extendify-agent-waiting'); | |
| 259 | + }, [waiting]); | |
| 250 | 260 | |
| 251 | 261 | useEffect(() => { |
| 252 | 262 | if (!working) return; |
| 253 | 263 | const root = document.querySelector('.wp-site-blocks'); |