⚠️ Supplement: My requirement is that I need to upload the file to the backend first, so I get the url
address to display, for markdown
and txt
files need to use fetch
to get them first, and for other displays just use the url
link directly.
Different documents are implemented in different ways, the following classification is explained, in total, is divided into the following categories:
- Own label file:
png、jpg、jpeg、audio、video
- Text-only documents:
markdown & txt
office
type of file:doc、xlsx、ppt
embed
Introduce the document:pdf
-
iframe
: Introduction of an external full website
Own label file: png、jpg、jpeg、audio、video
For image, audio and video previews, you can directly use the corresponding tags as follows:
Picture: png、jpg、jpeg
Sample code:
<img src={url} key={docId} alt={name} width="100%" />;
The preview results are as follows:
Audio: audio
Sample code:
<audio ref={audioRef} controls controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="audio/mpeg" />
</audio>
The preview results are as follows:
Video: video
Sample code:
<video ref={videoRef} controls muted controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="video/mp4" />
</video>
The preview results are as follows:
Complete code on the positioning of audio and video:
import React, { useRef, useEffect } from 'react';
interface IProps {
type: 'audio' | 'video';
url: string;
timeInSeconds: number;
}
function AudioAndVideo(props: IProps) {
const { type, url, timeInSeconds } = props;
const videoRef = useRef<HTMLVideoElement>(null);
const audioRef = useRef<HTMLAudioElement>(null);
useEffect(() => {
const secondsTime = timeInSeconds / 1000;
if (type === 'audio' && audioRef.current) {
audioRef.current.currentTime = secondsTime;
}
if (type === 'video' && videoRef.current) {
videoRef.current.currentTime = secondsTime;
}
}, [type, timeInSeconds]);
return (
<div>
{type === 'audio' ? (
<audio ref={audioRef} controls controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="audio/mpeg" />
</audio>
) : (
<video ref={videoRef} controls muted controlsList="nodownload" style={{ width: '100%' }}>
<track kind="captions" />
<source src={url} type="video/mp4" />
</video>
)}
</div>
);
}
export default AudioAndVideo;
Text-only documents: markdown & txt
For a file of typemarkdown、txt
, if you get a file of typeurl
, you can’t display it directly, you need to request the content and then display it.
markdown
file
When displaying themarkdown
file, you need to fulfill the requirements of font highlighting, code highlighting, if there is font highlighting, you need to scroll to the location of the font, and if there is an external link, you need to open a new tab page and then open it again.
Two libraries need to be introduced:
marked
: It serves to convert (parse) markdown
text to HTML
.
highlight
: It allows developers to highlight code on web pages.
Code implementation of font highlighting:
The style of highlighting can be defined in the interline style
const highlightAndMarkFirst = (text: string, highlightText: string) => {
let firstMatchDone = false;
const regex = new RegExp(`(${highlightText})`, 'gi');
return text.replace(regex, (match) => {
if (!firstMatchDone) {
firstMatchDone = true;
return `<span id='first-match' style="color: red;">${match}</span>`;
}
return `<span style="color: red;">${match}</span>`;
});
};
Code implementation of code highlighting:
You need to use the library
hljs
to do the conversion
marked.use({
renderer: {
code(code, infostring) {
const validLang = !!(infostring && hljs.getLanguage(infostring));
const highlighted = validLang
? hljs.highlight(code, { language: infostring, ignoreIllegals: true }).value
: code;
return `<pre><code class="hljs ${infostring}">${highlighted}</code></pre>`;
}
},
});
Code implementation of the link to jump to the new tab
page.
marked.use({
renderer: {
link(href, title, text) {
const isExternal = !href.startsWith('/') && !href.startsWith('#');
if (isExternal) {
return `<a href="${href}" title="${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
}
return `<a href="${href}" title="${title}">${text}</a>`;
},
},
});
Code implementation of scrolling to the highlighted position:
It needs to be coupled with the code highlighting method above
const firstMatchElement = document.getElementById('first-match');
if (firstMatchElement) {
firstMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
The complete code is below:
The entrydocUrl
is the on-lineur
l address of themarkdown
file, andsearchText
is what needs to be highlighted.
import React, { useEffect, useState, useRef } from 'react';
import { marked } from 'marked';
import hljs from 'highlight.js';
const preStyle = {
width: '100%',
maxHeight: '64vh',
minHeight: '64vh',
overflow: 'auto',
};
function MarkdownViewer({ docUrl, searchText }: { docUrl: string; searchText: string }) {
const [markdown, setMarkdown] = useState('');
const markdownRef = useRef<HTMLDivElement | null>(null);
const highlightAndMarkFirst = (text: string, highlightText: string) => {
let firstMatchDone = false;
const regex = new RegExp(`(${highlightText})`, 'gi');
return text.replace(regex, (match) => {
if (!firstMatchDone) {
firstMatchDone = true;
return `<span id='first-match' style="color: red;">${match}</span>`;
}
return `<span style="color: red;">${match}</span>`;
});
};
useEffect(() => {
fetch(docUrl)
.then((response) => response.text())
.then((text) => {
const highlightedText = searchText ? highlightAndMarkFirst(text, searchText) : text;
setMarkdown(highlightedText);
})
.catch((error) => console.error('2:', error));
}, [searchText, docUrl]);
useEffect(() => {
if (markdownRef.current) {
marked.use({
renderer: {
code(code, infostring) {
const validLang = !!(infostring && hljs.getLanguage(infostring));
const highlighted = validLang
? hljs.highlight(code, { language: infostring, ignoreIllegals: true }).value
: code;
return `<pre><code class="hljs ${infostring}">${highlighted}</code></pre>`;
},
link(href, title, text) {
const isExternal = !href.startsWith('/') && !href.startsWith('#');
if (isExternal) {
return `<a href="${href}" title="${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
}
return `<a href="${href}" title="${title}">${text}</a>`;
},
},
});
const htmlContent = marked.parse(markdown);
markdownRef.current!.innerHTML = htmlContent as string;
const firstMatchElement = document.getElementById('first-match');
if (firstMatchElement) {
firstMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}, [markdown]);
return (
<div style={preStyle}>
<div ref={markdownRef} />
</div>
);
}
export default MarkdownViewer;
The preview results are as follows:
txt
File preview display
Supports highlighting and scrolling to a specific position
Support for highlighted code:
function highlightText(text: string) {
if (!searchText.trim()) return text;
const regex = new RegExp(`(${searchText})`, 'gi');
return text.replace(regex, `<span style="color: red">$1</span>`);
}
Full Code:
import React, { useEffect, useState, useRef } from 'react';
import { preStyle } from './config';
function TextFileViewer({ docurl, searchText }: { docurl: string; searchText: string }) {
const [paragraphs, setParagraphs] = useState<string[]>([]);
const targetRef = useRef<HTMLDivElement | null>(null);
function highlightText(text: string) {
if (!searchText.trim()) return text;
const regex = new RegExp(`(${searchText})`, 'gi');
return text.replace(regex, `<span style="color: red">$1</span>`);
}
useEffect(() => {
fetch(docurl)
.then((response) => response.text())
.then((text) => {
const highlightedText = highlightText(text);
const paras = highlightedText
.split('\n')
.map((para) => para.trim())
.filter((para) => para);
setParagraphs(paras);
})
.catch((error) => {
console.error('1:', error);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [docurl, searchText]);
useEffect(() => {
const timer = setTimeout(() => {
if (targetRef.current) {
targetRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, 100);
return () => clearTimeout(timer);
}, [paragraphs]);
return (
<div style={preStyle}>
{paragraphs.map((para: string, index: number) => {
const paraKey = para + index;
const isTarget = para.includes(`>${searchText}<`);
return (
<p key={paraKey} ref={isTarget && !targetRef.current ? targetRef : null}>
<div dangerouslySetInnerHTML={{ __html: para }} />
</p>
);
})}
</div>
);
}
export default TextFileViewer;
The preview results are as follows:
office
type of file: doc、xlsx、ppt
doc、xlsx、ppt
For the preview of the file, it is enough to use the online preview link atoffice
+ the onlineurl
of our file.
About positioning: with this method I temporarily try is not able to locate the page number, so the positioning function I take is the back-end will beoffice
file into
Sample code:
<iframe
src={`https://view.officeapps.live.com/op/view.aspx?src=${url}`}
width="100%"
height="500px"
frameBorder="0"
></iframe>
The preview results are as follows:
embed
Introduce the document: pdf
In theembed
, thishttpsUrl
is the link address of your
Sample code:
<embed src={`${httpsUrl}`} style={preStyle} key={`${httpsUrl}`} />;
Regarding the positioning, it is actually the page number spliced on the address sourcePage
which is as follows:
const httpsUrl = sourcePage
? `${doc.url}#page=${sourcePage}`
: doc.url;
<embed src={`${httpsUrl}`} style={preStyle} key={`${httpsUrl}`} />;
The preview results are as follows:
iframe
: Introduction of an external full website
In addition to the various files above, we also need to preview some external URLs, then we need to use theiframe
method
Sample code:
<iframe
title="网址"
width="100%"
height="100%"
src={doc.url}
allow="microphone;camera;midi;encrypted-media;"/>