Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new registerInserterMediaCategory API to make media categories extensible #51542

Merged
merged 4 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
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