Yuki Takei 1 год назад
Родитель
Сommit
074ef5adeb

+ 8 - 23
apps/app/src/components/Page/PageView.tsx

@@ -4,10 +4,8 @@ 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 { useSlidesByFrontmatter } 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';
@@ -57,7 +55,6 @@ 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,
@@ -79,6 +76,10 @@ export const PageView = (props: Props): JSX.Element => {
   const shouldExpandContent = useShouldExpandContent(page);
 
 
+  const markdown = page?.revision?.body;
+  const isSlide = useSlidesByFrontmatter(markdown, rendererConfig.isEnabledMarp);
+
+
   // ***************************  Auto Scroll  ***************************
   useEffect(() => {
     // do nothing if hash is empty
@@ -96,22 +97,6 @@ export const PageView = (props: Props): JSX.Element => {
   // *******************************  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 />;
@@ -150,8 +135,8 @@ export const PageView = (props: Props): JSX.Element => {
       return <NotFoundPage path={pagePath} />;
     }
 
-    const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
     const markdown = page.revision.body;
+    const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
 
     return (
       <>
@@ -159,8 +144,8 @@ export const PageView = (props: Props): JSX.Element => {
 
         <div className="flex-expand-vert justify-content-between">
 
-          { parseFrontmatterResult != null
-            ? <SlideRenderer marp={parseFrontmatterResult.marp} markdown={markdown} />
+          { isSlide != null
+            ? <SlideRenderer marp={isSlide.marp} markdown={markdown} />
             : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
           }
 

+ 1 - 3
apps/app/src/components/Page/SlideRenderer.tsx

@@ -1,6 +1,5 @@
 import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 
-import { useIsEnabledMarp } from '~/stores/context';
 import { usePresentationViewOptions } from '~/stores/renderer';
 
 import { Slides } from '../Presentation/Slides';
@@ -13,13 +12,12 @@ type SlideRendererProps = {
 export const SlideRenderer = (props: SlideRendererProps): JSX.Element => {
 
   const { markdown, marp = false } = props;
-  const { data: enabledMarp = false } = useIsEnabledMarp();
 
   const { data: rendererOptions } = usePresentationViewOptions();
 
   return (
     <Slides
-      hasMarpFlag={enabledMarp && marp}
+      hasMarpFlag={marp}
       options={{ rendererOptions: rendererOptions as ReactMarkdownOptions }}
     >
       {markdown}

+ 6 - 18
apps/app/src/components/PageEditor/Preview.tsx

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

+ 6 - 3
apps/app/src/components/PagePresentationModal.tsx

@@ -1,6 +1,7 @@
 import React, { useCallback } from 'react';
 
 import type { PresentationProps } from '@growi/presentation/dist/client';
+import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useFullScreen } from '@growi/ui/dist/utils';
 import dynamic from 'next/dynamic';
@@ -39,6 +40,10 @@ const PagePresentationModal = (): JSX.Element => {
 
   const { data: isEnabledMarp } = useIsEnabledMarp();
 
+  const markdown = currentPage?.revision?.body;
+
+  const isSlide = useSlidesByFrontmatter(markdown, isEnabledMarp);
+
   const toggleFullscreenHandler = useCallback(() => {
     if (fullscreen.active) {
       fullscreen.exit();
@@ -61,8 +66,6 @@ const PagePresentationModal = (): JSX.Element => {
     return <></>;
   }
 
-  const markdown = currentPage?.revision?.body;
-
   return (
     <Modal
       isOpen={isOpen}
@@ -92,7 +95,7 @@ const PagePresentationModal = (): JSX.Element => {
               },
               isDarkMode,
             }}
-            isEnabledMarp={isEnabledMarp}
+            marp={isSlide?.marp}
           >
             {markdown}
           </Presentation>

+ 9 - 24
apps/app/src/components/ShareLinkPageView.tsx

@@ -1,15 +1,14 @@
-import React, { useMemo, useState } from 'react';
+import React, { useMemo } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
-import type { UseSlide } from '@growi/presentation/dist/services';
-import { parseSlideFrontmatterInMarkdown } from '@growi/presentation/dist/services';
+import { useSlidesByFrontmatter } 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';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
+import { useIsEnabledMarp } from '~/stores/context';
 import { useIsNotFound } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
@@ -44,14 +43,16 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     isExpired, disableLinkSharing,
   } = props;
 
-  const [parseFrontmatterResult, setParseFrontmatterResult] = useState<UseSlide|undefined>();
-
   const { data: isNotFoundMeta } = useIsNotFound();
 
   const { data: viewOptions } = useViewOptions();
 
   const shouldExpandContent = useShouldExpandContent(page);
 
+  const markdown = page?.revision?.body;
+
+  const isSlide = useSlidesByFrontmatter(markdown, rendererConfig.isEnabledMarp);
+
   const isNotFound = isNotFoundMeta || page == null || shareLink == null;
 
   const specialContents = useMemo(() => {
@@ -60,22 +61,6 @@ 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} />
   );
@@ -106,8 +91,8 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
     const markdown = page.revision.body;
 
-    return parseFrontmatterResult != null
-      ? <SlideRenderer marp={parseFrontmatterResult.marp} markdown={markdown} />
+    return isSlide != null
+      ? <SlideRenderer marp={isSlide.marp} markdown={markdown} />
       : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />;
   };
 

+ 4 - 21
packages/presentation/src/client/components/Presentation.tsx

@@ -1,8 +1,7 @@
-import { useEffect, useLayoutEffect, useState } from 'react';
+import { useEffect } from 'react';
 
 import Reveal from 'reveal.js';
 
-import { UseSlide, parseSlideFrontmatterInMarkdown } from '../../services';
 import type { PresentationOptions } from '../consts';
 
 import { Slides } from './Slides';
@@ -34,30 +33,14 @@ const removeAllHiddenElements = () => {
 
 export type PresentationProps = {
   options: PresentationOptions,
-  isEnabledMarp: boolean,
+  marp?: boolean,
   children?: string,
 }
 
 export const Presentation = (props: PresentationProps): JSX.Element => {
-  const { options, isEnabledMarp, children } = props;
+  const { options, marp, children } = props;
   const { revealOptions } = options;
 
-  const [parseFrontmatterResult, setParseFrontmatterResult] = useState<UseSlide|undefined>();
-
-  useLayoutEffect(() => {
-    if (children == null) {
-      return;
-    }
-
-    (async() => {
-      const parseFrontmatterResult = await parseSlideFrontmatterInMarkdown(children);
-
-      if (parseFrontmatterResult != null) {
-        setParseFrontmatterResult(parseFrontmatterResult);
-      }
-    })();
-  }, [children]);
-
   useEffect(() => {
     if (children == null) {
       return;
@@ -77,7 +60,7 @@ export const Presentation = (props: PresentationProps): JSX.Element => {
 
   return (
     <div className={`grw-presentation ${styles['grw-presentation']} reveal`}>
-      <Slides options={options} hasMarpFlag={isEnabledMarp && parseFrontmatterResult?.marp} presentation>{children}</Slides>
+      <Slides options={options} hasMarpFlag={marp} presentation>{children}</Slides>
     </div>
   );
 };

+ 1 - 1
packages/presentation/src/services/index.ts

@@ -1 +1 @@
-export * from './parse-slide-frontmatter';
+export * from './use-slides-by-frontmatter';

+ 31 - 23
packages/presentation/src/services/parse-slide-frontmatter.ts → packages/presentation/src/services/use-slides-by-frontmatter.ts

@@ -1,3 +1,5 @@
+import { useEffect, useMemo, useState } from 'react';
+
 import remarkFrontmatter from 'remark-frontmatter';
 import remarkParse from 'remark-parse';
 import remarkStringify from 'remark-stringify';
@@ -38,31 +40,37 @@ export type UseSlide = {
  * @param markdown Markdwon document
  * @returns An UseSlide instance. If the markdown does not contain neither "marp" or "slide" attribute in frontmatter, it returns undefined.
  */
-export const parseSlideFrontmatterInMarkdown = async(markdown?: string): Promise<UseSlide | undefined> => {
-
-  let result: ParseResult | undefined;
-
-  await unified()
-    .use(remarkParse)
-    .use(remarkStringify)
-    .use(remarkFrontmatter, ['yaml'])
-    .use(() => ((obj) => {
-      if (obj.children[0]?.type === 'yaml') {
-        result = parseSlideFrontmatter(obj.children[0]?.value as string);
-      }
-    }))
-    .process(markdown as string);
-
-  if (result == null) {
-    return;
-  }
+export const useSlidesByFrontmatter = (markdown?: string, isEnabledMarp?: boolean): UseSlide | undefined => {
+
+  const [parseResult, setParseResult] = useState<UseSlide|undefined>();
+
+  const processor = useMemo(() => {
+    return unified()
+      .use(remarkParse)
+      .use(remarkStringify)
+      .use(remarkFrontmatter, ['yaml'])
+      .use(() => ((obj) => {
+        if (obj.children[0]?.type === 'yaml') {
+          const result = parseSlideFrontmatter(obj.children[0]?.value);
+          setParseResult(result.marp || result.slide ? result : undefined);
+        }
+        else {
+          setParseResult(undefined);
+        }
+      }));
+  }, []);
+
+  useEffect(() => {
+    if (markdown == null) {
+      return;
+    }
 
-  const { marp, slide } = result;
+    processor.process(markdown);
+  }, [markdown, processor]);
 
-  if (!marp && !slide) {
-    return;
-  }
 
-  return { marp };
+  return parseResult != null
+    ? { marp: isEnabledMarp && parseResult?.marp }
+    : undefined;
 
 };