diff --git a/src/modules/blocks/AutoBlocksRenderer.tsx b/src/modules/blocks/AutoBlocksRenderer.tsx index affac99b7..3dfd1dc2d 100644 --- a/src/modules/blocks/AutoBlocksRenderer.tsx +++ b/src/modules/blocks/AutoBlocksRenderer.tsx @@ -15,7 +15,7 @@ import { RenderPlainChatText } from '~/modules/blocks/plaintext/RenderPlainChatT import { RenderCode, RenderCodeMemo } from './code/RenderCode'; import { RenderMarkdown, RenderMarkdownMemo } from './markdown/RenderMarkdown'; import { RenderTextDiff } from './textdiff/RenderTextDiff'; -import { heuristicIsBlockTextHTML, RenderHtmlResponse } from './html/RenderHtmlResponse'; +import { heuristicIsBlockPureHTML, RenderHtmlResponse } from './html/RenderHtmlResponse'; import { heuristicLegacyImageBlocks, heuristicMarkdownImageReferenceBlocks, RenderImageURL } from './image/RenderImageURL'; @@ -50,7 +50,7 @@ function parseBlocksFromText(text: string, disableParsing: boolean, forceTextDif return [{ type: 'diffb', textDiffs: forceTextDiffs }]; // special case: this could be generated by a proxy that returns an HTML page instead of the API response - if (heuristicIsBlockTextHTML(text)) + if (heuristicIsBlockPureHTML(text)) return [{ type: 'htmlb', html: text }]; // special case: markdown image references (e.g. ![alt text](https://example.com/image.png)) diff --git a/src/modules/blocks/code/RenderCode.tsx b/src/modules/blocks/code/RenderCode.tsx index 25d100595..64109f3a7 100644 --- a/src/modules/blocks/code/RenderCode.tsx +++ b/src/modules/blocks/code/RenderCode.tsx @@ -18,19 +18,23 @@ import type { CodeBlock } from '../blocks.types'; import { ButtonCodePen, isCodePenSupported } from './ButtonCodePen'; import { ButtonJsFiddle, isJSFiddleSupported } from './ButtonJSFiddle'; import { ButtonStackBlitz, isStackBlitzSupported } from './ButtonStackBlitz'; +import { OverlayButton, overlayButtonsActiveSx, overlayButtonsClassName, overlayButtonsSx } from '../OverlayButton'; import { RenderCodeHtmlIFrame } from './RenderCodeHtmlIFrame'; import { RenderCodeMermaid } from './RenderCodeMermaid'; -import { RenderCodePlantUML, usePlantUmlSvg } from './RenderCodePlantUML'; import { RenderCodeSVG } from './RenderCodeSVG'; import { RenderCodeSyntax } from './RenderCodeSyntax'; -import { heuristicIsBlockTextHTML } from '../html/RenderHtmlResponse'; +import { heuristicIsBlockPlantUML, RenderCodePlantUML, usePlantUmlSvg } from './RenderCodePlantUML'; +import { heuristicIsBlockPureHTML } from '../html/RenderHtmlResponse'; // style for line-numbers import './RenderCode.css'; -import { OverlayButton, overlayButtonsActiveSx, overlayButtonsClassName, overlayButtonsSx } from '~/modules/blocks/OverlayButton'; +// RenderCode + +export const RenderCodeMemo = React.memo(RenderCode); + interface RenderCodeBaseProps { codeBlock: CodeBlock, fitScreen?: boolean, @@ -40,12 +44,33 @@ interface RenderCodeBaseProps { sx?: SxProps, } -interface RenderCodeImplProps extends RenderCodeBaseProps { - highlightCode: (inferredCodeLanguage: string | null, blockCode: string, addLineNumbers: boolean) => string, - inferCodeLanguage: (blockTitle: string, code: string) => string | null, +export function RenderCode(props: RenderCodeBaseProps) { + return ( + }> + <_DynamicPrism {...props} /> + + ); } -function RenderCodeImpl(props: RenderCodeImplProps) { + +// Lazy loader of the heavy prism functions +const _DynamicPrism = React.lazy(async () => { + + // Dynamically import the code highlight functions + const { highlightCode, inferCodeLanguage } = await import('./codePrism'); + + return { + default: (props: RenderCodeBaseProps) => , + }; +}); + + +// + +function RenderCodeImpl(props: RenderCodeBaseProps & { + highlightCode: (inferredCodeLanguage: string | null, blockCode: string, addLineNumbers: boolean) => string, + inferCodeLanguage: (blockTitle: string, code: string) => string | null, +}) { // state const [fitScreen, setFitScreen] = React.useState(!!props.fitScreen); @@ -67,33 +92,28 @@ function RenderCodeImpl(props: RenderCodeImplProps) { optimizeLightweight, } = props; + // heuristics for specialized rendering - const isHTML = heuristicIsBlockTextHTML(blockCode); - const renderHTML = isHTML && showHTML; + const isHTMLCode = heuristicIsBlockPureHTML(blockCode); + const renderHTML = isHTMLCode && showHTML; - const isMermaid = blockTitle === 'mermaid' && blockComplete; - const renderMermaid = isMermaid && showMermaid; + const isMermaidCode = blockTitle === 'mermaid' && blockComplete; + const renderMermaid = isMermaidCode && showMermaid; - const isPlantUML = - (blockCode.startsWith('@startuml') && blockCode.endsWith('@enduml')) - || (blockCode.startsWith('@startmindmap') && blockCode.endsWith('@endmindmap')) - || (blockCode.startsWith('@startsalt') && blockCode.endsWith('@endsalt')) - || (blockCode.startsWith('@startwbs') && blockCode.endsWith('@endwbs')) - || (blockCode.startsWith('@startgantt') && blockCode.endsWith('@endgantt')); - - let renderPlantUML = isPlantUML && showPlantUML; + const isPlantUMLCode = heuristicIsBlockPlantUML(blockCode); + let renderPlantUML = isPlantUMLCode && showPlantUML; const { data: plantUmlSvgData, error: plantUmlError } = usePlantUmlSvg(renderPlantUML, blockCode); renderPlantUML = renderPlantUML && (!!plantUmlSvgData || !!plantUmlError); - const isSVG = (blockCode.startsWith('\n'); - const renderSVG = isSVG && showSVG; + const isSVGCode = (blockCode.startsWith('\n'); + const renderSVG = isSVGCode && showSVG; const canScaleSVG = renderSVG && blockCode.includes('viewBox="'); - const renderCode = !renderHTML && !renderMermaid && !renderPlantUML && !renderSVG; + const renderSyntaxHighlight = !renderHTML && !renderMermaid && !renderPlantUML && !renderSVG; - const cannotRenderLineNumbers = !renderCode || showSoftWrap; + const cannotRenderLineNumbers = !renderSyntaxHighlight || showSoftWrap; const renderLineNumbers = showLineNumbers && !cannotRenderLineNumbers; // heuristic for language, and syntax highlight @@ -104,13 +124,13 @@ function RenderCodeImpl(props: RenderCodeImplProps) { }, [inferCodeLanguage, blockTitle, blockCode, highlightCode, renderLineNumbers]); - const canCodePen = blockComplete && isCodePenSupported(inferredCodeLanguage, isSVG); + const canCodePen = blockComplete && isCodePenSupported(inferredCodeLanguage, isSVGCode); const canJSFiddle = blockComplete && isJSFiddleSupported(inferredCodeLanguage, blockCode); const canStackBlitz = blockComplete && isStackBlitzSupported(inferredCodeLanguage); let showBlockTitle = (blockTitle != inferredCodeLanguage) && (blockTitle.includes('.') || blockTitle.includes('://')); - // hide the block title when rendering HTML + // Beautify: hide the block title when rendering HTML if (renderHTML) showBlockTitle = false; const isBorderless = (renderHTML || renderSVG) && !showBlockTitle; @@ -167,11 +187,12 @@ function RenderCodeImpl(props: RenderCodeImplProps) { : (renderPlantUML && plantUmlSvgData) ? : } + {/* [overlay] Buttons (Code blocks (SVG, diagrams, HTML, syntax, ...)) */} {/* Show HTML */} - {isHTML && ( + {isHTMLCode && ( setShowHTML(!showHTML)}> @@ -180,7 +201,7 @@ function RenderCodeImpl(props: RenderCodeImplProps) { )} {/* Show SVG */} - {isSVG && ( + {isSVGCode && ( setShowSVG(!showSVG)}> @@ -189,20 +210,20 @@ function RenderCodeImpl(props: RenderCodeImplProps) { )} {/* Show Diagrams */} - {(isMermaid || isPlantUML) && ( + {(isMermaidCode || isPlantUMLCode) && ( {/* Toggle rendering */} { - if (isMermaid) setShowMermaid(on => !on); - if (isPlantUML) setShowPlantUML(on => !on); + if (isMermaidCode) setShowMermaid(on => !on); + if (isPlantUMLCode) setShowPlantUML(on => !on); }}> {/* Fit-To-Screen */} - {((isMermaid && showMermaid) || (isPlantUML && showPlantUML && !plantUmlError) || (isSVG && showSVG && canScaleSVG)) && ( + {((isMermaidCode && showMermaid) || (isPlantUMLCode && showPlantUML && !plantUmlError) || (isSVGCode && showSVG && canScaleSVG)) && ( setFitScreen(on => !on)}> @@ -224,18 +245,18 @@ function RenderCodeImpl(props: RenderCodeImplProps) { {/* Group: Text Options */} {/* Soft Wrap toggle */} - {renderCode && ( + {renderSyntaxHighlight && ( - setShowSoftWrap(!showSoftWrap)}> + setShowSoftWrap(!showSoftWrap)}> )} {/* Line Numbers toggle */} - {renderCode && ( + {renderSyntaxHighlight && ( - setShowLineNumbers(!showLineNumbers)}> + setShowLineNumbers(!showLineNumbers)}> @@ -255,25 +276,3 @@ function RenderCodeImpl(props: RenderCodeImplProps) { ); } - -// Dynamically import the heavy prism functions -const DynamicPrism = React.lazy(async () => { - - // Dynamically import the code highlight functions - const { highlightCode, inferCodeLanguage } = await import('./codePrism'); - - return { - default: (props: RenderCodeBaseProps) => - , - }; -}); - -export function RenderCode(props: RenderCodeBaseProps) { - return ( - }> - - - ); -} - -export const RenderCodeMemo = React.memo(RenderCode); diff --git a/src/modules/blocks/code/RenderCodePlantUML.tsx b/src/modules/blocks/code/RenderCodePlantUML.tsx index 30371f8a9..f92d60946 100644 --- a/src/modules/blocks/code/RenderCodePlantUML.tsx +++ b/src/modules/blocks/code/RenderCodePlantUML.tsx @@ -6,6 +6,15 @@ import { frontendSideFetch } from '~/common/util/clientFetchers'; import { patchSvgString } from '~/modules/blocks/code/RenderCodeSVG'; +export function heuristicIsBlockPlantUML(blockCode: string) { + return (blockCode.startsWith('@startuml') && blockCode.endsWith('@enduml')) + || (blockCode.startsWith('@startmindmap') && blockCode.endsWith('@endmindmap')) + || (blockCode.startsWith('@startsalt') && blockCode.endsWith('@endsalt')) + || (blockCode.startsWith('@startwbs') && blockCode.endsWith('@endwbs')) + || (blockCode.startsWith('@startgantt') && blockCode.endsWith('@endgantt')); +} + + // PlantUML -> SVG fetchers export function usePlantUmlSvg(enabled: boolean, blockCode: string) { diff --git a/src/modules/blocks/html/RenderHtmlResponse.tsx b/src/modules/blocks/html/RenderHtmlResponse.tsx index 394189daf..6ad7d698c 100644 --- a/src/modules/blocks/html/RenderHtmlResponse.tsx +++ b/src/modules/blocks/html/RenderHtmlResponse.tsx @@ -13,7 +13,7 @@ import { RenderCodeHtmlIFrame } from '../code/RenderCodeHtmlIFrame'; // this is used by the blocks parser (for full text detection) and by the Code component (for inline rendering) -export function heuristicIsBlockTextHTML(text: string): boolean { +export function heuristicIsBlockPureHTML(text: string): boolean { return [' text.startsWith(start)); }