forgeplus-react/src/components/render-html.jsx

147 lines
4.3 KiB
JavaScript

import React, { useEffect, useRef, useMemo , useState } from 'react'
import 'katex/dist/katex.min.css';
import marked, { getTocContent, cleanToc, getMathExpressions, resetMathExpressions } from '../common/marked';
import 'code-prettify';
import dompurify from 'dompurify';
import { getEmoji } from '../forge/Main/emoji';
import axios from 'axios';
import { renderToString } from 'katex'
const preRegex = /<pre[^>]*>/g;
const strRegexSub = /:([a-zA-Z_]+):/g;
const quoteRegex = /\[[#][0-9]{0,}\]\(\/(.*?)\/(.*?)\/issues\/[0-9]{0,}\)/g;
function _unescape(str) {
let div = document.createElement('div')
div.innerHTML = str
return div.childNodes.length === 0 ? "" : div.childNodes[0].nodeValue
}
export default ({
value = '',
className,
style = {},
url,
owner=undefined,
projectsId=undefined
}) => {
const [ issues , setIssues ] = useState([]);
useEffect(()=>{
if(owner&&projectsId){
getIssueList();
}
},[owner,projectsId])
function getIssueList(){
axios.get(`/v1/${owner}/${projectsId}/issues`,{params:{
only_name:true,sort_direction:"desc",sort_by:"issues.created_on"
}}).then(result=>{
if(result){
let data = result.data.issues;
setIssues(data);
}
})
}
let str = String(value);
const html = useMemo(() => {
let rs = marked(str);
const math_expressions = getMathExpressions();
if (str.match(/\[TOC\]/)) {
rs = rs.replace("<p>[TOC]</p>", getTocContent())
cleanToc()
}
// 循环匹配所有emoji
let matchStr = str.match(strRegexSub);
if(matchStr && matchStr.length>0){
for(var i=0;i < matchStr.length;i++){
rs = rs.replace(matchStr[i],getEmoji(matchStr[i]));
}
}
if(owner && projectsId && issues && issues.length>0){
let matchQuote = str.match(quoteRegex);
if(matchQuote && matchQuote.length>0){
let getIndexReg = /(?<=#)(.+?)(?=\])/g;
for(var x=0;x<matchQuote.length;x++){
let getIndex = matchQuote[x].match(getIndexReg);
if(getIndex && getIndex.length>0 && getIndex[0]){
let index = getIndex[0];
let filter = issues.filter(f=>f.project_issues_index.toString() === index);
if(filter && filter.length === 1){
let content = `#${index}:${filter[0].subject}`;
rs = rs.replace(`#${index}`,content);
}else{
let content = `<span>#${index}(已删除)</span>`;
rs = rs.replace(`<a href="`+`/${owner}/${projectsId}/issues/${index}`+`">#${index}</a>`,content);
}
}
}
}
}
rs = rs.replace(/(__special_katext_id_\d+__)/g, (_match, capture) => {
const { type, expression } = math_expressions[capture];
return renderToString(_unescape(expression) || '', { displayMode: type === 'block', throwOnError: false, output: 'html' })
})
rs = rs.replace(/▁/g, "▁▁▁")
resetMathExpressions()
return dompurify.sanitize(rs)
}, [str,issues]);
// 锚点跳转,链接地址里含#对应的id
useEffect(()=>{
if(url && url.hash && html){
let u = url.hash;
if(u){
let id = decodeURIComponent(u.split("#")[1]);
let ele = document.getElementById(id);
if(ele){
window.scrollTo(0, ele.offsetTop);
}
}
}
},[url,html])
const el = useRef();
function onAncherHandler(e) {
let target = e.target;
if (target.tagName.toUpperCase() === 'A') {
let ancher = target.getAttribute('href');
if (ancher && ancher.startsWith('#')) {
e.preventDefault()
let viewEl = document.getElementById(ancher.replace('#', ''))
if (viewEl) {
viewEl.scrollIntoView(true)
}
}
}
}
useEffect(() => {
if (el.current && html) {
if (html.match(preRegex)) {
window.PR.prettyPrint()
}
}
if (el.current) {
el.current.addEventListener('click', onAncherHandler)
return () => {
el.current.removeEventListener('click', onAncherHandler)
resetMathExpressions()
cleanToc()
}
}
}, [html, el.current, onAncherHandler])
return (
<div
ref={el}
style={style}
className={`${className ? className : ''} markdown-body`}
dangerouslySetInnerHTML={{ __html: html }}
></div>
)
}