Browse Source

Merge pull request #8529 from weseek/imprv/140199-141158-all-option-in-editor-config

imprv: All editor config option is available
Yuki Takei 2 years ago
parent
commit
9e83fac479

+ 2 - 2
apps/app/_obsolete/src/components/PageEditor/Editor.tsx

@@ -5,6 +5,7 @@ import React, {
   useEffect,
 } from 'react';
 
+import type { EditorSettings } from '@growi/editor';
 import Dropzone from 'react-dropzone';
 import { useTranslation } from 'react-i18next';
 import {
@@ -12,7 +13,6 @@ import {
 } from 'reactstrap';
 
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import type { IEditorSettings } from '~/interfaces/editor-settings';
 import { useDefaultIndentSize } from '~/stores/context';
 import { useEditorSettings } from '~/stores/editor';
 import { useIsMobile } from '~/stores/ui';
@@ -36,7 +36,7 @@ export type EditorPropsType = {
   isUploadAllFileAllowed?: boolean,
   onChange?: (newValue: string, isClean?: boolean) => void,
   onUpload?: (file) => void,
-  editorSettings?: IEditorSettings,
+  editorSettings?: EditorSettings,
   indentSize?: number,
   onDragEnter?: (event: any) => void,
   onMarkdownHelpButtonClicked?: () => void,

+ 5 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -311,6 +311,11 @@
     }
   },
   "page_edit": {
+    "input_channels": "Input channels",
+    "theme": "Theme",
+    "keymap": "Keymap",
+    "indent": "Indent",
+    "editor_config": "Editor Config",
     "Show active line": "Show active line",
     "auto_format_table": "Auto format table",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",

+ 5 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -344,6 +344,11 @@
     }
   },
   "page_edit": {
+    "input_channels": "チャンネル名",
+    "theme": "テーマ",
+    "keymap": "キーマップ",
+    "indent": "インデント",
+    "editor_config": "エディタ設定",
     "Show active line": "アクティブ行をハイライト",
     "auto_format_table": "表の自動整形",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",

+ 5 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -301,6 +301,11 @@
 		}
 	},
 	"page_edit": {
+    "input_channels": "频道名",
+    "theme": "主题",
+    "keymap": "键表",
+    "indent": "缩进",
+    "editor_config": "编辑器配置",
 		"Show active line": "显示活动行",
 		"auto_format_table": "自动格式化表格",
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",

+ 3 - 1
apps/app/src/components/PageComment/CommentEditor.tsx

@@ -20,7 +20,7 @@ import {
   useCurrentUser, useIsSlackConfigured, useAcceptedUploadFileType,
 } from '~/stores/context';
 import {
-  useSWRxSlackChannels, useIsSlackEnabled, useIsEnabledUnsavedWarning,
+  useSWRxSlackChannels, useIsSlackEnabled, useIsEnabledUnsavedWarning, useEditorSettings,
 } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
 import { useNextThemes } from '~/stores/use-next-themes';
@@ -79,6 +79,7 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   const { data: acceptedUploadFileType } = useAcceptedUploadFileType();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: isSlackConfigured } = useIsSlackConfigured();
+  const { data: editorSettings } = useEditorSettings();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const {
     increment: incrementEditingCommentsNum,
@@ -336,6 +337,7 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
                 onChange={onChangeHandler}
                 onSave={postCommentHandler}
                 onUpload={uploadHandler}
+                editorSettings={editorSettings}
               />
               {/* <Editor
                 ref={editorRef}

+ 19 - 15
apps/app/src/components/PageEditor/OptionsSelector.tsx

@@ -2,8 +2,8 @@ import React, {
   memo, useCallback, useMemo, useState,
 } from 'react';
 
-import type {
-  EditorTheme, KeyMapMode,
+import {
+  type EditorTheme, type KeyMapMode, DEFAULT_KEYMAP, DEFAULT_THEME,
 } from '@growi/editor';
 import { useTranslation } from 'next-i18next';
 import Image from 'next/image';
@@ -14,11 +14,6 @@ import {
 import { useIsIndentSizeForced } from '~/stores/context';
 import { useEditorSettings, useCurrentIndentSize } from '~/stores/editor';
 
-import {
-  DEFAULT_THEME, DEFAULT_KEYMAP,
-} from '../../interfaces/editor-settings';
-
-
 type RadioListItemProps = {
   onClick: () => void,
   icon?: React.ReactNode,
@@ -91,6 +86,7 @@ const EDITORTHEME_LABEL_MAP: EditorThemeToLabel = {
 
 const ThemeSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX.Element => {
 
+  const { t } = useTranslation();
   const { data: editorSettings, update } = useEditorSettings();
   const selectedTheme = editorSettings?.theme ?? DEFAULT_THEME;
 
@@ -106,7 +102,7 @@ const ThemeSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX
   ), [update, selectedTheme]);
 
   return (
-    <Selector header="Theme" onClickBefore={onClickBefore} items={listItems} />
+    <Selector header={t('page_edit.theme')} onClickBefore={onClickBefore} items={listItems} />
   );
 });
 ThemeSelector.displayName = 'ThemeSelector';
@@ -125,6 +121,7 @@ const KEYMAP_LABEL_MAP: KeyMapModeToLabel = {
 
 const KeymapSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX.Element => {
 
+  const { t } = useTranslation();
   const { data: editorSettings, update } = useEditorSettings();
   const selectedKeymapMode = editorSettings?.keymapMode ?? DEFAULT_KEYMAP;
 
@@ -144,7 +141,7 @@ const KeymapSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JS
 
 
   return (
-    <Selector header="Keymap" onClickBefore={onClickBefore} items={listItems} />
+    <Selector header={t('page_edit.keymap')} onClickBefore={onClickBefore} items={listItems} />
   );
 });
 KeymapSelector.displayName = 'KeymapSelector';
@@ -154,6 +151,7 @@ const TYPICAL_INDENT_SIZE = [2, 4];
 
 const IndentSizeSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX.Element => {
 
+  const { t } = useTranslation();
   const { data: currentIndentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
 
   const listItems = useMemo(() => (
@@ -167,7 +165,7 @@ const IndentSizeSelector = memo(({ onClickBefore }: {onClickBefore: () => void})
   ), [currentIndentSize, mutateCurrentIndentSize]);
 
   return (
-    <Selector header="Indent" onClickBefore={onClickBefore} items={listItems} />
+    <Selector header={t('page_edit.indent')} onClickBefore={onClickBefore} items={listItems} />
   );
 });
 IndentSizeSelector.displayName = 'IndentSizeSelector';
@@ -260,6 +258,8 @@ type OptionStatus = typeof OptionsStatus[keyof typeof OptionsStatus];
 
 export const OptionsSelector = ({ collapsed }: {collapsed?: boolean}): JSX.Element => {
 
+  const { t } = useTranslation();
+
   const [dropdownOpen, setDropdownOpen] = useState(false);
 
   const [status, setStatus] = useState<OptionStatus>(OptionsStatus.Home);
@@ -282,7 +282,7 @@ export const OptionsSelector = ({ collapsed }: {collapsed?: boolean}): JSX.Eleme
         <span className="material-symbols-outlined py-0 fs-5"> settings </span>
         {
           collapsed ? <></>
-            : <label className="ms-1 me-1">Editor Config</label>
+            : <label className="ms-1 me-1">{t('page_edit.editor_config')}</label>
         }
       </DropdownToggle>
       <DropdownMenu container="body">
@@ -290,21 +290,25 @@ export const OptionsSelector = ({ collapsed }: {collapsed?: boolean}): JSX.Eleme
           status === OptionsStatus.Home && (
             <div className="d-flex flex-column">
               <label className="text-muted ms-3">
-                Editor Config
+                {t('page_edit.editor_config')}
               </label>
               <hr className="my-1" />
-              <ChangeStateButton onClick={() => setStatus(OptionsStatus.Theme)} header="Theme" data={EDITORTHEME_LABEL_MAP[editorSettings.theme ?? ''] ?? ''} />
+              <ChangeStateButton
+                onClick={() => setStatus(OptionsStatus.Theme)}
+                header={t('page_edit.theme')}
+                data={EDITORTHEME_LABEL_MAP[editorSettings.theme ?? ''] ?? ''}
+              />
               <hr className="my-1" />
               <ChangeStateButton
                 onClick={() => setStatus(OptionsStatus.Keymap)}
-                header="Keymap"
+                header={t('page_edit.keymap')}
                 data={KEYMAP_LABEL_MAP[editorSettings.keymapMode ?? ''] ?? ''}
               />
               <hr className="my-1" />
               <ChangeStateButton
                 disabled={isIndentSizeForced}
                 onClick={() => setStatus(OptionsStatus.Indent)}
-                header="Indent"
+                header={t('page_edit.indent')}
                 data={currentIndentSize.toString() ?? ''}
               />
               <hr className="my-1" />

+ 1 - 2
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -447,9 +447,8 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
             user={user ?? undefined}
             pageId={pageId ?? undefined}
             initialValue={initialValue}
+            editorSettings={editorSettings}
             onEditorsUpdated={onEditorsUpdated}
-            editorTheme={editorSettings?.theme}
-            editorKeymap={editorSettings?.keymapMode}
           />
         </div>
         <div

+ 3 - 2
apps/app/src/components/SlackNotification.tsx

@@ -1,5 +1,6 @@
 /* eslint-disable react/prop-types */
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { PopoverBody, PopoverHeader, UncontrolledPopover } from 'reactstrap';
@@ -56,7 +57,7 @@ export const SlackNotification: FC<SlackNotificationProps> = ({
           id={idForSlackPopover}
           type="text"
           value={slackChannels}
-          placeholder="Input channels"
+          placeholder={t('page_edit.input_channels', 'Input channels')}
           onChange={updateSlackChannelsHandler}
         />
         <UncontrolledPopover trigger="focus" placement="top" target={idForSlackPopover}>

+ 0 - 11
apps/app/src/interfaces/editor-settings.ts

@@ -1,11 +0,0 @@
-import { type EditorTheme, type KeyMapMode } from '@growi/editor';
-
-export const DEFAULT_KEYMAP = 'default';
-export const DEFAULT_THEME = 'defaultlight';
-
-export interface IEditorSettings {
-  theme: undefined | EditorTheme,
-  keymapMode: undefined | KeyMapMode,
-  styleActiveLine: boolean,
-  autoFormatMarkdownTable: boolean,
-}

+ 4 - 4
apps/app/src/server/models/editor-settings.ts

@@ -1,13 +1,13 @@
+import type { EditorSettings } from '@growi/editor';
+import type { Model, Document } from 'mongoose';
 import {
-  Schema, Model, Document,
+  Schema,
 } from 'mongoose';
 
-import { IEditorSettings } from '~/interfaces/editor-settings';
-
 import { getOrCreateModel } from '../util/mongoose-utils';
 
 
-export interface EditorSettingsDocument extends IEditorSettings, Document {
+export interface EditorSettingsDocument extends EditorSettings, Document {
   userId: Schema.Types.ObjectId,
 }
 export type EditorSettingsModel = Model<EditorSettingsDocument>

+ 4 - 4
apps/app/src/stores/editor.tsx

@@ -2,12 +2,12 @@ import { useCallback } from 'react';
 
 import { type Nullable } from '@growi/core';
 import { withUtils, type SWRResponseWithUtils } from '@growi/core/dist/swr';
+import type { EditorSettings } from '@growi/editor';
 import useSWR, { type SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiGet } from '~/client/util/apiv1-client';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import type { IEditorSettings } from '~/interfaces/editor-settings';
 import type { SlackChannels } from '~/interfaces/user-trigger-notification';
 
 import {
@@ -29,13 +29,13 @@ export const useEditingMarkdown = (initialData?: string): SWRResponse<string, Er
 
 
 type EditorSettingsOperation = {
-  update: (updateData: Partial<IEditorSettings>) => Promise<void>,
+  update: (updateData: Partial<EditorSettings>) => Promise<void>,
 }
 
 // TODO: Enable localStorageMiddleware
 //   - Unabling localStorageMiddleware occurrs a flickering problem when loading theme.
 //   - see: https://github.com/weseek/growi/pull/6781#discussion_r1000285786
-export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, IEditorSettings, Error> => {
+export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, EditorSettings, Error> => {
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
@@ -51,7 +51,7 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
     },
   );
 
-  return withUtils<EditorSettingsOperation, IEditorSettings, Error>(swrResult, {
+  return withUtils<EditorSettingsOperation, EditorSettings, Error>(swrResult, {
     update: async(updateData) => {
       const { data, mutate } = swrResult;
 

+ 13 - 49
packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -1,21 +1,22 @@
 import {
-  forwardRef, useMemo, useRef, useEffect, useState,
+  forwardRef, useMemo, useRef, useEffect,
 } from 'react';
 
 import { indentUnit } from '@codemirror/language';
-import { Prec, Extension } from '@codemirror/state';
-import { EditorView } from '@codemirror/view';
+import {
+  EditorView,
+} from '@codemirror/view';
 import { AcceptedUploadFileType } from '@growi/core';
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 
-import { GlobalCodeMirrorEditorKey } from '../../consts';
+import { EditorSettings, GlobalCodeMirrorEditorKey } from '../../consts';
 import {
-  useFileDropzone, FileDropzoneOverlay, getEditorTheme, type EditorTheme, getKeymap, type KeyMapMode,
+  useFileDropzone, FileDropzoneOverlay,
 } from '../../services';
 import {
   adjustPasteData, getStrFromBol,
 } from '../../services/paste-util/paste-markdown-util';
-import { useCodeMirrorEditorIsolated } from '../../stores';
+import { useDefaultExtensions, useCodeMirrorEditorIsolated, useEditorSettings } from '../../stores';
 
 import { Toolbar } from './Toolbar';
 
@@ -31,8 +32,7 @@ const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
 export type CodeMirrorEditorProps = {
   acceptedUploadFileType?: AcceptedUploadFileType,
   indentSize?: number,
-  editorTheme?: EditorTheme,
-  editorKeymap?: KeyMapMode,
+  editorSettings?: EditorSettings,
   onChange?: (value: string) => void,
   onSave?: () => void,
   onUpload?: (files: File[]) => void,
@@ -48,8 +48,7 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
     editorKey,
     acceptedUploadFileType = AcceptedUploadFileType.NONE,
     indentSize,
-    editorTheme,
-    editorKeymap,
+    editorSettings,
     onChange,
     onSave,
     onUpload,
@@ -65,6 +64,9 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   }, [onChange]);
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey, containerRef.current, cmProps);
 
+  useDefaultExtensions(codeMirrorEditor);
+  useEditorSettings(codeMirrorEditor, editorSettings, onSave);
+
   useEffect(() => {
     if (indentSize == null) {
       return;
@@ -76,6 +78,7 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
   }, [codeMirrorEditor, indentSize]);
 
+
   useEffect(() => {
     const handlePaste = (event: ClipboardEvent) => {
       event.preventDefault();
@@ -150,45 +153,6 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   }, [onScroll, codeMirrorEditor]);
 
 
-  const [themeExtension, setThemeExtension] = useState<Extension | undefined>(undefined);
-  useEffect(() => {
-    const settingTheme = async(name?: EditorTheme) => {
-      setThemeExtension(await getEditorTheme(name ?? 'defaultlight'));
-    };
-    settingTheme(editorTheme);
-  }, [codeMirrorEditor, editorTheme, setThemeExtension]);
-
-  useEffect(() => {
-    if (themeExtension == null) {
-      return;
-    }
-    // React CodeMirror has default theme which is default prec
-    // and extension have to be higher prec here than default theme.
-    const cleanupFunction = codeMirrorEditor?.appendExtensions(Prec.high(themeExtension));
-    return cleanupFunction;
-  }, [codeMirrorEditor, themeExtension]);
-
-
-  const [keymapExtension, setKeymapExtension] = useState<Extension | undefined>(undefined);
-  useEffect(() => {
-    const settingKeyMap = async(name?: KeyMapMode) => {
-      setKeymapExtension(await getKeymap(name ?? 'default'));
-    };
-    settingKeyMap(editorKeymap);
-
-  }, [codeMirrorEditor, editorKeymap, setKeymapExtension]);
-
-  useEffect(() => {
-    if (keymapExtension == null) {
-      return;
-    }
-
-    // Prevent these Keybind from overwriting the originally defined keymap.
-    const cleanupFunction = codeMirrorEditor?.appendExtensions(Prec.low(keymapExtension));
-    return cleanupFunction;
-
-  }, [codeMirrorEditor, keymapExtension, onSave]);
-
   const {
     getRootProps,
     getInputProps,

+ 3 - 5
packages/editor/src/components/CodeMirrorEditorComment.tsx

@@ -18,8 +18,7 @@ type Props = CodeMirrorEditorProps & object
 
 export const CodeMirrorEditorComment = (props: Props): JSX.Element => {
   const {
-    acceptedUploadFileType,
-    onSave, onChange, onUpload,
+    onSave, ...otherProps
   } = props;
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.COMMENT);
@@ -57,9 +56,8 @@ export const CodeMirrorEditorComment = (props: Props): JSX.Element => {
   return (
     <CodeMirrorEditor
       editorKey={GlobalCodeMirrorEditorKey.COMMENT}
-      acceptedUploadFileType={acceptedUploadFileType}
-      onChange={onChange}
-      onUpload={onUpload}
+      onSave={onSave}
+      {...otherProps}
     />
   );
 };

+ 3 - 12
packages/editor/src/components/CodeMirrorEditorMain.tsx

@@ -26,10 +26,8 @@ type Props = CodeMirrorEditorProps & {
 
 export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
   const {
-    acceptedUploadFileType,
-    indentSize, user, pageId, initialValue,
-    editorTheme, editorKeymap,
-    onSave, onChange, onUpload, onScroll, onEditorsUpdated,
+    user, pageId, initialValue,
+    onSave, onEditorsUpdated, ...otherProps
   } = props;
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
@@ -66,18 +64,11 @@ export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
     return cleanupFunction;
   }, [codeMirrorEditor, onSave]);
 
-
   return (
     <CodeMirrorEditor
       editorKey={GlobalCodeMirrorEditorKey.MAIN}
-      onChange={onChange}
       onSave={onSave}
-      onUpload={onUpload}
-      onScroll={onScroll}
-      acceptedUploadFileType={acceptedUploadFileType}
-      indentSize={indentSize}
-      editorTheme={editorTheme}
-      editorKeymap={editorKeymap}
+      {...otherProps}
     />
   );
 };

+ 12 - 3
packages/editor/src/components/playground/Playground.tsx

@@ -5,7 +5,7 @@ import {
 import { AcceptedUploadFileType } from '@growi/core';
 import { toast } from 'react-toastify';
 
-import { GlobalCodeMirrorEditorKey } from '../../consts';
+import { EditorSettings, GlobalCodeMirrorEditorKey } from '../../consts';
 import type { EditorTheme, KeyMapMode } from '../../services';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 import { CodeMirrorEditorMain } from '../CodeMirrorEditorMain';
@@ -18,6 +18,7 @@ export const Playground = (): JSX.Element => {
   const [markdownToPreview, setMarkdownToPreview] = useState('');
   const [editorTheme, setEditorTheme] = useState<EditorTheme>('defaultlight');
   const [editorKeymap, setEditorKeymap] = useState<KeyMapMode>('default');
+  const [editorSettings, setEditorSettings] = useState<EditorSettings>();
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
@@ -34,6 +35,15 @@ export const Playground = (): JSX.Element => {
     codeMirrorEditor?.setCaretLine();
   }, [codeMirrorEditor]);
 
+  useEffect(() => {
+    setEditorSettings({
+      theme: editorTheme,
+      keymapMode: editorKeymap,
+      styleActiveLine: true,
+      autoFormatMarkdownTable: true,
+    });
+  }, [setEditorSettings, editorKeymap, editorTheme]);
+
   // set handler to save with shortcut key
   const saveHandler = useCallback(() => {
     // eslint-disable-next-line no-console
@@ -65,8 +75,7 @@ export const Playground = (): JSX.Element => {
             onUpload={uploadHandler}
             indentSize={4}
             acceptedUploadFileType={AcceptedUploadFileType.ALL}
-            editorTheme={editorTheme}
-            editorKeymap={editorKeymap}
+            editorSettings={editorSettings}
           />
         </div>
         <div className="flex-expand-vert d-none d-lg-flex bg-light text-dark border-start border-dark-subtle p-3">

+ 8 - 0
packages/editor/src/consts/editor-settings.ts

@@ -0,0 +1,8 @@
+import { EditorTheme, KeyMapMode } from '../services';
+
+export interface EditorSettings {
+  theme: undefined | EditorTheme,
+  keymapMode: undefined | KeyMapMode,
+  styleActiveLine: boolean,
+  autoFormatMarkdownTable: boolean,
+}

+ 1 - 0
packages/editor/src/consts/index.ts

@@ -1,2 +1,3 @@
 export * from './global-code-mirror-editor-key';
 export * from './ydoc-awareness-user-color';
+export * from './editor-settings';

+ 4 - 62
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -1,27 +1,11 @@
 import { useMemo } from 'react';
 
-import { indentWithTab, defaultKeymap, deleteCharBackward } from '@codemirror/commands';
 import {
-  markdown, markdownLanguage,
-} from '@codemirror/lang-markdown';
-import { syntaxHighlighting, HighlightStyle, defaultHighlightStyle } from '@codemirror/language';
-import { languages } from '@codemirror/language-data';
-import {
-  EditorState, Prec, type Extension,
+  EditorState,
 } from '@codemirror/state';
-import { keymap, EditorView } from '@codemirror/view';
-import type { Command } from '@codemirror/view';
-import { tags } from '@lezer/highlight';
+import { EditorView } from '@codemirror/view';
 import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
 import deepmerge from 'ts-deepmerge';
-// see: https://github.com/yjs/y-codemirror.next#example
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore
-import { yUndoManagerKeymap } from 'y-codemirror.next';
-
-import { emojiAutocompletionSettings } from '../../extensions/emojiAutocompletionSettings';
-import { insertNewlineContinueMarkup } from '../../list-util/insert-newline-continue-markup';
-import { insertNewRowToMarkdownTable, isInTable } from '../../table-util/insert-new-row-to-table-markdown';
 
 import { useAppendExtensions, type AppendExtensions } from './utils/append-extensions';
 import { useFocus, type Focus } from './utils/focus';
@@ -35,34 +19,6 @@ import { useReplaceText, type ReplaceText } from './utils/replace-text';
 import { useSetCaretLine, type SetCaretLine } from './utils/set-caret-line';
 
 
-const onPressEnter: Command = (editor) => {
-
-  if (isInTable(editor)) {
-    insertNewRowToMarkdownTable(editor);
-    return true;
-  }
-
-  insertNewlineContinueMarkup(editor);
-
-  return true;
-};
-
-// set new markdownKeymap instead of default one
-// https://github.com/codemirror/lang-markdown/blob/main/src/index.ts#L17
-const markdownKeymap = [
-  { key: 'Backspace', run: deleteCharBackward },
-  { key: 'Enter', run: onPressEnter },
-];
-
-const markdownHighlighting = HighlightStyle.define([
-  { tag: tags.heading1, class: 'cm-header-1 cm-header' },
-  { tag: tags.heading2, class: 'cm-header-2 cm-header' },
-  { tag: tags.heading3, class: 'cm-header-3 cm-header' },
-  { tag: tags.heading4, class: 'cm-header-4 cm-header' },
-  { tag: tags.heading5, class: 'cm-header-5 cm-header' },
-  { tag: tags.heading6, class: 'cm-header-6 cm-header' },
-]);
-
 type UseCodeMirrorEditorUtils = {
   initDoc: InitDoc,
   appendExtensions: AppendExtensions,
@@ -81,28 +37,12 @@ export type UseCodeMirrorEditor = {
 } & UseCodeMirrorEditorUtils;
 
 
-const defaultExtensions: Extension[] = [
-  EditorView.lineWrapping,
-  markdown({ base: markdownLanguage, codeLanguages: languages, addKeymap: false }),
-  keymap.of(markdownKeymap),
-  keymap.of([indentWithTab]),
-  Prec.lowest(keymap.of(defaultKeymap)),
-  syntaxHighlighting(markdownHighlighting),
-  Prec.lowest(syntaxHighlighting(defaultHighlightStyle)),
-  emojiAutocompletionSettings,
-  keymap.of(yUndoManagerKeymap),
-];
-
-
 export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor => {
 
   const mergedProps = useMemo(() => {
     return deepmerge(
       props ?? {},
       {
-        extensions: [
-          defaultExtensions,
-        ],
         // Reset settings of react-codemirror.
         // Extensions are defined first will be used if they have the same priority.
         // If extensions conflict, disable them here.
@@ -113,6 +53,8 @@ export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor
         basicSetup: {
           defaultKeymap: false,
           dropCursor: false,
+          highlightActiveLine: false,
+          highlightActiveLineGutter: false,
           // Disabled react-codemirror history for Y.UndoManager
           history: false,
         },

+ 2 - 1
packages/editor/src/services/editor-theme/index.ts

@@ -1,6 +1,6 @@
 import { Extension } from '@codemirror/state';
 
-export const getEditorTheme = async(themeName: EditorTheme): Promise<Extension> => {
+export const getEditorTheme = async(themeName?: EditorTheme): Promise<Extension> => {
   switch (themeName) {
     case 'eclipse':
       return (await import('@uiw/codemirror-theme-eclipse')).eclipse;
@@ -37,5 +37,6 @@ const EditorTheme = {
   kimbie: 'kimbie',
 } as const;
 
+export const DEFAULT_THEME = 'defaultlight';
 export const AllEditorTheme = Object.values(EditorTheme);
 export type EditorTheme = typeof EditorTheme[keyof typeof EditorTheme]

+ 3 - 3
packages/editor/src/services/keymaps/index.ts

@@ -2,7 +2,7 @@ import { Extension } from '@codemirror/state';
 import { keymap } from '@codemirror/view';
 
 
-export const getKeymap = async(keyMapName: KeyMapMode, onSave?: () => void): Promise<Extension> => {
+export const getKeymap = async(keyMapName?: KeyMapMode, onSave?: () => void): Promise<Extension> => {
   switch (keyMapName) {
     case 'vim':
       return (await import('./vim')).vimKeymap(onSave);
@@ -10,9 +10,8 @@ export const getKeymap = async(keyMapName: KeyMapMode, onSave?: () => void): Pro
       return (await import('@replit/codemirror-emacs')).emacs();
     case 'vscode':
       return keymap.of((await import('@replit/codemirror-vscode-keymap')).vscodeKeymap);
-    case 'default':
-      return keymap.of((await import('@codemirror/commands')).defaultKeymap);
   }
+  return keymap.of((await import('@codemirror/commands')).defaultKeymap);
 };
 
 const KeyMapMode = {
@@ -22,5 +21,6 @@ const KeyMapMode = {
   vscode: 'vscode',
 } as const;
 
+export const DEFAULT_KEYMAP = 'default';
 export const AllKeyMap = Object.values(KeyMapMode);
 export type KeyMapMode = typeof KeyMapMode[keyof typeof KeyMapMode];

+ 2 - 0
packages/editor/src/stores/index.ts

@@ -1,3 +1,5 @@
 export * from './codemirror-editor';
 export * from './use-resolved-theme';
 export * from './use-collaborative-editor-mode';
+export * from './use-editor-settings';
+export * from './use-default-extensions';

+ 52 - 0
packages/editor/src/stores/use-default-extensions.ts

@@ -0,0 +1,52 @@
+import { indentWithTab, defaultKeymap, deleteCharBackward } from '@codemirror/commands';
+import {
+  markdown, markdownLanguage,
+} from '@codemirror/lang-markdown';
+import { syntaxHighlighting, HighlightStyle, defaultHighlightStyle } from '@codemirror/language';
+import { languages } from '@codemirror/language-data';
+import {
+  Prec, type Extension,
+} from '@codemirror/state';
+import { keymap, EditorView, KeyBinding } from '@codemirror/view';
+import { tags } from '@lezer/highlight';
+// see: https://github.com/yjs/y-codemirror.next#example
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import { yUndoManagerKeymap } from 'y-codemirror.next';
+
+import type { UseCodeMirrorEditor } from '../services';
+import { emojiAutocompletionSettings } from '../services/extensions/emojiAutocompletionSettings';
+
+
+// set new markdownKeymap instead of default one
+// https://github.com/codemirror/lang-markdown/blob/main/src/index.ts#L17
+const markdownKeymap: KeyBinding[] = [
+  { key: 'Backspace', run: deleteCharBackward },
+];
+
+const markdownHighlighting = HighlightStyle.define([
+  { tag: tags.heading1, class: 'cm-header-1 cm-header' },
+  { tag: tags.heading2, class: 'cm-header-2 cm-header' },
+  { tag: tags.heading3, class: 'cm-header-3 cm-header' },
+  { tag: tags.heading4, class: 'cm-header-4 cm-header' },
+  { tag: tags.heading5, class: 'cm-header-5 cm-header' },
+  { tag: tags.heading6, class: 'cm-header-6 cm-header' },
+]);
+
+const defaultExtensions: Extension[] = [
+  EditorView.lineWrapping,
+  markdown({ base: markdownLanguage, codeLanguages: languages, addKeymap: false }),
+  keymap.of(markdownKeymap),
+  keymap.of([indentWithTab]),
+  Prec.lowest(keymap.of(defaultKeymap)),
+  keymap.of(yUndoManagerKeymap),
+  syntaxHighlighting(markdownHighlighting),
+  Prec.lowest(syntaxHighlighting(defaultHighlightStyle)),
+  emojiAutocompletionSettings,
+];
+
+export const useDefaultExtensions = (
+    codeMirrorEditor?: UseCodeMirrorEditor,
+): void => {
+  codeMirrorEditor?.appendExtensions([defaultExtensions]);
+};

+ 91 - 0
packages/editor/src/stores/use-editor-settings.ts

@@ -0,0 +1,91 @@
+import { useEffect, useCallback, useState } from 'react';
+
+import { Prec, Extension } from '@codemirror/state';
+import {
+  keymap, type Command, highlightActiveLine, highlightActiveLineGutter,
+} from '@codemirror/view';
+
+import type { EditorSettings } from '../consts';
+import type { UseCodeMirrorEditor, EditorTheme, KeyMapMode } from '../services';
+import { getEditorTheme, getKeymap } from '../services';
+import { insertNewlineContinueMarkup } from '../services/list-util/insert-newline-continue-markup';
+import { insertNewRowToMarkdownTable, isInTable } from '../services/table-util/insert-new-row-to-table-markdown';
+
+export const useEditorSettings = (
+    codeMirrorEditor?: UseCodeMirrorEditor,
+    editorSetings?: EditorSettings,
+    onSave?: () => void,
+): void => {
+
+  useEffect(() => {
+    if (editorSetings?.styleActiveLine == null) {
+      return;
+    }
+    const extensions = (editorSetings?.styleActiveLine) ? [[highlightActiveLine(), highlightActiveLineGutter()]] : [[]];
+
+    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(extensions);
+    return cleanupFunction;
+
+  }, [codeMirrorEditor, editorSetings?.styleActiveLine]);
+
+  const onPressEnter: Command = useCallback((editor) => {
+    if (isInTable(editor) && editorSetings?.autoFormatMarkdownTable) {
+      insertNewRowToMarkdownTable(editor);
+      return true;
+    }
+    insertNewlineContinueMarkup(editor);
+    return true;
+  }, [editorSetings?.autoFormatMarkdownTable]);
+
+
+  useEffect(() => {
+
+    const extension = keymap.of([
+      { key: 'Enter', run: onPressEnter },
+    ]);
+
+    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(extension);
+    return cleanupFunction;
+
+  }, [codeMirrorEditor, onPressEnter]);
+
+  const [themeExtension, setThemeExtension] = useState<Extension | undefined>(undefined);
+  useEffect(() => {
+    const settingTheme = async(name?: EditorTheme) => {
+      setThemeExtension(await getEditorTheme(name));
+    };
+    settingTheme(editorSetings?.theme);
+  }, [codeMirrorEditor, editorSetings?.theme, setThemeExtension]);
+
+  useEffect(() => {
+    if (themeExtension == null) {
+      return;
+    }
+    // React CodeMirror has default theme which is default prec
+    // and extension have to be higher prec here than default theme.
+    const cleanupFunction = codeMirrorEditor?.appendExtensions(Prec.high(themeExtension));
+    return cleanupFunction;
+  }, [codeMirrorEditor, themeExtension]);
+
+
+  const [keymapExtension, setKeymapExtension] = useState<Extension | undefined>(undefined);
+  useEffect(() => {
+    const settingKeyMap = async(name?: KeyMapMode) => {
+      setKeymapExtension(await getKeymap(name, onSave));
+    };
+    settingKeyMap(editorSetings?.keymapMode);
+
+  }, [codeMirrorEditor, editorSetings?.keymapMode, setKeymapExtension, onSave]);
+
+  useEffect(() => {
+    if (keymapExtension == null) {
+      return;
+    }
+
+    // Prevent these Keybind from overwriting the originally defined keymap.
+    const cleanupFunction = codeMirrorEditor?.appendExtensions(Prec.low(keymapExtension));
+    return cleanupFunction;
+
+  }, [codeMirrorEditor, keymapExtension]);
+
+};