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

Merge pull request #8011 from weseek/feat/editor-initialization

feat: Editor initialization
Yuki Takei 2 лет назад
Родитель
Сommit
3ac1b0e969

+ 1 - 1
apps/app/src/components/Layout/BasicLayout.tsx

@@ -40,7 +40,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
 
           <div className="flex-expand-vert">{/* neccessary for nested {children} make expanded */}
             <AlertSiteUrlUndefined />
-            <div className="flex-expand-horiz h-100">{/* neccessary for nested {children} make expanded */}
+            <div className="flex-expand-horiz">{/* neccessary for nested {children} make expanded */}
               {children}
             </div>
           </div>

+ 66 - 57
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -8,7 +8,7 @@ import nodePath from 'path';
 
 import type { IPageHasId } from '@growi/core';
 import { pathUtils } from '@growi/core/dist/utils';
-import { CodeMirrorEditorContainer, useCodeMirrorEditor } from '@growi/editor';
+import { CodeMirrorEditorContainer, useCodeMirrorEditorMain } from '@growi/editor';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -85,7 +85,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { t } = useTranslation();
   const router = useRouter();
 
-  const editorRef = useRef<IEditorMethods>(null);
   const previewRef = useRef<HTMLDivElement>(null);
   const codeMirrorEditorContainerRef = useRef<HTMLDivElement>(null);
 
@@ -116,14 +115,8 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
-  const { setContainer } = useCodeMirrorEditor({
-    container: codeMirrorEditorContainerRef.current,
-  });
-  useEffect(() => {
-    if (codeMirrorEditorContainerRef.current != null) {
-      setContainer(codeMirrorEditorContainerRef.current);
-    }
-  }, [setContainer]);
+  const { data: codemirrorEditor } = useCodeMirrorEditorMain(codeMirrorEditorContainerRef.current);
+  const { initDoc } = codemirrorEditor ?? {};
 
   const { data: rendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
@@ -156,7 +149,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   }, [isNotFound, currentPathname, editingMarkdown, isEnabledAttachTitleHeader, templateBodyData]);
 
-  const markdownToSave = useRef<string>(initialValue);
   const [markdownToPreview, setMarkdownToPreview] = useState<string>(initialValue);
 
   const { data: socket } = useGlobalSocket();
@@ -179,11 +171,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     setCreatedPageRevisionIdWithAttachment(undefined);
   }, [router]);
 
-  useEffect(() => {
-    markdownToSave.current = initialValue;
-    setMarkdownToPreview(initialValue);
-  }, [initialValue]);
-
   useEffect(() => {
     if (socket == null) { return }
 
@@ -211,7 +198,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   }, [grantData, isSlackEnabled, pageTags]);
 
   const setMarkdownWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string, isClean: boolean) => {
-    markdownToSave.current = value;
     setMarkdownToPreview(value);
 
     // Displays an unsaved warning alert
@@ -235,7 +221,10 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       mutateWaitingSaveProcessing(true);
 
       const { page } = await saveOrUpdate(
-        markdownToSave.current,
+        // TODO: get contents from the custom hook
+        // refs: https://redmine.weseek.co.jp/issues/128973
+        // markdownToSave.current,
+        '',
         { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
         options,
       );
@@ -312,9 +301,11 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
    * @param {any} file
    */
   const uploadHandler = useCallback(async(file) => {
-    if (editorRef.current == null) {
-      return;
-    }
+    // TODO: implement
+    // refs: https://redmine.weseek.co.jp/issues/126528
+    // if (editorRef.current == null) {
+    //   return;
+    // }
 
     try {
       // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -335,9 +326,11 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       if (pageId != null) {
         formData.append('page_id', pageId);
       }
-      if (pageId == null && markdownToSave.current != null) {
-        formData.append('page_body', markdownToSave.current);
-      }
+      // TODO: get contents from the custom hook
+      // refs: https://redmine.weseek.co.jp/issues/128973
+      // if (pageId == null && markdownToSave.current != null) {
+      //   formData.append('page_body', markdownToSave.current);
+      // }
 
       res = await apiPostForm('/attachments.add', formData);
       const attachment = res.attachment;
@@ -349,7 +342,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
         // modify to "![fileName](url)" syntax
         insertText = `!${insertText}`;
       }
-      editorRef.current.insertText(insertText);
+      // TODO: implement
+      // refs: https://redmine.weseek.co.jp/issues/126528
+      // editorRef.current.insertText(insertText);
 
       // when if created newly
       // Not using 'mutateGrant' to inherit the grant of the parent page
@@ -367,7 +362,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       toastError(e);
     }
     finally {
-      editorRef.current.terminateUploadingState();
+      // TODO: implement
+      // refs: https://redmine.weseek.co.jp/issues/126528
+      // editorRef.current.terminateUploadingState();
     }
   }, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
 
@@ -445,24 +442,24 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
    * scroll Editor component by scroll event of Preview component
    * @param {number} offset
    */
-  const scrollEditorByPreviewScroll = useCallback((offset: number) => {
-    if (editorRef.current == null || previewRef.current == null) {
-      return;
-    }
+  // const scrollEditorByPreviewScroll = useCallback((offset: number) => {
+  //   if (editorRef.current == null || previewRef.current == null) {
+  //     return;
+  //   }
 
-    // prevent circular invocation
-    if (isOriginOfScrollSyncEditor) {
-      isOriginOfScrollSyncEditor = false; // turn off the flag
-      return;
-    }
+  //   // prevent circular invocation
+  //   if (isOriginOfScrollSyncEditor) {
+  //     isOriginOfScrollSyncEditor = false; // turn off the flag
+  //     return;
+  //   }
 
-    // turn on the flag
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    isOriginOfScrollSyncPreview = true;
+  //   // turn on the flag
+  //   // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  //   isOriginOfScrollSyncPreview = true;
 
-    scrollSyncHelper.scrollEditor(editorRef.current, previewRef.current, offset);
-  }, []);
-  const scrollEditorByPreviewScrollWithThrottle = useMemo(() => throttle(20, scrollEditorByPreviewScroll), [scrollEditorByPreviewScroll]);
+  //   scrollSyncHelper.scrollEditor(editorRef.current, previewRef.current, offset);
+  // }, []);
+  // const scrollEditorByPreviewScrollWithThrottle = useMemo(() => throttle(20, scrollEditorByPreviewScroll), [scrollEditorByPreviewScroll]);
 
   const afterResolvedHandler = useCallback(async() => {
     // get page data from db
@@ -487,24 +484,29 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     if (initialValue == null) {
       return;
     }
-    markdownToSave.current = initialValue;
+    // markdownToSave.current = initialValue;
+    initDoc?.(initialValue);
     setMarkdownToPreview(initialValue);
     mutateIsEnabledUnsavedWarning(false);
-  }, [initialValue, mutateIsEnabledUnsavedWarning]);
+  }, [initDoc, initialValue, mutateIsEnabledUnsavedWarning]);
 
   // initial caret line
   useEffect(() => {
-    if (editorRef.current != null) {
-      editorRef.current.setCaretLine(0);
-    }
+    // TODO: implement
+    // refs: https://redmine.weseek.co.jp/issues/126516
+    // if (editorRef.current != null) {
+    //   editorRef.current.setCaretLine(0);
+    // }
   }, []);
 
   // set handler to set caret line
   useEffect(() => {
     const handler = (line) => {
-      if (editorRef.current != null) {
-        editorRef.current.setCaretLine(line);
-      }
+      // TODO: implement
+      // refs: https://redmine.weseek.co.jp/issues/126516
+      // if (editorRef.current != null) {
+      //   editorRef.current.setCaretLine(line);
+      // }
       if (previewRef.current != null) {
         scrollSyncHelper.scrollPreview(previewRef.current, line);
       }
@@ -527,9 +529,11 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   // set handler to focus
   useEffect(() => {
-    if (editorRef.current != null && editorMode === EditorMode.Editor) {
-      editorRef.current.forceToFocus();
-    }
+    // TODO: implement
+    // refs: https://redmine.weseek.co.jp/issues/126516
+    // if (editorRef.current != null && editorMode === EditorMode.Editor) {
+    //   editorRef.current.forceToFocus();
+    // }
   }, [editorMode]);
 
   // Detect indent size from contents (only when users are allowed to change it)
@@ -551,9 +555,12 @@ 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(() => {
-    editorRef.current?.setValue(initialValue);
-    editorRef.current?.setCaretLine(0);
-  }, [initialValue]);
+    initDoc?.(initialValue);
+
+    // TODO: implement
+    // refs: https://redmine.weseek.co.jp/issues/126516
+    // editorRef.current?.setCaretLine(0);
+  }, [initDoc, initialValue]);
 
   useEffect(() => {
     router.events.on('routeChangeComplete', onRouterChangeComplete);
@@ -573,7 +580,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const isUploadable = isUploadableImage || isUploadableFile;
 
   return (
-    <div data-testid="page-editor" id="page-editor" className={`flex-grow-1 d-flex overflow-y-auto ${props.visibility ? '' : 'd-none'}`}>
+    <div data-testid="page-editor" id="page-editor" className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>
       <div className="page-editor-editor-container flex-expand-vert">
         {/* <Editor
           ref={editorRef}
@@ -595,7 +602,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
           rendererOptions={rendererOptions}
           markdown={markdownToPreview}
           pagePath={currentPagePath}
-          onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
+          // TODO: implement
+          // refs: https://redmine.weseek.co.jp/issues/126519
+          // onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
         />
       </div>
       {/*

+ 1 - 1
apps/app/src/components/SearchPage/SearchPageBase.tsx

@@ -169,7 +169,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
     : undefined;
 
   return (
-    <div className="search-result-base flex-grow-1 d-flex overflow-y-auto" data-testid="search-result-base">
+    <div className="search-result-base flex-expand-horiz" data-testid="search-result-base">
 
       <div className="flex-expand-vert border boder-gray search-result-list" id="search-result-list">
 

+ 1 - 3
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -213,9 +213,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     <div
       key={page._id}
       data-testid="search-result-content"
-      className={`search-result-content ${styles['search-result-content']}
-        dynamic-layout-root ${growiLayoutFluidClass}
-        overflow-y-auto`}
+      className={`dynamic-layout-root ${growiLayoutFluidClass} search-result-content ${styles['search-result-content']}`}
     >
       <div className="grw-page-path-text-muted-container">
         { isRenderable && (

+ 6 - 0
apps/app/src/stores/use-static-swr.ts

@@ -0,0 +1,6 @@
+import { useSWRStatic } from '@growi/core/dist/swr';
+
+/**
+ * @deprecated Import { uswSWRStatic } from '@growi/core/dist/swr' instead.
+ */
+export const useStaticSWR = useSWRStatic;

+ 0 - 35
apps/app/src/stores/use-static-swr.tsx

@@ -1,35 +0,0 @@
-import assert from 'assert';
-
-import {
-  Key, SWRConfiguration, SWRResponse, useSWRConfig,
-} from 'swr';
-import useSWRImmutable from 'swr/immutable';
-
-
-export function useStaticSWR<Data, Error>(key: Key): SWRResponse<Data, Error>;
-export function useStaticSWR<Data, Error>(key: Key, data: Data | undefined): SWRResponse<Data, Error>;
-export function useStaticSWR<Data, Error>(key: Key, data: Data | undefined,
-  configuration: SWRConfiguration<Data, Error> | undefined): SWRResponse<Data, Error>;
-
-export function useStaticSWR<Data, Error>(
-    ...args: readonly [Key]
-    | readonly [Key, Data | undefined]
-    | readonly [Key, Data | undefined, SWRConfiguration<Data, Error> | undefined]
-): SWRResponse<Data, Error> {
-  const [key, data, configuration] = args;
-
-  assert.notStrictEqual(configuration?.fetcher, null, 'useStaticSWR does not support \'configuration.fetcher\'');
-
-  const { cache } = useSWRConfig();
-  const swrResponse = useSWRImmutable(key, null, {
-    ...configuration,
-    fallbackData: configuration?.fallbackData ?? cache.get(key)?.data,
-  });
-
-  // write data to cache directly
-  if (data !== undefined) {
-    cache.set(key, { ...cache.get(key), data });
-  }
-
-  return swrResponse;
-}

+ 4 - 1
packages/core/scss/placeholders/_flex-expand.scss

@@ -1,8 +1,10 @@
 // ref: https://discuss.codemirror.net/t/how-to-fit-the-codemirror-6-widget-into-a-flex-div/4207/4
 %flex-expand-horiz {
   display: flex;
-  flex: 1;
   flex-direction: row;
+  flex-grow: 1;
+  height: 100%;
+  overflow-y: auto;
 }
 
 // ref: https://discuss.codemirror.net/t/how-to-fit-the-codemirror-6-widget-into-a-flex-div/4207/4
@@ -10,4 +12,5 @@
   display: flex;
   flex: 1;
   flex-direction: column;
+  overflow-y: auto;
 }

+ 1 - 0
packages/core/src/swr/index.ts

@@ -1 +1,2 @@
+export * from './use-swr-static';
 export * from './with-utils';

+ 7 - 5
packages/editor/src/stores/use-static-swr.tsx → packages/core/src/swr/use-swr-static.ts

@@ -4,19 +4,21 @@ import {
 import useSWRImmutable from 'swr/immutable';
 
 
-export function useStaticSWR<Data, Error>(key: Key): SWRResponse<Data, Error>;
-export function useStaticSWR<Data, Error>(key: Key, data: Data | undefined): SWRResponse<Data, Error>;
-export function useStaticSWR<Data, Error>(key: Key, data: Data | undefined,
+export function useSWRStatic<Data, Error>(key: Key): SWRResponse<Data, Error>;
+export function useSWRStatic<Data, Error>(key: Key, data: Data | undefined): SWRResponse<Data, Error>;
+export function useSWRStatic<Data, Error>(key: Key, data: Data | undefined,
   configuration: SWRConfiguration<Data, Error> | undefined): SWRResponse<Data, Error>;
 
-export function useStaticSWR<Data, Error>(
+export function useSWRStatic<Data, Error>(
     ...args: readonly [Key]
     | readonly [Key, Data | undefined]
     | readonly [Key, Data | undefined, SWRConfiguration<Data, Error> | undefined]
 ): SWRResponse<Data, Error> {
   const [key, data, configuration] = args;
 
-  // assert.notStrictEqual(configuration?.fetcher, null, 'useStaticSWR does not support \'configuration.fetcher\'');
+  if (configuration?.fetcher != null) {
+    throw new Error("useSWRStatic does not support 'configuration.fetcher'");
+  }
 
   const { cache } = useSWRConfig();
   const swrResponse = useSWRImmutable(key, null, {

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

@@ -9,16 +9,14 @@ export const Playground = (): JSX.Element => {
 
   const containerRef = useRef(null);
 
-  useCodeMirrorEditorMain({
-    container: containerRef.current,
-  });
+  useCodeMirrorEditorMain(containerRef.current);
 
   return (
     <>
       <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>
         <div className="text-white">GrowiSubNavigation</div>
       </div>
-      <div className="flex-grow-1 d-flex overflow-y-auto">
+      <div className="flex-expand-horiz">
         <div className="flex-expand-vert">
           <CodeMirrorEditorContainer ref={containerRef} />
         </div>

+ 4 - 14
packages/editor/src/components/playground/PlaygroundController.tsx

@@ -4,22 +4,12 @@ import { useCodeMirrorEditorMain } from '../../stores';
 
 export const PlaygroundController = (): JSX.Element => {
 
-  const { data: states } = useCodeMirrorEditorMain();
+  const { data } = useCodeMirrorEditorMain();
 
+  const initDoc = data?.initDoc;
   const initEditorValue = useCallback(() => {
-    if (states == null) {
-      return;
-    }
-
-    states.view?.dispatch({
-      changes: {
-        from: 0,
-        to: states.view.state.doc.toString().length,
-        insert: '# Header\n\n- foo\n-bar\n',
-      },
-    });
-
-  }, [states]);
+    initDoc?.('# Header\n\n- foo\n-bar\n');
+  }, [initDoc]);
 
   return (
     <>

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

@@ -1,2 +1,3 @@
 export * from './components';
 export * from './services';
+export * from './stores';

+ 0 - 42
packages/editor/src/services/codemirror-editor.ts

@@ -1,42 +0,0 @@
-import { useEffect } from 'react';
-
-import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
-import { languages } from '@codemirror/language-data';
-import { EditorState, Extension } from '@codemirror/state';
-import { EditorView, scrollPastEnd } from '@codemirror/view';
-import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
-
-
-export type UseCodeMirrorEditor = UseCodeMirror;
-
-export type UseCodeMirrorEditorStates = {
-  state: EditorState | undefined;
-  setState: import('react').Dispatch<import('react').SetStateAction<EditorState | undefined>>;
-  view: EditorView | undefined;
-  setView: import('react').Dispatch<import('react').SetStateAction<EditorView | undefined>>;
-  container: HTMLDivElement | undefined;
-  setContainer: import('react').Dispatch<import('react').SetStateAction<HTMLDivElement | undefined>>;
-}
-
-const defaultExtensions: Extension[] = [
-  markdown({ base: markdownLanguage, codeLanguages: languages }),
-  scrollPastEnd(),
-];
-
-export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorEditorStates => {
-
-  const codemirror = useCodeMirror({
-    extensions: defaultExtensions,
-    ...props,
-  });
-
-  const { setContainer } = codemirror;
-
-  useEffect(() => {
-    if (props?.container != null) {
-      setContainer(props.container);
-    }
-  }, [props?.container, setContainer]);
-
-  return codemirror;
-};

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

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

+ 11 - 0
packages/editor/src/services/codemirror-editor/interfaces/react-codemirror.ts

@@ -0,0 +1,11 @@
+import type { EditorState } from '@codemirror/state';
+import type { EditorView } from '@codemirror/view';
+
+export type UseCodeMirrorEditorStates = {
+  state: EditorState | undefined;
+  setState: import('react').Dispatch<import('react').SetStateAction<EditorState | undefined>>;
+  view: EditorView | undefined;
+  setView: import('react').Dispatch<import('react').SetStateAction<EditorView | undefined>>;
+  container: HTMLDivElement | undefined;
+  setContainer: import('react').Dispatch<import('react').SetStateAction<HTMLDivElement | undefined>>;
+}

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

@@ -0,0 +1,70 @@
+import { useCallback, useEffect } from 'react';
+
+import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
+import { languages } from '@codemirror/language-data';
+import { EditorState, type EditorStateConfig, type Extension } 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,
+}
+
+export type UseCodeMirrorEditorResponse = UseCodeMirrorEditorStates & UseCodeMirrorEditorUtils;
+
+const defaultExtensions: Extension[] = [
+  markdown({ base: markdownLanguage, codeLanguages: languages }),
+];
+
+const defaultExtensionsToInit: Extension[] = [
+  ...basicSetup(),
+  ...defaultExtensions,
+];
+
+export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorEditorResponse => {
+
+  const codemirror = useCodeMirror({
+    extensions: defaultExtensions,
+    ...props,
+  });
+
+  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]);
+
+  useEffect(() => {
+    if (props?.container != null) {
+      setContainer(props.container);
+    }
+  }, [props?.container, setContainer]);
+
+  return {
+    ...codemirror,
+    initState,
+    initDoc,
+  };
+};

+ 29 - 5
packages/editor/src/stores/codemirror-editor.ts

@@ -1,12 +1,36 @@
-import type { SWRResponse } from 'swr';
+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 type { UseCodeMirrorEditor, UseCodeMirrorEditorStates } from '../services';
+import type { UseCodeMirrorEditor, UseCodeMirrorEditorResponse } from '../services';
 import { useCodeMirrorEditor } from '../services';
 
-import { useStaticSWR } from './use-static-swr';
+const defaultExtensionsMain: Extension[] = [
+  scrollPastEnd(),
+];
+
+type MainEditorUtils = {
+  // impl something
+};
+
+export const useCodeMirrorEditorMain = (container?: HTMLDivElement | null): SWRResponseWithUtils<MainEditorUtils, UseCodeMirrorEditorResponse> => {
+  const props = useMemo<UseCodeMirrorEditor>(() => {
+    return {
+      container,
+      autoFocus: true,
+      extensions: defaultExtensionsMain,
+    };
+  }, [container]);
 
-export const useCodeMirrorEditorMain = (props?: UseCodeMirrorEditor): SWRResponse<UseCodeMirrorEditorStates> => {
   const states = useCodeMirrorEditor(props);
-  return useStaticSWR('codeMirrorEditorMain', props != null ? states : undefined);
+
+  const swrResponse = useSWRStatic('codeMirrorEditorMain', container != null ? states : undefined);
+
+  return withUtils(swrResponse, {
+    // impl something
+  });
 };

+ 13 - 0
turbo.json

@@ -19,6 +19,11 @@
       "cache": false
     },
 
+    "@growi/editor#build": {
+      "dependsOn": ["@growi/core#build"],
+      "outputs": ["dist/**"],
+      "outputMode": "new-only"
+    },
     "@growi/pluginkit#build": {
       "dependsOn": ["@growi/core#build"],
       "outputs": ["dist/**"],
@@ -68,6 +73,11 @@
       "outputMode": "new-only"
     },
 
+    "@growi/editor#dev": {
+      "dependsOn": ["@growi/core#dev"],
+      "outputs": ["dist/**"],
+      "outputMode": "new-only"
+    },
     "@growi/pluginkit#dev": {
       "dependsOn": ["@growi/core#dev"],
       "outputs": ["dist/**"],
@@ -144,6 +154,9 @@
       "persistent": true
     },
 
+    "@growi/editor#lint": {
+      "dependsOn": ["@growi/core#dev"]
+    },
     "@growi/pluginkit#lint": {
       "dependsOn": ["@growi/core#dev"]
     },