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 | */
|
5 | /**
|
6 | * @module list/documentlist/utils/listwalker
|
7 | */
|
8 | import { first, toArray } from 'ckeditor5/src/utils';
|
9 | import { isListItemBlock } from './model';
|
10 | /**
|
11 | * Document list blocks iterator.
|
12 | */
|
13 | export default class ListWalker {
|
14 | /**
|
15 | * Creates a document list iterator.
|
16 | *
|
17 | * @param startElement The start list item block element.
|
18 | * @param options.direction The iterating direction.
|
19 | * @param options.includeSelf Whether start block should be included in the result (if it's matching other criteria).
|
20 | * @param options.sameAttributes Additional attributes that must be the same for each block.
|
21 | * @param options.sameIndent Whether blocks with the same indent level as the start block should be included
|
22 | * in the result.
|
23 | * @param options.lowerIndent Whether blocks with a lower indent level than the start block should be included
|
24 | * in the result.
|
25 | * @param options.higherIndent Whether blocks with a higher indent level than the start block should be included
|
26 | * in the result.
|
27 | */
|
28 | constructor(startElement, options) {
|
29 | this._startElement = startElement;
|
30 | this._referenceIndent = startElement.getAttribute('listIndent');
|
31 | this._isForward = options.direction == 'forward';
|
32 | this._includeSelf = !!options.includeSelf;
|
33 | this._sameAttributes = toArray(options.sameAttributes || []);
|
34 | this._sameIndent = !!options.sameIndent;
|
35 | this._lowerIndent = !!options.lowerIndent;
|
36 | this._higherIndent = !!options.higherIndent;
|
37 | }
|
38 | /**
|
39 | * Performs only first step of iteration and returns the result.
|
40 | *
|
41 | * @param startElement The start list item block element.
|
42 | * @param options.direction The iterating direction.
|
43 | * @param options.includeSelf Whether start block should be included in the result (if it's matching other criteria).
|
44 | * @param options.sameAttributes Additional attributes that must be the same for each block.
|
45 | * @param options.sameIndent Whether blocks with the same indent level as the start block should be included
|
46 | * in the result.
|
47 | * @param options.lowerIndent Whether blocks with a lower indent level than the start block should be included
|
48 | * in the result.
|
49 | * @param options.higherIndent Whether blocks with a higher indent level than the start block should be included
|
50 | * in the result.
|
51 | */
|
52 | static first(startElement, options) {
|
53 | const walker = new this(startElement, options);
|
54 | const iterator = walker[Symbol.iterator]();
|
55 | return first(iterator);
|
56 | }
|
57 | /**
|
58 | * Iterable interface.
|
59 | */
|
60 | *[Symbol.iterator]() {
|
61 | const nestedItems = [];
|
62 | for (const { node } of iterateSiblingListBlocks(this._getStartNode(), this._isForward ? 'forward' : 'backward')) {
|
63 | const indent = node.getAttribute('listIndent');
|
64 | // Leaving a nested list.
|
65 | if (indent < this._referenceIndent) {
|
66 | // Abort searching blocks.
|
67 | if (!this._lowerIndent) {
|
68 | break;
|
69 | }
|
70 | // While searching for lower indents, update the reference indent to find another parent in the next step.
|
71 | this._referenceIndent = indent;
|
72 | }
|
73 | // Entering a nested list.
|
74 | else if (indent > this._referenceIndent) {
|
75 | // Ignore nested blocks.
|
76 | if (!this._higherIndent) {
|
77 | continue;
|
78 | }
|
79 | // Collect nested blocks to verify if they are really nested, or it's a different item.
|
80 | if (!this._isForward) {
|
81 | nestedItems.push(node);
|
82 | continue;
|
83 | }
|
84 | }
|
85 | // Same indent level block.
|
86 | else {
|
87 | // Ignore same indent block.
|
88 | if (!this._sameIndent) {
|
89 | // While looking for nested blocks, stop iterating while encountering first same indent block.
|
90 | if (this._higherIndent) {
|
91 | // No more nested blocks so yield nested items.
|
92 | if (nestedItems.length) {
|
93 | yield* nestedItems;
|
94 | nestedItems.length = 0;
|
95 | }
|
96 | break;
|
97 | }
|
98 | continue;
|
99 | }
|
100 | // Abort if item has any additionally specified attribute different.
|
101 | if (this._sameAttributes.some(attr => node.getAttribute(attr) !== this._startElement.getAttribute(attr))) {
|
102 | break;
|
103 | }
|
104 | }
|
105 | // There is another block for the same list item so the nested items were in the same list item.
|
106 | if (nestedItems.length) {
|
107 | yield* nestedItems;
|
108 | nestedItems.length = 0;
|
109 | }
|
110 | yield node;
|
111 | }
|
112 | }
|
113 | /**
|
114 | * Returns the model element to start iterating.
|
115 | */
|
116 | _getStartNode() {
|
117 | if (this._includeSelf) {
|
118 | return this._startElement;
|
119 | }
|
120 | return this._isForward ?
|
121 | this._startElement.nextSibling :
|
122 | this._startElement.previousSibling;
|
123 | }
|
124 | }
|
125 | /**
|
126 | * Iterates sibling list blocks starting from the given node.
|
127 | *
|
128 | * @internal
|
129 | * @param node The model node.
|
130 | * @param direction Iteration direction.
|
131 | * @returns The object with `node` and `previous` {@link module:engine/model/element~Element blocks}.
|
132 | */
|
133 | export function* iterateSiblingListBlocks(node, direction = 'forward') {
|
134 | const isForward = direction == 'forward';
|
135 | let previous = null;
|
136 | while (isListItemBlock(node)) {
|
137 | yield { node, previous };
|
138 | previous = node;
|
139 | node = isForward ? node.nextSibling : node.previousSibling;
|
140 | }
|
141 | }
|
142 | /**
|
143 | * The iterable protocol over the list elements.
|
144 | *
|
145 | * @internal
|
146 | */
|
147 | export class ListBlocksIterable {
|
148 | /**
|
149 | * @param listHead The head element of a list.
|
150 | */
|
151 | constructor(listHead) {
|
152 | this._listHead = listHead;
|
153 | }
|
154 | /**
|
155 | * List blocks iterator.
|
156 | *
|
157 | * Iterates over all blocks of a list.
|
158 | */
|
159 | [Symbol.iterator]() {
|
160 | return iterateSiblingListBlocks(this._listHead, 'forward');
|
161 | }
|
162 | }
|