Merge pull request #157 from lixiaoming-bit/feature/diff-doc-version

This commit is contained in:
fantasticit 2022-08-13 12:45:19 +08:00 committed by GitHub
commit 9c8125ec53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 8 deletions

View File

@ -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",

View File

@ -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 {

View File

@ -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)}>

View File

@ -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;
};

View File

@ -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: