SlideViewerRenderer.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client/index.mjs';
  2. import * as drawio from '@growi/remark-drawio';
  3. // eslint-disable-next-line import/extensions
  4. import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client/index.mjs';
  5. import katex from 'rehype-katex';
  6. import sanitize from 'rehype-sanitize';
  7. import breaks from 'remark-breaks';
  8. import math from 'remark-math';
  9. import useSWR, { type SWRResponse } from 'swr';
  10. import deepmerge from 'ts-deepmerge';
  11. import type { Pluggable } from 'unified';
  12. import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
  13. import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
  14. import * as mermaid from '~/features/mermaid';
  15. import { RehypeSanitizeOption } from '~/interfaces/rehype';
  16. import type { RendererOptions } from '~/interfaces/renderer-options';
  17. import type { RendererConfig } from '~/interfaces/services/renderer';
  18. import * as keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
  19. import * as attachment from '~/services/renderer/remark-plugins/attachment';
  20. import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
  21. import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
  22. import {
  23. commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin,
  24. } from '~/services/renderer/renderer';
  25. import { useRendererConfig } from '~/stores/context';
  26. import { useCurrentPagePath } from '~/stores/page';
  27. const generateSimpleViewOptions = (
  28. config: RendererConfig,
  29. pagePath: string,
  30. highlightKeywords?: string | string[],
  31. overrideIsEnabledLinebreaks?: boolean,
  32. ): RendererOptions => {
  33. const options = generateCommonOptions(pagePath);
  34. const { remarkPlugins, rehypePlugins, components } = options;
  35. // add remark plugins
  36. remarkPlugins.push(
  37. math,
  38. [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
  39. drawio.remarkPlugin,
  40. mermaid.remarkPlugin,
  41. xsvToTable.remarkPlugin,
  42. attachment.remarkPlugin,
  43. lsxGrowiDirective.remarkPlugin,
  44. refsGrowiDirective.remarkPlugin,
  45. );
  46. const isEnabledLinebreaks = overrideIsEnabledLinebreaks ?? config.isEnabledLinebreaks;
  47. if (isEnabledLinebreaks) {
  48. remarkPlugins.push(breaks);
  49. }
  50. if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
  51. injectCustomSanitizeOption(config);
  52. }
  53. const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
  54. ? [sanitize, deepmerge(
  55. commonSanitizeOption,
  56. drawio.sanitizeOption,
  57. mermaid.sanitizeOption,
  58. attachment.sanitizeOption,
  59. lsxGrowiDirective.sanitizeOption,
  60. refsGrowiDirective.sanitizeOption,
  61. )]
  62. : () => {};
  63. // add rehype plugins
  64. rehypePlugins.push(
  65. [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
  66. [refsGrowiDirective.rehypePlugin, { pagePath }],
  67. [keywordHighlighter.rehypePlugin, { keywords: highlightKeywords }],
  68. rehypeSanitizePlugin,
  69. katex,
  70. );
  71. // add components
  72. if (components != null) {
  73. components.lsx = lsxGrowiDirective.LsxImmutable;
  74. components.ref = refsGrowiDirective.RefImmutable;
  75. components.refs = refsGrowiDirective.RefsImmutable;
  76. components.refimg = refsGrowiDirective.RefImgImmutable;
  77. components.refsimg = refsGrowiDirective.RefsImgImmutable;
  78. components.gallery = refsGrowiDirective.GalleryImmutable;
  79. components.drawio = drawio.DrawioViewer;
  80. components.mermaid = mermaid.MermaidViewer;
  81. components.attachment = RichAttachment;
  82. components.img = LightBox;
  83. }
  84. if (config.isEnabledXssPrevention) {
  85. verifySanitizePlugin(options, false);
  86. }
  87. return options;
  88. };
  89. const generatePresentationViewOptions = (
  90. config: RendererConfig,
  91. pagePath: string,
  92. ): RendererOptions => {
  93. // based on simple view options
  94. const options = generateSimpleViewOptions(config, pagePath);
  95. if (config.isEnabledXssPrevention) {
  96. verifySanitizePlugin(options, false);
  97. }
  98. return options;
  99. };
  100. export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
  101. const { data: currentPagePath } = useCurrentPagePath();
  102. const { data: rendererConfig } = useRendererConfig();
  103. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  104. return useSWR(
  105. isAllDataValid
  106. ? ['presentationViewOptions', currentPagePath, rendererConfig]
  107. : null,
  108. async([, currentPagePath, rendererConfig]) => {
  109. return generatePresentationViewOptions(rendererConfig, currentPagePath);
  110. },
  111. {
  112. revalidateOnFocus: false,
  113. revalidateOnReconnect: false,
  114. },
  115. );
  116. };