softbreakcommand.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /**
  2. * @module enter/softbreakcommand
  3. */
  4. import Command from '@ckeditor/ckeditor5-core/src/command';
  5. /**
  6. * Soft break command.
  7. *
  8. * @extends module:core/command~Command
  9. */
  10. export default class SoftBreakCommand extends Command {
  11. /**
  12. * @inheritDoc
  13. */
  14. execute() {
  15. const model = this.editor.model;
  16. const doc = model.document;
  17. model.change( writer => {
  18. softBreakAction( this.editor.model, writer, doc.selection, model.schema );
  19. this.fire( 'afterExecute', { writer } );
  20. } );
  21. }
  22. }
  23. // Creates a new block in the way that the <kbd>Enter</kbd> key is expected to work.
  24. //
  25. // @param {module:engine/model~Model} model
  26. // @param {module:engine/model/writer~Writer} writer
  27. // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  28. // Selection on which the action should be performed.
  29. // @param {module:engine/model/schema~Schema} schema
  30. function softBreakAction( model, writer, selection, schema ) {
  31. const isSelectionEmpty = selection.isCollapsed;
  32. const range = selection.getFirstRange();
  33. const startElement = range.start.parent;
  34. const endElement = range.end.parent;
  35. // Don't touch the roots and other limit elements.
  36. if ( schema.isLimit( startElement ) || schema.isLimit( endElement ) ) {
  37. // Delete the selected content but only if inside a single limit element.
  38. // Abort, when crossing limit elements boundary (e.g. <limit1>x[x</limit1>donttouchme<limit2>y]y</limit2>).
  39. // This is an edge case and it's hard to tell what should actually happen because such a selection
  40. // is not entirely valid.
  41. if ( !isSelectionEmpty && startElement == endElement ) {
  42. model.deleteContent( selection );
  43. }
  44. return;
  45. }
  46. if ( isSelectionEmpty ) {
  47. insertBreak( writer, selection, range.end );
  48. } else {
  49. const leaveUnmerged = !( range.start.isAtStart && range.end.isAtEnd );
  50. const isContainedWithinOneElement = ( startElement == endElement );
  51. model.deleteContent( selection, { leaveUnmerged } );
  52. if ( leaveUnmerged ) {
  53. // Partially selected elements.
  54. //
  55. // <h>x[xx]x</h> -> <h>x^x</h> -> <h>x</h><h>^x</h>
  56. if ( isContainedWithinOneElement ) {
  57. insertBreak( writer, selection, selection.focus );
  58. }
  59. // Selection over multiple elements.
  60. //
  61. // <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
  62. else {
  63. writer.setSelection( endElement, 0 );
  64. }
  65. }
  66. }
  67. }
  68. function insertBreak( writer, selection, position ) {
  69. const newBreak = writer.createElement( 'br' );
  70. if ( position.isAtEnd ) {
  71. // If the position is at the end of the element then insert the break at the end
  72. writer.insert( newBreak, position, 'after' );
  73. writer.setSelection( newBreak, 'after' );
  74. } else if ( position.isAtStart ) {
  75. // If the position is at the start of element, insert break at start
  76. writer.insert( newBreak, position, 'before' );
  77. writer.setSelection( newBreak, 'after' );
  78. } else {
  79. writer.insert( newBreak, position, 'end' );
  80. writer.setSelection( newBreak, 'after' );
  81. }
  82. }