@ckeditor/ckeditor5-media-embed
Version:
Media embed feature for CKEditor 5.
502 lines (501 loc) • 18.6 kB
TypeScript
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module media-embed/mediaembedconfig
*/
import type { ToolbarConfigItem } from '@ckeditor/ckeditor5-core';
import type { ArrayOrItem } from '@ckeditor/ckeditor5-utils';
/**
* The configuration of the media embed features.
*
* Read more about {@glink features/media-embed/media-embed-configuration configuring the media embed feature}.
*
* ```ts
* ClassicEditor
* .create( {
* mediaEmbed: ... // Media embed feature options.
* } )
* .then( ... )
* .catch( ... );
* ```
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*/
export interface MediaEmbedConfig {
/**
* The default media providers supported by the editor.
*
* The names of providers with rendering functions (previews):
*
* * "dailymotion",
* * "spotify",
* * "youtube",
* * "vimeo"
*
* The names of providers without rendering functions:
*
* * "instagram",
* * "twitter",
* * "googleMaps",
* * "flickr",
* * "facebook"
*
* See the {@link module:media-embed/mediaembedconfig~MediaEmbedProvider provider syntax} to learn more about
* different kinds of media and media providers.
*
* **Note**: The default media provider configuration may not support all possible media URLs,
* only the most common are included.
*
* Media without rendering functions are always represented in the data using the "semantic" markup. See
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`} to
* learn more about possible data outputs.
*
* The priority of media providers corresponds to the order of configuration. The first provider
* to match the URL is always used, even if there are other providers that support a particular URL.
* The URL is never matched against the remaining providers.
*
* To discard **all** default media providers, simply override this configuration with your own
* {@link module:media-embed/mediaembedconfig~MediaEmbedProvider definitions}:
*
* ```ts
* ClassicEditor
* .create( {
* plugins: [ MediaEmbed, ... ],
* mediaEmbed: {
* providers: [
* {
* name: 'myProvider',
* url: /^example\.com\/media\/(\w+)/,
* html: match => '...'
* },
* ...
* ]
* }
* } )
* .then( ... )
* .catch( ... );
* ```
*
* You can take inspiration from the default configuration of this feature which you can find in:
* https://github.com/ckeditor/ckeditor5-media-embed/blob/master/src/mediaembedediting.js
*
* To **extend** the list of default providers, use
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}.
*
* To **remove** certain providers, use
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#removeProviders `config.mediaEmbed.removeProviders`}.
*/
providers?: Array<MediaEmbedProvider>;
/**
* The additional media providers supported by the editor. This configuration helps extend the default
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#providers}.
*
* ```ts
* ClassicEditor
* .create( {
* plugins: [ MediaEmbed, ... ],
* mediaEmbed: {
* extraProviders: [
* {
* name: 'extraProvider',
* url: /^example\.com\/media\/(\w+)/,
* html: match => '...'
* },
* ...
* ]
* }
* } )
* .then( ... )
* .catch( ... );
* ```
*
* See the {@link module:media-embed/mediaembedconfig~MediaEmbedProvider provider syntax} to learn more.
*/
extraProviders?: Array<MediaEmbedProvider>;
/**
* The list of media providers that should not be used despite being available in
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#providers `config.mediaEmbed.providers`} and
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}
*
* ```ts
* mediaEmbed: {
* removeProviders: [ 'youtube', 'twitter' ]
* }
* ```
*/
removeProviders?: Array<string>;
/**
* Overrides the element name used for "semantic" data.
*
* This is not relevant if
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`} is set to `true`.
*
* When not set, the feature produces the `<oembed>` tag:
*
* ```html
* <figure class="media">
* <oembed url="https://url"></oembed>
* </figure>
* ```
*
* To override the element name with, for instance, the `o-embed` name:
*
* ```ts
* mediaEmbed: {
* elementName: 'o-embed'
* }
* ```
*
* This will produce semantic data with the `<o-embed>` tag:
*
* ```html
* <figure class="media">
* <o-embed url="https://url"></o-embed>
* </figure>
* ```
*
* @default 'oembed'
*/
elementName?: string;
/**
* Controls the data format produced by the feature.
*
* When `false` (default), the feature produces "semantic" data, i.e. it does not include the preview of
* the media, just the `<oembed>` tag with the `url` attribute:
*
* ```ts
* <figure class="media">
* <oembed url="https://url"></oembed>
* </figure>
* ```
*
* When `true`, the media is represented in the output in the same way it looks in the editor,
* i.e. the media preview is saved to the database:
*
* ```ts
* <figure class="media">
* <div data-oembed-url="https://url">
* <iframe src="https://preview"></iframe>
* </div>
* </figure>
* ```
*
* **Note:** Media without preview are always represented in the data using the "semantic" markup
* regardless of the value of the `previewsInData`. Learn more about different kinds of media
* in the {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#providers `config.mediaEmbed.providers`}
* configuration description.
*
* @default false
*/
previewsInData?: boolean;
/**
* Items to be placed in the media embed toolbar.
* This option requires adding {@link module:media-embed/mediaembedtoolbar~MediaEmbedToolbar} to the plugin list.
*
* Each entry is one of:
*
* * a component name (string) — including the built-in alignment buttons (e.g. `'mediaEmbed:alignCenter'`)
* and built-in dropdowns (`'mediaEmbed:wrapText'`, `'mediaEmbed:breakText'`),
* * a custom split-button media style dropdown definition (object) following the
* {@link module:media-embed/mediaembedconfig~MediaStyleDropdownDefinition} shape — registered
* alongside the built-in dropdowns and inheriting the same auto-skip / fallback-defaultItem behavior,
* * a generic nested toolbar grouping (`{ label, items }`) — same shape as in
* {@link module:core/editor/editorconfig~EditorConfig#toolbar `config.toolbar`}.
*
* ```ts
* mediaEmbed: {
* toolbar: [
* 'mediaEmbed:alignCenter',
* {
* name: 'mediaEmbed:myAlignments',
* title: 'Alignment',
* items: [ 'mediaEmbed:alignBlockLeft', 'mediaEmbed:alignBlockRight' ],
* defaultItem: 'mediaEmbed:alignBlockLeft'
* }
* ]
* }
* ```
*/
toolbar?: Array<ToolbarConfigItem | MediaStyleDropdownDefinition>;
/**
* The {@link module:media-embed/mediaembedstyle~MediaEmbedStyle media embed style} feature configuration.
*
* Available out of the box: five built-in alignment styles — `'alignLeft'`, `'alignBlockLeft'`,
* `'alignCenter'` (default), `'alignBlockRight'`, `'alignRight'`.
*
* Restrict the set to a subset of built-ins:
*
* ```ts
* mediaEmbed: {
* styles: {
* options: [ 'alignBlockLeft', 'alignCenter', 'alignBlockRight' ]
* }
* }
* ```
*
* Override fields of a built-in by re-declaring with the same `name`:
*
* ```ts
* mediaEmbed: {
* styles: {
* options: [
* { name: 'alignCenter', title: 'Center' },
* 'alignLeft',
* 'alignRight'
* ]
* }
* }
* ```
*
* Register a custom (e.g. semantical) style by supplying a complete definition:
*
* ```ts
* import sideIcon from 'path/to/icon.svg';
*
* mediaEmbed: {
* styles: {
* options: [
* 'alignCenter',
* {
* name: 'side',
* title: 'Side media',
* icon: sideIcon,
* className: 'media-style-side'
* }
* ]
* }
* }
* ```
*
* When omitted, all five built-in styles are available.
*/
styles?: MediaStyleConfig;
}
/**
* The configuration for the {@link module:media-embed/mediaembedstyle~MediaEmbedStyle} feature.
*
* See {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#styles `config.mediaEmbed.styles`}
* for details and examples.
*/
export interface MediaStyleConfig {
/**
* A list of media style options. Each entry is either:
*
* * a string referencing a built-in style by name (`'alignLeft'`, `'alignBlockLeft'`,
* `'alignCenter'`, `'alignBlockRight'`, `'alignRight'`),
* * an object overriding fields of a built-in (matched by `name`),
* * an object defining a new custom style.
*
* Defaults to all five built-in styles when omitted.
*/
options?: Array<string | MediaStyleOptionDefinition>;
}
/**
* The definition of a single media style option used by the
* {@link module:media-embed/mediaembedstyle~MediaEmbedStyle media embed style} feature.
*
* To customize a built-in style, re-declare it with the same `name` — only the fields
* you set will be replaced; the rest are inherited from the built-in. To register a
* brand-new style, provide a fresh `name` and a complete definition (`title`, `icon`,
* and — unless `isDefault: true` — `className`).
*
* ```ts
* import sideIcon from 'path/to/icon.svg';
*
* const sideStyle = {
* name: 'side',
* title: 'Side media',
* icon: sideIcon,
* className: 'media-style-side'
* };
* ```
*
* Each option registers a toggle button under the name `'mediaEmbed:{name}'` in the
* {@link module:ui/componentfactory~ComponentFactory UI component factory}.
*/
export interface MediaStyleOptionDefinition {
/**
* The unique style name. It is used to:
*
* * reference a built-in style or define a custom one,
* * store the chosen style in the model as the `mediaStyle` attribute,
* * register the toolbar button under `'mediaEmbed:{name}'`.
*/
name: string;
/**
* The button title. The title is wrapped in `editor.t()` at button creation,
* so titles that match keys in the official translation set will be localized
* automatically.
*
* Required when defining a custom style. Inherited from the built-in style with
* the matching `name` when overriding a built-in.
*/
title?: string;
/**
* The button icon. Either an SVG XML source string, or one of the keys in
* `DEFAULT_ICONS` (`'inlineLeft'`, `'left'`, `'center'`, `'right'`, `'inlineRight'`)
* to use one of the icons shipped with the plugin.
*
* Required when defining a custom style. Inherited from the built-in style with
* the matching `name` when overriding a built-in.
*/
icon?: string;
/**
* The CSS class added to the view `<figure>` when this style is applied. Required
* for every non-default style — default styles are encoded as the absence of the
* `mediaStyle` attribute, so they intentionally have no class.
*
* Inherited from the built-in style with the matching `name` when not set.
*/
className?: string;
/**
* When `true`, this style is the default state — applying it removes the
* `mediaStyle` attribute from the model element (no class is written to the view).
* Default styles must not define a `className`.
*
* Inherited from the built-in style with the matching `name` when not set.
*/
isDefault?: boolean;
}
/**
* A media style option resolved by the normalizer — built-in inheritance has been applied
* and {@link module:media-embed/mediaembedstyle/utils~normalizeStyles} has already verified
* the required fields. UI and editing internals consume this shape.
*
* @internal
*/
export type NormalizedMediaStyleOption = Required<Pick<MediaStyleOptionDefinition, 'name' | 'title' | 'icon'>> & Pick<MediaStyleOptionDefinition, 'className' | 'isDefault'>;
/**
* The definition of a split-button dropdown that groups several media style buttons.
*
* Integrators can declare custom dropdowns inline in
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#toolbar `config.mediaEmbed.toolbar`}
* alongside button-name strings; `defaultItem` is the discriminator that distinguishes a
* split-button media style dropdown from a generic toolbar grouping.
*
* ```ts
* mediaEmbed: {
* toolbar: [
* 'mediaEmbed:alignCenter',
* {
* name: 'mediaEmbed:myAlignments',
* title: 'Alignment',
* items: [ 'mediaEmbed:alignBlockLeft', 'mediaEmbed:alignBlockRight' ],
* defaultItem: 'mediaEmbed:alignBlockLeft'
* }
* ]
* }
* ```
*
* All names (`name`, `items[]`, `defaultItem`) use the full prefixed component-factory form.
* Items referencing styles that are not in the resolved
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#styles `config.mediaEmbed.styles`} list
* are filtered out at registration time. A dropdown that ends up with fewer than two items is
* skipped. If the configured `defaultItem` was filtered out, the first surviving item is used.
*/
export interface MediaStyleDropdownDefinition {
/**
* The dropdown name. Registered as-is in the UI component factory, so it must use the
* `mediaEmbed:` prefix (for example, `'mediaEmbed:myAlignments'`).
*/
name: string;
/**
* The dropdown title, used both for the split-button label and the dropdown arrow tooltip.
*/
title: string;
/**
* Prefixed style names included in the dropdown (for example,
* `[ 'mediaEmbed:alignBlockLeft', 'mediaEmbed:alignBlockRight' ]`).
*/
items: Array<string>;
/**
* The default child whose icon and label the split button mirrors when no child is active.
* Must be one of the `items`.
*/
defaultItem: string;
}
/**
* The media embed provider descriptor. Used in
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#providers `config.mediaEmbed.providers`} and
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}.
*
* See {@link module:media-embed/mediaembedconfig~MediaEmbedConfig} to learn more.
*
* ```ts
* {
* name: 'example',
*
* // The following RegExp matches https://www.example.com/media/{media id},
* // (either with "http(s)://" and "www" or without), so the valid URLs are:
* //
* // * https://www.example.com/media/{media id},
* // * http://www.example.com/media/{media id},
* // * www.example.com/media/{media id},
* // * example.com/media/{media id}
* url: /^example\.com\/media\/(\w+)/,
*
* // The rendering function of the provider.
* // Used to represent the media when editing the content (i.e. in the view)
* // and also in the data output of the editor if semantic data output is disabled.
* html: match => `The HTML representing the media with ID=${ match[ 1 ] }.`
* }
* ```
*
* You can allow any sort of media in the editor using the "allow–all" `RegExp`.
* But mind that, since URLs are processed in the order of configuration, if one of the previous
* `RegExps` matches the URL, it will have a precedence over this one.
*
* ```ts
* {
* name: 'allow-all',
* url: /^.+/
* }
* ```
*
* To implement responsive media, set an `aspect-ratio` on the iframe. The HTML `width` and
* `height` attributes act as the intrinsic size (useful for layout hints in containers like
* table cells), while CSS `width: 100%` and `height: auto` make the element scale with its
* container while preserving the declared aspect ratio. The iframe is wrapped in a plain
* `<div>` so external styles or queries that target this wrapper continue to work:
*
* ```ts
* {
* ...
* html: match =>
* '<div>' +
* `<iframe src="..." width="1280" height="720" ` +
* `style="width: 100%; height: auto; aspect-ratio: 16 / 9; border: 0; display: block;" ` +
* 'frameborder="0" allowfullscreen>' +
* '</iframe>' +
* '</div>'
* }
* ```
*/
export interface MediaEmbedProvider {
/**
* The name of the provider. Used e.g. when
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#removeProviders removing providers}.
*/
name: string;
/**
* The `RegExp` object (or array of objects) defining the URL of the media.
* If any URL matches the `RegExp`, it becomes the media in the editor model, as defined by the provider. The result
* of matching (output of `String.prototype.match()`) is passed to the `html` rendering function of the media.
*
* **Note:** You do not need to include the protocol (`http://`, `https://`) and `www` subdomain in your `RegExps`,
* they are stripped from the URLs before matching anyway.
*/
url: ArrayOrItem<RegExp>;
/**
* The rendering function of the media. The function receives the entire matching
* array from the corresponding `url` `RegExp` as an argument, allowing rendering a dedicated
* preview of the media identified by a certain ID or a hash. When not defined, the media embed feature
* will use a generic media representation in the view and output data.
* Note that when
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`}
* is `true`, the rendering function **will always** be used for the media in the editor data output.
*/
html?: (match: RegExpMatchArray) => string;
}