ShareLinkPageView.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { type JSX, memo, useCallback, useMemo } from 'react';
  2. import dynamic from 'next/dynamic';
  3. import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
  4. import { PagePathNavTitle } from '~/components/Common/PagePathNavTitle';
  5. import type { RendererConfig } from '~/interfaces/services/renderer';
  6. import type { IShareLinkHasId } from '~/interfaces/share-link';
  7. import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
  8. import { generateSSRViewOptions } from '~/services/renderer/renderer';
  9. import { useCurrentPageData, usePageNotFound } from '~/states/page';
  10. import { useViewOptions } from '~/stores/renderer';
  11. import loggerFactory from '~/utils/logger';
  12. import { PageContentFooter } from '../PageView/PageContentFooter';
  13. import { PageViewLayout } from '../PageView/PageViewLayout';
  14. import RevisionRenderer from '../PageView/RevisionRenderer';
  15. import ShareLinkAlert from './ShareLinkAlert';
  16. const logger = loggerFactory('growi:components:ShareLinkPageView');
  17. const PageSideContents = dynamic(
  18. () =>
  19. import('~/client/components/PageSideContents').then(
  20. (mod) => mod.PageSideContents,
  21. ),
  22. { ssr: false },
  23. );
  24. const ForbiddenPage = dynamic(
  25. () => import('~/client/components/ForbiddenPage'),
  26. { ssr: false },
  27. );
  28. const SlideRenderer = dynamic(
  29. () =>
  30. import('~/client/components/Page/SlideRenderer').then(
  31. (mod) => mod.SlideRenderer,
  32. ),
  33. { ssr: false },
  34. );
  35. type Props = {
  36. pagePath: string;
  37. rendererConfig: RendererConfig;
  38. shareLink?: IShareLinkHasId;
  39. isExpired?: boolean;
  40. disableLinkSharing: boolean;
  41. };
  42. export const ShareLinkPageView = memo((props: Props): JSX.Element => {
  43. const { pagePath, rendererConfig, shareLink, isExpired, disableLinkSharing } =
  44. props;
  45. const isNotFoundMeta = usePageNotFound();
  46. const page = useCurrentPageData();
  47. const { data: viewOptions } = useViewOptions();
  48. const shouldExpandContent = useShouldExpandContent(page);
  49. const markdown = page?.revision?.body;
  50. const isSlide = useSlidesByFrontmatter(
  51. markdown,
  52. rendererConfig.isEnabledMarp,
  53. );
  54. const isNotFound = isNotFoundMeta || page == null || shareLink == null;
  55. const specialContents = useMemo(() => {
  56. if (disableLinkSharing) {
  57. return <ForbiddenPage isLinkSharingDisabled={props.disableLinkSharing} />;
  58. }
  59. }, [disableLinkSharing, props.disableLinkSharing]);
  60. const headerContents = (
  61. <PagePathNavTitle
  62. pageId={page?._id}
  63. pagePath={pagePath}
  64. isWipPage={page?.wip}
  65. />
  66. );
  67. const sideContents = !isNotFound ? <PageSideContents page={page} /> : null;
  68. const footerContents = !isNotFound ? <PageContentFooter page={page} /> : null;
  69. const Contents = useCallback(() => {
  70. if (isNotFound || page.revision == null) {
  71. // biome-ignore lint/complexity/noUselessFragments: ignore
  72. return <></>;
  73. }
  74. if (isExpired) {
  75. return (
  76. <h2 className="text-muted mt-4">
  77. <span className="material-symbols-outlined" aria-hidden="true">
  78. block
  79. </span>
  80. <span> Page is expired</span>
  81. </h2>
  82. );
  83. }
  84. const rendererOptions =
  85. viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
  86. const markdown = page.revision.body;
  87. return isSlide != null ? (
  88. <SlideRenderer marp={isSlide.marp} markdown={markdown} />
  89. ) : (
  90. <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
  91. );
  92. }, [
  93. isExpired,
  94. isSlide,
  95. pagePath,
  96. viewOptions,
  97. page?.revision?.body,
  98. rendererConfig,
  99. page?.revision,
  100. isNotFound,
  101. isSlide?.marp,
  102. ]);
  103. return (
  104. <PageViewLayout
  105. headerContents={headerContents}
  106. sideContents={sideContents}
  107. expandContentWidth={shouldExpandContent}
  108. footerContents={footerContents}
  109. >
  110. {specialContents}
  111. {specialContents == null && (
  112. <>
  113. {isNotFound && (
  114. <h2 className="text-muted mt-4">
  115. <span className="material-symbols-outlined" aria-hidden="true">
  116. block
  117. </span>
  118. <span> Page is not found</span>
  119. </h2>
  120. )}
  121. {!isNotFound && (
  122. <>
  123. <ShareLinkAlert
  124. expiredAt={shareLink.expiredAt}
  125. createdAt={shareLink.createdAt}
  126. />
  127. <div className="mb-5">
  128. <Contents />
  129. </div>
  130. </>
  131. )}
  132. </>
  133. )}
  134. </PageViewLayout>
  135. );
  136. });
  137. ShareLinkPageView.displayName = 'ShareLinkPageView';