@@ -20,45 +20,83 @@ import {
2020 * - Keyword operators replaced with Unicode symbols
2121 * - Bracket content rendered as subscripts using <sub> tags
2222 * - Assignment arrows rendered as ←
23+ *
24+ * Two modes:
25+ * - CSS classes (default): uses .ra-prev-* classes from App.css, supports dark: variants
26+ * - Inline styles (forExport=true): hardcoded light colors for PNG export on white background
2327 */
2428
25- const previewWrap = ( type : string , text : string ) =>
26- `<span class="ra-prev-${ type } ">${ text } </span>` ;
29+ // Inline styles for export only (always light, white background)
30+ const EXPORT_STYLES = {
31+ op : "color: #7c3aed; font-weight: bold;" ,
32+ logic : "color: #1d4ed8; font-weight: bold;" ,
33+ str : "color: #047857;" ,
34+ num : "color: #b45309;" ,
35+ comment : "color: #6b7280; font-style: italic;" ,
36+ assign : "color: #7c3aed; font-weight: bold;" ,
37+ sub : "font-size: 0.9em; color: #1f2937;" ,
38+ paren : "color: #9ca3af;" ,
39+ } ;
40+
41+ type TagFn = ( token : string , content : string ) => string ;
42+ type SubFn = ( content : string ) => string ;
43+
44+ function cssTag ( token : string , content : string ) : string {
45+ return `<span class="ra-prev-${ token } ">${ content } </span>` ;
46+ }
47+
48+ function cssSub ( content : string ) : string {
49+ return `<sub class="ra-prev-sub">${ content } </sub>` ;
50+ }
51+
52+ function makeExportTag ( ) : TagFn {
53+ return ( token : string , content : string ) => {
54+ const style = EXPORT_STYLES [ token as keyof typeof EXPORT_STYLES ] || "" ;
55+ return style ? `<span style="${ style } ">${ content } </span>` : content ;
56+ } ;
57+ }
58+
59+ function makeExportSub ( ) : SubFn {
60+ return ( content : string ) => `<sub style="${ EXPORT_STYLES . sub } ">${ content } </sub>` ;
61+ }
2762
28- function renderSubscriptHtml ( content : string ) : string {
29- return `<sub class="ra-prev-sub">${ highlightSubContent ( content , previewWrap ) } </sub>` ;
63+ function makeSubWrap ( tag : TagFn ) {
64+ return ( type : string , text : string ) => tag ( type , text ) ;
65+ }
66+
67+ function renderSubscriptHtml ( content : string , tag : TagFn , sub : SubFn ) : string {
68+ return sub ( highlightSubContent ( content , makeSubWrap ( tag ) ) ) ;
3069}
3170
3271/** Consume a subscript and return [newIndex, html] */
33- function consumeSubscript ( code : string , i : number ) : [ number , string ] {
72+ function consumeSubscript ( code : string , i : number , tag : TagFn , sub : SubFn ) : [ number , string ] {
3473 const beforeWs = i ;
3574 const wsI = skipWs ( code , i ) ;
3675
37- // Try bracket content
3876 const [ bracketI , bracketContent ] = extractBracketContent ( code , i ) ;
3977 if ( bracketContent !== null ) {
40- return [ bracketI , renderSubscriptHtml ( bracketContent ) ] ;
78+ return [ bracketI , renderSubscriptHtml ( bracketContent , tag , sub ) ] ;
4179 }
4280
43- // Try implicit subscript
4481 const [ implI , implContent ] = extractImplicitSubscript ( code , wsI , beforeWs ) ;
4582 if ( implContent !== null ) {
46- return [ implI , renderSubscriptHtml ( implContent ) ] ;
83+ return [ implI , renderSubscriptHtml ( implContent , tag , sub ) ] ;
4784 }
4885
4986 return [ beforeWs , "" ] ;
5087}
5188
52- export function renderRAPreview ( code : string ) : string {
89+ export function renderRAPreview ( code : string , forExport = false ) : string {
90+ const tag : TagFn = forExport ? makeExportTag ( ) : cssTag ;
91+ const sub : SubFn = forExport ? makeExportSub ( ) : cssSub ;
5392 const result : string [ ] = [ ] ;
5493 let i = 0 ;
5594
5695 while ( i < code . length ) {
57- // Comments
5896 if ( code [ i ] === "-" && i + 1 < code . length && code [ i + 1 ] === "-" ) {
5997 let comment = "" ;
6098 while ( i < code . length && code [ i ] !== "\n" ) { comment += code [ i ] ; i ++ ; }
61- result . push ( `<span class="ra-prev- comment"> ${ esc ( comment ) } </span>` ) ;
99+ result . push ( tag ( " comment", esc ( comment ) ) ) ;
62100 continue ;
63101 }
64102
@@ -68,109 +106,105 @@ export function renderRAPreview(code: string): string {
68106 continue ;
69107 }
70108
71- // Unicode unary operators
72109 if ( UNARY_SYMBOLS . has ( code [ i ] ) ) {
73- result . push ( `<span class="ra-prev- op"> ${ esc ( code [ i ] ) } </span>` ) ;
110+ result . push ( tag ( " op", esc ( code [ i ] ) ) ) ;
74111 i ++ ;
75- const [ newI , html ] = consumeSubscript ( code , i ) ;
112+ const [ newI , html ] = consumeSubscript ( code , i , tag , sub ) ;
76113 i = newI ;
77114 result . push ( html ) ;
78115 continue ;
79116 }
80117
81- // |X| or |><| natural join
82118 if ( code [ i ] === "|" ) {
83119 if ( i + 2 < code . length && ( code [ i + 1 ] === "X" || code [ i + 1 ] === "x" ) && code [ i + 2 ] === "|" ) {
84- result . push ( "<span class=\"ra-prev-op\">⋈</span>" ) ;
120+ result . push ( tag ( "op" , "⋈" ) ) ;
85121 i += 3 ;
86122 continue ;
87123 }
88124 if ( i + 3 < code . length && code [ i + 1 ] === ">" && code [ i + 2 ] === "<" && code [ i + 3 ] === "|" ) {
89- result . push ( "<span class=\"ra-prev-op\">⋈</span>" ) ;
125+ result . push ( tag ( "op" , "⋈" ) ) ;
90126 i += 4 ;
91127 continue ;
92128 }
93129 }
94130
95- // Binary symbols
96131 if ( BINARY_SYMBOLS . has ( code [ i ] ) ) {
97- result . push ( `<span class="ra-prev- op"> ${ esc ( code [ i ] ) } </span>` ) ;
132+ result . push ( tag ( " op", esc ( code [ i ] ) ) ) ;
98133 i ++ ;
99134 if ( i < code . length && ( code [ i ] === "[" || code [ i ] === "{" || code [ i ] === "_" ) ) {
100- const [ newI , html ] = consumeSubscript ( code , i ) ;
135+ const [ newI , html ] = consumeSubscript ( code , i , tag , sub ) ;
101136 i = newI ;
102137 result . push ( html ) ;
103138 }
104139 continue ;
105140 }
106141
107- // <- assignment
108142 if ( code [ i ] === "<" && i + 1 < code . length && code [ i + 1 ] === "-" ) {
109- result . push ( "<span class=\"ra-prev- assign\">←</span>" ) ;
143+ result . push ( tag ( " assign" , "←" ) ) ;
110144 i += 2 ;
111145 continue ;
112146 }
113147
114- // -> rename
115148 if ( code [ i ] === "-" && i + 1 < code . length && code [ i + 1 ] === ">" ) {
116- result . push ( "<span class=\"ra-prev-op\">→</span>" ) ;
149+ result . push ( tag ( "op" , "→" ) ) ;
117150 i += 2 ;
118151 continue ;
119152 }
120153
121- // String literals
122154 if ( code [ i ] === "'" ) {
123155 let str = "'" ;
124156 i ++ ;
125157 while ( i < code . length && code [ i ] !== "'" ) { str += code [ i ] ; i ++ ; }
126158 if ( i < code . length ) { str += "'" ; i ++ ; }
127- result . push ( `<span class="ra-prev- str"> ${ esc ( str ) } </span>` ) ;
159+ result . push ( tag ( " str", esc ( str ) ) ) ;
128160 continue ;
129161 }
130162
131- // Numbers
132163 if ( / \d / . test ( code [ i ] ) ) {
133164 let num = "" ;
134165 while ( i < code . length && / [ \d . ] / . test ( code [ i ] ) ) { num += code [ i ] ; i ++ ; }
135- result . push ( `<span class="ra-prev- num"> ${ esc ( num ) } </span>` ) ;
166+ result . push ( tag ( " num", esc ( num ) ) ) ;
136167 continue ;
137168 }
138169
139- // Identifiers and keywords
140170 if ( IDENT_START . test ( code [ i ] ) ) {
141171 let ident = "" ;
142172 while ( i < code . length && IDENT_CHAR . test ( code [ i ] ) ) { ident += code [ i ] ; i ++ ; }
143173 const lower = ident . toLowerCase ( ) ;
144174 if ( UNARY_KEYWORD_SYMBOLS [ lower ] ) {
145- result . push ( `<span class="ra-prev- op"> ${ UNARY_KEYWORD_SYMBOLS [ lower ] } </span>` ) ;
146- const [ newI , html ] = consumeSubscript ( code , i ) ;
175+ result . push ( tag ( " op", UNARY_KEYWORD_SYMBOLS [ lower ] ) ) ;
176+ const [ newI , html ] = consumeSubscript ( code , i , tag , sub ) ;
147177 i = newI ;
148178 result . push ( html ) ;
149179 } else if ( BINARY_KEYWORD_SYMBOLS [ lower ] ) {
150- result . push ( `<span class="ra-prev- op"> ${ BINARY_KEYWORD_SYMBOLS [ lower ] } </span>` ) ;
180+ result . push ( tag ( " op", BINARY_KEYWORD_SYMBOLS [ lower ] ) ) ;
151181 const j = skipWs ( code , i ) ;
152182 if ( j < code . length && ( code [ j ] === "[" || code [ j ] === "{" || code [ j ] === "_" ) ) {
153- const [ newI , html ] = consumeSubscript ( code , j ) ;
183+ const [ newI , html ] = consumeSubscript ( code , j , tag , sub ) ;
154184 i = newI ;
155185 result . push ( html ) ;
156186 }
157187 } else if ( LOGIC_KEYWORDS . has ( lower ) ) {
158- result . push ( `<span class="ra-prev- logic"> ${ esc ( ident ) } </span>` ) ;
188+ result . push ( tag ( " logic", esc ( ident ) ) ) ;
159189 } else {
160190 result . push ( esc ( ident ) ) ;
161191 }
162192 continue ;
163193 }
164194
165- // Comparison operators
166195 if ( "<>!=" . includes ( code [ i ] ) ) {
167196 let op = code [ i ] ; i ++ ;
168197 if ( i < code . length && ( code [ i ] === "=" || code [ i ] === ">" ) ) { op += code [ i ] ; i ++ ; }
169- result . push ( `<span class="ra-prev-logic">${ esc ( op ) } </span>` ) ;
198+ result . push ( tag ( "logic" , esc ( op ) ) ) ;
199+ continue ;
200+ }
201+
202+ if ( code [ i ] === "(" || code [ i ] === ")" ) {
203+ result . push ( tag ( "paren" , esc ( code [ i ] ) ) ) ;
204+ i ++ ;
170205 continue ;
171206 }
172207
173- // Everything else
174208 result . push ( esc ( code [ i ] ) ) ;
175209 i ++ ;
176210 }
@@ -196,7 +230,7 @@ export default function RAPreview({ code }: RAPreviewProps) {
196230 return (
197231 < div className = "w-full max-w-4xl mt-2" >
198232 < div
199- className = "font-serif text-base px-3 py-2 rounded-md bg-gray-50 dark:bg-slate-800/50 border border-gray-200 dark:border-slate-700 overflow-x-auto"
233+ className = "font-mono text-base text-left px-3 py-2 rounded-md bg-gray-50 dark:bg-slate-800 border border-gray-200 dark:border-slate-700 overflow-x-auto"
200234 dangerouslySetInnerHTML = { { __html : html } }
201235 />
202236 </ div >
0 commit comments