From a7c9febb9f9fbe4641223c8ae7ee22fcf15182e0 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 15:38:43 +0300 Subject: [PATCH 01/24] Rewrite slate-lists code for simpler interfaces --- packages/slate-lists/src/Lists.ts | 57 ------------------- packages/slate-lists/src/index.ts | 16 +----- .../slate-lists/src/lib/canDeleteBackward.ts | 11 ++-- .../slate-lists/src/lib/createDefaultNode.ts | 12 ---- packages/slate-lists/src/lib/createList.ts | 8 --- .../slate-lists/src/lib/createListItem.ts | 15 ----- .../slate-lists/src/lib/createListItemText.ts | 13 ----- packages/slate-lists/src/lib/decreaseDepth.ts | 9 ++- .../src/lib/decreaseListItemDepth.ts | 20 +++---- .../src/lib/getListItemsInRange.ts | 13 +---- packages/slate-lists/src/lib/getListType.ts | 20 +++++-- .../slate-lists/src/lib/getListsInRange.ts | 14 ++--- packages/slate-lists/src/lib/getNestedList.ts | 20 +++---- packages/slate-lists/src/lib/getParentList.ts | 20 ++----- .../slate-lists/src/lib/getParentListItem.ts | 22 ++----- packages/slate-lists/src/lib/increaseDepth.ts | 12 ++-- .../src/lib/increaseListItemDepth.ts | 17 ++---- packages/slate-lists/src/lib/index.ts | 7 --- .../src/lib/isCursorAtStartOfListItem.ts | 7 +-- .../src/lib/isCursorInEmptyListItem.ts | 9 ++- packages/slate-lists/src/lib/isList.ts | 11 ---- packages/slate-lists/src/lib/isListItem.ts | 11 ---- .../slate-lists/src/lib/isListItemText.ts | 11 ---- .../src/lib/listItemContainsText.ts | 16 ++---- .../lib/mergeListWithPreviousSiblingList.ts | 18 +++--- .../src/lib/moveListItemsToAnotherList.ts | 11 ++-- .../slate-lists/src/lib/moveListToListItem.ts | 12 ++-- packages/slate-lists/src/lib/normalizeList.ts | 14 ++--- .../src/lib/normalizeListChildren.ts | 40 +++++-------- .../src/lib/normalizeListItemChildren.ts | 49 +++++----------- .../src/lib/normalizeListItemTextChildren.ts | 9 +-- .../src/lib/normalizeOrphanListItem.ts | 18 ++---- .../src/lib/normalizeOrphanListItemText.ts | 18 ++---- .../src/lib/normalizeOrphanNestedList.ts | 19 +++---- .../src/lib/normalizeSiblingLists.ts | 14 ++--- packages/slate-lists/src/lib/setListType.ts | 9 ++- packages/slate-lists/src/lib/splitListItem.ts | 18 +++--- packages/slate-lists/src/lib/unwrapList.ts | 10 ++-- packages/slate-lists/src/lib/wrapInList.ts | 41 ++++++------- packages/slate-lists/src/types.ts | 57 ++++++++++--------- packages/slate-lists/src/withLists.ts | 50 ++++++++++------ 41 files changed, 256 insertions(+), 522 deletions(-) delete mode 100644 packages/slate-lists/src/Lists.ts delete mode 100644 packages/slate-lists/src/lib/createDefaultNode.ts delete mode 100644 packages/slate-lists/src/lib/createList.ts delete mode 100644 packages/slate-lists/src/lib/createListItem.ts delete mode 100644 packages/slate-lists/src/lib/createListItemText.ts delete mode 100644 packages/slate-lists/src/lib/isList.ts delete mode 100644 packages/slate-lists/src/lib/isListItem.ts delete mode 100644 packages/slate-lists/src/lib/isListItemText.ts diff --git a/packages/slate-lists/src/Lists.ts b/packages/slate-lists/src/Lists.ts deleted file mode 100644 index 82b7fddfa..000000000 --- a/packages/slate-lists/src/Lists.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - canDeleteBackward, - decreaseDepth, - decreaseListItemDepth, - getListItemsInRange, - getListsInRange, - getListType, - getNestedList, - getParentList, - getParentListItem, - increaseDepth, - increaseListItemDepth, - isCursorAtStartOfListItem, - isCursorInEmptyListItem, - isList, - isListItem, - isListItemText, - listItemContainsText, - moveListItemsToAnotherList, - moveListToListItem, - setListType, - splitListItem, - unwrapList, - wrapInList, -} from './lib'; -import type { ListsOptions } from './types'; - -/** - * Creates an API adapter with functions bound to passed options. - */ -export function Lists(options: ListsOptions) { - return { - canDeleteBackward: canDeleteBackward.bind(null, options), - decreaseDepth: decreaseDepth.bind(null, options), - decreaseListItemDepth: decreaseListItemDepth.bind(null, options), - getListItemsInRange: getListItemsInRange.bind(null, options), - getListsInRange: getListsInRange.bind(null, options), - getListType: getListType.bind(null, options), - getNestedList: getNestedList.bind(null, options), - getParentList: getParentList.bind(null, options), - getParentListItem: getParentListItem.bind(null, options), - increaseDepth: increaseDepth.bind(null, options), - increaseListItemDepth: increaseListItemDepth.bind(null, options), - isCursorAtStartOfListItem: isCursorAtStartOfListItem.bind(null, options), - isCursorInEmptyListItem: isCursorInEmptyListItem.bind(null, options), - isList: isList.bind(null, options), - isListItem: isListItem.bind(null, options), - isListItemText: isListItemText.bind(null, options), - listItemContainsText: listItemContainsText.bind(null, options), - moveListItemsToAnotherList: moveListItemsToAnotherList.bind(null, options), - moveListToListItem: moveListToListItem.bind(null, options), - setListType: setListType.bind(null, options), - splitListItem: splitListItem.bind(null, options), - unwrapList: unwrapList.bind(null, options), - wrapInList: wrapInList.bind(null, options), - }; -} diff --git a/packages/slate-lists/src/index.ts b/packages/slate-lists/src/index.ts index 3f10af1c7..a5e43ab32 100644 --- a/packages/slate-lists/src/index.ts +++ b/packages/slate-lists/src/index.ts @@ -1,18 +1,4 @@ -import type { ElementNode, TextNode } from '@prezly/slate-types'; -import type { BaseEditor } from 'slate'; -import type { HistoryEditor } from 'slate-history'; -import type { ReactEditor } from 'slate-react'; - -declare module 'slate' { - interface CustomTypes { - Editor: BaseEditor & ReactEditor & HistoryEditor; - Element: ElementNode; - Text: TextNode; - } -} - -export * from './lib'; -export { Lists } from './Lists'; +export * as ListsEditor from './lib'; export * from './types'; export { withLists } from './withLists'; export { withListsReact } from './withListsReact'; diff --git a/packages/slate-lists/src/lib/canDeleteBackward.ts b/packages/slate-lists/src/lib/canDeleteBackward.ts index 30f8ad4e2..73c31e650 100644 --- a/packages/slate-lists/src/lib/canDeleteBackward.ts +++ b/packages/slate-lists/src/lib/canDeleteBackward.ts @@ -1,7 +1,6 @@ import { EditorCommands } from '@prezly/slate-commons'; -import type { Editor } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getListItemsInRange } from './getListItemsInRange'; import { getParentListItem } from './getParentListItem'; @@ -10,15 +9,15 @@ import { isCursorAtStartOfListItem } from './isCursorAtStartOfListItem'; /** * Returns true when editor.deleteBackward() is safe to call (it won't break the structure). */ -export function canDeleteBackward(options: ListsOptions, editor: Editor): boolean { - const listItemsInSelection = getListItemsInRange(options, editor, editor.selection); +export function canDeleteBackward(editor: ListsEditor): boolean { + const listItemsInSelection = getListItemsInRange(editor, editor.selection); if (listItemsInSelection.length === 0) { return true; } const [[, listItemPath]] = listItemsInSelection; - const isInNestedList = getParentListItem(options, editor, listItemPath) !== null; + const isInNestedList = getParentListItem(editor, listItemPath) !== null; const isFirstListItem = EditorCommands.getPreviousSibling(editor, listItemPath) === null; - return isInNestedList || !isFirstListItem || !isCursorAtStartOfListItem(options, editor); + return isInNestedList || !isFirstListItem || !isCursorAtStartOfListItem(editor); } diff --git a/packages/slate-lists/src/lib/createDefaultNode.ts b/packages/slate-lists/src/lib/createDefaultNode.ts deleted file mode 100644 index 87aa5aeee..000000000 --- a/packages/slate-lists/src/lib/createDefaultNode.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Element } from 'slate'; - -import type { ListsOptions } from '../types'; - -export function createDefaultNode(options: ListsOptions): Element { - return { - children: [], - // @prezly/slate-lists package should not assume what default block type is. - // It is up to @prezly/slate-lists package user to ensure that they pass correct options - type: options.defaultBlockType as any, - }; -} diff --git a/packages/slate-lists/src/lib/createList.ts b/packages/slate-lists/src/lib/createList.ts deleted file mode 100644 index e65c08cd7..000000000 --- a/packages/slate-lists/src/lib/createList.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ListNode } from '@prezly/slate-types'; - -export function createList(type: string, children: ListNode['children'] = []): ListNode { - return { - children, - type: type as ListNode['type'], - }; -} diff --git a/packages/slate-lists/src/lib/createListItem.ts b/packages/slate-lists/src/lib/createListItem.ts deleted file mode 100644 index 2aba75b86..000000000 --- a/packages/slate-lists/src/lib/createListItem.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ListItemNode } from '@prezly/slate-types'; - -import type { ListsOptions } from '../types'; - -import { createListItemText } from './createListItemText'; - -export function createListItem( - options: ListsOptions, - children?: ListItemNode['children'], -): ListItemNode { - return { - children: Array.isArray(children) ? children : [createListItemText(options)], - type: options.listItemType as ListItemNode['type'], - }; -} diff --git a/packages/slate-lists/src/lib/createListItemText.ts b/packages/slate-lists/src/lib/createListItemText.ts deleted file mode 100644 index ca01b5bdf..000000000 --- a/packages/slate-lists/src/lib/createListItemText.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ListItemTextNode } from '@prezly/slate-types'; - -import type { ListsOptions } from '../types'; - -export function createListItemText( - options: ListsOptions, - children: ListItemTextNode['children'] = [{ text: '' }], -): ListItemTextNode { - return { - children, - type: options.listItemTextType as ListItemTextNode['type'], - }; -} diff --git a/packages/slate-lists/src/lib/decreaseDepth.ts b/packages/slate-lists/src/lib/decreaseDepth.ts index 104c0face..72111ab24 100644 --- a/packages/slate-lists/src/lib/decreaseDepth.ts +++ b/packages/slate-lists/src/lib/decreaseDepth.ts @@ -1,7 +1,6 @@ import { EditorCommands, nodeIdManager } from '@prezly/slate-commons'; -import type { Editor } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { decreaseListItemDepth } from './decreaseListItemDepth'; import { getListItemsInRange } from './getListItemsInRange'; @@ -10,12 +9,12 @@ import { getListItemsInRange } from './getListItemsInRange'; * Decreases nesting depth of all "list-items" in the current selection. * All "list-items" in the root "list" will become "default" nodes. */ -export function decreaseDepth(options: ListsOptions, editor: Editor): void { +export function decreaseDepth(editor: ListsEditor): void { if (!editor.selection) { return; } - const listItemsInRange = getListItemsInRange(options, editor, editor.selection); + const listItemsInRange = getListItemsInRange(editor, editor.selection); const unreachableListItems = EditorCommands.getUnreachableAncestors(listItemsInRange); // When calling `decreaseListItemDepth` the paths and references to "list-items" // can change, so we need a way of marking the "list-items" scheduled for transformation. @@ -33,6 +32,6 @@ export function decreaseDepth(options: ListsOptions, editor: Editor): void { } const [, listItemEntryPath] = listItemEntry; - decreaseListItemDepth(options, editor, listItemEntryPath); + decreaseListItemDepth(editor, listItemEntryPath); }); } diff --git a/packages/slate-lists/src/lib/decreaseListItemDepth.ts b/packages/slate-lists/src/lib/decreaseListItemDepth.ts index 5f57801f9..f66454019 100644 --- a/packages/slate-lists/src/lib/decreaseListItemDepth.ts +++ b/packages/slate-lists/src/lib/decreaseListItemDepth.ts @@ -1,9 +1,9 @@ -import type { Element } from 'slate'; import { Editor, Node, Path, Transforms } from 'slate'; import { NESTED_LIST_PATH_INDEX, TEXT_PATH_INDEX } from '../constants'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; +import { getListType } from './getListType'; import { getParentList } from './getParentList'; import { getParentListItem } from './getParentListItem'; import { increaseListItemDepth } from './increaseListItemDepth'; @@ -11,12 +11,8 @@ import { increaseListItemDepth } from './increaseListItemDepth'; /** * Decreases nesting depth of "list-item" at a given Path. */ -export function decreaseListItemDepth( - options: ListsOptions, - editor: Editor, - listItemPath: Path, -): void { - const parentList = getParentList(options, editor, listItemPath); +export function decreaseListItemDepth(editor: ListsEditor, listItemPath: Path): void { + const parentList = getParentList(editor, listItemPath); if (!parentList) { // It should never happen. @@ -24,7 +20,7 @@ export function decreaseListItemDepth( } const [parentListNode, parentListPath] = parentList; - const parentListItem = getParentListItem(options, editor, listItemPath); + const parentListItem = getParentListItem(editor, listItemPath); const listItemIndex = listItemPath[listItemPath.length - 1]; const previousSiblings = parentListNode.children.slice(0, listItemIndex); const nextSiblings = parentListNode.children.slice(listItemIndex + 1); @@ -35,7 +31,7 @@ export function decreaseListItemDepth( // The next sibling path is always the same, because once we move out the next sibling, // another one will take its place. const nextSiblingPath = [...parentListPath, listItemIndex + 1]; - increaseListItemDepth(options, editor, nextSiblingPath); + increaseListItemDepth(editor, nextSiblingPath); }); Editor.withoutNormalizing(editor, () => { @@ -60,7 +56,7 @@ export function decreaseListItemDepth( if (Node.has(editor, listItemNestedListPath)) { Transforms.setNodes( editor, - { type: parentListNode.type }, + editor.createListNode(getListType(editor, parentListNode)), { at: listItemNestedListPath }, ); Transforms.liftNodes(editor, { at: listItemNestedListPath }); @@ -70,7 +66,7 @@ export function decreaseListItemDepth( if (Node.has(editor, listItemTextPath)) { Transforms.setNodes( editor, - { type: options.defaultBlockType as Element['type'] }, + editor.createDefaultTextNode(), { at: listItemTextPath }, ); Transforms.liftNodes(editor, { at: listItemTextPath }); diff --git a/packages/slate-lists/src/lib/getListItemsInRange.ts b/packages/slate-lists/src/lib/getListItemsInRange.ts index 1a5134668..a4988e11e 100644 --- a/packages/slate-lists/src/lib/getListItemsInRange.ts +++ b/packages/slate-lists/src/lib/getListItemsInRange.ts @@ -1,19 +1,12 @@ import type { Element, NodeEntry } from 'slate'; import { Editor, Path, Range } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isListItem } from './isListItem'; +import type { ListsEditor } from '../types'; /** * Returns all "list-items" in a given Range. - * @param at defaults to current selection if not specified */ -export function getListItemsInRange( - options: ListsOptions, - editor: Editor, - at: Range | null | undefined, -): NodeEntry[] { +export function getListItemsInRange(editor: ListsEditor, at?: Range | null): NodeEntry[] { if (!at) { return []; } @@ -21,7 +14,7 @@ export function getListItemsInRange( const rangeStartPoint = Range.start(at); const listItemsInSelection = Editor.nodes(editor, { at, - match: (node) => isListItem(options, node), + match: editor.isListItemNode, }); return Array.from(listItemsInSelection).filter(([, path]) => { diff --git a/packages/slate-lists/src/lib/getListType.ts b/packages/slate-lists/src/lib/getListType.ts index de85076e5..b12ac7716 100644 --- a/packages/slate-lists/src/lib/getListType.ts +++ b/packages/slate-lists/src/lib/getListType.ts @@ -1,15 +1,23 @@ +import type { Node } from 'slate'; import { Element } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; +import { ListType } from '../types'; /** * Returns the "type" of a given list node. */ -export function getListType(options: ListsOptions, node: unknown): string { - if (Element.isElement(node)) { - return node.type; +export function getListType(editor: ListsEditor, node: Node): ListType { + const isElement = Element.isElement(node); + + if (isElement && editor.isListNode(node, ListType.ORDERED)) { + return ListType.ORDERED; + } + + if (isElement && editor.isListNode(node, ListType.UNORDERED)) { + return ListType.UNORDERED; } - // It should never happen. - return options.listTypes[0]; + // This should never happen. + return ListType.UNORDERED; } diff --git a/packages/slate-lists/src/lib/getListsInRange.ts b/packages/slate-lists/src/lib/getListsInRange.ts index 3ade0b210..8788dd8b1 100644 --- a/packages/slate-lists/src/lib/getListsInRange.ts +++ b/packages/slate-lists/src/lib/getListsInRange.ts @@ -1,22 +1,20 @@ -import type { Editor, Element, NodeEntry, Range } from 'slate'; +import type { Element, NodeEntry, Range } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getListItemsInRange } from './getListItemsInRange'; import { getParentList } from './getParentList'; /** - * Returns all "lists" in a given Range. - * @param at defaults to current selection if not specified + * Get all lists in the given Range. */ export function getListsInRange( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, at: Range | null | undefined, ): NodeEntry[] { - const listItemsInRange = getListItemsInRange(options, editor, at); + const listItemsInRange = getListItemsInRange(editor, at); const lists = listItemsInRange - .map(([, listItemPath]) => getParentList(options, editor, listItemPath)) + .map(([, listItemPath]) => getParentList(editor, listItemPath)) .filter((list) => list !== null); // TypeScript complains about `null`s even though we filter for them, hence the typecast. return lists as NodeEntry[]; diff --git a/packages/slate-lists/src/lib/getNestedList.ts b/packages/slate-lists/src/lib/getNestedList.ts index 3f5ef0fbe..db7cd5b01 100644 --- a/packages/slate-lists/src/lib/getNestedList.ts +++ b/packages/slate-lists/src/lib/getNestedList.ts @@ -1,21 +1,15 @@ -import type { Editor, Element, NodeEntry, Path } from 'slate'; +import type { Element, NodeEntry, Path } from 'slate'; import { Node } from 'slate'; import { NESTED_LIST_PATH_INDEX } from '../constants'; -import type { ListsOptions } from '../types'; - -import { isList } from './isList'; +import type { ListsEditor } from '../types'; /** * Returns "list" node nested in "list-item" at a given path. * Returns null if there is no nested "list". */ -export function getNestedList( - options: ListsOptions, - editor: Editor, - listItemPath: Path, -): NodeEntry | null { - const nestedListPath = [...listItemPath, NESTED_LIST_PATH_INDEX]; +export function getNestedList(editor: ListsEditor, path: Path): NodeEntry | null { + const nestedListPath = [...path, NESTED_LIST_PATH_INDEX]; if (!Node.has(editor, nestedListPath)) { return null; @@ -23,10 +17,10 @@ export function getNestedList( const nestedList = Node.get(editor, nestedListPath); - if (!isList(options, nestedList)) { + if (editor.isListNode(nestedList)) { // Sanity check. - return null; + return [nestedList, nestedListPath]; } - return [nestedList, nestedListPath]; + return null; } diff --git a/packages/slate-lists/src/lib/getParentList.ts b/packages/slate-lists/src/lib/getParentList.ts index 20c673fe4..32e13cf27 100644 --- a/packages/slate-lists/src/lib/getParentList.ts +++ b/packages/slate-lists/src/lib/getParentList.ts @@ -2,27 +2,17 @@ import type { ElementNode } from '@prezly/slate-types'; import type { NodeEntry, Path } from 'slate'; import { Editor } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isList } from './isList'; +import type { ListsEditor } from '../types'; /** * Returns parent "list" node of "list-item" at a given path. * Returns null if there is no parent "list". */ -export function getParentList( - options: ListsOptions, - editor: Editor, - listItemPath: Path, -): NodeEntry | null { +export function getParentList(editor: ListsEditor, path: Path): NodeEntry | null { const parentList = Editor.above(editor, { - at: listItemPath, - match: (node) => isList(options, node), + at: path, + match: (node) => editor.isListNode(node), }); - if (parentList && isList(options, parentList[0])) { - return parentList; - } - - return null; + return parentList ?? null; } diff --git a/packages/slate-lists/src/lib/getParentListItem.ts b/packages/slate-lists/src/lib/getParentListItem.ts index 2dbca19ce..b619787e8 100644 --- a/packages/slate-lists/src/lib/getParentListItem.ts +++ b/packages/slate-lists/src/lib/getParentListItem.ts @@ -1,28 +1,18 @@ import type { ElementNode } from '@prezly/slate-types'; -import type { NodeEntry, Path } from 'slate'; +import type { Element, NodeEntry, Path } from 'slate'; import { Editor } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isListItem } from './isListItem'; +import type { ListsEditor } from '../types'; /** * Returns parent "list-item" node of "list-item" at a given path. * Returns null if there is no parent "list-item". */ -export function getParentListItem( - options: ListsOptions, - editor: Editor, - listItemPath: Path, -): NodeEntry | null { +export function getParentListItem(editor: ListsEditor, path: Path): NodeEntry | null { const parentListItem = Editor.above(editor, { - at: listItemPath, - match: (node) => isListItem(options, node), + at: path, + match: (node) => editor.isListItemNode(node), }); - if (parentListItem && isListItem(options, parentListItem[0])) { - return parentListItem; - } - - return null; + return parentListItem ?? null; } diff --git a/packages/slate-lists/src/lib/increaseDepth.ts b/packages/slate-lists/src/lib/increaseDepth.ts index a35355e63..87057626c 100644 --- a/packages/slate-lists/src/lib/increaseDepth.ts +++ b/packages/slate-lists/src/lib/increaseDepth.ts @@ -1,7 +1,7 @@ import { EditorCommands, nodeIdManager } from '@prezly/slate-commons'; -import type { Editor } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; +import { ListType } from '../types'; import { getListItemsInRange } from './getListItemsInRange'; import { increaseListItemDepth } from './increaseListItemDepth'; @@ -11,12 +11,12 @@ import { wrapInList } from './wrapInList'; * Increases nesting depth of all "list-items" in the current selection. * All nodes matching options.wrappableTypes in the selection will be converted to "list-items" and wrapped in a "list". */ -export function increaseDepth(options: ListsOptions, editor: Editor): void { +export function increaseDepth(editor: ListsEditor): void { if (!editor.selection) { return; } - const listItemsInRange = getListItemsInRange(options, editor, editor.selection); + const listItemsInRange = getListItemsInRange(editor, editor.selection); const indentableListItemsInRange = listItemsInRange.filter(([, listItemPath]) => { const previousListItem = EditorCommands.getPreviousSibling(editor, listItemPath); return previousListItem !== null; @@ -29,7 +29,7 @@ export function increaseDepth(options: ListsOptions, editor: Editor): void { }); // Before we indent "list-items", we want to convert every non list-related block in selection to a "list". - wrapInList(options, editor, options.listTypes[0]); + wrapInList(editor, ListType.UNORDERED); unreachableListItemsIds.forEach((id) => { const listItemEntry = nodeIdManager.get(editor, id); @@ -41,6 +41,6 @@ export function increaseDepth(options: ListsOptions, editor: Editor): void { } const [, listItemEntryPath] = listItemEntry; - increaseListItemDepth(options, editor, listItemEntryPath); + increaseListItemDepth(editor, listItemEntryPath); }); } diff --git a/packages/slate-lists/src/lib/increaseListItemDepth.ts b/packages/slate-lists/src/lib/increaseListItemDepth.ts index 56ab2dd6b..260b29e19 100644 --- a/packages/slate-lists/src/lib/increaseListItemDepth.ts +++ b/packages/slate-lists/src/lib/increaseListItemDepth.ts @@ -2,21 +2,14 @@ import { EditorCommands } from '@prezly/slate-commons'; import { Editor, Node, Path, Transforms } from 'slate'; import { NESTED_LIST_PATH_INDEX } from '../constants'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; -import { createList } from './createList'; import { getListType } from './getListType'; -import { isList } from './isList'; -import { isListItem } from './isListItem'; /** * Increases nesting depth of "list-item" at a given Path. */ -export function increaseListItemDepth( - options: ListsOptions, - editor: Editor, - listItemPath: Path, -): void { +export function increaseListItemDepth(editor: ListsEditor, listItemPath: Path): void { const previousListItem = EditorCommands.getPreviousSibling(editor, listItemPath); if (!previousListItem) { @@ -27,7 +20,7 @@ export function increaseListItemDepth( const [previousListItemNode, previousListItemPath] = previousListItem; - if (!isListItem(options, previousListItemNode)) { + if (!editor.isListItemNode(previousListItemNode)) { // Sanity check. return; } @@ -40,13 +33,13 @@ export function increaseListItemDepth( if (!previousListItemHasChildList) { const listNodePath = Path.ancestors(listItemPath, { reverse: true })[0]; const listNode = Node.get(editor, listNodePath); - const newList = createList(getListType(options, listNode)); + const newList = editor.createListNode(getListType(editor, listNode)); Transforms.insertNodes(editor, newList, { at: previousListItemChildListPath }); } const previousListItemChildList = Node.get(editor, previousListItemChildListPath); - if (isList(options, previousListItemChildList)) { + if (editor.isListNode(previousListItemChildList)) { const index = previousListItemHasChildList ? previousListItemChildList.children.length : 0; diff --git a/packages/slate-lists/src/lib/index.ts b/packages/slate-lists/src/lib/index.ts index 230904757..92b8be375 100644 --- a/packages/slate-lists/src/lib/index.ts +++ b/packages/slate-lists/src/lib/index.ts @@ -1,9 +1,5 @@ export { canDeleteBackward } from './canDeleteBackward'; export { cloneContentsMonkeyPatch } from './cloneContentsMonkeyPatch'; -export { createDefaultNode } from './createDefaultNode'; -export { createList } from './createList'; -export { createListItem } from './createListItem'; -export { createListItemText } from './createListItemText'; export { decreaseDepth } from './decreaseDepth'; export { decreaseListItemDepth } from './decreaseListItemDepth'; export { getListItemsInRange } from './getListItemsInRange'; @@ -16,9 +12,6 @@ export { increaseDepth } from './increaseDepth'; export { increaseListItemDepth } from './increaseListItemDepth'; export { isCursorAtStartOfListItem } from './isCursorAtStartOfListItem'; export { isCursorInEmptyListItem } from './isCursorInEmptyListItem'; -export { isList } from './isList'; -export { isListItem } from './isListItem'; -export { isListItemText } from './isListItemText'; export { listItemContainsText } from './listItemContainsText'; export { mergeListWithPreviousSiblingList } from './mergeListWithPreviousSiblingList'; export { moveListItemsToAnotherList } from './moveListItemsToAnotherList'; diff --git a/packages/slate-lists/src/lib/isCursorAtStartOfListItem.ts b/packages/slate-lists/src/lib/isCursorAtStartOfListItem.ts index a6e61c763..53515fcb0 100644 --- a/packages/slate-lists/src/lib/isCursorAtStartOfListItem.ts +++ b/packages/slate-lists/src/lib/isCursorAtStartOfListItem.ts @@ -1,20 +1,19 @@ import { EditorCommands } from '@prezly/slate-commons'; -import type { Editor } from 'slate'; import { Range } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getListItemsInRange } from './getListItemsInRange'; /** * Returns true when editor has collapsed selection and the cursor is at the beginning of a "list-item". */ -export function isCursorAtStartOfListItem(options: ListsOptions, editor: Editor): boolean { +export function isCursorAtStartOfListItem(editor: ListsEditor): boolean { if (!editor.selection || Range.isExpanded(editor.selection)) { return false; } - const listItemsInSelection = getListItemsInRange(options, editor, editor.selection); + const listItemsInSelection = getListItemsInRange(editor, editor.selection); if (listItemsInSelection.length !== 1) { return false; diff --git a/packages/slate-lists/src/lib/isCursorInEmptyListItem.ts b/packages/slate-lists/src/lib/isCursorInEmptyListItem.ts index c95c6aabd..370a708dd 100644 --- a/packages/slate-lists/src/lib/isCursorInEmptyListItem.ts +++ b/packages/slate-lists/src/lib/isCursorInEmptyListItem.ts @@ -1,7 +1,6 @@ -import type { Editor } from 'slate'; import { Range } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getListItemsInRange } from './getListItemsInRange'; import { listItemContainsText } from './listItemContainsText'; @@ -9,12 +8,12 @@ import { listItemContainsText } from './listItemContainsText'; /** * Returns true when editor has collapsed selection and the cursor is in an empty "list-item". */ -export function isCursorInEmptyListItem(options: ListsOptions, editor: Editor): boolean { +export function isCursorInEmptyListItem(editor: ListsEditor): boolean { if (!editor.selection || Range.isExpanded(editor.selection)) { return false; } - const listItemsInSelection = getListItemsInRange(options, editor, editor.selection); + const listItemsInSelection = getListItemsInRange(editor, editor.selection); if (listItemsInSelection.length !== 1) { return false; @@ -22,5 +21,5 @@ export function isCursorInEmptyListItem(options: ListsOptions, editor: Editor): const [[listItemNode]] = listItemsInSelection; - return !listItemContainsText(options, editor, listItemNode); + return !listItemContainsText(editor, listItemNode); } diff --git a/packages/slate-lists/src/lib/isList.ts b/packages/slate-lists/src/lib/isList.ts deleted file mode 100644 index 8d3bc619f..000000000 --- a/packages/slate-lists/src/lib/isList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ElementNode } from '@prezly/slate-types'; -import { isElementNode } from '@prezly/slate-types'; - -import type { ListsOptions } from '../types'; - -/** - * Checks whether node.type is an Element matching any of options.listTypes. - */ -export function isList(options: ListsOptions, node: unknown): node is ElementNode { - return isElementNode(node, options.listTypes); -} diff --git a/packages/slate-lists/src/lib/isListItem.ts b/packages/slate-lists/src/lib/isListItem.ts deleted file mode 100644 index 294144b24..000000000 --- a/packages/slate-lists/src/lib/isListItem.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ElementNode } from '@prezly/slate-types'; -import { isElementNode } from '@prezly/slate-types'; - -import type { ListsOptions } from '../types'; - -/** - * Checks whether node.type is an Element matching options.listItemType. - */ -export function isListItem(options: ListsOptions, node: unknown): node is ElementNode { - return isElementNode(node, options.listItemType); -} diff --git a/packages/slate-lists/src/lib/isListItemText.ts b/packages/slate-lists/src/lib/isListItemText.ts deleted file mode 100644 index bba1db80a..000000000 --- a/packages/slate-lists/src/lib/isListItemText.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ElementNode } from '@prezly/slate-types'; -import { isElementNode } from '@prezly/slate-types'; - -import type { ListsOptions } from '../types'; - -/** - * Checks whether node.type is an Element matching options.listItemTextType. - */ -export function isListItemText(options: ListsOptions, node: unknown): node is ElementNode { - return isElementNode(node, options.listItemTextType); -} diff --git a/packages/slate-lists/src/lib/listItemContainsText.ts b/packages/slate-lists/src/lib/listItemContainsText.ts index 63b98f430..87a0664b2 100644 --- a/packages/slate-lists/src/lib/listItemContainsText.ts +++ b/packages/slate-lists/src/lib/listItemContainsText.ts @@ -1,25 +1,19 @@ +import type { Node } from 'slate'; import { Editor } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isListItem } from './isListItem'; -import { isListItemText } from './isListItemText'; +import type { ListsEditor } from '../types'; /** * Returns true if given "list-item" node contains a non-empty "list-item-text" node. */ -export function listItemContainsText( - options: ListsOptions, - editor: Editor, - node: unknown, -): boolean { - if (!isListItem(options, node)) { +export function listItemContainsText(editor: ListsEditor, node: Node): boolean { + if (!editor.isListItemNode(node)) { return false; } const [listItemText] = node.children; - if (!isListItemText(options, listItemText)) { + if (!editor.isListItemTextNode(listItemText)) { return false; } diff --git a/packages/slate-lists/src/lib/mergeListWithPreviousSiblingList.ts b/packages/slate-lists/src/lib/mergeListWithPreviousSiblingList.ts index 03ff47031..5029e0c1b 100644 --- a/packages/slate-lists/src/lib/mergeListWithPreviousSiblingList.ts +++ b/packages/slate-lists/src/lib/mergeListWithPreviousSiblingList.ts @@ -1,18 +1,17 @@ import { EditorCommands } from '@prezly/slate-commons'; -import type { Editor, Node, NodeEntry } from 'slate'; +import type { Node, NodeEntry } from 'slate'; import { Transforms } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; +import { getListType } from './getListType'; import { getParentListItem } from './getParentListItem'; -import { isList } from './isList'; export function mergeListWithPreviousSiblingList( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, [node, path]: NodeEntry, ): boolean { - if (!isList(options, node)) { + if (!editor.isListNode(node)) { // This function does not know how to normalize other nodes. return false; } @@ -26,12 +25,13 @@ export function mergeListWithPreviousSiblingList( const [previousSiblingNode] = previousSibling; - if (!isList(options, previousSiblingNode)) { + if (!editor.isListNode(previousSiblingNode)) { return false; } - const isNestedList = Boolean(getParentListItem(options, editor, path)); - const isPreviousSiblingSameListType = previousSiblingNode.type === node.type; + const isNestedList = Boolean(getParentListItem(editor, path)); + const isPreviousSiblingSameListType = + getListType(editor, previousSiblingNode) === getListType(editor, node); if (!isPreviousSiblingSameListType && !isNestedList) { // If previous sibling "list" is of a different type, then this fix does not apply diff --git a/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts b/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts index 5995748aa..609c159c5 100644 --- a/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts +++ b/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts @@ -1,16 +1,13 @@ -import type { Editor, Node, NodeEntry } from 'slate'; +import type { Node, NodeEntry } from 'slate'; import { Transforms } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isList } from './isList'; +import type { ListsEditor } from '../types'; /** * Moves all "list-items" from one "list" to the end of another "list". */ export function moveListItemsToAnotherList( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, parameters: { at: NodeEntry; to: NodeEntry; @@ -19,7 +16,7 @@ export function moveListItemsToAnotherList( const [sourceListNode, sourceListPath] = parameters.at; const [targetListNode, targetListPath] = parameters.to; - if (!isList(options, sourceListNode) || !isList(options, targetListNode)) { + if (!editor.isListNode(sourceListNode) || !editor.isListNode(targetListNode)) { // Sanity check. return; } diff --git a/packages/slate-lists/src/lib/moveListToListItem.ts b/packages/slate-lists/src/lib/moveListToListItem.ts index 627c2c4e9..7c783cd9d 100644 --- a/packages/slate-lists/src/lib/moveListToListItem.ts +++ b/packages/slate-lists/src/lib/moveListToListItem.ts @@ -1,18 +1,14 @@ -import type { Editor, Node, NodeEntry } from 'slate'; +import type { Node, NodeEntry } from 'slate'; import { Transforms } from 'slate'; import { NESTED_LIST_PATH_INDEX } from '../constants'; -import type { ListsOptions } from '../types'; - -import { isList } from './isList'; -import { isListItem } from './isListItem'; +import type { ListsEditor } from '../types'; /** * Nests (moves) given "list" in a given "list-item". */ export function moveListToListItem( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, parameters: { at: NodeEntry; to: NodeEntry; @@ -21,7 +17,7 @@ export function moveListToListItem( const [sourceListNode, sourceListPath] = parameters.at; const [targetListNode, targetListPath] = parameters.to; - if (!isList(options, sourceListNode) || !isListItem(options, targetListNode)) { + if (!editor.isListNode(sourceListNode) || !editor.isListItemNode(targetListNode)) { // Sanity check. return; } diff --git a/packages/slate-lists/src/lib/normalizeList.ts b/packages/slate-lists/src/lib/normalizeList.ts index bd688ca40..0a846c9ed 100644 --- a/packages/slate-lists/src/lib/normalizeList.ts +++ b/packages/slate-lists/src/lib/normalizeList.ts @@ -3,20 +3,14 @@ import { isElementNode } from '@prezly/slate-types'; import type { Node, NodeEntry } from 'slate'; import { Editor, Element, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isList } from './isList'; +import type { ListsEditor } from '../types'; /** * A "list" can have no parent (be at the root) or have a "list-item" parent (nested list). * In any other case we will try to unwrap it, or lift it up. */ -export function normalizeList( - options: ListsOptions, - editor: Editor, - [node, path]: NodeEntry, -): boolean { - if (!isList(options, node)) { +export function normalizeList(editor: ListsEditor, [node, path]: NodeEntry): boolean { + if (!editor.isListNode(node)) { // This function does not know how to normalize other nodes. return false; } @@ -35,7 +29,7 @@ export function normalizeList( if ( isElementNode(ancestorNode) && - [...options.listTypes, options.listItemType].includes(ancestorNode.type) + (editor.isListNode(ancestorNode) || editor.isListItemNode(ancestorNode)) ) { return false; } diff --git a/packages/slate-lists/src/lib/normalizeListChildren.ts b/packages/slate-lists/src/lib/normalizeListChildren.ts index 0a35b8f19..0b41ef13e 100644 --- a/packages/slate-lists/src/lib/normalizeListChildren.ts +++ b/packages/slate-lists/src/lib/normalizeListChildren.ts @@ -1,25 +1,15 @@ -import type { Editor, NodeEntry } from 'slate'; +import type { NodeEntry } from 'slate'; import { Element, Node, Text, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; - -import { createListItem } from './createListItem'; -import { createListItemText } from './createListItemText'; -import { isList } from './isList'; -import { isListItem } from './isListItem'; -import { isListItemText } from './isListItemText'; +import type { ListsEditor } from '../types'; /** * All children of a "list" have to be "list-items". It can happen (e.g. during pasting) that * this will not be true, so we have to convert all non-"list-item" children of a "list" * into "list-items". */ -export function normalizeListChildren( - options: ListsOptions, - editor: Editor, - [node, path]: NodeEntry, -): boolean { - if (!isList(options, node)) { +export function normalizeListChildren(editor: ListsEditor, [node, path]: NodeEntry): boolean { + if (!editor.isListNode(node)) { // This function does not know how to normalize other nodes. return false; } @@ -53,7 +43,9 @@ export function normalizeListChildren( Transforms.wrapNodes( editor, - createListItem(options, [createListItemText(options, [childNode])]), + editor.createListItemNode({ + children: [editor.createListItemTextNode({ children: [childNode] })], + }), { at: childPath }, ); normalized = true; @@ -64,26 +56,22 @@ export function normalizeListChildren( return; } - if (isListItemText(options, childNode)) { - Transforms.wrapNodes(editor, createListItem(options), { at: childPath }); + if (editor.isListItemTextNode(childNode)) { + Transforms.wrapNodes(editor, editor.createListItemNode(), { at: childPath }); normalized = true; return; } - if (isList(options, childNode)) { + if (editor.isListNode(childNode)) { // Wrap it into a list item so that `normalizeOrphanNestedList` can take care of it. - Transforms.wrapNodes(editor, createListItem(options), { at: childPath }); + Transforms.wrapNodes(editor, editor.createListItemNode(), { at: childPath }); normalized = true; return; } - if (!isListItem(options, childNode)) { - Transforms.setNodes( - editor, - { type: options.listItemTextType as Element['type'] }, - { at: childPath }, - ); - Transforms.wrapNodes(editor, createListItem(options), { at: childPath }); + if (!editor.isListItemNode(childNode)) { + Transforms.setNodes(editor, editor.createListItemTextNode(), { at: childPath }); + Transforms.wrapNodes(editor, editor.createListItemNode(), { at: childPath }); normalized = true; } }); diff --git a/packages/slate-lists/src/lib/normalizeListItemChildren.ts b/packages/slate-lists/src/lib/normalizeListItemChildren.ts index c8bc74636..81ab15a19 100644 --- a/packages/slate-lists/src/lib/normalizeListItemChildren.ts +++ b/packages/slate-lists/src/lib/normalizeListItemChildren.ts @@ -1,23 +1,16 @@ -import type { Editor, NodeEntry } from 'slate'; -import { Element, Node, Text, Transforms } from 'slate'; +import type { NodeEntry } from 'slate'; +import { Node, Text, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; - -import { createListItem } from './createListItem'; -import { createListItemText } from './createListItemText'; -import { isList } from './isList'; -import { isListItem } from './isListItem'; -import { isListItemText } from './isListItemText'; +import type { ListsEditor } from '../types'; /** * A "list-item" can have a single "list-item-text" and optionally an extra "list" as a child. */ export function normalizeListItemChildren( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, [node, path]: NodeEntry, ): boolean { - if (!isListItem(options, node)) { + if (!editor.isListItemNode(node)) { // This function does not know how to normalize other nodes. return false; } @@ -28,13 +21,15 @@ export function normalizeListItemChildren( const [childNode, childPath] = children[childIndex]; if (Text.isText(childNode) || editor.isInline(childNode)) { - const listItemText = createListItemText(options, [childNode]); + const listItemText = editor.createListItemTextNode({ + children: [childNode], + }); Transforms.wrapNodes(editor, listItemText, { at: childPath }); if (childIndex > 0) { const [previousChildNode] = children[childIndex - 1]; - if (isListItemText(options, previousChildNode)) { + if (editor.isListItemTextNode(previousChildNode)) { Transforms.mergeNodes(editor, { at: childPath }); } } @@ -42,34 +37,18 @@ export function normalizeListItemChildren( return true; } - // Casting `as Element` here, because of TypeScript incorrectly complaining that `childNode` - // is of type `never`, even though we just checked if it's an `Element`. - if (Element.isElement(childNode) && typeof (childNode as Element).type === 'undefined') { - // It can happen during pasting that the `type` attribute will be missing. - Transforms.setNodes( - editor, - { type: options.listItemTextType as Element['type'] }, - { at: childPath }, - ); - return true; - } - - if (isListItem(options, childNode)) { + if (editor.isListItemNode(childNode)) { Transforms.liftNodes(editor, { at: childPath }); return true; } - if (isListItemText(options, childNode) && childIndex !== 0) { - Transforms.wrapNodes(editor, createListItem(options), { at: childPath }); + if (editor.isListItemTextNode(childNode) && childIndex !== 0) { + Transforms.wrapNodes(editor, editor.createListItemNode(), { at: childPath }); return true; } - if (!isListItemText(options, childNode) && !isList(options, childNode)) { - Transforms.setNodes( - editor, - { type: options.listItemTextType as Element['type'] }, - { at: childPath }, - ); + if (!editor.isListItemTextNode(childNode) && !editor.isListNode(childNode)) { + Transforms.setNodes(editor, editor.createListItemTextNode(), { at: childPath }); return true; } } diff --git a/packages/slate-lists/src/lib/normalizeListItemTextChildren.ts b/packages/slate-lists/src/lib/normalizeListItemTextChildren.ts index 01062f0b3..9befe46e5 100644 --- a/packages/slate-lists/src/lib/normalizeListItemTextChildren.ts +++ b/packages/slate-lists/src/lib/normalizeListItemTextChildren.ts @@ -1,19 +1,16 @@ import type { NodeEntry } from 'slate'; import { Editor, Element, Node, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; - -import { isListItemText } from './isListItemText'; +import type { ListsEditor } from '../types'; /** * A "list-item-text" can have only inline nodes in it. */ export function normalizeListItemTextChildren( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, [node, path]: NodeEntry, ): boolean { - if (!isListItemText(options, node)) { + if (!editor.isListItemTextNode(node)) { // This function does not know how to normalize other nodes. return false; } diff --git a/packages/slate-lists/src/lib/normalizeOrphanListItem.ts b/packages/slate-lists/src/lib/normalizeOrphanListItem.ts index c21594dac..073f7e9f3 100644 --- a/packages/slate-lists/src/lib/normalizeOrphanListItem.ts +++ b/packages/slate-lists/src/lib/normalizeOrphanListItem.ts @@ -1,10 +1,9 @@ -import type { Element, Node, NodeEntry } from 'slate'; +import type { Node, NodeEntry } from 'slate'; import { Editor, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getParentList } from './getParentList'; -import { isListItem } from './isListItem'; /** * If "list-item" somehow (e.g. by deleting everything around it) ends up @@ -15,16 +14,15 @@ import { isListItem } from './isListItem'; * pasting, so we have a separate rule for that in `deserializeHtml`. */ export function normalizeOrphanListItem( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, [node, path]: NodeEntry, ): boolean { - if (!isListItem(options, node)) { + if (!editor.isListItemNode(node)) { // This function does not know how to normalize other nodes. return false; } - const parentList = getParentList(options, editor, path); + const parentList = getParentList(editor, path); if (parentList) { // If there is a parent "list", then the fix does not apply. @@ -33,11 +31,7 @@ export function normalizeOrphanListItem( Editor.withoutNormalizing(editor, () => { Transforms.unwrapNodes(editor, { at: path }); - Transforms.setNodes( - editor, - { type: options.defaultBlockType as Element['type'] }, - { at: path }, - ); + Transforms.setNodes(editor, editor.createDefaultTextNode(), { at: path }); }); return true; diff --git a/packages/slate-lists/src/lib/normalizeOrphanListItemText.ts b/packages/slate-lists/src/lib/normalizeOrphanListItemText.ts index a9432692f..35e46cea2 100644 --- a/packages/slate-lists/src/lib/normalizeOrphanListItemText.ts +++ b/packages/slate-lists/src/lib/normalizeOrphanListItemText.ts @@ -1,10 +1,9 @@ -import type { Editor, Element, Node, NodeEntry } from 'slate'; +import type { Node, NodeEntry } from 'slate'; import { Transforms } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getParentListItem } from './getParentListItem'; -import { isListItemText } from './isListItemText'; /** * If "list-item-text" somehow (e.g. by deleting everything around it) ends up @@ -15,27 +14,22 @@ import { isListItemText } from './isListItemText'; * pasting, so we have a separate rule for that in `deserializeHtml`. */ export function normalizeOrphanListItemText( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, [node, path]: NodeEntry, ): boolean { - if (!isListItemText(options, node)) { + if (!editor.isListItemTextNode(node)) { // This function does not know how to normalize other nodes. return false; } - const parentListItem = getParentListItem(options, editor, path); + const parentListItem = getParentListItem(editor, path); if (parentListItem) { // If there is a parent "list-item", then the fix does not apply. return false; } - Transforms.setNodes( - editor, - { type: options.defaultBlockType as Element['type'] }, - { at: path }, - ); + Transforms.setNodes(editor, editor.createDefaultTextNode(), { at: path }); return true; } diff --git a/packages/slate-lists/src/lib/normalizeOrphanNestedList.ts b/packages/slate-lists/src/lib/normalizeOrphanNestedList.ts index ac3cf8b8f..43db79f49 100644 --- a/packages/slate-lists/src/lib/normalizeOrphanNestedList.ts +++ b/packages/slate-lists/src/lib/normalizeOrphanNestedList.ts @@ -1,12 +1,10 @@ import { EditorCommands } from '@prezly/slate-commons'; -import type { Editor, NodeEntry } from 'slate'; +import type { NodeEntry } from 'slate'; import { Node, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { getNestedList } from './getNestedList'; -import { isList } from './isList'; -import { isListItem } from './isListItem'; import { moveListItemsToAnotherList } from './moveListItemsToAnotherList'; import { moveListToListItem } from './moveListToListItem'; @@ -15,11 +13,10 @@ import { moveListToListItem } from './moveListToListItem'; * unwrap that nested "list" and try to nest it in previous sibling "list-item". */ export function normalizeOrphanNestedList( - options: ListsOptions, - editor: Editor, + editor: ListsEditor, [node, path]: NodeEntry, ): boolean { - if (!isListItem(options, node)) { + if (!editor.isListItemNode(node)) { // This function does not know how to normalize other nodes. return false; } @@ -33,7 +30,7 @@ export function normalizeOrphanNestedList( const [list] = children; const [listNode, listPath] = list; - if (!isList(options, listNode)) { + if (!editor.isListNode(listNode)) { // If the first child is not a "list", then this fix does not apply. return false; } @@ -42,15 +39,15 @@ export function normalizeOrphanNestedList( if (previousListItem) { const [, previousListItemPath] = previousListItem; - const previousListItemNestedList = getNestedList(options, editor, previousListItemPath); + const previousListItemNestedList = getNestedList(editor, previousListItemPath); if (previousListItemNestedList) { - moveListItemsToAnotherList(options, editor, { + moveListItemsToAnotherList(editor, { at: list, to: previousListItemNestedList, }); } else { - moveListToListItem(options, editor, { + moveListToListItem(editor, { at: list, to: previousListItem, }); diff --git a/packages/slate-lists/src/lib/normalizeSiblingLists.ts b/packages/slate-lists/src/lib/normalizeSiblingLists.ts index 37de3f408..8fb46cff2 100644 --- a/packages/slate-lists/src/lib/normalizeSiblingLists.ts +++ b/packages/slate-lists/src/lib/normalizeSiblingLists.ts @@ -1,7 +1,7 @@ import { EditorCommands } from '@prezly/slate-commons'; -import type { Editor, Node, NodeEntry } from 'slate'; +import type { Node, NodeEntry } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { mergeListWithPreviousSiblingList } from './mergeListWithPreviousSiblingList'; @@ -9,12 +9,8 @@ import { mergeListWithPreviousSiblingList } from './mergeListWithPreviousSibling * If there are 2 "lists" of the same type next to each other, merge them together. * If there are 2 nested "lists" next to each other, merge them together. */ -export function normalizeSiblingLists( - options: ListsOptions, - editor: Editor, - entry: NodeEntry, -): boolean { - const normalized = mergeListWithPreviousSiblingList(options, editor, entry); +export function normalizeSiblingLists(editor: ListsEditor, entry: NodeEntry): boolean { + const normalized = mergeListWithPreviousSiblingList(editor, entry); if (normalized) { return true; @@ -27,5 +23,5 @@ export function normalizeSiblingLists( return false; } - return mergeListWithPreviousSiblingList(options, editor, nextSibling); + return mergeListWithPreviousSiblingList(editor, nextSibling); } diff --git a/packages/slate-lists/src/lib/setListType.ts b/packages/slate-lists/src/lib/setListType.ts index c3b3bcd66..56e0df634 100644 --- a/packages/slate-lists/src/lib/setListType.ts +++ b/packages/slate-lists/src/lib/setListType.ts @@ -1,20 +1,19 @@ import { nodeIdManager } from '@prezly/slate-commons'; -import type { Editor, Element } from 'slate'; import { Transforms } from 'slate'; -import type { ListsOptions } from '../types'; +import type { ListType, ListsEditor } from '../types'; import { getListsInRange } from './getListsInRange'; /** * Sets "type" of all "list" nodes in the current selection. */ -export function setListType(options: ListsOptions, editor: Editor, listType: string): void { +export function setListType(editor: ListsEditor, listType: ListType): void { if (!editor.selection) { return; } - const lists = getListsInRange(options, editor, editor.selection); + const lists = getListsInRange(editor, editor.selection); const listsIds = lists.map((list) => nodeIdManager.assign(editor, list)); listsIds.forEach((id) => { @@ -27,6 +26,6 @@ export function setListType(options: ListsOptions, editor: Editor, listType: str } const [, listPath] = listEntry; - Transforms.setNodes(editor, { type: listType as Element['type'] }, { at: listPath }); + Transforms.setNodes(editor, editor.createListNode(listType), { at: listPath }); }); } diff --git a/packages/slate-lists/src/lib/splitListItem.ts b/packages/slate-lists/src/lib/splitListItem.ts index fba6ed95f..f8526108c 100644 --- a/packages/slate-lists/src/lib/splitListItem.ts +++ b/packages/slate-lists/src/lib/splitListItem.ts @@ -2,10 +2,8 @@ import { EditorCommands } from '@prezly/slate-commons'; import { Editor, Node, Path, Range, Transforms } from 'slate'; import { NESTED_LIST_PATH_INDEX, TEXT_PATH_INDEX } from '../constants'; -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; -import { createListItem } from './createListItem'; -import { createListItemText } from './createListItemText'; import { getListItemsInRange } from './getListItemsInRange'; /** @@ -13,7 +11,7 @@ import { getListItemsInRange } from './getListItemsInRange'; * ends up in a "list-item" node, it will break that "list-item" into 2 nodes, splitting * the text at the cursor location. */ -export function splitListItem(options: ListsOptions, editor: Editor): void { +export function splitListItem(editor: ListsEditor): void { if (!editor.selection) { return; } @@ -23,7 +21,7 @@ export function splitListItem(options: ListsOptions, editor: Editor): void { Transforms.delete(editor); } - const listItemsInSelection = getListItemsInRange(options, editor, editor.selection); + const listItemsInSelection = getListItemsInRange(editor, editor.selection); if (listItemsInSelection.length !== 1) { // Selection is collapsed, so there should be either 0 or 1 "list-item" in selection. @@ -42,7 +40,9 @@ export function splitListItem(options: ListsOptions, editor: Editor): void { ); if (isStart) { - const newListItem = createListItem(options, [createListItemText(options)]); + const newListItem = editor.createListItemNode({ + children: [editor.createListItemTextNode()], + }); Transforms.insertNodes(editor, newListItem, { at: listItemPath }); return; } @@ -53,7 +53,9 @@ export function splitListItem(options: ListsOptions, editor: Editor): void { Editor.withoutNormalizing(editor, () => { if (isEnd) { - const newListItem = createListItem(options, [createListItemText(options)]); + const newListItem = editor.createListItemNode({ + children: [editor.createListItemTextNode()], + }); Transforms.insertNodes(editor, newListItem, { at: newListItemPath }); // Move the cursor to the new "list-item". Transforms.select(editor, newListItemPath); @@ -62,7 +64,7 @@ export function splitListItem(options: ListsOptions, editor: Editor): void { Transforms.splitNodes(editor); // The current "list-item-text" has a parent "list-item", the new one needs its own. - Transforms.wrapNodes(editor, createListItem(options), { at: newListItemTextPath }); + Transforms.wrapNodes(editor, editor.createListItemNode(), { at: newListItemTextPath }); // Move the new "list-item" up to be a sibling of the original "list-item". Transforms.moveNodes(editor, { diff --git a/packages/slate-lists/src/lib/unwrapList.ts b/packages/slate-lists/src/lib/unwrapList.ts index e82416f88..51c17e272 100644 --- a/packages/slate-lists/src/lib/unwrapList.ts +++ b/packages/slate-lists/src/lib/unwrapList.ts @@ -1,6 +1,4 @@ -import type { Editor } from 'slate'; - -import type { ListsOptions } from '../types'; +import type { ListsEditor } from '../types'; import { decreaseDepth } from './decreaseDepth'; import { getListItemsInRange } from './getListItemsInRange'; @@ -9,8 +7,8 @@ import { getListItemsInRange } from './getListItemsInRange'; * Unwraps all "list-items" in the current selection. * No list be left in the current selection. */ -export function unwrapList(options: ListsOptions, editor: Editor): void { - while (getListItemsInRange(options, editor, editor.selection).length > 0) { - decreaseDepth(options, editor); +export function unwrapList(editor: ListsEditor): void { + while (getListItemsInRange(editor, editor.selection).length > 0) { + decreaseDepth(editor); } } diff --git a/packages/slate-lists/src/lib/wrapInList.ts b/packages/slate-lists/src/lib/wrapInList.ts index 807693985..5f99dc556 100644 --- a/packages/slate-lists/src/lib/wrapInList.ts +++ b/packages/slate-lists/src/lib/wrapInList.ts @@ -1,34 +1,31 @@ import { nodeIdManager } from '@prezly/slate-commons'; import { Editor, Element, Transforms } from 'slate'; -import type { ListsOptions } from '../types'; - -import { createList } from './createList'; -import { createListItem } from './createListItem'; +import type { ListsEditor } from '../types'; +import type { ListType } from '../types'; /** - * All nodes matching options.wrappableTypes in the current selection - * will be converted to "list-items" and wrapped in "lists". + * All nodes matching `isListNestable()` in the current selection + * will be converted to list items and then wrapped in lists. + * + * @see ListsEditor.isListNestable() */ -export function wrapInList(options: ListsOptions, editor: Editor, listType: string): void { +export function wrapInList(editor: ListsEditor, listType: ListType): void { if (!editor.selection) { return; } - const listNodeTypes = [...options.listTypes, options.listItemType, options.listItemTextType]; const nonListEntries = Array.from( Editor.nodes(editor, { at: editor.selection, match: (node) => { - if (!Element.isElement(node)) { - return false; - } - - if (listNodeTypes.includes(node.type)) { - return false; - } - - return options.wrappableTypes.includes(node.type); + return ( + Element.isElement(node) && + !editor.isListNode(node) && + !editor.isListItemNode(node) && + !editor.isListItemTextNode(node) && + editor.isListNestable(node) + ); }, }), ); @@ -48,13 +45,9 @@ export function wrapInList(options: ListsOptions, editor: Editor, listType: stri const [, nonListEntryPath] = nonListEntry; Editor.withoutNormalizing(editor, () => { - Transforms.setNodes( - editor, - { type: options.listItemTextType as Element['type'] }, - { at: nonListEntryPath }, - ); - Transforms.wrapNodes(editor, createListItem(options), { at: nonListEntryPath }); - Transforms.wrapNodes(editor, createList(listType), { at: nonListEntryPath }); + Transforms.setNodes(editor, editor.createListItemTextNode(), { at: nonListEntryPath }); + Transforms.wrapNodes(editor, editor.createListItemNode(), { at: nonListEntryPath }); + Transforms.wrapNodes(editor, editor.createListNode(listType), { at: nonListEntryPath }); }); }); } diff --git a/packages/slate-lists/src/types.ts b/packages/slate-lists/src/types.ts index 02de2bda1..d25a6a3bb 100644 --- a/packages/slate-lists/src/types.ts +++ b/packages/slate-lists/src/types.ts @@ -1,28 +1,31 @@ -import type { ElementNode } from '@prezly/slate-types'; - -export interface ListsOptions { - /** - * Type of the node that "listItemTextType" will become when it is unwrapped or normalized. - */ - defaultBlockType: ElementNode['type']; - - /** - * Type of the node representing list item text. - */ - listItemTextType: ElementNode['type']; - - /** - * Type of the node representing list item. - */ - listItemType: ElementNode['type']; - - /** - * Types of nodes representing lists. The first type will be the default type (e.g. when wrapping with lists). - */ - listTypes: ElementNode['type'][]; - - /** - * Types of nodes that can be converted into a node representing list item text. - */ - wrappableTypes: ElementNode['type'][]; +import type { BaseEditor, Element, Node } from 'slate'; + +export enum ListType { + ORDERED = 'ol', + UNORDERED = 'ul', +} + +export interface ListsSchema { + isDefaultTextNode(node: Node): node is T; + + isListNode(node: Node, type?: ListType): node is T; + + isListItemNode(node: Node): node is T; + + isListItemTextNode(node: Node): node is T; + + isListNestable(node: Node): boolean; + + createDefaultTextNode(props?: Partial>): T; + + createListNode( + type?: ListType, + props?: Partial>, + ): T; + + createListItemNode(props?: Partial>): T; + + createListItemTextNode(props?: Partial>): T; } + +export interface ListsEditor extends ListsSchema, BaseEditor {} diff --git a/packages/slate-lists/src/withLists.ts b/packages/slate-lists/src/withLists.ts index d444539e2..0bcc75a89 100644 --- a/packages/slate-lists/src/withLists.ts +++ b/packages/slate-lists/src/withLists.ts @@ -1,6 +1,4 @@ -/* eslint-disable no-param-reassign */ - -import type { Editor } from 'slate'; +import type { Editor, NodeEntry } from 'slate'; import { normalizeList, @@ -12,9 +10,11 @@ import { normalizeOrphanNestedList, normalizeSiblingLists, } from './lib'; -import type { ListsOptions } from './types'; +import type { ListsEditor, ListsSchema } from './types'; + +type Normalizer = (editor: ListsEditor, entry: NodeEntry) => boolean; -const normalizers = [ +const LIST_NORMALIZERS: Normalizer[] = [ normalizeList, normalizeListChildren, normalizeListItemChildren, @@ -28,22 +28,36 @@ const normalizers = [ /** * Enables normalizations that enforce schema constraints and recover from unsupported cases. */ -export function withLists(options: ListsOptions) { - return function (editor: T): T { - const { normalizeNode } = editor; +export function withLists(schema: ListsSchema) { + return function (editor: T): T & ListsEditor { + const listsEditor: T & ListsEditor = Object.assign(editor, { + isDefaultTextNode: schema.isDefaultTextNode, + isListNode: schema.isListNode, + isListItemNode: schema.isListItemNode, + isListItemTextNode: schema.isListItemTextNode, + isListNestable: schema.isListNestable, + createDefaultTextNode: schema.createDefaultTextNode, + createListNode: schema.createListNode, + createListItemNode: schema.createListItemNode, + createListItemTextNode: schema.createListItemTextNode, + }); + + return withNormalizations(listsEditor, LIST_NORMALIZERS); + }; +} - editor.normalizeNode = (entry) => { - for (const normalizer of normalizers) { - const normalized = normalizer(options, editor, entry); +function withNormalizations(editor: T, normalizers: Normalizer[]): T { + const { normalizeNode } = editor; - if (normalized) { - return; - } + editor.normalizeNode = (entry) => { + for (const normalize of normalizers) { + const changed = normalize(editor, entry); + if (changed) { + return; } + } - normalizeNode(entry); - }; - - return editor; + normalizeNode(entry); }; + return editor; } From 4fa5a4fa9472cbf2cc1749cb82ad1b0643292a62 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 16:59:19 +0300 Subject: [PATCH 02/24] Remove ambient `slate` type declarations from `slate-commons` --- .../src/EditableWithExtensions.tsx | 9 +- packages/slate-commons/src/commands/focus.ts | 2 +- .../src/commands/getCurrentDomNode.ts | 3 +- .../src/commands/insertNodes.test.tsx | 197 ++++++++---------- .../src/commands/isMarkActive.test.tsx | 17 +- .../src/commands/isMarkActive.ts | 4 +- .../src/commands/toggleMark.test.tsx | 14 +- .../slate-commons/src/commands/toggleMark.ts | 6 +- packages/slate-commons/src/index.ts | 13 -- packages/slate-commons/src/jsx.ts | 2 +- packages/slate-commons/src/test-utils.ts | 13 +- .../src/modules/editor-v4/EditorV4.tsx | 1 + 12 files changed, 130 insertions(+), 151 deletions(-) diff --git a/packages/slate-commons/src/EditableWithExtensions.tsx b/packages/slate-commons/src/EditableWithExtensions.tsx index 63c50a295..e9d2eadfe 100644 --- a/packages/slate-commons/src/EditableWithExtensions.tsx +++ b/packages/slate-commons/src/EditableWithExtensions.tsx @@ -1,7 +1,9 @@ /* eslint-disable react-hooks/exhaustive-deps */ import type { FunctionComponent } from 'react'; import React, { useCallback, useMemo } from 'react'; -import { Editable, useSlateStatic } from 'slate-react'; +import type { Editor } from 'slate'; +import type { ReactEditor } from 'slate-react'; +import { Editable } from 'slate-react'; import { combineDecorate, @@ -20,8 +22,10 @@ import type { RenderLeaf, } from './types'; + export interface Props { decorate?: Decorate; + editor: Editor & ReactEditor; /** * Each extension fields will be combined by role. * @@ -67,6 +71,7 @@ export interface Props { export const EditableWithExtensions: FunctionComponent = ({ decorate, + editor, extensions = [], onDOMBeforeInput: onDOMBeforeInputList = [], onDOMBeforeInputDeps = [], @@ -78,8 +83,6 @@ export const EditableWithExtensions: FunctionComponent = ({ renderLeafDeps = [], ...props }) => { - const editor = useSlateStatic(); - const combinedDecorate: Decorate = useMemo( function () { const decorateFns = createExtensionsDecorators(editor, extensions); diff --git a/packages/slate-commons/src/commands/focus.ts b/packages/slate-commons/src/commands/focus.ts index f998a6bd0..a360bfcbc 100644 --- a/packages/slate-commons/src/commands/focus.ts +++ b/packages/slate-commons/src/commands/focus.ts @@ -3,7 +3,7 @@ import { ReactEditor } from 'slate-react'; import { moveCursorToEndOfDocument } from './moveCursorToEndOfDocument'; -export function focus(editor: Editor): void { +export function focus(editor: Editor & ReactEditor): void { ReactEditor.focus(editor); moveCursorToEndOfDocument(editor); } diff --git a/packages/slate-commons/src/commands/getCurrentDomNode.ts b/packages/slate-commons/src/commands/getCurrentDomNode.ts index 706b3511f..7553f5513 100644 --- a/packages/slate-commons/src/commands/getCurrentDomNode.ts +++ b/packages/slate-commons/src/commands/getCurrentDomNode.ts @@ -1,9 +1,10 @@ import type { Editor } from 'slate'; +import type { ReactEditor } from 'slate-react'; import { getCurrentNodeEntry } from './getCurrentNodeEntry'; import { toDomNode } from './toDomNode'; -export function getCurrentDomNode(editor: Editor): HTMLElement | null { +export function getCurrentDomNode(editor: Editor & ReactEditor): HTMLElement | null { const [currentNode] = getCurrentNodeEntry(editor) || []; if (!currentNode) { diff --git a/packages/slate-commons/src/commands/insertNodes.test.tsx b/packages/slate-commons/src/commands/insertNodes.test.tsx index 3eec604ca..00d247959 100644 --- a/packages/slate-commons/src/commands/insertNodes.test.tsx +++ b/packages/slate-commons/src/commands/insertNodes.test.tsx @@ -1,11 +1,9 @@ /** @jsx jsx */ -import type { LinkNode } from '@prezly/slate-types'; -import { PARAGRAPH_NODE_TYPE } from '@prezly/slate-types'; -import type { Editor } from 'slate'; +import type { Editor, Node } from 'slate'; import { jsx } from '../jsx'; -import { createEditor, INLINE_ELEMENT, INLINE_VOID_ELEMENT, VOID_ELEMENT } from '../test-utils'; +import { createEditor } from '../test-utils'; import { insertNodes } from './insertNodes'; @@ -88,12 +86,14 @@ describe('insertNodes', () => { ) as unknown as Editor; - insertNodes(editor, [ - { - children: [{ text: 'dolor' }], - type: PARAGRAPH_NODE_TYPE, - }, - ]); + insertNodes( + editor, + nodes([ + + dolor + , + ]), + ); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -129,12 +129,11 @@ describe('insertNodes', () => { insertNodes( editor, - [ - { - children: [{ text: 'dolor' }], - type: PARAGRAPH_NODE_TYPE, - }, - ], + nodes([ + + dolor + , + ]), { ensureEmptyParagraphAfter: true }, ); @@ -172,16 +171,14 @@ describe('insertNodes', () => { insertNodes( editor, - [ - { - children: [{ text: 'dolor' }], - type: PARAGRAPH_NODE_TYPE, - }, - { - children: [{ text: '' }], - type: PARAGRAPH_NODE_TYPE, - }, - ], + nodes([ + + dolor + , + + + , + ]), { ensureEmptyParagraphAfter: true }, ); @@ -219,19 +216,19 @@ describe('insertNodes', () => { ) as unknown as Editor; - insertNodes(editor, [ - { text: 'xxx' }, - { - type: INLINE_ELEMENT, - children: [{ text: 'yyy' }], - href: 'https://example.com', - } as LinkNode, - { - type: PARAGRAPH_NODE_TYPE, - children: [{ text: 'dolor' }], - }, - { text: 'zzz' }, - ]); + insertNodes( + editor, + nodes([ + xxx, + + yyy + , + + dolor + , + zzz, + ]), + ); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -259,12 +256,14 @@ describe('insertNodes', () => { ) as unknown as Editor; - insertNodes(editor, [ - { - type: PARAGRAPH_NODE_TYPE, - children: [{ text: 'dolor' }], - }, - ]); + insertNodes( + editor, + nodes([ + + dolor + , + ]), + ); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -299,20 +298,20 @@ describe('insertNodes', () => { ) as unknown as Editor; - insertNodes(editor, [ - { text: 'xxx' }, - { - type: INLINE_ELEMENT, - children: [{ text: 'yyy' }], - href: 'https://example.com', - } as LinkNode, - { text: 'zzz' }, - { - children: [{ text: 'dolor' }], - type: PARAGRAPH_NODE_TYPE, - }, - { text: 'aaa' }, - ]); + insertNodes( + editor, + nodes([ + xxx, + + yyy + , + zzz, + + dolor + , + aaa, + ]), + ); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -347,15 +346,16 @@ describe('insertNodes', () => { ) as unknown as Editor; - insertNodes(editor, [ - { text: 'xxx' }, - { - type: INLINE_ELEMENT, - children: [{ text: 'yyy' }], - href: 'https://example.com', - } as LinkNode, - { text: 'zzz' }, - ]); + insertNodes( + editor, + nodes([ + xxx, + + yyy + , + zzz, + ]), + ); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -388,22 +388,12 @@ describe('insertNodes', () => { ) as unknown as Editor; insertNodes(editor, [ - { - children: [{ text: '' }], - type: VOID_ELEMENT, - }, - { - text: 'lorem', - }, - { - bold: true, - text: ' ', - }, - { - bold: true, - text: 'ipsum', - }, - ]); + + + , + lorem, + {' ipsum'}, + ] as unknown as Node[]); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -438,30 +428,25 @@ describe('insertNodes', () => { ) as unknown as Editor; - insertNodes(editor, [ - { - children: [{ text: '' }], - type: VOID_ELEMENT, - }, - { - text: 'lorem', - }, - { - type: INLINE_VOID_ELEMENT, - href: 'https://example.com', - children: [{ text: 'ipsum' }], - } as LinkNode, - { - bold: true, - text: ' ', - }, - { - bold: true, - text: 'dolor', - }, - ]); + insertNodes( + editor, + nodes([ + + + , + lorem, + + ipsum + , + {' dolor'}, + ]), + ); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); }); + +function nodes(nodes: JSX.IntrinsicElements[keyof JSX.IntrinsicElements][]): Node[] { + return nodes as unknown as Node[]; +} diff --git a/packages/slate-commons/src/commands/isMarkActive.test.tsx b/packages/slate-commons/src/commands/isMarkActive.test.tsx index 99d89aa0a..3f1682439 100644 --- a/packages/slate-commons/src/commands/isMarkActive.test.tsx +++ b/packages/slate-commons/src/commands/isMarkActive.test.tsx @@ -1,20 +1,19 @@ /** @jsx jsx */ -import type { Editor } from 'slate'; +import type { BaseText, Editor } from 'slate'; import { jsx } from '../jsx'; import { isMarkActive } from './isMarkActive'; -const EXAMPLE_MARK_1 = 'bold'; -const EXAMPLE_MARK_2 = 'underlined'; +type StyledText = BaseText & { bold?: boolean; underlined?: boolean }; describe('isMarkActive', () => { it('Returns "true" when mark is active', () => { const editor = ( - + lorem ipsum @@ -22,15 +21,15 @@ describe('isMarkActive', () => { ) as unknown as Editor; - expect(isMarkActive(editor, EXAMPLE_MARK_1)).toBe(true); + expect(isMarkActive(editor, 'bold')).toBe(true); }); it('Returns "false" when mark is inactive', () => { const editor = ( - lorem ipsum - + lorem ipsum + lorem ipsum @@ -38,7 +37,7 @@ describe('isMarkActive', () => { ) as unknown as Editor; - expect(isMarkActive(editor, EXAMPLE_MARK_1)).toBe(false); + expect(isMarkActive(editor, 'bold')).toBe(false); }); it('Returns "false" when there is no selection', () => { @@ -50,6 +49,6 @@ describe('isMarkActive', () => { ) as unknown as Editor; - expect(isMarkActive(editor, EXAMPLE_MARK_1)).toBe(false); + expect(isMarkActive(editor, 'bold')).toBe(false); }); }); diff --git a/packages/slate-commons/src/commands/isMarkActive.ts b/packages/slate-commons/src/commands/isMarkActive.ts index 8e6b360e4..f17dcd6df 100644 --- a/packages/slate-commons/src/commands/isMarkActive.ts +++ b/packages/slate-commons/src/commands/isMarkActive.ts @@ -1,7 +1,7 @@ import type { Text } from 'slate'; import { Editor } from 'slate'; -export function isMarkActive(editor: Editor, mark: keyof Omit): boolean { - const marks = Editor.marks(editor); +export function isMarkActive(editor: Editor, mark: keyof Omit): boolean { + const marks = Editor.marks(editor) as Record; return marks ? marks[mark] === true : false; } diff --git a/packages/slate-commons/src/commands/toggleMark.test.tsx b/packages/slate-commons/src/commands/toggleMark.test.tsx index 0b92ccbd8..141e1d4c1 100644 --- a/packages/slate-commons/src/commands/toggleMark.test.tsx +++ b/packages/slate-commons/src/commands/toggleMark.test.tsx @@ -1,12 +1,14 @@ /** @jsx jsx */ -import type { Editor } from 'slate'; +import type { BaseText, Editor } from 'slate'; import { jsx } from '../jsx'; import { toggleMark } from './toggleMark'; -const EXAMPLE_MARK_1 = 'bold'; +interface StyledText extends BaseText { + bold?: boolean; +} describe('toggleMark', () => { it('Adds the mark when it is inactive', () => { @@ -25,7 +27,7 @@ describe('toggleMark', () => { const expected = ( - + lorem ipsum @@ -34,7 +36,7 @@ describe('toggleMark', () => { ) as unknown as Editor; - toggleMark(editor, EXAMPLE_MARK_1); + toggleMark(editor, 'bold'); expect(editor.children).toEqual(expected.children); }); @@ -43,7 +45,7 @@ describe('toggleMark', () => { const editor = ( - + lorem ipsum @@ -64,7 +66,7 @@ describe('toggleMark', () => { ) as unknown as Editor; - toggleMark(editor, EXAMPLE_MARK_1); + toggleMark(editor, 'bold'); expect(editor.children).toEqual(expected.children); }); diff --git a/packages/slate-commons/src/commands/toggleMark.ts b/packages/slate-commons/src/commands/toggleMark.ts index 200e45299..850d8835f 100644 --- a/packages/slate-commons/src/commands/toggleMark.ts +++ b/packages/slate-commons/src/commands/toggleMark.ts @@ -3,12 +3,12 @@ import { Editor } from 'slate'; import { isMarkActive } from './isMarkActive'; -export function toggleMark(editor: Editor, mark: keyof Omit, force?: boolean): void { +export function toggleMark(editor: Editor, mark: keyof Omit, force?: boolean): void { const shouldSet = force ?? !isMarkActive(editor, mark); if (shouldSet) { - Editor.addMark(editor, mark, true); + Editor.addMark(editor, mark as string, true); } else { - Editor.removeMark(editor, mark); + Editor.removeMark(editor, mark as string); } } diff --git a/packages/slate-commons/src/index.ts b/packages/slate-commons/src/index.ts index a2d0b1a7c..46e8abefc 100644 --- a/packages/slate-commons/src/index.ts +++ b/packages/slate-commons/src/index.ts @@ -1,16 +1,3 @@ -import type { ElementNode, TextNode } from '@prezly/slate-types'; -import type { BaseEditor } from 'slate'; -import type { HistoryEditor } from 'slate-history'; -import type { ReactEditor } from 'slate-react'; - -declare module 'slate' { - interface CustomTypes { - Editor: BaseEditor & ReactEditor & HistoryEditor; - Element: ElementNode; - Text: TextNode; - } -} - export { EditableWithExtensions } from './EditableWithExtensions'; export * as EditorCommands from './commands'; export { diff --git a/packages/slate-commons/src/jsx.ts b/packages/slate-commons/src/jsx.ts index 7ab1dfb82..9c14715f5 100644 --- a/packages/slate-commons/src/jsx.ts +++ b/packages/slate-commons/src/jsx.ts @@ -51,7 +51,7 @@ declare global { // using 'h-text' instead of 'text' to avoid collision with React typings, see: // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0182cd9094aa081558a3c4bfc970bbdfb71d891d/types/react/index.d.ts#L3136 'h-text': { - [key: string]: any; // allow marks + [key: string]: any; children?: ReactNode; }; } diff --git a/packages/slate-commons/src/test-utils.ts b/packages/slate-commons/src/test-utils.ts index b7bc416a8..caedaf5cb 100644 --- a/packages/slate-commons/src/test-utils.ts +++ b/packages/slate-commons/src/test-utils.ts @@ -7,6 +7,7 @@ import { LINK_NODE_TYPE, } from '@prezly/slate-types'; import type { Editor } from 'slate'; +import { Element } from 'slate'; export const INLINE_ELEMENT = LINK_NODE_TYPE; export const INLINE_VOID_ELEMENT = LINK_NODE_TYPE; @@ -18,14 +19,14 @@ function withGenericTestElements(editor: T): T { const { isInline, isVoid } = editor; editor.isInline = (element) => - [INLINE_ELEMENT, INLINE_VOID_ELEMENT].includes(element.type as string) - ? true - : isInline(element); + Element.isElementType(element, INLINE_ELEMENT) || + Element.isElementType(element, INLINE_VOID_ELEMENT) || + isInline(element); editor.isVoid = (element) => - [VOID_ELEMENT, INLINE_VOID_ELEMENT].includes(element.type as string) - ? true - : isVoid(element); + Element.isElementType(element, VOID_ELEMENT) || + Element.isElementType(element, INLINE_VOID_ELEMENT) || + isVoid(element); return editor; } diff --git a/packages/slate-editor/src/modules/editor-v4/EditorV4.tsx b/packages/slate-editor/src/modules/editor-v4/EditorV4.tsx index d02a2d377..11c8e6e7c 100644 --- a/packages/slate-editor/src/modules/editor-v4/EditorV4.tsx +++ b/packages/slate-editor/src/modules/editor-v4/EditorV4.tsx @@ -336,6 +336,7 @@ const EditorV4: FunctionComponent = (props) => { > Date: Wed, 13 Apr 2022 17:58:42 +0300 Subject: [PATCH 03/24] Refactor slate-lists tests - to not depend on global JSX declarations - to not depend on `@prezly/slate-types` - to not depend on `@prezly/slate-hyperscript` --- packages/slate-lists/src/jsx.ts | 200 +-- .../src/lib/decreaseDepth.test.tsx | 922 ++++++------- .../src/lib/getListItemsInRange.test.tsx | 268 ++-- .../src/lib/increaseDepth.test.tsx | 982 +++++++------- .../slate-lists/src/lib/setListType.test.tsx | 306 ++--- .../src/lib/splitListItem.test.tsx | 954 +++++++------- .../slate-lists/src/lib/unwrapList.test.tsx | 364 ++--- .../slate-lists/src/lib/wrapInList.test.tsx | 311 ++--- packages/slate-lists/src/test-utils.ts | 48 - packages/slate-lists/src/types.ts | 21 +- packages/slate-lists/src/withLists.test.tsx | 1167 +++++++++-------- packages/slate-lists/tsconfig.json | 1 - 12 files changed, 2792 insertions(+), 2752 deletions(-) delete mode 100644 packages/slate-lists/src/test-utils.ts diff --git a/packages/slate-lists/src/jsx.ts b/packages/slate-lists/src/jsx.ts index 748a44c70..1cb0d7cf1 100644 --- a/packages/slate-lists/src/jsx.ts +++ b/packages/slate-lists/src/jsx.ts @@ -1,108 +1,112 @@ -/* eslint-disable @typescript-eslint/no-namespace */ +import type { ComponentType, ReactNode } from 'react'; +import type * as Slate from 'slate'; +import { createEditor, Element } from 'slate'; +import { createEditor as createEditorFactory, createHyperscript } from 'slate-hyperscript'; -import { createHyperscript } from '@prezly/slate-hyperscript'; -import { - BULLETED_LIST_NODE_TYPE, - LIST_ITEM_NODE_TYPE, - LIST_ITEM_TEXT_NODE_TYPE, - NUMBERED_LIST_NODE_TYPE, - PARAGRAPH_NODE_TYPE, -} from '@prezly/slate-types'; -import type { ReactNode } from 'react'; +import type { ListsSchema } from './types'; +import { ListType } from './types'; +import { withLists } from './withLists'; -import { INLINE_ELEMENT, UNWRAPPABLE_ELEMENT } from './test-utils'; +type AllOrNothing = { [key in keyof T]: T[key] } | { [key in keyof T]?: never }; -declare global { - namespace JSX { - // This is copied from "packages/slate-hyperscript/src/index.ts" - // TODO: find a way to not have to copy it and still have type hinting - // when using hyperscript. - // See: https://github.com/prezly/slate/issues/6 - interface IntrinsicElements { - anchor: - | { - offset?: never; - path?: never; - } - | { - offset: number; - path: number[]; - }; - cursor: { - children?: never; - }; - editor: { - children?: ReactNode; - }; - element: { - [key: string]: any; - children?: ReactNode; - type: string; - }; - focus: - | { - offset?: never; - path?: never; - } - | { - offset: number; - path: number[]; - }; - fragment: { - children?: ReactNode; - }; - selection: { - children?: ReactNode; - }; - // using 'h-text' instead of 'text' to avoid collision with React typings, see: - // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0182cd9094aa081558a3c4bfc970bbdfb71d891d/types/react/index.d.ts#L3136 - 'h-text': { - [key: string]: any; // allow marks - children?: ReactNode; - }; - } +export const PARAGRAPH_TYPE = 'paragraph'; +export const LINK_TYPE = 'link'; +export const DIVIDER_TYPE = 'divider'; +export const ORDERED_LIST_TYPE = ListType.ORDERED; +export const UNORDERED_LIST_TYPE = ListType.UNORDERED; +export const LIST_ITEM_TYPE = 'li'; +export const LIST_ITEM_TEXT_TYPE = 'li-text'; + +export const Editor = 'editor' as any as ComponentType; +export const Untyped = 'untyped' as any as ComponentType; +export const Anchor = 'anchor' as any as ComponentType>; +export const Focus = 'focus' as any as ComponentType>; +export const Cursor = 'cursor' as any as ComponentType; +export const Selection = 'selection' as any as ComponentType; +export const Fragment = 'fragment' as any as ComponentType; +export const Text = 'text' as any as ComponentType<{ children?: ReactNode; [mark: string]: any }>; +export const Paragraph = PARAGRAPH_TYPE as any as ComponentType; +export const Link = LINK_TYPE as any as ComponentType<{ href: string; children?: ReactNode }>; +export const Divider = DIVIDER_TYPE as any as ComponentType; +export const OrderedList = ORDERED_LIST_TYPE as any as ComponentType; +export const UnorderedList = UNORDERED_LIST_TYPE as any as ComponentType; +export const ListItem = LIST_ITEM_TYPE as any as ComponentType; +export const ListItemText = LIST_ITEM_TEXT_TYPE as any as ComponentType; + +const INLINE_ELEMENTS = [LINK_TYPE]; +const VOID_ELEMENTS = [DIVIDER_TYPE]; - interface IntrinsicElements { - 'h-element-with-no-type': { - children?: ReactNode; - }; - // it's a "link" in our tests - because we have to pick something - // but it could have been any other inline element - 'h-inline-element': { - children?: ReactNode; - href: string; - }; - 'h-li': { - children?: ReactNode; - }; - 'h-li-text': { - children?: ReactNode; - }; - 'h-ol': { - children?: ReactNode; - }; - 'h-p': { - children?: ReactNode; - }; - 'h-ul': { - children?: ReactNode; - }; - 'h-unwrappable-element': { - children?: ReactNode; - }; +const SCHEMA: ListsSchema = { + isDefaultTextNode(node): node is Element { + return Element.isElementType(node, PARAGRAPH_TYPE); + }, + isListNode(node, type): node is Element { + if (type) { + return Element.isElementType(node, type); } - } -} + return ( + Element.isElementType(node, ORDERED_LIST_TYPE) || + Element.isElementType(node, UNORDERED_LIST_TYPE) + ); + }, + isListItemNode(node): node is Element { + return Element.isElementType(node, LIST_ITEM_TYPE); + }, + isListItemTextNode(node): node is Element { + return Element.isElementType(node, LIST_ITEM_TEXT_TYPE); + }, + isListNestable(node): boolean { + return Element.isElementType(node, PARAGRAPH_TYPE); + }, + createDefaultTextNode(props = {}) { + return { children: [{ text: '' }], ...props, type: PARAGRAPH_TYPE }; + }, + createListNode(type: ListType = ListType.UNORDERED, props = {}) { + return { children: [{ text: '' }], ...props, type }; + }, + createListItemNode(props = {}) { + return { children: [{ text: '' }], ...props, type: LIST_ITEM_TYPE }; + }, + createListItemTextNode(props = {}) { + return { children: [{ text: '' }], ...props, type: LIST_ITEM_TEXT_TYPE }; + }, +}; export const jsx = createHyperscript({ elements: { - 'h-element-with-no-type': {}, - 'h-inline-element': { type: INLINE_ELEMENT }, - 'h-li': { type: LIST_ITEM_NODE_TYPE }, - 'h-li-text': { type: LIST_ITEM_TEXT_NODE_TYPE }, - 'h-ol': { type: NUMBERED_LIST_NODE_TYPE }, - 'h-p': { type: PARAGRAPH_NODE_TYPE }, - 'h-ul': { type: BULLETED_LIST_NODE_TYPE }, - 'h-unwrappable-element': { type: UNWRAPPABLE_ELEMENT }, + untyped: {}, + [PARAGRAPH_TYPE]: { type: PARAGRAPH_TYPE }, + [LINK_TYPE]: { type: LINK_TYPE }, + [DIVIDER_TYPE]: { type: DIVIDER_TYPE }, + [ORDERED_LIST_TYPE]: { type: ORDERED_LIST_TYPE }, + [UNORDERED_LIST_TYPE]: { type: UNORDERED_LIST_TYPE }, + [LIST_ITEM_TYPE]: { type: LIST_ITEM_TYPE }, + [LIST_ITEM_TEXT_TYPE]: { type: LIST_ITEM_TEXT_TYPE }, + }, + creators: { + editor: createEditorFactory(function () { + const decorators = [withInlineElements, withVoidElements, withLists(SCHEMA)]; + + return decorators.reduce((editor, decorate) => decorate(editor), createEditor()); + }), }, }); + +function withVoidElements(editor: T, types: string[] = VOID_ELEMENTS): T { + const { isVoid } = editor; + editor.isVoid = function (node) { + return types.some((type) => Element.isElementType(node, type)) || isVoid(node); + }; + return editor; +} + +function withInlineElements( + editor: T, + types: string[] = INLINE_ELEMENTS, +): T { + const { isInline } = editor; + editor.isInline = function (node) { + return types.some((type) => Element.isElementType(node, type)) || isInline(node); + }; + return editor; +} diff --git a/packages/slate-lists/src/lib/decreaseDepth.test.tsx b/packages/slate-lists/src/lib/decreaseDepth.test.tsx index 30bad5476..7a0c0ea6f 100644 --- a/packages/slate-lists/src/lib/decreaseDepth.test.tsx +++ b/packages/slate-lists/src/lib/decreaseDepth.test.tsx @@ -1,37 +1,49 @@ /** @jsx jsx */ -import type { Editor } from 'slate'; +import { + jsx, + Editor, + OrderedList, + UnorderedList, + ListItem, + ListItemText, + Text, + Paragraph, + Cursor, + Anchor, + Focus, +} from '../jsx'; +import type { ListsEditor } from '../types'; -import { jsx } from '../jsx'; -import { createListsEditor, lists } from '../test-utils'; +import { decreaseDepth } from './decreaseDepth'; describe('decreaseDepth - no selected items', () => { it('Does nothing when there is no selection', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -40,307 +52,307 @@ describe('decreaseDepth - no selected items', () => { describe('decreaseDepth - single item selected', () => { it('Converts list item to a paragraph when there is no grandparent list', () => { - const editor = createListsEditor( - - - - - + const editor = ( + + + + + lorem ipsum - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - + + + lorem ipsum - - - - - ) as unknown as Editor; + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Moves list-item to the grandparent list', () => { - const editor = createListsEditor( - - - - - lorem - - - - - ipsum - - - - - + const editor = ( + + + + + lorem + + + + + ipsum + + + + + dolor sit amet - - - - - - - - , - ); + + + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - - - - ipsum - - - - - - - + + + + + lorem + + + + + ipsum + + + + + + + dolor sit amet - - - - - - - ) as unknown as Editor; + + + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Moves list-item to the grandparent list and removes the parent list if empty', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - + const editor = ( + + + + + lorem ipsum + + + + + dolor sit amet - - - - - - - - , - ); + + + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - + + + + + lorem ipsum + + + + + dolor sit amet - - - - - - - ) as unknown as Editor; + + + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Moves list-item to the grandparent list and moves succeeding siblings into a new nested list', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - aaa - - - - - + const editor = ( + + + + + lorem ipsum + + + + + aaa + + + + + dolor sit amet - - - - - - - bbb - - - - - ccc - - - - - - , - ); + + + + + + + bbb + + + + + ccc + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - aaa - - - - - - - + + + + + lorem ipsum + + + + + aaa + + + + + + + dolor sit amet - - - - - - - bbb - - - - - ccc - - - - - - - ) as unknown as Editor; + + + + + + + bbb + + + + + ccc + + + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Converts list-item into a paragraph, moves it out of the list and moves succeeding siblings into a new list', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - aaa - - - - - - - + const editor = ( + + + + + lorem ipsum + + + + + aaa + + + + + + + dolor sit amet - - - - - - - bbb - - - - - ccc - - - - , - ); + + + + + + + bbb + + + + + ccc + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - aaa - - - - - - - + + + + + lorem ipsum + + + + + aaa + + + + + + + dolor sit amet - - - - - - - bbb - - - - - ccc - - - - - ) as unknown as Editor; + + + + + + + bbb + + + + + ccc + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -349,191 +361,191 @@ describe('decreaseDepth - single item selected', () => { describe('decreaseDepth - multiple items selected', () => { it('Decreases depth of all list items in selection that have no list items ancesors in selection', () => { - const editor = createListsEditor( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - + const editor = ( + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - Nested Lists A3 - - - - - - - Nested Lists B - - - - - Nested Lists B1 - - - - - Nested Lists B1a - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested Lists A3 + + + + + + + Nested Lists B + + + + + Nested Lists B1 + + + + + Nested Lists B1a + + + + + Nested Lists - B1b - - - - - - - - Nested Lists B2 - - - - - - - Nested Lists C - - - - , - ); + B1b + + + + + + + + Nested Lists B2 + + + + + + + Nested Lists C + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - - - + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - Nested Lists A3 - - - - - Nested Lists B - - - - - Nested Lists B1 - - - - - Nested Lists B1a - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested Lists A3 + + + + + Nested Lists B + + + + + Nested Lists B1 + + + + + Nested Lists B1a + + + + + Nested Lists - B1b - - - - - - - - Nested Lists B2 - - - - - Nested Lists C - - - - - ) as unknown as Editor; + B1b + + + + + + + + Nested Lists B2 + + + + + Nested Lists C + + + + + ) as unknown as ListsEditor; - lists.decreaseDepth(editor); + decreaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); diff --git a/packages/slate-lists/src/lib/getListItemsInRange.test.tsx b/packages/slate-lists/src/lib/getListItemsInRange.test.tsx index 69ddd2566..1f5bd0683 100644 --- a/packages/slate-lists/src/lib/getListItemsInRange.test.tsx +++ b/packages/slate-lists/src/lib/getListItemsInRange.test.tsx @@ -1,147 +1,157 @@ /** @jsx jsx */ -import { jsx } from '../jsx'; -import { createListsEditor, options } from '../test-utils'; +import { + jsx, + Editor, + OrderedList, + UnorderedList, + ListItem, + ListItemText, + Text, + Anchor, + Focus, +} from '../jsx'; +import type { ListsEditor } from '../types'; import { getListItemsInRange } from './getListItemsInRange'; describe('getListItemsInRange', () => { it('Returns an empty array when there is no selection', () => { - const editor = createListsEditor( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - - - Nested Lists C - - - - , - ); + const editor = ( + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + + + Nested Lists C + + + + + ) as unknown as ListsEditor; - const listItemsInRange = getListItemsInRange(options, editor, editor.selection); + const listItemsInRange = getListItemsInRange(editor, editor.selection); expect(listItemsInRange).toEqual([]); }); it('Finds all partially selected list items', () => { - const editor = createListsEditor( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - + const editor = ( + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - Nested Lists A3 - - - - - - - Nested Lists B - - - - - Nested Lists B1 - - - - - Nested Lists B1a - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested Lists A3 + + + + + + + Nested Lists B + + + + + Nested Lists B1 + + + + + Nested Lists B1a + + + + + Nested Lists - B1b - - - - - - - - Nested Lists B2 - - - - - - - Nested Lists C - - - - , - ); + B1b + + + + + + + + Nested Lists B2 + + + + + + + Nested Lists C + + + + + ) as unknown as ListsEditor; - const listItemsInRange = getListItemsInRange(options, editor, editor.selection); + const listItemsInRange = getListItemsInRange(editor, editor.selection); const listItemsPathsInRange = listItemsInRange.map(([, path]) => path); expect(listItemsPathsInRange).toEqual([ diff --git a/packages/slate-lists/src/lib/increaseDepth.test.tsx b/packages/slate-lists/src/lib/increaseDepth.test.tsx index 4a6b3c1e6..6a7258f8e 100644 --- a/packages/slate-lists/src/lib/increaseDepth.test.tsx +++ b/packages/slate-lists/src/lib/increaseDepth.test.tsx @@ -1,37 +1,49 @@ /** @jsx jsx */ -import type { Editor } from 'slate'; - -import { jsx } from '../jsx'; -import { createListsEditor, lists } from '../test-utils'; +import { + jsx, + Editor, + UnorderedList, + OrderedList, + ListItem, + ListItemText, + Text, + Paragraph, + Anchor, + Focus, + Cursor, +} from '../jsx'; +import type { ListsEditor } from '../types'; + +import { increaseDepth } from './increaseDepth'; describe('increaseDepth - no selected items', () => { it('Does nothing when there is no selection', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -40,196 +52,196 @@ describe('increaseDepth - no selected items', () => { describe('increaseDepth - single item selected', () => { it('Does nothing when there is no preceding sibling list item', () => { - const editor = createListsEditor( - - - - - + const editor = ( + + + + + lorem ipsum - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - + + + + + lorem ipsum - - - - - - - ) as unknown as Editor; + + + + + + + ) as unknown as ListsEditor; - lists.increaseDepth(editor); + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Moves list-item to the child list of a preceding sibling list item', () => { - const editor = createListsEditor( - - - - - lorem - - - - - ipsum - - - - - - - + const editor = ( + + + + + lorem + + + + + ipsum + + + + + + + dolor sit amet - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - - - - ipsum - - - - - + + + + + lorem + + + + + ipsum + + + + + dolor sit amet - - - - - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + + + + + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Creates a child list in preceding sibling list item and moves list-item there', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - + const editor = ( + + + + + lorem ipsum + + + + + dolor sit amet - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - + + + + + lorem ipsum + + + + + dolor sit amet - - - - - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + + + + + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Creates a child list in preceding sibling list item and moves list-item there, maintaining list type', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - + const editor = ( + + + + + lorem ipsum + + + + + dolor sit amet - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - + + + + + lorem ipsum + + + + + dolor sit amet - - - - - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + + + + + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -238,258 +250,258 @@ describe('increaseDepth - single item selected', () => { describe('increaseDepth - multiple items selected', () => { it('Increases depth of all indentable list items in selection that have no list items ancestors in selection (A)', () => { - const editor = createListsEditor( - - - - - - + const editor = ( + + + + + + aaa - - - - - - bbb - - - - - + + + + + + bbb + + + + + ccc - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - + + + + + + aaa - - - - - - bbb - - - - - + + + + + + bbb + + + + + ccc - - - - - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + + + + + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Increases depth of all indentable list items in selection that have no list items ancestors in selection (B)', () => { - const editor = createListsEditor( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - + const editor = ( + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - Nested Lists A3 - - - - - - - Nested Lists B - - - - - Nested Lists B1 - - - - - Nested Lists B1a - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested Lists A3 + + + + + + + Nested Lists B + + + + + Nested Lists B1 + + + + + Nested Lists B1a + + + + + Nested Lists - B1b - - - - - - - - Nested Lists B2 - - - - - - - Nested Lists C - - - - , - ); + B1b + + + + + + + + Nested Lists B2 + + + + + + + Nested Lists C + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - Nested Lists A3 - - - - - - - Nested Lists B - - - - - Nested Lists B1 - - - - - Nested Lists B1a - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested Lists A3 + + + + + + + Nested Lists B + + + + + Nested Lists B1 + + + + + Nested Lists B1a + + + + + Nested Lists - B1b - - - - - - - - Nested Lists B2 - - - - - - - - - Nested Lists C - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + B1b + + + + + + + + Nested Lists B2 + + + + + + + + + Nested Lists C + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -498,81 +510,81 @@ describe('increaseDepth - multiple items selected', () => { describe('increaseDepth - multiple items and paragraphs selected', () => { it('Converts paragraphs into lists items and merges them together', () => { - const editor = createListsEditor( - - - - + const editor = ( + + + + aaa - - - - - - bbb - - - - - ccc - - - - - ddd - - - - - + + + + + + bbb + + + + + ccc + + + + + ddd + + + + + eee - - - - , - ); + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - + + + + + + aaa - - - - - - bbb - - - - - ccc - - - - - ddd - - - - - - - + + + + + + bbb + + + + + ccc + + + + + ddd + + + + + + + eee - - - - - - - ) as unknown as Editor; - - lists.increaseDepth(editor); + + + + + + + ) as unknown as ListsEditor; + + increaseDepth(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); diff --git a/packages/slate-lists/src/lib/setListType.test.tsx b/packages/slate-lists/src/lib/setListType.test.tsx index 12cb2e9ad..0cfc58cce 100644 --- a/packages/slate-lists/src/lib/setListType.test.tsx +++ b/packages/slate-lists/src/lib/setListType.test.tsx @@ -1,38 +1,48 @@ /** @jsx jsx */ -import { NUMBERED_LIST_NODE_TYPE } from '@prezly/slate-types'; -import type { Editor } from 'slate'; +import { + jsx, + Editor, + OrderedList, + UnorderedList, + ListItem, + ListItemText, + Text, + Anchor, + Focus, +} from '../jsx'; +import type { ListsEditor } from '../types'; +import { ListType } from '../types'; -import { jsx } from '../jsx'; -import { createListsEditor, lists } from '../test-utils'; +import { setListType } from './setListType'; describe('setListType - no selection', () => { it('Does nothing when there is no selection', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; - lists.setListType(editor, NUMBERED_LIST_NODE_TYPE); + setListType(editor, ListType.ORDERED); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -41,135 +51,135 @@ describe('setListType - no selection', () => { describe('setListType - selection with paragraphs and lists of multiple types', () => { it('Changes lists types in selection', () => { - const editor = createListsEditor( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - + const editor = ( + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested - Lists A3 - - - - - - - - Nested Lists B - - - - , - ); + Lists A3 + + + + + + + + Nested Lists B + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - Nested Lists A - - - - - Nested Lists A1 - - - - - Nested Lists A1a - - - - - Nested Lists A1b - - - - - - - + + + + + Nested Lists A + + + + + Nested Lists A1 + + + + + Nested Lists A1a + + + + + Nested Lists A1b + + + + + + + Nested - Lists A2 - - - - - - Nested Lists A2a - - - - - Nested Lists A2b - - - - - - - + Lists A2 + + + + + + Nested Lists A2a + + + + + Nested Lists A2b + + + + + + + Nested - Lists A3 - - - - - - - - Nested Lists B - - - - - ) as unknown as Editor; + Lists A3 + + + + + + + + Nested Lists B + + + + + ) as unknown as ListsEditor; - lists.setListType(editor, NUMBERED_LIST_NODE_TYPE); + setListType(editor, ListType.ORDERED); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); diff --git a/packages/slate-lists/src/lib/splitListItem.test.tsx b/packages/slate-lists/src/lib/splitListItem.test.tsx index 818ae1f47..3c784f0b6 100644 --- a/packages/slate-lists/src/lib/splitListItem.test.tsx +++ b/packages/slate-lists/src/lib/splitListItem.test.tsx @@ -1,80 +1,92 @@ /** @jsx jsx */ -import type { Editor } from 'slate'; - -import { jsx } from '../jsx'; -import { createListsEditor, lists } from '../test-utils'; +import { + jsx, + Editor, + UnorderedList, + OrderedList, + ListItem, + ListItemText, + Text, + Paragraph, + Cursor, + Anchor, + Focus, +} from '../jsx'; +import type { ListsEditor } from '../types'; + +import { splitListItem } from './splitListItem'; describe('splitListItem - no selected items', () => { it('Does nothing when there is no selection', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Does nothing when there are no list items in selection', () => { - const editor = createListsEditor( - - - + const editor = ( + + + lorem - - - - - - - lorem ipsum - - - - , - ); + + + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - + + + lorem - - - - - - - lorem ipsum - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -83,126 +95,126 @@ describe('splitListItem - no selected items', () => { describe('splitListItem - collapsed selection', () => { it('Creates new empty sibling list item when cursor is at the end of an item', () => { - const editor = createListsEditor( - - - - - + const editor = ( + + + + + lorem ipsum - - - - - - , - ); + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - - - - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + lorem ipsum + + + + + + + + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Creates new empty sibling list item when cursor is at the beginning of an item', () => { - const editor = createListsEditor( - - - - - - + const editor = ( + + + + + + lorem ipsum - - - - - , - ); + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - - - - - - + + + + + + + + + + + lorem ipsum - - - - - - ) as unknown as Editor; + + + + + + ) as unknown as ListsEditor; - lists.splitListItem(editor); + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Creates a new sibling list item when cursor is in the middle of an item', () => { - const editor = createListsEditor( - - - - - + const editor = ( + + + + + lorem - + ipsum - - - - - , - ); + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - - - - - + + + + + lorem + + + + + + ipsum - - - - - - ) as unknown as Editor; + + + + + + ) as unknown as ListsEditor; - lists.splitListItem(editor); + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -211,198 +223,198 @@ describe('splitListItem - collapsed selection', () => { describe('splitListItem - collapsed selection - nested lists', () => { it('Creates new sibling list item in nested list when cursor is at the end of an item', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - + const editor = ( + + + + + lorem ipsum + + + + + lorem ipsum - - - - - - - - - dolor sit - - - - , - ); + + + + + + + + + dolor sit + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - lorem ipsum - - - - - - - - - - - - - - dolor sit - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + lorem ipsum + + + + + lorem ipsum + + + + + + + + + + + + + + dolor sit + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Creates new sibling list item in nested list when cursor is at the beginning of an item', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - - + const editor = ( + + + + + lorem ipsum + + + + + + lorem ipsum - - - - - - - - dolor sit - - - - , - ); + + + + + + + + dolor sit + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - - - - - - - + + + + + lorem ipsum + + + + + + + + + + + lorem ipsum - - - - - - - - dolor sit - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + + + + dolor sit + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Creates new sibling list item in nested list when cursor is in the middle of an item', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - + const editor = ( + + + + + lorem ipsum + + + + + lorem - + ipsum - - - - - - - - dolor sit - - - - , - ); + + + + + + + + dolor sit + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - lorem - - - - - - + + + + + lorem ipsum + + + + + lorem + + + + + + ipsum - - - - - - - - dolor sit - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + + + + dolor sit + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -411,79 +423,79 @@ describe('splitListItem - collapsed selection - nested lists', () => { describe('splitListItem - collapsed selection - deeply nested lists', () => { it('Creates new sibling list item in nested list when cursor is at the end of an item with nested list', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - + const editor = ( + + + + + lorem ipsum + + + + + lorem ipsum - - - - - - - lorem - - - - - ipsum - - - - - - - - , - ); + + + + + + + lorem + + + + + ipsum + + + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - lorem ipsum - - - - - - - - - - - - lorem - - - - - ipsum - - - - - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + lorem ipsum + + + + + lorem ipsum + + + + + + + + + + + + lorem + + + + + ipsum + + + + + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -492,89 +504,89 @@ describe('splitListItem - collapsed selection - deeply nested lists', () => { describe('splitListItem - expanded selection - deeply nested lists', () => { it('Removes selected text, creates new sibling list item in nested list when cursor is at the end of an item with nested list', () => { - // it's an interesting case because Transforms.delete will break in half, - // leaving as its only child which will be normalized by withLists - const editor = createListsEditor( - - - - - lorem ipsum - - - - - - + // it's an interesting case because Transforms.delete will break in half, + // leaving as its only child which will be normalized by withLists + const editor = ( + + + + + lorem ipsum + + + + + + lorem ipsum - - - - - - + + + + + + lorem ipsum - - - - - - - lorem - - - - - ipsum - - - - - - - - , - ); + + + + + + + lorem + + + + + ipsum + + + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - - - - - - - - - - - - - lorem - - - - - ipsum - - - - - - - - - ) as unknown as Editor; - - lists.splitListItem(editor); + + + + + lorem ipsum + + + + + + + + + + + + + + + + + lorem + + + + + ipsum + + + + + + + + + ) as unknown as ListsEditor; + + splitListItem(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); diff --git a/packages/slate-lists/src/lib/unwrapList.test.tsx b/packages/slate-lists/src/lib/unwrapList.test.tsx index 338e27b12..f4ff8af3f 100644 --- a/packages/slate-lists/src/lib/unwrapList.test.tsx +++ b/packages/slate-lists/src/lib/unwrapList.test.tsx @@ -1,37 +1,47 @@ /** @jsx jsx */ -import type { Editor } from 'slate'; - -import { jsx } from '../jsx'; -import { createListsEditor, lists } from '../test-utils'; +import { + jsx, + Editor, + OrderedList, + UnorderedList, + ListItem, + ListItemText, + Text, + Paragraph, + Cursor, +} from '../jsx'; +import type { ListsEditor } from '../types'; + +import { unwrapList } from './unwrapList'; describe('unwrapList - no selection', () => { it('Does nothing when there is no selection', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; - - lists.unwrapList(editor); + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; + + unwrapList(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -40,181 +50,181 @@ describe('unwrapList - no selection', () => { describe('unwrapList - selection within item', () => { it('Converts only list item into a paragraph', () => { - const editor = createListsEditor( - - - - - + const editor = ( + + + + + lorem - ipsum - - - - - , - ); + ipsum + + + + + + ) as unknown as ListsEditor; const expected = ( - - - + + + lorem - ipsum - - - - ) as unknown as Editor; + ipsum + + + + ) as unknown as ListsEditor; - lists.unwrapList(editor); + unwrapList(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Converts middle list item into a paragraph', () => { - const editor = createListsEditor( - - - - - dolor - - - - - + const editor = ( + + + + + dolor + + + + + lorem - ipsum - - - - - - sit - - - - , - ); + ipsum + + + + + + sit + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - dolor - - - - - + + + + + dolor + + + + + lorem - ipsum - - - - - - sit - - - - - ) as unknown as Editor; - - lists.unwrapList(editor); + ipsum + + + + + + sit + + + + + ) as unknown as ListsEditor; + + unwrapList(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); }); it('Converts nested middle list item into a paragraph', () => { - const editor = createListsEditor( - - - - - lorem - - - - - ipsum - - - - - lorem - - - - - + const editor = ( + + + + + lorem + + + + + ipsum + + + + + lorem + + + + + ipsum - - - - - - - dolor - - - - - - - dolor - - - - , - ); + + + + + + + dolor + + + + + + + dolor + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - - - - ipsum - - - - - lorem - - - - - - - + + + + + lorem + + + + + ipsum + + + + + lorem + + + + + + + ipsum - - - - - - - dolor - - - - - dolor - - - - - ) as unknown as Editor; - - lists.unwrapList(editor); + + + + + + + dolor + + + + + dolor + + + + + ) as unknown as ListsEditor; + + unwrapList(editor); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); diff --git a/packages/slate-lists/src/lib/wrapInList.test.tsx b/packages/slate-lists/src/lib/wrapInList.test.tsx index 0f10cba7b..eb477dafb 100644 --- a/packages/slate-lists/src/lib/wrapInList.test.tsx +++ b/packages/slate-lists/src/lib/wrapInList.test.tsx @@ -1,50 +1,61 @@ /** @jsx jsx */ -import { BULLETED_LIST_NODE_TYPE } from '@prezly/slate-types'; -import type { Editor } from 'slate'; - -import { jsx } from '../jsx'; -import { createListsEditor, lists } from '../test-utils'; +import { + Anchor, + Divider, + Editor, + Focus, + jsx, + ListItem, + ListItemText, + Paragraph, + Text, + UnorderedList, +} from '../jsx'; +import type { ListsEditor } from '../types'; +import { ListType } from '../types'; + +import { wrapInList } from './wrapInList'; describe('wrapInList - no selection', () => { it('Does nothing when there is no selection', () => { - const editor = createListsEditor( - - - aaa - - - - - lorem ipsum - - - - - bbb - - , - ); + const editor = ( + + + aaa + + + + + lorem ipsum + + + + + bbb + + + ) as unknown as ListsEditor; const expected = ( - - - aaa - - - - - lorem ipsum - - - - - bbb - - - ) as unknown as Editor; - - lists.wrapInList(editor, BULLETED_LIST_NODE_TYPE); + + + aaa + + + + + lorem ipsum + + + + + bbb + + + ) as unknown as ListsEditor; + + wrapInList(editor, ListType.UNORDERED); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -53,35 +64,35 @@ describe('wrapInList - no selection', () => { describe('wrapInList - selection with wrappable nodes', () => { it('Converts wrappable node into list', () => { - const editor = createListsEditor( - - - - + const editor = ( + + + + aaa - - - - , - ); + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - + + + + + + aaa - - - - - - - ) as unknown as Editor; + + + + + + + ) as unknown as ListsEditor; - lists.wrapInList(editor, BULLETED_LIST_NODE_TYPE); + wrapInList(editor, ListType.UNORDERED); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -90,59 +101,59 @@ describe('wrapInList - selection with wrappable nodes', () => { describe('wrapInList - selection with lists and wrappable nodes', () => { it('Converts wrappable nodes into lists items and merges them together', () => { - const editor = createListsEditor( - - - - + const editor = ( + + + + aaa - - - - - - lorem ipsum - - - - - + + + + + + lorem ipsum + + + + + bbb - - - - , - ); + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - + + + + + + aaa - - - - - - lorem ipsum - - - - - + + + + + + lorem ipsum + + + + + bbb - - - - - - - ) as unknown as Editor; + + + + + + + ) as unknown as ListsEditor; - lists.wrapInList(editor, BULLETED_LIST_NODE_TYPE); + wrapInList(editor, ListType.UNORDERED); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); @@ -151,53 +162,53 @@ describe('wrapInList - selection with lists and wrappable nodes', () => { describe('wrapInList - selection with lists, wrappable & unwrappable nodes', () => { it('Converts wrappable nodes into lists items and merges them together, but leaves out unwrappable nodes', () => { - const editor = createListsEditor( - - - - + const editor = ( + + + + aaa - - - - bbb - - - + + + + bbb + + + ccc - - - - , - ); + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - + + + + + + aaa - - - - - - bbb - - - - - + + + + + + bbb + + + + + ccc - - - - - ) as unknown as Editor; + + + + + ) as unknown as ListsEditor; - lists.wrapInList(editor, BULLETED_LIST_NODE_TYPE); + wrapInList(editor, ListType.UNORDERED); expect(editor.children).toEqual(expected.children); expect(editor.selection).toEqual(expected.selection); diff --git a/packages/slate-lists/src/test-utils.ts b/packages/slate-lists/src/test-utils.ts deleted file mode 100644 index f71b48faf..000000000 --- a/packages/slate-lists/src/test-utils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint-disable no-param-reassign */ - -import { - BULLETED_LIST_NODE_TYPE, - DIVIDER_NODE_TYPE, - LINK_NODE_TYPE, - LIST_ITEM_NODE_TYPE, - LIST_ITEM_TEXT_NODE_TYPE, - NUMBERED_LIST_NODE_TYPE, - PARAGRAPH_NODE_TYPE, - isElementNode, -} from '@prezly/slate-types'; -import type { Editor } from 'slate'; - -import { Lists } from './Lists'; -import type { ListsOptions } from './types'; -import { withLists } from './withLists'; - -export const INLINE_ELEMENT = LINK_NODE_TYPE; - -export const UNWRAPPABLE_ELEMENT = DIVIDER_NODE_TYPE; - -export const options: ListsOptions = { - defaultBlockType: PARAGRAPH_NODE_TYPE, - listItemTextType: LIST_ITEM_TEXT_NODE_TYPE, - listItemType: LIST_ITEM_NODE_TYPE, - listTypes: [BULLETED_LIST_NODE_TYPE, NUMBERED_LIST_NODE_TYPE], - wrappableTypes: [PARAGRAPH_NODE_TYPE], -}; - -export const lists = Lists(options); - -function withInlineElement(editor: T): T { - const { isInline } = editor; - - editor.isInline = (element) => { - if (isElementNode(element, INLINE_ELEMENT)) { - return true; - } - return isInline(element); - }; - - return editor; -} - -export function createListsEditor(input: JSX.Element) { - return withInlineElement(withLists(options)(input as unknown as Editor)); -} diff --git a/packages/slate-lists/src/types.ts b/packages/slate-lists/src/types.ts index d25a6a3bb..83a45fc66 100644 --- a/packages/slate-lists/src/types.ts +++ b/packages/slate-lists/src/types.ts @@ -1,4 +1,4 @@ -import type { BaseEditor, Element, Node } from 'slate'; +import type { BaseEditor, Descendant, Element, Node } from 'slate'; export enum ListType { ORDERED = 'ol', @@ -6,26 +6,23 @@ export enum ListType { } export interface ListsSchema { - isDefaultTextNode(node: Node): node is T; + isDefaultTextNode(node: Node): node is Element; - isListNode(node: Node, type?: ListType): node is T; + isListNode(node: Node, type?: ListType): node is Element; - isListItemNode(node: Node): node is T; + isListItemNode(node: Node): node is Element; - isListItemTextNode(node: Node): node is T; + isListItemTextNode(node: Node): node is Element; isListNestable(node: Node): boolean; - createDefaultTextNode(props?: Partial>): T; + createDefaultTextNode(props?: { children?: Descendant[] }): Element; - createListNode( - type?: ListType, - props?: Partial>, - ): T; + createListNode(type?: ListType, props?: { children?: Descendant[] }): Element; - createListItemNode(props?: Partial>): T; + createListItemNode(props?: { children?: Descendant[] }): Element; - createListItemTextNode(props?: Partial>): T; + createListItemTextNode(props?: { children?: Descendant[] }): Element; } export interface ListsEditor extends ListsSchema, BaseEditor {} diff --git a/packages/slate-lists/src/withLists.test.tsx b/packages/slate-lists/src/withLists.test.tsx index 0162840a9..f7dafdd64 100644 --- a/packages/slate-lists/src/withLists.test.tsx +++ b/packages/slate-lists/src/withLists.test.tsx @@ -1,89 +1,100 @@ /** @jsx jsx */ -import { Editor } from 'slate'; - -import { jsx } from './jsx'; -import { createListsEditor } from './test-utils'; +import { Editor as Slate } from 'slate'; + +import { + jsx, + Editor, + OrderedList, + UnorderedList, + ListItem, + ListItemText, + Text, + Paragraph, + Link, + Untyped, +} from './jsx'; +import type { ListsEditor } from './types'; describe('withLists - normalizeListChildren', () => { it('Converts paragraph into list-item when it is a child of a list', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - dolor - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + dolor + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - dolor - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + dolor + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Wraps list in list-item when it is a child of a list', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - - lorem ipsum - - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + + lorem ipsum + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - lorem ipsum - - - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); @@ -91,247 +102,247 @@ describe('withLists - normalizeListChildren', () => { describe('withLists - normalizeListItemChildren', () => { it('Lifts up list-items when they are children of list-item', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - dolor - - - - - sit - - - - - amet - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + dolor + + + + + sit + + + + + amet + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - dolor - - - - - sit - - - - - amet - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + dolor + + + + + sit + + + + + amet + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Normalizes paragraph children of list items', () => { - const editor = createListsEditor( - - - - - - lorem - - - - - - ipsum - - - - , - ); + const editor = ( + + + + + + lorem + + + + + + ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - - - - ipsum - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem + + + + + ipsum + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Wraps extra list-item-text in list-item and lifts it up when it is a child of list-item', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - dolor sit - - - - amet - - - , - ); + const editor = ( + + + + + lorem ipsum + + + dolor sit + + + + amet + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - dolor sit - - - - - amet - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + dolor sit + + + + + amet + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Wraps inline list-item children in list-item-text', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - - - lorem ipsum - - - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Wraps inline list-item children and sibling texts in list-item-text', () => { - const editor = createListsEditor( - - - - lorem - - ipsum - - dolor - - - , - ); + const editor = ( + + + + lorem + + ipsum + + dolor + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - ipsum - - dolor - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem + + ipsum + + dolor + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Adds missing type attribute to block list-item children', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); @@ -339,47 +350,47 @@ describe('withLists - normalizeListItemChildren', () => { describe('withLists - normalizeListItemTextChildren', () => { it('Unwraps block children of list-item-text elements', () => { - const editor = createListsEditor( - - - - - - lorem ipsum - - - - - - - - dolor sit amet - - - - - - , - ); + const editor = ( + + + + + + lorem ipsum + + + + + + + + dolor sit amet + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - dolor sit amet - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + dolor sit amet + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); @@ -387,33 +398,33 @@ describe('withLists - normalizeListItemTextChildren', () => { describe('withLists - normalizeOrphanListItem', () => { it('Converts orphan list-item into paragraph', () => { - const editor = createListsEditor( - - - - lorem ipsum - - - - - dolor sit - - - , - ); + const editor = ( + + + + lorem ipsum + + + + + dolor sit + + + + ) as unknown as ListsEditor; const expected = ( - - - lorem ipsum - - - dolor sit - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + lorem ipsum + + + dolor sit + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); @@ -421,29 +432,29 @@ describe('withLists - normalizeOrphanListItem', () => { describe('withLists - normalizeOrphanListItemText', () => { it('Converts orphan list-item-text into paragraph', () => { - const editor = createListsEditor( - - - lorem ipsum - - - dolor sit - - , - ); + const editor = ( + + + lorem ipsum + + + dolor sit + + + ) as unknown as ListsEditor; const expected = ( - - - lorem ipsum - - - dolor sit - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + lorem ipsum + + + dolor sit + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); @@ -451,149 +462,149 @@ describe('withLists - normalizeOrphanListItemText', () => { describe('withLists - normalizeOrphanNestedList', () => { it('Unwraps the nested list when it does not have sibling list-item-text', () => { - const editor = createListsEditor( - - - - - - - lorem ipsum - - - - - - , - ); + const editor = ( + + + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it("Moves items from nested list to previous list-item's nested list when it does not have sibling list-item-text", () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - aaa - - - - - bbb - - - - - - - - - lorem ipsum - - - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + aaa + + + + + bbb + + + + + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - aaa - - - - - bbb - - - - - lorem ipsum - - - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + aaa + + + + + bbb + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Moves nested list to previous list item when it does not have sibling list-item-text', () => { - const editor = createListsEditor( - - - - - lorem ipsum - - - - - - - lorem ipsum - - - - - - , - ); + const editor = ( + + + + + lorem ipsum + + + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem ipsum - - - - - lorem ipsum - - - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem ipsum + + + + + lorem ipsum + + + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); @@ -601,105 +612,105 @@ describe('withLists - normalizeOrphanNestedList', () => { describe('withLists - normalizeSiblingLists', () => { it('Merges sibling lists of same type', () => { - const editor = createListsEditor( - - - lorem - - - - - ipsum - - - - - - - - - - - , - ); + const editor = ( + + + lorem + + + + + ipsum + + + + + + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - lorem - - - - - ipsum - - - - - - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + lorem + + + + + ipsum + + + + + + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); it('Merges sibling lists of different types when they are nested lists', () => { - const editor = createListsEditor( - - - - - lorem - - - - - ipsum - - - - - - - dolor - - - - - - , - ); + const editor = ( + + + + + lorem + + + + + ipsum + + + + + + + dolor + + + + + + + ) as unknown as ListsEditor; const expected = ( - - - - - lorem - - - - - ipsum - - - - - dolor - - - - - - - ) as unknown as Editor; - - Editor.normalize(editor, { force: true }); + + + + + lorem + + + + + ipsum + + + + + dolor + + + + + + + ) as unknown as ListsEditor; + + Slate.normalize(editor, { force: true }); expect(editor.children).toEqual(expected.children); }); diff --git a/packages/slate-lists/tsconfig.json b/packages/slate-lists/tsconfig.json index 0ca67b57e..552557b1f 100644 --- a/packages/slate-lists/tsconfig.json +++ b/packages/slate-lists/tsconfig.json @@ -5,7 +5,6 @@ "emitDeclarationOnly": true, "declarationDir": "./build/types", "rootDir": "./src", - "typeRoots": ["./src/@types"] }, "include": ["./src"], "exclude": ["node_modules"], From c0401486af4dfae2f99d8ee2a2ebed082a7bb4a2 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 18:07:18 +0300 Subject: [PATCH 04/24] Remove `slate-lists` dependency on `slate-types` --- package-lock.json | 44 +++++++++---------- packages/slate-lists/package.json | 1 - packages/slate-lists/src/jsx.ts | 8 ++-- packages/slate-lists/src/lib/getNestedList.ts | 6 +-- packages/slate-lists/src/lib/getParentList.ts | 9 ++-- .../slate-lists/src/lib/getParentListItem.ts | 3 +- .../src/lib/increaseListItemDepth.ts | 7 ++- .../src/lib/listItemContainsText.ts | 17 +++---- .../src/lib/moveListItemsToAnotherList.ts | 23 +++++----- packages/slate-lists/src/lib/normalizeList.ts | 6 +-- packages/slate-lists/src/types.ts | 8 ++-- 11 files changed, 63 insertions(+), 69 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d2f76dfc..11d290215 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32391,14 +32391,14 @@ }, "packages/slate-commons": { "name": "@prezly/slate-commons", - "version": "0.29.1", + "version": "0.29.4", "license": "MIT", "dependencies": { - "@prezly/slate-types": "^0.29.1", + "@prezly/slate-types": "^0.29.4", "uuid": "^8.3.0" }, "devDependencies": { - "@prezly/slate-hyperscript": "^0.29.1", + "@prezly/slate-hyperscript": "^0.29.4", "@types/uuid": "^8.3.0" }, "peerDependencies": { @@ -32410,7 +32410,7 @@ }, "packages/slate-editor": { "name": "@prezly/slate-editor", - "version": "0.29.1", + "version": "0.29.4", "license": "MIT", "dependencies": { "@popperjs/core": "^2.6.0", @@ -32419,10 +32419,10 @@ "@prezly/linear-partition": "^1.0.2", "@prezly/progress-promise": "^1.0.1", "@prezly/sdk": "^6.12.1", - "@prezly/slate-commons": "^0.29.1", - "@prezly/slate-hyperscript": "^0.29.1", - "@prezly/slate-lists": "^0.29.1", - "@prezly/slate-types": "^0.29.1", + "@prezly/slate-commons": "^0.29.4", + "@prezly/slate-hyperscript": "^0.29.4", + "@prezly/slate-lists": "^0.29.4", + "@prezly/slate-types": "^0.29.4", "@prezly/uploadcare": "^1.1.2", "@prezly/uploadcare-widget": "^3.16.1", "@udecode/plate-core": "^9.0.0", @@ -32491,7 +32491,7 @@ }, "packages/slate-hyperscript": { "name": "@prezly/slate-hyperscript", - "version": "0.29.1", + "version": "0.29.4", "license": "MIT", "dependencies": { "is-plain-object": "^5.0.0" @@ -32511,15 +32511,14 @@ }, "packages/slate-lists": { "name": "@prezly/slate-lists", - "version": "0.29.1", + "version": "0.29.4", "license": "MIT", "dependencies": { - "@prezly/slate-commons": "^0.29.1", - "@prezly/slate-types": "^0.29.1", + "@prezly/slate-commons": "^0.29.4", "uuid": "^8.3.0" }, "devDependencies": { - "@prezly/slate-hyperscript": "^0.29.1", + "@prezly/slate-hyperscript": "^0.29.4", "@types/uuid": "^8.3.0" }, "peerDependencies": { @@ -32530,7 +32529,7 @@ }, "packages/slate-types": { "name": "@prezly/slate-types", - "version": "0.29.1", + "version": "0.29.4", "license": "MIT", "dependencies": { "@prezly/sdk": "^6.12.1", @@ -36306,8 +36305,8 @@ "@prezly/slate-commons": { "version": "file:packages/slate-commons", "requires": { - "@prezly/slate-hyperscript": "^0.29.1", - "@prezly/slate-types": "^0.29.1", + "@prezly/slate-hyperscript": "^0.29.4", + "@prezly/slate-types": "^0.29.4", "@types/uuid": "^8.3.0", "uuid": "^8.3.0" } @@ -36322,10 +36321,10 @@ "@prezly/linear-partition": "^1.0.2", "@prezly/progress-promise": "^1.0.1", "@prezly/sdk": "^6.12.1", - "@prezly/slate-commons": "^0.29.1", - "@prezly/slate-hyperscript": "^0.29.1", - "@prezly/slate-lists": "^0.29.1", - "@prezly/slate-types": "^0.29.1", + "@prezly/slate-commons": "^0.29.4", + "@prezly/slate-hyperscript": "^0.29.4", + "@prezly/slate-lists": "^0.29.4", + "@prezly/slate-types": "^0.29.4", "@prezly/uploadcare": "^1.1.2", "@prezly/uploadcare-widget": "^3.16.1", "@storybook/addon-actions": "^6.4.17", @@ -36401,9 +36400,8 @@ "@prezly/slate-lists": { "version": "file:packages/slate-lists", "requires": { - "@prezly/slate-commons": "^0.29.1", - "@prezly/slate-hyperscript": "^0.29.1", - "@prezly/slate-types": "^0.29.1", + "@prezly/slate-commons": "^0.29.4", + "@prezly/slate-hyperscript": "^0.29.4", "@types/uuid": "^8.3.0", "uuid": "^8.3.0" } diff --git a/packages/slate-lists/package.json b/packages/slate-lists/package.json index d0da5c708..00980fd45 100644 --- a/packages/slate-lists/package.json +++ b/packages/slate-lists/package.json @@ -55,7 +55,6 @@ }, "dependencies": { "@prezly/slate-commons": "^0.29.4", - "@prezly/slate-types": "^0.29.4", "uuid": "^8.3.0" }, "devDependencies": { diff --git a/packages/slate-lists/src/jsx.ts b/packages/slate-lists/src/jsx.ts index 1cb0d7cf1..3112bf516 100644 --- a/packages/slate-lists/src/jsx.ts +++ b/packages/slate-lists/src/jsx.ts @@ -37,10 +37,10 @@ const INLINE_ELEMENTS = [LINK_TYPE]; const VOID_ELEMENTS = [DIVIDER_TYPE]; const SCHEMA: ListsSchema = { - isDefaultTextNode(node): node is Element { + isDefaultTextNode(node) { return Element.isElementType(node, PARAGRAPH_TYPE); }, - isListNode(node, type): node is Element { + isListNode(node, type) { if (type) { return Element.isElementType(node, type); } @@ -49,10 +49,10 @@ const SCHEMA: ListsSchema = { Element.isElementType(node, UNORDERED_LIST_TYPE) ); }, - isListItemNode(node): node is Element { + isListItemNode(node) { return Element.isElementType(node, LIST_ITEM_TYPE); }, - isListItemTextNode(node): node is Element { + isListItemTextNode(node) { return Element.isElementType(node, LIST_ITEM_TEXT_TYPE); }, isListNestable(node): boolean { diff --git a/packages/slate-lists/src/lib/getNestedList.ts b/packages/slate-lists/src/lib/getNestedList.ts index db7cd5b01..b07c93524 100644 --- a/packages/slate-lists/src/lib/getNestedList.ts +++ b/packages/slate-lists/src/lib/getNestedList.ts @@ -1,5 +1,5 @@ -import type { Element, NodeEntry, Path } from 'slate'; -import { Node } from 'slate'; +import type { NodeEntry, Path } from 'slate'; +import { Node, Element } from 'slate'; import { NESTED_LIST_PATH_INDEX } from '../constants'; import type { ListsEditor } from '../types'; @@ -17,7 +17,7 @@ export function getNestedList(editor: ListsEditor, path: Path): NodeEntry | null { - const parentList = Editor.above(editor, { +export function getParentList(editor: ListsEditor, path: Path): NodeEntry | null { + const parentList = Editor.above(editor, { at: path, - match: (node) => editor.isListNode(node), + match: (node): node is Element => editor.isListNode(node), }); return parentList ?? null; diff --git a/packages/slate-lists/src/lib/getParentListItem.ts b/packages/slate-lists/src/lib/getParentListItem.ts index b619787e8..a6da361b7 100644 --- a/packages/slate-lists/src/lib/getParentListItem.ts +++ b/packages/slate-lists/src/lib/getParentListItem.ts @@ -1,4 +1,3 @@ -import type { ElementNode } from '@prezly/slate-types'; import type { Element, NodeEntry, Path } from 'slate'; import { Editor } from 'slate'; @@ -9,7 +8,7 @@ import type { ListsEditor } from '../types'; * Returns null if there is no parent "list-item". */ export function getParentListItem(editor: ListsEditor, path: Path): NodeEntry | null { - const parentListItem = Editor.above(editor, { + const parentListItem = Editor.above(editor, { at: path, match: (node) => editor.isListItemNode(node), }); diff --git a/packages/slate-lists/src/lib/increaseListItemDepth.ts b/packages/slate-lists/src/lib/increaseListItemDepth.ts index 260b29e19..e25da91db 100644 --- a/packages/slate-lists/src/lib/increaseListItemDepth.ts +++ b/packages/slate-lists/src/lib/increaseListItemDepth.ts @@ -1,5 +1,5 @@ import { EditorCommands } from '@prezly/slate-commons'; -import { Editor, Node, Path, Transforms } from 'slate'; +import { Editor, Element, Node, Path, Transforms } from 'slate'; import { NESTED_LIST_PATH_INDEX } from '../constants'; import type { ListsEditor } from '../types'; @@ -39,7 +39,10 @@ export function increaseListItemDepth(editor: ListsEditor, listItemPath: Path): const previousListItemChildList = Node.get(editor, previousListItemChildListPath); - if (editor.isListNode(previousListItemChildList)) { + if ( + Element.isElement(previousListItemChildList) && + editor.isListNode(previousListItemChildList) + ) { const index = previousListItemHasChildList ? previousListItemChildList.children.length : 0; diff --git a/packages/slate-lists/src/lib/listItemContainsText.ts b/packages/slate-lists/src/lib/listItemContainsText.ts index 87a0664b2..9061ebe4c 100644 --- a/packages/slate-lists/src/lib/listItemContainsText.ts +++ b/packages/slate-lists/src/lib/listItemContainsText.ts @@ -1,5 +1,5 @@ import type { Node } from 'slate'; -import { Editor } from 'slate'; +import { Editor, Element } from 'slate'; import type { ListsEditor } from '../types'; @@ -7,15 +7,12 @@ import type { ListsEditor } from '../types'; * Returns true if given "list-item" node contains a non-empty "list-item-text" node. */ export function listItemContainsText(editor: ListsEditor, node: Node): boolean { - if (!editor.isListItemNode(node)) { - return false; - } - - const [listItemText] = node.children; + if (Element.isElement(node) && editor.isListItemNode(node)) { + const [listItemText] = node.children; - if (!editor.isListItemTextNode(listItemText)) { - return false; + if (Element.isElement(listItemText) && editor.isListItemTextNode(listItemText)) { + return !Editor.isEmpty(editor, listItemText); + } } - - return !Editor.isEmpty(editor, listItemText); + return false; } diff --git a/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts b/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts index 609c159c5..9fafef283 100644 --- a/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts +++ b/packages/slate-lists/src/lib/moveListItemsToAnotherList.ts @@ -1,5 +1,5 @@ import type { Node, NodeEntry } from 'slate'; -import { Transforms } from 'slate'; +import { Element, Transforms } from 'slate'; import type { ListsEditor } from '../types'; @@ -16,15 +16,18 @@ export function moveListItemsToAnotherList( const [sourceListNode, sourceListPath] = parameters.at; const [targetListNode, targetListPath] = parameters.to; - if (!editor.isListNode(sourceListNode) || !editor.isListNode(targetListNode)) { + if ( + Element.isElement(sourceListNode) && + Element.isElement(targetListNode) && + editor.isListNode(sourceListNode) && + editor.isListNode(targetListNode) + ) { // Sanity check. - return; - } - - for (let i = 0; i < sourceListNode.children.length; ++i) { - Transforms.moveNodes(editor, { - at: [...sourceListPath, 0], - to: [...targetListPath, targetListNode.children.length + i], - }); + for (let i = 0; i < sourceListNode.children.length; ++i) { + Transforms.moveNodes(editor, { + at: [...sourceListPath, 0], + to: [...targetListPath, targetListNode.children.length + i], + }); + } } } diff --git a/packages/slate-lists/src/lib/normalizeList.ts b/packages/slate-lists/src/lib/normalizeList.ts index 0a846c9ed..01fae8b55 100644 --- a/packages/slate-lists/src/lib/normalizeList.ts +++ b/packages/slate-lists/src/lib/normalizeList.ts @@ -1,5 +1,4 @@ import { EditorCommands } from '@prezly/slate-commons'; -import { isElementNode } from '@prezly/slate-types'; import type { Node, NodeEntry } from 'slate'; import { Editor, Element, Transforms } from 'slate'; @@ -27,10 +26,7 @@ export function normalizeList(editor: ListsEditor, [node, path]: NodeEntry return false; } - if ( - isElementNode(ancestorNode) && - (editor.isListNode(ancestorNode) || editor.isListItemNode(ancestorNode)) - ) { + if (editor.isListNode(ancestorNode) || editor.isListItemNode(ancestorNode)) { return false; } diff --git a/packages/slate-lists/src/types.ts b/packages/slate-lists/src/types.ts index 83a45fc66..ec835b61d 100644 --- a/packages/slate-lists/src/types.ts +++ b/packages/slate-lists/src/types.ts @@ -6,13 +6,13 @@ export enum ListType { } export interface ListsSchema { - isDefaultTextNode(node: Node): node is Element; + isDefaultTextNode(node: Node): boolean; - isListNode(node: Node, type?: ListType): node is Element; + isListNode(node: Node, type?: ListType): boolean; - isListItemNode(node: Node): node is Element; + isListItemNode(node: Node): boolean; - isListItemTextNode(node: Node): node is Element; + isListItemTextNode(node: Node): boolean; isListNestable(node: Node): boolean; From 9d5a4d00b4c325aeafc2e07ca8904ded36b18c92 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 18:51:49 +0300 Subject: [PATCH 05/24] Split `slate-lists` lib folder into smaller ones --- packages/slate-lists/src/index.ts | 1 + .../src/lib/cloneContentsMonkeyPatch.ts | 64 ------------------- packages/slate-lists/src/lib/index.ts | 9 --- .../slate-lists/src/normalizations/index.ts | 8 +++ .../{lib => normalizations}/normalizeList.ts | 0 .../normalizeListChildren.ts | 0 .../normalizeListItemChildren.ts | 0 .../normalizeListItemTextChildren.ts | 0 .../normalizeOrphanListItem.ts | 2 +- .../normalizeOrphanListItemText.ts | 2 +- .../normalizeOrphanNestedList.ts | 5 +- .../normalizeSiblingLists.ts | 3 +- packages/slate-lists/src/util/index.ts | 2 + .../src/util/patchRangeCloneContents.ts | 62 ++++++++++++++++++ .../src/util/withRangeCloneContentsPatched.ts | 10 +++ packages/slate-lists/src/withLists.ts | 2 +- packages/slate-lists/src/withListsReact.ts | 8 +-- 17 files changed, 92 insertions(+), 86 deletions(-) delete mode 100644 packages/slate-lists/src/lib/cloneContentsMonkeyPatch.ts create mode 100644 packages/slate-lists/src/normalizations/index.ts rename packages/slate-lists/src/{lib => normalizations}/normalizeList.ts (100%) rename packages/slate-lists/src/{lib => normalizations}/normalizeListChildren.ts (100%) rename packages/slate-lists/src/{lib => normalizations}/normalizeListItemChildren.ts (100%) rename packages/slate-lists/src/{lib => normalizations}/normalizeListItemTextChildren.ts (100%) rename packages/slate-lists/src/{lib => normalizations}/normalizeOrphanListItem.ts (95%) rename packages/slate-lists/src/{lib => normalizations}/normalizeOrphanListItemText.ts (94%) rename packages/slate-lists/src/{lib => normalizations}/normalizeOrphanNestedList.ts (91%) rename packages/slate-lists/src/{lib => normalizations}/normalizeSiblingLists.ts (89%) create mode 100644 packages/slate-lists/src/util/index.ts create mode 100644 packages/slate-lists/src/util/patchRangeCloneContents.ts create mode 100644 packages/slate-lists/src/util/withRangeCloneContentsPatched.ts diff --git a/packages/slate-lists/src/index.ts b/packages/slate-lists/src/index.ts index a5e43ab32..058ffaad2 100644 --- a/packages/slate-lists/src/index.ts +++ b/packages/slate-lists/src/index.ts @@ -1,4 +1,5 @@ export * as ListsEditor from './lib'; +export * as Normalizations from './normalizations'; export * from './types'; export { withLists } from './withLists'; export { withListsReact } from './withListsReact'; diff --git a/packages/slate-lists/src/lib/cloneContentsMonkeyPatch.ts b/packages/slate-lists/src/lib/cloneContentsMonkeyPatch.ts deleted file mode 100644 index f5ec99976..000000000 --- a/packages/slate-lists/src/lib/cloneContentsMonkeyPatch.ts +++ /dev/null @@ -1,64 +0,0 @@ -const originalCloneContents = Range.prototype.cloneContents; - -function wrapInFragment(nodes: (string | Node)[]): DocumentFragment { - const fragment = document.createDocumentFragment(); - fragment.append(...nodes); - return fragment; -} - -function wrapInList(nodes: (string | Node)[], nodeName: 'OL' | 'UL'): HTMLElement { - const listElement = document.createElement(nodeName); - listElement.append(...nodes); - return listElement; -} - -function wrapInLi(nodes: (string | Node)[]): HTMLElement { - const listItemElement = document.createElement('li'); - listItemElement.append(...nodes); - return listItemElement; -} - -export const cloneContentsMonkeyPatch = { - /** - * Activates `Range.prototype.cloneContents` override that ensures in the cloned contents: - * - there are no
  • children elements without parent
  • element - * - there are no
  • elements without parent
      or
        elements - */ - activate() { - Range.prototype.cloneContents = function cloneContents(): DocumentFragment { - const contents = originalCloneContents.apply(this); - - if ( - this.commonAncestorContainer.nodeName === 'OL' || - this.commonAncestorContainer.nodeName === 'UL' - ) { - return wrapInFragment([ - wrapInList([...contents.childNodes], this.commonAncestorContainer.nodeName), - ]); - } - - if ( - this.commonAncestorContainer.nodeName === 'LI' && - this.commonAncestorContainer.parentElement && - (this.commonAncestorContainer.parentElement.nodeName === 'OL' || - this.commonAncestorContainer.parentElement.nodeName === 'UL') - ) { - return wrapInFragment([ - wrapInList( - [wrapInLi([...contents.childNodes])], - this.commonAncestorContainer.parentElement.nodeName, - ), - ]); - } - - return contents; - }; - }, - - /** - * Brings back the native `Range.prototype.cloneContents`. - */ - deactivate() { - Range.prototype.cloneContents = originalCloneContents; - }, -}; diff --git a/packages/slate-lists/src/lib/index.ts b/packages/slate-lists/src/lib/index.ts index 92b8be375..f6fb1602d 100644 --- a/packages/slate-lists/src/lib/index.ts +++ b/packages/slate-lists/src/lib/index.ts @@ -1,5 +1,4 @@ export { canDeleteBackward } from './canDeleteBackward'; -export { cloneContentsMonkeyPatch } from './cloneContentsMonkeyPatch'; export { decreaseDepth } from './decreaseDepth'; export { decreaseListItemDepth } from './decreaseListItemDepth'; export { getListItemsInRange } from './getListItemsInRange'; @@ -16,14 +15,6 @@ export { listItemContainsText } from './listItemContainsText'; export { mergeListWithPreviousSiblingList } from './mergeListWithPreviousSiblingList'; export { moveListItemsToAnotherList } from './moveListItemsToAnotherList'; export { moveListToListItem } from './moveListToListItem'; -export { normalizeList } from './normalizeList'; -export { normalizeListChildren } from './normalizeListChildren'; -export { normalizeListItemChildren } from './normalizeListItemChildren'; -export { normalizeListItemTextChildren } from './normalizeListItemTextChildren'; -export { normalizeOrphanListItem } from './normalizeOrphanListItem'; -export { normalizeOrphanListItemText } from './normalizeOrphanListItemText'; -export { normalizeOrphanNestedList } from './normalizeOrphanNestedList'; -export { normalizeSiblingLists } from './normalizeSiblingLists'; export { setListType } from './setListType'; export { splitListItem } from './splitListItem'; export { unwrapList } from './unwrapList'; diff --git a/packages/slate-lists/src/normalizations/index.ts b/packages/slate-lists/src/normalizations/index.ts new file mode 100644 index 000000000..d1907b68f --- /dev/null +++ b/packages/slate-lists/src/normalizations/index.ts @@ -0,0 +1,8 @@ +export { normalizeList } from './normalizeList'; +export { normalizeListChildren } from './normalizeListChildren'; +export { normalizeListItemChildren } from './normalizeListItemChildren'; +export { normalizeListItemTextChildren } from './normalizeListItemTextChildren'; +export { normalizeOrphanListItem } from './normalizeOrphanListItem'; +export { normalizeOrphanListItemText } from './normalizeOrphanListItemText'; +export { normalizeOrphanNestedList } from './normalizeOrphanNestedList'; +export { normalizeSiblingLists } from './normalizeSiblingLists'; diff --git a/packages/slate-lists/src/lib/normalizeList.ts b/packages/slate-lists/src/normalizations/normalizeList.ts similarity index 100% rename from packages/slate-lists/src/lib/normalizeList.ts rename to packages/slate-lists/src/normalizations/normalizeList.ts diff --git a/packages/slate-lists/src/lib/normalizeListChildren.ts b/packages/slate-lists/src/normalizations/normalizeListChildren.ts similarity index 100% rename from packages/slate-lists/src/lib/normalizeListChildren.ts rename to packages/slate-lists/src/normalizations/normalizeListChildren.ts diff --git a/packages/slate-lists/src/lib/normalizeListItemChildren.ts b/packages/slate-lists/src/normalizations/normalizeListItemChildren.ts similarity index 100% rename from packages/slate-lists/src/lib/normalizeListItemChildren.ts rename to packages/slate-lists/src/normalizations/normalizeListItemChildren.ts diff --git a/packages/slate-lists/src/lib/normalizeListItemTextChildren.ts b/packages/slate-lists/src/normalizations/normalizeListItemTextChildren.ts similarity index 100% rename from packages/slate-lists/src/lib/normalizeListItemTextChildren.ts rename to packages/slate-lists/src/normalizations/normalizeListItemTextChildren.ts diff --git a/packages/slate-lists/src/lib/normalizeOrphanListItem.ts b/packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts similarity index 95% rename from packages/slate-lists/src/lib/normalizeOrphanListItem.ts rename to packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts index 073f7e9f3..893ee20cf 100644 --- a/packages/slate-lists/src/lib/normalizeOrphanListItem.ts +++ b/packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts @@ -1,9 +1,9 @@ import type { Node, NodeEntry } from 'slate'; import { Editor, Transforms } from 'slate'; +import { getParentList } from '../lib'; import type { ListsEditor } from '../types'; -import { getParentList } from './getParentList'; /** * If "list-item" somehow (e.g. by deleting everything around it) ends up diff --git a/packages/slate-lists/src/lib/normalizeOrphanListItemText.ts b/packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts similarity index 94% rename from packages/slate-lists/src/lib/normalizeOrphanListItemText.ts rename to packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts index 35e46cea2..2facda746 100644 --- a/packages/slate-lists/src/lib/normalizeOrphanListItemText.ts +++ b/packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts @@ -1,9 +1,9 @@ import type { Node, NodeEntry } from 'slate'; import { Transforms } from 'slate'; +import { getParentListItem } from '../lib'; import type { ListsEditor } from '../types'; -import { getParentListItem } from './getParentListItem'; /** * If "list-item-text" somehow (e.g. by deleting everything around it) ends up diff --git a/packages/slate-lists/src/lib/normalizeOrphanNestedList.ts b/packages/slate-lists/src/normalizations/normalizeOrphanNestedList.ts similarity index 91% rename from packages/slate-lists/src/lib/normalizeOrphanNestedList.ts rename to packages/slate-lists/src/normalizations/normalizeOrphanNestedList.ts index 43db79f49..8fcfa699b 100644 --- a/packages/slate-lists/src/lib/normalizeOrphanNestedList.ts +++ b/packages/slate-lists/src/normalizations/normalizeOrphanNestedList.ts @@ -2,12 +2,9 @@ import { EditorCommands } from '@prezly/slate-commons'; import type { NodeEntry } from 'slate'; import { Node, Transforms } from 'slate'; +import { getNestedList, moveListItemsToAnotherList, moveListToListItem } from '../lib'; import type { ListsEditor } from '../types'; -import { getNestedList } from './getNestedList'; -import { moveListItemsToAnotherList } from './moveListItemsToAnotherList'; -import { moveListToListItem } from './moveListToListItem'; - /** * If there is a nested "list" inside a "list-item" without a "list-item-text" * unwrap that nested "list" and try to nest it in previous sibling "list-item". diff --git a/packages/slate-lists/src/lib/normalizeSiblingLists.ts b/packages/slate-lists/src/normalizations/normalizeSiblingLists.ts similarity index 89% rename from packages/slate-lists/src/lib/normalizeSiblingLists.ts rename to packages/slate-lists/src/normalizations/normalizeSiblingLists.ts index 8fb46cff2..a010b0f0b 100644 --- a/packages/slate-lists/src/lib/normalizeSiblingLists.ts +++ b/packages/slate-lists/src/normalizations/normalizeSiblingLists.ts @@ -1,10 +1,9 @@ import { EditorCommands } from '@prezly/slate-commons'; import type { Node, NodeEntry } from 'slate'; +import { mergeListWithPreviousSiblingList } from '../lib'; import type { ListsEditor } from '../types'; -import { mergeListWithPreviousSiblingList } from './mergeListWithPreviousSiblingList'; - /** * If there are 2 "lists" of the same type next to each other, merge them together. * If there are 2 nested "lists" next to each other, merge them together. diff --git a/packages/slate-lists/src/util/index.ts b/packages/slate-lists/src/util/index.ts new file mode 100644 index 000000000..eb9ac0bbd --- /dev/null +++ b/packages/slate-lists/src/util/index.ts @@ -0,0 +1,2 @@ +export { patchRangeCloneContents } from './patchRangeCloneContents'; +export { withRangeCloneContentsPatched } from './withRangeCloneContentsPatched'; diff --git a/packages/slate-lists/src/util/patchRangeCloneContents.ts b/packages/slate-lists/src/util/patchRangeCloneContents.ts new file mode 100644 index 000000000..2f56a3fdc --- /dev/null +++ b/packages/slate-lists/src/util/patchRangeCloneContents.ts @@ -0,0 +1,62 @@ +function wrapInFragment(nodes: (string | Node)[]): DocumentFragment { + const fragment = document.createDocumentFragment(); + fragment.append(...nodes); + return fragment; +} + +function wrapInList(nodes: (string | Node)[], nodeName: 'OL' | 'UL'): HTMLElement { + const listElement = document.createElement(nodeName); + listElement.append(...nodes); + return listElement; +} + +function wrapInLi(nodes: (string | Node)[]): HTMLElement { + const listItemElement = document.createElement('li'); + listItemElement.append(...nodes); + return listItemElement; +} + +/** + * Activates `Range.prototype.cloneContents` override that ensures in the cloned contents: + * - there are no
      • children elements without parent
      • element + * - there are no
      • elements without parent
          or
            elements + */ +export function patchRangeCloneContents() { + const originalCloneContents = Range.prototype.cloneContents; + + Range.prototype.cloneContents = function cloneContents(): DocumentFragment { + const contents = originalCloneContents.apply(this); + + if ( + this.commonAncestorContainer.nodeName === 'OL' || + this.commonAncestorContainer.nodeName === 'UL' + ) { + return wrapInFragment([ + wrapInList([...contents.childNodes], this.commonAncestorContainer.nodeName), + ]); + } + + if ( + this.commonAncestorContainer.nodeName === 'LI' && + this.commonAncestorContainer.parentElement && + (this.commonAncestorContainer.parentElement.nodeName === 'OL' || + this.commonAncestorContainer.parentElement.nodeName === 'UL') + ) { + return wrapInFragment([ + wrapInList( + [wrapInLi([...contents.childNodes])], + this.commonAncestorContainer.parentElement.nodeName, + ), + ]); + } + + return contents; + }; + + /** + * Brings back the original `Range.prototype.cloneContents`. + */ + return function undo() { + Range.prototype.cloneContents = originalCloneContents; + }; +} diff --git a/packages/slate-lists/src/util/withRangeCloneContentsPatched.ts b/packages/slate-lists/src/util/withRangeCloneContentsPatched.ts new file mode 100644 index 000000000..f5bcc1a5d --- /dev/null +++ b/packages/slate-lists/src/util/withRangeCloneContentsPatched.ts @@ -0,0 +1,10 @@ +import { patchRangeCloneContents } from './patchRangeCloneContents'; + +export function withRangeCloneContentsPatched(callback: () => void) { + const undo = patchRangeCloneContents(); + try { + callback(); + } finally { + undo(); + } +} diff --git a/packages/slate-lists/src/withLists.ts b/packages/slate-lists/src/withLists.ts index 0bcc75a89..09aa4d4cf 100644 --- a/packages/slate-lists/src/withLists.ts +++ b/packages/slate-lists/src/withLists.ts @@ -9,7 +9,7 @@ import { normalizeOrphanListItemText, normalizeOrphanNestedList, normalizeSiblingLists, -} from './lib'; +} from './normalizations'; import type { ListsEditor, ListsSchema } from './types'; type Normalizer = (editor: ListsEditor, entry: NodeEntry) => boolean; diff --git a/packages/slate-lists/src/withListsReact.ts b/packages/slate-lists/src/withListsReact.ts index 7bb2e3e39..693f809c5 100644 --- a/packages/slate-lists/src/withListsReact.ts +++ b/packages/slate-lists/src/withListsReact.ts @@ -2,7 +2,7 @@ import type { ReactEditor } from 'slate-react'; -import { cloneContentsMonkeyPatch } from './lib'; +import { withRangeCloneContentsPatched } from './util'; /** * Enables Range.prototype.cloneContents monkey patch to improve pasting behavior @@ -12,9 +12,9 @@ export function withListsReact(editor: T): T { const { setFragmentData } = editor; editor.setFragmentData = (data: DataTransfer) => { - cloneContentsMonkeyPatch.activate(); - setFragmentData(data); - cloneContentsMonkeyPatch.deactivate(); + withRangeCloneContentsPatched(function() { + setFragmentData(data); + }); }; return editor; From e2ed27ff3bd0c4c8eb08723322568331500ef759 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 18:53:33 +0300 Subject: [PATCH 06/24] Cleanup tsconfig --- packages/slate-lists/tsconfig.build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/slate-lists/tsconfig.build.json b/packages/slate-lists/tsconfig.build.json index 98945424b..05b0b78c3 100644 --- a/packages/slate-lists/tsconfig.build.json +++ b/packages/slate-lists/tsconfig.build.json @@ -4,7 +4,7 @@ "module": "es6", "target": "es6" }, - "exclude": ["node_modules", "**/*.test.*", "**/jsx.ts", "**/test-utils.ts"], + "exclude": ["node_modules", "**/*.test.*", "**/jsx.ts"], "references": [ { "path": "../slate-types/tsconfig.json" }, { "path": "../slate-commons/tsconfig.build.json" }, From db48875a702bca78d9b4fb55a1176b791e66879d Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 18:53:57 +0300 Subject: [PATCH 07/24] Change `slate-lists` hyperscript dependency to stock `slate-hyperscript` --- package-lock.json | 6 +++--- packages/slate-lists/package.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11d290215..6b3a69e8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32518,8 +32518,8 @@ "uuid": "^8.3.0" }, "devDependencies": { - "@prezly/slate-hyperscript": "^0.29.4", - "@types/uuid": "^8.3.0" + "@types/uuid": "^8.3.0", + "slate-hyperscript": "^0.67.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0", @@ -36401,8 +36401,8 @@ "version": "file:packages/slate-lists", "requires": { "@prezly/slate-commons": "^0.29.4", - "@prezly/slate-hyperscript": "^0.29.4", "@types/uuid": "^8.3.0", + "slate-hyperscript": "*", "uuid": "^8.3.0" } }, diff --git a/packages/slate-lists/package.json b/packages/slate-lists/package.json index 00980fd45..0deac5a29 100644 --- a/packages/slate-lists/package.json +++ b/packages/slate-lists/package.json @@ -58,7 +58,7 @@ "uuid": "^8.3.0" }, "devDependencies": { - "@prezly/slate-hyperscript": "^0.29.4", - "@types/uuid": "^8.3.0" + "@types/uuid": "^8.3.0", + "slate-hyperscript": "^0.67.0" } } From ce86082c0df31ca6fa20645ab5baf7b7afdcfae4 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 19:08:29 +0300 Subject: [PATCH 08/24] Relax `WithOverrides` type constraints --- packages/slate-commons/src/types/WithOverrides.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/slate-commons/src/types/WithOverrides.ts b/packages/slate-commons/src/types/WithOverrides.ts index 305fedea3..4fa3d7284 100644 --- a/packages/slate-commons/src/types/WithOverrides.ts +++ b/packages/slate-commons/src/types/WithOverrides.ts @@ -1,5 +1,3 @@ -import type { BaseEditor } from 'slate'; -import type { HistoryEditor } from 'slate-history'; -import type { ReactEditor } from 'slate-react'; +import type { Editor } from 'slate'; -export type WithOverrides = (editor: T) => T; +export type WithOverrides = (editor: T) => T; From 5bba069c8e1658e970b76373c08d7c137d253302 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 19:12:15 +0300 Subject: [PATCH 09/24] Fix withListsReact type declaration --- packages/slate-lists/src/withListsReact.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/slate-lists/src/withListsReact.ts b/packages/slate-lists/src/withListsReact.ts index 693f809c5..e4bd1f8a2 100644 --- a/packages/slate-lists/src/withListsReact.ts +++ b/packages/slate-lists/src/withListsReact.ts @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ +import type { Editor } from 'slate'; import type { ReactEditor } from 'slate-react'; import { withRangeCloneContentsPatched } from './util'; @@ -8,7 +9,7 @@ import { withRangeCloneContentsPatched } from './util'; * Enables Range.prototype.cloneContents monkey patch to improve pasting behavior * in few edge cases. */ -export function withListsReact(editor: T): T { +export function withListsReact(editor: T): T { const { setFragmentData } = editor; editor.setFragmentData = (data: DataTransfer) => { From fc3c9648575258a071e51bf9e6b5be414c8f0ca7 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 19:26:45 +0300 Subject: [PATCH 10/24] Extend `isListNode` to check for specific list type --- packages/slate-types/src/nodes/ListNode.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/slate-types/src/nodes/ListNode.ts b/packages/slate-types/src/nodes/ListNode.ts index 42b5d51da..e1029f339 100644 --- a/packages/slate-types/src/nodes/ListNode.ts +++ b/packages/slate-types/src/nodes/ListNode.ts @@ -3,15 +3,14 @@ import { isElementNode } from './ElementNode'; import type { Alignable } from './interfaces'; export const BULLETED_LIST_NODE_TYPE = 'bulleted-list'; - export const NUMBERED_LIST_NODE_TYPE = 'numbered-list'; - export const LIST_ITEM_NODE_TYPE = 'list-item'; - export const LIST_ITEM_TEXT_NODE_TYPE = 'list-item-text'; -export interface ListNode extends ElementNode, Alignable { - type: typeof BULLETED_LIST_NODE_TYPE | typeof NUMBERED_LIST_NODE_TYPE; +type ListType = typeof BULLETED_LIST_NODE_TYPE | typeof NUMBERED_LIST_NODE_TYPE; + +export interface ListNode extends ElementNode, Alignable { + type: T; children: ListItemNode[]; } @@ -24,8 +23,12 @@ export interface ListItemTextNode extends ElementNode { type: typeof LIST_ITEM_TEXT_NODE_TYPE; } -export function isListNode(value: any): value is ListNode { - return isElementNode(value, [BULLETED_LIST_NODE_TYPE, NUMBERED_LIST_NODE_TYPE]); +export function isListNode(value: any): value is ListNode; +export function isListNode(value: any, type: T): value is ListNode; +export function isListNode(value: any, type?: ListType): boolean { + return type + ? isElementNode(value, type) + : isElementNode(value, [BULLETED_LIST_NODE_TYPE, NUMBERED_LIST_NODE_TYPE]); } export function isListItemNode(value: any): value is ListItemNode { From adca040dc0ec259f62d44974fb64798352345f16 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 20:14:09 +0300 Subject: [PATCH 11/24] Fix conflicting exports in slate-lists --- packages/slate-lists/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/slate-lists/src/index.ts b/packages/slate-lists/src/index.ts index 058ffaad2..447e0e2ee 100644 --- a/packages/slate-lists/src/index.ts +++ b/packages/slate-lists/src/index.ts @@ -1,4 +1,4 @@ -export * as ListsEditor from './lib'; +export * as Lists from './lib'; export * as Normalizations from './normalizations'; export * from './types'; export { withLists } from './withLists'; From d54ba8a5d8c0c547c3b6b5b852a52bcf6c33bf89 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Wed, 13 Apr 2022 20:14:43 +0300 Subject: [PATCH 12/24] Update editor-v4-rich-formatting extension to new slate-lists implementation --- packages/slate-editor/src/index.ts | 3 +- .../editor-v4-autoformat/withAutoformat.ts | 9 +--- .../RichFormattingExtension.tsx | 10 +++- .../createOnKeyDown.ts | 18 +++---- .../editor-v4-rich-formatting/index.ts | 1 - .../lib/toggleBlock.ts | 16 ++++-- .../lib/withResetRichFormattingOnBreak.ts | 6 +-- .../editor-v4-rich-formatting/lists.ts | 20 ------- .../editor-v4-rich-formatting/withLists.ts | 54 +++++++++++++++++++ .../withRichFormatting.ts | 11 ---- .../src/modules/editor-v4/createEditorV4.ts | 2 - .../src/modules/editor-v4/useCreateEditor.ts | 4 +- 12 files changed, 90 insertions(+), 64 deletions(-) delete mode 100644 packages/slate-editor/src/modules/editor-v4-rich-formatting/lists.ts create mode 100644 packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts delete mode 100644 packages/slate-editor/src/modules/editor-v4-rich-formatting/withRichFormatting.ts diff --git a/packages/slate-editor/src/index.ts b/packages/slate-editor/src/index.ts index 568c265e4..ba860646b 100644 --- a/packages/slate-editor/src/index.ts +++ b/packages/slate-editor/src/index.ts @@ -1,3 +1,4 @@ +import type { ListsEditor } from '@prezly/slate-lists'; import type { ElementNode, TextNode } from '@prezly/slate-types'; import type { BaseEditor } from 'slate'; import type { HistoryEditor } from 'slate-history'; @@ -5,7 +6,7 @@ import type { ReactEditor } from 'slate-react'; declare module 'slate' { interface CustomTypes { - Editor: BaseEditor & ReactEditor & HistoryEditor; + Editor: BaseEditor & ReactEditor & HistoryEditor & ListsEditor; Element: ElementNode; Text: TextNode; } diff --git a/packages/slate-editor/src/modules/editor-v4-autoformat/withAutoformat.ts b/packages/slate-editor/src/modules/editor-v4-autoformat/withAutoformat.ts index c2827943a..4d73a1c0d 100644 --- a/packages/slate-editor/src/modules/editor-v4-autoformat/withAutoformat.ts +++ b/packages/slate-editor/src/modules/editor-v4-autoformat/withAutoformat.ts @@ -1,15 +1,10 @@ import { EditorCommands } from '@prezly/slate-commons'; -import type { BaseEditor } from 'slate'; -import type { HistoryEditor } from 'slate-history'; -import type { ReactEditor } from 'slate-react'; +import type { Editor } from 'slate'; import { autoformatBlock, autoformatMark, autoformatText } from './transforms'; import type { AutoformatRule } from './types'; -export function withAutoformat( - editor: T, - rules: AutoformatRule[], -): T { +export function withAutoformat(editor: T, rules: AutoformatRule[]): T { const { insertText } = editor; const autoformatters = { diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx b/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx index f0aff1dae..4f1fd6338 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx @@ -7,8 +7,13 @@ import { RichTextElement, Text } from './components'; import { RICH_FORMATTING_EXTENSION_ID } from './constants'; import { createDeserialize } from './createDeserialize'; import { createOnKeyDown } from './createOnKeyDown'; -import { isRichTextElement, normalizeRedundantRichTextAttributes } from './lib'; +import { + isRichTextElement, + normalizeRedundantRichTextAttributes, + withResetRichFormattingOnBreak, +} from './lib'; import { ElementType } from './types'; +import { withLists } from './withLists'; interface Parameters { blocks: boolean; @@ -38,4 +43,7 @@ export const RichFormattingExtension = ({ blocks }: Parameters): Extension => ({ ElementType.HEADING_ONE, ElementType.HEADING_TWO, ], + withOverrides(editor) { + return withResetRichFormattingOnBreak(withLists(editor)); + }, }); diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts index 2fffe881f..6e391a788 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts @@ -1,10 +1,10 @@ import { EditorCommands } from '@prezly/slate-commons'; +import { Lists } from '@prezly/slate-lists'; import { isHotkey } from 'is-hotkey'; import type { KeyboardEvent } from 'react'; import { Editor } from 'slate'; import { ReactEditor } from 'slate-react'; -import { lists } from './lists'; import { MarkType } from './types'; const MARK_HOTKEYS: { hotkey: string; mark: MarkType }[] = [ @@ -23,7 +23,7 @@ function marksOnKeyDown(event: KeyboardEvent, editor: Editor) { } function listsOnKeyDown(event: KeyboardEvent, editor: Editor) { - const listItemsInSelection = lists.getListItemsInRange(editor, editor.selection); + const listItemsInSelection = Lists.getListItemsInRange(editor, editor.selection); // Since we're overriding the default Tab key behavior // we need to bring back the possibility to blur the editor @@ -35,26 +35,26 @@ function listsOnKeyDown(event: KeyboardEvent, editor: Editor) { if (isHotkey('tab', event.nativeEvent)) { event.preventDefault(); - lists.increaseDepth(editor); + Lists.increaseDepth(editor); } if (isHotkey('shift+tab', event.nativeEvent)) { event.preventDefault(); - lists.decreaseDepth(editor); + Lists.decreaseDepth(editor); } - if (isHotkey('backspace', event.nativeEvent) && !lists.canDeleteBackward(editor)) { + if (isHotkey('backspace', event.nativeEvent) && !Lists.canDeleteBackward(editor)) { event.preventDefault(); - lists.decreaseDepth(editor); + Lists.decreaseDepth(editor); } if (isHotkey('enter', event.nativeEvent)) { - if (lists.isCursorInEmptyListItem(editor)) { + if (Lists.isCursorInEmptyListItem(editor)) { event.preventDefault(); - lists.decreaseDepth(editor); + Lists.decreaseDepth(editor); } else if (listItemsInSelection.length > 0) { event.preventDefault(); - lists.splitListItem(editor); + Lists.splitListItem(editor); } } } diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/index.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/index.ts index 39be90bc6..21bc63d20 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/index.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/index.ts @@ -4,4 +4,3 @@ export { isRichTextBlockElement, isRichTextElement, toggleBlock } from './lib'; export { RichFormattingExtension } from './RichFormattingExtension'; export type { RichFormattingExtensionParameters, RichTextElementType } from './types'; export { ElementType, MarkType } from './types'; -export { withRichFormatting } from './withRichFormatting'; diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/toggleBlock.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/toggleBlock.ts index 12b8eeb1c..c7ccd9a7e 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/toggleBlock.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/toggleBlock.ts @@ -1,9 +1,9 @@ import { EditorCommands } from '@prezly/slate-commons'; +import { Lists, ListType } from '@prezly/slate-lists'; import type { ElementNode } from '@prezly/slate-types'; import type { Editor } from 'slate'; import { Transforms } from 'slate'; -import { lists } from '../lists'; import { ElementType } from '../types'; export function toggleBlock(editor: Editor, type: T['type']): void { @@ -13,13 +13,19 @@ export function toggleBlock(editor: Editor, type: T['type return; } - if (type === ElementType.BULLETED_LIST || type === ElementType.NUMBERED_LIST) { - lists.wrapInList(editor, type); - lists.setListType(editor, type); + if (type === ElementType.BULLETED_LIST) { + Lists.wrapInList(editor, ListType.UNORDERED); + Lists.setListType(editor, ListType.UNORDERED); return; } - lists.unwrapList(editor); + if (type === ElementType.NUMBERED_LIST) { + Lists.wrapInList(editor, ListType.ORDERED); + Lists.setListType(editor, ListType.ORDERED); + return; + } + + Lists.unwrapList(editor); if (path && EditorCommands.isCursorInEmptyParagraph(editor, { trim: true })) { EditorCommands.removeChildren(editor, [currentNode, path]); diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/withResetRichFormattingOnBreak.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/withResetRichFormattingOnBreak.ts index ccdb2c3f9..4a3c8b62c 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/withResetRichFormattingOnBreak.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/lib/withResetRichFormattingOnBreak.ts @@ -1,11 +1,9 @@ /* eslint-disable no-param-reassign */ import { EditorCommands } from '@prezly/slate-commons'; -import { isList } from '@prezly/slate-lists'; +import { isListNode } from '@prezly/slate-types'; import type { Editor } from 'slate'; -import { options } from '../lists'; - import { isRichTextBlockElement } from './isRichTextBlockElement'; export function withResetRichFormattingOnBreak(editor: T): T { @@ -21,7 +19,7 @@ export function withResetRichFormattingOnBreak(editor: T): T { if ( isRichTextBlockElement(currentNode) && - !isList(options, currentNode) && + !isListNode(currentNode) && EditorCommands.isSelectionAtBlockEnd(editor) ) { EditorCommands.insertEmptyParagraph(editor); diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/lists.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/lists.ts deleted file mode 100644 index 8ed1a8275..000000000 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/lists.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ListsOptions } from '@prezly/slate-lists'; -import { Lists } from '@prezly/slate-lists'; -import { PARAGRAPH_NODE_TYPE } from '@prezly/slate-types'; - -import { ElementType } from './types'; - -export const options: ListsOptions = { - defaultBlockType: PARAGRAPH_NODE_TYPE, - listItemTextType: ElementType.LIST_ITEM_TEXT, - listItemType: ElementType.LIST_ITEM, - listTypes: [ElementType.BULLETED_LIST, ElementType.NUMBERED_LIST], - wrappableTypes: [ - PARAGRAPH_NODE_TYPE, - ElementType.BLOCK_QUOTE, - ElementType.HEADING_ONE, - ElementType.HEADING_TWO, - ], -}; - -export const lists = Lists(options); diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts new file mode 100644 index 000000000..178e0c657 --- /dev/null +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts @@ -0,0 +1,54 @@ +import type { ListsEditor, ListsSchema } from '@prezly/slate-lists'; +import { ListType, withLists as withListsMethods, withListsReact } from '@prezly/slate-lists'; +import { + BULLETED_LIST_NODE_TYPE, + isHeadingNode, + isListItemNode, + isListItemTextNode, + isListNode, + isParagraphNode, + isQuoteNode, + LIST_ITEM_NODE_TYPE, + LIST_ITEM_TEXT_NODE_TYPE, + NUMBERED_LIST_NODE_TYPE, + PARAGRAPH_NODE_TYPE, +} from '@prezly/slate-types'; +import type { Editor } from 'slate'; + +const SCHEMA: ListsSchema = { + isDefaultTextNode: isParagraphNode, + isListNode(node, type?) { + if (type === ListType.ORDERED) { + return isListNode(node, NUMBERED_LIST_NODE_TYPE); + } + if (type === ListType.UNORDERED) { + return isListNode(node, BULLETED_LIST_NODE_TYPE); + } + return isListNode(node); + }, + isListItemNode, + isListItemTextNode, + isListNestable(node): boolean { + return isParagraphNode(node) || isHeadingNode(node) || isQuoteNode(node); + }, + createDefaultTextNode(props = {}) { + return { children: [{ text: '' }], ...props, type: PARAGRAPH_NODE_TYPE }; + }, + createListNode(type: ListType = ListType.UNORDERED, props = {}) { + return { + children: [{ text: '' }], + ...props, + type: type === ListType.ORDERED ? NUMBERED_LIST_NODE_TYPE : BULLETED_LIST_NODE_TYPE, + }; + }, + createListItemNode(props = {}) { + return { children: [{ text: '' }], ...props, type: LIST_ITEM_NODE_TYPE }; + }, + createListItemTextNode(props = {}) { + return { children: [{ text: '' }], ...props, type: LIST_ITEM_TEXT_NODE_TYPE }; + }, +}; + +export function withLists(editor: T): T & ListsEditor { + return withListsReact(withListsMethods(SCHEMA)(editor)); +} diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withRichFormatting.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withRichFormatting.ts deleted file mode 100644 index ff58ff04a..000000000 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withRichFormatting.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable no-param-reassign */ - -import { withLists, withListsReact } from '@prezly/slate-lists'; -import type { Editor } from 'slate'; - -import { withResetRichFormattingOnBreak } from './lib'; -import { options } from './lists'; - -export function withRichFormatting(editor: T): T { - return withResetRichFormattingOnBreak(withListsReact(withLists(options)(editor))); -} diff --git a/packages/slate-editor/src/modules/editor-v4/createEditorV4.ts b/packages/slate-editor/src/modules/editor-v4/createEditorV4.ts index debdcc5c1..d8ce4ec0c 100644 --- a/packages/slate-editor/src/modules/editor-v4/createEditorV4.ts +++ b/packages/slate-editor/src/modules/editor-v4/createEditorV4.ts @@ -15,7 +15,6 @@ import { flow } from '#lodash'; import { withImages } from '#modules/editor-v4-image'; import { withLoaders } from '#modules/editor-v4-loader'; -import { withRichFormatting } from '#modules/editor-v4-rich-formatting'; import { withDeserializeHtml, @@ -51,7 +50,6 @@ export function createEditorV4( withUserFriendlyDeleteBehavior, withDeserializeHtml(getExtensions), withSlatePasting, - withRichFormatting, withImages, withFilePasting(getExtensions), ...overrides, diff --git a/packages/slate-editor/src/modules/editor-v4/useCreateEditor.ts b/packages/slate-editor/src/modules/editor-v4/useCreateEditor.ts index 38ea0306e..21b06c9ca 100644 --- a/packages/slate-editor/src/modules/editor-v4/useCreateEditor.ts +++ b/packages/slate-editor/src/modules/editor-v4/useCreateEditor.ts @@ -4,8 +4,6 @@ import type { KeyboardEvent } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import type { Editor } from 'slate'; import { createEditor } from 'slate'; -import type { HistoryEditor } from 'slate-history'; -import type { ReactEditor } from 'slate-react'; import { useLatest } from '#lib'; @@ -22,7 +20,7 @@ interface Parameters { } interface State { - editor: ReactEditor & HistoryEditor; + editor: Editor; onKeyDownList: OnKeyDown[]; } From e2a7f6ae1c18af5e3561ba1bf86bfde9f0528646 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 14 Apr 2022 17:30:07 +0300 Subject: [PATCH 13/24] Rename `isListNestable` to `isAllowedListDescendant` --- .../src/modules/editor-v4-rich-formatting/withLists.ts | 6 +++--- packages/slate-lists/src/jsx.ts | 6 +++--- packages/slate-lists/src/lib/wrapInList.ts | 6 +++--- packages/slate-lists/src/types.ts | 4 ++-- packages/slate-lists/src/withLists.ts | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts index 178e0c657..fe1c80dcd 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts @@ -16,6 +16,9 @@ import { import type { Editor } from 'slate'; const SCHEMA: ListsSchema = { + isAllowedListDescendant(node): boolean { + return isParagraphNode(node) || isHeadingNode(node) || isQuoteNode(node); + }, isDefaultTextNode: isParagraphNode, isListNode(node, type?) { if (type === ListType.ORDERED) { @@ -28,9 +31,6 @@ const SCHEMA: ListsSchema = { }, isListItemNode, isListItemTextNode, - isListNestable(node): boolean { - return isParagraphNode(node) || isHeadingNode(node) || isQuoteNode(node); - }, createDefaultTextNode(props = {}) { return { children: [{ text: '' }], ...props, type: PARAGRAPH_NODE_TYPE }; }, diff --git a/packages/slate-lists/src/jsx.ts b/packages/slate-lists/src/jsx.ts index 3112bf516..535cc649d 100644 --- a/packages/slate-lists/src/jsx.ts +++ b/packages/slate-lists/src/jsx.ts @@ -37,6 +37,9 @@ const INLINE_ELEMENTS = [LINK_TYPE]; const VOID_ELEMENTS = [DIVIDER_TYPE]; const SCHEMA: ListsSchema = { + isAllowedListDescendant(node): boolean { + return Element.isElementType(node, PARAGRAPH_TYPE); + }, isDefaultTextNode(node) { return Element.isElementType(node, PARAGRAPH_TYPE); }, @@ -55,9 +58,6 @@ const SCHEMA: ListsSchema = { isListItemTextNode(node) { return Element.isElementType(node, LIST_ITEM_TEXT_TYPE); }, - isListNestable(node): boolean { - return Element.isElementType(node, PARAGRAPH_TYPE); - }, createDefaultTextNode(props = {}) { return { children: [{ text: '' }], ...props, type: PARAGRAPH_TYPE }; }, diff --git a/packages/slate-lists/src/lib/wrapInList.ts b/packages/slate-lists/src/lib/wrapInList.ts index 5f99dc556..15a954200 100644 --- a/packages/slate-lists/src/lib/wrapInList.ts +++ b/packages/slate-lists/src/lib/wrapInList.ts @@ -5,10 +5,10 @@ import type { ListsEditor } from '../types'; import type { ListType } from '../types'; /** - * All nodes matching `isListNestable()` in the current selection + * All nodes matching `isAllowedListDescendant()` in the current selection * will be converted to list items and then wrapped in lists. * - * @see ListsEditor.isListNestable() + * @see ListsEditor.isAllowedListDescendant() */ export function wrapInList(editor: ListsEditor, listType: ListType): void { if (!editor.selection) { @@ -24,7 +24,7 @@ export function wrapInList(editor: ListsEditor, listType: ListType): void { !editor.isListNode(node) && !editor.isListItemNode(node) && !editor.isListItemTextNode(node) && - editor.isListNestable(node) + editor.isAllowedListDescendant(node) ); }, }), diff --git a/packages/slate-lists/src/types.ts b/packages/slate-lists/src/types.ts index ec835b61d..89ddb38ec 100644 --- a/packages/slate-lists/src/types.ts +++ b/packages/slate-lists/src/types.ts @@ -6,6 +6,8 @@ export enum ListType { } export interface ListsSchema { + isAllowedListDescendant(node: Node): boolean; + isDefaultTextNode(node: Node): boolean; isListNode(node: Node, type?: ListType): boolean; @@ -14,8 +16,6 @@ export interface ListsSchema { isListItemTextNode(node: Node): boolean; - isListNestable(node: Node): boolean; - createDefaultTextNode(props?: { children?: Descendant[] }): Element; createListNode(type?: ListType, props?: { children?: Descendant[] }): Element; diff --git a/packages/slate-lists/src/withLists.ts b/packages/slate-lists/src/withLists.ts index 09aa4d4cf..cdfca8cc9 100644 --- a/packages/slate-lists/src/withLists.ts +++ b/packages/slate-lists/src/withLists.ts @@ -31,11 +31,11 @@ const LIST_NORMALIZERS: Normalizer[] = [ export function withLists(schema: ListsSchema) { return function (editor: T): T & ListsEditor { const listsEditor: T & ListsEditor = Object.assign(editor, { + isAllowedListDescendant: schema.isAllowedListDescendant, isDefaultTextNode: schema.isDefaultTextNode, isListNode: schema.isListNode, isListItemNode: schema.isListItemNode, isListItemTextNode: schema.isListItemTextNode, - isListNestable: schema.isListNestable, createDefaultTextNode: schema.createDefaultTextNode, createListNode: schema.createListNode, createListItemNode: schema.createListItemNode, From 3d2016a50427feccf2ddfbf033bc3c6465cc8e90 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 14 Apr 2022 17:32:35 +0300 Subject: [PATCH 14/24] Reuse existing createParagraph() func --- .../src/modules/editor-v4-paragraphs/lib/createParagraph.ts | 3 ++- .../editor-v4-paragraphs/lib/parseSerializedElement.ts | 2 +- .../src/modules/editor-v4-rich-formatting/withLists.ts | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts b/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts index aeb1f48c1..5bc1e3c48 100644 --- a/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts +++ b/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts @@ -2,8 +2,9 @@ import type { ParagraphNode } from '@prezly/slate-types'; import { PARAGRAPH_NODE_TYPE } from '@prezly/slate-types'; export function createParagraph( - children: ParagraphNode['children'] = [{ text: '' }], + props: { children?: ParagraphNode['children'] } = {}, ): ParagraphNode { + const { children = [{ text: '' }] } = props; return { type: PARAGRAPH_NODE_TYPE, children, diff --git a/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/parseSerializedElement.ts b/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/parseSerializedElement.ts index f34948ffe..782edf43f 100644 --- a/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/parseSerializedElement.ts +++ b/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/parseSerializedElement.ts @@ -7,7 +7,7 @@ export function parseSerializedElement(serialized: string): ParagraphNode | unde const parsed = JSON.parse(serialized); if (isParagraphNode(parsed)) { - return createParagraph(parsed.children); + return createParagraph(parsed); } return undefined; diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts index fe1c80dcd..eab657cbb 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts @@ -11,10 +11,11 @@ import { LIST_ITEM_NODE_TYPE, LIST_ITEM_TEXT_NODE_TYPE, NUMBERED_LIST_NODE_TYPE, - PARAGRAPH_NODE_TYPE, } from '@prezly/slate-types'; import type { Editor } from 'slate'; +import { createParagraph } from '#modules/editor-v4-paragraphs'; + const SCHEMA: ListsSchema = { isAllowedListDescendant(node): boolean { return isParagraphNode(node) || isHeadingNode(node) || isQuoteNode(node); @@ -32,7 +33,7 @@ const SCHEMA: ListsSchema = { isListItemNode, isListItemTextNode, createDefaultTextNode(props = {}) { - return { children: [{ text: '' }], ...props, type: PARAGRAPH_NODE_TYPE }; + return createParagraph(props); }, createListNode(type: ListType = ListType.UNORDERED, props = {}) { return { From 6f91437326e739e76ae4f9b8f5f4095aacb7426f Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Fri, 15 Apr 2022 11:59:08 +0300 Subject: [PATCH 15/24] Move lists keydown logic to the slate-lists package For easy reusability --- package-lock.json | 16 +++- .../RichFormattingExtension.tsx | 6 +- .../createOnKeyDown.ts | 84 ------------------- .../createOnKeyDownHandler.ts | 40 +++++++++ packages/slate-lists/package.json | 1 + packages/slate-lists/src/index.ts | 1 + packages/slate-lists/src/onKeyDown.ts | 57 +++++++++++++ 7 files changed, 117 insertions(+), 88 deletions(-) delete mode 100644 packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts create mode 100644 packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDownHandler.ts create mode 100644 packages/slate-lists/src/onKeyDown.ts diff --git a/package-lock.json b/package-lock.json index 6b3a69e8b..34a417cfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32515,6 +32515,7 @@ "license": "MIT", "dependencies": { "@prezly/slate-commons": "^0.29.4", + "is-hotkey": "^0.2.0", "uuid": "^8.3.0" }, "devDependencies": { @@ -32527,6 +32528,11 @@ "slate-react": "^0.71.0" } }, + "packages/slate-lists/node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, "packages/slate-types": { "name": "@prezly/slate-types", "version": "0.29.4", @@ -36402,8 +36408,16 @@ "requires": { "@prezly/slate-commons": "^0.29.4", "@types/uuid": "^8.3.0", - "slate-hyperscript": "*", + "is-hotkey": "*", + "slate-hyperscript": "^0.67.0", "uuid": "^8.3.0" + }, + "dependencies": { + "is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + } } }, "@prezly/slate-types": { diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx b/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx index 4f1fd6338..d16021119 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx @@ -6,7 +6,7 @@ import type { RenderElementProps } from 'slate-react'; import { RichTextElement, Text } from './components'; import { RICH_FORMATTING_EXTENSION_ID } from './constants'; import { createDeserialize } from './createDeserialize'; -import { createOnKeyDown } from './createOnKeyDown'; +import { createOnKeyDownHandler } from './createOnKeyDownHandler'; import { isRichTextElement, normalizeRedundantRichTextAttributes, @@ -24,7 +24,7 @@ export const RichFormattingExtension = ({ blocks }: Parameters): Extension => ({ deserialize: createDeserialize({ blocks }), inlineTypes: [], normalizers: [normalizeRedundantRichTextAttributes], - onKeyDown: createOnKeyDown({ blocks }), + onKeyDown: createOnKeyDownHandler({ blocks }), renderElement: ({ attributes, children, element }: RenderElementProps) => { if (blocks && isRichTextElement(element)) { return ( @@ -44,6 +44,6 @@ export const RichFormattingExtension = ({ blocks }: Parameters): Extension => ({ ElementType.HEADING_TWO, ], withOverrides(editor) { - return withResetRichFormattingOnBreak(withLists(editor)); + return withResetRichFormattingOnBreak(blocks ? withLists(editor) : editor); }, }); diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts deleted file mode 100644 index 6e391a788..000000000 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDown.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { EditorCommands } from '@prezly/slate-commons'; -import { Lists } from '@prezly/slate-lists'; -import { isHotkey } from 'is-hotkey'; -import type { KeyboardEvent } from 'react'; -import { Editor } from 'slate'; -import { ReactEditor } from 'slate-react'; - -import { MarkType } from './types'; - -const MARK_HOTKEYS: { hotkey: string; mark: MarkType }[] = [ - { hotkey: 'mod+b', mark: MarkType.BOLD }, - { hotkey: 'mod+i', mark: MarkType.ITALIC }, - { hotkey: 'mod+u', mark: MarkType.UNDERLINED }, -]; - -function marksOnKeyDown(event: KeyboardEvent, editor: Editor) { - return MARK_HOTKEYS.forEach(({ hotkey, mark }) => { - if (isHotkey(hotkey, event.nativeEvent)) { - event.preventDefault(); - EditorCommands.toggleMark(editor, mark); - } - }); -} - -function listsOnKeyDown(event: KeyboardEvent, editor: Editor) { - const listItemsInSelection = Lists.getListItemsInRange(editor, editor.selection); - - // Since we're overriding the default Tab key behavior - // we need to bring back the possibility to blur the editor - // with keyboard. - if (isHotkey('esc', event.nativeEvent)) { - event.preventDefault(); - ReactEditor.blur(editor); - } - - if (isHotkey('tab', event.nativeEvent)) { - event.preventDefault(); - Lists.increaseDepth(editor); - } - - if (isHotkey('shift+tab', event.nativeEvent)) { - event.preventDefault(); - Lists.decreaseDepth(editor); - } - - if (isHotkey('backspace', event.nativeEvent) && !Lists.canDeleteBackward(editor)) { - event.preventDefault(); - Lists.decreaseDepth(editor); - } - - if (isHotkey('enter', event.nativeEvent)) { - if (Lists.isCursorInEmptyListItem(editor)) { - event.preventDefault(); - Lists.decreaseDepth(editor); - } else if (listItemsInSelection.length > 0) { - event.preventDefault(); - Lists.splitListItem(editor); - } - } -} - -function softBreakOnKeyDown(event: KeyboardEvent, editor: Editor) { - if (isHotkey('shift+enter', event.nativeEvent) && !event.isDefaultPrevented()) { - event.preventDefault(); - Editor.insertText(editor, '\n'); - } -} - -export function createOnKeyDown(parameters: { blocks: boolean }) { - return (event: KeyboardEvent, editor: Editor) => { - softBreakOnKeyDown(event, editor); - marksOnKeyDown(event, editor); - - if (parameters.blocks) { - listsOnKeyDown(event, editor); - - // Slate does not always trigger normalization when one would expect it to. - // So we want to force it after we perform lists operations, as it fixes - // many unexpected behaviors. - // https://github.com/ianstormtaylor/slate/issues/3758 - Editor.normalize(editor, { force: true }); - } - }; -} diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDownHandler.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDownHandler.ts new file mode 100644 index 000000000..cd81c6cdc --- /dev/null +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/createOnKeyDownHandler.ts @@ -0,0 +1,40 @@ +import { EditorCommands } from '@prezly/slate-commons'; +import { onKeyDown as onListsKeyDown } from '@prezly/slate-lists'; +import { isHotkey } from 'is-hotkey'; +import type { KeyboardEvent } from 'react'; +import { Editor } from 'slate'; + +import { MarkType } from './types'; + +const MARK_HOTKEYS: { hotkey: string; mark: MarkType }[] = [ + { hotkey: 'mod+b', mark: MarkType.BOLD }, + { hotkey: 'mod+i', mark: MarkType.ITALIC }, + { hotkey: 'mod+u', mark: MarkType.UNDERLINED }, +]; + +function marksOnKeyDown(event: KeyboardEvent, editor: Editor) { + return MARK_HOTKEYS.forEach(({ hotkey, mark }) => { + if (isHotkey(hotkey, event.nativeEvent)) { + event.preventDefault(); + EditorCommands.toggleMark(editor, mark); + } + }); +} + +function softBreakOnKeyDown(event: KeyboardEvent, editor: Editor) { + if (isHotkey('shift+enter', event.nativeEvent) && !event.isDefaultPrevented()) { + event.preventDefault(); + Editor.insertText(editor, '\n'); + } +} + +export function createOnKeyDownHandler(parameters: { blocks: boolean }) { + return (event: KeyboardEvent, editor: Editor) => { + softBreakOnKeyDown(event, editor); + marksOnKeyDown(event, editor); + + if (parameters.blocks) { + onListsKeyDown(editor, event); + } + }; +} diff --git a/packages/slate-lists/package.json b/packages/slate-lists/package.json index 0deac5a29..13f9fd70d 100644 --- a/packages/slate-lists/package.json +++ b/packages/slate-lists/package.json @@ -55,6 +55,7 @@ }, "dependencies": { "@prezly/slate-commons": "^0.29.4", + "is-hotkey": "^0.2.0", "uuid": "^8.3.0" }, "devDependencies": { diff --git a/packages/slate-lists/src/index.ts b/packages/slate-lists/src/index.ts index 447e0e2ee..20996619e 100644 --- a/packages/slate-lists/src/index.ts +++ b/packages/slate-lists/src/index.ts @@ -1,5 +1,6 @@ export * as Lists from './lib'; export * as Normalizations from './normalizations'; +export { onKeyDown } from './onKeyDown'; export * from './types'; export { withLists } from './withLists'; export { withListsReact } from './withListsReact'; diff --git a/packages/slate-lists/src/onKeyDown.ts b/packages/slate-lists/src/onKeyDown.ts new file mode 100644 index 000000000..0006fcd2d --- /dev/null +++ b/packages/slate-lists/src/onKeyDown.ts @@ -0,0 +1,57 @@ +import { isHotkey } from 'is-hotkey'; +import type { KeyboardEvent } from 'react'; +import { Editor } from 'slate'; +import { ReactEditor } from 'slate-react'; + +import { + canDeleteBackward, + decreaseDepth, + getListItemsInRange, + increaseDepth, + isCursorInEmptyListItem, + splitListItem, +} from './lib'; +import type { ListsEditor } from './types'; + +export function onKeyDown(editor: ListsEditor & ReactEditor, event: KeyboardEvent) { + const listItemsInSelection = getListItemsInRange(editor, editor.selection); + + // Since we're overriding the default Tab key behavior + // we need to bring back the possibility to blur the editor + // with keyboard. + if (isHotkey('esc', event.nativeEvent)) { + event.preventDefault(); + ReactEditor.blur(editor); + } + + if (isHotkey('tab', event.nativeEvent)) { + event.preventDefault(); + increaseDepth(editor); + } + + if (isHotkey('shift+tab', event.nativeEvent)) { + event.preventDefault(); + decreaseDepth(editor); + } + + if (isHotkey('backspace', event.nativeEvent) && !canDeleteBackward(editor)) { + event.preventDefault(); + decreaseDepth(editor); + } + + if (isHotkey('enter', event.nativeEvent)) { + if (isCursorInEmptyListItem(editor)) { + event.preventDefault(); + decreaseDepth(editor); + } else if (listItemsInSelection.length > 0) { + event.preventDefault(); + splitListItem(editor); + } + } + + // Slate does not always trigger normalization when one would expect it to. + // So we want to force it after we perform lists operations, as it fixes + // many unexpected behaviors. + // https://github.com/ianstormtaylor/slate/issues/3758 + Editor.normalize(editor, { force: true }); +} From 356fdf8c3461f9d98a812efcf8b76b8f779666b8 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Fri, 15 Apr 2022 12:04:41 +0300 Subject: [PATCH 16/24] Rename decorators to resolve naming conflicts --- .../editor-v4-rich-formatting/RichFormattingExtension.tsx | 4 ++-- .../{withLists.ts => withListsFormatting.ts} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename packages/slate-editor/src/modules/editor-v4-rich-formatting/{withLists.ts => withListsFormatting.ts} (87%) diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx b/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx index d16021119..fc9316185 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/RichFormattingExtension.tsx @@ -13,7 +13,7 @@ import { withResetRichFormattingOnBreak, } from './lib'; import { ElementType } from './types'; -import { withLists } from './withLists'; +import { withListsFormatting } from './withListsFormatting'; interface Parameters { blocks: boolean; @@ -44,6 +44,6 @@ export const RichFormattingExtension = ({ blocks }: Parameters): Extension => ({ ElementType.HEADING_TWO, ], withOverrides(editor) { - return withResetRichFormattingOnBreak(blocks ? withLists(editor) : editor); + return withResetRichFormattingOnBreak(blocks ? withListsFormatting(editor) : editor); }, }); diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts similarity index 87% rename from packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts rename to packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts index eab657cbb..7cb88f43f 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withLists.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts @@ -1,5 +1,5 @@ import type { ListsEditor, ListsSchema } from '@prezly/slate-lists'; -import { ListType, withLists as withListsMethods, withListsReact } from '@prezly/slate-lists'; +import { ListType, withLists, withListsReact } from '@prezly/slate-lists'; import { BULLETED_LIST_NODE_TYPE, isHeadingNode, @@ -50,6 +50,6 @@ const SCHEMA: ListsSchema = { }, }; -export function withLists(editor: T): T & ListsEditor { - return withListsReact(withListsMethods(SCHEMA)(editor)); +export function withListsFormatting(editor: T): T & ListsEditor { + return withListsReact(withLists(SCHEMA)(editor)); } From 1062335ce85d2abef8d24bf4b349573d3a18a4b9 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Fri, 15 Apr 2022 12:12:00 +0300 Subject: [PATCH 17/24] Fix prettier --- packages/slate-commons/src/EditableWithExtensions.tsx | 1 - packages/slate-commons/src/commands/toggleMark.ts | 6 +++++- packages/slate-lists/src/lib/decreaseListItemDepth.ts | 8 +++----- .../src/normalizations/normalizeOrphanListItem.ts | 1 - .../src/normalizations/normalizeOrphanListItemText.ts | 1 - packages/slate-lists/src/withListsReact.ts | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/slate-commons/src/EditableWithExtensions.tsx b/packages/slate-commons/src/EditableWithExtensions.tsx index e9d2eadfe..5bf25593d 100644 --- a/packages/slate-commons/src/EditableWithExtensions.tsx +++ b/packages/slate-commons/src/EditableWithExtensions.tsx @@ -22,7 +22,6 @@ import type { RenderLeaf, } from './types'; - export interface Props { decorate?: Decorate; editor: Editor & ReactEditor; diff --git a/packages/slate-commons/src/commands/toggleMark.ts b/packages/slate-commons/src/commands/toggleMark.ts index 850d8835f..f48974d6f 100644 --- a/packages/slate-commons/src/commands/toggleMark.ts +++ b/packages/slate-commons/src/commands/toggleMark.ts @@ -3,7 +3,11 @@ import { Editor } from 'slate'; import { isMarkActive } from './isMarkActive'; -export function toggleMark(editor: Editor, mark: keyof Omit, force?: boolean): void { +export function toggleMark( + editor: Editor, + mark: keyof Omit, + force?: boolean, +): void { const shouldSet = force ?? !isMarkActive(editor, mark); if (shouldSet) { diff --git a/packages/slate-lists/src/lib/decreaseListItemDepth.ts b/packages/slate-lists/src/lib/decreaseListItemDepth.ts index f66454019..6d22f74b7 100644 --- a/packages/slate-lists/src/lib/decreaseListItemDepth.ts +++ b/packages/slate-lists/src/lib/decreaseListItemDepth.ts @@ -64,11 +64,9 @@ export function decreaseListItemDepth(editor: ListsEditor, listItemPath: Path): } if (Node.has(editor, listItemTextPath)) { - Transforms.setNodes( - editor, - editor.createDefaultTextNode(), - { at: listItemTextPath }, - ); + Transforms.setNodes(editor, editor.createDefaultTextNode(), { + at: listItemTextPath, + }); Transforms.liftNodes(editor, { at: listItemTextPath }); Transforms.liftNodes(editor, { at: listItemPath }); } diff --git a/packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts b/packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts index 893ee20cf..a762f277a 100644 --- a/packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts +++ b/packages/slate-lists/src/normalizations/normalizeOrphanListItem.ts @@ -4,7 +4,6 @@ import { Editor, Transforms } from 'slate'; import { getParentList } from '../lib'; import type { ListsEditor } from '../types'; - /** * If "list-item" somehow (e.g. by deleting everything around it) ends up * at the root of the editor, we have to convert it into a "default-block". diff --git a/packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts b/packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts index 2facda746..88f8b3aa7 100644 --- a/packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts +++ b/packages/slate-lists/src/normalizations/normalizeOrphanListItemText.ts @@ -4,7 +4,6 @@ import { Transforms } from 'slate'; import { getParentListItem } from '../lib'; import type { ListsEditor } from '../types'; - /** * If "list-item-text" somehow (e.g. by deleting everything around it) ends up * at the root of the editor, we have to convert it into a "default-block". diff --git a/packages/slate-lists/src/withListsReact.ts b/packages/slate-lists/src/withListsReact.ts index e4bd1f8a2..3c5f9f051 100644 --- a/packages/slate-lists/src/withListsReact.ts +++ b/packages/slate-lists/src/withListsReact.ts @@ -13,7 +13,7 @@ export function withListsReact(editor: T): T { const { setFragmentData } = editor; editor.setFragmentData = (data: DataTransfer) => { - withRangeCloneContentsPatched(function() { + withRangeCloneContentsPatched(function () { setFragmentData(data); }); }; From 222e96f024c83f08a68c3627bbf419c93f8d1631 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Fri, 15 Apr 2022 12:19:07 +0300 Subject: [PATCH 18/24] Fix TS typing --- packages/slate-commons/src/commands/isMarkActive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/slate-commons/src/commands/isMarkActive.ts b/packages/slate-commons/src/commands/isMarkActive.ts index f17dcd6df..319dd9133 100644 --- a/packages/slate-commons/src/commands/isMarkActive.ts +++ b/packages/slate-commons/src/commands/isMarkActive.ts @@ -2,6 +2,6 @@ import type { Text } from 'slate'; import { Editor } from 'slate'; export function isMarkActive(editor: Editor, mark: keyof Omit): boolean { - const marks = Editor.marks(editor) as Record; + const marks = Editor.marks(editor) as Record; return marks ? marks[mark] === true : false; } From 454be9294ba8658b98caf06a31c6a820072c1e55 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 21 Apr 2022 15:13:11 +0300 Subject: [PATCH 19/24] Update slate-lists README --- packages/slate-lists/README.md | 351 ++++++++++++++++++++------------- 1 file changed, 211 insertions(+), 140 deletions(-) diff --git a/packages/slate-lists/README.md b/packages/slate-lists/README.md index aa6015517..11937c67d 100644 --- a/packages/slate-lists/README.md +++ b/packages/slate-lists/README.md @@ -60,7 +60,7 @@ import { Node } from 'slate'; interface ListNode { children: ListItemNode[]; - type: 'bulleted-list' | 'numbered-list'; // see ListsOptions to customize this + type: 'bulleted-list' | 'numbered-list'; // see ListsSchema to customize this } interface ListItemNode { @@ -93,7 +93,8 @@ yarn add @prezly/slate-lists ## User guide -Let's start with a minimal Slate + React example which we will be adding lists support to. Nothing interesting here just yet. +Let's start with a minimal Slate + React example which we will be adding lists support to. +Nothing interesting here just yet. Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-0-initial-state-9gmff?file=/src/MyEditor.tsx @@ -116,29 +117,67 @@ export const MyEditor = () => { }; ``` -### Define [`ListsOptions`](src/types.ts) +### Define [`ListsSchema`](src/types.ts) -First you're going to want to define options that will be passed to the extension. Just create an object matching the [`ListsOptions`](src/types.ts) interface. +First you're going to want to define schema that will be passed to the extension. +Just create an object matching the [`ListsSchema`](src/types.ts) interface. Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-1-define-options-m564b?file=/src/MyEditor.tsx ```diff import { useMemo, useState } from 'react'; - import { createEditor, Node } from 'slate'; +-import { createEditor, Node } from 'slate'; ++import { createEditor, Element, Node } from 'slate'; import { Editable, Slate, withReact } from 'slate-react'; -+import { ListsOptions } from '@prezly/slate-lists'; ++import { type ListsSchema, ListType } from '@prezly/slate-lists'; + -+const options: ListsOptions = { -+ defaultBlockType: 'paragraph', -+ listItemTextType: 'list-item-text', -+ listItemType: 'list-item', -+ listTypes: ['ordered-list', 'unordered-list'], -+ wrappableTypes: ['paragraph'] ++const schema: ListsSchema = { ++ isAllowedListDescendant(node) { ++ return Element.isElementType(node, PARAGRAPH_TYPE); ++ }, ++ isDefaultTextNode(node) { ++ return Element.isElementType(node, 'paragraph'); ++ }, ++ isListNode(node, type?) { ++ if (type === ListType.UNORDERED) return Element.isElementType(node, 'bulleted-list'); ++ if (type === ListType.ORDERED) return Element.isElementType(node, 'numbered-list'); ++ return Element.isElementType(node, 'ordered-list') || Element.isElementType(node, 'numbered-list'); ++ }, ++ isListItemNode(node) { ++ return Element.isElementNode(node, 'list-item'); ++ }, ++ isListItemTextNode(node) { ++ return Element.isElementNode(node, 'list-item-text'); ++ }, ++ createDefaultTextNode({ children } = {}) { ++ return { ++ type: 'paragraph', ++ children: children ?? [{ text: '' }], ++ }; ++ }, ++ createListNode({ children } = {}, type = ListType.UNORDERED) { ++ return { ++ type: type === ListType.UNORDERED ? 'bulleted-list' : 'numbered-list', ++ children: children ?? [this.creteListItemNode()], ++ }; ++ }, ++ createListItemNode({ children } = {}) { ++ return { ++ type: 'list-item', ++ children: children ?? [this.createListItemTextNode()], ++ }; ++ }, ++ createListItemTextNode({ children } = {}) { ++ return { ++ type: 'list-item-text', ++ children: children ?? [{ text: '' }], ++ }; ++ }, +}; const initialValue: Node[] = [{ type: 'paragraph', children: [{ text: 'Hello world!' }] }]; - const MyEditor = () => { + export const MyEditor = () => { const [value, setValue] = useState(initialValue); const editor = useMemo(() => withReact(createEditor()), []); @@ -148,29 +187,66 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-1-define-opt ); }; - - export default MyEditor; ``` ### Use [`withLists`](src/lib/withLists.ts) plugin -[`withLists`](src/lib/withLists.ts) is a [Slate plugin](https://docs.slatejs.org/concepts/07-plugins) that enables [normalizations](https://docs.slatejs.org/concepts/10-normalizing) which enforce [schema](#Schema) constraints and recover from unsupported structures. +[`withLists`](src/lib/withLists.ts) is a [Slate plugin](https://docs.slatejs.org/concepts/07-plugins) +that enables [normalizations](https://docs.slatejs.org/concepts/10-normalizing) +which enforce [schema](#Schema) constraints and recover from unsupported structures. Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-2-use-withlists-plugin-5splt?file=/src/MyEditor.tsx ```diff import { useMemo, useState } from 'react'; import { createEditor, Node } from 'slate'; + import { createEditor, Element, Node } from 'slate'; import { Editable, Slate, withReact } from 'slate-react'; --import { ListsOptions } from '@prezly/slate-lists'; -+import { ListsOptions, withLists } from '@prezly/slate-lists'; - - const options: ListsOptions = { - defaultBlockType: 'paragraph', - listItemTextType: 'list-item-text', - listItemType: 'list-item', - listTypes: ['ordered-list', 'unordered-list'], - wrappableTypes: ['paragraph'], +-import { type ListsSchema, ListType } from '@prezly/slate-lists'; ++import { type ListsSchema, ListType, withLists } from '@prezly/slate-lists'; + + const schema: ListsSchema = { + isAllowedListDescendant(node) { + return Element.isElementType(node, PARAGRAPH_TYPE); + }, + isDefaultTextNode(node) { + return Element.isElementType(node, 'paragraph'); + }, + isListNode(node, type?) { + if (type === ListType.UNORDERED) return Element.isElementType(node, 'bulleted-list'); + if (type === ListType.ORDERED) return Element.isElementType(node, 'numbered-list'); + return Element.isElementType(node, 'ordered-list') || Element.isElementType(node, 'numbered-list'); + }, + isListItemNode(node) { + return Element.isElementNode(node, 'list-item'); + }, + isListItemTextNode(node) { + return Element.isElementNode(node, 'list-item-text'); + }, + createDefaultTextNode({ children } = {}) { + return { + type: 'paragraph', + children: children ?? [{ text: '' }], + }; + }, + createListNode({ children } = {}, type = ListType.UNORDERED) { + return { + type: type === ListType.UNORDERED ? 'bulleted-list' : 'numbered-list', + children: children ?? [this.creteListItemNode()], + }; + }, + createListItemNode({ children } = {}) { + return { + type: 'list-item', + children: children ?? [this.createListItemTextNode()], + }; + }, + createListItemTextNode({ children } = {}) { + return { + type: 'list-item-text', + children: children ?? [{ text: '' }], + }; + }, }; const initialValue: Node[] = [{ type: 'paragraph', children: [{ text: 'Hello world!' }] }]; @@ -178,8 +254,10 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-2-use-withli export const MyEditor = () => { const [value, setValue] = useState(initialValue); - const editor = useMemo(() => withReact(createEditor()), []); -+ const baseEditor = useMemo(() => withReact(createEditor()), []); -+ const editor = useMemo(() => withLists(options)(baseEditor), [baseEditor]); ++ const editor = useMemo(function() { ++ const baseEditor = withReact(createEditor()); ++ return withLists(schema)(baseEditor); ++ }, []); return ( @@ -187,77 +265,78 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-2-use-withli ); }; - ``` ### Use [`withListsReact`](src/lib/withListsReact.ts) plugin -[`withListsReact`](src/lib/withListsReact.ts) is useful on the client-side - it's a [Slate plugin](https://docs.slatejs.org/concepts/07-plugins) that overrides `editor.setFragmentData`. It enables `Range.prototype.cloneContents` monkey patch to improve copying behavior in some edge cases. +[`withListsReact`](src/lib/withListsReact.ts) is useful on the client-side - +it's a [Slate plugin](https://docs.slatejs.org/concepts/07-plugins) - that overrides `editor.setFragmentData`. +It enables `Range.prototype.cloneContents` monkey patch to improve copying behavior in some edge cases. Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-3-use-withlistsreact-plugin-rgubg?file=/src/MyEditor.tsx ```diff import { useMemo, useState } from 'react'; import { createEditor, Node } from 'slate'; + import { createEditor, Element, Node } from 'slate'; import { Editable, Slate, withReact } from 'slate-react'; --import { ListsOptions, withLists } from '@prezly/slate-lists'; -+import { ListsOptions, withLists, withListsReact } from '@prezly/slate-lists'; - - const options: ListsOptions = { - defaultBlockType: 'paragraph', - listItemTextType: 'list-item-text', - listItemType: 'list-item', - listTypes: ['ordered-list', 'unordered-list'], - wrappableTypes: ['paragraph'], +-import { type ListsSchema, ListType, withLists } from '@prezly/slate-lists'; ++import { type ListsSchema, ListType, withLists, withListsReact } from '@prezly/slate-lists'; + + const schema: ListsSchema = { + isAllowedListDescendant(node) { + return Element.isElementType(node, PARAGRAPH_TYPE); + }, + isDefaultTextNode(node) { + return Element.isElementType(node, 'paragraph'); + }, + isListNode(node, type?) { + if (type === ListType.UNORDERED) return Element.isElementType(node, 'bulleted-list'); + if (type === ListType.ORDERED) return Element.isElementType(node, 'numbered-list'); + return Element.isElementType(node, 'ordered-list') || Element.isElementType(node, 'numbered-list'); + }, + isListItemNode(node) { + return Element.isElementNode(node, 'list-item'); + }, + isListItemTextNode(node) { + return Element.isElementNode(node, 'list-item-text'); + }, + createDefaultTextNode({ children } = {}) { + return { + type: 'paragraph', + children: children ?? [{ text: '' }], + }; + }, + createListNode({ children } = {}, type = ListType.UNORDERED) { + return { + type: type === ListType.UNORDERED ? 'bulleted-list' : 'numbered-list', + children: children ?? [this.creteListItemNode()], + }; + }, + createListItemNode({ children } = {}) { + return { + type: 'list-item', + children: children ?? [this.createListItemTextNode()], + }; + }, + createListItemTextNode({ children } = {}) { + return { + type: 'list-item-text', + children: children ?? [{ text: '' }], + }; + }, }; const initialValue: Node[] = [{ type: 'paragraph', children: [{ text: 'Hello world!' }] }]; - const MyEditor = () => { - const [value, setValue] = useState(initialValue); - const baseEditor = useMemo(() => withReact(createEditor()), []); -- const editor = useMemo(() => withLists(options)(baseEditor), [baseEditor]); -+ const editor = useMemo(() => withListsReact(withLists(options)(baseEditor)), [baseEditor]); - - return ( - - - - ); - }; - - export default MyEditor; -``` - -### Use [`Lists`](src/Lists.ts) - -It's time to pass the [`ListsOptions`](src/types.ts) instance to [`Lists`](src/Lists.ts) function. It will create an object (`lists`) with utilities and transforms bound to the options you passed to it. Those are the building blocks you're going to use when adding lists support to your editor. Use them to implement UI controls, keyboard shortcuts, etc. - -Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-4-use-lists-v5fop?file=/src/MyEditor.tsx - -```diff - import { useMemo, useState } from 'react'; - import { createEditor, Node } from 'slate'; - import { Editable, Slate, withReact } from 'slate-react'; --import { ListsOptions, withLists, withListsReact } from '@prezly/slate-lists'; -+import { Lists, ListsOptions, withLists, withListsReact } from '@prezly/slate-lists'; - - const options: ListsOptions = { - defaultBlockType: 'paragraph', - listItemTextType: 'list-item-text', - listItemType: 'list-item', - listTypes: ['ordered-list', 'unordered-list'], - wrappableTypes: ['paragraph'], - }; -+ -+ const lists = Lists(options); - - const initialValue: Node[] = [{ type: 'paragraph', children: [{ text: 'Hello world!' }] }]; - export const MyEditor = () => { const [value, setValue] = useState(initialValue); - const baseEditor = useMemo(() => withReact(createEditor()), []); - const editor = useMemo(() => withListsReact(withLists(options)(baseEditor)), [baseEditor]); + const editor = useMemo(() => withReact(createEditor()), []); + const editor = useMemo(function() { + const baseEditor = withReact(createEditor()); +- return withLists(schema)(baseEditor); ++ return withListsReact(withLists(schema)(baseEditor)); + }, []); return ( @@ -269,7 +348,7 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-4-use-lists- ### Good to go -Now you can use the [API exposed on the `lists` instance](#Lists). +Now you can use the [API exposed on the `Lists` functions](#Lists). Be sure to check the [complete usage example](#Demo). @@ -279,83 +358,90 @@ There are JSDocs for all core functionality. Only core API is documented although all utility functions are exposed. Should you ever need anything beyond the core API, please have a look at [`src/index.ts`](src/index.ts) to see what's available. -- [`ListsOptions`](#ListsOptions) +- [`ListsSchema`](#ListsSchema) - [`withLists`](#withLists) - [`withListsReact`](#withListsReact) - [`Lists`](#Lists) -### [`ListsOptions`](src/types.ts) +### [`ListsSchema`](src/types.ts) + +Lists schema wires the Lists plugin to your project-level defined Slate model. +It is designed with 100% customization in mind, not depending on any specific node types, or non-core interfaces. + +| Name | Description | +|---------------------------|------------------------------------------------------------------------------------------------------------------------| +| `isAllowedListDescendant` | Check if a node can be converted to a list item text node. | +| `isDefaultTextNode` | Check if a node is a plain default text node, that list item text node will become when it is unwrapped or normalized. | +| `isListNode` | Check if a node is representing a list. | +| `isListItemNode` | Check if a node is representing a list item. | +| `isListItemTextNode` | Check if a node is representing a list item text. | +| `createDefaultTextNode` | Create a plain default text node. List item text nodes become these when unwrapped or normalized. | +| `createListNode` | Create a new list node of the given type. | +| `createListItemNode` | Create a new list item node. | +| `createListItemTextNode` | Create a new list item text node. | + +### [`ListsEditor`](src/types.ts) -All options are required. +ListsEditor is an instance of Slate `Editor`, extends with `ListsSchema` methods: -| Name | Type | Description | -| ------------------ | ---------- | ----------------------------------------------------------------------------------------------------------- | -| `defaultBlockType` | `string` | Type of the node that `listItemTextType` will become when it is unwrapped or normalized. | -| `listItemTextType` | `string` | Type of the node representing list item text. | -| `listItemType` | `string` | Type of the node representing list item. | -| `listTypes` | `string[]` | Types of nodes representing lists. The first type will be the default type (e.g. when wrapping with lists). | -| `wrappableTypes` | `string[]` | Types of nodes that can be converted into a node representing list item text. | +```ts +type ListsEditor = Editor & ListsSchema; +``` ### [`withLists`](src/lib/withLists.ts) -```tsx +```ts /** * Enables normalizations that enforce schema constraints and recover from unsupported cases. */ -withLists(options: ListsOptions) => ((editor: T) => T) +withLists(schema: ListsSchema) => ((editor: T) => T & ListsEditor) ``` ### [`withListsReact`](src/lib/withListsReact.ts) -```tsx +```ts /** * Enables Range.prototype.cloneContents monkey patch to improve pasting behavior * in few edge cases. */ -withListsReact(editor: T): T +withListsReact(editor: T): T ``` ### [`Lists`](src/Lists.ts) -```tsx -/** - * Creates an API adapter with functions bound to passed options. - */ -Lists(options: ListsOptions) => :ListsApiAdapter: -``` - -Note: `:ListsApiAdapter:` is actually an implicit interface (`ReturnType`). +`Lists` is a namespace export with all list-related editor utility functions. +Most of them require an instance of [`ListsEditor`](#ListsEditor) as the first argument. -Here are its methods: +Here are the functions methods: -```tsx +```ts /** * Returns true when editor.deleteBackward() is safe to call (it won't break the structure). */ -canDeleteBackward(editor: Editor) => boolean +canDeleteBackward(editor: ListsEditor) => boolean /** * Decreases nesting depth of all "list-items" in the current selection. * All "list-items" in the root "list" will become "default" nodes. */ -decreaseDepth(editor: Editor) => void +decreaseDepth(editor: ListsEditor) => void /** * Decreases nesting depth of "list-item" at a given Path. */ -decreaseListItemDepth(editor: Editor, listItemPath: Path) => void +decreaseListItemDepth(editor: ListsEditor, listItemPath: Path) => void /** * Returns all "list-items" in a given Range. * @param at defaults to current selection if not specified */ -getListItemsInRange(editor: Editor, at: Range | null | undefined) => NodeEntry[] +getListItemsInRange(editor: ListsEditor, at: Range | null | undefined) => NodeEntry[] /** * Returns all "lists" in a given Range. * @param at defaults to current selection if not specified */ -getListsInRange(editor: Editor, at: Range | null | undefined) => NodeEntry[] +getListsInRange(editor: ListsEditor, at: Range | null | undefined) => NodeEntry[] /** * Returns the "type" of a given list node. @@ -366,94 +452,79 @@ getListType(node: Node) => string * Returns "list" node nested in "list-item" at a given path. * Returns null if there is no nested "list". */ -getNestedList(editor: Editor, listItemPath: Path) => NodeEntry | null +getNestedList(editor: ListsEditor, listItemPath: Path) => NodeEntry | null /** * Returns parent "list" node of "list-item" at a given path. * Returns null if there is no parent "list". */ -getParentList(editor: Editor, listItemPath: Path) => NodeEntry | null +getParentList(editor: ListsEditor, listItemPath: Path) => NodeEntry | null /** * Returns parent "list-item" node of "list-item" at a given path. * Returns null if there is no parent "list-item". */ -getParentListItem(editor: Editor, listItemPath: Path) => NodeEntry | null +getParentListItem(editor: ListsEditor, listItemPath: Path) => NodeEntry | null /** * Increases nesting depth of all "list-items" in the current selection. * All nodes matching options.wrappableTypes in the selection will be converted to "list-items" and wrapped in a "list". */ -increaseDepth(editor: Editor) => void +increaseDepth(editor: ListsEditor) => void /** * Increases nesting depth of "list-item" at a given Path. */ -increaseListItemDepth(editor: Editor, listItemPath: Path) => void +increaseListItemDepth(editor: ListsEditor, listItemPath: Path) => void /** * Returns true when editor has collapsed selection and the cursor is in an empty "list-item". */ -isCursorInEmptyListItem(editor: Editor) => boolean +isCursorInEmptyListItem(editor: ListsEditor) => boolean /** * Returns true when editor has collapsed selection and the cursor is at the beginning of a "list-item". */ -isCursorAtStartOfListItem(editor: Editor) => boolean - -/** - * Checks whether node.type is an Element matching any of options.listTypes. - */ -isList(node: Node) => node is Element - -/** - * Checks whether node.type is an Element matching options.listItemType. - */ -isListItem(node: Node) => node is Element - -/** - * Checks whether node.type is an Element matching options.listItemTextType. - */ -isListItemText(node: Node) => node is Element +isCursorAtStartOfListItem(editor: ListsEditor) => boolean /** * Returns true if given "list-item" node contains a non-empty "list-item-text" node. */ -listItemContainsText(editor: Editor, node: Node) => boolean +listItemContainsText(editor: ListsEditor, node: Node) => boolean /** * Moves all "list-items" from one "list" to the end of another "list". */ -moveListItemsToAnotherList(editor: Editor, parameters: { at: NodeEntry; to: NodeEntry; }) => void +moveListItemsToAnotherList(editor: ListsEditor, parameters: { at: NodeEntry; to: NodeEntry; }) => void /** * Nests (moves) given "list" in a given "list-item". */ -moveListToListItem(editor: Editor, parameters: { at: NodeEntry; to: NodeEntry; }) => void +moveListToListItem(editor: ListsEditor, parameters: { at: NodeEntry; to: NodeEntry; }) => void /** * Sets "type" of all "list" nodes in the current selection. */ -setListType(editor: Editor, listType: string) => void +setListType(editor: ListsEditor, listType: string) => void /** * Collapses the current selection (by removing everything in it) and if the cursor * ends up in a "list-item" node, it will break that "list-item" into 2 nodes, splitting * the text at the cursor location. */ -splitListItem(editor: Editor) => void +splitListItem(editor: ListsEditor) => void /** * Unwraps all "list-items" in the current selection. * No list be left in the current selection. */ -unwrapList(editor: Editor) => void +unwrapList(editor: ListsEditor) => void /** * All nodes matching options.wrappableTypes in the current selection * will be converted to "list-items" and wrapped in "lists". */ -wrapInList(editor: Editor, listType: string) => void +wrapInList(editor: ListsEditor, listType: ListType) => void ``` ---- From d24c26b170121e4e5ea276b9167c1e2087194bee Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 21 Apr 2022 15:20:56 +0300 Subject: [PATCH 20/24] Update package-lock.json --- package-lock.json | 94 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23bbce478..82f49fab5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32391,14 +32391,14 @@ }, "packages/slate-commons": { "name": "@prezly/slate-commons", - "version": "0.30.0", + "version": "0.31.1", "license": "MIT", "dependencies": { - "@prezly/slate-types": "^0.30.0", + "@prezly/slate-types": "^0.31.1", "uuid": "^8.3.0" }, "devDependencies": { - "@prezly/slate-hyperscript": "^0.30.0", + "@prezly/slate-hyperscript": "^0.31.1", "@types/uuid": "^8.3.0" }, "peerDependencies": { @@ -32410,7 +32410,7 @@ }, "packages/slate-editor": { "name": "@prezly/slate-editor", - "version": "0.30.0", + "version": "0.31.1", "license": "MIT", "dependencies": { "@popperjs/core": "^2.6.0", @@ -32419,10 +32419,10 @@ "@prezly/linear-partition": "^1.0.2", "@prezly/progress-promise": "^1.0.1", "@prezly/sdk": "^6.12.1", - "@prezly/slate-commons": "^0.30.0", - "@prezly/slate-hyperscript": "^0.30.0", - "@prezly/slate-lists": "^0.30.0", - "@prezly/slate-types": "^0.30.0", + "@prezly/slate-commons": "^0.31.1", + "@prezly/slate-hyperscript": "^0.31.1", + "@prezly/slate-lists": "^0.31.1", + "@prezly/slate-types": "^0.31.1", "@prezly/uploadcare": "^1.1.2", "@prezly/uploadcare-widget": "^3.16.1", "@udecode/plate-core": "^9.0.0", @@ -32491,7 +32491,7 @@ }, "packages/slate-hyperscript": { "name": "@prezly/slate-hyperscript", - "version": "0.30.0", + "version": "0.31.1", "license": "MIT", "dependencies": { "is-plain-object": "^5.0.0" @@ -32511,7 +32511,7 @@ }, "packages/slate-lists": { "name": "@prezly/slate-lists", - "version": "0.30.0", + "version": "0.31.1", "license": "MIT", "dependencies": { "@prezly/slate-commons": "^0.30.0", @@ -32528,14 +32528,50 @@ "slate-react": "^0.71.0" } }, + "packages/slate-lists/node_modules/@prezly/slate-commons": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@prezly/slate-commons/-/slate-commons-0.30.0.tgz", + "integrity": "sha512-y4SghF34rMOyCgQzPIOdYW4nHRjVRbvCuyHbcSaRYpjgB4s3a6yTbYMXLg1axvJQKYah3LfRcVNeEjterQz3SQ==", + "dependencies": { + "@prezly/slate-types": "^0.30.0", + "uuid": "^8.3.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0", + "slate": "^0.71.0", + "slate-history": "^0.66.0", + "slate-react": "^0.71.0" + } + }, + "packages/slate-lists/node_modules/@prezly/slate-types": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@prezly/slate-types/-/slate-types-0.30.0.tgz", + "integrity": "sha512-CyRABw3j5N9C9OEqIy2sFtgp4RgAgGuzSO8/RqzSwh388ohFo8HD1Iw6IlRbqjK5UhlcOZo9JV6XKeKHXZQx6w==", + "dependencies": { + "@prezly/sdk": "^6.12.1", + "@prezly/uploads": "^0.2.1", + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": "~0.71.0" + } + }, "packages/slate-lists/node_modules/is-hotkey": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" }, + "packages/slate-lists/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "packages/slate-types": { "name": "@prezly/slate-types", - "version": "0.30.0", + "version": "0.31.1", "license": "MIT", "dependencies": { "@prezly/sdk": "^6.12.1", @@ -36311,8 +36347,8 @@ "@prezly/slate-commons": { "version": "file:packages/slate-commons", "requires": { - "@prezly/slate-hyperscript": "^0.30.0", - "@prezly/slate-types": "^0.30.0", + "@prezly/slate-hyperscript": "^0.31.1", + "@prezly/slate-types": "^0.31.1", "@types/uuid": "^8.3.0", "uuid": "^8.3.0" } @@ -36327,10 +36363,10 @@ "@prezly/linear-partition": "^1.0.2", "@prezly/progress-promise": "^1.0.1", "@prezly/sdk": "^6.12.1", - "@prezly/slate-commons": "^0.30.0", - "@prezly/slate-hyperscript": "^0.30.0", - "@prezly/slate-lists": "^0.30.0", - "@prezly/slate-types": "^0.30.0", + "@prezly/slate-commons": "^0.31.1", + "@prezly/slate-hyperscript": "^0.31.1", + "@prezly/slate-lists": "^0.31.1", + "@prezly/slate-types": "^0.31.1", "@prezly/uploadcare": "^1.1.2", "@prezly/uploadcare-widget": "^3.16.1", "@storybook/addon-actions": "^6.4.17", @@ -36413,10 +36449,34 @@ "uuid": "^8.3.0" }, "dependencies": { + "@prezly/slate-commons": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@prezly/slate-commons/-/slate-commons-0.30.0.tgz", + "integrity": "sha512-y4SghF34rMOyCgQzPIOdYW4nHRjVRbvCuyHbcSaRYpjgB4s3a6yTbYMXLg1axvJQKYah3LfRcVNeEjterQz3SQ==", + "requires": { + "@prezly/slate-types": "^0.30.0", + "uuid": "^8.3.0" + } + }, + "@prezly/slate-types": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@prezly/slate-types/-/slate-types-0.30.0.tgz", + "integrity": "sha512-CyRABw3j5N9C9OEqIy2sFtgp4RgAgGuzSO8/RqzSwh388ohFo8HD1Iw6IlRbqjK5UhlcOZo9JV6XKeKHXZQx6w==", + "requires": { + "@prezly/sdk": "^6.12.1", + "@prezly/uploads": "^0.2.1", + "is-plain-object": "^5.0.0" + } + }, "is-hotkey": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" } } }, From 995f609043ebc2093fc47dab564795a9c74a519d Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 21 Apr 2022 15:32:58 +0300 Subject: [PATCH 21/24] A few more updates to README --- packages/slate-lists/README.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/slate-lists/README.md b/packages/slate-lists/README.md index 11937c67d..4dfbb6b9a 100644 --- a/packages/slate-lists/README.md +++ b/packages/slate-lists/README.md @@ -36,18 +36,23 @@ Source code: https://codesandbox.io/s/prezlyslate-lists-demo-complete-example-h9 ## Constraints -- all list-related nodes have a `type: string` attribute (you can customize the supported string values via [`ListsOptions`](src/types.ts)) -- there is an assumption that a _default_ node `type` to which this extension can convert list-related nodes to exists (e.g. during normalization, or unwrapping lists) +- There are two types of lists: ordered and unordered. + You can initialize the plugin to work with any project-level data model via [`ListsSchema`](src/types.ts). + +- There is an assumption that there is a _default_ text node type to which the plugin can convert list-related nodes + (e.g. during normalization, or unwrapping lists). Normally, this is a paragraph block, but it's up to you. ## Schema -- a **list** node can only contain **list item** nodes -- a **list item** node can contain either: - - a **list item text** node - - a **list item text** node and a **list** node (in that order) (nesting lists) -- a **list** node can either: - - have no parent node - - have a parent **list item** node +- a **list** node can only contain **list item** nodes + +- a **list item** node can contain either: + - a **list item text** node + - a pair of **list item text** node and a **list** node (in that order) (nesting lists) + +- a **list** node can either: + - have no parent node + - have a parent **list item** node
            As TypeScript interfaces... @@ -60,17 +65,17 @@ import { Node } from 'slate'; interface ListNode { children: ListItemNode[]; - type: 'bulleted-list' | 'numbered-list'; // see ListsSchema to customize this + type: 'bulleted-list' | 'numbered-list'; // depends on your ListsSchema } interface ListItemNode { children: [ListItemTextNode] | [ListItemTextNode, ListNode]; - type: 'list-item'; // see ListsOptions to customize this + type: 'list-item'; // depends on your ListsSchema } interface ListItemTextNode { children: Node[]; // by default everything is allowed here - type: 'list-item-text'; // see ListsOptions to customize this + type: 'list-item-text'; // depends on your ListsSchema } ``` From bc73aecf384d8a335dea6306f29fce7e13d967ac Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 21 Apr 2022 15:36:15 +0300 Subject: [PATCH 22/24] Fix @prezly/slate-lists dependencies versions --- package-lock.json | 31 +++---------------------------- packages/slate-lists/package.json | 2 +- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82f49fab5..fb071cc12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32514,7 +32514,7 @@ "version": "0.31.1", "license": "MIT", "dependencies": { - "@prezly/slate-commons": "^0.30.0", + "@prezly/slate-commons": "^0.31.0", "is-hotkey": "^0.2.0", "uuid": "^8.3.0" }, @@ -32528,21 +32528,6 @@ "slate-react": "^0.71.0" } }, - "packages/slate-lists/node_modules/@prezly/slate-commons": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@prezly/slate-commons/-/slate-commons-0.30.0.tgz", - "integrity": "sha512-y4SghF34rMOyCgQzPIOdYW4nHRjVRbvCuyHbcSaRYpjgB4s3a6yTbYMXLg1axvJQKYah3LfRcVNeEjterQz3SQ==", - "dependencies": { - "@prezly/slate-types": "^0.30.0", - "uuid": "^8.3.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0", - "slate": "^0.71.0", - "slate-history": "^0.66.0", - "slate-react": "^0.71.0" - } - }, "packages/slate-lists/node_modules/@prezly/slate-types": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/@prezly/slate-types/-/slate-types-0.30.0.tgz", @@ -36442,25 +36427,15 @@ "@prezly/slate-lists": { "version": "file:packages/slate-lists", "requires": { - "@prezly/slate-commons": "^0.30.0", + "@prezly/slate-commons": "^0.31.0", "@types/uuid": "^8.3.0", "is-hotkey": "^0.2.0", "slate-hyperscript": "^0.67.0", "uuid": "^8.3.0" }, "dependencies": { - "@prezly/slate-commons": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@prezly/slate-commons/-/slate-commons-0.30.0.tgz", - "integrity": "sha512-y4SghF34rMOyCgQzPIOdYW4nHRjVRbvCuyHbcSaRYpjgB4s3a6yTbYMXLg1axvJQKYah3LfRcVNeEjterQz3SQ==", - "requires": { - "@prezly/slate-types": "^0.30.0", - "uuid": "^8.3.0" - } - }, "@prezly/slate-types": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@prezly/slate-types/-/slate-types-0.30.0.tgz", + "version": "https://registry.npmjs.org/@prezly/slate-types/-/slate-types-0.30.0.tgz", "integrity": "sha512-CyRABw3j5N9C9OEqIy2sFtgp4RgAgGuzSO8/RqzSwh388ohFo8HD1Iw6IlRbqjK5UhlcOZo9JV6XKeKHXZQx6w==", "requires": { "@prezly/sdk": "^6.12.1", diff --git a/packages/slate-lists/package.json b/packages/slate-lists/package.json index 208ca4d8f..9243cfc9c 100644 --- a/packages/slate-lists/package.json +++ b/packages/slate-lists/package.json @@ -54,7 +54,7 @@ "slate-react": "^0.71.0" }, "dependencies": { - "@prezly/slate-commons": "^0.30.0", + "@prezly/slate-commons": "^0.31.0", "is-hotkey": "^0.2.0", "uuid": "^8.3.0" }, From 19ee54fd19d2e9f49ca655ea0b2212fc3af29630 Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 21 Apr 2022 16:16:47 +0300 Subject: [PATCH 23/24] Tweak ListsSchema to create proper list children structure by default --- .../lib/createParagraph.ts | 9 ++++----- .../withListsFormatting.ts | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts b/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts index 5bc1e3c48..b035ebd89 100644 --- a/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts +++ b/packages/slate-editor/src/modules/editor-v4-paragraphs/lib/createParagraph.ts @@ -1,12 +1,11 @@ import type { ParagraphNode } from '@prezly/slate-types'; import { PARAGRAPH_NODE_TYPE } from '@prezly/slate-types'; -export function createParagraph( - props: { children?: ParagraphNode['children'] } = {}, -): ParagraphNode { - const { children = [{ text: '' }] } = props; +type Props = Partial>; + +export function createParagraph({ children }: Props = {}): ParagraphNode { return { type: PARAGRAPH_NODE_TYPE, - children, + children: children ?? [{ text: '' }], }; } diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts index 7cb88f43f..a79dd6fce 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts @@ -35,18 +35,23 @@ const SCHEMA: ListsSchema = { createDefaultTextNode(props = {}) { return createParagraph(props); }, - createListNode(type: ListType = ListType.UNORDERED, props = {}) { + createListNode(type: ListType = ListType.UNORDERED, { children } = {}) { return { - children: [{ text: '' }], - ...props, type: type === ListType.ORDERED ? NUMBERED_LIST_NODE_TYPE : BULLETED_LIST_NODE_TYPE, + children: children ?? [this.createListItemNode()], }; }, - createListItemNode(props = {}) { - return { children: [{ text: '' }], ...props, type: LIST_ITEM_NODE_TYPE }; + createListItemNode({ children } = {}) { + return { + type: LIST_ITEM_NODE_TYPE, + children: children ?? [this.createListItemTextNode()], + }; }, - createListItemTextNode(props = {}) { - return { children: [{ text: '' }], ...props, type: LIST_ITEM_TEXT_NODE_TYPE }; + createListItemTextNode({ children } = {}) { + return { + type: LIST_ITEM_TEXT_NODE_TYPE, + children: children ?? [{ text: '' }], + }; }, }; From 7e77235ae3fedc5d8e5f5c8661dde9c745c9170d Mon Sep 17 00:00:00 2001 From: Ivan Voskoboinyk Date: Thu, 21 Apr 2022 16:21:44 +0300 Subject: [PATCH 24/24] Rename `isAllowedListDescendant` to `isConvertibleToListTextNode` To better describe what it's needed for --- .../withListsFormatting.ts | 2 +- packages/slate-lists/README.md | 28 +++++++++---------- packages/slate-lists/src/jsx.ts | 2 +- packages/slate-lists/src/lib/wrapInList.ts | 6 ++-- packages/slate-lists/src/types.ts | 2 +- packages/slate-lists/src/withLists.ts | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts index a79dd6fce..63e2a2815 100644 --- a/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts +++ b/packages/slate-editor/src/modules/editor-v4-rich-formatting/withListsFormatting.ts @@ -17,7 +17,7 @@ import type { Editor } from 'slate'; import { createParagraph } from '#modules/editor-v4-paragraphs'; const SCHEMA: ListsSchema = { - isAllowedListDescendant(node): boolean { + isConvertibleToListTextNode(node) { return isParagraphNode(node) || isHeadingNode(node) || isQuoteNode(node); }, isDefaultTextNode: isParagraphNode, diff --git a/packages/slate-lists/README.md b/packages/slate-lists/README.md index 4dfbb6b9a..110c2b651 100644 --- a/packages/slate-lists/README.md +++ b/packages/slate-lists/README.md @@ -137,7 +137,7 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-1-define-opt +import { type ListsSchema, ListType } from '@prezly/slate-lists'; + +const schema: ListsSchema = { -+ isAllowedListDescendant(node) { ++ isConvertibleToListTextNode(node) { + return Element.isElementType(node, PARAGRAPH_TYPE); + }, + isDefaultTextNode(node) { @@ -211,7 +211,7 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-2-use-withli +import { type ListsSchema, ListType, withLists } from '@prezly/slate-lists'; const schema: ListsSchema = { - isAllowedListDescendant(node) { + isConvertibleToListTextNode(node) { return Element.isElementType(node, PARAGRAPH_TYPE); }, isDefaultTextNode(node) { @@ -289,7 +289,7 @@ Live example: https://codesandbox.io/s/prezlyslate-lists-user-guide-3-use-withli +import { type ListsSchema, ListType, withLists, withListsReact } from '@prezly/slate-lists'; const schema: ListsSchema = { - isAllowedListDescendant(node) { + isConvertibleToListTextNode(node) { return Element.isElementType(node, PARAGRAPH_TYPE); }, isDefaultTextNode(node) { @@ -373,17 +373,17 @@ Only core API is documented although all utility functions are exposed. Should y Lists schema wires the Lists plugin to your project-level defined Slate model. It is designed with 100% customization in mind, not depending on any specific node types, or non-core interfaces. -| Name | Description | -|---------------------------|------------------------------------------------------------------------------------------------------------------------| -| `isAllowedListDescendant` | Check if a node can be converted to a list item text node. | -| `isDefaultTextNode` | Check if a node is a plain default text node, that list item text node will become when it is unwrapped or normalized. | -| `isListNode` | Check if a node is representing a list. | -| `isListItemNode` | Check if a node is representing a list item. | -| `isListItemTextNode` | Check if a node is representing a list item text. | -| `createDefaultTextNode` | Create a plain default text node. List item text nodes become these when unwrapped or normalized. | -| `createListNode` | Create a new list node of the given type. | -| `createListItemNode` | Create a new list item node. | -| `createListItemTextNode` | Create a new list item text node. | +| Name | Description | +|-------------------------------|------------------------------------------------------------------------------------------------------------------------| +| `isConvertibleToListTextNode` | Check if a node can be converted to a list item text node. | +| `isDefaultTextNode` | Check if a node is a plain default text node, that list item text node will become when it is unwrapped or normalized. | +| `isListNode` | Check if a node is representing a list. | +| `isListItemNode` | Check if a node is representing a list item. | +| `isListItemTextNode` | Check if a node is representing a list item text. | +| `createDefaultTextNode` | Create a plain default text node. List item text nodes become these when unwrapped or normalized. | +| `createListNode` | Create a new list node of the given type. | +| `createListItemNode` | Create a new list item node. | +| `createListItemTextNode` | Create a new list item text node. | ### [`ListsEditor`](src/types.ts) diff --git a/packages/slate-lists/src/jsx.ts b/packages/slate-lists/src/jsx.ts index 535cc649d..29f2294bc 100644 --- a/packages/slate-lists/src/jsx.ts +++ b/packages/slate-lists/src/jsx.ts @@ -37,7 +37,7 @@ const INLINE_ELEMENTS = [LINK_TYPE]; const VOID_ELEMENTS = [DIVIDER_TYPE]; const SCHEMA: ListsSchema = { - isAllowedListDescendant(node): boolean { + isConvertibleToListTextNode(node) { return Element.isElementType(node, PARAGRAPH_TYPE); }, isDefaultTextNode(node) { diff --git a/packages/slate-lists/src/lib/wrapInList.ts b/packages/slate-lists/src/lib/wrapInList.ts index 15a954200..5dd9d9860 100644 --- a/packages/slate-lists/src/lib/wrapInList.ts +++ b/packages/slate-lists/src/lib/wrapInList.ts @@ -5,10 +5,10 @@ import type { ListsEditor } from '../types'; import type { ListType } from '../types'; /** - * All nodes matching `isAllowedListDescendant()` in the current selection + * All nodes matching `isConvertibleToListTextNode()` in the current selection * will be converted to list items and then wrapped in lists. * - * @see ListsEditor.isAllowedListDescendant() + * @see ListsEditor.isConvertibleToListTextNode() */ export function wrapInList(editor: ListsEditor, listType: ListType): void { if (!editor.selection) { @@ -24,7 +24,7 @@ export function wrapInList(editor: ListsEditor, listType: ListType): void { !editor.isListNode(node) && !editor.isListItemNode(node) && !editor.isListItemTextNode(node) && - editor.isAllowedListDescendant(node) + editor.isConvertibleToListTextNode(node) ); }, }), diff --git a/packages/slate-lists/src/types.ts b/packages/slate-lists/src/types.ts index 89ddb38ec..df1d3a275 100644 --- a/packages/slate-lists/src/types.ts +++ b/packages/slate-lists/src/types.ts @@ -6,7 +6,7 @@ export enum ListType { } export interface ListsSchema { - isAllowedListDescendant(node: Node): boolean; + isConvertibleToListTextNode(node: Node): boolean; isDefaultTextNode(node: Node): boolean; diff --git a/packages/slate-lists/src/withLists.ts b/packages/slate-lists/src/withLists.ts index cdfca8cc9..1261918ff 100644 --- a/packages/slate-lists/src/withLists.ts +++ b/packages/slate-lists/src/withLists.ts @@ -31,7 +31,7 @@ const LIST_NORMALIZERS: Normalizer[] = [ export function withLists(schema: ListsSchema) { return function (editor: T): T & ListsEditor { const listsEditor: T & ListsEditor = Object.assign(editor, { - isAllowedListDescendant: schema.isAllowedListDescendant, + isConvertibleToListTextNode: schema.isConvertibleToListTextNode, isDefaultTextNode: schema.isDefaultTextNode, isListNode: schema.isListNode, isListItemNode: schema.isListItemNode,