Ver Fonte

Merge pull request #6546 from weseek/imprv/104022-draw-io-on-editor-navbar

Imprv/104022 show drawio modal on editor navbar
cao há 3 anos atrás
pai
commit
08ce5b23b3

+ 2 - 0
packages/app/src/components/Layout/BasicLayout.tsx

@@ -19,6 +19,7 @@ const PageDeleteModal = dynamic(() => import('../PageDeleteModal'), { ssr: false
 const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
 const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
 const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
 const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
 const PageAccessoriesModal = dynamic(() => import('../PageAccessoriesModal'), { ssr: false });
 const PageAccessoriesModal = dynamic(() => import('../PageAccessoriesModal'), { ssr: false });
+const DrawioModal = dynamic(() => import('../PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });
 // Fab
 // Fab
 const Fab = dynamic(() => import('../Fab').then(mod => mod.Fab), { ssr: false });
 const Fab = dynamic(() => import('../Fab').then(mod => mod.Fab), { ssr: false });
 
 
@@ -58,6 +59,7 @@ export const BasicLayout = ({
       <PageRenameModal />
       <PageRenameModal />
       <PagePresentationModal />
       <PagePresentationModal />
       <PageAccessoriesModal />
       <PageAccessoriesModal />
+      <DrawioModal />
       <HotkeysManager />
       <HotkeysManager />
 
 
       <Fab />
       <Fab />

+ 43 - 35
packages/app/src/components/Page.tsx

@@ -3,12 +3,14 @@ import React, {
   useEffect, useRef, useState,
   useEffect, useRef, useState,
 } from 'react';
 } from 'react';
 
 
+import EventEmitter from 'events';
+
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 // import { debounce } from 'throttle-debounce';
 // import { debounce } from 'throttle-debounce';
 
 
 import { HtmlElementNode } from 'rehype-toc';
 import { HtmlElementNode } from 'rehype-toc';
 
 
-// import { getOptionsToSave } from '~/client/util/editor';
+import { getOptionsToSave } from '~/client/util/editor';
 import {
 import {
   useIsGuestUser, useCurrentPageTocNode, useShareLinkId,
   useIsGuestUser, useCurrentPageTocNode, useShareLinkId,
 } from '~/stores/context';
 } from '~/stores/context';
@@ -23,11 +25,13 @@ import {
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import RevisionRenderer from './Page/RevisionRenderer';
 import RevisionRenderer from './Page/RevisionRenderer';
-
-// TODO: import dynamically
+import { DrawioModal } from './PageEditor/DrawioModal';
 // import MarkdownTable from '~/client/models/MarkdownTable';
 // import MarkdownTable from '~/client/models/MarkdownTable';
-// import mdu from './PageEditor/MarkdownDrawioUtil';
-// import mtu from './PageEditor/MarkdownTableUtil';
+import mdu from './PageEditor/MarkdownDrawioUtil';
+import mtu from './PageEditor/MarkdownTableUtil';
+
+
+declare const globalEmitter: EventEmitter;
 
 
 // const DrawioModal = dynamic(() => import('./PageEditor/DrawioModal'), { ssr: false });
 // const DrawioModal = dynamic(() => import('./PageEditor/DrawioModal'), { ssr: false });
 const GridEditModal = dynamic(() => import('./PageEditor/GridEditModal'), { ssr: false });
 const GridEditModal = dynamic(() => import('./PageEditor/GridEditModal'), { ssr: false });
@@ -132,35 +136,35 @@ class PageSubstance extends React.Component<PageSubstanceProps> {
   }
   }
 
 
   async saveHandlerForDrawioModal(drawioData) {
   async saveHandlerForDrawioModal(drawioData) {
-    // const {
-    //   isSlackEnabled, slackChannels, pageContainer, pageTags, grant, grantGroupId, grantGroupName, mutateIsEnabledUnsavedWarning,
-    // } = this.props;
-    // const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
-
-    // const newMarkdown = mdu.replaceDrawioInMarkdown(
-    //   drawioData,
-    //   this.props.pageContainer.state.markdown,
-    //   this.state.currentTargetDrawioArea.beginLineNumber,
-    //   this.state.currentTargetDrawioArea.endLineNumber,
-    // );
-
-    // try {
-    //   // disable unsaved warning
-    //   mutateIsEnabledUnsavedWarning(false);
-
-    //   // eslint-disable-next-line no-unused-vars
-    //   const { page, tags } = await pageContainer.save(newMarkdown, this.props.editorMode, optionsToSave);
-    //   logger.debug('success to save');
-
-    //   pageContainer.showSuccessToastr();
-    // }
-    // catch (error) {
-    //   logger.error('failed to save', error);
-    //   pageContainer.showErrorToastr(error);
-    // }
-    // finally {
-    //   this.setState({ currentTargetDrawioArea: null });
-    // }
+  //   const {
+  //     isSlackEnabled, slackChannels, pageContainer, pageTags, grant, grantGroupId, grantGroupName, mutateIsEnabledUnsavedWarning,
+  //   } = this.props;
+  //   const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
+
+    //   const newMarkdown = mdu.replaceDrawioInMarkdown(
+    //     drawioData,
+    //     this.props.pageContainer.state.markdown,
+    //     this.state.currentTargetDrawioArea.beginLineNumber,
+    //     this.state.currentTargetDrawioArea.endLineNumber,
+    //   );
+
+    //   try {
+    //     // disable unsaved warning
+    //     mutateIsEnabledUnsavedWarning(false);
+
+    //     // eslint-disable-next-line no-unused-vars
+    //     const { page, tags } = await pageContainer.save(newMarkdown, this.props.editorMode, optionsToSave);
+    //     logger.debug('success to save');
+
+  //     pageContainer.showSuccessToastr();
+  //   }
+  //   catch (error) {
+  //     logger.error('failed to save', error);
+  //     pageContainer.showErrorToastr(error);
+  //   }
+  //   finally {
+  //     this.setState({ currentTargetDrawioArea: null });
+  //   }
   }
   }
 
 
   override render() {
   override render() {
@@ -182,7 +186,11 @@ class PageSubstance extends React.Component<PageSubstanceProps> {
             <GridEditModal ref={this.gridEditModal} />
             <GridEditModal ref={this.gridEditModal} />
             <LinkEditModal ref={this.linkEditModal} />
             <LinkEditModal ref={this.linkEditModal} />
             {/* <HandsontableModal ref={this.handsontableModal} onSave={this.saveHandlerForHandsontableModal} /> */}
             {/* <HandsontableModal ref={this.handsontableModal} onSave={this.saveHandlerForHandsontableModal} /> */}
-            {/* <DrawioModal ref={this.drawioModal} onSave={this.saveHandlerForDrawioModal} /> */}
+            {/* TODO: use global DrawioModal https://redmine.weseek.co.jp/issues/105981 */}
+            {/* <DrawioModal
+              ref={this.drawioModal}
+              onSave={this.saveHandlerForDrawioModal}
+            /> */}
           </>
           </>
         )}
         )}
       </div>
       </div>

+ 19 - 16
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 
 
 import { createValidator } from '@growi/codemirror-textlint';
 import { createValidator } from '@growi/codemirror-textlint';
 import { commands } from 'codemirror';
 import { commands } from 'codemirror';
@@ -10,13 +10,14 @@ import * as loadScript from 'simple-load-script';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
 import InterceptorManager from '~/services/interceptor-manager';
 import InterceptorManager from '~/services/interceptor-manager';
+import { useDrawioModal } from '~/stores/modal';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 
 
 import AbstractEditor from './AbstractEditor';
 import AbstractEditor from './AbstractEditor';
 import CommentMentionHelper from './CommentMentionHelper';
 import CommentMentionHelper from './CommentMentionHelper';
-// import DrawioModal from './DrawioModal';
+import { DrawioModal } from './DrawioModal';
 import EditorIcon from './EditorIcon';
 import EditorIcon from './EditorIcon';
 import EmojiPicker from './EmojiPicker';
 import EmojiPicker from './EmojiPicker';
 import EmojiPickerHelper from './EmojiPickerHelper';
 import EmojiPickerHelper from './EmojiPickerHelper';
@@ -142,7 +143,6 @@ class CodeMirrorEditor extends AbstractEditor {
     this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
     this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
     this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
     this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
     this.showHandsonTableHandler = this.showHandsonTableHandler.bind(this);
     this.showHandsonTableHandler = this.showHandsonTableHandler.bind(this);
-    this.showDrawioHandler = this.showDrawioHandler.bind(this);
 
 
     this.foldDrawioSection = this.foldDrawioSection.bind(this);
     this.foldDrawioSection = this.foldDrawioSection.bind(this);
     this.onSaveForDrawio = this.onSaveForDrawio.bind(this);
     this.onSaveForDrawio = this.onSaveForDrawio.bind(this);
@@ -799,10 +799,6 @@ class CodeMirrorEditor extends AbstractEditor {
     // this.handsontableModal.current.show(mtu.getMarkdownTable(this.getCodeMirror()));
     // this.handsontableModal.current.show(mtu.getMarkdownTable(this.getCodeMirror()));
   }
   }
 
 
-  showDrawioHandler() {
-    // this.drawioModal.current.show(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()));
-  }
-
 
 
   // fold draw.io section (::: drawio ~ :::)
   // fold draw.io section (::: drawio ~ :::)
   foldDrawioSection() {
   foldDrawioSection() {
@@ -954,7 +950,7 @@ class CodeMirrorEditor extends AbstractEditor {
         color={null}
         color={null}
         bssize="small"
         bssize="small"
         title="draw.io"
         title="draw.io"
-        onClick={this.showDrawioHandler}
+        onClick={() => this.props.onClickDrawioBtn(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()))}
       >
       >
         <EditorIcon icon="Drawio" />
         <EditorIcon icon="Drawio" />
       </Button>,
       </Button>,
@@ -1059,17 +1055,11 @@ class CodeMirrorEditor extends AbstractEditor {
           ref={this.linkEditModal}
           ref={this.linkEditModal}
           onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
           onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
         />
         />
-        {/*
-        <HandsontableModal
+        {/* <HandsontableModal
           ref={this.handsontableModal}
           ref={this.handsontableModal}
           onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
           onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
           autoFormatMarkdownTable={this.props.editorSettings.autoFormatMarkdownTable}
           autoFormatMarkdownTable={this.props.editorSettings.autoFormatMarkdownTable}
         /> */}
         /> */}
-        {/* <DrawioModal
-          ref={this.drawioModal}
-          onSave={this.onSaveForDrawio}
-        /> */}
-
       </div>
       </div>
     );
     );
   }
   }
@@ -1088,4 +1078,17 @@ CodeMirrorEditor.defaultProps = {
   lineNumbers: true,
   lineNumbers: true,
 };
 };
 
 
-export default CodeMirrorEditor;
+
+const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
+  const { open: openDrawioModal } = useDrawioModal();
+
+  const openDrawioModalHandler = useCallback((drawioMxFile) => {
+    openDrawioModal(drawioMxFile);
+  }, [openDrawioModal]);
+
+  return <CodeMirrorEditor ref={ref} onClickDrawioBtn={openDrawioModalHandler} {...props} />;
+});
+
+CodeMirrorEditorFc.displayName = 'CodeMirrorEditorFc';
+
+export default CodeMirrorEditorFc;

+ 0 - 172
packages/app/src/components/PageEditor/DrawioModal.jsx

@@ -1,172 +0,0 @@
-import React from 'react';
-
-import i18next from 'i18next';
-import PropTypes from 'prop-types';
-import {
-  Modal,
-  ModalBody,
-} from 'reactstrap';
-
-import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
-
-class DrawioModal extends React.PureComponent {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      show: false,
-      drawioMxFile: '',
-    };
-
-    this.headerColor = '#334455';
-    this.fontFamily = "Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif";
-
-    this.init = this.init.bind(this);
-    this.cancel = this.cancel.bind(this);
-    this.receiveFromDrawio = this.receiveFromDrawio.bind(this);
-  }
-
-  init(drawioMxFile) {
-    const initDrawioMxFile = drawioMxFile;
-    this.setState(
-      {
-        drawioMxFile: initDrawioMxFile,
-      },
-    );
-  }
-
-  show(drawioMxFile) {
-    this.init(drawioMxFile);
-
-    window.addEventListener('message', this.receiveFromDrawio);
-    this.setState({ show: true });
-  }
-
-  hide() {
-    this.setState({
-      show: false,
-    });
-  }
-
-  cancel() {
-    this.hide();
-  }
-
-  receiveFromDrawio(event) {
-    if (event.data === 'ready') {
-      event.source.postMessage(this.state.drawioMxFile, '*');
-      return;
-    }
-
-    if (event.data === '{"event":"configure"}') {
-      if (event.source == null) {
-        return;
-      }
-
-      // refs:
-      //  * https://desk.draw.io/support/solutions/articles/16000103852-how-to-customise-the-draw-io-interface
-      //  * https://desk.draw.io/support/solutions/articles/16000042544-how-does-embed-mode-work-
-      //  * https://desk.draw.io/support/solutions/articles/16000058316-how-to-configure-draw-io-
-      event.source.postMessage(JSON.stringify({
-        action: 'configure',
-        config: {
-          css: `
-          .geMenubarContainer { background-color: ${this.headerColor} !important; }
-          .geMenubar { background-color: ${this.headerColor} !important; }
-          .geEditor { font-family: ${this.fontFamily} !important; }
-          html td.mxPopupMenuItem {
-            font-family: ${this.fontFamily} !important;
-            font-size: 8pt !important;
-          }
-          `,
-          customFonts: ['Lato', 'Charter'],
-        },
-      }), '*');
-
-      return;
-    }
-
-    if (typeof event.data === 'string' && event.data.match(/mxfile/)) {
-      if (event.data.length > 0) {
-        const parser = new DOMParser();
-        const dom = parser.parseFromString(event.data, 'text/xml');
-        const value = dom.getElementsByTagName('diagram')[0].innerHTML;
-
-        if (this.props.onSave != null) {
-          this.props.onSave(value);
-        }
-      }
-
-      window.removeEventListener('message', this.receiveFromDrawio);
-      this.hide();
-
-      return;
-    }
-
-    if (typeof event.data === 'string' && event.data.length === 0) {
-      window.removeEventListener('message', this.receiveFromDrawio);
-      this.hide();
-
-      return;
-    }
-
-    // NOTHING DONE. (Receive unknown iframe message.)
-  }
-
-  get drawioURL() {
-    const { config } = this.props.appContainer;
-
-    const drawioUri = config.env.DRAWIO_URI || 'https://embed.diagrams.net/';
-    const url = new URL(drawioUri);
-
-    // refs: https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported-
-    url.searchParams.append('spin', 1);
-    url.searchParams.append('embed', 1);
-    url.searchParams.append('lang', getDiagramsNetLangCode(i18next.language));
-    url.searchParams.append('ui', 'atlas');
-    url.searchParams.append('configure', 1);
-
-    return url;
-  }
-
-  render() {
-    return (
-      <Modal
-        isOpen={this.state.show}
-        toggle={this.cancel}
-        backdrop="static"
-        className="drawio-modal grw-body-only-modal-expanded"
-        size="xl"
-        keyboard={false}
-      >
-        <ModalBody className="p-0">
-          {/* Loading spinner */}
-          <div className="w-100 h-100 position-absolute d-flex">
-            <div className="mx-auto my-auto">
-              <i className="fa fa-3x fa-spinner fa-pulse mx-auto text-muted"></i>
-            </div>
-          </div>
-          {/* iframe */}
-          <div className="w-100 h-100 position-absolute d-flex">
-            { this.state.show && (
-              <iframe
-                src={this.drawioURL}
-                className="border-0 flex-grow-1"
-              >
-              </iframe>
-            ) }
-          </div>
-        </ModalBody>
-      </Modal>
-    );
-  }
-
-}
-
-DrawioModal.propTypes = {
-  onSave: PropTypes.func,
-};
-
-
-export default DrawioModal;

+ 77 - 0
packages/app/src/components/PageEditor/DrawioModal.tsx

@@ -0,0 +1,77 @@
+import React, {
+  useMemo,
+} from 'react';
+
+import {
+  Modal,
+  ModalBody,
+} from 'reactstrap';
+
+
+import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
+import { useDrawioUri } from '~/stores/context';
+import { useDrawioModal } from '~/stores/modal';
+import { usePersonalSettings } from '~/stores/personal-settings';
+
+
+type Props = {
+  // onSave: (drawioData) => void,
+};
+
+export const DrawioModal = (props: Props): JSX.Element => {
+  const { data: growiDrawioUri } = useDrawioUri();
+  const { data: personalSettingsInfo } = usePersonalSettings();
+
+
+  const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
+  const isOpened = drawioModalData?.isOpened ?? false;
+
+  const cancel = () => {
+    closeDrawioModal();
+  };
+
+  const drawioUrl = useMemo(() => {
+    const drawioUri = growiDrawioUri || 'https://embed.diagrams.net/';
+    const url = new URL(drawioUri);
+
+    // refs: https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported-
+    url.searchParams.append('spin', '1');
+    url.searchParams.append('embed', '1');
+    url.searchParams.append('lang', getDiagramsNetLangCode(personalSettingsInfo?.lang || 'en'));
+    url.searchParams.append('ui', 'atlas');
+    url.searchParams.append('configure', '1');
+
+    return url;
+  }, [growiDrawioUri, personalSettingsInfo?.lang]);
+
+
+  return (
+    <Modal
+      isOpen={isOpened}
+      toggle={cancel}
+      backdrop="static"
+      className="drawio-modal grw-body-only-modal-expanded"
+      size="xl"
+      keyboard={false}
+    >
+      <ModalBody className="p-0">
+        {/* Loading spinner */}
+        <div className="w-100 h-100 position-absolute d-flex">
+          <div className="mx-auto my-auto">
+            <i className="fa fa-3x fa-spinner fa-pulse mx-auto text-muted"></i>
+          </div>
+        </div>
+        {/* iframe */}
+        <div className="w-100 h-100 position-absolute d-flex">
+          { isOpened && (
+            <iframe
+              src={drawioUrl.href}
+              className="border-0 flex-grow-1"
+            >
+            </iframe>
+          ) }
+        </div>
+      </ModalBody>
+    </Modal>
+  );
+};

+ 8 - 2
packages/app/src/components/PageEditor/Editor.tsx

@@ -9,12 +9,14 @@ import {
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
+import { IEditorSettings } from '~/interfaces/editor-settings';
 import { useDefaultIndentSize } from '~/stores/context';
 import { useDefaultIndentSize } from '~/stores/context';
 import { useEditorSettings } from '~/stores/editor';
 import { useEditorSettings } from '~/stores/editor';
 import { useIsMobile } from '~/stores/ui';
 import { useIsMobile } from '~/stores/ui';
 
 
 import { IEditorMethods } from '../../interfaces/editor-methods';
 import { IEditorMethods } from '../../interfaces/editor-methods';
 
 
+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';
@@ -31,7 +33,11 @@ export type EditorPropsType = {
   isTextlintEnabled?: boolean,
   isTextlintEnabled?: boolean,
   onChange?: (newValue: string, isClean?: boolean) => void,
   onChange?: (newValue: string, isClean?: boolean) => void,
   onUpload?: (file) => void,
   onUpload?: (file) => void,
+  editorSettings?: IEditorSettings,
   indentSize?: number,
   indentSize?: number,
+  onDragEnter?: (event: any) => void,
+  onMarkdownHelpButtonClicked?: () => void,
+  onAddAttachmentButtonClicked?: () => void,
   onScroll?: ({ line: number }) => void,
   onScroll?: ({ line: number }) => void,
   onScrollCursorIntoView?: (line: number) => void,
   onScrollCursorIntoView?: (line: number) => void,
   onSave?: () => Promise<void>,
   onSave?: () => Promise<void>,
@@ -59,7 +65,8 @@ const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props
   const { data: isMobile } = useIsMobile();
   const { data: isMobile } = useIsMobile();
 
 
   const dropzoneRef = useRef<DropzoneRef>(null);
   const dropzoneRef = useRef<DropzoneRef>(null);
-  const cmEditorRef = useRef<CodeMirrorEditor>(null);
+  // CodeMirrorEditor ref
+  const cmEditorRef = useRef<AbstractEditor<any>>(null);
   const taEditorRef = useRef<TextAreaEditor>(null);
   const taEditorRef = useRef<TextAreaEditor>(null);
 
 
   const editorSubstance = useCallback(() => {
   const editorSubstance = useCallback(() => {
@@ -285,7 +292,6 @@ const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props
 
 
                 {/* for PC */}
                 {/* for PC */}
                 { !isMobile && (
                 { !isMobile && (
-                  // eslint-disable-next-line arrow-body-style
                   <CodeMirrorEditor
                   <CodeMirrorEditor
                     ref={cmEditorRef}
                     ref={cmEditorRef}
                     indentSize={indentSize ?? defaultIndentSize}
                     indentSize={indentSize ?? defaultIndentSize}

+ 0 - 7
packages/app/src/interfaces/services/renderer.ts

@@ -1,12 +1,5 @@
 import { XssOptionConfig } from '~/services/xss/xssOption';
 import { XssOptionConfig } from '~/services/xss/xssOption';
 
 
-// export type GrowiHydratedEnv = {
-//   DRAWIO_URI: string | null,
-//   HACKMD_URI: string | null,
-//   NO_CDN: string | null,
-//   GROWI_CLOUD_URI: string | null,
-//   GROWI_APP_ID_FOR_GROWI_CLOUD: string | null,
-// }
 
 
 export type RendererConfig = {
 export type RendererConfig = {
   isEnabledLinebreaks: boolean,
   isEnabledLinebreaks: boolean,

+ 6 - 7
packages/app/src/pages/[[...path]].page.tsx

@@ -58,7 +58,7 @@ import {
   useIsForbidden, useIsNotFound, useIsTrashPage, useIsSharedUser,
   useIsForbidden, useIsNotFound, useIsTrashPage, useIsSharedUser,
   useIsEnabledStaleNotification, useIsIdenticalPath,
   useIsEnabledStaleNotification, useIsIdenticalPath,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useDisableLinkSharing,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useDisableLinkSharing,
-  useHackmdUri,
+  useDrawioUri, useHackmdUri,
   useIsAclEnabled, useIsUserPage,
   useIsAclEnabled, useIsUserPage,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useIsSlackConfigured, useRendererConfig, useEditingMarkdown,
   useIsSlackConfigured, useRendererConfig, useEditingMarkdown,
@@ -147,9 +147,9 @@ type Props = CommonProps & {
   // isMailerSetup: boolean,
   // isMailerSetup: boolean,
   isAclEnabled: boolean,
   isAclEnabled: boolean,
   // hasSlackConfig: boolean,
   // hasSlackConfig: boolean,
-  // drawioUri: string,
+  drawioUri: string,
   hackmdUri: string,
   hackmdUri: string,
-  // noCdn: string,
+  noCdn: string,
   // highlightJsStyle: string,
   // highlightJsStyle: string,
   isAllReplyShown: boolean,
   isAllReplyShown: boolean,
   // isContainerFluid: boolean,
   // isContainerFluid: boolean,
@@ -213,12 +213,11 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useIsMailerSetup(props.isMailerSetup);
   // useIsMailerSetup(props.isMailerSetup);
   useIsAclEnabled(props.isAclEnabled);
   useIsAclEnabled(props.isAclEnabled);
   // useHasSlackConfig(props.hasSlackConfig);
   // useHasSlackConfig(props.hasSlackConfig);
-  // useDrawioUri(props.drawioUri);
+  useDrawioUri(props.drawioUri);
   useHackmdUri(props.hackmdUri);
   useHackmdUri(props.hackmdUri);
   // useNoCdn(props.noCdn);
   // useNoCdn(props.noCdn);
   // useIndentSize(props.adminPreferredIndentSize);
   // useIndentSize(props.adminPreferredIndentSize);
   useDisableLinkSharing(props.disableLinkSharing);
   useDisableLinkSharing(props.disableLinkSharing);
-
   useRendererConfig(props.rendererConfig);
   useRendererConfig(props.rendererConfig);
   // useRendererSettings(props.rendererSettingsStr != null ? JSON.parse(props.rendererSettingsStr) : undefined);
   // useRendererSettings(props.rendererSettingsStr != null ? JSON.parse(props.rendererSettingsStr) : undefined);
   // useGrowiRendererConfig(props.growiRendererConfigStr != null ? JSON.parse(props.growiRendererConfigStr) : undefined);
   // useGrowiRendererConfig(props.growiRendererConfigStr != null ? JSON.parse(props.growiRendererConfigStr) : undefined);
@@ -491,9 +490,9 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   // props.isMailerSetup = mailService.isMailerSetup;
   // props.isMailerSetup = mailService.isMailerSetup;
   props.isAclEnabled = aclService.isAclEnabled();
   props.isAclEnabled = aclService.isAclEnabled();
   // props.hasSlackConfig = slackNotificationService.hasSlackConfig();
   // props.hasSlackConfig = slackNotificationService.hasSlackConfig();
-  // props.drawioUri = configManager.getConfig('crowi', 'app:drawioUri');
+  props.drawioUri = configManager.getConfig('crowi', 'app:drawioUri');
   props.hackmdUri = configManager.getConfig('crowi', 'app:hackmdUri');
   props.hackmdUri = configManager.getConfig('crowi', 'app:hackmdUri');
-  // props.noCdn = configManager.getConfig('crowi', 'app:noCdn');
+  props.noCdn = configManager.getConfig('crowi', 'app:noCdn');
   // props.highlightJsStyle = configManager.getConfig('crowi', 'customize:highlightJsStyle');
   // props.highlightJsStyle = configManager.getConfig('crowi', 'customize:highlightJsStyle');
   props.isAllReplyShown = configManager.getConfig('crowi', 'customize:isAllReplyShown');
   props.isAllReplyShown = configManager.getConfig('crowi', 'customize:isAllReplyShown');
   // props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');
   // props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');

+ 4 - 0
packages/app/src/stores/context.tsx

@@ -130,6 +130,10 @@ export const useRevisionIdHackmdSynced = (initialData?: Nullable<any>): SWRRespo
   return useStaticSWR<Nullable<any>, Error>('revisionIdHackmdSynced', initialData);
   return useStaticSWR<Nullable<any>, Error>('revisionIdHackmdSynced', initialData);
 };
 };
 
 
+export const useDrawioUri = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
+  return useStaticSWR<Nullable<string>, Error>('drawioUri', initialData);
+};
+
 export const useHackmdUri = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
 export const useHackmdUri = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
   return useStaticSWR<Nullable<string>, Error>('hackmdUri', initialData);
   return useStaticSWR<Nullable<string>, Error>('hackmdUri', initialData);
 };
 };

+ 97 - 0
packages/app/src/stores/modal.tsx

@@ -5,6 +5,7 @@ import {
   OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
   OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
 } from '~/interfaces/ui';
 } from '~/interfaces/ui';
 import { IUserGroupHasId } from '~/interfaces/user';
 import { IUserGroupHasId } from '~/interfaces/user';
+import { dwawioConfig } from '~/utils/drawio-config';
 
 
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
 
 
@@ -438,3 +439,99 @@ export const useShortcutsModal = (): SWRResponse<ShortcutsModalStatus, Error> &
     },
     },
   };
   };
 };
 };
+
+
+/*
+* DrawioModal
+*/
+
+type DrawioModalStatus = {
+  isOpened: boolean,
+  drawioMxFile: string,
+}
+
+type DrawioModalStatusUtils = {
+  open(drawioMxFile: string): void,
+  close(): void,
+  receiveFromDrawio(event, drawioMxFile: string): void,
+}
+
+export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioModalStatus, Error> & DrawioModalStatusUtils => {
+  const initialData: DrawioModalStatus = {
+    isOpened: false,
+    drawioMxFile: '',
+  };
+  const swrResponse = useStaticSWR<DrawioModalStatus, Error>('drawioModalStatus', status, { fallbackData: initialData });
+
+  const close = (): void => {
+    swrResponse.mutate({ isOpened: false, drawioMxFile: '' });
+  };
+
+  const receiveFromDrawio = (event, drawioMxFile: string) => {
+
+    if (event.data === 'ready') {
+      event.source.postMessage(drawioMxFile, '*');
+      return;
+    }
+
+    if (event.data === '{"event":"configure"}') {
+      if (event.source == null) {
+        return;
+      }
+
+      // refs:
+      //  * https://desk.draw.io/support/solutions/articles/16000103852-how-to-customise-the-draw-io-interface
+      //  * https://desk.draw.io/support/solutions/articles/16000042544-how-does-embed-mode-work-
+      //  * https://desk.draw.io/support/solutions/articles/16000058316-how-to-configure-draw-io-
+      event.source.postMessage(JSON.stringify({
+        action: 'configure',
+        config: dwawioConfig,
+      }), '*');
+
+      return;
+    }
+
+    if (typeof event.data === 'string' && event.data.match(/mxfile/)) {
+      if (event.data.length > 0) {
+        const parser = new DOMParser();
+        const dom = parser.parseFromString(event.data, 'text/xml');
+        const drawioData = dom.getElementsByTagName('diagram')[0].innerHTML;
+
+        /*
+        * Saving Drawio will be implemented by the following tasks
+        * https://redmine.weseek.co.jp/issues/100845
+        * https://redmine.weseek.co.jp/issues/104507
+        */
+
+        // if (props.onSave != null) {
+        //   props.onSave(drawioData);
+        // }
+      }
+
+      window.removeEventListener('message', () => receiveFromDrawio);
+      close();
+
+      return;
+    }
+
+    if (typeof event.data === 'string' && event.data.length === 0) {
+      close();
+
+      return;
+    }
+
+    // NOTHING DONE. (Receive unknown iframe message.)
+  };
+
+  const open = (drawioMxFile: string): void => {
+    window.addEventListener('message', e => receiveFromDrawio(e, drawioMxFile));
+    swrResponse.mutate({ isOpened: true, drawioMxFile });
+  };
+
+  return {
+    ...swrResponse,
+    open,
+    close,
+    receiveFromDrawio,
+  };
+};

+ 15 - 0
packages/app/src/utils/drawio-config.ts

@@ -0,0 +1,15 @@
+const headerColor = '#334455';
+const fontFamily = "Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif";
+
+export const dwawioConfig = {
+  css: `
+  .geMenubarContainer { background-color: ${headerColor} !important; }
+  .geMenubar { background-color: ${headerColor} !important; }
+  .geEditor { font-family: ${fontFamily} !important; }
+  html td.mxPopupMenuItem {
+    font-family: ${fontFamily} !important;
+    font-size: 8pt !important;
+  }
+  `,
+  customFonts: ['Lato', 'Charter'],
+};