|
|
@@ -3,30 +3,24 @@ import React, { useCallback, useMemo } from 'react';
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
import * as ReactDOMServer from 'react-dom/server';
|
|
|
|
|
|
+import { usePageStatusAlert } from '~/client/services/update-page/conflict';
|
|
|
import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
|
|
|
-import { useEditingMarkdown, useIsConflict } from '~/stores/editor';
|
|
|
-import { useConflictDiffModal } from '~/stores/modal';
|
|
|
+import { useEditingMarkdown } from '~/stores/editor';
|
|
|
import { useSWRMUTxCurrentPage, useSWRxCurrentPage } from '~/stores/page';
|
|
|
import { useRemoteRevisionId, useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
|
|
|
+import { EditorMode, useEditorMode } from '~/stores/ui';
|
|
|
|
|
|
import { Username } from './User/Username';
|
|
|
|
|
|
import styles from './PageStatusAlert.module.scss';
|
|
|
|
|
|
-type AlertComponentContents = {
|
|
|
- additionalClasses: string[],
|
|
|
- label: JSX.Element,
|
|
|
- btn: JSX.Element
|
|
|
-}
|
|
|
-
|
|
|
export const PageStatusAlert = (): JSX.Element => {
|
|
|
-
|
|
|
const { t } = useTranslation();
|
|
|
- const { data: isConflict } = useIsConflict();
|
|
|
const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
|
|
|
- const { open: openConflictDiffModal } = useConflictDiffModal();
|
|
|
const { data: isGuestUser } = useIsGuestUser();
|
|
|
const { data: isReadOnlyUser } = useIsReadOnlyUser();
|
|
|
+ const { data: pageStatusAlertData } = usePageStatusAlert();
|
|
|
+ const { data: editorMode } = useEditorMode();
|
|
|
|
|
|
// store remote latest page data
|
|
|
const { data: remoteRevisionId } = useRemoteRevisionId();
|
|
|
@@ -42,92 +36,69 @@ export const PageStatusAlert = (): JSX.Element => {
|
|
|
}, [mutateEditingMarkdown, mutatePageData]);
|
|
|
|
|
|
const onClickResolveConflict = useCallback(() => {
|
|
|
- // openConflictDiffModal();
|
|
|
- }, [openConflictDiffModal]);
|
|
|
-
|
|
|
- // TODO: re-impl for builtin editor
|
|
|
- //
|
|
|
- // const getContentsForSomeoneEditingAlert = useCallback((): AlertComponentContents => {
|
|
|
- // return {
|
|
|
- // additionalClasses: ['bg-success', 'd-hackmd-none'],
|
|
|
- // label:
|
|
|
- // <>
|
|
|
- // <span className="material-symbols-outlined">person</span>
|
|
|
- // {t('hackmd.someone_editing')}
|
|
|
- // </>,
|
|
|
- // btn:
|
|
|
- // <a href="#hackmd" key="btnOpenHackmdSomeoneEditing" className="btn btn-outline-white">
|
|
|
- // <span class="material-symbols-outlined">description</span>
|
|
|
- // Open HackMD Editor
|
|
|
- // </a>,
|
|
|
- // };
|
|
|
- // }, [t]);
|
|
|
-
|
|
|
- const getContentsForUpdatedAlert = useCallback((): AlertComponentContents => {
|
|
|
+ pageStatusAlertData?.onConflict?.();
|
|
|
+ }, [pageStatusAlertData]);
|
|
|
|
|
|
+ const alertContentsForView = useMemo(() => {
|
|
|
const usernameComponentToString = ReactDOMServer.renderToString(<Username user={remoteRevisionLastUpdateUser} />);
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <p className="card-text grw-card-label-container">
|
|
|
+ {/* eslint-disable-next-line react/no-danger */}
|
|
|
+ <span dangerouslySetInnerHTML={{ __html: `${usernameComponentToString} ${t('edited this page')}` }} />
|
|
|
+ </p>
|
|
|
+ <p className="card-text grw-card-btn-container">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onClick={() => refreshPage()}
|
|
|
+ className="btn btn-outline-white me-4"
|
|
|
+ >
|
|
|
+ <span className="material-symbols-outlined">refresh</span>
|
|
|
+ {t('Load latest')}
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ }, [refreshPage, remoteRevisionLastUpdateUser, t]);
|
|
|
|
|
|
- const label1 = isConflict
|
|
|
- ? t('modal_resolve_conflict.file_conflicting_with_newer_remote')
|
|
|
- // eslint-disable-next-line react/no-danger
|
|
|
- : <span dangerouslySetInnerHTML={{ __html: `${usernameComponentToString} ${t('edited this page')}` }} />;
|
|
|
-
|
|
|
- return {
|
|
|
- additionalClasses: ['bg-warning text-dark'],
|
|
|
- label:
|
|
|
- <>
|
|
|
- <span className="material-symbols-outlined">lightbulb</span>
|
|
|
- {label1}
|
|
|
- </>,
|
|
|
- btn:
|
|
|
- <>
|
|
|
- <button type="button" onClick={() => refreshPage()} className="btn btn-outline-white me-4">
|
|
|
- <span className="material-symbols-outlined">refresh</span>
|
|
|
- {t('Load latest')}
|
|
|
- </button>
|
|
|
- { isConflict && (
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- onClick={onClickResolveConflict}
|
|
|
- className="btn btn-outline-white"
|
|
|
- >
|
|
|
- <span className="material-symbols-outlined">description</span>
|
|
|
- {t('modal_resolve_conflict.resolve_conflict')}
|
|
|
- </button>
|
|
|
- )}
|
|
|
- </>,
|
|
|
- };
|
|
|
- }, [remoteRevisionLastUpdateUser, isConflict, t, onClickResolveConflict, refreshPage]);
|
|
|
-
|
|
|
- const alertComponentContents = useMemo(() => {
|
|
|
- const isRevisionOutdated = revision?._id !== remoteRevisionId;
|
|
|
-
|
|
|
- // 'revision?._id' and 'remoteRevisionId' are can not be undefined
|
|
|
- if (revision?._id == null || remoteRevisionId == null) { return }
|
|
|
-
|
|
|
- // when remote revision is newer than both
|
|
|
- if (isRevisionOutdated) {
|
|
|
- return getContentsForUpdatedAlert();
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }, [revision?._id, remoteRevisionId, getContentsForUpdatedAlert]);
|
|
|
+ const alertContentsForEditor = useMemo(() => {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <p className="card-text grw-card-label-container">
|
|
|
+ {t('modal_resolve_conflict.file_conflicting_with_newer_remote')}
|
|
|
+ </p>
|
|
|
+ <p className="card-text grw-card-btn-container">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onClick={onClickResolveConflict}
|
|
|
+ className="btn btn-outline-white"
|
|
|
+ >
|
|
|
+ <span className="material-symbols-outlined">description</span>
|
|
|
+ {t('modal_resolve_conflict.resolve_conflict')}
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ }, [onClickResolveConflict, t]);
|
|
|
|
|
|
- if (!!isGuestUser || !!isReadOnlyUser || alertComponentContents == null) { return <></> }
|
|
|
+ const isRevisionOutdated = (revision?._id != null || remoteRevisionId != null) && revision?._id !== remoteRevisionId;
|
|
|
+ if (!!isGuestUser || !!isReadOnlyUser || !isRevisionOutdated) {
|
|
|
+ return <></>;
|
|
|
+ }
|
|
|
|
|
|
- const { additionalClasses, label, btn } = alertComponentContents;
|
|
|
+ const hasConflictHandler = pageStatusAlertData?.onConflict != null;
|
|
|
+ if (!hasConflictHandler && editorMode === EditorMode.Editor) {
|
|
|
+ return <></>;
|
|
|
+ }
|
|
|
|
|
|
return (
|
|
|
- <div className={`${styles['grw-page-status-alert']} card text-white fixed-bottom animated fadeInUp faster ${additionalClasses.join(' ')}`}>
|
|
|
+ <div className={`${styles['grw-page-status-alert']} card text-white fixed-bottom animated fadeInUp faster bg-warning text-dark`}>
|
|
|
<div className="card-body">
|
|
|
- <p className="card-text grw-card-label-container">
|
|
|
- {label}
|
|
|
- </p>
|
|
|
- <p className="card-text grw-card-btn-container">
|
|
|
- {btn}
|
|
|
- </p>
|
|
|
+ { editorMode === EditorMode.View
|
|
|
+ ? alertContentsForView
|
|
|
+ : alertContentsForEditor
|
|
|
+ }
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
-
|
|
|
};
|