UNPKG

6.05 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 { splitListItemBefore, expandListBlocksToCompleteItems, getListItemBlocks, getListItems, removeListAttributes, outdentFollowingItems, ListItemUid, sortBlocks, getSelectedBlockObject, isListItemBlock } from './utils/model';
7/**
8 * The list command. It is used by the {@link module:list/documentlist~DocumentList document list feature}.
9 */
10export default class DocumentListCommand extends Command {
11 /**
12 * Creates an instance of the command.
13 *
14 * @param editor The editor instance.
15 * @param type List type that will be handled by this command.
16 */
17 constructor(editor, type) {
18 super(editor);
19 this.type = type;
20 }
21 /**
22 * @inheritDoc
23 */
24 refresh() {
25 this.value = this._getValue();
26 this.isEnabled = this._checkEnabled();
27 }
28 /**
29 * Executes the list command.
30 *
31 * @fires execute
32 * @fires afterExecute
33 * @param options Command options.
34 * @param options.forceValue If set, it will force the command behavior. If `true`, the command will try to convert the
35 * selected items and potentially the neighbor elements to the proper list items. If set to `false` it will convert selected elements
36 * to paragraphs. If not set, the command will toggle selected elements to list items or paragraphs, depending on the selection.
37 */
38 execute(options = {}) {
39 const model = this.editor.model;
40 const document = model.document;
41 const selectedBlockObject = getSelectedBlockObject(model);
42 const blocks = Array.from(document.selection.getSelectedBlocks())
43 .filter(block => model.schema.checkAttribute(block, 'listType'));
44 // Whether we are turning off some items.
45 const turnOff = options.forceValue !== undefined ? !options.forceValue : this.value;
46 model.change(writer => {
47 if (turnOff) {
48 const lastBlock = blocks[blocks.length - 1];
49 // Split the first block from the list item.
50 const itemBlocks = getListItemBlocks(lastBlock, { direction: 'forward' });
51 const changedBlocks = [];
52 if (itemBlocks.length > 1) {
53 changedBlocks.push(...splitListItemBefore(itemBlocks[1], writer));
54 }
55 // Convert list blocks to plain blocks.
56 changedBlocks.push(...removeListAttributes(blocks, writer));
57 // Outdent items following the selected list item.
58 changedBlocks.push(...outdentFollowingItems(lastBlock, writer));
59 this._fireAfterExecute(changedBlocks);
60 }
61 // Turning on the list items for a collapsed selection inside a list item.
62 else if ((selectedBlockObject || document.selection.isCollapsed) && isListItemBlock(blocks[0])) {
63 const changedBlocks = getListItems(selectedBlockObject || blocks[0]);
64 for (const block of changedBlocks) {
65 writer.setAttribute('listType', this.type, block);
66 }
67 this._fireAfterExecute(changedBlocks);
68 }
69 // Turning on the list items for a non-collapsed selection.
70 else {
71 const changedBlocks = [];
72 for (const block of blocks) {
73 // Promote the given block to the list item.
74 if (!block.hasAttribute('listType')) {
75 writer.setAttributes({
76 listIndent: 0,
77 listItemId: ListItemUid.next(),
78 listType: this.type
79 }, block);
80 changedBlocks.push(block);
81 }
82 // Change the type of list item.
83 else {
84 for (const node of expandListBlocksToCompleteItems(block, { withNested: false })) {
85 if (node.getAttribute('listType') != this.type) {
86 writer.setAttribute('listType', this.type, node);
87 changedBlocks.push(node);
88 }
89 }
90 }
91 }
92 this._fireAfterExecute(changedBlocks);
93 }
94 });
95 }
96 /**
97 * Fires the `afterExecute` event.
98 *
99 * @param changedBlocks The changed list elements.
100 */
101 _fireAfterExecute(changedBlocks) {
102 this.fire('afterExecute', sortBlocks(new Set(changedBlocks)));
103 }
104 /**
105 * Checks the command's {@link #value}.
106 *
107 * @returns The current value.
108 */
109 _getValue() {
110 const selection = this.editor.model.document.selection;
111 const blocks = Array.from(selection.getSelectedBlocks());
112 if (!blocks.length) {
113 return false;
114 }
115 for (const block of blocks) {
116 if (block.getAttribute('listType') != this.type) {
117 return false;
118 }
119 }
120 return true;
121 }
122 /**
123 * Checks whether the command can be enabled in the current context.
124 *
125 * @returns Whether the command should be enabled.
126 */
127 _checkEnabled() {
128 const selection = this.editor.model.document.selection;
129 const schema = this.editor.model.schema;
130 const blocks = Array.from(selection.getSelectedBlocks());
131 if (!blocks.length) {
132 return false;
133 }
134 // If command value is true it means that we are in list item, so the command should be enabled.
135 if (this.value) {
136 return true;
137 }
138 for (const block of blocks) {
139 if (schema.checkAttribute(block, 'listType')) {
140 return true;
141 }
142 }
143 return false;
144 }
145}