ソースを参照

refactor SlideRenderer

Yuki Takei 1 年間 前
コミット
bec2809aea

+ 26 - 4
apps/app/src/components/Page/PageView.tsx

@@ -4,7 +4,10 @@ import React, {
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
+import type { UseSlide } from '@growi/presentation/dist/services';
+import { parseSlideFrontmatterInMarkdown } from '@growi/presentation/dist/services';
 import dynamic from 'next/dynamic';
+import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 import { useShouldExpandContent } from '~/client/services/layout';
 import type { RendererConfig } from '~/interfaces/services/renderer';
@@ -27,7 +30,6 @@ import { UserInfo } from '../User/UserInfo';
 import type { UsersHomepageFooterProps } from '../UsersHomepageFooter';
 
 import RevisionRenderer from './RevisionRenderer';
-import { SlideRenderer } from './SlideRenderer';
 
 import styles from './PageView.module.scss';
 
@@ -41,6 +43,7 @@ const Comments = dynamic<CommentsProps>(() => import('../Comments').then(mod =>
 const UsersHomepageFooter = dynamic<UsersHomepageFooterProps>(() => import('../UsersHomepageFooter')
   .then(mod => mod.UsersHomepageFooter), { ssr: false });
 const IdenticalPathPage = dynamic(() => import('../IdenticalPathPage').then(mod => mod.IdenticalPathPage), { ssr: false });
+const SlideRenderer = dynamic(() => import('./SlideRenderer').then(mod => mod.SlideRenderer), { ssr: false });
 
 
 type Props = {
@@ -54,6 +57,7 @@ export const PageView = (props: Props): JSX.Element => {
   const commentsContainerRef = useRef<HTMLDivElement>(null);
 
   const [isCommentsLoaded, setCommentsLoaded] = useState(false);
+  const [parseFrontmatterResult, setParseFrontmatterResult] = useState<UseSlide|undefined>();
 
   const {
     pagePath, initialPage, rendererConfig,
@@ -91,6 +95,23 @@ export const PageView = (props: Props): JSX.Element => {
   }, [isCommentsLoaded]);
   // *******************************  end  *******************************
 
+
+  useIsomorphicLayoutEffect(() => {
+    if (isNotFound || page?.revision == null) {
+      return;
+    }
+
+    const markdown = page.revision.body;
+
+    (async() => {
+      const parseFrontmatterResult = await parseSlideFrontmatterInMarkdown(markdown);
+
+      if (parseFrontmatterResult != null) {
+        setParseFrontmatterResult(parseFrontmatterResult);
+      }
+    })();
+  }, []);
+
   const specialContents = useMemo(() => {
     if (isIdenticalPathPage) {
       return <IdenticalPathPage />;
@@ -138,9 +159,10 @@ export const PageView = (props: Props): JSX.Element => {
 
         <div className="flex-expand-vert justify-content-between">
 
-          <SlideRenderer markdown={markdown}>
-            <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
-          </SlideRenderer>
+          { parseFrontmatterResult != null
+            ? <SlideRenderer marp={parseFrontmatterResult.marp} markdown={markdown} />
+            : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
+          }
 
           { !isIdenticalPathPage && !isNotFound && (
             <div id="comments-container" ref={commentsContainerRef}>

+ 14 - 16
apps/app/src/components/Page/SlideRenderer.tsx

@@ -1,30 +1,28 @@
-import { isServer } from '@growi/core/dist/utils';
-import { parseSlideFrontmatterInMarkdown } from '@growi/presentation';
+import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 
 import { useIsEnabledMarp } from '~/stores/context';
+import { usePresentationViewOptions } from '~/stores/renderer';
 
-import { SlideViewer } from '../SlideViewer';
+import { Slides } from '../Presentation/Slides';
 
 type SlideRendererProps = {
-  markdown: string
-  children: JSX.Element
+  markdown: string,
+  marp?: boolean,
 };
 
 export const SlideRenderer = (props: SlideRendererProps): JSX.Element => {
 
-  const { markdown, children } = props;
-  const { data: enabledMarp } = useIsEnabledMarp();
+  const { markdown, marp = false } = props;
+  const { data: enabledMarp = false } = useIsEnabledMarp();
 
-  if (isServer()) {
-    return children;
-  }
-
-  const [marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
-  const useMarp = (enabledMarp ?? false) && marp;
+  const { data: rendererOptions } = usePresentationViewOptions();
 
   return (
-    (useMarp || useSlide)
-      ? (<SlideViewer marp={useMarp}>{markdown}</SlideViewer>)
-      : (children)
+    <Slides
+      hasMarpFlag={enabledMarp && marp}
+      options={{ rendererOptions: rendererOptions as ReactMarkdownOptions }}
+    >
+      {markdown}
+    </Slides>
   );
 };

+ 21 - 10
apps/app/src/components/PageEditor/Preview.tsx

@@ -1,13 +1,14 @@
 import type { CSSProperties } from 'react';
-import React from 'react';
+import React, { useState } from 'react';
 
-import { parseSlideFrontmatterInMarkdown } from '@growi/presentation';
+import type { UseSlide } from '@growi/presentation/dist/services';
+import { parseSlideFrontmatterInMarkdown } from '@growi/presentation/dist/services';
+import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 import type { RendererOptions } from '~/interfaces/renderer-options';
-import { useIsEnabledMarp } from '~/stores/context';
 
 import RevisionRenderer from '../Page/RevisionRenderer';
-import { SlideViewer } from '../SlideViewer';
+import { SlideRenderer } from '../Page/SlideRenderer';
 
 import styles from './Preview.module.scss';
 
@@ -31,11 +32,21 @@ const Preview = (props: Props): JSX.Element => {
     expandContentWidth,
   } = props;
 
+  const [parseFrontmatterResult, setParseFrontmatterResult] = useState<UseSlide|undefined>();
+
   const fluidLayoutClass = expandContentWidth ? 'fluid-layout' : '';
 
-  const { data: enabledMarp } = useIsEnabledMarp();
-  const [marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
-  const useMarp = (enabledMarp ?? false) && marp;
+  useIsomorphicLayoutEffect(() => {
+    if (markdown == null) return;
+
+    (async() => {
+      const parseFrontmatterResult = await parseSlideFrontmatterInMarkdown(markdown);
+
+      if (parseFrontmatterResult != null) {
+        setParseFrontmatterResult(parseFrontmatterResult);
+      }
+    })();
+  }, []);
 
   return (
     <div
@@ -45,9 +56,9 @@ const Preview = (props: Props): JSX.Element => {
     >
       { markdown != null
         && (
-          (useMarp || useSlide)
-            ? (<SlideViewer marp={useMarp}>{markdown}</SlideViewer>)
-            : (<RevisionRenderer rendererOptions={rendererOptions} markdown={markdown}></RevisionRenderer>)
+          parseFrontmatterResult != null
+            ? <SlideRenderer marp={parseFrontmatterResult.marp} markdown={markdown} />
+            : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown}></RevisionRenderer>
         )
       }
     </div>

+ 1 - 1
apps/app/src/components/PagePresentationModal.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 
-import type { PresentationProps } from '@growi/presentation';
+import type { PresentationProps } from '@growi/presentation/dist/client';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useFullScreen } from '@growi/ui/dist/utils';
 import dynamic from 'next/dynamic';

+ 1 - 1
apps/app/src/components/Presentation/Presentation.tsx

@@ -1,4 +1,4 @@
-import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation';
+import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation/dist/client';
 
 import '@growi/presentation/dist/style.css';
 

+ 1 - 1
apps/app/src/components/Presentation/Slides.tsx

@@ -1,4 +1,4 @@
-import { Slides as SlidesSubstance, type SlidesProps } from '@growi/presentation';
+import { Slides as SlidesSubstance, type SlidesProps } from '@growi/presentation/dist/client';
 
 import '@growi/presentation/dist/style.css';
 

+ 26 - 12
apps/app/src/components/ShareLinkPageView.tsx

@@ -1,7 +1,10 @@
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
+import type { UseSlide } from '@growi/presentation/dist/services';
+import { parseSlideFrontmatterInMarkdown } from '@growi/presentation/dist/services';
 import dynamic from 'next/dynamic';
+import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 import { useShouldExpandContent } from '~/client/services/layout';
 import type { RendererConfig } from '~/interfaces/services/renderer';
@@ -15,7 +18,6 @@ import { PagePathNavSticky } from './Common/PagePathNav';
 import { PageViewLayout } from './Common/PageViewLayout';
 import RevisionRenderer from './Page/RevisionRenderer';
 import ShareLinkAlert from './Page/ShareLinkAlert';
-import { SlideRenderer } from './Page/SlideRenderer';
 import type { PageSideContentsProps } from './PageSideContents';
 
 
@@ -24,6 +26,7 @@ const logger = loggerFactory('growi:Page');
 
 const PageSideContents = dynamic<PageSideContentsProps>(() => import('./PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
 const ForbiddenPage = dynamic(() => import('./ForbiddenPage'), { ssr: false });
+const SlideRenderer = dynamic(() => import('./Page/SlideRenderer').then(mod => mod.SlideRenderer), { ssr: false });
 
 type Props = {
   pagePath: string,
@@ -41,6 +44,8 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     isExpired, disableLinkSharing,
   } = props;
 
+  const [parseFrontmatterResult, setParseFrontmatterResult] = useState<UseSlide|undefined>();
+
   const { data: isNotFoundMeta } = useIsNotFound();
 
   const { data: viewOptions } = useViewOptions();
@@ -55,6 +60,22 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     }
   }, [disableLinkSharing, props.disableLinkSharing]);
 
+  useIsomorphicLayoutEffect(() => {
+    if (isNotFound || page?.revision == null) {
+      return;
+    }
+
+    const markdown = page.revision.body;
+
+    (async() => {
+      const parseFrontmatterResult = await parseSlideFrontmatterInMarkdown(markdown);
+
+      if (parseFrontmatterResult != null) {
+        setParseFrontmatterResult(parseFrontmatterResult);
+      }
+    })();
+  }, []);
+
   const headerContents = (
     <PagePathNavSticky pageId={page?._id} pagePath={pagePath} />
   );
@@ -85,16 +106,9 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
     const markdown = page.revision.body;
 
-    return (
-      <>
-        <SlideRenderer markdown={markdown}>
-          <RevisionRenderer
-            rendererOptions={rendererOptions}
-            markdown={markdown}
-          />
-        </SlideRenderer>
-      </>
-    );
+    return parseFrontmatterResult != null
+      ? <SlideRenderer marp={parseFrontmatterResult.marp} markdown={markdown} />
+      : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />;
   };
 
   return (

+ 0 - 32
apps/app/src/components/SlideViewer.tsx

@@ -1,32 +0,0 @@
-import React from 'react';
-
-import dynamic from 'next/dynamic';
-import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
-
-import { usePresentationViewOptions } from '~/stores/renderer';
-
-const Slides = dynamic(() => import('./Presentation/Slides').then(mod => mod.Slides), { ssr: false });
-
-type SlideViewerProps = {
-  marp: boolean,
-  children: string,
-}
-
-export const SlideViewer = React.memo((props: SlideViewerProps) => {
-  const {
-    marp, children,
-  } = props;
-
-  const { data: rendererOptions } = usePresentationViewOptions();
-
-  return (
-    <Slides
-      hasMarpFlag={marp}
-      options={{ rendererOptions: rendererOptions as ReactMarkdownOptions }}
-    >
-      {children}
-    </Slides>
-  );
-});
-
-SlideViewer.displayName = 'SlideViewer';