Просмотр исходного кода

Merge pull request #7953 from weseek/imprv/115672-126744-ignore-custom-type

imprv: Ignore custom type when creating slide
Yuki Takei 2 лет назад
Родитель
Сommit
68171c572e

+ 0 - 1
apps/app/src/client/services/renderer/renderer.tsx

@@ -19,7 +19,6 @@ import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
-// eslint-disable-next-line import/no-cycle
 import { SlideViewer } from '~/components/ReactMarkdownComponents/SlideViewer';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import * as mermaid from '~/features/mermaid';

+ 87 - 0
apps/app/src/client/services/renderer/slide-viewer-renderer.tsx

@@ -0,0 +1,87 @@
+import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
+import * as drawio from '@growi/remark-drawio';
+// eslint-disable-next-line import/extensions
+import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
+import katex from 'rehype-katex';
+import sanitize from 'rehype-sanitize';
+import math from 'remark-math';
+import deepmerge from 'ts-deepmerge';
+import type { Pluggable } from 'unified';
+
+import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
+import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
+import * as mermaid from '~/features/mermaid';
+import { RehypeSanitizeOption } from '~/interfaces/rehype';
+import type { RendererOptions } from '~/interfaces/renderer-options';
+import type { RendererConfig } from '~/interfaces/services/renderer';
+import * as attachment from '~/services/renderer/remark-plugins/attachment';
+import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
+import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
+import {
+  commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin,
+} from '~/services/renderer/renderer';
+
+
+export const generatePresentationViewOptions = (
+    config: RendererConfig,
+    pagePath: string,
+): RendererOptions => {
+  const options = generateCommonOptions(pagePath);
+
+  const { remarkPlugins, rehypePlugins, components } = options;
+
+  // add remark plugins
+  remarkPlugins.push(
+    math,
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    drawio.remarkPlugin,
+    mermaid.remarkPlugin,
+    xsvToTable.remarkPlugin,
+    attachment.remarkPlugin,
+    lsxGrowiDirective.remarkPlugin,
+    refsGrowiDirective.remarkPlugin,
+  );
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      drawio.sanitizeOption,
+      mermaid.sanitizeOption,
+      attachment.sanitizeOption,
+      lsxGrowiDirective.sanitizeOption,
+      refsGrowiDirective.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    [refsGrowiDirective.rehypePlugin, { pagePath }],
+    rehypeSanitizePlugin,
+    katex,
+  );
+
+  // add components
+  if (components != null) {
+    components.lsx = lsxGrowiDirective.LsxImmutable;
+    components.ref = refsGrowiDirective.RefImmutable;
+    components.refs = refsGrowiDirective.RefsImmutable;
+    components.refimg = refsGrowiDirective.RefImgImmutable;
+    components.refsimg = refsGrowiDirective.RefsImgImmutable;
+    components.gallery = refsGrowiDirective.GalleryImmutable;
+    components.drawio = drawio.DrawioViewer;
+    components.mermaid = mermaid.MermaidViewer;
+    components.attachment = RichAttachment;
+    components.img = LightBox;
+  }
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};

+ 3 - 6
apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx

@@ -4,16 +4,13 @@ import { MARP_CONTAINER_CLASS_NAME } from '@growi/presentation';
 import dynamic from 'next/dynamic';
 import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 
-// TODO: Fix Dependency cycle
-// https://redmine.weseek.co.jp/issues/126744
-// eslint-disable-next-line import/no-cycle
-import { usePresentationViewOptions } from '~/stores/renderer';
+import { usePresentationViewOptions } from '~/stores/slide-viewer-renderer';
 
 
 const Slides = dynamic(() => import('@growi/presentation').then(mod => mod.Slides), { ssr: false });
 
 type SlideViewerProps = {
-  marp: string,
+  marp: string | undefined,
   children: string,
 }
 
@@ -28,7 +25,7 @@ export const SlideViewer: React.FC<SlideViewerProps> = React.memo((props: SlideV
     <div className={`${MARP_CONTAINER_CLASS_NAME}`}>
       <div className="slides">
         <Slides
-          hasMarpFlag={marp === 'marp'}
+          hasMarpFlag={marp != null}
           options={{ rendererOptions: rendererOptions as ReactMarkdownOptions }}
         >
           {children}

+ 0 - 1
apps/app/src/stores/renderer.tsx

@@ -1,4 +1,3 @@
-/* eslint-disable import/no-cycle */
 import { useCallback } from 'react';
 
 import type { HtmlElementNode } from 'rehype-toc';

+ 27 - 0
apps/app/src/stores/slide-viewer-renderer.tsx

@@ -0,0 +1,27 @@
+import useSWR, { type SWRResponse } from 'swr';
+
+import type { RendererOptions } from '~/interfaces/renderer-options';
+import { useRendererConfig } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
+
+
+export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { data: rendererConfig } = useRendererConfig();
+
+  const isAllDataValid = currentPagePath != null && rendererConfig != null;
+
+  return useSWR(
+    isAllDataValid
+      ? ['presentationViewOptions', currentPagePath, rendererConfig]
+      : null,
+    async([, currentPagePath, rendererConfig]) => {
+      const { generatePresentationViewOptions } = await import('~/client/services/renderer/slide-viewer-renderer');
+      return generatePresentationViewOptions(rendererConfig, currentPagePath);
+    },
+    {
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+    },
+  );
+};

+ 35 - 9
packages/presentation/src/services/renderer/slides.ts

@@ -9,6 +9,35 @@ import { visit } from 'unist-util-visit';
 
 const SUPPORTED_ATTRIBUTES = ['children', 'marp'];
 
+const nodeToMakrdown = (node: Node) => {
+  return toMarkdown(node as Root, {
+    extensions: [
+      frontmatterToMarkdown(['yaml']),
+      gfmToMarkdown(),
+    ],
+  });
+};
+
+// Allow node tree to be converted to markdown
+const removeCustomType = (tree: Node) => {
+  // Try toMarkdown() on all Node.
+  visit(tree, (node) => {
+    const tmp = node?.children;
+    node.children = [];
+    try {
+      nodeToMakrdown(node);
+    }
+    catch (err) {
+      // if some Node cannot convert to markdown, change to a convertible type
+      node.type = 'text';
+      node.value = '';
+    }
+    finally {
+      node.children = tmp;
+    }
+  });
+};
+
 const rewriteNode = (tree: Node, node: Node, isEnabledMarp: boolean) => {
   let slide = false;
   let marp = false;
@@ -32,6 +61,10 @@ const rewriteNode = (tree: Node, node: Node, isEnabledMarp: boolean) => {
 
   if (marp || slide) {
 
+    removeCustomType(tree);
+
+    const markdown = nodeToMakrdown(tree);
+
     const newNode: Node = {
       type: 'root',
       data: {},
@@ -43,15 +76,8 @@ const rewriteNode = (tree: Node, node: Node, isEnabledMarp: boolean) => {
     tree.children = [newNode];
     data.hName = 'slide';
     data.hProperties = {
-      marp: marp ? 'marp' : '',
-      children: toMarkdown(tree as Root, {
-        extensions: [
-          frontmatterToMarkdown(['yaml']),
-          gfmToMarkdown(),
-          // TODO: add new extension remark-growi-directive to markdown
-          // https://redmine.weseek.co.jp/issues/126744
-        ],
-      }),
+      marp: marp ? '' : undefined,
+      children: markdown,
     };
   }
 };