mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 07:04:45 -04:00
Feature/lazy load components (#590)
This commit is contained in:
parent
9c38655a92
commit
101292da92
46 changed files with 261 additions and 248 deletions
|
@ -1,19 +1,21 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
import { renderAbc } from 'abcjs'
|
||||
|
||||
export interface AbcFrameProps {
|
||||
code: string
|
||||
}
|
||||
|
||||
export const AbcFrame: React.FC<AbcFrameProps> = ({ code }) => {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (container.current) {
|
||||
renderAbc(container.current, code)
|
||||
if (!container.current) {
|
||||
return
|
||||
}
|
||||
const actualContainer = container.current
|
||||
import(/* webpackChunkName: "abc.js" */ 'abcjs').then((imp) => {
|
||||
imp.renderAbc(actualContainer, code)
|
||||
}).catch(() => { console.error('error while loading abcjs') })
|
||||
}, [code])
|
||||
|
||||
return (
|
||||
<div ref={container} className={'bg-white text-center'}/>
|
||||
)
|
||||
return <div ref={container} className={'bg-white text-center'}/>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { parse } from 'flowchart.js'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -17,20 +16,21 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
|
|||
if (diagramRef.current === null) {
|
||||
return
|
||||
}
|
||||
const parserOutput = parse(code)
|
||||
try {
|
||||
parserOutput.drawSVG(diagramRef.current, {
|
||||
'line-width': 2,
|
||||
fill: 'none',
|
||||
'font-size': '16px',
|
||||
'font-family': 'Source Code Pro, twemoji, monospace'
|
||||
})
|
||||
setError(false)
|
||||
} catch (error) {
|
||||
setError(true)
|
||||
}
|
||||
|
||||
const currentDiagramRef = diagramRef.current
|
||||
import(/* webpackChunkName: "flowchart.js" */ 'flowchart.js').then((imp) => {
|
||||
const parserOutput = imp.parse(code)
|
||||
try {
|
||||
parserOutput.drawSVG(currentDiagramRef, {
|
||||
'line-width': 2,
|
||||
fill: 'none',
|
||||
'font-size': '16px',
|
||||
'font-family': 'Source Code Pro, twemoji, monospace'
|
||||
})
|
||||
setError(false)
|
||||
} catch (error) {
|
||||
setError(true)
|
||||
}
|
||||
}).catch(() => { console.error('error while loading flowchart.js') })
|
||||
|
||||
return () => {
|
||||
Array.from(currentDiagramRef.children).forEach(value => value.remove())
|
||||
|
@ -41,8 +41,8 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
|
|||
return (
|
||||
<Alert variant={'danger'}>
|
||||
<Trans i18nKey={'renderer.flowchart.invalidSyntax'}/>
|
||||
</Alert>
|
||||
)
|
||||
</Alert>)
|
||||
} else {
|
||||
return <div ref={diagramRef} className={'text-center'}/>
|
||||
}
|
||||
return <div ref={diagramRef} className={'text-center'}/>
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { graphviz } from 'd3-graphviz'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import '@hpcc-js/wasm'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
|
||||
export interface GraphvizFrameProps {
|
||||
|
@ -25,14 +23,18 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
|
|||
if (!container.current) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
setError(undefined)
|
||||
graphviz(container.current, { useWorker: false, zoom: false })
|
||||
.onerror(showError)
|
||||
.renderDot(code)
|
||||
} catch (error) {
|
||||
showError(error)
|
||||
}
|
||||
const actualContainer = container.current
|
||||
|
||||
Promise.all([import(/* webpackChunkName: "d3-graphviz" */ 'd3-graphviz'), import('@hpcc-js/wasm')]).then(([imp]) => {
|
||||
try {
|
||||
setError(undefined)
|
||||
imp.graphviz(actualContainer, { useWorker: false, zoom: false })
|
||||
.onerror(showError)
|
||||
.renderDot(code)
|
||||
} catch (error) {
|
||||
showError(error)
|
||||
}
|
||||
}).catch(() => { console.error('error while loading graphviz') })
|
||||
}, [code, error, showError])
|
||||
|
||||
return <Fragment>
|
||||
|
@ -42,3 +44,5 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
|
|||
<div className={'text-center'} ref={container} />
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
export default GraphvizFrame
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import hljs from 'highlight.js'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import React, { Fragment, ReactElement, useEffect, useState } from 'react'
|
||||
import ReactHtmlParser from 'react-html-parser'
|
||||
import { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
||||
import '../../../utils/button-inside.scss'
|
||||
|
@ -21,10 +20,6 @@ export const escapeHtml = (unsafe: string): string => {
|
|||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const checkIfLanguageIsSupported = (language: string): boolean => {
|
||||
return hljs.listLanguages().includes(language)
|
||||
}
|
||||
|
||||
const correctLanguage = (language: string | undefined): string | undefined => {
|
||||
switch (language) {
|
||||
case 'html':
|
||||
|
@ -34,29 +29,36 @@ const correctLanguage = (language: string | undefined): string | undefined => {
|
|||
}
|
||||
}
|
||||
|
||||
const replaceCode = (code: string): ReactElement[][] => {
|
||||
return code.split('\n')
|
||||
.filter(line => !!line)
|
||||
.map(line => ReactHtmlParser(line))
|
||||
}
|
||||
|
||||
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
||||
const highlightedCode = useMemo(() => {
|
||||
const replacedLanguage = correctLanguage(language)
|
||||
return ((!!replacedLanguage && checkIfLanguageIsSupported(replacedLanguage)) ? hljs.highlight(replacedLanguage, code).value : escapeHtml(code))
|
||||
.split('\n')
|
||||
.filter(line => !!line)
|
||||
.map(line => ReactHtmlParser(line))
|
||||
}, [code, language])
|
||||
const [dom, setDom] = useState<ReactElement[]>()
|
||||
|
||||
useEffect(() => {
|
||||
import(/* webpackChunkName: "highlight.js" */ 'highlight.js').then((hljs) => {
|
||||
const correctedLanguage = correctLanguage(language)
|
||||
const languageSupported = (lang: string) => hljs.listLanguages().includes(lang)
|
||||
const unreplacedCode = !!correctedLanguage && languageSupported(correctedLanguage) ? hljs.highlight(correctedLanguage, code).value : escapeHtml(code)
|
||||
const replacedDom = replaceCode(unreplacedCode).map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
|
||||
<div className={'codeline'}>
|
||||
{line}
|
||||
</div>
|
||||
</Fragment>
|
||||
))
|
||||
setDom(replacedDom)
|
||||
}).catch(() => { console.error('error while loading highlight.js') })
|
||||
}, [code, language, startLineNumber])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<code className={`hljs ${startLineNumber !== undefined ? 'showGutter' : ''} ${wrapLines ? 'wrapLines' : ''}`}>
|
||||
{
|
||||
highlightedCode
|
||||
.map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
|
||||
<div className={'codeline'}>
|
||||
{line}
|
||||
</div>
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
{ dom }
|
||||
</code>
|
||||
<div className={'text-right button-inside'}>
|
||||
<CopyToClipboardButton content={code}/>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { DomElement } from 'domhandler'
|
||||
import React from 'react'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import TeX from '@matejmazur/react-katex'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import './katex.scss'
|
||||
|
||||
const getNodeIfKatexBlock = (node: DomElement): (DomElement|undefined) => {
|
||||
if (node.name !== 'p' || !node.children || node.children.length === 0) {
|
||||
|
@ -17,13 +16,15 @@ const getNodeIfInlineKatex = (node: DomElement): (DomElement|undefined) => {
|
|||
return (node.name === 'app-katex' && node.attribs?.inline !== undefined) ? node : undefined
|
||||
}
|
||||
|
||||
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
|
||||
|
||||
export class KatexReplacer extends ComponentReplacer {
|
||||
public getReplacement (node: DomElement): React.ReactElement | undefined {
|
||||
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
|
||||
if (katex?.children && katex.children[0]) {
|
||||
const mathJaxContent = katex.children[0]?.data as string
|
||||
const isInline = (katex.attribs?.inline) !== undefined
|
||||
return <TeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
|
||||
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@import '../../../../../node_modules/katex/dist/katex.min';
|
|
@ -1,5 +1,3 @@
|
|||
import { transform } from 'markmap-lib/dist/transform'
|
||||
import { Markmap } from 'markmap-lib/dist/view'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
export interface MarkmapFrameProps {
|
||||
|
@ -13,12 +11,16 @@ export const MarkmapFrame: React.FC<MarkmapFrameProps> = ({ code }) => {
|
|||
if (!diagramContainer.current) {
|
||||
return
|
||||
}
|
||||
const svg: SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
svg.setAttribute('width', '100%')
|
||||
diagramContainer.current.querySelectorAll('svg').forEach(child => child.remove())
|
||||
diagramContainer.current.appendChild(svg)
|
||||
const data = transform(code)
|
||||
Markmap.create(svg, {}, data)
|
||||
const actualContainer = diagramContainer.current
|
||||
Promise.all([import(/* webpackChunkName: "markmap" */ 'markmap-lib/dist/transform'), import(/* webpackChunkName: "markmap" */ 'markmap-lib/dist/view')])
|
||||
.then(([transform, view]) => {
|
||||
const svg: SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
svg.setAttribute('width', '100%')
|
||||
actualContainer.querySelectorAll('svg').forEach(child => child.remove())
|
||||
actualContainer.appendChild(svg)
|
||||
const data = transform.transform(code)
|
||||
view.Markmap.create(svg, {}, data)
|
||||
}).catch(() => { console.error('error while loading markmap') })
|
||||
}, [code])
|
||||
|
||||
return <div className={'text-center'} ref={diagramContainer}/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import embed, { VisualizationSpec } from 'vega-embed'
|
||||
import { VisualizationSpec } from 'vega-embed'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
|
||||
export interface VegaChartProps {
|
||||
|
@ -25,27 +25,31 @@ export const VegaChart: React.FC<VegaChartProps> = ({ code }) => {
|
|||
if (!diagramContainer.current) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const spec: VisualizationSpec = JSON.parse(code)
|
||||
showError('')
|
||||
embed(diagramContainer.current, spec, {
|
||||
actions: {
|
||||
export: true,
|
||||
source: false,
|
||||
compiled: false,
|
||||
editor: false
|
||||
},
|
||||
i18n: {
|
||||
PNG_ACTION: t('renderer.vega-lite.png'),
|
||||
SVG_ACTION: t('renderer.vega-lite.svg')
|
||||
import(/* webpackChunkName: "vega" */ 'vega-embed').then((embed) => {
|
||||
try {
|
||||
if (!diagramContainer.current) {
|
||||
return
|
||||
}
|
||||
})
|
||||
.then(result => console.log(result))
|
||||
.catch(err => showError(err))
|
||||
} catch (err) {
|
||||
showError(t('renderer.vega-lite.errorJson'))
|
||||
}
|
||||
|
||||
const spec = JSON.parse(code) as VisualizationSpec
|
||||
embed.default(diagramContainer.current, spec, {
|
||||
actions: {
|
||||
export: true,
|
||||
source: false,
|
||||
compiled: false,
|
||||
editor: false
|
||||
},
|
||||
i18n: {
|
||||
PNG_ACTION: t('renderer.vega-lite.png'),
|
||||
SVG_ACTION: t('renderer.vega-lite.svg')
|
||||
}
|
||||
})
|
||||
.then(() => showError(''))
|
||||
.catch(err => showError(err))
|
||||
} catch (err) {
|
||||
showError(t('renderer.vega-lite.errorJson'))
|
||||
}
|
||||
}).catch(() => { console.error('error while loading vega-light') })
|
||||
}, [code, showError, t])
|
||||
|
||||
return <Fragment>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DomElement } from 'domhandler'
|
||||
import React, { Fragment, ReactElement } from 'react'
|
||||
import React, { ReactElement, Suspense } from 'react'
|
||||
import { convertNodeToElement, Transform } from 'react-html-parser'
|
||||
import {
|
||||
ComponentReplacer,
|
||||
|
@ -69,7 +69,9 @@ export const buildTransformer = (lineKeys: (LineKeys[] | undefined), allReplacer
|
|||
} else if (tryReplacement === undefined) {
|
||||
return nativeRenderer(node, key)
|
||||
} else {
|
||||
return <Fragment key={key}>{tryReplacement}</Fragment>
|
||||
return <Suspense key={key} fallback={<span>Loading...</span>}>
|
||||
{ tryReplacement }
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
return transform
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue