@ckeditor/ckeditor5-list
Version:
Ordered and unordered lists feature to CKEditor 5.
127 lines (126 loc) • 4.62 kB
JavaScript
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import { iterateSiblingListBlocks } from './listwalker.js';
import { getListItemBlocks, isListItemBlock, ListItemUid } from './model.js';
/**
* Based on the provided positions looks for the list head and stores it in the provided map.
*
* @internal
* @param position The search starting position.
* @param itemToListHead The map from list item element to the list head element.
*/
export function findAndAddListHeadToMap(position, itemToListHead) {
const previousNode = position.nodeBefore;
if (!isListItemBlock(previousNode)) {
const item = position.nodeAfter;
if (isListItemBlock(item)) {
itemToListHead.set(item, item);
}
}
else {
let listHead = previousNode;
// Previously, the loop below was defined like this:
//
// for ( { node: listHead } of iterateSiblingListBlocks( listHead, 'backward' ) )
//
// Unfortunately, such a destructuring is incorrectly transpiled by Babel and the loop never ends.
// See: https://github.com/ckeditor/ckeditor5-react/issues/345.
for (const { node } of iterateSiblingListBlocks(listHead, 'backward')) {
listHead = node;
if (itemToListHead.has(listHead)) {
return;
}
}
itemToListHead.set(previousNode, listHead);
}
}
/**
* Scans the list starting from the given list head element and fixes items' indentation.
*
* @internal
* @param listNodes The iterable of list nodes.
* @param writer The model writer.
* @returns Whether the model was modified.
*/
export function fixListIndents(listNodes, writer) {
let maxIndent = 0; // Guards local sublist max indents that need fixing.
let prevIndent = -1; // Previous item indent.
let fixBy = null;
let applied = false;
for (const { node } of listNodes) {
const itemIndent = node.getAttribute('listIndent');
if (itemIndent > maxIndent) {
let newIndent;
if (fixBy === null) {
fixBy = itemIndent - maxIndent;
newIndent = maxIndent;
}
else {
if (fixBy > itemIndent) {
fixBy = itemIndent;
}
newIndent = itemIndent - fixBy;
}
if (newIndent > prevIndent + 1) {
newIndent = prevIndent + 1;
}
writer.setAttribute('listIndent', newIndent, node);
applied = true;
prevIndent = newIndent;
}
else {
fixBy = null;
maxIndent = itemIndent + 1;
prevIndent = itemIndent;
}
}
return applied;
}
/**
* Scans the list starting from the given list head element and fixes items' types.
*
* @internal
* @param listNodes The iterable of list nodes.
* @param seenIds The set of already known IDs.
* @param writer The model writer.
* @returns Whether the model was modified.
*/
export function fixListItemIds(listNodes, seenIds, writer) {
const visited = new Set();
let applied = false;
for (const { node } of listNodes) {
if (visited.has(node)) {
continue;
}
let listType = node.getAttribute('listType');
let listItemId = node.getAttribute('listItemId');
// Use a new ID if this one was spot earlier (even in other list).
if (seenIds.has(listItemId)) {
listItemId = ListItemUid.next();
}
seenIds.add(listItemId);
// Make sure that all items in a simple list have unique IDs.
if (node.is('element', 'listItem')) {
if (node.getAttribute('listItemId') != listItemId) {
writer.setAttribute('listItemId', listItemId, node);
applied = true;
}
continue;
}
for (const block of getListItemBlocks(node, { direction: 'forward' })) {
visited.add(block);
// Use a new ID if a block of a bigger list item has different type.
if (block.getAttribute('listType') != listType) {
listItemId = ListItemUid.next();
listType = block.getAttribute('listType');
}
if (block.getAttribute('listItemId') != listItemId) {
writer.setAttribute('listItemId', listItemId, block);
applied = true;
}
}
}
return applied;
}