فهرست منبع

Merge pull request #8050 from weseek/feat/codemirror-editor-toolbar

feat: CodeMirror editor toolbar
Yuki Takei 2 سال پیش
والد
کامیت
34ff4e66ef
25فایلهای تغییر یافته به همراه256 افزوده شده و 71 حذف شده
  1. 1 1
      apps/app/package.json
  2. 1 3
      apps/app/src/components/Layout/BasicLayout.tsx
  3. 0 48
      apps/app/src/components/Layout/MainPane.tsx
  4. 3 0
      apps/app/src/components/Layout/PageViewLayout.module.scss
  5. 47 0
      apps/app/src/components/Layout/PageViewLayout.tsx
  6. 3 3
      apps/app/src/components/Page/PageView.tsx
  7. 3 3
      apps/app/src/components/ShareLinkPageView.tsx
  8. 1 0
      apps/app/src/styles/font-icons.scss
  9. 1 0
      packages/core/scss/placeholders/_flex-expand.scss
  10. 2 0
      packages/editor/package.json
  11. 0 5
      packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.module.scss
  12. 11 4
      packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx
  13. 37 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx
  14. 7 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  15. 7 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/EmojiButton.tsx
  16. 7 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TableButton.tsx
  17. 7 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TemplateButton.tsx
  18. 72 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx
  19. 3 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.module.scss
  20. 23 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.tsx
  21. 1 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/index.ts
  22. 10 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/scss/toolbar-button.scss
  23. 1 0
      packages/editor/src/components/CodeMirrorEditor/index.ts
  24. 4 0
      packages/editor/src/main.scss
  25. 4 4
      yarn.lock

+ 1 - 1
apps/app/package.json

@@ -240,7 +240,7 @@
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
     "load-css-file": "^1.0.0",
-    "material-icons": "^1.11.3",
+    "material-icons": "^1.13.10",
     "mongodb-memory-server": "^8.12.2",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",

+ 1 - 3
apps/app/src/components/Layout/BasicLayout.tsx

@@ -40,9 +40,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
 
           <div className="flex-expand-vert">{/* neccessary for nested {children} make expanded */}
             <AlertSiteUrlUndefined />
-            <div className="flex-expand-horiz">{/* neccessary for nested {children} make expanded */}
-              {children}
-            </div>
+            {children}
           </div>
         </div>
 

+ 0 - 48
apps/app/src/components/Layout/MainPane.tsx

@@ -1,48 +0,0 @@
-import { ReactNode } from 'react';
-
-
-type Props = {
-  children?: ReactNode,
-  sideContents?: ReactNode,
-  footerContents?: ReactNode,
-}
-
-export const MainPane = (props: Props): JSX.Element => {
-  const {
-    children, sideContents, footerContents,
-  } = props;
-
-  return (
-    <>
-      <div className="flex-grow-1">
-        <div id="main" className="main">
-          <div id="content-main" className="content-main container-lg grw-container-convertible">
-            { sideContents != null
-              ? (
-                <div className="d-flex flex-column flex-column-reverse flex-lg-row">
-                  <div className="flex-grow-1 flex-basis-0 mw-0">
-                    {children}
-                  </div>
-                  <div className="grw-side-contents-container d-edit-none" data-vrt-blackout-side-contents>
-                    <div className="grw-side-contents-sticky-container">
-                      {sideContents}
-                    </div>
-                  </div>
-                </div>
-              )
-              : (
-                <>{children}</>
-              )
-            }
-          </div>
-        </div>
-      </div>
-
-      { footerContents != null && (
-        <footer className="footer d-edit-none">
-          {footerContents}
-        </footer>
-      ) }
-    </>
-  );
-};

+ 3 - 0
apps/app/src/components/Layout/PageViewLayout.module.scss

@@ -0,0 +1,3 @@
+.page-view-layout :global {
+  min-height: calc(100vh - 116px - 250px); // 100vh - subnavigation height - page-comments-row minimum height
+}

+ 47 - 0
apps/app/src/components/Layout/PageViewLayout.tsx

@@ -0,0 +1,47 @@
+import type { ReactNode } from 'react';
+
+import styles from './PageViewLayout.module.scss';
+
+type Props = {
+  children?: ReactNode,
+  sideContents?: ReactNode,
+  footerContents?: ReactNode,
+}
+
+export const PageViewLayout = (props: Props): JSX.Element => {
+  const {
+    children, sideContents, footerContents,
+  } = props;
+
+  return (
+    <>
+      <div id="main" className={`main page-view-layout ${styles['page-view-layout']}`}>
+        <div id="content-main" className="content-main container-lg grw-container-convertible">
+          { sideContents != null
+            ? (
+              <div className="d-flex flex-column flex-column-reverse flex-lg-row">
+                <div className="flex-grow-1 flex-basis-0 mw-0">
+                  {children}
+                </div>
+                <div className="grw-side-contents-container d-edit-none" data-vrt-blackout-side-contents>
+                  <div className="grw-side-contents-sticky-container">
+                    {sideContents}
+                  </div>
+                </div>
+              </div>
+            )
+            : (
+              <>{children}</>
+            )
+          }
+        </div>
+      </div>
+
+      { footerContents != null && (
+        <footer className="footer d-edit-none">
+          {footerContents}
+        </footer>
+      ) }
+    </>
+  );
+};

+ 3 - 3
apps/app/src/components/Page/PageView.tsx

@@ -17,7 +17,7 @@ import { useIsMobile } from '~/stores/ui';
 
 
 import type { CommentsProps } from '../Comments';
-import { MainPane } from '../Layout/MainPane';
+import { PageViewLayout } from '../Layout/PageViewLayout';
 import { PageAlerts } from '../PageAlert/PageAlerts';
 import { PageContentFooter } from '../PageContentFooter';
 import type { PageSideContentsProps } from '../PageSideContents';
@@ -140,7 +140,7 @@ export const PageView = (props: Props): JSX.Element => {
   };
 
   return (
-    <MainPane
+    <PageViewLayout
       sideContents={sideContents}
       footerContents={footerContents}
     >
@@ -156,6 +156,6 @@ export const PageView = (props: Props): JSX.Element => {
         </>
       )}
 
-    </MainPane>
+    </PageViewLayout>
   );
 };

+ 3 - 3
apps/app/src/components/ShareLinkPageView.tsx

@@ -10,7 +10,7 @@ import { useIsNotFound } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
-import { MainPane } from './Layout/MainPane';
+import { PageViewLayout } from './Layout/PageViewLayout';
 import RevisionRenderer from './Page/RevisionRenderer';
 import ShareLinkAlert from './Page/ShareLinkAlert';
 import type { PageSideContentsProps } from './PageSideContents';
@@ -85,7 +85,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
   };
 
   return (
-    <MainPane
+    <PageViewLayout
       sideContents={sideContents}
     >
       { specialContents }
@@ -107,6 +107,6 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
           ) }
         </>
       ) }
-    </MainPane>
+    </PageViewLayout>
   );
 };

+ 1 - 0
apps/app/src/styles/font-icons.scss

@@ -3,4 +3,5 @@
 @import 'font-awesome';
 @import 'simple-line-icons';
 @import 'material-icons/iconfont/filled';
+@import 'material-icons/iconfont/outlined';
 @import '@icon/themify-icons/themify-icons';

+ 1 - 0
packages/core/scss/placeholders/_flex-expand.scss

@@ -12,5 +12,6 @@
   display: flex;
   flex: 1;
   flex-direction: column;
+  height: 100%;
   overflow-y: auto;
 }

+ 2 - 0
packages/editor/package.json

@@ -32,8 +32,10 @@
     "bootstrap": "^5.3.1",
     "codemirror": "^6.0.1",
     "eslint-plugin-react-refresh": "^0.4.1",
+    "material-icons": "^1.13.10",
     "react-hook-form": "^7.45.4",
     "react-toastify": "^9.1.3",
+    "reactstrap": "^9.2.0",
     "swr": "^2.2.2",
     "ts-deepmerge": "^6.2.0"
   }

+ 0 - 5
packages/editor/src/components/CodeMirrorEditor.module.scss → packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.module.scss

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

+ 11 - 4
packages/editor/src/components/CodeMirrorEditor.tsx → packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -4,14 +4,16 @@ import {
 
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 
-import { GlobalCodeMirrorEditorKey } from '../consts';
-import { useCodeMirrorEditorIsolated } from '../stores';
+import { GlobalCodeMirrorEditorKey } from '../../consts';
+import { useCodeMirrorEditorIsolated } from '../../stores';
+
+import { Toolbar } from './Toolbar';
 
 import style from './CodeMirrorEditor.module.scss';
 
 const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
   return (
-    <div {...props} className={`${style['codemirror-editor-container']}`} ref={ref} />
+    <div {...props} className={`flex-expand-vert ${style['codemirror-editor-container']}`} ref={ref} />
   );
 });
 
@@ -36,5 +38,10 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   }, [onChange]);
   useCodeMirrorEditorIsolated(editorKey, containerRef.current, cmProps);
 
-  return <CodeMirrorEditorContainer ref={containerRef} />;
+  return (
+    <div className="flex-expand-vert">
+      <CodeMirrorEditorContainer ref={containerRef} />
+      <Toolbar />
+    </div>
+  );
 };

+ 37 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx

@@ -0,0 +1,37 @@
+import {
+  UncontrolledDropdown,
+  DropdownToggle,
+  DropdownMenu,
+  DropdownItem,
+} from 'reactstrap';
+
+export const AttachmentsDropup = (): JSX.Element => {
+  return (
+    <>
+      <UncontrolledDropdown direction="up" className="lh-1">
+        <DropdownToggle className="btn-toolbar-button rounded-circle">
+          <span className="material-icons fs-6">add</span>
+        </DropdownToggle>
+        <DropdownMenu>
+          <DropdownItem className="d-flex gap-1 align-items-center" header>
+            <span className="material-icons-outlined fs-5">add_circle_outline</span>
+            Attachments
+          </DropdownItem>
+          <DropdownItem divider />
+          <DropdownItem className="d-flex gap-1 align-items-center">
+            <span className="material-icons-outlined fs-5">attach_file</span>
+            Files
+          </DropdownItem>
+          <DropdownItem className="d-flex gap-1 align-items-center">
+            <span className="material-icons-outlined fs-5">image</span>
+            Images
+          </DropdownItem>
+          <DropdownItem className="d-flex gap-1 align-items-center">
+            <span className="material-icons-outlined fs-5">link</span>
+            Link
+          </DropdownItem>
+        </DropdownMenu>
+      </UncontrolledDropdown>
+    </>
+  );
+};

+ 7 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx

@@ -0,0 +1,7 @@
+export const DiagramButton = (): JSX.Element => {
+  return (
+    <button type="button" className="btn btn-toolbar-button">
+      <span className="material-icons-outlined fs-6">lan</span>
+    </button>
+  );
+};

+ 7 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/EmojiButton.tsx

@@ -0,0 +1,7 @@
+export const EmojiButton = (): JSX.Element => {
+  return (
+    <button type="button" className="btn btn-toolbar-button">
+      <span className="material-icons-outlined fs-6">emoji_emotions</span>
+    </button>
+  );
+};

+ 7 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/TableButton.tsx

@@ -0,0 +1,7 @@
+export const TableButton = (): JSX.Element => {
+  return (
+    <button type="button" className="btn btn-toolbar-button">
+      <span className="material-icons-outlined fs-6">table_chart</span>
+    </button>
+  );
+};

+ 7 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/TemplateButton.tsx

@@ -0,0 +1,7 @@
+export const TemplateButton = (): JSX.Element => {
+  return (
+    <button type="button" className="btn btn-toolbar-button">
+      <span className="material-icons-outlined fs-6">file_copy</span>
+    </button>
+  );
+};

+ 72 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx

@@ -0,0 +1,72 @@
+import { useCallback, useState } from 'react';
+
+import { Collapse } from 'reactstrap';
+
+
+type TogglarProps = {
+  isOpen: boolean,
+  onClick?: () => void,
+}
+
+const TextFormatToolsToggler = (props: TogglarProps): JSX.Element => {
+
+  const { onClick } = props;
+
+  // TODO: change color by isOpen
+
+  return (
+    <button
+      type="button"
+      className="btn btn-toolbar-button"
+      onClick={onClick}
+    >
+      <span className="material-icons fs-5">text_increase</span>
+    </button>
+  );
+};
+
+export const TextFormatTools = (): JSX.Element => {
+  const [isOpen, setOpen] = useState(false);
+
+  const toggle = useCallback(() => {
+    setOpen(bool => !bool);
+  }, []);
+
+  return (
+    <div className="d-flex">
+      <TextFormatToolsToggler isOpen={isOpen} onClick={toggle} />
+
+      <Collapse isOpen={isOpen} horizontal>
+        <div className="d-flex px-1 gap-1" style={{ width: '220px' }}>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">format_bold</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">format_italic</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">format_strikethrough</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">block</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">code</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">format_list_bulleted</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">format_list_numbered</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">block</span>
+          </button>
+          <button type="button" className="btn btn-toolbar-button">
+            <span className="material-icons-outlined fs-5">checklist</span>
+          </button>
+        </div>
+      </Collapse>
+    </div>
+  );
+};

+ 3 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.module.scss

@@ -0,0 +1,3 @@
+.codemirror-editor-toolbar :global {
+  @import './scss/toolbar-button.scss';
+}

+ 23 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.tsx

@@ -0,0 +1,23 @@
+import { memo } from 'react';
+
+import { AttachmentsDropup } from './AttachmentsDropup';
+import { DiagramButton } from './DiagramButton';
+import { EmojiButton } from './EmojiButton';
+import { TableButton } from './TableButton';
+import { TemplateButton } from './TemplateButton';
+import { TextFormatTools } from './TextFormatTools';
+
+import styles from './Toolbar.module.scss';
+
+export const Toolbar = memo((): JSX.Element => {
+  return (
+    <div className={`d-flex gap-2 p-2 codemirror-editor-toolbar ${styles['codemirror-editor-toolbar']}`}>
+      <AttachmentsDropup />
+      <TextFormatTools />
+      <EmojiButton />
+      <TableButton />
+      <DiagramButton />
+      <TemplateButton />
+    </div>
+  );
+});

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

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

+ 10 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/scss/toolbar-button.scss

@@ -0,0 +1,10 @@
+.btn-toolbar-button {
+  width: 24px !important;
+  height: 24px !important;
+  padding: 0 !important;
+  font-size: 1rem !important;
+  line-height: 1 !important;
+  text-align: center !important;
+
+  border: 0;
+}

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

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

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

@@ -1,4 +1,8 @@
 @import 'bootstrap';
 @import 'react-toastify/scss/main';
 
+$material-icons-font-path: 'material-icons/iconfont/';
+@import 'material-icons/iconfont/filled';
+@import 'material-icons/iconfont/outlined';
+
 @import '@growi/core/scss/flex-expand';

+ 4 - 4
yarn.lock

@@ -11814,10 +11814,10 @@ markdown-table@^3.0.0:
   resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c"
   integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==
 
-material-icons@^1.11.3:
-  version "1.11.3"
-  resolved "https://registry.yarnpkg.com/material-icons/-/material-icons-1.11.3.tgz#a17ae4387df09d0fc117df19aecdcd07d2266769"
-  integrity sha512-pGkZ5LJOLH/R3jlqL2HXGVi36cucTn/RxSkPtqsinxB2xoDE0fZStwAUZlMKcW0z4qqDamBwVvzM1IVQe6OQDw==
+material-icons@^1.13.10:
+  version "1.13.10"
+  resolved "https://registry.yarnpkg.com/material-icons/-/material-icons-1.13.10.tgz#08f128aab3976c2f1ac03a35a0b030e945817291"
+  integrity sha512-XSESl/zo7XzD9nz8ihUq5HO0DzsvnVex9t8hpH8pqY1SFlESAdHlMQQbcCyyP58mPp6Wm1tyt0OaDNZhBT9lXQ==
 
 mathjax-full@^3.2.2:
   version "3.2.2"