8
0

editingcontroller.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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 engine/controller/editingcontroller
  7. */
  8. import RootEditableElement from '../view/rooteditableelement';
  9. import View from '../view/view';
  10. import Mapper from '../conversion/mapper';
  11. import DowncastDispatcher from '../conversion/downcastdispatcher';
  12. import { clearAttributes, convertCollapsedSelection, convertRangeSelection, insertText, remove } from '../conversion/downcasthelpers';
  13. import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
  14. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  15. import { convertSelectionChange } from '../conversion/upcasthelpers';
  16. // @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
  17. /**
  18. * Controller for the editing pipeline. The editing pipeline controls {@link ~EditingController#model model} rendering,
  19. * including selection handling. It also creates the {@link ~EditingController#view view} which builds a
  20. * browser-independent virtualization over the DOM elements. The editing controller also attaches default converters.
  21. *
  22. * @mixes module:utils/observablemixin~ObservableMixin
  23. */
  24. export default class EditingController {
  25. /**
  26. * Creates an editing controller instance.
  27. *
  28. * @param {module:engine/model/model~Model} model Editing model.
  29. */
  30. constructor( model ) {
  31. /**
  32. * Editor model.
  33. *
  34. * @readonly
  35. * @member {module:engine/model/model~Model}
  36. */
  37. this.model = model;
  38. /**
  39. * Editing view controller.
  40. *
  41. * @readonly
  42. * @member {module:engine/view/view~View}
  43. */
  44. this.view = new View();
  45. /**
  46. * Mapper which describes the model-view binding.
  47. *
  48. * @readonly
  49. * @member {module:engine/conversion/mapper~Mapper}
  50. */
  51. this.mapper = new Mapper();
  52. /**
  53. * Downcast dispatcher that converts changes from the model to {@link #view the editing view}.
  54. *
  55. * @readonly
  56. * @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher} #downcastDispatcher
  57. */
  58. this.downcastDispatcher = new DowncastDispatcher( {
  59. mapper: this.mapper
  60. } );
  61. const doc = this.model.document;
  62. const selection = doc.selection;
  63. const markers = this.model.markers;
  64. // When plugins listen on model changes (on selection change, post fixers, etc) and change the view as a result of
  65. // model's change, they might trigger view rendering before the conversion is completed (e.g. before the selection
  66. // is converted). We disable rendering for the length of the outermost model change() block to prevent that.
  67. //
  68. // See https://github.com/ckeditor/ckeditor5-engine/issues/1528
  69. this.listenTo( this.model, '_beforeChanges', () => {
  70. this.view._disableRendering( true );
  71. }, { priority: 'highest' } );
  72. this.listenTo( this.model, '_afterChanges', () => {
  73. this.view._disableRendering( false );
  74. }, { priority: 'lowest' } );
  75. // Whenever model document is changed, convert those changes to the view (using model.Document#differ).
  76. // Do it on 'low' priority, so changes are converted after other listeners did their job.
  77. // Also convert model selection.
  78. this.listenTo( doc, 'change', () => {
  79. this.view.change( writer => {
  80. this.downcastDispatcher.convertChanges( doc.differ, markers, writer );
  81. this.downcastDispatcher.convertSelection( selection, markers, writer );
  82. } );
  83. }, { priority: 'low' } );
  84. // Convert selection from the view to the model when it changes in the view.
  85. this.listenTo( this.view.document, 'selectionChange', convertSelectionChange( this.model, this.mapper ) );
  86. // Attach default model converters.
  87. this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } );
  88. this.downcastDispatcher.on( 'remove', remove(), { priority: 'low' } );
  89. // Attach default model selection converters.
  90. this.downcastDispatcher.on( 'selection', clearAttributes(), { priority: 'low' } );
  91. this.downcastDispatcher.on( 'selection', convertRangeSelection(), { priority: 'low' } );
  92. this.downcastDispatcher.on( 'selection', convertCollapsedSelection(), { priority: 'low' } );
  93. // Binds {@link module:engine/view/document~Document#roots view roots collection} to
  94. // {@link module:engine/model/document~Document#roots model roots collection} so creating
  95. // model root automatically creates corresponding view root.
  96. this.view.document.roots.bindTo( this.model.document.roots ).using( root => {
  97. // $graveyard is a special root that has no reflection in the view.
  98. if ( root.rootName == '$graveyard' ) {
  99. return null;
  100. }
  101. const viewRoot = new RootEditableElement( root.name );
  102. viewRoot.rootName = root.rootName;
  103. viewRoot._document = this.view.document;
  104. this.mapper.bindElements( root, viewRoot );
  105. return viewRoot;
  106. } );
  107. // @if CK_DEBUG_ENGINE // initDocumentDumping( this.model.document );
  108. // @if CK_DEBUG_ENGINE // initDocumentDumping( this.view.document );
  109. // @if CK_DEBUG_ENGINE // dumpTrees( this.model.document, this.model.document.version );
  110. // @if CK_DEBUG_ENGINE // dumpTrees( this.view.document, this.model.document.version );
  111. // @if CK_DEBUG_ENGINE // this.model.document.on( 'change', () => {
  112. // @if CK_DEBUG_ENGINE // dumpTrees( this.view.document, this.model.document.version );
  113. // @if CK_DEBUG_ENGINE // }, { priority: 'lowest' } );
  114. }
  115. /**
  116. * Removes all event listeners attached to the `EditingController`. Destroys all objects created
  117. * by `EditingController` that need to be destroyed.
  118. */
  119. destroy() {
  120. this.view.destroy();
  121. this.stopListening();
  122. }
  123. }
  124. mix( EditingController, ObservableMixin );