RevisionDiff.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import { type JSX, useMemo } from 'react';
  2. import Link from 'next/link';
  3. import type { IRevisionHasId } from '@growi/core';
  4. import { GrowiThemeSchemeType } from '@growi/core';
  5. import { returnPathForURL } from '@growi/core/dist/utils/path-utils';
  6. import { PresetThemesMetadatas } from '@growi/preset-themes';
  7. import { createPatch } from 'diff';
  8. import type { Diff2HtmlConfig } from 'diff2html';
  9. import { html } from 'diff2html';
  10. import { ColorSchemeType } from 'diff2html/lib/types';
  11. import { useTranslation } from 'next-i18next';
  12. import urljoin from 'url-join';
  13. import { Themes, useNextThemes } from '~/stores-universal/use-next-themes';
  14. import UserDate from '../../../components/User/UserDate';
  15. import { useSWRxGrowiThemeSetting } from '../../../stores/admin/customize';
  16. import styles from './RevisionDiff.module.scss';
  17. import 'diff2html/bundles/css/diff2html.min.css';
  18. const moduleClass = styles['revision-diff-container'];
  19. type RevisioinDiffProps = {
  20. currentRevision: IRevisionHasId;
  21. previousRevision: IRevisionHasId;
  22. revisionDiffOpened: boolean;
  23. currentPageId: string;
  24. currentPagePath: string;
  25. onClose: () => void;
  26. };
  27. export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
  28. const { t } = useTranslation();
  29. const {
  30. currentRevision,
  31. previousRevision,
  32. revisionDiffOpened,
  33. currentPageId,
  34. currentPagePath,
  35. onClose,
  36. } = props;
  37. const { theme: userTheme } = useNextThemes();
  38. const { data: growiTheme } = useSWRxGrowiThemeSetting();
  39. const colorScheme: ColorSchemeType = useMemo(() => {
  40. if (growiTheme == null) {
  41. return ColorSchemeType.AUTO;
  42. }
  43. const growiThemeSchemeType =
  44. growiTheme.pluginThemesMetadatas[0]?.schemeType ??
  45. PresetThemesMetadatas.find(
  46. (theme) => theme.name === growiTheme.currentTheme,
  47. )?.schemeType;
  48. switch (growiThemeSchemeType) {
  49. case GrowiThemeSchemeType.DARK:
  50. return ColorSchemeType.DARK;
  51. case GrowiThemeSchemeType.LIGHT:
  52. return ColorSchemeType.LIGHT;
  53. default:
  54. // growiThemeSchemeType === GrowiThemeSchemeType.BOTH
  55. }
  56. switch (userTheme) {
  57. case Themes.DARK:
  58. return ColorSchemeType.DARK;
  59. case Themes.LIGHT:
  60. return ColorSchemeType.LIGHT;
  61. default:
  62. return ColorSchemeType.AUTO;
  63. }
  64. }, [growiTheme, userTheme]);
  65. const previousText =
  66. currentRevision._id === previousRevision._id ? '' : previousRevision.body;
  67. const patch = createPatch(
  68. currentRevision.pageId, // currentRevision.path is DEPRECATED
  69. previousText,
  70. currentRevision.body,
  71. );
  72. const option: Diff2HtmlConfig = {
  73. outputFormat: 'side-by-side',
  74. drawFileList: false,
  75. colorScheme,
  76. };
  77. const diffViewHTML = revisionDiffOpened ? html(patch, option) : '';
  78. const diffView = { __html: diffViewHTML };
  79. return (
  80. <div className={moduleClass}>
  81. <div className="container">
  82. <div className="row mt-2">
  83. <div className="col px-0 py-2">
  84. <span className="fw-bold">
  85. {t('page_history.comparing_source')}
  86. </span>
  87. <Link
  88. href={urljoin(
  89. returnPathForURL(currentPagePath, currentPageId),
  90. `?revisionId=${previousRevision._id}`,
  91. )}
  92. className="small ms-2
  93. link-created-at
  94. link-secondary link-opacity-75 link-opacity-100-hover
  95. link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
  96. onClick={onClose}
  97. prefetch={false}
  98. >
  99. <UserDate dateTime={previousRevision.createdAt} />
  100. </Link>
  101. </div>
  102. <div className="col px-0 py-2">
  103. <span className="fw-bold">
  104. {t('page_history.comparing_target')}
  105. </span>
  106. <Link
  107. href={urljoin(
  108. returnPathForURL(currentPagePath, currentPageId),
  109. `?revisionId=${currentRevision._id}`,
  110. )}
  111. className="small ms-2
  112. link-created-at
  113. link-secondary link-opacity-75 link-opacity-100-hover
  114. link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
  115. onClick={onClose}
  116. prefetch={false}
  117. >
  118. <UserDate dateTime={currentRevision.createdAt} />
  119. </Link>
  120. </div>
  121. </div>
  122. </div>
  123. <div
  124. className="revision-history-diff pb-1"
  125. // biome-ignore lint/security/noDangerouslySetInnerHtml: diff view is pre-sanitized HTML
  126. dangerouslySetInnerHTML={diffView}
  127. />
  128. </div>
  129. );
  130. };