@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
254 lines (244 loc) • 7.48 kB
JavaScript
/**
* WordPress dependencies
*/
import { PanelBody, Button, Spinner, __unstableMotion as motion, __unstableAnimatePresence as AnimatePresence } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { useState } from '@wordpress/element';
import { isBlobURL } from '@wordpress/blob';
/**
* Internal dependencies
*/
import { fetchMedia } from './media-util';
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
function flattenBlocks(blocks) {
const result = [];
blocks.forEach(block => {
result.push(block);
result.push(...flattenBlocks(block.innerBlocks));
});
return result;
}
/**
* Determine whether a block has external media.
*
* Different blocks use different attribute names (and potentially
* different logic as well) in determining whether the media is
* present, and whether it's external.
*
* @param {{name: string, attributes: Object}} block The block.
* @return {boolean?} Whether the block has external media
*/
function hasExternalMedia(block) {
if (block.name === 'core/image' || block.name === 'core/cover') {
return block.attributes.url && !block.attributes.id;
}
if (block.name === 'core/media-text') {
return block.attributes.mediaUrl && !block.attributes.mediaId;
}
return undefined;
}
/**
* Retrieve media info from a block.
*
* Different blocks use different attribute names, so we need this
* function to normalize things into a consistent naming scheme.
*
* @param {{name: string, attributes: Object}} block The block.
* @return {{url: ?string, alt: ?string, id: ?number}} The media info for the block.
*/
function getMediaInfo(block) {
if (block.name === 'core/image' || block.name === 'core/cover') {
const {
url,
alt,
id
} = block.attributes;
return {
url,
alt,
id
};
}
if (block.name === 'core/media-text') {
const {
mediaUrl: url,
mediaAlt: alt,
mediaId: id
} = block.attributes;
return {
url,
alt,
id
};
}
return {};
}
// Image component to represent a single image in the upload dialog.
function Image({
clientId,
alt,
url
}) {
const {
selectBlock
} = useDispatch(blockEditorStore);
return /*#__PURE__*/_jsx(motion.img, {
tabIndex: 0,
role: "button",
"aria-label": __('Select image block.'),
onClick: () => {
selectBlock(clientId);
},
onKeyDown: event => {
if (event.key === 'Enter' || event.key === ' ') {
selectBlock(clientId);
event.preventDefault();
}
},
alt: alt,
src: url,
animate: {
opacity: 1
},
exit: {
opacity: 0,
scale: 0
},
style: {
width: '36px',
height: '36px',
objectFit: 'cover',
borderRadius: '2px',
cursor: 'pointer'
},
whileHover: {
scale: 1.08
}
}, clientId);
}
export default function MaybeUploadMediaPanel() {
const [isUploading, setIsUploading] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
const [hadUploadError, setHadUploadError] = useState(false);
const {
editorBlocks,
mediaUpload
} = useSelect(select => ({
editorBlocks: select(blockEditorStore).getBlocks(),
mediaUpload: select(blockEditorStore).getSettings().mediaUpload
}), []);
// Get a list of blocks with external media.
const blocksWithExternalMedia = flattenBlocks(editorBlocks).filter(block => hasExternalMedia(block));
const {
updateBlockAttributes
} = useDispatch(blockEditorStore);
if (!mediaUpload || !blocksWithExternalMedia.length) {
return null;
}
const panelBodyTitle = [__('Suggestion:'), /*#__PURE__*/_jsx("span", {
className: "editor-post-publish-panel__link",
children: __('External media')
}, "label")];
/**
* Update an individual block to point to newly-added library media.
*
* Different blocks use different attribute names, so we need this
* function to ensure we modify the correct attributes for each type.
*
* @param {{name: string, attributes: Object}} block The block.
* @param {{id: number, url: string}} media Media library file info.
*/
function updateBlockWithUploadedMedia(block, media) {
if (block.name === 'core/image' || block.name === 'core/cover') {
updateBlockAttributes(block.clientId, {
id: media.id,
url: media.url
});
}
if (block.name === 'core/media-text') {
updateBlockAttributes(block.clientId, {
mediaId: media.id,
mediaUrl: media.url
});
}
}
// Handle fetching and uploading all external media in the post.
function uploadImages() {
setIsUploading(true);
setHadUploadError(false);
// Multiple blocks can be using the same URL, so we
// should ensure we only fetch and upload each of them once.
const mediaUrls = new Set(blocksWithExternalMedia.map(block => {
const {
url
} = getMediaInfo(block);
return url;
}));
// Create an upload promise for each URL, that we can wait for in all
// blocks that make use of that media.
const uploadPromises = Object.fromEntries(Object.entries(fetchMedia([...mediaUrls])).map(([url, filePromise]) => {
const uploadPromise = filePromise.then(blob => new Promise((resolve, reject) => {
mediaUpload({
filesList: [blob],
onFileChange: ([media]) => {
if (isBlobURL(media.url)) {
return;
}
resolve(media);
},
onError() {
reject();
}
});
}));
return [url, uploadPromise];
}));
// Wait for all blocks to be updated with library media.
Promise.allSettled(blocksWithExternalMedia.map(block => {
const {
url
} = getMediaInfo(block);
return uploadPromises[url].then(media => updateBlockWithUploadedMedia(block, media)).then(() => setIsAnimating(true)).catch(() => setHadUploadError(true));
})).finally(() => {
setIsUploading(false);
});
}
return /*#__PURE__*/_jsxs(PanelBody, {
initialOpen: true,
title: panelBodyTitle,
children: [/*#__PURE__*/_jsx("p", {
children: __('Upload external images to the Media Library. Images from different domains may load slowly, display incorrectly, or be removed unexpectedly.')
}), /*#__PURE__*/_jsxs("div", {
style: {
display: 'inline-flex',
flexWrap: 'wrap',
gap: '8px'
},
children: [/*#__PURE__*/_jsx(AnimatePresence, {
onExitComplete: () => setIsAnimating(false),
children: blocksWithExternalMedia.map(block => {
const {
url,
alt
} = getMediaInfo(block);
return /*#__PURE__*/_jsx(Image, {
clientId: block.clientId,
url: url,
alt: alt
}, block.clientId);
})
}), isUploading || isAnimating ? /*#__PURE__*/_jsx(Spinner, {}) : /*#__PURE__*/_jsx(Button, {
__next40pxDefaultSize: true,
variant: "primary",
onClick: uploadImages,
children: __('Upload')
})]
}), hadUploadError && /*#__PURE__*/_jsx("p", {
children: __('Upload failed, try again.')
})]
});
}
//# sourceMappingURL=maybe-upload-media.js.map