renderer.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import { useCallback, useEffect } from 'react';
  2. import type { HtmlElementNode } from 'rehype-toc';
  3. import useSWR, { type SWRConfiguration, type SWRResponse } from 'swr';
  4. import { getGrowiFacade } from '~/features/growi-plugin/client/utils/growi-facade-utils';
  5. import type { RendererOptions } from '~/interfaces/renderer-options';
  6. import type { RendererConfigExt } from '~/interfaces/services/renderer';
  7. import { useCurrentPagePath } from '~/states/page';
  8. import { useRendererConfig } from '~/states/server-configurations';
  9. import { useSetTocNode } from '~/states/ui/toc';
  10. import { useNextThemes } from '~/stores-universal/use-next-themes';
  11. import loggerFactory from '~/utils/logger';
  12. const logger = loggerFactory('growi:cli:services:renderer');
  13. const useRendererConfigExt = (): RendererConfigExt | null => {
  14. const rendererConfig = useRendererConfig();
  15. const { isDarkMode } = useNextThemes();
  16. return rendererConfig == null
  17. ? null
  18. : ({
  19. ...rendererConfig,
  20. isDarkMode,
  21. } satisfies RendererConfigExt);
  22. };
  23. export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
  24. const currentPagePath = useCurrentPagePath();
  25. const rendererConfig = useRendererConfigExt();
  26. const setTocNode = useSetTocNode();
  27. const storeTocNodeHandler = useCallback(
  28. (toc: HtmlElementNode) => {
  29. setTocNode(toc);
  30. },
  31. [setTocNode],
  32. );
  33. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  34. const customGenerater =
  35. getGrowiFacade().markdownRenderer?.optionsGenerators
  36. ?.customGenerateViewOptions;
  37. return useSWR(
  38. isAllDataValid
  39. ? ['viewOptions', currentPagePath, rendererConfig, customGenerater]
  40. : null,
  41. async ([, currentPagePath, rendererConfig]) => {
  42. if (customGenerater != null) {
  43. return customGenerater(
  44. currentPagePath,
  45. rendererConfig,
  46. storeTocNodeHandler,
  47. );
  48. }
  49. const { generateViewOptions } = await import(
  50. '~/client/services/renderer/renderer'
  51. );
  52. return generateViewOptions(
  53. currentPagePath,
  54. rendererConfig,
  55. storeTocNodeHandler,
  56. );
  57. },
  58. {
  59. keepPreviousData: true,
  60. revalidateOnFocus: false,
  61. revalidateOnReconnect: false,
  62. },
  63. );
  64. };
  65. export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
  66. const currentPagePath = useCurrentPagePath();
  67. const rendererConfig = useRendererConfigExt();
  68. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  69. const customGenerater =
  70. getGrowiFacade().markdownRenderer?.optionsGenerators
  71. ?.customGeneratePreviewOptions;
  72. return useSWR(
  73. isAllDataValid
  74. ? ['previewOptions', rendererConfig, currentPagePath, customGenerater]
  75. : null,
  76. async ([, rendererConfig, pagePath]) => {
  77. if (customGenerater != null) {
  78. return customGenerater(rendererConfig, pagePath);
  79. }
  80. const { generatePreviewOptions } = await import(
  81. '~/client/services/renderer/renderer'
  82. );
  83. return generatePreviewOptions(rendererConfig, pagePath);
  84. },
  85. {
  86. keepPreviousData: true,
  87. revalidateOnFocus: false,
  88. revalidateOnReconnect: false,
  89. },
  90. );
  91. };
  92. export const useCommentForCurrentPageOptions = (): SWRResponse<
  93. RendererOptions,
  94. Error
  95. > => {
  96. const currentPagePath = useCurrentPagePath();
  97. const rendererConfig = useRendererConfigExt();
  98. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  99. return useSWR(
  100. isAllDataValid
  101. ? ['commentPreviewOptions', rendererConfig, currentPagePath]
  102. : null,
  103. async ([, rendererConfig, currentPagePath]) => {
  104. const { generateSimpleViewOptions } = await import(
  105. '~/client/services/renderer/renderer'
  106. );
  107. return generateSimpleViewOptions(
  108. rendererConfig,
  109. currentPagePath,
  110. undefined,
  111. rendererConfig.isEnabledLinebreaksInComments,
  112. );
  113. },
  114. {
  115. keepPreviousData: true,
  116. revalidateOnFocus: false,
  117. revalidateOnReconnect: false,
  118. },
  119. );
  120. };
  121. export const useCommentPreviewOptions = useCommentForCurrentPageOptions;
  122. export const useSelectedPagePreviewOptions = (
  123. pagePath: string,
  124. highlightKeywords?: string | string[],
  125. ): SWRResponse<RendererOptions, Error> => {
  126. const rendererConfig = useRendererConfigExt();
  127. const isAllDataValid = rendererConfig != null;
  128. return useSWR(
  129. isAllDataValid
  130. ? [
  131. 'selectedPagePreviewOptions',
  132. rendererConfig,
  133. pagePath,
  134. highlightKeywords,
  135. ]
  136. : null,
  137. async ([, rendererConfig, pagePath, highlightKeywords]) => {
  138. const { generateSimpleViewOptions } = await import(
  139. '~/client/services/renderer/renderer'
  140. );
  141. return generateSimpleViewOptions(
  142. rendererConfig,
  143. pagePath,
  144. highlightKeywords,
  145. );
  146. },
  147. {
  148. revalidateOnFocus: false,
  149. revalidateOnReconnect: false,
  150. },
  151. );
  152. };
  153. export const useSearchResultOptions = useSelectedPagePreviewOptions;
  154. export const useTimelineOptions = useSelectedPagePreviewOptions;
  155. export const useCustomSidebarOptions = (
  156. config?: SWRConfiguration,
  157. ): SWRResponse<RendererOptions, Error> => {
  158. const rendererConfig = useRendererConfigExt();
  159. const isAllDataValid = rendererConfig != null;
  160. return useSWR(
  161. isAllDataValid ? ['customSidebarOptions', rendererConfig] : null,
  162. async ([, rendererConfig]) => {
  163. const { generateSimpleViewOptions } = await import(
  164. '~/client/services/renderer/renderer'
  165. );
  166. return generateSimpleViewOptions(rendererConfig, '/');
  167. },
  168. {
  169. ...config,
  170. keepPreviousData: true,
  171. revalidateOnFocus: false,
  172. revalidateOnReconnect: false,
  173. },
  174. );
  175. };
  176. export const usePresentationViewOptions = (): SWRResponse<
  177. RendererOptions,
  178. Error
  179. > => {
  180. const currentPagePath = useCurrentPagePath();
  181. const rendererConfig = useRendererConfigExt();
  182. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  183. useEffect(() => {
  184. if (rendererConfig == null) {
  185. logger.warn('RendererConfig is undefined or missing.');
  186. }
  187. }, [rendererConfig]);
  188. return useSWR(
  189. isAllDataValid
  190. ? ['presentationViewOptions', currentPagePath, rendererConfig]
  191. : null,
  192. async ([, currentPagePath, rendererConfig]) => {
  193. const { generatePresentationViewOptions } = await import(
  194. '~/client/services/renderer/renderer'
  195. );
  196. return generatePresentationViewOptions(rendererConfig, currentPagePath);
  197. },
  198. {
  199. revalidateOnFocus: false,
  200. revalidateOnReconnect: false,
  201. },
  202. );
  203. };