| 1 |
import { createBlock, parse, serialize } from '@wordpress/blocks'; |
| 2 |
|
| 3 |
// core/media-text keys its image off media* attributes; url/id do nothing there. |
| 4 |
const IMAGE_ATTRIBUTES = { |
| 5 |
'core/image': { url: 'url', id: 'id', alt: 'alt' }, |
| 6 |
'core/cover': { url: 'url', id: 'id', alt: 'alt' }, |
| 7 |
'core/media-text': { |
| 8 |
url: 'mediaUrl', |
| 9 |
id: 'mediaId', |
| 10 |
alt: 'mediaAlt', |
| 11 |
// A video side would keep rendering as one. |
| 12 |
set: { mediaType: 'image' }, |
| 13 |
// mediaSizeSlug names a size of the attachment being replaced. |
| 14 |
clear: ['mediaSizeSlug'], |
| 15 |
}, |
| 16 |
}; |
| 17 |
|
| 18 |
// Swapping an un-fetched url into the live img flashes broken while it loads. |
| 19 |
export const preloadImage = (src) => |
| 20 |
new Promise((resolve, reject) => { |
| 21 |
const img = new Image(); |
| 22 |
img.onload = () => resolve(src); |
| 23 |
img.onerror = reject; |
| 24 |
img.src = src; |
| 25 |
}); |
| 26 |
|
| 27 |
// The block's own save() regenerates the markup (img src, wp-image-{id}) from |
| 28 |
// the attributes, so nothing here touches the HTML. |
| 29 |
export const swapBlockImage = (serializedBlock, image) => { |
| 30 |
const url = image?.source_url || image?.url; |
| 31 |
if (!url || !image?.id) return null; |
| 32 |
// The attachment's alt carries the AI-generated disclosure prefix. |
| 33 |
const alt = image.alt_text ?? ''; |
| 34 |
const parsed = parse(serializedBlock); |
| 35 |
if (!parsed.some((block) => IMAGE_ATTRIBUTES[block.name])) return null; |
| 36 |
const blocks = parsed.map((block) => { |
| 37 |
const slots = IMAGE_ATTRIBUTES[block.name]; |
| 38 |
if (!slots) return block; |
| 39 |
const attributes = { |
| 40 |
...block.attributes, |
| 41 |
[slots.url]: url, |
| 42 |
[slots.id]: image.id, |
| 43 |
...slots.set, |
| 44 |
}; |
| 45 |
if (alt) attributes[slots.alt] = alt; |
| 46 |
else delete attributes[slots.alt]; |
| 47 |
for (const name of slots.clear ?? []) delete attributes[name]; |
| 48 |
// Remove the import class to stop the unsplash cron from seeing it |
| 49 |
const className = (attributes.className ?? '') |
| 50 |
.split(/\s+/) |
| 51 |
.filter((cls) => cls && cls !== 'extendify-image-import') |
| 52 |
.join(' '); |
| 53 |
if (className) attributes.className = className; |
| 54 |
else delete attributes.className; |
| 55 |
// serialize() echoes a drifted block's original source, dropping the swap — |
| 56 |
// rebuild it (the editor's own recovery) so the new image reaches the markup. |
| 57 |
return block.isValid === false |
| 58 |
? createBlock(block.name, attributes, block.innerBlocks) |
| 59 |
: { ...block, attributes }; |
| 60 |
}); |
| 61 |
return serialize(blocks); |
| 62 |
}; |
| 63 |
|