PageStatusAlert.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import React, { useCallback, useMemo } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import {
  4. useHasDraftOnHackmd, useIsHackmdDraftUpdatingInRealtime, useRemoteRevisionId, useRevisionIdHackmdSynced,
  5. } from '~/stores/hackmd';
  6. import { useSWRxCurrentPage } from '~/stores/page';
  7. type AlertComponentContents = {
  8. additionalClasses: string[],
  9. label: JSX.Element,
  10. btn: JSX.Element
  11. }
  12. export const PageStatusAlert = (): JSX.Element => {
  13. const { t } = useTranslation();
  14. const { data: isHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
  15. const { data: hasDraftOnHackmd } = useHasDraftOnHackmd();
  16. const { data: revisionIdHackmdSynced } = useRevisionIdHackmdSynced();
  17. const { data: remoteRevisionId } = useRemoteRevisionId();
  18. const { data: pageData } = useSWRxCurrentPage();
  19. const revision = pageData?.revision;
  20. const refreshPage = useCallback(() => {
  21. window.location.reload();
  22. }, []);
  23. const onClickResolveConflict = useCallback(() => {
  24. // this.props.pageContainer.setState({
  25. // isConflictDiffModalOpen: true,
  26. // });
  27. }, []);
  28. const getContentsForSomeoneEditingAlert = useCallback((): AlertComponentContents => {
  29. return {
  30. additionalClasses: ['bg-success', 'd-hackmd-none'],
  31. label:
  32. <>
  33. <i className="icon-fw icon-people"></i>
  34. {t('hackmd.someone_editing')}
  35. </>,
  36. btn:
  37. <a href="#hackmd" key="btnOpenHackmdSomeoneEditing" className="btn btn-outline-white">
  38. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  39. Open HackMD Editor
  40. </a>,
  41. };
  42. }, [t]);
  43. const getContentsForDraftExistsAlert = useCallback((): AlertComponentContents => {
  44. return {
  45. additionalClasses: ['bg-success', 'd-hackmd-none'],
  46. label:
  47. <>
  48. <i className="icon-fw icon-pencil"></i>
  49. {t('hackmd.this_page_has_draft')}
  50. </>,
  51. btn:
  52. <a href="#hackmd" key="btnOpenHackmdPageHasDraft" className="btn btn-outline-white">
  53. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  54. Open HackMD Editor
  55. </a>,
  56. };
  57. }, [t]);
  58. const getContentsForUpdatedAlert = useCallback((): AlertComponentContents => {
  59. // const pageEditor = appContainer.getComponentInstance('PageEditor');
  60. const isConflictOnEdit = false;
  61. // if (pageEditor != null) {
  62. // const markdownOnEdit = pageEditor.getMarkdown();
  63. // isConflictOnEdit = markdownOnEdit !== pageContainer.state.markdown;
  64. // }
  65. // TODO: re-impl with Next.js way
  66. // const usernameComponentToString = ReactDOMServer.renderToString(<Username user={pageContainer.state.lastUpdateUser} />);
  67. // const label1 = isConflictOnEdit
  68. // ? t('modal_resolve_conflict.file_conflicting_with_newer_remote')
  69. // // eslint-disable-next-line react/no-danger
  70. // : <span dangerouslySetInnerHTML={{ __html: `${usernameComponentToString} ${t('edited this page')}` }} />;
  71. const label1 = '(TBD -- 2022.09.13)';
  72. return {
  73. additionalClasses: ['bg-warning'],
  74. label:
  75. <>
  76. <i className="icon-fw icon-bulb"></i>
  77. {label1}
  78. </>,
  79. btn:
  80. <>
  81. <button type="button" onClick={() => refreshPage()} className="btn btn-outline-white mr-4">
  82. <i className="icon-fw icon-reload mr-1"></i>
  83. {t('Load latest')}
  84. </button>
  85. { isConflictOnEdit && (
  86. <button
  87. type="button"
  88. onClick={onClickResolveConflict}
  89. className="btn btn-outline-white"
  90. >
  91. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  92. {t('modal_resolve_conflict.resolve_conflict')}
  93. </button>
  94. )}
  95. </>,
  96. };
  97. }, [t, onClickResolveConflict, refreshPage]);
  98. const alertComponentContents = useMemo(() => {
  99. const isRevisionOutdated = revision?._id !== remoteRevisionId;
  100. const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
  101. // when remote revision is newer than both
  102. if (isHackmdDocumentOutdated && isRevisionOutdated) {
  103. return getContentsForUpdatedAlert();
  104. }
  105. // when someone editing with HackMD
  106. if (isHackmdDraftUpdatingInRealtime) {
  107. return getContentsForSomeoneEditingAlert();
  108. }
  109. // when the draft of HackMD is newest
  110. if (hasDraftOnHackmd) {
  111. return getContentsForDraftExistsAlert();
  112. }
  113. return null;
  114. }, [
  115. revision?._id,
  116. remoteRevisionId,
  117. revisionIdHackmdSynced,
  118. isHackmdDraftUpdatingInRealtime,
  119. hasDraftOnHackmd,
  120. getContentsForUpdatedAlert,
  121. getContentsForSomeoneEditingAlert,
  122. getContentsForDraftExistsAlert,
  123. ]);
  124. if (alertComponentContents == null) { return <></> }
  125. const { additionalClasses, label, btn } = alertComponentContents;
  126. return (
  127. <div className={`card grw-page-status-alert text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
  128. <div className="card-body">
  129. <p className="card-text grw-card-label-container">
  130. {label}
  131. </p>
  132. <p className="card-text grw-card-btn-container">
  133. {btn}
  134. </p>
  135. </div>
  136. </div>
  137. );
  138. };