Lexical: Use custom transformers
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
27975e3ca5
commit
7dda1155d4
|
@ -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);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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,
|
||||||
|
];
|
|
@ -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', '');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue