Lexical: Use custom transformers

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-04-18 23:31:19 +02:00
parent 27975e3ca5
commit 7dda1155d4
3 changed files with 97 additions and 9 deletions

View File

@ -6,7 +6,7 @@ Copyright (c) Meta Platforms, Inc. and affiliates.
This source code is licensed under the MIT license found in the This source code is licensed under the MIT license found in the
LICENSE file in the /app/soapbox/features/compose/editor directory. LICENSE file in the /app/soapbox/features/compose/editor directory.
*/ */
import { $convertFromMarkdownString, $convertToMarkdownString, TRANSFORMERS } from '@lexical/markdown'; import { $convertFromMarkdownString, $convertToMarkdownString } from '@lexical/markdown';
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
import { LexicalComposer, InitialConfigType } from '@lexical/react/LexicalComposer'; import { LexicalComposer, InitialConfigType } from '@lexical/react/LexicalComposer';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
@ -32,6 +32,7 @@ import DraggableBlockPlugin from './plugins/draggable-block-plugin';
import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin'; import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin';
import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin'; import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin';
import { MentionPlugin } from './plugins/mention-plugin'; import { MentionPlugin } from './plugins/mention-plugin';
import { TO_WYSIWYG_TRANSFORMERS } from './transformers';
const StatePlugin = ({ composeId }: { composeId: string }) => { const StatePlugin = ({ composeId }: { composeId: string }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -108,7 +109,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
return function() { return function() {
if (compose.content_type === 'text/markdown') { if (compose.content_type === 'text/markdown') {
$convertFromMarkdownString(compose.text, TRANSFORMERS); $convertFromMarkdownString(compose.text, TO_WYSIWYG_TRANSFORMERS);
} else { } else {
const paragraph = $createParagraphNode(); const paragraph = $createParagraphNode();
const textNode = $createTextNode(compose.text); const textNode = $createTextNode(compose.text);
@ -175,7 +176,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
{autoFocus && <AutoFocusPlugin />} {autoFocus && <AutoFocusPlugin />}
<OnChangePlugin onChange={(_, editor) => { <OnChangePlugin onChange={(_, editor) => {
editor.update(() => { editor.update(() => {
if (editorStateRef) (editorStateRef as any).current = $convertToMarkdownString(TRANSFORMERS); if (editorStateRef) (editorStateRef as any).current = $convertToMarkdownString(TO_WYSIWYG_TRANSFORMERS);
}); });
}} }}
/> />

View File

@ -0,0 +1,92 @@
// 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_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

@ -1,11 +1,6 @@
import data, { EmojiData } from './data'; import data, { EmojiData } from './data';
const stripLeadingZeros = /^0+/; const stripLeadingZeros = /^0+/;
function replaceAll(str: string, find: string, replace: string) {
return str.replace(new RegExp(find, 'g'), replace);
}
interface UnicodeMap { interface UnicodeMap {
[s: string]: { [s: string]: {
unified: string unified: string
@ -80,7 +75,7 @@ const stripcodes = (unified: string, native: string) => {
if (unified.includes('200d') && !(unified in blacklist)) { if (unified.includes('200d') && !(unified in blacklist)) {
return stripped; return stripped;
} else { } else {
return replaceAll(stripped, '-fe0f', ''); return stripped.replaceAll('-fe0f', '');
} }
}; };