RevisionCompareContainer.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { Container } from 'unstated';
  2. import loggerFactory from '@alias/logger';
  3. import { toastError } from '../util/apiNotification';
  4. const logger = loggerFactory('growi:PageHistoryContainer');
  5. /**
  6. * Service container for personal settings page (RevisionCompare.jsx)
  7. * @extends {Container} unstated Container
  8. */
  9. export default class RevisionCompareContainer extends Container {
  10. constructor(appContainer, pageContainer) {
  11. super();
  12. this.appContainer = appContainer;
  13. this.pageContainer = pageContainer;
  14. this.state = {
  15. errMessage: null,
  16. fromRevision: null,
  17. toRevision: null,
  18. revisions: [],
  19. }
  20. this.readyRevisions = this.readyRevisions.bind(this);
  21. this.fetchPageRevisionBody = this.fetchPageRevisionBody.bind(this);
  22. this.fetchAllPageRevisions = this.fetchAllPageRevisions.bind(this);
  23. this.fetchPageRevision = this.fetchPageRevision.bind(this);
  24. this.handleFromRevisionChange = this.handleFromRevisionChange.bind(this);
  25. this.handleToRevisionChange = this.handleToRevisionChange.bind(this);
  26. }
  27. /**
  28. * Workaround for the mangling in production build to break constructor.name
  29. */
  30. static getClassName() {
  31. return 'RevisionCompareContainer';
  32. }
  33. async readyRevisions() {
  34. const [fromRevisionIdParam, toRevisionIdParam] = this.pageContainer.state.compareRevisionIds || [];
  35. await this.fetchAllPageRevisions();
  36. if (fromRevisionIdParam) {
  37. await this.handleFromRevisionChange(fromRevisionIdParam);
  38. }
  39. if (toRevisionIdParam) {
  40. await this.handleToRevisionChange(toRevisionIdParam);
  41. }
  42. }
  43. /**
  44. * Fetch page revision body by revision_id in argument
  45. * @param {string} revisionId
  46. */
  47. async fetchPageRevisionBody(revisionId) {
  48. const { pageId, shareLinkId } = this.pageContainer.state;
  49. try {
  50. const res = await this.appContainer.apiv3Get(`/revisions/${revisionId}`, { pageId, shareLinkId });
  51. if (res == null || res.data == undefined || res.data.revision == undefined) {
  52. logger.warn(`cannot get revision. revisionId: ${revisionId}`);
  53. return null;
  54. }
  55. return res.data.revision;
  56. }
  57. catch (err) {
  58. toastError(err);
  59. this.setState({ errorMessage: err.message });
  60. logger.error(err);
  61. }
  62. }
  63. /**
  64. * Fetch all page revisions
  65. *
  66. * Each revision to be saved contains only "_id" and "createdAt", and "body" is initialized to null.
  67. * ex. [{_id: "5ff03fded799ebc858a09266", body: null, creat…}, {_id: "5ff03fbed799ebc858a09262", body: null, creat…}]
  68. */
  69. async fetchAllPageRevisions() {
  70. const { pageId, shareLinkId } = this.pageContainer.state;
  71. // fetch all page revisions that are sorted update day time descending
  72. let max = 1000; // Maximum number of loops to avoid infinite loops.
  73. let newRevisions = [];
  74. let page = 1;
  75. let res = null;
  76. do {
  77. res = await this.appContainer.apiv3Get('/revisions/list', {
  78. pageId, shareLinkId, page, limit: 1,
  79. });
  80. newRevisions = newRevisions.concat(res.data.docs.map(rev => {
  81. const { _id, createdAt } = rev;
  82. return { _id, createdAt, body: null };
  83. }));
  84. page++;
  85. } while(res.data.hasNextPage && --max > 0);
  86. this.setState({ revisions: newRevisions });
  87. }
  88. /**
  89. * Fetch specified page revision
  90. * If revision's body is empty, it will be completed.
  91. * @param {string} revisionId
  92. * @return {revision} revision
  93. */
  94. async fetchPageRevision(revisionId) {
  95. try {
  96. const compactRevision = this.state.revisions.find(rev => rev._id === revisionId);
  97. if (this.state.revisions.find(rev => rev._id === revisionId) === undefined) {
  98. return null;
  99. }
  100. if (compactRevision.body == null) {
  101. const fullRevision = await this.fetchPageRevisionBody(revisionId);
  102. compactRevision.body = fullRevision.body;
  103. // cache revision body
  104. const newRevisions = this.state.revisions.map(rev => {
  105. if (rev._id === revisionId) {
  106. return { ...rev, body: fullRevision.body };
  107. }
  108. return rev;
  109. });
  110. this.setState( { revisions: newRevisions });
  111. }
  112. return compactRevision;
  113. }
  114. catch (err) {
  115. toastError(err);
  116. this.setState({ errorMessage: err.message });
  117. logger.error(err);
  118. }
  119. }
  120. async handleFromRevisionChange(revisionId) {
  121. const fromRevision = await this.fetchPageRevision(revisionId);
  122. this.setState({ fromRevision })
  123. }
  124. async handleToRevisionChange(revisionId) {
  125. const toRevision = await this.fetchPageRevision(revisionId);
  126. this.setState({ toRevision })
  127. }
  128. }