renderer.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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 { useRendererConfig } from '~/stores-universal/context';
  8. import { useNextThemes } from '~/stores-universal/use-next-themes';
  9. import loggerFactory from '~/utils/logger';
  10. import { useCurrentPagePath } from './page';
  11. import { useCurrentPageTocNode } from './ui';
  12. const logger = loggerFactory('growi:cli:services:renderer');
  13. const useRendererConfigExt = (): RendererConfigExt | null => {
  14. const { data: 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 { data: currentPagePath } = useCurrentPagePath();
  25. const rendererConfig = useRendererConfigExt();
  26. const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
  27. const storeTocNodeHandler = useCallback(
  28. (toc: HtmlElementNode) => {
  29. mutateCurrentPageTocNode(toc, { revalidate: false });
  30. },
  31. [mutateCurrentPageTocNode],
  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 useTocOptions = (): SWRResponse<RendererOptions, Error> => {
  66. const { data: currentPagePath } = useCurrentPagePath();
  67. const rendererConfig = useRendererConfigExt();
  68. const { data: tocNode } = useCurrentPageTocNode();
  69. const isAllDataValid =
  70. currentPagePath != null && rendererConfig != null && tocNode != null;
  71. return useSWR(
  72. isAllDataValid
  73. ? ['tocOptions', currentPagePath, tocNode, rendererConfig]
  74. : null,
  75. async ([, , tocNode, rendererConfig]) => {
  76. const { generateTocOptions } = await import(
  77. '~/client/services/renderer/renderer'
  78. );
  79. return generateTocOptions(rendererConfig, tocNode);
  80. },
  81. {
  82. keepPreviousData: true,
  83. revalidateOnFocus: false,
  84. revalidateOnReconnect: false,
  85. },
  86. );
  87. };
  88. export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
  89. const { data: currentPagePath } = useCurrentPagePath();
  90. const rendererConfig = useRendererConfigExt();
  91. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  92. const customGenerater =
  93. getGrowiFacade().markdownRenderer?.optionsGenerators
  94. ?.customGeneratePreviewOptions;
  95. return useSWR(
  96. isAllDataValid
  97. ? ['previewOptions', rendererConfig, currentPagePath, customGenerater]
  98. : null,
  99. async ([, rendererConfig, pagePath]) => {
  100. if (customGenerater != null) {
  101. return customGenerater(rendererConfig, pagePath);
  102. }
  103. const { generatePreviewOptions } = await import(
  104. '~/client/services/renderer/renderer'
  105. );
  106. return generatePreviewOptions(rendererConfig, pagePath);
  107. },
  108. {
  109. keepPreviousData: true,
  110. revalidateOnFocus: false,
  111. revalidateOnReconnect: false,
  112. },
  113. );
  114. };
  115. export const useCommentForCurrentPageOptions = (): SWRResponse<
  116. RendererOptions,
  117. Error
  118. > => {
  119. const { data: currentPagePath } = useCurrentPagePath();
  120. const rendererConfig = useRendererConfigExt();
  121. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  122. return useSWR(
  123. isAllDataValid
  124. ? ['commentPreviewOptions', rendererConfig, currentPagePath]
  125. : null,
  126. async ([, rendererConfig, currentPagePath]) => {
  127. const { generateSimpleViewOptions } = await import(
  128. '~/client/services/renderer/renderer'
  129. );
  130. return generateSimpleViewOptions(
  131. rendererConfig,
  132. currentPagePath,
  133. undefined,
  134. rendererConfig.isEnabledLinebreaksInComments,
  135. );
  136. },
  137. {
  138. keepPreviousData: true,
  139. revalidateOnFocus: false,
  140. revalidateOnReconnect: false,
  141. },
  142. );
  143. };
  144. export const useCommentPreviewOptions = useCommentForCurrentPageOptions;
  145. export const useSelectedPagePreviewOptions = (
  146. pagePath: string,
  147. highlightKeywords?: string | string[],
  148. ): SWRResponse<RendererOptions, Error> => {
  149. const rendererConfig = useRendererConfigExt();
  150. const isAllDataValid = rendererConfig != null;
  151. return useSWR(
  152. isAllDataValid
  153. ? [
  154. 'selectedPagePreviewOptions',
  155. rendererConfig,
  156. pagePath,
  157. highlightKeywords,
  158. ]
  159. : null,
  160. async ([, rendererConfig, pagePath, highlightKeywords]) => {
  161. const { generateSimpleViewOptions } = await import(
  162. '~/client/services/renderer/renderer'
  163. );
  164. return generateSimpleViewOptions(
  165. rendererConfig,
  166. pagePath,
  167. highlightKeywords,
  168. );
  169. },
  170. {
  171. revalidateOnFocus: false,
  172. revalidateOnReconnect: false,
  173. },
  174. );
  175. };
  176. export const useSearchResultOptions = useSelectedPagePreviewOptions;
  177. export const useTimelineOptions = useSelectedPagePreviewOptions;
  178. export const useCustomSidebarOptions = (
  179. config?: SWRConfiguration,
  180. ): SWRResponse<RendererOptions, Error> => {
  181. const rendererConfig = useRendererConfigExt();
  182. const isAllDataValid = rendererConfig != null;
  183. return useSWR(
  184. isAllDataValid ? ['customSidebarOptions', rendererConfig] : null,
  185. async ([, rendererConfig]) => {
  186. const { generateSimpleViewOptions } = await import(
  187. '~/client/services/renderer/renderer'
  188. );
  189. return generateSimpleViewOptions(rendererConfig, '/');
  190. },
  191. {
  192. ...config,
  193. keepPreviousData: true,
  194. revalidateOnFocus: false,
  195. revalidateOnReconnect: false,
  196. },
  197. );
  198. };
  199. export const usePresentationViewOptions = (): SWRResponse<
  200. RendererOptions,
  201. Error
  202. > => {
  203. const { data: currentPagePath } = useCurrentPagePath();
  204. const rendererConfig = useRendererConfigExt();
  205. const isAllDataValid = currentPagePath != null && rendererConfig != null;
  206. useEffect(() => {
  207. if (rendererConfig == null) {
  208. logger.warn('RendererConfig is undefined or missing.');
  209. }
  210. }, [rendererConfig]);
  211. return useSWR(
  212. isAllDataValid
  213. ? ['presentationViewOptions', currentPagePath, rendererConfig]
  214. : null,
  215. async ([, currentPagePath, rendererConfig]) => {
  216. const { generatePresentationViewOptions } = await import(
  217. '~/client/services/renderer/renderer'
  218. );
  219. return generatePresentationViewOptions(rendererConfig, currentPagePath);
  220. },
  221. {
  222. revalidateOnFocus: false,
  223. revalidateOnReconnect: false,
  224. },
  225. );
  226. };