Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated the enter feature with document lists #11098

Merged
merged 11 commits into from
Jan 12, 2022
66 changes: 64 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,68 @@ 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, the integration with the enter key is done after the default handler in EnterCommand.
niegowski marked this conversation as resolved.
Show resolved Hide resolved
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