mediaembedediting.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module media-embed/mediaembedediting
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import { modelToViewUrlAttributeConverter } from './converters';
  10. import InsertMediaCommand from './insertmediacommand';
  11. import { toMediaWidget, createMediaFigureElement } from './utils';
  12. import { MediaRegistry } from './mediaregistry';
  13. import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
  14. import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
  15. /**
  16. * The media embed editing feature.
  17. *
  18. * @extends module:core/plugin~Plugin
  19. */
  20. export default class MediaEmbedEditing extends Plugin {
  21. /**
  22. * @inheritDoc
  23. */
  24. constructor( editor ) {
  25. super( editor );
  26. editor.config.define( 'mediaEmbed', {
  27. media: [
  28. {
  29. url: [
  30. /^(https:\/\/)?(www\.)?dailymotion\.com\/video\/(\w+)/
  31. ],
  32. html: id =>
  33. '<div style="position: relative; padding-bottom: 100%; height: 0; ">' +
  34. `<iframe src="https://www.dailymotion.com/embed/video/${ id }" ` +
  35. 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
  36. 'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' +
  37. '</iframe>' +
  38. '</div>'
  39. },
  40. {
  41. url: [
  42. /^(https:\/\/)?(www\.)?open\.spotify\.com\/(artist\/\w+)/,
  43. /^(https:\/\/)?(www\.)?open\.spotify\.com\/(album\/\w+)/,
  44. /^(https:\/\/)?(www\.)?open\.spotify\.com\/(track\/\w+)/
  45. ],
  46. html: id =>
  47. '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
  48. `<iframe src="https://open.spotify.com/embed/${ id }" ` +
  49. 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
  50. 'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
  51. '</iframe>' +
  52. '</div>'
  53. },
  54. {
  55. url: [
  56. /^(https:\/\/)?(www\.)?youtube\.com\/watch\?v=(\w+)/,
  57. /^(https:\/\/)?(www\.)?youtube\.com\/v\/(\w+)/,
  58. /^(https:\/\/)?(www\.)?youtube\.com\/embed\/(\w+)/,
  59. /^(https:\/\/)?youtu\.be\/(\w+)/
  60. ],
  61. html: id =>
  62. '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
  63. `<iframe src="https://www.youtube.com/embed/${ id }" ` +
  64. 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
  65. 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' +
  66. '</iframe>' +
  67. '</div>'
  68. },
  69. {
  70. url: [
  71. /^(https:\/\/)?(www\.)?vimeo\.com\/(\d+)/,
  72. /^(https:\/\/)?(www\.)?vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
  73. /^(https:\/\/)?(www\.)?vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
  74. /^(https:\/\/)?(www\.)?vimeo\.com\/channels\/[^/]+\/(\d+)/,
  75. /^(https:\/\/)?(www\.)?vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
  76. /^(https:\/\/)?(www\.)?vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
  77. /^(https:\/\/)?(www\.)?player\.vimeo\.com\/video\/(\d+)/
  78. ],
  79. html: id =>
  80. '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
  81. `<iframe src="https://player.vimeo.com/video/${ id }" ` +
  82. 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
  83. 'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' +
  84. '</iframe>' +
  85. '</div>'
  86. },
  87. /^(https:\/\/)?(www\.)?instagram\.com\/p\/(\w+)/,
  88. /^(https:\/\/)?(www\.)?twitter\.com/,
  89. /^(https:\/\/)?(www\.)?google\.com\/maps/,
  90. /^(https:\/\/)?(www\.)?flickr\.com/,
  91. /^(https:\/\/)?(www\.)?facebook\.com/
  92. ]
  93. } );
  94. /**
  95. * The media registry managing the media providers in the editor.
  96. *
  97. * @member {module:media-embed/mediaregistry~MediaRegistry} #mediaRegistry
  98. */
  99. this.mediaRegistry = new MediaRegistry( editor.locale, editor.config.get( 'mediaEmbed.media' ) );
  100. }
  101. /**
  102. * @inheritDoc
  103. */
  104. init() {
  105. const editor = this.editor;
  106. const schema = editor.model.schema;
  107. const t = editor.t;
  108. const conversion = editor.conversion;
  109. const semanticDataOutput = editor.config.get( 'mediaEmbed.semanticDataOutput' );
  110. const mediaRegistry = this.mediaRegistry;
  111. editor.commands.add( 'mediaEmbed', new InsertMediaCommand( editor ) );
  112. // Configure the schema.
  113. schema.register( 'media', {
  114. isObject: true,
  115. isBlock: true,
  116. allowWhere: '$block',
  117. allowAttributes: [ 'url' ]
  118. } );
  119. // Model -> Data
  120. conversion.for( 'dataDowncast' ).add( downcastElementToElement( {
  121. model: 'media',
  122. view: ( modelElement, viewWriter ) => {
  123. const url = modelElement.getAttribute( 'url' );
  124. return createMediaFigureElement( viewWriter, mediaRegistry, url, {
  125. useSemanticWrapper: semanticDataOutput || !url,
  126. renderContent: !semanticDataOutput
  127. } );
  128. }
  129. } ) );
  130. // Model -> Data (url -> data-oembed-url)
  131. conversion.for( 'dataDowncast' ).add(
  132. modelToViewUrlAttributeConverter( mediaRegistry, {
  133. semanticDataOutput
  134. } ) );
  135. // Model -> View (element)
  136. conversion.for( 'editingDowncast' ).add( downcastElementToElement( {
  137. model: 'media',
  138. view: ( modelElement, viewWriter ) => {
  139. const url = modelElement.getAttribute( 'url' );
  140. const figure = createMediaFigureElement( viewWriter, mediaRegistry, url, {
  141. renderForEditingView: true,
  142. renderContent: true
  143. } );
  144. return toMediaWidget( figure, viewWriter, t( 'media widget' ) );
  145. }
  146. } ) );
  147. // Model -> View (url -> data-oembed-url)
  148. conversion.for( 'editingDowncast' ).add(
  149. modelToViewUrlAttributeConverter( mediaRegistry, {
  150. renderForEditingView: true
  151. } ) );
  152. // View -> Model (data-oembed-url -> url)
  153. conversion.for( 'upcast' )
  154. // Upcast semantic media.
  155. .add( upcastElementToElement( {
  156. view: {
  157. name: 'oembed',
  158. attributes: {
  159. 'url': true
  160. }
  161. },
  162. model: ( viewMedia, modelWriter ) => {
  163. const url = viewMedia.getAttribute( 'url' );
  164. if ( mediaRegistry.hasMedia( url ) ) {
  165. return modelWriter.createElement( 'media', { url } );
  166. }
  167. }
  168. } ) )
  169. // Upcast non-semantic media.
  170. .add( upcastElementToElement( {
  171. view: {
  172. name: 'div',
  173. attributes: {
  174. 'data-oembed-url': true
  175. }
  176. },
  177. model: ( viewMedia, modelWriter ) => {
  178. const url = viewMedia.getAttribute( 'data-oembed-url' );
  179. if ( mediaRegistry.hasMedia( url ) ) {
  180. return modelWriter.createElement( 'media', { url } );
  181. }
  182. }
  183. } ) );
  184. }
  185. }