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

re-impl the extension for saving with shortcut key

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

+ 46 - 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 = debounce(100, throttle(150, (value: string) => {
+    setMarkdownToPreview(value);
+  }));
+  const mutateIsEnabledUnsavedWarningWithDebounce = debounce(600, throttle(900, (value: string) => {
+    // Displays an unsaved warning alert
+    mutateIsEnabledUnsavedWarning(value !== codeMirrorEditor?.getDoc());
+  }));
+
+  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,31 @@ 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]);
+
+  // TODO: remove markdownToPreview from deps
+  // https://redmine.weseek.co.jp/issues/129532
+  }, [codeMirrorEditor, markdownToPreview, 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 +554,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);

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

@@ -2,8 +2,9 @@ import {
   useEffect, useMemo, useRef, useState,
 } from 'react';
 
-import { EditorView } from '@codemirror/view';
-import { ReactCodeMirrorProps, UseCodeMirror } from '@uiw/react-codemirror';
+import { keymap } from '@codemirror/view';
+import { ReactCodeMirrorProps } from '@uiw/react-codemirror';
+import { toast } from 'react-toastify';
 
 import { CodeMirrorEditorContainer } from '..';
 import { useCodeMirrorEditorMain } from '../../stores';
@@ -24,6 +25,26 @@ export const Playground = (): JSX.Element => {
   }, []);
   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 (
     <>
       <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>

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

@@ -53,23 +53,20 @@ export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor
   }, [view]);
 
   // implement appendExtension method
-  const appendExtension = useCallback((extension: Extension, compartment?: Compartment): CleanupFunction | undefined => {
+  const appendExtension = useCallback((extension: Extension): CleanupFunction | undefined => {
+    const compartment = new Compartment();
     view?.dispatch({
       effects: StateEffect.appendConfig.of(
-        compartment != null
-          ? compartment.of(extension)
-          : extension,
+        compartment.of(extension),
       ),
     });
 
-    return compartment != null
-      // return cleanup function
-      ? () => {
-        view?.dispatch({
-          effects: compartment?.reconfigure([]),
-        });
-      }
-      : undefined;
+    // return cleanup function
+    return () => {
+      view?.dispatch({
+        effects: compartment?.reconfigure([]),
+      });
+    };
   }, [view]);
 
   // implement getDoc method