This is an automated email from the ASF dual-hosted git repository.

robin0716 pushed a commit to branch refactor/editor
in repository https://gitbox.apache.org/repos/asf/incubator-answer.git

commit 122a5827b92b5dca20d7dab2f1848d454c4fad67
Author: robin <[email protected]>
AuthorDate: Mon Apr 15 16:39:00 2024 +0800

    refactor(ui): Refactor CodeMirror toolbar components and update dependencies
---
 ui/src/components/Editor/ToolBars/formula.tsx | 106 ----------------
 ui/src/components/Editor/ToolBars/image.tsx   |   8 +-
 ui/src/components/Editor/ToolBars/index.ts    |   2 -
 ui/src/components/Editor/types.ts             |  47 ++++++-
 ui/src/components/Editor/utils/index.ts       | 173 ++++++++++++--------------
 5 files changed, 129 insertions(+), 207 deletions(-)

diff --git a/ui/src/components/Editor/ToolBars/formula.tsx 
b/ui/src/components/Editor/ToolBars/formula.tsx
deleted file mode 100644
index 9b273e87..00000000
--- a/ui/src/components/Editor/ToolBars/formula.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { FC, useState, memo } from 'react';
-import { Dropdown } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
-
-const Formula: FC<IEditorContext> = ({ editor, wrapText }) => {
-  const { t } = useTranslation('translation', { keyPrefix: 'editor' });
-  const formulaList = [
-    {
-      type: 'line',
-      label: t('formula.options.inline'),
-    },
-    {
-      type: 'block',
-      label: t('formula.options.block'),
-    },
-  ];
-  const item = {
-    label: 'formula',
-    tip: t('formula.text'),
-  };
-  const [isShow, setShowState] = useState(false);
-  const [isLocked, setLockState] = useState(false);
-
-  const handleClick = (type, label) => {
-    if (!editor) {
-      return;
-    }
-    if (type === 'line') {
-      wrapText('\\\\( ', ' \\\\)', label);
-    } else {
-      const cursor = editor.getCursor();
-
-      wrapText('\n$$\n', '\n$$\n', label);
-
-      editor.setSelection(
-        { line: cursor.line + 2, ch: 0 },
-        { line: cursor.line + 2, ch: label.length },
-      );
-    }
-    editor?.focus();
-    setShowState(false);
-  };
-  const onAddFormula = () => {
-    if (isLocked) {
-      return;
-    }
-    setShowState(!isShow);
-  };
-
-  const handleMouseEnter = () => {
-    setLockState(true);
-  };
-
-  const handleMouseLeave = () => {
-    setLockState(false);
-  };
-  return (
-    <ToolItem
-      as="dropdown"
-      {...item}
-      isShow={isShow}
-      onClick={onAddFormula}
-      onBlur={onAddFormula}>
-      <Dropdown.Menu
-        onMouseEnter={handleMouseEnter}
-        onMouseLeave={handleMouseLeave}>
-        {formulaList.map((formula) => {
-          return (
-            <Dropdown.Item
-              key={formula.label}
-              onClick={(e) => {
-                e.preventDefault();
-                handleClick(formula.type, formula.label);
-              }}>
-              {formula.label}
-            </Dropdown.Item>
-          );
-        })}
-      </Dropdown.Menu>
-    </ToolItem>
-  );
-};
-
-export default memo(Formula);
diff --git a/ui/src/components/Editor/ToolBars/image.tsx 
b/ui/src/components/Editor/ToolBars/image.tsx
index d007408f..60099193 100644
--- a/ui/src/components/Editor/ToolBars/image.tsx
+++ b/ui/src/components/Editor/ToolBars/image.tsx
@@ -21,20 +21,16 @@ import { useEffect, useState, memo } from 'react';
 import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 
-import { EditorView } from '@codemirror/view';
 import { EditorState, StateEffect } from '@codemirror/state';
 
 import { Modal as AnswerModal } from '@/components';
 import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { IEditorContext, Editor } from '../types';
 import { uploadImage } from '@/services';
-import { ExtendEditor } from '../utils';
 
 let context: IEditorContext;
 const Image = ({ editorInstance }) => {
-  const [editor, setEditor] = useState<EditorView & ExtendEditor>(
-    editorInstance,
-  );
+  const [editor, setEditor] = useState<Editor>(editorInstance);
   const { t } = useTranslation('translation', { keyPrefix: 'editor' });
 
   const loadingText = `![${t('image.uploading')}...]()`;
diff --git a/ui/src/components/Editor/ToolBars/index.ts 
b/ui/src/components/Editor/ToolBars/index.ts
index f3bc8209..ce04587d 100644
--- a/ui/src/components/Editor/ToolBars/index.ts
+++ b/ui/src/components/Editor/ToolBars/index.ts
@@ -18,7 +18,6 @@
  */
 
 import Table from './table';
-import Formula from './formula';
 import OL from './ol';
 import UL from './ul';
 import Indent from './indent';
@@ -36,7 +35,6 @@ import Chart from './chart';
 
 export {
   Table,
-  Formula,
   OL,
   UL,
   Indent,
diff --git a/ui/src/components/Editor/types.ts 
b/ui/src/components/Editor/types.ts
index ebc7dbca..be4d8511 100644
--- a/ui/src/components/Editor/types.ts
+++ b/ui/src/components/Editor/types.ts
@@ -16,9 +16,54 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { EditorView, Command } from '@codemirror/view';
 
-import type { Editor } from 'codemirror';
+export interface Position {
+  ch: number;
+  line: number;
+  sticky?: string | undefined;
+}
+export interface ExtendEditor {
+  addKeyMap: (keyMap: Record<string, Command>) => void;
+  on: (
+    event:
+      | 'change'
+      | 'focus'
+      | 'blur'
+      | 'dragenter'
+      | 'dragover'
+      | 'drop'
+      | 'paste',
+    callback: (e?) => void,
+  ) => void;
+  getValue: () => string;
+  setValue: (value: string) => void;
+  off: (
+    event:
+      | 'change'
+      | 'focus'
+      | 'blur'
+      | 'dragenter'
+      | 'dragover'
+      | 'drop'
+      | 'paste',
+    callback: (e?) => void,
+  ) => void;
+  getSelection: () => string;
+  replaceSelection: (value: string) => void;
+  focus: () => void;
+  wrapText: (before: string, after?: string, defaultText?: string) => void;
+  replaceLines: (
+    replace: Parameters<Array<string>['map']>[0],
+    symbolLen?: number,
+  ) => void;
+  appendBlock: (content: string) => void;
+  getCursor: () => Position;
+  replaceRange: (value: string, from: Position, to: Position) => void;
+  setSelection: (anchor: Position, head?: Position) => void;
+}
 
+export type Editor = EditorView & ExtendEditor;
 export interface CodeMirrorEditor extends Editor {
   display: any;
 
diff --git a/ui/src/components/Editor/utils/index.ts 
b/ui/src/components/Editor/utils/index.ts
index 9c65a923..92fcbae3 100644
--- a/ui/src/components/Editor/utils/index.ts
+++ b/ui/src/components/Editor/utils/index.ts
@@ -1,4 +1,3 @@
-/* eslint-disable */
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -20,18 +19,13 @@
 
 import { useEffect, useState } from 'react';
 
-import type { Position } from 'codemirror';
+import { minimalSetup } from 'codemirror';
 import { EditorSelection, EditorState, StateEffect } from '@codemirror/state';
-import {
-  EditorView,
-  keymap,
-  KeyBinding,
-  Command,
-  placeholder,
-} from '@codemirror/view';
-import { markdown } from '@codemirror/lang-markdown';
-import type CodeMirror from 'codemirror';
-import 'codemirror/lib/codemirror.css';
+import { EditorView, keymap, KeyBinding, placeholder } from '@codemirror/view';
+import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
+import { languages } from '@codemirror/language-data';
+
+import { Editor, Position } from '../types';
 
 export function htmlRender(el: HTMLElement | null) {
   if (!el) return;
@@ -72,46 +66,8 @@ export function htmlRender(el: HTMLElement | null) {
     }
   });
 }
-export interface ExtendEditor {
-  addKeyMap: (keyMap: Record<string, Command>) => void;
-  on: (
-    event:
-      | 'change'
-      | 'focus'
-      | 'blur'
-      | 'dragenter'
-      | 'dragover'
-      | 'drop'
-      | 'paste',
-    callback: (e?) => void,
-  ) => void;
-  getValue: () => string;
-  setValue: (value: string) => void;
-  off: (
-    event:
-      | 'change'
-      | 'focus'
-      | 'blur'
-      | 'dragenter'
-      | 'dragover'
-      | 'drop'
-      | 'paste',
-    callback: (e?) => void,
-  ) => void;
-  getSelection: () => string;
-  replaceSelection: (value: string) => void;
-  focus: () => void;
-  wrapText: (before: string, after?: string, defaultText?: string) => void;
-  replaceLines: (
-    replace: Parameters<Array<string>['map']>[0],
-    symbolLen?: number,
-  ) => void;
-  appendBlock: (content: string) => Position;
-  getCursor: () => Position;
-  replaceRange: (value: string, from: Position, to: Position) => void;
-}
 
-const createEditorUtils = (editor: EditorView & ExtendEditor) => {
+const createEditorUtils = (editor: Editor) => {
   editor.focus = () => {
     editor.contentDOM.focus();
   };
@@ -119,9 +75,8 @@ const createEditorUtils = (editor: EditorView & 
ExtendEditor) => {
   editor.getCursor = () => {
     const range = editor.state.selection.ranges[0];
     const line = editor.state.doc.lineAt(range.from).number;
-    const from = editor.state.doc.line(line).from;
-    const to = editor.state.doc.line(line).to;
-    return { from, to, ch: range.from - from };
+    const { from, to } = editor.state.doc.line(line);
+    return { from, to, ch: range.from - from, line };
   };
 
   editor.addKeyMap = (keyMap) => {
@@ -161,6 +116,19 @@ const createEditorUtils = (editor: EditorView & 
ExtendEditor) => {
     });
   };
 
+  editor.setSelection = (anchor: Position, head?: Position) => {
+    editor.dispatch({
+      selection: EditorSelection.create([
+        EditorSelection.range(
+          editor.state.doc.line(anchor.line).from + anchor.ch,
+          head
+            ? editor.state.doc.line(head.line).from + head.ch
+            : editor.state.doc.line(anchor.line).from + anchor.ch,
+        ),
+      ]),
+    });
+  };
+
   editor.on = (event, callback) => {
     if (event === 'change') {
       const change = EditorView.updateListener.of((update) => {
@@ -242,11 +210,13 @@ const createEditorUtils = (editor: EditorView & 
ExtendEditor) => {
       editor.replaceSelection(`${before}${defaultText}${after}`);
     }
   };
-  editor.replaceLines = (replace: Parameters<Array<string>['map']>[0]) => {
+  editor.replaceLines = (
+    replace: Parameters<Array<string>['map']>[0],
+    symbolLen = 0,
+  ) => {
     const range = editor.state.selection.ranges[0];
     const line = editor.state.doc.lineAt(range.from).number;
-    const from = editor.state.doc.line(line).from;
-    const to = editor.state.doc.line(line).to;
+    const { from, to } = editor.state.doc.line(line);
     const lines = editor.state.sliceDoc(from, to).split('\n');
 
     const insert = lines.map(replace).join('\n');
@@ -261,20 +231,25 @@ const createEditorUtils = (editor: EditorView & 
ExtendEditor) => {
           insert,
         },
       ],
-      selection: EditorSelection.single(selectionStart, selectionEnd),
+      selection: EditorSelection.create([
+        EditorSelection.range(selectionStart + symbolLen, selectionEnd),
+      ]),
     });
   };
 
-  editor.appendBlock = (content: string): Position => {
+  editor.appendBlock = (content: string) => {
     const range = editor.state.selection.ranges[0];
     const line = editor.state.doc.lineAt(range.from).number;
-    const from = editor.state.doc.line(line).from;
-    const to = editor.state.doc.line(line).to;
+    const { from, to } = editor.state.doc.line(line);
+
     let insert = `\n\n${content}`;
+
     let selection = EditorSelection.single(to, to + content.length);
     if (from === to) {
       insert = `${content}\n`;
-      selection = EditorSelection.single(from, from + content.length);
+      selection = EditorSelection.create([
+        EditorSelection.cursor(to + content.length),
+      ]);
     }
 
     editor.dispatch({
@@ -293,8 +268,9 @@ const createEditorUtils = (editor: EditorView & 
ExtendEditor) => {
     selectionStart: Position,
     selectionEnd: Position,
   ) => {
-    const from = selectionStart.from;
-    const to = selectionEnd.to + selectionEnd.ch;
+    const from =
+      editor.state.doc.line(selectionStart.line).from + selectionStart.ch;
+    const to = editor.state.doc.line(selectionEnd.line).from + selectionEnd.ch;
     editor.dispatch({
       changes: [
         {
@@ -309,6 +285,7 @@ const createEditorUtils = (editor: EditorView & 
ExtendEditor) => {
 
   return editor;
 };
+
 export const useEditor = ({
   editorRef,
   placeholder: placeholderText,
@@ -317,29 +294,41 @@ export const useEditor = ({
   onFocus,
   onBlur,
 }) => {
-  const [editor, setEditor] = useState<CodeMirror.Editor | null>(null);
+  const [editor, setEditor] = useState<Editor | null>(null);
   const [value, setValue] = useState<string>('');
   const init = async () => {
-    const theme = EditorView.theme({
-      '&': {
-        width: '100%',
-        height: '100%',
-      },
-      '&.cm-focused': {
-        outline: 'none',
-      },
-      '.cm-content': {
-        width: '100%',
-        padding: '1rem',
-      },
-      '.cm-line': {
-        whiteSpace: 'pre-wrap',
-        wordWrap: 'break-word',
-        wordBreak: 'break-all',
+    const theme = EditorView.theme(
+      {
+        '&': {
+          width: '100%',
+          height: '100%',
+        },
+        '&.cm-focused': {
+          outline: 'none',
+        },
+        '.cm-content': {
+          width: '100%',
+          padding: '1rem',
+        },
+        '.cm-line': {
+          whiteSpace: 'pre-wrap',
+          wordWrap: 'break-word',
+          wordBreak: 'break-all',
+        },
       },
-    });
-    let startState = EditorState.create({
-      extensions: [markdown(), theme, placeholder(placeholderText)],
+      { dark: false },
+    );
+
+    const startState = EditorState.create({
+      extensions: [
+        minimalSetup,
+        markdown({
+          codeLanguages: languages,
+          base: markdownLanguage,
+        }),
+        theme,
+        placeholder(placeholderText),
+      ],
     });
 
     const view = new EditorView({
@@ -347,30 +336,30 @@ export const useEditor = ({
       state: startState,
     });
 
-    const editor = createEditorUtils(view as EditorView & ExtendEditor);
+    const cm = createEditorUtils(view as Editor);
 
     if (autoFocus) {
       setTimeout(() => {
-        editor.focus();
+        cm.focus();
       }, 10);
     }
 
-    editor.on('change', () => {
-      const newValue = editor.getValue();
+    cm.on('change', () => {
+      const newValue = cm.getValue();
       setValue(newValue);
     });
 
-    editor.on('focus', () => {
+    cm.on('focus', () => {
       onFocus?.();
     });
 
-    editor.on('blur', () => {
+    cm.on('blur', () => {
       onBlur?.();
     });
 
-    setEditor(editor);
+    setEditor(cm);
 
-    return editor;
+    return cm;
   };
 
   useEffect(() => {

Reply via email to