Fix config editor syntax highlighting with character parser
All checks were successful
Deploy GSM / deploy (push) Successful in 25s

Replace regex-based tokenization with character-by-character parsing
to completely avoid issues with numbers in CSS class names.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 13:58:59 +01:00
parent 7aad85c5d4
commit 52a06e435f

View File

@@ -84,41 +84,81 @@ export default function HytaleConfigEditor({ token }) {
if (!text) return ''
// Escape HTML first
let result = text
let escaped = text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// Use placeholder tokens to avoid regex conflicts
const tokens = []
let tokenIndex = 0
// Process character by character to build highlighted output
let result = ''
let i = 0
// Replace strings with placeholders
result = result.replace(/"([^"\\]|\\.)*"/g, (match) => {
const isKey = result.indexOf(match + ':') !== -1
const className = isKey ? 'text-blue-400' : 'text-amber-300'
tokens.push(`<span class="${className}">${match}</span>`)
return `\x00${tokenIndex++}\x00`
})
// Replace booleans and null
result = result.replace(/\b(true|false|null)\b/g, (match) => {
tokens.push(`<span class="text-purple-400">${match}</span>`)
return `\x00${tokenIndex++}\x00`
})
// Replace numbers (only standalone, not inside placeholders)
result = result.replace(/(?<!\x00)(-?\d+\.?\d*)(?!\x00)/g, (match) => {
if (match === '' || match === '-') return match
tokens.push(`<span class="text-cyan-400">${match}</span>`)
return `\x00${tokenIndex++}\x00`
})
// Replace brackets
result = result.replace(/([{}\[\]])/g, '<span class="text-gray-500">$1</span>')
// Restore tokens
result = result.replace(/\x00(\d+)\x00/g, (_, idx) => tokens[parseInt(idx)])
while (i < escaped.length) {
// Check for string start
if (escaped[i] === '"') {
let str = '"'
i++
while (i < escaped.length && escaped[i] !== '"') {
if (escaped[i] === '\\' && i + 1 < escaped.length) {
str += escaped[i] + escaped[i + 1]
i += 2
} else {
str += escaped[i]
i++
}
}
if (i < escaped.length) {
str += '"'
i++
}
// Check if it's a key (followed by colon)
let j = i
while (j < escaped.length && /\s/.test(escaped[j])) j++
const isKey = escaped[j] === ':'
const className = isKey ? 'text-blue-400' : 'text-amber-300'
result += `<span class="${className}">${str}</span>`
}
// Check for brackets
else if (/[{}\[\]]/.test(escaped[i])) {
result += `<span class="text-gray-400">${escaped[i]}</span>`
i++
}
// Check for numbers
else if (/[-\d]/.test(escaped[i]) && (i === 0 || /[\s,:\[\{]/.test(escaped[i-1]))) {
let num = ''
if (escaped[i] === '-') {
num += '-'
i++
}
while (i < escaped.length && /[\d.]/.test(escaped[i])) {
num += escaped[i]
i++
}
if (num && num !== '-') {
result += `<span class="text-cyan-400">${num}</span>`
} else {
result += num
}
}
// Check for booleans and null
else if (escaped.slice(i, i + 4) === 'true') {
result += '<span class="text-purple-400">true</span>'
i += 4
}
else if (escaped.slice(i, i + 5) === 'false') {
result += '<span class="text-purple-400">false</span>'
i += 5
}
else if (escaped.slice(i, i + 4) === 'null') {
result += '<span class="text-purple-400">null</span>'
i += 4
}
// Regular character
else {
result += escaped[i]
i++
}
}
return result
}