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

Merge pull request #9005 from weseek/fix/undo-in-comment-editor

fix: Undo in the comment editor
Yuki Takei 1 год назад
Родитель
Сommit
146f09681c

+ 9 - 5
apps/app/src/client/components/PageComment/CommentEditor.tsx

@@ -9,6 +9,7 @@ import { CodeMirrorEditorComment } from '@growi/editor/dist/client/components/Co
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
 import { useResolvedThemeForEditor } from '@growi/editor/dist/client/stores/use-resolved-theme';
 import { UserPicture } from '@growi/ui/dist/components';
+import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import {
@@ -208,10 +209,13 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
     });
   }, [codeMirrorEditor, pageId]);
 
-  const onChangeHandler = useCallback(async(value: string) => {
-    const dirtyNum = await evaluateEditorDirtyMap(editorKey, value);
-    mutateIsEnabledUnsavedWarning(dirtyNum > 0);
-  }, [editorKey, evaluateEditorDirtyMap, mutateIsEnabledUnsavedWarning]);
+  const cmProps = useMemo<ReactCodeMirrorProps>(() => ({
+    onChange: async(value: string) => {
+      const dirtyNum = await evaluateEditorDirtyMap(editorKey, value);
+      mutateIsEnabledUnsavedWarning(dirtyNum > 0);
+    },
+  }), [editorKey, evaluateEditorDirtyMap, mutateIsEnabledUnsavedWarning]);
+
 
   // initialize CodeMirrorEditor
   useEffect(() => {
@@ -260,10 +264,10 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
             <CodeMirrorEditorComment
               editorKey={editorKey}
               acceptedUploadFileType={acceptedUploadFileType}
-              onChange={onChangeHandler}
               onSave={postCommentHandler}
               onUpload={uploadHandler}
               editorSettings={editorSettings}
+              cmProps={cmProps}
             />
           </TabPane>
           <TabPane tabId="comment_preview">

+ 10 - 6
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -3,7 +3,6 @@ import React, {
   useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
 } from 'react';
 
-
 import type EventEmitter from 'events';
 import nodePath from 'path';
 
@@ -14,6 +13,7 @@ import { CodeMirrorEditorMain } from '@growi/editor/dist/client/components/CodeM
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
 import { useResolvedThemeForEditor } from '@growi/editor/dist/client/stores/use-resolved-theme';
 import { useRect } from '@growi/ui/dist/utils';
+import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { throttle, debounce } from 'throttle-debounce';
@@ -159,10 +159,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     setMarkdownToPreview(value);
   })), []);
 
-  const markdownChangedHandler = useCallback((value: string) => {
-    setMarkdownPreviewWithDebounce(value);
-  }, [setMarkdownPreviewWithDebounce]);
-
 
   const { scrollEditorHandler, scrollPreviewHandler } = useScrollSync(GlobalCodeMirrorEditorKey.MAIN, previewRef);
 
@@ -267,6 +263,14 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     });
   }, [codeMirrorEditor, pageId]);
 
+
+  const cmProps = useMemo<ReactCodeMirrorProps>(() => ({
+    onChange: (value: string) => {
+      setMarkdownPreviewWithDebounce(value);
+    },
+  }), [setMarkdownPreviewWithDebounce]);
+
+
   // set handler to save and return to View
   useEffect(() => {
     globalEmitter.on('saveAndReturnToView', saveAndReturnToViewHandler);
@@ -363,7 +367,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
         <div className="page-editor-editor-container flex-expand-vert border-end">
           <CodeMirrorEditorMain
             isEditorMode={editorMode === EditorMode.Editor}
-            onChange={markdownChangedHandler}
             onSave={saveWithShortcut}
             onUpload={uploadHandler}
             acceptedUploadFileType={acceptedUploadFileType}
@@ -374,6 +377,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
             initialValue={initialValue}
             editorSettings={editorSettings}
             onEditorsUpdated={onEditorsUpdated}
+            cmProps={cmProps}
           />
         </div>
         <div

+ 5 - 7
packages/editor/src/client/components-internal/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -34,10 +34,13 @@ const CodeMirrorEditorContainer = forwardRef<HTMLDivElement, DetailedHTMLProps<R
 );
 
 export type CodeMirrorEditorProps = {
+  /**
+   * Specity the props for the react-codemirror component. **This must be a memolized object.**
+   */
+  cmProps?: ReactCodeMirrorProps,
   acceptedUploadFileType?: AcceptedUploadFileType,
   indentSize?: number,
   editorSettings?: EditorSettings,
-  onChange?: (value: string) => void,
   onSave?: () => void,
   onUpload?: (files: File[]) => void,
   onScroll?: () => void,
@@ -53,10 +56,10 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
     editorKey,
     hideToolbar,
 
+    cmProps,
     acceptedUploadFileType = AcceptedUploadFileType.NONE,
     indentSize,
     editorSettings,
-    onChange,
     onSave,
     onUpload,
     onScroll,
@@ -64,11 +67,6 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
   const containerRef = useRef(null);
 
-  const cmProps = useMemo<ReactCodeMirrorProps>(() => {
-    return {
-      onChange,
-    };
-  }, [onChange]);
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey, containerRef.current, cmProps);
 
   useDefaultExtensions(codeMirrorEditor);

+ 7 - 2
packages/editor/src/client/components-internal/playground/Playground.tsx

@@ -1,8 +1,9 @@
 import {
-  useCallback, useEffect, useState,
+  useCallback, useEffect, useMemo, useState,
 } from 'react';
 
 import { AcceptedUploadFileType } from '@growi/core';
+import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 import { toast } from 'react-toastify';
 
 import { GlobalCodeMirrorEditorKey } from '../../../consts';
@@ -62,6 +63,10 @@ export const Playground = (): JSX.Element => {
 
   }, [codeMirrorEditor]);
 
+  const cmProps = useMemo<ReactCodeMirrorProps>(() => ({
+    onChange: setMarkdownToPreview,
+  }), []);
+
   return (
     <div className="d-flex flex-column vw-100 flex-expand-vh-100">
       <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>
@@ -72,11 +77,11 @@ export const Playground = (): JSX.Element => {
           <CodeMirrorEditorMain
             isEditorMode
             onSave={saveHandler}
-            onChange={setMarkdownToPreview}
             onUpload={uploadHandler}
             indentSize={4}
             acceptedUploadFileType={AcceptedUploadFileType.ALL}
             editorSettings={editorSettings}
+            cmProps={cmProps}
           />
         </div>
         <div className="flex-expand-vert d-none d-lg-flex bg-light text-dark border-start border-dark-subtle p-3">

+ 15 - 2
packages/editor/src/client/components/CodeMirrorEditorMain.tsx

@@ -1,8 +1,10 @@
-import { useEffect } from 'react';
+import { useEffect, useMemo } from 'react';
 
 import { type Extension } from '@codemirror/state';
 import { keymap, scrollPastEnd } from '@codemirror/view';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
+import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
+import deepmerge from 'ts-deepmerge';
 
 import { GlobalCodeMirrorEditorKey } from '../../consts';
 import { CodeMirrorEditor, type CodeMirrorEditorProps } from '../components-internal/CodeMirrorEditor';
@@ -28,7 +30,7 @@ type Props = CodeMirrorEditorProps & {
 
 export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
   const {
-    user, pageId, initialValue, isEditorMode,
+    user, pageId, initialValue, isEditorMode, cmProps,
     onSave, onEditorsUpdated, ...otherProps
   } = props;
 
@@ -66,10 +68,21 @@ export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
     return cleanupFunction;
   }, [codeMirrorEditor, onSave]);
 
+  const cmPropsOverride = useMemo<ReactCodeMirrorProps>(() => deepmerge(
+    cmProps ?? {},
+    {
+      // Disable the basic history configuration since this component uses Y.UndoManager instead
+      basicSetup: {
+        history: false,
+      },
+    },
+  ), [cmProps]);
+
   return (
     <CodeMirrorEditor
       editorKey={GlobalCodeMirrorEditorKey.MAIN}
       onSave={onSave}
+      cmProps={cmPropsOverride}
       {...otherProps}
     />
   );

+ 18 - 22
packages/editor/src/client/services/use-codemirror-editor/use-codemirror-editor.ts

@@ -40,29 +40,25 @@ export type UseCodeMirrorEditor = {
 
 export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor => {
 
-  const mergedProps = useMemo(() => {
-    return deepmerge(
-      props ?? {},
-      {
-        // Reset settings of react-codemirror.
-        // Extensions are defined first will be used if they have the same priority.
-        // If extensions conflict, disable them here.
-        // And add them to defaultExtensions: Extension[] with a lower priority.
-        // ref: https://codemirror.net/examples/config/
-        // ------- Start -------
-        indentWithTab: false,
-        basicSetup: {
-          defaultKeymap: false,
-          dropCursor: false,
-          highlightActiveLine: false,
-          highlightActiveLineGutter: false,
-          // Disabled react-codemirror history for Y.UndoManager
-          history: false,
-        },
-        // ------- End -------
+  const mergedProps = useMemo(() => deepmerge(
+    {
+      // Reset settings of react-codemirror.
+      // Extensions are defined first will be used if they have the same priority.
+      // If extensions conflict, disable them here.
+      // And add them to defaultExtensions: Extension[] with a lower priority.
+      // ref: https://codemirror.net/examples/config/
+      // ------- Start -------
+      indentWithTab: false,
+      basicSetup: {
+        defaultKeymap: false,
+        dropCursor: false,
+        highlightActiveLine: false,
+        highlightActiveLineGutter: false,
       },
-    );
-  }, [props]);
+      // ------- End -------
+    },
+    props ?? {},
+  ), [props]);
 
   const { state, view } = useCodeMirror(mergedProps);
 

+ 4 - 8
packages/editor/src/client/stores/codemirror-editor.ts

@@ -26,14 +26,10 @@ export const useCodeMirrorEditorIsolated = (
   const currentData = ref.current;
 
   const swrKey = key != null ? `codeMirrorEditor_${key}` : null;
-  const mergedProps = useMemo<UseCodeMirror>(() => {
-    return deepmerge(
-      props ?? {},
-      {
-        container,
-      },
-    );
-  }, [container, props]);
+  const mergedProps = useMemo<UseCodeMirror>(() => deepmerge(
+    { container },
+    props ?? {},
+  ), [container, props]);
 
   const newData = useCodeMirrorEditor(mergedProps);