@ckeditor/ckeditor5-media-embed
Version:
Media embed feature for CKEditor 5.
233 lines (232 loc) • 8.33 kB
JavaScript
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import { IconView, Template } from 'ckeditor5/src/ui';
import { logWarning, toArray } from 'ckeditor5/src/utils';
import mediaPlaceholderIcon from '../theme/icons/media-placeholder.svg';
const mediaPlaceholderIconViewBox = '0 0 64 42';
/**
* A bridge between the raw media content provider definitions and the editor view content.
*
* It helps translating media URLs to corresponding {@link module:engine/view/element~Element view elements}.
*
* Mostly used by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} plugin.
*/
export default class MediaRegistry {
/**
* Creates an instance of the {@link module:media-embed/mediaregistry~MediaRegistry} class.
*
* @param locale The localization services instance.
* @param config The configuration of the media embed feature.
*/
constructor(locale, config) {
const providers = config.providers;
const extraProviders = config.extraProviders || [];
const removedProviders = new Set(config.removeProviders);
const providerDefinitions = providers
.concat(extraProviders)
.filter(provider => {
const name = provider.name;
if (!name) {
/**
* One of the providers (or extra providers) specified in the media embed configuration
* has no name and will not be used by the editor. In order to get this media
* provider working, double check your editor configuration.
*
* @error media-embed-no-provider-name
*/
logWarning('media-embed-no-provider-name', { provider });
return false;
}
return !removedProviders.has(name);
});
this.locale = locale;
this.providerDefinitions = providerDefinitions;
}
/**
* Checks whether the passed URL is representing a certain media type allowed in the editor.
*
* @param url The URL to be checked
*/
hasMedia(url) {
return !!this._getMedia(url);
}
/**
* For the given media URL string and options, it returns the {@link module:engine/view/element~Element view element}
* representing that media.
*
* **Note:** If no URL is specified, an empty view element is returned.
*
* @param writer The view writer used to produce a view element.
* @param url The URL to be translated into a view element.
*/
getMediaViewElement(writer, url, options) {
return this._getMedia(url).getViewElement(writer, options);
}
/**
* Returns a `Media` instance for the given URL.
*
* @param url The URL of the media.
* @returns The `Media` instance or `null` when there is none.
*/
_getMedia(url) {
if (!url) {
return new Media(this.locale);
}
url = url.trim();
for (const definition of this.providerDefinitions) {
const previewRenderer = definition.html;
const pattern = toArray(definition.url);
for (const subPattern of pattern) {
const match = this._getUrlMatches(url, subPattern);
if (match) {
return new Media(this.locale, url, match, previewRenderer);
}
}
}
return null;
}
/**
* Tries to match `url` to `pattern`.
*
* @param url The URL of the media.
* @param pattern The pattern that should accept the media URL.
*/
_getUrlMatches(url, pattern) {
// 1. Try to match without stripping the protocol and "www" subdomain.
let match = url.match(pattern);
if (match) {
return match;
}
// 2. Try to match after stripping the protocol.
let rawUrl = url.replace(/^https?:\/\//, '');
match = rawUrl.match(pattern);
if (match) {
return match;
}
// 3. Try to match after stripping the "www" subdomain.
rawUrl = rawUrl.replace(/^www\./, '');
match = rawUrl.match(pattern);
if (match) {
return match;
}
return null;
}
}
/**
* Represents media defined by the provider configuration.
*
* It can be rendered to the {@link module:engine/view/element~Element view element} and used in the editing or data pipeline.
*/
class Media {
constructor(locale, url, match, previewRenderer) {
this.url = this._getValidUrl(url);
this._locale = locale;
this._match = match;
this._previewRenderer = previewRenderer;
}
/**
* Returns the view element representation of the media.
*
* @param writer The view writer used to produce a view element.
*/
getViewElement(writer, options) {
const attributes = {};
let viewElement;
if (options.renderForEditingView || (options.renderMediaPreview && this.url && this._previewRenderer)) {
if (this.url) {
attributes['data-oembed-url'] = this.url;
}
if (options.renderForEditingView) {
attributes.class = 'ck-media__wrapper';
}
const mediaHtml = this._getPreviewHtml(options);
viewElement = writer.createRawElement('div', attributes, (domElement, domConverter) => {
domConverter.setContentOf(domElement, mediaHtml);
});
}
else {
if (this.url) {
attributes.url = this.url;
}
viewElement = writer.createEmptyElement(options.elementName, attributes);
}
writer.setCustomProperty('media-content', true, viewElement);
return viewElement;
}
/**
* Returns the HTML string of the media content preview.
*/
_getPreviewHtml(options) {
if (this._previewRenderer) {
return this._previewRenderer(this._match);
}
else {
// The placeholder only makes sense for editing view and media which have URLs.
// Placeholder is never displayed in data and URL-less media have no content.
if (this.url && options.renderForEditingView) {
return this._getPlaceholderHtml();
}
return '';
}
}
/**
* Returns the placeholder HTML when the media has no content preview.
*/
_getPlaceholderHtml() {
const icon = new IconView();
const t = this._locale.t;
icon.content = mediaPlaceholderIcon;
icon.viewBox = mediaPlaceholderIconViewBox;
const placeholder = new Template({
tag: 'div',
attributes: {
class: 'ck ck-reset_all ck-media__placeholder'
},
children: [
{
tag: 'div',
attributes: {
class: 'ck-media__placeholder__icon'
},
children: [icon]
},
{
tag: 'a',
attributes: {
class: 'ck-media__placeholder__url',
target: '_blank',
rel: 'noopener noreferrer',
href: this.url,
'data-cke-tooltip-text': t('Open media in new tab')
},
children: [
{
tag: 'span',
attributes: {
class: 'ck-media__placeholder__url__text'
},
children: [this.url]
}
]
}
]
}).render();
return placeholder.outerHTML;
}
/**
* Returns the full URL to the specified media.
*
* @param url The URL of the media.
*/
_getValidUrl(url) {
if (!url) {
return null;
}
if (url.match(/^https?/)) {
return url;
}
return 'https://' + url;
}
}