Skip to content

Commit 14df731

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

7 files changed

Lines changed: 380 additions & 241 deletions

File tree

src/App.css

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,22 @@ 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; }
49+
/* RA Preview styling — light mode */
50+
.ra-prev-op { color: #a78bfa; font-weight: bold; }
51+
.ra-prev-assign { color: #a78bfa; font-weight: bold; }
52+
.ra-prev-logic { color: #1d4ed8; font-weight: bold; }
53+
.ra-prev-str { color: #047857; }
54+
.ra-prev-num { color: #b45309; }
55+
.ra-prev-comment { color: #6b7280; font-style: italic; }
56+
.ra-prev-sub { font-size: 0.9em; color: #4c1d95; }
57+
.ra-prev-paren { color: #9ca3af; }
58+
59+
/* RA Preview styling — dark mode */
5760
.dark .ra-prev-op { color: #a78bfa; }
5861
.dark .ra-prev-assign { color: #a78bfa; }
5962
.dark .ra-prev-logic { color: #60a5fa; }
6063
.dark .ra-prev-str { color: #34d399; }
6164
.dark .ra-prev-num { color: #fbbf24; }
65+
.dark .ra-prev-comment { color: #9ca3af; }
6266
.dark .ra-prev-sub { color: #c4b5fd; }
67+
.dark .ra-prev-paren { color: #6b7280; }

src/App.tsx

Lines changed: 3 additions & 3 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,7 +950,7 @@ 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"

src/ExportRenderer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ 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"
61-
dangerouslySetInnerHTML={{ __html: renderRAPreview(query.code) }}
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"
61+
dangerouslySetInnerHTML={{ __html: renderRAPreview(query.code, true) }}
6262
/>
6363
) : (
6464
<Editor

src/ra-engine/RAPreview.tsx

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

Comments
 (0)