mirror of
https://github.com/facebook/lexical.git
synced 2025-05-21 00:57:23 +08:00
Add unstable serialization logic for node JSON parsing (#2157)
* Add unstable serialization logic for node JSON parsing * Ooops * Freeze editorState * Migrate code node * Address feedback * Address feedback * Address feedback * Address more feedback * Address more feedback * Address FlowFixMes * update types * prettier * remove import * polish types * fix types * add ut for unstable APIs * fix rebase issue * oops * wip * more nodes * types * prettier * add tests for core nodes * update codes.json * Merge global files * Rename global type defs * Update packages/lexical-link/src/index.js Co-authored-by: Gerard Rovira <zurfyx@users.noreply.github.com> * fix linter an versions * more versions Co-authored-by: acywatson <acy.watson@gmail.com> Co-authored-by: John Flockton <thegreatercurve@users.noreply.github.com> Co-authored-by: Gerard Rovira <zurfyx@users.noreply.github.com>
This commit is contained in:
2
libdefs/globals.d.ts
vendored
2
libdefs/globals.d.ts
vendored
@ -10,3 +10,5 @@ declare module '*.jpg' {
|
|||||||
const content: any;
|
const content: any;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Spread<T1, T2> = {[K in Exclude<keyof T1, keyof T2>]: T1[K]} & T2;
|
||||||
|
23
packages/lexical-code/LexicalCode.d.ts
vendored
23
packages/lexical-code/LexicalCode.d.ts
vendored
@ -14,9 +14,12 @@ import type {
|
|||||||
RangeSelection,
|
RangeSelection,
|
||||||
EditorThemeClasses,
|
EditorThemeClasses,
|
||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
|
SerializedElementNode,
|
||||||
|
SerializedTextNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {ElementNode, TextNode} from 'lexical';
|
import {ElementNode, TextNode} from 'lexical';
|
||||||
|
import {Spread} from 'libdefs/globals';
|
||||||
|
|
||||||
declare class CodeNode extends ElementNode {
|
declare class CodeNode extends ElementNode {
|
||||||
static getType(): string;
|
static getType(): string;
|
||||||
@ -31,6 +34,8 @@ declare class CodeNode extends ElementNode {
|
|||||||
collapseAtStart(): true;
|
collapseAtStart(): true;
|
||||||
setLanguage(language: string): void;
|
setLanguage(language: string): void;
|
||||||
getLanguage(): string | void;
|
getLanguage(): string | void;
|
||||||
|
importJSON(serializedNode: SerializedCodeNode): CodeNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
declare function $createCodeNode(language?: string): CodeNode;
|
declare function $createCodeNode(language?: string): CodeNode;
|
||||||
declare function $isCodeNode(
|
declare function $isCodeNode(
|
||||||
@ -74,3 +79,21 @@ declare function $isCodeHighlightNode(
|
|||||||
): node is CodeHighlightNode;
|
): node is CodeHighlightNode;
|
||||||
|
|
||||||
declare function registerCodeHighlighting(editor: LexicalEditor): () => void;
|
declare function registerCodeHighlighting(editor: LexicalEditor): () => void;
|
||||||
|
|
||||||
|
type SerializedCodeNode = Spread<
|
||||||
|
{
|
||||||
|
language: string | null | undefined;
|
||||||
|
type: 'code';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
type SerializedCodeHighlightNode = Spread<
|
||||||
|
{
|
||||||
|
highlightType: string | null | undefined;
|
||||||
|
type: 'code-highlight';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedTextNode
|
||||||
|
>;
|
||||||
|
@ -18,6 +18,8 @@ import type {
|
|||||||
NodeKey,
|
NodeKey,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
|
SerializedTextNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import * as Prism from 'prismjs';
|
import * as Prism from 'prismjs';
|
||||||
@ -39,6 +41,7 @@ import {
|
|||||||
mergeRegister,
|
mergeRegister,
|
||||||
removeClassNamesFromElement,
|
removeClassNamesFromElement,
|
||||||
} from '@lexical/utils';
|
} from '@lexical/utils';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
$createLineBreakNode,
|
$createLineBreakNode,
|
||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
@ -59,6 +62,24 @@ import {
|
|||||||
|
|
||||||
const DEFAULT_CODE_LANGUAGE = 'javascript';
|
const DEFAULT_CODE_LANGUAGE = 'javascript';
|
||||||
|
|
||||||
|
type SerializedCodeNode = Spread<
|
||||||
|
{
|
||||||
|
language: string | null | undefined;
|
||||||
|
type: 'code';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
type SerializedCodeHighlightNode = Spread<
|
||||||
|
{
|
||||||
|
highlightType: string | null | undefined;
|
||||||
|
type: 'code-highlight';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedTextNode
|
||||||
|
>;
|
||||||
|
|
||||||
const mapToPrismLanguage = (
|
const mapToPrismLanguage = (
|
||||||
language: string | null | undefined,
|
language: string | null | undefined,
|
||||||
): string | null | undefined => {
|
): string | null | undefined => {
|
||||||
@ -99,6 +120,11 @@ export class CodeHighlightNode extends TextNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHighlightType(): string | null | undefined {
|
||||||
|
const self = this.getLatest<CodeHighlightNode>();
|
||||||
|
return self.__highlightType;
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const element = super.createDOM(config);
|
const element = super.createDOM(config);
|
||||||
const className = getHighlightThemeClass(
|
const className = getHighlightThemeClass(
|
||||||
@ -134,6 +160,25 @@ export class CodeHighlightNode extends TextNode {
|
|||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(
|
||||||
|
serializedNode: SerializedCodeHighlightNode,
|
||||||
|
): CodeHighlightNode {
|
||||||
|
const node = $createCodeHighlightNode(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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent formatting (bold, underline, etc)
|
// Prevent formatting (bold, underline, etc)
|
||||||
setFormat(format: number): this {
|
setFormat(format: number): this {
|
||||||
return this;
|
return this;
|
||||||
@ -266,6 +311,22 @@ 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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation
|
// Mutation
|
||||||
insertNewAfter(
|
insertNewAfter(
|
||||||
selection: RangeSelection,
|
selection: RangeSelection,
|
||||||
|
8
packages/lexical-hashtag/LexicalHashtag.d.ts
vendored
8
packages/lexical-hashtag/LexicalHashtag.d.ts
vendored
@ -6,7 +6,12 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedTextNode,
|
||||||
|
} from 'lexical';
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
|
|
||||||
export declare class HashtagNode extends TextNode {
|
export declare class HashtagNode extends TextNode {
|
||||||
@ -16,6 +21,7 @@ export declare class HashtagNode extends TextNode {
|
|||||||
createDOM(config: EditorConfig): HTMLElement;
|
createDOM(config: EditorConfig): HTMLElement;
|
||||||
canInsertTextBefore(): boolean;
|
canInsertTextBefore(): boolean;
|
||||||
isTextEntity(): true;
|
isTextEntity(): true;
|
||||||
|
static importJSON(serializedNode: SerializedTextNode): HashtagNode;
|
||||||
}
|
}
|
||||||
export function $createHashtagNode(text?: string): TextNode;
|
export function $createHashtagNode(text?: string): TextNode;
|
||||||
export function $isHashtagNode(
|
export function $isHashtagNode(
|
||||||
|
@ -7,17 +7,24 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedTextNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
|
|
||||||
declare export class HashtagNode extends TextNode {
|
declare export class HashtagNode extends TextNode {
|
||||||
static getType(): string;
|
static getType(): string;
|
||||||
static clone(node: HashtagNode): HashtagNode;
|
static clone(node: HashtagNode): HashtagNode;
|
||||||
|
static importJSON(serializedNode: SerializedTextNode): HashtagNode;
|
||||||
constructor(text: string, key?: NodeKey): void;
|
constructor(text: string, key?: NodeKey): void;
|
||||||
createDOM(config: EditorConfig): HTMLElement;
|
createDOM(config: EditorConfig): HTMLElement;
|
||||||
canInsertTextBefore(): boolean;
|
canInsertTextBefore(): boolean;
|
||||||
isTextEntity(): true;
|
isTextEntity(): true;
|
||||||
|
exportJSON(): SerializedTextNode;
|
||||||
}
|
}
|
||||||
declare export function $createHashtagNode(text?: string): HashtagNode;
|
declare export function $createHashtagNode(text?: string): HashtagNode;
|
||||||
declare export function $isHashtagNode(
|
declare export function $isHashtagNode(
|
||||||
|
@ -7,7 +7,12 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedTextNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {addClassNamesToElement} from '@lexical/utils';
|
import {addClassNamesToElement} from '@lexical/utils';
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
@ -31,6 +36,22 @@ export class HashtagNode extends TextNode {
|
|||||||
return element;
|
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 {
|
canInsertTextBefore(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,20 @@ import type {
|
|||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {addClassNamesToElement} from '@lexical/utils';
|
import {addClassNamesToElement} from '@lexical/utils';
|
||||||
import {$isElementNode, createCommand, ElementNode} from 'lexical';
|
import {$isElementNode, createCommand, ElementNode} from 'lexical';
|
||||||
|
import invariant from 'shared/invariant';
|
||||||
|
|
||||||
|
export type SerializedLinkNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'link',
|
||||||
|
url: string,
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class LinkNode extends ElementNode {
|
export class LinkNode extends ElementNode {
|
||||||
__url: string;
|
__url: string;
|
||||||
@ -67,6 +77,22 @@ export class LinkNode extends ElementNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedLinkNode): LinkNode {
|
||||||
|
const node = $createLinkNode(serializedNode.url);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'link',
|
||||||
|
url: this.getURL(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getURL(): string {
|
getURL(): string {
|
||||||
return this.getLatest().__url;
|
return this.getLatest().__url;
|
||||||
}
|
}
|
||||||
@ -119,6 +145,13 @@ export function $isLinkNode(node: ?LexicalNode): boolean %checks {
|
|||||||
return node instanceof LinkNode;
|
return node instanceof LinkNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedAutoLinkNode = {
|
||||||
|
...SerializedLinkNode,
|
||||||
|
type: 'autolink',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
// Custom node type to override `canInsertTextAfter` that will
|
// Custom node type to override `canInsertTextAfter` that will
|
||||||
// allow typing within the link
|
// allow typing within the link
|
||||||
export class AutoLinkNode extends LinkNode {
|
export class AutoLinkNode extends LinkNode {
|
||||||
@ -131,6 +164,28 @@ export class AutoLinkNode extends LinkNode {
|
|||||||
return new AutoLinkNode(node.__url, node.__key);
|
return new AutoLinkNode(node.__url, node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(
|
||||||
|
serializedNode: SerializedLinkNode | SerializedAutoLinkNode,
|
||||||
|
): AutoLinkNode {
|
||||||
|
invariant(
|
||||||
|
serializedNode.type !== 'autolink',
|
||||||
|
'Incorrect node type received in importJSON for %s',
|
||||||
|
this.getType(),
|
||||||
|
);
|
||||||
|
const node = $createAutoLinkNode(serializedNode.url);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'autolink',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
insertNewAfter(selection: RangeSelection): null | ElementNode {
|
insertNewAfter(selection: RangeSelection): null | ElementNode {
|
||||||
const element = this.getParentOrThrow().insertNewAfter(selection);
|
const element = this.getParentOrThrow().insertNewAfter(selection);
|
||||||
if ($isElementNode(element)) {
|
if ($isElementNode(element)) {
|
||||||
|
26
packages/lexical-list/LexicalList.d.ts
vendored
26
packages/lexical-list/LexicalList.d.ts
vendored
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ListNodeTagType} from './src/LexicalListNode';
|
import {ListNodeTagType} from './src/LexicalListNode';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
ElementNode,
|
ElementNode,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
@ -14,6 +15,7 @@ import {
|
|||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
LexicalCommand,
|
LexicalCommand,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
export type ListType = 'number' | 'bullet' | 'check';
|
export type ListType = 'number' | 'bullet' | 'check';
|
||||||
@ -40,13 +42,18 @@ export declare class ListItemNode extends ElementNode {
|
|||||||
getChecked(): boolean | void;
|
getChecked(): boolean | void;
|
||||||
setChecked(boolean): this;
|
setChecked(boolean): this;
|
||||||
toggleChecked(): void;
|
toggleChecked(): void;
|
||||||
|
static importJSON(serializedNode: SerializedListItemNode): ListItemNode;
|
||||||
|
exportJSON(): SerializedListItemNode;
|
||||||
}
|
}
|
||||||
export declare class ListNode extends ElementNode {
|
export declare class ListNode extends ElementNode {
|
||||||
canBeEmpty(): false;
|
canBeEmpty(): false;
|
||||||
append(...nodesToAppend: LexicalNode[]): ListNode;
|
append(...nodesToAppend: LexicalNode[]): ListNode;
|
||||||
getTag(): ListNodeTagType;
|
getTag(): ListNodeTagType;
|
||||||
getListType(): ListType;
|
getListType(): ListType;
|
||||||
|
static importJSON(serializedNode: SerializedListNode): ListNode;
|
||||||
|
exportJSON(): SerializedListNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function outdentList(): void;
|
export function outdentList(): void;
|
||||||
export function removeList(editor: LexicalEditor): boolean;
|
export function removeList(editor: LexicalEditor): boolean;
|
||||||
|
|
||||||
@ -54,3 +61,22 @@ export var INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void>;
|
|||||||
export var INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void>;
|
export var INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void>;
|
||||||
export var INSERT_CHECK_LIST_COMMAND: LexicalCommand<void>;
|
export var INSERT_CHECK_LIST_COMMAND: LexicalCommand<void>;
|
||||||
export var REMOVE_LIST_COMMAND: LexicalCommand<void>;
|
export var REMOVE_LIST_COMMAND: LexicalCommand<void>;
|
||||||
|
|
||||||
|
export type SerializedListItemNode = Spread<
|
||||||
|
{
|
||||||
|
checked: boolean | void;
|
||||||
|
value: number;
|
||||||
|
type: 'listitem';
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SerializedListNode = Spread<
|
||||||
|
{
|
||||||
|
listType: ListType;
|
||||||
|
start: number;
|
||||||
|
tag: ListNodeTagType;
|
||||||
|
type: 'list';
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
@ -13,6 +13,7 @@ import type {
|
|||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
LexicalCommand,
|
LexicalCommand,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import {ElementNode} from 'lexical';
|
import {ElementNode} from 'lexical';
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ declare export class ListItemNode extends ElementNode {
|
|||||||
getChecked(): boolean | void;
|
getChecked(): boolean | void;
|
||||||
setChecked(boolean): this;
|
setChecked(boolean): this;
|
||||||
toggleChecked(): void;
|
toggleChecked(): void;
|
||||||
|
static importJSON(serializedNode: SerializedListItemNode): ListItemNode;
|
||||||
}
|
}
|
||||||
declare export class ListNode extends ElementNode {
|
declare export class ListNode extends ElementNode {
|
||||||
__tag: ListNodeTagType;
|
__tag: ListNodeTagType;
|
||||||
@ -62,6 +64,7 @@ declare export class ListNode extends ElementNode {
|
|||||||
getTag(): ListNodeTagType;
|
getTag(): ListNodeTagType;
|
||||||
getStart(): number;
|
getStart(): number;
|
||||||
getListType(): ListType;
|
getListType(): ListType;
|
||||||
|
static importJSON(serializedNode: SerializedListNode): ListNode;
|
||||||
}
|
}
|
||||||
declare export function outdentList(): void;
|
declare export function outdentList(): void;
|
||||||
declare export function removeList(editor: LexicalEditor): boolean;
|
declare export function removeList(editor: LexicalEditor): boolean;
|
||||||
@ -70,3 +73,22 @@ declare export var INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void>;
|
|||||||
declare export var INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void>;
|
declare export var INSERT_ORDERED_LIST_COMMAND: LexicalCommand<void>;
|
||||||
declare export var INSERT_CHECK_LIST_COMMAND: LexicalCommand<void>;
|
declare export var INSERT_CHECK_LIST_COMMAND: LexicalCommand<void>;
|
||||||
declare export var REMOVE_LIST_COMMAND: LexicalCommand<void>;
|
declare export var REMOVE_LIST_COMMAND: LexicalCommand<void>;
|
||||||
|
|
||||||
|
export type SerializedListItemNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
checked: boolean | void,
|
||||||
|
value: number,
|
||||||
|
type: 'listitem',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedListNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
listType: ListType,
|
||||||
|
start: number,
|
||||||
|
tag: ListNodeTagType,
|
||||||
|
type: 'list',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
@ -19,6 +19,7 @@ import type {
|
|||||||
NodeSelection,
|
NodeSelection,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -41,6 +42,15 @@ import {
|
|||||||
updateChildrenListItemValue,
|
updateChildrenListItemValue,
|
||||||
} from './formatList';
|
} from './formatList';
|
||||||
|
|
||||||
|
export type SerializedListItemNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
checked: boolean | void,
|
||||||
|
type: 'listitem',
|
||||||
|
value: number,
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class ListItemNode extends ElementNode {
|
export class ListItemNode extends ElementNode {
|
||||||
__value: number;
|
__value: number;
|
||||||
__checked: boolean | void;
|
__checked: boolean | void;
|
||||||
@ -96,6 +106,23 @@ export class ListItemNode extends ElementNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedListItemNode): ListItemNode {
|
||||||
|
const node = new ListItemNode(serializedNode.value, serializedNode.checked);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
checked: this.getChecked(),
|
||||||
|
type: 'listitem',
|
||||||
|
value: this.getValue(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
append(...nodes: LexicalNode[]): ListItemNode {
|
append(...nodes: LexicalNode[]): ListItemNode {
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i];
|
const node = nodes[i];
|
||||||
@ -262,8 +289,13 @@ export class ListItemNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getIndent(): number {
|
getIndent(): number {
|
||||||
|
// If we don't have a parent, we are likely serializing
|
||||||
|
const parent = this.getParent();
|
||||||
|
if (parent === null) {
|
||||||
|
return this.getLatest().__indent;
|
||||||
|
}
|
||||||
// ListItemNode should always have a ListNode for a parent.
|
// ListItemNode should always have a ListNode for a parent.
|
||||||
let listNodeParent = this.getParentOrThrow().getParentOrThrow();
|
let listNodeParent = parent.getParentOrThrow();
|
||||||
let indentLevel = 0;
|
let indentLevel = 0;
|
||||||
while ($isListItemNode(listNodeParent)) {
|
while ($isListItemNode(listNodeParent)) {
|
||||||
listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow();
|
listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow();
|
||||||
|
@ -14,6 +14,7 @@ import type {
|
|||||||
EditorThemeClasses,
|
EditorThemeClasses,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,6 +26,16 @@ import {$createTextNode, ElementNode} from 'lexical';
|
|||||||
import {$createListItemNode, $isListItemNode} from '.';
|
import {$createListItemNode, $isListItemNode} from '.';
|
||||||
import {$getListDepth} from './utils';
|
import {$getListDepth} from './utils';
|
||||||
|
|
||||||
|
export type SerializedListNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
listType: ListType,
|
||||||
|
start: number,
|
||||||
|
tag: ListNodeTagType,
|
||||||
|
type: 'list',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export type ListType = 'number' | 'bullet' | 'check';
|
export type ListType = 'number' | 'bullet' | 'check';
|
||||||
|
|
||||||
export type ListNodeTagType = 'ul' | 'ol';
|
export type ListNodeTagType = 'ul' | 'ol';
|
||||||
@ -103,6 +114,24 @@ export class ListNode extends ElementNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedListNode): ListNode {
|
||||||
|
const node = $createListNode(serializedNode.listType, serializedNode.start);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
listType: this.getListType(),
|
||||||
|
start: this.getStart(),
|
||||||
|
tag: this.getTag(),
|
||||||
|
type: 'list',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
canBeEmpty(): false {
|
canBeEmpty(): false {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
19
packages/lexical-overflow/LexicalOverflow.d.ts
vendored
19
packages/lexical-overflow/LexicalOverflow.d.ts
vendored
@ -6,7 +6,14 @@
|
|||||||
*
|
*
|
||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
import type {EditorConfig, LexicalNode, NodeKey, RangeSelection} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
|
} from 'lexical';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {ElementNode} from 'lexical';
|
import {ElementNode} from 'lexical';
|
||||||
export declare class OverflowNode extends ElementNode {
|
export declare class OverflowNode extends ElementNode {
|
||||||
static getType(): string;
|
static getType(): string;
|
||||||
@ -16,6 +23,16 @@ export declare class OverflowNode extends ElementNode {
|
|||||||
updateDOM(prevNode: OverflowNode, dom: HTMLElement): boolean;
|
updateDOM(prevNode: OverflowNode, dom: HTMLElement): boolean;
|
||||||
insertNewAfter(selection: RangeSelection): null | LexicalNode;
|
insertNewAfter(selection: RangeSelection): null | LexicalNode;
|
||||||
excludeFromCopy(): boolean;
|
excludeFromCopy(): boolean;
|
||||||
|
static importJSON(serializedNode: SerializedOverflowNode): OverflowNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $createOverflowNode(): OverflowNode;
|
export function $createOverflowNode(): OverflowNode;
|
||||||
export function $isOverflowNode(node: LexicalNode | null): node is OverflowNode;
|
export function $isOverflowNode(node: LexicalNode | null): node is OverflowNode;
|
||||||
|
|
||||||
|
export type SerializedOverflowNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'overflow';
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
@ -6,7 +6,13 @@
|
|||||||
*
|
*
|
||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
import type {EditorConfig, LexicalNode, NodeKey, RangeSelection} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
|
} from 'lexical';
|
||||||
import {ElementNode} from 'lexical';
|
import {ElementNode} from 'lexical';
|
||||||
declare export class OverflowNode extends ElementNode {
|
declare export class OverflowNode extends ElementNode {
|
||||||
static getType(): string;
|
static getType(): string;
|
||||||
@ -16,8 +22,16 @@ declare export class OverflowNode extends ElementNode {
|
|||||||
updateDOM(prevNode: OverflowNode, dom: HTMLElement): boolean;
|
updateDOM(prevNode: OverflowNode, dom: HTMLElement): boolean;
|
||||||
insertNewAfter(selection: RangeSelection): null | LexicalNode;
|
insertNewAfter(selection: RangeSelection): null | LexicalNode;
|
||||||
excludeFromCopy(): boolean;
|
excludeFromCopy(): boolean;
|
||||||
|
static importJSON(serializedNode: SerializedOverflowNode): OverflowNode;
|
||||||
}
|
}
|
||||||
declare export function $createOverflowNode(): OverflowNode;
|
declare export function $createOverflowNode(): OverflowNode;
|
||||||
declare export function $isOverflowNode(
|
declare export function $isOverflowNode(
|
||||||
node: ?LexicalNode,
|
node: ?LexicalNode,
|
||||||
): boolean %checks(node instanceof OverflowNode);
|
): boolean %checks(node instanceof OverflowNode);
|
||||||
|
|
||||||
|
export type SerializedOverflowNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'overflow',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
@ -7,10 +7,23 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode, NodeKey, RangeSelection} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {ElementNode} from 'lexical';
|
import {ElementNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedOverflowNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'overflow',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class OverflowNode extends ElementNode {
|
export class OverflowNode extends ElementNode {
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'overflow';
|
return 'overflow';
|
||||||
@ -20,11 +33,22 @@ export class OverflowNode extends ElementNode {
|
|||||||
return new OverflowNode(node.__key);
|
return new OverflowNode(node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedOverflowNode): OverflowNode {
|
||||||
|
return $createOverflowNode();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(key?: NodeKey): void {
|
constructor(key?: NodeKey): void {
|
||||||
super(key);
|
super(key);
|
||||||
this.__type = 'overflow';
|
this.__type = 'overflow';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'overflow',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const div = document.createElement('span');
|
const div = document.createElement('span');
|
||||||
const className = config.theme.characterLimit;
|
const className = config.theme.characterLimit;
|
||||||
|
@ -6,12 +6,26 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedTextNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedEmojiNode = Spread<
|
||||||
|
{
|
||||||
|
className: string;
|
||||||
|
type: 'emoji';
|
||||||
|
},
|
||||||
|
SerializedTextNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class EmojiNode extends TextNode {
|
export class EmojiNode extends TextNode {
|
||||||
__className?: string;
|
__className: string;
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'emoji';
|
return 'emoji';
|
||||||
@ -47,6 +61,31 @@ export class EmojiNode extends TextNode {
|
|||||||
super.updateDOM(prevNode, inner as HTMLElement, config);
|
super.updateDOM(prevNode, inner as HTMLElement, config);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedEmojiNode): EmojiNode {
|
||||||
|
const node = $createEmojiNode(
|
||||||
|
serializedNode.className,
|
||||||
|
serializedNode.text,
|
||||||
|
);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setDetail(serializedNode.detail);
|
||||||
|
node.setMode(serializedNode.mode);
|
||||||
|
node.setStyle(serializedNode.style);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedEmojiNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
className: this.getClassName(),
|
||||||
|
type: 'emoji',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getClassName(): string {
|
||||||
|
const self = this.getLatest<EmojiNode>();
|
||||||
|
return self.__className;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $isEmojiNode(
|
export function $isEmojiNode(
|
||||||
|
@ -11,10 +11,12 @@ import type {
|
|||||||
EditorConfig,
|
EditorConfig,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||||
import {mergeRegister} from '@lexical/utils';
|
import {mergeRegister} from '@lexical/utils';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
$getNodeByKey,
|
$getNodeByKey,
|
||||||
COMMAND_PRIORITY_HIGH,
|
COMMAND_PRIORITY_HIGH,
|
||||||
@ -114,6 +116,15 @@ function EquationComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedEquationNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'equation';
|
||||||
|
equation: string;
|
||||||
|
inline: boolean;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class EquationNode extends DecoratorNode<JSX.Element> {
|
export class EquationNode extends DecoratorNode<JSX.Element> {
|
||||||
__equation: string;
|
__equation: string;
|
||||||
__inline: boolean;
|
__inline: boolean;
|
||||||
@ -132,6 +143,23 @@ export class EquationNode extends DecoratorNode<JSX.Element> {
|
|||||||
this.__inline = inline ?? false;
|
this.__inline = inline ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedEquationNode): EquationNode {
|
||||||
|
const node = $createEquationNode(
|
||||||
|
serializedNode.equation,
|
||||||
|
serializedNode.inline,
|
||||||
|
);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedEquationNode {
|
||||||
|
return {
|
||||||
|
equation: this.getEquation(),
|
||||||
|
inline: this.__inline,
|
||||||
|
type: 'emoji',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
exportDOM(): DOMExportOutput {
|
exportDOM(): DOMExportOutput {
|
||||||
const element = document.createElement(this.__inline ? 'span' : 'div');
|
const element = document.createElement(this.__inline ? 'span' : 'div');
|
||||||
element.innerText = this.__equation;
|
element.innerText = this.__equation;
|
||||||
|
@ -7,11 +7,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {ExcalidrawElementFragment} from './ExcalidrawModal';
|
import type {ExcalidrawElementFragment} from './ExcalidrawModal';
|
||||||
import type {EditorConfig, LexicalEditor, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalEditor,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||||
import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection';
|
import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection';
|
||||||
import {mergeRegister} from '@lexical/utils';
|
import {mergeRegister} from '@lexical/utils';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
$getNodeByKey,
|
$getNodeByKey,
|
||||||
$getSelection,
|
$getSelection,
|
||||||
@ -193,6 +200,15 @@ function ExcalidrawComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedExcalidrawNode = Spread<
|
||||||
|
{
|
||||||
|
data: string;
|
||||||
|
type: 'excalidraw';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class ExcalidrawNode extends DecoratorNode<JSX.Element> {
|
export class ExcalidrawNode extends DecoratorNode<JSX.Element> {
|
||||||
__data: string;
|
__data: string;
|
||||||
|
|
||||||
@ -204,6 +220,18 @@ export class ExcalidrawNode extends DecoratorNode<JSX.Element> {
|
|||||||
return new ExcalidrawNode(node.__data, node.__key);
|
return new ExcalidrawNode(node.__data, node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedExcalidrawNode): ExcalidrawNode {
|
||||||
|
return new ExcalidrawNode(serializedNode.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedExcalidrawNode {
|
||||||
|
return {
|
||||||
|
data: this.__data,
|
||||||
|
type: 'excalidraw',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(data = '[]', key?: NodeKey) {
|
constructor(data = '[]', key?: NodeKey) {
|
||||||
super(key);
|
super(key);
|
||||||
this.__data = data;
|
this.__data = data;
|
||||||
|
@ -12,6 +12,7 @@ import type {
|
|||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import './ImageNode.css';
|
import './ImageNode.css';
|
||||||
@ -29,6 +30,7 @@ import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
|
|||||||
import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
|
import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
|
||||||
import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection';
|
import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection';
|
||||||
import {mergeRegister} from '@lexical/utils';
|
import {mergeRegister} from '@lexical/utils';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
$getNodeByKey,
|
$getNodeByKey,
|
||||||
$getSelection,
|
$getSelection,
|
||||||
@ -310,6 +312,21 @@ function ImageComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedImageNode = Spread<
|
||||||
|
{
|
||||||
|
altText: string;
|
||||||
|
caption: LexicalEditor;
|
||||||
|
height?: number;
|
||||||
|
maxWidth: number;
|
||||||
|
showCaption: boolean;
|
||||||
|
src: string;
|
||||||
|
width?: number;
|
||||||
|
type: 'image';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class ImageNode extends DecoratorNode<JSX.Element> {
|
export class ImageNode extends DecoratorNode<JSX.Element> {
|
||||||
__src: string;
|
__src: string;
|
||||||
__altText: string;
|
__altText: string;
|
||||||
@ -336,6 +353,20 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedImageNode): ImageNode {
|
||||||
|
const {altText, height, width, maxWidth, caption, src, showCaption} =
|
||||||
|
serializedNode;
|
||||||
|
return $createImageNode({
|
||||||
|
altText,
|
||||||
|
caption,
|
||||||
|
height,
|
||||||
|
maxWidth,
|
||||||
|
showCaption,
|
||||||
|
src,
|
||||||
|
width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
src: string,
|
src: string,
|
||||||
altText: string,
|
altText: string,
|
||||||
@ -363,6 +394,20 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
|
|||||||
return {element};
|
return {element};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedImageNode {
|
||||||
|
return {
|
||||||
|
altText: this.getAltText(),
|
||||||
|
caption: this.__caption,
|
||||||
|
height: this.__height === 'inherit' ? 0 : this.__height,
|
||||||
|
maxWidth: this.__maxWidth,
|
||||||
|
showCaption: this.__showCaption,
|
||||||
|
src: this.getSrc(),
|
||||||
|
type: 'image',
|
||||||
|
version: 1,
|
||||||
|
width: this.__width === 'inherit' ? 0 : this.__width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
setWidthAndHeight(
|
setWidthAndHeight(
|
||||||
width: 'inherit' | number,
|
width: 'inherit' | number,
|
||||||
height: 'inherit' | number,
|
height: 'inherit' | number,
|
||||||
|
@ -6,10 +6,19 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode} from 'lexical';
|
import type {EditorConfig, LexicalNode, SerializedTextNode} from 'lexical';
|
||||||
|
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedKeywordNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'keyword';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedTextNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class KeywordNode extends TextNode {
|
export class KeywordNode extends TextNode {
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'keyword';
|
return 'keyword';
|
||||||
@ -19,6 +28,23 @@ export class KeywordNode extends TextNode {
|
|||||||
return new KeywordNode(node.__text, node.__key);
|
return new KeywordNode(node.__text, node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedKeywordNode): KeywordNode {
|
||||||
|
const node = $createKeywordNode(serializedNode.text);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setDetail(serializedNode.detail);
|
||||||
|
node.setMode(serializedNode.mode);
|
||||||
|
node.setStyle(serializedNode.style);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedKeywordNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'keyword',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
dom.style.cursor = 'default';
|
dom.style.cursor = 'default';
|
||||||
|
@ -6,10 +6,25 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedTextNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedMentionNode = Spread<
|
||||||
|
{
|
||||||
|
mentionName: string;
|
||||||
|
type: 'mention';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedTextNode
|
||||||
|
>;
|
||||||
|
|
||||||
const mentionStyle = 'background-color: rgba(24, 119, 232, 0.2)';
|
const mentionStyle = 'background-color: rgba(24, 119, 232, 0.2)';
|
||||||
export class MentionNode extends TextNode {
|
export class MentionNode extends TextNode {
|
||||||
__mention: string;
|
__mention: string;
|
||||||
@ -21,12 +36,30 @@ export class MentionNode extends TextNode {
|
|||||||
static clone(node: MentionNode): MentionNode {
|
static clone(node: MentionNode): MentionNode {
|
||||||
return new MentionNode(node.__mention, node.__text, node.__key);
|
return new MentionNode(node.__mention, node.__text, node.__key);
|
||||||
}
|
}
|
||||||
|
static importJSON(serializedNode: SerializedMentionNode): MentionNode {
|
||||||
|
const node = $createMentionNode(serializedNode.mentionName);
|
||||||
|
node.setTextContent(serializedNode.text);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setDetail(serializedNode.detail);
|
||||||
|
node.setMode(serializedNode.mode);
|
||||||
|
node.setStyle(serializedNode.style);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(mentionName: string, text?: string, key?: NodeKey) {
|
constructor(mentionName: string, text?: string, key?: NodeKey) {
|
||||||
super(text ?? mentionName, key);
|
super(text ?? mentionName, key);
|
||||||
this.__mention = mentionName;
|
this.__mention = mentionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedMentionNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
mentionName: this.__mention,
|
||||||
|
type: 'mention',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
dom.style.cssText = mentionStyle;
|
dom.style.cssText = mentionStyle;
|
||||||
|
@ -6,12 +6,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {LexicalNode, NodeKey} from 'lexical';
|
import type {LexicalNode, NodeKey, SerializedLexicalNode} from 'lexical';
|
||||||
|
|
||||||
import './PollNode.css';
|
import './PollNode.css';
|
||||||
|
|
||||||
import {useCollaborationContext} from '@lexical/react/LexicalCollaborationPlugin';
|
import {useCollaborationContext} from '@lexical/react/LexicalCollaborationPlugin';
|
||||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {$getNodeByKey, DecoratorNode} from 'lexical';
|
import {$getNodeByKey, DecoratorNode} from 'lexical';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {useMemo, useRef} from 'react';
|
import {useMemo, useRef} from 'react';
|
||||||
@ -189,6 +190,16 @@ function PollComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedPollNode = Spread<
|
||||||
|
{
|
||||||
|
question: string;
|
||||||
|
options: Options;
|
||||||
|
type: 'poll';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class PollNode extends DecoratorNode<JSX.Element> {
|
export class PollNode extends DecoratorNode<JSX.Element> {
|
||||||
__question: string;
|
__question: string;
|
||||||
__options: Options;
|
__options: Options;
|
||||||
@ -201,12 +212,27 @@ export class PollNode extends DecoratorNode<JSX.Element> {
|
|||||||
return new PollNode(node.__question, node.__options, node.__key);
|
return new PollNode(node.__question, node.__options, node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedPollNode): PollNode {
|
||||||
|
const node = $createPollNode(serializedNode.question);
|
||||||
|
serializedNode.options.forEach(node.addOption);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(question: string, options?: Options, key?: NodeKey) {
|
constructor(question: string, options?: Options, key?: NodeKey) {
|
||||||
super(key);
|
super(key);
|
||||||
this.__question = question;
|
this.__question = question;
|
||||||
this.__options = options || [createPollOption(), createPollOption()];
|
this.__options = options || [createPollOption(), createPollOption()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedPollNode {
|
||||||
|
return {
|
||||||
|
options: this.__options,
|
||||||
|
question: this.__question,
|
||||||
|
type: 'poll',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
addOption(option: Option): void {
|
addOption(option: Option): void {
|
||||||
const self = this.getWritable<PollNode>();
|
const self = this.getWritable<PollNode>();
|
||||||
const options = Array.from(self.__options);
|
const options = Array.from(self.__options);
|
||||||
|
@ -6,7 +6,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig, LexicalEditor, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
EditorConfig,
|
||||||
|
LexicalEditor,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import './StickyNode.css';
|
import './StickyNode.css';
|
||||||
|
|
||||||
@ -18,6 +24,7 @@ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
|||||||
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
|
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
|
||||||
import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer';
|
import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer';
|
||||||
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
|
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
$getNodeByKey,
|
$getNodeByKey,
|
||||||
$setSelection,
|
$setSelection,
|
||||||
@ -266,10 +273,24 @@ function StickyComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StickyNoteColor = 'pink' | 'yellow';
|
||||||
|
|
||||||
|
export type SerializedStickyNode = Spread<
|
||||||
|
{
|
||||||
|
xOffset: number;
|
||||||
|
yOffset: number;
|
||||||
|
color: StickyNoteColor;
|
||||||
|
caption: LexicalEditor;
|
||||||
|
type: 'sticky';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class StickyNode extends DecoratorNode<JSX.Element> {
|
export class StickyNode extends DecoratorNode<JSX.Element> {
|
||||||
__x: number;
|
__x: number;
|
||||||
__y: number;
|
__y: number;
|
||||||
__color: 'pink' | 'yellow';
|
__color: StickyNoteColor;
|
||||||
__caption: LexicalEditor;
|
__caption: LexicalEditor;
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
@ -285,6 +306,14 @@ export class StickyNode extends DecoratorNode<JSX.Element> {
|
|||||||
node.__key,
|
node.__key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
static importJSON(serializedNode: SerializedStickyNode): StickyNode {
|
||||||
|
return new StickyNode(
|
||||||
|
serializedNode.xOffset,
|
||||||
|
serializedNode.yOffset,
|
||||||
|
serializedNode.color,
|
||||||
|
serializedNode.caption,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
x: number,
|
x: number,
|
||||||
@ -300,6 +329,17 @@ export class StickyNode extends DecoratorNode<JSX.Element> {
|
|||||||
this.__color = color;
|
this.__color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedStickyNode {
|
||||||
|
return {
|
||||||
|
caption: this.__caption,
|
||||||
|
color: this.__color,
|
||||||
|
type: 'sticky',
|
||||||
|
version: 1,
|
||||||
|
xOffset: this.__x,
|
||||||
|
yOffset: this.__y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.style.display = 'contents';
|
div.style.display = 'contents';
|
||||||
|
@ -9,7 +9,11 @@
|
|||||||
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
||||||
|
|
||||||
import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents';
|
import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents';
|
||||||
import {DecoratorBlockNode} from '@lexical/react/LexicalDecoratorBlockNode';
|
import {
|
||||||
|
DecoratorBlockNode,
|
||||||
|
SerializedDecoratorBlockNode,
|
||||||
|
} from '@lexical/react/LexicalDecoratorBlockNode';
|
||||||
|
import {Spread} from 'libdefs/globals';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
@ -42,6 +46,7 @@ function TweetComponent({
|
|||||||
|
|
||||||
const createTweet = useCallback(async () => {
|
const createTweet = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
// @ts-expect-error Twitter is attached to the window.
|
||||||
await window.twttr.widgets.createTweet(tweetID, containerRef.current);
|
await window.twttr.widgets.createTweet(tweetID, containerRef.current);
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -88,6 +93,15 @@ function TweetComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedTweetNode = Spread<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
type: 'tweet';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedDecoratorBlockNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class TweetNode extends DecoratorBlockNode<JSX.Element> {
|
export class TweetNode extends DecoratorBlockNode<JSX.Element> {
|
||||||
__id: string;
|
__id: string;
|
||||||
|
|
||||||
@ -99,6 +113,21 @@ export class TweetNode extends DecoratorBlockNode<JSX.Element> {
|
|||||||
return new TweetNode(node.__id, node.__format, node.__key);
|
return new TweetNode(node.__id, node.__format, node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedTweetNode): TweetNode {
|
||||||
|
const node = $createTweetNode(serializedNode.id);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedTweetNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
id: this.getId(),
|
||||||
|
type: 'tweet',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
|
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
|
||||||
super(format, key);
|
super(format, key);
|
||||||
this.__id = id;
|
this.__id = id;
|
||||||
|
@ -6,10 +6,19 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorConfig} from 'lexical';
|
import type {EditorConfig, SerializedTextNode} from 'lexical';
|
||||||
|
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {TextNode} from 'lexical';
|
import {TextNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedTypeaheadNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'typeahead';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedTextNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class TypeaheadNode extends TextNode {
|
export class TypeaheadNode extends TextNode {
|
||||||
static clone(node: TypeaheadNode): TypeaheadNode {
|
static clone(node: TypeaheadNode): TypeaheadNode {
|
||||||
return new TypeaheadNode(node.__text, node.__key);
|
return new TypeaheadNode(node.__text, node.__key);
|
||||||
@ -19,6 +28,23 @@ export class TypeaheadNode extends TextNode {
|
|||||||
return 'typeahead';
|
return 'typeahead';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedTypeaheadNode): TypeaheadNode {
|
||||||
|
const node = $createTypeaheadNode(serializedNode.text);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setDetail(serializedNode.detail);
|
||||||
|
node.setMode(serializedNode.mode);
|
||||||
|
node.setStyle(serializedNode.style);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedTypeaheadNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'typeahead',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
dom.style.cssText = 'color: #ccc;';
|
dom.style.cssText = 'color: #ccc;';
|
||||||
|
@ -9,7 +9,11 @@
|
|||||||
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
||||||
|
|
||||||
import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents';
|
import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents';
|
||||||
import {DecoratorBlockNode} from '@lexical/react/LexicalDecoratorBlockNode';
|
import {
|
||||||
|
DecoratorBlockNode,
|
||||||
|
SerializedDecoratorBlockNode,
|
||||||
|
} from '@lexical/react/LexicalDecoratorBlockNode';
|
||||||
|
import {Spread} from 'libdefs/globals';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
type YouTubeComponentProps = Readonly<{
|
type YouTubeComponentProps = Readonly<{
|
||||||
@ -34,6 +38,15 @@ function YouTubeComponent({format, nodeKey, videoID}: YouTubeComponentProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedYouTubeNode = Spread<
|
||||||
|
{
|
||||||
|
videoID: string;
|
||||||
|
type: 'youtube';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedDecoratorBlockNode
|
||||||
|
>;
|
||||||
|
|
||||||
export class YouTubeNode extends DecoratorBlockNode<JSX.Element> {
|
export class YouTubeNode extends DecoratorBlockNode<JSX.Element> {
|
||||||
__id: string;
|
__id: string;
|
||||||
|
|
||||||
@ -45,6 +58,21 @@ export class YouTubeNode extends DecoratorBlockNode<JSX.Element> {
|
|||||||
return new YouTubeNode(node.__id, node.__format, node.__key);
|
return new YouTubeNode(node.__id, node.__format, node.__key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedYouTubeNode): YouTubeNode {
|
||||||
|
const node = $createYouTubeNode(serializedNode.videoID);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedYouTubeNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'youtube',
|
||||||
|
version: 1,
|
||||||
|
videoID: this.__id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
|
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
|
||||||
super(format, key);
|
super(format, key);
|
||||||
this.__id = id;
|
this.__id = id;
|
||||||
|
@ -7,15 +7,25 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
ElementFormatType,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {DecoratorNode} from 'lexical';
|
import {DecoratorNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedDecoratorBlockNode = SerializedLexicalNode & {
|
||||||
|
format: ElementFormatType;
|
||||||
|
};
|
||||||
|
|
||||||
declare class DecoratorBlockNode<T> extends DecoratorNode<T> {
|
declare class DecoratorBlockNode<T> extends DecoratorNode<T> {
|
||||||
__format: ElementFormatType;
|
__format: ElementFormatType;
|
||||||
constructor(format?: ElementFormatType | null, key?: NodeKey);
|
constructor(format?: ElementFormatType | null, key?: NodeKey);
|
||||||
createDOM(): HTMLElement;
|
createDOM(): HTMLElement;
|
||||||
setFormat(format: ElementFormatType): void;
|
setFormat(format: ElementFormatType): void;
|
||||||
|
exportJSON(): SerializedDecoratorBlockNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare function $isDecoratorBlockNode<T>(
|
declare function $isDecoratorBlockNode<T>(
|
||||||
|
@ -7,10 +7,21 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
import type {
|
||||||
|
ElementFormatType,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {DecoratorNode} from 'lexical';
|
import {DecoratorNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedDecoratorBlockNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
format: ElementFormatType,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class DecoratorBlockNode extends DecoratorNode<React$Node> {
|
export class DecoratorBlockNode extends DecoratorNode<React$Node> {
|
||||||
__format: ?ElementFormatType;
|
__format: ?ElementFormatType;
|
||||||
|
|
||||||
@ -19,6 +30,14 @@ export class DecoratorBlockNode extends DecoratorNode<React$Node> {
|
|||||||
this.__format = format;
|
this.__format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedDecoratorBlockNode {
|
||||||
|
return {
|
||||||
|
format: this.__format || '',
|
||||||
|
type: 'decorator-block',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(): HTMLElement {
|
createDOM(): HTMLElement {
|
||||||
return document.createElement('div');
|
return document.createElement('div');
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,17 @@ import type {
|
|||||||
DOMExportOutput,
|
DOMExportOutput,
|
||||||
LexicalCommand,
|
LexicalCommand,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
|
SerializedLexicalNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {createCommand, DecoratorNode} from 'lexical';
|
import {createCommand, DecoratorNode} from 'lexical';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export type SerializedHorizontalRuleNode = SerializedLexicalNode & {
|
||||||
|
type: 'horizontalrule',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand<void> =
|
export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand<void> =
|
||||||
createCommand();
|
createCommand();
|
||||||
|
|
||||||
@ -47,6 +53,19 @@ export class HorizontalRuleNode extends DecoratorNode<React$Node> {
|
|||||||
return {element: document.createElement('hr')};
|
return {element: document.createElement('hr')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(
|
||||||
|
serializedNode: SerializedHorizontalRuleNode,
|
||||||
|
): HorizontalRuleNode {
|
||||||
|
return $createHorizontalRuleNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedLexicalNode {
|
||||||
|
return {
|
||||||
|
type: 'horizontalrule',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(): HTMLElement {
|
createDOM(): HTMLElement {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.style.display = 'contents';
|
div.style.display = 'contents';
|
||||||
|
24
packages/lexical-rich-text/LexicalRichText.d.ts
vendored
24
packages/lexical-rich-text/LexicalRichText.d.ts
vendored
@ -5,6 +5,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
EditorConfig,
|
EditorConfig,
|
||||||
@ -13,8 +14,11 @@ import type {
|
|||||||
NodeKey,
|
NodeKey,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import {ElementNode} from 'lexical';
|
import {ElementNode} from 'lexical';
|
||||||
|
import {Spread} from 'libdefs/globals';
|
||||||
|
|
||||||
export type InitialEditorStateType = null | string | EditorState | (() => void);
|
export type InitialEditorStateType = null | string | EditorState | (() => void);
|
||||||
|
|
||||||
export declare class QuoteNode extends ElementNode {
|
export declare class QuoteNode extends ElementNode {
|
||||||
@ -25,6 +29,7 @@ export declare class QuoteNode extends ElementNode {
|
|||||||
updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean;
|
updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean;
|
||||||
insertNewAfter(): ParagraphNode;
|
insertNewAfter(): ParagraphNode;
|
||||||
collapseAtStart(): true;
|
collapseAtStart(): true;
|
||||||
|
importJSON(serializedNode: SerializedQuoteNode): QuoteNode;
|
||||||
}
|
}
|
||||||
export function $createQuoteNode(): QuoteNode;
|
export function $createQuoteNode(): QuoteNode;
|
||||||
export function $isQuoteNode(
|
export function $isQuoteNode(
|
||||||
@ -42,7 +47,9 @@ export declare class HeadingNode extends ElementNode {
|
|||||||
static importDOM(): DOMConversionMap | null;
|
static importDOM(): DOMConversionMap | null;
|
||||||
insertNewAfter(): ParagraphNode;
|
insertNewAfter(): ParagraphNode;
|
||||||
collapseAtStart(): true;
|
collapseAtStart(): true;
|
||||||
|
importJSON(serializedNode: SerializedHeadingNode): QuoteNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode;
|
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode;
|
||||||
export function $isHeadingNode(
|
export function $isHeadingNode(
|
||||||
node: LexicalNode | null | undefined,
|
node: LexicalNode | null | undefined,
|
||||||
@ -51,3 +58,20 @@ export function registerRichText(
|
|||||||
editor: LexicalEditor,
|
editor: LexicalEditor,
|
||||||
initialEditorState?: InitialEditorStateType,
|
initialEditorState?: InitialEditorStateType,
|
||||||
): () => void;
|
): () => void;
|
||||||
|
|
||||||
|
export type SerializedHeadingNode = Spread<
|
||||||
|
{
|
||||||
|
tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
|
||||||
|
type: 'heading';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SerializedQuoteNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'quote';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
@ -16,6 +16,7 @@ import type {
|
|||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
|
SerializedElementNode,
|
||||||
TextFormatType,
|
TextFormatType,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ import {
|
|||||||
addClassNamesToElement,
|
addClassNamesToElement,
|
||||||
mergeRegister,
|
mergeRegister,
|
||||||
} from '@lexical/utils';
|
} from '@lexical/utils';
|
||||||
|
import {Spread} from 'globals';
|
||||||
import {
|
import {
|
||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
@ -72,6 +74,23 @@ import {CAN_USE_BEFORE_INPUT, IS_IOS, IS_SAFARI} from 'shared-ts/environment';
|
|||||||
|
|
||||||
export type InitialEditorStateType = null | string | EditorState | (() => void);
|
export type InitialEditorStateType = null | string | EditorState | (() => void);
|
||||||
|
|
||||||
|
export type SerializedHeadingNode = Spread<
|
||||||
|
{
|
||||||
|
tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
|
||||||
|
type: 'heading';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SerializedQuoteNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'quote';
|
||||||
|
version: 1;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
// Convoluted logic to make this work with Flow. Order matters.
|
// Convoluted logic to make this work with Flow. Order matters.
|
||||||
const options = {tag: 'history-merge'};
|
const options = {tag: 'history-merge'};
|
||||||
const setEditorOptions: {
|
const setEditorOptions: {
|
||||||
@ -107,6 +126,21 @@ export class QuoteNode extends ElementNode {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedQuoteNode): QuoteNode {
|
||||||
|
const node = $createQuoteNode();
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'quote',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation
|
// Mutation
|
||||||
|
|
||||||
insertNewAfter(): ParagraphNode {
|
insertNewAfter(): ParagraphNode {
|
||||||
@ -202,6 +236,23 @@ export class HeadingNode extends ElementNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedHeadingNode): HeadingNode {
|
||||||
|
const node = $createHeadingNode(serializedNode.tag);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedHeadingNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
tag: this.__tag,
|
||||||
|
type: 'heading',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation
|
// Mutation
|
||||||
|
|
||||||
insertNewAfter(): ParagraphNode {
|
insertNewAfter(): ParagraphNode {
|
||||||
|
@ -15,6 +15,8 @@ import type {
|
|||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedElementNode,
|
||||||
|
SerializedGridCellNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {addClassNamesToElement} from '@lexical/utils';
|
import {addClassNamesToElement} from '@lexical/utils';
|
||||||
@ -34,6 +36,14 @@ export const TableCellHeaderStates = {
|
|||||||
|
|
||||||
export type TableCellHeaderState = $Values<typeof TableCellHeaderStates>;
|
export type TableCellHeaderState = $Values<typeof TableCellHeaderStates>;
|
||||||
|
|
||||||
|
export type SerializedTableCellNode = {
|
||||||
|
...SerializedGridCellNode,
|
||||||
|
headerState: TableCellHeaderState,
|
||||||
|
type: 'tablecell',
|
||||||
|
width: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class TableCellNode extends GridCellNode {
|
export class TableCellNode extends GridCellNode {
|
||||||
__headerState: TableCellHeaderState;
|
__headerState: TableCellHeaderState;
|
||||||
__width: ?number;
|
__width: ?number;
|
||||||
@ -64,6 +74,14 @@ export class TableCellNode extends GridCellNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedTableCellNode): TableCellNode {
|
||||||
|
return $createTableCellNode(
|
||||||
|
serializedNode.headerState,
|
||||||
|
serializedNode.colSpan,
|
||||||
|
serializedNode.width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
headerState?: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
|
headerState?: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
|
||||||
colSpan?: number = 1,
|
colSpan?: number = 1,
|
||||||
@ -115,6 +133,15 @@ export class TableCellNode extends GridCellNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
headerState: this.__headerState,
|
||||||
|
width: this.getWidth(),
|
||||||
|
type: 'tablecell',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getTag(): string {
|
getTag(): string {
|
||||||
return this.hasHeader() ? 'th' : 'td';
|
return this.hasHeader() ? 'th' : 'td';
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import type {
|
|||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {addClassNamesToElement} from '@lexical/utils';
|
import {addClassNamesToElement} from '@lexical/utils';
|
||||||
@ -27,6 +28,13 @@ import {$isTableCellNode} from './LexicalTableCellNode';
|
|||||||
import {$isTableRowNode} from './LexicalTableRowNode';
|
import {$isTableRowNode} from './LexicalTableRowNode';
|
||||||
import {getTableGrid} from './LexicalTableSelectionHelpers';
|
import {getTableGrid} from './LexicalTableSelectionHelpers';
|
||||||
|
|
||||||
|
export type SerializedTableNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'table',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class TableNode extends GridNode {
|
export class TableNode extends GridNode {
|
||||||
__grid: ?Grid;
|
__grid: ?Grid;
|
||||||
|
|
||||||
@ -47,10 +55,22 @@ export class TableNode extends GridNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedTableNode): TableNode {
|
||||||
|
return $createTableNode();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(key?: NodeKey): void {
|
constructor(key?: NodeKey): void {
|
||||||
super(key);
|
super(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'table',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
|
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
|
||||||
const tableElement = document.createElement('table');
|
const tableElement = document.createElement('table');
|
||||||
|
|
||||||
|
@ -13,11 +13,20 @@ import type {
|
|||||||
EditorConfig,
|
EditorConfig,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {addClassNamesToElement} from '@lexical/utils';
|
import {addClassNamesToElement} from '@lexical/utils';
|
||||||
import {GridRowNode} from 'lexical';
|
import {GridRowNode} from 'lexical';
|
||||||
|
|
||||||
|
export type SerializedTableRowNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
height: number,
|
||||||
|
type: 'tablerow',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class TableRowNode extends GridRowNode {
|
export class TableRowNode extends GridRowNode {
|
||||||
__height: ?number;
|
__height: ?number;
|
||||||
|
|
||||||
@ -38,11 +47,23 @@ export class TableRowNode extends GridRowNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedTableRowNode): TableRowNode {
|
||||||
|
return $createTableRowNode(serializedNode.height);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(height?: ?number, key?: NodeKey): void {
|
constructor(height?: ?number, key?: NodeKey): void {
|
||||||
super(key);
|
super(key);
|
||||||
this.__height = height;
|
this.__height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'tablerow',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const element = document.createElement('tr');
|
const element = document.createElement('tr');
|
||||||
|
|
||||||
|
71
packages/lexical/Lexical.d.ts
vendored
71
packages/lexical/Lexical.d.ts
vendored
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Class, $ReadOnly} from 'utility-types';
|
import {Class, $ReadOnly} from 'utility-types';
|
||||||
|
import {Spread} from 'globals';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LexicalCommands
|
* LexicalCommands
|
||||||
@ -161,6 +162,10 @@ export declare class LexicalEditor {
|
|||||||
parseEditorState(
|
parseEditorState(
|
||||||
maybeStringifiedEditorState: string | ParsedEditorState,
|
maybeStringifiedEditorState: string | ParsedEditorState,
|
||||||
): EditorState;
|
): EditorState;
|
||||||
|
unstable_parseEditorState(
|
||||||
|
maybeStringifiedEditorState: string | SerializedEditorState,
|
||||||
|
updateFn?: () => void,
|
||||||
|
): EditorState;
|
||||||
update(updateFn: () => void, options?: EditorUpdateOptions): boolean;
|
update(updateFn: () => void, options?: EditorUpdateOptions): boolean;
|
||||||
focus(callbackFn?: () => void): void;
|
focus(callbackFn?: () => void): void;
|
||||||
blur(): void;
|
blur(): void;
|
||||||
@ -291,6 +296,7 @@ export interface EditorState {
|
|||||||
isEmpty(): boolean;
|
isEmpty(): boolean;
|
||||||
read<V>(callbackFn: () => V): V;
|
read<V>(callbackFn: () => V): V;
|
||||||
toJSON(space?: string | number): JSONEditorState;
|
toJSON(space?: string | number): JSONEditorState;
|
||||||
|
unstable_toJSON(): SerializedEditorState;
|
||||||
clone(
|
clone(
|
||||||
selection?: RangeSelection | NodeSelection | GridSelection | null,
|
selection?: RangeSelection | NodeSelection | GridSelection | null,
|
||||||
): EditorState;
|
): EditorState;
|
||||||
@ -605,7 +611,10 @@ export declare class TextNode extends LexicalNode {
|
|||||||
toggleFormat(type: TextFormatType): TextNode;
|
toggleFormat(type: TextFormatType): TextNode;
|
||||||
toggleDirectionless(): TextNode;
|
toggleDirectionless(): TextNode;
|
||||||
toggleUnmergeable(): TextNode;
|
toggleUnmergeable(): TextNode;
|
||||||
setMode(type: TextModeType): TextNode;
|
setMode(type: TextModeType): this;
|
||||||
|
setDetail(detail: number): TextNode;
|
||||||
|
getDetail(): number;
|
||||||
|
getMode(): TextModeType;
|
||||||
setTextContent(text: string): TextNode;
|
setTextContent(text: string): TextNode;
|
||||||
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
|
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
|
||||||
spliceText(
|
spliceText(
|
||||||
@ -619,6 +628,8 @@ export declare class TextNode extends LexicalNode {
|
|||||||
splitText(...splitOffsets: Array<number>): Array<TextNode>;
|
splitText(...splitOffsets: Array<number>): Array<TextNode>;
|
||||||
mergeWithSibling(target: TextNode): TextNode;
|
mergeWithSibling(target: TextNode): TextNode;
|
||||||
isTextEntity(): boolean;
|
isTextEntity(): boolean;
|
||||||
|
static importJSON(serializedTextNode: SerializedTextNode): TextNode;
|
||||||
|
exportJSON(): SerializedTextNode;
|
||||||
}
|
}
|
||||||
export function $createTextNode(text?: string): TextNode;
|
export function $createTextNode(text?: string): TextNode;
|
||||||
export function $isTextNode(
|
export function $isTextNode(
|
||||||
@ -635,6 +646,10 @@ export declare class LineBreakNode extends LexicalNode {
|
|||||||
getTextContent(): '\n';
|
getTextContent(): '\n';
|
||||||
createDOM(): HTMLElement;
|
createDOM(): HTMLElement;
|
||||||
updateDOM(): false;
|
updateDOM(): false;
|
||||||
|
static importJSON(
|
||||||
|
serializedLineBreakNode: SerializedLexicalNode,
|
||||||
|
): LineBreakNode;
|
||||||
|
exportJSON(): SerializedLexicalNode;
|
||||||
}
|
}
|
||||||
export function $createLineBreakNode(): LineBreakNode;
|
export function $createLineBreakNode(): LineBreakNode;
|
||||||
export function $isLineBreakNode(
|
export function $isLineBreakNode(
|
||||||
@ -658,6 +673,8 @@ export declare class RootNode extends ElementNode {
|
|||||||
updateDOM(prevNode: RootNode, dom: HTMLElement): false;
|
updateDOM(prevNode: RootNode, dom: HTMLElement): false;
|
||||||
append(...nodesToAppend: Array<LexicalNode>): ElementNode;
|
append(...nodesToAppend: Array<LexicalNode>): ElementNode;
|
||||||
canBeEmpty(): false;
|
canBeEmpty(): false;
|
||||||
|
static importJSON(serializedRootNode: SerializedRootNode): RootNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
export function $isRootNode(
|
export function $isRootNode(
|
||||||
node: LexicalNode | null | undefined,
|
node: LexicalNode | null | undefined,
|
||||||
@ -674,6 +691,7 @@ export declare class ElementNode extends LexicalNode {
|
|||||||
__dir: 'ltr' | 'rtl' | null;
|
__dir: 'ltr' | 'rtl' | null;
|
||||||
constructor(key?: NodeKey);
|
constructor(key?: NodeKey);
|
||||||
getFormat(): number;
|
getFormat(): number;
|
||||||
|
getFormatType(): 'left' | 'center' | 'right' | 'justify';
|
||||||
getIndent(): number;
|
getIndent(): number;
|
||||||
getChildren<T extends Array<LexicalNode>>(): T;
|
getChildren<T extends Array<LexicalNode>>(): T;
|
||||||
getChildrenKeys(): Array<NodeKey>;
|
getChildrenKeys(): Array<NodeKey>;
|
||||||
@ -722,6 +740,7 @@ export declare class ElementNode extends LexicalNode {
|
|||||||
deleteCount: number,
|
deleteCount: number,
|
||||||
nodesToInsert: Array<LexicalNode>,
|
nodesToInsert: Array<LexicalNode>,
|
||||||
): ElementNode;
|
): ElementNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
export function $isElementNode(
|
export function $isElementNode(
|
||||||
node: LexicalNode | null | undefined,
|
node: LexicalNode | null | undefined,
|
||||||
@ -751,6 +770,10 @@ export declare class ParagraphNode extends ElementNode {
|
|||||||
updateDOM(prevNode: ParagraphNode, dom: HTMLElement): boolean;
|
updateDOM(prevNode: ParagraphNode, dom: HTMLElement): boolean;
|
||||||
insertNewAfter(): ParagraphNode;
|
insertNewAfter(): ParagraphNode;
|
||||||
collapseAtStart(): boolean;
|
collapseAtStart(): boolean;
|
||||||
|
static importJSON(
|
||||||
|
serializedParagraphNode: SerializedElementNode,
|
||||||
|
): ParagraphNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
export function $createParagraphNode(): ParagraphNode;
|
export function $createParagraphNode(): ParagraphNode;
|
||||||
export function $isParagraphNode(
|
export function $isParagraphNode(
|
||||||
@ -797,3 +820,49 @@ export function $getDecoratorNode(
|
|||||||
* LexicalVersion
|
* LexicalVersion
|
||||||
*/
|
*/
|
||||||
export declare var VERSION: string;
|
export declare var VERSION: string;
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
|
||||||
|
export type SerializedLexicalNode = {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedTextNode = Spread<
|
||||||
|
{
|
||||||
|
detail: number;
|
||||||
|
format: number;
|
||||||
|
mode: TextModeType;
|
||||||
|
style: string;
|
||||||
|
text: string;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SerializedElementNode = Spread<
|
||||||
|
{
|
||||||
|
children: Array<SerializedLexicalNode>;
|
||||||
|
direction: 'ltr' | 'rtl' | null;
|
||||||
|
format: 'left' | 'center' | 'right' | 'justify';
|
||||||
|
indent: number;
|
||||||
|
},
|
||||||
|
SerializedLexicalNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SerializedRootNode = Spread<
|
||||||
|
{
|
||||||
|
type: 'root';
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SerializedGridCellNode = Spread<
|
||||||
|
{
|
||||||
|
colSpan: number;
|
||||||
|
},
|
||||||
|
SerializedElementNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface SerializedEditorState {
|
||||||
|
root: SerializedRootNode;
|
||||||
|
}
|
||||||
|
@ -167,6 +167,10 @@ declare export class LexicalEditor {
|
|||||||
parseEditorState(
|
parseEditorState(
|
||||||
maybeStringifiedEditorState: string | ParsedEditorState,
|
maybeStringifiedEditorState: string | ParsedEditorState,
|
||||||
): EditorState;
|
): EditorState;
|
||||||
|
unstable_parseEditorState<SerializedNode>(
|
||||||
|
maybeStringifiedEditorState: string | SerializedEditorState<SerializedNode>,
|
||||||
|
updateFn?: () => void,
|
||||||
|
): EditorState;
|
||||||
update(updateFn: () => void, options?: EditorUpdateOptions): boolean;
|
update(updateFn: () => void, options?: EditorUpdateOptions): boolean;
|
||||||
focus(callbackFn?: () => void): void;
|
focus(callbackFn?: () => void): void;
|
||||||
blur(): void;
|
blur(): void;
|
||||||
@ -292,6 +296,7 @@ export interface EditorState {
|
|||||||
isEmpty(): boolean;
|
isEmpty(): boolean;
|
||||||
read<V>(callbackFn: () => V): V;
|
read<V>(callbackFn: () => V): V;
|
||||||
toJSON(space?: string | number): JSONEditorState;
|
toJSON(space?: string | number): JSONEditorState;
|
||||||
|
unstable_toJSON<SerializedNode>(): SerializedEditorState<SerializedNode>;
|
||||||
clone(
|
clone(
|
||||||
selection?: RangeSelection | NodeSelection | GridSelection | null,
|
selection?: RangeSelection | NodeSelection | GridSelection | null,
|
||||||
): EditorState;
|
): EditorState;
|
||||||
@ -631,6 +636,9 @@ declare export class TextNode extends LexicalNode {
|
|||||||
toggleDirectionless(): this;
|
toggleDirectionless(): this;
|
||||||
toggleUnmergeable(): this;
|
toggleUnmergeable(): this;
|
||||||
setMode(type: TextModeType): this;
|
setMode(type: TextModeType): this;
|
||||||
|
setDetail(detail: number): this;
|
||||||
|
getDetail(): number;
|
||||||
|
getMode(): TextModeType;
|
||||||
setTextContent(text: string): TextNode;
|
setTextContent(text: string): TextNode;
|
||||||
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
|
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
|
||||||
spliceText(
|
spliceText(
|
||||||
@ -644,6 +652,8 @@ declare export class TextNode extends LexicalNode {
|
|||||||
splitText(...splitOffsets: Array<number>): Array<TextNode>;
|
splitText(...splitOffsets: Array<number>): Array<TextNode>;
|
||||||
mergeWithSibling(target: TextNode): TextNode;
|
mergeWithSibling(target: TextNode): TextNode;
|
||||||
isTextEntity(): boolean;
|
isTextEntity(): boolean;
|
||||||
|
static importJSON(serializedTextNode: SerializedTextNode): TextNode;
|
||||||
|
exportJSON(): SerializedTextNode;
|
||||||
}
|
}
|
||||||
declare export function $createTextNode(text?: string): TextNode;
|
declare export function $createTextNode(text?: string): TextNode;
|
||||||
declare export function $isTextNode(
|
declare export function $isTextNode(
|
||||||
@ -661,6 +671,10 @@ declare export class LineBreakNode extends LexicalNode {
|
|||||||
getTextContent(): '\n';
|
getTextContent(): '\n';
|
||||||
createDOM(): HTMLElement;
|
createDOM(): HTMLElement;
|
||||||
updateDOM(): false;
|
updateDOM(): false;
|
||||||
|
static importJSON(
|
||||||
|
serializedLineBreakNode: SerializedLineBreakNode,
|
||||||
|
): LineBreakNode;
|
||||||
|
exportJSON(): SerializedLexicalNode;
|
||||||
}
|
}
|
||||||
declare export function $createLineBreakNode(): LineBreakNode;
|
declare export function $createLineBreakNode(): LineBreakNode;
|
||||||
declare export function $isLineBreakNode(
|
declare export function $isLineBreakNode(
|
||||||
@ -693,7 +707,7 @@ declare export function $isRootNode(
|
|||||||
/**
|
/**
|
||||||
* LexicalElementNode
|
* LexicalElementNode
|
||||||
*/
|
*/
|
||||||
export type ElementFormatType = 'left' | 'center' | 'right' | 'justify';
|
export type ElementFormatType = 'left' | 'center' | 'right' | 'justify' | '';
|
||||||
declare export class ElementNode extends LexicalNode {
|
declare export class ElementNode extends LexicalNode {
|
||||||
__children: Array<NodeKey>;
|
__children: Array<NodeKey>;
|
||||||
__format: number;
|
__format: number;
|
||||||
@ -701,6 +715,7 @@ declare export class ElementNode extends LexicalNode {
|
|||||||
__dir: 'ltr' | 'rtl' | null;
|
__dir: 'ltr' | 'rtl' | null;
|
||||||
constructor(key?: NodeKey): void;
|
constructor(key?: NodeKey): void;
|
||||||
getFormat(): number;
|
getFormat(): number;
|
||||||
|
getFormatType(): ElementFormatType;
|
||||||
getIndent(): number;
|
getIndent(): number;
|
||||||
getChildren<T: Array<LexicalNode>>(): T;
|
getChildren<T: Array<LexicalNode>>(): T;
|
||||||
getChildrenKeys(): Array<NodeKey>;
|
getChildrenKeys(): Array<NodeKey>;
|
||||||
@ -749,6 +764,7 @@ declare export class ElementNode extends LexicalNode {
|
|||||||
deleteCount: number,
|
deleteCount: number,
|
||||||
nodesToInsert: Array<LexicalNode>,
|
nodesToInsert: Array<LexicalNode>,
|
||||||
): ElementNode;
|
): ElementNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
declare export function $isElementNode(
|
declare export function $isElementNode(
|
||||||
node: ?LexicalNode,
|
node: ?LexicalNode,
|
||||||
@ -779,6 +795,10 @@ declare export class ParagraphNode extends ElementNode {
|
|||||||
updateDOM(prevNode: ParagraphNode, dom: HTMLElement): boolean;
|
updateDOM(prevNode: ParagraphNode, dom: HTMLElement): boolean;
|
||||||
insertNewAfter(): ParagraphNode;
|
insertNewAfter(): ParagraphNode;
|
||||||
collapseAtStart(): boolean;
|
collapseAtStart(): boolean;
|
||||||
|
static importJSON(
|
||||||
|
serializedParagraphNode: SerializedParagraphNode,
|
||||||
|
): ParagraphNode;
|
||||||
|
exportJSON(): SerializedElementNode;
|
||||||
}
|
}
|
||||||
declare export function $createParagraphNode(): ParagraphNode;
|
declare export function $createParagraphNode(): ParagraphNode;
|
||||||
declare export function $isParagraphNode(
|
declare export function $isParagraphNode(
|
||||||
@ -843,3 +863,58 @@ export type EventHandler = (
|
|||||||
* LexicalVersion
|
* LexicalVersion
|
||||||
*/
|
*/
|
||||||
declare export var VERSION: string;
|
declare export var VERSION: string;
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
|
||||||
|
export type SerializedLexicalNode = {
|
||||||
|
type: string,
|
||||||
|
version: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedTextNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
detail: number,
|
||||||
|
format: number,
|
||||||
|
mode: TextModeType,
|
||||||
|
style: string,
|
||||||
|
text: string,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedElementNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
children: Array<SerializedLexicalNode>,
|
||||||
|
direction: 'ltr' | 'rtl' | null,
|
||||||
|
format: ElementFormatType,
|
||||||
|
indent: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedParagraphNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'paragraph',
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedLineBreakNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
type: 'linebreak',
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedRootNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'root',
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SerializedGridCellNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
colSpan: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SerializedEditorState<SerializedNode> {
|
||||||
|
root: SerializedRootNode<SerializedNode>;
|
||||||
|
}
|
||||||
|
@ -85,9 +85,23 @@ export const ELEMENT_TYPE_TO_FORMAT: {[ElementFormatType]: number} = {
|
|||||||
right: IS_ALIGN_RIGHT,
|
right: IS_ALIGN_RIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ELEMENT_FORMAT_TO_TYPE: {[number]: ElementFormatType} = {
|
||||||
|
[IS_ALIGN_CENTER]: 'center',
|
||||||
|
[IS_ALIGN_JUSTIFY]: 'justify',
|
||||||
|
[IS_ALIGN_LEFT]: 'left',
|
||||||
|
[IS_ALIGN_RIGHT]: 'right',
|
||||||
|
};
|
||||||
|
|
||||||
export const TEXT_MODE_TO_TYPE: {[TextModeType]: 0 | 1 | 2 | 3} = {
|
export const TEXT_MODE_TO_TYPE: {[TextModeType]: 0 | 1 | 2 | 3} = {
|
||||||
inert: IS_INERT,
|
inert: IS_INERT,
|
||||||
normal: IS_NORMAL,
|
normal: IS_NORMAL,
|
||||||
segmented: IS_SEGMENTED,
|
segmented: IS_SEGMENTED,
|
||||||
token: IS_TOKEN,
|
token: IS_TOKEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TEXT_TYPE_TO_MODE: {[number]: TextModeType} = {
|
||||||
|
[IS_INERT]: 'inert',
|
||||||
|
[IS_NORMAL]: 'normal',
|
||||||
|
[IS_SEGMENTED]: 'segmented',
|
||||||
|
[IS_TOKEN]: 'token',
|
||||||
|
};
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {EditorState, ParsedEditorState} from './LexicalEditorState';
|
import type {
|
||||||
|
EditorState,
|
||||||
|
ParsedEditorState,
|
||||||
|
SerializedEditorState,
|
||||||
|
} from './LexicalEditorState';
|
||||||
import type {DOMConversion, LexicalNode, NodeKey} from './LexicalNode';
|
import type {DOMConversion, LexicalNode, NodeKey} from './LexicalNode';
|
||||||
|
|
||||||
import getDOMSelection from 'shared/getDOMSelection';
|
import getDOMSelection from 'shared/getDOMSelection';
|
||||||
@ -22,6 +26,7 @@ import {
|
|||||||
commitPendingUpdates,
|
commitPendingUpdates,
|
||||||
parseEditorState,
|
parseEditorState,
|
||||||
triggerListeners,
|
triggerListeners,
|
||||||
|
unstable_parseEditorState,
|
||||||
updateEditor,
|
updateEditor,
|
||||||
} from './LexicalUpdates';
|
} from './LexicalUpdates';
|
||||||
import {
|
import {
|
||||||
@ -575,6 +580,7 @@ export class LexicalEditor {
|
|||||||
}
|
}
|
||||||
commitPendingUpdates(this);
|
commitPendingUpdates(this);
|
||||||
}
|
}
|
||||||
|
// TODO: once unstable_parseEditorState is stable, swap that for this.
|
||||||
parseEditorState(
|
parseEditorState(
|
||||||
maybeStringifiedEditorState: string | ParsedEditorState,
|
maybeStringifiedEditorState: string | ParsedEditorState,
|
||||||
): EditorState {
|
): EditorState {
|
||||||
@ -584,6 +590,16 @@ export class LexicalEditor {
|
|||||||
: maybeStringifiedEditorState;
|
: maybeStringifiedEditorState;
|
||||||
return parseEditorState(parsedEditorState, this);
|
return parseEditorState(parsedEditorState, this);
|
||||||
}
|
}
|
||||||
|
unstable_parseEditorState(
|
||||||
|
maybeStringifiedEditorState: string | SerializedEditorState,
|
||||||
|
updateFn?: () => void,
|
||||||
|
): EditorState {
|
||||||
|
const serializedEditorState =
|
||||||
|
typeof maybeStringifiedEditorState === 'string'
|
||||||
|
? JSON.parse(maybeStringifiedEditorState)
|
||||||
|
: maybeStringifiedEditorState;
|
||||||
|
return unstable_parseEditorState(serializedEditorState, this, updateFn);
|
||||||
|
}
|
||||||
update(updateFn: () => void, options?: EditorUpdateOptions): void {
|
update(updateFn: () => void, options?: EditorUpdateOptions): void {
|
||||||
updateEditor(this, updateFn, options);
|
updateEditor(this, updateFn, options);
|
||||||
}
|
}
|
||||||
|
@ -15,25 +15,34 @@ import type {
|
|||||||
NodeSelection,
|
NodeSelection,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
} from './LexicalSelection';
|
} from './LexicalSelection';
|
||||||
|
import type {SerializedRootNode} from './nodes/LexicalRootNode';
|
||||||
|
|
||||||
|
import invariant from '../../shared/src/invariant';
|
||||||
|
import {$isElementNode} from '.';
|
||||||
import {
|
import {
|
||||||
$isGridSelection,
|
$isGridSelection,
|
||||||
$isNodeSelection,
|
$isNodeSelection,
|
||||||
$isRangeSelection,
|
$isRangeSelection,
|
||||||
} from './LexicalSelection';
|
} from './LexicalSelection';
|
||||||
import {readEditorState} from './LexicalUpdates';
|
import {readEditorState} from './LexicalUpdates';
|
||||||
|
import {$getRoot} from './LexicalUtils';
|
||||||
import {$createRootNode} from './nodes/LexicalRootNode';
|
import {$createRootNode} from './nodes/LexicalRootNode';
|
||||||
|
|
||||||
|
// TODO: deprecated
|
||||||
export type ParsedEditorState = {
|
export type ParsedEditorState = {
|
||||||
_nodeMap: Array<[NodeKey, ParsedNode]>,
|
_nodeMap: Array<[NodeKey, ParsedNode]>,
|
||||||
_selection: null | ParsedSelection,
|
_selection: null | ParsedSelection,
|
||||||
};
|
};
|
||||||
|
// TODO: deprecated
|
||||||
export type JSONEditorState = {
|
export type JSONEditorState = {
|
||||||
_nodeMap: Array<[NodeKey, LexicalNode]>,
|
_nodeMap: Array<[NodeKey, LexicalNode]>,
|
||||||
_selection: null | ParsedSelection,
|
_selection: null | ParsedSelection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface SerializedEditorState {
|
||||||
|
root: SerializedRootNode;
|
||||||
|
}
|
||||||
|
|
||||||
export function editorStateHasDirtySelection(
|
export function editorStateHasDirtySelection(
|
||||||
editorState: EditorState,
|
editorState: EditorState,
|
||||||
editor: LexicalEditor,
|
editor: LexicalEditor,
|
||||||
@ -59,6 +68,35 @@ export function createEmptyEditorState(): EditorState {
|
|||||||
return new EditorState(new Map([['root', $createRootNode()]]));
|
return new EditorState(new Map([['root', $createRootNode()]]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportNodeToJSON<SerializedNode>(node: LexicalNode): SerializedNode {
|
||||||
|
const serializedNode = node.exportJSON();
|
||||||
|
const nodeClass = node.constructor;
|
||||||
|
if (serializedNode.type !== nodeClass.getType()) {
|
||||||
|
invariant(
|
||||||
|
false,
|
||||||
|
'LexicalNode: Node %s does not implement .exportJSON().',
|
||||||
|
nodeClass.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const children = node.getChildren();
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
const serializedChildNode = exportNodeToJSON(child);
|
||||||
|
serializedChildren.push(serializedChildNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serializedNode;
|
||||||
|
}
|
||||||
|
|
||||||
export class EditorState {
|
export class EditorState {
|
||||||
_nodeMap: NodeMap;
|
_nodeMap: NodeMap;
|
||||||
_selection: null | RangeSelection | NodeSelection | GridSelection;
|
_selection: null | RangeSelection | NodeSelection | GridSelection;
|
||||||
@ -90,6 +128,7 @@ export class EditorState {
|
|||||||
editorState._readOnly = true;
|
editorState._readOnly = true;
|
||||||
return editorState;
|
return editorState;
|
||||||
}
|
}
|
||||||
|
// TODO: remove when we use the other toJSON
|
||||||
toJSON(space?: string | number): JSONEditorState {
|
toJSON(space?: string | number): JSONEditorState {
|
||||||
const selection = this._selection;
|
const selection = this._selection;
|
||||||
|
|
||||||
@ -132,4 +171,9 @@ export class EditorState {
|
|||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
unstable_toJSON(): SerializedEditorState {
|
||||||
|
return readEditorState(this, () => ({
|
||||||
|
root: exportNodeToJSON($getRoot()),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,12 @@ import {
|
|||||||
|
|
||||||
export type NodeMap = Map<NodeKey, LexicalNode>;
|
export type NodeMap = Map<NodeKey, LexicalNode>;
|
||||||
|
|
||||||
|
export type SerializedLexicalNode = {
|
||||||
|
type: string,
|
||||||
|
version: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export function removeNode(
|
export function removeNode(
|
||||||
nodeToRemove: LexicalNode,
|
nodeToRemove: LexicalNode,
|
||||||
restoreSelection: boolean,
|
restoreSelection: boolean,
|
||||||
@ -600,6 +606,22 @@ export class LexicalNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe: Intentional work around for Flow
|
||||||
|
exportJSON(): Object {
|
||||||
|
invariant(false, 'exportJSON: base method not extended');
|
||||||
|
}
|
||||||
|
|
||||||
|
static importJSON(
|
||||||
|
// $FlowFixMe: Intentional work around for Flow
|
||||||
|
serializedNode: Object,
|
||||||
|
): LexicalNode {
|
||||||
|
invariant(
|
||||||
|
false,
|
||||||
|
'LexicalNode: Node %s does not implement .importJSON().',
|
||||||
|
this.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Setters and mutators
|
// Setters and mutators
|
||||||
|
|
||||||
remove(preserveEmptyParent?: boolean): void {
|
remove(preserveEmptyParent?: boolean): void {
|
||||||
|
@ -12,19 +12,24 @@ import type {
|
|||||||
LexicalCommand,
|
LexicalCommand,
|
||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
MutatedNodes,
|
MutatedNodes,
|
||||||
|
RegisteredNodes,
|
||||||
Transform,
|
Transform,
|
||||||
} from './LexicalEditor';
|
} from './LexicalEditor';
|
||||||
import type {ParsedEditorState} from './LexicalEditorState';
|
import type {
|
||||||
|
ParsedEditorState,
|
||||||
|
SerializedEditorState,
|
||||||
|
} from './LexicalEditorState';
|
||||||
import type {LexicalNode} from './LexicalNode';
|
import type {LexicalNode} from './LexicalNode';
|
||||||
import type {NodeParserState, ParsedNode} from './LexicalParsing';
|
import type {NodeParserState, ParsedNode} from './LexicalParsing';
|
||||||
|
|
||||||
import invariant from 'shared/invariant';
|
import invariant from 'shared/invariant';
|
||||||
|
|
||||||
import {$isTextNode} from '.';
|
import {$isElementNode, $isTextNode} from '.';
|
||||||
import {FULL_RECONCILE, NO_DIRTY_NODES} from './LexicalConstants';
|
import {FULL_RECONCILE, NO_DIRTY_NODES} from './LexicalConstants';
|
||||||
import {resetEditor} from './LexicalEditor';
|
import {resetEditor} from './LexicalEditor';
|
||||||
import {
|
import {
|
||||||
cloneEditorState,
|
cloneEditorState,
|
||||||
|
createEmptyEditorState,
|
||||||
EditorState,
|
EditorState,
|
||||||
editorStateHasDirtySelection,
|
editorStateHasDirtySelection,
|
||||||
} from './LexicalEditorState';
|
} from './LexicalEditorState';
|
||||||
@ -242,6 +247,7 @@ function $applyAllTransforms(
|
|||||||
editor._dirtyElements = dirtyElements;
|
editor._dirtyElements = dirtyElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: once unstable_parseEditorState is stable, swap that for this.
|
||||||
export function parseEditorState(
|
export function parseEditorState(
|
||||||
parsedEditorState: ParsedEditorState,
|
parsedEditorState: ParsedEditorState,
|
||||||
editor: LexicalEditor,
|
editor: LexicalEditor,
|
||||||
@ -279,6 +285,77 @@ export function parseEditorState(
|
|||||||
return editorState;
|
return editorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InternalSerializedNode = {
|
||||||
|
children?: Array<InternalSerializedNode>,
|
||||||
|
type: string,
|
||||||
|
version: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseSerializedNode<SerializedNode: InternalSerializedNode>(
|
||||||
|
serializedNode: SerializedNode,
|
||||||
|
registeredNodes: RegisteredNodes,
|
||||||
|
): LexicalNode {
|
||||||
|
const type = serializedNode.type;
|
||||||
|
const registeredNode = registeredNodes.get(type);
|
||||||
|
if (registeredNode === undefined) {
|
||||||
|
invariant(false, 'parseEditorState: type "%s" + not found', type);
|
||||||
|
}
|
||||||
|
const nodeClass = registeredNode.klass;
|
||||||
|
if (serializedNode.type !== nodeClass.getType()) {
|
||||||
|
invariant(
|
||||||
|
false,
|
||||||
|
'LexicalNode: Node %s does not implement .importJSON().',
|
||||||
|
nodeClass.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const node = nodeClass.importJSON(serializedNode);
|
||||||
|
const children = serializedNode.children;
|
||||||
|
if ($isElementNode(node) && Array.isArray(children)) {
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const serializedJSONChildNode = children[i];
|
||||||
|
const childNode = parseSerializedNode(
|
||||||
|
serializedJSONChildNode,
|
||||||
|
registeredNodes,
|
||||||
|
);
|
||||||
|
node.append(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unstable_parseEditorState(
|
||||||
|
serializedEditorState: SerializedEditorState,
|
||||||
|
editor: LexicalEditor,
|
||||||
|
updateFn: void | (() => void),
|
||||||
|
): EditorState {
|
||||||
|
const editorState = createEmptyEditorState();
|
||||||
|
const previousActiveEditorState = activeEditorState;
|
||||||
|
const previousReadOnlyMode = isReadOnlyMode;
|
||||||
|
const previousActiveEditor = activeEditor;
|
||||||
|
activeEditorState = editorState;
|
||||||
|
isReadOnlyMode = false;
|
||||||
|
activeEditor = editor;
|
||||||
|
try {
|
||||||
|
const registeredNodes = editor._nodes;
|
||||||
|
// $FlowFixMe: intentional cast to our internal type
|
||||||
|
const serializedNode: InternalSerializedNode = serializedEditorState.root;
|
||||||
|
parseSerializedNode(serializedNode, registeredNodes);
|
||||||
|
if (updateFn) {
|
||||||
|
updateFn();
|
||||||
|
}
|
||||||
|
// Make the editorState immutable
|
||||||
|
editorState._readOnly = true;
|
||||||
|
if (__DEV__) {
|
||||||
|
handleDEVOnlyPendingUpdateGuarantees(editorState);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
activeEditorState = previousActiveEditorState;
|
||||||
|
isReadOnlyMode = previousReadOnlyMode;
|
||||||
|
activeEditor = previousActiveEditor;
|
||||||
|
}
|
||||||
|
return editorState;
|
||||||
|
}
|
||||||
|
|
||||||
// This technically isn't an update but given we need
|
// This technically isn't an update but given we need
|
||||||
// exposure to the module's active bindings, we have this
|
// exposure to the module's active bindings, we have this
|
||||||
// function here
|
// function here
|
||||||
|
@ -1006,6 +1006,26 @@ describe('LexicalEditor tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('exportJSON API - parses parsed JSON', async () => {
|
||||||
|
await update(() => {
|
||||||
|
const paragraph = $createParagraphNode();
|
||||||
|
originalText = $createTextNode('Hello world');
|
||||||
|
originalText.select(6, 11);
|
||||||
|
paragraph.append(originalText);
|
||||||
|
$getRoot().append(paragraph);
|
||||||
|
});
|
||||||
|
const stringifiedEditorState = JSON.stringify(
|
||||||
|
editor.getEditorState().unstable_toJSON(),
|
||||||
|
);
|
||||||
|
const parsedEditorStateFromObject = editor.unstable_parseEditorState(
|
||||||
|
JSON.parse(stringifiedEditorState),
|
||||||
|
);
|
||||||
|
parsedEditorStateFromObject.read(() => {
|
||||||
|
const root = $getRoot();
|
||||||
|
expect(root.getTextContent()).toMatch(/Hello world/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('range selection', () => {
|
describe('range selection', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
init();
|
init();
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
*
|
*
|
||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
import type {NodeKey} from '../LexicalNode';
|
import type {NodeKey, SerializedLexicalNode} from '../LexicalNode';
|
||||||
import type {
|
import type {
|
||||||
GridSelection,
|
GridSelection,
|
||||||
NodeSelection,
|
NodeSelection,
|
||||||
@ -17,7 +17,11 @@ import type {
|
|||||||
import invariant from 'shared/invariant';
|
import invariant from 'shared/invariant';
|
||||||
|
|
||||||
import {$isRootNode, $isTextNode, TextNode} from '../';
|
import {$isRootNode, $isTextNode, TextNode} from '../';
|
||||||
import {DOUBLE_LINE_BREAK, ELEMENT_TYPE_TO_FORMAT} from '../LexicalConstants';
|
import {
|
||||||
|
DOUBLE_LINE_BREAK,
|
||||||
|
ELEMENT_FORMAT_TO_TYPE,
|
||||||
|
ELEMENT_TYPE_TO_FORMAT,
|
||||||
|
} from '../LexicalConstants';
|
||||||
import {LexicalNode} from '../LexicalNode';
|
import {LexicalNode} from '../LexicalNode';
|
||||||
import {
|
import {
|
||||||
$getSelection,
|
$getSelection,
|
||||||
@ -32,7 +36,16 @@ import {
|
|||||||
removeFromParent,
|
removeFromParent,
|
||||||
} from '../LexicalUtils';
|
} from '../LexicalUtils';
|
||||||
|
|
||||||
export type ElementFormatType = 'left' | 'center' | 'right' | 'justify';
|
export type SerializedElementNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
children: Array<SerializedLexicalNode>,
|
||||||
|
direction: 'ltr' | 'rtl' | null,
|
||||||
|
format: 'left' | 'center' | 'right' | 'justify' | '',
|
||||||
|
indent: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ElementFormatType = 'left' | 'center' | 'right' | 'justify' | '';
|
||||||
|
|
||||||
export class ElementNode extends LexicalNode {
|
export class ElementNode extends LexicalNode {
|
||||||
__children: Array<NodeKey>;
|
__children: Array<NodeKey>;
|
||||||
@ -52,6 +65,10 @@ export class ElementNode extends LexicalNode {
|
|||||||
const self = this.getLatest();
|
const self = this.getLatest();
|
||||||
return self.__format;
|
return self.__format;
|
||||||
}
|
}
|
||||||
|
getFormatType(): 'left' | 'center' | 'right' | 'justify' | '' {
|
||||||
|
const format = this.getFormat();
|
||||||
|
return ELEMENT_FORMAT_TO_TYPE[format] || '';
|
||||||
|
}
|
||||||
getIndent(): number {
|
getIndent(): number {
|
||||||
const self = this.getLatest();
|
const self = this.getLatest();
|
||||||
return self.__indent;
|
return self.__indent;
|
||||||
@ -283,7 +300,7 @@ export class ElementNode extends LexicalNode {
|
|||||||
setFormat(type: ElementFormatType): this {
|
setFormat(type: ElementFormatType): this {
|
||||||
errorOnReadOnly();
|
errorOnReadOnly();
|
||||||
const self = this.getWritable();
|
const self = this.getWritable();
|
||||||
self.__format = ELEMENT_TYPE_TO_FORMAT[type];
|
self.__format = ELEMENT_TYPE_TO_FORMAT[type] || 0;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
setIndent(indentLevel: number): this {
|
setIndent(indentLevel: number): this {
|
||||||
@ -410,6 +427,17 @@ export class ElementNode extends LexicalNode {
|
|||||||
|
|
||||||
return writableSelf;
|
return writableSelf;
|
||||||
}
|
}
|
||||||
|
// JSON serialization
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
children: [],
|
||||||
|
direction: this.getDirection(),
|
||||||
|
format: this.getFormatType(),
|
||||||
|
indent: this.getIndent(),
|
||||||
|
type: 'element',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
// These are intended to be extends for specific element heuristics.
|
// These are intended to be extends for specific element heuristics.
|
||||||
insertNewAfter(selection: RangeSelection): null | LexicalNode {
|
insertNewAfter(selection: RangeSelection): null | LexicalNode {
|
||||||
return null;
|
return null;
|
||||||
|
@ -7,16 +7,29 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {LexicalNode, NodeKey} from 'lexical';
|
import type {LexicalNode, NodeKey, SerializedElementNode} from 'lexical';
|
||||||
|
|
||||||
import {ElementNode} from './LexicalElementNode';
|
import {ElementNode} from './LexicalElementNode';
|
||||||
|
|
||||||
|
export type SerializedGridCellNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
colSpan: number,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class GridCellNode extends ElementNode {
|
export class GridCellNode extends ElementNode {
|
||||||
__colSpan: number;
|
__colSpan: number;
|
||||||
|
|
||||||
constructor(colSpan: number, key?: NodeKey) {
|
constructor(colSpan: number, key?: NodeKey) {
|
||||||
super(key);
|
super(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
colSpan: this.__colSpan,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $isGridCellNode(node: ?LexicalNode): boolean %checks {
|
export function $isGridCellNode(node: ?LexicalNode): boolean %checks {
|
||||||
|
@ -11,10 +11,17 @@ import type {
|
|||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput,
|
DOMConversionOutput,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
} from '../LexicalNode';
|
} from '../LexicalNode';
|
||||||
|
|
||||||
import {LexicalNode} from '../LexicalNode';
|
import {LexicalNode} from '../LexicalNode';
|
||||||
|
|
||||||
|
export type SerializedLineBreakNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
type: 'linebreak',
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class LineBreakNode extends LexicalNode {
|
export class LineBreakNode extends LexicalNode {
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'linebreak';
|
return 'linebreak';
|
||||||
@ -48,6 +55,19 @@ export class LineBreakNode extends LexicalNode {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(
|
||||||
|
serializedLineBreakNode: SerializedLineBreakNode,
|
||||||
|
): LineBreakNode {
|
||||||
|
return $createLineBreakNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedLexicalNode {
|
||||||
|
return {
|
||||||
|
type: 'linebreak',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertLineBreakElement(node: Node): DOMConversionOutput {
|
function convertLineBreakElement(node: Node): DOMConversionOutput {
|
||||||
|
@ -17,11 +17,19 @@ import type {
|
|||||||
DOMExportOutput,
|
DOMExportOutput,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
} from '../LexicalNode';
|
} from '../LexicalNode';
|
||||||
|
import type {SerializedElementNode} from './LexicalElementNode';
|
||||||
|
|
||||||
import {getCachedClassNameArray} from '../LexicalUtils';
|
import {getCachedClassNameArray} from '../LexicalUtils';
|
||||||
import {ElementNode} from './LexicalElementNode';
|
import {ElementNode} from './LexicalElementNode';
|
||||||
import {$isTextNode} from './LexicalTextNode';
|
import {$isTextNode} from './LexicalTextNode';
|
||||||
|
|
||||||
|
export type SerializedParagraphNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class ParagraphNode extends ElementNode {
|
export class ParagraphNode extends ElementNode {
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'paragraph';
|
return 'paragraph';
|
||||||
@ -72,6 +80,22 @@ export class ParagraphNode extends ElementNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode {
|
||||||
|
const node = $createParagraphNode();
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedElementNode {
|
||||||
|
return {
|
||||||
|
...super.exportJSON(),
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation
|
// Mutation
|
||||||
|
|
||||||
insertNewAfter(): ParagraphNode {
|
insertNewAfter(): ParagraphNode {
|
||||||
|
@ -9,14 +9,21 @@
|
|||||||
|
|
||||||
import type {LexicalNode} from '../LexicalNode';
|
import type {LexicalNode} from '../LexicalNode';
|
||||||
import type {ParsedElementNode} from '../LexicalParsing';
|
import type {ParsedElementNode} from '../LexicalParsing';
|
||||||
|
import type {SerializedElementNode} from './LexicalElementNode';
|
||||||
|
|
||||||
import invariant from 'shared/invariant';
|
import invariant from 'shared/invariant';
|
||||||
|
|
||||||
import {NO_DIRTY_NODES} from '../LexicalConstants';
|
import {NO_DIRTY_NODES} from '../LexicalConstants';
|
||||||
import {getActiveEditor, isCurrentlyReadOnlyMode} from '../LexicalUpdates';
|
import {getActiveEditor, isCurrentlyReadOnlyMode} from '../LexicalUpdates';
|
||||||
|
import {$getRoot} from '../LexicalUtils';
|
||||||
import {$isDecoratorNode} from './LexicalDecoratorNode';
|
import {$isDecoratorNode} from './LexicalDecoratorNode';
|
||||||
import {$isElementNode, ElementNode} from './LexicalElementNode';
|
import {$isElementNode, ElementNode} from './LexicalElementNode';
|
||||||
|
|
||||||
|
export type SerializedRootNode = {
|
||||||
|
...SerializedElementNode,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export class RootNode extends ElementNode {
|
export class RootNode extends ElementNode {
|
||||||
__cachedText: null | string;
|
__cachedText: null | string;
|
||||||
|
|
||||||
@ -93,6 +100,26 @@ export class RootNode extends ElementNode {
|
|||||||
return super.append(...nodesToAppend);
|
return super.append(...nodesToAppend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedRootNode): RootNode {
|
||||||
|
// We don't create a root, and instead use the existing root.
|
||||||
|
const node = $getRoot();
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setIndent(serializedNode.indent);
|
||||||
|
node.setDirection(serializedNode.direction);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedRootNode {
|
||||||
|
return {
|
||||||
|
children: [],
|
||||||
|
direction: this.getDirection(),
|
||||||
|
format: this.getFormatType(),
|
||||||
|
indent: this.getIndent(),
|
||||||
|
type: 'root',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// TODO: Deprecated
|
||||||
toJSON(): ParsedElementNode {
|
toJSON(): ParsedElementNode {
|
||||||
return {
|
return {
|
||||||
__children: this.__children,
|
__children: this.__children,
|
||||||
|
@ -12,6 +12,7 @@ import type {
|
|||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput,
|
DOMConversionOutput,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
|
SerializedLexicalNode,
|
||||||
} from '../LexicalNode';
|
} from '../LexicalNode';
|
||||||
import type {
|
import type {
|
||||||
GridSelection,
|
GridSelection,
|
||||||
@ -37,6 +38,7 @@ import {
|
|||||||
IS_UNMERGEABLE,
|
IS_UNMERGEABLE,
|
||||||
TEXT_MODE_TO_TYPE,
|
TEXT_MODE_TO_TYPE,
|
||||||
TEXT_TYPE_TO_FORMAT,
|
TEXT_TYPE_TO_FORMAT,
|
||||||
|
TEXT_TYPE_TO_MODE,
|
||||||
} from '../LexicalConstants';
|
} from '../LexicalConstants';
|
||||||
import {LexicalNode} from '../LexicalNode';
|
import {LexicalNode} from '../LexicalNode';
|
||||||
import {
|
import {
|
||||||
@ -55,6 +57,16 @@ import {
|
|||||||
toggleTextFormatType,
|
toggleTextFormatType,
|
||||||
} from '../LexicalUtils';
|
} from '../LexicalUtils';
|
||||||
|
|
||||||
|
export type SerializedTextNode = {
|
||||||
|
...SerializedLexicalNode,
|
||||||
|
detail: number,
|
||||||
|
format: number,
|
||||||
|
mode: TextModeType,
|
||||||
|
style: string,
|
||||||
|
text: string,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
export type TextFormatType =
|
export type TextFormatType =
|
||||||
| 'bold'
|
| 'bold'
|
||||||
| 'underline'
|
| 'underline'
|
||||||
@ -264,6 +276,16 @@ export class TextNode extends LexicalNode {
|
|||||||
return self.__format;
|
return self.__format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDetail(): number {
|
||||||
|
const self = this.getLatest();
|
||||||
|
return self.__detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMode(): TextModeType {
|
||||||
|
const self = this.getLatest();
|
||||||
|
return TEXT_TYPE_TO_MODE[self.__mode];
|
||||||
|
}
|
||||||
|
|
||||||
getStyle(): string {
|
getStyle(): string {
|
||||||
const self = this.getLatest();
|
const self = this.getLatest();
|
||||||
return self.__style;
|
return self.__style;
|
||||||
@ -447,6 +469,27 @@ export class TextNode extends LexicalNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static importJSON(serializedNode: SerializedTextNode): TextNode {
|
||||||
|
const node = $createTextNode(serializedNode.text);
|
||||||
|
node.setFormat(serializedNode.format);
|
||||||
|
node.setDetail(serializedNode.detail);
|
||||||
|
node.setMode(serializedNode.mode);
|
||||||
|
node.setStyle(serializedNode.style);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportJSON(): SerializedTextNode {
|
||||||
|
return {
|
||||||
|
detail: this.getDetail(),
|
||||||
|
format: this.getFormat(),
|
||||||
|
mode: this.getMode(),
|
||||||
|
style: this.getStyle(),
|
||||||
|
text: this.getTextContent(),
|
||||||
|
type: 'text',
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Mutators
|
// Mutators
|
||||||
selectionTransform(
|
selectionTransform(
|
||||||
prevSelection: null | RangeSelection | NodeSelection | GridSelection,
|
prevSelection: null | RangeSelection | NodeSelection | GridSelection,
|
||||||
@ -460,6 +503,13 @@ export class TextNode extends LexicalNode {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDetail(detail: number): this {
|
||||||
|
errorOnReadOnly();
|
||||||
|
const self = this.getWritable();
|
||||||
|
self.__detail = detail;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
setStyle(style: string): this {
|
setStyle(style: string): this {
|
||||||
errorOnReadOnly();
|
errorOnReadOnly();
|
||||||
const self = this.getWritable();
|
const self = this.getWritable();
|
||||||
|
@ -82,6 +82,26 @@ describe('LexicalElementNode tests', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('exportJSON()', () => {
|
||||||
|
test('should return and object conforming to the expected schema', async () => {
|
||||||
|
await update(() => {
|
||||||
|
const node = $createTestElementNode();
|
||||||
|
// 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.
|
||||||
|
expect(node.exportJSON()).toStrictEqual({
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'element',
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getChildren()', () => {
|
describe('getChildren()', () => {
|
||||||
test('no children', async () => {
|
test('no children', async () => {
|
||||||
await update(() => {
|
await update(() => {
|
||||||
|
@ -25,6 +25,21 @@ describe('LexicalLineBreakNode tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('LineBreakNode.exportJSON() should return and object conforming to the expected schema', async () => {
|
||||||
|
const {editor} = testEnv;
|
||||||
|
await editor.update(() => {
|
||||||
|
const node = $createLineBreakNode();
|
||||||
|
// 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.
|
||||||
|
expect(node.exportJSON()).toStrictEqual({
|
||||||
|
type: 'linebreak',
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('LineBreakNode.createDOM()', async () => {
|
test('LineBreakNode.createDOM()', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {initializeUnitTest} from '../../__tests__/utils';
|
import {initializeUnitTest} from '../../../__tests__/utils';
|
||||||
|
|
||||||
const editorConfig = Object.freeze({
|
const editorConfig = Object.freeze({
|
||||||
theme: {
|
theme: {
|
||||||
@ -37,6 +37,25 @@ describe('LexicalParagraphNode tests', () => {
|
|||||||
expect(() => new ParagraphNode()).toThrow();
|
expect(() => new ParagraphNode()).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ParagraphNode.exportJSON() should return and object conforming to the expected schema', async () => {
|
||||||
|
const {editor} = testEnv;
|
||||||
|
await editor.update(() => {
|
||||||
|
const node = $createParagraphNode();
|
||||||
|
// 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.
|
||||||
|
expect(node.exportJSON()).toStrictEqual({
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('ParagraphNode.createDOM()', async () => {
|
test('ParagraphNode.createDOM()', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
@ -58,6 +58,25 @@ describe('LexicalRootNode tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('RootNode.exportJSON() should return and object conforming to the expected schema', async () => {
|
||||||
|
const {editor} = testEnv;
|
||||||
|
await editor.update(() => {
|
||||||
|
const node = $createRootNode();
|
||||||
|
// 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.
|
||||||
|
expect(node.exportJSON()).toStrictEqual({
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'root',
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('RootNode.clone()', async () => {
|
test('RootNode.clone()', async () => {
|
||||||
const rootNodeClone = rootNode.constructor.clone();
|
const rootNodeClone = rootNode.constructor.clone();
|
||||||
expect(rootNodeClone).not.toBe(rootNode);
|
expect(rootNodeClone).not.toBe(rootNode);
|
||||||
|
@ -108,6 +108,27 @@ describe('LexicalTextNode tests', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('exportJSON()', () => {
|
||||||
|
test('should return and object conforming to the expected schema', async () => {
|
||||||
|
await update(() => {
|
||||||
|
const node = $createTextNode();
|
||||||
|
// 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.
|
||||||
|
expect(node.exportJSON()).toStrictEqual({
|
||||||
|
detail: 0,
|
||||||
|
format: 0,
|
||||||
|
mode: 'normal',
|
||||||
|
style: '',
|
||||||
|
text: '',
|
||||||
|
type: 'text',
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('root.getTextContent()', () => {
|
describe('root.getTextContent()', () => {
|
||||||
test('writable nodes', async () => {
|
test('writable nodes', async () => {
|
||||||
let nodeKey;
|
let nodeKey;
|
||||||
|
@ -191,7 +191,7 @@ async function build(name, inputFile, outputFile, isProd) {
|
|||||||
{
|
{
|
||||||
transform(source) {
|
transform(source) {
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
extractCodes && findAndRecordErrorCodes(source);
|
extractCodes && findAndRecordErrorCodes(source, isTypeScript);
|
||||||
return source;
|
return source;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -68,5 +68,11 @@
|
|||||||
"66": "createNode: node does not exist in nodeMap",
|
"66": "createNode: node does not exist in nodeMap",
|
||||||
"67": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
|
"67": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
|
||||||
"68": "reconcileNode: parentDOM is null",
|
"68": "reconcileNode: parentDOM is null",
|
||||||
"69": "Reconciliation: could not find DOM element for node key \"${key}\""
|
"69": "Reconciliation: could not find DOM element for node key \"${key}\"",
|
||||||
|
"70": "Incorrect node type received in importJSON for %s",
|
||||||
|
"71": "LexicalNode: Node %s does not implement .exportJSON().",
|
||||||
|
"72": "LexicalNode: Node %s is an element but .exportJSON() does not have a children array.",
|
||||||
|
"73": "parseEditorState: type \"%s\" + not found",
|
||||||
|
"74": "LexicalNode: Node %s does not implement .importJSON().",
|
||||||
|
"75": "exportJSON: base method not extended"
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,19 @@ const traverse = require('@babel/traverse').default;
|
|||||||
const evalToString = require('./evalToString');
|
const evalToString = require('./evalToString');
|
||||||
const invertObject = require('./invertObject');
|
const invertObject = require('./invertObject');
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
'classProperties',
|
||||||
|
'jsx',
|
||||||
|
'trailingFunctionCommas',
|
||||||
|
'objectRestSpread',
|
||||||
|
];
|
||||||
|
|
||||||
const babylonOptions = {
|
const babylonOptions = {
|
||||||
// As a parser, babylon has its own options and we can't directly
|
// As a parser, babylon has its own options and we can't directly
|
||||||
// import/require a babel preset. It should be kept **the same** as
|
// import/require a babel preset. It should be kept **the same** as
|
||||||
// the `babel-plugin-syntax-*` ones specified in
|
// the `babel-plugin-syntax-*` ones specified in
|
||||||
// https://github.com/facebook/fbjs/blob/master/packages/babel-preset-fbjs/configure.js
|
// https://github.com/facebook/fbjs/blob/master/packages/babel-preset-fbjs/configure.js
|
||||||
plugins: [
|
plugins,
|
||||||
'classProperties',
|
|
||||||
'flow',
|
|
||||||
'jsx',
|
|
||||||
'trailingFunctionCommas',
|
|
||||||
'objectRestSpread',
|
|
||||||
],
|
|
||||||
|
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -99,7 +99,13 @@ module.exports = function (opts) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function extractErrors(source) {
|
return function extractErrors(source, isTypeScript) {
|
||||||
|
if (isTypeScript) {
|
||||||
|
babylonOptions.plugins = [...plugins, 'typescript'];
|
||||||
|
} else {
|
||||||
|
babylonOptions.plugins = [...plugins, 'flow'];
|
||||||
|
}
|
||||||
|
|
||||||
transform(source);
|
transform(source);
|
||||||
flush();
|
flush();
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user