PageStatusAlert.jsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import React from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import PropTypes from 'prop-types';
  4. // import AppContainer from '~/client/services/AppContainer';
  5. // import PageContainer from '~/client/services/PageContainer';
  6. import Username from '~/components/User/Username';
  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.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" key="btnOpenHackmdSomeoneEditing" 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. getContentsForDraftExistsAlert(isRealtime) {
  49. const { t } = this.props;
  50. return [
  51. ['bg-success', 'd-hackmd-none'],
  52. <>
  53. <i className="icon-fw icon-pencil"></i>
  54. {t('hackmd.this_page_has_draft')}
  55. </>,
  56. <a href="#hackmd" key="btnOpenHackmdPageHasDraft" className="btn btn-outline-white">
  57. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  58. Open HackMD Editor
  59. </a>,
  60. ];
  61. }
  62. getContentsForUpdatedAlert() {
  63. const { t } = this.props;
  64. // const pageEditor = appContainer.getComponentInstance('PageEditor');
  65. const isConflictOnEdit = false;
  66. // if (pageEditor != null) {
  67. // const markdownOnEdit = pageEditor.getMarkdown();
  68. // isConflictOnEdit = markdownOnEdit !== pageContainer.state.markdown;
  69. // }
  70. // TODO: re-impl with Next.js way
  71. // const usernameComponentToString = ReactDOMServer.renderToString(<Username user={pageContainer.state.lastUpdateUser} />);
  72. // const label1 = isConflictOnEdit
  73. // ? t('modal_resolve_conflict.file_conflicting_with_newer_remote')
  74. // // eslint-disable-next-line react/no-danger
  75. // : <span dangerouslySetInnerHTML={{ __html: `${usernameComponentToString} ${t('edited this page')}` }} />;
  76. const label1 = '(TBD -- 2022.09.13)';
  77. return [
  78. ['bg-warning'],
  79. <>
  80. <i className="icon-fw icon-bulb"></i>
  81. {label1}
  82. </>,
  83. <>
  84. <button type="button" onClick={() => this.refreshPage()} className="btn btn-outline-white mr-4">
  85. <i className="icon-fw icon-reload mr-1"></i>
  86. {t('Load latest')}
  87. </button>
  88. { isConflictOnEdit && (
  89. <button
  90. type="button"
  91. onClick={this.onClickResolveConflict}
  92. className="btn btn-outline-white"
  93. >
  94. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  95. {t('modal_resolve_conflict.resolve_conflict')}
  96. </button>
  97. )}
  98. </>,
  99. ];
  100. }
  101. render() {
  102. const {
  103. revisionId, revisionIdHackmdSynced, remoteRevisionId, hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime,
  104. } = this.props.pageContainer.state;
  105. const isRevisionOutdated = revisionId !== remoteRevisionId;
  106. const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
  107. let getContentsFunc = null;
  108. // when remote revision is newer than both
  109. if (isHackmdDocumentOutdated && isRevisionOutdated) {
  110. getContentsFunc = this.getContentsForUpdatedAlert;
  111. }
  112. // when someone editing with HackMD
  113. else if (isHackmdDraftUpdatingInRealtime) {
  114. getContentsFunc = this.getContentsForSomeoneEditingAlert;
  115. }
  116. // when the draft of HackMD is newest
  117. else if (hasDraftOnHackmd) {
  118. getContentsFunc = this.getContentsForDraftExistsAlert;
  119. }
  120. // do not render anything
  121. else {
  122. return null;
  123. }
  124. const [additionalClasses, label, btn] = getContentsFunc();
  125. return (
  126. <div className={`card grw-page-status-alert text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
  127. <div className="card-body">
  128. <p className="card-text grw-card-label-container">
  129. {label}
  130. </p>
  131. <p className="card-text grw-card-btn-container">
  132. {btn}
  133. </p>
  134. </div>
  135. </div>
  136. );
  137. }
  138. }
  139. PageStatusAlert.propTypes = {
  140. t: PropTypes.func.isRequired, // i18next
  141. // appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  142. // pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  143. };
  144. const PageStatusAlertWrapperFC = (props) => {
  145. const { t } = useTranslation();
  146. return <PageStatusAlert t={t} {...props} />;
  147. };
  148. export default PageStatusAlertWrapperFC;