RevisionCompareContainer.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. await this.fetchAllPageRevisions();
  35. const latestRevisionId = this.state.revisions[0]._id;
  36. const { compareRevisionIds } = this.pageContainer.state;
  37. const fromRevisionIdParam = compareRevisionIds[0] || latestRevisionId;
  38. const toRevisionIdParam = compareRevisionIds[1] || latestRevisionId;
  39. await this.handleFromRevisionChange(fromRevisionIdParam);
  40. await this.handleToRevisionChange(toRevisionIdParam);
  41. }
  42. /**
  43. * Fetch page revision body by revision_id in argument
  44. * @param {string} revisionId
  45. */
  46. async fetchPageRevisionBody(revisionId) {
  47. const { pageId, shareLinkId } = this.pageContainer.state;
  48. try {
  49. const res = await this.appContainer.apiv3Get(`/revisions/${revisionId}`, { pageId, shareLinkId });
  50. if (res == null || res.data == undefined || res.data.revision == undefined) {
  51. logger.warn(`cannot get revision. revisionId: ${revisionId}`);
  52. return null;
  53. }
  54. return res.data.revision.body;
  55. }
  56. catch (err) {
  57. toastError(err);
  58. this.setState({ errorMessage: err.message });
  59. logger.error(err);
  60. }
  61. }
  62. /**
  63. * Fetch all page revisions
  64. *
  65. * Each revision to be saved contains only "_id" and "createdAt", and "body" is initialized to null.
  66. * ex. [{_id: "5ff03fded799ebc858a09266", body: null, creat…}, {_id: "5ff03fbed799ebc858a09262", body: null, creat…}]
  67. */
  68. async fetchAllPageRevisions() {
  69. const { pageId, shareLinkId } = this.pageContainer.state;
  70. // fetch all page revisions that are sorted update day time descending
  71. let max = 1000; // Maximum number of loops to avoid infinite loops.
  72. let newRevisions = [];
  73. let page = 1;
  74. let res = null;
  75. do {
  76. res = await this.appContainer.apiv3Get('/revisions/list', {
  77. pageId, shareLinkId, page, limit: 1,
  78. });
  79. newRevisions = newRevisions.concat(res.data.docs.map(rev => {
  80. const { _id, createdAt } = rev;
  81. return { _id, createdAt, body: null };
  82. }));
  83. page++;
  84. } while(res.data.hasNextPage && --max > 0);
  85. this.setState({ revisions: newRevisions });
  86. }
  87. /**
  88. * Fetch specified page revision
  89. * If revision's body is empty, it will be completed.
  90. * @param {string} revisionId
  91. * @return {revision} revision
  92. */
  93. async fetchPageRevision(revisionId) {
  94. try {
  95. const compactRevision = this.state.revisions.find(rev => rev._id === revisionId);
  96. if (this.state.revisions.find(rev => rev._id === revisionId) === undefined) {
  97. return null;
  98. }
  99. if (compactRevision.body == null) {
  100. const body = await this.fetchPageRevisionBody(revisionId);
  101. compactRevision.body = body;
  102. // cache revision body
  103. const newRevisions = this.state.revisions.map(rev => {
  104. if (rev._id === revisionId) {
  105. return { ...rev, body };
  106. }
  107. return rev;
  108. });
  109. this.setState( { revisions: newRevisions });
  110. }
  111. return compactRevision;
  112. }
  113. catch (err) {
  114. toastError(err);
  115. this.setState({ errorMessage: err.message });
  116. logger.error(err);
  117. }
  118. }
  119. async handleFromRevisionChange(revisionId) {
  120. const fromRevision = await this.fetchPageRevision(revisionId);
  121. this.setState({ fromRevision })
  122. }
  123. async handleToRevisionChange(revisionId) {
  124. const toRevision = await this.fetchPageRevision(revisionId);
  125. this.setState({ toRevision })
  126. }
  127. }