Plugin Tabs noticias
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "core/embed",
|
||||
"title": "Embed",
|
||||
"category": "embed",
|
||||
"description": "Add a block that displays content pulled from other sites, like Twitter or YouTube.",
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"caption": {
|
||||
"type": "string",
|
||||
"source": "html",
|
||||
"selector": "figcaption"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerNameSlug": {
|
||||
"type": "string"
|
||||
},
|
||||
"allowResponsive": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"responsive": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"previewable": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"align": true
|
||||
},
|
||||
"editorStyle": "wp-block-embed-editor",
|
||||
"style": "wp-block-embed"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export const ASPECT_RATIOS = [
|
||||
// Common video resolutions.
|
||||
{ ratio: '2.33', className: 'wp-embed-aspect-21-9' },
|
||||
{ ratio: '2.00', className: 'wp-embed-aspect-18-9' },
|
||||
{ ratio: '1.78', className: 'wp-embed-aspect-16-9' },
|
||||
{ ratio: '1.33', className: 'wp-embed-aspect-4-3' },
|
||||
// Vertical video and instagram square video support.
|
||||
{ ratio: '1.00', className: 'wp-embed-aspect-1-1' },
|
||||
{ ratio: '0.56', className: 'wp-embed-aspect-9-16' },
|
||||
{ ratio: '0.50', className: 'wp-embed-aspect-1-2' },
|
||||
];
|
||||
|
||||
export const WP_EMBED_TYPE = 'wp-embed';
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { RichText } from '@wordpress/block-editor';
|
||||
|
||||
const { attributes: blockAttributes } = metadata;
|
||||
|
||||
const deprecated = [
|
||||
{
|
||||
attributes: blockAttributes,
|
||||
save( { attributes: { url, caption, type, providerNameSlug } } ) {
|
||||
if ( ! url ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const embedClassName = classnames( 'wp-block-embed', {
|
||||
[ `is-type-${ type }` ]: type,
|
||||
[ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
|
||||
} );
|
||||
|
||||
return (
|
||||
<figure className={ embedClassName }>
|
||||
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
|
||||
{ ! RichText.isEmpty( caption ) && (
|
||||
<RichText.Content
|
||||
tagName="figcaption"
|
||||
value={ caption }
|
||||
/>
|
||||
) }
|
||||
</figure>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default deprecated;
|
||||
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
createUpgradedEmbedBlock,
|
||||
getClassNames,
|
||||
fallback,
|
||||
getAttributesFromPreview,
|
||||
getEmbedInfoByProvider,
|
||||
} 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 ]
|
||||
);
|
||||
|
||||
/**
|
||||
* @return {Object} Attributes derived from the preview, merged with the current attributes.
|
||||
*/
|
||||
const getMergedAttributes = () => {
|
||||
const { allowResponsive, className } = attributes;
|
||||
return {
|
||||
...attributes,
|
||||
...getAttributesFromPreview(
|
||||
preview,
|
||||
title,
|
||||
className,
|
||||
responsive,
|
||||
allowResponsive
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
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 ) {
|
||||
// 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.
|
||||
setAttributes( getMergedAttributes() );
|
||||
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;
|
||||
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
createUpgradedEmbedBlock,
|
||||
getClassNames,
|
||||
fallback,
|
||||
getAttributesFromPreview,
|
||||
getEmbedInfoByProvider,
|
||||
} 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';
|
||||
import EmbedLinkSettings from './embed-link-settings';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { _x } from '@wordpress/i18n';
|
||||
import { useCallback, useState, useEffect } from '@wordpress/element';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import {
|
||||
useBlockProps,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { View } from '@wordpress/primitives';
|
||||
|
||||
// The inline preview feature will be released progressible, for this reason
|
||||
// the embed will only be considered previewable for the following providers list.
|
||||
const PREVIEWABLE_PROVIDERS = [ 'youtube', 'twitter', 'instagram', 'vimeo' ];
|
||||
// Some providers are rendering the inline preview as a WordPress embed and
|
||||
// are not supported yet, so we need to disallow them with a fixed providers list.
|
||||
const NOT_PREVIEWABLE_WP_EMBED_PROVIDERS = [ 'pinterest' ];
|
||||
|
||||
const WP_EMBED_TYPE = 'wp-embed';
|
||||
|
||||
const EmbedEdit = ( props ) => {
|
||||
const {
|
||||
attributes: { align, providerNameSlug, previewable, responsive, url },
|
||||
attributes,
|
||||
isSelected,
|
||||
onReplace,
|
||||
setAttributes,
|
||||
insertBlocksAfter,
|
||||
onFocus,
|
||||
clientId,
|
||||
} = props;
|
||||
|
||||
const defaultEmbedInfo = {
|
||||
title: _x( 'Embed', 'block title' ),
|
||||
icon: embedContentIcon,
|
||||
};
|
||||
const embedInfoByProvider = getEmbedInfoByProvider( providerNameSlug );
|
||||
const { icon, title } = embedInfoByProvider || defaultEmbedInfo;
|
||||
|
||||
const { wasBlockJustInserted } = useSelect(
|
||||
( select ) => ( {
|
||||
wasBlockJustInserted: select(
|
||||
blockEditorStore
|
||||
).wasBlockJustInserted( clientId, 'inserter_menu' ),
|
||||
} ),
|
||||
[ clientId ]
|
||||
);
|
||||
const [ isEditingURL, setIsEditingURL ] = useState(
|
||||
isSelected && wasBlockJustInserted && ! url
|
||||
);
|
||||
const [ showEmbedBottomSheet, setShowEmbedBottomSheet ] = useState(
|
||||
isEditingURL
|
||||
);
|
||||
const { invalidateResolution } = useDispatch( coreStore );
|
||||
|
||||
const {
|
||||
preview,
|
||||
fetching,
|
||||
themeSupportsResponsive,
|
||||
cannotEmbed,
|
||||
} = useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
getEmbedPreview,
|
||||
hasFinishedResolution,
|
||||
isPreviewEmbedFallback,
|
||||
getThemeSupports,
|
||||
} = select( coreStore );
|
||||
if ( ! url ) {
|
||||
return { fetching: false, cannotEmbed: false };
|
||||
}
|
||||
|
||||
const embedPreview = getEmbedPreview( url );
|
||||
const hasResolvedEmbedPreview = hasFinishedResolution(
|
||||
'getEmbedPreview',
|
||||
[ url ]
|
||||
);
|
||||
const previewIsFallback = isPreviewEmbedFallback( url );
|
||||
|
||||
// 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 `code` set to 404, rather
|
||||
// than generating a fallback response as other embeds do.
|
||||
const wordpressCantEmbed = embedPreview?.code === '404';
|
||||
const validPreview =
|
||||
!! embedPreview && ! badEmbedProvider && ! wordpressCantEmbed;
|
||||
|
||||
return {
|
||||
preview: validPreview ? embedPreview : undefined,
|
||||
fetching: ! hasResolvedEmbedPreview,
|
||||
themeSupportsResponsive: getThemeSupports()[
|
||||
'responsive-embeds'
|
||||
],
|
||||
cannotEmbed: ! validPreview || previewIsFallback,
|
||||
};
|
||||
},
|
||||
[ url ]
|
||||
);
|
||||
|
||||
/**
|
||||
* 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 ) => {
|
||||
const { allowResponsive, className } = attributes;
|
||||
return {
|
||||
...attributes,
|
||||
...getAttributesFromPreview(
|
||||
preview,
|
||||
title,
|
||||
ignorePreviousClassName ? undefined : className,
|
||||
responsive,
|
||||
allowResponsive
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
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 = url.replace( /\/$/, '' );
|
||||
setIsEditingURL( false );
|
||||
setAttributes( { url: newURL } );
|
||||
}, [ preview?.html, url ] );
|
||||
|
||||
// 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 ] );
|
||||
|
||||
useEffect( () => setShowEmbedBottomSheet( isEditingURL ), [
|
||||
isEditingURL,
|
||||
] );
|
||||
|
||||
const onEditURL = useCallback( ( value ) => {
|
||||
// The order of the following calls is important, we need to update the URL attribute before changing `isEditingURL`,
|
||||
// otherwise the side-effect that potentially replaces the block when updating the local state won't use the new URL
|
||||
// for creating the new block.
|
||||
setAttributes( { url: value } );
|
||||
setIsEditingURL( false );
|
||||
}, [] );
|
||||
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
if ( fetching ) {
|
||||
return (
|
||||
<View { ...blockProps }>
|
||||
<EmbedLoading />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const showEmbedPlaceholder = ! preview || cannotEmbed;
|
||||
|
||||
// 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 {
|
||||
type,
|
||||
allowResponsive,
|
||||
className: classFromPreview,
|
||||
} = getMergedAttributes();
|
||||
const className = classnames( classFromPreview, props.className );
|
||||
|
||||
const isProviderPreviewable =
|
||||
PREVIEWABLE_PROVIDERS.includes( providerNameSlug ) ||
|
||||
// For WordPress embeds, we enable the inline preview for all its providers
|
||||
// except the ones that are not supported yet.
|
||||
( WP_EMBED_TYPE === type &&
|
||||
! NOT_PREVIEWABLE_WP_EMBED_PROVIDERS.includes( providerNameSlug ) );
|
||||
|
||||
const linkLabel = WP_EMBED_TYPE === type ? 'WordPress' : title;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ showEmbedPlaceholder ? (
|
||||
<>
|
||||
<View { ...blockProps }>
|
||||
<EmbedPlaceholder
|
||||
icon={ icon }
|
||||
isSelected={ isSelected }
|
||||
label={ title }
|
||||
onPress={ ( event ) => {
|
||||
onFocus( event );
|
||||
setIsEditingURL( true );
|
||||
} }
|
||||
cannotEmbed={ cannotEmbed }
|
||||
fallback={ () => fallback( url, onReplace ) }
|
||||
tryAgain={ () => {
|
||||
invalidateResolution( 'getEmbedPreview', [
|
||||
url,
|
||||
] );
|
||||
} }
|
||||
openEmbedLinkSettings={ () =>
|
||||
setShowEmbedBottomSheet( true )
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EmbedControls
|
||||
themeSupportsResponsive={ themeSupportsResponsive }
|
||||
blockSupportsResponsive={ responsive }
|
||||
allowResponsive={ allowResponsive }
|
||||
toggleResponsive={ toggleResponsive }
|
||||
url={ url }
|
||||
linkLabel={ linkLabel }
|
||||
onEditURL={ onEditURL }
|
||||
/>
|
||||
<View { ...blockProps }>
|
||||
<EmbedPreview
|
||||
align={ align }
|
||||
className={ className }
|
||||
clientId={ clientId }
|
||||
icon={ icon }
|
||||
insertBlocksAfter={ insertBlocksAfter }
|
||||
isSelected={ isSelected }
|
||||
label={ title }
|
||||
onFocus={ onFocus }
|
||||
preview={ preview }
|
||||
isProviderPreviewable={ isProviderPreviewable }
|
||||
previewable={ previewable }
|
||||
type={ type }
|
||||
url={ url }
|
||||
isDefaultEmbedInfo={ ! embedInfoByProvider }
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
) }
|
||||
<EmbedLinkSettings
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
value={ url }
|
||||
label={ linkLabel }
|
||||
isVisible={ showEmbedBottomSheet }
|
||||
onClose={ () => setShowEmbedBottomSheet( false ) }
|
||||
onSubmit={ onEditURL }
|
||||
withBottomSheet
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedEdit;
|
||||
@@ -0,0 +1,45 @@
|
||||
.wp-block-embed {
|
||||
// Remove the left and right margin the figure is born with.
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
||||
// Necessary because we use responsive trickery to set width/height,
|
||||
// therefore the video doesn't intrinsically clear floats like an image does.
|
||||
clear: both;
|
||||
|
||||
&.is-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// Stops long URLs from breaking out of the no preview available screen
|
||||
.components-placeholder__error {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.components-placeholder__learn-more {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.block-library-embed__interactive-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.wp-block[data-align="left"],
|
||||
.wp-block[data-align="right"] {
|
||||
> .wp-block-embed {
|
||||
max-width: 360px;
|
||||
width: 100%;
|
||||
|
||||
// Unless these have a min-width, they collapse when floated.
|
||||
.wp-block-embed__wrapper {
|
||||
min-width: $break-zoomed-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
ToolbarButton,
|
||||
PanelBody,
|
||||
ToggleControl,
|
||||
ToolbarGroup,
|
||||
} from '@wordpress/components';
|
||||
import { BlockControls, InspectorControls } from '@wordpress/block-editor';
|
||||
import { edit } from '@wordpress/icons';
|
||||
|
||||
function getResponsiveHelp( checked ) {
|
||||
return checked
|
||||
? __(
|
||||
'This embed will preserve its aspect ratio when the browser is resized.'
|
||||
)
|
||||
: __(
|
||||
'This embed may not preserve its aspect ratio when the browser is resized.'
|
||||
);
|
||||
}
|
||||
|
||||
const EmbedControls = ( {
|
||||
blockSupportsResponsive,
|
||||
showEditButton,
|
||||
themeSupportsResponsive,
|
||||
allowResponsive,
|
||||
toggleResponsive,
|
||||
switchBackToURLInput,
|
||||
} ) => (
|
||||
<>
|
||||
<BlockControls>
|
||||
<ToolbarGroup>
|
||||
{ showEditButton && (
|
||||
<ToolbarButton
|
||||
className="components-toolbar__control"
|
||||
label={ __( 'Edit URL' ) }
|
||||
icon={ edit }
|
||||
onClick={ switchBackToURLInput }
|
||||
/>
|
||||
) }
|
||||
</ToolbarGroup>
|
||||
</BlockControls>
|
||||
{ themeSupportsResponsive && blockSupportsResponsive && (
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __( 'Media settings' ) }
|
||||
className="blocks-responsive"
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __( 'Resize for smaller devices' ) }
|
||||
checked={ allowResponsive }
|
||||
help={ getResponsiveHelp }
|
||||
onChange={ toggleResponsive }
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
|
||||
export default EmbedControls;
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EmbedLinkSettings from './embed-link-settings';
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { store as editPostStore } from '@wordpress/edit-post';
|
||||
|
||||
function getResponsiveHelp( checked ) {
|
||||
return checked
|
||||
? __(
|
||||
'This embed will preserve its aspect ratio when the browser is resized.'
|
||||
)
|
||||
: __(
|
||||
'This embed may not preserve its aspect ratio when the browser is resized.'
|
||||
);
|
||||
}
|
||||
|
||||
const EmbedControls = ( {
|
||||
blockSupportsResponsive,
|
||||
themeSupportsResponsive,
|
||||
allowResponsive,
|
||||
toggleResponsive,
|
||||
url,
|
||||
linkLabel,
|
||||
onEditURL,
|
||||
} ) => {
|
||||
const { closeGeneralSidebar: closeSettingsBottomSheet } = useDispatch(
|
||||
editPostStore
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InspectorControls>
|
||||
{ themeSupportsResponsive && blockSupportsResponsive && (
|
||||
<PanelBody title={ __( 'Media settings' ) }>
|
||||
<ToggleControl
|
||||
label={ __( 'Resize for smaller devices' ) }
|
||||
checked={ allowResponsive }
|
||||
help={ getResponsiveHelp }
|
||||
onChange={ toggleResponsive }
|
||||
/>
|
||||
</PanelBody>
|
||||
) }
|
||||
<PanelBody title={ __( 'Link settings' ) }>
|
||||
<EmbedLinkSettings
|
||||
value={ url }
|
||||
label={ linkLabel }
|
||||
onSubmit={ ( value ) => {
|
||||
closeSettingsBottomSheet();
|
||||
onEditURL( value );
|
||||
} }
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedControls;
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
LinkSettingsNavigation,
|
||||
FooterMessageLink,
|
||||
} from '@wordpress/components';
|
||||
import { isURL } from '@wordpress/url';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||
|
||||
const EmbedLinkSettings = ( {
|
||||
autoFocus,
|
||||
value,
|
||||
label,
|
||||
isVisible,
|
||||
onClose,
|
||||
onSubmit,
|
||||
withBottomSheet,
|
||||
} ) => {
|
||||
const url = useRef( value );
|
||||
const [ inputURL, setInputURL ] = useState( value );
|
||||
const { createErrorNotice } = useDispatch( noticesStore );
|
||||
|
||||
const linkSettingsOptions = {
|
||||
url: {
|
||||
label: sprintf(
|
||||
// translators: %s: embed block variant's label e.g: "Twitter".
|
||||
__( '%s link' ),
|
||||
label
|
||||
),
|
||||
placeholder: __( 'Add link' ),
|
||||
autoFocus,
|
||||
autoFill: true,
|
||||
},
|
||||
footer: {
|
||||
label: (
|
||||
<FooterMessageLink
|
||||
href={ __(
|
||||
'https://wordpress.org/support/article/embeds/'
|
||||
) }
|
||||
value={ __( 'Learn more about embeds' ) }
|
||||
/>
|
||||
),
|
||||
separatorType: 'topFullWidth',
|
||||
},
|
||||
};
|
||||
|
||||
const onDismiss = useCallback( () => {
|
||||
if ( ! isURL( url.current ) && url.current !== '' ) {
|
||||
createErrorNotice( __( 'Invalid URL. Please enter a valid URL.' ) );
|
||||
// If the URL was already defined, we submit it to stop showing the embed placeholder.
|
||||
onSubmit( value );
|
||||
return;
|
||||
}
|
||||
onSubmit( url.current );
|
||||
}, [ onSubmit, value ] );
|
||||
|
||||
useEffect( () => {
|
||||
url.current = value;
|
||||
setInputURL( value );
|
||||
}, [ value ] );
|
||||
|
||||
/**
|
||||
* If the Embed Bottom Sheet component does not utilize a bottom sheet then the onDismiss action is not
|
||||
* called. Here we are wiring the onDismiss to the onClose callback that gets triggered when input is submitted.
|
||||
*/
|
||||
const performOnCloseOperations = useCallback( () => {
|
||||
if ( onClose ) {
|
||||
onClose();
|
||||
}
|
||||
|
||||
if ( ! withBottomSheet ) {
|
||||
onDismiss();
|
||||
}
|
||||
}, [ onClose ] );
|
||||
|
||||
const onSetAttributes = useCallback( ( attributes ) => {
|
||||
url.current = attributes.url;
|
||||
setInputURL( attributes.url );
|
||||
}, [] );
|
||||
|
||||
return (
|
||||
<LinkSettingsNavigation
|
||||
isVisible={ isVisible }
|
||||
url={ inputURL }
|
||||
onClose={ performOnCloseOperations }
|
||||
onDismiss={ onDismiss }
|
||||
setAttributes={ onSetAttributes }
|
||||
options={ linkSettingsOptions }
|
||||
testID="embed-edit-url-modal"
|
||||
withBottomSheet={ withBottomSheet }
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedLinkSettings;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { Spinner } from '@wordpress/components';
|
||||
|
||||
const EmbedLoading = () => (
|
||||
<div className="wp-block-embed is-loading">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default EmbedLoading;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ActivityIndicator, View } from 'react-native';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import styles from './styles.scss';
|
||||
|
||||
const EmbedLoading = () => {
|
||||
const style = usePreferredColorSchemeStyle(
|
||||
styles[ 'embed-preview__loading' ],
|
||||
styles[ 'embed-preview__loading--dark' ]
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={ style }>
|
||||
<ActivityIndicator animating />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedLoading;
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { TouchableOpacity, TouchableWithoutFeedback, Text } from 'react-native';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { View } from '@wordpress/primitives';
|
||||
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useRef, useState } from '@wordpress/element';
|
||||
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
|
||||
import { requestPreview } from '@wordpress/react-native-bridge';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { store as editorStore } from '@wordpress/editor';
|
||||
import { BottomSheet, Icon, TextControl } from '@wordpress/components';
|
||||
import { help } from '@wordpress/icons';
|
||||
import { BlockIcon } from '@wordpress/block-editor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import styles from './styles.scss';
|
||||
|
||||
const EmbedNoPreview = ( {
|
||||
label,
|
||||
icon,
|
||||
isSelected,
|
||||
onPress,
|
||||
previewable,
|
||||
isDefaultEmbedInfo,
|
||||
} ) => {
|
||||
const shouldRequestReview = useRef( false );
|
||||
const [ isSheetVisible, setIsSheetVisible ] = useState( false );
|
||||
|
||||
const { postType } = useSelect( ( select ) => ( {
|
||||
postType: select( editorStore ).getEditedPostAttribute( 'type' ),
|
||||
} ) );
|
||||
|
||||
const containerStyle = usePreferredColorSchemeStyle(
|
||||
styles.embed__container,
|
||||
styles[ 'embed__container--dark' ]
|
||||
);
|
||||
const labelStyle = usePreferredColorSchemeStyle(
|
||||
styles.embed__label,
|
||||
styles[ 'embed__label--dark' ]
|
||||
);
|
||||
const descriptionStyle = usePreferredColorSchemeStyle(
|
||||
styles.embed__description,
|
||||
styles[ 'embed__description--dark' ]
|
||||
);
|
||||
const helpIconStyle = usePreferredColorSchemeStyle(
|
||||
styles[ 'embed-no-preview__help-icon' ],
|
||||
styles[ 'embed-no-preview__help-icon--dark' ]
|
||||
);
|
||||
|
||||
const sheetIconStyle = usePreferredColorSchemeStyle(
|
||||
styles[ 'embed-no-preview__sheet-icon' ],
|
||||
styles[ 'embed-no-preview__sheet-icon--dark' ]
|
||||
);
|
||||
const sheetTitleStyle = usePreferredColorSchemeStyle(
|
||||
styles[ 'embed-no-preview__sheet-title' ],
|
||||
styles[ 'embed-no-preview__sheet-title--dark' ]
|
||||
);
|
||||
const sheetDescriptionStyle = usePreferredColorSchemeStyle(
|
||||
styles[ 'embed-no-preview__sheet-description' ],
|
||||
styles[ 'embed-no-preview__sheet-description--dark' ]
|
||||
);
|
||||
const sheetButtonStyle = usePreferredColorSchemeStyle(
|
||||
styles[ 'embed-no-preview__sheet-button' ],
|
||||
styles[ 'embed-no-preview__sheet-button--dark' ]
|
||||
);
|
||||
|
||||
const previewButtonA11yHint =
|
||||
postType === 'page'
|
||||
? __( 'Double tap to preview page.' )
|
||||
: __( 'Double tap to preview post.' );
|
||||
const previewButtonText =
|
||||
postType === 'page' ? __( 'Preview page' ) : __( 'Preview post' );
|
||||
const comingSoonDescription =
|
||||
postType === 'page'
|
||||
? sprintf(
|
||||
// translators: %s: embed block variant's label e.g: "Twitter".
|
||||
__(
|
||||
'We’re working hard on adding support for %s previews. In the meantime, you can preview the embedded content on the page.'
|
||||
),
|
||||
label
|
||||
)
|
||||
: sprintf(
|
||||
// translators: %s: embed block variant's label e.g: "Twitter".
|
||||
__(
|
||||
'We’re working hard on adding support for %s previews. In the meantime, you can preview the embedded content on the post.'
|
||||
),
|
||||
label
|
||||
);
|
||||
|
||||
function onOpenSheet() {
|
||||
setIsSheetVisible( true );
|
||||
}
|
||||
|
||||
function onCloseSheet() {
|
||||
setIsSheetVisible( false );
|
||||
}
|
||||
|
||||
function onDismissSheet() {
|
||||
// The preview request has to be done after the bottom sheet modal is dismissed,
|
||||
// otherwise the preview native modal is not displayed.
|
||||
if ( shouldRequestReview.current ) {
|
||||
requestPreview();
|
||||
}
|
||||
shouldRequestReview.current = false;
|
||||
}
|
||||
|
||||
function onPressContainer() {
|
||||
onPress();
|
||||
onOpenSheet();
|
||||
}
|
||||
|
||||
function onPressHelp() {
|
||||
onPressContainer();
|
||||
}
|
||||
|
||||
const embedNoProviderPreview = (
|
||||
<>
|
||||
<TouchableWithoutFeedback
|
||||
accessibilityRole={ 'button' }
|
||||
accessibilityHint={ previewButtonA11yHint }
|
||||
disabled={ ! isSelected }
|
||||
onPress={ onPressContainer }
|
||||
>
|
||||
<View style={ containerStyle }>
|
||||
<BlockIcon icon={ icon } />
|
||||
<Text style={ labelStyle }>{ label }</Text>
|
||||
<Text style={ descriptionStyle }>
|
||||
{ sprintf(
|
||||
// translators: %s: embed block variant's label e.g: "Twitter".
|
||||
__( '%s previews not yet available' ),
|
||||
label
|
||||
) }
|
||||
</Text>
|
||||
<Text style={ styles.embed__action }>
|
||||
{ previewButtonText.toUpperCase() }
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
accessibilityHint={ __( 'Tap here to show help' ) }
|
||||
accessibilityLabel={ __( 'Help button' ) }
|
||||
accessibilityRole={ 'button' }
|
||||
disabled={ ! isSelected }
|
||||
onPress={ onPressHelp }
|
||||
style={ helpIconStyle }
|
||||
>
|
||||
<Icon
|
||||
icon={ help }
|
||||
fill={ helpIconStyle.fill }
|
||||
size={ helpIconStyle.width }
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<BottomSheet
|
||||
isVisible={ isSheetVisible }
|
||||
hideHeader
|
||||
onDismiss={ onDismissSheet }
|
||||
onClose={ onCloseSheet }
|
||||
testID="embed-no-preview-modal"
|
||||
>
|
||||
<View style={ styles[ 'embed-no-preview__container' ] }>
|
||||
<View style={ sheetIconStyle }>
|
||||
<Icon
|
||||
icon={ help }
|
||||
fill={ sheetIconStyle.fill }
|
||||
size={ sheetIconStyle.width }
|
||||
/>
|
||||
</View>
|
||||
<Text style={ sheetTitleStyle }>
|
||||
{ isDefaultEmbedInfo
|
||||
? __( 'Embed block previews are coming soon' )
|
||||
: sprintf(
|
||||
// translators: %s: embed block variant's label e.g: "Twitter".
|
||||
__(
|
||||
'%s embed block previews are coming soon'
|
||||
),
|
||||
label
|
||||
) }
|
||||
</Text>
|
||||
<Text style={ sheetDescriptionStyle }>
|
||||
{ comingSoonDescription }
|
||||
</Text>
|
||||
</View>
|
||||
<TextControl
|
||||
label={ previewButtonText }
|
||||
separatorType="topFullWidth"
|
||||
onPress={ () => {
|
||||
shouldRequestReview.current = true;
|
||||
onCloseSheet();
|
||||
} }
|
||||
labelStyle={ sheetButtonStyle }
|
||||
/>
|
||||
<TextControl
|
||||
label={ __( 'Dismiss' ) }
|
||||
separatorType="topFullWidth"
|
||||
onPress={ onCloseSheet }
|
||||
labelStyle={ sheetButtonStyle }
|
||||
/>
|
||||
</BottomSheet>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ previewable ? (
|
||||
embedNoProviderPreview
|
||||
) : (
|
||||
<View style={ containerStyle }>
|
||||
<BlockIcon icon={ icon } />
|
||||
<Text style={ labelStyle }>
|
||||
{ __( 'No preview available' ) }
|
||||
</Text>
|
||||
</View>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedNoPreview;
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { Button, Placeholder, ExternalLink } from '@wordpress/components';
|
||||
import { BlockIcon } from '@wordpress/block-editor';
|
||||
|
||||
const EmbedPlaceholder = ( {
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
onSubmit,
|
||||
onChange,
|
||||
cannotEmbed,
|
||||
fallback,
|
||||
tryAgain,
|
||||
} ) => {
|
||||
return (
|
||||
<Placeholder
|
||||
icon={ <BlockIcon icon={ icon } showColors /> }
|
||||
label={ label }
|
||||
className="wp-block-embed"
|
||||
instructions={ __(
|
||||
'Paste a link to the content you want to display on your site.'
|
||||
) }
|
||||
>
|
||||
<form onSubmit={ onSubmit }>
|
||||
<input
|
||||
type="url"
|
||||
value={ value || '' }
|
||||
className="components-placeholder__input"
|
||||
aria-label={ label }
|
||||
placeholder={ __( 'Enter URL to embed here…' ) }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
<Button variant="primary" type="submit">
|
||||
{ _x( 'Embed', 'button label' ) }
|
||||
</Button>
|
||||
</form>
|
||||
<div className="components-placeholder__learn-more">
|
||||
<ExternalLink
|
||||
href={ __(
|
||||
'https://wordpress.org/support/article/embeds/'
|
||||
) }
|
||||
>
|
||||
{ __( 'Learn more about embeds' ) }
|
||||
</ExternalLink>
|
||||
</div>
|
||||
{ cannotEmbed && (
|
||||
<div className="components-placeholder__error">
|
||||
<div className="components-placeholder__instructions">
|
||||
{ __( 'Sorry, this content could not be embedded.' ) }
|
||||
</div>
|
||||
<Button variant="secondary" onClick={ tryAgain }>
|
||||
{ _x( 'Try again', 'button label' ) }
|
||||
</Button>{ ' ' }
|
||||
<Button variant="secondary" onClick={ fallback }>
|
||||
{ _x( 'Convert to link', 'button label' ) }
|
||||
</Button>
|
||||
</div>
|
||||
) }
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedPlaceholder;
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { View, Text, TouchableWithoutFeedback } from 'react-native';
|
||||
import { compact } from 'lodash';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
|
||||
import { Icon, Picker } from '@wordpress/components';
|
||||
import { BlockIcon } from '@wordpress/block-editor';
|
||||
import { useRef } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import styles from './styles.scss';
|
||||
import { noticeOutline } from '../../../components/src/mobile/gridicons';
|
||||
|
||||
const EmbedPlaceholder = ( {
|
||||
icon,
|
||||
isSelected,
|
||||
label,
|
||||
onPress,
|
||||
cannotEmbed,
|
||||
fallback,
|
||||
tryAgain,
|
||||
openEmbedLinkSettings,
|
||||
} ) => {
|
||||
const containerStyle = usePreferredColorSchemeStyle(
|
||||
styles.embed__container,
|
||||
styles[ 'embed__container--dark' ]
|
||||
);
|
||||
const labelStyle = usePreferredColorSchemeStyle(
|
||||
styles.embed__label,
|
||||
styles[ 'embed__label--dark' ]
|
||||
);
|
||||
const descriptionStyle = styles.embed__description;
|
||||
const descriptionErrorStyle = styles[ 'embed__description--error' ];
|
||||
const actionStyle = usePreferredColorSchemeStyle(
|
||||
styles.embed__action,
|
||||
styles[ 'embed__action--dark' ]
|
||||
);
|
||||
const embedIconErrorStyle = styles[ 'embed__icon--error' ];
|
||||
|
||||
const cannotEmbedMenuPickerRef = useRef();
|
||||
|
||||
const errorPickerOptions = {
|
||||
retry: {
|
||||
id: 'retryOption',
|
||||
label: __( 'Retry' ),
|
||||
value: 'retryOption',
|
||||
onSelect: tryAgain,
|
||||
},
|
||||
convertToLink: {
|
||||
id: 'convertToLinkOption',
|
||||
label: __( 'Convert to link' ),
|
||||
value: 'convertToLinkOption',
|
||||
onSelect: fallback,
|
||||
},
|
||||
editLink: {
|
||||
id: 'editLinkOption',
|
||||
label: __( 'Edit link' ),
|
||||
value: 'editLinkOption',
|
||||
onSelect: openEmbedLinkSettings,
|
||||
},
|
||||
};
|
||||
|
||||
const options = compact( [
|
||||
cannotEmbed && errorPickerOptions.retry,
|
||||
cannotEmbed && errorPickerOptions.convertToLink,
|
||||
cannotEmbed && errorPickerOptions.editLink,
|
||||
] );
|
||||
|
||||
function onPickerSelect( value ) {
|
||||
const selectedItem = options.find( ( item ) => item.value === value );
|
||||
selectedItem.onSelect();
|
||||
}
|
||||
|
||||
// When the content cannot be embedded the onPress should trigger the Picker instead of the onPress prop.
|
||||
function resolveOnPressEvent() {
|
||||
if ( cannotEmbed ) {
|
||||
cannotEmbedMenuPickerRef.current?.presentPicker();
|
||||
} else {
|
||||
onPress();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback
|
||||
accessibilityRole={ 'button' }
|
||||
accessibilityHint={
|
||||
cannotEmbed
|
||||
? __( 'Double tap to view embed options.' )
|
||||
: __( 'Double tap to add a link.' )
|
||||
}
|
||||
onPress={ resolveOnPressEvent }
|
||||
disabled={ ! isSelected }
|
||||
>
|
||||
<View style={ containerStyle }>
|
||||
{ cannotEmbed ? (
|
||||
<>
|
||||
<Icon
|
||||
icon={ noticeOutline }
|
||||
fill={ embedIconErrorStyle.fill }
|
||||
style={ embedIconErrorStyle }
|
||||
/>
|
||||
<Text
|
||||
style={ [
|
||||
descriptionStyle,
|
||||
descriptionErrorStyle,
|
||||
] }
|
||||
>
|
||||
{ __( 'Unable to embed media' ) }
|
||||
</Text>
|
||||
<Text style={ actionStyle }>
|
||||
{ __( 'More options' ) }
|
||||
</Text>
|
||||
<Picker
|
||||
title={ __( 'Embed options' ) }
|
||||
ref={ cannotEmbedMenuPickerRef }
|
||||
options={ options }
|
||||
onChange={ onPickerSelect }
|
||||
hideCancelButton
|
||||
leftAlign
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BlockIcon icon={ icon } />
|
||||
<Text style={ labelStyle }>{ label }</Text>
|
||||
<Text style={ actionStyle }>
|
||||
{ __( 'ADD LINK' ) }
|
||||
</Text>
|
||||
</>
|
||||
) }
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedPlaceholder;
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getPhotoHtml } from './util';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames/dedupe';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Placeholder, SandBox } from '@wordpress/components';
|
||||
import { RichText, BlockIcon } from '@wordpress/block-editor';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import WpEmbedPreview from './wp-embed-preview';
|
||||
|
||||
class EmbedPreview extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
this.hideOverlay = this.hideOverlay.bind( this );
|
||||
this.state = {
|
||||
interactive: false,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps( nextProps, state ) {
|
||||
if ( ! nextProps.isSelected && state.interactive ) {
|
||||
// We only want to change this when the block is not selected, because changing it when
|
||||
// the block becomes selected makes the overlap disappear too early. Hiding the overlay
|
||||
// happens on mouseup when the overlay is clicked.
|
||||
return { interactive: false };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
hideOverlay() {
|
||||
// This is called onMouseUp on the overlay. We can't respond to the `isSelected` prop
|
||||
// changing, because that happens on mouse down, and the overlay immediately disappears,
|
||||
// and the mouse event can end up in the preview content. We can't use onClick on
|
||||
// the overlay to hide it either, because then the editor misses the mouseup event, and
|
||||
// thinks we're multi-selecting blocks.
|
||||
this.setState( { interactive: true } );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
preview,
|
||||
previewable,
|
||||
url,
|
||||
type,
|
||||
caption,
|
||||
onCaptionChange,
|
||||
isSelected,
|
||||
className,
|
||||
icon,
|
||||
label,
|
||||
insertBlocksAfter,
|
||||
} = this.props;
|
||||
const { scripts } = preview;
|
||||
const { interactive } = this.state;
|
||||
|
||||
const html = 'photo' === type ? getPhotoHtml( preview ) : preview.html;
|
||||
const parsedHost = new URL( url ).host.split( '.' );
|
||||
const parsedHostBaseUrl = parsedHost
|
||||
.splice( parsedHost.length - 2, parsedHost.length - 1 )
|
||||
.join( '.' );
|
||||
const iframeTitle = sprintf(
|
||||
// translators: %s: host providing embed content e.g: www.youtube.com
|
||||
__( 'Embedded content from %s' ),
|
||||
parsedHostBaseUrl
|
||||
);
|
||||
const sandboxClassnames = classnames(
|
||||
type,
|
||||
className,
|
||||
'wp-block-embed__wrapper'
|
||||
);
|
||||
|
||||
// Disabled because the overlay div doesn't actually have a role or functionality
|
||||
// as far as the user is concerned. We're just catching the first click so that
|
||||
// the block can be selected without interacting with the embed preview that the overlay covers.
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
const embedWrapper =
|
||||
'wp-embed' === type ? (
|
||||
<WpEmbedPreview html={ html } />
|
||||
) : (
|
||||
<div className="wp-block-embed__wrapper">
|
||||
<SandBox
|
||||
html={ html }
|
||||
scripts={ scripts }
|
||||
title={ iframeTitle }
|
||||
type={ sandboxClassnames }
|
||||
onFocus={ this.hideOverlay }
|
||||
/>
|
||||
{ ! interactive && (
|
||||
<div
|
||||
className="block-library-embed__interactive-overlay"
|
||||
onMouseUp={ this.hideOverlay }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
||||
|
||||
return (
|
||||
<figure
|
||||
className={ classnames( className, 'wp-block-embed', {
|
||||
'is-type-video': 'video' === type,
|
||||
} ) }
|
||||
>
|
||||
{ previewable ? (
|
||||
embedWrapper
|
||||
) : (
|
||||
<Placeholder
|
||||
icon={ <BlockIcon icon={ icon } showColors /> }
|
||||
label={ label }
|
||||
>
|
||||
<p className="components-placeholder__error">
|
||||
<a href={ url }>{ url }</a>
|
||||
</p>
|
||||
<p className="components-placeholder__error">
|
||||
{ sprintf(
|
||||
/* translators: %s: host providing embed content e.g: www.youtube.com */
|
||||
__(
|
||||
"Embedded content from %s can't be previewed in the editor."
|
||||
),
|
||||
parsedHostBaseUrl
|
||||
) }
|
||||
</p>
|
||||
</Placeholder>
|
||||
) }
|
||||
{ ( ! RichText.isEmpty( caption ) || isSelected ) && (
|
||||
<RichText
|
||||
tagName="figcaption"
|
||||
placeholder={ __( 'Add caption' ) }
|
||||
value={ caption }
|
||||
onChange={ onCaptionChange }
|
||||
inlineToolbar
|
||||
__unstableOnSplitAtEnd={ () =>
|
||||
insertBlocksAfter( createBlock( 'core/paragraph' ) )
|
||||
}
|
||||
/>
|
||||
) }
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmbedPreview;
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { TouchableWithoutFeedback } from 'react-native';
|
||||
import { isEmpty } from 'lodash';
|
||||
import classnames from 'classnames/dedupe';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { View } from '@wordpress/primitives';
|
||||
import {
|
||||
BlockCaption,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { memo, useState } from '@wordpress/element';
|
||||
import { SandBox } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getPhotoHtml } from './util';
|
||||
import EmbedNoPreview from './embed-no-preview';
|
||||
import WpEmbedPreview from './wp-embed-preview';
|
||||
import styles from './styles.scss';
|
||||
|
||||
const EmbedPreview = ( {
|
||||
align,
|
||||
className,
|
||||
clientId,
|
||||
icon,
|
||||
insertBlocksAfter,
|
||||
isSelected,
|
||||
label,
|
||||
onFocus,
|
||||
preview,
|
||||
previewable,
|
||||
isProviderPreviewable,
|
||||
type,
|
||||
url,
|
||||
isDefaultEmbedInfo,
|
||||
} ) => {
|
||||
const [ isCaptionSelected, setIsCaptionSelected ] = useState( false );
|
||||
const { locale } = useSelect( blockEditorStore ).getSettings();
|
||||
|
||||
const wrapperStyle = styles[ 'embed-preview__wrapper' ];
|
||||
const wrapperAlignStyle =
|
||||
styles[ `embed-preview__wrapper--align-${ align }` ];
|
||||
const sandboxAlignStyle =
|
||||
styles[ `embed-preview__sandbox--align-${ align }` ];
|
||||
|
||||
function accessibilityLabelCreator( caption ) {
|
||||
return isEmpty( caption )
|
||||
? /* translators: accessibility text. Empty Embed caption. */
|
||||
__( 'Embed caption. Empty' )
|
||||
: sprintf(
|
||||
/* translators: accessibility text. %s: Embed caption. */
|
||||
__( 'Embed caption. %s' ),
|
||||
caption
|
||||
);
|
||||
}
|
||||
|
||||
function onEmbedPreviewPress() {
|
||||
setIsCaptionSelected( false );
|
||||
}
|
||||
|
||||
function onFocusCaption() {
|
||||
if ( onFocus ) {
|
||||
onFocus();
|
||||
}
|
||||
if ( ! isCaptionSelected ) {
|
||||
setIsCaptionSelected( true );
|
||||
}
|
||||
}
|
||||
|
||||
const { provider_url: providerUrl } = preview;
|
||||
const html = 'photo' === type ? getPhotoHtml( preview ) : preview.html;
|
||||
const parsedHost = new URL( url ).host.split( '.' );
|
||||
const parsedHostBaseUrl = parsedHost
|
||||
.splice( parsedHost.length - 2, parsedHost.length - 1 )
|
||||
.join( '.' );
|
||||
const iframeTitle = sprintf(
|
||||
// translators: %s: host providing embed content e.g: www.youtube.com
|
||||
__( 'Embedded content from %s' ),
|
||||
parsedHostBaseUrl
|
||||
);
|
||||
const sandboxClassnames = classnames(
|
||||
type,
|
||||
className,
|
||||
'wp-block-embed__wrapper'
|
||||
);
|
||||
|
||||
const PreviewContent = 'wp-embed' === type ? WpEmbedPreview : SandBox;
|
||||
const embedWrapper = (
|
||||
<>
|
||||
<TouchableWithoutFeedback
|
||||
onPress={ () => {
|
||||
if ( onFocus ) {
|
||||
onFocus();
|
||||
}
|
||||
if ( isCaptionSelected ) {
|
||||
setIsCaptionSelected( false );
|
||||
}
|
||||
} }
|
||||
>
|
||||
<View
|
||||
pointerEvents="box-only"
|
||||
style={ [ wrapperStyle, wrapperAlignStyle ] }
|
||||
>
|
||||
<PreviewContent
|
||||
html={ html }
|
||||
lang={ locale }
|
||||
title={ iframeTitle }
|
||||
type={ sandboxClassnames }
|
||||
providerUrl={ providerUrl }
|
||||
url={ url }
|
||||
containerStyle={ sandboxAlignStyle }
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
accessible={ ! isSelected }
|
||||
onPress={ onEmbedPreviewPress }
|
||||
disabled={ ! isSelected }
|
||||
>
|
||||
<View>
|
||||
{ isProviderPreviewable && previewable ? (
|
||||
embedWrapper
|
||||
) : (
|
||||
<EmbedNoPreview
|
||||
label={ label }
|
||||
icon={ icon }
|
||||
isSelected={ isSelected }
|
||||
onPress={ () => setIsCaptionSelected( false ) }
|
||||
previewable={ previewable }
|
||||
isDefaultEmbedInfo={ isDefaultEmbedInfo }
|
||||
/>
|
||||
) }
|
||||
<BlockCaption
|
||||
accessibilityLabelCreator={ accessibilityLabelCreator }
|
||||
accessible
|
||||
clientId={ clientId }
|
||||
insertBlocksAfter={ insertBlocksAfter }
|
||||
isSelected={ isCaptionSelected }
|
||||
onFocus={ onFocusCaption }
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo( EmbedPreview );
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { G, Path, SVG } from '@wordpress/components';
|
||||
|
||||
export const embedContentIcon = (
|
||||
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<Path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V9.8l4.7-5.3H19c.3 0 .5.2.5.5v14zm-6-9.5L16 12l-2.5 2.8 1.1 1L18 12l-3.5-3.5-1 1zm-3 0l-1-1L6 12l3.5 3.8 1.1-1L8 12l2.5-2.5z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedAudioIcon = (
|
||||
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<Path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V9.8l4.7-5.3H19c.3 0 .5.2.5.5v14zM13.2 7.7c-.4.4-.7 1.1-.7 1.9v3.7c-.4-.3-.8-.4-1.3-.4-1.2 0-2.2 1-2.2 2.2 0 1.2 1 2.2 2.2 2.2.5 0 1-.2 1.4-.5.9-.6 1.4-1.6 1.4-2.6V9.6c0-.4.1-.6.2-.8.3-.3 1-.3 1.6-.3h.2V7h-.2c-.7 0-1.8 0-2.6.7z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedPhotoIcon = (
|
||||
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<Path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.2 4.5H19c.3 0 .5.2.5.5v8.4l-3-2.9c-.3-.3-.8-.3-1 0L11.9 14 9 12c-.3-.2-.6-.2-.8 0l-3.6 2.6V9.8l4.6-5.3zm9.8 15H5c-.3 0-.5-.2-.5-.5v-2.4l4.1-3 3 1.9c.3.2.7.2.9-.1L16 12l3.5 3.4V19c0 .3-.2.5-.5.5z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedVideoIcon = (
|
||||
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<Path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V9.8l4.7-5.3H19c.3 0 .5.2.5.5v14zM10 15l5-3-5-3v6z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedTwitterIcon = {
|
||||
foreground: '#1da1f2',
|
||||
src: (
|
||||
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<G>
|
||||
<Path d="M22.23 5.924c-.736.326-1.527.547-2.357.646.847-.508 1.498-1.312 1.804-2.27-.793.47-1.67.812-2.606.996C18.325 4.498 17.258 4 16.078 4c-2.266 0-4.103 1.837-4.103 4.103 0 .322.036.635.106.935-3.41-.17-6.433-1.804-8.457-4.287-.353.607-.556 1.312-.556 2.064 0 1.424.724 2.68 1.825 3.415-.673-.022-1.305-.207-1.86-.514v.052c0 1.988 1.415 3.647 3.293 4.023-.344.095-.707.145-1.08.145-.265 0-.522-.026-.773-.074.522 1.63 2.038 2.817 3.833 2.85-1.404 1.1-3.174 1.757-5.096 1.757-.332 0-.66-.02-.98-.057 1.816 1.164 3.973 1.843 6.29 1.843 7.547 0 11.675-6.252 11.675-11.675 0-.178-.004-.355-.012-.53.802-.578 1.497-1.3 2.047-2.124z"></Path>
|
||||
</G>
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedYouTubeIcon = {
|
||||
foreground: '#ff0000',
|
||||
src: (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="M21.8 8s-.195-1.377-.795-1.984c-.76-.797-1.613-.8-2.004-.847-2.798-.203-6.996-.203-6.996-.203h-.01s-4.197 0-6.996.202c-.39.046-1.242.05-2.003.846C2.395 6.623 2.2 8 2.2 8S2 9.62 2 11.24v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.77 2.205.855 1.6.153 6.8.2 6.8.2s4.203-.005 7-.208c.392-.047 1.244-.05 2.005-.847.6-.607.795-1.985.795-1.985s.2-1.618.2-3.237v-1.517C22 9.62 21.8 8 21.8 8zM9.935 14.595v-5.62l5.403 2.82-5.403 2.8z" />
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedFacebookIcon = {
|
||||
foreground: '#3b5998',
|
||||
src: (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="M20 3H4c-.6 0-1 .4-1 1v16c0 .5.4 1 1 1h8.6v-7h-2.3v-2.7h2.3v-2c0-2.3 1.4-3.6 3.5-3.6 1 0 1.8.1 2.1.1v2.4h-1.4c-1.1 0-1.3.5-1.3 1.3v1.7h2.7l-.4 2.8h-2.3v7H20c.5 0 1-.4 1-1V4c0-.6-.4-1-1-1z" />
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedInstagramIcon = (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<G>
|
||||
<Path d="M12 4.622c2.403 0 2.688.01 3.637.052.877.04 1.354.187 1.67.31.42.163.72.358 1.036.673.315.315.51.615.673 1.035.123.317.27.794.31 1.67.043.95.052 1.235.052 3.638s-.01 2.688-.052 3.637c-.04.877-.187 1.354-.31 1.67-.163.42-.358.72-.673 1.036-.315.315-.615.51-1.035.673-.317.123-.794.27-1.67.31-.95.043-1.234.052-3.638.052s-2.688-.01-3.637-.052c-.877-.04-1.354-.187-1.67-.31-.42-.163-.72-.358-1.036-.673-.315-.315-.51-.615-.673-1.035-.123-.317-.27-.794-.31-1.67-.043-.95-.052-1.235-.052-3.638s.01-2.688.052-3.637c.04-.877.187-1.354.31-1.67.163-.42.358-.72.673-1.036.315-.315.615-.51 1.035-.673.317-.123.794-.27 1.67-.31.95-.043 1.235-.052 3.638-.052M12 3c-2.444 0-2.75.01-3.71.054s-1.613.196-2.185.418c-.592.23-1.094.538-1.594 1.04-.5.5-.807 1-1.037 1.593-.223.572-.375 1.226-.42 2.184C3.01 9.25 3 9.555 3 12s.01 2.75.054 3.71.196 1.613.418 2.186c.23.592.538 1.094 1.038 1.594s1.002.808 1.594 1.038c.572.222 1.227.375 2.185.418.96.044 1.266.054 3.71.054s2.75-.01 3.71-.054 1.613-.196 2.186-.418c.592-.23 1.094-.538 1.594-1.038s.808-1.002 1.038-1.594c.222-.572.375-1.227.418-2.185.044-.96.054-1.266.054-3.71s-.01-2.75-.054-3.71-.196-1.613-.418-2.186c-.23-.592-.538-1.094-1.038-1.594s-1.002-.808-1.594-1.038c-.572-.222-1.227-.375-2.185-.418C14.75 3.01 14.445 3 12 3zm0 4.378c-2.552 0-4.622 2.07-4.622 4.622s2.07 4.622 4.622 4.622 4.622-2.07 4.622-4.622S14.552 7.378 12 7.378zM12 15c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3zm4.804-8.884c-.596 0-1.08.484-1.08 1.08s.484 1.08 1.08 1.08c.596 0 1.08-.484 1.08-1.08s-.483-1.08-1.08-1.08z"></Path>
|
||||
</G>
|
||||
</SVG>
|
||||
);
|
||||
export const embedWordPressIcon = {
|
||||
foreground: '#0073AA',
|
||||
src: (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<G>
|
||||
<Path d="M12.158 12.786l-2.698 7.84c.806.236 1.657.365 2.54.365 1.047 0 2.05-.18 2.986-.51-.024-.037-.046-.078-.065-.123l-2.762-7.57zM3.008 12c0 3.56 2.07 6.634 5.068 8.092L3.788 8.342c-.5 1.117-.78 2.354-.78 3.658zm15.06-.454c0-1.112-.398-1.88-.74-2.48-.456-.74-.883-1.368-.883-2.11 0-.825.627-1.595 1.51-1.595.04 0 .078.006.116.008-1.598-1.464-3.73-2.36-6.07-2.36-3.14 0-5.904 1.613-7.512 4.053.21.008.41.012.58.012.94 0 2.395-.114 2.395-.114.484-.028.54.684.057.74 0 0-.487.058-1.03.086l3.275 9.74 1.968-5.902-1.4-3.838c-.485-.028-.944-.085-.944-.085-.486-.03-.43-.77.056-.742 0 0 1.484.114 2.368.114.94 0 2.397-.114 2.397-.114.486-.028.543.684.058.74 0 0-.488.058-1.03.086l3.25 9.665.897-2.997c.456-1.17.684-2.137.684-2.907zm1.82-3.86c.04.286.06.593.06.924 0 .912-.17 1.938-.683 3.22l-2.746 7.94c2.672-1.558 4.47-4.454 4.47-7.77 0-1.564-.4-3.033-1.1-4.314zM12 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10z"></Path>
|
||||
</G>
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedSpotifyIcon = {
|
||||
foreground: '#1db954',
|
||||
src: (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2m4.586 14.424c-.18.295-.563.387-.857.207-2.35-1.434-5.305-1.76-8.786-.963-.335.077-.67-.133-.746-.47-.077-.334.132-.67.47-.745 3.808-.87 7.076-.496 9.712 1.115.293.18.386.563.206.857M17.81 13.7c-.226.367-.706.482-1.072.257-2.687-1.652-6.785-2.13-9.965-1.166-.413.127-.848-.106-.973-.517-.125-.413.108-.848.52-.973 3.632-1.102 8.147-.568 11.234 1.328.366.226.48.707.256 1.072m.105-2.835C14.692 8.95 9.375 8.775 6.297 9.71c-.493.15-1.016-.13-1.166-.624-.148-.495.13-1.017.625-1.167 3.532-1.073 9.404-.866 13.115 1.337.445.264.59.838.327 1.282-.264.443-.838.59-1.282.325" />
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedFlickrIcon = (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="m6.5 7c-2.75 0-5 2.25-5 5s2.25 5 5 5 5-2.25 5-5-2.25-5-5-5zm11 0c-2.75 0-5 2.25-5 5s2.25 5 5 5 5-2.25 5-5-2.25-5-5-5z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedVimeoIcon = {
|
||||
foreground: '#1ab7ea',
|
||||
src: (
|
||||
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<G>
|
||||
<Path d="M22.396 7.164c-.093 2.026-1.507 4.8-4.245 8.32C15.323 19.16 12.93 21 10.97 21c-1.214 0-2.24-1.12-3.08-3.36-.56-2.052-1.118-4.105-1.68-6.158-.622-2.24-1.29-3.36-2.004-3.36-.156 0-.7.328-1.634.98l-.978-1.26c1.027-.903 2.04-1.806 3.037-2.71C6 3.95 7.03 3.328 7.716 3.265c1.62-.156 2.616.95 2.99 3.32.404 2.558.685 4.148.84 4.77.468 2.12.982 3.18 1.543 3.18.435 0 1.09-.687 1.963-2.064.872-1.376 1.34-2.422 1.402-3.142.125-1.187-.343-1.782-1.4-1.782-.5 0-1.013.115-1.542.34 1.023-3.35 2.977-4.976 5.862-4.883 2.14.063 3.148 1.45 3.024 4.16z"></Path>
|
||||
</G>
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedRedditIcon = (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="M22 11.816c0-1.256-1.02-2.277-2.277-2.277-.593 0-1.122.24-1.526.613-1.48-.965-3.455-1.594-5.647-1.69l1.17-3.702 3.18.75c.01 1.027.847 1.86 1.877 1.86 1.035 0 1.877-.84 1.877-1.877 0-1.035-.842-1.877-1.877-1.877-.77 0-1.43.466-1.72 1.13L13.55 3.92c-.204-.047-.4.067-.46.26l-1.35 4.27c-2.317.037-4.412.67-5.97 1.67-.402-.355-.917-.58-1.493-.58C3.02 9.54 2 10.56 2 11.815c0 .814.433 1.523 1.078 1.925-.037.222-.06.445-.06.673 0 3.292 4.01 5.97 8.94 5.97s8.94-2.678 8.94-5.97c0-.214-.02-.424-.052-.632.687-.39 1.154-1.12 1.154-1.964zm-3.224-7.422c.606 0 1.1.493 1.1 1.1s-.493 1.1-1.1 1.1-1.1-.494-1.1-1.1.493-1.1 1.1-1.1zm-16 7.422c0-.827.673-1.5 1.5-1.5.313 0 .598.103.838.27-.85.675-1.477 1.478-1.812 2.36-.32-.274-.525-.676-.525-1.13zm9.183 7.79c-4.502 0-8.165-2.33-8.165-5.193S7.457 9.22 11.96 9.22s8.163 2.33 8.163 5.193-3.663 5.193-8.164 5.193zM20.635 13c-.326-.89-.948-1.7-1.797-2.383.247-.186.55-.3.882-.3.827 0 1.5.672 1.5 1.5 0 .482-.23.91-.586 1.184zm-11.64 1.704c-.76 0-1.397-.616-1.397-1.376 0-.76.636-1.397 1.396-1.397.76 0 1.376.638 1.376 1.398 0 .76-.616 1.376-1.376 1.376zm7.405-1.376c0 .76-.615 1.376-1.375 1.376s-1.4-.616-1.4-1.376c0-.76.64-1.397 1.4-1.397.76 0 1.376.638 1.376 1.398zm-1.17 3.38c.15.152.15.398 0 .55-.675.674-1.728 1.002-3.22 1.002l-.01-.002-.012.002c-1.492 0-2.544-.328-3.218-1.002-.152-.152-.152-.398 0-.55.152-.152.4-.15.55 0 .52.52 1.394.775 2.67.775l.01.002.01-.002c1.276 0 2.15-.253 2.67-.775.15-.152.398-.152.55 0z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedTumblrIcon = {
|
||||
foreground: '#35465c',
|
||||
src: (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="M19 3H5a2 2 0 00-2 2v14c0 1.1.9 2 2 2h14a2 2 0 002-2V5a2 2 0 00-2-2zm-5.69 14.66c-2.72 0-3.1-1.9-3.1-3.16v-3.56H8.49V8.99c1.7-.62 2.54-1.99 2.64-2.87 0-.06.06-.41.06-.58h1.9v3.1h2.17v2.3h-2.18v3.1c0 .47.13 1.3 1.2 1.26h1.1v2.36c-1.01.02-2.07 0-2.07 0z" />
|
||||
</SVG>
|
||||
),
|
||||
};
|
||||
export const embedAmazonIcon = (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path d="M18.42 14.58c-.51-.66-1.05-1.23-1.05-2.5V7.87c0-1.8.15-3.45-1.2-4.68-1.05-1.02-2.79-1.35-4.14-1.35-2.6 0-5.52.96-6.12 4.14-.06.36.18.54.4.57l2.66.3c.24-.03.42-.27.48-.5.24-1.12 1.17-1.63 2.2-1.63.56 0 1.22.21 1.55.7.4.56.33 1.31.33 1.97v.36c-1.59.18-3.66.27-5.16.93a4.63 4.63 0 0 0-2.93 4.44c0 2.82 1.8 4.23 4.1 4.23 1.95 0 3.03-.45 4.53-1.98.51.72.66 1.08 1.59 1.83.18.09.45.09.63-.1v.04l2.1-1.8c.24-.21.2-.48.03-.75zm-5.4-1.2c-.45.75-1.14 1.23-1.92 1.23-1.05 0-1.65-.81-1.65-1.98 0-2.31 2.1-2.73 4.08-2.73v.6c0 1.05.03 1.92-.5 2.88z" />
|
||||
<Path d="M21.69 19.2a17.62 17.62 0 0 1-21.6-1.57c-.23-.2 0-.5.28-.33a23.88 23.88 0 0 0 20.93 1.3c.45-.19.84.3.39.6z" />
|
||||
<Path d="M22.8 17.96c-.36-.45-2.22-.2-3.1-.12-.23.03-.3-.18-.05-.36 1.5-1.05 3.96-.75 4.26-.39.3.36-.1 2.82-1.5 4.02-.21.18-.42.1-.3-.15.3-.8 1.02-2.58.69-3z" />
|
||||
</SVG>
|
||||
);
|
||||
export const embedAnimotoIcon = (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path
|
||||
d="m.0206909 21 19.8160091-13.07806 3.5831 6.20826z"
|
||||
fill="#4bc7ee"
|
||||
/>
|
||||
<Path
|
||||
d="m23.7254 19.0205-10.1074-17.18468c-.6421-1.114428-1.7087-1.114428-2.3249 0l-11.2931 19.16418h22.5655c1.279 0 1.8019-.8905 1.1599-1.9795z"
|
||||
fill="#d4cdcb"
|
||||
/>
|
||||
<Path
|
||||
d="m.0206909 21 15.2439091-16.38571 4.3029 7.32271z"
|
||||
fill="#c3d82e"
|
||||
/>
|
||||
<Path
|
||||
d="m13.618 1.83582c-.6421-1.114428-1.7087-1.114428-2.3249 0l-11.2931 19.16418 15.2646-16.38573z"
|
||||
fill="#e4ecb0"
|
||||
/>
|
||||
<Path d="m.0206909 21 19.5468091-9.063 1.6621 2.8344z" fill="#209dbd" />
|
||||
<Path
|
||||
d="m.0206909 21 17.9209091-11.82623 1.6259 2.76323z"
|
||||
fill="#7cb3c9"
|
||||
/>
|
||||
</SVG>
|
||||
);
|
||||
export const embedDailymotionIcon = (
|
||||
<SVG viewBox="0 0 24 24">
|
||||
<Path
|
||||
d="m12.1479 18.5957c-2.4949 0-4.28131-1.7558-4.28131-4.0658 0-2.2176 1.78641-4.0965 4.09651-4.0965 2.2793 0 4.0349 1.7864 4.0349 4.1581 0 2.2794-1.7556 4.0042-3.8501 4.0042zm8.3521-18.5957-4.5329 1v7c-1.1088-1.41691-2.8028-1.8787-4.8049-1.8787-2.09443 0-3.97329.76993-5.5133 2.27917-1.72483 1.66323-2.6489 3.78863-2.6489 6.16033 0 2.5873.98562 4.8049 2.89526 6.499 1.44763 1.2936 3.17251 1.9402 5.17454 1.9402 1.9713 0 3.4498-.5236 4.8973-1.9402v1.9402h4.5329c0-7.6359 0-15.3641 0-23z"
|
||||
fill="#333436"
|
||||
/>
|
||||
</SVG>
|
||||
);
|
||||
export const embedPinterestIcon = (
|
||||
<SVG width="24" height="24" viewBox="0 0 24 24" version="1.1">
|
||||
<Path d="M12.289,2C6.617,2,3.606,5.648,3.606,9.622c0,1.846,1.025,4.146,2.666,4.878c0.25,0.111,0.381,0.063,0.439-0.169 c0.044-0.175,0.267-1.029,0.365-1.428c0.032-0.128,0.017-0.237-0.091-0.362C6.445,11.911,6.01,10.75,6.01,9.668 c0-2.777,2.194-5.464,5.933-5.464c3.23,0,5.49,2.108,5.49,5.122c0,3.407-1.794,5.768-4.13,5.768c-1.291,0-2.257-1.021-1.948-2.277 c0.372-1.495,1.089-3.112,1.089-4.191c0-0.967-0.542-1.775-1.663-1.775c-1.319,0-2.379,1.309-2.379,3.059 c0,1.115,0.394,1.869,0.394,1.869s-1.302,5.279-1.54,6.261c-0.405,1.666,0.053,4.368,0.094,4.604 c0.021,0.126,0.167,0.169,0.25,0.063c0.129-0.165,1.699-2.419,2.142-4.051c0.158-0.59,0.817-2.995,0.817-2.995 c0.43,0.784,1.681,1.446,3.013,1.446c3.963,0,6.822-3.494,6.822-7.833C20.394,5.112,16.849,2,12.289,2" />
|
||||
</SVG>
|
||||
);
|
||||
|
||||
export const embedWolframIcon = (
|
||||
<SVG viewBox="0 0 44 44">
|
||||
<Path d="M32.59521,22.001l4.31885-4.84473-6.34131-1.38379.646-6.459-5.94336,2.61035L22,6.31934l-3.27344,5.60351L12.78418,9.3125l.645,6.458L7.08643,17.15234,11.40479,21.999,7.08594,26.84375l6.34131,1.38379-.64551,6.458,5.94287-2.60938L22,37.68066l3.27344-5.60351,5.94287,2.61035-.64551-6.458,6.34277-1.38183Zm.44385,2.75244L30.772,23.97827l-1.59558-2.07391,1.97888.735Zm-8.82147,6.1579L22.75,33.424V30.88977l1.52228-2.22168ZM18.56226,13.48816,19.819,15.09534l-2.49219-.88642L15.94037,12.337Zm6.87719.00116,2.62043-1.15027-1.38654,1.86981L24.183,15.0946Zm3.59357,2.6029-1.22546,1.7381.07525-2.73486,1.44507-1.94867ZM22,29.33008l-2.16406-3.15686L22,23.23688l2.16406,2.93634Zm-4.25458-9.582-.10528-3.836,3.60986,1.284v3.73242Zm5.00458-2.552,3.60986-1.284-.10528,3.836L22.75,20.92853Zm-7.78174-1.10559-.29352-2.94263,1.44245,1.94739.07519,2.73321Zm2.30982,5.08319,3.50817,1.18164-2.16247,2.9342-3.678-1.08447Zm2.4486,7.49285L21.25,30.88977v2.53485L19.78052,30.91Zm3.48707-6.31121,3.50817-1.18164,2.33228,3.03137-3.678,1.08447Zm10.87219-4.28113-2.714,3.04529L28.16418,19.928l1.92176-2.72565ZM24.06036,12.81769l-2.06012,2.6322-2.059-2.63318L22,9.292ZM9.91455,18.07227l4.00079-.87195,1.921,2.72735-3.20794,1.19019Zm2.93024,4.565,1.9801-.73462L13.228,23.97827l-2.26838.77429Zm-1.55591,3.58819L13.701,25.4021l2.64935.78058-2.14447.67853Zm3.64868,1.977L18.19,27.17334l.08313,3.46332L14.52979,32.2793Zm10.7876,2.43549.08447-3.464,3.25165,1.03052.407,4.07684Zm4.06824-3.77478-2.14545-.68,2.65063-.781,2.41266.825Z" />
|
||||
</SVG>
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import edit from './edit';
|
||||
import save from './save';
|
||||
import metadata from './block.json';
|
||||
import transforms from './transforms';
|
||||
import variations from './variations';
|
||||
import deprecated from './deprecated';
|
||||
import { embedContentIcon } from './icons';
|
||||
|
||||
const { name } = metadata;
|
||||
export { metadata, name };
|
||||
|
||||
export const settings = {
|
||||
icon: embedContentIcon,
|
||||
edit,
|
||||
save,
|
||||
transforms,
|
||||
variations,
|
||||
deprecated,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames/dedupe';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { RichText, useBlockProps } from '@wordpress/block-editor';
|
||||
|
||||
export default function save( { attributes } ) {
|
||||
const { url, caption, type, providerNameSlug } = attributes;
|
||||
|
||||
if ( ! url ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const className = classnames( 'wp-block-embed', {
|
||||
[ `is-type-${ type }` ]: type,
|
||||
[ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
|
||||
[ `wp-block-embed-${ providerNameSlug }` ]: providerNameSlug,
|
||||
} );
|
||||
|
||||
return (
|
||||
<figure { ...useBlockProps.save( { className } ) }>
|
||||
<div className="wp-block-embed__wrapper">
|
||||
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
|
||||
</div>
|
||||
{ ! RichText.isEmpty( caption ) && (
|
||||
<RichText.Content tagName="figcaption" value={ caption } />
|
||||
) }
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Apply max-width to floated items that have no intrinsic width
|
||||
.wp-block[data-align="left"] > [data-type="core/embed"],
|
||||
.wp-block[data-align="right"] > [data-type="core/embed"],
|
||||
.wp-block-embed.alignleft,
|
||||
.wp-block-embed.alignright {
|
||||
// Instagram widgets have a min-width of 326px, so go a bit beyond that.
|
||||
max-width: 360px;
|
||||
width: 100%;
|
||||
|
||||
// Unless these have a min-width, they collapse when floated.
|
||||
.wp-block-embed__wrapper {
|
||||
min-width: $break-zoomed-in;
|
||||
}
|
||||
}
|
||||
|
||||
// Supply a min-width when inside a cover block, to prevent it from collapsing.
|
||||
.wp-block-cover .wp-block-embed {
|
||||
min-width: 320px;
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
.wp-block-embed {
|
||||
margin: 0 0 1em 0;
|
||||
overflow-wrap: break-word; // Break long strings of text without spaces so they don't overflow the block.
|
||||
|
||||
// Supply caption styles to embeds, even if the theme hasn't opted in.
|
||||
// Reason being: the new markup, figcaptions, are not likely to be styled in the majority of existing themes,
|
||||
// so we supply the styles so as to not appear broken or unstyled in those.
|
||||
figcaption {
|
||||
@include caption-style();
|
||||
}
|
||||
|
||||
// Don't allow iframe to overflow it's container.
|
||||
iframe {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-embed__wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Add responsiveness to embeds with aspect ratios.
|
||||
.wp-embed-responsive .wp-has-aspect-ratio {
|
||||
.wp-block-embed__wrapper::before {
|
||||
content: "";
|
||||
display: block;
|
||||
padding-top: 50%; // Default to 2:1 aspect ratio.
|
||||
}
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wp-embed-responsive {
|
||||
.wp-embed-aspect-21-9 .wp-block-embed__wrapper::before {
|
||||
padding-top: 42.85%; // 9 / 21 * 100
|
||||
}
|
||||
|
||||
.wp-embed-aspect-18-9 .wp-block-embed__wrapper::before {
|
||||
padding-top: 50%; // 9 / 18 * 100
|
||||
}
|
||||
|
||||
.wp-embed-aspect-16-9 .wp-block-embed__wrapper::before {
|
||||
padding-top: 56.25%; // 9 / 16 * 100
|
||||
}
|
||||
|
||||
.wp-embed-aspect-4-3 .wp-block-embed__wrapper::before {
|
||||
padding-top: 75%; // 3 / 4 * 100
|
||||
}
|
||||
|
||||
.wp-embed-aspect-1-1 .wp-block-embed__wrapper::before {
|
||||
padding-top: 100%; // 1 / 1 * 100
|
||||
}
|
||||
|
||||
.wp-embed-aspect-9-16 .wp-block-embed__wrapper::before {
|
||||
padding-top: 177.77%; // 16 / 9 * 100
|
||||
}
|
||||
|
||||
.wp-embed-aspect-1-2 .wp-block-embed__wrapper::before {
|
||||
padding-top: 200%; // 2 / 1 * 100
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
.embed__container {
|
||||
flex: 1;
|
||||
min-height: 142;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $gray-lighten-30;
|
||||
padding-left: 12;
|
||||
padding-right: 12;
|
||||
padding-top: 12;
|
||||
padding-bottom: 12;
|
||||
border-top-left-radius: 4;
|
||||
border-top-right-radius: 4;
|
||||
border-bottom-left-radius: 4;
|
||||
border-bottom-right-radius: 4;
|
||||
}
|
||||
|
||||
.embed__container--dark {
|
||||
background-color: $background-dark-secondary;
|
||||
}
|
||||
|
||||
.embed__icon--error {
|
||||
margin-bottom: 6;
|
||||
fill: $alert-red;
|
||||
}
|
||||
|
||||
.embed__label {
|
||||
text-align: center;
|
||||
margin-top: 4;
|
||||
margin-bottom: 4;
|
||||
font-size: 14;
|
||||
font-weight: 500;
|
||||
color: $gray-90;
|
||||
}
|
||||
|
||||
.embed__label--dark {
|
||||
color: $gray-10;
|
||||
}
|
||||
|
||||
.embed__description {
|
||||
font-size: $default-font-size;
|
||||
text-align: center;
|
||||
margin-bottom: 4;
|
||||
color: $light-secondary;
|
||||
}
|
||||
|
||||
.embed__description--dark {
|
||||
color: $dark-secondary;
|
||||
}
|
||||
|
||||
.embed__description--error {
|
||||
color: $alert-red;
|
||||
}
|
||||
|
||||
.embed__action {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: $blue-wordpress;
|
||||
font-size: 14;
|
||||
font-weight: 500;
|
||||
margin-top: 4;
|
||||
}
|
||||
|
||||
.embed-preview__loading {
|
||||
flex: 1;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: $gray-lighten-30;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.embed-preview__wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.embed-preview__wrapper--align-left {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.embed-preview__wrapper--align-right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.embed-preview__sandbox--align-left {
|
||||
max-width: 360px;
|
||||
}
|
||||
.embed-preview__sandbox--align-right {
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.embed-preview__loading--dark {
|
||||
background-color: $background-dark-secondary;
|
||||
}
|
||||
|
||||
.embed-no-preview__container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.embed-no-preview__help-icon {
|
||||
position: absolute;
|
||||
top: 12;
|
||||
right: 12;
|
||||
width: 16;
|
||||
height: 16;
|
||||
fill: $light-secondary;
|
||||
}
|
||||
|
||||
.embed-no-preview__help-icon--dark {
|
||||
fill: $gray-20;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-icon {
|
||||
width: 36;
|
||||
height: 36;
|
||||
fill: $light-quaternary;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-icon--dark {
|
||||
fill: $gray-20;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-title {
|
||||
font-weight: 600;
|
||||
font-size: 20;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
color: $gray-90;
|
||||
|
||||
padding-top: 8;
|
||||
padding-bottom: 8;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-title--dark {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-description {
|
||||
font-size: 16;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
|
||||
padding-bottom: 24;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-description--dark {
|
||||
color: $gray-20;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-button {
|
||||
color: $blue-50;
|
||||
}
|
||||
|
||||
.embed-no-preview__sheet-button--dark {
|
||||
color: $blue-30;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Embed block alignment options sets Align center option 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true,\\"align\\":\\"center\\"} -->
|
||||
<figure class=\\"wp-block-embed aligncenter is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block alignment options sets Align left option 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true,\\"align\\":\\"left\\"} -->
|
||||
<figure class=\\"wp-block-embed alignleft is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block alignment options sets Align right option 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true,\\"align\\":\\"right\\"} -->
|
||||
<figure class=\\"wp-block-embed alignright is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block alignment options sets Full width option 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true,\\"align\\":\\"full\\"} -->
|
||||
<figure class=\\"wp-block-embed alignfull is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block alignment options sets Wide width option 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true,\\"align\\":\\"wide\\"} -->
|
||||
<figure class=\\"wp-block-embed alignwide is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block block settings toggles resize for smaller devices media settings 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"allowResponsive\\":false,\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block create by pasting URL creates embed block when pasting URL in paragraph block 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://www.youtube.com/watch?v=lXMskKTw3Bc\\",\\"type\\":\\"video\\",\\"providerNameSlug\\":\\"youtube\\",\\"responsive\\":true,\\"className\\":\\"wp-embed-aspect-16-9 wp-has-aspect-ratio\\"} -->
|
||||
<figure class=\\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://www.youtube.com/watch?v=lXMskKTw3Bc
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block create by pasting URL creates link when pasting URL in paragraph block 1`] = `
|
||||
"<!-- wp:paragraph -->
|
||||
<p><a href=\\"https://www.youtube.com/watch?v=lXMskKTw3Bc\\">https://www.youtube.com/watch?v=lXMskKTw3Bc</a></p>
|
||||
<!-- /wp:paragraph -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block displays cannot embed on the placeholder if preview data is null 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/testing\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/testing
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block edit URL edits URL when edited after setting a bad URL of a provider 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block edit URL keeps the previous URL if an invalid URL is set 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block edit URL keeps the previous URL if no URL is set 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block edit URL replaces URL 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://www.youtube.com/watch?v=lXMskKTw3Bc\\",\\"type\\":\\"video\\",\\"providerNameSlug\\":\\"youtube\\",\\"responsive\\":true,\\"className\\":\\"wp-embed-aspect-16-9 wp-has-aspect-ratio\\"} -->
|
||||
<figure class=\\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://www.youtube.com/watch?v=lXMskKTw3Bc
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block edit URL sets empty state when setting an empty URL 1`] = `"<!-- wp:embed {\\"url\\":\\"\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insert via slash inserter insert generic embed block 1`] = `"<!-- wp:embed /-->"`;
|
||||
|
||||
exports[`Embed block insert via slash inserter inserts Twitter embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insert via slash inserter inserts Vimeo embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"vimeo\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insert via slash inserter inserts WordPress embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"wordpress\\"} /-->"`;
|
||||
|
||||
exports[`Embed block insert via slash inserter inserts YouTube embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"youtube\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insertion inserts Twitter embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insertion inserts Vimeo embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"vimeo\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insertion inserts WordPress embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"wordpress\\"} /-->"`;
|
||||
|
||||
exports[`Embed block insertion inserts YouTube embed block 1`] = `"<!-- wp:embed {\\"providerNameSlug\\":\\"youtube\\",\\"responsive\\":true} /-->"`;
|
||||
|
||||
exports[`Embed block insertion inserts generic embed block 1`] = `"<!-- wp:embed /-->"`;
|
||||
|
||||
exports[`Embed block retry allows editing link if request failed 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\"} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block retry converts to link if preview request failed 1`] = `
|
||||
"<!-- wp:paragraph -->
|
||||
<p><a href=\\"https://twitter.com/notnownikki\\">https://twitter.com/notnownikki</a></p>
|
||||
<!-- /wp:paragraph -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block retry retries loading the preview if initial request failed 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block set URL upon block insertion auto-pastes the URL from clipboard 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block set URL upon block insertion sets a valid URL when dismissing edit URL modal 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block set URL upon block insertion sets empty URL when dismissing edit URL modal 1`] = `"<!-- wp:embed {\\"url\\":\\"\\"} /-->"`;
|
||||
|
||||
exports[`Embed block set URL when empty block auto-pastes the URL from clipboard 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block set URL when empty block sets a valid URL when dismissing edit URL modal 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
|
||||
exports[`Embed block set URL when empty block sets empty URL when dismissing edit URL modal 1`] = `"<!-- wp:embed {\\"url\\":\\"\\"} /-->"`;
|
||||
|
||||
exports[`Embed block sets block caption 1`] = `
|
||||
"<!-- wp:embed {\\"url\\":\\"https://twitter.com/notnownikki\\",\\"type\\":\\"rich\\",\\"providerNameSlug\\":\\"twitter\\",\\"responsive\\":true} -->
|
||||
<figure class=\\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\\"><div class=\\"wp-block-embed__wrapper\\">
|
||||
https://twitter.com/notnownikki
|
||||
</div><figcaption>Caption</figcaption></figure>
|
||||
<!-- /wp:embed -->"
|
||||
`;
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import {
|
||||
registerBlockType,
|
||||
unregisterBlockType,
|
||||
registerBlockVariation,
|
||||
unregisterBlockVariation,
|
||||
} from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
findMoreSuitableBlock,
|
||||
getClassNames,
|
||||
createUpgradedEmbedBlock,
|
||||
getEmbedInfoByProvider,
|
||||
removeAspectRatioClasses,
|
||||
} from '../util';
|
||||
import { embedInstagramIcon } from '../icons';
|
||||
import variations from '../variations';
|
||||
import metadata from '../block.json';
|
||||
|
||||
const { name: DEFAULT_EMBED_BLOCK, attributes } = metadata;
|
||||
|
||||
jest.mock( '@wordpress/data/src/components/use-select', () => () => ( {} ) );
|
||||
|
||||
describe( 'utils', () => {
|
||||
beforeAll( () => {
|
||||
registerBlockType( DEFAULT_EMBED_BLOCK, {
|
||||
title: 'Embed',
|
||||
category: 'embed',
|
||||
attributes,
|
||||
variations,
|
||||
} );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
unregisterBlockType( DEFAULT_EMBED_BLOCK );
|
||||
} );
|
||||
|
||||
describe( 'findMoreSuitableBlock', () => {
|
||||
test( 'findMoreSuitableBlock matches a URL to a block name', () => {
|
||||
const twitterURL = 'https://twitter.com/notnownikki';
|
||||
const youtubeURL = 'https://www.youtube.com/watch?v=bNnfuvC1LlU';
|
||||
const unknownURL = 'https://example.com/';
|
||||
|
||||
expect( findMoreSuitableBlock( twitterURL ) ).toEqual(
|
||||
expect.objectContaining( { name: 'twitter' } )
|
||||
);
|
||||
expect( findMoreSuitableBlock( youtubeURL ) ).toEqual(
|
||||
expect.objectContaining( { name: 'youtube' } )
|
||||
);
|
||||
expect( findMoreSuitableBlock( unknownURL ) ).toBeUndefined();
|
||||
} );
|
||||
} );
|
||||
describe( 'getClassNames', () => {
|
||||
it( 'should return aspect ratio class names for iframes with width and height', () => {
|
||||
const html = '<iframe height="9" width="16"></iframe>';
|
||||
const expected = 'wp-embed-aspect-16-9 wp-has-aspect-ratio';
|
||||
expect( getClassNames( html ) ).toEqual( expected );
|
||||
} );
|
||||
|
||||
it( 'should not return aspect ratio class names if we do not allow responsive', () => {
|
||||
const html = '<iframe height="9" width="16"></iframe>';
|
||||
const expected = '';
|
||||
expect( getClassNames( html, '', false ) ).toEqual( expected );
|
||||
} );
|
||||
|
||||
it( 'should preserve exsiting class names when removing responsive classes', () => {
|
||||
const html = '<iframe height="9" width="16"></iframe>';
|
||||
const expected = 'lovely';
|
||||
expect(
|
||||
getClassNames(
|
||||
html,
|
||||
'lovely wp-embed-aspect-16-9 wp-has-aspect-ratio',
|
||||
false
|
||||
)
|
||||
).toEqual( expected );
|
||||
} );
|
||||
|
||||
it( 'should return the same falsy value as passed for existing classes when no new classes are added', () => {
|
||||
const html = '<iframe></iframe>';
|
||||
const expected = undefined;
|
||||
expect( getClassNames( html, undefined, false ) ).toEqual(
|
||||
expected
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should preserve existing classes and replace aspect ratio related classes with the current embed preview', () => {
|
||||
const html = '<iframe height="3" width="4"></iframe>';
|
||||
const expected =
|
||||
'wp-block-embed wp-embed-aspect-4-3 wp-has-aspect-ratio';
|
||||
expect(
|
||||
getClassNames(
|
||||
html,
|
||||
'wp-block-embed wp-embed-aspect-16-9 wp-has-aspect-ratio',
|
||||
true
|
||||
)
|
||||
).toEqual( expected );
|
||||
} );
|
||||
} );
|
||||
describe( 'removeAspectRatioClasses', () => {
|
||||
it( 'should return the same falsy value as received', () => {
|
||||
const existingClassNames = undefined;
|
||||
expect( removeAspectRatioClasses( existingClassNames ) ).toEqual(
|
||||
existingClassNames
|
||||
);
|
||||
} );
|
||||
it( 'should preserve existing classes, if no aspect ratio classes exist', () => {
|
||||
const existingClassNames = 'wp-block-embed is-type-video';
|
||||
expect( removeAspectRatioClasses( existingClassNames ) ).toEqual(
|
||||
existingClassNames
|
||||
);
|
||||
} );
|
||||
it( 'should remove the aspect ratio classes', () => {
|
||||
const existingClassNames =
|
||||
'wp-block-embed is-type-video wp-embed-aspect-16-9 wp-has-aspect-ratio';
|
||||
expect( removeAspectRatioClasses( existingClassNames ) ).toEqual(
|
||||
'wp-block-embed is-type-video'
|
||||
);
|
||||
} );
|
||||
} );
|
||||
describe( 'createUpgradedEmbedBlock', () => {
|
||||
describe( 'do not create new block', () => {
|
||||
it( 'when block type does not exist', () => {
|
||||
const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w';
|
||||
|
||||
unregisterBlockType( DEFAULT_EMBED_BLOCK );
|
||||
|
||||
expect(
|
||||
createUpgradedEmbedBlock( {
|
||||
attributes: { url: youtubeURL },
|
||||
} )
|
||||
).toBeUndefined();
|
||||
|
||||
registerBlockType( DEFAULT_EMBED_BLOCK, {
|
||||
title: 'Embed',
|
||||
category: 'embed',
|
||||
attributes,
|
||||
variations,
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'when block variation does not exist', () => {
|
||||
const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w';
|
||||
|
||||
unregisterBlockVariation( DEFAULT_EMBED_BLOCK, 'youtube' );
|
||||
|
||||
expect(
|
||||
createUpgradedEmbedBlock( {
|
||||
attributes: { url: youtubeURL },
|
||||
} )
|
||||
).toBeUndefined();
|
||||
|
||||
registerBlockVariation(
|
||||
DEFAULT_EMBED_BLOCK,
|
||||
variations.find( ( { name } ) => name === 'youtube' )
|
||||
);
|
||||
} );
|
||||
it( 'when no url provided', () => {
|
||||
expect(
|
||||
createUpgradedEmbedBlock( { name: 'some name' } )
|
||||
).toBeUndefined();
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should return a YouTube embed block when given a YouTube URL', () => {
|
||||
const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
|
||||
|
||||
const result = createUpgradedEmbedBlock( {
|
||||
attributes: { url: youtubeURL },
|
||||
} );
|
||||
|
||||
expect( result ).toEqual(
|
||||
expect.objectContaining( {
|
||||
name: DEFAULT_EMBED_BLOCK,
|
||||
attributes: expect.objectContaining( {
|
||||
providerNameSlug: 'youtube',
|
||||
} ),
|
||||
} )
|
||||
);
|
||||
} );
|
||||
} );
|
||||
describe( 'getEmbedInfoByProvider', () => {
|
||||
it( 'should return embed info from existent variation', () => {
|
||||
expect( getEmbedInfoByProvider( 'instagram' ) ).toEqual(
|
||||
expect.objectContaining( {
|
||||
icon: embedInstagramIcon,
|
||||
title: 'Instagram',
|
||||
} )
|
||||
);
|
||||
} );
|
||||
it( 'should return undefined if not found in variations', () => {
|
||||
expect(
|
||||
getEmbedInfoByProvider( 'i do not exist' )
|
||||
).toBeUndefined();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
.wp-block-embed figcaption {
|
||||
@include caption-style-theme();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
|
||||
const { name: EMBED_BLOCK } = metadata;
|
||||
|
||||
/**
|
||||
* Default transforms for generic embeds.
|
||||
*/
|
||||
const transforms = {
|
||||
from: [
|
||||
{
|
||||
type: 'raw',
|
||||
isMatch: ( node ) =>
|
||||
node.nodeName === 'P' &&
|
||||
/^\s*(https?:\/\/\S+)\s*$/i.test( node.textContent ) &&
|
||||
node.textContent?.match( /https/gi )?.length === 1,
|
||||
transform: ( node ) => {
|
||||
return createBlock( EMBED_BLOCK, {
|
||||
url: node.textContent.trim(),
|
||||
} );
|
||||
},
|
||||
},
|
||||
],
|
||||
to: [
|
||||
{
|
||||
type: 'block',
|
||||
blocks: [ 'core/paragraph' ],
|
||||
isMatch: ( { url } ) => !! url,
|
||||
transform: ( { url, caption } ) => {
|
||||
let value = `<a href="${ url }">${ url }</a>`;
|
||||
if ( caption?.trim() ) {
|
||||
value += `<br />${ caption }`;
|
||||
}
|
||||
return createBlock( 'core/paragraph', {
|
||||
content: value,
|
||||
} );
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default transforms;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import webTransforms from './transforms.js';
|
||||
import transformationCategories from '../transformationCategories';
|
||||
|
||||
const transforms = {
|
||||
...webTransforms,
|
||||
supportedMobileTransforms: transformationCategories.media,
|
||||
};
|
||||
|
||||
export default transforms;
|
||||
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ASPECT_RATIOS, WP_EMBED_TYPE } from './constants';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { kebabCase } from 'lodash';
|
||||
import classnames from 'classnames/dedupe';
|
||||
import memoize from 'memize';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { renderToString } from '@wordpress/element';
|
||||
import {
|
||||
createBlock,
|
||||
getBlockType,
|
||||
getBlockVariations,
|
||||
} from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
|
||||
const { name: DEFAULT_EMBED_BLOCK } = metadata;
|
||||
|
||||
/** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */
|
||||
|
||||
/**
|
||||
* Returns the embed block's information by matching the provided service provider
|
||||
*
|
||||
* @param {string} provider The embed block's provider
|
||||
* @return {WPBlockVariation} The embed block's information
|
||||
*/
|
||||
export const getEmbedInfoByProvider = ( provider ) =>
|
||||
getBlockVariations( DEFAULT_EMBED_BLOCK )?.find(
|
||||
( { name } ) => name === provider
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if any of the regular expressions match the URL.
|
||||
*
|
||||
* @param {string} url The URL to test.
|
||||
* @param {Array} patterns The list of regular expressions to test agains.
|
||||
* @return {boolean} True if any of the regular expressions match the URL.
|
||||
*/
|
||||
export const matchesPatterns = ( url, patterns = [] ) =>
|
||||
patterns.some( ( pattern ) => url.match( pattern ) );
|
||||
|
||||
/**
|
||||
* Finds the block variation that should be used for the URL,
|
||||
* based on the provided URL and the variation's patterns.
|
||||
*
|
||||
* @param {string} url The URL to test.
|
||||
* @return {WPBlockVariation} The block variation that should be used for this URL
|
||||
*/
|
||||
export const findMoreSuitableBlock = ( url ) =>
|
||||
getBlockVariations( DEFAULT_EMBED_BLOCK )?.find( ( { patterns } ) =>
|
||||
matchesPatterns( url, patterns )
|
||||
);
|
||||
|
||||
export const isFromWordPress = ( html ) =>
|
||||
html && html.includes( 'class="wp-embedded-content"' );
|
||||
|
||||
export const getPhotoHtml = ( photo ) => {
|
||||
// 100% width for the preview so it fits nicely into the document, some "thumbnails" are
|
||||
// actually the full size photo. If thumbnails not found, use full image.
|
||||
const imageUrl = photo.thumbnail_url || photo.url;
|
||||
const photoPreview = (
|
||||
<p>
|
||||
<img src={ imageUrl } alt={ photo.title } width="100%" />
|
||||
</p>
|
||||
);
|
||||
return renderToString( photoPreview );
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a more suitable embed block based on the passed in props
|
||||
* and attributes generated from an embed block's preview.
|
||||
*
|
||||
* We require `attributesFromPreview` to be generated from the latest attributes
|
||||
* and preview, and because of the way the react lifecycle operates, we can't
|
||||
* guarantee that the attributes contained in the block's props are the latest
|
||||
* versions, so we require that these are generated separately.
|
||||
* See `getAttributesFromPreview` in the generated embed edit component.
|
||||
*
|
||||
* @param {Object} props The block's props.
|
||||
* @param {Object} [attributesFromPreview] Attributes generated from the block's most up to date preview.
|
||||
* @return {Object|undefined} A more suitable embed block if one exists.
|
||||
*/
|
||||
export const createUpgradedEmbedBlock = (
|
||||
props,
|
||||
attributesFromPreview = {}
|
||||
) => {
|
||||
const { preview, attributes = {} } = props;
|
||||
const { url, providerNameSlug, type, ...restAttributes } = attributes;
|
||||
|
||||
if ( ! url || ! getBlockType( DEFAULT_EMBED_BLOCK ) ) return;
|
||||
|
||||
const matchedBlock = findMoreSuitableBlock( url );
|
||||
|
||||
// WordPress blocks can work on multiple sites, and so don't have patterns,
|
||||
// so if we're in a WordPress block, assume the user has chosen it for a WordPress URL.
|
||||
const isCurrentBlockWP =
|
||||
providerNameSlug === 'wordpress' || type === WP_EMBED_TYPE;
|
||||
// If current block is not WordPress and a more suitable block found
|
||||
// that is different from the current one, create the new matched block.
|
||||
const shouldCreateNewBlock =
|
||||
! isCurrentBlockWP &&
|
||||
matchedBlock &&
|
||||
( matchedBlock.attributes.providerNameSlug !== providerNameSlug ||
|
||||
! providerNameSlug );
|
||||
if ( shouldCreateNewBlock ) {
|
||||
return createBlock( DEFAULT_EMBED_BLOCK, {
|
||||
url,
|
||||
...restAttributes,
|
||||
...matchedBlock.attributes,
|
||||
} );
|
||||
}
|
||||
|
||||
const wpVariation = getBlockVariations( DEFAULT_EMBED_BLOCK )?.find(
|
||||
( { name } ) => name === 'wordpress'
|
||||
);
|
||||
|
||||
// We can't match the URL for WordPress embeds, we have to check the HTML instead.
|
||||
if (
|
||||
! wpVariation ||
|
||||
! preview ||
|
||||
! isFromWordPress( preview.html ) ||
|
||||
isCurrentBlockWP
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not the WordPress embed block so transform it into one.
|
||||
return createBlock( DEFAULT_EMBED_BLOCK, {
|
||||
url,
|
||||
...wpVariation.attributes,
|
||||
// By now we have the preview, but when the new block first renders, it
|
||||
// won't have had all the attributes set, and so won't get the correct
|
||||
// type and it won't render correctly. So, we pass through the current attributes
|
||||
// here so that the initial render works when we switch to the WordPress
|
||||
// block. This only affects the WordPress block because it can't be
|
||||
// rendered in the usual Sandbox (it has a sandbox of its own) and it
|
||||
// relies on the preview to set the correct render type.
|
||||
...attributesFromPreview,
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all previously set aspect ratio related classes and return the rest
|
||||
* existing class names.
|
||||
*
|
||||
* @param {string} existingClassNames Any existing class names.
|
||||
* @return {string} The class names without any aspect ratio related class.
|
||||
*/
|
||||
export const removeAspectRatioClasses = ( existingClassNames ) => {
|
||||
if ( ! existingClassNames ) {
|
||||
// Avoids extraneous work and also, by returning the same value as
|
||||
// received, ensures the post is not dirtied by a change of the block
|
||||
// attribute from `undefined` to an emtpy string.
|
||||
return existingClassNames;
|
||||
}
|
||||
const aspectRatioClassNames = ASPECT_RATIOS.reduce(
|
||||
( accumulator, { className } ) => {
|
||||
accumulator[ className ] = false;
|
||||
return accumulator;
|
||||
},
|
||||
{ 'wp-has-aspect-ratio': false }
|
||||
);
|
||||
return classnames( existingClassNames, aspectRatioClassNames );
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns class names with any relevant responsive aspect ratio names.
|
||||
*
|
||||
* @param {string} html The preview HTML that possibly contains an iframe with width and height set.
|
||||
* @param {string} existingClassNames Any existing class names.
|
||||
* @param {boolean} allowResponsive If the responsive class names should be added, or removed.
|
||||
* @return {string} Deduped class names.
|
||||
*/
|
||||
export function getClassNames(
|
||||
html,
|
||||
existingClassNames,
|
||||
allowResponsive = true
|
||||
) {
|
||||
if ( ! allowResponsive ) {
|
||||
return removeAspectRatioClasses( existingClassNames );
|
||||
}
|
||||
|
||||
const previewDocument = document.implementation.createHTMLDocument( '' );
|
||||
previewDocument.body.innerHTML = html;
|
||||
const iframe = previewDocument.body.querySelector( 'iframe' );
|
||||
|
||||
// If we have a fixed aspect iframe, and it's a responsive embed block.
|
||||
if ( iframe && iframe.height && iframe.width ) {
|
||||
const aspectRatio = ( iframe.width / iframe.height ).toFixed( 2 );
|
||||
// Given the actual aspect ratio, find the widest ratio to support it.
|
||||
for (
|
||||
let ratioIndex = 0;
|
||||
ratioIndex < ASPECT_RATIOS.length;
|
||||
ratioIndex++
|
||||
) {
|
||||
const potentialRatio = ASPECT_RATIOS[ ratioIndex ];
|
||||
if ( aspectRatio >= potentialRatio.ratio ) {
|
||||
// Evaluate the difference between actual aspect ratio and closest match.
|
||||
// If the difference is too big, do not scale the embed according to aspect ratio.
|
||||
const ratioDiff = aspectRatio - potentialRatio.ratio;
|
||||
if ( ratioDiff > 0.1 ) {
|
||||
// No close aspect ratio match found.
|
||||
return removeAspectRatioClasses( existingClassNames );
|
||||
}
|
||||
// Close aspect ratio match found.
|
||||
return classnames(
|
||||
removeAspectRatioClasses( existingClassNames ),
|
||||
potentialRatio.className,
|
||||
'wp-has-aspect-ratio'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return existingClassNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback behaviour for unembeddable URLs.
|
||||
* Creates a paragraph block containing a link to the URL, and calls `onReplace`.
|
||||
*
|
||||
* @param {string} url The URL that could not be embedded.
|
||||
* @param {Function} onReplace Function to call with the created fallback block.
|
||||
*/
|
||||
export function fallback( url, onReplace ) {
|
||||
const link = <a href={ url }>{ url }</a>;
|
||||
onReplace(
|
||||
createBlock( 'core/paragraph', { content: renderToString( link ) } )
|
||||
);
|
||||
}
|
||||
|
||||
/***
|
||||
* Gets block attributes based on the preview and responsive state.
|
||||
*
|
||||
* @param {Object} preview The preview data.
|
||||
* @param {string} title The block's title, e.g. Twitter.
|
||||
* @param {Object} currentClassNames The block's current class names.
|
||||
* @param {boolean} isResponsive Boolean indicating if the block supports responsive content.
|
||||
* @param {boolean} allowResponsive Apply responsive classes to fixed size content.
|
||||
* @return {Object} Attributes and values.
|
||||
*/
|
||||
export const getAttributesFromPreview = memoize(
|
||||
(
|
||||
preview,
|
||||
title,
|
||||
currentClassNames,
|
||||
isResponsive,
|
||||
allowResponsive = true
|
||||
) => {
|
||||
if ( ! preview ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const attributes = {};
|
||||
// Some plugins only return HTML with no type info, so default this to 'rich'.
|
||||
let { type = 'rich' } = preview;
|
||||
// If we got a provider name from the API, use it for the slug, otherwise we use the title,
|
||||
// because not all embed code gives us a provider name.
|
||||
const { html, provider_name: providerName } = preview;
|
||||
const providerNameSlug = kebabCase(
|
||||
( providerName || title ).toLowerCase()
|
||||
);
|
||||
|
||||
if ( isFromWordPress( html ) ) {
|
||||
type = WP_EMBED_TYPE;
|
||||
}
|
||||
|
||||
if ( html || 'photo' === type ) {
|
||||
attributes.type = type;
|
||||
attributes.providerNameSlug = providerNameSlug;
|
||||
}
|
||||
|
||||
attributes.className = getClassNames(
|
||||
html,
|
||||
currentClassNames,
|
||||
isResponsive && allowResponsive
|
||||
);
|
||||
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
embedContentIcon,
|
||||
embedAudioIcon,
|
||||
embedPhotoIcon,
|
||||
embedVideoIcon,
|
||||
embedTwitterIcon,
|
||||
embedYouTubeIcon,
|
||||
embedFacebookIcon,
|
||||
embedInstagramIcon,
|
||||
embedWordPressIcon,
|
||||
embedSpotifyIcon,
|
||||
embedFlickrIcon,
|
||||
embedVimeoIcon,
|
||||
embedRedditIcon,
|
||||
embedTumblrIcon,
|
||||
embedAmazonIcon,
|
||||
embedAnimotoIcon,
|
||||
embedDailymotionIcon,
|
||||
embedPinterestIcon,
|
||||
embedWolframIcon,
|
||||
} from './icons';
|
||||
|
||||
/** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */
|
||||
|
||||
/**
|
||||
* Template option choices for predefined columns layouts.
|
||||
*
|
||||
* @type {WPBlockVariation[]}
|
||||
*/
|
||||
const variations = [
|
||||
{
|
||||
name: 'twitter',
|
||||
title: 'Twitter',
|
||||
icon: embedTwitterIcon,
|
||||
keywords: [ 'tweet', __( 'social' ) ],
|
||||
description: __( 'Embed a tweet.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?twitter\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'twitter', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'youtube',
|
||||
title: 'YouTube',
|
||||
icon: embedYouTubeIcon,
|
||||
keywords: [ __( 'music' ), __( 'video' ) ],
|
||||
description: __( 'Embed a YouTube video.' ),
|
||||
patterns: [
|
||||
/^https?:\/\/((m|www)\.)?youtube\.com\/.+/i,
|
||||
/^https?:\/\/youtu\.be\/.+/i,
|
||||
],
|
||||
attributes: { providerNameSlug: 'youtube', responsive: true },
|
||||
},
|
||||
{
|
||||
// Deprecate Facebook Embed per FB policy
|
||||
// See: https://developers.facebook.com/docs/plugins/oembed-legacy
|
||||
name: 'facebook',
|
||||
title: 'Facebook',
|
||||
icon: embedFacebookIcon,
|
||||
keywords: [ __( 'social' ) ],
|
||||
description: __( 'Embed a Facebook post.' ),
|
||||
scope: [ 'block' ],
|
||||
patterns: [],
|
||||
attributes: {
|
||||
providerNameSlug: 'facebook',
|
||||
previewable: false,
|
||||
responsive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Deprecate Instagram per FB policy
|
||||
// See: https://developers.facebook.com/docs/instagram/oembed-legacy
|
||||
name: 'instagram',
|
||||
title: 'Instagram',
|
||||
icon: embedInstagramIcon,
|
||||
keywords: [ __( 'image' ), __( 'social' ) ],
|
||||
description: __( 'Embed an Instagram post.' ),
|
||||
scope: [ 'block' ],
|
||||
patterns: [],
|
||||
attributes: { providerNameSlug: 'instagram', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'wordpress',
|
||||
title: 'WordPress',
|
||||
icon: embedWordPressIcon,
|
||||
keywords: [ __( 'post' ), __( 'blog' ) ],
|
||||
description: __( 'Embed a WordPress post.' ),
|
||||
attributes: {
|
||||
providerNameSlug: 'wordpress',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'soundcloud',
|
||||
title: 'SoundCloud',
|
||||
icon: embedAudioIcon,
|
||||
keywords: [ __( 'music' ), __( 'audio' ) ],
|
||||
description: __( 'Embed SoundCloud content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?soundcloud\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'soundcloud', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'spotify',
|
||||
title: 'Spotify',
|
||||
icon: embedSpotifyIcon,
|
||||
keywords: [ __( 'music' ), __( 'audio' ) ],
|
||||
description: __( 'Embed Spotify content.' ),
|
||||
patterns: [ /^https?:\/\/(open|play)\.spotify\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'spotify', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'flickr',
|
||||
title: 'Flickr',
|
||||
icon: embedFlickrIcon,
|
||||
keywords: [ __( 'image' ) ],
|
||||
description: __( 'Embed Flickr content.' ),
|
||||
patterns: [
|
||||
/^https?:\/\/(www\.)?flickr\.com\/.+/i,
|
||||
/^https?:\/\/flic\.kr\/.+/i,
|
||||
],
|
||||
attributes: { providerNameSlug: 'flickr', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'vimeo',
|
||||
title: 'Vimeo',
|
||||
icon: embedVimeoIcon,
|
||||
keywords: [ __( 'video' ) ],
|
||||
description: __( 'Embed a Vimeo video.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?vimeo\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'vimeo', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'animoto',
|
||||
title: 'Animoto',
|
||||
icon: embedAnimotoIcon,
|
||||
description: __( 'Embed an Animoto video.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?(animoto|video214)\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'animoto', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'cloudup',
|
||||
title: 'Cloudup',
|
||||
icon: embedContentIcon,
|
||||
description: __( 'Embed Cloudup content.' ),
|
||||
patterns: [ /^https?:\/\/cloudup\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'cloudup', responsive: true },
|
||||
},
|
||||
{
|
||||
// Deprecated since CollegeHumor content is now powered by YouTube.
|
||||
name: 'collegehumor',
|
||||
title: 'CollegeHumor',
|
||||
icon: embedVideoIcon,
|
||||
description: __( 'Embed CollegeHumor content.' ),
|
||||
scope: [ 'block' ],
|
||||
patterns: [],
|
||||
attributes: { providerNameSlug: 'collegehumor', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'crowdsignal',
|
||||
title: 'Crowdsignal',
|
||||
icon: embedContentIcon,
|
||||
keywords: [ 'polldaddy', __( 'survey' ) ],
|
||||
description: __( 'Embed Crowdsignal (formerly Polldaddy) content.' ),
|
||||
patterns: [
|
||||
/^https?:\/\/((.+\.)?polldaddy\.com|poll\.fm|.+\.survey\.fm)\/.+/i,
|
||||
],
|
||||
attributes: { providerNameSlug: 'crowdsignal', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'dailymotion',
|
||||
title: 'Dailymotion',
|
||||
icon: embedDailymotionIcon,
|
||||
keywords: [ __( 'video' ) ],
|
||||
description: __( 'Embed a Dailymotion video.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?dailymotion\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'dailymotion', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'imgur',
|
||||
title: 'Imgur',
|
||||
icon: embedPhotoIcon,
|
||||
description: __( 'Embed Imgur content.' ),
|
||||
patterns: [ /^https?:\/\/(.+\.)?imgur\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'imgur', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'issuu',
|
||||
title: 'Issuu',
|
||||
icon: embedContentIcon,
|
||||
description: __( 'Embed Issuu content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?issuu\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'issuu', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'kickstarter',
|
||||
title: 'Kickstarter',
|
||||
icon: embedContentIcon,
|
||||
description: __( 'Embed Kickstarter content.' ),
|
||||
patterns: [
|
||||
/^https?:\/\/(www\.)?kickstarter\.com\/.+/i,
|
||||
/^https?:\/\/kck\.st\/.+/i,
|
||||
],
|
||||
attributes: { providerNameSlug: 'kickstarter', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'mixcloud',
|
||||
title: 'Mixcloud',
|
||||
icon: embedAudioIcon,
|
||||
keywords: [ __( 'music' ), __( 'audio' ) ],
|
||||
description: __( 'Embed Mixcloud content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?mixcloud\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'mixcloud', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'reddit',
|
||||
title: 'Reddit',
|
||||
icon: embedRedditIcon,
|
||||
description: __( 'Embed a Reddit thread.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?reddit\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'reddit', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'reverbnation',
|
||||
title: 'ReverbNation',
|
||||
icon: embedAudioIcon,
|
||||
description: __( 'Embed ReverbNation content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?reverbnation\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'reverbnation', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'screencast',
|
||||
title: 'Screencast',
|
||||
icon: embedVideoIcon,
|
||||
description: __( 'Embed Screencast content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?screencast\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'screencast', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'scribd',
|
||||
title: 'Scribd',
|
||||
icon: embedContentIcon,
|
||||
description: __( 'Embed Scribd content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?scribd\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'scribd', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'slideshare',
|
||||
title: 'Slideshare',
|
||||
icon: embedContentIcon,
|
||||
description: __( 'Embed Slideshare content.' ),
|
||||
patterns: [ /^https?:\/\/(.+?\.)?slideshare\.net\/.+/i ],
|
||||
attributes: { providerNameSlug: 'slideshare', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'smugmug',
|
||||
title: 'SmugMug',
|
||||
icon: embedPhotoIcon,
|
||||
description: __( 'Embed SmugMug content.' ),
|
||||
patterns: [ /^https?:\/\/(.+\.)?smugmug\.com\/.*/i ],
|
||||
attributes: {
|
||||
providerNameSlug: 'smugmug',
|
||||
previewable: false,
|
||||
responsive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'speaker-deck',
|
||||
title: 'Speaker Deck',
|
||||
icon: embedContentIcon,
|
||||
description: __( 'Embed Speaker Deck content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?speakerdeck\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'speaker-deck', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'tiktok',
|
||||
title: 'TikTok',
|
||||
icon: embedVideoIcon,
|
||||
keywords: [ __( 'video' ) ],
|
||||
description: __( 'Embed a TikTok video.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?tiktok\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'tiktok', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'ted',
|
||||
title: 'TED',
|
||||
icon: embedVideoIcon,
|
||||
description: __( 'Embed a TED video.' ),
|
||||
patterns: [ /^https?:\/\/(www\.|embed\.)?ted\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'ted', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'tumblr',
|
||||
title: 'Tumblr',
|
||||
icon: embedTumblrIcon,
|
||||
keywords: [ __( 'social' ) ],
|
||||
description: __( 'Embed a Tumblr post.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?tumblr\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'tumblr', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'videopress',
|
||||
title: 'VideoPress',
|
||||
icon: embedVideoIcon,
|
||||
keywords: [ __( 'video' ) ],
|
||||
description: __( 'Embed a VideoPress video.' ),
|
||||
patterns: [ /^https?:\/\/videopress\.com\/.+/i ],
|
||||
attributes: { providerNameSlug: 'videopress', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'wordpress-tv',
|
||||
title: 'WordPress.tv',
|
||||
icon: embedVideoIcon,
|
||||
description: __( 'Embed a WordPress.tv video.' ),
|
||||
patterns: [ /^https?:\/\/wordpress\.tv\/.+/i ],
|
||||
attributes: { providerNameSlug: 'wordpress-tv', responsive: true },
|
||||
},
|
||||
{
|
||||
name: 'amazon-kindle',
|
||||
title: 'Amazon Kindle',
|
||||
icon: embedAmazonIcon,
|
||||
keywords: [ __( 'ebook' ) ],
|
||||
description: __( 'Embed Amazon Kindle content.' ),
|
||||
patterns: [
|
||||
/^https?:\/\/([a-z0-9-]+\.)?(amazon|amzn)(\.[a-z]{2,4})+\/.+/i,
|
||||
/^https?:\/\/(www\.)?(a\.co|z\.cn)\/.+/i,
|
||||
],
|
||||
attributes: { providerNameSlug: 'amazon-kindle' },
|
||||
},
|
||||
{
|
||||
name: 'pinterest',
|
||||
title: 'Pinterest',
|
||||
icon: embedPinterestIcon,
|
||||
keywords: [ __( 'social' ), __( 'bookmark' ) ],
|
||||
description: __( 'Embed Pinterest pins, boards, and profiles.' ),
|
||||
patterns: [
|
||||
/^https?:\/\/([a-z]{2}|www)\.pinterest\.com(\.(au|mx))?\/.*/i,
|
||||
],
|
||||
attributes: { providerNameSlug: 'pinterest' },
|
||||
},
|
||||
{
|
||||
name: 'wolfram-cloud',
|
||||
title: 'Wolfram',
|
||||
icon: embedWolframIcon,
|
||||
description: __( 'Embed Wolfram notebook content.' ),
|
||||
patterns: [ /^https?:\/\/(www\.)?wolframcloud\.com\/obj\/.+/i ],
|
||||
attributes: { providerNameSlug: 'wolfram-cloud', responsive: true },
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Add `isActive` function to all `embed` variations, if not defined.
|
||||
* `isActive` function is used to find a variation match from a created
|
||||
* Block by providing its attributes.
|
||||
*/
|
||||
variations.forEach( ( variation ) => {
|
||||
if ( variation.isActive ) return;
|
||||
variation.isActive = ( blockAttributes, variationAttributes ) =>
|
||||
blockAttributes.providerNameSlug ===
|
||||
variationAttributes.providerNameSlug;
|
||||
} );
|
||||
|
||||
export default variations;
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { useMergeRefs, useFocusableIframe } from '@wordpress/compose';
|
||||
import { useRef, useEffect, useMemo } from '@wordpress/element';
|
||||
|
||||
/** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */
|
||||
|
||||
const attributeMap = {
|
||||
class: 'className',
|
||||
frameborder: 'frameBorder',
|
||||
marginheight: 'marginHeight',
|
||||
marginwidth: 'marginWidth',
|
||||
};
|
||||
|
||||
export default function WpEmbedPreview( { html } ) {
|
||||
const ref = useRef();
|
||||
const props = useMemo( () => {
|
||||
const doc = new window.DOMParser().parseFromString( html, 'text/html' );
|
||||
const iframe = doc.querySelector( 'iframe' );
|
||||
const iframeProps = {};
|
||||
|
||||
if ( ! iframe ) return iframeProps;
|
||||
|
||||
Array.from( iframe.attributes ).forEach( ( { name, value } ) => {
|
||||
if ( name === 'style' ) return;
|
||||
iframeProps[ attributeMap[ name ] || name ] = value;
|
||||
} );
|
||||
|
||||
return iframeProps;
|
||||
}, [ html ] );
|
||||
|
||||
useEffect( () => {
|
||||
const { ownerDocument } = ref.current;
|
||||
const { defaultView } = ownerDocument;
|
||||
|
||||
/**
|
||||
* Checks for WordPress embed events signaling the height change when
|
||||
* iframe content loads or iframe's window is resized. The event is
|
||||
* sent from WordPress core via the window.postMessage API.
|
||||
*
|
||||
* References:
|
||||
* window.postMessage:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
|
||||
* WordPress core embed-template on load:
|
||||
* https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L143
|
||||
* WordPress core embed-template on resize:
|
||||
* https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L187
|
||||
*
|
||||
* @param {MessageEvent} event Message event.
|
||||
*/
|
||||
function resizeWPembeds( { data: { secret, message, value } = {} } ) {
|
||||
if ( message !== 'height' || secret !== props[ 'data-secret' ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current.height = value;
|
||||
}
|
||||
|
||||
defaultView.addEventListener( 'message', resizeWPembeds );
|
||||
return () => {
|
||||
defaultView.removeEventListener( 'message', resizeWPembeds );
|
||||
};
|
||||
}, [] );
|
||||
|
||||
return (
|
||||
<div className="wp-block-embed__wrapper">
|
||||
<iframe
|
||||
ref={ useMergeRefs( [ ref, useFocusableIframe() ] ) }
|
||||
title={ props.title }
|
||||
{ ...props }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { memo, useMemo } from '@wordpress/element';
|
||||
import { SandBox } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Checks for WordPress embed events signaling the height change when iframe
|
||||
* content loads or iframe's window is resized. The event is sent from
|
||||
* WordPress core via the window.postMessage API.
|
||||
*
|
||||
* References:
|
||||
* window.postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
|
||||
* WordPress core embed-template on load: https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L143
|
||||
* WordPress core embed-template on resize: https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L187
|
||||
*/
|
||||
const observeAndResizeJS = `
|
||||
( function() {
|
||||
if ( ! document.body || ! window.parent ) {
|
||||
return;
|
||||
}
|
||||
|
||||
function sendResize( { data: { secret, message, value } = {} } ) {
|
||||
if (
|
||||
[ secret, message, value ].some(
|
||||
( attribute ) => ! attribute
|
||||
) ||
|
||||
message !== 'height'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
document
|
||||
.querySelectorAll( 'iframe[data-secret="' + secret + '"' )
|
||||
.forEach( ( iframe ) => {
|
||||
if ( +iframe.height !== value ) {
|
||||
iframe.height = value;
|
||||
}
|
||||
} );
|
||||
|
||||
// The function postMessage is exposed by the react-native-webview library
|
||||
// to communicate between React Native and the WebView, in this case,
|
||||
// we use it for notifying resize changes.
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify( {
|
||||
action: 'resize',
|
||||
height: value,
|
||||
}));
|
||||
}
|
||||
|
||||
window.addEventListener( 'message', sendResize );
|
||||
} )();`;
|
||||
|
||||
function WpEmbedPreview( { html, ...rest } ) {
|
||||
const wpEmbedHtml = useMemo( () => {
|
||||
const doc = new window.DOMParser().parseFromString( html, 'text/html' );
|
||||
const iframe = doc.querySelector( 'iframe' );
|
||||
|
||||
if ( iframe ) {
|
||||
iframe.removeAttribute( 'style' );
|
||||
}
|
||||
|
||||
const blockQuote = doc.querySelector( 'blockquote' );
|
||||
|
||||
if ( blockQuote ) {
|
||||
blockQuote.innerHTML = '';
|
||||
}
|
||||
|
||||
return doc.body.innerHTML;
|
||||
}, [ html ] );
|
||||
|
||||
return (
|
||||
<SandBox
|
||||
customJS={ observeAndResizeJS }
|
||||
html={ wpEmbedHtml }
|
||||
{ ...rest }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo( WpEmbedPreview );
|
||||
Reference in New Issue
Block a user