feat: add JsonCodeEditor component and integrate it into JsonGrid and JsonViewer

This commit is contained in:
2026-02-24 09:52:34 +08:00
parent 94bfa746db
commit 058c16b99f
4 changed files with 84 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"chat.tools.terminal.autoApprove": {
"pnpm": true
}
}
+75
View File
@@ -0,0 +1,75 @@
import { useMemo, useRef } from "react"
type JsonCodeEditorProps = {
value: string
onChange: (value: string) => void
}
const tokenRegex = /"(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"\s*:?|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g
function escapeHtml(input: string): string {
return input
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
}
function getTokenClass(token: string): string {
if (token.startsWith('"')) {
return token.endsWith(":") ? "text-indigo-600" : "text-emerald-600"
}
if (token === "true" || token === "false") {
return "text-violet-600"
}
if (token === "null") {
return "text-slate-500 italic"
}
return "text-amber-600"
}
function highlightJson(input: string): string {
let result = ""
let lastIndex = 0
for (const match of input.matchAll(tokenRegex)) {
const token = match[0]
const index = match.index ?? 0
result += escapeHtml(input.slice(lastIndex, index))
result += `<span class="${getTokenClass(token)}">${escapeHtml(token)}</span>`
lastIndex = index + token.length
}
result += escapeHtml(input.slice(lastIndex))
return result || " "
}
export default function JsonCodeEditor({ value, onChange }: JsonCodeEditorProps) {
const highlighted = useMemo(() => highlightJson(value), [value])
const preRef = useRef<HTMLPreElement>(null)
const syncScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {
if (!preRef.current) return
preRef.current.scrollTop = event.currentTarget.scrollTop
preRef.current.scrollLeft = event.currentTarget.scrollLeft
}
return (
<div className="relative flex-1 min-h-0">
<pre
ref={preRef}
aria-hidden
className="absolute inset-0 m-0 p-4 font-mono text-sm leading-6 overflow-auto whitespace-pre-wrap wrap-break-word"
>
<code dangerouslySetInnerHTML={{ __html: highlighted }} />
</pre>
<textarea
className="absolute inset-0 w-full h-full p-4 font-mono text-sm leading-6 resize-none overflow-auto bg-transparent text-transparent caret-slate-900 outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all"
value={value}
onChange={(e) => onChange(e.target.value)}
onScroll={syncScroll}
spellCheck={false}
/>
</div>
)
}
+2 -6
View File
@@ -1,5 +1,6 @@
import { useCallback, useMemo, useState } from "react" import { useCallback, useMemo, useState } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import JsonCodeEditor from "@/components/json-code-editor"
type RowRecord = Record<string, unknown> type RowRecord = Record<string, unknown>
@@ -84,12 +85,7 @@ export default function JsonGrid() {
{t("jsonGrid.jsonInput")} {t("jsonGrid.jsonInput")}
</span> </span>
</div> </div>
<textarea <JsonCodeEditor value={jsonInput} onChange={setJsonInput} />
className="flex-1 w-full p-4 font-mono text-sm outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all resize-none overflow-auto"
value={jsonInput}
onChange={(e) => setJsonInput(e.target.value)}
spellCheck={false}
/>
</div> </div>
<div className="w-[65%] bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden flex flex-col min-h-0"> <div className="w-[65%] bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden flex flex-col min-h-0">
+2 -6
View File
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import jp from "jsonpath" import jp from "jsonpath"
import JsonTreeNode from "@/components/json-tree-node" import JsonTreeNode from "@/components/json-tree-node"
import JsonCodeEditor from "@/components/json-code-editor"
/** /**
* JSON Viewer page component that displays the JSON visualisation tool in DevLab. * JSON Viewer page component that displays the JSON visualisation tool in DevLab.
@@ -83,12 +84,7 @@ export default function JsonViewer() {
{t("jsonViewer.jsonSource")} {t("jsonViewer.jsonSource")}
</span> </span>
</div> </div>
<textarea <JsonCodeEditor value={jsonInput} onChange={setJsonInput} />
className="flex-1 w-full p-4 font-mono text-sm outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all resize-none overflow-auto"
value={jsonInput}
onChange={(e) => setJsonInput(e.target.value)}
spellCheck={false}
/>
</div> </div>
{/* JSONPath Expression - fixed height */} {/* JSONPath Expression - fixed height */}