@wordpress/block-library
Version:
Block library for the WordPress editor.
358 lines (319 loc) • 11.3 kB
JavaScript
import { createElement } from "@wordpress/element";
/**
* External dependencies
*/
import classnames from 'classnames';
import { get, isEmpty, pick } from 'lodash';
/**
* WordPress dependencies
*/
import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob';
import { Placeholder } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { BlockAlignmentControl, BlockControls, BlockIcon, MediaPlaceholder, useBlockProps, store as blockEditorStore, __experimentalUseBorderProps as useBorderProps } from '@wordpress/block-editor';
import { useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { image as icon } from '@wordpress/icons';
import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
*/
import Image from './image'; // Much of this description is duplicated from MediaPlaceholder.
const placeholder = content => {
return createElement(Placeholder, {
className: "block-editor-media-placeholder",
withIllustration: true,
icon: icon,
label: __('Image'),
instructions: __('Upload an image file, pick one from your media library, or add one with a URL.')
}, content);
};
/**
* Module constants
*/
import { LINK_DESTINATION_ATTACHMENT, LINK_DESTINATION_CUSTOM, LINK_DESTINATION_MEDIA, LINK_DESTINATION_NONE, ALLOWED_MEDIA_TYPES } from './constants';
export const pickRelevantMediaFiles = (image, size) => {
const imageProps = pick(image, ['alt', 'id', 'link', 'caption']);
imageProps.url = get(image, ['sizes', size, 'url']) || get(image, ['media_details', 'sizes', size, 'source_url']) || image.url;
return imageProps;
};
/**
* Is the URL a temporary blob URL? A blob URL is one that is used temporarily
* while the image is being uploaded and will not have an id yet allocated.
*
* @param {number=} id The id of the image.
* @param {string=} url The url of the image.
*
* @return {boolean} Is the URL a Blob URL
*/
const isTemporaryImage = (id, url) => !id && isBlobURL(url);
/**
* Is the url for the image hosted externally. An externally hosted image has no
* id and is not a blob url.
*
* @param {number=} id The id of the image.
* @param {string=} url The url of the image.
*
* @return {boolean} Is the url an externally hosted url?
*/
export const isExternalImage = (id, url) => url && !id && !isBlobURL(url);
/**
* Checks if WP generated default image size. Size generation is skipped
* when the image is smaller than the said size.
*
* @param {Object} image
* @param {string} defaultSize
*
* @return {boolean} Whether or not it has default image size.
*/
function hasDefaultSize(image, defaultSize) {
var _image$sizes$defaultS, _image$sizes, _image$media_details$, _image$media_details, _image$media_details$2;
return 'url' in ((_image$sizes$defaultS = image === null || image === void 0 ? void 0 : (_image$sizes = image.sizes) === null || _image$sizes === void 0 ? void 0 : _image$sizes[defaultSize]) !== null && _image$sizes$defaultS !== void 0 ? _image$sizes$defaultS : {}) || 'source_url' in ((_image$media_details$ = image === null || image === void 0 ? void 0 : (_image$media_details = image.media_details) === null || _image$media_details === void 0 ? void 0 : (_image$media_details$2 = _image$media_details.sizes) === null || _image$media_details$2 === void 0 ? void 0 : _image$media_details$2[defaultSize]) !== null && _image$media_details$ !== void 0 ? _image$media_details$ : {});
}
export function ImageEdit(_ref) {
let {
attributes,
setAttributes,
isSelected,
className,
insertBlocksAfter,
onReplace,
context,
clientId
} = _ref;
const {
url = '',
alt,
caption,
align,
id,
width,
height,
sizeSlug
} = attributes;
const [temporaryURL, setTemporaryURL] = useState();
const altRef = useRef();
useEffect(() => {
altRef.current = alt;
}, [alt]);
const captionRef = useRef();
useEffect(() => {
captionRef.current = caption;
}, [caption]);
const ref = useRef();
const {
imageDefaultSize,
mediaUpload,
isContentLocked
} = useSelect(select => {
const {
getSettings,
__unstableGetContentLockingParent
} = select(blockEditorStore);
const settings = getSettings();
return {
imageDefaultSize: settings.imageDefaultSize,
mediaUpload: settings.mediaUpload,
isContentLocked: !!__unstableGetContentLockingParent(clientId)
};
}, []);
const {
createErrorNotice
} = useDispatch(noticesStore);
function onUploadError(message) {
createErrorNotice(message, {
type: 'snackbar'
});
setAttributes({
src: undefined,
id: undefined,
url: undefined
});
setTemporaryURL(undefined);
}
function onSelectImage(media) {
var _window, _window$wp, _window$wp$media, _window$wp$media$view, _window$wp$media$view2, _window$wp$media$view3;
if (!media || !media.url) {
setAttributes({
url: undefined,
alt: undefined,
id: undefined,
title: undefined,
caption: undefined
});
return;
}
if (isBlobURL(media.url)) {
setTemporaryURL(media.url);
return;
}
setTemporaryURL();
let mediaAttributes = pickRelevantMediaFiles(media, imageDefaultSize); // If a caption text was meanwhile written by the user,
// make sure the text is not overwritten by empty captions.
if (captionRef.current && !get(mediaAttributes, ['caption'])) {
const {
caption: omittedCaption,
...restMediaAttributes
} = mediaAttributes;
mediaAttributes = restMediaAttributes;
}
let additionalAttributes; // Reset the dimension attributes if changing to a different image.
if (!media.id || media.id !== id) {
additionalAttributes = {
width: undefined,
height: undefined,
// Fallback to size "full" if there's no default image size.
// It means the image is smaller, and the block will use a full-size URL.
sizeSlug: hasDefaultSize(media, imageDefaultSize) ? imageDefaultSize : 'full'
};
} else {
// Keep the same url when selecting the same file, so "Image Size"
// option is not changed.
additionalAttributes = {
url
};
} // Check if default link setting should be used.
let linkDestination = attributes.linkDestination;
if (!linkDestination) {
// Use the WordPress option to determine the proper default.
// The constants used in Gutenberg do not match WP options so a little more complicated than ideal.
// TODO: fix this in a follow up PR, requires updating media-text and ui component.
switch (((_window = window) === null || _window === void 0 ? void 0 : (_window$wp = _window.wp) === null || _window$wp === void 0 ? void 0 : (_window$wp$media = _window$wp.media) === null || _window$wp$media === void 0 ? void 0 : (_window$wp$media$view = _window$wp$media.view) === null || _window$wp$media$view === void 0 ? void 0 : (_window$wp$media$view2 = _window$wp$media$view.settings) === null || _window$wp$media$view2 === void 0 ? void 0 : (_window$wp$media$view3 = _window$wp$media$view2.defaultProps) === null || _window$wp$media$view3 === void 0 ? void 0 : _window$wp$media$view3.link) || LINK_DESTINATION_NONE) {
case 'file':
case LINK_DESTINATION_MEDIA:
linkDestination = LINK_DESTINATION_MEDIA;
break;
case 'post':
case LINK_DESTINATION_ATTACHMENT:
linkDestination = LINK_DESTINATION_ATTACHMENT;
break;
case LINK_DESTINATION_CUSTOM:
linkDestination = LINK_DESTINATION_CUSTOM;
break;
case LINK_DESTINATION_NONE:
linkDestination = LINK_DESTINATION_NONE;
break;
}
} // Check if the image is linked to it's media.
let href;
switch (linkDestination) {
case LINK_DESTINATION_MEDIA:
href = media.url;
break;
case LINK_DESTINATION_ATTACHMENT:
href = media.link;
break;
}
mediaAttributes.href = href;
setAttributes({ ...mediaAttributes,
...additionalAttributes,
linkDestination
});
}
function onSelectURL(newURL) {
if (newURL !== url) {
setAttributes({
url: newURL,
id: undefined,
width: undefined,
height: undefined,
sizeSlug: imageDefaultSize
});
}
}
function updateAlignment(nextAlign) {
const extraUpdatedAttributes = ['wide', 'full'].includes(nextAlign) ? {
width: undefined,
height: undefined
} : {};
setAttributes({ ...extraUpdatedAttributes,
align: nextAlign
});
}
let isTemp = isTemporaryImage(id, url); // Upload a temporary image on mount.
useEffect(() => {
if (!isTemp) {
return;
}
const file = getBlobByURL(url);
if (file) {
mediaUpload({
filesList: [file],
onFileChange: _ref2 => {
let [img] = _ref2;
onSelectImage(img);
},
allowedTypes: ALLOWED_MEDIA_TYPES,
onError: message => {
isTemp = false;
onUploadError(message);
}
});
}
}, []); // If an image is temporary, revoke the Blob url when it is uploaded (and is
// no longer temporary).
useEffect(() => {
if (isTemp) {
setTemporaryURL(url);
return;
}
revokeBlobURL(temporaryURL);
}, [isTemp, url]);
const isExternal = isExternalImage(id, url);
const src = isExternal ? url : undefined;
const mediaPreview = !!url && createElement("img", {
alt: __('Edit image'),
title: __('Edit image'),
className: 'edit-image-preview',
src: url
});
const borderProps = useBorderProps(attributes);
const classes = classnames(className, {
'is-transient': temporaryURL,
'is-resized': !!width || !!height,
[`size-${sizeSlug}`]: sizeSlug,
'has-custom-border': !!borderProps.className || !isEmpty(borderProps.style)
});
const blockProps = useBlockProps({
ref,
className: classes
});
return createElement("figure", blockProps, (temporaryURL || url) && createElement(Image, {
temporaryURL: temporaryURL,
attributes: attributes,
setAttributes: setAttributes,
isSelected: isSelected,
insertBlocksAfter: insertBlocksAfter,
onReplace: onReplace,
onSelectImage: onSelectImage,
onSelectURL: onSelectURL,
onUploadError: onUploadError,
containerRef: ref,
context: context,
clientId: clientId,
isContentLocked: isContentLocked
}), !url && !isContentLocked && createElement(BlockControls, {
group: "block"
}, createElement(BlockAlignmentControl, {
value: align,
onChange: updateAlignment
})), createElement(MediaPlaceholder, {
icon: createElement(BlockIcon, {
icon: icon
}),
onSelect: onSelectImage,
onSelectURL: onSelectURL,
onError: onUploadError,
placeholder: placeholder,
accept: "image/*",
allowedTypes: ALLOWED_MEDIA_TYPES,
value: {
id,
src
},
mediaPreview: mediaPreview,
disableMediaButtons: temporaryURL || url
}));
}
export default ImageEdit;
//# sourceMappingURL=edit.js.map