| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- import { type JSX, memo, useCallback, useId, useMemo, useRef } from 'react';
- import dynamic from 'next/dynamic';
- import { isDeepEquals } from '@growi/core/dist/utils/is-deep-equals';
- import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
- import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
- import { PagePathNavTitle } from '~/components/Common/PagePathNavTitle';
- import type { RendererConfig } from '~/interfaces/services/renderer';
- import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
- import {
- useCurrentPageData,
- useCurrentPageId,
- useIsForbidden,
- useIsIdenticalPath,
- useIsNotCreatable,
- usePageNotFound,
- } from '~/states/page';
- import { useViewOptions } from '~/stores/renderer';
- import { UserInfo } from '../User/UserInfo';
- import { PageAlerts } from './PageAlerts/PageAlerts';
- import { PageContentFooter } from './PageContentFooter';
- import { PageViewLayout } from './PageViewLayout';
- import { useHashAutoScroll } from './use-hash-auto-scroll';
- // biome-ignore-start lint/style/noRestrictedImports: no-problem dynamic import
- const NotCreatablePage = dynamic(
- () =>
- import('~/client/components/NotCreatablePage').then(
- (mod) => mod.NotCreatablePage,
- ),
- { ssr: false },
- );
- const ForbiddenPage = dynamic(
- () => import('~/client/components/ForbiddenPage'),
- { ssr: false },
- );
- const NotFoundPage = dynamic(() => import('~/client/components/NotFoundPage'), {
- ssr: false,
- });
- const PageSideContents = dynamic(
- () =>
- import('~/client/components/PageSideContents').then(
- (mod) => mod.PageSideContents,
- ),
- { ssr: false },
- );
- const PageContentsUtilities = dynamic(
- () =>
- import('~/client/components/Page/PageContentsUtilities').then(
- (mod) => mod.PageContentsUtilities,
- ),
- { ssr: false },
- );
- const Comments = dynamic(
- () => import('~/client/components/Comments').then((mod) => mod.Comments),
- { ssr: false },
- );
- const UsersHomepageFooter = dynamic(
- () =>
- import('~/client/components/UsersHomepageFooter').then(
- (mod) => mod.UsersHomepageFooter,
- ),
- { ssr: false },
- );
- const IdenticalPathPage = dynamic(
- () =>
- import('~/client/components/IdenticalPathPage').then(
- (mod) => mod.IdenticalPathPage,
- ),
- { ssr: false },
- );
- const SlideRenderer = dynamic(
- () =>
- import('~/client/components/Page/SlideRenderer').then(
- (mod) => mod.SlideRenderer,
- ),
- { ssr: false },
- );
- const PageContentRenderer = dynamic(
- () => import('./PageContentRenderer').then((mod) => mod.PageContentRenderer),
- { ssr: true },
- );
- // biome-ignore-end lint/style/noRestrictedImports: no-problem dynamic import
- type Props = {
- pagePath: string;
- rendererConfig: RendererConfig;
- className?: string;
- };
- // Custom comparison function for memo to prevent unnecessary re-renders
- const arePropsEqual = (prevProps: Props, nextProps: Props): boolean =>
- prevProps.pagePath === nextProps.pagePath &&
- prevProps.className === nextProps.className &&
- isDeepEquals(prevProps.rendererConfig, nextProps.rendererConfig);
- const PageViewComponent = (props: Props): JSX.Element => {
- const commentsContainerRef = useRef<HTMLDivElement>(null);
- const { pagePath, rendererConfig, className } = props;
- const currentPageId = useCurrentPageId();
- const isIdenticalPathPage = useIsIdenticalPath();
- const isForbidden = useIsForbidden();
- const isNotCreatable = useIsNotCreatable();
- const isNotFoundMeta = usePageNotFound();
- const contentContainerId = useId();
- const page = useCurrentPageData();
- const { data: viewOptions } = useViewOptions();
- const isNotFound = isNotFoundMeta || page == null;
- const isUsersHomepagePath = isUsersHomepage(pagePath);
- const shouldExpandContent = useShouldExpandContent(page);
- const markdown = page?.revision?.body;
- const isSlide = useSlidesByFrontmatter(
- markdown,
- rendererConfig.isEnabledMarp,
- );
- // Auto-scroll to URL hash target, handling lazy-rendered content
- useHashAutoScroll({ key: currentPageId, contentContainerId });
- const specialContents = useMemo(() => {
- if (isIdenticalPathPage) {
- return <IdenticalPathPage />;
- }
- if (isForbidden) {
- return <ForbiddenPage />;
- }
- if (isNotCreatable) {
- return <NotCreatablePage />;
- }
- }, [isForbidden, isIdenticalPathPage, isNotCreatable]);
- const headerContents = (
- <PagePathNavTitle
- pageId={page?._id}
- pagePath={pagePath}
- isWipPage={page?.wip}
- />
- );
- const sideContents =
- !isNotFound && !isNotCreatable ? <PageSideContents page={page} /> : null;
- const footerContents =
- !isIdenticalPathPage && !isNotFound ? (
- <>
- {isUsersHomepagePath && page.creator != null && (
- <UsersHomepageFooter creatorId={page.creator._id} />
- )}
- <PageContentFooter page={page} />
- </>
- ) : null;
- const Contents = useCallback(() => {
- if (isNotFound || page?.revision == null) {
- return <NotFoundPage path={pagePath} />;
- }
- const markdown = page.revision.body;
- return (
- <>
- <PageContentsUtilities />
- <div className="flex-expand-vert justify-content-between">
- {isSlide != null ? (
- <SlideRenderer marp={isSlide.marp} markdown={markdown} />
- ) : (
- <PageContentRenderer
- rendererOptions={viewOptions}
- rendererConfig={rendererConfig}
- pagePath={pagePath}
- markdown={markdown}
- />
- )}
- {!isIdenticalPathPage && !isNotFound && (
- <div id="comments-container" ref={commentsContainerRef}>
- <Comments
- pageId={page._id}
- pagePath={pagePath}
- revision={page.revision}
- />
- </div>
- )}
- </div>
- </>
- );
- }, [
- isNotFound,
- page?.revision,
- page?._id,
- rendererConfig,
- pagePath,
- viewOptions,
- isSlide,
- isIdenticalPathPage,
- page,
- ]);
- return (
- <PageViewLayout
- className={className}
- headerContents={headerContents}
- sideContents={sideContents}
- footerContents={footerContents}
- expandContentWidth={shouldExpandContent}
- >
- <PageAlerts />
- {specialContents}
- {specialContents == null && (
- <>
- {isUsersHomepagePath && page?.creator != null && (
- <UserInfo author={page.creator} />
- )}
- <div id={contentContainerId} className="flex-expand-vert">
- <Contents />
- </div>
- </>
- )}
- </PageViewLayout>
- );
- };
- export const PageView = memo(PageViewComponent, arePropsEqual);
- PageView.displayName = 'PageView';
|