Skip to content

Commit 73eec26

Browse files
committed
Fix editor highlighter, preview rendering, dark mode, and export readability
1 parent a82599e commit 73eec26

7 files changed

Lines changed: 363 additions & 248 deletions

File tree

src/App.css

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,4 @@ body {
4646
background-color: rgba(34, 197, 94, 0.15);
4747
}
4848

49-
/* RA Preview styling */
50-
.ra-prev-op { color: #7c3aed; font-weight: bold; }
51-
.ra-prev-assign { color: #7c3aed; font-weight: bold; }
52-
.ra-prev-logic { color: #2563eb; font-weight: bold; }
53-
.ra-prev-str { color: #059669; }
54-
.ra-prev-num { color: #d97706; }
55-
.ra-prev-comment { color: #9ca3af; font-style: italic; }
56-
.ra-prev-sub { font-size: 0.8em; color: #6d28d9; }
57-
.dark .ra-prev-op { color: #a78bfa; }
58-
.dark .ra-prev-assign { color: #a78bfa; }
59-
.dark .ra-prev-logic { color: #60a5fa; }
60-
.dark .ra-prev-str { color: #34d399; }
61-
.dark .ra-prev-num { color: #fbbf24; }
62-
.dark .ra-prev-sub { color: #c4b5fd; }
49+
/* RA Preview styling is handled via inline styles in RAPreview.tsx */

src/App.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ function App() {
849849
height: exportRenderer.clientHeight,
850850
pixelRatio: 1
851851
}).then((dataUrl) => {
852-
triggerDownload(dataUrl, `validator_${question.id}_${question.category.display_number}${question.display_sequence}.png`);
852+
triggerDownload(dataUrl, `validator_${editorMode === "ra" ? "ra_" : ""}${question.id}_${question.category.display_number}${question.display_sequence}.png`);
853853
setExportQuestion(undefined);
854854
setExportQuery(undefined);
855855
setExportingStatus(0);
@@ -938,7 +938,7 @@ function App() {
938938
value={editorMode === "ra" ? t("raPlaceholder") : t("selectQuestionComment")}
939939
disabled={true}
940940
onValueChange={_code => null}
941-
highlight={code => editorMode === "ra" ? highlightRA(code) : highlight(code, languages.sql)}
941+
highlight={code => editorMode === "ra" ? highlightRA(code, isDarkMode) : highlight(code, languages.sql)}
942942
padding={10}
943943
tabSize={2}
944944
className="font-mono text-base w-full dark:bg-slate-800 bg-slate-100 min-h-32 rounded-md"
@@ -950,14 +950,14 @@ function App() {
950950
itemID="editor"
951951
value={query}
952952
onValueChange={code => setQuery(code)}
953-
highlight={code => editorMode === "ra" ? highlightRA(code) : highlight(code, languages.sql)}
953+
highlight={code => editorMode === "ra" ? highlightRA(code, isDarkMode) : highlight(code, languages.sql)}
954954
padding={10}
955955
tabSize={2}
956956
className="font-mono text-base w-full dark:bg-slate-800 bg-slate-100 border dark:border-slate-600 border-gray-300 min-h-32 rounded-md"
957957
ref={editorRef}
958958
/>
959959
}
960-
{editorMode === "ra" && query !== undefined && <RAPreview code={query} />}
960+
{editorMode === "ra" && query !== undefined && <RAPreview code={query} dark={isDarkMode} />}
961961
</div>
962962

963963
{/* Error/Warning and Action Buttons - Same Row */}

src/ExportRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const ExportRenderer = React.forwardRef<HTMLDivElement, ExportRendererProps>(({
5757
}
5858
{query?.mode === "ra" ? (
5959
<div
60-
className="font-serif text-xl w-full bg-slate-200 max-w-4xl min-h-40 my-2 p-3 rounded"
60+
className="font-mono text-2xl text-left w-full bg-white text-gray-900 max-w-4xl min-h-40 my-2 p-3 rounded border border-gray-300"
6161
dangerouslySetInnerHTML={{ __html: renderRAPreview(query.code) }}
6262
/>
6363
) : (

src/ra-engine/RAPreview.tsx

Lines changed: 68 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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

181208
interface 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

Comments
 (0)