diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f60bf45 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "chat.tools.terminal.autoApprove": { + "pnpm": true + } +} \ No newline at end of file diff --git a/src/components/json-code-editor/index.tsx b/src/components/json-code-editor/index.tsx new file mode 100644 index 0000000..1f0c1e8 --- /dev/null +++ b/src/components/json-code-editor/index.tsx @@ -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, ">") +} + +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 += `${escapeHtml(token)}` + 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(null) + + const syncScroll = (event: React.UIEvent) => { + if (!preRef.current) return + preRef.current.scrollTop = event.currentTarget.scrollTop + preRef.current.scrollLeft = event.currentTarget.scrollLeft + } + + return ( + + + + + onChange(e.target.value)} + onScroll={syncScroll} + spellCheck={false} + /> + + ) +} diff --git a/src/page/json-grid/index.tsx b/src/page/json-grid/index.tsx index bbf7999..a9351c2 100644 --- a/src/page/json-grid/index.tsx +++ b/src/page/json-grid/index.tsx @@ -1,5 +1,6 @@ import { useCallback, useMemo, useState } from "react" import { useTranslation } from "react-i18next" +import JsonCodeEditor from "@/components/json-code-editor" type RowRecord = Record @@ -84,12 +85,7 @@ export default function JsonGrid() { {t("jsonGrid.jsonInput")} - setJsonInput(e.target.value)} - spellCheck={false} - /> + diff --git a/src/page/json-viewer/index.tsx b/src/page/json-viewer/index.tsx index f2d275a..4b879e2 100644 --- a/src/page/json-viewer/index.tsx +++ b/src/page/json-viewer/index.tsx @@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import jp from "jsonpath" 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. @@ -83,12 +84,7 @@ export default function JsonViewer() { {t("jsonViewer.jsonSource")} - setJsonInput(e.target.value)} - spellCheck={false} - /> + {/* JSONPath Expression - fixed height */}
+ +