FIX: HTML entity rendering in syntax highilghter (> -> >)
Browse files
pr_review_dashboard/components/DiffView.js
CHANGED
|
@@ -7,7 +7,6 @@ import { useState, useCallback } from "react";
|
|
| 7 |
function highlightCode(text) {
|
| 8 |
if (!text) return text;
|
| 9 |
|
| 10 |
-
// Order matters for some regexes
|
| 11 |
const rules = [
|
| 12 |
{ cls: 'tk-cm', re: /(#.*$|\/\/.*$|\/\*[\s\S]*?\*\/)/gm }, // Comments
|
| 13 |
{ cls: 'tk-st', re: /(['"`])(?:(?!\1)[^\\]|\\.)*\1/g }, // Strings
|
|
@@ -18,26 +17,35 @@ function highlightCode(text) {
|
|
| 18 |
{ cls: 'tk-op', re: /([-+*/%=<>!&|^~]+)/g }, // Operators
|
| 19 |
];
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
const id = `__TK_${idx}_${placeholders.length}__`;
|
| 31 |
-
placeholders.push({ id, html: `<span class="${rule.cls}">${match}</span>` });
|
| 32 |
return id;
|
| 33 |
});
|
| 34 |
});
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
return
|
| 41 |
}
|
| 42 |
|
| 43 |
export default function DiffView({ diff, onCodeSubmit, isProcessing, isAccepted }) {
|
|
@@ -172,7 +180,6 @@ export default function DiffView({ diff, onCodeSubmit, isProcessing, isAccepted
|
|
| 172 |
dangerouslySetInnerHTML={{ __html: line.type === 'meta' ? line.text : highlightCode(line.text) }}
|
| 173 |
/>
|
| 174 |
|
| 175 |
-
{/* Intra-line Action Buttons (Shown on Hover) */}
|
| 176 |
{(line.type === 'add' || line.type === 'del') && !isAccepted && (
|
| 177 |
<div className="hunk-actions">
|
| 178 |
<button className="hunk-btn accept">Accept</button>
|
|
|
|
| 7 |
function highlightCode(text) {
|
| 8 |
if (!text) return text;
|
| 9 |
|
|
|
|
| 10 |
const rules = [
|
| 11 |
{ cls: 'tk-cm', re: /(#.*$|\/\/.*$|\/\*[\s\S]*?\*\/)/gm }, // Comments
|
| 12 |
{ cls: 'tk-st', re: /(['"`])(?:(?!\1)[^\\]|\\.)*\1/g }, // Strings
|
|
|
|
| 17 |
{ cls: 'tk-op', re: /([-+*/%=<>!&|^~]+)/g }, // Operators
|
| 18 |
];
|
| 19 |
|
| 20 |
+
// 1. First, we identify all tokens and replace them with unique placeholders
|
| 21 |
+
// This prevents HTML escaping from breaking the regex matching later.
|
| 22 |
+
const tokens = [];
|
| 23 |
+
let processed = text;
|
| 24 |
|
| 25 |
+
rules.forEach((rule, ruleIdx) => {
|
| 26 |
+
processed = processed.replace(rule.re, (match) => {
|
| 27 |
+
const id = `__TOKEN_${ruleIdx}_${tokens.length}__`;
|
| 28 |
+
tokens.push({ id, html: `<span class="${rule.cls}">${match}</span>` });
|
|
|
|
|
|
|
| 29 |
return id;
|
| 30 |
});
|
| 31 |
});
|
| 32 |
|
| 33 |
+
// 2. Now escape the remaining text (non-tokens) for HTML safety
|
| 34 |
+
let finalHtml = processed
|
| 35 |
+
.replace(/&/g, "&")
|
| 36 |
+
.replace(/</g, "<")
|
| 37 |
+
.replace(/>/g, ">");
|
| 38 |
+
|
| 39 |
+
// 3. Put the highlighted tokens back into the escaped string
|
| 40 |
+
// We go backwards to handle any nested-like replacements (though regexes here are flat)
|
| 41 |
+
for (let i = tokens.length - 1; i >= 0; i--) {
|
| 42 |
+
const t = tokens[i];
|
| 43 |
+
// We need to make sure the token HTML itself is not escaped in step 2.
|
| 44 |
+
// Since we used IDs like __TOKEN... they are safe.
|
| 45 |
+
finalHtml = finalHtml.replace(t.id, t.html);
|
| 46 |
+
}
|
| 47 |
|
| 48 |
+
return finalHtml;
|
| 49 |
}
|
| 50 |
|
| 51 |
export default function DiffView({ diff, onCodeSubmit, isProcessing, isAccepted }) {
|
|
|
|
| 180 |
dangerouslySetInnerHTML={{ __html: line.type === 'meta' ? line.text : highlightCode(line.text) }}
|
| 181 |
/>
|
| 182 |
|
|
|
|
| 183 |
{(line.type === 'add' || line.type === 'del') && !isAccepted && (
|
| 184 |
<div className="hunk-actions">
|
| 185 |
<button className="hunk-btn accept">Accept</button>
|