Skip to content

Commit

Permalink
Add new registerInserterMediaCategory API to make media categories …
Browse files Browse the repository at this point in the history
…extensible (#51542)

* Add new `registerInserterMediaCategory` API to make media categories extensible

* make `registerInserterMediaCategory` an action

* add tests

* add docs
  • Loading branch information
ntsekouras authored Jun 23, 2023
1 parent ed02496 commit 15e5833
Show file tree
Hide file tree
Showing 7 changed files with 409 additions and 66 deletions.
102 changes: 102 additions & 0 deletions docs/reference-guides/data/data-core-block-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,108 @@ _Returns_
- `Object`: Action object.
### registerInserterMediaCategory
Registers a new inserter media category. Once registered, the media category is available in the inserter's media tab.
The following interfaces are used:
_Type Definition_
- _InserterMediaRequest_ `Object`: Interface for inserter media requests.
_Properties_
- _per_page_ `number`: How many items to fetch per page.
- _search_ `string`: The search term to use for filtering the results.
_Type Definition_
- _InserterMediaItem_ `Object`: 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).
_Properties_
- _title_ `string`: The title of the media item.
- _url_ \`string: The source url of the media item.
- _previewUrl_ `[string]`: The preview source url of the media item to display in the media list.
- _id_ `[number]`: The WordPress id of the media item.
- _sourceId_ `[number|string]`: The id of the media item from external source.
- _alt_ `[string]`: The alt text of the media item.
- _caption_ `[string]`: The caption of the media item.
_Usage_
```js
wp.data.dispatch( 'core/block-editor' ).registerInserterMediaCategory( {
name: 'openverse',
labels: {
name: 'Openverse',
search_items: 'Search Openverse',
},
mediaType: 'image',
async fetch( query = {} ) {
const defaultArgs = {
mature: false,
excluded_source: 'flickr,inaturalist,wikimedia',
license: 'pdm,cc0',
};
const finalQuery = { ...query, ...defaultArgs };
// Sometimes you might need to map the supported request params according to `InserterMediaRequest`.
// interface. In this example the `search` query param is named `q`.
const mapFromInserterMediaRequest = {
per_page: 'page_size',
search: 'q',
};
const url = new URL( 'https://api.openverse.engineering/v1/images/' );
Object.entries( finalQuery ).forEach( ( [ key, value ] ) => {
const queryKey = mapFromInserterMediaRequest[ key ] || key;
url.searchParams.set( queryKey, value );
} );
const response = await window.fetch( url, {
headers: {
'User-Agent': 'WordPress/inserter-media-fetch',
},
} );
const jsonResponse = await response.json();
const results = jsonResponse.results;
return results.map( ( result ) => ( {
...result,
// If your response result includes an `id` prop that you want to access later, it should
// be mapped to `InserterMediaItem`'s `sourceId` prop. This can be useful if you provide
// a report URL getter.
// Additionally you should always clear the `id` value of your response results because
// it is used to identify WordPress media items.
sourceId: result.id,
id: undefined,
caption: result.caption,
previewUrl: result.thumbnail,
} ) );
},
getReportUrl: ( { sourceId } ) =>
`https://wordpress.org/openverse/image/${ sourceId }/report/`,
isExternalResource: true,
} );
```
_Parameters_
- _category_ `InserterMediaCategory`: The inserter media category to register.
_Type Definition_
- _InserterMediaCategory_ `Object`: Interface for inserter media category.
_Properties_
- _name_ `string`: The name of the media category, that should be unique among all media categories.
- _labels_ `Object`: Labels for the media category.
- _labels.name_ `string`: General name of the media category. It's used in the inserter media items list.
- _labels.search_items_ `[string]`: Label for searching items. Default is ‘Search Posts’ / ‘Search Pages’.
- _mediaType_ `('image'|'audio'|'video')`: The media type of the media category.
- _fetch_ `(InserterMediaRequest) => Promise<InserterMediaItem[]>`: The function to fetch media items for the category.
- _getReportUrl_ `[(InserterMediaItem) => string]`: If the media category supports reporting media items, this function should return the report url for the media item. It accepts the `InserterMediaItem` as an argument.
- _isExternalResource_ `[boolean]`: If the media category is an external resource, this should be set to true. This is used to avoid making a request to the external resource when the user
### removeBlock
Returns an action object used in signalling that the block with the specified client ID is to be removed.
Expand Down
24 changes: 2 additions & 22 deletions packages/block-editor/src/components/inserter/media-tab/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,8 @@ import { useSelect } from '@wordpress/data';
*/
import { store as blockEditorStore } from '../../../store';

/**
* 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.
*/
/** @typedef {import('./api').InserterMediaRequest} InserterMediaRequest */
/** @typedef {import('./api').InserterMediaItem} InserterMediaItem */

/**
* Fetches media items based on the provided category.
Expand Down
191 changes: 191 additions & 0 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint no-console: [ 'error', { allow: [ 'error', 'warn' ] } ] */
/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -1692,3 +1693,193 @@ export function __unstableSetTemporarilyEditingAsBlocks(
temporarilyEditingAsBlocks,
};
}

/**
* 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.
*/

/**
* Registers a new inserter media category. Once registered, the media category is
* available in the inserter's media tab.
*
* The following interfaces are used:
*
* _Type Definition_
*
* - _InserterMediaRequest_ `Object`: Interface for inserter media requests.
*
* _Properties_
*
* - _per_page_ `number`: How many items to fetch per page.
* - _search_ `string`: The search term to use for filtering the results.
*
* _Type Definition_
*
* - _InserterMediaItem_ `Object`: 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).
*
* _Properties_
*
* - _title_ `string`: The title of the media item.
* - _url_ `string: The source url of the media item.
* - _previewUrl_ `[string]`: The preview source url of the media item to display in the media list.
* - _id_ `[number]`: The WordPress id of the media item.
* - _sourceId_ `[number|string]`: The id of the media item from external source.
* - _alt_ `[string]`: The alt text of the media item.
* - _caption_ `[string]`: The caption of the media item.
*
* @param {InserterMediaCategory} category The inserter media category to register.
*
* @example
* ```js
*
* wp.data.dispatch('core/block-editor').registerInserterMediaCategory( {
* name: 'openverse',
* labels: {
* name: 'Openverse',
* search_items: 'Search Openverse',
* },
* mediaType: 'image',
* async fetch( query = {} ) {
* const defaultArgs = {
* mature: false,
* excluded_source: 'flickr,inaturalist,wikimedia',
* license: 'pdm,cc0',
* };
* const finalQuery = { ...query, ...defaultArgs };
* // Sometimes you might need to map the supported request params according to `InserterMediaRequest`.
* // interface. In this example the `search` query param is named `q`.
* const mapFromInserterMediaRequest = {
* per_page: 'page_size',
* search: 'q',
* };
* const url = new URL( 'https://api.openverse.engineering/v1/images/' );
* Object.entries( finalQuery ).forEach( ( [ key, value ] ) => {
* const queryKey = mapFromInserterMediaRequest[ key ] || key;
* url.searchParams.set( queryKey, value );
* } );
* const response = await window.fetch( url, {
* headers: {
* 'User-Agent': 'WordPress/inserter-media-fetch',
* },
* } );
* const jsonResponse = await response.json();
* const results = jsonResponse.results;
* return results.map( ( result ) => ( {
* ...result,
* // If your response result includes an `id` prop that you want to access later, it should
* // be mapped to `InserterMediaItem`'s `sourceId` prop. This can be useful if you provide
* // a report URL getter.
* // Additionally you should always clear the `id` value of your response results because
* // it is used to identify WordPress media items.
* sourceId: result.id,
* id: undefined,
* caption: result.caption,
* previewUrl: result.thumbnail,
* } ) );
* },
* getReportUrl: ( { sourceId } ) =>
* `https://wordpress.org/openverse/image/${ sourceId }/report/`,
* isExternalResource: true,
* } );
* ```
*
* @typedef {Object} InserterMediaCategory Interface for inserter media category.
* @property {string} name The name of the media category, that should be unique among all media categories.
* @property {Object} labels Labels for the media category.
* @property {string} labels.name General name of the media category. It's used in the inserter media items list.
* @property {string} [labels.search_items='Search'] Label for searching items. Default is ‘Search Posts’ / ‘Search Pages’.
* @property {('image'|'audio'|'video')} mediaType The media type of the media category.
* @property {(InserterMediaRequest) => Promise<InserterMediaItem[]>} fetch The function to fetch media items for the category.
* @property {(InserterMediaItem) => string} [getReportUrl] If the media category supports reporting media items, this function should return
* the report url for the media item. It accepts the `InserterMediaItem` as an argument.
* @property {boolean} [isExternalResource] If the media category is an external resource, this should be set to true.
* This is used to avoid making a request to the external resource when the user
*
*/
export const registerInserterMediaCategory =
( category ) =>
( { select, dispatch } ) => {
if ( ! category || typeof category !== 'object' ) {
console.error(
'Category should be an `InserterMediaCategory` object.'
);
return;
}
if ( ! category.name ) {
console.error(
'Category should have a `name` that should be unique among all media categories.'
);
return;
}
if ( ! category.labels?.name ) {
console.error( 'Category should have a `labels.name`.' );
return;
}
if ( ! [ 'image', 'audio', 'video' ].includes( category.mediaType ) ) {
console.error(
'Category should have `mediaType` property that is one of `image|audio|video`.'
);
return;
}
if ( ! category.fetch || typeof category.fetch !== 'function' ) {
console.error(
'Category should have a `fetch` function defined with the following signature `(InserterMediaRequest) => Promise<InserterMediaItem[]>`.'
);
return;
}
const { inserterMediaCategories = [] } = select.getSettings();
if (
inserterMediaCategories.some(
( { name } ) => name === category.name
)
) {
console.error(
`A category is already registered with the same name: "${ category.name }".`
);
return;
}
if (
inserterMediaCategories.some(
( { labels: { name } } ) => name === category.labels?.name
)
) {
console.error(
`A category is already registered with the same labels.name: "${ category.labels.name }".`
);
return;
}
// `inserterMediaCategories` is a private block editor setting, which means it cannot
// be updated through the public `updateSettings` action. We preserve this setting as
// private, so extenders can only add new inserter media categories and don't have any
// control over the core media categories.
dispatch( {
type: 'UPDATE_SETTINGS',
settings: {
inserterMediaCategories: [
...inserterMediaCategories,
{ ...category, isExternalResource: true },
],
},
} );
};
Loading

0 comments on commit 15e5833

Please sign in to comment.