Taichi Masuyama 3 лет назад
Родитель
Сommit
136b442d0c

+ 26 - 20
packages/app/src/components/Page.jsx → packages/app/src/components/Page.tsx

@@ -3,7 +3,6 @@ import React, {
 } from 'react';
 
 import dynamic from 'next/dynamic';
-import PropTypes from 'prop-types';
 // import { debounce } from 'throttle-debounce';
 
 import { blinkSectionHeaderAtBoot } from '~/client/util/blink-section-header';
@@ -30,9 +29,28 @@ import RevisionRenderer from './Page/RevisionRenderer';
 
 const logger = loggerFactory('growi:Page');
 
-class PageSubstance extends React.Component {
+type PageSubstanceProps = {
+  rendererOptions: any,
+  page: any,
+  pageTags?: string[],
+  editorMode: string,
+  isGuestUser: boolean,
+  isMobile?: boolean,
+  isSlackEnabled: boolean,
+  slackChannels: string,
+};
+
+class PageSubstance extends React.Component<PageSubstanceProps> {
+
+  gridEditModal: any;
+
+  linkEditModal: any;
 
-  constructor(props) {
+  handsontableModal: any;
+
+  drawioModal: any;
+
+  constructor(props: PageSubstanceProps) {
     super(props);
 
     this.state = {
@@ -138,7 +156,7 @@ class PageSubstance extends React.Component {
     // }
   }
 
-  render() {
+  override render() {
     const {
       rendererOptions, page, isMobile, isGuestUser,
     } = this.props;
@@ -171,32 +189,20 @@ class PageSubstance extends React.Component {
 
 }
 
-PageSubstance.propTypes = {
-  rendererOptions: PropTypes.object.isRequired,
-
-  page: PropTypes.any.isRequired,
-  pageTags:  PropTypes.arrayOf(PropTypes.string),
-  editorMode: PropTypes.string.isRequired,
-  isGuestUser: PropTypes.bool.isRequired,
-  isMobile: PropTypes.bool,
-  isSlackEnabled: PropTypes.bool.isRequired,
-  slackChannels: PropTypes.string.isRequired,
-};
-
 export const Page = (props) => {
+  const pageRef = useRef(null);
+
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: editorMode } = useEditorMode();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isMobile } = useIsMobile();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPage?.path);
   const { data: isSlackEnabled } = useIsSlackEnabled();
-  const { data: pageTags } = usePageTagsForEditors();
+  const { data: pageTags } = usePageTagsForEditors(null); // TODO: pass pageId
   const { data: rendererOptions } = useViewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { data: isBlinkedAtBoot, mutate: mutateBlinkedAtBoot } = useIsBlinkedHeaderAtBoot();
 
-  const pageRef = useRef(null);
-
   useEffect(() => {
     if (isBlinkedAtBoot) {
       return;
@@ -253,7 +259,7 @@ export const Page = (props) => {
       isMobile={isMobile}
       isSlackEnabled={isSlackEnabled}
       pageTags={pageTags}
-      slackChannels={slackChannelsData.toString()}
+      slackChannels={slackChannelsData?.toString()}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
     />
   );

+ 11 - 5
packages/app/src/services/renderer/renderer.ts

@@ -1,9 +1,12 @@
+import { MutableRefObject } from 'react';
+
+import { HastNode } from 'hast-util-select';
 import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 import katex from 'rehype-katex';
 import raw from 'rehype-raw';
 import sanitize, { defaultSchema as sanitizeDefaultSchema } from 'rehype-sanitize';
 import slug from 'rehype-slug';
-import toc, { HtmlElementNode } from 'rehype-toc';
+import toc from 'rehype-toc';
 import breaks from 'remark-breaks';
 import emoji from 'remark-emoji';
 import gfm from 'remark-gfm';
@@ -245,7 +248,7 @@ const generateCommonOptions = (pagePath: string|undefined, config: RendererConfi
 export const generateViewOptions = (
     pagePath: string,
     config: RendererConfig,
-    storeTocNode: (node: HtmlElementNode) => void,
+    tocRef: MutableRefObject<HastNode | undefined>,
 ): RendererOptions => {
 
   const options = generateCommonOptions(pagePath, config);
@@ -267,7 +270,7 @@ export const generateViewOptions = (
     rehypePlugins.push([toc, {
       nav: false,
       headings: ['h1', 'h2', 'h3'],
-      customizeTOC: (toc: HtmlElementNode) => {
+      customizeTOC: (toc: HastNode) => {
         // method for replace <ol> to <ul>
         const replacer = (children) => {
           children.forEach((child) => {
@@ -280,7 +283,10 @@ export const generateViewOptions = (
           });
         };
         replacer([toc]); // replace <ol> to <ul>
-        storeTocNode(toc); // store tocNode to global state with swr
+
+        // For storing tocNode to global state with swr
+        tocRef.current = toc;
+
         return false; // not show toc in body
       },
     }]);
@@ -311,7 +317,7 @@ export const generateViewOptions = (
   return options;
 };
 
-export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementNode | undefined): RendererOptions => {
+export const generateTocOptions = (config: RendererConfig, tocNode: HastNode | undefined): RendererOptions => {
 
   const options = generateCommonOptions(undefined, config);
 

+ 7 - 7
packages/app/src/stores/context.tsx

@@ -1,6 +1,4 @@
-import EventEmitter from 'events';
-
-import { HtmlElementNode } from 'rehype-toc';
+import { HastNode } from 'hast-util-select';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
@@ -232,10 +230,6 @@ export const useRendererConfig = (initialData?: RendererConfig): SWRResponse<Ren
   return useStaticSWR('growiRendererConfig', initialData);
 };
 
-export const useCurrentPageTocNode = (): SWRResponse<HtmlElementNode, any> => {
-  return useStaticSWR('currentPageTocNode');
-};
-
 export const useIsBlinkedHeaderAtBoot = (initialData?: boolean): SWRResponse<boolean, Error> => {
   return useStaticSWR('isBlinkedAtBoot', initialData);
 };
@@ -278,3 +272,9 @@ export const useIsEditable = (): SWRResponse<boolean, Error> => {
     },
   );
 };
+
+export const useCurrentPageTocNode = (): SWRResponse<HastNode, any> => {
+  const { data: currentPagePath } = useCurrentPagePath();
+
+  return useStaticSWR(['currentPageTocNode', currentPagePath]);
+};

+ 18 - 6
packages/app/src/stores/renderer.tsx

@@ -1,3 +1,6 @@
+import { useEffect, useRef } from 'react';
+
+import { HastNode } from 'hast-util-select';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
@@ -10,7 +13,7 @@ import {
 
 
 import {
-  useCurrentPageId, useCurrentPagePath, useCurrentPageTocNode, useRendererConfig,
+  useCurrentPagePath, useCurrentPageTocNode, useRendererConfig,
 } from './context';
 
 interface ReactMarkdownOptionsGenerator {
@@ -42,7 +45,11 @@ const _useOptionsBase = (
 export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: rendererConfig } = useRendererConfig();
-  const { mutate: storeTocNode } = useCurrentPageTocNode();
+  const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
+
+  // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
+  // The toc node passed by customizeTOC is assigned to tocRef.current.
+  const tocRef = useRef<HastNode>();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
 
@@ -50,26 +57,31 @@ export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
     ? ['viewOptions', currentPagePath, rendererConfig]
     : null;
 
+  useEffect(() => {
+    mutateCurrentPageTocNode(tocRef.current);
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [mutateCurrentPageTocNode, tocRef.current]); // include tocRef.current to call mutateCurrentPageTocNode when tocRef.current changes
+
   return useSWRImmutable<RendererOptions, Error>(
     key,
-    (rendererId, currentPagePath, rendererConfig) => generateViewOptions(currentPagePath, rendererConfig, storeTocNode),
+    (rendererId, currentPagePath, rendererConfig) => generateViewOptions(currentPagePath, rendererConfig, tocRef),
   );
 };
 
 export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
-  const { data: pageId } = useCurrentPageId();
+  const { data: currentPagePath } = useCurrentPagePath();
   const { data: rendererConfig } = useRendererConfig();
   const { data: tocNode } = useCurrentPageTocNode();
 
   const isAllDataValid = rendererConfig != null;
 
   const key = isAllDataValid
-    ? ['tocOptions', pageId, rendererConfig]
+    ? ['tocOptions', currentPagePath, tocNode, rendererConfig]
     : null;
 
   return useSWRImmutable<RendererOptions, Error>(
     key,
-    (rendererId, pageId, rendererConfig) => generateTocOptions(rendererConfig, tocNode),
+    (rendererId, path, tocNode, rendererConfig) => generateTocOptions(rendererConfig, tocNode),
   );
 };