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

Merge pull request #8031 from weseek/feat/save-with-new-editor

feat: Save with new editor
Yuki Takei 2 лет назад
Родитель
Сommit
b26befa024

+ 33 - 27
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -5,7 +5,8 @@ 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';
@@ -116,7 +117,11 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
   const { data: codemirrorEditor } = useCodeMirrorEditorMain(codeMirrorEditorContainerRef.current);
-  const { initDoc, focus: focusToEditor, setCaretLine } = codemirrorEditor ?? {};
+  const {
+    appendExtension,
+    initDoc, getDoc,
+    focus: focusToEditor, setCaretLine,
+  } = codemirrorEditor ?? {};
 
   const { data: rendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
@@ -221,10 +226,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       mutateWaitingSaveProcessing(true);
 
       const { page } = await saveOrUpdate(
-        // TODO: get contents from the custom hook
-        // refs: https://redmine.weseek.co.jp/issues/128973
-        // markdownToSave.current,
-        '',
+        getDoc?.() ?? '',
         { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
         options,
       );
@@ -250,16 +252,13 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     }
 
   }, [
+    getDoc,
     currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId,
     currentPagePath, currentRevisionId,
     mutateWaitingSaveProcessing, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser,
   ]);
 
   const saveAndReturnToViewHandler = useCallback(async(opts: {slackChannels: string, overwriteScopesOfDescendants?: boolean}) => {
-    if (editorMode !== EditorMode.Editor) {
-      return;
-    }
-
     const page = await save(opts);
     if (page == null) {
       return;
@@ -272,13 +271,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       updateStateAfterSave?.();
     }
     mutateEditorMode(EditorMode.View);
-  }, [editorMode, save, isNotFound, mutateEditorMode, router, updateStateAfterSave]);
+  }, [save, isNotFound, mutateEditorMode, router, updateStateAfterSave]);
 
   const saveWithShortcut = useCallback(async() => {
-    if (editorMode !== EditorMode.Editor) {
-      return;
-    }
-
     const page = await save();
     if (page == null) {
       return;
@@ -293,7 +288,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     toastSuccess(t('toaster.save_succeeded'));
     mutateEditorMode(EditorMode.Editor);
 
-  }, [editorMode, isNotFound, mutateEditorMode, router, save, t, updateStateAfterSave]);
+  }, [isNotFound, mutateEditorMode, router, save, t, updateStateAfterSave]);
 
 
   /**
@@ -301,12 +296,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
    * @param {any} file
    */
   const uploadHandler = useCallback(async(file) => {
-    // 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
       let res: any = await apiGet('/attachments.limit', {
@@ -326,11 +315,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       if (pageId != null) {
         formData.append('page_id', pageId);
       }
-      // 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);
-      // }
+      if (pageId == null) {
+        formData.append('page_body', getDoc?.() ?? '');
+      }
 
       res = await apiPostForm('/attachments.add', formData);
       const attachment = res.attachment;
@@ -520,6 +507,25 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     };
   }, [saveAndReturnToViewHandler]);
 
+  // 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;
+        },
+      },
+    ]);
+
+    const cleanupFunction = appendExtension?.(extension, compartment);
+
+    return cleanupFunction;
+  }, [appendExtension, saveWithShortcut]);
+
   // set handler to focus
   useLayoutEffect(() => {
     if (editorMode === EditorMode.Editor) {

+ 38 - 2
packages/editor/src/services/codemirror-editor/use-codemirror-editor.ts

@@ -2,7 +2,9 @@ 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 {
+  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';
@@ -12,12 +14,16 @@ 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 }),
 ];
@@ -30,8 +36,11 @@ const defaultExtensionsToInit: Extension[] = [
 export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorEditorResponse => {
 
   const codemirror = useCodeMirror({
-    extensions: defaultExtensions,
     ...props,
+    extensions: [
+      ...defaultExtensions,
+      ...(props?.extensions ?? []),
+    ],
   });
 
   const { view, setContainer } = codemirror;
@@ -58,6 +67,31 @@ export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorE
     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();
@@ -90,6 +124,8 @@ export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorE
     ...codemirror,
     initState,
     initDoc,
+    appendExtension,
+    getDoc,
     focus,
     setCaretLine,
   };