RevisionDiff.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { useMemo } from 'react';
  2. import type { IRevisionHasPageId } from '@growi/core';
  3. import { returnPathForURL } from '@growi/core/dist/utils/path-utils';
  4. import { createPatch } from 'diff';
  5. import type { Diff2HtmlConfig } from 'diff2html';
  6. import { html } from 'diff2html';
  7. import { ColorSchemeType } from 'diff2html/lib/types';
  8. import { useTranslation } from 'next-i18next';
  9. import Link from 'next/link';
  10. import urljoin from 'url-join';
  11. import { Themes, useNextThemes } from '~/stores/use-next-themes';
  12. import UserDate from '../User/UserDate';
  13. import styles from './RevisionDiff.module.scss';
  14. import 'diff2html/bundles/css/diff2html.min.css';
  15. type RevisioinDiffProps = {
  16. currentRevision: IRevisionHasPageId,
  17. previousRevision: IRevisionHasPageId,
  18. revisionDiffOpened: boolean,
  19. currentPageId: string,
  20. currentPagePath: string,
  21. onClose: () => void,
  22. }
  23. export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
  24. const { t } = useTranslation();
  25. const {
  26. currentRevision, previousRevision, revisionDiffOpened, currentPageId, currentPagePath, onClose,
  27. } = props;
  28. const { theme } = useNextThemes();
  29. const colorScheme: ColorSchemeType = useMemo(() => {
  30. switch (theme) {
  31. case Themes.DARK:
  32. return ColorSchemeType.DARK;
  33. case Themes.LIGHT:
  34. return ColorSchemeType.LIGHT;
  35. default:
  36. return ColorSchemeType.AUTO;
  37. }
  38. }, [theme]);
  39. const previousText = (currentRevision._id === previousRevision._id) ? '' : previousRevision.body;
  40. const patch = createPatch(
  41. currentRevision.pageId, // currentRevision.path is DEPRECATED
  42. previousText,
  43. currentRevision.body,
  44. );
  45. const option: Diff2HtmlConfig = {
  46. outputFormat: 'side-by-side',
  47. drawFileList: false,
  48. colorScheme,
  49. };
  50. const diffViewHTML = revisionDiffOpened ? html(patch, option) : '';
  51. const diffView = { __html: diffViewHTML };
  52. return (
  53. <div className={`${styles['revision-diff-container']}`}>
  54. <div className="container">
  55. <div className="row mt-2">
  56. <div className="col px-0 py-2">
  57. <span className="fw-bold">{t('page_history.comparing_source')}</span>
  58. <Link
  59. href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${previousRevision._id}`)}
  60. className="small ms-2
  61. link-created-at
  62. link-secondary link-opacity-75 link-opacity-100-hover
  63. link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
  64. onClick={onClose}
  65. prefetch={false}
  66. >
  67. <UserDate dateTime={previousRevision.createdAt} />
  68. </Link>
  69. </div>
  70. <div className="col px-0 py-2">
  71. <span className="fw-bold">{t('page_history.comparing_target')}</span>
  72. <Link
  73. href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${currentRevision._id}`)}
  74. className="small ms-2
  75. link-created-at
  76. link-secondary link-opacity-75 link-opacity-100-hover
  77. link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
  78. onClick={onClose}
  79. prefetch={false}
  80. >
  81. <UserDate dateTime={currentRevision.createdAt} />
  82. </Link>
  83. </div>
  84. </div>
  85. </div>
  86. {/* eslint-disable-next-line react/no-danger */}
  87. <div className="revision-history-diff pb-1" dangerouslySetInnerHTML={diffView} />
  88. </div>
  89. );
  90. };