8
0

mediaembedediting.js 6.4 KB

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