PageStatusAlert.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import React, { useCallback, useMemo } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import * as ReactDOMServer from 'react-dom/server';
  4. import { useEditingMarkdown, useIsConflict } from '~/stores/editor';
  5. import {
  6. useHasDraftOnHackmd, useIsHackmdDraftUpdatingInRealtime, useRevisionIdHackmdSynced,
  7. } from '~/stores/hackmd';
  8. import { useConflictDiffModal } from '~/stores/modal';
  9. import { useSWRxCurrentPage } from '~/stores/page';
  10. import { useRemoteRevisionId, useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
  11. import { Username } from './User/Username';
  12. import styles from './PageStatusAlert.module.scss';
  13. type AlertComponentContents = {
  14. additionalClasses: string[],
  15. label: JSX.Element,
  16. btn: JSX.Element
  17. }
  18. export const PageStatusAlert = (): JSX.Element => {
  19. const { t } = useTranslation();
  20. const { data: isHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
  21. const { data: hasDraftOnHackmd } = useHasDraftOnHackmd();
  22. const { data: isConflict } = useIsConflict();
  23. const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
  24. const { open: openConflictDiffModal } = useConflictDiffModal();
  25. // store remote latest page data
  26. const { data: revisionIdHackmdSynced } = useRevisionIdHackmdSynced();
  27. const { data: remoteRevisionId } = useRemoteRevisionId();
  28. const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
  29. const { data: pageData, mutate: mutatePageData } = useSWRxCurrentPage();
  30. const revision = pageData?.revision;
  31. const refreshPage = useCallback(async() => {
  32. const updatedPageData = await mutatePageData();
  33. mutateEditingMarkdown(updatedPageData?.revision.body);
  34. }, [mutateEditingMarkdown, mutatePageData]);
  35. const onClickResolveConflict = useCallback(() => {
  36. openConflictDiffModal();
  37. }, [openConflictDiffModal]);
  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 usernameComponentToString = ReactDOMServer.renderToString(<Username user={remoteRevisionLastUpdateUser} />);
  70. const label1 = isConflict
  71. ? t('modal_resolve_conflict.file_conflicting_with_newer_remote')
  72. // eslint-disable-next-line react/no-danger
  73. : <span dangerouslySetInnerHTML={{ __html: `${usernameComponentToString} ${t('edited this page')}` }} />;
  74. return {
  75. additionalClasses: ['bg-warning'],
  76. label:
  77. <>
  78. <i className="icon-fw icon-bulb"></i>
  79. {label1}
  80. </>,
  81. btn:
  82. <>
  83. <button type="button" onClick={() => refreshPage()} className="btn btn-outline-white mr-4">
  84. <i className="icon-fw icon-reload mr-1"></i>
  85. {t('Load latest')}
  86. </button>
  87. { isConflict && (
  88. <button
  89. type="button"
  90. onClick={onClickResolveConflict}
  91. className="btn btn-outline-white"
  92. >
  93. <i className="fa fa-fw fa-file-text-o mr-1"></i>
  94. {t('modal_resolve_conflict.resolve_conflict')}
  95. </button>
  96. )}
  97. </>,
  98. };
  99. }, [remoteRevisionLastUpdateUser, isConflict, t, onClickResolveConflict, refreshPage]);
  100. const alertComponentContents = useMemo(() => {
  101. const isRevisionOutdated = revision?._id !== remoteRevisionId;
  102. const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
  103. // when remote revision is newer than both
  104. if (isHackmdDocumentOutdated && isRevisionOutdated) {
  105. return getContentsForUpdatedAlert();
  106. }
  107. // when someone editing with HackMD
  108. if (isHackmdDraftUpdatingInRealtime) {
  109. return getContentsForSomeoneEditingAlert();
  110. }
  111. // when the draft of HackMD is newest
  112. if (hasDraftOnHackmd) {
  113. return getContentsForDraftExistsAlert();
  114. }
  115. return null;
  116. }, [
  117. revision?._id,
  118. remoteRevisionId,
  119. revisionIdHackmdSynced,
  120. isHackmdDraftUpdatingInRealtime,
  121. hasDraftOnHackmd,
  122. getContentsForUpdatedAlert,
  123. getContentsForSomeoneEditingAlert,
  124. getContentsForDraftExistsAlert,
  125. ]);
  126. if (alertComponentContents == null) { return <></> }
  127. const { additionalClasses, label, btn } = alertComponentContents;
  128. return (
  129. <div className={`${styles['grw-page-status-alert']} card text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
  130. <div className="card-body">
  131. <p className="card-text grw-card-label-container">
  132. {label}
  133. </p>
  134. <p className="card-text grw-card-btn-container">
  135. {btn}
  136. </p>
  137. </div>
  138. </div>
  139. );
  140. };