Руководство по использованию плагина Lexical Markdown с React.

Отказ от ответственности. Подумайте дважды, прежде чем использовать плагин Markdown, если вы собираетесь использовать собственные узлы; Позвольте мне вам сказать, что преобразование пользовательского узла в уценку и наоборот потребует создания собственных Трансформеров, а это непростая задача.

Предварительные условия

Эта статья является продолжением предыдущей:



Трансформеры

Прежде чем мы добавим MarkdownShortcutPlugin в наше приложение, нам нужно понять, что такое ТРАНСФОРМЕРЫ:

import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
import { TRANSFORMERS } from '@lexical/markdown';

... 

<MarkdownShortcutPlugin transformers={TRANSFORMERS} />

ТРАНСФОРМАТОРЫ — это массив объектов…

const TRANSFORMERS: Array<Transformer> = [
  ...ELEMENT_TRANSFORMERS,
  ...TEXT_FORMAT_TRANSFORMERS,
  ...TEXT_MATCH_TRANSFORMERS,
];

… которые имеют свойства, которые объясняют Markdown, как Node должен быть представлен в нотации Markdown и какой Node следует использовать при преобразовании из Markdown:

Пример:

export const UNORDERED_LIST: ElementTransformer = {
  dependencies: [ListNode, ListItemNode],
  export: (node, exportChildren) => {
    return $isListNode(node) ? listExport(node, exportChildren, 0) : null;
  },
  regExp: /^(\s*)[-*+]\s/,
  replace: listReplace('bullet'),
  type: 'element',
};

Объяснение:

MarkdownPlugin будет анализировать текст Markdown и использовать regExp для поиска конкретного совпадения. Например, несортированный список будет представлен как:

- Hello
- Hello

  • ^(\s*)[-*+]\s найдет два совпадения;
  • replace будет запущена функция, преобразующая текст в ListNode;
  • Функция export используется, когда мы конвертируем Node в Markdown;

Теперь давайте добавим MarkdownShortcutPlugin в наше приложение.

MarkdownShortcutПлагин

Добавьте компонент MarkdownShortcutPlugin плагина LexicalComposer со свойством transformers. Нам нужно будет добавить зависимости узлов, необходимые для Markdown:

Index: src/App.tsx
@@ -4,6 +4,8 @@
 import { ContentEditable } from '@lexical/react/LexicalContentEditable';
 import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
 import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
+import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
+import { TRANSFORMERS } from '@lexical/markdown';
 import {
    OnChangePlugin,
    CustomTextActions,
@@ -14,11 +16,14 @@
    CustomBannerPlugin,
    CustomBannerActions,
 } from './components';
-import { HeadingNode } from '@lexical/rich-text';
 import initialStae from './initialState.json';
+import { HeadingNode, QuoteNode } from '@lexical/rich-text';
+import { CodeNode } from '@lexical/code';
+import { ListItemNode, ListNode } from '@lexical/list';
+import { LinkNode } from '@lexical/link';
 import { BannerNode, ImageNode } from './nodes';
 import { CustomDraggableBlockPlugin, DraggableWrapper } from './plugins';
 
 import './App.css';
 
 export const App: React.FC = () => {
@@ -51,7 +56,16 @@
 
    const lexicalConfig: InitialConfigType = {
       namespace: 'My Rich Text Editor',
-      nodes: [BannerNode, HeadingNode, ImageNode],
+      nodes: [
+         BannerNode,
+         HeadingNode,
+         ImageNode,
+         QuoteNode,
+         CodeNode,
+         ListNode,
+         ListItemNode,
+         LinkNode,
+      ],
       editable: true,
       theme: {
          root: 'root',
@@ -98,6 +112,7 @@
             />
             <HistoryPlugin />
             <OnChangePlugin />
+            <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
             <CustomHeadingPlugin />
             <CustomBannerPlugin />
             <CustomDraggableBlockPlugin />

Затем добавьте действия Markdown:

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $createTextNode, $getRoot } from 'lexical';
import { $createCodeNode, $isCodeNode } from '@lexical/code';
import { $convertFromMarkdownString, $convertToMarkdownString } from '@lexical/markdown';
import { TRANSFORMERS } from '@lexical/markdown';

export const MarkdownActions = () => {
   const [editor] = useLexicalComposerContext();

   const handleOnClick = () => {
      editor.update(() => {
         const root = $getRoot();
         const firstChild = root.getFirstChild();

         if ($isCodeNode(firstChild) && firstChild.getLanguage() === 'markdown') {
            // Markdown -> Node
            $convertFromMarkdownString(firstChild.getTextContent(), TRANSFORMERS);
         } else {
            // Node -> Markdown
            const markdown = $convertToMarkdownString(TRANSFORMERS);
            root.clear().append($createCodeNode('markdown').append($createTextNode(markdown)));
         }
      });
   };

   return (
      <div style={{ marginTop: '10px' }}>
         <span style={{ fontWeight: 'bold' }}>Markdown</span>
         <div>
            <button onClick={handleOnClick}>MARKDOWN</button>
         </div>
      </div>
   );
};

Добавьте новые простые стили CSS для Markdown:

Index: src/App.css
@@ -54,6 +54,13 @@
     outline: none;
 }
 
+.markdown-code {
+    font-size: 12px;
+    line-height: 1.5;
+    background-color: #dedede;
+    display: block;
+}
+
 p {
     margin: 0;
 }

Добавьте компонент MarkdownActions в App.tsx:

Index: src/App.tsx
@@ -15,6 +15,7 @@
    CustomHeadingPlugin,
    CustomBannerPlugin,
    CustomBannerActions,
+   MarkdownActions,
 } from './components';
 import { HeadingNode, QuoteNode } from '@lexical/rich-text';
 import { CodeNode } from '@lexical/code';
@@ -80,6 +81,7 @@
             superscript: 'text-superscript',
          },
          banner: 'banner',
+         code: 'markdown-code',
       },
       onError: (e) => {
          console.log('ERROR:', e);
@@ -104,6 +106,7 @@
                <CustomHeadingActions />
                <CustomTextActions />
                <CustomAlignActions />
+               <MarkdownActions />
             </div>
             <RichTextPlugin
                contentEditable={CustomContent}

Давайте посмотрим, что мы получили на данный момент:

Как вы можете видеть, наш перетаскиваемый элемент из предыдущей статьи станет видимым после преобразования узлов в Markdown. Давайте это исправим.

Исправить перетаскиваемый элемент

Во-первых, нам нужно будет обновить draggableStore новыми свойствами, которые будут определять режимы «Вкл./Выкл.» Markdown:

Index: src/plugins/CustomDraggableBlockPlugin/store/draggableStore.ts
@@ -27,6 +27,8 @@
    line?: Line;
    setLine: (value: Line) => void;
    resetState: () => void;
+   isMarkdown: boolean;
+   setIsMarkdown: (value: boolean) => void;
 }
 
 export const draggableStore = create<MiniLibraryStoreProps, [['zustand/devtools', never]]>(
@@ -73,6 +75,16 @@
                { type: 'setLine', value: newLine },
             );
          },
+         isMarkdown: false,
+         setIsMarkdown: (value: boolean) => {
+            setState(
+               {
+                  isMarkdown: value,
+               },
+               false,
+               { type: 'setIsMarkdown', value },
+            );
+         },
       }),
       { name: 'draggableStore' },
    ),
@@ -80,9 +92,10 @@
 
 export const useDraggableStore = () =>
    draggableStore(
-      ({ draggable, resetState }) => ({
+      ({ draggable, resetState, isMarkdown }) => ({
          draggable,
          resetState,
+         isMarkdown,
       }),
       shallow,
    );

После этого мы обновим действия уценки, которые обновят состояние isMarkdown:

Index: src/components/MarkdownActions/MarkdownActions.tsx
@@ -3,7 +3,7 @@
 import { $createCodeNode, $isCodeNode } from '@lexical/code';
 import { $convertFromMarkdownString, $convertToMarkdownString } from '@lexical/markdown';
 import { TRANSFORMERS } from '@lexical/markdown';
+import { draggableStore } from '../../plugins';
 export const MarkdownActions = () => {
    const [editor] = useLexicalComposerContext();
 
@@ -15,11 +15,13 @@
 
             $convertFromMarkdownString(firstChild.getTextContent(), TRANSFORMERS);
+            draggableStore.getState().setIsMarkdown(false);
          } else { 
             const markdown = $convertToMarkdownString(TRANSFORMERS);
             root.clear().append($createCodeNode('markdown').append($createTextNode(markdown)));
+            draggableStore.getState().setIsMarkdown(true);
          }
       });
    };

Обновить компонент CustomDraggableBlockPlugin:

Index: src/plugins/CustomDraggableBlockPlugin/CustomDraggableBlockPlugin.tsx
@@ -3,10 +3,13 @@
 import { DRAGGABLE_WRAPPER_ID, DraggableElement, OnDragLine } from './components';
 import { useOnDrop, useDragListeners } from './hooks';
 import { createPortal } from 'react-dom';
+import { useDraggableStore } from './store';
 
 export const CustomDraggableBlockPlugin: React.FC = () => {
    const [editor] = useLexicalComposerContext();
 
+   const { isMarkdown } = useDraggableStore();
+
    useDragListeners();
    useOnDrop();
 
@@ -14,7 +17,7 @@
 
    const wrapperHtmlElement = document.getElementById(DRAGGABLE_WRAPPER_ID);
 
-   if (!isEditable || !wrapperHtmlElement) {
+   if (!isEditable || !wrapperHtmlElement || isMarkdown) {
       return null;
    }

Результат:

Репозиторий GitHub:



Статьи по теме:







На простом английском языке

Спасибо, что вы являетесь частью нашего сообщества! Прежде чем уйти: