UNPKG

@wordpress/block-library

Version:
254 lines (229 loc) 7.13 kB
/** * 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;