Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2e6a32c
[Inserter]: Try Openverse integration
ntsekouras Dec 1, 2022
5a35da4
revamp media categories handling for extensibility
ntsekouras Dec 7, 2022
6062eba
adjust hover preview styles
ntsekouras Dec 7, 2022
6f31afd
map response media items to interface
ntsekouras Dec 8, 2022
5786e2b
sync site editor media categories
ntsekouras Dec 8, 2022
42181d1
create Openverse caption/attribution
ntsekouras Dec 15, 2022
109ffff
use sprintf
ntsekouras Dec 16, 2022
e36c9e2
add description field in media categories
ntsekouras Dec 19, 2022
d7af988
address feedback
ntsekouras Dec 20, 2022
341e84c
filter media categories with `allowedMimeTypes`
ntsekouras Dec 20, 2022
3051e09
add search label, trim tooltip titles and fix typo
ntsekouras Dec 22, 2022
c840359
fix list items styles on hover/active
ntsekouras Dec 22, 2022
25ec141
shorten attribution/caption
ntsekouras Dec 23, 2022
c9034e0
use smaller images for previews
ntsekouras Dec 23, 2022
7ca77a4
remove `File:` from Openverse title if has one - client side
ntsekouras Dec 23, 2022
91f0348
feedback
ntsekouras Jan 5, 2023
7d78f28
add fallback media src + exclude more sources + remove description
ntsekouras Jan 13, 2023
135beaa
add report UI + API
ntsekouras Jan 13, 2023
812210b
address feedback
ntsekouras Jan 16, 2023
b3dfcdc
add custom user-agent header in Openverse requests
ntsekouras Jan 17, 2023
65cf7d9
Update packages/block-editor/src/components/inserter/media-tab/hooks.js
ntsekouras Jan 18, 2023
fee64f2
`InserterMediaCategory` interface and feedback
ntsekouras Jan 19, 2023
01f91ef
translatable Openverse labels
ntsekouras Jan 20, 2023
71cf78b
Apply suggestions from code review
ntsekouras Jan 20, 2023
e30fc38
feedback part 1
ntsekouras Jan 20, 2023
0b8a0fe
update API to have top level `labels` object
ntsekouras Jan 20, 2023
b13e611
change caption translation handling
ntsekouras Jan 20, 2023
ce6d0be
More changes to caption generation:
mcsf Jan 23, 2023
1759ab5
handle non existing creator
ntsekouras Jan 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/compat/wordpress-6.2/edit-form-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
function gutenberg_preload_paths_6_2( $preload_paths, $context ) {
// Preload initial media requests that are needed to conditionally display the media tab in the inserter.
foreach ( array( 'image', 'video', 'audio' ) as $media_type ) {
$preload_paths[] = "wp/v2/media?context=view&per_page=1&_fields=id&media_type={$media_type}";
$preload_paths[] = "wp/v2/media?context=edit&per_page=1&orderBy=date&media_type={$media_type}";
}

return $preload_paths;
}
add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_preload_paths_6_2', 10, 2 );
223 changes: 158 additions & 65 deletions packages/block-editor/src/components/inserter/media-tab/hooks.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,182 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import { useEffect, useState, useRef, useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../../store';

export function useMediaResults( options = {} ) {
const [ results, setResults ] = useState();
const settings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
);
/**
* Interface for inserter media requests.
*
* @typedef {Object} InserterMediaRequest
* @property {number} per_page How many items to fetch per page.
* @property {string} search The search term to use for filtering the results.
*/

/**
* Interface for inserter media responses. Any media resource should
* map their response to this interface, in order to create the core
* WordPress media blocks (image, video, audio).
*
* @typedef {Object} InserterMediaItem
* @property {string} title The title of the media item.
* @property {string} url The source url of the media item.
* @property {string} [previewUrl] The preview source url of the media item to display in the media list.
* @property {number} [id] The WordPress id of the media item.
* @property {number|string} [sourceId] The id of the media item from external source.
* @property {string} [alt] The alt text of the media item.
* @property {string} [caption] The caption of the media item.
*/

/**
* Fetches media items based on the provided category.
* Each media category is responsible for providing a `fetch` function.
*
* @param {Object} category The media category to fetch results for.
* @param {InserterMediaRequest} query The query args to use for the request.
* @return {InserterMediaItem[]} The media results.
*/
export function useMediaResults( category, query = {} ) {
const [ mediaList, setMediaList ] = useState();
const [ isLoading, setIsLoading ] = useState( false );
// We need to keep track of the last request made because
// multiple request can be fired without knowing the order
// of resolution, and we need to ensure we are showing
// the results of the last request.
// In the future we could use AbortController to cancel previous
// requests, but we don't for now as it involves adding support
// for this to `core-data` package.
const lastRequest = useRef();
useEffect( () => {
( async () => {
setResults();
const _media = await settings?.__unstableFetchMedia( options );
if ( _media ) setResults( _media );
const key = JSON.stringify( {
category: category.name,
...query,
} );
lastRequest.current = key;
setIsLoading( true );
setMediaList( [] ); // Empty the previous results.
const _media = await category.fetch?.( query );
if ( key === lastRequest.current ) {
setMediaList( _media );
setIsLoading( false );
}
} )();
}, Object.values( options ) );
return results;
}, [ category.name, ...Object.values( query ) ] );
return { mediaList, isLoading };
}

function useInserterMediaCategories() {
const { inserterMediaCategories, allowedMimeTypes } = useSelect(
( select ) => {
const { getSettings } = select( blockEditorStore );
const {
__unstableInserterMediaCategories,
allowedMimeTypes: _allowedMimeTypes,
} = getSettings();
return {
inserterMediaCategories: __unstableInserterMediaCategories,
allowedMimeTypes: _allowedMimeTypes,
};
},
[]
);
// The allowed `mime_types` can be altered by `upload_mimes` filter and restrict
// some of them. In this case we shouldn't add the category to the available media
// categories list in the inserter.
const allowedCategories = useMemo( () => {
if ( ! inserterMediaCategories || ! allowedMimeTypes ) {
return;
}
return inserterMediaCategories.filter( ( category ) => {
// 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 }/` )
);
} );
}, [ inserterMediaCategories, allowedMimeTypes ] );
return allowedCategories;
}

const MEDIA_CATEGORIES = [
{ label: __( 'Images' ), name: 'images', mediaType: 'image' },
{ label: __( 'Videos' ), name: 'videos', mediaType: 'video' },
{ label: __( 'Audio' ), name: 'audio', mediaType: 'audio' },
];
export function useMediaCategories( rootClientId ) {
const [ categories, setCategories ] = useState( [] );
const { canInsertImage, canInsertVideo, canInsertAudio, fetchMedia } =
useSelect(
( select ) => {
const { canInsertBlockType, getSettings } =
select( blockEditorStore );
return {
fetchMedia: getSettings().__unstableFetchMedia,
canInsertImage: canInsertBlockType(
'core/image',
rootClientId
),
canInsertVideo: canInsertBlockType(
'core/video',
rootClientId
),
canInsertAudio: canInsertBlockType(
'core/audio',
rootClientId
),
};
},
[ rootClientId ]
);
const { canInsertImage, canInsertVideo, canInsertAudio } = useSelect(
( select ) => {
const { canInsertBlockType } = select( blockEditorStore );
return {
canInsertImage: canInsertBlockType(
'core/image',
rootClientId
),
canInsertVideo: canInsertBlockType(
'core/video',
rootClientId
),
canInsertAudio: canInsertBlockType(
'core/audio',
rootClientId
),
};
},
[ rootClientId ]
);
const inserterMediaCategories = useInserterMediaCategories();
useEffect( () => {
( async () => {
// If `__unstableFetchMedia` is not defined in block
// editor settings, do not set any media categories.
if ( ! fetchMedia ) return;
const query = {
context: 'view',
per_page: 1,
_fields: [ 'id' ],
};
const [ image, video, audio ] = await Promise.allSettled( [
fetchMedia( { ...query, media_type: 'image' } ),
fetchMedia( { ...query, media_type: 'video' } ),
fetchMedia( { ...query, media_type: 'audio' } ),
] );
// The `value` property is only present if the promise's status is "fulfilled".
const showImage = canInsertImage && !! image.value?.length;
const showVideo = canInsertVideo && !! video.value?.length;
const showAudio = canInsertAudio && !! audio.value?.length;
setCategories(
MEDIA_CATEGORIES.filter(
( { mediaType } ) =>
( mediaType === 'image' && showImage ) ||
( mediaType === 'video' && showVideo ) ||
( mediaType === 'audio' && showAudio )
const _categories = [];
// If `__unstableInserterMediaCategories` is not defined in
// block editor settings, do not show any media categories.
if ( ! inserterMediaCategories ) {
return;
}
// Loop through categories to check if they have at least one media item.
const categoriesHaveMedia = new Map(
await Promise.all(
inserterMediaCategories.map( async ( category ) => {
// Some sources are external and we don't need to make a request.
if ( category.isExternalResource ) {
return [ category.name, true ];
}
const results = await category.fetch( { per_page: 1 } );
return [ category.name, !! results.length ];
} )
)
);
// We need to filter out categories that don't have any media items or
// whose corresponding block type is not allowed to be inserted, based
// on the category's `mediaType`.
const canInsertMediaType = {
image: canInsertImage,
video: canInsertVideo,
audio: canInsertAudio,
};
inserterMediaCategories.forEach( ( category ) => {
if (
canInsertMediaType[ category.mediaType ] &&
categoriesHaveMedia.get( category.name )
) {
_categories.push( category );
}
} );
if ( !! _categories.length ) {
setCategories( _categories );
}
} )();
}, [ canInsertImage, canInsertVideo, canInsertAudio, fetchMedia ] );
}, [
canInsertImage,
canInsertVideo,
canInsertAudio,
inserterMediaCategories,
] );
return categories;
}
Loading