Parcourir la source

modify initialization method for sanitize plugin

Yuki Takei il y a 3 ans
Parent
commit
ae0ffda02a
1 fichiers modifiés avec 98 ajouts et 45 suppressions
  1. 98 45
      packages/app/src/services/renderer/renderer.ts

+ 98 - 45
packages/app/src/services/renderer/renderer.ts

@@ -1,4 +1,5 @@
 import growiPlugin from '@growi/remark-growi-plugin';
+import { Schema as SanitizeOption } from 'hast-util-sanitize';
 import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 import katex from 'rehype-katex';
 import raw from 'rehype-raw';
@@ -10,6 +11,7 @@ import emoji from 'remark-emoji';
 import gfm from 'remark-gfm';
 import math from 'remark-math';
 import deepmerge from 'ts-deepmerge';
+import { PluggableList, Pluggable, PluginTuple } from 'unified';
 
 
 import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
@@ -218,23 +220,50 @@ const logger = loggerFactory('growi:util:GrowiRenderer');
 
 // }
 
-export type RendererOptions = Partial<ReactMarkdownOptions>;
-
+type SanitizePlugin = PluginTuple<[SanitizeOption]>;
+export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehypePlugins' | 'children'> & {
+  remarkPlugins: PluggableList,
+  rehypePlugins: PluggableList,
+};
 
-const generateCommonOptions = (pagePath: string|undefined, config: RendererConfig): RendererOptions => {
-  const sanitizeOption = deepmerge(
-    sanitizeDefaultSchema,
-    {
-      tagNames: [
-        'ls',
-        'lsx',
-      ],
-      attributes: {
-        '*': ['class', 'className'],
-      },
+const commonSanitizeOption: SanitizeOption = deepmerge(
+  sanitizeDefaultSchema,
+  {
+    attributes: {
+      '*': ['class', 'className'],
     },
-  );
+  },
+);
+
+const isSanitizePlugin = (pluggable: Pluggable): pluggable is SanitizePlugin => {
+  if (!Array.isArray(pluggable) || pluggable.length < 2) {
+    return false;
+  }
+  const sanitizeOption = pluggable[1];
+  return 'tagNames' in sanitizeOption && 'attributes' in sanitizeOption;
+};
+
+const hasSanitizePluginAtTheLast = (options: RendererOptions): boolean => {
+  const { rehypePlugins } = options;
+  if (rehypePlugins == null || rehypePlugins.length === 0) {
+    return false;
+  }
+
+  // get the last element
+  const lastPluggableElem = rehypePlugins.slice(-1)[0];
+
+  return isSanitizePlugin(lastPluggableElem);
+};
+
+const verifySanitizePlugin = (options: RendererOptions): void => {
+  if (hasSanitizePluginAtTheLast(options)) {
+    return;
+  }
 
+  throw new Error('The specified options does not have sanitize plugin in \'rehypePlugins\'');
+};
+
+const generateCommonOptions = (pagePath: string|undefined, config: RendererConfig): RendererOptions => {
   return {
     remarkPlugins: [
       gfm,
@@ -246,7 +275,6 @@ const generateCommonOptions = (pagePath: string|undefined, config: RendererConfi
       [relativeLinksByPukiwikiLikeLinker, { pagePath }],
       [relativeLinks, { pagePath }],
       raw,
-      [sanitize, sanitizeOption],
       [addClass, {
         table: 'table table-bordered',
       }],
@@ -269,19 +297,19 @@ export const generateViewOptions = (
   const { remarkPlugins, rehypePlugins, components } = options;
 
   // add remark plugins
-  if (remarkPlugins != null) {
-    remarkPlugins.push(emoji);
-    remarkPlugins.push(math);
-    if (config.isEnabledLinebreaks) {
-      remarkPlugins.push(breaks);
-    }
-    remarkPlugins.push(lsxGrowiPlugin.remarkPlugin);
+  remarkPlugins.push(
+    emoji,
+    math,
+    lsxGrowiPlugin.remarkPlugin,
+  );
+  if (config.isEnabledLinebreaks) {
+    remarkPlugins.push(breaks);
   }
 
-  // store toc node
-  if (rehypePlugins != null) {
-    rehypePlugins.push(katex);
-    rehypePlugins.push([toc, {
+  // add rehype plugins
+  rehypePlugins.push(
+    katex,
+    [toc, {
       nav: false,
       headings: ['h1', 'h2', 'h3'],
       customizeTOC: (toc: HtmlElementNode) => {
@@ -304,11 +332,22 @@ export const generateViewOptions = (
 
         return false; // not show toc in body
       },
-    }]);
-  }
-  // renderer.rehypePlugins.push([autoLinkHeadings, {
-  //   behavior: 'append',
-  // }]);
+    }],
+    // [autoLinkHeadings, {
+    //   behavior: 'append',
+    // }]
+  );
+
+  const sanitizeOption = deepmerge(
+    commonSanitizeOption,
+    {
+      tagNames: [
+        'ls',
+        'lsx',
+      ],
+    },
+  );
+  rehypePlugins.push([sanitize, sanitizeOption]);
 
   // add components
   if (components != null) {
@@ -329,6 +368,7 @@ export const generateViewOptions = (
   // renderer.setMarkdownSettings({ breaks: rendererSettings.isEnabledLinebreaks });
   // renderer.configure();
 
+  verifySanitizePlugin(options);
   return options;
 };
 
@@ -339,20 +379,21 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
   const { remarkPlugins, rehypePlugins } = options;
 
   // add remark plugins
-  if (remarkPlugins != null) {
-    remarkPlugins.push(emoji);
-  }
-  // set toc node
-  if (rehypePlugins != null) {
-    rehypePlugins.push([toc, {
+  remarkPlugins.push(emoji);
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [toc, {
       headings: ['h1', 'h2', 'h3'],
       customizeTOC: () => tocNode,
-    }]);
-  }
+    }],
+    [sanitize, commonSanitizeOption],
+  );
   // renderer.rehypePlugins.push([autoLinkHeadings, {
   //   behavior: 'append',
   // }]);
 
+  verifySanitizePlugin(options);
   return options;
 };
 
@@ -369,19 +410,18 @@ export const generatePreviewOptions = (config: RendererConfig): RendererOptions
   // renderer.setMarkdownSettings({ breaks: rendererSettings?.isEnabledLinebreaks });
   // renderer.configure();
 
+  verifySanitizePlugin(options);
   return options;
 };
 
 export const generateCommentPreviewOptions = (config: RendererConfig): RendererOptions => {
   const options = generateCommonOptions(undefined, config);
-  const { remarkPlugins } = options;
+  const { remarkPlugins, rehypePlugins } = options;
 
   // add remark plugins
-  if (remarkPlugins != null) {
-    remarkPlugins.push(emoji);
-    if (config.isEnabledLinebreaksInComments) {
-      remarkPlugins.push(breaks);
-    }
+  remarkPlugins.push(emoji);
+  if (config.isEnabledLinebreaksInComments) {
+    remarkPlugins.push(breaks);
   }
 
   // renderer.addConfigurers([
@@ -391,11 +431,18 @@ export const generateCommentPreviewOptions = (config: RendererConfig): RendererO
   // renderer.setMarkdownSettings({ breaks: rendererSettings.isEnabledLinebreaksInComments });
   // renderer.configure();
 
+  // add rehype plugins
+  rehypePlugins.push(
+    [sanitize, commonSanitizeOption],
+  );
+
+  verifySanitizePlugin(options);
   return options;
 };
 
 export const generateOthersOptions = (config: RendererConfig): RendererOptions => {
   const options = generateCommonOptions(undefined, config);
+  const { rehypePlugins } = options;
 
   // renderer.addConfigurers([
   //   new TableConfigurer(),
@@ -404,5 +451,11 @@ export const generateOthersOptions = (config: RendererConfig): RendererOptions =
   // renderer.setMarkdownSettings({ breaks: rendererSettings.isEnabledLinebreaks });
   // renderer.configure();
 
+  // add rehype plugins
+  rehypePlugins.push(
+    [sanitize, commonSanitizeOption],
+  );
+
+  verifySanitizePlugin(options);
   return options;
 };