Add serialize and deserialize methods (#459)

This commit is contained in:
Denys Mikhalenko
2021-07-12 01:43:40 -07:00
committed by acywatson
parent 8f3d90075c
commit 4722c79425
13 changed files with 213 additions and 26 deletions

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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,

View File

@ -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);
}

View File

@ -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';

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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"
}