mediaembedediting.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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 {
  10. viewFigureToModel,
  11. modelToViewUrlAttributeConverter
  12. } from './converters';
  13. import InsertMediaCommand from './insertmediacommand';
  14. import { toMediaWidget, createMediaFigureElement } from './utils';
  15. import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
  16. import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
  17. /**
  18. * The media embed editing feature.
  19. *
  20. * @extends module:core/plugin~Plugin
  21. */
  22. export default class MediaEmbedEditing extends Plugin {
  23. /**
  24. * @inheritDoc
  25. */
  26. constructor( editor ) {
  27. super( editor );
  28. editor.config.define( 'mediaEmbed', {
  29. media: {
  30. dailymotion: {
  31. url: [
  32. /^(https:\/\/)?(www\.)?dailymotion\.com\/video\/(\w+)/
  33. ],
  34. html: id =>
  35. `<iframe src="https://www.dailymotion.com/embed/video/${ id }" ` +
  36. 'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' +
  37. '</iframe>'
  38. },
  39. instagram: {
  40. url: [
  41. /^(https:\/\/)?(www\.)?instagram\.com\/p\/(\w+)/
  42. ],
  43. html: id =>
  44. `<iframe src="http://instagram.com/p/${ id }/embed" ` +
  45. 'frameborder="0">' +
  46. '</iframe>'
  47. },
  48. spotify: {
  49. url: [
  50. /^(https:\/\/)?(www\.)?open\.spotify\.com\/(artist\/\w+)/,
  51. /^(https:\/\/)?(www\.)?open\.spotify\.com\/(album\/\w+)/,
  52. /^(https:\/\/)?(www\.)?open\.spotify\.com\/(track\/\w+)/
  53. ],
  54. html: id =>
  55. '<div class="ck-media__wrapper__aspect">' +
  56. `<iframe src="https://open.spotify.com/embed/${ id }" ` +
  57. 'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
  58. '</iframe>' +
  59. '</div>'
  60. },
  61. youtube: {
  62. url: [
  63. /^(https:\/\/)?(www\.)?youtube\.com\/watch\?v=(\w+)/,
  64. /^(https:\/\/)?(www\.)?youtube\.com\/v\/(\w+)/,
  65. /^(https:\/\/)?(www\.)?youtube\.com\/embed\/(\w+)/,
  66. /^(https:\/\/)?youtu\.be\/(\w+)/
  67. ],
  68. html: id =>
  69. '<div class="ck-media__wrapper__aspect">' +
  70. `<iframe src="https://www.youtube.com/embed/${ id }" ` +
  71. 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' +
  72. '</iframe>' +
  73. '</div>'
  74. },
  75. vimeo: {
  76. url: [
  77. /^(https:\/\/)?(www\.)?vimeo\.com\/(\d+)/,
  78. /^(https:\/\/)?(www\.)?vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
  79. /^(https:\/\/)?(www\.)?vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
  80. /^(https:\/\/)?(www\.)?vimeo\.com\/channels\/[^/]+\/(\d+)/,
  81. /^(https:\/\/)?(www\.)?vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
  82. /^(https:\/\/)?(www\.)?vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
  83. /^(https:\/\/)?(www\.)?player\.vimeo\.com\/video\/(\d+)/
  84. ],
  85. html: id =>
  86. '<div class="ck-media__wrapper__aspect">' +
  87. `<iframe src="https://player.vimeo.com/video/${ id }" ` +
  88. 'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' +
  89. '</iframe>' +
  90. '</div>'
  91. },
  92. twitter: {
  93. url: /^(https:\/\/)?(www\.)?twitter\.com/
  94. },
  95. googleMaps: {
  96. url: /^(https:\/\/)?(www\.)?google\.com\/maps/
  97. },
  98. flickr: {
  99. url: /^(https:\/\/)?(www\.)?flickr\.com/
  100. }
  101. }
  102. } );
  103. }
  104. /**
  105. * @inheritDoc
  106. */
  107. init() {
  108. const editor = this.editor;
  109. const schema = editor.model.schema;
  110. const t = editor.t;
  111. const conversion = editor.conversion;
  112. const semanticDataOutput = editor.config.get( 'mediaEmbed.semanticDataOutput' );
  113. editor.commands.add( 'insertMedia', new InsertMediaCommand( editor ) );
  114. // Configure the schema.
  115. schema.register( 'media', {
  116. isObject: true,
  117. isBlock: true,
  118. allowWhere: '$block',
  119. allowAttributes: [ 'url' ]
  120. } );
  121. // Model -> Data
  122. conversion.for( 'dataDowncast' ).add( downcastElementToElement( {
  123. model: 'media',
  124. view: ( modelElement, viewWriter ) => {
  125. return createMediaFigureElement( viewWriter, {
  126. withAspectWrapper: !semanticDataOutput
  127. } );
  128. }
  129. } ) );
  130. // Model -> Data (url -> data-oembed-url)
  131. conversion.for( 'dataDowncast' )
  132. .add( modelToViewUrlAttributeConverter( editor, {
  133. shouldRenderContent: !semanticDataOutput
  134. } ) );
  135. // Model -> View (element)
  136. conversion.for( 'editingDowncast' ).add( downcastElementToElement( {
  137. model: 'media',
  138. view: ( modelElement, viewWriter ) => {
  139. return toMediaWidget( createMediaFigureElement( viewWriter ), viewWriter, t( 'media widget' ) );
  140. }
  141. } ) );
  142. // Model -> View (url -> data-oembed-url)
  143. conversion.for( 'editingDowncast' )
  144. .add( modelToViewUrlAttributeConverter( editor, {
  145. isEditingPipeline: true
  146. } ) );
  147. // View -> Model (data-oembed-url -> url)
  148. conversion.for( 'upcast' )
  149. .add( upcastElementToElement( {
  150. view: {
  151. name: 'div',
  152. attributes: {
  153. 'data-oembed-url': true
  154. }
  155. },
  156. model: ( viewMedia, modelWriter ) => {
  157. return modelWriter.createElement( 'media', {
  158. url: viewMedia.getAttribute( 'data-oembed-url' )
  159. } );
  160. }
  161. } ) )
  162. .add( viewFigureToModel() );
  163. }
  164. }