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

Merge branch 'dev/7.0.x' into support/129279-revoke-well-classes

soumaeda 2 лет назад
Родитель
Сommit
42e2c7e143

+ 1 - 1
apps/app/package.json

@@ -172,7 +172,7 @@
     "react-multiline-clamp": "^2.0.0",
     "react-scroll": "^1.8.7",
     "react-syntax-highlighter": "^15.5.0",
-    "react-toastify": "^9.1.1",
+    "react-toastify": "^9.1.3",
     "react-use-ripple": "^1.5.2",
     "reactstrap": "^9.2.0",
     "reconnecting-websocket": "^4.4.0",

+ 44 - 44
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -5,11 +5,11 @@ import React, {
 import EventEmitter from 'events';
 import nodePath from 'path';
 
-import { Compartment } from '@codemirror/state';
 import { keymap } from '@codemirror/view';
 import type { IPageHasId } from '@growi/core';
 import { pathUtils } from '@growi/core/dist/utils';
 import { CodeMirrorEditorContainer, useCodeMirrorEditorMain } from '@growi/editor';
+import { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -18,7 +18,6 @@ import { throttle, debounce } from 'throttle-debounce';
 import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
 import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import { IEditorMethods } from '~/interfaces/editor-methods';
 import { OptionsToSave } from '~/interfaces/page-operation';
 import { SocketEventName } from '~/interfaces/websocket';
 import {
@@ -58,7 +57,6 @@ import loggerFactory from '~/utils/logger';
 import Preview from './Preview';
 import scrollSyncHelper from './ScrollSyncHelper';
 
-
 import '@growi/editor/dist/style.css';
 
 
@@ -116,19 +114,16 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
-  const { data: codemirrorEditor } = useCodeMirrorEditorMain(codeMirrorEditorContainerRef.current);
-  const {
-    appendExtension,
-    initDoc, getDoc,
-    focus: focusToEditor, setCaretLine,
-  } = codemirrorEditor ?? {};
+  const { data: socket } = useGlobalSocket();
 
   const { data: rendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
-  const saveOrUpdate = useSaveOrUpdate();
+  const { mutate: mutateIsConflict } = useIsConflict();
 
+  const saveOrUpdate = useSaveOrUpdate();
   const updateStateAfterSave = useUpdateStateAfterSave(pageId, { supressEditingMarkdownMutation: true });
 
+
   // TODO: remove workaround
   // for https://redmine.weseek.co.jp/issues/125923
   const [createdPageRevisionIdWithAttachment, setCreatedPageRevisionIdWithAttachment] = useState();
@@ -155,10 +150,26 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   }, [isNotFound, currentPathname, editingMarkdown, isEnabledAttachTitleHeader, templateBodyData]);
 
   const [markdownToPreview, setMarkdownToPreview] = useState<string>(initialValue);
-
-  const { data: socket } = useGlobalSocket();
-
-  const { mutate: mutateIsConflict } = useIsConflict();
+  const setMarkdownPreviewWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string) => {
+    setMarkdownToPreview(value);
+  })), []);
+  const mutateIsEnabledUnsavedWarningWithDebounce = useMemo(() => debounce(600, throttle(900, (value: string) => {
+    // Displays an unsaved warning alert
+    mutateIsEnabledUnsavedWarning(value !== initialValue);
+  })), [initialValue, mutateIsEnabledUnsavedWarning]);
+
+  const useCodeMirrorEditorMainProps = useMemo<ReactCodeMirrorProps>(() => {
+    return {
+      onChange: (value) => {
+        setMarkdownPreviewWithDebounce(value);
+        mutateIsEnabledUnsavedWarningWithDebounce(value);
+      },
+    };
+  }, [mutateIsEnabledUnsavedWarningWithDebounce, setMarkdownPreviewWithDebounce]);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(
+    codeMirrorEditorContainerRef.current,
+    useCodeMirrorEditorMainProps,
+  );
 
 
   const checkIsConflict = useCallback((data) => {
@@ -202,17 +213,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     return optionsToSave;
   }, [grantData, isSlackEnabled, pageTags]);
 
-  const setMarkdownWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string, isClean: boolean) => {
-    setMarkdownToPreview(value);
-
-    // Displays an unsaved warning alert
-    mutateIsEnabledUnsavedWarning(!isClean);
-  })), [mutateIsEnabledUnsavedWarning]);
-
-
-  const markdownChangedHandler = useCallback((value: string, isClean: boolean): void => {
-    setMarkdownWithDebounce(value, isClean);
-  }, [setMarkdownWithDebounce]);
 
   const save = useCallback(async(opts?: {slackChannels: string, overwriteScopesOfDescendants?: boolean}): Promise<IPageHasId | null> => {
     if (currentPathname == null || optionsToSave == null) {
@@ -226,7 +226,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       mutateWaitingSaveProcessing(true);
 
       const { page } = await saveOrUpdate(
-        getDoc?.() ?? '',
+        codeMirrorEditor?.getDoc() ?? '',
         { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
         options,
       );
@@ -252,7 +252,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     }
 
   }, [
-    getDoc,
+    codeMirrorEditor,
     currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId,
     currentPagePath, currentRevisionId,
     mutateWaitingSaveProcessing, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser,
@@ -316,7 +316,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
         formData.append('page_id', pageId);
       }
       if (pageId == null) {
-        formData.append('page_body', getDoc?.() ?? '');
+        formData.append('page_body', codeMirrorEditor?.getDoc() ?? '');
       }
 
       res = await apiPostForm('/attachments.add', formData);
@@ -353,7 +353,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       // refs: https://redmine.weseek.co.jp/issues/126528
       // editorRef.current.terminateUploadingState();
     }
-  }, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
+  }, [codeMirrorEditor, currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
 
 
   const scrollPreviewByEditorLine = useCallback((line: number) => {
@@ -472,20 +472,20 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       return;
     }
     // markdownToSave.current = initialValue;
-    initDoc?.(initialValue);
+    codeMirrorEditor?.initDoc(initialValue);
     setMarkdownToPreview(initialValue);
     mutateIsEnabledUnsavedWarning(false);
-  }, [initDoc, initialValue, mutateIsEnabledUnsavedWarning]);
+  }, [codeMirrorEditor, initialValue, mutateIsEnabledUnsavedWarning]);
 
   // initial caret line
   useEffect(() => {
-    setCaretLine?.();
-  }, [setCaretLine]);
+    codeMirrorEditor?.setCaretLine();
+  }, [codeMirrorEditor]);
 
   // set handler to set caret line
   useEffect(() => {
     const handler = (line) => {
-      setCaretLine?.(line);
+      codeMirrorEditor?.setCaretLine(line);
 
       if (previewRef.current != null) {
         scrollSyncHelper.scrollPreview(previewRef.current, line);
@@ -496,7 +496,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     return function cleanup() {
       globalEmitter.removeListener('setCaretLine', handler);
     };
-  }, [setCaretLine]);
+  }, [codeMirrorEditor]);
 
   // set handler to save and return to View
   useEffect(() => {
@@ -509,29 +509,29 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   // set handler to save with shortcut key
   useEffect(() => {
-    const compartment = new Compartment();
     const extension = keymap.of([
       {
         key: 'Mod-s',
         preventDefault: true,
         run: () => {
           saveWithShortcut();
-          return false;
+          return true;
         },
       },
     ]);
 
-    const cleanupFunction = appendExtension?.(extension, compartment);
+    const cleanupFunction = codeMirrorEditor?.appendExtension(extension);
 
     return cleanupFunction;
-  }, [appendExtension, saveWithShortcut]);
+
+  }, [codeMirrorEditor, saveWithShortcut]);
 
   // set handler to focus
   useLayoutEffect(() => {
     if (editorMode === EditorMode.Editor) {
-      focusToEditor?.();
+      codeMirrorEditor?.focus();
     }
-  }, [editorMode, focusToEditor]);
+  }, [codeMirrorEditor, editorMode]);
 
   // Detect indent size from contents (only when users are allowed to change it)
   useEffect(() => {
@@ -552,9 +552,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   // when transitioning to a different page, if the initialValue is the same,
   // UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
   const onRouterChangeComplete = useCallback(() => {
-    initDoc?.(initialValue);
-    setCaretLine?.();
-  }, [initDoc, initialValue, setCaretLine]);
+    codeMirrorEditor?.initDoc(initialValue);
+    codeMirrorEditor?.setCaretLine();
+  }, [codeMirrorEditor, initialValue]);
 
   useEffect(() => {
     router.events.on('routeChangeComplete', onRouterChangeComplete);

+ 1 - 0
packages/editor/package.json

@@ -33,6 +33,7 @@
     "codemirror": "^6.0.1",
     "eslint-plugin-react-refresh": "^0.4.1",
     "react-hook-form": "^7.45.4",
+    "react-toastify": "^9.1.3",
     "swr": "^2.0.3"
   }
 }

+ 37 - 2
packages/editor/src/components/playground/Playground.tsx

@@ -1,15 +1,49 @@
-import { useRef } from 'react';
+import {
+  useEffect, useMemo, useRef, useState,
+} from 'react';
+
+import { keymap } from '@codemirror/view';
+import { ReactCodeMirrorProps } from '@uiw/react-codemirror';
+import { toast } from 'react-toastify';
 
 import { CodeMirrorEditorContainer } from '..';
 import { useCodeMirrorEditorMain } from '../../stores';
 
 import { PlaygroundController } from './PlaygroundController';
+import { Preview } from './Preview';
 
 export const Playground = (): JSX.Element => {
 
+  const [markdownToPreview, setMarkdownToPreview] = useState('');
+
   const containerRef = useRef(null);
 
-  useCodeMirrorEditorMain(containerRef.current);
+  const props = useMemo<ReactCodeMirrorProps>(() => {
+    return {
+      onChange: setMarkdownToPreview,
+    };
+  }, []);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(containerRef.current, props);
+
+  // set handler to save with shortcut key
+  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);
+
+    return cleanupFunction;
+  }, [codeMirrorEditor]);
 
   return (
     <>
@@ -21,6 +55,7 @@ export const Playground = (): JSX.Element => {
           <CodeMirrorEditorContainer ref={containerRef} />
         </div>
         <div className="flex-expand-vert d-none d-lg-flex bg-light text-dark border-start border-dark-subtle p-3">
+          <Preview markdown={markdownToPreview} />
           <PlaygroundController />
         </div>
       </div>

+ 1 - 1
packages/editor/src/components/playground/PlaygroundController.tsx

@@ -69,7 +69,7 @@ export const SetCaretLineRow = (): JSX.Element => {
 
 export const PlaygroundController = (): JSX.Element => {
   return (
-    <div className="container">
+    <div className="container mt-5">
       <InitEditorValueRow />
       <SetCaretLineRow />
     </div>

+ 14 - 0
packages/editor/src/components/playground/Preview.tsx

@@ -0,0 +1,14 @@
+type Props = {
+  markdown?: string,
+}
+
+export const Preview = (props: Props): JSX.Element => {
+  return (
+    <div className="container">
+      <h3>Preview</h3>
+      <div className="card" style={{ minHeight: '200px' }}>
+        {props.markdown ?? ''}
+      </div>
+    </div>
+  );
+};

+ 1 - 0
packages/editor/src/main.scss

@@ -1,3 +1,4 @@
 @import 'bootstrap';
+@import 'react-toastify/scss/main';
 
 @import '@growi/core/scss/flex-expand';

+ 2 - 0
packages/editor/src/main.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
 
 import ReactDOM from 'react-dom/client';
+import { ToastContainer } from 'react-toastify';
 
 import { Playground } from './components/playground';
 
@@ -13,5 +14,6 @@ const rootElem = document.getElementById('root');
 ReactDOM.createRoot(rootElem!).render(
   <React.StrictMode>
     <Playground />
+    <ToastContainer />
   </React.StrictMode>,
 );

+ 1 - 1
packages/editor/src/services/codemirror-editor/index.ts

@@ -1 +1 @@
-export * from './use-codemirror-editor';
+export * from './use-codemirror-editor/use-codemirror-editor';

+ 0 - 132
packages/editor/src/services/codemirror-editor/use-codemirror-editor.ts

@@ -1,132 +0,0 @@
-import { useCallback, useEffect } from 'react';
-
-import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
-import { languages } from '@codemirror/language-data';
-import {
-  EditorState, StateEffect, type EditorStateConfig, type Extension, Compartment,
-} from '@codemirror/state';
-import { basicSetup, useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
-
-import { UseCodeMirrorEditorStates } from './interfaces/react-codemirror';
-
-export type UseCodeMirrorEditor = UseCodeMirror;
-
-type UseCodeMirrorEditorUtils = {
-  initState: (config?: EditorStateConfig) => void,
-  initDoc: (doc?: string) => void,
-  appendExtension: (extension: Extension, compartment?: Compartment) => CleanupFunction | undefined,
-  getDoc: () => string | undefined,
-  focus: () => void,
-  setCaretLine: (lineNumber?: number) => void,
-}
-
-export type UseCodeMirrorEditorResponse = UseCodeMirrorEditorStates & UseCodeMirrorEditorUtils;
-
-type CleanupFunction = () => void;
-
-const defaultExtensions: Extension[] = [
-  markdown({ base: markdownLanguage, codeLanguages: languages }),
-];
-
-const defaultExtensionsToInit: Extension[] = [
-  ...basicSetup(),
-  ...defaultExtensions,
-];
-
-export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorEditorResponse => {
-
-  const codemirror = useCodeMirror({
-    ...props,
-    extensions: [
-      ...defaultExtensions,
-      ...(props?.extensions ?? []),
-    ],
-  });
-
-  const { view, setContainer } = codemirror;
-
-  // implement initState method
-  const initState = useCallback((config?: EditorStateConfig): void => {
-    if (view == null) {
-      return;
-    }
-
-    const newState = EditorState.create({
-      ...config,
-      extensions: [
-        ...defaultExtensionsToInit,
-        ...(props?.extensions ?? []),
-      ],
-    });
-
-    view.setState(newState);
-  }, [props?.extensions, view]);
-
-  // implement initDoc method
-  const initDoc = useCallback((doc?: string): void => {
-    initState({ doc });
-  }, [initState]);
-
-  // implement appendExtension method
-  const appendExtension = useCallback((extension: Extension, compartment?: Compartment): CleanupFunction | undefined => {
-    view?.dispatch({
-      effects: StateEffect.appendConfig.of(
-        compartment != null
-          ? compartment.of(extension)
-          : extension,
-      ),
-    });
-
-    return compartment != null
-      // return cleanup function
-      ? () => {
-        view?.dispatch({
-          effects: compartment?.reconfigure([]),
-        });
-      }
-      : undefined;
-  }, [view]);
-
-  // implement getDoc method
-  const getDoc = useCallback((): string | undefined => {
-    return view?.state.doc.toString();
-  }, [view]);
-
-  // implement focus method
-  const focus = useCallback((): void => {
-    view?.focus();
-  }, [view]);
-
-  // implement setCaretLine method
-  const setCaretLine = useCallback((lineNumber?: number): void => {
-    if (view == null) {
-      return;
-    }
-
-    const posOfLineEnd = view.state.doc.line(lineNumber ?? 1).to;
-    view.dispatch({
-      selection: {
-        anchor: posOfLineEnd,
-        head: posOfLineEnd,
-      },
-    });
-    // focus
-    view.focus();
-  }, [view]);
-
-  useEffect(() => {
-    if (props?.container != null) {
-      setContainer(props.container);
-    }
-  }, [props?.container, setContainer]);
-
-  return {
-    ...codemirror,
-    initState,
-    initDoc,
-    appendExtension,
-    getDoc,
-    focus,
-    setCaretLine,
-  };
-};

+ 60 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -0,0 +1,60 @@
+import { useMemo } from 'react';
+
+import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
+import { languages } from '@codemirror/language-data';
+import { type Extension } from '@codemirror/state';
+import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
+
+import type { UseCodeMirrorEditorStates } from '../interfaces/react-codemirror';
+
+import { AppendExtension, useAppendExtension } from './utils/append-extension';
+import { useFocus, type Focus } from './utils/focus';
+import { useGetDoc, type GetDoc } from './utils/get-doc';
+import { useInitDoc, type InitDoc } from './utils/init-doc';
+import { useSetCaretLine, type SetCaretLine } from './utils/set-caret-line';
+
+type UseCodeMirrorEditorUtils = {
+  initDoc: InitDoc,
+  appendExtension: AppendExtension,
+  getDoc: GetDoc,
+  focus: Focus,
+  setCaretLine: SetCaretLine,
+}
+export type UseCodeMirrorEditor = UseCodeMirrorEditorStates & UseCodeMirrorEditorUtils;
+
+
+const defaultExtensions: Extension[] = [
+  markdown({ base: markdownLanguage, codeLanguages: languages }),
+];
+
+export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor => {
+
+  const mergedProps = useMemo<UseCodeMirror>(() => {
+    return {
+      ...props,
+      extensions: [
+        ...defaultExtensions,
+        ...(props?.extensions ?? []),
+      ],
+    };
+  }, [props]);
+
+  const codemirror = useCodeMirror(mergedProps);
+
+  const { view } = codemirror;
+
+  const initDoc = useInitDoc(view);
+  const appendExtension = useAppendExtension(view);
+  const getDoc = useGetDoc(view);
+  const focus = useFocus(view);
+  const setCaretLine = useSetCaretLine(view);
+
+  return {
+    ...codemirror,
+    initDoc,
+    appendExtension,
+    getDoc,
+    focus,
+    setCaretLine,
+  };
+};

+ 27 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/append-extension.ts

@@ -0,0 +1,27 @@
+import { useCallback } from 'react';
+
+import { Compartment, Extension, StateEffect } from '@codemirror/state';
+import { EditorView } from '@codemirror/view';
+
+type CleanupFunction = () => void;
+export type AppendExtension = (extension: Extension) => CleanupFunction | undefined;
+
+export const useAppendExtension = (view?: EditorView): AppendExtension => {
+
+  return useCallback((extension) => {
+    const compartment = new Compartment();
+    view?.dispatch({
+      effects: StateEffect.appendConfig.of(
+        compartment.of(extension),
+      ),
+    });
+
+    // return cleanup function
+    return () => {
+      view?.dispatch({
+        effects: compartment.reconfigure([]),
+      });
+    };
+  }, [view]);
+
+};

+ 13 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/focus.ts

@@ -0,0 +1,13 @@
+import { useCallback } from 'react';
+
+import { EditorView } from '@codemirror/view';
+
+export type Focus = () => void;
+
+export const useFocus = (view?: EditorView): Focus => {
+
+  return useCallback(() => {
+    view?.focus?.();
+  }, [view]);
+
+};

+ 13 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/get-doc.ts

@@ -0,0 +1,13 @@
+import { useCallback } from 'react';
+
+import { EditorView } from '@codemirror/view';
+
+export type GetDoc = () => string;
+
+export const useGetDoc = (view?: EditorView): GetDoc => {
+
+  return useCallback(() => {
+    return view?.state.doc.toString() ?? '';
+  }, [view]);
+
+};

+ 21 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/init-doc.ts

@@ -0,0 +1,21 @@
+import { useCallback } from 'react';
+
+import { Transaction } from '@codemirror/state';
+import { EditorView } from '@codemirror/view';
+
+export type InitDoc = (doc?: string) => void;
+
+export const useInitDoc = (view?: EditorView): InitDoc => {
+
+  return useCallback((doc) => {
+    view?.dispatch({
+      changes: {
+        from: 0,
+        to: view?.state.doc.length,
+        insert: doc,
+      },
+      annotations: Transaction.addToHistory.of(false),
+    });
+  }, [view]);
+
+};

+ 27 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/set-caret-line.ts

@@ -0,0 +1,27 @@
+import { useCallback } from 'react';
+
+import { EditorView } from '@codemirror/view';
+
+export type SetCaretLine = (lineNumber?: number) => void;
+
+export const useSetCaretLine = (view?: EditorView): SetCaretLine => {
+
+  return useCallback((lineNumber) => {
+    const doc = view?.state.doc;
+
+    if (doc == null) {
+      return;
+    }
+
+    const posOfLineEnd = doc.line(lineNumber ?? 1).to;
+    view?.dispatch({
+      selection: {
+        anchor: posOfLineEnd,
+        head: posOfLineEnd,
+      },
+    });
+    // focus
+    view?.focus();
+  }, [view]);
+
+};

+ 14 - 19
packages/editor/src/stores/codemirror-editor.ts

@@ -2,35 +2,30 @@ import { useMemo } from 'react';
 
 import { type Extension } from '@codemirror/state';
 import { scrollPastEnd } from '@codemirror/view';
-import {
-  type SWRResponseWithUtils, withUtils, useSWRStatic,
-} from '@growi/core/dist/swr';
+import { useSWRStatic } from '@growi/core/dist/swr';
+import type { ReactCodeMirrorProps, UseCodeMirror } from '@uiw/react-codemirror';
+import type { SWRResponse } from 'swr';
 
-import type { UseCodeMirrorEditor, UseCodeMirrorEditorResponse } from '../services';
+import type { UseCodeMirrorEditor } from '../services';
 import { useCodeMirrorEditor } from '../services';
 
 const defaultExtensionsMain: Extension[] = [
   scrollPastEnd(),
 ];
 
-type MainEditorUtils = {
-  // impl something
-};
-
-export const useCodeMirrorEditorMain = (container?: HTMLDivElement | null): SWRResponseWithUtils<MainEditorUtils, UseCodeMirrorEditorResponse> => {
-  const props = useMemo<UseCodeMirrorEditor>(() => {
+export const useCodeMirrorEditorMain = (container?: HTMLDivElement | null, props?: ReactCodeMirrorProps): SWRResponse<UseCodeMirrorEditor> => {
+  const mergedProps = useMemo<UseCodeMirror>(() => {
     return {
+      ...props,
       container,
-      autoFocus: true,
-      extensions: defaultExtensionsMain,
+      extensions: [
+        ...(props?.extensions ?? []),
+        ...defaultExtensionsMain,
+      ],
     };
-  }, [container]);
-
-  const states = useCodeMirrorEditor(props);
+  }, [container, props]);
 
-  const swrResponse = useSWRStatic('codeMirrorEditorMain', container != null ? states : undefined);
+  const states = useCodeMirrorEditor(mergedProps);
 
-  return withUtils(swrResponse, {
-    // impl something
-  });
+  return useSWRStatic('codeMirrorEditorMain', props != null ? states : undefined);
 };

+ 4 - 4
yarn.lock

@@ -14472,10 +14472,10 @@ react-syntax-highlighter@^15.5.0:
     prismjs "^1.27.0"
     refractor "^3.6.0"
 
-react-toastify@^9.1.1:
-  version "9.1.1"
-  resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.1.tgz#9280caea4a13dc1739c350d90660a630807bf10b"
-  integrity sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw==
+react-toastify@^9.1.3:
+  version "9.1.3"
+  resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.3.tgz#1e798d260d606f50e0fab5ee31daaae1d628c5ff"
+  integrity sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==
   dependencies:
     clsx "^1.1.1"