Use lexical-remark for Lexical<->Markdown conversions

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-07-18 11:58:32 +02:00
parent 6a172f525f
commit 3c5025c7f3
5 changed files with 1544 additions and 146 deletions

View File

@ -6,7 +6,6 @@ Copyright (c) Meta Platforms, Inc. and affiliates.
This source code is licensed under the MIT license found in the
LICENSE file in the /app/soapbox/features/compose/editor directory.
*/
import { $convertFromMarkdownString, $convertToMarkdownString } from '@lexical/markdown';
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
import { AutoLinkPlugin, createLinkMatcherWithRegExp } from '@lexical/react/LexicalAutoLinkPlugin';
import { LexicalComposer, InitialConfigType } from '@lexical/react/LexicalComposer';
@ -20,6 +19,7 @@ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import clsx from 'clsx';
import { $createParagraphNode, $createTextNode, $getRoot } from 'lexical';
import { $createRemarkExport, $createRemarkImport } from 'lexical-remark';
import React, { useMemo, useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -39,7 +39,6 @@ import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin';
import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin';
import MentionPlugin from './plugins/mention-plugin';
import StatePlugin from './plugins/state-plugin';
import { TO_WYSIWYG_TRANSFORMERS } from './transformers';
interface IComposeEditor {
className?: string
@ -107,7 +106,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
return function() {
if (compose.content_type === 'text/markdown') {
$convertFromMarkdownString(compose.text, TO_WYSIWYG_TRANSFORMERS);
$createRemarkImport({})(compose.text);
} else {
const paragraph = $createParagraphNode();
const textNode = $createTextNode(compose.text);
@ -175,9 +174,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
/>
{autoFocus && <AutoFocusPlugin />}
<OnChangePlugin onChange={(_, editor) => {
editor.update(() => {
if (editorStateRef) (editorStateRef as any).current = $convertToMarkdownString(TO_WYSIWYG_TRANSFORMERS);
});
if (editorStateRef) (editorStateRef as any).current = editor.getEditorState().read($createRemarkExport());
}}
/>
<HistoryPlugin />

View File

@ -1,114 +0,0 @@
// Adapted from: https://github.com/facebook/lexical/issues/2715#issuecomment-1209090485
import {
BOLD_ITALIC_UNDERSCORE,
BOLD_ITALIC_STAR,
BOLD_STAR,
BOLD_UNDERSCORE,
STRIKETHROUGH,
INLINE_CODE,
HEADING,
QUOTE,
ORDERED_LIST,
UNORDERED_LIST,
LINK,
TextMatchTransformer,
} from '@lexical/markdown';
const replaceEscapedChars = (text: string): string => {
// convert "\*" to "*", "\_" to "_", "\~" to "~", ...
return text
.replaceAll('\\*', '*')
.replaceAll('\\_', '_')
.replaceAll('\\-', '-')
.replaceAll('\\#', '#')
.replaceAll('\\>', '>')
.replaceAll('\\+', '+')
.replaceAll('\\~', '~');
};
const replaceUnescapedChars = (text: string, regexes: RegExp[]): string => {
// convert "*" to "", "_" to "", "~" to "" (all chars, which are not escaped - means with "\" in front)
for (const regex of regexes) {
text = text.replaceAll(regex, '');
}
return text;
};
const UNESCAPE_ITALIC_UNDERSCORE_IMPORT_REGEX =
/([\_])(?<!(?:\1|\w).)(?![_*\s])(.*?[^_*\s])(?=\1)([\_])(?!\w|\3)/;
const UNESCAPE_ITALIC_UNDERSCORE_REGEX =
/([\_])(?<!(?:\1|\w).)(?![_*\s])(.*?[^_*\s])(?=\1)([\_])(?!\w|\3)/;
export const UNESCAPE_ITALIC_UNDERSCORE: TextMatchTransformer = {
dependencies: [],
export: () => null,
importRegExp: UNESCAPE_ITALIC_UNDERSCORE_IMPORT_REGEX,
regExp: UNESCAPE_ITALIC_UNDERSCORE_REGEX,
replace: (textNode, _) => {
const notEscapedUnderscoreRegex = /(?<![\\]{1})[\_]{1}/g;
const textContent = replaceUnescapedChars(textNode.getTextContent(), [
notEscapedUnderscoreRegex,
]);
textNode.setTextContent(replaceEscapedChars(textContent));
textNode.setFormat('italic');
},
trigger: '_',
type: 'text-match',
};
const UNESCAPE_ITALIC_STAR_IMPORT_REGEX =
/([\*])(?<!(?:\1|\w).)(?![_*\s])(.*?[^_*\s])(?=\1)([\*])(?!\w|\3)/;
const UNESCAPE_ITALIC_STAR_REGEX =
/([\*])(?<!(?:\1|\w).)(?![_*\s])(.*?[^_*\s])(?=\1)([\*])(?!\w|\3)/;
export const UNESCAPE_ITALIC_STAR: TextMatchTransformer = {
dependencies: [],
export: () => null,
importRegExp: UNESCAPE_ITALIC_STAR_IMPORT_REGEX,
regExp: UNESCAPE_ITALIC_STAR_REGEX,
replace: (textNode, _) => {
const notEscapedStarRegex = /(?<![\\]{1})[\*]{1}/g;
const textContent = replaceUnescapedChars(textNode.getTextContent(), [
notEscapedStarRegex,
]);
textNode.setTextContent(replaceEscapedChars(textContent));
textNode.setFormat('italic');
},
trigger: '*',
type: 'text-match',
};
const UNESCAPE_BACKSLASH_IMPORT_REGEX = /(\\(?:\\\\)?).*?\1*[\~\*\_\{\}\[\]\(\)\#\+\-\.\!]/;
const UNESCAPE_BACKSLASH_REGEX = /(\\(?:\\\\)?).*?\1*[\~\*\_\{\}\[\]\(\)\#\+\-\.\!]$/;
export const UNESCAPE_BACKSLASH: TextMatchTransformer = {
dependencies: [],
export: () => null,
importRegExp: UNESCAPE_BACKSLASH_IMPORT_REGEX,
regExp: UNESCAPE_BACKSLASH_REGEX,
replace: (textNode, _) => {
if (textNode) {
textNode.setTextContent(replaceEscapedChars(textNode.getTextContent()));
}
},
trigger: '\\',
type: 'text-match',
};
export const TO_WYSIWYG_TRANSFORMERS = [
UNESCAPE_BACKSLASH,
BOLD_ITALIC_UNDERSCORE,
BOLD_ITALIC_STAR,
BOLD_STAR,
BOLD_UNDERSCORE,
STRIKETHROUGH,
UNESCAPE_ITALIC_UNDERSCORE,
UNESCAPE_ITALIC_STAR,
INLINE_CODE,
HEADING,
QUOTE,
ORDERED_LIST,
UNORDERED_LIST,
LINK,
];

View File

@ -58,7 +58,6 @@
"@lexical/hashtag": "^0.11.2",
"@lexical/link": "^0.11.2",
"@lexical/list": "^0.11.2",
"@lexical/markdown": "^0.11.2",
"@lexical/react": "^0.11.2",
"@lexical/rich-text": "^0.11.2",
"@lexical/selection": "^0.11.2",
@ -138,6 +137,7 @@
"intl-pluralrules": "^1.3.1",
"leaflet": "^1.8.0",
"lexical": "^0.11.2",
"lexical-remark": "^0.3.8",
"libphonenumber-js": "^1.10.8",
"line-awesome": "^1.3.0",
"localforage": "^1.10.0",

View File

@ -160,6 +160,7 @@ const configuration: Configuration = {
// https://github.com/facebook/react/issues/20235#issuecomment-1061708958
'react/jsx-runtime': 'react/jsx-runtime.js',
'react/jsx-dev-runtime': 'react/jsx-dev-runtime.js',
'process/browser': require.resolve('process/browser'),
},
},

1564
yarn.lock

File diff suppressed because it is too large Load Diff