UNPKG

5.32 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5import { Command } from 'ckeditor5/src/core';
6import { first } from 'ckeditor5/src/utils';
7/**
8 * The list indent command. It is used by the {@link module:list/list~List list feature}.
9 */
10export default class IndentCommand extends Command {
11 /**
12 * Creates an instance of the command.
13 *
14 * @param editor The editor instance.
15 * @param indentDirection The direction of indent. If it is equal to `backward`, the command will outdent a list item.
16 */
17 constructor(editor, indentDirection) {
18 super(editor);
19 this._indentBy = indentDirection == 'forward' ? 1 : -1;
20 }
21 /**
22 * @inheritDoc
23 */
24 refresh() {
25 this.isEnabled = this._checkEnabled();
26 }
27 /**
28 * Indents or outdents (depending on the {@link #constructor}'s `indentDirection` parameter) selected list items.
29 *
30 * @fires execute
31 */
32 execute() {
33 const model = this.editor.model;
34 const doc = model.document;
35 let itemsToChange = Array.from(doc.selection.getSelectedBlocks());
36 model.change(writer => {
37 const lastItem = itemsToChange[itemsToChange.length - 1];
38 // Indenting a list item should also indent all the items that are already sub-items of indented item.
39 let next = lastItem.nextSibling;
40 // Check all items after last indented item, as long as their indent is bigger than indent of that item.
41 while (next && next.name == 'listItem' &&
42 next.getAttribute('listIndent') > lastItem.getAttribute('listIndent')) {
43 itemsToChange.push(next);
44 next = next.nextSibling;
45 }
46 // We need to be sure to keep model in correct state after each small change, because converters
47 // bases on that state and assumes that model is correct.
48 // Because of that, if the command outdents items, we will outdent them starting from the last item, as
49 // it is safer.
50 if (this._indentBy < 0) {
51 itemsToChange = itemsToChange.reverse();
52 }
53 for (const item of itemsToChange) {
54 const indent = item.getAttribute('listIndent') + this._indentBy;
55 // If indent is lower than 0, it means that the item got outdented when it was not indented.
56 // This means that we need to convert that list item to paragraph.
57 if (indent < 0) {
58 // To keep the model as correct as possible, first rename listItem, then remove attributes,
59 // as listItem without attributes is very incorrect and will cause problems in converters.
60 // No need to remove attributes, will be removed by post fixer.
61 writer.rename(item, 'paragraph');
62 }
63 // If indent is >= 0, change the attribute value.
64 else {
65 writer.setAttribute('listIndent', indent, item);
66 }
67 }
68 // It allows to execute an action after executing the `~IndentCommand#execute` method, for example adjusting
69 // attributes of changed list items.
70 this.fire('_executeCleanup', itemsToChange);
71 });
72 }
73 /**
74 * Checks whether the command can be enabled in the current context.
75 *
76 * @returns Whether the command should be enabled.
77 */
78 _checkEnabled() {
79 // Check whether any of position's ancestor is a list item.
80 const listItem = first(this.editor.model.document.selection.getSelectedBlocks());
81 // If selection is not in a list item, the command is disabled.
82 if (!listItem || !listItem.is('element', 'listItem')) {
83 return false;
84 }
85 if (this._indentBy > 0) {
86 // Cannot indent first item in it's list. Check if before `listItem` is a list item that is in same list.
87 // To be in the same list, the item has to have same attributes and cannot be "split" by an item with lower indent.
88 const indent = listItem.getAttribute('listIndent');
89 const type = listItem.getAttribute('listType');
90 let prev = listItem.previousSibling;
91 while (prev && prev.is('element', 'listItem') && prev.getAttribute('listIndent') >= indent) {
92 if (prev.getAttribute('listIndent') == indent) {
93 // The item is on the same level.
94 // If it has same type, it means that we found a preceding sibling from the same list.
95 // If it does not have same type, it means that `listItem` is on different list (this can happen only
96 // on top level lists, though).
97 return prev.getAttribute('listType') == type;
98 }
99 prev = prev.previousSibling;
100 }
101 // Could not find similar list item, this means that `listItem` is first in its list.
102 return false;
103 }
104 // If we are outdenting it is enough to be in list item. Every list item can always be outdented.
105 return true;
106 }
107}