mirror of https://github.com/fantasticit/think.git
feature: 增加版本比对
This commit is contained in:
parent
099c44cded
commit
732aa1d132
|
@ -63,6 +63,7 @@
|
||||||
"dompurify": "^2.3.5",
|
"dompurify": "^2.3.5",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"html-to-docx": "^1.4.0",
|
"html-to-docx": "^1.4.0",
|
||||||
|
"htmldiff-js": "^1.0.5",
|
||||||
"interactjs": "^1.10.11",
|
"interactjs": "^1.10.11",
|
||||||
"katex": "^0.15.2",
|
"katex": "^0.15.2",
|
||||||
"kity": "^2.0.4",
|
"kity": "^2.0.4",
|
||||||
|
|
|
@ -39,6 +39,44 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global {
|
||||||
|
#diff-visual {
|
||||||
|
del {
|
||||||
|
color: #cb4000;
|
||||||
|
text-decoration: none;
|
||||||
|
list-style: none;
|
||||||
|
background-color: #ffeaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
del::before {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #8a8f8d;
|
||||||
|
text-decoration: none;
|
||||||
|
content: '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
ins {
|
||||||
|
color: green;
|
||||||
|
text-decoration: none;
|
||||||
|
list-style: none;
|
||||||
|
background-color: #e9ffe9;
|
||||||
|
}
|
||||||
|
|
||||||
|
ins::before {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #8a8f8d;
|
||||||
|
text-decoration: none;
|
||||||
|
content: '+';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { IconChevronLeft } from '@douyinfe/semi-icons';
|
import { IconChevronLeft } from '@douyinfe/semi-icons';
|
||||||
import { Button, Modal, Typography } from '@douyinfe/semi-ui';
|
import { Button, Modal, Select, Space, Tag, Typography } from '@douyinfe/semi-ui';
|
||||||
import { EditorContent, useEditor } from '@tiptap/react';
|
import { EditorContent, useEditor } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { LocaleTime } from 'components/locale-time';
|
import { LocaleTime } from 'components/locale-time';
|
||||||
import { useDocumentVersion } from 'data/document';
|
import { useDocumentVersion } from 'data/document';
|
||||||
|
import { generateDiffHtml } from 'helpers/generate-html';
|
||||||
import { safeJSONParse } from 'helpers/json';
|
import { safeJSONParse } from 'helpers/json';
|
||||||
import { DocumentVersionControl } from 'hooks/use-document-version';
|
import { DocumentVersionControl } from 'hooks/use-document-version';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
|
@ -28,6 +29,7 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
||||||
const { visible, toggleVisible } = DocumentVersionControl.useHook();
|
const { visible, toggleVisible } = DocumentVersionControl.useHook();
|
||||||
const { data, loading, error, refresh } = useDocumentVersion(documentId, { enabled: visible });
|
const { data, loading, error, refresh } = useDocumentVersion(documentId, { enabled: visible });
|
||||||
const [selectedVersion, setSelectedVersion] = useState(null);
|
const [selectedVersion, setSelectedVersion] = useState(null);
|
||||||
|
const [diffVersion, setDiffVersion] = useState(null);
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
editable: false,
|
editable: false,
|
||||||
|
@ -42,10 +44,18 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
||||||
|
|
||||||
const select = useCallback(
|
const select = useCallback(
|
||||||
(version) => {
|
(version) => {
|
||||||
|
setDiffVersion(null);
|
||||||
setSelectedVersion(version);
|
setSelectedVersion(version);
|
||||||
editor.commands.setContent(safeJSONParse(version.data, { default: {} }).default);
|
editor.commands.setContent(safeJSONParse(version.data, { default: {} }).default);
|
||||||
},
|
},
|
||||||
[editor]
|
[editor, setDiffVersion]
|
||||||
|
);
|
||||||
|
|
||||||
|
const changeDiffVision = useCallback(
|
||||||
|
(version) => {
|
||||||
|
setDiffVersion(version);
|
||||||
|
},
|
||||||
|
[setDiffVersion]
|
||||||
);
|
);
|
||||||
|
|
||||||
const restore = useCallback(() => {
|
const restore = useCallback(() => {
|
||||||
|
@ -54,6 +64,20 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
||||||
close();
|
close();
|
||||||
}, [selectedVersion, close, onSelect]);
|
}, [selectedVersion, close, onSelect]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (diffVersion && selectedVersion) {
|
||||||
|
const historyVersion = data.find((item) => item.version === diffVersion);
|
||||||
|
|
||||||
|
const diffHtml = generateDiffHtml(
|
||||||
|
safeJSONParse(selectedVersion.data, { default: {} }).default,
|
||||||
|
safeJSONParse(historyVersion.data, { default: {} }).default
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = document.getElementById('diff-visual');
|
||||||
|
element.innerHTML = diffHtml;
|
||||||
|
}
|
||||||
|
}, [diffVersion, data, selectedVersion]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
if (!data.length) return;
|
if (!data.length) return;
|
||||||
|
@ -77,11 +101,28 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
||||||
版本记录
|
版本记录
|
||||||
</Title>
|
</Title>
|
||||||
</div>
|
</div>
|
||||||
|
{selectedVersion && !isMobile && (
|
||||||
<div>
|
<div>
|
||||||
<Button type="primary" theme="solid" disabled={!onSelect || !selectedVersion} onClick={restore}>
|
<Tag color="light-blue" style={{ padding: '12px 14px', fontSize: '14px' }}>
|
||||||
恢复此记录
|
{new Date(+selectedVersion.version).toLocaleString()}
|
||||||
</Button>
|
</Tag>
|
||||||
|
<div style={{ padding: '0 8px' }}>与</div>
|
||||||
|
<Select placeholder="请选择历史" size="small" onChange={changeDiffVision} value={diffVersion} showClear>
|
||||||
|
{data.map(({ version }) => {
|
||||||
|
return (
|
||||||
|
<Select.Option value={version} key={version} disabled={version === selectedVersion.version}>
|
||||||
|
{new Date(+version).toLocaleString()}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
<div style={{ paddingLeft: '8px' }}>对比</div>
|
||||||
|
<Space style={{ position: 'absolute', right: '240px' }}>
|
||||||
|
<Tag style={{ backgroundColor: '#e9ffe9' }}>增加的内容</Tag>
|
||||||
|
<Tag style={{ backgroundColor: '#ffeaea' }}>删除的内容</Tag>
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
theme="light"
|
theme="light"
|
||||||
|
@ -93,6 +134,9 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
||||||
>
|
>
|
||||||
刷新
|
刷新
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button type="primary" theme="solid" disabled={!onSelect || !selectedVersion} onClick={restore}>
|
||||||
|
恢复此记录
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -106,7 +150,11 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
||||||
<div className={styles.contentWrap}>
|
<div className={styles.contentWrap}>
|
||||||
<main className={cls(isMobile && styles.isMobile)}>
|
<main className={cls(isMobile && styles.isMobile)}>
|
||||||
<div className={'container'}>
|
<div className={'container'}>
|
||||||
|
{diffVersion ? (
|
||||||
|
<div id="diff-visual" className="ProseMirror"></div>
|
||||||
|
) : (
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<aside className={cls(isMobile && styles.isMobile)}>
|
<aside className={cls(isMobile && styles.isMobile)}>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { generateHTML } from '@tiptap/core';
|
||||||
|
import HtmlDiff from 'htmldiff-js';
|
||||||
|
import { CollaborationKit } from 'tiptap/editor';
|
||||||
|
|
||||||
|
const json2html = (json) => generateHTML(json, CollaborationKit);
|
||||||
|
export const generateDiffHtml = (selected, other) => {
|
||||||
|
const selectedHtml = json2html(selected);
|
||||||
|
const otherHtml = json2html(other);
|
||||||
|
let diffHtml = HtmlDiff.execute(selectedHtml, otherHtml);
|
||||||
|
diffHtml = diffHtml.replace(/<iframe\s*[^>]*>(.*?)<\/iframe>/gi, '');
|
||||||
|
return diffHtml;
|
||||||
|
};
|
|
@ -109,6 +109,7 @@ importers:
|
||||||
eslint-plugin-simple-import-sort: ^7.0.0
|
eslint-plugin-simple-import-sort: ^7.0.0
|
||||||
fs-extra: ^10.0.0
|
fs-extra: ^10.0.0
|
||||||
html-to-docx: ^1.4.0
|
html-to-docx: ^1.4.0
|
||||||
|
htmldiff-js: ^1.0.5
|
||||||
interactjs: ^1.10.11
|
interactjs: ^1.10.11
|
||||||
katex: ^0.15.2
|
katex: ^0.15.2
|
||||||
kity: ^2.0.4
|
kity: ^2.0.4
|
||||||
|
@ -207,6 +208,7 @@ importers:
|
||||||
dompurify: 2.3.5
|
dompurify: 2.3.5
|
||||||
downloadjs: 1.4.7
|
downloadjs: 1.4.7
|
||||||
html-to-docx: 1.4.0
|
html-to-docx: 1.4.0
|
||||||
|
htmldiff-js: 1.0.5
|
||||||
interactjs: 1.10.11
|
interactjs: 1.10.11
|
||||||
katex: 0.15.2
|
katex: 0.15.2
|
||||||
kity: 2.0.4
|
kity: 2.0.4
|
||||||
|
@ -6708,6 +6710,10 @@ packages:
|
||||||
htmlparser2: 3.10.1
|
htmlparser2: 3.10.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/htmldiff-js/1.0.5:
|
||||||
|
resolution: {integrity: sha512-rmow9353OK0elkub15Sbze8Nj7BYfduqoJJw4yEvHHjOcHeCazNPk0PoUbjE8SvxKgjymeRIFU/OnS8jtitRtA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/htmlparser2/3.10.1:
|
/htmlparser2/3.10.1:
|
||||||
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
|
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in New Issue