diff --git a/components/JsonEditor.tsx b/components/JsonEditor.tsx index c7870e7ca..11b13487c 100644 --- a/components/JsonEditor.tsx +++ b/components/JsonEditor.tsx @@ -36,6 +36,9 @@ type Meta = { valid?: boolean; isSchema?: boolean; indent?: boolean; + highlightLines?: string; + highlightLineStart?: number; + highlightLineEnd?: number; }; type Path = number[]; @@ -60,6 +63,43 @@ type MultipathDecoration = { syntaxPart: SyntaxPart; }; +function parseHighlightLines(meta: Meta | null): Set { + const out = new Set(); + if (!meta) return out; + + // Support start/end range + if ( + typeof meta.highlightLineStart === 'number' && + typeof meta.highlightLineEnd === 'number' + ) { + const start = Math.min(meta.highlightLineStart, meta.highlightLineEnd); + const end = Math.max(meta.highlightLineStart, meta.highlightLineEnd); + for (let i = start; i <= end; i++) out.add(i); + return out; + } + + // Support highlightLines string: "5" or "2-9" or "2-5,7,9-10" + const input = meta.highlightLines; + if (!input) return out; + + for (const part of input + .split(',') + .map((p) => p.trim()) + .filter(Boolean)) { + const m = part.match(/^(\d+)(?:-(\d+))?$/); + if (!m) continue; + + const start = Number(m[1]); + const end = m[2] ? Number(m[2]) : start; + + const lo = Math.min(start, end); + const hi = Math.max(start, end); + + for (let i = lo; i <= hi; i++) out.add(i); + } + + return out; +} const META_REGEX = /^\s*\/\/ props (?{.*}).*\n/; // Prevent annoying error messages because slate is not SSR ready @@ -358,6 +398,10 @@ export default function JsonEditor({ return null; } }, [codeContent, isJsonMode]); + const highlightedLines = React.useMemo( + () => parseHighlightLines(meta), + [meta], + ); const parsedCode: null | any = React.useMemo(() => { try { @@ -698,9 +742,13 @@ export default function JsonEditor({ const line = path[0] + 1; /* istanbul ignore else : no else block to test */ if (element.type === 'paragraph') { + const isHighlighted = highlightedLines.has(line); return ( { }); }); + it('should highlight a single line when highlightLines is provided', () => { + const code = `// props { "highlightLines": "2" } +{ + "a": 1, + "b": 2 +}`; + + cy.mount(); + + // line 2 is the `"a": 1,` line + cy.get('[data-test="json-editor"]') + .contains('"a"') + .closest('span.relative') + .should('have.class', 'bg-white/10'); + + // line 3 should NOT be highlighted + cy.get('[data-test="json-editor"]') + .contains('"b"') + .closest('span.relative') + .should('not.have.class', 'bg-white/10'); + }); + + it('should highlight a range of lines when highlightLines is a range', () => { + const code = `// props { "highlightLines": "1-2" } +{ + "a": 1, + "b": 2 +}`; + + cy.mount(); + + // line 1 is "{" + cy.get('[data-test="json-editor"]') + .contains('{') + .closest('span.relative') + .should('have.class', 'bg-white/10'); + + // line 2 is `"a": 1,` + cy.get('[data-test="json-editor"]') + .contains('"a"') + .closest('span.relative') + .should('have.class', 'bg-white/10'); + }); + it('should highlight lines using highlightLineStart and highlightLineEnd', () => { + const code = `// props { "highlightLineStart": 1, "highlightLineEnd": 2 } +{ + "a": 1, + "b": 2 +}`; + + cy.mount(); + + // line 1 "{" + cy.get('[data-test="json-editor"]') + .contains('{') + .closest('span.relative') + .should('have.class', 'bg-white/10'); + + // line 2 `"a": 1,` + cy.get('[data-test="json-editor"]') + .contains('"a"') + .closest('span.relative') + .should('have.class', 'bg-white/10'); + + // line 3 `"b": 2` should NOT be highlighted + cy.get('[data-test="json-editor"]') + .contains('"b"') + .closest('span.relative') + .should('not.have.class', 'bg-white/10'); + }); + // should render with meta regex props and check schema compliant it('should render with meta regex props', () => { const schemaCompliantMetaProps = diff --git a/pages/understanding-json-schema/structuring.md b/pages/understanding-json-schema/structuring.md index 5aa02de0c..79c0de602 100644 --- a/pages/understanding-json-schema/structuring.md +++ b/pages/understanding-json-schema/structuring.md @@ -206,7 +206,7 @@ The URI `https://example.com/schemas/address#/properties/street_address` identifies the highlighted subschema in the following schema. ```json -// props { "isSchema": true } +// props { "isSchema": true, "highlightLines": "5" } { "$id": "https://example.com/schemas/address", "type": "object", @@ -253,7 +253,7 @@ The URI `https://example.com/schemas/address#street_address` identifies the subschema on the highlighted part of the following schema. ```json -// props { "isSchema": true } +// props { "isSchema": true, "highlightLines": "5" } { "$id": "https://example.com/schemas/address", "type": "object",