@@ -22,43 +22,71 @@ import {
2222 * - Assignment arrows rendered as ←
2323 */
2424
25- const previewWrap = ( type : string , text : string ) =>
26- `<span class="ra-prev-${ type } ">${ text } </span>` ;
25+ // Light theme (for exports and light mode)
26+ const LIGHT = {
27+ op : "color: #5b21b6; font-weight: bold;" ,
28+ logic : "color: #1d4ed8; font-weight: bold;" ,
29+ str : "color: #047857;" ,
30+ num : "color: #b45309;" ,
31+ comment : "color: #6b7280; font-style: italic;" ,
32+ assign : "color: #5b21b6; font-weight: bold;" ,
33+ sub : "font-size: 0.85em; color: #3b0764;" ,
34+ paren : "color: #9ca3af;" ,
35+ ident : "" ,
36+ } ;
37+
38+ // Dark theme
39+ const DARK = {
40+ op : "color: #a78bfa; font-weight: bold;" ,
41+ logic : "color: #60a5fa; font-weight: bold;" ,
42+ str : "color: #34d399;" ,
43+ num : "color: #fbbf24;" ,
44+ comment : "color: #9ca3af; font-style: italic;" ,
45+ assign : "color: #a78bfa; font-weight: bold;" ,
46+ sub : "font-size: 0.85em; color: #c4b5fd;" ,
47+ paren : "color: #6b7280;" ,
48+ ident : "" ,
49+ } ;
50+
51+ type Styles = typeof LIGHT ;
52+
53+ function makeWrap ( styles : Styles ) {
54+ return ( type : string , text : string ) =>
55+ `<span style="${ styles [ type as keyof Styles ] || "" } ">${ text } </span>` ;
56+ }
2757
28- function renderSubscriptHtml ( content : string ) : string {
29- return `<sub class="ra-prev- sub">${ highlightSubContent ( content , previewWrap ) } </sub>` ;
58+ function renderSubscriptHtml ( content : string , styles : Styles ) : string {
59+ return `<sub style=" ${ styles . sub } ">${ highlightSubContent ( content , makeWrap ( styles ) ) } </sub>` ;
3060}
3161
3262/** Consume a subscript and return [newIndex, html] */
33- function consumeSubscript ( code : string , i : number ) : [ number , string ] {
63+ function consumeSubscript ( code : string , i : number , s : Styles ) : [ number , string ] {
3464 const beforeWs = i ;
3565 const wsI = skipWs ( code , i ) ;
3666
37- // Try bracket content
3867 const [ bracketI , bracketContent ] = extractBracketContent ( code , i ) ;
3968 if ( bracketContent !== null ) {
40- return [ bracketI , renderSubscriptHtml ( bracketContent ) ] ;
69+ return [ bracketI , renderSubscriptHtml ( bracketContent , s ) ] ;
4170 }
4271
43- // Try implicit subscript
4472 const [ implI , implContent ] = extractImplicitSubscript ( code , wsI , beforeWs ) ;
4573 if ( implContent !== null ) {
46- return [ implI , renderSubscriptHtml ( implContent ) ] ;
74+ return [ implI , renderSubscriptHtml ( implContent , s ) ] ;
4775 }
4876
4977 return [ beforeWs , "" ] ;
5078}
5179
52- export function renderRAPreview ( code : string ) : string {
80+ export function renderRAPreview ( code : string , dark = false ) : string {
81+ const s = dark ? DARK : LIGHT ;
5382 const result : string [ ] = [ ] ;
5483 let i = 0 ;
5584
5685 while ( i < code . length ) {
57- // Comments
5886 if ( code [ i ] === "-" && i + 1 < code . length && code [ i + 1 ] === "-" ) {
5987 let comment = "" ;
6088 while ( i < code . length && code [ i ] !== "\n" ) { comment += code [ i ] ; i ++ ; }
61- result . push ( `<span class="ra-prev- comment">${ esc ( comment ) } </span>` ) ;
89+ result . push ( `<span style=" ${ s . comment } ">${ esc ( comment ) } </span>` ) ;
6290 continue ;
6391 }
6492
@@ -68,94 +96,86 @@ export function renderRAPreview(code: string): string {
6896 continue ;
6997 }
7098
71- // Unicode unary operators
7299 if ( UNARY_SYMBOLS . has ( code [ i ] ) ) {
73- result . push ( `<span class="ra-prev-op ">${ esc ( code [ i ] ) } </span>` ) ;
100+ result . push ( `<span style=" ${ s . op } ">${ esc ( code [ i ] ) } </span>` ) ;
74101 i ++ ;
75- const [ newI , html ] = consumeSubscript ( code , i ) ;
102+ const [ newI , html ] = consumeSubscript ( code , i , s ) ;
76103 i = newI ;
77104 result . push ( html ) ;
78105 continue ;
79106 }
80107
81- // |X| or |><| natural join
82108 if ( code [ i ] === "|" ) {
83109 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>" ) ;
110+ result . push ( ` <span style=" ${ s . op } ">⋈</span>` ) ;
85111 i += 3 ;
86112 continue ;
87113 }
88114 if ( i + 3 < code . length && code [ i + 1 ] === ">" && code [ i + 2 ] === "<" && code [ i + 3 ] === "|" ) {
89- result . push ( " <span class=\"ra-prev-op\ ">⋈</span>" ) ;
115+ result . push ( ` <span style=" ${ s . op } ">⋈</span>` ) ;
90116 i += 4 ;
91117 continue ;
92118 }
93119 }
94120
95- // Binary symbols
96121 if ( BINARY_SYMBOLS . has ( code [ i ] ) ) {
97- result . push ( `<span class="ra-prev-op ">${ esc ( code [ i ] ) } </span>` ) ;
122+ result . push ( `<span style=" ${ s . op } ">${ esc ( code [ i ] ) } </span>` ) ;
98123 i ++ ;
99124 if ( i < code . length && ( code [ i ] === "[" || code [ i ] === "{" || code [ i ] === "_" ) ) {
100- const [ newI , html ] = consumeSubscript ( code , i ) ;
125+ const [ newI , html ] = consumeSubscript ( code , i , s ) ;
101126 i = newI ;
102127 result . push ( html ) ;
103128 }
104129 continue ;
105130 }
106131
107- // <- assignment
108132 if ( code [ i ] === "<" && i + 1 < code . length && code [ i + 1 ] === "-" ) {
109- result . push ( " <span class=\"ra-prev- assign\ ">←</span>" ) ;
133+ result . push ( ` <span style=" ${ s . assign } ">←</span>` ) ;
110134 i += 2 ;
111135 continue ;
112136 }
113137
114- // -> rename
115138 if ( code [ i ] === "-" && i + 1 < code . length && code [ i + 1 ] === ">" ) {
116- result . push ( " <span class=\"ra-prev-op\ ">→</span>" ) ;
139+ result . push ( ` <span style=" ${ s . op } ">→</span>` ) ;
117140 i += 2 ;
118141 continue ;
119142 }
120143
121- // String literals
122144 if ( code [ i ] === "'" ) {
123145 let str = "'" ;
124146 i ++ ;
125147 while ( i < code . length && code [ i ] !== "'" ) { str += code [ i ] ; i ++ ; }
126148 if ( i < code . length ) { str += "'" ; i ++ ; }
127- result . push ( `<span class="ra-prev- str">${ esc ( str ) } </span>` ) ;
149+ result . push ( `<span style=" ${ s . str } ">${ esc ( str ) } </span>` ) ;
128150 continue ;
129151 }
130152
131- // Numbers
132153 if ( / \d / . test ( code [ i ] ) ) {
133154 let num = "" ;
134155 while ( i < code . length && / [ \d . ] / . test ( code [ i ] ) ) { num += code [ i ] ; i ++ ; }
135- result . push ( `<span class="ra-prev- num">${ esc ( num ) } </span>` ) ;
156+ result . push ( `<span style=" ${ s . num } ">${ esc ( num ) } </span>` ) ;
136157 continue ;
137158 }
138159
139- // Identifiers and keywords
140160 if ( IDENT_START . test ( code [ i ] ) ) {
141161 let ident = "" ;
142162 while ( i < code . length && IDENT_CHAR . test ( code [ i ] ) ) { ident += code [ i ] ; i ++ ; }
143163 const lower = ident . toLowerCase ( ) ;
144164 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 ) ;
165+ result . push ( `<span style=" ${ s . op } ">${ UNARY_KEYWORD_SYMBOLS [ lower ] } </span>` ) ;
166+ const [ newI , html ] = consumeSubscript ( code , i , s ) ;
147167 i = newI ;
148168 result . push ( html ) ;
149169 } else if ( BINARY_KEYWORD_SYMBOLS [ lower ] ) {
150- result . push ( `<span class="ra-prev-op ">${ BINARY_KEYWORD_SYMBOLS [ lower ] } </span>` ) ;
170+ result . push ( `<span style=" ${ s . op } ">${ BINARY_KEYWORD_SYMBOLS [ lower ] } </span>` ) ;
151171 const j = skipWs ( code , i ) ;
152172 if ( j < code . length && ( code [ j ] === "[" || code [ j ] === "{" || code [ j ] === "_" ) ) {
153- const [ newI , html ] = consumeSubscript ( code , j ) ;
173+ const [ newI , html ] = consumeSubscript ( code , j , s ) ;
154174 i = newI ;
155175 result . push ( html ) ;
156176 }
157177 } else if ( LOGIC_KEYWORDS . has ( lower ) ) {
158- result . push ( `<span class="ra-prev- logic">${ esc ( ident ) } </span>` ) ;
178+ result . push ( `<span style=" ${ s . logic } ">${ esc ( ident ) } </span>` ) ;
159179 } else {
160180 result . push ( esc ( ident ) ) ;
161181 }
@@ -166,7 +186,14 @@ export function renderRAPreview(code: string): string {
166186 if ( "<>!=" . includes ( code [ i ] ) ) {
167187 let op = code [ i ] ; i ++ ;
168188 if ( i < code . length && ( code [ i ] === "=" || code [ i ] === ">" ) ) { op += code [ i ] ; i ++ ; }
169- result . push ( `<span class="ra-prev-logic">${ esc ( op ) } </span>` ) ;
189+ result . push ( `<span style="${ s . logic } ">${ esc ( op ) } </span>` ) ;
190+ continue ;
191+ }
192+
193+ // Parentheses — render gray
194+ if ( code [ i ] === "(" || code [ i ] === ")" ) {
195+ result . push ( `<span style="${ s . paren } ">${ esc ( code [ i ] ) } </span>` ) ;
196+ i ++ ;
170197 continue ;
171198 }
172199
@@ -180,23 +207,24 @@ export function renderRAPreview(code: string): string {
180207
181208interface RAPreviewProps {
182209 code : string ;
210+ dark ?: boolean ;
183211}
184212
185- export default function RAPreview ( { code } : RAPreviewProps ) {
213+ export default function RAPreview ( { code, dark = false } : RAPreviewProps ) {
186214 const html = useMemo ( ( ) => {
187215 const trimmed = code . trim ( ) ;
188216 if ( ! trimmed || trimmed . startsWith ( "--" ) && ! trimmed . includes ( "\n" ) ) {
189217 return null ;
190218 }
191- return renderRAPreview ( trimmed ) ;
192- } , [ code ] ) ;
219+ return renderRAPreview ( trimmed , dark ) ;
220+ } , [ code , dark ] ) ;
193221
194222 if ( ! html ) return null ;
195223
196224 return (
197225 < div className = "w-full max-w-4xl mt-2" >
198226 < 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"
227+ className = "font-mono text-base text-left 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"
200228 dangerouslySetInnerHTML = { { __html : html } }
201229 />
202230 </ div >
0 commit comments