PageStatusAlert.jsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { parseISO, format } from 'date-fns';
  4. import { withTranslation } from 'react-i18next';
  5. import AppContainer from '~/client/services/AppContainer';
  6. import PageContainer from '~/client/services/PageContainer';
  7. import { withUnstatedContainers } from './UnstatedUtils';
  8. /**
  9. *
  10. * @author Yuki Takei <yuki@weseek.co.jp>
  11. *
  12. * @export
  13. * @class PageStatusAlert
  14. * @extends {React.Component}
  15. */
  16. class PageStatusAlert extends React.Component {
  17. constructor(props) {
  18. super(props);
  19. this.state = {
  20. };
  21. this.getContentsForSomeoneEditingAlert = this.getContentsForSomeoneEditingAlert.bind(this);
  22. this.getContentsForRevisionOutdated = this.getContentsForRevisionOutdated.bind(this);
  23. this.getContentsForDraftExistsAlert = this.getContentsForDraftExistsAlert.bind(this);
  24. this.getContentsForUpdatedAlert = this.getContentsForUpdatedAlert.bind(this);
  25. this.onClickResolveConflict = this.onClickResolveConflict.bind(this);
  26. }
  27. refreshPage() {
  28. window.location.reload();
  29. }
  30. onClickResolveConflict() {
  31. this.props.pageContainer.setState({
  32. isConflictDiffModalOpen: true,
  33. });
  34. }
  35. getContentsForSomeoneEditingAlert() {
  36. const { t } = this.props;
  37. return [
  38. ['bg-success', 'd-hackmd-none'],
  39. <>
  40. <i className="icon-fw icon-people"></i>
  41. {t('hackmd.someone_editing')}
  42. </>,
  43. <a href="#hackmd" className="btn btn-outline-white">
  44. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  45. Open HackMD Editor
  46. </a>,
  47. ];
  48. }
  49. getContentsForRevisionOutdated() {
  50. const { t, appContainer, pageContainer } = this.props;
  51. const pageEditor = appContainer.getComponentInstance('PageEditor');
  52. let markdownOnEdit = '';
  53. let isConflictOnEdit = false;
  54. if (pageEditor != null) {
  55. markdownOnEdit = pageEditor.getMarkdown();
  56. isConflictOnEdit = markdownOnEdit !== pageContainer.state.markdown;
  57. }
  58. return [
  59. ['bg-warning', 'd-hackmd-none'],
  60. <>
  61. <i className="icon-fw icon-pencil"></i>
  62. {t('modal_resolve_conflict.file_conflicting_with_newer_remote')}
  63. </>,
  64. <>
  65. <button type="button" onClick={() => this.refreshPage()} className="btn btn-outline-white mr-4">
  66. <i className="icon-fw icon-reload mr-1"></i>
  67. {t('Load latest')}
  68. </button>
  69. {isConflictOnEdit
  70. && (
  71. <button
  72. type="button"
  73. onClick={this.onClickResolveConflict}
  74. className="btn btn-outline-white"
  75. >
  76. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  77. {t('modal_resolve_conflict.resolve_conflict')}
  78. </button>
  79. )
  80. }
  81. </>,
  82. ];
  83. }
  84. getContentsForDraftExistsAlert(isRealtime) {
  85. const { t } = this.props;
  86. return [
  87. ['bg-success', 'd-hackmd-none'],
  88. <>
  89. <i className="icon-fw icon-pencil"></i>
  90. {t('hackmd.this_page_has_draft')}
  91. </>,
  92. <a href="#hackmd" className="btn btn-outline-white">
  93. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  94. Open HackMD Editor
  95. </a>,
  96. ];
  97. }
  98. getContentsForUpdatedAlert() {
  99. const { t } = this.props;
  100. const label1 = t('edited this page');
  101. const label2 = t('Load latest');
  102. return [
  103. ['bg-warning'],
  104. <>
  105. <i className="icon-fw icon-bulb"></i>
  106. {this.props.pageContainer.state.lastUpdateUsername} {label1}
  107. </>,
  108. <a href="#" className="btn btn-outline-white" onClick={this.refreshPage}>
  109. <i className="icon-fw icon-reload mr-1"></i>
  110. {label2}
  111. </a>,
  112. ];
  113. }
  114. render() {
  115. const {
  116. revisionId, revisionIdHackmdSynced, remoteRevisionId, hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime,
  117. } = this.props.pageContainer.state;
  118. const isRevisionOutdated = revisionId !== remoteRevisionId;
  119. const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
  120. let getContentsFunc = null;
  121. // when conflicting on save
  122. if (isRevisionOutdated) {
  123. getContentsFunc = this.getContentsForRevisionOutdated;
  124. }
  125. // when remote revision is newer than both
  126. else if (isHackmdDocumentOutdated && isRevisionOutdated) {
  127. getContentsFunc = this.getContentsForUpdatedAlert;
  128. }
  129. // when someone editing with HackMD
  130. else if (isHackmdDraftUpdatingInRealtime) {
  131. getContentsFunc = this.getContentsForSomeoneEditingAlert;
  132. }
  133. // when the draft of HackMD is newest
  134. else if (hasDraftOnHackmd) {
  135. getContentsFunc = this.getContentsForDraftExistsAlert;
  136. }
  137. // do not render anything
  138. else {
  139. return null;
  140. }
  141. const [additionalClasses, label, btn] = getContentsFunc();
  142. return (
  143. <div className={`card grw-page-status-alert text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
  144. <div className="card-body">
  145. <p className="card-text grw-card-label-container">
  146. {label}
  147. </p>
  148. <p className="card-text grw-card-btn-container">
  149. {btn}
  150. </p>
  151. </div>
  152. </div>
  153. );
  154. }
  155. }
  156. /**
  157. * Wrapper component for using unstated
  158. */
  159. const PageStatusAlertWrapper = withUnstatedContainers(PageStatusAlert, [AppContainer, PageContainer]);
  160. PageStatusAlert.propTypes = {
  161. t: PropTypes.func.isRequired, // i18next
  162. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  163. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  164. };
  165. export default withTranslation()(PageStatusAlertWrapper);