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

Merge pull request #7968 from weseek/feat/new-editor

support: Update CodeMirror to v6
Yuki Takei 2 лет назад
Родитель
Сommit
b2e99e850c
43 измененных файлов с 1195 добавлено и 144 удалено
  1. 18 18
      .devcontainer/docker-compose.yml
  2. 1 1
      apps/app/package.json
  3. 0 0
      apps/app/src-obsoleted/components/PageEditor/CodeMirrorEditor.jsx
  4. 0 0
      apps/app/src-obsoleted/components/PageEditor/CodeMirrorEditor.module.scss
  5. 0 0
      apps/app/src-obsoleted/components/PageEditor/CommentMentionHelper.ts
  6. 0 0
      apps/app/src-obsoleted/components/PageEditor/ConflictDiffModal.tsx
  7. 0 0
      apps/app/src-obsoleted/components/PageEditor/EmojiPicker.tsx
  8. 0 0
      apps/app/src-obsoleted/components/PageEditor/EmojiPickerHelper.ts
  9. 0 0
      apps/app/src-obsoleted/components/UncontrolledCodeMirror.tsx
  10. 4 9
      apps/app/src/components/Common/LazyRenderer.tsx
  11. 1 10
      apps/app/src/components/Page/DisplaySwitcher.tsx
  12. 13 12
      apps/app/src/components/PageEditor/Editor.tsx
  13. 34 14
      apps/app/src/components/PageEditor/PageEditor.tsx
  14. 3 0
      apps/app/src/components/PageEditor/index.ts
  15. 5 9
      apps/app/src/styles/_editor.scss
  16. 0 46
      apps/app/src/styles/_mixins.scss
  17. 1 1
      package.json
  18. 2 0
      packages/editor/.eslintignore
  19. 13 0
      packages/editor/.eslintrc.cjs
  20. 24 0
      packages/editor/.gitignore
  21. 13 0
      packages/editor/index.html
  22. 37 0
      packages/editor/package.json
  23. 2 0
      packages/editor/src/@types/declaration.d.ts
  24. 13 0
      packages/editor/src/components/CodeMirrorEditorContainer.module.scss
  25. 9 0
      packages/editor/src/components/CodeMirrorEditorContainer.tsx
  26. 1 0
      packages/editor/src/components/index.ts
  27. 34 0
      packages/editor/src/components/playground/Playground.tsx
  28. 39 0
      packages/editor/src/components/playground/PlaygroundController.tsx
  29. 1 0
      packages/editor/src/components/playground/index.ts
  30. 2 0
      packages/editor/src/index.ts
  31. 1 0
      packages/editor/src/main.scss
  32. 17 0
      packages/editor/src/main.tsx
  33. 42 0
      packages/editor/src/services/codemirror-editor.ts
  34. 1 0
      packages/editor/src/services/index.ts
  35. 12 0
      packages/editor/src/stores/codemirror-editor.ts
  36. 1 0
      packages/editor/src/stores/index.ts
  37. 33 0
      packages/editor/src/stores/use-static-swr.tsx
  38. 1 0
      packages/editor/src/vite-env.d.ts
  39. 31 0
      packages/editor/tsconfig.json
  40. 11 0
      packages/editor/tsconfig.node.json
  41. 55 0
      packages/editor/vite.config.ts
  42. 0 1
      packages/remark-growi-directive/package.json
  43. 720 23
      yarn.lock

+ 18 - 18
.devcontainer/docker-compose.yml

@@ -31,16 +31,16 @@ services:
     image: mongo:6.0
     image: mongo:6.0
     restart: unless-stopped
     restart: unless-stopped
     ports:
     ports:
-      - 27017:27017
+      - 27018:27017
     volumes:
     volumes:
       - /data/db
       - /data/db
 
 
-  ogp:
-    image: ghcr.io/weseek/growi-unique-ogp:latest
-    ports:
-      - 8088:8088
-    restart: unless-stopped
-    tty: true
+  # ogp:
+  #   image: ghcr.io/weseek/growi-unique-ogp:latest
+  #   ports:
+  #     - 8088:8088
+  #   restart: unless-stopped
+  #   tty: true
 
 
   # This container requires '../../growi-docker-compose' repository
   # This container requires '../../growi-docker-compose' repository
   #   cloned from https://github.com/weseek/growi-docker-compose.git
   #   cloned from https://github.com/weseek/growi-docker-compose.git
@@ -52,7 +52,7 @@ services:
         - version=8.7.0
         - version=8.7.0
     restart: unless-stopped
     restart: unless-stopped
     ports:
     ports:
-      - 9200:9200
+      - 9201:9200
     environment:
     environment:
       - bootstrap.memory_lock=true
       - bootstrap.memory_lock=true
       - "ES_JAVA_OPTS=-Xms256m -Xmx256m"
       - "ES_JAVA_OPTS=-Xms256m -Xmx256m"
@@ -66,15 +66,15 @@ services:
       - ../../growi-docker-compose/elasticsearch/v8/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
       - ../../growi-docker-compose/elasticsearch/v8/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
 
 
   #need to adjust kibana version based on elasticsearch version (use same version as elasticsearch version)
   #need to adjust kibana version based on elasticsearch version (use same version as elasticsearch version)
-  kibana:
-    image: docker.elastic.co/kibana/kibana:8.7.0
-    restart: unless-stopped
-    environment:
-      ELASTICSEARCH_HOSTS: 'http://elasticsearch:9200'
-    ports:
-      - 5601:5601
-    depends_on:
-      - elasticsearch
+  # kibana:
+  #   image: docker.elastic.co/kibana/kibana:8.7.0
+  #   restart: unless-stopped
+  #   environment:
+  #     ELASTICSEARCH_HOSTS: 'http://elasticsearch:9200'
+  #   ports:
+  #     - 5601:5601
+  #   depends_on:
+  #     - elasticsearch
 
 
   # This container requires '../../growi-docker-compose' repository
   # This container requires '../../growi-docker-compose' repository
   #   cloned from https://github.com/weseek/growi-docker-compose.git
   #   cloned from https://github.com/weseek/growi-docker-compose.git
@@ -89,7 +89,7 @@ services:
       - CMD_DB_URL=sqlite://dummyhost/hackmd/sqlite/codimd.db
       - CMD_DB_URL=sqlite://dummyhost/hackmd/sqlite/codimd.db
       - CMD_CSP_ENABLE=false
       - CMD_CSP_ENABLE=false
     ports:
     ports:
-      - 3010:3000
+      - 3011:3000
     volumes:
     volumes:
       - /files/sqlite
       - /files/sqlite
 volumes:
 volumes:

+ 1 - 1
apps/app/package.json

@@ -211,6 +211,7 @@
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   },
   "devDependencies": {
   "devDependencies": {
+    "@growi/editor": "link:../../packages/editor",
     "@growi/presentation": "link:../../packages/presentation",
     "@growi/presentation": "link:../../packages/presentation",
     "@growi/ui": "link:../../packages/ui",
     "@growi/ui": "link:../../packages/ui",
     "@handsontable/react": "=2.1.0",
     "@handsontable/react": "=2.1.0",
@@ -224,7 +225,6 @@
     "autoprefixer": "^9.0.0",
     "autoprefixer": "^9.0.0",
     "babel-loader": "^8.2.5",
     "babel-loader": "^8.2.5",
     "bootstrap": "^5.3.1",
     "bootstrap": "^5.3.1",
-    "codemirror": "^5.64.0",
     "connect-browser-sync": "^2.1.0",
     "connect-browser-sync": "^2.1.0",
     "diff2html": "^3.4.35",
     "diff2html": "^3.4.35",
     "eazy-logger": "^3.1.0",
     "eazy-logger": "^3.1.0",

+ 0 - 0
apps/app/src/components/PageEditor/CodeMirrorEditor.jsx → apps/app/src-obsoleted/components/PageEditor/CodeMirrorEditor.jsx


+ 0 - 0
apps/app/src/components/PageEditor/CodeMirrorEditor.module.scss → apps/app/src-obsoleted/components/PageEditor/CodeMirrorEditor.module.scss


+ 0 - 0
apps/app/src/components/PageEditor/CommentMentionHelper.ts → apps/app/src-obsoleted/components/PageEditor/CommentMentionHelper.ts


+ 0 - 0
apps/app/src/components/PageEditor/ConflictDiffModal.tsx → apps/app/src-obsoleted/components/PageEditor/ConflictDiffModal.tsx


+ 0 - 0
apps/app/src/components/PageEditor/EmojiPicker.tsx → apps/app/src-obsoleted/components/PageEditor/EmojiPicker.tsx


+ 0 - 0
apps/app/src/components/PageEditor/EmojiPickerHelper.ts → apps/app/src-obsoleted/components/PageEditor/EmojiPickerHelper.ts


+ 0 - 0
apps/app/src/components/UncontrolledCodeMirror.tsx → apps/app/src-obsoleted/components/UncontrolledCodeMirror.tsx


+ 4 - 9
apps/app/src/components/Common/LazyRenderer.tsx

@@ -21,17 +21,12 @@ export const LazyRenderer = (props: Props): JSX.Element => {
     setActivated(shouldRender);
     setActivated(shouldRender);
   }, [isActivated, shouldRender]);
   }, [isActivated, shouldRender]);
 
 
-  const additionalClassName = shouldRender ? '' : 'd-none';
-
   if (!isActivated) {
   if (!isActivated) {
     return <></>;
     return <></>;
   }
   }
 
 
-  return (
-    <>
-      { React.cloneElement(children, {
-        className: `${children.props.className ?? ''} ${additionalClassName}`,
-      }) }
-    </>
-  );
+  const child = React.Children.only(children);
+
+  return React.cloneElement(child, { visibility: shouldRender });
+
 };
 };

+ 1 - 10
apps/app/src/components/Page/DisplaySwitcher.tsx

@@ -13,7 +13,6 @@ import { LazyRenderer } from '../Common/LazyRenderer';
 
 
 
 
 const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
 const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
-const PageEditorByHackmd = dynamic(() => import('../PageEditorByHackmd').then(mod => mod.PageEditorByHackmd), { ssr: false });
 const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
 const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
 
 
 
 
@@ -38,15 +37,7 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
       { isViewMode && pageView }
       { isViewMode && pageView }
 
 
       <LazyRenderer shouldRender={isEditable === true && editorMode === EditorMode.Editor}>
       <LazyRenderer shouldRender={isEditable === true && editorMode === EditorMode.Editor}>
-        <div data-testid="page-editor" id="page-editor" className="editor-root">
-          <PageEditor />
-        </div>
-      </LazyRenderer>
-
-      <LazyRenderer shouldRender={isEditable === true && editorMode === EditorMode.HackMD}>
-        <div id="page-editor-with-hackmd" className="editor-root">
-          <PageEditorByHackmd />
-        </div>
+        <PageEditor />
       </LazyRenderer>
       </LazyRenderer>
 
 
       { isEditable && !isViewMode && <EditorNavbarBottom /> }
       { isEditable && !isViewMode && <EditorNavbarBottom /> }

+ 13 - 12
apps/app/src/components/PageEditor/Editor.tsx

@@ -20,7 +20,7 @@ import { IEditorMethods } from '../../interfaces/editor-methods';
 
 
 import AbstractEditor from './AbstractEditor';
 import AbstractEditor from './AbstractEditor';
 import { Cheatsheet } from './Cheatsheet';
 import { Cheatsheet } from './Cheatsheet';
-import CodeMirrorEditor from './CodeMirrorEditor';
+// import CodeMirrorEditor from './CodeMirrorEditor';
 import pasteHelper from './PasteHelper';
 import pasteHelper from './PasteHelper';
 import TextAreaEditor from './TextAreaEditor';
 import TextAreaEditor from './TextAreaEditor';
 
 
@@ -306,17 +306,18 @@ const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props
 
 
                 {/* for PC */}
                 {/* for PC */}
                 { !isMobile && (
                 { !isMobile && (
-                  <CodeMirrorEditor
-                    ref={cmEditorRef}
-                    indentSize={indentSize ?? defaultIndentSize}
-                    onPasteFiles={pasteFilesHandler}
-                    onDragEnter={dragEnterHandler}
-                    onMarkdownHelpButtonClicked={() => { setIsCheatsheetModalShown(true) }}
-                    onAddAttachmentButtonClicked={addAttachmentHandler}
-                    editorSettings={editorSettings}
-                    isGfmMode={isGfmMode}
-                    {...props}
-                  />
+                  // <CodeMirrorEditor
+                  //   ref={cmEditorRef}
+                  //   indentSize={indentSize ?? defaultIndentSize}
+                  //   onPasteFiles={pasteFilesHandler}
+                  //   onDragEnter={dragEnterHandler}
+                  //   onMarkdownHelpButtonClicked={() => { setIsCheatsheetModalShown(true) }}
+                  //   onAddAttachmentButtonClicked={addAttachmentHandler}
+                  //   editorSettings={editorSettings}
+                  //   isGfmMode={isGfmMode}
+                  //   {...props}
+                  // />
+                  <></>
                 )}
                 )}
 
 
                 {/* for mobile */}
                 {/* for mobile */}

+ 34 - 14
apps/app/src/components/PageEditor.tsx → apps/app/src/components/PageEditor/PageEditor.tsx

@@ -8,6 +8,7 @@ import nodePath from 'path';
 
 
 import type { IPageHasId } from '@growi/core';
 import type { IPageHasId } from '@growi/core';
 import { pathUtils } from '@growi/core/dist/utils';
 import { pathUtils } from '@growi/core/dist/utils';
+import { CodeMirrorEditorContainer, useCodeMirrorEditor } from '@growi/editor';
 import detectIndent from 'detect-indent';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
@@ -51,10 +52,13 @@ import loggerFactory from '~/utils/logger';
 
 
 
 
 // import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
 // import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
-import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
-import Editor from './PageEditor/Editor';
-import Preview from './PageEditor/Preview';
-import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
+// import { ConflictDiffModal } from './ConflictDiffModal';
+// import Editor from './Editor';
+import Preview from './Preview';
+import scrollSyncHelper from './ScrollSyncHelper';
+
+
+import '@growi/editor/dist/style.css';
 
 
 
 
 const logger = loggerFactory('growi:PageEditor');
 const logger = loggerFactory('growi:PageEditor');
@@ -71,11 +75,20 @@ let lastScrolledDateWithCursor: Date | null = null;
 let isOriginOfScrollSyncEditor = false;
 let isOriginOfScrollSyncEditor = false;
 let isOriginOfScrollSyncPreview = false;
 let isOriginOfScrollSyncPreview = false;
 
 
-const PageEditor = React.memo((): JSX.Element => {
+
+type Props = {
+  visibility?: boolean,
+}
+
+export const PageEditor = React.memo((props: Props): JSX.Element => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
   const router = useRouter();
   const router = useRouter();
 
 
+  const editorRef = useRef<IEditorMethods>(null);
+  const previewRef = useRef<HTMLDivElement>(null);
+  const codeMirrorEditorContainerRef = useRef<HTMLDivElement>(null);
+
   const { data: isNotFound } = useIsNotFound();
   const { data: isNotFound } = useIsNotFound();
   const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
   const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
@@ -103,6 +116,15 @@ const PageEditor = React.memo((): JSX.Element => {
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
 
+  const { setContainer } = useCodeMirrorEditor({
+    container: codeMirrorEditorContainerRef.current,
+  });
+  useEffect(() => {
+    if (codeMirrorEditorContainerRef.current != null) {
+      setContainer(codeMirrorEditorContainerRef.current);
+    }
+  }, [setContainer]);
+
   const { data: rendererOptions } = usePreviewOptions();
   const { data: rendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const saveOrUpdate = useSaveOrUpdate();
   const saveOrUpdate = useSaveOrUpdate();
@@ -142,9 +164,6 @@ const PageEditor = React.memo((): JSX.Element => {
   const { mutate: mutateIsConflict } = useIsConflict();
   const { mutate: mutateIsConflict } = useIsConflict();
 
 
 
 
-  const editorRef = useRef<IEditorMethods>(null);
-  const previewRef = useRef<HTMLDivElement>(null);
-
   const checkIsConflict = useCallback((data) => {
   const checkIsConflict = useCallback((data) => {
     const { s2cMessagePageUpdated } = data;
     const { s2cMessagePageUpdated } = data;
 
 
@@ -554,9 +573,9 @@ const PageEditor = React.memo((): JSX.Element => {
   const isUploadable = isUploadableImage || isUploadableFile;
   const isUploadable = isUploadableImage || isUploadableFile;
 
 
   return (
   return (
-    <div className="d-flex flex-wrap">
+    <div data-testid="page-editor" id="page-editor" className={`d-flex flex-grow-1 overflow-auto ${props.visibility ? '' : 'd-none'}`}>
       <div className="page-editor-editor-container flex-grow-1 flex-basis-0 mw-0">
       <div className="page-editor-editor-container flex-grow-1 flex-basis-0 mw-0">
-        <Editor
+        {/* <Editor
           ref={editorRef}
           ref={editorRef}
           value={initialValue}
           value={initialValue}
           isUploadable={isUploadable}
           isUploadable={isUploadable}
@@ -567,9 +586,10 @@ const PageEditor = React.memo((): JSX.Element => {
           onChange={markdownChangedHandler}
           onChange={markdownChangedHandler}
           onUpload={uploadHandler}
           onUpload={uploadHandler}
           onSave={saveWithShortcut}
           onSave={saveWithShortcut}
-        />
+        /> */}
+        <CodeMirrorEditorContainer ref={codeMirrorEditorContainerRef} />
       </div>
       </div>
-      <div className="d-none d-lg-block page-editor-preview-container flex-grow-1 flex-basis-0 mw-0">
+      <div className="d-none d-lg-flex page-editor-preview-container justify-content-center flex-grow-1 flex-basis-0 mw-0">
         <Preview
         <Preview
           ref={previewRef}
           ref={previewRef}
           rendererOptions={rendererOptions}
           rendererOptions={rendererOptions}
@@ -578,6 +598,7 @@ const PageEditor = React.memo((): JSX.Element => {
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
         />
         />
       </div>
       </div>
+      {/*
       <ConflictDiffModal
       <ConflictDiffModal
         isOpen={conflictDiffModalStatus?.isOpened}
         isOpen={conflictDiffModalStatus?.isOpened}
         onClose={() => closeConflictDiffModal()}
         onClose={() => closeConflictDiffModal()}
@@ -585,9 +606,8 @@ const PageEditor = React.memo((): JSX.Element => {
         optionsToSave={optionsToSave}
         optionsToSave={optionsToSave}
         afterResolvedHandler={afterResolvedHandler}
         afterResolvedHandler={afterResolvedHandler}
       />
       />
+       */}
     </div>
     </div>
   );
   );
 });
 });
 PageEditor.displayName = 'PageEditor';
 PageEditor.displayName = 'PageEditor';
-
-export default PageEditor;

+ 3 - 0
apps/app/src/components/PageEditor/index.ts

@@ -0,0 +1,3 @@
+import { PageEditor } from './PageEditor';
+
+export default PageEditor;

+ 5 - 9
apps/app/src/styles/_editor.scss

@@ -1,7 +1,6 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use './variables' as var;
 @use './variables' as var;
 
 
-@import './mixins' ;
 @import './organisms/wiki-custom-sidebar';
 @import './organisms/wiki-custom-sidebar';
 
 
 // global imported
 // global imported
@@ -26,14 +25,9 @@
     }
     }
   }
   }
 
 
-  // calculate margin
-  $editor-margin-top: var.$grw-subnav-height-on-edit;
-  @include expand-editor($editor-margin-top);
-
-  @include bs.media-breakpoint-up(lg) {
-    // calculate margin
-    $editor-margin-top: var.$grw-subnav-height-lg-on-edit;
-    @include expand-editor($editor-margin-top);
+  .page-wrapper {
+    top: 0;
+    height: 100vh;
   }
   }
 
 
   // show
   // show
@@ -211,11 +205,13 @@
     }
     }
 
 
     .page-editor-preview-body {
     .page-editor-preview-body {
+      flex-grow: 1;
       padding: 18px 15px 0;
       padding: 18px 15px 0;
       overflow-y: scroll;
       overflow-y: scroll;
     }
     }
     // editing /Sidebar
     // editing /Sidebar
     .page-editor-preview-body.preview-sidebar {
     .page-editor-preview-body.preview-sidebar {
+      flex-grow: 0;
       width: 320px;
       width: 320px;
       margin-right: auto;
       margin-right: auto;
       margin-left: auto;
       margin-left: auto;

+ 0 - 46
apps/app/src/styles/_mixins.scss

@@ -18,52 +18,6 @@
   }
   }
 }
 }
 
 
-@mixin expand-editor($editor-margin-top) {
-  $header-plus-footer: $editor-margin-top + var.$grw-editor-navbar-bottom-height;
-
-  $editor-margin: $header-plus-footer //
-    + 25px //   add .btn-open-dropzone height
-    + 30px; //  add .navbar-editor height
-
-  .editor-root {
-    width: 100%;
-    height: calc(100vh - #{$header-plus-footer});
-    min-height: calc(100vh - #{$header-plus-footer}); // for IE11
-    margin-top: 0px !important;
-
-    // left(editor)
-    .page-editor-editor-container {
-      height: calc(100vh - #{$header-plus-footer});
-      min-height: calc(100vh - #{$header-plus-footer}); // for IE11
-
-      .react-codemirror2,
-      .CodeMirror,
-      .CodeMirror-scroll,
-      .textarea-editor {
-        height: calc(100vh - #{$editor-margin});
-      }
-    }
-
-    // right(preview)
-    .page-editor-preview-container,
-    .page-editor-preview-body {
-      height: calc(100vh - #{$header-plus-footer});
-      min-height: calc(100vh - #{$header-plus-footer}); // for IE11
-    }
-  }
-
-  .editor-root#page-editor-with-hackmd {
-    &,
-    .hackmd-preinit,
-    .hackmd-error,
-    #iframe-hackmd-container > iframe {
-      width: 100%;
-      height: calc(100vh - #{$header-plus-footer});
-      min-height: calc(100vh - #{$header-plus-footer}); // for IE11
-    }
-  }
-}
-
 @mixin apply-navigation-transition() {
 @mixin apply-navigation-transition() {
   transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
   transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
   transition-duration: 300ms;
   transition-duration: 300ms;

+ 1 - 1
package.json

@@ -91,7 +91,7 @@
     "tsconfig-paths": "^3.9.0",
     "tsconfig-paths": "^3.9.0",
     "typescript": "~5.0.0",
     "typescript": "~5.0.0",
     "vite": "^4.4.0",
     "vite": "^4.4.0",
-    "vite-plugin-dts": "^2.3.0",
+    "vite-plugin-dts": "^3.3.1",
     "vite-tsconfig-paths": "^4.2.0",
     "vite-tsconfig-paths": "^4.2.0",
     "vitest": "^0.31.4",
     "vitest": "^0.31.4",
     "vitest-mock-extended": "^1.1.3"
     "vitest-mock-extended": "^1.1.3"

+ 2 - 0
packages/editor/.eslintignore

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

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

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

+ 24 - 0
packages/editor/.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 13 - 0
packages/editor/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + React + TS</title>
+  </head>
+  <body>
+    <div id="root" class="d-flex flex-column vw-100 vh-100"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>

+ 37 - 0
packages/editor/package.json

@@ -0,0 +1,37 @@
+{
+  "name": "@growi/editor",
+  "version": "6.2.0-RC.0",
+  "license": "MIT",
+  "type": "module",
+  "module": "dist/index.js",
+  "types": "dist/index.d.ts",
+  "scripts": {
+    "build": "tsc && vite build",
+    "clean": "shx rm -rf dist",
+    "dev": "vite build --mode dev",
+    "watch": "yarn dev -w --emptyOutDir=false",
+    "serve": "vite",
+    "lint:js": "yarn eslint **/*.{js,ts}",
+    "lint:typecheck": "tsc",
+    "lint": "npm-run-all -p lint:*",
+    "version": "yarn version --no-git-tag-version --preid=RC"
+  },
+  "dependencies": {
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
+  },
+  "devDependencies": {
+    "@codemirror/lang-markdown": "^6.2.0",
+    "@codemirror/language-data": "^6.3.1",
+    "@codemirror/state": "^6.2.1",
+    "@codemirror/view": "^6.15.3",
+    "@popperjs/core": "^2.11.8",
+    "@types/react": "^18.2.14",
+    "@types/react-dom": "^18.2.6",
+    "@uiw/react-codemirror": "^4.21.8",
+    "bootstrap": "^5.3.1",
+    "codemirror": "^6.0.1",
+    "eslint-plugin-react-refresh": "^0.4.1",
+    "swr": "^2.0.3"
+  }
+}

+ 2 - 0
packages/editor/src/@types/declaration.d.ts

@@ -0,0 +1,2 @@
+// prevent TS2307: Cannot find module './xxx.module.scss' or its corresponding type declarations.
+declare module '*.scss';

+ 13 - 0
packages/editor/src/components/CodeMirrorEditorContainer.module.scss

@@ -0,0 +1,13 @@
+.codemirror-editor-container :global {
+
+  display: flex;
+  align-items: stretch;
+  width: 100%;
+  height: 100%;
+
+  .cm-editor {
+    width: 100%;
+    height: 100%;
+  }
+
+}

+ 9 - 0
packages/editor/src/components/CodeMirrorEditorContainer.tsx

@@ -0,0 +1,9 @@
+import { forwardRef } from 'react';
+
+import style from './CodeMirrorEditorContainer.module.scss';
+
+export const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
+  return (
+    <div {...props} className={`${style['codemirror-editor-container']}`} ref={ref} />
+  );
+});

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

@@ -0,0 +1 @@
+export * from './CodeMirrorEditorContainer';

+ 34 - 0
packages/editor/src/components/playground/Playground.tsx

@@ -0,0 +1,34 @@
+import { useRef } from 'react';
+
+import { CodeMirrorEditorContainer } from '..';
+import { useCodeMirrorEditorMain } from '../../stores';
+
+import { PlaygroundController } from './PlaygroundController';
+
+export const Playground = (): JSX.Element => {
+
+  const containerRef = useRef(null);
+
+  useCodeMirrorEditorMain({
+    container: containerRef.current,
+  });
+
+  return (
+    <>
+      <div className="flex-grow-1 d-flex flex-column justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>
+        <div className="text-white">GrowiSubNavigation</div>
+      </div>
+      <div className="flex-grow-1 d-flex overflow-y-auto">
+        <div className="flex-grow-1 d-flex flex-column" style={{ flexBasis: 0 }}>
+          <CodeMirrorEditorContainer ref={containerRef} />
+        </div>
+        <div className="flex-grow-1 mw-0 d-flex flex-column bg-light border-start border-dark-subtle p-3" style={{ flexBasis: 0 }}>
+          <PlaygroundController />
+        </div>
+      </div>
+      <div className="flex-grow-1 d-flex flex-column justify-content-center align-items-center bg-dark" style={{ minHeight: '50px' }}>
+        <div className="text-white">EditorNavbarBottom</div>
+      </div>
+    </>
+  );
+};

+ 39 - 0
packages/editor/src/components/playground/PlaygroundController.tsx

@@ -0,0 +1,39 @@
+import { useCallback } from 'react';
+
+import { useCodeMirrorEditorMain } from '../../stores';
+
+export const PlaygroundController = (): JSX.Element => {
+
+  const { data: states } = useCodeMirrorEditorMain();
+
+  const initEditorValue = useCallback(() => {
+    if (states == null) {
+      return;
+    }
+
+    states.view?.dispatch({
+      changes: {
+        from: 0,
+        to: states.view.state.doc.toString().length,
+        insert: '# Header\n\n- foo\n-bar\n',
+      },
+    });
+
+  }, [states]);
+
+  return (
+    <>
+      <div className="row">
+        <div className="column">
+          <button
+            type="button"
+            className="btn btn-outline-secondary"
+            onClick={() => initEditorValue()}
+          >
+            Initialize editor value
+          </button>
+        </div>
+      </div>
+    </>
+  );
+};

+ 1 - 0
packages/editor/src/components/playground/index.ts

@@ -0,0 +1 @@
+export * from './Playground';

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

@@ -0,0 +1,2 @@
+export * from './components';
+export * from './services';

+ 1 - 0
packages/editor/src/main.scss

@@ -0,0 +1 @@
+@import 'bootstrap';

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

@@ -0,0 +1,17 @@
+import React from 'react';
+
+import ReactDOM from 'react-dom/client';
+
+import { Playground } from './components/playground';
+
+import './main.scss';
+
+
+const rootElem = document.getElementById('root');
+
+// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ReactDOM.createRoot(rootElem!).render(
+  <React.StrictMode>
+    <Playground />
+  </React.StrictMode>,
+);

+ 42 - 0
packages/editor/src/services/codemirror-editor.ts

@@ -0,0 +1,42 @@
+import { useEffect } from 'react';
+
+import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
+import { languages } from '@codemirror/language-data';
+import { EditorState, Extension } from '@codemirror/state';
+import { EditorView, scrollPastEnd } from '@codemirror/view';
+import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
+
+
+export type UseCodeMirrorEditor = UseCodeMirror;
+
+export type UseCodeMirrorEditorStates = {
+  state: EditorState | undefined;
+  setState: import('react').Dispatch<import('react').SetStateAction<EditorState | undefined>>;
+  view: EditorView | undefined;
+  setView: import('react').Dispatch<import('react').SetStateAction<EditorView | undefined>>;
+  container: HTMLDivElement | undefined;
+  setContainer: import('react').Dispatch<import('react').SetStateAction<HTMLDivElement | undefined>>;
+}
+
+const defaultExtensions: Extension[] = [
+  markdown({ base: markdownLanguage, codeLanguages: languages }),
+  scrollPastEnd(),
+];
+
+export const useCodeMirrorEditor = (props?: UseCodeMirrorEditor): UseCodeMirrorEditorStates => {
+
+  const codemirror = useCodeMirror({
+    extensions: defaultExtensions,
+    ...props,
+  });
+
+  const { setContainer } = codemirror;
+
+  useEffect(() => {
+    if (props?.container != null) {
+      setContainer(props.container);
+    }
+  }, [props?.container, setContainer]);
+
+  return codemirror;
+};

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

@@ -0,0 +1 @@
+export * from './codemirror-editor';

+ 12 - 0
packages/editor/src/stores/codemirror-editor.ts

@@ -0,0 +1,12 @@
+import type { SWRResponse } from 'swr';
+
+
+import type { UseCodeMirrorEditor, UseCodeMirrorEditorStates } from '../services';
+import { useCodeMirrorEditor } from '../services';
+
+import { useStaticSWR } from './use-static-swr';
+
+export const useCodeMirrorEditorMain = (props?: UseCodeMirrorEditor): SWRResponse<UseCodeMirrorEditorStates> => {
+  const states = useCodeMirrorEditor(props);
+  return useStaticSWR('codeMirrorEditorMain', props != null ? states : undefined);
+};

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

@@ -0,0 +1 @@
+export * from './codemirror-editor';

+ 33 - 0
packages/editor/src/stores/use-static-swr.tsx

@@ -0,0 +1,33 @@
+import {
+  Key, SWRConfiguration, SWRResponse, useSWRConfig,
+} from 'swr';
+import useSWRImmutable from 'swr/immutable';
+
+
+export function useStaticSWR<Data, Error>(key: Key): SWRResponse<Data, Error>;
+export function useStaticSWR<Data, Error>(key: Key, data: Data | undefined): SWRResponse<Data, Error>;
+export function useStaticSWR<Data, Error>(key: Key, data: Data | undefined,
+  configuration: SWRConfiguration<Data, Error> | undefined): SWRResponse<Data, Error>;
+
+export function useStaticSWR<Data, Error>(
+    ...args: readonly [Key]
+    | readonly [Key, Data | undefined]
+    | readonly [Key, Data | undefined, SWRConfiguration<Data, Error> | undefined]
+): SWRResponse<Data, Error> {
+  const [key, data, configuration] = args;
+
+  // assert.notStrictEqual(configuration?.fetcher, null, 'useStaticSWR does not support \'configuration.fetcher\'');
+
+  const { cache } = useSWRConfig();
+  const swrResponse = useSWRImmutable(key, null, {
+    ...configuration,
+    fallbackData: configuration?.fallbackData ?? cache.get(key)?.data,
+  });
+
+  // write data to cache directly
+  if (data !== undefined) {
+    cache.set(key, { ...cache.get(key), data });
+  }
+
+  return swrResponse;
+}

+ 1 - 0
packages/editor/src/vite-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 31 - 0
packages/editor/tsconfig.json

@@ -0,0 +1,31 @@
+{
+  "$schema": "http://json.schemastore.org/tsconfig",
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+
+    "baseUrl": ".",
+    "paths": {
+      "/*": ["./public/*"]
+    }
+  },
+  "include": ["src"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 11 - 0
packages/editor/tsconfig.node.json

@@ -0,0 +1,11 @@
+{
+  "$schema": "http://json.schemastore.org/tsconfig",
+  "compilerOptions": {
+    "composite": true,
+    "skipLibCheck": true,
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 55 - 0
packages/editor/vite.config.ts

@@ -0,0 +1,55 @@
+import path from 'path';
+
+import react from '@vitejs/plugin-react';
+import glob from 'glob';
+import { nodeExternals } from 'rollup-plugin-node-externals';
+import { defineConfig } from 'vite';
+import dts from 'vite-plugin-dts';
+
+
+const excludeFiles = [
+  '**/@types/*',
+  '**/components/playground/*',
+  '**/main.tsx',
+  '**/vite-env.d.ts',
+];
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    react(),
+    dts({
+      exclude: [
+        ...excludeFiles,
+      ],
+      copyDtsFiles: true,
+    }),
+    {
+      ...nodeExternals({
+        devDeps: true,
+        builtinsPrefix: 'ignore',
+      }),
+      enforce: 'pre',
+    },
+  ],
+  build: {
+    outDir: 'dist',
+    sourcemap: true,
+    lib: {
+      entry: glob.sync(path.resolve(__dirname, 'src/**/*.{ts,tsx}'), {
+        ignore: [
+          ...excludeFiles,
+          '**/*.spec.ts',
+        ],
+      }),
+      name: 'editor-libs',
+      formats: ['es'],
+    },
+    rollupOptions: {
+      output: {
+        preserveModules: true,
+        preserveModulesRoot: 'src',
+      },
+    },
+  },
+});

+ 0 - 1
packages/remark-growi-directive/package.json

@@ -54,7 +54,6 @@
     "tape": "^5.0.0",
     "tape": "^5.0.0",
     "to-vfile": "^7.0.0",
     "to-vfile": "^7.0.0",
     "type-coverage": "^2.0.0",
     "type-coverage": "^2.0.0",
-    "typescript": "^4.0.0",
     "unist-util-remove-position": "^4.0.0"
     "unist-util-remove-position": "^4.0.0"
   },
   },
   "typeCoverage": {
   "typeCoverage": {

Разница между файлами не показана из-за своего большого размера
+ 720 - 23
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов