diff --git a/web/package-lock.json b/web/package-lock.json index f7e92da..217ec58 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,6 +11,13 @@ "@antv/g6": "^5.1.1", "@antv/graphin": "^3.0.5", "@base-ui/react": "^1.6.0", + "@codemirror/commands": "^6.11.1", + "@codemirror/lang-html": "^6.4.12", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/language": "^6.13.1", + "@codemirror/search": "^6.7.2", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.15", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -966,6 +973,149 @@ "node": ">=14.21.3" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.4", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.4.tgz", + "integrity": "sha512-yF8RhOf2SXlg00+qtupzLhrglcas61boqaONiNcbPIebHCpJocpqTBA6dyrtUrQO47hqyTdeob/3XnYilT7TaQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.13.1.tgz", + "integrity": "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/streamparser": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz", + "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/streamparser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/streamparser/-/streamparser-6.0.0.tgz", + "integrity": "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.43.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.15.tgz", + "integrity": "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1885,6 +2035,80 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.4.tgz", + "integrity": "sha512-xt3Q4z64xM+xhg4M7NXqw1N//1O74bUFo5kz6fwMeC4qwxSuRdwkYSmjcqCEEKYkNw5e67f2hcNqBoNERAEU0w==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.9.tgz", + "integrity": "sha512-ZmzgvTOBdJWGIsY/ItAfN7UW8DV802oZG+jeG6E508SwEzz5YhuAajptpWPICvIyqYAD2+VjMdOS40GgGkyEOQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.6.tgz", + "integrity": "sha512-sEvOqpl4vJQca0NugSQK02vQPbc5KrvoojnYGoqqFgFO0MtcYhwGiHKVEB+LuXYJnfyXo4QPA8ztbrEa4Fw5NA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.11.tgz", + "integrity": "sha512-UwgZcl1R66H2hDzBrA7kWyYmX6s8p5TQ+e/t7CuvgCtBEPxbEPaHYHu+pJBvTvDotEJpuQkJjbXuHiw5Cixxgw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", @@ -5172,6 +5396,12 @@ "dev": true, "license": "MIT" }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -10259,6 +10489,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -10928,6 +11164,12 @@ "d3-timer": "^3.0.1" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", diff --git a/web/package.json b/web/package.json index 8182698..9f8c6ef 100644 --- a/web/package.json +++ b/web/package.json @@ -23,6 +23,13 @@ "@antv/g6": "^5.1.1", "@antv/graphin": "^3.0.5", "@base-ui/react": "^1.6.0", + "@codemirror/commands": "^6.11.1", + "@codemirror/lang-html": "^6.4.12", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/language": "^6.13.1", + "@codemirror/search": "^6.7.2", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.15", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", diff --git a/web/src/app/(main)/function/findings/page.tsx b/web/src/app/(main)/function/findings/page.tsx index f07bab8..b226a60 100644 --- a/web/src/app/(main)/function/findings/page.tsx +++ b/web/src/app/(main)/function/findings/page.tsx @@ -370,6 +370,7 @@ export default function FindingsPage() { query, sort, assetScope: activeAssetScope ?? undefined, + ...dateRange, }, true, ); @@ -922,8 +923,8 @@ export default function FindingsPage() { })} -
- +
+ setSort(v as "severity" | "time")}> - + @@ -1018,7 +1019,7 @@ export default function FindingsPage() { -
+
{selectedIds.size > 0 && ( 已选 {selectedIds.size} 条 )} diff --git a/web/src/components/finding-date-filter.tsx b/web/src/components/finding-date-filter.tsx index 9208f8c..b5ad215 100644 --- a/web/src/components/finding-date-filter.tsx +++ b/web/src/components/finding-date-filter.tsx @@ -104,7 +104,7 @@ export function FindingDateFilter({ {label} - +

按发现时间筛选

选择年月日和时分,范围包含起止时刻。

@@ -112,14 +112,14 @@ export function FindingDateFilter({
开始时间 -
+
apply({ fromDate: value })} label="开始日期" /> apply({ fromTime: value })} aria-label="开始时分" />
结束时间 -
+
apply({ toDate: value })} label="结束日期" /> apply({ toTime: value })} aria-label="结束时分" />
diff --git a/web/src/components/finding-reproduction-panel.tsx b/web/src/components/finding-reproduction-panel.tsx index bbb3d30..e388037 100644 --- a/web/src/components/finding-reproduction-panel.tsx +++ b/web/src/components/finding-reproduction-panel.tsx @@ -82,6 +82,24 @@ function packetText(detail: FindingTrafficDetail, side: "request" | "response") return [head, body].filter(Boolean).join("\n\n").trim(); } +function formatCapturedAt(value: number | string | null | undefined) { + if (value === null || value === undefined || value === "") return ""; + const numeric = typeof value === "number" ? value : Number(value); + const date = Number.isFinite(numeric) + ? new Date(numeric < 10_000_000_000 ? numeric * 1000 : numeric) + : new Date(String(value)); + if (Number.isNaN(date.getTime())) return ""; + return new Intl.DateTimeFormat("zh-CN", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }).format(date); +} + function PacketDisclosure({ findingId, bindingId, @@ -181,10 +199,13 @@ function PacketDisclosure({ ) : null} {detail ? (
-
+
{detail.binding.snapshot.method} {detail.binding.snapshot.status} - {detail.binding.snapshot.url} + {detail.binding.snapshot.url} + + {formatCapturedAt(detail.binding.snapshot.captured_at) || "时间未知"} +

请求 Request

diff --git a/web/src/components/http-code-block.tsx b/web/src/components/http-code-block.tsx index 6d35240..882d1c8 100644 --- a/web/src/components/http-code-block.tsx +++ b/web/src/components/http-code-block.tsx @@ -7,6 +7,7 @@ import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn, copyText } from "@/lib/utils"; +import { PacketCodeEditor } from "@/components/packet-code-editor"; function statusTone(status: number) { if (status >= 500) return "text-red-500"; @@ -308,34 +309,8 @@ export function HttpCodeBlock({ raw }: { raw: string }) { {copied ? "已复制" : "复制报文"}
- {/* biome-ignore lint/a11y/useSemanticElements: textarea cannot preserve line numbers and syntax-highlighting markup. */} -
{ - const text = selectedPacketText(event.currentTarget, value, lines); - if (text === null) return; - event.preventDefault(); - event.clipboardData.setData("text/plain", text); - }} - onKeyDown={(event) => { - if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "a") return; - event.preventDefault(); - const selection = window.getSelection(); - if (!selection) return; - const range = document.createRange(); - range.selectNodeContents(event.currentTarget); - selection.removeAllRanges(); - selection.addRange(range); - }} - > - - - +
+
diff --git a/web/src/components/packet-code-editor.tsx b/web/src/components/packet-code-editor.tsx new file mode 100644 index 0000000..b201d97 --- /dev/null +++ b/web/src/components/packet-code-editor.tsx @@ -0,0 +1,114 @@ +"use client"; + +import * as React from "react"; +import { EditorState } from "@codemirror/state"; +import { + drawSelection, + EditorView, + highlightActiveLine, + keymap, + lineNumbers, +} from "@codemirror/view"; +import { defaultKeymap, indentWithTab } from "@codemirror/commands"; +import { defaultHighlightStyle, HighlightStyle, StreamLanguage, syntaxHighlighting } from "@codemirror/language"; +import { tags } from "@lezer/highlight"; + +const packetHighlight = HighlightStyle.define([ + { tag: tags.keyword, color: "#d95656", fontWeight: "700" }, + { tag: tags.string, color: "#0284c7" }, + { tag: tags.variableName, color: "#286dcc", fontWeight: "650" }, + { tag: tags.typeName, color: "#6b7280" }, + { tag: tags.number, color: "#c026d3" }, + { tag: tags.bool, color: "#e11d48" }, +]); + +const httpLanguage = StreamLanguage.define({ + startState: () => ({ line: 0 }), + token(stream, state) { + if (stream.sol()) state.line += 1; + if (stream.match(/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|CONNECT|TRACE)\b/)) return "keyword"; + if (stream.match(/^HTTP\/\d(?:\.\d)?\b/)) return "typeName"; + if (stream.match(/^[A-Za-z][A-Za-z0-9-]*(?=:)/)) return "variableName"; + if (stream.match(/^https?:\/\/\S+/)) return "string"; + if (stream.match(/^(?:\d{3})(?:\s+[^\n]*)?/)) return "number"; + if (stream.match(/^\b(?:true|false|null)\b/)) return "bool"; + stream.next(); + return null; + }, +}); + +const packetTheme = EditorView.theme({ + "&": { height: "100%", backgroundColor: "transparent", color: "var(--foreground)" }, + ".cm-scroller": { overflow: "auto", fontFamily: "var(--font-mono)", fontSize: "0.75rem", fontWeight: "500", lineHeight: "1.65rem" }, + ".cm-content": { minHeight: "100%", padding: "0.5rem 0.5rem" }, + ".cm-gutters": { backgroundColor: "transparent", color: "var(--muted-foreground)", border: "0", borderRight: "1px solid var(--border)" }, + ".cm-lineNumbers .cm-gutterElement": { minWidth: "2rem", padding: "0 0.5rem" }, + ".cm-activeLine": { backgroundColor: "transparent" }, + ".cm-activeLineGutter": { backgroundColor: "transparent" }, + ".cm-selectionBackground, ::selection": { backgroundColor: "#c9d2e3" }, + ".cm-cursor": { borderLeftColor: "#222" }, +}, { dark: false }); + +export function PacketCodeEditor({ + value, + onChange, + readOnly = false, + wrapLines = true, + ariaLabel = "HTTP 报文代码", +}: { + value: string; + onChange?: (value: string) => void; + readOnly?: boolean; + wrapLines?: boolean; + ariaLabel?: string; +}) { + const host = React.useRef(null); + const viewRef = React.useRef(null); + const valueRef = React.useRef(value); + const onChangeRef = React.useRef(onChange); + onChangeRef.current = onChange; + + React.useEffect(() => { + if (!host.current) return; + const state = EditorState.create({ + doc: value, + extensions: [ + lineNumbers(), + drawSelection(), + highlightActiveLine(), + keymap.of([...defaultKeymap, indentWithTab]), + httpLanguage, + syntaxHighlighting(defaultHighlightStyle), + syntaxHighlighting(packetHighlight), + packetTheme, + ...(wrapLines ? [EditorView.lineWrapping] : []), + EditorView.editable.of(!readOnly), + EditorState.readOnly.of(readOnly), + EditorView.contentAttributes.of({ role: "textbox", "aria-label": ariaLabel, "aria-multiline": "true" }), + EditorView.updateListener.of((update) => { + if (!update.docChanged) return; + const next = update.state.doc.toString(); + valueRef.current = next; + onChangeRef.current?.(next); + }), + ], + }); + const view = new EditorView({ state, parent: host.current }); + viewRef.current = view; + return () => { + viewRef.current = null; + view.destroy(); + }; + }, [ariaLabel, readOnly, wrapLines]); + + React.useEffect(() => { + const view = viewRef.current; + if (!view || valueRef.current === value) return; + const current = view.state.doc.toString(); + if (current === value) return; + view.dispatch({ changes: { from: 0, to: current.length, insert: value } }); + valueRef.current = value; + }, [value]); + + return
; +} diff --git a/web/src/components/repeater/repeater-text-pane.tsx b/web/src/components/repeater/repeater-text-pane.tsx index 960353b..0748700 100644 --- a/web/src/components/repeater/repeater-text-pane.tsx +++ b/web/src/components/repeater/repeater-text-pane.tsx @@ -6,8 +6,7 @@ import { WrapTextIcon } from "lucide-react"; import { LiveClock } from "@/app/(main)/_components/live-clock"; import { CopyButton } from "@/components/copy-button"; -import { HttpPacketSyntax } from "@/components/http-code-block"; -import { Textarea } from "@/components/ui/textarea"; +import { PacketCodeEditor } from "@/components/packet-code-editor"; import { cn } from "@/lib/utils"; export function RepeaterTextPane({ @@ -16,8 +15,6 @@ export function RepeaterTextPane({ value, onChange, disabled, - placeholder, - syntaxHighlight = false, toolbar, panelClassName, showClock = false, @@ -33,11 +30,8 @@ export function RepeaterTextPane({ panelClassName?: string; showClock?: boolean; }) { - const gutter = React.useRef(null); - const syntax = React.useRef(null); const [wrapLines, setWrapLines] = React.useState(true); const lines = React.useMemo(() => value.split(/\r\n|\r|\n/).length, [value]); - const numbers = React.useMemo(() => Array.from({ length: lines }, (_, index) => index + 1).join("\n"), [lines]); return (
-
- -