Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Generic ImportJSON/ExportJSON #3931

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ffd4532
core change
GermanJablo Feb 17, 2023
ac2aa99
remove unnecessary code
GermanJablo Feb 17, 2023
23725d9
core change for exportJSON
GermanJablo Feb 17, 2023
c975cc6
refactor core changes of exportJSON()
GermanJablo Feb 17, 2023
a242121
remove unnecessary code
GermanJablo Feb 17, 2023
247e823
autogenerated serialized node types
GermanJablo Feb 18, 2023
d5aba2a
save changes
GermanJablo Feb 18, 2023
cd92f74
fix types I'm pretty sure of
GermanJablo Feb 19, 2023
a9a4e72
fix types I'm pretty sure of 2
GermanJablo Feb 19, 2023
c9b0112
fix types I'm not 100% sure about
GermanJablo Feb 19, 2023
8e2cf49
Merge branch 'LexicalNodeType' into ImportJSON
GermanJablo Feb 19, 2023
b048b0b
shadowRoot type problem
GermanJablo Feb 19, 2023
ad61492
fix types errors
GermanJablo Feb 19, 2023
9a06725
rare problem I have to revisit
GermanJablo Feb 19, 2023
3707352
fix ts errors en tests
GermanJablo Feb 19, 2023
6e4e588
fix ts
GermanJablo Feb 19, 2023
923718c
solving/answering comments
GermanJablo Feb 20, 2023
64ac286
Export and import can now be overridden
GermanJablo Feb 22, 2023
68a17af
docs update
GermanJablo Feb 22, 2023
b161a92
fix node replacements
GermanJablo Feb 23, 2023
57be7a3
unit tests check parsed editorstates, not stringified
GermanJablo Feb 24, 2023
469d410
Remove serialized types
GermanJablo Feb 24, 2023
23050bd
Merge remote-tracking branch 'upstream/main' into pr/EgonBolton/3931
GermanJablo Feb 24, 2023
69bef9a
fix stickynote exportJSON
GermanJablo Feb 25, 2023
e371e24
fix stickynote exportJSON2
GermanJablo Feb 25, 2023
48f9af4
reduce any types
GermanJablo Feb 25, 2023
205f640
versioning docs
GermanJablo Feb 25, 2023
aba5306
fix types
GermanJablo Feb 25, 2023
52a2462
integrity error
GermanJablo Feb 25, 2023
7788bf2
fix type error?
GermanJablo Feb 25, 2023
68336bd
improve docs
GermanJablo Feb 26, 2023
afc48b5
fix e2e error with mentionNode
GermanJablo Feb 26, 2023
cec3086
restore deleted test
GermanJablo Feb 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 6 additions & 43 deletions packages/lexical-clipboard/src/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
NodeSelection,
RangeSelection,
SELECTION_CHANGE_COMMAND,
SerializedTextNode,
SerializedNode,
} from 'lexical';
import invariant from 'shared/invariant';

Expand Down Expand Up @@ -341,46 +341,11 @@ function $mergeGridNodesStrategy(
}
}

export interface BaseSerializedNode {
children?: Array<BaseSerializedNode>;
type: string;
version: number;
}

function exportNodeToJSON<T extends LexicalNode>(node: T): BaseSerializedNode {
const serializedNode = node.exportJSON();
const nodeClass = node.constructor;

// @ts-expect-error TODO Replace Class utility type with InstanceType
if (serializedNode.type !== nodeClass.getType()) {
invariant(
false,
'LexicalNode: Node %s does not implement .exportJSON().',
nodeClass.name,
);
}

// @ts-expect-error TODO Replace Class utility type with InstanceType
const serializedChildren = serializedNode.children;

if ($isElementNode(node)) {
if (!Array.isArray(serializedChildren)) {
invariant(
false,
'LexicalNode: Node %s is an element but .exportJSON() does not have a children array.',
nodeClass.name,
);
}
}

return serializedNode;
}

function $appendNodesToJSON(
editor: LexicalEditor,
selection: RangeSelection | NodeSelection | GridSelection | null,
currentNode: LexicalNode,
targetArray: Array<BaseSerializedNode> = [],
targetArray: Array<SerializedNode> = [],
): boolean {
let shouldInclude =
selection != null ? currentNode.isSelected(selection) : true;
Expand All @@ -398,7 +363,7 @@ function $appendNodesToJSON(
}
const children = $isElementNode(target) ? target.getChildren() : [];

const serializedNode = exportNodeToJSON(target);
const serializedNode = target.exportJSON();

// TODO: TextNode calls getTextContent() (NOT node.__text) within it's exportJSON method
// which uses getLatest() to get the text from the original node with the same key.
Expand All @@ -412,7 +377,7 @@ function $appendNodesToJSON(
// TextNodes, such as code tokens, we will get a 'blank' TextNode here, i.e., one
// with text of length 0. We don't want this, it makes a confusing mess. Reset!
if (text.length > 0) {
(serializedNode as SerializedTextNode).text = text;
serializedNode.text = text;
} else {
shouldInclude = false;
}
Expand Down Expand Up @@ -450,9 +415,7 @@ function $appendNodesToJSON(
}

// TODO why $ function with Editor instance?
export function $generateJSONFromSelectedNodes<
SerializedNode extends BaseSerializedNode,
>(
export function $generateJSONFromSelectedNodes(
editor: LexicalEditor,
selection: RangeSelection | NodeSelection | GridSelection | null,
): {
Expand All @@ -473,7 +436,7 @@ export function $generateJSONFromSelectedNodes<
}

export function $generateNodesFromSerializedNodes(
serializedNodes: Array<BaseSerializedNode>,
serializedNodes: Array<SerializedNode>,
): Array<LexicalNode> {
const nodes = [];
for (let i = 0; i < serializedNodes.length; i++) {
Expand Down
37 changes: 2 additions & 35 deletions packages/lexical-code/src/CodeHighlightNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {
type EditorThemeClasses,
type LexicalNode,
type NodeKey,
type SerializedTextNode,
type Spread,
TextNode,
ElementNode,
} from 'lexical';
Expand Down Expand Up @@ -45,15 +43,6 @@ import {$createCodeNode} from './CodeNode';

export const DEFAULT_CODE_LANGUAGE = 'javascript';

type SerializedCodeHighlightNode = Spread<
{
highlightType: string | null | undefined;
type: 'code-highlight';
version: 1;
},
SerializedTextNode
>;

export const CODE_LANGUAGE_FRIENDLY_NAME_MAP: Record<string, string> = {
c: 'C',
clike: 'C-like',
Expand Down Expand Up @@ -116,6 +105,7 @@ export class CodeHighlightNode extends TextNode {
) {
super(text, key);
this.__highlightType = highlightType;
return $applyNodeReplacement(this);
}

static getType(): string {
Expand Down Expand Up @@ -170,29 +160,6 @@ export class CodeHighlightNode extends TextNode {
return update;
}

static importJSON(
serializedNode: SerializedCodeHighlightNode,
): CodeHighlightNode {
const node = $createCodeHighlightNode(
serializedNode.text,
serializedNode.highlightType,
);
node.setFormat(serializedNode.format);
node.setDetail(serializedNode.detail);
node.setMode(serializedNode.mode);
node.setStyle(serializedNode.style);
return node;
}

exportJSON(): SerializedCodeHighlightNode {
return {
...super.exportJSON(),
highlightType: this.getHighlightType(),
type: 'code-highlight',
version: 1,
};
}

// Prevent formatting (bold, underline, etc)
setFormat(format: number): this {
return this;
Expand Down Expand Up @@ -223,7 +190,7 @@ export function $createCodeHighlightNode(
text: string,
highlightType?: string | null | undefined,
): CodeHighlightNode {
return $applyNodeReplacement(new CodeHighlightNode(text, highlightType));
return new CodeHighlightNode(text, highlightType);
}

export function $isCodeHighlightNode(
Expand Down
31 changes: 2 additions & 29 deletions packages/lexical-code/src/CodeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import type {
NodeKey,
ParagraphNode,
RangeSelection,
SerializedElementNode,
Spread,
} from 'lexical';
import type {CodeHighlightNode} from '@lexical/code';

Expand Down Expand Up @@ -50,15 +48,6 @@ import {
} from './CodeHighlightNode';
import * as Prism from 'prismjs';

export type SerializedCodeNode = Spread<
{
language: string | null | undefined;
type: 'code';
version: 1;
},
SerializedElementNode
>;

const mapToPrismLanguage = (
language: string | null | undefined,
): string | null | undefined => {
Expand Down Expand Up @@ -96,6 +85,7 @@ export class CodeNode extends ElementNode {
constructor(language?: string | null | undefined, key?: NodeKey) {
super(key);
this.__language = mapToPrismLanguage(language);
return $applyNodeReplacement(this);
}

// View
Expand Down Expand Up @@ -200,23 +190,6 @@ export class CodeNode extends ElementNode {
};
}

static importJSON(serializedNode: SerializedCodeNode): CodeNode {
const node = $createCodeNode(serializedNode.language);
node.setFormat(serializedNode.format);
node.setIndent(serializedNode.indent);
node.setDirection(serializedNode.direction);
return node;
}

exportJSON(): SerializedCodeNode {
return {
...super.exportJSON(),
language: this.getLanguage(),
type: 'code',
version: 1,
};
}

// Mutation
insertNewAfter(
selection: RangeSelection,
Expand Down Expand Up @@ -300,7 +273,7 @@ export class CodeNode extends ElementNode {
export function $createCodeNode(
language?: string | null | undefined,
): CodeNode {
return $applyNodeReplacement(new CodeNode(language));
return new CodeNode(language);
}

export function $isCodeNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ describe('LexicalCodeNode tests', () => {
const node = $createCodeNode('javascript');
// If you broke this test, you changed the public interface of a
// serialized Lexical Core Node. Please ensure the correct adapter
// logic is in place in the corresponding importJSON method
// to accomodate these changes.
// logic is in place to accomodate these changes.
expect(node.exportJSON()).toStrictEqual({
children: [],
direction: null,
Expand Down
1 change: 0 additions & 1 deletion packages/lexical-code/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ export {
getLastCodeHighlightNodeOfLine,
normalizeCodeLang,
} from './CodeHighlightNode';
export type {SerializedCodeNode} from './CodeNode';
export {$createCodeNode, $isCodeNode, CodeNode} from './CodeNode';
9 changes: 1 addition & 8 deletions packages/lexical-hashtag/flow/LexicalHashtag.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,18 @@
* @flow strict
*/

import type {
EditorConfig,
LexicalNode,
NodeKey,
SerializedTextNode,
} from 'lexical';
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';

import {TextNode} from 'lexical';

declare export class HashtagNode extends TextNode {
static getType(): string;
// $FlowFixMe
static clone(node: HashtagNode): HashtagNode;
static importJSON(serializedNode: SerializedTextNode): HashtagNode;
constructor(text: string, key?: NodeKey): void;
createDOM(config: EditorConfig): HTMLElement;
canInsertTextBefore(): boolean;
isTextEntity(): true;
exportJSON(): SerializedTextNode;
}
declare export function $createHashtagNode(text?: string): HashtagNode;
declare export function $isHashtagNode(
Expand Down
26 changes: 3 additions & 23 deletions packages/lexical-hashtag/src/LexicalHashtagNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@
*
*/

import type {
EditorConfig,
LexicalNode,
NodeKey,
SerializedTextNode,
} from 'lexical';
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';

import {addClassNamesToElement} from '@lexical/utils';
import {$applyNodeReplacement, TextNode} from 'lexical';
Expand All @@ -28,6 +23,7 @@ export class HashtagNode extends TextNode {

constructor(text: string, key?: NodeKey) {
super(text, key);
return $applyNodeReplacement(this);
}

createDOM(config: EditorConfig): HTMLElement {
Expand All @@ -36,22 +32,6 @@ export class HashtagNode extends TextNode {
return element;
}

static importJSON(serializedNode: SerializedTextNode): HashtagNode {
const node = $createHashtagNode(serializedNode.text);
node.setFormat(serializedNode.format);
node.setDetail(serializedNode.detail);
node.setMode(serializedNode.mode);
node.setStyle(serializedNode.style);
return node;
}

exportJSON(): SerializedTextNode {
return {
...super.exportJSON(),
type: 'hashtag',
};
}

canInsertTextBefore(): boolean {
return false;
}
Expand All @@ -62,7 +42,7 @@ export class HashtagNode extends TextNode {
}

export function $createHashtagNode(text = ''): HashtagNode {
return $applyNodeReplacement(new HashtagNode(text));
return new HashtagNode(text);
}

export function $isHashtagNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ describe('LexicalHashtagNode tests', () => {
const node = $createHashtagNode('therickestrickofall');
// If you broke this test, you changed the public interface of a
// serialized Lexical Core Node. Please ensure the correct adapter
// logic is in place in the corresponding importJSON method
// to accomodate these changes.
// logic is in place to accomodate these changes.
expect(node.exportJSON()).toStrictEqual({
detail: 0,
format: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import {
COMMAND_PRIORITY_CRITICAL,
LexicalEditor,
REDO_COMMAND,
SerializedElementNode,
SerializedTextNode,
UNDO_COMMAND,
} from 'lexical/src';
import {TestComposer} from 'lexical/src/__tests__/utils';
Expand Down Expand Up @@ -154,21 +152,15 @@ describe('LexicalHistory tests', () => {
expect(afterQuoteInsertionJSONState.root.children.length).toBe(2);
expect(afterQuoteInsertionJSONState.root.children[0].type).toBe('quote');

expect(
(afterQuoteInsertionJSONState.root.children as SerializedElementNode[])[0]
.children.length,
).toBe(1);
expect(
(afterQuoteInsertionJSONState.root.children as SerializedElementNode[])[0]
.children[0].type,
).toBe('text');
expect(
(
(
afterQuoteInsertionJSONState.root.children as SerializedElementNode[]
)[0].children[0] as SerializedTextNode
).text,
).toBe('AAA');
expect(afterQuoteInsertionJSONState.root.children[0].children.length).toBe(
1,
);
expect(afterQuoteInsertionJSONState.root.children[0].children[0].type).toBe(
'text',
);
expect(afterQuoteInsertionJSONState.root.children[0].children[0].text).toBe(
'AAA',
);

await ReactTestUtils.act(async () => {
await editor.update(() => {
Expand Down
Loading