Merge pull request #3 from onixbyte/develop

v1.1.1: Hightlight for JSONPath Expression
This commit is contained in:
2026-02-24 16:03:46 +08:00
committed by GitHub
3 changed files with 72 additions and 12 deletions
+3 -3
View File
@@ -13,9 +13,9 @@ export default function BmiCalculator() {
const [bmi, setBmi] = useState<number | null>(null) const [bmi, setBmi] = useState<number | null>(null)
const [bmiCategory, setBmiCategory] = useState<string>("") const [bmiCategory, setBmiCategory] = useState<string>("")
const calculateBMI = () => { const calculateBodyMassIndex = () => {
const weightNum = parseFloat(weight) const weightNum = parseFloat(weight)
const heightNum = parseFloat(height) / 100 // Convert cm to meters const heightNum = parseFloat(height) / 100 // Convert cm to metres
if (weightNum > 0 && heightNum > 0) { if (weightNum > 0 && heightNum > 0) {
const bmiValue = weightNum / (heightNum * heightNum) const bmiValue = weightNum / (heightNum * heightNum)
@@ -132,7 +132,7 @@ export default function BmiCalculator() {
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex flex-col gap-2 pt-2"> <div className="flex flex-col gap-2 pt-2">
<button <button
onClick={calculateBMI} onClick={calculateBodyMassIndex}
disabled={!weight || !height} disabled={!weight || !height}
className="w-full bg-indigo-600 text-white py-2.5 px-4 rounded-lg text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:bg-slate-300 disabled:cursor-not-allowed transition-colours"> className="w-full bg-indigo-600 text-white py-2.5 px-4 rounded-lg text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:bg-slate-300 disabled:cursor-not-allowed transition-colours">
{t("bmi.calculate")} {t("bmi.calculate")}
+11
View File
@@ -16,6 +16,17 @@ interface ChangelogVersion {
} }
const CHANGELOG_DATA: ChangelogVersion[] = [ const CHANGELOG_DATA: ChangelogVersion[] = [
{
version: "1.1.1",
date: "2026-02-24",
entries: [
{
type: "feat",
title: "JSON Path Highlighting",
description: "Add highlighting for JSON Path expression."
}
]
},
{ {
version: "1.1.0", version: "1.1.0",
date: "2026-02-24", date: "2026-02-24",
+54 -5
View File
@@ -5,6 +5,43 @@ import JsonTreeNode from "@/components/json-tree-node"
import JsonCodeEditor from "@/components/json-code-editor" import JsonCodeEditor from "@/components/json-code-editor"
import Seo from "@/components/seo" import Seo from "@/components/seo"
const jsonPathTokenRegex = /(\$)|(\.\.)|(\.)|(\[\*\])|(\[\d+\])|(\[(?:'[^']*'|"[^"]*")\])|(\*)|(@)|(\?)|(\(|\))|([A-Za-z_][\w-]*)/g
function escapeHtml(input: string): string {
return input
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
}
function getJsonPathTokenClass(token: string): string {
if (token === "$" || token === "@") return "text-indigo-600"
if (token === "." || token === "..") return "text-slate-400"
if (token === "*" || token === "[*]") return "text-violet-600"
if (token.startsWith("[") && token.endsWith("]")) return "text-amber-600"
if (token === "?" || token === "(" || token === ")") return "text-rose-600"
return "text-emerald-600"
}
function highlightJsonPath(input: string): string {
if (!input) return " "
let result = ""
let lastIndex = 0
for (const match of input.matchAll(jsonPathTokenRegex)) {
const token = match[0]
const index = match.index ?? 0
result += escapeHtml(input.slice(lastIndex, index))
result += `<span class="${getJsonPathTokenClass(token)}">${escapeHtml(token)}</span>`
lastIndex = index + token.length
}
result += escapeHtml(input.slice(lastIndex))
return result
}
/** /**
* JSON Viewer page component that displays the JSON visualisation tool in DevLab. * JSON Viewer page component that displays the JSON visualisation tool in DevLab.
*/ */
@@ -29,6 +66,7 @@ export default function JsonViewer() {
const [query, setQuery] = useState<string>("$.staff_members[*].name") const [query, setQuery] = useState<string>("$.staff_members[*].name")
const [copiedCsv, setCopiedCsv] = useState(false) const [copiedCsv, setCopiedCsv] = useState(false)
const [copiedRawJson, setCopiedRawJson] = useState(false) const [copiedRawJson, setCopiedRawJson] = useState(false)
const highlightedQuery = useMemo(() => highlightJsonPath(query), [query])
const isPlainObject = (value: unknown): value is Record<string, unknown> => { const isPlainObject = (value: unknown): value is Record<string, unknown> => {
return value !== null && typeof value === "object" && !Array.isArray(value) return value !== null && typeof value === "object" && !Array.isArray(value)
@@ -147,19 +185,30 @@ export default function JsonViewer() {
<span className="text-red-500 normal-case">{t("jsonViewer.invalidSyntax")}</span> <span className="text-red-500 normal-case">{t("jsonViewer.invalidSyntax")}</span>
)} )}
</label> </label>
<div
className={`relative w-full border rounded-lg transition-all shadow-sm ${
result.queryError
? "border-red-300 focus-within:ring-2 focus-within:ring-red-500 focus-within:border-red-500"
: "border-slate-200 focus-within:ring-2 focus-within:ring-indigo-500 focus-within:border-indigo-500"
}`}
>
<pre
aria-hidden
className="m-0 p-3 font-mono text-sm leading-6 whitespace-pre overflow-hidden pointer-events-none"
>
<code dangerouslySetInnerHTML={{ __html: highlightedQuery }} />
</pre>
<input <input
type="text" type="text"
className={`w-full p-3 font-mono text-sm border rounded-lg focus:ring-2 outline-none transition-all shadow-sm ${ className="absolute inset-0 w-full h-full p-3 font-mono text-sm leading-6 bg-transparent text-transparent caret-slate-900 outline-none placeholder:text-slate-400"
result.queryError
? "border-red-300 focus:ring-red-500 focus:border-red-500"
: "border-slate-200 focus:ring-indigo-500 focus:border-indigo-500"
}`}
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
placeholder={t("jsonViewer.placeholder")} placeholder={t("jsonViewer.placeholder")}
spellCheck={false}
/> />
</div> </div>
</div> </div>
</div>
{/* Right visualisation panel - 70% */} {/* Right visualisation panel - 70% */}
<div className="w-[70%] bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden min-h-0"> <div className="w-[70%] bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden min-h-0">