Extract code highlighting from async call

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-05-02 16:53:20 +02:00
parent c6857a46c8
commit 9b69914b60
3 changed files with 57 additions and 39 deletions

View file

@ -9,9 +9,10 @@ import { CopyToClipboardButton } from '../../../common/copyable/copy-to-clipboar
import styles from './highlighted-code.module.scss'
import { cypressAttribute, cypressId } from '../../../../utils/cypress-attribute'
import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary'
import { useAsyncHighlightedCodeDom } from './hooks/use-async-highlighted-code-dom'
import { useAsyncHighlightJsImport } from './hooks/use-async-highlight-js-import'
import { useAttachLineNumbers } from './hooks/use-attach-line-numbers'
import { testId } from '../../../../utils/test-id'
import { useCodeDom } from './hooks/use-code-dom'
export interface HighlightedCodeProps {
code: string
@ -30,8 +31,9 @@ export interface HighlightedCodeProps {
*/
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
const showGutter = startLineNumber !== undefined
const { loading, error, value: highlightedLines } = useAsyncHighlightedCodeDom(code, language)
const wrappedDomLines = useAttachLineNumbers(highlightedLines, startLineNumber)
const { value: hljsApi, loading, error } = useAsyncHighlightJsImport()
const codeDom = useCodeDom(code, hljsApi, language)
const wrappedDomLines = useAttachLineNumbers(codeDom, startLineNumber)
return (
<AsyncLoadingBoundary loading={loading} error={!!error} componentName={'highlight.js'}>

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useAsync } from 'react-use'
import { Logger } from '../../../../../utils/logger'
import type { AsyncState } from 'react-use/lib/useAsyncFn'
import type { HLJSApi } from 'highlight.js'
const log = new Logger('HighlightedCode')
/**
* Lazy loads the highlight js library.
*
* @return the loaded js lib
*/
export const useAsyncHighlightJsImport = (): AsyncState<HLJSApi> => {
return useAsync(async () => {
try {
return (await import(/* webpackChunkName: "highlight.js" */ '../../../../common/hljs/hljs')).default
} catch (error) {
log.error('Error while loading highlight.js', error)
throw error
}
}, [])
}

View file

@ -5,12 +5,32 @@
*/
import type { ReactElement } from 'react'
import React, { Fragment } from 'react'
import React, { Fragment, useMemo } from 'react'
import { MarkdownExtensionCollection } from '../../markdown-extension-collection'
import convertHtmlToReact from '@hedgedoc/html-to-react'
import { useAsync } from 'react-use'
import { Logger } from '../../../../../utils/logger'
import type { AsyncState } from 'react-use/lib/useAsyncFn'
import type { HLJSApi } from 'highlight.js'
/**
* Highlights the given code using highlight.js. If the language wasn't recognized then it won't be highlighted.
*
* @param code The code to highlight
* @param hljs The highlight.js API. Needs to be imported or lazy loaded.
* @param language The language of the code to use for highlighting
* @return The react elements that represent the highlighted code
*/
export const useCodeDom = (code: string, hljs: HLJSApi | undefined, language?: string): ReactElement[] | undefined => {
return useMemo(() => {
if (!hljs) {
return
}
if (!!language && hljs.listLanguages().includes(language)) {
const highlightedHtml = hljs.highlight(code, { language }).value
return createHtmlLinesToReactDOM(omitNewLineAtEnd(highlightedHtml).split('\n'))
} else {
return createPlaintextToReactDOM(code)
}
}, [code, hljs, language])
}
const nodeProcessor = new MarkdownExtensionCollection([]).buildFlatNodeProcessor()
@ -40,38 +60,6 @@ const createPlaintextToReactDOM = (text: string): ReactElement[] => {
return text.split('\n').map((line, lineIndex) => React.createElement('span', { key: lineIndex }, line))
}
export interface HighlightedCodeProps {
code: string
language?: string
startLineNumber?: number
}
const log = new Logger('HighlightedCode')
/**
* Highlights the given code using highlight.js. If the language wasn't recognized then it won't be highlighted.
*
* @param code The code to highlight
* @param language The language of the code to use for highlighting
* @return {@link AsyncState async state} that contains the converted React elements
*/
export const useAsyncHighlightedCodeDom = (code: string, language?: string): AsyncState<ReactElement[]> => {
return useAsync(async () => {
try {
const hljs = (await import(/* webpackChunkName: "highlight.js" */ '../../../../common/hljs/hljs')).default
if (!!language && hljs.listLanguages().includes(language)) {
const highlightedHtml = hljs.highlight(code, { language }).value
return createHtmlLinesToReactDOM(omitNewLineAtEnd(highlightedHtml).split('\n'))
} else {
return createPlaintextToReactDOM(code)
}
} catch (error) {
log.error('Error while loading highlight.js', error)
throw error
}
}, [code, language])
}
/**
* Returns the given code but without the last new line if the string ends with a new line.
*