@wordpress/block-library
Version:
Block library for the WordPress editor.
254 lines (229 loc) • 7.13 kB
JavaScript
/**
* Internal dependencies
*/
import {
createUpgradedEmbedBlock,
getClassNames,
fallback,
getEmbedInfoByProvider,
getMergedAttributesWithPreview,
} from './util';
import EmbedControls from './embed-controls';
import { embedContentIcon } from './icons';
import EmbedLoading from './embed-loading';
import EmbedPlaceholder from './embed-placeholder';
import EmbedPreview from './embed-preview';
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { __, _x, sprintf } from '@wordpress/i18n';
import { useState, useEffect } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { View } from '@wordpress/primitives';
const EmbedEdit = ( props ) => {
const {
attributes: {
providerNameSlug,
previewable,
responsive,
url: attributesUrl,
},
attributes,
isSelected,
onReplace,
setAttributes,
insertBlocksAfter,
onFocus,
} = props;
const defaultEmbedInfo = {
title: _x( 'Embed', 'block title' ),
icon: embedContentIcon,
};
const { icon, title } =
getEmbedInfoByProvider( providerNameSlug ) || defaultEmbedInfo;
const [ url, setURL ] = useState( attributesUrl );
const [ isEditingURL, setIsEditingURL ] = useState( false );
const { invalidateResolution } = useDispatch( coreStore );
const { preview, fetching, themeSupportsResponsive, cannotEmbed } =
useSelect(
( select ) => {
const {
getEmbedPreview,
isPreviewEmbedFallback,
isRequestingEmbedPreview,
getThemeSupports,
} = select( coreStore );
if ( ! attributesUrl ) {
return { fetching: false, cannotEmbed: false };
}
const embedPreview = getEmbedPreview( attributesUrl );
const previewIsFallback =
isPreviewEmbedFallback( attributesUrl );
// The external oEmbed provider does not exist. We got no type info and no html.
const badEmbedProvider =
embedPreview?.html === false &&
embedPreview?.type === undefined;
// Some WordPress URLs that can't be embedded will cause the API to return
// a valid JSON response with no HTML and `data.status` set to 404, rather
// than generating a fallback response as other embeds do.
const wordpressCantEmbed = embedPreview?.data?.status === 404;
const validPreview =
!! embedPreview &&
! badEmbedProvider &&
! wordpressCantEmbed;
return {
preview: validPreview ? embedPreview : undefined,
fetching: isRequestingEmbedPreview( attributesUrl ),
themeSupportsResponsive:
getThemeSupports()[ 'responsive-embeds' ],
cannotEmbed: ! validPreview || previewIsFallback,
};
},
[ attributesUrl ]
);
/**
* Returns the attributes derived from the preview, merged with the current attributes.
*
* @param {boolean} ignorePreviousClassName Determines if the previous className attribute should be ignored when merging.
* @return {Object} Merged attributes.
*/
const getMergedAttributes = ( ignorePreviousClassName = false ) =>
getMergedAttributesWithPreview(
attributes,
preview,
title,
responsive,
ignorePreviousClassName
);
const toggleResponsive = () => {
const { allowResponsive, className } = attributes;
const { html } = preview;
const newAllowResponsive = ! allowResponsive;
setAttributes( {
allowResponsive: newAllowResponsive,
className: getClassNames(
html,
className,
responsive && newAllowResponsive
),
} );
};
useEffect( () => {
if ( ! preview?.html || ! cannotEmbed || fetching ) {
return;
}
// At this stage, we're not fetching the preview and know it can't be embedded,
// so try removing any trailing slash, and resubmit.
const newURL = attributesUrl.replace( /\/$/, '' );
setURL( newURL );
setIsEditingURL( false );
setAttributes( { url: newURL } );
}, [ preview?.html, attributesUrl ] );
// Handle incoming preview.
useEffect( () => {
if ( preview && ! isEditingURL ) {
// When obtaining an incoming preview, we set the attributes derived from
// the preview data. In this case when getting the merged attributes,
// we ignore the previous classname because it might not match the expected
// classes by the new preview.
setAttributes( getMergedAttributes( true ) );
if ( onReplace ) {
const upgradedBlock = createUpgradedEmbedBlock(
props,
getMergedAttributes()
);
if ( upgradedBlock ) {
onReplace( upgradedBlock );
}
}
}
}, [ preview, isEditingURL ] );
const blockProps = useBlockProps();
if ( fetching ) {
return (
<View { ...blockProps }>
<EmbedLoading />
</View>
);
}
// translators: %s: type of embed e.g: "YouTube", "Twitter", etc. "Embed" is used when no specific type exists
const label = sprintf( __( '%s URL' ), title );
// No preview, or we can't embed the current URL, or we've clicked the edit button.
const showEmbedPlaceholder = ! preview || cannotEmbed || isEditingURL;
if ( showEmbedPlaceholder ) {
return (
<View { ...blockProps }>
<EmbedPlaceholder
icon={ icon }
label={ label }
onFocus={ onFocus }
onSubmit={ ( event ) => {
if ( event ) {
event.preventDefault();
}
setIsEditingURL( false );
setAttributes( { url } );
} }
value={ url }
cannotEmbed={ cannotEmbed }
onChange={ ( event ) => setURL( event.target.value ) }
fallback={ () => fallback( url, onReplace ) }
tryAgain={ () => {
invalidateResolution( 'getEmbedPreview', [ url ] );
} }
/>
</View>
);
}
// Even though we set attributes that get derived from the preview,
// we don't access them directly because for the initial render,
// the `setAttributes` call will not have taken effect. If we're
// rendering responsive content, setting the responsive classes
// after the preview has been rendered can result in unwanted
// clipping or scrollbars. The `getAttributesFromPreview` function
// that `getMergedAttributes` uses is memoized so that we're not
// calculating them on every render.
const {
caption,
type,
allowResponsive,
className: classFromPreview,
} = getMergedAttributes();
const className = classnames( classFromPreview, props.className );
return (
<>
<EmbedControls
showEditButton={ preview && ! cannotEmbed }
themeSupportsResponsive={ themeSupportsResponsive }
blockSupportsResponsive={ responsive }
allowResponsive={ allowResponsive }
toggleResponsive={ toggleResponsive }
switchBackToURLInput={ () => setIsEditingURL( true ) }
/>
<View { ...blockProps }>
<EmbedPreview
preview={ preview }
previewable={ previewable }
className={ className }
url={ url }
type={ type }
caption={ caption }
onCaptionChange={ ( value ) =>
setAttributes( { caption: value } )
}
isSelected={ isSelected }
icon={ icon }
label={ label }
insertBlocksAfter={ insertBlocksAfter }
/>
</View>
</>
);
};
export default EmbedEdit;