8
0

entercommand.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module enter/entercommand
  7. */
  8. import Command from '@ckeditor/ckeditor5-core/src/command/command';
  9. import Position from '@ckeditor/ckeditor5-engine/src/model/position';
  10. /**
  11. * Enter command. It is used by the {@link module:enter/enter~Enter Enter feature} to handle the <kbd>Enter</kbd> key.
  12. *
  13. * @extends modue:core/command/command~Command
  14. */
  15. export default class EnterCommand extends Command {
  16. /**
  17. * @inheritDoc
  18. */
  19. _doExecute() {
  20. const doc = this.editor.document;
  21. const batch = doc.batch();
  22. doc.enqueueChanges( () => {
  23. enterBlock( this.editor.data, batch, doc.selection, doc.schema );
  24. this.fire( 'afterExecute', { batch } );
  25. } );
  26. }
  27. }
  28. // Creates a new block in the way that the <kbd>Enter</kbd> key is expected to work.
  29. //
  30. // @param {engine.controller.DataController} dataController
  31. // @param {module:engine/model/batch~Batch} batch A batch to which the deltas will be added.
  32. // @param {module:engine/model/selection~Selection} selection Selection on which the action should be performed.
  33. // @param {module:engine/model/schema~Schema} schema
  34. function enterBlock( dataController, batch, selection, schema ) {
  35. const isSelectionEmpty = selection.isCollapsed;
  36. const range = selection.getFirstRange();
  37. const startElement = range.start.parent;
  38. const endElement = range.end.parent;
  39. // Do nothing if selection starts or ends inside `limit` elements.
  40. if ( schema.limits.has( startElement.name ) || schema.limits.has( endElement.name ) ) {
  41. return;
  42. }
  43. // Don't touch the root.
  44. if ( startElement.root == startElement ) {
  45. if ( !isSelectionEmpty ) {
  46. dataController.deleteContent( selection, batch );
  47. }
  48. return;
  49. }
  50. if ( isSelectionEmpty ) {
  51. splitBlock( batch, selection, range.start );
  52. } else {
  53. const shouldMerge = range.start.isAtStart && range.end.isAtEnd;
  54. const isContainedWithinOneElement = ( startElement == endElement );
  55. dataController.deleteContent( selection, batch, { merge: shouldMerge } );
  56. if ( !shouldMerge ) {
  57. // Partially selected elements.
  58. //
  59. // <h>x[xx]x</h> -> <h>x^x</h> -> <h>x</h><h>^x</h>
  60. if ( isContainedWithinOneElement ) {
  61. splitBlock( batch, selection, selection.focus );
  62. }
  63. // Selection over multiple elements.
  64. //
  65. // <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
  66. else {
  67. selection.collapse( endElement );
  68. }
  69. }
  70. }
  71. }
  72. function splitBlock( batch, selection, splitPos ) {
  73. if ( splitPos.isAtEnd ) {
  74. // If the split is at the end of element, instead of splitting, just create a clone of position's parent
  75. // element and insert it after cloned element. The result is the same but less operations are taken
  76. // and it's more semantically correct (when it comes to operational transformation).
  77. const oldElement = splitPos.parent;
  78. const newElement = new oldElement.constructor( oldElement.name, oldElement.getAttributes() );
  79. batch.insert( Position.createAfter( splitPos.parent ), newElement );
  80. } else {
  81. batch.split( splitPos );
  82. }
  83. selection.collapse( splitPos.parent.nextSibling );
  84. }