/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ import Command from '../command/command.js'; import Element from '../engine/model/element.js'; import LivePosition from '../engine/model/liveposition.js'; import Position from '../engine/model/position.js'; /** * The Enter command. It is used by the {@link enter.Enter Enter feature} to handle the Enter key. * * @member enter * @extends ckeditor5.command.Command */ export default class EnterCommand extends Command { _doExecute() { const doc = this.editor.document; doc.enqueueChanges( () => { enterBlock( doc.batch(), doc.selection, { defaultBlockName: 'paragraph' } ); } ); } } /** * Creates a new block in the way that the Enter key is expected to work. * * @param {engine.model.Batch} batch A batch to which the deltas will be added. * @param {engine.model.Selection} selection * @param {Object} options * @param {Boolean} options.defaultBlockName The name of the block which should be created when Enter leaves * another block. Usually set to `'paragraph'`. For example, when creating a block in `foo^`, * the result will be `foo^`. */ export function enterBlock( batch, selection, options = {} ) { const defaultBlockName = options.defaultBlockName; const doc = batch.document; const isSelectionEmpty = selection.isCollapsed; const range = selection.getFirstRange(); const startElement = range.start.parent; const endElement = range.end.parent; // Don't touch the root. if ( startElement.root == startElement ) { if ( !isSelectionEmpty ) { doc.composer.deleteContents( batch, selection ); } return; } if ( isSelectionEmpty ) { splitBlock( batch, selection, range.start, defaultBlockName ); } else { const shouldMerge = range.start.isAtStart && range.end.isAtEnd; const isContainedWithinOneElement = ( startElement == endElement ); doc.composer.deleteContents( batch, selection, { merge: shouldMerge } ); // Fully selected elements. // // [xx

yy]

-> ^ ->

^

// [xxyy] -> ^ ->

^

if ( shouldMerge ) { // We'll lose the ref to the renamed element, so let's keep a position inside it // (offsets won't change, so it will stay in place). See ckeditor5-engine#367. const pos = Position.createFromPosition( selection.focus ); const newBlockName = getNewBlockName( doc, startElement, defaultBlockName ); if ( startElement.name != newBlockName ) { batch.rename( newBlockName, startElement ); } selection.collapse( pos ); } // Partially selected elements. // // x[xx]x -> x^x -> x^x else if ( isContainedWithinOneElement ) { splitBlock( batch, selection, selection.focus, defaultBlockName ); } // Selection over multilpe elements. // // x[x

y]y

-> x^

y

-> x

^y

else { selection.collapse( endElement ); } } } function splitBlock( batch, selection, splitPos, defaultBlockName ) { const doc = batch.document; const parent = splitPos.parent; if ( splitPos.isAtEnd ) { const newElement = new Element( getNewBlockName( doc, parent, defaultBlockName ) ); batch.insert( Position.createAfter( parent ), newElement ); selection.collapse( newElement ); } else { // TODO After ckeditor5-engine#340 is fixed we'll be able to base on splitPos's location. const endPos = LivePosition.createFromPosition( splitPos ); endPos.stickiness = 'STICKS_TO_NEXT'; batch.split( splitPos ); selection.collapse( endPos ); endPos.detach(); } } function getNewBlockName( doc, startElement, defaultBlockName ) { if ( doc.schema.check( { name: defaultBlockName, inside: startElement.parent.name } ) ) { return defaultBlockName; } return startElement.name; }