entercommand.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Command from '../command/command.js';
  6. import Element from '../engine/model/element.js';
  7. import LivePosition from '../engine/model/liveposition.js';
  8. import Position from '../engine/model/position.js';
  9. /**
  10. * The Enter command. It is used by the {@link enter.Enter Enter feature} to handle the <kbd>Enter</kbd> key.
  11. *
  12. * @member enter
  13. * @extends ckeditor5.command.Command
  14. */
  15. export default class EnterCommand extends Command {
  16. _doExecute() {
  17. const doc = this.editor.document;
  18. doc.enqueueChanges( () => {
  19. enterBlock( doc.batch(), doc.selection, { defaultBlockName: 'paragraph' } );
  20. } );
  21. }
  22. }
  23. /**
  24. * Creates a new block in the way that the <kbd>Enter</kbd> key is expected to work.
  25. *
  26. * @param {engine.model.Batch} batch A batch to which the deltas will be added.
  27. * @param {engine.model.Selection} selection
  28. * @param {Object} options
  29. * @param {Boolean} options.defaultBlockName The name of the block which should be created when Enter leaves
  30. * another block. Usually set to `'paragraph'`. For example, when creating a block in `<heading>foo^</heading>`,
  31. * the result will be `<heading>foo</heading><paragraph>^</paragraph>`.
  32. */
  33. export function enterBlock( batch, selection, options = {} ) {
  34. const defaultBlockName = options.defaultBlockName;
  35. const doc = batch.document;
  36. const isSelectionEmpty = selection.isCollapsed;
  37. const range = selection.getFirstRange();
  38. const startElement = range.start.parent;
  39. const endElement = range.end.parent;
  40. // Don't touch the root.
  41. if ( startElement.root == startElement ) {
  42. if ( !isSelectionEmpty ) {
  43. doc.composer.deleteContents( batch, selection );
  44. }
  45. return;
  46. }
  47. if ( isSelectionEmpty ) {
  48. splitBlock( batch, selection, range.start, defaultBlockName );
  49. } else {
  50. const shouldMerge = range.start.isAtStart && range.end.isAtEnd;
  51. const isContainedWithinOneElement = ( startElement == endElement );
  52. doc.composer.deleteContents( batch, selection, { merge: shouldMerge } );
  53. // Fully selected elements.
  54. //
  55. // <h>[xx</h><p>yy]<p> -> <h>^</h> -> <p>^</p>
  56. // <h>[xxyy]</h> -> <h>^</h> -> <p>^</p>
  57. if ( shouldMerge ) {
  58. // We'll lose the ref to the renamed element, so let's keep a position inside it
  59. // (offsets won't change, so it will stay in place). See ckeditor5-engine#367.
  60. const pos = Position.createFromPosition( selection.focus );
  61. const newBlockName = getNewBlockName( doc, startElement, defaultBlockName );
  62. if ( startElement.name != newBlockName ) {
  63. batch.rename( newBlockName, startElement );
  64. }
  65. selection.collapse( pos );
  66. }
  67. // Partially selected elements.
  68. //
  69. // <h>x[xx]x</h> -> <h>x^x</h> -> <h>x</h><h>^x</h>
  70. else if ( isContainedWithinOneElement ) {
  71. splitBlock( batch, selection, selection.focus, defaultBlockName );
  72. }
  73. // Selection over multilpe elements.
  74. //
  75. // <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
  76. else {
  77. selection.collapse( endElement );
  78. }
  79. }
  80. }
  81. function splitBlock( batch, selection, splitPos, defaultBlockName ) {
  82. const doc = batch.document;
  83. const parent = splitPos.parent;
  84. if ( splitPos.isAtEnd ) {
  85. const newElement = new Element( getNewBlockName( doc, parent, defaultBlockName ) );
  86. batch.insert( Position.createAfter( parent ), newElement );
  87. selection.collapse( newElement );
  88. } else {
  89. // TODO After ckeditor5-engine#340 is fixed we'll be able to base on splitPos's location.
  90. const endPos = LivePosition.createFromPosition( splitPos );
  91. endPos.stickiness = 'STICKS_TO_NEXT';
  92. batch.split( splitPos );
  93. selection.collapse( endPos );
  94. endPos.detach();
  95. }
  96. }
  97. function getNewBlockName( doc, startElement, defaultBlockName ) {
  98. if ( doc.schema.check( { name: defaultBlockName, inside: startElement.parent.name } ) ) {
  99. return defaultBlockName;
  100. }
  101. return startElement.name;
  102. }