| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- import { useCallback, useEffect, useRef } from 'react';
- import type { HtmlElementNode } from 'rehype-toc';
- import useSWR, { type SWRConfiguration, type SWRResponse } from 'swr';
- import { getGrowiFacade } from '~/features/growi-plugin/client/utils/growi-facade-utils';
- import type { RendererOptions } from '~/interfaces/renderer-options';
- import type { RendererConfigExt } from '~/interfaces/services/renderer';
- import { useCurrentPagePath } from '~/states/page';
- import { useRendererConfig } from '~/states/server-configurations';
- import { useSetTocNode } from '~/states/ui/toc';
- import { useNextThemes } from '~/stores-universal/use-next-themes';
- import loggerFactory from '~/utils/logger';
- const logger = loggerFactory('growi:cli:services:renderer');
- const useRendererConfigExt = (): RendererConfigExt | null => {
- const rendererConfig = useRendererConfig();
- const { isDarkMode } = useNextThemes();
- return rendererConfig == null
- ? null
- : ({
- ...rendererConfig,
- isDarkMode,
- } satisfies RendererConfigExt);
- };
- export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
- const currentPagePath = useCurrentPagePath();
- const rendererConfig = useRendererConfigExt();
- const setTocNode = useSetTocNode();
- // Store TOC node in a ref during render phase (called by rehype plugin inside ReactMarkdown),
- // then sync to atom after commit to avoid "Cannot update a component while rendering a different component"
- const pendingTocNodeRef = useRef<HtmlElementNode | null>(null);
- const storeTocNodeHandler = useCallback((toc: HtmlElementNode) => {
- pendingTocNodeRef.current = toc;
- }, []);
- // No dependency array: runs after every render because the ref mutation
- // is invisible to React's dependency tracking
- useEffect(() => {
- if (pendingTocNodeRef.current != null) {
- setTocNode(pendingTocNodeRef.current);
- pendingTocNodeRef.current = null;
- }
- });
- const isAllDataValid = currentPagePath != null && rendererConfig != null;
- const customGenerater =
- getGrowiFacade().markdownRenderer?.optionsGenerators
- ?.customGenerateViewOptions;
- return useSWR(
- isAllDataValid
- ? ['viewOptions', currentPagePath, rendererConfig, customGenerater]
- : null,
- async ([, currentPagePath, rendererConfig]) => {
- if (customGenerater != null) {
- return customGenerater(
- currentPagePath,
- rendererConfig,
- storeTocNodeHandler,
- );
- }
- const { generateViewOptions } = await import(
- '~/client/services/renderer/renderer'
- );
- return generateViewOptions(
- currentPagePath,
- rendererConfig,
- storeTocNodeHandler,
- );
- },
- {
- keepPreviousData: true,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
- };
- export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
- const currentPagePath = useCurrentPagePath();
- const rendererConfig = useRendererConfigExt();
- const isAllDataValid = currentPagePath != null && rendererConfig != null;
- const customGenerater =
- getGrowiFacade().markdownRenderer?.optionsGenerators
- ?.customGeneratePreviewOptions;
- return useSWR(
- isAllDataValid
- ? ['previewOptions', rendererConfig, currentPagePath, customGenerater]
- : null,
- async ([, rendererConfig, pagePath]) => {
- if (customGenerater != null) {
- return customGenerater(rendererConfig, pagePath);
- }
- const { generatePreviewOptions } = await import(
- '~/client/services/renderer/renderer'
- );
- return generatePreviewOptions(rendererConfig, pagePath);
- },
- {
- keepPreviousData: true,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
- };
- export const useCommentForCurrentPageOptions = (): SWRResponse<
- RendererOptions,
- Error
- > => {
- const currentPagePath = useCurrentPagePath();
- const rendererConfig = useRendererConfigExt();
- const isAllDataValid = currentPagePath != null && rendererConfig != null;
- return useSWR(
- isAllDataValid
- ? ['commentPreviewOptions', rendererConfig, currentPagePath]
- : null,
- async ([, rendererConfig, currentPagePath]) => {
- const { generateSimpleViewOptions } = await import(
- '~/client/services/renderer/renderer'
- );
- return generateSimpleViewOptions(
- rendererConfig,
- currentPagePath,
- undefined,
- rendererConfig.isEnabledLinebreaksInComments,
- );
- },
- {
- keepPreviousData: true,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
- };
- export const useCommentPreviewOptions = useCommentForCurrentPageOptions;
- export const useSelectedPagePreviewOptions = (
- pagePath: string,
- highlightKeywords?: string | string[],
- ): SWRResponse<RendererOptions, Error> => {
- const rendererConfig = useRendererConfigExt();
- const isAllDataValid = rendererConfig != null;
- return useSWR(
- isAllDataValid
- ? [
- 'selectedPagePreviewOptions',
- rendererConfig,
- pagePath,
- highlightKeywords,
- ]
- : null,
- async ([, rendererConfig, pagePath, highlightKeywords]) => {
- const { generateSimpleViewOptions } = await import(
- '~/client/services/renderer/renderer'
- );
- return generateSimpleViewOptions(
- rendererConfig,
- pagePath,
- highlightKeywords,
- );
- },
- {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
- };
- export const useSearchResultOptions = useSelectedPagePreviewOptions;
- export const useTimelineOptions = useSelectedPagePreviewOptions;
- export const useCustomSidebarOptions = (
- config?: SWRConfiguration,
- ): SWRResponse<RendererOptions, Error> => {
- const rendererConfig = useRendererConfigExt();
- const isAllDataValid = rendererConfig != null;
- return useSWR(
- isAllDataValid ? ['customSidebarOptions', rendererConfig] : null,
- async ([, rendererConfig]) => {
- const { generateSimpleViewOptions } = await import(
- '~/client/services/renderer/renderer'
- );
- return generateSimpleViewOptions(rendererConfig, '/');
- },
- {
- ...config,
- keepPreviousData: true,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
- };
- export const usePresentationViewOptions = (): SWRResponse<
- RendererOptions,
- Error
- > => {
- const currentPagePath = useCurrentPagePath();
- const rendererConfig = useRendererConfigExt();
- const isAllDataValid = currentPagePath != null && rendererConfig != null;
- useEffect(() => {
- if (rendererConfig == null) {
- logger.warn('RendererConfig is undefined or missing.');
- }
- }, [rendererConfig]);
- return useSWR(
- isAllDataValid
- ? ['presentationViewOptions', currentPagePath, rendererConfig]
- : null,
- async ([, currentPagePath, rendererConfig]) => {
- const { generatePresentationViewOptions } = await import(
- '~/client/services/renderer/renderer'
- );
- return generatePresentationViewOptions(rendererConfig, currentPagePath);
- },
- {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
- };
|