RevisionLoader.tsx 2.9 KB

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