PageView.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import React, {
  2. useEffect, useMemo, useRef, useState,
  3. } from 'react';
  4. import { type IPagePopulatedToShowRevision, pagePathUtils } from '@growi/core';
  5. import dynamic from 'next/dynamic';
  6. import type { RendererConfig } from '~/interfaces/services/renderer';
  7. import { generateSSRViewOptions } from '~/services/renderer/renderer';
  8. import {
  9. useIsForbidden, useIsIdenticalPath, useIsNotCreatable, useIsNotFound,
  10. } from '~/stores/context';
  11. import { useSWRxCurrentPage } from '~/stores/page';
  12. import { useViewOptions } from '~/stores/renderer';
  13. import { useIsMobile } from '~/stores/ui';
  14. import { registerGrowiFacade } from '~/utils/growi-facade';
  15. import type { CommentsProps } from '../Comments';
  16. import { MainPane } from '../Layout/MainPane';
  17. import { PageAlerts } from '../PageAlert/PageAlerts';
  18. import { PageContentFooter } from '../PageContentFooter';
  19. import type { PageSideContentsProps } from '../PageSideContents';
  20. import { UserInfo } from '../User/UserInfo';
  21. import type { UsersHomePageFooterProps } from '../UsersHomePageFooter';
  22. import RevisionRenderer from './RevisionRenderer';
  23. import styles from './PageView.module.scss';
  24. const { isUsersHomePage } = pagePathUtils;
  25. const NotCreatablePage = dynamic(() => import('../NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
  26. const ForbiddenPage = dynamic(() => import('../ForbiddenPage'), { ssr: false });
  27. const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
  28. const PageSideContents = dynamic<PageSideContentsProps>(() => import('../PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
  29. const PageContentsUtilities = dynamic(() => import('./PageContentsUtilities').then(mod => mod.PageContentsUtilities), { ssr: false });
  30. const Comments = dynamic<CommentsProps>(() => import('../Comments').then(mod => mod.Comments), { ssr: false });
  31. const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../UsersHomePageFooter')
  32. .then(mod => mod.UsersHomePageFooter), { ssr: false });
  33. const IdenticalPathPage = dynamic(() => import('../IdenticalPathPage').then(mod => mod.IdenticalPathPage), { ssr: false });
  34. type Props = {
  35. pagePath: string,
  36. rendererConfig: RendererConfig,
  37. initialPage?: IPagePopulatedToShowRevision,
  38. }
  39. export const PageView = (props: Props): JSX.Element => {
  40. const commentsContainerRef = useRef<HTMLDivElement>(null);
  41. const [isCommentsLoaded, setCommentsLoaded] = useState(false);
  42. const {
  43. pagePath, initialPage, rendererConfig,
  44. } = props;
  45. const { data: isIdenticalPathPage } = useIsIdenticalPath();
  46. const { data: isForbidden } = useIsForbidden();
  47. const { data: isNotCreatable } = useIsNotCreatable();
  48. const { data: isNotFoundMeta } = useIsNotFound();
  49. const { data: isMobile } = useIsMobile();
  50. const { data: pageBySWR } = useSWRxCurrentPage();
  51. const { data: viewOptions, mutate: mutateRendererOptions } = useViewOptions();
  52. const page = pageBySWR ?? initialPage;
  53. const isNotFound = isNotFoundMeta || page == null;
  54. const isUsersHomePagePath = isUsersHomePage(pagePath);
  55. // register to facade
  56. useEffect(() => {
  57. registerGrowiFacade({
  58. markdownRenderer: {
  59. optionsMutators: {
  60. viewOptionsMutator: mutateRendererOptions,
  61. },
  62. },
  63. });
  64. }, [mutateRendererOptions]);
  65. // *************************** Auto Scroll ***************************
  66. useEffect(() => {
  67. // do nothing if hash is empty
  68. const { hash } = window.location;
  69. if (hash.length === 0) {
  70. return;
  71. }
  72. const targetId = hash.slice(1);
  73. const target = document.getElementById(targetId);
  74. target?.scrollIntoView();
  75. }, [isCommentsLoaded]);
  76. // ******************************* end *******************************
  77. const specialContents = useMemo(() => {
  78. if (isIdenticalPathPage) {
  79. return <IdenticalPathPage />;
  80. }
  81. if (isForbidden) {
  82. return <ForbiddenPage />;
  83. }
  84. if (isNotCreatable) {
  85. return <NotCreatablePage />;
  86. }
  87. }, [isForbidden, isIdenticalPathPage, isNotCreatable]);
  88. const sideContents = !isNotFound && !isNotCreatable
  89. ? (
  90. <PageSideContents page={page} />
  91. )
  92. : null;
  93. const footerContents = !isIdenticalPathPage && !isNotFound
  94. ? (
  95. <>
  96. <div id="comments-container" ref={commentsContainerRef}>
  97. <Comments pageId={page._id} pagePath={pagePath} revision={page.revision} onLoaded={() => setCommentsLoaded(true)} />
  98. </div>
  99. { isUsersHomePagePath && (
  100. <UsersHomePageFooter creatorId={page.creator._id}/>
  101. ) }
  102. <PageContentFooter page={page} />
  103. </>
  104. )
  105. : null;
  106. const Contents = () => {
  107. if (isNotFound) {
  108. return <NotFoundPage path={pagePath} />;
  109. }
  110. const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
  111. const markdown = page.revision.body;
  112. return (
  113. <>
  114. <PageContentsUtilities />
  115. <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
  116. </>
  117. );
  118. };
  119. return (
  120. <MainPane
  121. sideContents={sideContents}
  122. footerContents={footerContents}
  123. >
  124. <PageAlerts />
  125. { specialContents }
  126. { specialContents == null && (
  127. <>
  128. { isUsersHomePagePath && <UserInfo author={page?.creator} /> }
  129. <div className={`mb-5 ${isMobile ? `page-mobile ${styles['page-mobile']}` : ''}`}>
  130. <Contents />
  131. </div>
  132. </>
  133. ) }
  134. </MainPane>
  135. );
  136. };