clipboard.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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 clipboard/clipboard
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ClipboardObserver from './clipboardobserver';
  10. import plainTextToHtml from './utils/plaintexttohtml';
  11. import normalizeClipboardHtml from './utils/normalizeclipboarddata';
  12. import viewToPlainText from './utils/viewtoplaintext.js';
  13. import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
  14. import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
  15. /**
  16. * The clipboard feature. It is responsible for intercepting the `paste` and `drop` events and
  17. * passing the pasted content through the clipboard pipeline in order to insert it into the editor's content.
  18. * It also handles the `cut` and `copy` events to fill the native clipboard with serialized editor's data.
  19. *
  20. * Read more about the clipboard integration in {@glink framework/guides/deep-dive/clipboard "Clipboard" deep dive} guide.
  21. *
  22. * @extends module:core/plugin~Plugin
  23. */
  24. export default class Clipboard extends Plugin {
  25. /**
  26. * @inheritDoc
  27. */
  28. static get pluginName() {
  29. return 'Clipboard';
  30. }
  31. /**
  32. * @inheritDoc
  33. */
  34. init() {
  35. const editor = this.editor;
  36. const modelDocument = editor.model.document;
  37. const view = editor.editing.view;
  38. const viewDocument = view.document;
  39. /**
  40. * Data processor used to convert pasted HTML to a view structure.
  41. *
  42. * @private
  43. * @member {module:engine/dataprocessor/htmldataprocessor~HtmlDataProcessor} #_htmlDataProcessor
  44. */
  45. this._htmlDataProcessor = new HtmlDataProcessor( viewDocument );
  46. view.addObserver( ClipboardObserver );
  47. // The clipboard paste pipeline.
  48. // Pasting and dropping is disabled when editor is read-only.
  49. // See: https://github.com/ckeditor/ckeditor5-clipboard/issues/26.
  50. this.listenTo( viewDocument, 'clipboardInput', evt => {
  51. if ( editor.isReadOnly ) {
  52. evt.stop();
  53. }
  54. }, { priority: 'highest' } );
  55. this.listenTo( viewDocument, 'clipboardInput', ( evt, data ) => {
  56. const dataTransfer = data.dataTransfer;
  57. let content = '';
  58. if ( dataTransfer.getData( 'text/html' ) ) {
  59. content = normalizeClipboardHtml( dataTransfer.getData( 'text/html' ) );
  60. } else if ( dataTransfer.getData( 'text/plain' ) ) {
  61. content = plainTextToHtml( dataTransfer.getData( 'text/plain' ) );
  62. }
  63. content = this._htmlDataProcessor.toView( content );
  64. const eventInfo = new EventInfo( this, 'inputTransformation' );
  65. this.fire( eventInfo, { content, dataTransfer } );
  66. // If CKEditor handled the input, do not bubble the original event any further.
  67. // This helps external integrations recognize that fact and act accordingly.
  68. // https://github.com/ckeditor/ckeditor5-upload/issues/92
  69. if ( eventInfo.stop.called ) {
  70. evt.stop();
  71. }
  72. view.scrollToTheSelection();
  73. }, { priority: 'low' } );
  74. this.listenTo( this, 'inputTransformation', ( evt, data ) => {
  75. if ( !data.content.isEmpty ) {
  76. const dataController = this.editor.data;
  77. const model = this.editor.model;
  78. // Convert the pasted content to a model document fragment.
  79. // Conversion is contextual, but in this case we need an "all allowed" context and for that
  80. // we use the $clipboardHolder item.
  81. const modelFragment = dataController.toModel( data.content, '$clipboardHolder' );
  82. if ( modelFragment.childCount == 0 ) {
  83. return;
  84. }
  85. model.insertContent( modelFragment );
  86. evt.stop();
  87. }
  88. }, { priority: 'low' } );
  89. // The clipboard copy/cut pipeline.
  90. function onCopyCut( evt, data ) {
  91. const dataTransfer = data.dataTransfer;
  92. data.preventDefault();
  93. const content = editor.data.toView( editor.model.getSelectedContent( modelDocument.selection ) );
  94. viewDocument.fire( 'clipboardOutput', { dataTransfer, content, method: evt.name } );
  95. }
  96. this.listenTo( viewDocument, 'copy', onCopyCut, { priority: 'low' } );
  97. this.listenTo( viewDocument, 'cut', ( evt, data ) => {
  98. // Cutting is disabled when editor is read-only.
  99. // See: https://github.com/ckeditor/ckeditor5-clipboard/issues/26.
  100. if ( editor.isReadOnly ) {
  101. data.preventDefault();
  102. } else {
  103. onCopyCut( evt, data );
  104. }
  105. }, { priority: 'low' } );
  106. this.listenTo( viewDocument, 'clipboardOutput', ( evt, data ) => {
  107. if ( !data.content.isEmpty ) {
  108. data.dataTransfer.setData( 'text/html', this._htmlDataProcessor.toData( data.content ) );
  109. data.dataTransfer.setData( 'text/plain', viewToPlainText( data.content ) );
  110. }
  111. if ( data.method == 'cut' ) {
  112. editor.model.deleteContent( modelDocument.selection );
  113. }
  114. }, { priority: 'low' } );
  115. }
  116. }
  117. /**
  118. * Fired with a `content` and `dataTransfer` objects. The `content` which comes from the clipboard (was pasted or dropped)
  119. * should be processed in order to be inserted into the editor. The `dataTransfer` object is available
  120. * in case the transformation functions needs access to a raw clipboard data.
  121. *
  122. * It is a part of the {@glink framework/guides/deep-dive/clipboard#input-pipeline "clipboard input pipeline"}.
  123. *
  124. * @see module:clipboard/clipboardobserver~ClipboardObserver
  125. * @see module:clipboard/clipboard~Clipboard
  126. * @event module:clipboard/clipboard~Clipboard#event:inputTransformation
  127. * @param {Object} data Event data.
  128. * @param {module:engine/view/documentfragment~DocumentFragment} data.content Event data. Content to be inserted into the editor.
  129. * It can be modified by the event listeners. Read more about the clipboard pipelines in
  130. * {@glink framework/guides/deep-dive/clipboard "Clipboard" deep dive}.
  131. * @param {module:clipboard/datatransfer~DataTransfer} data.dataTransfer Data transfer instance.
  132. */
  133. /**
  134. * Fired on {@link module:engine/view/document~Document#event:copy} and {@link module:engine/view/document~Document#event:cut}
  135. * with a copy of selected content. The content can be processed before it ends up in the clipboard.
  136. *
  137. * It is a part of the {@glink framework/guides/deep-dive/clipboard#output-pipeline "clipboard output pipeline"}.
  138. *
  139. * @see module:clipboard/clipboardobserver~ClipboardObserver
  140. * @see module:clipboard/clipboard~Clipboard
  141. * @event module:engine/view/document~Document#event:clipboardOutput
  142. * @param {module:clipboard/clipboard~ClipboardOutputEventData} data Event data.
  143. */
  144. /**
  145. * The value of the {@link module:engine/view/document~Document#event:clipboardOutput} event.
  146. *
  147. * @class module:clipboard/clipboard~ClipboardOutputEventData
  148. */
  149. /**
  150. * Data transfer instance.
  151. *
  152. * @readonly
  153. * @member {module:clipboard/datatransfer~DataTransfer} module:clipboard/clipboard~ClipboardOutputEventData#dataTransfer
  154. */
  155. /**
  156. * Content to be put into the clipboard. It can be modified by the event listeners.
  157. * Read more about the clipboard pipelines in {@glink framework/guides/deep-dive/clipboard "Clipboard" deep dive}.
  158. *
  159. * @member {module:engine/view/documentfragment~DocumentFragment} module:clipboard/clipboard~ClipboardOutputEventData#content
  160. */
  161. /**
  162. * Whether the event was triggered by copy or cut operation.
  163. *
  164. * @member {'copy'|'cut'} module:clipboard/clipboard~ClipboardOutputEventData#method
  165. */