From 9b69914b6085383c9372959b001fee73d9e888a4 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Mon, 2 May 2022 16:53:20 +0200 Subject: [PATCH] Extract code highlighting from async call Signed-off-by: Tilman Vatteroth --- .../highlighted-fence/highlighted-code.tsx | 8 ++- .../hooks/use-async-highlight-js-import.tsx | 28 +++++++++ ...hlighted-code-dom.tsx => use-code-dom.tsx} | 60 ++++++++----------- 3 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx rename src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/{use-async-highlighted-code-dom.tsx => use-code-dom.tsx} (61%) diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx b/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx index 6c0d4f702..30c7bd1fd 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx +++ b/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx @@ -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 = ({ 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 ( diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx b/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx new file mode 100644 index 000000000..511cead54 --- /dev/null +++ b/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx @@ -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 => { + 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 + } + }, []) +} diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlighted-code-dom.tsx b/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-code-dom.tsx similarity index 61% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlighted-code-dom.tsx rename to src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-code-dom.tsx index 9fa345d52..9146860f8 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlighted-code-dom.tsx +++ b/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-code-dom.tsx @@ -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 => { - 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. *