| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- /**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- /**
- * @module media-embed/mediaregistry
- */
- /* globals console */
- import mediaPlaceholderIcon from '../theme/icons/media-placeholder.svg';
- import TooltipView from '@ckeditor/ckeditor5-ui/src/tooltip/tooltipview';
- import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
- import Template from '@ckeditor/ckeditor5-ui/src/template';
- import { attachLinkToDocumentation } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
- 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 {module:utils/locale~Locale} locale The localization services instance.
- * @param {module:media-embed/mediaembed~MediaEmbedConfig} 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.
- *
- * @warning media-embed-no-provider-name
- */
- console.warn( attachLinkToDocumentation(
- 'media-embed-no-provider-name: The configured media provider has no name and cannot be used.'
- ), { provider } );
- return false;
- }
- return !removedProviders.has( name );
- } );
- /**
- * The locale {@link module:utils/locale~Locale} instance.
- *
- * @member {module:utils/locale~Locale}
- */
- this.locale = locale;
- /**
- * The media provider definitions available for the registry. Usually corresponding with the
- * {@link module:media-embed/mediaembed~MediaEmbedConfig media configuration}.
- *
- * @member {Array}
- */
- this.providerDefinitions = providerDefinitions;
- }
- /**
- * Checks whether the passed URL is representing a certain media type allowed in the editor.
- *
- * @param {String} url The URL to be checked
- * @returns {Boolean}
- */
- 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 {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
- * @param {String} url The URL to be translated into a view element.
- * @param {Object} options
- * @param {String} [options.renderMediaPreview]
- * @param {String} [options.renderForEditingView]
- * @returns {module:engine/view/element~Element}
- */
- getMediaViewElement( writer, url, options ) {
- return this._getMedia( url ).getViewElement( writer, options );
- }
- /**
- * Returns a `Media` instance for the given URL.
- *
- * @protected
- * @param {String} url The URL of the media.
- * @returns {module:media-embed/mediaregistry~Media|null} 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;
- let pattern = definition.url;
- if ( !Array.isArray( pattern ) ) {
- pattern = [ pattern ];
- }
- 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`.
- *
- * @private
- * @param {String} url The URL of the media.
- * @param {RegExp} pattern The pattern that should accept the media URL.
- * @returns {Array|null}
- */
- _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.
- *
- * @private
- */
- class Media {
- constructor( locale, url, match, previewRenderer ) {
- /**
- * The URL this Media instance represents.
- *
- * @member {String}
- */
- this.url = this._getValidUrl( url );
- /**
- * Shorthand for {@link module:utils/locale~Locale#t}.
- *
- * @see module:utils/locale~Locale#t
- * @method
- */
- this._t = locale.t;
- /**
- * The output of the `RegExp.match` which validated the {@link #url} of this media.
- *
- * @member {Object}
- */
- this._match = match;
- /**
- * The function returning the HTML string preview of this media.
- *
- * @member {Function}
- */
- this._previewRenderer = previewRenderer;
- }
- /**
- * Returns the view element representation of the media.
- *
- * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
- * @param {Object} options
- * @param {String} [options.renderMediaPreview]
- * @param {String} [options.renderForEditingView]
- * @returns {module:engine/view/element~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.createUIElement( 'div', attributes, function( domDocument ) {
- const domElement = this.toDomElement( domDocument );
- domElement.innerHTML = mediaHtml;
- return domElement;
- } );
- } else {
- if ( this.url ) {
- attributes.url = this.url;
- }
- viewElement = writer.createEmptyElement( 'oembed', attributes );
- }
- writer.setCustomProperty( 'media-content', true, viewElement );
- return viewElement;
- }
- /**
- * Returns the HTML string of the media content preview.
- *
- * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
- * @param {Object} options
- * @param {String} [options.renderForEditingView]
- * @returns {String}
- */
- _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.
- *
- * @returns {String}
- */
- _getPlaceholderHtml() {
- const tooltip = new TooltipView();
- const icon = new IconView();
- tooltip.text = this._t( 'Open media in new tab' );
- 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
- },
- children: [
- {
- tag: 'span',
- attributes: {
- class: 'ck-media__placeholder__url__text'
- },
- children: [ this.url ]
- },
- tooltip
- ]
- }
- ]
- } ).render();
- return placeholder.outerHTML;
- }
- /**
- * Returns the full URL to the specified media.
- *
- * @param {String} url The URL of the media.
- * @returns {String|null}
- */
- _getValidUrl( url ) {
- if ( !url ) {
- return null;
- }
- if ( url.match( /^https?/ ) ) {
- return url;
- }
- return 'https://' + url;
- }
- }
|