Explorar o código

Merge pull request #8015 from weseek/feat/new-editor-set-cursor

feat(editor): setCursor method
Yuki Takei %!s(int64=2) %!d(string=hai) anos
pai
achega
d4370d56a3

+ 14 - 26
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useCallback, useEffect, useMemo, useRef, useState,
+  useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
 } from 'react';
 } from 'react';
 
 
 import EventEmitter from 'events';
 import EventEmitter from 'events';
@@ -116,7 +116,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
 
   const { data: codemirrorEditor } = useCodeMirrorEditorMain(codeMirrorEditorContainerRef.current);
   const { data: codemirrorEditor } = useCodeMirrorEditorMain(codeMirrorEditorContainerRef.current);
-  const { initDoc } = codemirrorEditor ?? {};
+  const { initDoc, focus: focusToEditor, setCaretLine } = codemirrorEditor ?? {};
 
 
   const { data: rendererOptions } = usePreviewOptions();
   const { data: rendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
@@ -492,21 +492,14 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
 
   // initial caret line
   // initial caret line
   useEffect(() => {
   useEffect(() => {
-    // TODO: implement
-    // refs: https://redmine.weseek.co.jp/issues/126516
-    // if (editorRef.current != null) {
-    //   editorRef.current.setCaretLine(0);
-    // }
-  }, []);
+    setCaretLine?.();
+  }, [setCaretLine]);
 
 
   // set handler to set caret line
   // set handler to set caret line
   useEffect(() => {
   useEffect(() => {
     const handler = (line) => {
     const handler = (line) => {
-      // TODO: implement
-      // refs: https://redmine.weseek.co.jp/issues/126516
-      // if (editorRef.current != null) {
-      //   editorRef.current.setCaretLine(line);
-      // }
+      setCaretLine?.(line);
+
       if (previewRef.current != null) {
       if (previewRef.current != null) {
         scrollSyncHelper.scrollPreview(previewRef.current, line);
         scrollSyncHelper.scrollPreview(previewRef.current, line);
       }
       }
@@ -516,7 +509,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     return function cleanup() {
     return function cleanup() {
       globalEmitter.removeListener('setCaretLine', handler);
       globalEmitter.removeListener('setCaretLine', handler);
     };
     };
-  }, []);
+  }, [setCaretLine]);
 
 
   // set handler to save and return to View
   // set handler to save and return to View
   useEffect(() => {
   useEffect(() => {
@@ -528,13 +521,11 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   }, [saveAndReturnToViewHandler]);
   }, [saveAndReturnToViewHandler]);
 
 
   // set handler to focus
   // set handler to focus
-  useEffect(() => {
-    // TODO: implement
-    // refs: https://redmine.weseek.co.jp/issues/126516
-    // if (editorRef.current != null && editorMode === EditorMode.Editor) {
-    //   editorRef.current.forceToFocus();
-    // }
-  }, [editorMode]);
+  useLayoutEffect(() => {
+    if (editorMode === EditorMode.Editor) {
+      focusToEditor?.();
+    }
+  }, [editorMode, focusToEditor]);
 
 
   // Detect indent size from contents (only when users are allowed to change it)
   // Detect indent size from contents (only when users are allowed to change it)
   useEffect(() => {
   useEffect(() => {
@@ -556,11 +547,8 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   // UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
   // UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
   const onRouterChangeComplete = useCallback(() => {
   const onRouterChangeComplete = useCallback(() => {
     initDoc?.(initialValue);
     initDoc?.(initialValue);
-
-    // TODO: implement
-    // refs: https://redmine.weseek.co.jp/issues/126516
-    // editorRef.current?.setCaretLine(0);
-  }, [initDoc, initialValue]);
+    setCaretLine?.();
+  }, [initDoc, initialValue, setCaretLine]);
 
 
   useEffect(() => {
   useEffect(() => {
     router.events.on('routeChangeComplete', onRouterChangeComplete);
     router.events.on('routeChangeComplete', onRouterChangeComplete);

+ 1 - 0
packages/editor/package.json

@@ -32,6 +32,7 @@
     "bootstrap": "^5.3.1",
     "bootstrap": "^5.3.1",
     "codemirror": "^6.0.1",
     "codemirror": "^6.0.1",
     "eslint-plugin-react-refresh": "^0.4.1",
     "eslint-plugin-react-refresh": "^0.4.1",
+    "react-hook-form": "^7.45.4",
     "swr": "^2.0.3"
     "swr": "^2.0.3"
   }
   }
 }
 }

+ 60 - 12
packages/editor/src/components/playground/PlaygroundController.tsx

@@ -1,8 +1,10 @@
 import { useCallback } from 'react';
 import { useCallback } from 'react';
 
 
+import { useForm } from 'react-hook-form';
+
 import { useCodeMirrorEditorMain } from '../../stores';
 import { useCodeMirrorEditorMain } from '../../stores';
 
 
-export const PlaygroundController = (): JSX.Element => {
+export const InitEditorValueRow = (): JSX.Element => {
 
 
   const { data } = useCodeMirrorEditorMain();
   const { data } = useCodeMirrorEditorMain();
 
 
@@ -12,18 +14,64 @@ export const PlaygroundController = (): JSX.Element => {
   }, [initDoc]);
   }, [initDoc]);
 
 
   return (
   return (
-    <>
-      <div className="row">
-        <div className="column">
-          <button
-            type="button"
-            className="btn btn-outline-secondary"
-            onClick={() => initEditorValue()}
-          >
-            Initialize editor value
-          </button>
+    <div className="row">
+      <div className="col">
+        <button
+          type="button"
+          className="btn btn-outline-secondary"
+          onClick={() => initEditorValue()}
+        >
+          Initialize editor value
+        </button>
+      </div>
+    </div>
+  );
+};
+
+type SetCaretLineRowFormData = {
+  lineNumber: number | string;
+};
+
+export const SetCaretLineRow = (): JSX.Element => {
+
+  const { data } = useCodeMirrorEditorMain();
+  const { register, handleSubmit } = useForm<SetCaretLineRowFormData>({
+    defaultValues: {
+      lineNumber: 1,
+    },
+  });
+
+  const setCaretLine = data?.setCaretLine;
+  const onSubmit = handleSubmit((submitData) => {
+    const lineNumber = Number(submitData.lineNumber) || 1;
+    setCaretLine?.(lineNumber);
+  });
+
+  return (
+    <form className="row mt-3" onSubmit={onSubmit}>
+      <div className="col">
+        <div className="input-group">
+          <input
+            {...register('lineNumber')}
+            type="number"
+            className="form-control"
+            placeholder="Input line number"
+            aria-label="line number"
+            aria-describedby="button-set-cursor"
+          />
+          <button type="submit" className="btn btn-outline-secondary" id="button-set-cursor">Set the cursor</button>
         </div>
         </div>
       </div>
       </div>
-    </>
+    </form>
+
+  );
+};
+
+export const PlaygroundController = (): JSX.Element => {
+  return (
+    <div className="container">
+      <InitEditorValueRow />
+      <SetCaretLineRow />
+    </div>
   );
   );
 };
 };

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

@@ -12,6 +12,8 @@ export type UseCodeMirrorEditor = UseCodeMirror;
 type UseCodeMirrorEditorUtils = {
 type UseCodeMirrorEditorUtils = {
   initState: (config?: EditorStateConfig) => void,
   initState: (config?: EditorStateConfig) => void,
   initDoc: (doc?: string) => void,
   initDoc: (doc?: string) => void,
+  focus: () => void,
+  setCaretLine: (lineNumber?: number) => void,
 }
 }
 
 
 export type UseCodeMirrorEditorResponse = UseCodeMirrorEditorStates & UseCodeMirrorEditorUtils;
 export type UseCodeMirrorEditorResponse = UseCodeMirrorEditorStates & UseCodeMirrorEditorUtils;
@@ -56,6 +58,28 @@ export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorE
     initState({ doc });
     initState({ doc });
   }, [initState]);
   }, [initState]);
 
 
+  // implement focus method
+  const focus = useCallback((): void => {
+    view?.focus();
+  }, [view]);
+
+  // implement setCaretLine method
+  const setCaretLine = useCallback((lineNumber?: number): void => {
+    if (view == null) {
+      return;
+    }
+
+    const posOfLineEnd = view.state.doc.line(lineNumber ?? 1).to;
+    view.dispatch({
+      selection: {
+        anchor: posOfLineEnd,
+        head: posOfLineEnd,
+      },
+    });
+    // focus
+    view.focus();
+  }, [view]);
+
   useEffect(() => {
   useEffect(() => {
     if (props?.container != null) {
     if (props?.container != null) {
       setContainer(props.container);
       setContainer(props.container);
@@ -66,5 +90,7 @@ export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorE
     ...codemirror,
     ...codemirror,
     initState,
     initState,
     initDoc,
     initDoc,
+    focus,
+    setCaretLine,
   };
   };
 };
 };

+ 5 - 0
yarn.lock

@@ -14333,6 +14333,11 @@ react-fast-compare@^3.0.1:
   resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
   resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
   integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
   integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
 
 
+react-hook-form@^7.45.4:
+  version "7.45.4"
+  resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.4.tgz#73d228b704026ae95d7e5f7b207a681b173ec62a"
+  integrity sha512-HGDV1JOOBPZj10LB3+OZgfDBTn+IeEsNOKiq/cxbQAIbKaiJUe/KV8DBUzsx0Gx/7IG/orWqRRm736JwOfUSWQ==
+
 react-hotkeys@^2.0.0:
 react-hotkeys@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f"
   resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f"