| @@ -30,8 +30,18 @@ | ||
| 30 | 30 | delete styles.backgroundColor; |
| 31 | 31 | return styles && Object.keys(styles).length ? styles : null; |
| 32 | 32 | }; |
| 33 | 33 | |
| 34 | +// "A bit smaller" needs the picture's rendered width; the wrapper's is the row's. | |
| 35 | +const PICTURE_TYPES = new Set(['core/image', 'core/site-logo']); | |
| 36 | + | |
| 37 | +const pictureWidth = (el) => { | |
| 38 | + const img = el.matches?.('img') ? el : el.querySelector?.('img'); | |
| 39 | + if (!img) return null; | |
| 40 | + const width = img.ownerDocument.defaultView.getComputedStyle(img).width; | |
| 41 | + return width && width !== 'auto' && width !== '0px' ? width : null; | |
| 42 | +}; | |
| 43 | + | |
| 34 | 44 | // style.css lives in a scoped `wp-custom-css-*` rule, invisible in the block code. |
| 35 | 45 | const customCss = (el) => { |
| 36 | 46 | const cls = [...(el.classList ?? [])].find((c) => |
| 37 | 47 | c.startsWith('wp-custom-css-'), |
| @@ -67,9 +77,13 @@ | ||
| 67 | 77 | return clone; |
| 68 | 78 | }; |
| 69 | 79 | |
| 70 | 80 | const ownText = (clone) => { |
| 71 | - const text = (clone.textContent || '').replace(/\s+/g, ' ').trim(); | |
| 81 | + const readable = clone.cloneNode(true); | |
| 82 | + for (const hidden of readable.querySelectorAll('[aria-hidden="true"]')) { | |
| 83 | + hidden.remove(); | |
| 84 | + } | |
| 85 | + const text = (readable.textContent || '').replace(/\s+/g, ' ').trim(); | |
| 72 | 86 | return text.length > 80 ? `${text.slice(0, 80)}…` : text; |
| 73 | 87 | }; |
| 74 | 88 | |
| 75 | 89 | const colorSlugs = (clone) => { |
| @@ -87,8 +101,34 @@ | ||
| 87 | 101 | } |
| 88 | 102 | return out; |
| 89 | 103 | }; |
| 90 | 104 | |
| 105 | +// Menu and icon colours live on the container, never the link that was clicked. | |
| 106 | +// detectBlockType refuses these types so a click can't select the wrapper. | |
| 107 | +const STYLE_OWNERS = [ | |
| 108 | + { selector: '.wp-block-navigation', type: 'core/navigation' }, | |
| 109 | + { selector: '.wp-block-social-links', type: 'core/social-links' }, | |
| 110 | +]; | |
| 111 | + | |
| 112 | +export const ownerAncestorFor = (root) => { | |
| 113 | + if (!root?.parentElement) return null; | |
| 114 | + const inScope = partScope(root); | |
| 115 | + for (const { selector, type } of STYLE_OWNERS) { | |
| 116 | + const el = root.parentElement.closest(selector); | |
| 117 | + if (!el || !inScope(el)) continue; | |
| 118 | + const blockId = blockIdOf(el); | |
| 119 | + if (!blockId) continue; | |
| 120 | + const styles = renderedStyles(el); | |
| 121 | + return { | |
| 122 | + blockId, | |
| 123 | + type, | |
| 124 | + ...colorSlugs(ownSubtree(el)), | |
| 125 | + ...(styles && { styles }), | |
| 126 | + }; | |
| 127 | + } | |
| 128 | + return null; | |
| 129 | +}; | |
| 130 | + | |
| 91 | 131 | // Per-block summary of the selection — enough context for the agent to pick |
| 92 | 132 | // targets ("the red button"); schemas + full code are fetched after the pick. |
| 93 | 133 | export const buildSubtreeManifest = (root) => { |
| 94 | 134 | if (!root) return []; |
| @@ -112,9 +152,12 @@ | ||
| 112 | 152 | !colors.backgroundColor && |
| 113 | 153 | !colors.textColor |
| 114 | 154 | ) |
| 115 | 155 | continue; |
| 116 | - const styles = renderedStyles(el); | |
| 156 | + const width = PICTURE_TYPES.has(type) ? pictureWidth(el) : null; | |
| 157 | + const styles = width | |
| 158 | + ? { ...renderedStyles(el), width } | |
| 159 | + : renderedStyles(el); | |
| 117 | 160 | const css = customCss(el); |
| 118 | 161 | manifest.push({ |
| 119 | 162 | blockId, |
| 120 | 163 | type, |