RevisionLoader.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import React, { useEffect, useState, useCallback } from 'react';
  2. import { Ref, IRevision, IRevisionHasId } from '@growi/core';
  3. import { useTranslation } from 'next-i18next';
  4. import { Waypoint } from 'react-waypoint';
  5. import { apiv3Get } from '~/client/util/apiv3-client';
  6. import { RendererOptions } from '~/services/renderer/renderer';
  7. import loggerFactory from '~/utils/logger';
  8. import RevisionRenderer from './RevisionRenderer';
  9. export const ROOT_ELEM_ID = 'revision-loader' as const;
  10. export type RevisionLoaderProps = {
  11. rendererOptions: RendererOptions,
  12. pageId: string,
  13. revisionId: Ref<IRevision>,
  14. lazy?: boolean,
  15. onRevisionLoaded?: (revision: IRevisionHasId) => void,
  16. }
  17. const logger = loggerFactory('growi:Page:RevisionLoader');
  18. // Always render '#revision-loader' for MutationObserver of SearchResultContent
  19. const RevisionLoaderRoot = (props: React.HTMLAttributes<HTMLDivElement>): JSX.Element => (
  20. <div data-testid={'テストID'} id={ROOT_ELEM_ID} {...props}>{props.children}</div>
  21. );
  22. /**
  23. * Load data from server and render RevisionBody component
  24. */
  25. export const RevisionLoader = (props: RevisionLoaderProps): JSX.Element => {
  26. const { t } = useTranslation();
  27. const {
  28. rendererOptions, pageId, revisionId, lazy, onRevisionLoaded,
  29. } = props;
  30. const [isLoading, setIsLoading] = useState<boolean>();
  31. const [isLoaded, setIsLoaded] = useState<boolean>();
  32. const [markdown, setMarkdown] = useState<string>('');
  33. const [errors, setErrors] = useState<any | null>();
  34. const loadData = useCallback(async() => {
  35. if (!isLoaded && !isLoading) {
  36. setIsLoading(true);
  37. }
  38. // load data with REST API
  39. try {
  40. const res = await apiv3Get(`/revisions/${revisionId}`, { pageId });
  41. setMarkdown(res.data?.revision?.body);
  42. setErrors(null);
  43. if (onRevisionLoaded != null) {
  44. onRevisionLoaded(res.data.revision);
  45. }
  46. }
  47. catch (errors) {
  48. setErrors(errors);
  49. }
  50. finally {
  51. setIsLoaded(true);
  52. setIsLoading(false);
  53. }
  54. }, [isLoaded, isLoading, onRevisionLoaded, pageId, revisionId]);
  55. useEffect(() => {
  56. if (!lazy) {
  57. loadData();
  58. }
  59. }, [lazy, loadData]);
  60. const onWaypointChange = (event) => {
  61. if (event.currentPosition === Waypoint.above || event.currentPosition === Waypoint.inside) {
  62. loadData();
  63. }
  64. return;
  65. };
  66. /* ----- before load ----- */
  67. if (lazy && !isLoaded) {
  68. return (
  69. <Waypoint onPositionChange={onWaypointChange} bottomOffset="-100px">
  70. <></>
  71. </Waypoint>
  72. );
  73. }
  74. /* ----- loading ----- */
  75. if (isLoading) {
  76. return (
  77. <div className="wiki">
  78. <div className="text-muted text-center">
  79. <i className="fa fa-2x fa-spinner fa-pulse mr-1"></i>
  80. </div>
  81. </div>
  82. );
  83. }
  84. /* ----- after load ----- */
  85. const isForbidden = errors != null && errors[0].code === 'forbidden-page';
  86. if (isForbidden) {
  87. setMarkdown(`<i class="icon-exclamation p-1"></i>${t('not_allowed_to_see_this_page')}`);
  88. }
  89. else if (errors != null) {
  90. const errorMessages = errors.map((error) => {
  91. return `<i class="icon-exclamation p-1"></i><span class="text-muted"><em>${error.message}</em></span>`;
  92. });
  93. setMarkdown(errorMessages.join('\n'));
  94. }
  95. return (
  96. <RevisionLoaderRoot>
  97. <RevisionRenderer
  98. rendererOptions={rendererOptions}
  99. markdown={markdown}
  100. />
  101. </RevisionLoaderRoot>
  102. );
  103. };