110 lines
3 KiB
TypeScript
110 lines
3 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
import { renderAsync } from 'docx-preview';
|
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
|
import styles from '../ContentPreview.module.css';
|
|
|
|
interface WordRendererProps {
|
|
blob: Blob;
|
|
fileName: string;
|
|
mimeType?: string;
|
|
onError: (message: string) => void;
|
|
}
|
|
|
|
const SUPPORTED_MIME_TYPES = new Set([
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
]);
|
|
|
|
export function isWordMimeType(mimeType?: string, fileName?: string): boolean {
|
|
if (mimeType && SUPPORTED_MIME_TYPES.has(mimeType)) return true;
|
|
if (fileName && /\.docx$/i.test(fileName)) return true;
|
|
return false;
|
|
}
|
|
|
|
export function WordRenderer({ blob, fileName, mimeType, onError }: WordRendererProps) {
|
|
const { t } = useLanguage();
|
|
const bodyRef = useRef<HTMLDivElement | null>(null);
|
|
const styleRef = useRef<HTMLDivElement | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
|
|
const isLegacyDoc = useMemo(
|
|
() => mimeType === 'application/msword' || /\.doc$/i.test(fileName),
|
|
[mimeType, fileName],
|
|
);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
if (isLegacyDoc) {
|
|
const msg = t(
|
|
'Das alte Word-Format (.doc) wird nicht unterstützt. Bitte konvertiere die Datei in .docx.',
|
|
);
|
|
setLocalError(msg);
|
|
setLoading(false);
|
|
onError(msg);
|
|
return;
|
|
}
|
|
|
|
const body = bodyRef.current;
|
|
const styleContainer = styleRef.current;
|
|
if (!body || !styleContainer) return;
|
|
|
|
body.innerHTML = '';
|
|
styleContainer.innerHTML = '';
|
|
setLoading(true);
|
|
setLocalError(null);
|
|
|
|
renderAsync(blob, body, styleContainer, {
|
|
className: 'docx-preview',
|
|
inWrapper: true,
|
|
ignoreWidth: false,
|
|
ignoreHeight: false,
|
|
ignoreFonts: false,
|
|
breakPages: true,
|
|
experimental: true,
|
|
trimXmlDeclaration: true,
|
|
useBase64URL: true,
|
|
renderHeaders: true,
|
|
renderFooters: true,
|
|
renderFootnotes: true,
|
|
renderEndnotes: true,
|
|
})
|
|
.then(() => {
|
|
if (cancelled) return;
|
|
setLoading(false);
|
|
})
|
|
.catch(err => {
|
|
if (cancelled) return;
|
|
const msg =
|
|
err?.message ?? t('Word-Dokument konnte nicht gerendert werden.');
|
|
setLocalError(msg);
|
|
setLoading(false);
|
|
onError(msg);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [blob, isLegacyDoc, onError, t]);
|
|
|
|
if (localError) {
|
|
return (
|
|
<div className={styles.textContainer}>
|
|
<div className={styles.textHeader}>
|
|
<span className={styles.textTitle}>{fileName}</span>
|
|
</div>
|
|
<div style={{ padding: '1rem', color: 'var(--color-error)' }}>{localError}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={styles.docxContainer}>
|
|
{loading && (
|
|
<div className={styles.docxLoading}>{t('Word-Dokument wird geladen...')}</div>
|
|
)}
|
|
<div ref={styleRef} />
|
|
<div ref={bodyRef} className={styles.docxBody} />
|
|
</div>
|
|
);
|
|
}
|