PageStatusAlert.jsx 5.4 KB

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