From 89ff98b84f878e935133b04efa973ce5c54aac84 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 11:51:58 +0100 Subject: [PATCH 1/7] Provide a way of marking markdown plugin accept node depednencies --- .../lexical-markdown/src/MarkdownShortcuts.ts | 17 +++++++++++- .../src/MarkdownTransformers.ts | 27 ++++++++++++++++--- packages/lexical/flow/Lexical.js.flow | 2 ++ packages/lexical/src/index.ts | 5 +++- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/lexical-markdown/src/MarkdownShortcuts.ts b/packages/lexical-markdown/src/MarkdownShortcuts.ts index 6aa4ba68441..9b95f5c49d5 100644 --- a/packages/lexical-markdown/src/MarkdownShortcuts.ts +++ b/packages/lexical-markdown/src/MarkdownShortcuts.ts @@ -17,6 +17,7 @@ import type {ElementNode, LexicalEditor, TextNode} from 'lexical'; import {$isCodeNode} from '@lexical/code'; import { $createRangeSelection, + $getEditor, $getSelection, $isLineBreakNode, $isRangeSelection, @@ -54,8 +55,14 @@ function runElementTransformers( if (textContent[anchorOffset - 1] !== ' ') { return false; } + const editor = $getEditor(); - for (const {regExp, replace} of elementTransformers) { + for (const {dependencies, regExp, replace} of elementTransformers) { + if (dependencies !== undefined) { + if (!editor.hasNodes(dependencies)) { + continue; + } + } const match = textContent.match(regExp); if (match && match[0].length === anchorOffset) { @@ -86,6 +93,8 @@ function runTextMatchTransformers( return false; } + const editor = $getEditor(); + // If typing in the middle of content, remove the tail to do // reg exp match up to a string end (caret position) if (anchorOffset < textContent.length) { @@ -93,6 +102,12 @@ function runTextMatchTransformers( } for (const transformer of transformers) { + const dependencies = transformer.dependencies; + if (dependencies !== undefined) { + if (!editor.hasNodes(dependencies)) { + continue; + } + } const match = textContent.match(transformer.regExp); if (match === null) { diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index 7ead2d45737..abdccd6ec3e 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -6,23 +6,33 @@ * */ -import type {ListNode, ListType} from '@lexical/list'; +import type {ListType} from '@lexical/list'; import type {HeadingTagType} from '@lexical/rich-text'; -import type {ElementNode, LexicalNode, TextFormatType, TextNode} from 'lexical'; +import type { + ElementNode, + Klass, + LexicalNode, + TextFormatType, + TextNode, +} from 'lexical'; -import {$createCodeNode, $isCodeNode} from '@lexical/code'; -import {$createLinkNode, $isLinkNode} from '@lexical/link'; +import {$createCodeNode, $isCodeNode, CodeNode} from '@lexical/code'; +import {$createLinkNode, $isLinkNode, LinkNode} from '@lexical/link'; import { $createListItemNode, $createListNode, $isListItemNode, $isListNode, + ListItemNode, + ListNode, } from '@lexical/list'; import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, + HeadingNode, + QuoteNode, } from '@lexical/rich-text'; import {$createLineBreakNode, $createTextNode, $isTextNode} from 'lexical'; @@ -32,6 +42,7 @@ export type Transformer = | TextMatchTransformer; export type ElementTransformer = { + dependencies?: Array>; export: ( node: LexicalNode, // eslint-disable-next-line no-shadow @@ -55,6 +66,7 @@ export type TextFormatTransformer = Readonly<{ }>; export type TextMatchTransformer = Readonly<{ + dependencies?: Array>; export: ( node: LexicalNode, // eslint-disable-next-line no-shadow @@ -144,6 +156,7 @@ const listExport = ( }; export const HEADING: ElementTransformer = { + dependencies: [HeadingNode], export: (node, exportChildren) => { if (!$isHeadingNode(node)) { return null; @@ -160,6 +173,7 @@ export const HEADING: ElementTransformer = { }; export const QUOTE: ElementTransformer = { + dependencies: [QuoteNode], export: (node, exportChildren) => { if (!$isQuoteNode(node)) { return null; @@ -196,6 +210,7 @@ export const QUOTE: ElementTransformer = { }; export const CODE: ElementTransformer = { + dependencies: [CodeNode], export: (node: LexicalNode) => { if (!$isCodeNode(node)) { return null; @@ -217,6 +232,7 @@ export const CODE: ElementTransformer = { }; export const UNORDERED_LIST: ElementTransformer = { + dependencies: [ListNode, ListItemNode], export: (node, exportChildren) => { return $isListNode(node) ? listExport(node, exportChildren, 0) : null; }, @@ -226,6 +242,7 @@ export const UNORDERED_LIST: ElementTransformer = { }; export const CHECK_LIST: ElementTransformer = { + dependencies: [ListNode, ListItemNode], export: (node, exportChildren) => { return $isListNode(node) ? listExport(node, exportChildren, 0) : null; }, @@ -235,6 +252,7 @@ export const CHECK_LIST: ElementTransformer = { }; export const ORDERED_LIST: ElementTransformer = { + dependencies: [ListNode, ListItemNode], export: (node, exportChildren) => { return $isListNode(node) ? listExport(node, exportChildren, 0) : null; }, @@ -299,6 +317,7 @@ export const ITALIC_UNDERSCORE: TextFormatTransformer = { // - code should go first as it prevents any transformations inside // - then longer tags match (e.g. ** or __ should go before * or _) export const LINK: TextMatchTransformer = { + dependencies: [LinkNode], export: (node, exportChildren, exportFormat) => { if (!$isLinkNode(node)) { return null; diff --git a/packages/lexical/flow/Lexical.js.flow b/packages/lexical/flow/Lexical.js.flow index e02ee756d02..8bd71a9833c 100644 --- a/packages/lexical/flow/Lexical.js.flow +++ b/packages/lexical/flow/Lexical.js.flow @@ -835,6 +835,8 @@ type InternalSerializedNode = { version: number, }; +declare export function $getEditor(): LexicalEditor; + declare export function $parseSerializedNode( serializedNode: InternalSerializedNode, ): LexicalNode; diff --git a/packages/lexical/src/index.ts b/packages/lexical/src/index.ts index c699d7279da..4f015ba4629 100644 --- a/packages/lexical/src/index.ts +++ b/packages/lexical/src/index.ts @@ -121,7 +121,10 @@ export { $isNodeSelection, $isRangeSelection, } from './LexicalSelection'; -export {$parseSerializedNode} from './LexicalUpdates'; +export { + getActiveEditor as $getEditor, + $parseSerializedNode, +} from './LexicalUpdates'; export { $getDecoratorNode, $getNearestNodeFromDOMNode, From 8e84f5da04f348db78dced56b2ffd624b1dc3d47 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 14:14:01 +0100 Subject: [PATCH 2/7] Fix flow types --- packages/lexical-markdown/flow/LexicalMarkdown.js.flow | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow index ae13ca650e3..83b0d08b12d 100644 --- a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow +++ b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow @@ -21,6 +21,7 @@ export type Transformer = | TextMatchTransformer; export type ElementTransformer = { + dependencies?: Array>; export: ( node: LexicalNode, traverseChildren: (node: ElementNode) => string, @@ -43,6 +44,7 @@ export type TextFormatTransformer = $ReadOnly<{ }>; export type TextMatchTransformer = $ReadOnly<{ + dependencies?: Array>; export: ( node: LexicalNode, exportChildren: (node: ElementNode) => string, From 2a77b94476ad1dc79cab63cdfd408c33b0516aed Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 14:18:12 +0100 Subject: [PATCH 3/7] Fix missing deps --- packages/lexical-markdown/flow/LexicalMarkdown.js.flow | 4 ++-- packages/lexical-markdown/src/MarkdownTransformers.ts | 4 ++-- packages/lexical-react/src/LexicalMarkdownShortcutPlugin.tsx | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow index 83b0d08b12d..5196c253433 100644 --- a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow +++ b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow @@ -21,7 +21,7 @@ export type Transformer = | TextMatchTransformer; export type ElementTransformer = { - dependencies?: Array>; + dependencies: Array>; export: ( node: LexicalNode, traverseChildren: (node: ElementNode) => string, @@ -44,7 +44,7 @@ export type TextFormatTransformer = $ReadOnly<{ }>; export type TextMatchTransformer = $ReadOnly<{ - dependencies?: Array>; + dependencies: Array>; export: ( node: LexicalNode, exportChildren: (node: ElementNode) => string, diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index abdccd6ec3e..1e65203802b 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -42,7 +42,7 @@ export type Transformer = | TextMatchTransformer; export type ElementTransformer = { - dependencies?: Array>; + dependencies: Array>; export: ( node: LexicalNode, // eslint-disable-next-line no-shadow @@ -66,7 +66,7 @@ export type TextFormatTransformer = Readonly<{ }>; export type TextMatchTransformer = Readonly<{ - dependencies?: Array>; + dependencies: Array>; export: ( node: LexicalNode, // eslint-disable-next-line no-shadow diff --git a/packages/lexical-react/src/LexicalMarkdownShortcutPlugin.tsx b/packages/lexical-react/src/LexicalMarkdownShortcutPlugin.tsx index b4d805c96c6..0ea861a3321 100644 --- a/packages/lexical-react/src/LexicalMarkdownShortcutPlugin.tsx +++ b/packages/lexical-react/src/LexicalMarkdownShortcutPlugin.tsx @@ -14,10 +14,12 @@ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import { $createHorizontalRuleNode, $isHorizontalRuleNode, + HorizontalRuleNode, } from '@lexical/react/LexicalHorizontalRuleNode'; import {useEffect} from 'react'; const HR: ElementTransformer = { + dependencies: [HorizontalRuleNode], export: (node: LexicalNode) => { return $isHorizontalRuleNode(node) ? '***' : null; }, From 1b3ecd1d20f026fa83b0937739800b8e2b1d66bf Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 14:23:32 +0100 Subject: [PATCH 4/7] Fix missing deps --- .../src/plugins/MarkdownTransformers/index.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts b/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts index 1a349e2f4d5..26d6f88a7c5 100644 --- a/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts +++ b/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts @@ -22,6 +22,7 @@ import { import { $createHorizontalRuleNode, $isHorizontalRuleNode, + HorizontalRuleNode, } from '@lexical/react/LexicalHorizontalRuleNode'; import { $createTableCellNode, @@ -32,6 +33,7 @@ import { TableCellHeaderStates, TableCellNode, TableNode, + TableRowNode, } from '@lexical/table'; import { $createParagraphNode, @@ -41,11 +43,12 @@ import { $isTextNode, } from 'lexical'; -import {$createEquationNode, $isEquationNode} from '../../nodes/EquationNode'; -import {$createImageNode, $isImageNode} from '../../nodes/ImageNode'; -import {$createTweetNode, $isTweetNode} from '../../nodes/TweetNode'; +import {$createEquationNode, $isEquationNode, EquationNode} from '../../nodes/EquationNode'; +import {$createImageNode, $isImageNode, ImageNode} from '../../nodes/ImageNode'; +import {$createTweetNode, $isTweetNode, TweetNode} from '../../nodes/TweetNode'; export const HR: ElementTransformer = { + dependencies: [HorizontalRuleNode], export: (node: LexicalNode) => { return $isHorizontalRuleNode(node) ? '***' : null; }, @@ -66,6 +69,7 @@ export const HR: ElementTransformer = { }; export const IMAGE: TextMatchTransformer = { + dependencies: [ImageNode], export: (node, exportChildren, exportFormat) => { if (!$isImageNode(node)) { return null; @@ -89,6 +93,7 @@ export const IMAGE: TextMatchTransformer = { }; export const EQUATION: TextMatchTransformer = { + dependencies: [EquationNode], export: (node, exportChildren, exportFormat) => { if (!$isEquationNode(node)) { return null; @@ -108,6 +113,7 @@ export const EQUATION: TextMatchTransformer = { }; export const TWEET: ElementTransformer = { + dependencies: [TweetNode], export: (node) => { if (!$isTweetNode(node)) { return null; @@ -128,6 +134,7 @@ export const TWEET: ElementTransformer = { const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/; export const TABLE: ElementTransformer = { + dependencies: [TableNode, TableRowNode, TableCellNode], export: ( node: LexicalNode, exportChildren: (elementNode: ElementNode) => string, From c053a0fb22caee4d10c0272350939a9d7b04c7e5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 14:34:27 +0100 Subject: [PATCH 5/7] Prettier --- .../src/plugins/MarkdownTransformers/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts b/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts index 26d6f88a7c5..18c001b12fe 100644 --- a/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts +++ b/packages/lexical-playground/src/plugins/MarkdownTransformers/index.ts @@ -43,7 +43,11 @@ import { $isTextNode, } from 'lexical'; -import {$createEquationNode, $isEquationNode, EquationNode} from '../../nodes/EquationNode'; +import { + $createEquationNode, + $isEquationNode, + EquationNode, +} from '../../nodes/EquationNode'; import {$createImageNode, $isImageNode, ImageNode} from '../../nodes/ImageNode'; import {$createTweetNode, $isTweetNode, TweetNode} from '../../nodes/TweetNode'; From 5d43310f19106ceacccd62706de8b7ae7af977dd Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 15:14:47 +0100 Subject: [PATCH 6/7] Change logic --- .../lexical-markdown/src/MarkdownShortcuts.ts | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/lexical-markdown/src/MarkdownShortcuts.ts b/packages/lexical-markdown/src/MarkdownShortcuts.ts index 9b95f5c49d5..9d142e0cdd7 100644 --- a/packages/lexical-markdown/src/MarkdownShortcuts.ts +++ b/packages/lexical-markdown/src/MarkdownShortcuts.ts @@ -17,7 +17,6 @@ import type {ElementNode, LexicalEditor, TextNode} from 'lexical'; import {$isCodeNode} from '@lexical/code'; import { $createRangeSelection, - $getEditor, $getSelection, $isLineBreakNode, $isRangeSelection, @@ -25,6 +24,7 @@ import { $isTextNode, $setSelection, } from 'lexical'; +import invariant from 'shared/invariant'; import {TRANSFORMERS} from '.'; import {indexBy, PUNCTUATION_OR_SPACE, transformersByType} from './utils'; @@ -55,14 +55,8 @@ function runElementTransformers( if (textContent[anchorOffset - 1] !== ' ') { return false; } - const editor = $getEditor(); - for (const {dependencies, regExp, replace} of elementTransformers) { - if (dependencies !== undefined) { - if (!editor.hasNodes(dependencies)) { - continue; - } - } + for (const {regExp, replace} of elementTransformers) { const match = textContent.match(regExp); if (match && match[0].length === anchorOffset) { @@ -93,8 +87,6 @@ function runTextMatchTransformers( return false; } - const editor = $getEditor(); - // If typing in the middle of content, remove the tail to do // reg exp match up to a string end (caret position) if (anchorOffset < textContent.length) { @@ -102,12 +94,6 @@ function runTextMatchTransformers( } for (const transformer of transformers) { - const dependencies = transformer.dependencies; - if (dependencies !== undefined) { - if (!editor.hasNodes(dependencies)) { - continue; - } - } const match = textContent.match(transformer.regExp); if (match === null) { @@ -348,6 +334,19 @@ export function registerMarkdownShortcuts( ({trigger}) => trigger, ); + for (const transformer of transformers) { + const type = transformer.type; + if (type === 'element' || type === 'text-match') { + const dependencies = transformer.dependencies; + if (!editor.hasNodes(dependencies)) { + invariant( + false, + 'MarkdownShortcuts: missing dependency for transformer. Ensure node dependency is included in editor initial config.', + ); + } + } + } + const transform = ( parentNode: ElementNode, anchorNode: TextNode, From 3abff0695018c006231614e7995208af08d14e16 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 30 Aug 2022 15:21:53 +0100 Subject: [PATCH 7/7] Fix prettier --- packages/lexical-markdown/flow/LexicalMarkdown.js.flow | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow index 5196c253433..39d642578f4 100644 --- a/packages/lexical-markdown/flow/LexicalMarkdown.js.flow +++ b/packages/lexical-markdown/flow/LexicalMarkdown.js.flow @@ -21,7 +21,7 @@ export type Transformer = | TextMatchTransformer; export type ElementTransformer = { - dependencies: Array>; + dependencies: Array>, export: ( node: LexicalNode, traverseChildren: (node: ElementNode) => string, @@ -44,7 +44,7 @@ export type TextFormatTransformer = $ReadOnly<{ }>; export type TextMatchTransformer = $ReadOnly<{ - dependencies: Array>; + dependencies: Array>, export: ( node: LexicalNode, exportChildren: (node: ElementNode) => string,