mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 16:39:33 +08:00
Add serialize and deserialize methods (#459)
This commit is contained in:

committed by
acywatson

parent
8f3d90075c
commit
4722c79425
@ -13,6 +13,7 @@ import type {
|
||||
NodeKey,
|
||||
EditorThemeClasses,
|
||||
Selection,
|
||||
ParsedTextNode,
|
||||
} from 'outline';
|
||||
|
||||
import {useEffect} from 'react';
|
||||
@ -88,6 +89,11 @@ export default function useEmojis(editor: OutlineEditor): void {
|
||||
}, [editor]);
|
||||
}
|
||||
|
||||
export type ParsedEmojiNode = {
|
||||
...ParsedTextNode,
|
||||
__className: string,
|
||||
};
|
||||
|
||||
class EmojiNode extends TextNode {
|
||||
__className: string;
|
||||
|
||||
@ -96,7 +102,18 @@ class EmojiNode extends TextNode {
|
||||
this.__className = className;
|
||||
this.__type = 'emoji';
|
||||
}
|
||||
|
||||
serialize(): ParsedEmojiNode {
|
||||
const {__className} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__className,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__className, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__className = __className;
|
||||
}
|
||||
clone() {
|
||||
return new EmojiNode(this.__className, this.__text, this.__key);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
import type {OutlineEditor, NodeKey, TextNode} from 'outline';
|
||||
import type {OutlineEditor, ParsedNode, NodeKey, TextNode} from 'outline';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useEffect} from 'react';
|
||||
@ -38,6 +38,11 @@ function Keyword({children}: {children: string}): React.MixedElement {
|
||||
return <span className="keyword">{children}</span>;
|
||||
}
|
||||
|
||||
type ParsedKeywordNode = {
|
||||
...ParsedNode,
|
||||
__keyword: string,
|
||||
};
|
||||
|
||||
class KeywordNode extends DecoratorNode {
|
||||
__keyword: string;
|
||||
|
||||
@ -46,6 +51,18 @@ class KeywordNode extends DecoratorNode {
|
||||
this.__type = 'keyword';
|
||||
this.__keyword = keyword;
|
||||
}
|
||||
serialize(): ParsedKeywordNode {
|
||||
const {__keyword} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__keyword,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__keyword, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__keyword = __keyword;
|
||||
}
|
||||
clone(): KeywordNode {
|
||||
return new KeywordNode(this.__keyword, this.__key);
|
||||
}
|
||||
|
@ -7,7 +7,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {OutlineEditor, NodeKey, EditorThemeClasses} from 'outline';
|
||||
import type {
|
||||
OutlineEditor,
|
||||
ParsedTextNode,
|
||||
NodeKey,
|
||||
EditorThemeClasses,
|
||||
} from 'outline';
|
||||
|
||||
import React, {useCallback, useLayoutEffect, useMemo, useRef} from 'react';
|
||||
import {useEffect, useState} from 'react';
|
||||
@ -500,6 +505,11 @@ export default function useMentions(editor: OutlineEditor): React$Node {
|
||||
);
|
||||
}
|
||||
|
||||
type ParsedMentionNode = {
|
||||
...ParsedTextNode,
|
||||
__mention: string,
|
||||
};
|
||||
|
||||
class MentionNode extends TextNode {
|
||||
__mention: string;
|
||||
|
||||
@ -508,7 +518,18 @@ class MentionNode extends TextNode {
|
||||
this.__mention = mentionName;
|
||||
this.__type = 'mention';
|
||||
}
|
||||
|
||||
serialize(): ParsedMentionNode {
|
||||
const {__mention} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__mention,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__mention, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__mention = __mention;
|
||||
}
|
||||
clone() {
|
||||
return new MentionNode(this.__mention, this.__key, this.__text);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {NodeKey} from './OutlineNode';
|
||||
import type {NodeKey, ParsedNode} from './OutlineNode';
|
||||
|
||||
import {isTextNode, TextNode, isLineBreakNode} from '.';
|
||||
import {
|
||||
@ -74,6 +74,11 @@ function combineAdjacentTextNodes(
|
||||
}
|
||||
}
|
||||
|
||||
export type ParsedBlockNode = {
|
||||
...ParsedNode,
|
||||
__children: Array<NodeKey>,
|
||||
};
|
||||
|
||||
export class BlockNode extends OutlineNode {
|
||||
__children: Array<NodeKey>;
|
||||
|
||||
@ -81,6 +86,18 @@ export class BlockNode extends OutlineNode {
|
||||
super(key);
|
||||
this.__children = [];
|
||||
}
|
||||
serialize(): ParsedBlockNode {
|
||||
const {__children} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__children,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__children, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__children = __children;
|
||||
}
|
||||
getChildren(): Array<OutlineNode> {
|
||||
const self = this.getLatest();
|
||||
const children = self.__children;
|
||||
|
@ -46,7 +46,7 @@ export type ParsedNode = {
|
||||
__key: string,
|
||||
__type: string,
|
||||
__flags: number,
|
||||
__children: Array<NodeKey>,
|
||||
__parent: null | NodeKey,
|
||||
...
|
||||
};
|
||||
export type ParsedNodeMap = Map<NodeKey, ParsedNode>;
|
||||
@ -258,7 +258,29 @@ export class OutlineNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serialize(): ParsedNode {
|
||||
const {__type, __flags, __key, __parent} = this;
|
||||
return {
|
||||
__type,
|
||||
__flags,
|
||||
__key,
|
||||
__parent,
|
||||
};
|
||||
}
|
||||
deserialize({__type, __flags, __key, __parent, ...rest}: ParsedNode) {
|
||||
Object.assign(this, {
|
||||
__type,
|
||||
__flags,
|
||||
__key,
|
||||
__parent,
|
||||
});
|
||||
if (__DEV__) {
|
||||
const keys = Object.keys(rest);
|
||||
if (keys.length > 0) {
|
||||
invariant(false, 'Extraneous keys in serialized node data: %s', keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getters and Traversers
|
||||
|
||||
getType(): string {
|
||||
@ -753,7 +775,7 @@ function getNodeByKeyOrThrow<N: OutlineNode>(key: NodeKey): N {
|
||||
}
|
||||
|
||||
export function createNodeFromParse(
|
||||
parsedNode: ParsedNode,
|
||||
parsedNode: $FlowFixMe,
|
||||
parsedNodeMap: ParsedNodeMap,
|
||||
editor: OutlineEditor,
|
||||
parentKey: null | NodeKey,
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {Selection} from './OutlineSelection';
|
||||
import type {NodeKey} from './OutlineNode';
|
||||
import type {NodeKey, ParsedNode} from './OutlineNode';
|
||||
import type {EditorThemeClasses} from './OutlineEditor';
|
||||
|
||||
import {OutlineNode} from './OutlineNode';
|
||||
@ -191,6 +191,11 @@ function createTextInnerDOM(
|
||||
}
|
||||
}
|
||||
|
||||
export type ParsedTextNode = {
|
||||
...ParsedNode,
|
||||
__text: string,
|
||||
};
|
||||
|
||||
export class TextNode extends OutlineNode {
|
||||
__text: string;
|
||||
|
||||
@ -199,7 +204,18 @@ export class TextNode extends OutlineNode {
|
||||
this.__text = text;
|
||||
this.__type = 'text';
|
||||
}
|
||||
|
||||
serialize(): ParsedTextNode {
|
||||
const {__text} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__text,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__text, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__text = __text;
|
||||
}
|
||||
clone(): TextNode {
|
||||
return new TextNode(this.__text, this.__key);
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ export type {
|
||||
ParsedNodeMap,
|
||||
OutlineNode,
|
||||
} from './OutlineNode';
|
||||
export type {ParsedBlockNode} from './OutlineBlockNode';
|
||||
export type {ParsedTextNode} from './OutlineTextNode';
|
||||
export type {Selection} from './OutlineSelection';
|
||||
export type {TextFormatType} from './OutlineTextNode';
|
||||
|
||||
|
@ -7,7 +7,12 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {OutlineNode, NodeKey, EditorThemeClasses} from 'outline';
|
||||
import type {
|
||||
OutlineNode,
|
||||
NodeKey,
|
||||
ParsedBlockNode,
|
||||
EditorThemeClasses,
|
||||
} from 'outline';
|
||||
import type {ParagraphNode} from 'outline/ParagraphNode';
|
||||
|
||||
import {BlockNode} from 'outline';
|
||||
@ -15,6 +20,11 @@ import {createParagraphNode} from 'outline/ParagraphNode';
|
||||
|
||||
type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
|
||||
|
||||
export type ParsedHeadingNode = {
|
||||
...ParsedBlockNode,
|
||||
__tag: HeadingTagType,
|
||||
};
|
||||
|
||||
export class HeadingNode extends BlockNode {
|
||||
__tag: HeadingTagType;
|
||||
|
||||
@ -23,13 +33,17 @@ export class HeadingNode extends BlockNode {
|
||||
this.__tag = tag;
|
||||
this.__type = 'heading';
|
||||
}
|
||||
static parse(
|
||||
// $FlowFixMe: TODO: refine
|
||||
data: Object,
|
||||
): HeadingNode {
|
||||
const header = new HeadingNode(data._tag);
|
||||
header.flags = data.flags;
|
||||
return header;
|
||||
serialize(): ParsedHeadingNode {
|
||||
const {__tag} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__tag,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__tag, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__tag = __tag;
|
||||
}
|
||||
clone(): HeadingNode {
|
||||
return new HeadingNode(this.__tag, this.__key);
|
||||
|
@ -12,6 +12,7 @@ import type {
|
||||
NodeKey,
|
||||
OutlineNode,
|
||||
OutlineEditor,
|
||||
ParsedNode,
|
||||
} from 'outline';
|
||||
|
||||
import {DecoratorNode} from 'outline';
|
||||
@ -61,6 +62,12 @@ function Image({
|
||||
);
|
||||
}
|
||||
|
||||
export type ParsedImageNode = {
|
||||
...ParsedNode,
|
||||
__src: string,
|
||||
__altText: string,
|
||||
};
|
||||
|
||||
export class ImageNode extends DecoratorNode {
|
||||
__src: string;
|
||||
__altText: string;
|
||||
@ -71,6 +78,20 @@ export class ImageNode extends DecoratorNode {
|
||||
this.__src = src;
|
||||
this.__altText = altText;
|
||||
}
|
||||
serialize(): ParsedImageNode {
|
||||
const {__src, __altText} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__src,
|
||||
__altText,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__src, __altText, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__src = __src;
|
||||
this.__altText = __altText;
|
||||
}
|
||||
getTextContent(): string {
|
||||
return this.__altText;
|
||||
}
|
||||
|
@ -7,10 +7,15 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {NodeKey, EditorThemeClasses} from 'outline';
|
||||
import type {NodeKey, ParsedTextNode, EditorThemeClasses} from 'outline';
|
||||
|
||||
import {TextNode} from 'outline';
|
||||
|
||||
export type ParsedLinkNode = {
|
||||
...ParsedTextNode,
|
||||
__text: string,
|
||||
};
|
||||
|
||||
export class LinkNode extends TextNode {
|
||||
__url: string;
|
||||
|
||||
@ -19,6 +24,18 @@ export class LinkNode extends TextNode {
|
||||
this.__url = url;
|
||||
this.__type = 'link';
|
||||
}
|
||||
serialize(): ParsedLinkNode {
|
||||
const {__url} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__url,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__url, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__url = __url;
|
||||
}
|
||||
clone(): LinkNode {
|
||||
return new LinkNode(this.__text, this.__url, this.__key);
|
||||
}
|
||||
|
@ -7,12 +7,22 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {OutlineNode, NodeKey, EditorThemeClasses} from 'outline';
|
||||
import type {
|
||||
OutlineNode,
|
||||
NodeKey,
|
||||
ParsedBlockNode,
|
||||
EditorThemeClasses,
|
||||
} from 'outline';
|
||||
|
||||
import {BlockNode} from 'outline';
|
||||
|
||||
type ListNodeTagType = 'ul' | 'ol';
|
||||
|
||||
export type ParsedListNode = {
|
||||
...ParsedBlockNode,
|
||||
__tag: ListNodeTagType,
|
||||
};
|
||||
|
||||
export class ListNode extends BlockNode {
|
||||
__tag: ListNodeTagType;
|
||||
|
||||
@ -21,7 +31,18 @@ export class ListNode extends BlockNode {
|
||||
this.__tag = tag;
|
||||
this.__type = 'list';
|
||||
}
|
||||
|
||||
serialize(): ParsedListNode {
|
||||
const {__tag} = this;
|
||||
return {
|
||||
...super.serialize(),
|
||||
__tag,
|
||||
};
|
||||
}
|
||||
deserialize(data: $FlowFixMe) {
|
||||
const {__tag, ...rest} = data;
|
||||
super.deserialize(rest);
|
||||
this.__tag = __tag;
|
||||
}
|
||||
clone(): ListNode {
|
||||
return new ListNode(this.__tag, this.__key);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import type {
|
||||
Selection,
|
||||
TextFormatType,
|
||||
TextNode,
|
||||
ParsedNode,
|
||||
} from 'outline';
|
||||
|
||||
import {
|
||||
@ -42,7 +43,7 @@ function cloneWithProperties<T: OutlineNode>(node: T): T {
|
||||
|
||||
export function getNodesInRange(selection: Selection): {
|
||||
range: Array<NodeKey>,
|
||||
nodeMap: Array<[NodeKey, OutlineNode]>,
|
||||
nodeMap: Array<[NodeKey, ParsedNode]>,
|
||||
} {
|
||||
const anchorNode = selection.getAnchorNode();
|
||||
const focusNode = selection.getFocusNode();
|
||||
@ -61,7 +62,7 @@ export function getNodesInRange(selection: Selection): {
|
||||
endOffset = isBefore ? focusOffset : anchorOffset;
|
||||
firstNode.__text = firstNode.__text.slice(startOffset, endOffset);
|
||||
const key = firstNode.getKey();
|
||||
return {range: [key], nodeMap: [[key, firstNode]]};
|
||||
return {range: [key], nodeMap: [[key, firstNode.serialize()]]};
|
||||
}
|
||||
const nodes = selection.getNodes();
|
||||
const firstNode = nodes[0];
|
||||
@ -97,7 +98,7 @@ export function getNodesInRange(selection: Selection): {
|
||||
}
|
||||
|
||||
if (!nodeMap.has(nodeKey)) {
|
||||
nodeMap.set(nodeKey, node);
|
||||
nodeMap.set(nodeKey, node.serialize());
|
||||
}
|
||||
|
||||
if (parent === sourceParent && parent !== null) {
|
||||
@ -131,7 +132,7 @@ export function getNodesInRange(selection: Selection): {
|
||||
includeTopLevelBlock = true;
|
||||
}
|
||||
if (!nodeMap.has(currKey)) {
|
||||
nodeMap.set(currKey, node);
|
||||
nodeMap.set(currKey, node.serialize());
|
||||
}
|
||||
|
||||
const nextParent = node.getParent();
|
||||
|
@ -42,5 +42,6 @@
|
||||
"40": "reconcileNodeChildren: keyToMove to was not nextStartKey",
|
||||
"41": "storeDOMWithNodeKey: key was null",
|
||||
"42": "Reconciliation: could not find DOM element for node key \"${key}\"",
|
||||
"43": "resolveNonLineBreakOrInertNode: resolved node not a text node"
|
||||
"43": "resolveNonLineBreakOrInertNode: resolved node not a text node",
|
||||
"44": "Extraneous keys in serialized node data: %s"
|
||||
}
|
||||
|
Reference in New Issue
Block a user