RevisionLoader.tsx 3.1 KB

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