Skip to content

Commit

Permalink
⏸ [0.7] Switch the internal architecture to utilize doubly linked lis…
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored Dec 7, 2022
1 parent b1c378c commit d30c882
Show file tree
Hide file tree
Showing 20 changed files with 599 additions and 682 deletions.
9 changes: 6 additions & 3 deletions packages/lexical-devtools/src/components/TreeView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ function TreeView({
const node = map[nodeKey];
const children: Array<DevToolsNode> = [];

if (Object.prototype.hasOwnProperty.call(node, '__children')) {
node.__children.forEach((childKey: string) => {
if (Object.prototype.hasOwnProperty.call(node, '__first')) {
let childKey = node.__first;
while (childKey !== null) {
const child = map[nodeKey];
children.push(depthFirstSearch(map, childKey, depth + 1));
});
childKey = child.__next;
}
}

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-list/src/LexicalListItemNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class ListItemNode extends ElementNode {
const list = this.getParentOrThrow();

if ($isListNode(list)) {
const childrenKeys = list.__children;
const childrenKeys = list.getChildrenKeys();
const childrenLength = childrenKeys.length;
const index = childrenKeys.indexOf(this.__key);

Expand Down
23 changes: 21 additions & 2 deletions packages/lexical-offset/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import type {
EditorState,
ElementNode,
LexicalEditor,
NodeKey,
NodeMap,
Expand Down Expand Up @@ -437,7 +438,7 @@ function $createOffsetNode(
const start = state.offset;

if ($isElementNode(node)) {
const childKeys = node.__children;
const childKeys = node.getChildrenKeys();
const blockIsEmpty = childKeys.length === 0;
const child = blockIsEmpty
? null
Expand Down Expand Up @@ -537,6 +538,24 @@ function $createOffsetChild(
return firstNode;
}

export function createChildrenArray(
element: ElementNode,
nodeMap: null | NodeMap,
): Array<NodeKey> {
const children = [];
let nodeKey = element.__first;
while (nodeKey !== null) {
const node =
nodeMap === null ? $getNodeByKey(nodeKey) : nodeMap.get(nodeKey);
if (node === null || node === undefined) {
invariant(false, 'createChildrenArray: node does not exist in nodeMap');
}
children.push(nodeKey);
nodeKey = node.__next;
}
return children;
}

export function $createOffsetView(
editor: LexicalEditor,
blockOffsetSize = 1,
Expand All @@ -556,7 +575,7 @@ export function $createOffsetView(

const node = $createOffsetChild(
state,
root.__children,
createChildrenArray(root, nodeMap),
null,
nodeMap,
offsetMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -836,15 +836,22 @@ test.describe('TextFormatting', () => {
await moveRight(page, 8);
await page.keyboard.down('Shift');

await page.keyboard.type('c');
await assertSelection(page, {
anchorOffset: 2,
anchorPath: [0, 0, 0],
focusOffset: 3,
focusPath: [0, 6, 0],
});

await page.keyboard.type('z');

await assertHTML(
page,
html`
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">12c</span>
<span data-lexical-text="true">12z</span>
</p>
`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {mergeRegister} from '@lexical/utils';
import {
$getNearestNodeFromDOMNode,
$getNodeByKey,
$getRoot,
COMMAND_PRIORITY_HIGH,
COMMAND_PRIORITY_LOW,
DRAGOVER_COMMAND,
Expand Down Expand Up @@ -51,8 +52,7 @@ function getCurrentIndex(keysLength: number): number {
}

function getTopLevelNodeKeys(editor: LexicalEditor): string[] {
const root = editor.getEditorState()._nodeMap.get('root');
return root ? root.__children : [];
return editor.getEditorState().read(() => $getRoot().getChildrenKeys());
}

function getBlockElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ jest.mock('shared/environment', () => {
return {...originalModule, IS_FIREFOX: true};
});

Range.prototype.getBoundingClientRect = function (): DOMRect {
const rect = {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
x: 0,
y: 0,
};
return {
...rect,
toJSON() {
return rect;
},
};
};

initializeClipboard();

Range.prototype.getBoundingClientRect = function (): DOMRect {
Expand Down
156 changes: 0 additions & 156 deletions packages/lexical-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import {
$isRootOrShadowRoot,
$isTextNode,
$setSelection,
createEditor,
DEPRECATED_$isGridSelection,
EditorState,
ElementNode,
Klass,
LexicalEditor,
LexicalNode,
NodeKey,
} from 'lexical';
import invariant from 'shared/invariant';

Expand Down Expand Up @@ -306,160 +304,6 @@ export function registerNestedElementResolver<N extends ElementNode>(
return editor.registerNodeTransform(targetNode, elementNodeTransform);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ParseObject = any;

function unstable_internalCreateNodeFromParse(
parsedNode: ParseObject,
parsedNodeMap: Map<string, ParseObject>,
editor: LexicalEditor,
parentKey: null | NodeKey,
activeEditorState: EditorState,
): LexicalNode {
const nodeType = parsedNode.__type;
const registeredNode = editor._nodes.get(nodeType);

if (registeredNode === undefined) {
invariant(false, 'createNodeFromParse: type "%s" + not found', nodeType);
}

// Check for properties that are editors
for (const property in parsedNode) {
const value = parsedNode[property];

if (value != null && typeof value === 'object') {
const parsedEditorState = value.editorState;

if (parsedEditorState != null) {
const nestedEditor = createEditor({
namespace: parsedEditorState.namespace,
});
nestedEditor._nodes = editor._nodes;
nestedEditor._parentEditor = editor._parentEditor;
nestedEditor._pendingEditorState =
unstable_convertLegacyJSONEditorState(
nestedEditor,
parsedEditorState,
);
parsedNode[property] = nestedEditor;
}
}
}

const NodeKlass = registeredNode.klass;
const parsedKey = parsedNode.__key;
// We set the parsedKey to undefined before calling clone() so that
// we get a new random key assigned.
parsedNode.__key = undefined;
const node = NodeKlass.clone(parsedNode);
parsedNode.__key = parsedKey;
const key = node.__key;
activeEditorState._nodeMap.set(key, node);

node.__parent = parentKey;

// We will need to recursively handle the children in the case
// of a ElementNode.
if ($isElementNode(node)) {
const children = parsedNode.__children;

let prevNode = null;
for (let i = 0; i < children.length; i++) {
const childKey = children[i];
if (i === 0) {
node.__first = childKey;
} else if (i === children.length - 1) {
node.__last = childKey;
}
const parsedChild = parsedNodeMap.get(childKey);

if (parsedChild !== undefined) {
const child = unstable_internalCreateNodeFromParse(
parsedChild,
parsedNodeMap,
editor,
key,
activeEditorState,
);
const newChildKey = child.__key;
if (prevNode !== null) {
child.__prev = prevNode.__key;
prevNode.__next = newChildKey;
}
node.__children.push(newChildKey);
prevNode = child;
}
}

node.__indent = parsedNode.__indent;
node.__format = parsedNode.__format;
node.__dir = parsedNode.__dir;
} else if ($isTextNode(node)) {
node.__format = parsedNode.__format;
node.__style = parsedNode.__style;
node.__mode = parsedNode.__mode;
node.__detail = parsedNode.__detail;
}
return node;
}

function unstable_parseEditorState(
parsedEditorState: ParseObject,
editor: LexicalEditor,
): EditorState {
// This is hacky, do not do this!
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const EditorStateClass: any = editor._editorState.constructor;
const nodeMap = new Map();
const editorState = new EditorStateClass(nodeMap);
const parsedNodeMap: Map<string, ParseObject> = new Map(
parsedEditorState._nodeMap,
);
// root always exists in Map
const parsedRoot = parsedNodeMap.get('root');
const isUpdating = editor._updating;
try {
editor._updating = false;
editor.update(() => {
const dirtyElements = editor._dirtyElements;
const dirtyLeaves = editor._dirtyLeaves;
const dirtyType = editor._dirtyType;
editor._dirtyElements = new Map();
editor._dirtyLeaves = new Set();
editor._dirtyType = 0;
try {
unstable_internalCreateNodeFromParse(
parsedRoot,
parsedNodeMap,
editor,
null,
editorState,
);
} finally {
editor._dirtyElements = dirtyElements;
editor._dirtyLeaves = dirtyLeaves;
editor._dirtyType = dirtyType;
}
});
} finally {
editor._updating = isUpdating;
}
editorState._readOnly = true;
return editorState;
}

// TODO: remove this function in version 0.4
export function unstable_convertLegacyJSONEditorState(
editor: LexicalEditor,
maybeStringifiedEditorState: string,
): EditorState {
const parsedEditorState =
typeof maybeStringifiedEditorState === 'string'
? JSON.parse(maybeStringifiedEditorState)
: maybeStringifiedEditorState;
return unstable_parseEditorState(parsedEditorState, editor);
}

export function $restoreEditorState(
editor: LexicalEditor,
editorState: EditorState,
Expand Down
Loading

0 comments on commit d30c882

Please sign in to comment.