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

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

soumaeda 2 лет назад
Родитель
Сommit
a273229673
25 измененных файлов с 256 добавлено и 187 удалено
  1. 4 6
      apps/app/src/components/Admin/AuditLog/DateRangePicker.tsx
  2. 3 5
      apps/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx
  3. 27 19
      apps/app/src/components/Admin/AuditLogManagement.tsx
  4. 3 3
      apps/app/src/components/Admin/G2GDataTransferExportForm.tsx
  5. 3 3
      apps/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx
  6. 11 34
      apps/app/src/components/PageEditor/PageEditor.tsx
  7. 0 11
      apps/app/src/styles/_editor.scss
  8. 1 1
      package.json
  9. 2 2
      packages/core/src/swr/use-swr-static.ts
  10. 2 1
      packages/editor/package.json
  11. 0 0
      packages/editor/src/components/CodeMirrorEditor.module.scss
  12. 40 0
      packages/editor/src/components/CodeMirrorEditor.tsx
  13. 0 9
      packages/editor/src/components/CodeMirrorEditorContainer.tsx
  14. 65 0
      packages/editor/src/components/CodeMirrorEditorMain.tsx
  15. 2 1
      packages/editor/src/components/index.ts
  16. 22 31
      packages/editor/src/components/playground/Playground.tsx
  17. 4 3
      packages/editor/src/components/playground/PlaygroundController.tsx
  18. 4 0
      packages/editor/src/consts/global-code-mirror-editor-key.ts
  19. 1 0
      packages/editor/src/consts/index.ts
  20. 1 0
      packages/editor/src/index.ts
  21. 9 11
      packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts
  22. 0 27
      packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/append-extension.ts
  23. 33 0
      packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/append-extensions.ts
  24. 15 16
      packages/editor/src/stores/codemirror-editor.ts
  25. 4 4
      yarn.lock

+ 4 - 6
apps/app/src/components/Admin/AuditLog/DateRangePicker.tsx

@@ -18,11 +18,9 @@ const CustomInput = forwardRef<HTMLInputElement, CustomInputProps>((props: Custo
 
   return (
     <div className="input-group admin-audit-log">
-      <div>
-        <span className="input-group-text">
-          <i className="fa fa-fw fa-calendar" />
-        </span>
-      </div>
+      <span className="input-group-text">
+        <i className="fa fa-fw fa-calendar" />
+      </span>
       <input
         ref={ref}
         type="text"
@@ -62,7 +60,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = (props: DateRangePicker
   }, [onChange]);
 
   return (
-    <div className="btn-group me-2">
+    <div className="me-2">
       <DatePicker
         selectsRange
         startDate={startDate}

+ 3 - 5
apps/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx

@@ -110,11 +110,9 @@ const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Pro
 
   return (
     <div className="input-group me-2">
-      <div>
-        <span className="input-group-text">
-          <i className="icon-people" />
-        </span>
-      </div>
+      <span className="input-group-text">
+        <i className="icon-people" />
+      </span>
       <AsyncTypeahead
         ref={typeaheadRef}
         id="search-username-typeahead-asynctypeahead"

+ 27 - 19
apps/app/src/components/Admin/AuditLogManagement.tsx

@@ -172,28 +172,36 @@ export const AuditLogManagement: FC = () => {
         <AuditLogSettings />
       ) : (
         <>
-          <div className="mb-3">
-            <SearchUsernameTypeahead
-              ref={typeaheadRef}
-              onChange={setUsernamesHandler}
-            />
+          <div className="row row-cols-lg-auto mb-3 g-3">
+            <div className="col-12">
+              <SearchUsernameTypeahead
+                ref={typeaheadRef}
+                onChange={setUsernamesHandler}
+              />
+            </div>
 
-            <DateRangePicker
-              startDate={startDate}
-              endDate={endDate}
-              onChange={datePickerChangedHandler}
-            />
+            <div className="col-12">
+              <DateRangePicker
+                startDate={startDate}
+                endDate={endDate}
+                onChange={datePickerChangedHandler}
+              />
+            </div>
 
-            <SelectActionDropdown
-              actionMap={actionMap}
-              availableActions={auditLogAvailableActionsData || []}
-              onChangeAction={actionCheckboxChangedHandler}
-              onChangeMultipleAction={multipleActionCheckboxChangedHandler}
-            />
+            <div className="col-12">
+              <SelectActionDropdown
+                actionMap={actionMap}
+                availableActions={auditLogAvailableActionsData || []}
+                onChangeAction={actionCheckboxChangedHandler}
+                onChangeMultipleAction={multipleActionCheckboxChangedHandler}
+              />
+            </div>
 
-            <button type="button" className="btn btn-link" onClick={clearButtonPushedHandler}>
-              {t('admin:audit_log_management.clear')}
-            </button>
+            <div className="col-12">
+              <button type="button" className="btn btn-link" onClick={clearButtonPushedHandler}>
+                {t('admin:audit_log_management.clear')}
+              </button>
+            </div>
           </div>
 
           <p

+ 3 - 3
apps/app/src/components/Admin/G2GDataTransferExportForm.tsx

@@ -199,13 +199,13 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
 
   return (
     <>
-      <form className="mt-3">
-        <div>
+      <form className="mt-3 row row-cols-lg-auto g-3 align-items-center">
+        <div className="col-12">
           <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={checkAll}>
             <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
           </button>
         </div>
-        <div>
+        <div className="col-12">
           <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={uncheckAll}>
             <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
           </button>

+ 3 - 3
apps/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx

@@ -446,13 +446,13 @@ class ImportForm extends React.Component {
 
     return (
       <>
-        <form>
-          <div>
+        <form className="row row-cols-lg-auto g-3 align-items-center">
+          <div className="col-12">
             <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={this.checkAll}>
               <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
             </button>
           </div>
-          <div>
+          <div className="col-12">
             <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={this.uncheckAll}>
               <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
             </button>

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

@@ -5,11 +5,9 @@ import React, {
 import EventEmitter from 'events';
 import nodePath from 'path';
 
-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 { CodeMirrorEditorMain, GlobalCodeMirrorEditorKey, useCodeMirrorEditorIsolated } from '@growi/editor';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -166,18 +164,13 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     mutateIsEnabledUnsavedWarning(value !== initialValueRef.current);
   })), [mutateIsEnabledUnsavedWarning]);
 
-  const useCodeMirrorEditorMainProps = useMemo<ReactCodeMirrorProps>(() => {
-    return {
-      onChange: (value) => {
-        setMarkdownPreviewWithDebounce(value);
-        mutateIsEnabledUnsavedWarningWithDebounce(value);
-      },
-    };
+  const markdownChangedHandler = useCallback((value: string) => {
+    setMarkdownPreviewWithDebounce(value);
+    mutateIsEnabledUnsavedWarningWithDebounce(value);
   }, [mutateIsEnabledUnsavedWarningWithDebounce, setMarkdownPreviewWithDebounce]);
-  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(
-    codeMirrorEditorContainerRef.current,
-    useCodeMirrorEditorMainProps,
-  );
+
+
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
 
   const checkIsConflict = useCallback((data) => {
@@ -514,25 +507,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     };
   }, [saveAndReturnToViewHandler]);
 
-  // set handler to save with shortcut key
-  useEffect(() => {
-    const extension = keymap.of([
-      {
-        key: 'Mod-s',
-        preventDefault: true,
-        run: () => {
-          saveWithShortcut();
-          return true;
-        },
-      },
-    ]);
-
-    const cleanupFunction = codeMirrorEditor?.appendExtension(extension);
-
-    return cleanupFunction;
-
-  }, [codeMirrorEditor, saveWithShortcut]);
-
   // set handler to focus
   useLayoutEffect(() => {
     if (editorMode === EditorMode.Editor) {
@@ -595,7 +569,10 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
           onUpload={uploadHandler}
           onSave={saveWithShortcut}
         /> */}
-        <CodeMirrorEditorContainer ref={codeMirrorEditorContainerRef} />
+        <CodeMirrorEditorMain
+          onChange={markdownChangedHandler}
+          onSave={saveWithShortcut}
+        />
       </div>
       <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">
         <Preview

+ 0 - 11
apps/app/src/styles/_editor.scss

@@ -117,17 +117,6 @@
 
     .grw-taglabels-container {
       margin-bottom: 0;
-
-      // TODO: .form-inline dropped in bootstrap v5
-      // https://redmine.weseek.co.jp/issues/128307
-      // To scroll tags horizontally
-      .grw-tag-labels.form-inline {
-        flex-flow: row nowrap;
-        width: 100%;
-        overflow-x: auto;
-        overflow-y: hidden;
-        scrollbar-width: thin;
-      }
     }
   }
 

+ 1 - 1
package.json

@@ -43,7 +43,7 @@
     "cross-env": "^7.0.0",
     "dotenv-flow": "^3.2.0",
     "npm-run-all": "^4.1.5",
-    "ts-deepmerge": "^3.0.0",
+    "ts-deepmerge": "^6.2.0",
     "tslib": "^2.3.1",
     "yargs": "^17.7.1"
   },

+ 2 - 2
packages/core/src/swr/use-swr-static.ts

@@ -28,9 +28,9 @@ export function useSWRStatic<Data, Error>(
     ),
   });
 
-  // write data to cache directly
+  // update data
   if (key != null && data !== undefined) {
-    cache.set(key.toString(), { ...cache.get(key.toString()), data });
+    swrResponse.mutate(data, { optimisticData: data });
   }
 
   return swrResponse;

+ 2 - 1
packages/editor/package.json

@@ -34,6 +34,7 @@
     "eslint-plugin-react-refresh": "^0.4.1",
     "react-hook-form": "^7.45.4",
     "react-toastify": "^9.1.3",
-    "swr": "^2.2.2"
+    "swr": "^2.2.2",
+    "ts-deepmerge": "^6.2.0"
   }
 }

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


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

@@ -0,0 +1,40 @@
+import {
+  forwardRef, useMemo, useRef,
+} from 'react';
+
+import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
+
+import { GlobalCodeMirrorEditorKey } from '../consts';
+import { useCodeMirrorEditorIsolated } 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 = {
+  editorKey: string | GlobalCodeMirrorEditorKey,
+  onChange?: (value: string) => void,
+}
+
+export const CodeMirrorEditor = (props: Props): JSX.Element => {
+  const {
+    editorKey,
+    onChange,
+  } = props;
+
+  const containerRef = useRef(null);
+
+  const cmProps = useMemo<ReactCodeMirrorProps>(() => {
+    return {
+      onChange,
+    };
+  }, [onChange]);
+  useCodeMirrorEditorIsolated(editorKey, containerRef.current, cmProps);
+
+  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} />
-  );
-});

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

@@ -0,0 +1,65 @@
+import { useEffect } from 'react';
+
+import type { Extension } from '@codemirror/state';
+import { keymap, scrollPastEnd } from '@codemirror/view';
+
+import { GlobalCodeMirrorEditorKey } from '../consts';
+import { useCodeMirrorEditorIsolated } from '../stores';
+
+import { CodeMirrorEditor } from '.';
+
+
+const additionalExtensions: Extension[] = [
+  scrollPastEnd(),
+];
+
+
+type Props = {
+  onChange?: (value: string) => void,
+  onSave?: () => void,
+}
+
+export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
+  const {
+    onSave, onChange,
+  } = props;
+
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
+
+  // setup additional extensions
+  useEffect(() => {
+    return codeMirrorEditor?.appendExtensions?.(additionalExtensions);
+  }, [codeMirrorEditor]);
+
+  // 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?.appendExtensions?.(extension);
+
+    return cleanupFunction;
+  }, [codeMirrorEditor, onSave]);
+
+  return (
+    <CodeMirrorEditor
+      editorKey={GlobalCodeMirrorEditorKey.MAIN}
+      onChange={onChange}
+    />
+  );
+};

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

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

+ 22 - 31
packages/editor/src/components/playground/Playground.tsx

@@ -1,13 +1,12 @@
 import {
-  useEffect, useMemo, useRef, useState,
+  useCallback, useEffect, 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 { GlobalCodeMirrorEditorKey } from '../../consts';
+import { useCodeMirrorEditorIsolated } from '../../stores';
+import { CodeMirrorEditorMain } from '../CodeMirrorEditorMain';
 
 import { PlaygroundController } from './PlaygroundController';
 import { Preview } from './Preview';
@@ -16,37 +15,26 @@ export const Playground = (): JSX.Element => {
 
   const [markdownToPreview, setMarkdownToPreview] = useState('');
 
-  const containerRef = useRef(null);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
-  const props = useMemo<ReactCodeMirrorProps>(() => {
-    return {
-      onChange: setMarkdownToPreview,
-    };
-  }, []);
-  const { data: codeMirrorEditor } = useCodeMirrorEditorMain(containerRef.current, props);
+  const initialValue = '# header\n';
 
+  // initialize
   useEffect(() => {
-    codeMirrorEditor?.initDoc('# header\n');
-  }, [codeMirrorEditor]);
+    codeMirrorEditor?.initDoc(initialValue);
+    setMarkdownToPreview(initialValue);
+  }, [codeMirrorEditor, initialValue]);
 
-  // set handler to save with shortcut key
+  // initial caret line
   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]);
 
   return (
@@ -56,7 +44,10 @@ export const Playground = (): JSX.Element => {
       </div>
       <div className="flex-expand-horiz">
         <div className="flex-expand-vert">
-          <CodeMirrorEditorContainer ref={containerRef} />
+          <CodeMirrorEditorMain
+            onSave={saveHandler}
+            onChange={setMarkdownToPreview}
+          />
         </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} />

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

@@ -2,11 +2,12 @@ import { useCallback } from 'react';
 
 import { useForm } from 'react-hook-form';
 
-import { useCodeMirrorEditorMain } from '../../stores';
+import { GlobalCodeMirrorEditorKey } from '../../consts';
+import { useCodeMirrorEditorIsolated } from '../../stores';
 
 export const InitEditorValueRow = (): JSX.Element => {
 
-  const { data } = useCodeMirrorEditorMain();
+  const { data } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
   const initDoc = data?.initDoc;
   const initEditorValue = useCallback(() => {
@@ -34,7 +35,7 @@ type SetCaretLineRowFormData = {
 
 export const SetCaretLineRow = (): JSX.Element => {
 
-  const { data } = useCodeMirrorEditorMain();
+  const { data } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
   const { register, handleSubmit } = useForm<SetCaretLineRowFormData>({
     defaultValues: {
       lineNumber: 1,

+ 4 - 0
packages/editor/src/consts/global-code-mirror-editor-key.ts

@@ -0,0 +1,4 @@
+export const GlobalCodeMirrorEditorKey = {
+  MAIN: 'main',
+} as const;
+export type GlobalCodeMirrorEditorKey = typeof GlobalCodeMirrorEditorKey[keyof typeof GlobalCodeMirrorEditorKey]

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

@@ -0,0 +1 @@
+export * from './global-code-mirror-editor-key';

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

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

+ 9 - 11
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -5,8 +5,9 @@ import { languages } from '@codemirror/language-data';
 import { EditorState, type Extension } from '@codemirror/state';
 import { EditorView } from '@codemirror/view';
 import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
+import deepmerge from 'ts-deepmerge';
 
-import { AppendExtension, useAppendExtension } from './utils/append-extension';
+import { useAppendExtensions, type AppendExtensions } from './utils/append-extensions';
 import { useFocus, type Focus } from './utils/focus';
 import { useGetDoc, type GetDoc } from './utils/get-doc';
 import { useInitDoc, type InitDoc } from './utils/init-doc';
@@ -14,7 +15,7 @@ import { useSetCaretLine, type SetCaretLine } from './utils/set-caret-line';
 
 type UseCodeMirrorEditorUtils = {
   initDoc: InitDoc,
-  appendExtension: AppendExtension,
+  appendExtensions: AppendExtensions,
   getDoc: GetDoc,
   focus: Focus,
   setCaretLine: SetCaretLine,
@@ -32,19 +33,16 @@ const defaultExtensions: Extension[] = [
 export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor => {
 
   const mergedProps = useMemo<UseCodeMirror>(() => {
-    return {
-      ...props,
-      extensions: [
-        ...(props?.extensions ?? []),
-        ...defaultExtensions,
-      ],
-    };
+    return deepmerge(
+      props ?? {},
+      { extensions: defaultExtensions },
+    );
   }, [props]);
 
   const { state, view } = useCodeMirror(mergedProps);
 
   const initDoc = useInitDoc(view);
-  const appendExtension = useAppendExtension(view);
+  const appendExtensions = useAppendExtensions(view);
   const getDoc = useGetDoc(view);
   const focus = useFocus(view);
   const setCaretLine = useSetCaretLine(view);
@@ -53,7 +51,7 @@ export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor
     state,
     view,
     initDoc,
-    appendExtension,
+    appendExtensions,
     getDoc,
     focus,
     setCaretLine,

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

@@ -1,27 +0,0 @@
-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]);
-
-};

+ 33 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/append-extensions.ts

@@ -0,0 +1,33 @@
+import { useCallback } from 'react';
+
+import { Compartment, Extension, StateEffect } from '@codemirror/state';
+import { EditorView } from '@codemirror/view';
+
+type CleanupFunctions = () => void;
+export type AppendExtensions = (extensions: Extension | Extension[]) => CleanupFunctions | undefined;
+
+export const useAppendExtensions = (view?: EditorView): AppendExtensions => {
+
+  return useCallback((args) => {
+    const extensions = Array.isArray(args)
+      ? args
+      : [args];
+
+    const compartment = new Compartment();
+    view?.dispatch({
+      effects: extensions.map((extension) => {
+        return StateEffect.appendConfig.of(
+          compartment.of(extension),
+        );
+      }),
+    });
+
+    // return cleanup function
+    return () => {
+      view?.dispatch({
+        effects: compartment.reconfigure([]),
+      });
+    };
+  }, [view]);
+
+};

+ 15 - 16
packages/editor/src/stores/codemirror-editor.ts

@@ -1,10 +1,9 @@
 import { useMemo, useRef } from 'react';
 
-import { type Extension } from '@codemirror/state';
-import { scrollPastEnd } from '@codemirror/view';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type { ReactCodeMirrorProps, UseCodeMirror } from '@uiw/react-codemirror';
 import type { SWRResponse } from 'swr';
+import deepmerge from 'ts-deepmerge';
 
 import type { UseCodeMirrorEditor } from '../services';
 import { useCodeMirrorEditor } from '../services';
@@ -20,35 +19,35 @@ const isDeepEquals = <T extends object>(obj1: T, obj2: T): boolean => {
 };
 
 
-const defaultExtensionsMain: Extension[] = [
-  scrollPastEnd(),
-];
+export const useCodeMirrorEditorIsolated = (
+    key: string | null, container?: HTMLDivElement | null, props?: ReactCodeMirrorProps,
+): SWRResponse<UseCodeMirrorEditor> => {
 
-export const useCodeMirrorEditorMain = (container?: HTMLDivElement | null, props?: ReactCodeMirrorProps): SWRResponse<UseCodeMirrorEditor> => {
   const ref = useRef<UseCodeMirrorEditor>();
   const currentData = ref.current;
 
+  const swrKey = key != null ? `codeMirrorEditor_${key}` : null;
   const mergedProps = useMemo<UseCodeMirror>(() => {
-    return {
-      ...props,
-      container,
-      extensions: [
-        ...(props?.extensions ?? []),
-        ...defaultExtensionsMain,
-      ],
-    };
+    return deepmerge(
+      props ?? {},
+      {
+        container,
+      },
+    );
   }, [container, props]);
 
   const newData = useCodeMirrorEditor(mergedProps);
 
-  const shouldUpdate = props != null && (
+  const shouldUpdate = swrKey != null && container != null && props != null && (
     currentData == null
     || (isValid(newData) && !isDeepEquals(currentData, newData))
   );
 
   if (shouldUpdate) {
     ref.current = newData;
+    // eslint-disable-next-line no-console
+    console.info('Initializing codemirror for main');
   }
 
-  return useSWRStatic('codeMirrorEditorMain', shouldUpdate ? newData : undefined);
+  return useSWRStatic(swrKey, shouldUpdate ? newData : undefined);
 };

+ 4 - 4
yarn.lock

@@ -16647,10 +16647,10 @@ ts-dedent@^2.2.0:
   resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
   integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
 
-ts-deepmerge@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-3.0.0.tgz#231c48901606eb104ab51a74cb447af0e9e669e4"
-  integrity sha512-gpjFrde/nE3jp7l5cJTDpyhdbGdAIO/AkNjaz4V9odnopdLd9NVrQcBDEBiE/ucMV9dmNOcdgzOVwS7U6SsBhA==
+ts-deepmerge@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-6.2.0.tgz#77554381a4884d66cab799470bc2620a1c9d84e8"
+  integrity sha512-2qxI/FZVDPbzh63GwWIZYE7daWKtwXZYuyc8YNq0iTmMUwn4mL0jRLsp6hfFlgbdRSR4x2ppe+E86FnvEpN7Nw==
 
 ts-essentials@9.3.0:
   version "9.3.0"