From ad8bbf2c093545682da8b58e9d65ce04d888e9a6 Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Mon, 27 Feb 2023 20:00:38 +0200
Subject: [PATCH 1/8] [Inserter - Media tab]: Upload Openverse images when
inserted
---
.../inserter/media-tab/media-panel.js | 73 ++++++++++++++++++-
1 file changed, 71 insertions(+), 2 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-panel.js b/packages/block-editor/src/components/inserter/media-tab/media-panel.js
index 58ae7c49d27628..a42e8228ab34db 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-panel.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-panel.js
@@ -1,10 +1,13 @@
/**
* WordPress dependencies
*/
-import { useRef, useEffect } from '@wordpress/element';
+import { useRef, useEffect, useCallback } from '@wordpress/element';
import { Spinner, SearchControl } from '@wordpress/components';
import { focus } from '@wordpress/dom';
import { __ } from '@wordpress/i18n';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { store as noticesStore } from '@wordpress/notices';
+import { isBlobURL } from '@wordpress/blob';
/**
* Internal dependencies
@@ -13,8 +16,10 @@ import MediaList from './media-list';
import useDebouncedInput from '../hooks/use-debounced-input';
import { useMediaResults } from './hooks';
import InserterNoResults from '../no-results';
+import { store as blockEditorStore } from '../../../store';
const INITIAL_MEDIA_ITEMS_PER_PAGE = 10;
+const ALLOWED_MEDIA_TYPES = [ 'image' ];
export function MediaCategoryDialog( { rootClientId, onInsert, category } ) {
const container = useRef();
@@ -42,6 +47,70 @@ export function MediaCategoryPanel( { rootClientId, onInsert, category } ) {
per_page: !! debouncedSearch ? 20 : INITIAL_MEDIA_ITEMS_PER_PAGE,
search: debouncedSearch,
} );
+ const { createErrorNotice, createSuccessNotice } =
+ useDispatch( noticesStore );
+ const mediaUpload = useSelect(
+ ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
+ []
+ );
+ const onMediaInsert = useCallback(
+ ( block ) => {
+ const { id, url, caption } = block.attributes;
+ // Media item already exists in library, so just insert it.
+ if ( !! id ) {
+ onInsert( block );
+ return;
+ }
+ // Media item does not exist in library, so try to upload it.
+ // Fist fetch the image data. This may fail if the image host
+ // doesn't allow CORS with the domain.
+ // If that happens, we insert the image block using the external
+ // URL and let the user know about the implications of that.
+ window
+ .fetch( url )
+ .then( ( response ) => response.blob() )
+ .then( ( blob ) => {
+ mediaUpload( {
+ filesList: [ blob ],
+ additionalData: { caption },
+ onFileChange( [ img ] ) {
+ if ( isBlobURL( img.url ) ) {
+ return;
+ }
+ onInsert( {
+ ...block,
+ attributes: {
+ ...block.attributes,
+ id: img.id,
+ url: img.url,
+ },
+ } );
+ createSuccessNotice(
+ __( 'Image uploaded and inserted.' ),
+ {
+ type: 'snackbar',
+ }
+ );
+ },
+ allowedTypes: ALLOWED_MEDIA_TYPES,
+ onError( message ) {
+ createErrorNotice( message, { type: 'snackbar' } );
+ },
+ } );
+ } )
+ .catch( () => {
+ // TODO: should we insert it with an appropriate warning?
+ createErrorNotice(
+ 'The image cannot be uploaded to the media library. External images can be removed by the external provider without warning and could even have legal compliance issues',
+ {
+ type: 'snackbar',
+ }
+ );
+ onInsert( block );
+ } );
+ },
+ [ onInsert, mediaUpload, createErrorNotice, createSuccessNotice ]
+ );
const baseCssClass = 'block-editor-inserter__media-panel';
const searchLabel = category.labels.search_items || __( 'Search' );
return (
@@ -62,7 +131,7 @@ export function MediaCategoryPanel( { rootClientId, onInsert, category } ) {
{ ! isLoading && !! mediaList?.length && (
From 6213ab24645a9990fe03fa194e50472475597a74 Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 09:19:40 +0200
Subject: [PATCH 2/8] extract to separate hook
---
.../components/inserter/media-tab/hooks.js | 81 ++++++++++++++++++-
.../inserter/media-tab/media-panel.js | 74 +----------------
2 files changed, 82 insertions(+), 73 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/hooks.js b/packages/block-editor/src/components/inserter/media-tab/hooks.js
index d8e571dc242e05..95602f945cf725 100644
--- a/packages/block-editor/src/components/inserter/media-tab/hooks.js
+++ b/packages/block-editor/src/components/inserter/media-tab/hooks.js
@@ -1,14 +1,25 @@
/**
* WordPress dependencies
*/
-import { useEffect, useState, useRef, useMemo } from '@wordpress/element';
-import { useSelect } from '@wordpress/data';
+import {
+ useEffect,
+ useState,
+ useRef,
+ useMemo,
+ useCallback,
+} from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { store as noticesStore } from '@wordpress/notices';
+import { isBlobURL } from '@wordpress/blob';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../../store';
+const ALLOWED_MEDIA_TYPES = [ 'image' ];
+
/**
* Interface for inserter media requests.
*
@@ -189,3 +200,69 @@ export function useMediaCategories( rootClientId ) {
] );
return categories;
}
+
+export function useOnMediaInsert( onInsert ) {
+ const { createErrorNotice, createSuccessNotice } =
+ useDispatch( noticesStore );
+ const mediaUpload = useSelect(
+ ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
+ []
+ );
+ return useCallback(
+ ( block ) => {
+ const { id, url, caption } = block.attributes;
+ // Media item already exists in library, so just insert it.
+ if ( !! id ) {
+ onInsert( block );
+ return;
+ }
+ // Media item does not exist in library, so try to upload it.
+ // Fist fetch the image data. This may fail if the image host
+ // doesn't allow CORS with the domain.
+ // If this happens, we insert the image block using the external
+ // URL and let the user know about the possible implications.
+ window
+ .fetch( url )
+ .then( ( response ) => response.blob() )
+ .then( ( blob ) => {
+ mediaUpload( {
+ filesList: [ blob ],
+ additionalData: { caption },
+ onFileChange( [ img ] ) {
+ if ( isBlobURL( img.url ) ) {
+ return;
+ }
+ onInsert( {
+ ...block,
+ attributes: {
+ ...block.attributes,
+ id: img.id,
+ url: img.url,
+ },
+ } );
+ createSuccessNotice(
+ __( 'Image uploaded and inserted.' ),
+ {
+ type: 'snackbar',
+ }
+ );
+ },
+ allowedTypes: ALLOWED_MEDIA_TYPES,
+ onError( message ) {
+ createErrorNotice( message, { type: 'snackbar' } );
+ },
+ } );
+ } )
+ .catch( () => {
+ createErrorNotice(
+ __(
+ 'The image cannot be uploaded to the media library. External images can be removed by the external provider without warning and could even have legal compliance issues related to GDPR.'
+ ),
+ { type: 'snackbar' }
+ );
+ onInsert( block );
+ } );
+ },
+ [ onInsert, mediaUpload, createErrorNotice, createSuccessNotice ]
+ );
+}
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-panel.js b/packages/block-editor/src/components/inserter/media-tab/media-panel.js
index a42e8228ab34db..64f75e0bd98d19 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-panel.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-panel.js
@@ -1,25 +1,20 @@
/**
* WordPress dependencies
*/
-import { useRef, useEffect, useCallback } from '@wordpress/element';
+import { useRef, useEffect } from '@wordpress/element';
import { Spinner, SearchControl } from '@wordpress/components';
import { focus } from '@wordpress/dom';
import { __ } from '@wordpress/i18n';
-import { useSelect, useDispatch } from '@wordpress/data';
-import { store as noticesStore } from '@wordpress/notices';
-import { isBlobURL } from '@wordpress/blob';
/**
* Internal dependencies
*/
import MediaList from './media-list';
import useDebouncedInput from '../hooks/use-debounced-input';
-import { useMediaResults } from './hooks';
+import { useMediaResults, useOnMediaInsert } from './hooks';
import InserterNoResults from '../no-results';
-import { store as blockEditorStore } from '../../../store';
const INITIAL_MEDIA_ITEMS_PER_PAGE = 10;
-const ALLOWED_MEDIA_TYPES = [ 'image' ];
export function MediaCategoryDialog( { rootClientId, onInsert, category } ) {
const container = useRef();
@@ -47,70 +42,7 @@ export function MediaCategoryPanel( { rootClientId, onInsert, category } ) {
per_page: !! debouncedSearch ? 20 : INITIAL_MEDIA_ITEMS_PER_PAGE,
search: debouncedSearch,
} );
- const { createErrorNotice, createSuccessNotice } =
- useDispatch( noticesStore );
- const mediaUpload = useSelect(
- ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
- []
- );
- const onMediaInsert = useCallback(
- ( block ) => {
- const { id, url, caption } = block.attributes;
- // Media item already exists in library, so just insert it.
- if ( !! id ) {
- onInsert( block );
- return;
- }
- // Media item does not exist in library, so try to upload it.
- // Fist fetch the image data. This may fail if the image host
- // doesn't allow CORS with the domain.
- // If that happens, we insert the image block using the external
- // URL and let the user know about the implications of that.
- window
- .fetch( url )
- .then( ( response ) => response.blob() )
- .then( ( blob ) => {
- mediaUpload( {
- filesList: [ blob ],
- additionalData: { caption },
- onFileChange( [ img ] ) {
- if ( isBlobURL( img.url ) ) {
- return;
- }
- onInsert( {
- ...block,
- attributes: {
- ...block.attributes,
- id: img.id,
- url: img.url,
- },
- } );
- createSuccessNotice(
- __( 'Image uploaded and inserted.' ),
- {
- type: 'snackbar',
- }
- );
- },
- allowedTypes: ALLOWED_MEDIA_TYPES,
- onError( message ) {
- createErrorNotice( message, { type: 'snackbar' } );
- },
- } );
- } )
- .catch( () => {
- // TODO: should we insert it with an appropriate warning?
- createErrorNotice(
- 'The image cannot be uploaded to the media library. External images can be removed by the external provider without warning and could even have legal compliance issues',
- {
- type: 'snackbar',
- }
- );
- onInsert( block );
- } );
- },
- [ onInsert, mediaUpload, createErrorNotice, createSuccessNotice ]
- );
+ const onMediaInsert = useOnMediaInsert( onInsert );
const baseCssClass = 'block-editor-inserter__media-panel';
const searchLabel = category.labels.search_items || __( 'Search' );
return (
From 3ac74f20baa7ce340b31044663c8839e805c10f5 Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 09:57:12 +0200
Subject: [PATCH 3/8] Check all media categories against `allowedMimeTypes`
---
.../components/inserter/media-tab/hooks.js | 11 +-----
.../src/components/inserter/menu.js | 34 ++++++++-----------
2 files changed, 15 insertions(+), 30 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/hooks.js b/packages/block-editor/src/components/inserter/media-tab/hooks.js
index 95602f945cf725..b97c46335aec6a 100644
--- a/packages/block-editor/src/components/inserter/media-tab/hooks.js
+++ b/packages/block-editor/src/components/inserter/media-tab/hooks.js
@@ -109,13 +109,6 @@ function useInserterMediaCategories() {
) {
return false;
}
- // When a category has set `isExternalResource` to `true`, we
- // don't need to check for allowed mime types, as they are used
- // for restricting uploads for this media type and not for
- // inserting media from external sources.
- if ( category.isExternalResource ) {
- return true;
- }
return Object.values( allowedMimeTypes ).some( ( mimeType ) =>
mimeType.startsWith( `${ category.mediaType }/` )
);
@@ -242,9 +235,7 @@ export function useOnMediaInsert( onInsert ) {
} );
createSuccessNotice(
__( 'Image uploaded and inserted.' ),
- {
- type: 'snackbar',
- }
+ { type: 'snackbar' }
);
},
allowedTypes: ALLOWED_MEDIA_TYPES,
diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js
index 41491696e9010e..51026ec939dd5e 100644
--- a/packages/block-editor/src/components/inserter/menu.js
+++ b/packages/block-editor/src/components/inserter/menu.js
@@ -67,25 +67,19 @@ function InserterMenu(
insertionIndex: __experimentalInsertionIndex,
shouldFocusBlock,
} );
- const { showPatterns, inserterItems, enableOpenverseMediaCategory } =
- useSelect(
- ( select ) => {
- const {
- __experimentalGetAllowedPatterns,
- getInserterItems,
- getSettings,
- } = select( blockEditorStore );
- return {
- showPatterns: !! __experimentalGetAllowedPatterns(
- destinationRootClientId
- ).length,
- inserterItems: getInserterItems( destinationRootClientId ),
- enableOpenverseMediaCategory:
- getSettings().enableOpenverseMediaCategory,
- };
- },
- [ destinationRootClientId ]
- );
+ const { showPatterns, inserterItems } = useSelect(
+ ( select ) => {
+ const { __experimentalGetAllowedPatterns, getInserterItems } =
+ select( blockEditorStore );
+ return {
+ showPatterns: !! __experimentalGetAllowedPatterns(
+ destinationRootClientId
+ ).length,
+ inserterItems: getInserterItems( destinationRootClientId ),
+ };
+ },
+ [ destinationRootClientId ]
+ );
const hasReusableBlocks = useMemo( () => {
return inserterItems.some(
( { category } ) => category === 'reusable'
@@ -93,7 +87,7 @@ function InserterMenu(
}, [ inserterItems ] );
const mediaCategories = useMediaCategories( destinationRootClientId );
- const showMedia = !! mediaCategories.length || enableOpenverseMediaCategory;
+ const showMedia = !! mediaCategories.length;
const onInsert = useCallback(
( blocks, meta, shouldForceFocusBlock ) => {
From e9bb9dee125a228594ce3e361ba2af2ba4ce75a0 Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 11:18:38 +0200
Subject: [PATCH 4/8] add a spinner when is uploading image and extract Preview
in separate file
---
.../components/inserter/media-tab/hooks.js | 79 +------
.../inserter/media-tab/media-list.js | 125 +---------
.../inserter/media-tab/media-panel.js | 5 +-
.../inserter/media-tab/media-preview.js | 214 ++++++++++++++++++
.../src/components/inserter/style.scss | 11 +
5 files changed, 232 insertions(+), 202 deletions(-)
create mode 100644 packages/block-editor/src/components/inserter/media-tab/media-preview.js
diff --git a/packages/block-editor/src/components/inserter/media-tab/hooks.js b/packages/block-editor/src/components/inserter/media-tab/hooks.js
index b97c46335aec6a..2c7a6ae2983c6c 100644
--- a/packages/block-editor/src/components/inserter/media-tab/hooks.js
+++ b/packages/block-editor/src/components/inserter/media-tab/hooks.js
@@ -1,25 +1,14 @@
/**
* WordPress dependencies
*/
-import {
- useEffect,
- useState,
- useRef,
- useMemo,
- useCallback,
-} from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-import { useSelect, useDispatch } from '@wordpress/data';
-import { store as noticesStore } from '@wordpress/notices';
-import { isBlobURL } from '@wordpress/blob';
+import { useEffect, useState, useRef, useMemo } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../../store';
-const ALLOWED_MEDIA_TYPES = [ 'image' ];
-
/**
* Interface for inserter media requests.
*
@@ -193,67 +182,3 @@ export function useMediaCategories( rootClientId ) {
] );
return categories;
}
-
-export function useOnMediaInsert( onInsert ) {
- const { createErrorNotice, createSuccessNotice } =
- useDispatch( noticesStore );
- const mediaUpload = useSelect(
- ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
- []
- );
- return useCallback(
- ( block ) => {
- const { id, url, caption } = block.attributes;
- // Media item already exists in library, so just insert it.
- if ( !! id ) {
- onInsert( block );
- return;
- }
- // Media item does not exist in library, so try to upload it.
- // Fist fetch the image data. This may fail if the image host
- // doesn't allow CORS with the domain.
- // If this happens, we insert the image block using the external
- // URL and let the user know about the possible implications.
- window
- .fetch( url )
- .then( ( response ) => response.blob() )
- .then( ( blob ) => {
- mediaUpload( {
- filesList: [ blob ],
- additionalData: { caption },
- onFileChange( [ img ] ) {
- if ( isBlobURL( img.url ) ) {
- return;
- }
- onInsert( {
- ...block,
- attributes: {
- ...block.attributes,
- id: img.id,
- url: img.url,
- },
- } );
- createSuccessNotice(
- __( 'Image uploaded and inserted.' ),
- { type: 'snackbar' }
- );
- },
- allowedTypes: ALLOWED_MEDIA_TYPES,
- onError( message ) {
- createErrorNotice( message, { type: 'snackbar' } );
- },
- } );
- } )
- .catch( () => {
- createErrorNotice(
- __(
- 'The image cannot be uploaded to the media library. External images can be removed by the external provider without warning and could even have legal compliance issues related to GDPR.'
- ),
- { type: 'snackbar' }
- );
- onInsert( block );
- } );
- },
- [ onInsert, mediaUpload, createErrorNotice, createSuccessNotice ]
- );
-}
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-list.js b/packages/block-editor/src/components/inserter/media-tab/media-list.js
index 6eb316bad0d163..b745a54e25e9c0 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-list.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-list.js
@@ -1,129 +1,16 @@
-/**
- * External dependencies
- */
-import classnames from 'classnames';
-
/**
* WordPress dependencies
*/
import {
__unstableComposite as Composite,
__unstableUseCompositeState as useCompositeState,
- __unstableCompositeItem as CompositeItem,
- Tooltip,
- DropdownMenu,
- MenuGroup,
- MenuItem,
} from '@wordpress/components';
-import { __, sprintf } from '@wordpress/i18n';
-import { useMemo, useCallback, useState } from '@wordpress/element';
-import { cloneBlock } from '@wordpress/blocks';
-import { moreVertical, external } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
-import InserterDraggableBlocks from '../../inserter-draggable-blocks';
-import { getBlockAndPreviewFromMedia } from './utils';
-
-const MAXIMUM_TITLE_LENGTH = 25;
-const MEDIA_OPTIONS_POPOVER_PROPS = {
- position: 'bottom left',
- className:
- 'block-editor-inserter__media-list__item-preview-options__popover',
-};
-
-function MediaPreviewOptions( { category, media } ) {
- if ( ! category.getReportUrl ) {
- return null;
- }
- const reportUrl = category.getReportUrl( media );
- return (
-
- { () => (
-
-
- window.open( reportUrl, '_blank' ).focus()
- }
- icon={ external }
- >
- { sprintf(
- /* translators: %s: The media type to report e.g: "image", "video", "audio" */
- __( 'Report %s' ),
- category.mediaType
- ) }
-
-
- ) }
-
- );
-}
-
-function MediaPreview( { media, onClick, composite, category } ) {
- const [ isHovered, setIsHovered ] = useState( false );
- const [ block, preview ] = useMemo(
- () => getBlockAndPreviewFromMedia( media, category.mediaType ),
- [ media, category.mediaType ]
- );
- const title = media.title?.rendered || media.title;
- let truncatedTitle;
- if ( title.length > MAXIMUM_TITLE_LENGTH ) {
- const omission = '...';
- truncatedTitle =
- title.slice( 0, MAXIMUM_TITLE_LENGTH - omission.length ) + omission;
- }
- const onMouseEnter = useCallback( () => setIsHovered( true ), [] );
- const onMouseLeave = useCallback( () => setIsHovered( false ), [] );
- return (
-
- { ( { draggable, onDragStart, onDragEnd } ) => (
-
-
- { /* Adding `is-hovered` class to the wrapper element is needed
- because the options Popover is rendered outside of this node. */ }
-
-
onClick( block ) }
- aria-label={ title }
- >
-
- { preview }
-
-
-
-
-
-
- ) }
-
- );
-}
+import { MediaPreview } from './media-preview';
function MediaList( {
mediaList,
@@ -132,12 +19,6 @@ function MediaList( {
label = __( 'Media List' ),
} ) {
const composite = useCompositeState();
- const onPreviewClick = useCallback(
- ( block ) => {
- onClick( cloneBlock( block ) );
- },
- [ onClick ]
- );
return (
) ) }
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-panel.js b/packages/block-editor/src/components/inserter/media-tab/media-panel.js
index 64f75e0bd98d19..58ae7c49d27628 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-panel.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-panel.js
@@ -11,7 +11,7 @@ import { __ } from '@wordpress/i18n';
*/
import MediaList from './media-list';
import useDebouncedInput from '../hooks/use-debounced-input';
-import { useMediaResults, useOnMediaInsert } from './hooks';
+import { useMediaResults } from './hooks';
import InserterNoResults from '../no-results';
const INITIAL_MEDIA_ITEMS_PER_PAGE = 10;
@@ -42,7 +42,6 @@ export function MediaCategoryPanel( { rootClientId, onInsert, category } ) {
per_page: !! debouncedSearch ? 20 : INITIAL_MEDIA_ITEMS_PER_PAGE,
search: debouncedSearch,
} );
- const onMediaInsert = useOnMediaInsert( onInsert );
const baseCssClass = 'block-editor-inserter__media-panel';
const searchLabel = category.labels.search_items || __( 'Search' );
return (
@@ -63,7 +62,7 @@ export function MediaCategoryPanel( { rootClientId, onInsert, category } ) {
{ ! isLoading && !! mediaList?.length && (
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
new file mode 100644
index 00000000000000..13b9f538c3b173
--- /dev/null
+++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
@@ -0,0 +1,214 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import {
+ __unstableCompositeItem as CompositeItem,
+ Tooltip,
+ DropdownMenu,
+ MenuGroup,
+ MenuItem,
+ Spinner,
+} from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+import { useMemo, useCallback, useState } from '@wordpress/element';
+import { cloneBlock } from '@wordpress/blocks';
+import { moreVertical, external } from '@wordpress/icons';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { store as noticesStore } from '@wordpress/notices';
+import { isBlobURL } from '@wordpress/blob';
+
+/**
+ * Internal dependencies
+ */
+import InserterDraggableBlocks from '../../inserter-draggable-blocks';
+import { getBlockAndPreviewFromMedia } from './utils';
+import { store as blockEditorStore } from '../../../store';
+
+const ALLOWED_MEDIA_TYPES = [ 'image' ];
+const MAXIMUM_TITLE_LENGTH = 25;
+const MEDIA_OPTIONS_POPOVER_PROPS = {
+ position: 'bottom left',
+ className:
+ 'block-editor-inserter__media-list__item-preview-options__popover',
+};
+
+function MediaPreviewOptions( { category, media } ) {
+ if ( ! category.getReportUrl ) {
+ return null;
+ }
+ const reportUrl = category.getReportUrl( media );
+ return (
+
+ { () => (
+
+
+ window.open( reportUrl, '_blank' ).focus()
+ }
+ icon={ external }
+ >
+ { sprintf(
+ /* translators: %s: The media type to report e.g: "image", "video", "audio" */
+ __( 'Report %s' ),
+ category.mediaType
+ ) }
+
+
+ ) }
+
+ );
+}
+
+export function MediaPreview( { media, onClick, composite, category } ) {
+ const [ isHovered, setIsHovered ] = useState( false );
+ const [ isInserting, setIsInserting ] = useState( false );
+ const [ block, preview ] = useMemo(
+ () => getBlockAndPreviewFromMedia( media, category.mediaType ),
+ [ media, category.mediaType ]
+ );
+ const { createErrorNotice, createSuccessNotice } =
+ useDispatch( noticesStore );
+ const mediaUpload = useSelect(
+ ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
+ []
+ );
+ const onMediaInsert = useCallback(
+ ( previewBlock ) => {
+ // Prevent multiple uploads when we're in the process of inserting.
+ if ( isInserting ) {
+ return;
+ }
+ const clonedBlock = cloneBlock( previewBlock );
+ const { id, url, caption } = clonedBlock.attributes;
+ // Media item already exists in library, so just insert it.
+ if ( !! id ) {
+ onClick( clonedBlock );
+ return;
+ }
+ setIsInserting( true );
+ // Media item does not exist in library, so try to upload it.
+ // Fist fetch the image data. This may fail if the image host
+ // doesn't allow CORS with the domain.
+ // If this happens, we insert the image block using the external
+ // URL and let the user know about the possible implications.
+ window
+ .fetch( url )
+ .then( ( response ) => response.blob() )
+ .then( ( blob ) => {
+ mediaUpload( {
+ filesList: [ blob ],
+ additionalData: { caption },
+ onFileChange( [ img ] ) {
+ if ( isBlobURL( img.url ) ) {
+ return;
+ }
+ onClick( {
+ ...clonedBlock,
+ attributes: {
+ ...clonedBlock.attributes,
+ id: img.id,
+ url: img.url,
+ },
+ } );
+ createSuccessNotice(
+ __( 'Image uploaded and inserted.' ),
+ { type: 'snackbar' }
+ );
+ setIsInserting( false );
+ },
+ allowedTypes: ALLOWED_MEDIA_TYPES,
+ onError( message ) {
+ createErrorNotice( message, { type: 'snackbar' } );
+ setIsInserting( false );
+ },
+ } );
+ } )
+ .catch( () => {
+ createErrorNotice(
+ __(
+ 'The image cannot be uploaded to the media library. External images can be removed by the external provider without warning and could even have legal compliance issues related to GDPR.'
+ ),
+ { type: 'snackbar' }
+ );
+ onClick( clonedBlock );
+ setIsInserting( false );
+ } );
+ },
+ [
+ isInserting,
+ onClick,
+ mediaUpload,
+ createErrorNotice,
+ createSuccessNotice,
+ ]
+ );
+ const title = media.title?.rendered || media.title;
+ let truncatedTitle;
+ if ( title.length > MAXIMUM_TITLE_LENGTH ) {
+ const omission = '...';
+ truncatedTitle =
+ title.slice( 0, MAXIMUM_TITLE_LENGTH - omission.length ) + omission;
+ }
+ const onMouseEnter = useCallback( () => setIsHovered( true ), [] );
+ const onMouseLeave = useCallback( () => setIsHovered( false ), [] );
+ return (
+
+ { ( { draggable, onDragStart, onDragEnd } ) => (
+
+
+ { /* Adding `is-hovered` class to the wrapper element is needed
+ because the options Popover is rendered outside of this node. */ }
+
+
onMediaInsert( block ) }
+ aria-label={ title }
+ >
+
+ { preview }
+ { isInserting && (
+
+
+
+ ) }
+
+
+ { ! isInserting && (
+
+ ) }
+
+
+
+ ) }
+
+ );
+}
diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss
index 18ead7dbc484fa..204f8914525632 100644
--- a/packages/block-editor/src/components/inserter/style.scss
+++ b/packages/block-editor/src/components/inserter/style.scss
@@ -660,6 +660,17 @@ $block-inserter-tabs-height: 44px;
margin: 0 auto;
max-width: 100%;
}
+
+ .block-editor-inserter__media-list__item-preview-spinner {
+ display: flex;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ justify-content: center;
+ background: rgba($white, 0.7);
+ align-items: center;
+ pointer-events: none;
+ }
}
&:focus .block-editor-inserter__media-list__item-preview {
From cfe1680ea8da779dce194cd8247ec5645fc8224f Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 11:38:41 +0200
Subject: [PATCH 5/8] Add extra safeguard agains category fetching request
---
.../src/components/inserter/media-tab/hooks.js | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/hooks.js b/packages/block-editor/src/components/inserter/media-tab/hooks.js
index 2c7a6ae2983c6c..0822e2bf67e367 100644
--- a/packages/block-editor/src/components/inserter/media-tab/hooks.js
+++ b/packages/block-editor/src/components/inserter/media-tab/hooks.js
@@ -149,7 +149,15 @@ export function useMediaCategories( rootClientId ) {
if ( category.isExternalResource ) {
return [ category.name, true ];
}
- const results = await category.fetch( { per_page: 1 } );
+ let results = [];
+ try {
+ results = await category.fetch( {
+ per_page: 1,
+ } );
+ } catch ( e ) {
+ // If the request fails, we shallow the error and just don't show
+ // the category, in order to not break the media tab.
+ }
return [ category.name, !! results.length ];
} )
)
From fa608192d50052a3395f7448262d770ce90e6059 Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 13:44:09 +0200
Subject: [PATCH 6/8] add modal if the image fails to upload
---
.../inserter/media-tab/media-preview.js | 157 ++++++++++++------
.../src/components/inserter/style.scss | 11 ++
2 files changed, 116 insertions(+), 52 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
index 13b9f538c3b173..a79712c6158010 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
@@ -13,6 +13,11 @@ import {
MenuGroup,
MenuItem,
Spinner,
+ Modal,
+ Flex,
+ FlexItem,
+ Button,
+ __experimentalVStack as VStack,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { useMemo, useCallback, useState } from '@wordpress/element';
@@ -69,7 +74,47 @@ function MediaPreviewOptions( { category, media } ) {
);
}
+function InsertExternalImageModal( { onClose, onClick } ) {
+ return (
+
+
+
+ { __(
+ 'This image cannot be uploaded to your Media Library, but it can still be inserted as an external image.'
+ ) }
+
+
+ { __(
+ 'External images can be removed by the external provider without warning and could even have legal compliance issues related to GDPR.'
+ ) }
+
+
+
+
+
+ { __( 'Cancel' ) }
+
+
+
+
+ { __( 'Insert' ) }
+
+
+
+
+ );
+}
+
export function MediaPreview( { media, onClick, composite, category } ) {
+ const [ showModal, setShowModal ] = useState( false );
const [ isHovered, setIsHovered ] = useState( false );
const [ isInserting, setIsInserting ] = useState( false );
const [ block, preview ] = useMemo(
@@ -134,13 +179,7 @@ export function MediaPreview( { media, onClick, composite, category } ) {
} );
} )
.catch( () => {
- createErrorNotice(
- __(
- 'The image cannot be uploaded to the media library. External images can be removed by the external provider without warning and could even have legal compliance issues related to GDPR.'
- ),
- { type: 'snackbar' }
- );
- onClick( clonedBlock );
+ setShowModal( true );
setIsInserting( false );
} );
},
@@ -162,53 +201,67 @@ export function MediaPreview( { media, onClick, composite, category } ) {
const onMouseEnter = useCallback( () => setIsHovered( true ), [] );
const onMouseLeave = useCallback( () => setIsHovered( false ), [] );
return (
-
- { ( { draggable, onDragStart, onDragEnd } ) => (
-
-
- { /* Adding `is-hovered` class to the wrapper element is needed
+ <>
+
+ { ( { draggable, onDragStart, onDragEnd } ) => (
+
+
+ { /* Adding `is-hovered` class to the wrapper element is needed
because the options Popover is rendered outside of this node. */ }
-
-
onMediaInsert( block ) }
- aria-label={ title }
+
-
- { preview }
- { isInserting && (
-
-
-
- ) }
-
-
- { ! isInserting && (
-
- ) }
-
-
-
+ onMediaInsert( block ) }
+ aria-label={ title }
+ >
+
+ { preview }
+ { isInserting && (
+
+
+
+ ) }
+
+
+ { ! isInserting && (
+
+ ) }
+
+
+
+ ) }
+
+ { showModal && (
+ setShowModal( false ) }
+ onClick={ () => {
+ onClick( cloneBlock( block ) );
+ createSuccessNotice( __( 'Image inserted.' ), {
+ type: 'snackbar',
+ } );
+ setShowModal( false );
+ } }
+ />
) }
-
+ >
);
}
diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss
index 204f8914525632..fa0778af6d53f0 100644
--- a/packages/block-editor/src/components/inserter/style.scss
+++ b/packages/block-editor/src/components/inserter/style.scss
@@ -697,3 +697,14 @@ $block-inserter-tabs-height: 44px;
height: 100%;
}
}
+
+
+.block-editor-inserter-media-tab-media-preview-inserter-external-image-modal {
+ @include break-small() {
+ max-width: $break-mobile;
+ }
+
+ p {
+ margin: 0;
+ }
+}
From 15c2210ceb852829f776feb51e49fb3cbc5f15f4 Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 14:20:01 +0200
Subject: [PATCH 7/8] rename some props
---
.../inserter/media-tab/media-preview.js | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
index a79712c6158010..4851dcf123cc1b 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
@@ -74,7 +74,7 @@ function MediaPreviewOptions( { category, media } ) {
);
}
-function InsertExternalImageModal( { onClose, onClick } ) {
+function InsertExternalImageModal( { onClose, onSubmit } ) {
return (
-
+
{ __( 'Insert' ) }
@@ -114,7 +114,8 @@ function InsertExternalImageModal( { onClose, onClick } ) {
}
export function MediaPreview( { media, onClick, composite, category } ) {
- const [ showModal, setShowModal ] = useState( false );
+ const [ showExternalUploadModal, setShowExternalUploadModal ] =
+ useState( false );
const [ isHovered, setIsHovered ] = useState( false );
const [ isInserting, setIsInserting ] = useState( false );
const [ block, preview ] = useMemo(
@@ -179,7 +180,7 @@ export function MediaPreview( { media, onClick, composite, category } ) {
} );
} )
.catch( () => {
- setShowModal( true );
+ setShowExternalUploadModal( true );
setIsInserting( false );
} );
},
@@ -250,15 +251,15 @@ export function MediaPreview( { media, onClick, composite, category } ) {
) }
- { showModal && (
+ { showExternalUploadModal && (
setShowModal( false ) }
- onClick={ () => {
+ onClose={ () => setShowExternalUploadModal( false ) }
+ onSubmit={ () => {
onClick( cloneBlock( block ) );
createSuccessNotice( __( 'Image inserted.' ), {
type: 'snackbar',
} );
- setShowModal( false );
+ setShowExternalUploadModal( false );
} }
/>
) }
From 7784d6b0ac4e19ae0a4e8680fd25570106a7155a Mon Sep 17 00:00:00 2001
From: ntsekouras
Date: Tue, 28 Feb 2023 14:59:42 +0200
Subject: [PATCH 8/8] update copy
---
.../src/components/inserter/media-tab/media-preview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
index 4851dcf123cc1b..88648bf96531b6 100644
--- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js
+++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js
@@ -89,7 +89,7 @@ function InsertExternalImageModal( { onClose, onSubmit } ) {
{ __(
- 'External images can be removed by the external provider without warning and could even have legal compliance issues related to GDPR.'
+ 'External images can be removed by the external provider without warning and could even have legal compliance issues related to privacy legislation.'
) }