PageStatusAlert.tsx 5.5 KB

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