PageStatusAlert.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import React, { useCallback, useMemo } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import * as ReactDOMServer from 'react-dom/server';
  4. import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
  5. import { useEditingMarkdown, useIsConflict } from '~/stores/editor';
  6. import { useConflictDiffModal } from '~/stores/modal';
  7. import { useSWRMUTxCurrentPage, useSWRxCurrentPage } from '~/stores/page';
  8. import { useRemoteRevisionId, useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
  9. import { Username } from './User/Username';
  10. import styles from './PageStatusAlert.module.scss';
  11. type AlertComponentContents = {
  12. additionalClasses: string[],
  13. label: JSX.Element,
  14. btn: JSX.Element
  15. }
  16. export const PageStatusAlert = (): JSX.Element => {
  17. const { t } = useTranslation();
  18. const { data: isConflict } = useIsConflict();
  19. const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
  20. const { open: openConflictDiffModal } = useConflictDiffModal();
  21. const { data: isGuestUser } = useIsGuestUser();
  22. const { data: isReadOnlyUser } = useIsReadOnlyUser();
  23. // store remote latest page data
  24. const { data: remoteRevisionId } = useRemoteRevisionId();
  25. const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
  26. const { data: pageData } = useSWRxCurrentPage();
  27. const { trigger: mutatePageData } = useSWRMUTxCurrentPage();
  28. const revision = pageData?.revision;
  29. const refreshPage = useCallback(async() => {
  30. const updatedPageData = await mutatePageData();
  31. mutateEditingMarkdown(updatedPageData?.revision.body);
  32. }, [mutateEditingMarkdown, mutatePageData]);
  33. const onClickResolveConflict = useCallback(() => {
  34. openConflictDiffModal();
  35. }, [openConflictDiffModal]);
  36. // TODO: re-impl for builtin editor
  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 me-1"></i>
  49. // Open HackMD Editor
  50. // </a>,
  51. // };
  52. // }, [t]);
  53. const getContentsForUpdatedAlert = useCallback((): AlertComponentContents => {
  54. const usernameComponentToString = ReactDOMServer.renderToString(<Username user={remoteRevisionLastUpdateUser} />);
  55. const label1 = isConflict
  56. ? t('modal_resolve_conflict.file_conflicting_with_newer_remote')
  57. // eslint-disable-next-line react/no-danger
  58. : <span dangerouslySetInnerHTML={{ __html: `${usernameComponentToString} ${t('edited this page')}` }} />;
  59. return {
  60. additionalClasses: ['bg-warning text-dark'],
  61. label:
  62. <>
  63. <i className="icon-fw icon-bulb"></i>
  64. {label1}
  65. </>,
  66. btn:
  67. <>
  68. <button type="button" onClick={() => refreshPage()} className="btn btn-outline-white me-4">
  69. <i className="icon-fw icon-reload me-1"></i>
  70. {t('Load latest')}
  71. </button>
  72. { isConflict && (
  73. <button
  74. type="button"
  75. onClick={onClickResolveConflict}
  76. className="btn btn-outline-white"
  77. >
  78. <i className="fa fa-fw fa-file-text-o me-1"></i>
  79. {t('modal_resolve_conflict.resolve_conflict')}
  80. </button>
  81. )}
  82. </>,
  83. };
  84. }, [remoteRevisionLastUpdateUser, isConflict, t, onClickResolveConflict, refreshPage]);
  85. const alertComponentContents = useMemo(() => {
  86. const isRevisionOutdated = revision?._id !== remoteRevisionId;
  87. // 'revision?._id' and 'remoteRevisionId' are can not be undefined
  88. if (revision?._id == null || remoteRevisionId == null) { return }
  89. // when remote revision is newer than both
  90. if (isRevisionOutdated) {
  91. return getContentsForUpdatedAlert();
  92. }
  93. return null;
  94. }, [revision?._id, remoteRevisionId, getContentsForUpdatedAlert]);
  95. if (!!isGuestUser || !!isReadOnlyUser || alertComponentContents == null) { return <></> }
  96. const { additionalClasses, label, btn } = alertComponentContents;
  97. return (
  98. <div className={`${styles['grw-page-status-alert']} card text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
  99. <div className="card-body">
  100. <p className="card-text grw-card-label-container">
  101. {label}
  102. </p>
  103. <p className="card-text grw-card-btn-container">
  104. {btn}
  105. </p>
  106. </div>
  107. </div>
  108. );
  109. };