Skip to content

Commit

Permalink
Merge pull request #11098 from ckeditor/ck/10879-document-list-enter
Browse files Browse the repository at this point in the history
Internal (list): Integrated the enter feature with document lists. Closes #10879. Closes #10976.
  • Loading branch information
niegowski authored Jan 12, 2022
2 parents ba75822 + f46fcef commit 9cd6e85
Show file tree
Hide file tree
Showing 5 changed files with 2,067 additions and 447 deletions.
67 changes: 65 additions & 2 deletions packages/ckeditor5-list/src/documentlist/documentlistediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { CKEditorError } from 'ckeditor5/src/utils';

import DocumentListIndentCommand from './documentlistindentcommand';
import DocumentListCommand from './documentlistcommand';
import DocumentListSplitCommand from './documentlistsplitcommand';
import {
listItemDowncastConverter,
listItemParagraphDowncastConverter,
Expand All @@ -27,6 +28,11 @@ import {
fixListIndents,
fixListItemIds
} from './utils/postfixers';
import {
getAllListItemBlocks,
isFirstBlockOfListItem,
isLastBlockOfListItem
} from './utils/model';
import { iterateSiblingListBlocks } from './utils/listwalker';

import '../../theme/documentlist.css';
Expand Down Expand Up @@ -57,6 +63,8 @@ export default class DocumentListEditing extends Plugin {
init() {
const editor = this.editor;
const model = editor.model;
const commands = editor.commands;
const enterCommand = commands.get( 'enter' );

if ( editor.plugins.has( 'ListEditing' ) ) {
/**
Expand Down Expand Up @@ -84,14 +92,69 @@ export default class DocumentListEditing extends Plugin {

editor.commands.add( 'indentList', new DocumentListIndentCommand( editor, 'forward' ) );
editor.commands.add( 'outdentList', new DocumentListIndentCommand( editor, 'backward' ) );

editor.commands.add( 'splitListItem', new DocumentListSplitCommand( editor ) );

// Overwrite the default Enter key behavior: outdent or split the list in certain cases.
this.listenTo( editor.editing.view.document, 'enter', ( evt, data ) => {
const doc = model.document;
const positionParent = doc.selection.getFirstPosition().parent;

if ( doc.selection.isCollapsed && positionParent.hasAttribute( 'listItemId' ) && positionParent.isEmpty ) {
// * a → * a
// * [] → []
if ( isFirstBlockOfListItem( positionParent ) ) {
editor.execute( 'outdentList' );

data.preventDefault();
evt.stop();
}
// * a → * a
// [] → * []
else if ( isLastBlockOfListItem( positionParent ) ) {
editor.execute( 'splitListItem' );

data.preventDefault();
evt.stop();
}
}
}, { context: 'li' } );

// In some cases, after the default block splitting, we want to modify the new block to become a new list item
// instead of an additional block in the same list item.
this.listenTo( enterCommand, 'afterExecute', () => {
const splitCommand = commands.get( 'splitListItem' );

// The command has not refreshed because the change block related to EnterCommand#execute() is not over yet.
// Let's keep it up to date and take advantage of DocumentListSplitCommand#isEnabled.
splitCommand.refresh();

if ( !splitCommand.isEnabled ) {
return;
}

const doc = editor.model.document;
const positionParent = doc.selection.getLastPosition().parent;
const listItemBlocks = getAllListItemBlocks( positionParent );

// Keep in mind this split happens after the default enter handler was executed. For instance:
//
// │ Initial state │ After default enter │ Here in #afterExecute │
// ├───────────────────────────┼───────────────────────────┼───────────────────────────┤
// │ * a[] │ * a │ * a │
// │ │ [] │ * [] │
if ( listItemBlocks.length === 2 ) {
editor.execute( 'splitListItem' );
}
} );
}

/**
* @inheritDoc
*/
afterInit() {
const commands = this.editor.commands;

const editor = this.editor;
const commands = editor.commands;
const indent = commands.get( 'indent' );
const outdent = commands.get( 'outdent' );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module list/documentlist/documentlistsplitcommand
*/

import { Command } from 'ckeditor5/src/core';
import {
isFirstBlockOfListItem,
sortBlocks,
splitListItemBefore
} from './utils/model';

/**
* The document list split command that splits the list item at the selection.
*
* It is used by the {@link module:list/documentlist~DocumentList document list feature}.
*
* @extends module:core/command~Command
*/
export default class DocumentListSplitCommand extends Command {
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._checkEnabled();
}

/**
* Splits the list item at the selection.
*
* @fires execute
* @fires afterExecute
*/
execute() {
const editor = this.editor;

editor.model.change( writer => {
const positionParent = editor.model.document.selection.getFirstPosition().parent;
const changedBlocks = splitListItemBefore( positionParent, writer );

this._fireAfterExecute( changedBlocks );
} );
}

/**
* Fires the `afterExecute` event.
*
* @private
* @param {Array.<module:engine/model/element~Element>} changedBlocks The changed list elements.
*/
_fireAfterExecute( changedBlocks ) {
/**
* Event fired by the {@link #execute} method.
*
* It allows to execute an action after executing the {@link ~DocumentListSplitCommand#execute} method,
* for example adjusting attributes of changed list items.
*
* @protected
* @event afterExecute
*/
this.fire( 'afterExecute', sortBlocks( new Set( changedBlocks ) ) );
}

/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
const doc = this.editor.model.document;
const positionParent = doc.selection.getFirstPosition().parent;

return doc.selection.isCollapsed &&
positionParent.hasAttribute( 'listItemId' ) &&
!isFirstBlockOfListItem( positionParent );
}
}
Loading

0 comments on commit 9cd6e85

Please sign in to comment.