8
0

shiftentercommand.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 enter/shiftentercommand
  7. */
  8. import Command from '@ckeditor/ckeditor5-core/src/command';
  9. import { getCopyOnEnterAttributes } from './utils';
  10. /**
  11. * ShiftEnter command. It is used by the {@link module:enter/shiftenter~ShiftEnter ShiftEnter feature} to handle
  12. * the <kbd>Shift</kbd>+<kbd>Enter</kbd> keystroke.
  13. *
  14. * @extends module:core/command~Command
  15. */
  16. export default class ShiftEnterCommand extends Command {
  17. /**
  18. * @inheritDoc
  19. */
  20. execute() {
  21. const model = this.editor.model;
  22. const doc = model.document;
  23. model.change( writer => {
  24. softBreakAction( model, writer, doc.selection );
  25. this.fire( 'afterExecute', { writer } );
  26. } );
  27. }
  28. refresh() {
  29. const model = this.editor.model;
  30. const doc = model.document;
  31. this.isEnabled = isEnabled( model.schema, doc.selection );
  32. }
  33. }
  34. // Checks whether the ShiftEnter command should be enabled in the specified selection.
  35. //
  36. // @param {module:engine/model/schema~Schema} schema
  37. // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  38. function isEnabled( schema, selection ) {
  39. // At this moment it is okay to support single range selections only.
  40. // But in the future we may need to change that.
  41. if ( selection.rangeCount > 1 ) {
  42. return false;
  43. }
  44. const anchorPos = selection.anchor;
  45. // Check whether the break element can be inserted in the current selection anchor.
  46. if ( !anchorPos || !schema.checkChild( anchorPos, 'softBreak' ) ) {
  47. return false;
  48. }
  49. const range = selection.getFirstRange();
  50. const startElement = range.start.parent;
  51. const endElement = range.end.parent;
  52. // Do not modify the content if selection is cross-limit elements.
  53. if ( ( isInsideLimitElement( startElement, schema ) || isInsideLimitElement( endElement, schema ) ) && startElement !== endElement ) {
  54. return false;
  55. }
  56. return true;
  57. }
  58. // Creates a break in the way that the <kbd>Shift</kbd>+<kbd>Enter</kbd> keystroke is expected to work.
  59. //
  60. // @param {module:engine/model~Model} model
  61. // @param {module:engine/model/writer~Writer} writer
  62. // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  63. // Selection on which the action should be performed.
  64. function softBreakAction( model, writer, selection ) {
  65. const isSelectionEmpty = selection.isCollapsed;
  66. const range = selection.getFirstRange();
  67. const startElement = range.start.parent;
  68. const endElement = range.end.parent;
  69. const isContainedWithinOneElement = ( startElement == endElement );
  70. if ( isSelectionEmpty ) {
  71. const attributesToCopy = getCopyOnEnterAttributes( model.schema, selection.getAttributes() );
  72. insertBreak( model, writer, range.end );
  73. writer.removeSelectionAttribute( selection.getAttributeKeys() );
  74. writer.setSelectionAttribute( attributesToCopy );
  75. } else {
  76. const leaveUnmerged = !( range.start.isAtStart && range.end.isAtEnd );
  77. model.deleteContent( selection, { leaveUnmerged } );
  78. // Selection within one element:
  79. //
  80. // <h>x[xx]x</h> -> <h>x^x</h> -> <h>x<br>^x</h>
  81. if ( isContainedWithinOneElement ) {
  82. insertBreak( model, writer, selection.focus );
  83. }
  84. // Selection over multiple elements.
  85. //
  86. // <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
  87. //
  88. // We chose not to insert a line break in this case because:
  89. //
  90. // * it's not a very common scenario,
  91. // * it actually surprised me when I saw the "expected behavior" in real life.
  92. //
  93. // It's ok if the user will need to be more specific where they want the <br> to be inserted.
  94. else {
  95. // Move the selection to the 2nd element (last step of the example above).
  96. if ( leaveUnmerged ) {
  97. writer.setSelection( endElement, 0 );
  98. }
  99. }
  100. }
  101. }
  102. function insertBreak( model, writer, position ) {
  103. const breakLineElement = writer.createElement( 'softBreak' );
  104. model.insertContent( breakLineElement, position );
  105. writer.setSelection( breakLineElement, 'after' );
  106. }
  107. // Checks whether the specified `element` is a child of the limit element.
  108. //
  109. // Checking whether the `<p>` element is inside a limit element:
  110. // - <$root><p>Text.</p></$root> => false
  111. // - <$root><limitElement><p>Text</p></limitElement></$root> => true
  112. //
  113. // @param {module:engine/model/element~Element} element
  114. // @param {module:engine/schema~Schema} schema
  115. // @returns {Boolean}
  116. function isInsideLimitElement( element, schema ) {
  117. // `$root` is a limit element but in this case is an invalid element.
  118. if ( element.is( 'rootElement' ) ) {
  119. return false;
  120. }
  121. return schema.isLimit( element ) || isInsideLimitElement( element.parent, schema );
  122. }