Przeglądaj źródła

Merge pull request #6246 from weseek/feat/activate-page

feat: Activate page
Yuki Takei 3 lat temu
rodzic
commit
e224198a98

+ 0 - 1
packages/app/.env.development

@@ -7,7 +7,6 @@ MIGRATIONS_DIR=src/migrations/
 APP_SITE_URL=http://localhost:3000
 APP_SITE_URL=http://localhost:3000
 FILE_UPLOAD=mongodb
 FILE_UPLOAD=mongodb
 # MONGO_GRIDFS_TOTAL_LIMIT=10485760
 # MONGO_GRIDFS_TOTAL_LIMIT=10485760
-MATHJAX=1
 # NO_CDN=true
 # NO_CDN=true
 MONGO_URI="mongodb://mongo:27017/growi"
 MONGO_URI="mongodb://mongo:27017/growi"
 # REDIS_URI="http://redis:6379"
 # REDIS_URI="http://redis:6379"

+ 4 - 4
packages/app/src/client/models/MarkdownTable.js

@@ -1,6 +1,6 @@
+import csvToMarkdown from 'csv-to-markdown-table';
 import markdownTable from 'markdown-table';
 import markdownTable from 'markdown-table';
 import stringWidth from 'string-width';
 import stringWidth from 'string-width';
-import csvToMarkdown from 'csv-to-markdown-table';
 
 
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
 // https://regex101.com/r/7BN2fR/7
 // https://regex101.com/r/7BN2fR/7
@@ -8,9 +8,6 @@ const tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
 const tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
 const tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
 const linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
 const linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
 
 
-// set up DOMParser
-const domParser = new (window.DOMParser)();
-
 const defaultOptions = { stringLength: stringWidth };
 const defaultOptions = { stringLength: stringWidth };
 
 
 /**
 /**
@@ -67,6 +64,9 @@ export default class MarkdownTable {
    * The error message is a innerHTML, so must not assign it into element.innerHTML because it can lead to Mutation-based XSS
    * The error message is a innerHTML, so must not assign it into element.innerHTML because it can lead to Mutation-based XSS
    */
    */
   static fromHTMLTableTag(str) {
   static fromHTMLTableTag(str) {
+    // set up DOMParser
+    const domParser = new (window.DOMParser)();
+
     // use DOMParser to prevent DOM based XSS (https://developer.mozilla.org/en-US/docs/Web/API/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');
     const dom = domParser.parseFromString(str, 'application/xml');
 
 

+ 121 - 136
packages/app/src/components/Page.jsx

@@ -1,37 +1,31 @@
 import React, { useEffect, useRef } from 'react';
 import React, { useEffect, useRef } from 'react';
 
 
+import dynamic from 'next/dynamic';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 import MarkdownTable from '~/client/models/MarkdownTable';
-import AppContainer from '~/client/services/AppContainer';
-import EditorContainer from '~/client/services/EditorContainer';
-import PageContainer from '~/client/services/PageContainer';
 import { getOptionsToSave } from '~/client/util/editor';
 import { getOptionsToSave } from '~/client/util/editor';
 import GrowiRenderer from '~/services/renderer/growi-renderer';
 import GrowiRenderer from '~/services/renderer/growi-renderer';
 import {
 import {
-  useCurrentPagePath, useIsGuestUser,
+  useIsGuestUser,
 } from '~/stores/context';
 } from '~/stores/context';
 import {
 import {
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
 } from '~/stores/editor';
 } from '~/stores/editor';
+import { useSWRxCurrentPage } from '~/stores/page';
 import { useViewRenderer } from '~/stores/renderer';
 import { useViewRenderer } from '~/stores/renderer';
 import {
 import {
-  useEditorMode, useIsMobile, useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useEditorMode, useIsMobile,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import RevisionRenderer from './Page/RevisionRenderer';
 import RevisionRenderer from './Page/RevisionRenderer';
-import DrawioModal from './PageEditor/DrawioModal';
-import GridEditModal from './PageEditor/GridEditModal';
-import HandsontableModal from './PageEditor/HandsontableModal';
-import LinkEditModal from './PageEditor/LinkEditModal';
 import mdu from './PageEditor/MarkdownDrawioUtil';
 import mdu from './PageEditor/MarkdownDrawioUtil';
 import mtu from './PageEditor/MarkdownTableUtil';
 import mtu from './PageEditor/MarkdownTableUtil';
-import { withUnstatedContainers } from './UnstatedUtils';
 
 
 const logger = loggerFactory('growi:Page');
 const logger = loggerFactory('growi:Page');
 
 
-class Page extends React.Component {
+class PageSubstance extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
@@ -56,10 +50,10 @@ class Page extends React.Component {
    * @param endLineNumber
    * @param endLineNumber
    */
    */
   launchHandsontableModal(beginLineNumber, endLineNumber) {
   launchHandsontableModal(beginLineNumber, endLineNumber) {
-    const markdown = this.props.pageContainer.state.markdown;
-    const tableLines = markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber).join('\n');
-    this.setState({ currentTargetTableArea: { beginLineNumber, endLineNumber } });
-    this.handsontableModal.current.show(MarkdownTable.fromMarkdownString(tableLines));
+    // const markdown = this.props.pageContainer.state.markdown;
+    // const tableLines = markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber).join('\n');
+    // this.setState({ currentTargetTableArea: { beginLineNumber, endLineNumber } });
+    // this.handsontableModal.current.show(MarkdownTable.fromMarkdownString(tableLines));
   }
   }
 
 
   /**
   /**
@@ -68,96 +62,102 @@ class Page extends React.Component {
    * @param endLineNumber
    * @param endLineNumber
    */
    */
   launchDrawioModal(beginLineNumber, endLineNumber) {
   launchDrawioModal(beginLineNumber, endLineNumber) {
-    const markdown = this.props.pageContainer.state.markdown;
-    const drawioMarkdownArray = markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber);
-    const drawioData = drawioMarkdownArray.slice(1, drawioMarkdownArray.length - 1).join('\n').trim();
-    this.setState({ currentTargetDrawioArea: { beginLineNumber, endLineNumber } });
-    this.drawioModal.current.show(drawioData);
+    // const markdown = this.props.pageContainer.state.markdown;
+    // const drawioMarkdownArray = markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber);
+    // const drawioData = drawioMarkdownArray.slice(1, drawioMarkdownArray.length - 1).join('\n').trim();
+    // this.setState({ currentTargetDrawioArea: { beginLineNumber, endLineNumber } });
+    // this.drawioModal.current.show(drawioData);
   }
   }
 
 
   async saveHandlerForHandsontableModal(markdownTable) {
   async saveHandlerForHandsontableModal(markdownTable) {
-    const {
-      isSlackEnabled, slackChannels, pageContainer, mutateIsEnabledUnsavedWarning, grant, grantGroupId, grantGroupName, pageTags,
-    } = this.props;
-    const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
-
-    const newMarkdown = mtu.replaceMarkdownTableInMarkdown(
-      markdownTable,
-      this.props.pageContainer.state.markdown,
-      this.state.currentTargetTableArea.beginLineNumber,
-      this.state.currentTargetTableArea.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({ currentTargetTableArea: null });
-    }
+    // const {
+    //   isSlackEnabled, slackChannels, pageContainer, mutateIsEnabledUnsavedWarning, grant, grantGroupId, grantGroupName, pageTags,
+    // } = this.props;
+    // const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
+
+    // const newMarkdown = mtu.replaceMarkdownTableInMarkdown(
+    //   markdownTable,
+    //   this.props.pageContainer.state.markdown,
+    //   this.state.currentTargetTableArea.beginLineNumber,
+    //   this.state.currentTargetTableArea.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({ currentTargetTableArea: null });
+    // }
   }
   }
 
 
   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 });
+    // }
   }
   }
 
 
   render() {
   render() {
     const {
     const {
-      pageContainer, pagePath, isMobile, isGuestUser,
+      page, isMobile, isGuestUser,
     } = this.props;
     } = this.props;
-    const { markdown, revisionId } = pageContainer.state;
+    const { path } = page;
+    const { _id: revisionId, body: markdown } = page.revision;
+
+    // const DrawioModal = dynamic(() => import('./PageEditor/DrawioModal'), { ssr: false });
+    // const GridEditModal = dynamic(() => import('./PageEditor/GridEditModal'), { ssr: false });
+    // const HandsontableModal = dynamic(() => import('./PageEditor/HandsontableModal'), { ssr: false });
+    // const LinkEditModal = dynamic(() => import('./PageEditor/LinkEditModal'), { ssr: false });
 
 
     return (
     return (
       <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
       <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
 
 
         { revisionId != null && (
         { revisionId != null && (
-          <RevisionRenderer growiRenderer={this.props.growiRenderer} markdown={markdown} pagePath={pagePath} />
+          <RevisionRenderer growiRenderer={this.props.growiRenderer} markdown={markdown} pagePath={path} />
         )}
         )}
 
 
         { !isGuestUser && (
         { !isGuestUser && (
           <>
           <>
-            <GridEditModal ref={this.gridEditModal} />
-            <LinkEditModal ref={this.LinkEditModal} />
-            <HandsontableModal ref={this.handsontableModal} onSave={this.saveHandlerForHandsontableModal} />
-            <DrawioModal ref={this.drawioModal} onSave={this.saveHandlerForDrawioModal} />
+            {/* <GridEditModal ref={this.gridEditModal} /> */}
+            {/* <LinkEditModal ref={this.LinkEditModal} /> */}
+            {/* <HandsontableModal ref={this.handsontableModal} onSave={this.saveHandlerForHandsontableModal} /> */}
+            {/* <DrawioModal ref={this.drawioModal} onSave={this.saveHandlerForDrawioModal} /> */}
           </>
           </>
         )}
         )}
       </div>
       </div>
@@ -166,92 +166,77 @@ class Page extends React.Component {
 
 
 }
 }
 
 
-Page.propTypes = {
-  // TODO: remove this when omitting unstated is completed
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
+PageSubstance.propTypes = {
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
 
 
-  pagePath: PropTypes.string.isRequired,
+  page: PropTypes.any.isRequired,
   pageTags:  PropTypes.arrayOf(PropTypes.string),
   pageTags:  PropTypes.arrayOf(PropTypes.string),
   editorMode: PropTypes.string.isRequired,
   editorMode: PropTypes.string.isRequired,
   isGuestUser: PropTypes.bool.isRequired,
   isGuestUser: PropTypes.bool.isRequired,
   isMobile: PropTypes.bool,
   isMobile: PropTypes.bool,
   isSlackEnabled: PropTypes.bool.isRequired,
   isSlackEnabled: PropTypes.bool.isRequired,
   slackChannels: PropTypes.string.isRequired,
   slackChannels: PropTypes.string.isRequired,
-  grant: PropTypes.number.isRequired,
-  grantGroupId: PropTypes.string,
-  grantGroupName: PropTypes.string,
 };
 };
 
 
-const PageWrapper = (props) => {
-  const { data: currentPagePath } = useCurrentPagePath();
+export const Page = (props) => {
+  const { data: currentPage } = useSWRxCurrentPage();
   const { data: editorMode } = useEditorMode();
   const { data: editorMode } = useEditorMode();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isMobile } = useIsMobile();
   const { data: isMobile } = useIsMobile();
-  const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
+  const { data: slackChannelsData } = useSWRxSlackChannels(currentPage?.path);
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: pageTags } = usePageTagsForEditors();
   const { data: pageTags } = usePageTagsForEditors();
-  const { data: grant } = useSelectedGrant();
-  const { data: grantGroupId } = useSelectedGrantGroupId();
-  const { data: grantGroupName } = useSelectedGrantGroupName();
   const { data: growiRenderer } = useViewRenderer();
   const { data: growiRenderer } = useViewRenderer();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
 
 
   const pageRef = useRef(null);
   const pageRef = useRef(null);
 
 
-  // set handler to open DrawioModal
-  useEffect(() => {
-    const handler = (beginLineNumber, endLineNumber) => {
-      if (pageRef?.current != null) {
-        pageRef.current.launchDrawioModal(beginLineNumber, endLineNumber);
-      }
-    };
-    window.globalEmitter.on('launchDrawioModal', handler);
-
-    return function cleanup() {
-      window.globalEmitter.removeListener('launchDrawioModal', handler);
-    };
-  }, []);
-
-  // set handler to open HandsontableModal
-  useEffect(() => {
-    const handler = (beginLineNumber, endLineNumber) => {
-      if (pageRef?.current != null) {
-        pageRef.current.launchHandsontableModal(beginLineNumber, endLineNumber);
-      }
-    };
-    window.globalEmitter.on('launchHandsontableModal', handler);
-
-    return function cleanup() {
-      window.globalEmitter.removeListener('launchHandsontableModal', handler);
-    };
-  }, []);
-
-  if (currentPagePath == null || editorMode == null || isGuestUser == null || growiRenderer == null) {
+  // // set handler to open DrawioModal
+  // useEffect(() => {
+  //   const handler = (beginLineNumber, endLineNumber) => {
+  //     if (pageRef?.current != null) {
+  //       pageRef.current.launchDrawioModal(beginLineNumber, endLineNumber);
+  //     }
+  //   };
+  //   window.globalEmitter.on('launchDrawioModal', handler);
+
+  //   return function cleanup() {
+  //     window.globalEmitter.removeListener('launchDrawioModal', handler);
+  //   };
+  // }, []);
+
+  // // set handler to open HandsontableModal
+  // useEffect(() => {
+  //   const handler = (beginLineNumber, endLineNumber) => {
+  //     if (pageRef?.current != null) {
+  //       pageRef.current.launchHandsontableModal(beginLineNumber, endLineNumber);
+  //     }
+  //   };
+  //   window.globalEmitter.on('launchHandsontableModal', handler);
+
+  //   return function cleanup() {
+  //     window.globalEmitter.removeListener('launchHandsontableModal', handler);
+  //   };
+  // }, []);
+
+  if (currentPage == null || editorMode == null || isGuestUser == null || growiRenderer == null) {
     return null;
     return null;
   }
   }
 
 
 
 
   return (
   return (
-    <Page
+    <PageSubstance
       {...props}
       {...props}
       ref={pageRef}
       ref={pageRef}
       growiRenderer={growiRenderer}
       growiRenderer={growiRenderer}
-      pagePath={currentPagePath}
+      page={currentPage}
       editorMode={editorMode}
       editorMode={editorMode}
       isGuestUser={isGuestUser}
       isGuestUser={isGuestUser}
       isMobile={isMobile}
       isMobile={isMobile}
       isSlackEnabled={isSlackEnabled}
       isSlackEnabled={isSlackEnabled}
       pageTags={pageTags}
       pageTags={pageTags}
       slackChannels={slackChannelsData.toString()}
       slackChannels={slackChannelsData.toString()}
-      grant={grant}
-      grantGroupId={grantGroupId}
-      grantGroupName={grantGroupName}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
     />
     />
   );
   );
 };
 };
-
-export default withUnstatedContainers(PageWrapper, [AppContainer, PageContainer, EditorContainer]);

+ 2 - 3
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -17,7 +17,7 @@ import { EditorMode, useEditorMode } from '~/stores/ui';
 import CountBadge from '../Common/CountBadge';
 import CountBadge from '../Common/CountBadge';
 import PageListIcon from '../Icons/PageListIcon';
 import PageListIcon from '../Icons/PageListIcon';
 import NotFoundPage from '../NotFoundPage';
 import NotFoundPage from '../NotFoundPage';
-// import Page from '../Page';
+import { Page } from '../Page';
 // import PageEditor from '../PageEditor';
 // import PageEditor from '../PageEditor';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 import TableOfContents from '../TableOfContents';
 import TableOfContents from '../TableOfContents';
@@ -114,8 +114,7 @@ const DisplaySwitcher = (): JSX.Element => {
 
 
             <div className="flex-grow-1 flex-basis-0 mw-0">
             <div className="flex-grow-1 flex-basis-0 mw-0">
               { isUserPage && <UserInfo pageUser={pageUser} />}
               { isUserPage && <UserInfo pageUser={pageUser} />}
-              {/* { !isNotFound && <Page /> } */}
-              { !isNotFound && revision != null && isPopulated(revision) && revision.body }
+              { !isNotFound && <Page /> }
               { isNotFound && <NotFoundPage /> }
               { isNotFound && <NotFoundPage /> }
             </div>
             </div>
 
 

+ 0 - 1
packages/app/src/components/Page/RevisionBody.jsx

@@ -73,7 +73,6 @@ export default class RevisionBody extends React.PureComponent {
 
 
 RevisionBody.propTypes = {
 RevisionBody.propTypes = {
   html: PropTypes.string,
   html: PropTypes.string,
-  isMathJaxEnabled: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   renderMathJaxInRealtime: PropTypes.bool,
   renderMathJaxInRealtime: PropTypes.bool,
   additionalClassName: PropTypes.string,
   additionalClassName: PropTypes.string,

+ 13 - 21
packages/app/src/components/Page/RevisionRenderer.jsx

@@ -1,18 +1,17 @@
 import React from 'react';
 import React from 'react';
 
 
+import { loggerFactory } from '^/../codemirror-textlint/src/utils/logger';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import AppContainer from '~/client/services/AppContainer';
 import { blinkElem } from '~/client/util/blink-section-header';
 import { blinkElem } from '~/client/util/blink-section-header';
 import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
 import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
+import InterceptorManager from '~/services/interceptor-manager';
 import GrowiRenderer from '~/services/renderer/growi-renderer';
 import GrowiRenderer from '~/services/renderer/growi-renderer';
+import { useInterceptorManager } from '~/stores/context';
 import { useEditorSettings } from '~/stores/editor';
 import { useEditorSettings } from '~/stores/editor';
 
 
-import { withUnstatedContainers } from '../UnstatedUtils';
-
 import RevisionBody from './RevisionBody';
 import RevisionBody from './RevisionBody';
 
 
-import { loggerFactory } from '^/../codemirror-textlint/src/utils/logger';
 
 
 const logger = loggerFactory('components:Page:RevisionRenderer');
 const logger = loggerFactory('components:Page:RevisionRenderer');
 
 
@@ -45,7 +44,7 @@ class LegacyRevisionRenderer extends React.PureComponent {
 
 
   componentDidUpdate(prevProps) {
   componentDidUpdate(prevProps) {
     const { markdown: prevMarkdown, highlightKeywords: prevHighlightKeywords } = prevProps;
     const { markdown: prevMarkdown, highlightKeywords: prevHighlightKeywords } = prevProps;
-    const { markdown, highlightKeywords } = this.props;
+    const { markdown, highlightKeywords, interceptorManager } = this.props;
 
 
     // render only when props.markdown is updated
     // render only when props.markdown is updated
     if (markdown !== prevMarkdown || highlightKeywords !== prevHighlightKeywords) {
     if (markdown !== prevMarkdown || highlightKeywords !== prevHighlightKeywords) {
@@ -58,8 +57,6 @@ class LegacyRevisionRenderer extends React.PureComponent {
     const HeaderLinkArray = Array.from(HeaderLink);
     const HeaderLinkArray = Array.from(HeaderLink);
     addSmoothScrollEvent(HeaderLinkArray, blinkElem);
     addSmoothScrollEvent(HeaderLinkArray, blinkElem);
 
 
-    const { interceptorManager } = window;
-
     interceptorManager.process('postRenderHtml', this.currentRenderingContext);
     interceptorManager.process('postRenderHtml', this.currentRenderingContext);
   }
   }
 
 
@@ -130,11 +127,15 @@ class LegacyRevisionRenderer extends React.PureComponent {
 
 
   async renderHtml() {
   async renderHtml() {
     const {
     const {
-      appContainer, growiRenderer,
+      interceptorManager,
+      growiRenderer,
       highlightKeywords,
       highlightKeywords,
     } = this.props;
     } = this.props;
 
 
-    const { interceptorManager } = window;
+    if (interceptorManager == null || growiRenderer == null) {
+      return;
+    }
+
     const context = this.currentRenderingContext;
     const context = this.currentRenderingContext;
 
 
     await interceptorManager.process('preRender', context);
     await interceptorManager.process('preRender', context);
@@ -156,13 +157,9 @@ class LegacyRevisionRenderer extends React.PureComponent {
   }
   }
 
 
   render() {
   render() {
-    const config = this.props.appContainer.getConfig();
-    const isMathJaxEnabled = !!config.env.MATHJAX;
-
     return (
     return (
       <RevisionBody
       <RevisionBody
         html={this.state.html}
         html={this.state.html}
-        isMathJaxEnabled={isMathJaxEnabled}
         additionalClassName={this.props.additionalClassName}
         additionalClassName={this.props.additionalClassName}
         renderMathJaxOnInit
         renderMathJaxOnInit
       />
       />
@@ -172,7 +169,7 @@ class LegacyRevisionRenderer extends React.PureComponent {
 }
 }
 
 
 LegacyRevisionRenderer.propTypes = {
 LegacyRevisionRenderer.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  interceptorManager: PropTypes.instanceOf(InterceptorManager).isRequired,
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
   markdown: PropTypes.string.isRequired,
   markdown: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
@@ -181,17 +178,12 @@ LegacyRevisionRenderer.propTypes = {
   editorSettings: PropTypes.any,
   editorSettings: PropTypes.any,
 };
 };
 
 
-/**
- * Wrapper component for using unstated
- */
-const LegacyRevisionRendererWrapper = withUnstatedContainers(LegacyRevisionRenderer, [AppContainer]);
-
-
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 const RevisionRenderer = (props) => {
 const RevisionRenderer = (props) => {
+  const { data: interceptorManager } = useInterceptorManager();
   const { data: editorSettings } = useEditorSettings();
   const { data: editorSettings } = useEditorSettings();
 
 
-  return <LegacyRevisionRendererWrapper {...props} editorSettings={editorSettings} />;
+  return <LegacyRevisionRenderer {...props} interceptorManager={interceptorManager} editorSettings={editorSettings} />;
 };
 };
 
 
 RevisionRenderer.propTypes = {
 RevisionRenderer.propTypes = {

+ 0 - 1
packages/app/src/components/PageEditor/Preview.tsx

@@ -19,7 +19,6 @@ type Props = {
   growiRenderer: GrowiRenderer,
   growiRenderer: GrowiRenderer,
   markdown?: string,
   markdown?: string,
   pagePath?: string,
   pagePath?: string,
-  isMathJaxEnabled?: boolean,
   renderMathJaxOnInit?: boolean,
   renderMathJaxOnInit?: boolean,
   onScroll?: (scrollTop: number) => void,
   onScroll?: (scrollTop: number) => void,
 }
 }

+ 6 - 3
packages/app/src/components/TableOfContents.jsx

@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
 import PageContainer from '~/client/services/PageContainer';
 import PageContainer from '~/client/services/PageContainer';
 import { blinkElem } from '~/client/util/blink-section-header';
 import { blinkElem } from '~/client/util/blink-section-header';
 import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
 import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
+import { useGlobalEventEmitter } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 
@@ -25,6 +26,8 @@ const TableOfContents = (props) => {
   const { pageUser } = pageContainer.state;
   const { pageUser } = pageContainer.state;
   const isUserPage = pageUser != null;
   const isUserPage = pageUser != null;
 
 
+  const { data: globalEmitter } = useGlobalEventEmitter();
+
   const [tocHtml, setTocHtml] = useState('');
   const [tocHtml, setTocHtml] = useState('');
 
 
   const calcViewHeight = useCallback(() => {
   const calcViewHeight = useCallback(() => {
@@ -56,12 +59,12 @@ const TableOfContents = (props) => {
   // set handler to render ToC
   // set handler to render ToC
   useEffect(() => {
   useEffect(() => {
     const handler = html => setTocHtml(html);
     const handler = html => setTocHtml(html);
-    window.globalEmitter.on('renderTocHtml', handler);
+    globalEmitter.on('renderTocHtml', handler);
 
 
     return function cleanup() {
     return function cleanup() {
-      window.globalEmitter.removeListener('renderTocHtml', handler);
+      globalEmitter.removeListener('renderTocHtml', handler);
     };
     };
-  }, []);
+  }, [globalEmitter]);
 
 
   return (
   return (
     <StickyStretchableScroller
     <StickyStretchableScroller

+ 0 - 2
packages/app/src/interfaces/global.ts

@@ -4,11 +4,9 @@ import GrowiRenderer from '~/services/renderer/growi-renderer';
 import Xss from '~/services/xss';
 import Xss from '~/services/xss';
 
 
 import { IGraphViewer } from './graph-viewer';
 import { IGraphViewer } from './graph-viewer';
-import { IInterceptorManager } from './interceptor-manager';
 
 
 export type CustomWindow = Window
 export type CustomWindow = Window
                          & typeof globalThis
                          & typeof globalThis
-                         & { interceptorManager: IInterceptorManager }
                          & { globalEmitter: EventEmitter }
                          & { globalEmitter: EventEmitter }
                          & { GraphViewer: IGraphViewer }
                          & { GraphViewer: IGraphViewer }
                          & { growiRenderer: GrowiRenderer }
                          & { growiRenderer: GrowiRenderer }

+ 9 - 11
packages/app/src/interfaces/services/renderer.ts

@@ -7,18 +7,16 @@ export type RendererSettings = {
   isIndentSizeForced: boolean,
   isIndentSizeForced: boolean,
 };
 };
 
 
-export type GrowiHydratedEnv = {
-  PLANTUML_URI: string | null,
-  BLOCKDIAG_URI: string | null,
-  DRAWIO_URI: string | null,
-  HACKMD_URI: string | null,
-  MATHJAX: string | null,
-  NO_CDN: string | null,
-  GROWI_CLOUD_URI: string | null,
-  GROWI_APP_ID_FOR_GROWI_CLOUD: string | null,
-}
+// 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 GrowiRendererConfig = {
 export type GrowiRendererConfig = {
   highlightJsStyleBorder: boolean
   highlightJsStyleBorder: boolean
-  env: Pick<GrowiHydratedEnv, 'MATHJAX' | 'PLANTUML_URI' | 'BLOCKDIAG_URI'>
+  plantumlUri: string | null,
+  blockdiagUri: string | null,
 } & XssOptionConfig;
 } & XssOptionConfig;

+ 35 - 10
packages/app/src/pages/[[...path]].page.tsx

@@ -1,5 +1,7 @@
 import React, { useEffect } from 'react';
 import React, { useEffect } from 'react';
 
 
+import EventEmitter from 'events';
+
 import { isClient, pagePathUtils, pathUtils } from '@growi/core';
 import { isClient, pagePathUtils, pathUtils } from '@growi/core';
 import ExtensibleCustomError from 'extensible-custom-error';
 import ExtensibleCustomError from 'extensible-custom-error';
 import {
 import {
@@ -18,12 +20,15 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { useIndentSize } from '~/stores/editor';
 // import { useIndentSize } from '~/stores/editor';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
+import { CustomWindow } from '~/interfaces/global';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageWithMeta } from '~/interfaces/page';
+import { GrowiRendererConfig, RendererSettings } from '~/interfaces/services/renderer';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { PageModel, PageDocument } from '~/server/models/page';
 import { PageModel, PageDocument } from '~/server/models/page';
 import UserUISettings, { UserUISettingsDocument } from '~/server/models/user-ui-settings';
 import UserUISettings, { UserUISettingsDocument } from '~/server/models/user-ui-settings';
 import Xss from '~/services/xss';
 import Xss from '~/services/xss';
 import { useSWRxCurrentPage, useSWRxPageInfo, useSWRxPage } from '~/stores/page';
 import { useSWRxCurrentPage, useSWRxPageInfo, useSWRxPage } from '~/stores/page';
+import { useRendererSettings } from '~/stores/renderer';
 import {
 import {
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
 } from '~/stores/ui';
@@ -47,8 +52,8 @@ import {
   useIsForbidden, useIsNotFound, useIsTrashPage, useShared, useShareLinkId, useIsSharedUser, useIsAbleToDeleteCompletely,
   useIsForbidden, useIsNotFound, useIsTrashPage, useShared, useShareLinkId, useIsSharedUser, useIsAbleToDeleteCompletely,
   useAppTitle, useSiteUrl, useConfidential, useIsEnabledStaleNotification,
   useAppTitle, useSiteUrl, useConfidential, useIsEnabledStaleNotification,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsMailerSetup,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsMailerSetup,
-  useAclEnabled, useIsAclEnabled, useHasSlackConfig, useDrawioUri, useHackmdUri, useMathJax,
-  useNoCdn, useEditorConfig, useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname, useIsSlackConfigured,
+  useAclEnabled, useIsAclEnabled, useHasSlackConfig, useDrawioUri, useHackmdUri,
+  useNoCdn, useEditorConfig, useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname, useIsSlackConfigured, useGrowiRendererConfig,
 } from '../stores/context';
 } from '../stores/context';
 import { useXss } from '../stores/xss';
 import { useXss } from '../stores/xss';
 
 
@@ -95,7 +100,6 @@ type Props = CommonProps & {
   // hasSlackConfig: boolean,
   // hasSlackConfig: boolean,
   // drawioUri: string,
   // drawioUri: string,
   // hackmdUri: string,
   // hackmdUri: string,
-  // mathJax: string,
   // noCdn: string,
   // noCdn: string,
   // highlightJsStyle: string,
   // highlightJsStyle: string,
   // isAllReplyShown: boolean,
   // isAllReplyShown: boolean,
@@ -107,6 +111,9 @@ type Props = CommonProps & {
   // adminPreferredIndentSize: number,
   // adminPreferredIndentSize: number,
   // isIndentSizeForced: boolean,
   // isIndentSizeForced: boolean,
 
 
+  rendererSettings: RendererSettings,
+  growiRendererConfig: GrowiRendererConfig,
+
   // UI
   // UI
   userUISettings: UserUISettingsDocument | null
   userUISettings: UserUISettingsDocument | null
   // Sidebar
   // Sidebar
@@ -121,6 +128,11 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
 
 
   const { data: currentUser } = useCurrentUser(props.currentUser != null ? JSON.parse(props.currentUser) : null);
   const { data: currentUser } = useCurrentUser(props.currentUser != null ? JSON.parse(props.currentUser) : null);
 
 
+  // register global EventEmitter
+  if (isClient()) {
+    (window as CustomWindow).globalEmitter = new EventEmitter();
+  }
+
   // commons
   // commons
   useAppTitle(props.appTitle);
   useAppTitle(props.appTitle);
   useSiteUrl(props.siteUrl);
   useSiteUrl(props.siteUrl);
@@ -159,16 +171,14 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useHasSlackConfig(props.hasSlackConfig);
   // useHasSlackConfig(props.hasSlackConfig);
   // useDrawioUri(props.drawioUri);
   // useDrawioUri(props.drawioUri);
   // useHackmdUri(props.hackmdUri);
   // useHackmdUri(props.hackmdUri);
-  // useMathJax(props.mathJax);
   // useNoCdn(props.noCdn);
   // useNoCdn(props.noCdn);
   // useIndentSize(props.adminPreferredIndentSize);
   // useIndentSize(props.adminPreferredIndentSize);
 
 
-  // useRendererSettings({
-  //   isEnabledLinebreaks: props.isEnabledLinebreaks,
-  //   isEnabledLinebreaksInComments: props.isEnabledLinebreaksInComments,
-  //   adminPreferredIndentSize: props.adminPreferredIndentSize,
-  //   isIndentSizeForced: props.isIndentSizeForced,
-  // });
+  useRendererSettings(props.rendererSettings);
+  useGrowiRendererConfig(props.growiRendererConfig);
+  // useRendererSettings(props.rendererSettingsStr != null ? JSON.parse(props.rendererSettingsStr) : undefined);
+  // useGrowiRendererConfig(props.growiRendererConfigStr != null ? JSON.parse(props.growiRendererConfigStr) : undefined);
+
 
 
   // const { data: editorMode } = useEditorMode();
   // const { data: editorMode } = useEditorMode();
 
 
@@ -411,6 +421,21 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
   // props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   // props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   // props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
   // props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
 
 
+  props.rendererSettings = {
+    isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
+    isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+    adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
+    isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+  };
+  props.growiRendererConfig = {
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    attrWhiteList: crowi.xssService.getAttrWhiteList(),
+    tagWhiteList: crowi.xssService.getTagWhiteList(),
+    highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+    plantumlUri: process.env.PLANTUML_URI ?? null,
+    blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
+  };
+
   props.sidebarConfig = {
   props.sidebarConfig = {
     isSidebarDrawerMode: configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'),
     isSidebarDrawerMode: configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'),
     isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
     isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),

+ 0 - 6
packages/app/src/server/service/config-loader.ts

@@ -73,12 +73,6 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    ValueType.STRING,
     type:    ValueType.STRING,
     default: null,
     default: null,
   },
   },
-  MATHJAX: {
-    ns:      'crowi',
-    key:     'app:mathJax',
-    type:    ValueType.STRING,
-    default: null,
-  },
   NO_CDN: {
   NO_CDN: {
     ns:      'crowi',
     ns:      'crowi',
     key:     'app:noCdn',
     key:     'app:noCdn',

+ 1 - 1
packages/app/src/services/renderer/growi-renderer.ts

@@ -98,7 +98,7 @@ export default class GrowiRenderer {
       new TaskListsConfigurer(),
       new TaskListsConfigurer(),
       new HeaderConfigurer(),
       new HeaderConfigurer(),
       new EmojiConfigurer(),
       new EmojiConfigurer(),
-      new MathJaxConfigurer(this.growiRendererConfig),
+      new MathJaxConfigurer(),
       new DrawioViewerConfigurer(),
       new DrawioViewerConfigurer(),
       new PlantUMLConfigurer(this.growiRendererConfig),
       new PlantUMLConfigurer(this.growiRendererConfig),
       new BlockdiagConfigurer(this.growiRendererConfig),
       new BlockdiagConfigurer(this.growiRendererConfig),

+ 0 - 14
packages/app/src/services/renderer/markdown-it/blockdiag.js

@@ -1,14 +0,0 @@
-export default class BlockdiagConfigurer {
-
-  constructor(growiConfig) {
-    this.generateSourceUrl = growiConfig.env.BLOCKDIAG_URI || 'https://blockdiag-api.com/';
-  }
-
-  configure(md) {
-    md.use(require('markdown-it-blockdiag'), {
-      generateSourceUrl: this.generateSourceUrl,
-      marker: ':::',
-    });
-  }
-
-}

+ 18 - 0
packages/app/src/services/renderer/markdown-it/blockdiag.ts

@@ -0,0 +1,18 @@
+import { GrowiRendererConfig } from '~/interfaces/services/renderer';
+
+export default class BlockdiagConfigurer {
+
+  generateSourceUrl: string;
+
+  constructor(growiConfig: GrowiRendererConfig) {
+    this.generateSourceUrl = growiConfig.blockdiagUri || 'https://blockdiag-api.com/';
+  }
+
+  configure(md) {
+    md.use(require('markdown-it-blockdiag'), {
+      generateSourceUrl: this.generateSourceUrl,
+      marker: ':::',
+    });
+  }
+
+}

+ 1 - 7
packages/app/src/services/renderer/markdown-it/mathjax.js

@@ -1,13 +1,7 @@
 export default class MathJaxConfigurer {
 export default class MathJaxConfigurer {
 
 
-  constructor(growiConfig) {
-    this.isEnabled = !!growiConfig.env.MATHJAX; // convert to boolean
-  }
-
   configure(md) {
   configure(md) {
-    if (this.isEnabled) {
-      md.use(require('markdown-it-mathjax')());
-    }
+    md.use(require('markdown-it-mathjax')());
   }
   }
 
 
 }
 }

+ 6 - 2
packages/app/src/services/renderer/markdown-it/plantuml.js → packages/app/src/services/renderer/markdown-it/plantuml.ts

@@ -1,11 +1,15 @@
 import plantumlEncoder from 'plantuml-encoder';
 import plantumlEncoder from 'plantuml-encoder';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
+import { GrowiRendererConfig } from '~/interfaces/services/renderer';
+
 export default class PlantUMLConfigurer {
 export default class PlantUMLConfigurer {
 
 
-  constructor(growiConfig) {
+  serverUrl: string;
+
+  constructor(growiConfig: GrowiRendererConfig) {
     // Do NOT use HTTPS URL because plantuml.com refuse request except from members
     // Do NOT use HTTPS URL because plantuml.com refuse request except from members
-    this.serverUrl = growiConfig.env.PLANTUML_URI || 'http://plantuml.com/plantuml';
+    this.serverUrl = growiConfig.plantumlUri || 'http://plantuml.com/plantuml';
 
 
     this.generateSource = this.generateSource.bind(this);
     this.generateSource = this.generateSource.bind(this);
   }
   }

+ 12 - 1
packages/app/src/stores/context.tsx

@@ -1,10 +1,13 @@
-import { pagePathUtils } from '@growi/core';
+import EventEmitter from 'events';
+
 import { Key, SWRResponse } from 'swr';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
 
 
 import { SupportedActionType } from '~/interfaces/activity';
 import { SupportedActionType } from '~/interfaces/activity';
+import { CustomWindow } from '~/interfaces/global';
 import { GrowiRendererConfig } from '~/interfaces/services/renderer';
 import { GrowiRendererConfig } from '~/interfaces/services/renderer';
+import InterceptorManager from '~/services/interceptor-manager';
 
 
 import { TargetAndAncestors } from '../interfaces/page-listing-results';
 import { TargetAndAncestors } from '../interfaces/page-listing-results';
 import { IUser } from '../interfaces/user';
 import { IUser } from '../interfaces/user';
@@ -15,6 +18,14 @@ import { useStaticSWR } from './use-static-swr';
 type Nullable<T> = T | null;
 type Nullable<T> = T | null;
 
 
 
 
+export const useGlobalEventEmitter = (): SWRResponse<EventEmitter, Error> => {
+  return useStaticSWR<EventEmitter, Error>('globalEventEmitter', undefined, { fallbackData: (window as CustomWindow).globalEmitter });
+};
+
+export const useInterceptorManager = (): SWRResponse<InterceptorManager, Error> => {
+  return useStaticSWR<InterceptorManager, Error>('interceptorManager', undefined, { fallbackData: new InterceptorManager() });
+};
+
 export const useCsrfToken = (initialData?: string): SWRResponse<string, Error> => {
 export const useCsrfToken = (initialData?: string): SWRResponse<string, Error> => {
   return useStaticSWR<string, Error>('csrfToken', initialData);
   return useStaticSWR<string, Error>('csrfToken', initialData);
 };
 };

+ 2 - 2
packages/app/src/stores/page.tsx

@@ -14,7 +14,7 @@ import { IPageTagsInfo } from '../interfaces/tag';
 
 
 import { useCurrentPageId } from './context';
 import { useCurrentPageId } from './context';
 
 
-export const useSWRxPage = (pageId?: string, shareLinkId?: string, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
+export const useSWRxPage = (pageId?: string|null, shareLinkId?: string, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
   return useSWR<IPageHasId, Error>(
   return useSWR<IPageHasId, Error>(
     pageId != null ? ['/page', pageId, shareLinkId] : null,
     pageId != null ? ['/page', pageId, shareLinkId] : null,
     (endpoint, pageId, shareLinkId) => apiv3Get(endpoint, { pageId, shareLinkId }).then(result => result.data.page),
     (endpoint, pageId, shareLinkId) => apiv3Get(endpoint, { pageId, shareLinkId }).then(result => result.data.page),
@@ -32,7 +32,7 @@ export const useSWRxPageByPath = (path?: string): SWRResponse<IPageHasId, Error>
 export const useSWRxCurrentPage = (shareLinkId?: string, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
 export const useSWRxCurrentPage = (shareLinkId?: string, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
   const { data: currentPageId } = useCurrentPageId();
   const { data: currentPageId } = useCurrentPageId();
 
 
-  return useSWRxPage(currentPageId ?? undefined, shareLinkId, initialData);
+  return useSWRxPage(currentPageId, shareLinkId, initialData);
 };
 };