entercommand.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module enter/entercommand
  7. */
  8. import Command from '@ckeditor/ckeditor5-core/src/command';
  9. /**
  10. * Enter command. It is used by the {@link module:enter/enter~Enter Enter feature} to handle the <kbd>Enter</kbd> key.
  11. *
  12. * @extends module:core/command~Command
  13. */
  14. export default class EnterCommand extends Command {
  15. /**
  16. * @inheritDoc
  17. */
  18. execute() {
  19. const model = this.editor.model;
  20. const doc = model.document;
  21. model.change( writer => {
  22. enterBlock( this.editor.model, writer, doc.selection, model.schema );
  23. this.fire( 'afterExecute', { writer } );
  24. } );
  25. }
  26. }
  27. // Creates a new block in the way that the <kbd>Enter</kbd> key is expected to work.
  28. //
  29. // @param {module:engine/model~Model} model
  30. // @param {module:engine/model/writer~Writer} writer
  31. // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  32. // Selection on which the action should be performed.
  33. // @param {module:engine/model/schema~Schema} schema
  34. function enterBlock( model, writer, 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. // Don't touch the roots and other limit elements.
  40. if ( schema.isLimit( startElement ) || schema.isLimit( endElement ) ) {
  41. // Delete the selected content but only if inside a single limit element.
  42. // Abort, when crossing limit elements boundary (e.g. <limit1>x[x</limit1>donttouchme<limit2>y]y</limit2>).
  43. // This is an edge case and it's hard to tell what should actually happen because such a selection
  44. // is not entirely valid.
  45. if ( !isSelectionEmpty && startElement == endElement ) {
  46. model.deleteContent( selection );
  47. }
  48. return;
  49. }
  50. if ( isSelectionEmpty ) {
  51. const attributesToCopy = getCopyOnEnterAttributes( writer.model.schema, selection.getAttributes() );
  52. splitBlock( writer, range.start );
  53. writer.setSelectionAttribute( attributesToCopy );
  54. } else {
  55. const leaveUnmerged = !( range.start.isAtStart && range.end.isAtEnd );
  56. const isContainedWithinOneElement = ( startElement == endElement );
  57. model.deleteContent( selection, { leaveUnmerged } );
  58. if ( leaveUnmerged ) {
  59. // Partially selected elements.
  60. //
  61. // <h>x[xx]x</h> -> <h>x^x</h> -> <h>x</h><h>^x</h>
  62. if ( isContainedWithinOneElement ) {
  63. splitBlock( writer, selection.focus );
  64. }
  65. // Selection over multiple elements.
  66. //
  67. // <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
  68. else {
  69. writer.setSelection( endElement, 0 );
  70. }
  71. }
  72. }
  73. }
  74. function splitBlock( writer, splitPos ) {
  75. writer.split( splitPos );
  76. writer.setSelection( splitPos.parent.nextSibling, 0 );
  77. }
  78. function* getCopyOnEnterAttributes( schema, allAttributes ) {
  79. for ( const attribute of allAttributes ) {
  80. if ( attribute && schema.getAttributeProperties( attribute[ 0 ] ).copyOnEnter ) {
  81. yield attribute;
  82. }
  83. }
  84. }