Skip to content

Commit

Permalink
Merge pull request #7861 from ckeditor/i/7801
Browse files Browse the repository at this point in the history
Feature (list): Introduced the list styles feature that allows customizing the list's marker of the list item elements. Closes #7801.

Feature (theme-lark): Creates styles for the `ListStylesUI` plugin (see #7803).

MINOR BREAKING CHANGE (ui): It is now possible to override existing components when [adding new ones](https://ckeditor.com/docs/ckeditor5/latest/api/module_ui_componentfactory-ComponentFactory.html#function-add) to the [component factory](https://ckeditor.com/docs/ckeditor5/latest/api/module_ui_componentfactory-ComponentFactory.html) (previously an error was thrown) (see #7803).
  • Loading branch information
jodator authored Aug 21, 2020
2 parents 6dd6c45 + b3d5eb0 commit 137dd28
Show file tree
Hide file tree
Showing 31 changed files with 3,162 additions and 29 deletions.
22 changes: 21 additions & 1 deletion packages/ckeditor5-list/lang/contexts.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
{
"Numbered List": "Toolbar button tooltip for the Numbered List feature.",
"Bulleted List": "Toolbar button tooltip for the Bulleted List feature.",
"To-do List": "Toolbar button tooltip for the To-do List feature."
"To-do List": "Toolbar button tooltip for the To-do List feature.",
"Bulleted list styles toolbar": "The ARIA label of the toolbar displaying buttons allowing users to change the bulleted list style.",
"Numbered list styles toolbar": "The ARIA label of the toolbar displaying buttons allowing users to change the numbered list style.",
"Toggle the disc list style": "The ARIA label of the button that toggles the \"disc\" list style.",
"Toggle the circle list style": "The ARIA label of the button that toggles the \"circle\" list style.",
"Toggle the square list style": "The ARIA label of the button that toggles the \"square\" list style.",
"Toggle the decimal list style": "The ARIA label of the button that toggles the \"decimal\" list style.",
"Toggle the decimal with leading zero list style": "The ARIA label of the button that toggles the \"decimal with leading zero\" list style.",
"Toggle the lower–roman list style": "The ARIA label of the button that toggles the \"lower–roman\" list style.",
"Toggle the upper–roman list style": "The ARIA label of the button that toggles the \"upper–roman\" list style.",
"Toggle the lower–latin list style": "The ARIA label of the button that toggles the \"lower–latin\" list style.",
"Toggle the upper–latin list style": "The ARIA label of the button that toggles the \"upper–latin\" list style.",
"Disc": "The tooltip text of the button that toggles the \"disc\" list style.",
"Circle": "The tooltip text of the button that toggles the \"circle\" list style.",
"Square": "The tooltip text of the button that toggles the \"square\" list style.",
"Decimal": "The tooltip text of the button that toggles the \"decimal\" list style.",
"Decimal with leading zero": "The tooltip text of the button that toggles the \"decimal with leading zero\" list style.",
"Lower–roman": "The tooltip text of the button that toggles the \"lower–roman\" list style.",
"Upper-roman": "The tooltip text of the button that toggles the \"upper–roman\" list style.",
"Lower-latin": "The tooltip text of the button that toggles the \"lower–latin\" list style.",
"Upper-latin": "The tooltip text of the button that toggles the \"upper–latin\" list style."
}
2 changes: 2 additions & 0 deletions packages/ckeditor5-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
"@ckeditor/ckeditor5-basic-styles": "^21.0.0",
"@ckeditor/ckeditor5-block-quote": "^21.0.0",
"@ckeditor/ckeditor5-clipboard": "^21.0.0",
"@ckeditor/ckeditor5-essentials": "^21.0.0",
"@ckeditor/ckeditor5-editor-classic": "^21.0.0",
"@ckeditor/ckeditor5-enter": "^21.0.0",
"@ckeditor/ckeditor5-font": "^21.0.0",
"@ckeditor/ckeditor5-heading": "^21.0.0",
"@ckeditor/ckeditor5-highlight": "^21.0.0",
"@ckeditor/ckeditor5-indent": "^21.0.0",
"@ckeditor/ckeditor5-link": "^21.0.0",
"@ckeditor/ckeditor5-remove-format": "^21.0.0",
"@ckeditor/ckeditor5-table": "^21.0.0",
"@ckeditor/ckeditor5-typing": "^21.0.0",
"@ckeditor/ckeditor5-undo": "^21.0.0"
Expand Down
6 changes: 6 additions & 0 deletions packages/ckeditor5-list/src/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,12 @@ export function modelChangePostFixer( model, writer ) {
applied = true;
}

if ( item.hasAttribute( 'listStyle' ) ) {
writer.removeAttribute( 'listStyle', item );

applied = true;
}

for ( const innerItem of Array.from( model.createRangeIn( item ) ).filter( e => e.item.is( 'element', 'listItem' ) ) ) {
_addListToFix( innerItem.previousPosition );
}
Expand Down
12 changes: 12 additions & 0 deletions packages/ckeditor5-list/src/indentcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default class IndentCommand extends Command {
* Indents or outdents (depending on the {@link #constructor}'s `indentDirection` parameter) selected list items.
*
* @fires execute
* @fires _executeCleanup
*/
execute() {
const model = this.editor.model;
Expand Down Expand Up @@ -90,6 +91,17 @@ export default class IndentCommand extends Command {
writer.setAttribute( 'listIndent', indent, item );
}
}

/**
* Event fired by the {@link #execute} method.
*
* It allows to execute an action after executing the {@link ~IndentCommand#execute} method, e.g. adjusting
* attributes of changed list items.
*
* @protected
* @event _executeCleanup
*/
this.fire( '_executeCleanup', itemsToChange );
} );
}

Expand Down
11 changes: 11 additions & 0 deletions packages/ckeditor5-list/src/listcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ export default class ListCommand extends Command {
writer.setAttribute( 'listType', this.type, element );
}
}

/**
* Event fired by the {@link #execute} method.
*
* It allows to execute an action after executing the {@link ~ListCommand#execute} method, e.g. adjusting
* attributes of changed blocks.
*
* @protected
* @event _executeCleanup
*/
this.fire( '_executeCleanup', blocks );
} );
}

Expand Down
36 changes: 36 additions & 0 deletions packages/ckeditor5-list/src/liststyle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module list/liststyle
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ListStyleEditing from './liststyleediting';
import ListStyleUI from './liststyleui';

/**
* The list styles feature.
*
* This is a "glue" plugin that loads the {@link module:list/liststyleediting~ListStyleEditing list styles editing feature}
* and the {@link module:list/liststyleui~ListStyleUI list styles UI feature}.
*
* @extends module:core/plugin~Plugin
*/
export default class ListStyle extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ListStyleEditing, ListStyleUI ];
}

/**
* @inheritDoc
*/
static get pluginName() {
return 'ListStyle';
}
}
197 changes: 197 additions & 0 deletions packages/ckeditor5-list/src/liststylecommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module list/liststylecommand
*/

import Command from '@ckeditor/ckeditor5-core/src/command';
import TreeWalker from '@ckeditor/ckeditor5-engine/src/model/treewalker';

/**
* The list style command. It is used by the {@link module:list/liststyle~ListStyle list styles feature}.
*
* @extends module:core/command~Command
*/
export default class ListStyleCommand extends Command {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {String} defaultType The list type that will be used by default if the value was not specified during
* the command execution.
*/
constructor( editor, defaultType ) {
super( editor );

/**
* The default type of the list style.
*
* @protected
* @member {String}
*/
this._defaultType = defaultType;
}

/**
* @inheritDoc
*/
refresh() {
this.value = this._getValue();
this.isEnabled = this._checkEnabled();
}

/**
* Executes the command.
*
* @param {Object} options
* @param {String|null} options.type The type of the list styles, e.g. 'disc' or 'square'. If `null` specified, the default
* style will be applied.
* @protected
*/
execute( options = {} ) {
const model = this.editor.model;
const document = model.document;

// For all selected blocks find all list items that are being selected
// and update the `listStyle` attribute in those lists.
let listItems = [ ...document.selection.getSelectedBlocks() ]
.filter( element => element.is( 'element', 'listItem' ) )
.map( element => {
const position = model.change( writer => writer.createPositionAt( element, 0 ) );

return [
...getSiblingNodes( position, 'backward' ),
...getSiblingNodes( position, 'forward' )
];
} )
.flat();

// Since `getSelectedBlocks()` can return items that belong to the same list, and
// `getSiblingNodes()` returns the entire list, we need to remove duplicated items.
listItems = [ ...new Set( listItems ) ];

if ( !listItems.length ) {
return;
}

model.change( writer => {
for ( const item of listItems ) {
writer.setAttribute( 'listStyle', options.type || this._defaultType, item );
}
} );
}

/**
* Checks the command's {@link #value}.
*
* @private
* @returns {String|null} The current value.
*/
_getValue() {
const listItem = this.editor.model.document.selection.getFirstPosition().parent;

if ( listItem && listItem.is( 'element', 'listItem' ) ) {
return listItem.getAttribute( 'listStyle' );
}

return null;
}

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

const numberedList = editor.commands.get( 'numberedList' );
const bulletedList = editor.commands.get( 'bulletedList' );

return numberedList.isEnabled || bulletedList.isEnabled;
}
}

// Returns an array with all `listItem` elements that represents the same list.
//
// It means that values for `listIndent`, `listType`, and `listStyle` for all items
// are equal.
//
// @param {module:engine/model/position~Position} position Starting position.
// @param {'forward'|'backward'} direction Walking direction.
// @returns {Array.<module:engine/model/element~Element>
function getSiblingNodes( position, direction ) {
const items = [];
const listItem = position.parent;
const walkerOptions = {
ignoreElementEnd: true,
startPosition: position,
shallow: true,
direction
};
const limitIndent = listItem.getAttribute( 'listIndent' );
const nodes = [ ...new TreeWalker( walkerOptions ) ]
.filter( value => value.item.is( 'element' ) )
.map( value => value.item );

for ( const element of nodes ) {
// If found something else than `listItem`, we're out of the list scope.
if ( !element.is( 'element', 'listItem' ) ) {
break;
}

// If current parsed item has lower indent that element that the element that was a starting point,
// it means we left a nested list. Abort searching items.
//
// ■ List item 1. [listIndent=0]
// ○ List item 2.[] [listIndent=1], limitIndent = 1,
// ○ List item 3. [listIndent=1]
// ■ List item 4. [listIndent=0]
//
// Abort searching when leave nested list.
if ( element.getAttribute( 'listIndent' ) < limitIndent ) {
break;
}

// ■ List item 1.[] [listIndent=0] limitIndent = 0,
// ○ List item 2. [listIndent=1]
// ○ List item 3. [listIndent=1]
// ■ List item 4. [listIndent=0]
//
// Ignore nested lists.
if ( element.getAttribute( 'listIndent' ) > limitIndent ) {
continue;
}

// ■ List item 1.[] [listType=bulleted]
// 1. List item 2. [listType=numbered]
// 2.List item 3. [listType=numbered]
//
// Abort searching when found a different kind of a list.
if ( element.getAttribute( 'listType' ) !== listItem.getAttribute( 'listType' ) ) {
break;
}

// ■ List item 1.[] [listType=bulleted]
// ■ List item 2. [listType=bulleted]
// ○ List item 3. [listType=bulleted]
// ○ List item 4. [listType=bulleted]
//
// Abort searching when found a different list style.
if ( element.getAttribute( 'listStyle' ) !== listItem.getAttribute( 'listStyle' ) ) {
break;
}

if ( direction === 'backward' ) {
items.unshift( element );
} else {
items.push( element );
}
}

return items;
}
Loading

0 comments on commit 137dd28

Please sign in to comment.