mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-26 00:46:52 +08:00
Refactoring: Integrating the file preview component (#11523)
### What problem does this PR solve? Refactoring: Integrating the file preview component ### Type of change - [x] Refactoring
This commit is contained in:
@ -1,282 +0,0 @@
|
||||
// Copyright (c) 2017 PlanGrid, Inc.
|
||||
|
||||
.docxViewerWrapper {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global(.document-container) {
|
||||
padding: 30px;
|
||||
width: 700px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
html,
|
||||
bodyaddress,
|
||||
blockquote,
|
||||
body,
|
||||
dd,
|
||||
div,
|
||||
dl,
|
||||
dt,
|
||||
fieldset,
|
||||
form,
|
||||
frame,
|
||||
frameset,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
noframes,
|
||||
ol,
|
||||
p,
|
||||
ul,
|
||||
center,
|
||||
dir,
|
||||
hr,
|
||||
menu,
|
||||
pre {
|
||||
display: block;
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
li {
|
||||
display: list-item;
|
||||
list-style-type: disc;
|
||||
}
|
||||
head {
|
||||
display: none;
|
||||
}
|
||||
table {
|
||||
display: table;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
tr {
|
||||
display: table-row;
|
||||
}
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
tbody {
|
||||
display: table-row-group;
|
||||
}
|
||||
tfoot {
|
||||
display: table-footer-group;
|
||||
}
|
||||
col {
|
||||
display: table-column;
|
||||
}
|
||||
colgroup {
|
||||
display: table-column-group;
|
||||
}
|
||||
th {
|
||||
display: table-cell;
|
||||
}
|
||||
td {
|
||||
display: table-cell;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
caption {
|
||||
display: table-caption;
|
||||
}
|
||||
th {
|
||||
font-weight: bolder;
|
||||
text-align: center;
|
||||
}
|
||||
caption {
|
||||
text-align: center;
|
||||
}
|
||||
body {
|
||||
margin: 8px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.17em;
|
||||
margin: 0.83em 0;
|
||||
}
|
||||
h4,
|
||||
p,
|
||||
blockquote,
|
||||
ul,
|
||||
fieldset,
|
||||
form,
|
||||
ol,
|
||||
dl,
|
||||
dir,
|
||||
menu {
|
||||
margin: 1.12em 0;
|
||||
}
|
||||
h5 {
|
||||
font-size: 0.83em;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
h6 {
|
||||
font-size: 0.75em;
|
||||
margin: 1.67em 0;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
blockquote {
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
i,
|
||||
cite,
|
||||
em,
|
||||
var,
|
||||
address {
|
||||
font-style: italic;
|
||||
}
|
||||
pre,
|
||||
tt,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace;
|
||||
}
|
||||
pre {
|
||||
white-space: pre;
|
||||
}
|
||||
button,
|
||||
textarea,
|
||||
input,
|
||||
select {
|
||||
display: inline-block;
|
||||
}
|
||||
big {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
small,
|
||||
sub,
|
||||
sup {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
sub {
|
||||
vertical-align: sub;
|
||||
}
|
||||
sup {
|
||||
vertical-align: super;
|
||||
}
|
||||
table {
|
||||
border-spacing: 2px;
|
||||
}
|
||||
thead,
|
||||
tbody,
|
||||
tfoot {
|
||||
vertical-align: middle;
|
||||
}
|
||||
td,
|
||||
th,
|
||||
tr {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
s,
|
||||
strike,
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
hr {
|
||||
border: 1px inset;
|
||||
}
|
||||
ol,
|
||||
ul,
|
||||
dir,
|
||||
menu,
|
||||
dd {
|
||||
margin-left: 40px;
|
||||
}
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
ol ul,
|
||||
ol ul,
|
||||
ul ol,
|
||||
ul ol,
|
||||
ul ul,
|
||||
ul ul,
|
||||
ol ol,
|
||||
ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
u,
|
||||
ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
br:before {
|
||||
content: '\A';
|
||||
white-space: pre-line;
|
||||
}
|
||||
center {
|
||||
text-align: center;
|
||||
}
|
||||
:link,
|
||||
:visited {
|
||||
text-decoration: underline;
|
||||
}
|
||||
:focus {
|
||||
outline: thin dotted invert;
|
||||
}
|
||||
/* Begin bidirectionality settings (do not change) */
|
||||
BDO[DIR='ltr'] {
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
BDO[DIR='rtl'] {
|
||||
direction: rtl;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
*[DIR='ltr'] {
|
||||
direction: ltr;
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
*[DIR='rtl'] {
|
||||
direction: rtl;
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
@media print {
|
||||
h1 {
|
||||
page-break-before: always;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
ul,
|
||||
ol,
|
||||
dl {
|
||||
page-break-before: avoid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
import { Spin } from 'antd';
|
||||
import FileError from '../file-error';
|
||||
|
||||
import { useFetchDocx } from '../hooks';
|
||||
import styles from './index.less';
|
||||
|
||||
const Docx = ({ filePath }: { filePath: string }) => {
|
||||
const { succeed, containerRef, error } = useFetchDocx(filePath);
|
||||
|
||||
return (
|
||||
<>
|
||||
{succeed ? (
|
||||
<section className={styles.docxViewerWrapper}>
|
||||
<div id="docx" ref={containerRef} className={styles.box}>
|
||||
<Spin />
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<FileError>{error}</FileError>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Docx;
|
||||
@ -1,19 +0,0 @@
|
||||
import '@js-preview/excel/lib/index.css';
|
||||
import FileError from '../file-error';
|
||||
import { useFetchExcel } from '../hooks';
|
||||
|
||||
const Excel = ({ filePath }: { filePath: string }) => {
|
||||
const { status, containerRef, error } = useFetchExcel(filePath);
|
||||
|
||||
return (
|
||||
<div
|
||||
id="excel"
|
||||
ref={containerRef}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>
|
||||
{status || <FileError>{error}</FileError>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Excel;
|
||||
@ -1,4 +0,0 @@
|
||||
.errorWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
import { Alert, Flex } from 'antd';
|
||||
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import React from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
const FileError = ({ children }: React.PropsWithChildren) => {
|
||||
const { t } = useTranslate('fileManager');
|
||||
return (
|
||||
<Flex align="center" justify="center" className={styles.errorWrapper}>
|
||||
<Alert
|
||||
type="error"
|
||||
message={<h2>{children || t('fileError')}</h2>}
|
||||
></Alert>
|
||||
</Flex>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="bg-state-error-5 border border-state-error rounded-lg p-4 shadow-sm">
|
||||
<div className="flex ml-3">
|
||||
<div className="text-white font-medium">
|
||||
{children || t('fileError')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
import { Authorization } from '@/constants/authorization';
|
||||
import { getAuthorization } from '@/utils/authorization-util';
|
||||
import jsPreviewExcel from '@js-preview/excel';
|
||||
import axios from 'axios';
|
||||
import mammoth from 'mammoth';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const useCatchError = (api: string) => {
|
||||
const [error, setError] = useState('');
|
||||
const fetchDocument = useCallback(async () => {
|
||||
const ret = await axios.get(api);
|
||||
const { data } = ret;
|
||||
if (!(data instanceof ArrayBuffer) && data.code !== 0) {
|
||||
setError(data.message);
|
||||
}
|
||||
return ret;
|
||||
}, [api]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDocument();
|
||||
}, [fetchDocument]);
|
||||
|
||||
return { fetchDocument, error };
|
||||
};
|
||||
|
||||
export const useFetchDocument = () => {
|
||||
const fetchDocument = useCallback(async (api: string) => {
|
||||
const ret = await axios.get(api, {
|
||||
headers: {
|
||||
[Authorization]: getAuthorization(),
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
return ret;
|
||||
}, []);
|
||||
|
||||
return { fetchDocument };
|
||||
};
|
||||
|
||||
export const useFetchExcel = (filePath: string) => {
|
||||
const [status, setStatus] = useState(true);
|
||||
const { fetchDocument } = useFetchDocument();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { error } = useCatchError(filePath);
|
||||
|
||||
const fetchDocumentAsync = useCallback(async () => {
|
||||
let myExcelPreviewer;
|
||||
if (containerRef.current) {
|
||||
myExcelPreviewer = jsPreviewExcel.init(containerRef.current);
|
||||
}
|
||||
const jsonFile = await fetchDocument(filePath);
|
||||
myExcelPreviewer
|
||||
?.preview(jsonFile.data)
|
||||
.then(() => {
|
||||
console.log('succeed');
|
||||
setStatus(true);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn('failed', e);
|
||||
myExcelPreviewer.destroy();
|
||||
setStatus(false);
|
||||
});
|
||||
}, [filePath, fetchDocument]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDocumentAsync();
|
||||
}, [fetchDocumentAsync]);
|
||||
|
||||
return { status, containerRef, error };
|
||||
};
|
||||
|
||||
export const useFetchDocx = (filePath: string) => {
|
||||
const [succeed, setSucceed] = useState(true);
|
||||
const [error, setError] = useState<string>();
|
||||
const { fetchDocument } = useFetchDocument();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const fetchDocumentAsync = useCallback(async () => {
|
||||
try {
|
||||
const jsonFile = await fetchDocument(filePath);
|
||||
mammoth
|
||||
.convertToHtml(
|
||||
{ arrayBuffer: jsonFile.data },
|
||||
{ includeDefaultStyleMap: true },
|
||||
)
|
||||
.then((result) => {
|
||||
setSucceed(true);
|
||||
const docEl = document.createElement('div');
|
||||
docEl.className = 'document-container';
|
||||
docEl.innerHTML = result.value;
|
||||
const container = containerRef.current;
|
||||
if (container) {
|
||||
container.innerHTML = docEl.outerHTML;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setSucceed(false);
|
||||
});
|
||||
} catch (error: any) {
|
||||
setError(error.toString());
|
||||
}
|
||||
}, [filePath, fetchDocument]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDocumentAsync();
|
||||
}, [fetchDocumentAsync]);
|
||||
|
||||
return { succeed, containerRef, error };
|
||||
};
|
||||
@ -1,16 +1,22 @@
|
||||
import { Images } from '@/constants/common';
|
||||
import { api_host } from '@/utils/api';
|
||||
import { Flex } from 'antd';
|
||||
// import { Flex } from 'antd';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import Docx from './docx';
|
||||
import Excel from './excel';
|
||||
import Image from './image';
|
||||
import Md from './md';
|
||||
import Pdf from './pdf';
|
||||
import Text from './text';
|
||||
// import Docx from './docx';
|
||||
// import Excel from './excel';
|
||||
// import Image from './image';
|
||||
// import Md from './md';
|
||||
// import Pdf from './pdf';
|
||||
// import Text from './text';
|
||||
|
||||
import { DocPreviewer } from '@/components/document-preview/doc-preview';
|
||||
import { ExcelCsvPreviewer } from '@/components/document-preview/excel-preview';
|
||||
import { ImagePreviewer } from '@/components/document-preview/image-preview';
|
||||
import Md from '@/components/document-preview/md';
|
||||
import PdfPreview from '@/components/document-preview/pdf-preview';
|
||||
import { TxtPreviewer } from '@/components/document-preview/txt-preview';
|
||||
import { previewHtmlFile } from '@/utils/file-util';
|
||||
import styles from './index.less';
|
||||
// import styles from './index.less';
|
||||
|
||||
// TODO: The interface returns an incorrect content-type for the SVG.
|
||||
|
||||
@ -20,6 +26,7 @@ const DocumentViewer = () => {
|
||||
const ext = currentQueryParameters.get('ext');
|
||||
const prefix = currentQueryParameters.get('prefix');
|
||||
const api = `${api_host}/${prefix || 'file'}/get/${documentId}`;
|
||||
// request.head
|
||||
|
||||
if (ext === 'html' && documentId) {
|
||||
previewHtmlFile(documentId);
|
||||
@ -27,19 +34,24 @@ const DocumentViewer = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.viewerWrapper}>
|
||||
<section className="w-full h-full">
|
||||
{Images.includes(ext!) && (
|
||||
<Flex className={styles.image} align="center" justify="center">
|
||||
<Image src={api} preview={false}></Image>
|
||||
</Flex>
|
||||
<div className="flex w-full h-full items-center justify-center">
|
||||
{/* <Image src={api} preview={false}></Image> */}
|
||||
<ImagePreviewer className="w-full !h-dvh p-5" url={api} />
|
||||
</div>
|
||||
)}
|
||||
{ext === 'md' && <Md filePath={api}></Md>}
|
||||
{ext === 'txt' && <Text filePath={api}></Text>}
|
||||
{ext === 'md' && <Md url={api} className="!h-dvh p-5"></Md>}
|
||||
{ext === 'txt' && <TxtPreviewer url={api}></TxtPreviewer>}
|
||||
|
||||
{ext === 'pdf' && <Pdf url={api}></Pdf>}
|
||||
{(ext === 'xlsx' || ext === 'xls') && <Excel filePath={api}></Excel>}
|
||||
{ext === 'pdf' && (
|
||||
<PdfPreview url={api} className="!h-dvh p-5"></PdfPreview>
|
||||
)}
|
||||
{(ext === 'xlsx' || ext === 'xls') && (
|
||||
<ExcelCsvPreviewer url={api}></ExcelCsvPreviewer>
|
||||
)}
|
||||
|
||||
{ext === 'docx' && <Docx filePath={api}></Docx>}
|
||||
{ext === 'docx' && <DocPreviewer url={api}></DocPreviewer>}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import FileError from '../file-error';
|
||||
|
||||
interface MdProps {
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
const Md: React.FC<MdProps> = ({ filePath }) => {
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
fetch(filePath)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Failed to fetch markdown file');
|
||||
return res.text();
|
||||
})
|
||||
.then((text) => setContent(text))
|
||||
.catch((err) => setError(err.message));
|
||||
}, [filePath]);
|
||||
|
||||
if (error) return <FileError>{error}</FileError>;
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Md;
|
||||
@ -1,32 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import FileError from '../file-error';
|
||||
|
||||
interface TxtProps {
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
const Md: React.FC<TxtProps> = ({ filePath }) => {
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
fetch(filePath)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Failed to fetch text file');
|
||||
return res.text();
|
||||
})
|
||||
.then((text) => setContent(text))
|
||||
.catch((err) => setError(err.message));
|
||||
}, [filePath]);
|
||||
|
||||
if (error) return <FileError>{error}</FileError>;
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Md;
|
||||
Reference in New Issue
Block a user