Просмотр исходного кода

impl CodeMirrorEditor component

Yuki Takei 2 лет назад
Родитель
Сommit
b8fe3945ba

+ 11 - 14
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -8,8 +8,7 @@ import nodePath from 'path';
 import { keymap } from '@codemirror/view';
 import { keymap } from '@codemirror/view';
 import type { IPageHasId } from '@growi/core';
 import type { IPageHasId } from '@growi/core';
 import { pathUtils } from '@growi/core/dist/utils';
 import { pathUtils } from '@growi/core/dist/utils';
-import { CodeMirrorEditorContainer, useCodeMirrorEditorMain } from '@growi/editor';
-import { ReactCodeMirrorProps } from '@uiw/react-codemirror';
+import { CodeMirrorEditor, useCodeMirrorEditorMain } from '@growi/editor';
 import detectIndent from 'detect-indent';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
@@ -166,18 +165,13 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     mutateIsEnabledUnsavedWarning(value !== initialValueRef.current);
     mutateIsEnabledUnsavedWarning(value !== initialValueRef.current);
   })), [mutateIsEnabledUnsavedWarning]);
   })), [mutateIsEnabledUnsavedWarning]);
 
 
-  const useCodeMirrorEditorMainProps = useMemo<ReactCodeMirrorProps>(() => {
-    return {
-      onChange: (value) => {
-        setMarkdownPreviewWithDebounce(value);
-        mutateIsEnabledUnsavedWarningWithDebounce(value);
-      },
-    };
+  const markdownChangedHandler = useCallback((value: string) => {
+    setMarkdownPreviewWithDebounce(value);
+    mutateIsEnabledUnsavedWarningWithDebounce(value);
   }, [mutateIsEnabledUnsavedWarningWithDebounce, setMarkdownPreviewWithDebounce]);
   }, [mutateIsEnabledUnsavedWarningWithDebounce, setMarkdownPreviewWithDebounce]);
-  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(
-    codeMirrorEditorContainerRef.current,
-    useCodeMirrorEditorMainProps,
-  );
+
+
+  const { data: codeMirrorEditor } = useCodeMirrorEditorMain();
 
 
 
 
   const checkIsConflict = useCallback((data) => {
   const checkIsConflict = useCallback((data) => {
@@ -595,7 +589,10 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
           onUpload={uploadHandler}
           onUpload={uploadHandler}
           onSave={saveWithShortcut}
           onSave={saveWithShortcut}
         /> */}
         /> */}
-        <CodeMirrorEditorContainer ref={codeMirrorEditorContainerRef} />
+        <CodeMirrorEditor
+          onChange={markdownChangedHandler}
+          onSave={saveWithShortcut}
+        />
       </div>
       </div>
       <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">
       <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">
         <Preview
         <Preview

+ 0 - 0
packages/editor/src/components/CodeMirrorEditorContainer.module.scss → packages/editor/src/components/CodeMirrorEditor.module.scss


+ 65 - 0
packages/editor/src/components/CodeMirrorEditor.tsx

@@ -0,0 +1,65 @@
+import {
+  forwardRef, useEffect, useMemo, useRef,
+} from 'react';
+
+import { keymap } from '@codemirror/view';
+import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
+
+import { useCodeMirrorEditorMain } from '../stores';
+
+import style from './CodeMirrorEditor.module.scss';
+
+const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
+  return (
+    <div {...props} className={`${style['codemirror-editor-container']}`} ref={ref} />
+  );
+});
+
+
+type Props = {
+  onChange?: (value: string) => void,
+  onSave?: () => void,
+}
+
+export const CodeMirrorEditor = (props: Props): JSX.Element => {
+  const {
+    onSave, onChange,
+  } = props;
+
+  const containerRef = useRef(null);
+
+  const cmProps = useMemo<ReactCodeMirrorProps>(() => {
+    return {
+      doc: '',
+      onChange,
+    };
+  }, [onChange]);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(containerRef.current, cmProps);
+
+  // set handler to save with shortcut key
+  useEffect(() => {
+    if (onSave == null) {
+      return;
+    }
+
+    const extension = keymap.of([
+      {
+        key: 'Mod-s',
+        preventDefault: true,
+        run: () => {
+          const doc = codeMirrorEditor?.getDoc();
+          if (doc != null) {
+            onSave();
+          }
+          return true;
+        },
+      },
+    ]);
+
+    const cleanupFunction = codeMirrorEditor?.appendExtension?.(extension);
+
+    return cleanupFunction;
+  }, [codeMirrorEditor, onSave]);
+
+  return <CodeMirrorEditorContainer ref={containerRef} />;
+};

+ 0 - 9
packages/editor/src/components/CodeMirrorEditorContainer.tsx

@@ -1,9 +0,0 @@
-import { forwardRef } from 'react';
-
-import style from './CodeMirrorEditorContainer.module.scss';
-
-export const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
-  return (
-    <div {...props} className={`${style['codemirror-editor-container']}`} ref={ref} />
-  );
-});

+ 1 - 1
packages/editor/src/components/index.ts

@@ -1 +1 @@
-export * from './CodeMirrorEditorContainer';
+export * from './CodeMirrorEditor';

+ 20 - 30
packages/editor/src/components/playground/Playground.tsx

@@ -1,12 +1,10 @@
 import {
 import {
-  useEffect, useMemo, useRef, useState,
+  useCallback, useEffect, useState,
 } from 'react';
 } from 'react';
 
 
-import { keymap } from '@codemirror/view';
-import { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 import { toast } from 'react-toastify';
 import { toast } from 'react-toastify';
 
 
-import { CodeMirrorEditorContainer } from '..';
+import { CodeMirrorEditor } from '..';
 import { useCodeMirrorEditorMain } from '../../stores';
 import { useCodeMirrorEditorMain } from '../../stores';
 
 
 import { PlaygroundController } from './PlaygroundController';
 import { PlaygroundController } from './PlaygroundController';
@@ -16,37 +14,26 @@ export const Playground = (): JSX.Element => {
 
 
   const [markdownToPreview, setMarkdownToPreview] = useState('');
   const [markdownToPreview, setMarkdownToPreview] = useState('');
 
 
-  const containerRef = useRef(null);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorMain();
 
 
-  const props = useMemo<ReactCodeMirrorProps>(() => {
-    return {
-      onChange: setMarkdownToPreview,
-    };
-  }, []);
-  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(containerRef.current, props);
+  const initialValue = '# header\n';
 
 
+  // initialize
   useEffect(() => {
   useEffect(() => {
-    codeMirrorEditor?.initDoc('# header\n');
-  }, [codeMirrorEditor]);
+    codeMirrorEditor?.initDoc(initialValue);
+    setMarkdownToPreview(initialValue);
+  }, [codeMirrorEditor, initialValue]);
 
 
-  // set handler to save with shortcut key
+  // initial caret line
   useEffect(() => {
   useEffect(() => {
-    const extension = keymap.of([
-      {
-        key: 'Mod-s',
-        preventDefault: true,
-        run: () => {
-          // eslint-disable-next-line no-console
-          console.log({ doc: codeMirrorEditor?.getDoc() });
-          toast.success('Saved.', { autoClose: 2000 });
-          return true;
-        },
-      },
-    ]);
-
-    const cleanupFunction = codeMirrorEditor?.appendExtension?.(extension);
+    codeMirrorEditor?.setCaretLine();
+  }, [codeMirrorEditor]);
 
 
-    return cleanupFunction;
+  // set handler to save with shortcut key
+  const saveHandler = useCallback(() => {
+    // eslint-disable-next-line no-console
+    console.log({ doc: codeMirrorEditor?.getDoc() });
+    toast.success('Saved.', { autoClose: 2000 });
   }, [codeMirrorEditor]);
   }, [codeMirrorEditor]);
 
 
   return (
   return (
@@ -56,7 +43,10 @@ export const Playground = (): JSX.Element => {
       </div>
       </div>
       <div className="flex-expand-horiz">
       <div className="flex-expand-horiz">
         <div className="flex-expand-vert">
         <div className="flex-expand-vert">
-          <CodeMirrorEditorContainer ref={containerRef} />
+          <CodeMirrorEditor
+            onSave={saveHandler}
+            onChange={setMarkdownToPreview}
+          />
         </div>
         </div>
         <div className="flex-expand-vert d-none d-lg-flex bg-light text-dark border-start border-dark-subtle p-3">
         <div className="flex-expand-vert d-none d-lg-flex bg-light text-dark border-start border-dark-subtle p-3">
           <Preview markdown={markdownToPreview} />
           <Preview markdown={markdownToPreview} />

+ 6 - 1
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -1,4 +1,4 @@
-import { useMemo } from 'react';
+import { useEffect, useMemo } from 'react';
 
 
 import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
 import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
 import { languages } from '@codemirror/language-data';
 import { languages } from '@codemirror/language-data';
@@ -49,6 +49,11 @@ export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor
   const focus = useFocus(view);
   const focus = useFocus(view);
   const setCaretLine = useSetCaretLine(view);
   const setCaretLine = useSetCaretLine(view);
 
 
+  // workaround to fix the doc initialization not working issue -- 2023.08.31 Yuki Takei
+  useEffect(() => {
+    initDoc(' ');
+  }, [initDoc]);
+
   return {
   return {
     state,
     state,
     view,
     view,

+ 2 - 0
packages/editor/src/stores/codemirror-editor.ts

@@ -48,6 +48,8 @@ export const useCodeMirrorEditorMain = (container?: HTMLDivElement | null, props
 
 
   if (shouldUpdate) {
   if (shouldUpdate) {
     ref.current = newData;
     ref.current = newData;
+    // eslint-disable-next-line no-console
+    console.info('Initializing codemirror for main');
   }
   }
 
 
   return useSWRStatic('codeMirrorEditorMain', shouldUpdate ? newData : undefined);
   return useSWRStatic('codeMirrorEditorMain', shouldUpdate ? newData : undefined);