1- /** @file A WYSIWYG editor using Lexical.js . */
1+ /** @file A Markdown viewer component . */
22
3+ import { useLogger } from '#/providers/LoggerProvider'
4+ import { useText } from '#/providers/TextProvider'
35import { useSuspenseQuery } from '@tanstack/react-query'
46import type { RendererObject } from 'marked'
57import { marked } from 'marked'
6- import { useMemo } from 'react '
7- import { BUTTON_STYLES , TEXT_STYLE , type TestIdProps } from '../AriaComponents '
8+ import { type TestIdProps } from '../AriaComponents '
9+ import { DEFAULT_RENDERER } from './defaultRenderer '
810
911/** Props for a {@link MarkdownViewer}. */
1012export interface MarkdownViewerProps extends TestIdProps {
@@ -14,67 +16,43 @@ export interface MarkdownViewerProps extends TestIdProps {
1416 readonly renderer ?: RendererObject
1517}
1618
17- const defaultRenderer : RendererObject = {
18- /** The renderer for headings. */
19- heading ( { depth, tokens } ) {
20- return `<h${ depth } class="${ TEXT_STYLE ( { variant : 'h1' , className : 'my-2' } ) } ">${ this . parser . parseInline ( tokens ) } </h${ depth } >`
21- } ,
22- /** The renderer for paragraphs. */
23- paragraph ( { tokens } ) {
24- return `<p class="${ TEXT_STYLE ( { variant : 'body' , className : 'my-1' } ) } ">${ this . parser . parseInline ( tokens ) } </p>`
25- } ,
26- /** The renderer for list items. */
27- listitem ( { tokens } ) {
28- return `<li class="${ TEXT_STYLE ( { variant : 'body' } ) } ">${ this . parser . parseInline ( tokens ) } </li>`
29- } ,
30- /** The renderer for lists. */
31- list ( { items } ) {
32- return `<ul class="my-1 list-disc pl-3">${ items . map ( ( item ) => this . listitem ( item ) ) . join ( '\n' ) } </ul>`
33- } ,
34- /** The renderer for links. */
35- link ( { href, tokens } ) {
36- return `<a href="${ href } " target="_blank" rel="noopener noreferrer" class="${ BUTTON_STYLES ( { variant : 'link' } ) . base ( ) } ">${ this . parser . parseInline ( tokens ) } </a>`
37- } ,
38- /** The renderer for images. */
39- image ( { href, title } ) {
40- return `<img src="${ href } " alt="${ title } " class="my-1 h-auto max-w-full" />`
41- } ,
42- /** The renderer for code. */
43- code ( { text } ) {
44- return `<code class="block my-1 p-2 bg-primary/5 rounded-lg max-w-full overflow-auto max-h-48" >
45- <pre class="${ TEXT_STYLE ( { variant : 'body-sm' } ) } ">${ text } </pre>
46- </code>`
47- } ,
48- /** The renderer for blockquotes. */
49- blockquote ( { tokens } ) {
50- return `<blockquote class="${ 'relative my-1 pl-2 before:bg-primary/20 before:absolute before:left-0 before:top-0 before:h-full before:w-[1.5px] before:rounded-full' } ">${ this . parser . parse ( tokens ) } </blockquote>`
51- } ,
52- }
53-
5419/**
5520 * Markdown viewer component.
5621 * Parses markdown passed in as a `text` prop into HTML and displays it.
5722 */
5823export function MarkdownViewer ( props : MarkdownViewerProps ) {
59- const { text, imgUrlResolver, renderer = defaultRenderer , testId } = props
24+ const { text, imgUrlResolver, renderer = { } , testId } = props
6025
61- const markedInstance = useMemo (
62- ( ) => marked . use ( { renderer : Object . assign ( { } , defaultRenderer , renderer ) , async : true } ) ,
63- [ renderer ] ,
64- )
26+ const { getText } = useText ( )
27+ const logger = useLogger ( )
28+
29+ const markedInstance = marked . use ( { renderer : Object . assign ( { } , DEFAULT_RENDERER , renderer ) } )
6530
6631 const { data : markdownToHtml } = useSuspenseQuery ( {
67- queryKey : [ 'markdownToHtml' , { text } ] ,
68- queryFn : ( ) =>
69- markedInstance . parse ( text , {
32+ queryKey : [ 'markdownToHtml' , { text, imgUrlResolver , markedInstance } ] as const ,
33+ queryFn : ( { queryKey : [ , args ] } ) =>
34+ args . markedInstance . parse ( args . text , {
7035 async : true ,
7136 walkTokens : async ( token ) => {
7237 if ( token . type === 'image' && 'href' in token && typeof token . href === 'string' ) {
73- token . href = await imgUrlResolver ( token . href )
38+ const href = token . href
39+
40+ token . raw = href
41+ token . href = await args
42+ . imgUrlResolver ( href )
43+ . then ( ( url ) => {
44+ return url
45+ } )
46+ . catch ( ( error ) => {
47+ logger . error ( error )
48+ return null
49+ } )
50+ token . text = getText ( 'arbitraryFetchImageError' )
7451 }
7552 } ,
7653 } ) ,
7754 } )
55+
7856 return (
7957 < div
8058 className = "select-text"
0 commit comments