Yuki Takei 5 месяцев назад
Родитель
Сommit
0188acfc34
94 измененных файлов с 1465 добавлено и 1103 удалено
  1. 0 1
      biome.json
  2. 1 2
      packages/editor/.eslintignore
  3. 0 13
      packages/editor/.eslintrc.cjs
  4. 1 1
      packages/editor/package.json
  5. 59 45
      packages/editor/src/client/components-internal/CodeMirrorEditor/CodeMirrorEditor.tsx
  6. 18 24
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/AttachmentsDropdownItem.tsx
  7. 35 20
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx
  8. 8 4
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  9. 20 20
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/EmojiButton.tsx
  10. 11 7
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/LinkEditButton.tsx
  11. 8 5
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TableButton.tsx
  12. 11 5
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TemplateButton.tsx
  13. 75 25
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TextFormatTools.tsx
  14. 25 14
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/Toolbar.tsx
  15. 46 28
      packages/editor/src/client/components-internal/playground/Playground.tsx
  16. 10 8
      packages/editor/src/client/components-internal/playground/PlaygroundController.tsx
  17. 2 2
      packages/editor/src/client/components-internal/playground/Preview.tsx
  18. 7 3
      packages/editor/src/client/components-internal/playground/controller/KeymapControl.tsx
  19. 6 6
      packages/editor/src/client/components-internal/playground/controller/OutlineSecondaryButtons.tsx
  20. 7 3
      packages/editor/src/client/components-internal/playground/controller/PasteModeControl.tsx
  21. 7 1
      packages/editor/src/client/components-internal/playground/controller/SetCaretLineRow.tsx
  22. 7 3
      packages/editor/src/client/components-internal/playground/controller/ThemeControl.tsx
  23. 17 4
      packages/editor/src/client/components-internal/playground/controller/UnifiedMergeViewControl.tsx
  24. 12 20
      packages/editor/src/client/components/CodeMirrorEditorComment.tsx
  25. 39 32
      packages/editor/src/client/components/CodeMirrorEditorMain.tsx
  26. 16 17
      packages/editor/src/client/components/CodeMirrorEditorReadOnly.tsx
  27. 7 8
      packages/editor/src/client/components/diff/CodeMirrorEditorDiff.tsx
  28. 5 6
      packages/editor/src/client/components/diff/MergeViewer.tsx
  29. 3 1
      packages/editor/src/client/services-internal/editor-theme/index.ts
  30. 9 1
      packages/editor/src/client/services-internal/editor-theme/original-dark.ts
  31. 17 3
      packages/editor/src/client/services-internal/editor-theme/original-light.ts
  32. 24 17
      packages/editor/src/client/services-internal/extensions/emojiAutocompletionSettings.ts
  33. 3 7
      packages/editor/src/client/services-internal/extensions/setDataLine.ts
  34. 1 1
      packages/editor/src/client/services-internal/file-dropzone/index.ts
  35. 3 4
      packages/editor/src/client/services-internal/file-dropzone/use-file-dropzone/FileDropzoneOverlay.tsx
  36. 27 25
      packages/editor/src/client/services-internal/file-dropzone/use-file-dropzone/use-file-dropzone.ts
  37. 7 3
      packages/editor/src/client/services-internal/keymaps/index.ts
  38. 8 3
      packages/editor/src/client/services-internal/link-util/markdown-link-util.ts
  39. 11 4
      packages/editor/src/client/services-internal/list-util/insert-newline-continue-markup.ts
  40. 5 7
      packages/editor/src/client/services-internal/paste-util/paste-markdown-util.ts
  41. 9 9
      packages/editor/src/client/services-internal/table/insert-new-row-to-table-markdown.ts
  42. 5 11
      packages/editor/src/client/services-internal/table/use-show-table-icon.ts
  43. 2 1
      packages/editor/src/client/services-internal/unified-merge-view/index.ts
  44. 9 6
      packages/editor/src/client/services-internal/unified-merge-view/use-customized-button-styles.ts
  45. 53 38
      packages/editor/src/client/services-internal/unified-merge-view/use-unified-merge-view.ts
  46. 18 12
      packages/editor/src/client/services/unified-merge-view/index.ts
  47. 55 49
      packages/editor/src/client/services/use-codemirror-editor/use-codemirror-editor.ts
  48. 19 21
      packages/editor/src/client/services/use-codemirror-editor/utils/append-extensions.ts
  49. 9 12
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/add-multi-cursor.ts
  50. 5 7
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/generate-add-markdown-symbol-command.ts
  51. 11 6
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-blockquote.ts
  52. 11 6
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-bullet-list.ts
  53. 5 4
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-link.ts
  54. 8 5
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-numbered-list.ts
  55. 21 8
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-bold.ts
  56. 20 10
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-code-block.ts
  57. 9 5
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-code.ts
  58. 9 5
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-italic.ts
  59. 12 6
      packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-strikethrough.ts
  60. 0 3
      packages/editor/src/client/services/use-codemirror-editor/utils/focus.ts
  61. 3 7
      packages/editor/src/client/services/use-codemirror-editor/utils/fold-drawio.ts
  62. 0 5
      packages/editor/src/client/services/use-codemirror-editor/utils/get-doc.ts
  63. 13 13
      packages/editor/src/client/services/use-codemirror-editor/utils/init-doc.ts
  64. 38 37
      packages/editor/src/client/services/use-codemirror-editor/utils/insert-markdown-elements.ts
  65. 104 91
      packages/editor/src/client/services/use-codemirror-editor/utils/insert-prefix.ts
  66. 17 17
      packages/editor/src/client/services/use-codemirror-editor/utils/insert-text.ts
  67. 6 8
      packages/editor/src/client/services/use-codemirror-editor/utils/replace-text.ts
  68. 34 34
      packages/editor/src/client/services/use-codemirror-editor/utils/set-caret-line.ts
  69. 16 12
      packages/editor/src/client/stores/codemirror-editor.ts
  70. 49 38
      packages/editor/src/client/stores/use-collaborative-editor-mode.ts
  71. 18 11
      packages/editor/src/client/stores/use-default-extensions.ts
  72. 20 14
      packages/editor/src/client/stores/use-drawio.ts
  73. 51 37
      packages/editor/src/client/stores/use-editor-settings.ts
  74. 22 18
      packages/editor/src/client/stores/use-editor-shortcuts.ts
  75. 26 17
      packages/editor/src/client/stores/use-handsontable.ts
  76. 14 10
      packages/editor/src/client/stores/use-link-edit-modal.ts
  77. 15 9
      packages/editor/src/client/stores/use-resolved-theme.ts
  78. 12 8
      packages/editor/src/client/stores/use-secondary-ydocs.ts
  79. 14 10
      packages/editor/src/client/stores/use-template-modal.ts
  80. 5 5
      packages/editor/src/consts/editor-settings.ts
  81. 2 2
      packages/editor/src/consts/editor-themes.ts
  82. 2 1
      packages/editor/src/consts/global-code-mirror-editor-key.ts
  83. 2 2
      packages/editor/src/consts/index.ts
  84. 1 1
      packages/editor/src/consts/keymaps.ts
  85. 1 2
      packages/editor/src/consts/paste-mode.ts
  86. 5 1
      packages/editor/src/interfaces/delta.ts
  87. 7 6
      packages/editor/src/interfaces/editing-client.ts
  88. 0 2
      packages/editor/src/main.tsx
  89. 1 1
      packages/editor/src/models/index.ts
  90. 80 32
      packages/editor/src/models/linker.ts
  91. 0 2
      packages/editor/src/models/markdown-table.d.ts
  92. 15 12
      packages/editor/src/models/markdown-table.js
  93. 1 1
      packages/editor/src/utils/delta-to-changespecs.ts
  94. 3 10
      packages/editor/vite.config.ts

+ 0 - 1
biome.json

@@ -25,7 +25,6 @@
       "!apps/app/src/styles/prebuilt/**",
       "!apps/app/tmp/**",
       "!apps/slackbot-proxy/src/public/bootstrap/**",
-      "!packages/editor/**",
       "!packages/pdf-converter-client/src/index.ts",
       "!packages/pdf-converter-client/specs/**",
       "!apps/app/playwright/**",

+ 1 - 2
packages/editor/.eslintignore

@@ -1,2 +1 @@
-/dist/**
-vite-env.d.ts
+*

+ 0 - 13
packages/editor/.eslintrc.cjs

@@ -1,13 +0,0 @@
-module.exports = {
-  env: { browser: true, es2020: true },
-  extends: [
-    'weseek/react',
-  ],
-  plugins: ['react-refresh'],
-  rules: {
-    'react-refresh/only-export-components': [
-      'warn',
-      { allowConstantExport: true },
-    ],
-  },
-};

+ 1 - 1
packages/editor/package.json

@@ -13,7 +13,7 @@
     "dev": "vite build --mode dev",
     "watch": "pnpm run dev -w --emptyOutDir=false",
     "serve": "vite",
-    "lint:js": "eslint **/*.{js,ts}",
+    "lint:js": "biome check",
     "lint:typecheck": "vue-tsc --noEmit",
     "lint": "npm-run-all -p lint:*"
   },

+ 59 - 45
packages/editor/src/client/components-internal/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -1,64 +1,68 @@
 import type { DetailedHTMLProps, JSX } from 'react';
-import {
-  forwardRef, useMemo, useRef, useEffect,
-} from 'react';
-
+import { forwardRef, useEffect, useMemo, useRef } from 'react';
 import { indentUnit } from '@codemirror/language';
-import {
-  EditorView,
-} from '@codemirror/view';
+import { EditorView } from '@codemirror/view';
 import { AcceptedUploadFileType } from '@growi/core';
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 
-import { PasteMode, type EditorSettings, type GlobalCodeMirrorEditorKey } from '../../../consts';
 import {
-  useFileDropzone, FileDropzoneOverlay, useShowTableIcon, getStrFromBol, adjustPasteData,
+  type EditorSettings,
+  type GlobalCodeMirrorEditorKey,
+  PasteMode,
+} from '../../../consts';
+import {
+  adjustPasteData,
+  FileDropzoneOverlay,
+  getStrFromBol,
+  useFileDropzone,
+  useShowTableIcon,
 } from '../../services-internal';
 import { useCodeMirrorEditorIsolated } from '../../stores/codemirror-editor';
 import { useDefaultExtensions } from '../../stores/use-default-extensions';
 import { useEditorSettings } from '../../stores/use-editor-settings';
-
 import { Toolbar } from './Toolbar';
 
-
 import style from './CodeMirrorEditor.module.scss';
 
 const moduleClass = style['codemirror-editor'];
 
-
 // Fix IME cursor position issue by EditContext
 // ref: https://github.com/growilabs/growi/pull/9267
 // ref: https://discuss.codemirror.net/t/issue-with-google-japanese-ime-cursor-position-in-v6/8810/3
 (EditorView as unknown as { EDIT_CONTEXT: boolean }).EDIT_CONTEXT = false;
 
-
-const CodeMirrorEditorContainer = forwardRef<HTMLDivElement, DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>>(
-  (props, ref) => {
-    const { className = '', ...rest } = props;
-    return (
-      <div className={`${className} flex-expand-vert ${style['codemirror-editor-container']}`} ref={ref} {...rest} />
-    );
-  },
-);
+const CodeMirrorEditorContainer = forwardRef<
+  HTMLDivElement,
+  DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
+>((props, ref) => {
+  const { className = '', ...rest } = props;
+  return (
+    <div
+      className={`${className} flex-expand-vert ${style['codemirror-editor-container']}`}
+      ref={ref}
+      {...rest}
+    />
+  );
+});
 
 export type CodeMirrorEditorProps = {
   /**
    * Specity the props for the react-codemirror component. **This must be a memolized object.**
    */
-  cmProps?: ReactCodeMirrorProps,
-  acceptedUploadFileType?: AcceptedUploadFileType,
-  indentSize?: number,
-  editorSettings?: EditorSettings,
-  onSave?: () => void,
-  onUpload?: (files: File[]) => void,
-  onScroll?: () => void,
-}
+  cmProps?: ReactCodeMirrorProps;
+  acceptedUploadFileType?: AcceptedUploadFileType;
+  indentSize?: number;
+  editorSettings?: EditorSettings;
+  onSave?: () => void;
+  onUpload?: (files: File[]) => void;
+  onScroll?: () => void;
+};
 
 type Props = CodeMirrorEditorProps & {
-  editorKey: string | GlobalCodeMirrorEditorKey,
-  className?: string,
-  hideToolbar?: boolean,
-}
+  editorKey: string | GlobalCodeMirrorEditorKey;
+  className?: string;
+  hideToolbar?: boolean;
+};
 
 export const CodeMirrorEditor = (props: Props): JSX.Element => {
   const {
@@ -77,7 +81,11 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
   const containerRef = useRef(null);
 
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey, containerRef.current, cmProps);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(
+    editorKey,
+    containerRef.current,
+    cmProps,
+  );
 
   useDefaultExtensions(codeMirrorEditor);
   useEditorSettings(codeMirrorEditor, editorSettings, onSave);
@@ -92,7 +100,6 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
     const cleanupFunction = codeMirrorEditor?.appendExtensions?.(extension);
     return cleanupFunction;
-
   }, [codeMirrorEditor, indentSize]);
 
   const pasteMode = editorSettings?.pasteMode;
@@ -109,7 +116,11 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
       if (event.clipboardData.types.includes('text/plain')) {
         if (codeMirrorEditor == null) return;
 
-        if (pasteMode == null || pasteMode === PasteMode.both || pasteMode === PasteMode.text) {
+        if (
+          pasteMode == null ||
+          pasteMode === PasteMode.both ||
+          pasteMode === PasteMode.text
+        ) {
           const textData = event.clipboardData.getData('text/plain');
 
           const strFromBol = getStrFromBol(editor);
@@ -122,7 +133,11 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
       if (event.clipboardData.types.includes('Files')) {
         if (onUpload == null) return;
 
-        if (pasteMode == null || pasteMode === PasteMode.both || pasteMode === PasteMode.file) {
+        if (
+          pasteMode == null ||
+          pasteMode === PasteMode.both ||
+          pasteMode === PasteMode.file
+        ) {
           onUpload(Array.from(event.clipboardData.files));
         }
       }
@@ -134,11 +149,9 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
     const cleanupFunction = codeMirrorEditor?.appendExtensions(extension);
     return cleanupFunction;
-
   }, [codeMirrorEditor, pasteMode, onUpload]);
 
   useEffect(() => {
-
     const handleDrop = (event: DragEvent) => {
       // prevents conflicts between codemirror and react-dropzone during file drops.
       event.preventDefault();
@@ -150,11 +163,9 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
     const cleanupFunction = codeMirrorEditor?.appendExtensions(extension);
     return cleanupFunction;
-
   }, [codeMirrorEditor]);
 
   useEffect(() => {
-
     const handleScroll = (event: Event) => {
       event.preventDefault();
       if (onScroll != null) {
@@ -168,7 +179,6 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
     const cleanupFunction = codeMirrorEditor?.appendExtensions(extension);
     return cleanupFunction;
-
   }, [onScroll, codeMirrorEditor]);
 
   const {
@@ -189,7 +199,6 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   });
 
   const fileUploadState = useMemo(() => {
-
     if (isUploading) {
       return 'dropzone-uploading';
     }
@@ -221,8 +230,13 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   }, [isUploading, isDragAccept, isDragReject, acceptedUploadFileType]);
 
   return (
-    <div className={`${className} ${moduleClass} flex-expand-vert overflow-y-hidden`}>
-      <div {...getRootProps()} className={`dropzone  ${fileUploadState} flex-expand-vert`}>
+    <div
+      className={`${className} ${moduleClass} flex-expand-vert overflow-y-hidden`}
+    >
+      <div
+        {...getRootProps()}
+        className={`dropzone  ${fileUploadState} flex-expand-vert`}
+      >
         <input {...getInputProps()} />
         <FileDropzoneOverlay isEnabled={isDragActive} />
         <CodeMirrorEditorContainer ref={containerRef} />

+ 18 - 24
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/AttachmentsDropdownItem.tsx

@@ -1,36 +1,26 @@
-import type { ReactNode, JSX } from 'react';
-
+import type { JSX, ReactNode } from 'react';
 import type { AcceptedUploadFileType } from '@growi/core';
-import {
-  DropdownItem,
-} from 'reactstrap';
+import { DropdownItem } from 'reactstrap';
 
 import { useFileDropzone } from '../../../services-internal';
 
 type Props = {
-  acceptedUploadFileType: AcceptedUploadFileType,
-  children?: ReactNode,
-  onUpload?: (files: File[]) => void,
-  onClose?: () => void,
-}
+  acceptedUploadFileType: AcceptedUploadFileType;
+  children?: ReactNode;
+  onUpload?: (files: File[]) => void;
+  onClose?: () => void;
+};
 
 export const AttachmentsDropdownItem = (props: Props): JSX.Element => {
+  const { acceptedUploadFileType, children, onUpload, onClose } = props;
 
-  const {
-    acceptedUploadFileType,
-    children,
-    onUpload,
-    onClose,
-  } = props;
-
-  const {
-    getRootProps,
-    getInputProps,
-    open,
-  } = useFileDropzone({
+  const { getRootProps, getInputProps, open } = useFileDropzone({
     // close after uploading
     // https://github.com/growilabs/growi/pull/8564
-    onUpload: (files: File[]) => { onUpload?.(files); onClose?.() },
+    onUpload: (files: File[]) => {
+      onUpload?.(files);
+      onClose?.();
+    },
     acceptedUploadFileType,
     dropzoneOpts: {
       noClick: true,
@@ -45,7 +35,11 @@ export const AttachmentsDropdownItem = (props: Props): JSX.Element => {
   return (
     <div {...getRootProps()} className="dropzone">
       <input {...getInputProps()} />
-      <DropdownItem toggle={false} className="d-flex gap-2 align-items-center" onClick={open}>
+      <DropdownItem
+        toggle={false}
+        className="d-flex gap-2 align-items-center"
+        onClick={open}
+      >
         {children}
       </DropdownItem>
     </div>

+ 35 - 20
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx

@@ -1,15 +1,13 @@
-import { useState, type JSX } from 'react';
-
+import { type JSX, useState } from 'react';
 import { AcceptedUploadFileType } from '@growi/core';
 import {
-  DropdownToggle,
-  DropdownMenu,
-  DropdownItem,
   Dropdown,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
 } from 'reactstrap';
 
 import type { GlobalCodeMirrorEditorKey } from '../../../../consts';
-
 import { AttachmentsDropdownItem } from './AttachmentsDropdownItem';
 import { LinkEditButton } from './LinkEditButton';
 
@@ -17,12 +15,11 @@ import styles from './AttachmentsDropup.module.scss';
 
 const btnAttachmentToggleClass = styles['btn-attachment-toggle'];
 
-
 type Props = {
-  editorKey: string | GlobalCodeMirrorEditorKey,
-  acceptedUploadFileType: AcceptedUploadFileType,
-  onUpload?: (files: File[]) => void,
-}
+  editorKey: string | GlobalCodeMirrorEditorKey;
+  acceptedUploadFileType: AcceptedUploadFileType;
+  onUpload?: (files: File[]) => void;
+};
 
 export const AttachmentsDropup = (props: Props): JSX.Element => {
   const { acceptedUploadFileType, editorKey, onUpload } = props;
@@ -31,8 +28,16 @@ export const AttachmentsDropup = (props: Props): JSX.Element => {
 
   return (
     <>
-      <Dropdown isOpen={isOpen} toggle={() => setOpen(!isOpen)} direction="up" className="lh-1">
-        <DropdownToggle className={`${btnAttachmentToggleClass} btn-toolbar-button rounded-circle`} color="unset">
+      <Dropdown
+        isOpen={isOpen}
+        toggle={() => setOpen(!isOpen)}
+        direction="up"
+        className="lh-1"
+      >
+        <DropdownToggle
+          className={`${btnAttachmentToggleClass} btn-toolbar-button rounded-circle`}
+          color="unset"
+        >
           <span className="material-symbols-outlined fs-6">add</span>
         </DropdownToggle>
         <DropdownMenu>
@@ -42,19 +47,29 @@ export const AttachmentsDropup = (props: Props): JSX.Element => {
 
           <DropdownItem divider />
 
-          { acceptedUploadFileType === AcceptedUploadFileType.ALL && (
-            <AttachmentsDropdownItem acceptedUploadFileType={AcceptedUploadFileType.ALL} onUpload={onUpload} onClose={() => setOpen(false)}>
-              <span className="material-symbols-outlined fs-5">attach_file</span>
+          {acceptedUploadFileType === AcceptedUploadFileType.ALL && (
+            <AttachmentsDropdownItem
+              acceptedUploadFileType={AcceptedUploadFileType.ALL}
+              onUpload={onUpload}
+              onClose={() => setOpen(false)}
+            >
+              <span className="material-symbols-outlined fs-5">
+                attach_file
+              </span>
               Files
             </AttachmentsDropdownItem>
-          ) }
+          )}
 
-          { acceptedUploadFileType !== AcceptedUploadFileType.NONE && (
-            <AttachmentsDropdownItem acceptedUploadFileType={AcceptedUploadFileType.IMAGE} onUpload={onUpload} onClose={() => setOpen(false)}>
+          {acceptedUploadFileType !== AcceptedUploadFileType.NONE && (
+            <AttachmentsDropdownItem
+              acceptedUploadFileType={AcceptedUploadFileType.IMAGE}
+              onUpload={onUpload}
+              onClose={() => setOpen(false)}
+            >
               <span className="material-symbols-outlined fs-5">image</span>
               Images
             </AttachmentsDropdownItem>
-          ) }
+          )}
 
           <LinkEditButton editorKey={editorKey} />
         </DropdownMenu>

+ 8 - 4
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/DiagramButton.tsx

@@ -1,10 +1,10 @@
-import { useCallback, type JSX } from 'react';
+import { type JSX, useCallback } from 'react';
 
 import { useDrawioModalForEditor } from '../../../stores/use-drawio';
 
 type Props = {
-  editorKey: string,
-}
+  editorKey: string;
+};
 
 export const DiagramButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
@@ -13,7 +13,11 @@ export const DiagramButton = (props: Props): JSX.Element => {
     openDrawioModal(editorKey);
   }, [editorKey, openDrawioModal]);
   return (
-    <button type="button" className="btn btn-toolbar-button" onClick={onClickDiagramButton}>
+    <button
+      type="button"
+      className="btn btn-toolbar-button"
+      onClick={onClickDiagramButton}
+    >
       {/* TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015 */}
       <span className="growi-custom-icons fs-6">drawer_io</span>
     </button>

+ 20 - 20
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/EmojiButton.tsx

@@ -1,8 +1,4 @@
-import {
-  useState, useCallback,
-  type CSSProperties, type JSX,
-} from 'react';
-
+import { type CSSProperties, type JSX, useCallback, useState } from 'react';
 import emojiData from '@emoji-mart/data';
 import Picker from '@emoji-mart/react';
 import { Modal } from 'reactstrap';
@@ -11,8 +7,8 @@ import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
 import { useResolvedThemeForEditor } from '../../../stores/use-resolved-theme';
 
 type Props = {
-  editorKey: string,
-}
+  editorKey: string;
+};
 
 export const EmojiButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
@@ -23,20 +19,20 @@ export const EmojiButton = (props: Props): JSX.Element => {
   const { data: resolvedTheme } = useResolvedThemeForEditor();
   const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen]);
 
-  const selectEmoji = useCallback((emoji: { shortcodes: string }): void => {
-
-    if (!isOpen) {
-      return;
-    }
+  const selectEmoji = useCallback(
+    (emoji: { shortcodes: string }): void => {
+      if (!isOpen) {
+        return;
+      }
 
-    codeMirrorEditor?.insertText(emoji.shortcodes);
-
-    toggle();
-  }, [isOpen, toggle, codeMirrorEditor]);
+      codeMirrorEditor?.insertText(emoji.shortcodes);
 
+      toggle();
+    },
+    [isOpen, toggle, codeMirrorEditor],
+  );
 
   const setStyle = useCallback((): CSSProperties => {
-
     const view = codeMirrorEditor?.view;
     const cursorIndex = view?.state.selection.main.head;
 
@@ -73,10 +69,14 @@ export const EmojiButton = (props: Props): JSX.Element => {
       <button type="button" className="btn btn-toolbar-button" onClick={toggle}>
         <span className="material-symbols-outlined fs-5">emoji_emotions</span>
       </button>
-      { isOpen
-      && (
+      {isOpen && (
         <div className="mb-2 d-none d-md-block">
-          <Modal isOpen={isOpen} toggle={toggle} backdropClassName="emoji-picker-modal" fade={false}>
+          <Modal
+            isOpen={isOpen}
+            toggle={toggle}
+            backdropClassName="emoji-picker-modal"
+            fade={false}
+          >
             <span style={setStyle()}>
               <Picker
                 onEmojiSelect={selectEmoji}

+ 11 - 7
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/LinkEditButton.tsx

@@ -1,16 +1,17 @@
-import { useCallback, type JSX } from 'react';
-
+import { type JSX, useCallback } from 'react';
 import { DropdownItem } from 'reactstrap';
 
 import type { GlobalCodeMirrorEditorKey } from '../../../../consts';
-import { getMarkdownLink, replaceFocusedMarkdownLinkWithEditor } from '../../../services-internal';
+import {
+  getMarkdownLink,
+  replaceFocusedMarkdownLinkWithEditor,
+} from '../../../services-internal';
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
 import { useLinkEditModal } from '../../../stores/use-link-edit-modal';
 
-
 type Props = {
-  editorKey: string | GlobalCodeMirrorEditorKey,
-}
+  editorKey: string | GlobalCodeMirrorEditorKey;
+};
 
 export const LinkEditButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
@@ -33,7 +34,10 @@ export const LinkEditButton = (props: Props): JSX.Element => {
   }, [codeMirrorEditor?.view, openLinkEditModal]);
 
   return (
-    <DropdownItem className="d-flex gap-2 align-items-center" onClick={onClickOpenLinkEditModal}>
+    <DropdownItem
+      className="d-flex gap-2 align-items-center"
+      onClick={onClickOpenLinkEditModal}
+    >
       <span className="material-symbols-outlined fs-5">link</span>Link
     </DropdownItem>
   );

+ 8 - 5
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TableButton.tsx

@@ -1,12 +1,11 @@
-import { useCallback, type JSX } from 'react';
+import { type JSX, useCallback } from 'react';
 
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
 import { useHandsontableModalForEditor } from '../../../stores/use-handsontable';
 
-
 type Props = {
-  editorKey: string,
-}
+  editorKey: string;
+};
 
 export const TableButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
@@ -18,7 +17,11 @@ export const TableButton = (props: Props): JSX.Element => {
   }, [editor, openTableModal]);
 
   return (
-    <button type="button" className="btn btn-toolbar-button" onClick={onClickTableButton}>
+    <button
+      type="button"
+      className="btn btn-toolbar-button"
+      onClick={onClickTableButton}
+    >
       <span className="material-symbols-outlined fs-5">table_chart</span>
     </button>
   );

+ 11 - 5
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TemplateButton.tsx

@@ -1,11 +1,11 @@
-import { useCallback, type JSX } from 'react';
+import { type JSX, useCallback } from 'react';
 
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
 import { useTemplateModal } from '../../../stores/use-template-modal';
 
 type Props = {
-  editorKey: string,
-}
+  editorKey: string;
+};
 
 export const TemplateButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
@@ -15,14 +15,20 @@ export const TemplateButton = (props: Props): JSX.Element => {
   const onClickTempleteButton = useCallback(() => {
     const editor = codeMirrorEditor?.view;
     if (editor != null) {
-      const insertText = (text: string) => editor.dispatch(editor.state.replaceSelection(text));
+      const insertText = (text: string) =>
+        editor.dispatch(editor.state.replaceSelection(text));
       const onSubmit = (templateText: string) => insertText(templateText);
       openTemplateModal({ onSubmit });
     }
   }, [codeMirrorEditor?.view, openTemplateModal]);
 
   return (
-    <button type="button" className="btn btn-toolbar-button" onClick={onClickTempleteButton} data-testid="open-template-button">
+    <button
+      type="button"
+      className="btn btn-toolbar-button"
+      onClick={onClickTempleteButton}
+      data-testid="open-template-button"
+    >
       <span className="material-symbols-outlined fs-5">file_copy</span>
     </button>
   );

+ 75 - 25
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TextFormatTools.tsx

@@ -1,5 +1,4 @@
-import { useCallback, useState, type JSX } from 'react';
-
+import { type JSX, useCallback, useState } from 'react';
 import { Collapse } from 'reactstrap';
 
 import type { GlobalCodeMirrorEditorKey } from '../../../../consts';
@@ -10,12 +9,11 @@ import styles from './TextFormatTools.module.scss';
 const btnTextFormatToolsTogglerClass = styles['btn-text-format-tools-toggler'];
 
 type TogglarProps = {
-  isOpen: boolean,
-  onClick?: () => void,
-}
+  isOpen: boolean;
+  onClick?: () => void;
+};
 
 const TextFormatToolsToggler = (props: TogglarProps): JSX.Element => {
-
   const { isOpen, onClick } = props;
 
   const activeClass = isOpen ? 'active' : '';
@@ -32,9 +30,9 @@ const TextFormatToolsToggler = (props: TogglarProps): JSX.Element => {
 };
 
 type TextFormatToolsType = {
-  editorKey: string | GlobalCodeMirrorEditorKey,
-  onTextFormatToolsCollapseChange: () => void,
-}
+  editorKey: string | GlobalCodeMirrorEditorKey;
+  onTextFormatToolsCollapseChange: () => void;
+};
 
 export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
   const { editorKey, onTextFormatToolsCollapseChange } = props;
@@ -42,14 +40,17 @@ export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
 
   const toggle = useCallback(() => {
-    setOpen(bool => !bool);
+    setOpen((bool) => !bool);
   }, []);
 
   const onClickInsertMarkdownElements = (prefix: string, suffix: string) => {
     codeMirrorEditor?.insertMarkdownElements(prefix, suffix);
   };
 
-  const onClickInsertPrefix = (prefix: string, noSpaceIfPrefixExists?: boolean) => {
+  const onClickInsertPrefix = (
+    prefix: string,
+    noSpaceIfPrefixExists?: boolean,
+  ) => {
     codeMirrorEditor?.insertPrefix(prefix, noSpaceIfPrefixExists);
   };
 
@@ -57,35 +58,84 @@ export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
     <div className="d-flex">
       <TextFormatToolsToggler isOpen={isOpen} onClick={toggle} />
 
-      <Collapse isOpen={isOpen} horizontal onEntered={onTextFormatToolsCollapseChange} onExited={onTextFormatToolsCollapseChange}>
+      <Collapse
+        isOpen={isOpen}
+        horizontal
+        onEntered={onTextFormatToolsCollapseChange}
+        onExited={onTextFormatToolsCollapseChange}
+      >
         <div className="d-flex px-1 gap-1" style={{ width: '220px' }}>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertMarkdownElements('**', '**')}>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertMarkdownElements('**', '**')}
+          >
             <span className="material-symbols-outlined fs-5">format_bold</span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertMarkdownElements('*', '*')}>
-            <span className="material-symbols-outlined fs-5">format_italic</span>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertMarkdownElements('*', '*')}
+          >
+            <span className="material-symbols-outlined fs-5">
+              format_italic
+            </span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertMarkdownElements('~', '~')}>
-            <span className="material-symbols-outlined fs-5">format_strikethrough</span>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertMarkdownElements('~', '~')}
+          >
+            <span className="material-symbols-outlined fs-5">
+              format_strikethrough
+            </span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('#', true)}>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertPrefix('#', true)}
+          >
             {/* TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015 */}
             <span className="growi-custom-icons">header</span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertMarkdownElements('`', '`')}>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertMarkdownElements('`', '`')}
+          >
             <span className="material-symbols-outlined fs-5">code</span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('-')}>
-            <span className="material-symbols-outlined fs-5">format_list_bulleted</span>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertPrefix('-')}
+          >
+            <span className="material-symbols-outlined fs-5">
+              format_list_bulleted
+            </span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('1.')}>
-            <span className="material-symbols-outlined fs-5">format_list_numbered</span>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertPrefix('1.')}
+          >
+            <span className="material-symbols-outlined fs-5">
+              format_list_numbered
+            </span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('>')}>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertPrefix('>')}
+          >
             {/* TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015 */}
             <span className="growi-custom-icons">format_quote</span>
           </button>
-          <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('- [ ]')}>
+          <button
+            type="button"
+            className="btn btn-toolbar-button"
+            onClick={() => onClickInsertPrefix('- [ ]')}
+          >
             <span className="material-symbols-outlined fs-5">checklist</span>
           </button>
         </div>

+ 25 - 14
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/Toolbar.tsx

@@ -1,12 +1,8 @@
-import {
-  memo, useCallback, useRef, type JSX,
-} from 'react';
-
+import { type JSX, memo, useCallback, useRef } from 'react';
 import type { AcceptedUploadFileType } from '@growi/core';
 import SimpleBar from 'simplebar-react';
 
 import type { GlobalCodeMirrorEditorKey } from '../../../../consts';
-
 import { AttachmentsDropup } from './AttachmentsDropup';
 import { DiagramButton } from './DiagramButton';
 import { EmojiButton } from './EmojiButton';
@@ -17,10 +13,10 @@ import { TextFormatTools } from './TextFormatTools';
 import styles from './Toolbar.module.scss';
 
 type Props = {
-  editorKey: string | GlobalCodeMirrorEditorKey,
-  acceptedUploadFileType: AcceptedUploadFileType,
-  onUpload?: (files: File[]) => void,
-}
+  editorKey: string | GlobalCodeMirrorEditorKey;
+  acceptedUploadFileType: AcceptedUploadFileType;
+  onUpload?: (files: File[]) => void;
+};
 
 export const Toolbar = memo((props: Props): JSX.Element => {
   const { editorKey, acceptedUploadFileType, onUpload } = props;
@@ -30,16 +26,31 @@ export const Toolbar = memo((props: Props): JSX.Element => {
     if (simpleBarRef.current) {
       simpleBarRef.current.recalculate();
     }
-  }, [simpleBarRef]);
+  }, []);
 
   return (
     <>
-      <div className={`d-flex gap-2 py-1 px-2 px-md-3 border-top ${styles['codemirror-editor-toolbar']} align-items-center`}>
-        <AttachmentsDropup editorKey={editorKey} onUpload={onUpload} acceptedUploadFileType={acceptedUploadFileType} />
+      <div
+        className={`d-flex gap-2 py-1 px-2 px-md-3 border-top ${styles['codemirror-editor-toolbar']} align-items-center`}
+      >
+        <AttachmentsDropup
+          editorKey={editorKey}
+          onUpload={onUpload}
+          acceptedUploadFileType={acceptedUploadFileType}
+        />
         <div className="flex-grow-1">
-          <SimpleBar ref={simpleBarRef} autoHide style={{ overflowY: 'hidden' }}>
+          <SimpleBar
+            ref={simpleBarRef}
+            autoHide
+            style={{ overflowY: 'hidden' }}
+          >
             <div className="d-flex gap-2">
-              <TextFormatTools editorKey={editorKey} onTextFormatToolsCollapseChange={onTextFormatToolsCollapseChange} />
+              <TextFormatTools
+                editorKey={editorKey}
+                onTextFormatToolsCollapseChange={
+                  onTextFormatToolsCollapseChange
+                }
+              />
               <EmojiButton editorKey={editorKey} />
               <TableButton editorKey={editorKey} />
               <DiagramButton editorKey={editorKey} />

+ 46 - 28
packages/editor/src/client/components-internal/playground/Playground.tsx

@@ -1,24 +1,26 @@
-import {
-  useCallback, useEffect, useMemo, useState, type JSX,
-} from 'react';
-
+import { type JSX, useCallback, useEffect, useMemo, useState } from 'react';
 import { AcceptedUploadFileType } from '@growi/core';
-import { GLOBAL_SOCKET_KEY, GLOBAL_SOCKET_NS, useSWRStatic } from '@growi/core/dist/swr';
+import {
+  GLOBAL_SOCKET_KEY,
+  GLOBAL_SOCKET_NS,
+  useSWRStatic,
+} from '@growi/core/dist/swr';
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 import { toast } from 'react-toastify';
 
-import { GlobalCodeMirrorEditorKey } from '../../../consts';
 import type {
-  EditorSettings, EditorTheme, KeyMapMode, PasteMode,
+  EditorSettings,
+  EditorTheme,
+  KeyMapMode,
+  PasteMode,
 } from '../../../consts';
+import { GlobalCodeMirrorEditorKey } from '../../../consts';
 import { CodeMirrorEditorMain } from '../../components/CodeMirrorEditorMain';
 import { useCodeMirrorEditorIsolated } from '../../stores/codemirror-editor';
-
 import { PlaygroundController } from './PlaygroundController';
 import { Preview } from './Preview';
 
 export const Playground = (): JSX.Element => {
-
   const [markdownToPreview, setMarkdownToPreview] = useState('');
   const [editorTheme, setEditorTheme] = useState<EditorTheme>('defaultlight');
   const [editorKeymap, setEditorKeymap] = useState<KeyMapMode>('default');
@@ -26,7 +28,9 @@ export const Playground = (): JSX.Element => {
   const [enableUnifiedMergeView, setUnifiedMergeViewEnabled] = useState(false);
   const [editorSettings, setEditorSettings] = useState<EditorSettings>();
 
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(
+    GlobalCodeMirrorEditorKey.MAIN,
+  );
 
   const { mutate } = useSWRStatic(GLOBAL_SOCKET_KEY);
 
@@ -43,26 +47,29 @@ export const Playground = (): JSX.Element => {
       autoFormatMarkdownTable: true,
       pasteMode: editorPaste,
     });
-  }, [setEditorSettings, editorKeymap, editorTheme, editorPaste]);
+  }, [editorKeymap, editorTheme, editorPaste]);
 
   // initialize global socket
   useEffect(() => {
-    const setUpSocket = async() => {
+    const setUpSocket = async () => {
       const { io } = await import('socket.io-client');
       const socket = io(GLOBAL_SOCKET_NS, {
         transports: ['websocket'],
       });
 
       // eslint-disable-next-line no-console
-      socket.on('error', (err) => { console.error(err) });
+      socket.on('error', (err) => {
+        console.error(err);
+      });
       // eslint-disable-next-line no-console
-      socket.on('connect_error', (err) => { console.error('Failed to connect with websocket.', err) });
+      socket.on('connect_error', (err) => {
+        console.error('Failed to connect with websocket.', err);
+      });
 
       mutate(socket);
     };
 
     setUpSocket();
-
   }, [mutate]);
 
   // set handler to save with shortcut key
@@ -74,22 +81,30 @@ export const Playground = (): JSX.Element => {
 
   // the upload event handler
   // demo of uploading a file.
-  const uploadHandler = useCallback((files: File[]) => {
-    files.forEach((file) => {
-      // set dummy file name.
-      const insertText = `[${file.name}](/attachment/aaaabbbbccccdddd)\n`;
-      codeMirrorEditor?.insertText(insertText);
-    });
-
-  }, [codeMirrorEditor]);
+  const uploadHandler = useCallback(
+    (files: File[]) => {
+      files.forEach((file) => {
+        // set dummy file name.
+        const insertText = `[${file.name}](/attachment/aaaabbbbccccdddd)\n`;
+        codeMirrorEditor?.insertText(insertText);
+      });
+    },
+    [codeMirrorEditor],
+  );
 
-  const cmProps = useMemo<ReactCodeMirrorProps>(() => ({
-    onChange: setMarkdownToPreview,
-  }), []);
+  const cmProps = useMemo<ReactCodeMirrorProps>(
+    () => ({
+      onChange: setMarkdownToPreview,
+    }),
+    [],
+  );
 
   return (
     <div className="d-flex flex-column vw-100 flex-expand-vh-100">
-      <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>
+      <div
+        className="flex-expand-vert justify-content-center align-items-center bg-dark"
+        style={{ minHeight: '83px' }}
+      >
         <div className="text-white">GrowiSubNavigation</div>
       </div>
       <div className="flex-expand-horiz">
@@ -117,7 +132,10 @@ export const Playground = (): JSX.Element => {
           />
         </div>
       </div>
-      <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '50px' }}>
+      <div
+        className="flex-expand-vert justify-content-center align-items-center bg-dark"
+        style={{ minHeight: '50px' }}
+      >
         <div className="text-white">EditorNavbarBottom</div>
       </div>
     </div>

+ 10 - 8
packages/editor/src/client/components-internal/playground/PlaygroundController.tsx

@@ -1,6 +1,4 @@
 import type { EditorTheme, KeyMapMode, PasteMode } from '../../../consts';
-
-
 import { InitEditorValueRow } from './controller/InitEditorValueRow';
 import { KeymapControl } from './controller/KeymapControl';
 import { PasteModeControl } from './controller/PasteModeControl';
@@ -9,18 +7,22 @@ import { ThemeControl } from './controller/ThemeControl';
 import { UnifiedMergeViewControl } from './controller/UnifiedMergeViewControl';
 
 type PlaygroundControllerProps = {
-  setEditorTheme: (value: EditorTheme) => void
-  setEditorKeymap: (value: KeyMapMode) => void
-  setEditorPaste: (value: PasteMode) => void
-  setUnifiedMergeView: (value: boolean) => void
+  setEditorTheme: (value: EditorTheme) => void;
+  setEditorKeymap: (value: KeyMapMode) => void;
+  setEditorPaste: (value: PasteMode) => void;
+  setUnifiedMergeView: (value: boolean) => void;
 };
 
-export const PlaygroundController = (props: PlaygroundControllerProps): JSX.Element => {
+export const PlaygroundController = (
+  props: PlaygroundControllerProps,
+): JSX.Element => {
   return (
     <div className="container">
       <InitEditorValueRow />
       <SetCaretLineRow />
-      <UnifiedMergeViewControl onChange={bool => props.setUnifiedMergeView(bool)} />
+      <UnifiedMergeViewControl
+        onChange={(bool) => props.setUnifiedMergeView(bool)}
+      />
       <ThemeControl setEditorTheme={props.setEditorTheme} />
       <KeymapControl setEditorKeymap={props.setEditorKeymap} />
       <PasteModeControl setEditorPaste={props.setEditorPaste} />

+ 2 - 2
packages/editor/src/client/components-internal/playground/Preview.tsx

@@ -1,8 +1,8 @@
 import type { JSX } from 'react';
 
 type Props = {
-  markdown?: string,
-}
+  markdown?: string;
+};
 
 export const Preview = (props: Props): JSX.Element => {
   return (

+ 7 - 3
packages/editor/src/client/components-internal/playground/controller/KeymapControl.tsx

@@ -1,18 +1,22 @@
 import type { KeyMapMode } from '../../../../consts';
 import { AllKeyMap } from '../../../../consts';
-
 import { OutlineSecondaryButtons } from './OutlineSecondaryButtons';
 
 type KeymapControlProps = {
   setEditorKeymap: (value: KeyMapMode) => void;
 };
 
-export const KeymapControl = ({ setEditorKeymap }: KeymapControlProps): JSX.Element => {
+export const KeymapControl = ({
+  setEditorKeymap,
+}: KeymapControlProps): JSX.Element => {
   return (
     <div className="row mt-5">
       <h2>Keymaps</h2>
       <div className="col">
-        <OutlineSecondaryButtons<KeyMapMode> update={setEditorKeymap} items={AllKeyMap} />
+        <OutlineSecondaryButtons<KeyMapMode>
+          update={setEditorKeymap}
+          items={AllKeyMap}
+        />
       </div>
     </div>
   );

+ 6 - 6
packages/editor/src/client/components-internal/playground/controller/OutlineSecondaryButtons.tsx

@@ -1,15 +1,15 @@
 type OutlineSecondaryButtonsProps<V> = {
-  update: (value: V) => void,
-  items: V[],
-}
+  update: (value: V) => void;
+  items: V[];
+};
 
-export const OutlineSecondaryButtons = <V extends { toString: () => string }, >(
+export const OutlineSecondaryButtons = <V extends { toString: () => string }>(
   props: OutlineSecondaryButtonsProps<V>,
 ): JSX.Element => {
   const { update, items } = props;
   return (
     <div className="d-flex flex-wrap gap-1">
-      { items.map(item => (
+      {items.map((item) => (
         <button
           key={item.toString()}
           type="button"
@@ -18,7 +18,7 @@ export const OutlineSecondaryButtons = <V extends { toString: () => string }, >(
         >
           {item.toString()}
         </button>
-      )) }
+      ))}
     </div>
   );
 };

+ 7 - 3
packages/editor/src/client/components-internal/playground/controller/PasteModeControl.tsx

@@ -1,18 +1,22 @@
 import type { PasteMode } from '../../../../consts';
 import { AllPasteMode } from '../../../../consts';
-
 import { OutlineSecondaryButtons } from './OutlineSecondaryButtons';
 
 type PasteModeControlProps = {
   setEditorPaste: (value: PasteMode) => void;
 };
 
-export const PasteModeControl = ({ setEditorPaste }: PasteModeControlProps): JSX.Element => {
+export const PasteModeControl = ({
+  setEditorPaste,
+}: PasteModeControlProps): JSX.Element => {
   return (
     <div className="row mt-5">
       <h2>Paste mode</h2>
       <div className="col">
-        <OutlineSecondaryButtons<PasteMode> update={setEditorPaste} items={AllPasteMode} />
+        <OutlineSecondaryButtons<PasteMode>
+          update={setEditorPaste}
+          items={AllPasteMode}
+        />
       </div>
     </div>
   );

+ 7 - 1
packages/editor/src/client/components-internal/playground/controller/SetCaretLineRow.tsx

@@ -33,7 +33,13 @@ export const SetCaretLineRow = (): JSX.Element => {
             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>
+          <button
+            type="submit"
+            className="btn btn-outline-secondary"
+            id="button-set-cursor"
+          >
+            Set the cursor
+          </button>
         </div>
       </div>
     </form>

+ 7 - 3
packages/editor/src/client/components-internal/playground/controller/ThemeControl.tsx

@@ -1,18 +1,22 @@
 import type { EditorTheme } from '../../../../consts';
 import { AllEditorTheme } from '../../../../consts';
-
 import { OutlineSecondaryButtons } from './OutlineSecondaryButtons';
 
 type ThemeControlProps = {
   setEditorTheme: (value: EditorTheme) => void;
 };
 
-export const ThemeControl = ({ setEditorTheme }: ThemeControlProps): JSX.Element => {
+export const ThemeControl = ({
+  setEditorTheme,
+}: ThemeControlProps): JSX.Element => {
   return (
     <div className="row mt-5">
       <h2>Themes</h2>
       <div className="col">
-        <OutlineSecondaryButtons<EditorTheme> update={setEditorTheme} items={AllEditorTheme} />
+        <OutlineSecondaryButtons<EditorTheme>
+          update={setEditorTheme}
+          items={AllEditorTheme}
+        />
       </div>
     </div>
   );

+ 17 - 4
packages/editor/src/client/components-internal/playground/controller/UnifiedMergeViewControl.tsx

@@ -2,15 +2,28 @@ type UnifiedMergeViewControlProps = {
   onChange: (value: boolean) => void;
 };
 
-export const UnifiedMergeViewControl = ({ onChange }: UnifiedMergeViewControlProps): JSX.Element => {
+export const UnifiedMergeViewControl = ({
+  onChange,
+}: UnifiedMergeViewControlProps): JSX.Element => {
   return (
     <div className="row mt-5">
       <div className="col">
         <div className="form-check form-switch">
-          <input className="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckUnifiedMergeView" onChange={e => onChange(e.target.checked)} />
-          <label className="form-check-label" htmlFor="flexSwitchCheckUnifiedMergeView">Unified Merge View</label>
+          <input
+            className="form-check-input"
+            type="checkbox"
+            // biome-ignore lint/a11y/useAriaPropsForRole: ignore
+            role="switch"
+            id="flexSwitchCheckUnifiedMergeView"
+            onChange={(e) => onChange(e.target.checked)}
+          />
+          <label
+            className="form-check-label"
+            htmlFor="flexSwitchCheckUnifiedMergeView"
+          >
+            Unified Merge View
+          </label>
         </div>
-
       </div>
     </div>
   );

+ 12 - 20
packages/editor/src/client/components/CodeMirrorEditorComment.tsx

@@ -1,25 +1,22 @@
-import { memo, useEffect, type JSX } from 'react';
-
+import { type JSX, memo, useEffect } from 'react';
 import type { Extension } from '@codemirror/state';
 import { keymap } from '@codemirror/view';
 
 import type { GlobalCodeMirrorEditorKey } from '../../consts';
-import { CodeMirrorEditor, type CodeMirrorEditorProps } from '../components-internal/CodeMirrorEditor';
+import {
+  CodeMirrorEditor,
+  type CodeMirrorEditorProps,
+} from '../components-internal/CodeMirrorEditor';
 import { useCodeMirrorEditorIsolated } from '../stores/codemirror-editor';
 
-
-const additionalExtensions: Extension[] = [
-];
+const additionalExtensions: Extension[] = [];
 
 type Props = CodeMirrorEditorProps & {
-  editorKey: string | GlobalCodeMirrorEditorKey,
-}
+  editorKey: string | GlobalCodeMirrorEditorKey;
+};
 
 export const CodeMirrorEditorComment = memo((props: Props): JSX.Element => {
-  const {
-    editorKey,
-    onSave, ...rest
-  } = props;
+  const { editorKey, onSave, ...rest } = props;
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
 
@@ -48,16 +45,11 @@ export const CodeMirrorEditorComment = memo((props: Props): JSX.Element => {
       },
     ]);
 
-    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(keymapExtension);
+    const cleanupFunction =
+      codeMirrorEditor?.appendExtensions?.(keymapExtension);
 
     return cleanupFunction;
   }, [codeMirrorEditor, onSave]);
 
-  return (
-    <CodeMirrorEditor
-      editorKey={editorKey}
-      onSave={onSave}
-      {...rest}
-    />
-  );
+  return <CodeMirrorEditor editorKey={editorKey} onSave={onSave} {...rest} />;
 });

+ 39 - 32
packages/editor/src/client/components/CodeMirrorEditorMain.tsx

@@ -1,6 +1,5 @@
-import { useEffect, useMemo, type JSX } from 'react';
-
-import { type Extension } from '@codemirror/state';
+import { type JSX, useEffect, useMemo } from 'react';
+import type { Extension } from '@codemirror/state';
 import { keymap, scrollPastEnd } from '@codemirror/view';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
@@ -8,37 +7,44 @@ import deepmerge from 'ts-deepmerge';
 
 import { GlobalCodeMirrorEditorKey } from '../../consts';
 import type { EditingClient } from '../../interfaces';
-import { CodeMirrorEditor, type CodeMirrorEditorProps } from '../components-internal/CodeMirrorEditor';
-import { setDataLine, useUnifiedMergeView, codemirrorEditorClassForUnifiedMergeView } from '../services-internal';
+import {
+  CodeMirrorEditor,
+  type CodeMirrorEditorProps,
+} from '../components-internal/CodeMirrorEditor';
+import {
+  codemirrorEditorClassForUnifiedMergeView,
+  setDataLine,
+  useUnifiedMergeView,
+} from '../services-internal';
 import { useCodeMirrorEditorIsolated } from '../stores/codemirror-editor';
 import { useCollaborativeEditorMode } from '../stores/use-collaborative-editor-mode';
 
-
-const additionalExtensions: Extension[] = [
-  [
-    scrollPastEnd(),
-    setDataLine,
-  ],
-];
+const additionalExtensions: Extension[] = [[scrollPastEnd(), setDataLine]];
 
 type Props = CodeMirrorEditorProps & {
-  user?: IUserHasId,
-  pageId?: string,
-  initialValue?: string,
-  enableCollaboration?: boolean,
-  enableUnifiedMergeView?: boolean,
-  onEditorsUpdated?: (clientList: EditingClient[]) => void,
-}
+  user?: IUserHasId;
+  pageId?: string;
+  initialValue?: string;
+  enableCollaboration?: boolean;
+  enableUnifiedMergeView?: boolean;
+  onEditorsUpdated?: (clientList: EditingClient[]) => void;
+};
 
 export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
   const {
-    user, pageId,
-    enableCollaboration = false, enableUnifiedMergeView = false,
+    user,
+    pageId,
+    enableCollaboration = false,
+    enableUnifiedMergeView = false,
     cmProps,
-    onSave, onEditorsUpdated, ...otherProps
+    onSave,
+    onEditorsUpdated,
+    ...otherProps
   } = props;
 
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(
+    GlobalCodeMirrorEditorKey.MAIN,
+  );
 
   useCollaborativeEditorMode(enableCollaboration, codeMirrorEditor, {
     user,
@@ -79,15 +85,16 @@ export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
     return cleanupFunction;
   }, [codeMirrorEditor, onSave]);
 
-  const cmPropsOverride = useMemo<ReactCodeMirrorProps>(() => deepmerge(
-    cmProps ?? {},
-    {
-      // Disable the basic history configuration since this component uses Y.UndoManager instead
-      basicSetup: {
-        history: false,
-      },
-    },
-  ), [cmProps]);
+  const cmPropsOverride = useMemo<ReactCodeMirrorProps>(
+    () =>
+      deepmerge(cmProps ?? {}, {
+        // Disable the basic history configuration since this component uses Y.UndoManager instead
+        basicSetup: {
+          history: false,
+        },
+      }),
+    [cmProps],
+  );
 
   return (
     <CodeMirrorEditor

+ 16 - 17
packages/editor/src/client/components/CodeMirrorEditorReadOnly.tsx

@@ -1,6 +1,5 @@
-import { useEffect, type JSX } from 'react';
-
-import { type Extension, EditorState, Prec } from '@codemirror/state';
+import { type JSX, useEffect } from 'react';
+import { EditorState, type Extension, Prec } from '@codemirror/state';
 import { EditorView, keymap } from '@codemirror/view';
 
 import { GlobalCodeMirrorEditorKey } from '../../consts';
@@ -8,22 +7,22 @@ import { CodeMirrorEditor } from '../components-internal/CodeMirrorEditor';
 import { setDataLine } from '../services-internal';
 import { useCodeMirrorEditorIsolated } from '../stores/codemirror-editor';
 
-
 const additionalExtensions: Extension[] = [
-  [
-    setDataLine,
-    EditorState.readOnly.of(true),
-    EditorView.editable.of(false),
-  ],
+  [setDataLine, EditorState.readOnly.of(true), EditorView.editable.of(false)],
 ];
 
 type Props = {
-  markdown?: string,
-  onScroll?: () => void,
-}
+  markdown?: string;
+  onScroll?: () => void;
+};
 
-export const CodeMirrorEditorReadOnly = ({ markdown, onScroll }: Props): JSX.Element => {
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.READONLY);
+export const CodeMirrorEditorReadOnly = ({
+  markdown,
+  onScroll,
+}: Props): JSX.Element => {
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(
+    GlobalCodeMirrorEditorKey.READONLY,
+  );
 
   codeMirrorEditor?.initDoc(markdown);
 
@@ -31,7 +30,6 @@ export const CodeMirrorEditorReadOnly = ({ markdown, onScroll }: Props): JSX.Ele
     return codeMirrorEditor?.appendExtensions?.(additionalExtensions);
   }, [codeMirrorEditor]);
 
-
   // prevent Ctrl+V and paste
   useEffect(() => {
     const extension = keymap.of([
@@ -57,12 +55,13 @@ export const CodeMirrorEditorReadOnly = ({ markdown, onScroll }: Props): JSX.Ele
       paste: handlePaste,
     });
 
-    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(Prec.high(extension));
+    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(
+      Prec.high(extension),
+    );
 
     return cleanupFunction;
   }, [codeMirrorEditor]);
 
-
   return (
     <CodeMirrorEditor
       hideToolbar

+ 7 - 8
packages/editor/src/client/components/diff/CodeMirrorEditorDiff.tsx

@@ -1,7 +1,4 @@
-import {
-  useEffect, useRef, useMemo, type JSX,
-} from 'react';
-
+import { type JSX, useEffect, useMemo, useRef } from 'react';
 import type { Extension } from '@codemirror/state';
 import { placeholder, scrollPastEnd } from '@codemirror/view';
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
@@ -26,7 +23,11 @@ export const CodeMirrorEditorDiff = (): JSX.Element => {
     return {};
   }, []);
 
-  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.DIFF, codeMirrorRef.current, cmProps);
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(
+    GlobalCodeMirrorEditorKey.DIFF,
+    codeMirrorRef.current,
+    cmProps,
+  );
 
   useDefaultExtensions(codeMirrorEditor);
   useEditorSettings(codeMirrorEditor);
@@ -36,7 +37,5 @@ export const CodeMirrorEditorDiff = (): JSX.Element => {
     return codeMirrorEditor?.appendExtensions?.(additionalExtensions);
   }, [codeMirrorEditor]);
 
-  return (
-    <div ref={codeMirrorRef} />
-  );
+  return <div ref={codeMirrorRef} />;
 };

+ 5 - 6
packages/editor/src/client/components/diff/MergeViewer.tsx

@@ -1,13 +1,12 @@
 import { memo, useEffect, useRef } from 'react';
-
 import { MergeView } from '@codemirror/merge';
-import { type Extension, EditorState } from '@codemirror/state';
-import { EditorView, basicSetup } from 'codemirror';
+import { EditorState, type Extension } from '@codemirror/state';
+import { basicSetup, EditorView } from 'codemirror';
 
 type Props = {
-  leftBody: string
-  rightBody: string
-}
+  leftBody: string;
+  rightBody: string;
+};
 
 const MergeViewerExtensions: Extension = [
   basicSetup,

+ 3 - 1
packages/editor/src/client/services-internal/editor-theme/index.ts

@@ -2,7 +2,9 @@ import type { Extension } from '@codemirror/state';
 
 import type { EditorTheme } from '../../../consts';
 
-export const getEditorTheme = async(themeName?: EditorTheme): Promise<Extension> => {
+export const getEditorTheme = async (
+  themeName?: EditorTheme,
+): Promise<Extension> => {
   switch (themeName) {
     case 'eclipse':
       return (await import('./eclipse')).eclipse;

+ 9 - 1
packages/editor/src/client/services-internal/editor-theme/original-dark.ts

@@ -23,7 +23,15 @@ export const originalDark = createTheme({
     // { tag: t.moduleKeyword, color: 'red' },
     { tag: [t.tagName, t.modifier], color: '#BA6666' },
     { tag: [t.url, t.escape, t.regexp, t.link], color: '#8FA7C7' },
-    { tag: [t.number, t.definition(t.tagName), t.className, t.definition(t.variableName)], color: '#fbac52' },
+    {
+      tag: [
+        t.number,
+        t.definition(t.tagName),
+        t.className,
+        t.definition(t.variableName),
+      ],
+      color: '#fbac52',
+    },
     { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#BA6666' },
     { tag: t.variableName, color: '#539ac4' },
     { tag: [t.propertyName, t.typeName], color: '#629ccd' },

+ 17 - 3
packages/editor/src/client/services-internal/editor-theme/original-light.ts

@@ -4,7 +4,6 @@ import type { Extension } from '@codemirror/state';
 import { tags as t } from '@lezer/highlight';
 import { createTheme } from '@uiw/codemirror-themes';
 
-
 export const originalLight: Extension = createTheme({
   theme: 'light',
   settings: {
@@ -19,7 +18,10 @@ export const originalLight: Extension = createTheme({
     { tag: [t.standard(t.tagName), t.tagName], color: '#377148' },
     { tag: [t.comment, t.bracket], color: '#6a737d' },
     { tag: [t.className, t.propertyName], color: '#6f42c1' },
-    { tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#d73a49' },
+    {
+      tag: [t.keyword, t.typeName, t.typeOperator, t.typeName],
+      color: '#d73a49',
+    },
     { tag: [t.name, t.quote], color: '#22863a' },
     { tag: [t.heading], color: '#24292e', fontWeight: 'bold' },
     { tag: [t.emphasis], color: '#24292e', fontStyle: 'italic' },
@@ -29,7 +31,19 @@ export const originalLight: Extension = createTheme({
     { tag: [t.url, t.escape, t.regexp, t.link], color: '#032f62' },
     { tag: t.link, textDecoration: 'underline' },
     { tag: t.strikethrough, textDecoration: 'line-through' },
-    { tag: [t.variableName, t.attributeName, t.number, t.operator, t.character, t.brace, t.processingInstruction, t.inserted], color: '#516883' },
+    {
+      tag: [
+        t.variableName,
+        t.attributeName,
+        t.number,
+        t.operator,
+        t.character,
+        t.brace,
+        t.processingInstruction,
+        t.inserted,
+      ],
+      color: '#516883',
+    },
     { tag: [t.strong], color: '#744763' },
     { tag: t.invalid, color: '#cb2431' },
   ],

+ 24 - 17
packages/editor/src/client/services-internal/extensions/emojiAutocompletionSettings.ts

@@ -1,8 +1,11 @@
-import { type CompletionContext, type Completion, autocompletion } from '@codemirror/autocomplete';
+import {
+  autocompletion,
+  type Completion,
+  type CompletionContext,
+} from '@codemirror/autocomplete';
 import { syntaxTree } from '@codemirror/language';
 import emojiData from '@emoji-mart/data';
 
-
 const getEmojiDataArray = (): string[] => {
   const rawEmojiDataArray = emojiData.categories;
 
@@ -20,7 +23,9 @@ const getEmojiDataArray = (): string[] => {
   const fixedEmojiDataArray: string[] = [];
 
   emojiCategoriesData.forEach((value) => {
-    const tempArray = rawEmojiDataArray.find((obj: {id: string}) => obj.id === value)?.emojis;
+    const tempArray = rawEmojiDataArray.find(
+      (obj: { id: string }) => obj.id === value,
+    )?.emojis;
 
     if (tempArray == null) {
       return;
@@ -34,13 +39,13 @@ const getEmojiDataArray = (): string[] => {
 
 const emojiDataArray = getEmojiDataArray();
 
-const emojiOptions = emojiDataArray.map(
-  tag => ({ label: `:${tag}:`, type: tag }),
-);
+const emojiOptions = emojiDataArray.map((tag) => ({
+  label: `:${tag}:`,
+  type: tag,
+}));
 
 const TWO_OR_MORE_WORD_CHARACTERS_REGEX = /:\w{2,}$/;
 
-
 // EmojiAutocompletion is activated when two characters are entered into the editor.
 const emojiAutocompletion = (context: CompletionContext) => {
   const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1);
@@ -57,17 +62,19 @@ const emojiAutocompletion = (context: CompletionContext) => {
 };
 
 export const emojiAutocompletionSettings = autocompletion({
-  addToOptions: [{
-    render: (completion: Completion) => {
-      const emojiName = completion.type ?? '';
-      const emoji = emojiData.emojis[emojiName].skins[0].native;
-
-      const element = document.createElement('span');
-      element.innerHTML = emoji;
-      return element;
+  addToOptions: [
+    {
+      render: (completion: Completion) => {
+        const emojiName = completion.type ?? '';
+        const emoji = emojiData.emojis[emojiName].skins[0].native;
+
+        const element = document.createElement('span');
+        element.innerHTML = emoji;
+        return element;
+      },
+      position: 20,
     },
-    position: 20,
-  }],
+  ],
   icons: false,
   override: [emojiAutocompletion],
 });

+ 3 - 7
packages/editor/src/client/services-internal/extensions/setDataLine.ts

@@ -1,15 +1,13 @@
-
 // Ref: https://github.com/uiwjs/react-codemirror/blob/bf3b862923d0cb04ccf4bb9da0791bdc7fd6d29b/extensions/classname/src/index.ts
 
-
 import { RangeSetBuilder } from '@codemirror/state';
-import type { EditorView, DecorationSet, ViewUpdate } from '@codemirror/view';
+import type { DecorationSet, EditorView, ViewUpdate } from '@codemirror/view';
 import { Decoration, ViewPlugin } from '@codemirror/view';
 
 const stripeDeco = (view: EditorView) => {
   const builder = new RangeSetBuilder<Decoration>();
   for (const { from, to } of view.visibleRanges) {
-    for (let pos = from; pos <= to;) {
+    for (let pos = from; pos <= to; ) {
       const line = view.state.doc.lineAt(pos);
       const cls = line.number.toString();
       builder.add(
@@ -27,7 +25,6 @@ const stripeDeco = (view: EditorView) => {
 
 export const setDataLine = ViewPlugin.fromClass(
   class {
-
     decorations: DecorationSet;
 
     constructor(view: EditorView) {
@@ -39,9 +36,8 @@ export const setDataLine = ViewPlugin.fromClass(
         this.decorations = stripeDeco(update.view);
       }
     }
-
   },
   {
-    decorations: v => v.decorations,
+    decorations: (v) => v.decorations,
   },
 );

+ 1 - 1
packages/editor/src/client/services-internal/file-dropzone/index.ts

@@ -1,2 +1,2 @@
-export * from './use-file-dropzone/use-file-dropzone';
 export * from './use-file-dropzone/FileDropzoneOverlay';
+export * from './use-file-dropzone/use-file-dropzone';

+ 3 - 4
packages/editor/src/client/services-internal/file-dropzone/use-file-dropzone/FileDropzoneOverlay.tsx

@@ -1,8 +1,8 @@
 import type { JSX } from 'react';
 
 type Props = {
-  isEnabled: boolean,
-}
+  isEnabled: boolean;
+};
 
 export const FileDropzoneOverlay = (props: Props): JSX.Element => {
   const { isEnabled } = props;
@@ -11,8 +11,7 @@ export const FileDropzoneOverlay = (props: Props): JSX.Element => {
     return (
       <div className="overlay overlay-dropzone-active">
         <span className="overlay-content">
-          <span className="overlay-icon material-symbols-outlined">
-          </span>
+          <span className="overlay-icon material-symbols-outlined"></span>
         </span>
       </div>
     );

+ 27 - 25
packages/editor/src/client/services-internal/file-dropzone/use-file-dropzone/use-file-dropzone.ts

@@ -1,41 +1,43 @@
 import { useCallback, useState } from 'react';
-
 import { AcceptedUploadFileType } from '@growi/core';
+import type { Accept, DropzoneOptions, DropzoneState } from 'react-dropzone';
 import { useDropzone } from 'react-dropzone';
-import type { DropzoneOptions, DropzoneState, Accept } from 'react-dropzone';
-
 
 type FileDropzoneState = DropzoneState & {
-  isUploading: boolean,
-}
+  isUploading: boolean;
+};
 
 type Props = {
-  acceptedUploadFileType: AcceptedUploadFileType,
-  dropzoneOpts?: DropzoneOptions,
-  onUpload?: (files: File[]) => void,
-}
+  acceptedUploadFileType: AcceptedUploadFileType;
+  dropzoneOpts?: DropzoneOptions;
+  onUpload?: (files: File[]) => void;
+};
 
 export const useFileDropzone = (props: Props): FileDropzoneState => {
-
   const { acceptedUploadFileType, dropzoneOpts, onUpload } = props;
 
   const [isUploading, setIsUploading] = useState(false);
 
-  const dropHandler = useCallback((acceptedFiles: File[]) => {
-    if (onUpload == null) {
-      return;
-    }
-    if (acceptedUploadFileType === AcceptedUploadFileType.NONE) {
-      return;
-    }
-
-    setIsUploading(true);
-    onUpload(acceptedFiles);
-    setIsUploading(false);
-
-  }, [onUpload, setIsUploading, acceptedUploadFileType]);
-
-  const accept: Accept | undefined = (acceptedUploadFileType === AcceptedUploadFileType.IMAGE) ? { 'image/*': [] } : undefined;
+  const dropHandler = useCallback(
+    (acceptedFiles: File[]) => {
+      if (onUpload == null) {
+        return;
+      }
+      if (acceptedUploadFileType === AcceptedUploadFileType.NONE) {
+        return;
+      }
+
+      setIsUploading(true);
+      onUpload(acceptedFiles);
+      setIsUploading(false);
+    },
+    [onUpload, acceptedUploadFileType],
+  );
+
+  const accept: Accept | undefined =
+    acceptedUploadFileType === AcceptedUploadFileType.IMAGE
+      ? { 'image/*': [] }
+      : undefined;
 
   const dzState = useDropzone({
     onDrop: dropHandler,

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

@@ -3,15 +3,19 @@ import { keymap } from '@codemirror/view';
 
 import type { KeyMapMode } from '../../../consts';
 
-
-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);
     case 'emacs':
       return (await import('@replit/codemirror-emacs')).emacs();
     case 'vscode':
-      return keymap.of((await import('@replit/codemirror-vscode-keymap')).vscodeKeymap);
+      return keymap.of(
+        (await import('@replit/codemirror-vscode-keymap')).vscodeKeymap,
+      );
   }
   return keymap.of((await import('@codemirror/commands')).defaultKeymap);
 };

+ 8 - 3
packages/editor/src/client/services-internal/link-util/markdown-link-util.ts

@@ -12,14 +12,16 @@ const doc = (editor: EditorView) => {
 
 const getCursorLine = (editor: EditorView) => {
   return doc(editor).lineAt(curPos(editor));
-
 };
 
 export const isInLink = (editor: EditorView): boolean => {
   const cursorLine = getCursorLine(editor);
   const startPos = curPos(editor) - cursorLine.from;
 
-  const { beginningOfLink, endOfLink } = Linker.getBeginningAndEndIndexOfLink(cursorLine.text, startPos);
+  const { beginningOfLink, endOfLink } = Linker.getBeginningAndEndIndexOfLink(
+    cursorLine.text,
+    startPos,
+  );
   return beginningOfLink >= 0 && endOfLink >= 0;
 };
 export const getMarkdownLink = (editor: EditorView): Linker => {
@@ -36,6 +38,9 @@ export const getMarkdownLink = (editor: EditorView): Linker => {
   return Linker.fromLineWithIndex(cursorLine.text, startPos);
 };
 
-export const replaceFocusedMarkdownLinkWithEditor = (editor: EditorView, linkText: string): void => {
+export const replaceFocusedMarkdownLinkWithEditor = (
+  editor: EditorView,
+  linkText: string,
+): void => {
   editor.dispatch(editor.state.replaceSelection(linkText));
 };

+ 11 - 4
packages/editor/src/client/services-internal/list-util/insert-newline-continue-markup.ts

@@ -2,15 +2,22 @@ import type { ChangeSpec } from '@codemirror/state';
 import type { EditorView } from '@codemirror/view';
 
 // https://regex101.com/r/r9plEA/1
-const indentAndMarkRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
+const indentAndMarkRE =
+  /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
 // https://regex101.com/r/HFYoFN/1
-const indentAndMarkOnlyRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
+const indentAndMarkOnlyRE =
+  /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
 
 export const insertNewlineContinueMarkup = (editor: EditorView): void => {
-
   const changes: ChangeSpec[] = [];
 
-  let selection;
+  let selection:
+    | typeof editor.state.selection
+    | {
+        anchor: number;
+        head?: number;
+      }
+    | undefined;
 
   const curPos = editor.state.selection.main.head;
 

+ 5 - 7
packages/editor/src/client/services-internal/paste-util/paste-markdown-util.ts

@@ -1,9 +1,11 @@
 import type { EditorView } from '@codemirror/view';
 
 // https://regex101.com/r/r9plEA/1
-const indentAndMarkRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
+const indentAndMarkRE =
+  /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
 // https://regex101.com/r/HFYoFN/1
-const indentAndMarkOnlyRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
+const indentAndMarkOnlyRE =
+  /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
 
 const getBol = (editor: EditorView) => {
   const curPos = editor.state.selection.main.head;
@@ -17,7 +19,6 @@ export const getStrFromBol = (editor: EditorView): string => {
 };
 
 export const adjustPasteData = (strFromBol: string, text: string): string => {
-
   let adjusted = text;
 
   if (indentAndMarkOnlyRE.test(strFromBol)) {
@@ -28,7 +29,6 @@ export const adjustPasteData = (strFromBol: string, text: string): string => {
       const lines = text.match(/[^\r\n]+/g);
 
       const replacedLines = lines?.map((line, index) => {
-
         if (index === 0 && strFromBol.match(indentAndMarkOnlyRE)) {
           return line.replace(indentAndMarkRE, '');
         }
@@ -37,9 +37,7 @@ export const adjustPasteData = (strFromBol: string, text: string): string => {
       });
 
       adjusted = replacedLines ? replacedLines.join('\n') : '';
-    }
-
-    else {
+    } else {
       const replacedText = text.replace(/(\r\n|\r|\n)/g, `$1${strFromBol}`);
 
       adjusted = replacedText;

+ 9 - 9
packages/editor/src/client/services-internal/table/insert-new-row-to-table-markdown.ts

@@ -74,13 +74,15 @@ const addRowToMarkdownTable = (mdtable: MarkdownTable): any => {
   mdtable.table.push(newRow);
 };
 
-export const mergeMarkdownTable = (mdtableList: MarkdownTable[]): MarkdownTable => {
+export const mergeMarkdownTable = (
+  mdtableList: MarkdownTable[],
+): MarkdownTable => {
   let newTable: any[] = [];
   const options = mdtableList[0].options;
   mdtableList.forEach((mdtable) => {
     newTable = newTable.concat(mdtable.table);
   });
-  return (new MarkdownTable(newTable, options));
+  return new MarkdownTable(newTable, options);
 };
 
 const addRow = (editor: EditorView) => {
@@ -122,7 +124,6 @@ const addRow = (editor: EditorView) => {
 };
 
 const removeRow = (editor: EditorView) => {
-
   const curPos = getCurPos(editor);
 
   const curLine = editor.state.doc.lineAt(curPos).number;
@@ -168,7 +169,9 @@ const reformTable = (editor: EditorView) => {
     },
   });
 
-  const nextCurPos = isLastRow ? editor.state.doc.line(curLine).to : editor.state.doc.line(nextLine).from + 2;
+  const nextCurPos = isLastRow
+    ? editor.state.doc.line(curLine).to
+    : editor.state.doc.line(nextLine).from + 2;
 
   editor.dispatch({
     selection: { anchor: nextCurPos },
@@ -176,7 +179,6 @@ const reformTable = (editor: EditorView) => {
 };
 
 export const insertNewRowToMarkdownTable = (editor: EditorView): void => {
-
   const curPos = getCurPos(editor);
 
   const curLine = editor.state.doc.lineAt(curPos).number;
@@ -192,11 +194,9 @@ export const insertNewRowToMarkdownTable = (editor: EditorView): void => {
 
   if (isEndOfLine) {
     addRow(editor);
-  }
-  else if (isLastRow && emptyLineOfTableRE.test(strFromBol + strToEol)) {
+  } else if (isLastRow && emptyLineOfTableRE.test(strFromBol + strToEol)) {
     removeRow(editor);
-  }
-  else {
+  } else {
     reformTable(editor);
   }
 };

+ 5 - 11
packages/editor/src/client/services-internal/table/use-show-table-icon.ts

@@ -1,45 +1,40 @@
 import { useEffect, useState } from 'react';
-
 import type { ViewUpdate } from '@codemirror/view';
 import { EditorView } from 'codemirror';
 
-
 import type { UseCodeMirrorEditor } from '../../services';
-
 import { isInTable } from './insert-new-row-to-table-markdown';
 
 const markdownTableActivatedClass = 'markdown-table-activated';
 
-export const useShowTableIcon = (codeMirrorEditor?: UseCodeMirrorEditor): void => {
-
+export const useShowTableIcon = (
+  codeMirrorEditor?: UseCodeMirrorEditor,
+): void => {
   const [editorClass, setEditorClass] = useState('');
 
   const editor = codeMirrorEditor?.view;
 
   useEffect(() => {
-
     const handleFocusChanged = () => {
       if (editor == null) {
         return;
       }
       if (isInTable(editor)) {
         setEditorClass(markdownTableActivatedClass);
-      }
-      else {
+      } else {
         setEditorClass('');
       }
     };
 
     const cleanupFunction = codeMirrorEditor?.appendExtensions(
       EditorView.updateListener.of((v: ViewUpdate) => {
-        if (v.transactions.some(tr => tr.selection || tr.docChanged)) {
+        if (v.transactions.some((tr) => tr.selection || tr.docChanged)) {
           handleFocusChanged();
         }
       }),
     );
 
     return cleanupFunction;
-
   }, [codeMirrorEditor, editor]);
 
   useEffect(() => {
@@ -49,5 +44,4 @@ export const useShowTableIcon = (codeMirrorEditor?: UseCodeMirrorEditor): void =
 
     return cleanupFunction;
   }, [codeMirrorEditor, editorClass]);
-
 };

+ 2 - 1
packages/editor/src/client/services-internal/unified-merge-view/index.ts

@@ -1,4 +1,5 @@
 import styles from './use-unified-merge-view.module.scss';
 
 export * from './use-unified-merge-view';
-export const codemirrorEditorClassForUnifiedMergeView = styles['codemirror-editor'];
+export const codemirrorEditorClassForUnifiedMergeView =
+  styles['codemirror-editor'];

+ 9 - 6
packages/editor/src/client/services-internal/unified-merge-view/use-customized-button-styles.ts

@@ -1,11 +1,11 @@
 import { useEffect } from 'react';
-
 import { EditorView } from '@codemirror/view';
 
 import type { UseCodeMirrorEditor } from '../../services';
 
-export const useCustomizedButtonStyles = (codeMirrorEditor?: UseCodeMirrorEditor): void => {
-
+export const useCustomizedButtonStyles = (
+  codeMirrorEditor?: UseCodeMirrorEditor,
+): void => {
   // Setup button styles
   useEffect(() => {
     if (codeMirrorEditor?.view == null) {
@@ -13,10 +13,14 @@ export const useCustomizedButtonStyles = (codeMirrorEditor?: UseCodeMirrorEditor
     }
 
     const updateButtonStyles = () => {
-      const acceptButton = codeMirrorEditor.view?.dom.querySelector('button[name="accept"]');
+      const acceptButton = codeMirrorEditor.view?.dom.querySelector(
+        'button[name="accept"]',
+      );
       acceptButton?.classList.add('btn', 'btn-sm', 'btn-success');
 
-      const rejectButton = codeMirrorEditor.view?.dom.querySelector('button[name="reject"]');
+      const rejectButton = codeMirrorEditor.view?.dom.querySelector(
+        'button[name="reject"]',
+      );
       rejectButton?.classList.add('btn', 'btn-sm', 'btn-outline-secondary');
       // Set button text
       if (rejectButton != null) {
@@ -35,5 +39,4 @@ export const useCustomizedButtonStyles = (codeMirrorEditor?: UseCodeMirrorEditor
     const cleanupFunction = codeMirrorEditor?.appendExtensions([extension]);
     return cleanupFunction;
   }, [codeMirrorEditor]);
-
 };

+ 53 - 38
packages/editor/src/client/services-internal/unified-merge-view/use-unified-merge-view.ts

@@ -1,67 +1,73 @@
 import { useEffect } from 'react';
-
 import {
-  unifiedMergeView,
-  originalDocChangeEffect,
   getOriginalDoc,
+  originalDocChangeEffect,
+  unifiedMergeView,
   updateOriginalDoc,
 } from '@codemirror/merge';
 import type { StateEffect, Transaction } from '@codemirror/state';
-import {
-  ChangeSet,
-} from '@codemirror/state';
+import { ChangeSet } from '@codemirror/state';
 import { EditorView } from '@codemirror/view';
 import * as Y from 'yjs';
 
 import { deltaToChangeSpecs } from '../../../utils/delta-to-changespecs';
 import type { UseCodeMirrorEditor } from '../../services';
 import { useSecondaryYdocs } from '../../stores/use-secondary-ydocs';
-
 import { useCustomizedButtonStyles } from './use-customized-button-styles';
 
-
 // for avoiding apply update from primaryDoc to secondaryDoc twice
 const SYNC_BY_ACCEPT_CHUNK = 'synkByAcceptChunk';
 
-
 type Configuration = {
-  pageId?: string,
-}
+  pageId?: string;
+};
 
 export const useUnifiedMergeView = (
-    isEnabled: boolean,
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    configuration?: Configuration,
+  isEnabled: boolean,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  configuration?: Configuration,
 ): void => {
-
   const { pageId } = configuration ?? {};
 
-  const { primaryDoc, secondaryDoc } = useSecondaryYdocs(isEnabled, {
-    pageId,
-    useSecondary: isEnabled,
-  }) ?? {};
+  const { primaryDoc, secondaryDoc } =
+    useSecondaryYdocs(isEnabled, {
+      pageId,
+      useSecondary: isEnabled,
+    }) ?? {};
 
   useCustomizedButtonStyles(codeMirrorEditor);
 
   // setup unifiedMergeView
   useEffect(() => {
-    if (!isEnabled || primaryDoc == null || secondaryDoc == null || codeMirrorEditor == null) {
+    if (
+      !isEnabled ||
+      primaryDoc == null ||
+      secondaryDoc == null ||
+      codeMirrorEditor == null
+    ) {
       return;
     }
 
-    const extension = isEnabled ? [
-      unifiedMergeView({
-        original: codeMirrorEditor.getDoc(),
-      }),
-    ] : [];
+    const extension = isEnabled
+      ? [
+          unifiedMergeView({
+            original: codeMirrorEditor.getDoc(),
+          }),
+        ]
+      : [];
 
     const cleanupFunction = codeMirrorEditor?.appendExtensions(extension);
     return cleanupFunction;
-  }, [isEnabled, pageId, codeMirrorEditor, primaryDoc, secondaryDoc]);
+  }, [isEnabled, codeMirrorEditor, primaryDoc, secondaryDoc]);
 
   // Setup sync from primaryDoc to secondaryDoc
   useEffect(() => {
-    if (!isEnabled || primaryDoc == null || secondaryDoc == null || codeMirrorEditor == null) {
+    if (
+      !isEnabled ||
+      primaryDoc == null ||
+      secondaryDoc == null ||
+      codeMirrorEditor == null
+    ) {
       return;
     }
 
@@ -85,7 +91,10 @@ export const useUnifiedMergeView = (
         const changeSpecs = deltaToChangeSpecs(event.delta);
         const originalDoc = getOriginalDoc(codeMirrorEditor.view.state);
         const changeSet = ChangeSet.of(changeSpecs, originalDoc.length);
-        const effect = originalDocChangeEffect(codeMirrorEditor.view.state, changeSet);
+        const effect = originalDocChangeEffect(
+          codeMirrorEditor.view.state,
+          changeSet,
+        );
 
         // Dispatch in next tick to ensure state is updated
         setTimeout(() => {
@@ -106,27 +115,34 @@ export const useUnifiedMergeView = (
 
   // Setup sync from secondaryDoc to primaryDoc when accepting chunks
   useEffect(() => {
-    if (!isEnabled || primaryDoc == null || secondaryDoc == null || codeMirrorEditor == null) {
+    if (
+      !isEnabled ||
+      primaryDoc == null ||
+      secondaryDoc == null ||
+      codeMirrorEditor == null
+    ) {
       return;
     }
 
     const extension = EditorView.updateListener.of((update) => {
       // Find updateOriginalDoc effect which is dispatched when a chunk is accepted
       const updateOrigEffect = update.transactions
-        .flatMap<StateEffect<Transaction>>(tr => tr.effects)
-        .find(e => e.is(updateOriginalDoc));
+        .flatMap<StateEffect<Transaction>>((tr) => tr.effects)
+        .find((e) => e.is(updateOriginalDoc));
 
       if (updateOrigEffect != null) {
         const primaryYText = primaryDoc.getText('codemirror');
 
         primaryDoc.transact(() => {
           // fromA/toA positions are absolute document positions
-          updateOrigEffect.value.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
-            primaryYText.delete(fromA, toA - fromA);
-            if (inserted.length > 0) {
-              primaryYText.insert(fromA, inserted.toString());
-            }
-          });
+          updateOrigEffect.value.changes.iterChanges(
+            (fromA, toA, _fromB, _toB, inserted) => {
+              primaryYText.delete(fromA, toA - fromA);
+              if (inserted.length > 0) {
+                primaryYText.insert(fromA, inserted.toString());
+              }
+            },
+          );
         }, SYNC_BY_ACCEPT_CHUNK);
       }
     });
@@ -137,5 +153,4 @@ export const useUnifiedMergeView = (
       cleanup?.();
     };
   }, [codeMirrorEditor, isEnabled, primaryDoc, secondaryDoc]);
-
 };

+ 18 - 12
packages/editor/src/client/services/unified-merge-view/index.ts

@@ -1,15 +1,10 @@
 import { useEffect } from 'react';
-
-import {
-  acceptChunk,
-  getChunks,
-} from '@codemirror/merge';
+import { acceptChunk, getChunks } from '@codemirror/merge';
 import type { ViewUpdate } from '@codemirror/view';
 import { EditorView } from '@codemirror/view';
 
 import type { UseCodeMirrorEditor } from '..';
 
-
 export const acceptAllChunks = (view: EditorView): void => {
   // Get all chunks from the editor state
   const chunkData = getChunks(view.state);
@@ -28,19 +23,30 @@ type OnSelectedArgs = {
   selectedText: string;
   selectedTextIndex: number; // 0-based index in the selected text
   selectedTextFirstLineNumber: number; // 0-based line number
-}
+};
 
-type OnSelected = (args: OnSelectedArgs) => void
+type OnSelected = (args: OnSelectedArgs) => void;
 
-const processSelectedText = (editorView: EditorView | ViewUpdate, onSelected?: OnSelected) => {
+const processSelectedText = (
+  editorView: EditorView | ViewUpdate,
+  onSelected?: OnSelected,
+) => {
   const selection = editorView.state.selection.main;
   const selectedText = editorView.state.sliceDoc(selection.from, selection.to);
   const selectedTextIndex = selection.from;
-  const selectedTextFirstLineNumber = editorView.state.doc.lineAt(selection.from).number - 1; // 0-based line number;
-  onSelected?.({ selectedText, selectedTextIndex, selectedTextFirstLineNumber });
+  const selectedTextFirstLineNumber =
+    editorView.state.doc.lineAt(selection.from).number - 1; // 0-based line number;
+  onSelected?.({
+    selectedText,
+    selectedTextIndex,
+    selectedTextFirstLineNumber,
+  });
 };
 
-export const useTextSelectionEffect = (codeMirrorEditor?: UseCodeMirrorEditor, onSelected?: OnSelected): void => {
+export const useTextSelectionEffect = (
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  onSelected?: OnSelected,
+): void => {
   useEffect(() => {
     if (codeMirrorEditor == null) {
       return;

+ 55 - 49
packages/editor/src/client/services/use-codemirror-editor/use-codemirror-editor.ts

@@ -1,66 +1,72 @@
 import { useMemo } from 'react';
-
-import type {
-  EditorState,
-} from '@codemirror/state';
+import type { EditorState } from '@codemirror/state';
 import type { EditorView } from '@codemirror/view';
-import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
+import { type UseCodeMirror, useCodeMirror } from '@uiw/react-codemirror';
 import deepmerge from 'ts-deepmerge';
 
-import { useAppendExtensions, type AppendExtensions } from './utils/append-extensions';
-import { useFocus, type Focus } from './utils/focus';
+import {
+  type AppendExtensions,
+  useAppendExtensions,
+} from './utils/append-extensions';
+import { type Focus, useFocus } from './utils/focus';
 import type { FoldDrawio } from './utils/fold-drawio';
 import { useFoldDrawio } from './utils/fold-drawio';
 import type { GetDocString } from './utils/get-doc';
-import { useGetDoc, type GetDoc, useGetDocString } from './utils/get-doc';
-import { useInitDoc, type InitDoc } from './utils/init-doc';
-import { useInsertMarkdownElements, type InsertMarkdownElements } from './utils/insert-markdown-elements';
-import { useInsertPrefix, type InsertPrefix } from './utils/insert-prefix';
-import { useInsertText, type InsertText } from './utils/insert-text';
-import { useReplaceText, type ReplaceText } from './utils/replace-text';
-import { useSetCaretLine, type SetCaretLine } from './utils/set-caret-line';
-
+import { type GetDoc, useGetDoc, useGetDocString } from './utils/get-doc';
+import { type InitDoc, useInitDoc } from './utils/init-doc';
+import {
+  type InsertMarkdownElements,
+  useInsertMarkdownElements,
+} from './utils/insert-markdown-elements';
+import { type InsertPrefix, useInsertPrefix } from './utils/insert-prefix';
+import { type InsertText, useInsertText } from './utils/insert-text';
+import { type ReplaceText, useReplaceText } from './utils/replace-text';
+import { type SetCaretLine, useSetCaretLine } from './utils/set-caret-line';
 
 type UseCodeMirrorEditorUtils = {
-  initDoc: InitDoc,
-  appendExtensions: AppendExtensions,
-  getDoc: GetDoc,
-  getDocString: GetDocString,
-  focus: Focus,
-  setCaretLine: SetCaretLine,
-  insertText: InsertText,
-  replaceText: ReplaceText,
-  insertMarkdownElements: InsertMarkdownElements,
-  insertPrefix: InsertPrefix,
-  foldDrawio: FoldDrawio,
-}
+  initDoc: InitDoc;
+  appendExtensions: AppendExtensions;
+  getDoc: GetDoc;
+  getDocString: GetDocString;
+  focus: Focus;
+  setCaretLine: SetCaretLine;
+  insertText: InsertText;
+  replaceText: ReplaceText;
+  insertMarkdownElements: InsertMarkdownElements;
+  insertPrefix: InsertPrefix;
+  foldDrawio: FoldDrawio;
+};
 export type UseCodeMirrorEditor = {
   state: EditorState | undefined;
   view: EditorView | undefined;
 } & UseCodeMirrorEditorUtils;
 
-
-export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor => {
-
-  const mergedProps = useMemo(() => deepmerge(
-    {
-      // Reset settings of react-codemirror.
-      // Extensions are defined first will be used if they have the same priority.
-      // If extensions conflict, disable them here.
-      // And add them to defaultExtensions: Extension[] with a lower priority.
-      // ref: https://codemirror.net/examples/config/
-      // ------- Start -------
-      indentWithTab: false,
-      basicSetup: {
-        defaultKeymap: false,
-        dropCursor: false,
-        highlightActiveLine: false,
-        highlightActiveLineGutter: false,
-      },
-      // ------- End -------
-    },
-    props ?? {},
-  ), [props]);
+export const useCodeMirrorEditor = (
+  props?: UseCodeMirror,
+): UseCodeMirrorEditor => {
+  const mergedProps = useMemo(
+    () =>
+      deepmerge(
+        {
+          // Reset settings of react-codemirror.
+          // Extensions are defined first will be used if they have the same priority.
+          // If extensions conflict, disable them here.
+          // And add them to defaultExtensions: Extension[] with a lower priority.
+          // ref: https://codemirror.net/examples/config/
+          // ------- Start -------
+          indentWithTab: false,
+          basicSetup: {
+            defaultKeymap: false,
+            dropCursor: false,
+            highlightActiveLine: false,
+            highlightActiveLineGutter: false,
+          },
+          // ------- End -------
+        },
+        props ?? {},
+      ),
+    [props],
+  );
 
   const { state, view } = useCodeMirror(mergedProps);
 

+ 19 - 21
packages/editor/src/client/services/use-codemirror-editor/utils/append-extensions.ts

@@ -1,34 +1,32 @@
 import { useCallback } from 'react';
-
 import type { Extension } from '@codemirror/state';
 import { Compartment, StateEffect } from '@codemirror/state';
 import type { EditorView } from '@codemirror/view';
 
 type CleanupFunctions = () => void;
-export type AppendExtensions = (extensions: Extension | Extension[]) => CleanupFunctions | undefined;
+export type AppendExtensions = (
+  extensions: Extension | Extension[],
+) => CleanupFunctions | undefined;
 
 export const useAppendExtensions = (view?: EditorView): AppendExtensions => {
+  return useCallback(
+    (args) => {
+      const extensions = Array.isArray(args) ? args : [args];
 
-  return useCallback((args) => {
-    const extensions = Array.isArray(args)
-      ? args
-      : [args];
-
-    const compartment = new Compartment();
-    view?.dispatch({
-      effects: extensions.map((extension) => {
-        return StateEffect.appendConfig.of(
-          compartment.of(extension),
-        );
-      }),
-    });
-
-    // return cleanup function
-    return () => {
+      const compartment = new Compartment();
       view?.dispatch({
-        effects: compartment.reconfigure([]),
+        effects: extensions.map((extension) => {
+          return StateEffect.appendConfig.of(compartment.of(extension));
+        }),
       });
-    };
-  }, [view]);
 
+      // return cleanup function
+      return () => {
+        view?.dispatch({
+          effects: compartment.reconfigure([]),
+        });
+      };
+    },
+    [view],
+  );
 };

+ 9 - 12
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/add-multi-cursor.ts

@@ -1,19 +1,15 @@
 import { useCallback } from 'react';
-
 import type { SelectionRange } from '@codemirror/state';
 import { EditorSelection } from '@codemirror/state';
-import type { EditorView, Command, KeyBinding } from '@codemirror/view';
-
+import type { Command, EditorView, KeyBinding } from '@codemirror/view';
 
 const addMultiCursor = (view: EditorView, direction: 'up' | 'down') => {
-
   const selection = view.state.selection;
   const doc = view.state.doc;
   const ranges = selection.ranges;
   const newRanges: SelectionRange[] = [];
 
   ranges.forEach((range) => {
-
     const head = range.head;
     const line = doc.lineAt(head);
     const targetLine = direction === 'up' ? line.number - 1 : line.number + 1;
@@ -26,7 +22,6 @@ const addMultiCursor = (view: EditorView, direction: 'up' | 'down') => {
     const cursorPos = targetLineText.from + col;
 
     newRanges.push(EditorSelection.cursor(cursorPos));
-
   });
 
   if (newRanges.length) {
@@ -41,15 +36,17 @@ const addMultiCursor = (view: EditorView, direction: 'up' | 'down') => {
 };
 
 const useAddMultiCursorCommand = (direction: 'up' | 'down'): Command => {
-  return useCallback((view?: EditorView) => {
-    if (view == null) return false;
-    addMultiCursor(view, direction);
-    return true;
-  }, [direction]);
+  return useCallback(
+    (view?: EditorView) => {
+      if (view == null) return false;
+      addMultiCursor(view, direction);
+      return true;
+    },
+    [direction],
+  );
 };
 
 export const useAddMultiCursorKeyBindings = (): KeyBinding[] => {
-
   const upMultiCursorCommand = useAddMultiCursorCommand('up');
   const downMultiCursorCommand = useAddMultiCursorCommand('down');
 

+ 5 - 7
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/generate-add-markdown-symbol-command.ts

@@ -4,13 +4,12 @@ import type { InsertMarkdownElements } from '../insert-markdown-elements';
 import type { InsertPrefix } from '../insert-prefix';
 
 export const generateAddMarkdownSymbolCommand = (
-    insertMarkdown: InsertMarkdownElements | InsertPrefix,
-    prefix: string,
-    suffix?: string,
+  insertMarkdown: InsertMarkdownElements | InsertPrefix,
+  prefix: string,
+  suffix?: string,
 ): Command => {
-
   const isInsertMarkdownElements = (
-      fn: InsertMarkdownElements | InsertPrefix,
+    fn: InsertMarkdownElements | InsertPrefix,
   ): fn is InsertMarkdownElements => {
     return fn.length === 2;
   };
@@ -19,8 +18,7 @@ export const generateAddMarkdownSymbolCommand = (
     if (isInsertMarkdownElements(insertMarkdown)) {
       if (suffix == null) return false;
       insertMarkdown(prefix, suffix);
-    }
-    else {
+    } else {
       insertMarkdown(prefix);
     }
 

+ 11 - 6
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-blockquote.ts

@@ -1,17 +1,22 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertPrefix } from '../insert-prefix';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
-export const useInsertBlockquoteKeyBinding = (view?: EditorView): KeyBinding => {
-
+export const useInsertBlockquoteKeyBinding = (
+  view?: EditorView,
+): KeyBinding => {
   const insertPrefix = useInsertPrefix(view);
 
-  const insertBlockquoteCommand = generateAddMarkdownSymbolCommand(insertPrefix, '>');
+  const insertBlockquoteCommand = generateAddMarkdownSymbolCommand(
+    insertPrefix,
+    '>',
+  );
 
-  const insertBlockquoteKeyBinding = { key: 'mod-shift-9', run: insertBlockquoteCommand };
+  const insertBlockquoteKeyBinding = {
+    key: 'mod-shift-9',
+    run: insertBlockquoteCommand,
+  };
 
   return insertBlockquoteKeyBinding;
 };

+ 11 - 6
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-bullet-list.ts

@@ -1,17 +1,22 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertPrefix } from '../insert-prefix';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
-export const useInsertBulletListKeyBinding = (view?: EditorView): KeyBinding => {
-
+export const useInsertBulletListKeyBinding = (
+  view?: EditorView,
+): KeyBinding => {
   const insertPrefix = useInsertPrefix(view);
 
-  const insertBulletListCommand = generateAddMarkdownSymbolCommand(insertPrefix, '-');
+  const insertBulletListCommand = generateAddMarkdownSymbolCommand(
+    insertPrefix,
+    '-',
+  );
 
-  const insertBulletListKeyBinding = { key: 'mod-shift-8', run: insertBulletListCommand };
+  const insertBulletListKeyBinding = {
+    key: 'mod-shift-8',
+    run: insertBulletListCommand,
+  };
 
   return insertBulletListKeyBinding;
 };

+ 5 - 4
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-link.ts

@@ -1,15 +1,16 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertMarkdownElements } from '../insert-markdown-elements';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
 export const useInsertLinkKeyBinding = (view?: EditorView): KeyBinding => {
-
   const insertMarkdownElements = useInsertMarkdownElements(view);
 
-  const InsertLinkCommand = generateAddMarkdownSymbolCommand(insertMarkdownElements, '[', ']()');
+  const InsertLinkCommand = generateAddMarkdownSymbolCommand(
+    insertMarkdownElements,
+    '[',
+    ']()',
+  );
 
   const InsertLinkKeyBinding = { key: 'mod-shift-u', run: InsertLinkCommand };
 

+ 8 - 5
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/insert-numbered-list.ts

@@ -1,17 +1,20 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertPrefix } from '../insert-prefix';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
 export const useInsertNumberedKeyBinding = (view?: EditorView): KeyBinding => {
-
   const insertPrefix = useInsertPrefix(view);
 
-  const insertNumberedCommand = generateAddMarkdownSymbolCommand(insertPrefix, '1.');
+  const insertNumberedCommand = generateAddMarkdownSymbolCommand(
+    insertPrefix,
+    '1.',
+  );
 
-  const insertNumberedKeyBinding = { key: 'mod-shift-7', run: insertNumberedCommand };
+  const insertNumberedKeyBinding = {
+    key: 'mod-shift-7',
+    run: insertNumberedCommand,
+  };
 
   return insertNumberedKeyBinding;
 };

+ 21 - 8
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-bold.ts

@@ -1,23 +1,36 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
+import type { KeyMapMode } from 'src/consts';
 
 import { useInsertMarkdownElements } from '../insert-markdown-elements';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-import type { KeyMapMode } from 'src/consts';
-
-
-export const useMakeTextBoldKeyBinding = (view?: EditorView, keyMapName?: KeyMapMode): KeyBinding => {
-
+export const useMakeTextBoldKeyBinding = (
+  view?: EditorView,
+  keyMapName?: KeyMapMode,
+): KeyBinding => {
   const insertMarkdownElements = useInsertMarkdownElements(view);
 
   let makeTextBoldKeyBinding: KeyBinding;
   switch (keyMapName) {
     case 'vim':
-      makeTextBoldKeyBinding = { key: 'mod-shift-b', run: generateAddMarkdownSymbolCommand(insertMarkdownElements, '**', '**') };
+      makeTextBoldKeyBinding = {
+        key: 'mod-shift-b',
+        run: generateAddMarkdownSymbolCommand(
+          insertMarkdownElements,
+          '**',
+          '**',
+        ),
+      };
       break;
     default:
-      makeTextBoldKeyBinding = { key: 'mod-b', run: generateAddMarkdownSymbolCommand(insertMarkdownElements, '**', '**') };
+      makeTextBoldKeyBinding = {
+        key: 'mod-b',
+        run: generateAddMarkdownSymbolCommand(
+          insertMarkdownElements,
+          '**',
+          '**',
+        ),
+      };
   }
 
   return makeTextBoldKeyBinding;

+ 20 - 10
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-code-block.ts

@@ -1,5 +1,5 @@
+import type { ChangeSpec, Extension, SelectionRange } from '@codemirror/state';
 import { EditorSelection } from '@codemirror/state';
-import type { Extension, ChangeSpec, SelectionRange } from '@codemirror/state';
 import type { Command } from '@codemirror/view';
 import { EditorView } from '@codemirror/view';
 
@@ -13,7 +13,8 @@ const makeTextCodeBlock: Command = (view: EditorView) => {
     const startLine = doc.lineAt(range.from);
     const endLine = doc.lineAt(range.to);
     const selectedText = doc.sliceString(range.from, range.to, '');
-    const isAlreadyWrapped = selectedText.startsWith('```') && selectedText.endsWith('```');
+    const isAlreadyWrapped =
+      selectedText.startsWith('```') && selectedText.endsWith('```');
 
     const codeBlockMarkerLength = 4;
 
@@ -33,9 +34,13 @@ const makeTextCodeBlock: Command = (view: EditorView) => {
         insert: '',
       });
 
-      newSelections.push(EditorSelection.range(startLine.from, endMarkerStart - codeBlockMarkerLength));
-    }
-    else {
+      newSelections.push(
+        EditorSelection.range(
+          startLine.from,
+          endMarkerStart - codeBlockMarkerLength,
+        ),
+      );
+    } else {
       // Add code block markers
       changes.push({
         from: startLine.from,
@@ -48,10 +53,16 @@ const makeTextCodeBlock: Command = (view: EditorView) => {
       });
 
       if (selectedText.length === 0) {
-        newSelections.push(EditorSelection.cursor(startLine.from + codeBlockMarkerLength));
-      }
-      else {
-        newSelections.push(EditorSelection.range(startLine.from, endLine.to + codeBlockMarkerLength * 2));
+        newSelections.push(
+          EditorSelection.cursor(startLine.from + codeBlockMarkerLength),
+        );
+      } else {
+        newSelections.push(
+          EditorSelection.range(
+            startLine.from,
+            endLine.to + codeBlockMarkerLength * 2,
+          ),
+        );
       }
     }
   });
@@ -66,7 +77,6 @@ const makeTextCodeBlock: Command = (view: EditorView) => {
 
 const makeCodeBlockExtension: Extension = EditorView.domEventHandlers({
   keydown: (event, view) => {
-
     const isModKey = event.ctrlKey || event.metaKey;
 
     if (event.code === 'KeyC' && event.shiftKey && event.altKey && isModKey) {

+ 9 - 5
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-code.ts

@@ -1,17 +1,21 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertMarkdownElements } from '../insert-markdown-elements';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
 export const useMakeTextCodeKeyBinding = (view?: EditorView): KeyBinding => {
-
   const insertMarkdownElements = useInsertMarkdownElements(view);
 
-  const makeTextCodeCommand = generateAddMarkdownSymbolCommand(insertMarkdownElements, '`', '`');
+  const makeTextCodeCommand = generateAddMarkdownSymbolCommand(
+    insertMarkdownElements,
+    '`',
+    '`',
+  );
 
-  const makeTextCodeKeyBinding = { key: 'mod-shift-c', run: makeTextCodeCommand };
+  const makeTextCodeKeyBinding = {
+    key: 'mod-shift-c',
+    run: makeTextCodeCommand,
+  };
 
   return makeTextCodeKeyBinding;
 };

+ 9 - 5
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-italic.ts

@@ -1,17 +1,21 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertMarkdownElements } from '../insert-markdown-elements';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
 export const useMakeTextItalicKeyBinding = (view?: EditorView): KeyBinding => {
-
   const insertMarkdownElements = useInsertMarkdownElements(view);
 
-  const makeTextItalicCommand = generateAddMarkdownSymbolCommand(insertMarkdownElements, '*', '*');
+  const makeTextItalicCommand = generateAddMarkdownSymbolCommand(
+    insertMarkdownElements,
+    '*',
+    '*',
+  );
 
-  const makeTextItalicKeyBinding = { key: 'mod-shift-i', run: makeTextItalicCommand };
+  const makeTextItalicKeyBinding = {
+    key: 'mod-shift-i',
+    run: makeTextItalicCommand,
+  };
 
   return makeTextItalicKeyBinding;
 };

+ 12 - 6
packages/editor/src/client/services/use-codemirror-editor/utils/editor-shortcuts/make-text-strikethrough.ts

@@ -1,17 +1,23 @@
 import type { EditorView, KeyBinding } from '@codemirror/view';
 
 import { useInsertMarkdownElements } from '../insert-markdown-elements';
-
 import { generateAddMarkdownSymbolCommand } from './generate-add-markdown-symbol-command';
 
-
-export const useMakeTextStrikethroughKeyBinding = (view?: EditorView): KeyBinding => {
-
+export const useMakeTextStrikethroughKeyBinding = (
+  view?: EditorView,
+): KeyBinding => {
   const insertMarkdownElements = useInsertMarkdownElements(view);
 
-  const makeTextStrikethroughCommand = generateAddMarkdownSymbolCommand(insertMarkdownElements, '~~', '~~');
+  const makeTextStrikethroughCommand = generateAddMarkdownSymbolCommand(
+    insertMarkdownElements,
+    '~~',
+    '~~',
+  );
 
-  const makeTextStrikethroughKeyBinding = { key: 'mod-shift-x', run: makeTextStrikethroughCommand };
+  const makeTextStrikethroughKeyBinding = {
+    key: 'mod-shift-x',
+    run: makeTextStrikethroughCommand,
+  };
 
   return makeTextStrikethroughKeyBinding;
 };

+ 0 - 3
packages/editor/src/client/services/use-codemirror-editor/utils/focus.ts

@@ -1,13 +1,10 @@
 import { useCallback } from 'react';
-
 import type { EditorView } from '@codemirror/view';
 
 export type Focus = () => void;
 
 export const useFocus = (view?: EditorView): Focus => {
-
   return useCallback(() => {
     view?.focus?.();
   }, [view]);
-
 };

+ 3 - 7
packages/editor/src/client/services/use-codemirror-editor/utils/fold-drawio.ts

@@ -1,9 +1,7 @@
 import { useEffect } from 'react';
-
 import { foldEffect } from '@codemirror/language';
 import type { EditorView } from '@codemirror/view';
 
-
 export type FoldDrawio = void;
 
 const findAllDrawioSection = (view?: EditorView) => {
@@ -24,8 +22,7 @@ const findAllDrawioSection = (view?: EditorView) => {
       }
     }
     return lineNumbers;
-  }
-  catch (err) {
+  } catch (err) {
     if (err instanceof Error) {
       // eslint-disable-next-line no-console
       console.warn(err.toString());
@@ -51,10 +48,9 @@ const foldDrawioSection = (lineNumbers?: number[], view?: EditorView) => {
         }),
       });
     });
-  }
-  catch (err) {
+  } catch (err) {
     if (err instanceof Error) {
-    // eslint-disable-next-line no-console
+      // eslint-disable-next-line no-console
       console.warn(err.toString());
     }
   }

+ 0 - 5
packages/editor/src/client/services/use-codemirror-editor/utils/get-doc.ts

@@ -1,5 +1,4 @@
 import { useCallback } from 'react';
-
 import { Text } from '@codemirror/state';
 import type { EditorView } from '@codemirror/view';
 
@@ -7,17 +6,13 @@ export type GetDoc = () => Text;
 export type GetDocString = () => string;
 
 export const useGetDoc = (view?: EditorView): GetDoc => {
-
   return useCallback(() => {
     return view?.state.doc ?? Text.empty;
   }, [view]);
-
 };
 
 export const useGetDocString = (view?: EditorView): GetDocString => {
-
   return useCallback(() => {
     return (view?.state.doc ?? Text.empty).toString();
   }, [view]);
-
 };

+ 13 - 13
packages/editor/src/client/services/use-codemirror-editor/utils/init-doc.ts

@@ -1,21 +1,21 @@
 import { useCallback } from 'react';
-
 import { Transaction } from '@codemirror/state';
 import type { EditorView } from '@codemirror/view';
 
 export type InitDoc = (doc?: string) => void;
 
 export const useInitDoc = (view?: EditorView): InitDoc => {
-
-  return useCallback((doc) => {
-    view?.dispatch({
-      changes: {
-        from: 0,
-        to: view?.state.doc.length,
-        insert: doc,
-      },
-      annotations: Transaction.addToHistory.of(false),
-    });
-  }, [view]);
-
+  return useCallback(
+    (doc) => {
+      view?.dispatch({
+        changes: {
+          from: 0,
+          to: view?.state.doc.length,
+          insert: doc,
+        },
+        annotations: Transaction.addToHistory.of(false),
+      });
+    },
+    [view],
+  );
 };

+ 38 - 37
packages/editor/src/client/services/use-codemirror-editor/utils/insert-markdown-elements.ts

@@ -1,12 +1,7 @@
 import { useCallback } from 'react';
-
 import type { EditorView } from '@codemirror/view';
 
-
-export type InsertMarkdownElements = (
-  prefix: string,
-  suffix: string,
-) => void;
+export type InsertMarkdownElements = (prefix: string, suffix: string) => void;
 
 const removeSymbol = (text: string, prefix: string, suffix: string): string => {
   let result = text;
@@ -22,35 +17,41 @@ const removeSymbol = (text: string, prefix: string, suffix: string): string => {
   return result;
 };
 
-export const useInsertMarkdownElements = (view?: EditorView): InsertMarkdownElements => {
-
-  return useCallback((prefix, suffix) => {
-    if (view == null) return;
-
-    const from = view?.state.selection.main.from;
-    const to = view?.state.selection.main.to;
-
-    const selectedText = view?.state.sliceDoc(from, to);
-    const cursorPos = view?.state.selection.main.head;
-
-    let insertText: string;
-
-    if (selectedText?.startsWith(prefix) && selectedText?.endsWith(suffix)) {
-      insertText = removeSymbol(selectedText, prefix, suffix);
-    }
-    else {
-      insertText = prefix + selectedText + suffix;
-    }
-
-    const selection = (from === to) ? { anchor: from + prefix.length } : { anchor: from, head: from + insertText.length };
-
-    const transaction = view?.state.replaceSelection(insertText);
-
-    if (transaction == null || cursorPos == null) {
-      return;
-    }
-    view?.dispatch(transaction);
-    view?.dispatch({ selection });
-    view?.focus();
-  }, [view]);
+export const useInsertMarkdownElements = (
+  view?: EditorView,
+): InsertMarkdownElements => {
+  return useCallback(
+    (prefix, suffix) => {
+      if (view == null) return;
+
+      const from = view?.state.selection.main.from;
+      const to = view?.state.selection.main.to;
+
+      const selectedText = view?.state.sliceDoc(from, to);
+      const cursorPos = view?.state.selection.main.head;
+
+      let insertText: string;
+
+      if (selectedText?.startsWith(prefix) && selectedText?.endsWith(suffix)) {
+        insertText = removeSymbol(selectedText, prefix, suffix);
+      } else {
+        insertText = prefix + selectedText + suffix;
+      }
+
+      const selection =
+        from === to
+          ? { anchor: from + prefix.length }
+          : { anchor: from, head: from + insertText.length };
+
+      const transaction = view?.state.replaceSelection(insertText);
+
+      if (transaction == null || cursorPos == null) {
+        return;
+      }
+      view?.dispatch(transaction);
+      view?.dispatch({ selection });
+      view?.focus();
+    },
+    [view],
+  );
 };

+ 104 - 91
packages/editor/src/client/services/use-codemirror-editor/utils/insert-prefix.ts

@@ -1,14 +1,17 @@
 import { useCallback } from 'react';
-
 import type { ChangeSpec, Line, Text } from '@codemirror/state';
 import type { EditorView } from '@codemirror/view';
 
-export type InsertPrefix = (prefix: string, noSpaceIfPrefixExists?: boolean) => void;
+export type InsertPrefix = (
+  prefix: string,
+  noSpaceIfPrefixExists?: boolean,
+) => void;
 
 // https:// regex101.com/r/5ILXUX/1
 const LEADING_SPACES = /^\s*/;
 // https://regex101.com/r/ScAXzy/1
-const createPrefixPattern = (prefix: string) => new RegExp(`^\\s*(${prefix}+)\\s*`);
+const createPrefixPattern = (prefix: string) =>
+  new RegExp(`^\\s*(${prefix}+)\\s*`);
 
 const removePrefix = (text: string, prefix: string): string => {
   if (text.startsWith(prefix)) {
@@ -27,7 +30,12 @@ const allLinesEmpty = (doc: Text, startLine: Line, endLine: Line) => {
   return true;
 };
 
-const allLinesHavePrefix = (doc: Text, startLine: Line, endLine: Line, prefix: string) => {
+const allLinesHavePrefix = (
+  doc: Text,
+  startLine: Line,
+  endLine: Line,
+  prefix: string,
+) => {
   let hasNonEmptyLine = false;
 
   for (let i = startLine.number; i <= endLine.number; i++) {
@@ -46,77 +54,97 @@ const allLinesHavePrefix = (doc: Text, startLine: Line, endLine: Line, prefix: s
 };
 
 export const useInsertPrefix = (view?: EditorView): InsertPrefix => {
-  return useCallback((prefix: string, noSpaceIfPrefixExists = false) => {
-    if (view == null) {
-      return;
-    }
-
-    const { from, to } = view.state.selection.main;
-    const doc = view.state.doc;
-    const startLine = doc.lineAt(from);
-    const endLine = doc.lineAt(to);
+  return useCallback(
+    (prefix: string, noSpaceIfPrefixExists = false) => {
+      if (view == null) {
+        return;
+      }
 
-    const changes: ChangeSpec[] = [];
-    let totalLengthChange = 0;
+      const { from, to } = view.state.selection.main;
+      const doc = view.state.doc;
+      const startLine = doc.lineAt(from);
+      const endLine = doc.lineAt(to);
 
-    const isPrefixRemoval = allLinesHavePrefix(doc, startLine, endLine, prefix);
+      const changes: ChangeSpec[] = [];
+      let totalLengthChange = 0;
 
-    if (allLinesEmpty(doc, startLine, endLine)) {
-      for (let i = startLine.number; i <= endLine.number; i++) {
-        const line = view.state.doc.line(i);
-        const leadingSpaces = line.text.match(LEADING_SPACES)?.[0] || '';
-        const insertText = `${leadingSpaces}${prefix} `;
+      const isPrefixRemoval = allLinesHavePrefix(
+        doc,
+        startLine,
+        endLine,
+        prefix,
+      );
 
-        const change = {
-          from: line.from,
-          to: line.to,
-          insert: insertText,
-        };
+      if (allLinesEmpty(doc, startLine, endLine)) {
+        for (let i = startLine.number; i <= endLine.number; i++) {
+          const line = view.state.doc.line(i);
+          const leadingSpaces = line.text.match(LEADING_SPACES)?.[0] || '';
+          const insertText = `${leadingSpaces}${prefix} `;
 
-        changes.push(change);
-        totalLengthChange += insertText.length - (line.to - line.from);
-      }
-
-      view.dispatch({ changes });
-      view.dispatch({
-        selection: {
-          anchor: from + totalLengthChange,
-          head: to + totalLengthChange,
-        },
-      });
-      view.focus();
-      return;
-    }
+          const change = {
+            from: line.from,
+            to: line.to,
+            insert: insertText,
+          };
 
-    for (let i = startLine.number; i <= endLine.number; i++) {
-      const line = view.state.doc.line(i);
-      const trimmedLine = line.text.trim();
-      const leadingSpaces = line.text.match(LEADING_SPACES)?.[0] || '';
-      const contentTrimmed = line.text.trimStart();
+          changes.push(change);
+          totalLengthChange += insertText.length - (line.to - line.from);
+        }
 
-      if (trimmedLine === '') {
-        continue;
+        view.dispatch({ changes });
+        view.dispatch({
+          selection: {
+            anchor: from + totalLengthChange,
+            head: to + totalLengthChange,
+          },
+        });
+        view.focus();
+        return;
       }
 
-      let newLine = '';
-      let lengthChange = 0;
+      for (let i = startLine.number; i <= endLine.number; i++) {
+        const line = view.state.doc.line(i);
+        const trimmedLine = line.text.trim();
+        const leadingSpaces = line.text.match(LEADING_SPACES)?.[0] || '';
+        const contentTrimmed = line.text.trimStart();
 
-      if (isPrefixRemoval) {
-        const prefixPattern = createPrefixPattern(prefix);
-        const contentStartMatch = line.text.match(prefixPattern);
+        if (trimmedLine === '') {
+          continue;
+        }
 
-        if (contentStartMatch) {
-          if (noSpaceIfPrefixExists) {
-            const existingPrefixes = contentStartMatch[1];
-            const indentLevel = Math.floor(leadingSpaces.length / 2) * 2;
-            const newIndent = ' '.repeat(indentLevel);
-            newLine = `${newIndent}${existingPrefixes}${prefix} ${line.text.slice(contentStartMatch[0].length)}`;
+        let newLine = '';
+        let lengthChange = 0;
+
+        if (isPrefixRemoval) {
+          const prefixPattern = createPrefixPattern(prefix);
+          const contentStartMatch = line.text.match(prefixPattern);
+
+          if (contentStartMatch) {
+            if (noSpaceIfPrefixExists) {
+              const existingPrefixes = contentStartMatch[1];
+              const indentLevel = Math.floor(leadingSpaces.length / 2) * 2;
+              const newIndent = ' '.repeat(indentLevel);
+              newLine = `${newIndent}${existingPrefixes}${prefix} ${line.text.slice(contentStartMatch[0].length)}`;
+            } else {
+              const indentLevel = Math.floor(leadingSpaces.length / 2) * 2;
+              const newIndent = ' '.repeat(indentLevel);
+              const prefixRemovedText = removePrefix(contentTrimmed, prefix);
+              newLine = `${newIndent}${prefixRemovedText}`;
+            }
+
+            lengthChange = newLine.length - (line.to - line.from);
+
+            changes.push({
+              from: line.from,
+              to: line.to,
+              insert: newLine,
+            });
           }
-          else {
-            const indentLevel = Math.floor(leadingSpaces.length / 2) * 2;
-            const newIndent = ' '.repeat(indentLevel);
-            const prefixRemovedText = removePrefix(contentTrimmed, prefix);
-            newLine = `${newIndent}${prefixRemovedText}`;
+        } else {
+          if (noSpaceIfPrefixExists && contentTrimmed.startsWith(prefix)) {
+            newLine = `${leadingSpaces}${prefix}${contentTrimmed}`;
+          } else {
+            newLine = `${leadingSpaces}${prefix} ${contentTrimmed}`;
           }
 
           lengthChange = newLine.length - (line.to - line.from);
@@ -127,37 +155,22 @@ export const useInsertPrefix = (view?: EditorView): InsertPrefix => {
             insert: newLine,
           });
         }
+
+        totalLengthChange += lengthChange;
       }
-      else {
-        if (noSpaceIfPrefixExists && contentTrimmed.startsWith(prefix)) {
-          newLine = `${leadingSpaces}${prefix}${contentTrimmed}`;
-        }
-        else {
-          newLine = `${leadingSpaces}${prefix} ${contentTrimmed}`;
-        }
 
-        lengthChange = newLine.length - (line.to - line.from);
+      if (changes.length > 0) {
+        view.dispatch({ changes });
 
-        changes.push({
-          from: line.from,
-          to: line.to,
-          insert: newLine,
+        view.dispatch({
+          selection: {
+            anchor: from,
+            head: to + totalLengthChange,
+          },
         });
+        view.focus();
       }
-
-      totalLengthChange += lengthChange;
-    }
-
-    if (changes.length > 0) {
-      view.dispatch({ changes });
-
-      view.dispatch({
-        selection: {
-          anchor: from,
-          head: to + totalLengthChange,
-        },
-      });
-      view.focus();
-    }
-  }, [view]);
+    },
+    [view],
+  );
 };

+ 17 - 17
packages/editor/src/client/services/use-codemirror-editor/utils/insert-text.ts

@@ -1,24 +1,24 @@
 import { useCallback } from 'react';
-
 import type { EditorView } from '@codemirror/view';
 
 export type InsertText = (text: string) => void;
 
 export const useInsertText = (view?: EditorView): InsertText => {
-
-  return useCallback((text) => {
-    if (view == null) {
-      return;
-    }
-    const insertPos = view.state.selection.main.head;
-    view.dispatch({
-      changes: {
-        from: insertPos,
-        to: insertPos,
-        insert: text,
-      },
-      selection: { anchor: insertPos },
-    });
-  }, [view]);
-
+  return useCallback(
+    (text) => {
+      if (view == null) {
+        return;
+      }
+      const insertPos = view.state.selection.main.head;
+      view.dispatch({
+        changes: {
+          from: insertPos,
+          to: insertPos,
+          insert: text,
+        },
+        selection: { anchor: insertPos },
+      });
+    },
+    [view],
+  );
 };

+ 6 - 8
packages/editor/src/client/services/use-codemirror-editor/utils/replace-text.ts

@@ -1,15 +1,13 @@
 import { useCallback } from 'react';
-
 import type { EditorView } from '@codemirror/view';
 
 export type ReplaceText = (text: string) => void;
 
 export const useReplaceText = (view?: EditorView): ReplaceText => {
-
-  return useCallback((text) => {
-    view?.dispatch(
-      view?.state.replaceSelection(text),
-    );
-  }, [view]);
-
+  return useCallback(
+    (text) => {
+      view?.dispatch(view?.state.replaceSelection(text));
+    },
+    [view],
+  );
 };

+ 34 - 34
packages/editor/src/client/services/use-codemirror-editor/utils/set-caret-line.ts

@@ -1,8 +1,7 @@
 import { useCallback } from 'react';
-
 import { Compartment, StateEffect } from '@codemirror/state';
-import { EditorView } from '@codemirror/view';
 import type { ViewUpdate } from '@codemirror/view';
+import { EditorView } from '@codemirror/view';
 
 export type SetCaretLine = (lineNumber?: number, schedule?: boolean) => void;
 
@@ -21,35 +20,38 @@ const setCaretLine = (view?: EditorView, lineNumber?: number): void => {
         head: posOfLineEnd,
       },
       scrollIntoView: true,
-      effects: EditorView.scrollIntoView(posOfLineEnd, { x: 'end', y: 'center' }),
+      effects: EditorView.scrollIntoView(posOfLineEnd, {
+        x: 'end',
+        y: 'center',
+      }),
     });
     // focus
     view?.focus();
-  }
-  catch (_: unknown) {
+  } catch (_: unknown) {
     // if posOfLineEnd is not found.
   }
-
 };
 
-const setCaretLineScheduleForYjs = (view?: EditorView, lineNumber?: number): void => {
-
+const setCaretLineScheduleForYjs = (
+  view?: EditorView,
+  lineNumber?: number,
+): void => {
   const compartment = new Compartment();
 
-  const setCaretLineOnceExtension = EditorView.updateListener.of((v: ViewUpdate) => {
-
-    // TODO: use ySyncAnnotation for if statement and remove "currentPageYjsData?.hasRevisionBodyDiff === false" in Header.tsx
-    // Ref: https://github.com/yjs/y-codemirror.next/pull/30
-    if (v.docChanged && v.changes.desc.length === 0) {
-
-      setCaretLine(view, lineNumber);
-
-      // setCaretLineOnceExtension, which setCaretLineScheduleForYjs added, will remove itself from view.
-      view?.dispatch({
-        effects: compartment.reconfigure([]),
-      });
-    }
-  });
+  const setCaretLineOnceExtension = EditorView.updateListener.of(
+    (v: ViewUpdate) => {
+      // TODO: use ySyncAnnotation for if statement and remove "currentPageYjsData?.hasRevisionBodyDiff === false" in Header.tsx
+      // Ref: https://github.com/yjs/y-codemirror.next/pull/30
+      if (v.docChanged && v.changes.desc.length === 0) {
+        setCaretLine(view, lineNumber);
+
+        // setCaretLineOnceExtension, which setCaretLineScheduleForYjs added, will remove itself from view.
+        view?.dispatch({
+          effects: compartment.reconfigure([]),
+        });
+      }
+    },
+  );
 
   view?.dispatch({
     effects: StateEffect.appendConfig.of(
@@ -59,16 +61,14 @@ const setCaretLineScheduleForYjs = (view?: EditorView, lineNumber?: number): voi
 };
 
 export const useSetCaretLine = (view?: EditorView): SetCaretLine => {
-
-  return useCallback((lineNumber?: number, schedule?: boolean) => {
-
-    if (schedule) {
-      setCaretLineScheduleForYjs(view, lineNumber);
-    }
-    else {
-      setCaretLine(view, lineNumber);
-    }
-
-  }, [view]);
-
+  return useCallback(
+    (lineNumber?: number, schedule?: boolean) => {
+      if (schedule) {
+        setCaretLineScheduleForYjs(view, lineNumber);
+      } else {
+        setCaretLine(view, lineNumber);
+      }
+    },
+    [view],
+  );
 };

+ 16 - 12
packages/editor/src/client/stores/codemirror-editor.ts

@@ -1,8 +1,10 @@
 import { useMemo, useRef } from 'react';
-
 import { useSWRStatic } from '@growi/core/dist/swr';
 import { deepEquals } from '@growi/core/dist/utils';
-import type { ReactCodeMirrorProps, UseCodeMirror } from '@uiw/react-codemirror';
+import type {
+  ReactCodeMirrorProps,
+  UseCodeMirror,
+} from '@uiw/react-codemirror';
 import type { SWRResponse } from 'swr';
 import deepmerge from 'ts-deepmerge';
 
@@ -15,24 +17,26 @@ const isValid = (u: UseCodeMirrorEditor) => {
 };
 
 export const useCodeMirrorEditorIsolated = (
-    key: string | null, container?: HTMLDivElement | null, props?: ReactCodeMirrorProps,
+  key: string | null,
+  container?: HTMLDivElement | null,
+  props?: ReactCodeMirrorProps,
 ): SWRResponse<UseCodeMirrorEditor> => {
-
   const ref = useRef<UseCodeMirrorEditor | null>(null);
   const currentData = ref.current;
 
   const swrKey = key != null ? `codeMirrorEditor_${key}` : null;
-  const mergedProps = useMemo<UseCodeMirror>(() => deepmerge(
-    { container },
-    props ?? {},
-  ), [container, props]);
+  const mergedProps = useMemo<UseCodeMirror>(
+    () => deepmerge({ container }, props ?? {}),
+    [container, props],
+  );
 
   const newData = useCodeMirrorEditor(mergedProps);
 
-  const shouldUpdate = swrKey != null && container != null && (
-    currentData == null
-    || (isValid(newData) && !isDeepEquals(currentData, newData))
-  );
+  const shouldUpdate =
+    swrKey != null &&
+    container != null &&
+    (currentData == null ||
+      (isValid(newData) && !isDeepEquals(currentData, newData)));
 
   if (shouldUpdate) {
     ref.current = newData;

+ 49 - 38
packages/editor/src/client/stores/use-collaborative-editor-mode.ts

@@ -1,5 +1,4 @@
 import { useEffect, useState } from 'react';
-
 import { keymap } from '@codemirror/view';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import { yCollab, yUndoManagerKeymap } from 'y-codemirror.next';
@@ -9,34 +8,30 @@ import * as Y from 'yjs';
 import { userColor } from '../../consts';
 import type { EditingClient } from '../../interfaces';
 import type { UseCodeMirrorEditor } from '../services';
-
 import { useSecondaryYdocs } from './use-secondary-ydocs';
 
-
 type Configuration = {
-  user?: IUserHasId,
-  pageId?: string,
-  reviewMode?: boolean,
-  onEditorsUpdated?: (clientList: EditingClient[]) => void,
-}
+  user?: IUserHasId;
+  pageId?: string;
+  reviewMode?: boolean;
+  onEditorsUpdated?: (clientList: EditingClient[]) => void;
+};
 
 export const useCollaborativeEditorMode = (
-    isEnabled: boolean,
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    configuration?: Configuration,
+  isEnabled: boolean,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  configuration?: Configuration,
 ): void => {
-  const {
-    user, pageId, onEditorsUpdated, reviewMode,
-  } = configuration ?? {};
+  const { user, pageId, onEditorsUpdated, reviewMode } = configuration ?? {};
 
-  const { primaryDoc, activeDoc } = useSecondaryYdocs(isEnabled, {
-    pageId,
-    useSecondary: reviewMode,
-  }) ?? {};
+  const { primaryDoc, activeDoc } =
+    useSecondaryYdocs(isEnabled, {
+      pageId,
+      useSecondary: reviewMode,
+    }) ?? {};
 
   const [provider, setProvider] = useState<SocketIOProvider>();
 
-
   // reset editors
   useEffect(() => {
     if (!isEnabled) return;
@@ -45,25 +40,23 @@ export const useCollaborativeEditorMode = (
 
   // Setup provider
   useEffect(() => {
-
     let _provider: SocketIOProvider | undefined;
     let providerSyncHandler: (isSync: boolean) => void;
-    let updateAwarenessHandler: (update: { added: number[]; updated: number[]; removed: number[]; }) => void;
+    let updateAwarenessHandler: (update: {
+      added: number[];
+      updated: number[];
+      removed: number[];
+    }) => void;
 
     setProvider(() => {
       if (!isEnabled || pageId == null || primaryDoc == null) {
         return undefined;
       }
 
-      _provider = new SocketIOProvider(
-        '/',
-        pageId,
-        primaryDoc,
-        {
-          autoConnect: true,
-          resyncInterval: 3000,
-        },
-      );
+      _provider = new SocketIOProvider('/', pageId, primaryDoc, {
+        autoConnect: true,
+        resyncInterval: 3000,
+      });
 
       const userLocalState: EditingClient = {
         clientId: primaryDoc.clientID,
@@ -80,7 +73,10 @@ export const useCollaborativeEditorMode = (
 
       providerSyncHandler = (isSync: boolean) => {
         if (isSync && onEditorsUpdated != null) {
-          const clientList: EditingClient[] = Array.from(awareness.getStates().values(), value => value.editors);
+          const clientList: EditingClient[] = Array.from(
+            awareness.getStates().values(),
+            (value) => value.editors,
+          );
           if (Array.isArray(clientList)) {
             onEditorsUpdated(clientList);
           }
@@ -90,13 +86,20 @@ export const useCollaborativeEditorMode = (
       _provider.on('sync', providerSyncHandler);
 
       // update args type see: SocketIOProvider.Awareness.awarenessUpdate
-      updateAwarenessHandler = (update: { added: number[]; updated: number[]; removed: number[]; }) => {
+      updateAwarenessHandler = (update: {
+        added: number[];
+        updated: number[];
+        removed: number[];
+      }) => {
         // remove the states of disconnected clients
-        update.removed.forEach(clientId => awareness.states.delete(clientId));
+        update.removed.forEach((clientId) => awareness.states.delete(clientId));
 
         // update editor list
         if (onEditorsUpdated != null) {
-          const clientList: EditingClient[] = Array.from(awareness.states.values(), value => value.editors);
+          const clientList: EditingClient[] = Array.from(
+            awareness.states.values(),
+            (value) => value.editors,
+          );
           if (Array.isArray(clientList)) {
             onEditorsUpdated(clientList);
           }
@@ -119,7 +122,13 @@ export const useCollaborativeEditorMode = (
 
   // Setup Ydoc Extensions
   useEffect(() => {
-    if (!isEnabled || !primaryDoc || !activeDoc || !provider || !codeMirrorEditor) {
+    if (
+      !isEnabled ||
+      !primaryDoc ||
+      !activeDoc ||
+      !provider ||
+      !codeMirrorEditor
+    ) {
       return;
     }
 
@@ -135,11 +144,13 @@ export const useCollaborativeEditorMode = (
       yCollab(activeText, provider.awareness, { undoManager }),
     ];
 
-    const cleanupFunctions = extensions.map(ext => codeMirrorEditor.appendExtensions([ext]));
+    const cleanupFunctions = extensions.map((ext) =>
+      codeMirrorEditor.appendExtensions([ext]),
+    );
 
     return () => {
-      cleanupFunctions.forEach(cleanup => cleanup?.());
+      cleanupFunctions.forEach((cleanup) => cleanup?.());
       codeMirrorEditor.initDoc('');
     };
-  }, [isEnabled, codeMirrorEditor, provider, primaryDoc, activeDoc, reviewMode]);
+  }, [isEnabled, codeMirrorEditor, provider, primaryDoc, activeDoc]);
 };

+ 18 - 11
packages/editor/src/client/stores/use-default-extensions.ts

@@ -1,20 +1,23 @@
-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';
+  defaultKeymap,
+  deleteCharBackward,
+  indentWithTab,
+} from '@codemirror/commands';
+import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
 import {
-  Prec, type Extension,
-} from '@codemirror/state';
+  defaultHighlightStyle,
+  HighlightStyle,
+  syntaxHighlighting,
+} from '@codemirror/language';
+import { languages } from '@codemirror/language-data';
+import { type Extension, Prec } from '@codemirror/state';
 import type { KeyBinding } from '@codemirror/view';
-import { keymap, EditorView } from '@codemirror/view';
+import { EditorView, keymap } from '@codemirror/view';
 import { tags } from '@lezer/highlight';
 
 import type { UseCodeMirrorEditor } from '../services';
 import { emojiAutocompletionSettings } from '../services-internal';
 
-
 // set new markdownKeymap instead of default one
 // https://github.com/codemirror/lang-markdown/blob/main/src/index.ts#L17
 const markdownKeymap: KeyBinding[] = [
@@ -32,7 +35,11 @@ const markdownHighlighting = HighlightStyle.define([
 
 const defaultExtensions: Extension[] = [
   EditorView.lineWrapping,
-  markdown({ base: markdownLanguage, codeLanguages: languages, addKeymap: false }),
+  markdown({
+    base: markdownLanguage,
+    codeLanguages: languages,
+    addKeymap: false,
+  }),
   keymap.of(markdownKeymap),
   keymap.of([indentWithTab]),
   Prec.lowest(keymap.of(defaultKeymap)),
@@ -42,7 +49,7 @@ const defaultExtensions: Extension[] = [
 ];
 
 export const useDefaultExtensions = (
-    codeMirrorEditor?: UseCodeMirrorEditor,
+  codeMirrorEditor?: UseCodeMirrorEditor,
 ): void => {
   codeMirrorEditor?.appendExtensions([defaultExtensions]);
 };

+ 20 - 14
packages/editor/src/client/stores/use-drawio.ts

@@ -1,32 +1,38 @@
 import { useCallback } from 'react';
-
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type { SWRResponse } from 'swr';
 
 type DrawioModalStatus = {
-  isOpened: boolean,
-  editorKey: string | undefined,
-}
+  isOpened: boolean;
+  editorKey: string | undefined;
+};
 
 type DrawioModalStatusUtils = {
-  open(
-    editorKey: string,
-  ): void,
-  close(): void,
-}
+  open(editorKey: string): void;
+  close(): void;
+};
 
-export const useDrawioModalForEditor = (status?: DrawioModalStatus): SWRResponse<DrawioModalStatus, Error> & DrawioModalStatusUtils => {
+export const useDrawioModalForEditor = (
+  status?: DrawioModalStatus,
+): SWRResponse<DrawioModalStatus, Error> & DrawioModalStatusUtils => {
   const initialData: DrawioModalStatus = {
     isOpened: false,
     editorKey: undefined,
   };
-  const swrResponse = useSWRStatic<DrawioModalStatus, Error>('drawioModalStatusForEditor', status, { fallbackData: initialData });
+  const swrResponse = useSWRStatic<DrawioModalStatus, Error>(
+    'drawioModalStatusForEditor',
+    status,
+    { fallbackData: initialData },
+  );
 
   const { mutate } = swrResponse;
 
-  const open = useCallback((editorKey: string | undefined): void => {
-    mutate({ isOpened: true, editorKey });
-  }, [mutate]);
+  const open = useCallback(
+    (editorKey: string | undefined): void => {
+      mutate({ isOpened: true, editorKey });
+    },
+    [mutate],
+  );
 
   const close = useCallback((): void => {
     mutate({ isOpened: false, editorKey: undefined });

+ 51 - 37
packages/editor/src/client/stores/use-editor-settings.ts

@@ -1,65 +1,73 @@
-import { useEffect, useCallback, useState } from 'react';
-
+import { useCallback, useEffect, useState } from 'react';
 import type { Extension } from '@codemirror/state';
 import { Prec } from '@codemirror/state';
 import {
-  keymap, type Command, highlightActiveLine, highlightActiveLineGutter,
+  type Command,
+  highlightActiveLine,
+  highlightActiveLineGutter,
+  keymap,
 } from '@codemirror/view';
 
-import {
-  type EditorSettings, type KeyMapMode, type EditorTheme,
-} from '../../consts';
+import type { EditorSettings, EditorTheme, KeyMapMode } from '../../consts';
 import type { UseCodeMirrorEditor } from '../services';
 import {
-  getEditorTheme, getKeymap, insertNewlineContinueMarkup, insertNewRowToMarkdownTable, isInTable,
+  getEditorTheme,
+  getKeymap,
+  insertNewlineContinueMarkup,
+  insertNewRowToMarkdownTable,
+  isInTable,
 } from '../services-internal';
-
 import { useEditorShortcuts } from './use-editor-shortcuts';
 
 const useStyleActiveLine = (
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    styleActiveLine?: boolean,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  styleActiveLine?: boolean,
 ): void => {
   useEffect(() => {
     if (styleActiveLine == null) {
       return;
     }
-    const extensions = styleActiveLine ? [[highlightActiveLine(), highlightActiveLineGutter()]] : [[]];
+    const extensions = styleActiveLine
+      ? [[highlightActiveLine(), highlightActiveLineGutter()]]
+      : [[]];
     const cleanupFunction = codeMirrorEditor?.appendExtensions?.(extensions);
     return cleanupFunction;
   }, [codeMirrorEditor, styleActiveLine]);
 };
 
 const useEnterKeyHandler = (
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    autoFormatMarkdownTable?: boolean,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  autoFormatMarkdownTable?: boolean,
 ): void => {
-  const onPressEnter: Command = useCallback((editor) => {
-    if (isInTable(editor) && autoFormatMarkdownTable) {
-      insertNewRowToMarkdownTable(editor);
+  const onPressEnter: Command = useCallback(
+    (editor) => {
+      if (isInTable(editor) && autoFormatMarkdownTable) {
+        insertNewRowToMarkdownTable(editor);
+        return true;
+      }
+      insertNewlineContinueMarkup(editor);
       return true;
-    }
-    insertNewlineContinueMarkup(editor);
-    return true;
-  }, [autoFormatMarkdownTable]);
+    },
+    [autoFormatMarkdownTable],
+  );
 
   useEffect(() => {
-    const extension = keymap.of([
-      { key: 'Enter', run: onPressEnter },
-    ]);
+    const extension = keymap.of([{ key: 'Enter', run: onPressEnter }]);
     const cleanupFunction = codeMirrorEditor?.appendExtensions?.(extension);
     return cleanupFunction;
   }, [codeMirrorEditor, onPressEnter]);
 };
 
 const useThemeExtension = (
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    theme?: EditorTheme,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  theme?: EditorTheme,
 ): void => {
-  const [themeExtension, setThemeExtension] = useState<Extension | undefined>(undefined);
+  const [themeExtension, setThemeExtension] = useState<Extension | undefined>(
+    undefined,
+  );
 
   useEffect(() => {
-    const settingTheme = async(name?: EditorTheme) => {
+    const settingTheme = async (name?: EditorTheme) => {
       setThemeExtension(await getEditorTheme(name));
     };
     settingTheme(theme);
@@ -69,20 +77,24 @@ const useThemeExtension = (
     if (themeExtension == null) {
       return;
     }
-    const cleanupFunction = codeMirrorEditor?.appendExtensions(Prec.high(themeExtension));
+    const cleanupFunction = codeMirrorEditor?.appendExtensions(
+      Prec.high(themeExtension),
+    );
     return cleanupFunction;
   }, [codeMirrorEditor, themeExtension]);
 };
 
 const useKeymapExtension = (
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    keymapMode?: KeyMapMode,
-    onSave?: () => void,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  keymapMode?: KeyMapMode,
+  onSave?: () => void,
 ): void => {
-  const [keymapExtension, setKeymapExtension] = useState<Extension | undefined>(undefined);
+  const [keymapExtension, setKeymapExtension] = useState<Extension | undefined>(
+    undefined,
+  );
 
   useEffect(() => {
-    const settingKeyMap = async(name?: KeyMapMode) => {
+    const settingKeyMap = async (name?: KeyMapMode) => {
       setKeymapExtension(await getKeymap(name, onSave));
     };
     settingKeyMap(keymapMode);
@@ -92,15 +104,17 @@ const useKeymapExtension = (
     if (keymapExtension == null) {
       return;
     }
-    const cleanupFunction = codeMirrorEditor?.appendExtensions(Prec.low(keymapExtension));
+    const cleanupFunction = codeMirrorEditor?.appendExtensions(
+      Prec.low(keymapExtension),
+    );
     return cleanupFunction;
   }, [codeMirrorEditor, keymapExtension]);
 };
 
 export const useEditorSettings = (
-    codeMirrorEditor?: UseCodeMirrorEditor,
-    editorSettings?: EditorSettings,
-    onSave?: () => void,
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  editorSettings?: EditorSettings,
+  onSave?: () => void,
 ): void => {
   useEditorShortcuts(codeMirrorEditor, editorSettings?.keymapMode);
   useStyleActiveLine(codeMirrorEditor, editorSettings?.styleActiveLine);

+ 22 - 18
packages/editor/src/client/stores/use-editor-shortcuts.ts

@@ -1,9 +1,7 @@
 import { useEffect } from 'react';
-
 import type { EditorView } from '@codemirror/view';
-import {
-  keymap, type KeyBinding,
-} from '@codemirror/view';
+import { type KeyBinding, keymap } from '@codemirror/view';
+import type { KeyMapMode } from 'src/consts';
 
 import type { UseCodeMirrorEditor } from '../services';
 import { useAddMultiCursorKeyBindings } from '../services/use-codemirror-editor/utils/editor-shortcuts/add-multi-cursor';
@@ -17,14 +15,17 @@ import { useMakeCodeBlockExtension } from '../services/use-codemirror-editor/uti
 import { useMakeTextItalicKeyBinding } from '../services/use-codemirror-editor/utils/editor-shortcuts/make-text-italic';
 import { useMakeTextStrikethroughKeyBinding } from '../services/use-codemirror-editor/utils/editor-shortcuts/make-text-strikethrough';
 
-
-import type { KeyMapMode } from 'src/consts';
-
-const useKeyBindings = (view?: EditorView, keymapModeName?: KeyMapMode): KeyBinding[] => {
-
-  const makeTextBoldKeyBinding = useMakeTextBoldKeyBinding(view, keymapModeName);
+const useKeyBindings = (
+  view?: EditorView,
+  keymapModeName?: KeyMapMode,
+): KeyBinding[] => {
+  const makeTextBoldKeyBinding = useMakeTextBoldKeyBinding(
+    view,
+    keymapModeName,
+  );
   const makeTextItalicKeyBinding = useMakeTextItalicKeyBinding(view);
-  const makeTextStrikethroughKeyBinding = useMakeTextStrikethroughKeyBinding(view);
+  const makeTextStrikethroughKeyBinding =
+    useMakeTextStrikethroughKeyBinding(view);
   const makeTextCodeCommand = useMakeTextCodeKeyBinding(view);
   const insertNumberedKeyBinding = useInsertNumberedKeyBinding(view);
   const insertBulletListKeyBinding = useInsertBulletListKeyBinding(view);
@@ -47,29 +48,32 @@ const useKeyBindings = (view?: EditorView, keymapModeName?: KeyMapMode): KeyBind
   return keyBindings;
 };
 
-export const useEditorShortcuts = (codeMirrorEditor?: UseCodeMirrorEditor, keymapModeName?: KeyMapMode): void => {
-
+export const useEditorShortcuts = (
+  codeMirrorEditor?: UseCodeMirrorEditor,
+  keymapModeName?: KeyMapMode,
+): void => {
   const keyBindings = useKeyBindings(codeMirrorEditor?.view, keymapModeName);
 
   // Since key combinations of 4 or more keys cannot be implemented with CodeMirror's keybinding, they are implemented as Extensions.
   const makeCodeBlockExtension = useMakeCodeBlockExtension();
 
   useEffect(() => {
-    const cleanupFunction = codeMirrorEditor?.appendExtensions?.([makeCodeBlockExtension]);
+    const cleanupFunction = codeMirrorEditor?.appendExtensions?.([
+      makeCodeBlockExtension,
+    ]);
     return cleanupFunction;
   }, [codeMirrorEditor, makeCodeBlockExtension]);
 
   useEffect(() => {
-
     if (keyBindings == null) {
       return;
     }
 
     const keyboardShortcutsExtension = keymap.of(keyBindings);
 
-    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(keyboardShortcutsExtension);
+    const cleanupFunction = codeMirrorEditor?.appendExtensions?.(
+      keyboardShortcutsExtension,
+    );
     return cleanupFunction;
-
   }, [codeMirrorEditor, keyBindings]);
-
 };

+ 26 - 17
packages/editor/src/client/stores/use-handsontable.ts

@@ -1,39 +1,48 @@
 import { useCallback } from 'react';
-
 import type { EditorView } from '@codemirror/view';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type { SWRResponse } from 'swr';
 
 type HandsontableModalStatus = {
-  isOpened: boolean,
-  editor?: EditorView,
-}
+  isOpened: boolean;
+  editor?: EditorView;
+};
 
 type HandsontableModalStatusUtils = {
-  open(
-    editor?: EditorView,
-  ): void
-  close(): void
-}
+  open(editor?: EditorView): void;
+  close(): void;
+};
 
-export const useHandsontableModalForEditor = (status?: HandsontableModalStatus): SWRResponse<HandsontableModalStatus, Error> & HandsontableModalStatusUtils => {
+export const useHandsontableModalForEditor = (
+  status?: HandsontableModalStatus,
+): SWRResponse<HandsontableModalStatus, Error> &
+  HandsontableModalStatusUtils => {
   const initialData: HandsontableModalStatus = {
     isOpened: false,
     editor: undefined,
   };
 
-  const swrResponse = useSWRStatic<HandsontableModalStatus, Error>('handsontableModalStatus', status, { fallbackData: initialData });
+  const swrResponse = useSWRStatic<HandsontableModalStatus, Error>(
+    'handsontableModalStatus',
+    status,
+    { fallbackData: initialData },
+  );
 
   const { mutate } = swrResponse;
 
-  const open = useCallback((editor?: EditorView): void => {
-    mutate({
-      isOpened: true, editor,
-    });
-  }, [mutate]);
+  const open = useCallback(
+    (editor?: EditorView): void => {
+      mutate({
+        isOpened: true,
+        editor,
+      });
+    },
+    [mutate],
+  );
   const close = useCallback((): void => {
     mutate({
-      isOpened: false, editor: undefined,
+      isOpened: false,
+      editor: undefined,
     });
   }, [mutate]);
 

+ 14 - 10
packages/editor/src/client/stores/use-link-edit-modal.ts

@@ -4,20 +4,24 @@ import type { SWRResponse } from 'swr';
 import type { Linker } from '../../models';
 
 type LinkEditModalStatus = {
-  isOpened: boolean,
-  defaultMarkdownLink?: Linker,
-  onSave?: (linkText: string) => void
-}
+  isOpened: boolean;
+  defaultMarkdownLink?: Linker;
+  onSave?: (linkText: string) => void;
+};
 
 type LinkEditModalUtils = {
-  open(defaultMarkdownLink: Linker, onSave: (linkText: string) => void): void,
-  close(): void,
-}
-
-export const useLinkEditModal = (): SWRResponse<LinkEditModalStatus, Error> & LinkEditModalUtils => {
+  open(defaultMarkdownLink: Linker, onSave: (linkText: string) => void): void;
+  close(): void;
+};
 
+export const useLinkEditModal = (): SWRResponse<LinkEditModalStatus, Error> &
+  LinkEditModalUtils => {
   const initialStatus: LinkEditModalStatus = { isOpened: false };
-  const swrResponse = useSWRStatic<LinkEditModalStatus, Error>('linkEditModal', undefined, { fallbackData: initialStatus });
+  const swrResponse = useSWRStatic<LinkEditModalStatus, Error>(
+    'linkEditModal',
+    undefined,
+    { fallbackData: initialStatus },
+  );
 
   return Object.assign(swrResponse, {
     open: (defaultMarkdownLink: Linker, onSave: (linkText: string) => void) => {

+ 15 - 9
packages/editor/src/client/stores/use-resolved-theme.ts

@@ -1,24 +1,30 @@
 import { useCallback } from 'react';
-
 import type { ColorScheme } from '@growi/core';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type { SWRResponse } from 'swr';
 import { mutate } from 'swr';
 
 type ResolvedThemeStatus = {
-  themeData: ColorScheme,
-}
+  themeData: ColorScheme;
+};
 
 type ResolvedThemeUtils = {
-  mutateResolvedThemeForEditor(resolvedTheme: ColorScheme): void
-}
+  mutateResolvedThemeForEditor(resolvedTheme: ColorScheme): void;
+};
 
-export const useResolvedThemeForEditor = (): SWRResponse<ResolvedThemeStatus, Error> & ResolvedThemeUtils => {
+export const useResolvedThemeForEditor = (): SWRResponse<
+  ResolvedThemeStatus,
+  Error
+> &
+  ResolvedThemeUtils => {
   const swrResponse = useSWRStatic<ResolvedThemeStatus, Error>('resolvedTheme');
 
-  const mutateResolvedThemeForEditor = useCallback((resolvedTheme: ColorScheme) => {
-    mutate('resolvedTheme', { themeData: resolvedTheme });
-  }, []);
+  const mutateResolvedThemeForEditor = useCallback(
+    (resolvedTheme: ColorScheme) => {
+      mutate('resolvedTheme', { themeData: resolvedTheme });
+    },
+    [],
+  );
 
   return {
     ...swrResponse,

+ 12 - 8
packages/editor/src/client/stores/use-secondary-ydocs.ts

@@ -1,24 +1,25 @@
 import { useEffect } from 'react';
-
 import useSWRImmutable from 'swr/immutable';
 import * as Y from 'yjs';
 
 type Configuration = {
   pageId?: string;
   useSecondary?: boolean;
-}
-
+};
 
 type StoredYDocs = {
   primaryDoc: Y.Doc;
   secondaryDoc: Y.Doc | undefined;
-}
+};
 
 type YDocsState = StoredYDocs & {
-  activeDoc: Y.Doc,
-}
+  activeDoc: Y.Doc;
+};
 
-export const useSecondaryYdocs = (isEnabled: boolean, configuration?: Configuration): YDocsState | null => {
+export const useSecondaryYdocs = (
+  isEnabled: boolean,
+  configuration?: Configuration,
+): YDocsState | null => {
   const { pageId, useSecondary = false } = configuration ?? {};
   const cacheKey = `swr-ydocs:${pageId}`;
 
@@ -56,7 +57,10 @@ export const useSecondaryYdocs = (isEnabled: boolean, configuration?: Configurat
     };
   }, [docs, isEnabled, useSecondary, mutate]);
 
-  if (docs?.primaryDoc == null || (useSecondary && docs?.secondaryDoc == null)) {
+  if (
+    docs?.primaryDoc == null ||
+    (useSecondary && docs?.secondaryDoc == null)
+  ) {
     return null;
   }
 

+ 14 - 10
packages/editor/src/client/stores/use-template-modal.ts

@@ -3,21 +3,25 @@ import type { SWRResponse } from 'swr';
 
 type TemplateSelectedCallback = (templateText: string) => void;
 type TemplateModalOptions = {
-  onSubmit?: TemplateSelectedCallback,
-}
+  onSubmit?: TemplateSelectedCallback;
+};
 export type TemplateModalStatus = TemplateModalOptions & {
-  isOpened: boolean,
-}
+  isOpened: boolean;
+};
 
 type TemplateModalUtils = {
-  open(opts: TemplateModalOptions): void,
-  close(): void,
-}
-
-export const useTemplateModal = (): SWRResponse<TemplateModalStatus, Error> & TemplateModalUtils => {
+  open(opts: TemplateModalOptions): void;
+  close(): void;
+};
 
+export const useTemplateModal = (): SWRResponse<TemplateModalStatus, Error> &
+  TemplateModalUtils => {
   const initialStatus: TemplateModalStatus = { isOpened: false };
-  const swrResponse = useSWRStatic<TemplateModalStatus, Error>('templateModal', undefined, { fallbackData: initialStatus });
+  const swrResponse = useSWRStatic<TemplateModalStatus, Error>(
+    'templateModal',
+    undefined,
+    { fallbackData: initialStatus },
+  );
 
   return Object.assign(swrResponse, {
     open: (opts: TemplateModalOptions) => {

+ 5 - 5
packages/editor/src/consts/editor-settings.ts

@@ -3,9 +3,9 @@ import type { KeyMapMode } from './keymaps';
 import type { PasteMode } from './paste-mode';
 
 export interface EditorSettings {
-  theme: undefined | EditorTheme,
-  keymapMode: undefined | KeyMapMode,
-  pasteMode: undefined | PasteMode,
-  styleActiveLine: boolean,
-  autoFormatMarkdownTable: boolean,
+  theme: undefined | EditorTheme;
+  keymapMode: undefined | KeyMapMode;
+  pasteMode: undefined | PasteMode;
+  styleActiveLine: boolean;
+  autoFormatMarkdownTable: boolean;
 }

+ 2 - 2
packages/editor/src/consts/editor-themes.ts

@@ -3,7 +3,7 @@ const EditorTheme = {
   eclipse: 'eclipse',
   basic: 'basic',
   ayu: 'ayu',
-  rosepine:  'rosepine',
+  rosepine: 'rosepine',
   defaultdark: 'defaultdark',
   material: 'material',
   nord: 'nord',
@@ -13,4 +13,4 @@ const EditorTheme = {
 
 export const DEFAULT_THEME = 'defaultlight';
 export const AllEditorTheme = Object.values(EditorTheme);
-export type EditorTheme = typeof EditorTheme[keyof typeof EditorTheme];
+export type EditorTheme = (typeof EditorTheme)[keyof typeof EditorTheme];

+ 2 - 1
packages/editor/src/consts/global-code-mirror-editor-key.ts

@@ -4,4 +4,5 @@ export const GlobalCodeMirrorEditorKey = {
   DIFF: 'diff',
   READONLY: 'readonly',
 } as const;
-export type GlobalCodeMirrorEditorKey = typeof GlobalCodeMirrorEditorKey[keyof typeof GlobalCodeMirrorEditorKey]
+export type GlobalCodeMirrorEditorKey =
+  (typeof GlobalCodeMirrorEditorKey)[keyof typeof GlobalCodeMirrorEditorKey];

+ 2 - 2
packages/editor/src/consts/index.ts

@@ -1,6 +1,6 @@
-export * from './global-code-mirror-editor-key';
-export * from './ydoc-awareness-user-color';
 export * from './editor-settings';
 export * from './editor-themes';
+export * from './global-code-mirror-editor-key';
 export * from './keymaps';
 export * from './paste-mode';
+export * from './ydoc-awareness-user-color';

+ 1 - 1
packages/editor/src/consts/keymaps.ts

@@ -7,4 +7,4 @@ const KeyMapMode = {
 
 export const DEFAULT_KEYMAP = 'default';
 export const AllKeyMap = Object.values(KeyMapMode);
-export type KeyMapMode = typeof KeyMapMode[keyof typeof KeyMapMode];
+export type KeyMapMode = (typeof KeyMapMode)[keyof typeof KeyMapMode];

+ 1 - 2
packages/editor/src/consts/paste-mode.ts

@@ -1,4 +1,3 @@
-
 export const PasteMode = {
   both: 'both',
   text: 'text',
@@ -7,4 +6,4 @@ export const PasteMode = {
 
 export const DEFAULT_PASTE_MODE = PasteMode.both;
 export const AllPasteMode = Object.values(PasteMode);
-export type PasteMode = typeof PasteMode[keyof typeof PasteMode];
+export type PasteMode = (typeof PasteMode)[keyof typeof PasteMode];

+ 5 - 1
packages/editor/src/interfaces/delta.ts

@@ -1,2 +1,6 @@
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type Delta = Array<{insert?:string|object|Array<any>, delete?:number, retain?:number}>;
+export type Delta = Array<{
+  insert?: string | object | Array<any>;
+  delete?: number;
+  retain?: number;
+}>;

+ 7 - 6
packages/editor/src/interfaces/editing-client.ts

@@ -1,8 +1,9 @@
 import type { IUser } from '@growi/core';
 
-export type EditingClient = Pick<IUser, 'name'> & Partial<Pick<IUser, 'username' | 'imageUrlCached'>> & {
-  clientId: number;
-  userId?: string;
-  color: string;
-  colorLight: string;
-}
+export type EditingClient = Pick<IUser, 'name'> &
+  Partial<Pick<IUser, 'username' | 'imageUrlCached'>> & {
+    clientId: number;
+    userId?: string;
+    color: string;
+    colorLight: string;
+  };

+ 0 - 2
packages/editor/src/main.tsx

@@ -1,5 +1,4 @@
 import React from 'react';
-
 import ReactDOM from 'react-dom/client';
 import { ToastContainer } from 'react-toastify';
 
@@ -7,7 +6,6 @@ import { Playground } from './client/components-internal/playground';
 
 import './main.scss';
 
-
 const rootElem = document.getElementById('root');
 
 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion

+ 1 - 1
packages/editor/src/models/index.ts

@@ -1,2 +1,2 @@
-export * from './markdown-table';
 export * from './linker';
+export * from './markdown-table';

+ 80 - 32
packages/editor/src/models/linker.ts

@@ -1,7 +1,6 @@
 import { encodeSpaces } from '@growi/core/dist/utils/page-path-utils';
 
 export class Linker {
-
   type: string;
 
   label: string | undefined;
@@ -60,74 +59,112 @@ export class Linker {
     // if str doesn't mean a linker, create a link whose label is str
     let label = str;
     let link = '';
-    let type = this.types.markdownLink;
+    let type = Linker.types.markdownLink;
 
     // pukiwiki with separator ">".
-    if (str.match(this.patterns.pukiwikiLinkWithLabel)) {
-      type = this.types.pukiwikiLink;
+    if (str.match(Linker.patterns.pukiwikiLinkWithLabel)) {
+      type = Linker.types.pukiwikiLink;
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ({ label, link } = str.match(this.patterns.pukiwikiLinkWithLabel)!.groups!);
+      ({ label, link } = str.match(Linker.patterns.pukiwikiLinkWithLabel)!
+        .groups!);
     }
     // pukiwiki without separator ">".
-    else if (str.match(this.patterns.pukiwikiLinkWithoutLabel)) {
-      type = this.types.pukiwikiLink;
+    else if (str.match(Linker.patterns.pukiwikiLinkWithoutLabel)) {
+      type = Linker.types.pukiwikiLink;
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ({ label } = str.match(this.patterns.pukiwikiLinkWithoutLabel)!.groups!);
+      ({ label } = str.match(Linker.patterns.pukiwikiLinkWithoutLabel)!
+        .groups!);
       link = label;
     }
     // markdown
-    else if (str.match(this.patterns.markdownLink)) {
-      type = this.types.markdownLink;
+    else if (str.match(Linker.patterns.markdownLink)) {
+      type = Linker.types.markdownLink;
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ({ label, link } = str.match(this.patterns.markdownLink)!.groups!);
+      ({ label, link } = str.match(Linker.patterns.markdownLink)!.groups!);
     }
     // growi
-    else if (str.match(this.patterns.growiLink)) {
-      type = this.types.growiLink;
+    else if (str.match(Linker.patterns.growiLink)) {
+      type = Linker.types.growiLink;
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ({ label } = str.match(this.patterns.growiLink)!.groups!);
+      ({ label } = str.match(Linker.patterns.growiLink)!.groups!);
       link = label;
     }
 
-    return new Linker(
-      type,
-      label,
-      link,
-    );
+    return new Linker(type, label, link);
   }
 
   // create an instance of Linker from text with index
   static fromLineWithIndex(line: string, index: number): Linker {
-    const { beginningOfLink, endOfLink } = this.getBeginningAndEndIndexOfLink(line, index);
+    const { beginningOfLink, endOfLink } = Linker.getBeginningAndEndIndexOfLink(
+      line,
+      index,
+    );
     // if index is in a link, extract it from line
     let linkStr = '';
     if (beginningOfLink >= 0 && endOfLink >= 0) {
       linkStr = line.substring(beginningOfLink, endOfLink);
     }
-    return this.fromMarkdownString(linkStr);
+    return Linker.fromMarkdownString(linkStr);
   }
 
   // return beginning and end indices of link
   // if index is not in a link, return { beginningOfLink: -1, endOfLink: -1 }
-  static getBeginningAndEndIndexOfLink(line: string, index: number): { beginningOfLink: number; endOfLink: number } {
+  static getBeginningAndEndIndexOfLink(
+    line: string,
+    index: number,
+  ): { beginningOfLink: number; endOfLink: number } {
     let beginningOfLink: number;
     let endOfLink: number;
 
     // pukiwiki link ('[[link]]')
-    [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[[', ']]');
+    [beginningOfLink, endOfLink] =
+      Linker.getBeginningAndEndIndexWithPrefixAndSuffix(
+        line,
+        index,
+        '[[',
+        ']]',
+      );
 
     // markdown link ('[label](link)')
-    if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
-      [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[', ')', '](');
+    if (
+      beginningOfLink < 0 ||
+      endOfLink < 0 ||
+      beginningOfLink > index ||
+      endOfLink < index
+    ) {
+      [beginningOfLink, endOfLink] =
+        Linker.getBeginningAndEndIndexWithPrefixAndSuffix(
+          line,
+          index,
+          '[',
+          ')',
+          '](',
+        );
     }
 
     // growi link ('[/link]')
-    if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
-      [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[/', ']');
+    if (
+      beginningOfLink < 0 ||
+      endOfLink < 0 ||
+      beginningOfLink > index ||
+      endOfLink < index
+    ) {
+      [beginningOfLink, endOfLink] =
+        Linker.getBeginningAndEndIndexWithPrefixAndSuffix(
+          line,
+          index,
+          '[/',
+          ']',
+        );
     }
 
     // return { beginningOfLink: -1, endOfLink: -1 }
-    if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
+    if (
+      beginningOfLink < 0 ||
+      endOfLink < 0 ||
+      beginningOfLink > index ||
+      endOfLink < index
+    ) {
       [beginningOfLink, endOfLink] = [-1, -1];
     }
 
@@ -135,15 +172,26 @@ export class Linker {
   }
 
   // return begin and end indices as an array only when index is between prefix and suffix and link contains containText.
-  static getBeginningAndEndIndexWithPrefixAndSuffix(line: string, index: number, prefix: string, suffix: string, containText = ''): [number, number] {
+  static getBeginningAndEndIndexWithPrefixAndSuffix(
+    line: string,
+    index: number,
+    prefix: string,
+    suffix: string,
+    containText = '',
+  ): [number, number] {
     const beginningIndex = line.lastIndexOf(prefix, index);
-    const indexOfContainText = line.indexOf(containText, beginningIndex + prefix.length);
-    const endIndex = line.indexOf(suffix, indexOfContainText + containText.length);
+    const indexOfContainText = line.indexOf(
+      containText,
+      beginningIndex + prefix.length,
+    );
+    const endIndex = line.indexOf(
+      suffix,
+      indexOfContainText + containText.length,
+    );
 
     if (beginningIndex < 0 || indexOfContainText < 0 || endIndex < 0) {
       return [-1, -1];
     }
     return [beginningIndex, endIndex + suffix.length];
   }
-
 }

+ 0 - 2
packages/editor/src/models/markdown-table.d.ts

@@ -1,5 +1,4 @@
 export declare class MarkdownTable {
-
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   static fromHTMLTableTag(str: any): MarkdownTable;
 
@@ -20,5 +19,4 @@ export declare class MarkdownTable {
   clone(): MarkdownTable;
 
   normalizeCells(): MarkdownTable;
-
 }

+ 15 - 12
packages/editor/src/models/markdown-table.js

@@ -15,7 +15,6 @@ const defaultOptions = { stringLength: stringWidth };
  *   ref. https://github.com/wooorm/markdown-table
  */
 export class MarkdownTable {
-
   constructor(table, options) {
     this.table = table || [];
     this.options = Object.assign(options || {}, defaultOptions);
@@ -47,8 +46,7 @@ export class MarkdownTable {
       for (let j = 0; j < this.table[i].length; j++) {
         if (this.table[i][j] != null) {
           this.table[i][j] = this.table[i][j].trim().replace(/\r?\n/g, ' ');
-        }
-        else {
+        } else {
           this.table[i][j] = '';
         }
       }
@@ -65,7 +63,7 @@ export class MarkdownTable {
    */
   static fromHTMLTableTag(str) {
     // set up DOMParser
-    const domParser = new (window.DOMParser)();
+    const domParser = new window.DOMParser();
 
     // use DOMParser to prevent DOM based XSS (https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
     const dom = domParser.parseFromString(str, 'application/xml');
@@ -102,7 +100,9 @@ export class MarkdownTable {
    * return a MarkdownTable instance made from a string of delimiter-separated values
    */
   static fromDSV(str, delimiter) {
-    return MarkdownTable.fromMarkdownString(csvToMarkdown(str, delimiter, true));
+    return MarkdownTable.fromMarkdownString(
+      csvToMarkdown(str, delimiter, true),
+    );
   }
 
   /**
@@ -117,7 +117,10 @@ export class MarkdownTable {
     for (let n = 0; n < arrMDTableLines.length; n++) {
       const line = arrMDTableLines[n];
 
-      if (tableAlignmentLineRE.test(line) && !tableAlignmentLineNegRE.test(line)) {
+      if (
+        tableAlignmentLineRE.test(line) &&
+        !tableAlignmentLineNegRE.test(line)
+      ) {
         // parse line which described alignment
         const alignRuleRE = [
           { align: 'c', regex: /^:-+:$/ },
@@ -128,11 +131,12 @@ export class MarkdownTable {
         lineText = line.replace(/^\||\|$/g, ''); // strip off pipe charactor which is placed head of line and last of line.
         lineText = lineText.replace(/\s*/g, '');
         aligns = lineText.split(/\|/).map((col) => {
-          const rule = alignRuleRE.find((rule) => { return col.match(rule.regex) });
-          return (rule != null) ? rule.align : '';
+          const rule = alignRuleRE.find((rule) => {
+            return col.match(rule.regex);
+          });
+          return rule != null ? rule.align : '';
         });
-      }
-      else if (linePartOfTableRE.test(line)) {
+      } else if (linePartOfTableRE.test(line)) {
         // parse line whether header or body
         let lineText = '';
         lineText = line.replace(/\s*\|\s*/g, '|');
@@ -141,7 +145,6 @@ export class MarkdownTable {
         contents.push(row);
       }
     }
-    return (new MarkdownTable(contents, { align: aligns }));
+    return new MarkdownTable(contents, { align: aligns });
   }
-
 }

+ 1 - 1
packages/editor/src/utils/delta-to-changespecs.ts

@@ -1,4 +1,4 @@
-import { type ChangeSpec } from '@codemirror/state';
+import type { ChangeSpec } from '@codemirror/state';
 
 import type { Delta } from '../interfaces';
 

+ 3 - 10
packages/editor/vite.config.ts

@@ -1,6 +1,4 @@
-import path from 'path';
-
-
+import path from 'node:path';
 import react from '@vitejs/plugin-react';
 import glob from 'glob';
 import { nodeExternals } from 'rollup-plugin-node-externals';
@@ -47,9 +45,7 @@ export default defineConfig({
     devSocketIOPlugin(),
     dts({
       entryRoot: 'src',
-      exclude: [
-        ...excludeFiles,
-      ],
+      exclude: [...excludeFiles],
       copyDtsFiles: true,
     }),
     {
@@ -65,10 +61,7 @@ export default defineConfig({
     sourcemap: true,
     lib: {
       entry: glob.sync(path.resolve(__dirname, 'src/**/*.{ts,tsx}'), {
-        ignore: [
-          ...excludeFiles,
-          '**/*.spec.ts',
-        ],
+        ignore: [...excludeFiles, '**/*.spec.ts'],
       }),
       name: 'editor-libs',
       formats: ['es'],