@@ -25,7 +25,7 @@ import {
2525 YouTubeEmbedBlock ,
2626 getVariantStyles ,
2727} from "@/components/block-types"
28- import { parseGraphFromTable , type GraphType } from "@/lib/graphFromTable"
28+ import { parseGraphFromTable , type GraphType , type ParsedGraph } from "@/lib/graphFromTable"
2929import { AsyncVideo } from "@/components/block-types/Video"
3030import type { QuizQuestion } from "@/types/block"
3131import type { BlockVariant , VariantStyle } from "@/types/block-types"
@@ -41,6 +41,58 @@ function parseGraphTag(line: string): GraphType | null {
4141 return match [ 1 ] . toLowerCase ( ) as GraphType
4242}
4343
44+ function parseMermaidList ( raw : string ) : string [ ] {
45+ const inner = raw . trim ( ) . replace ( / ^ \[ / , "" ) . replace ( / \] $ / , "" )
46+ if ( ! inner ) return [ ]
47+ const tokens : string [ ] = [ ]
48+ const re = / " ( [ ^ " ] + ) " | ' ( [ ^ ' ] + ) ' | ( [ ^ , \] ] + ) / g
49+ let match : RegExpExecArray | null
50+ while ( ( match = re . exec ( inner ) ) !== null ) {
51+ const value = ( match [ 1 ] ?? match [ 2 ] ?? match [ 3 ] ?? "" ) . trim ( )
52+ if ( value ) tokens . push ( value )
53+ }
54+ return tokens
55+ }
56+
57+ function parseMermaidNumbers ( raw : string ) : number [ ] {
58+ const inner = raw . trim ( ) . replace ( / ^ \[ / , "" ) . replace ( / \] $ / , "" )
59+ if ( ! inner ) return [ ]
60+ return inner
61+ . split ( "," )
62+ . map ( ( part ) => Number ( part . trim ( ) ) )
63+ . filter ( ( value ) => Number . isFinite ( value ) )
64+ }
65+
66+ function parseMermaidXyChart ( code : string ) : ParsedGraph | null {
67+ if ( ! / \b x y c h a r t - b e t a \b / i. test ( code ) ) return null
68+
69+ const xAxisMatch = code . match ( / ^ \s * x - a x i s \s + ( .+ ) $ / im)
70+ const yAxisMatch = code . match ( / ^ \s * y - a x i s \s + " ( [ ^ " ] + ) " / im)
71+ const barMatch = code . match ( / ^ \s * b a r \s + \[ ( [ ^ \] ] + ) \] / im)
72+ const lineMatch = code . match ( / ^ \s * l i n e \s + \[ ( [ ^ \] ] + ) \] / im)
73+
74+ const values = barMatch
75+ ? parseMermaidNumbers ( barMatch [ 1 ] )
76+ : lineMatch
77+ ? parseMermaidNumbers ( lineMatch [ 1 ] )
78+ : [ ]
79+ if ( values . length === 0 ) return null
80+
81+ const parsedLabels = xAxisMatch ? parseMermaidList ( xAxisMatch [ 1 ] ) : [ ]
82+ const labels = Array . from ( { length : values . length } , ( _ , idx ) => parsedLabels [ idx ] || `Item ${ idx + 1 } ` )
83+ const count = Math . min ( labels . length , values . length )
84+ if ( count === 0 ) return null
85+
86+ return {
87+ type : barMatch ? "bar" : "line" ,
88+ labels : labels . slice ( 0 , count ) ,
89+ series : [ {
90+ name : yAxisMatch ?. [ 1 ] ?. trim ( ) || "Value" ,
91+ values : values . slice ( 0 , count ) ,
92+ } ] ,
93+ }
94+ }
95+
4496function findNextIndex ( haystack : string , from : number , needle : string ) : number {
4597 const idx = haystack . indexOf ( needle , from )
4698 return idx >= 0 ? idx : Number . POSITIVE_INFINITY
@@ -1045,10 +1097,54 @@ export function Markdownish({
10451097 }
10461098 if ( endIndex >= 0 ) {
10471099 i = endIndex
1100+ const code = collected . join ( "\n" )
1101+ const lowerLang = lang . toLowerCase ( )
1102+ if ( lowerLang === "mermaid" ) {
1103+ const mermaidGraph = parseMermaidXyChart ( code )
1104+ if ( mermaidGraph ) {
1105+ const graphSnippet = `\`\`\`mermaid\n${ code } \n\`\`\``
1106+ const graphActions = effectiveSourceBlockId
1107+ ? resolveBlockActions ( "graph" , { sourceBlockId : effectiveSourceBlockId } , {
1108+ chart : mermaidGraph ,
1109+ markdownSnippet : graphSnippet ,
1110+ } )
1111+ : [ ]
1112+ const graphAnalyze = graphActions . find ( ( action ) => action . request . actionType === "analyze" )
1113+ const graphConvertActions = graphActions . filter ( ( action ) => action . request . actionType === "convert" )
1114+ elements . push (
1115+ < GraphBlock
1116+ key = { `code-graph-${ i } ` }
1117+ chart = { mermaidGraph }
1118+ variant = { variant }
1119+ COLORS = { COLORS }
1120+ onAnalyze = {
1121+ graphAnalyze && onBlockAction
1122+ ? ( ) => onBlockAction ( { ...graphAnalyze . request , sourceBlockId : effectiveSourceBlockId } )
1123+ : undefined
1124+ }
1125+ onConvertType = {
1126+ graphConvertActions . length > 0 && onBlockAction
1127+ ? ( targetType ) =>
1128+ ( ( ) => {
1129+ const matched =
1130+ graphConvertActions . find ( ( action ) => action . request . targetGraphType === targetType ) ||
1131+ graphConvertActions [ 0 ]
1132+ onBlockAction ( {
1133+ ...matched . request ,
1134+ sourceBlockId : effectiveSourceBlockId ,
1135+ } )
1136+ } ) ( )
1137+ : undefined
1138+ }
1139+ /> ,
1140+ )
1141+ continue
1142+ }
1143+ }
10481144 elements . push (
10491145 < CodeBlock
10501146 key = { `code-${ i } ` }
1051- code = { collected . join ( "\n" ) }
1147+ code = { code }
10521148 language = { lang }
10531149 variant = { variant }
10541150 COLORS = { COLORS }
0 commit comments