diff --git a/apps/local/package.json b/apps/local/package.json index b652b39f5..2ed8fe9c6 100644 --- a/apps/local/package.json +++ b/apps/local/package.json @@ -17,11 +17,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.23", "@radix-ui/react-slot": "^1.3.3", - "@shikijs/core": "^3.23.0", - "@shikijs/engine-javascript": "^3.23.0", - "@shikijs/langs": "^3.23.0", - "@shikijs/themes": "^3.23.0", "@tailwindcss/vite": "^4.3.3", + "@twinkleplop/json": "0.1.4", "@uidotdev/usehooks": "^2.4.1", "@vercel/analytics": "^2.0.1", "class-variance-authority": "^0.7.1", diff --git a/apps/local/src/App.integration.test.tsx b/apps/local/src/App.integration.test.tsx index bd434522a..94746ce5d 100644 --- a/apps/local/src/App.integration.test.tsx +++ b/apps/local/src/App.integration.test.tsx @@ -211,7 +211,7 @@ describe('local receiver to viewer integration', () => { fireEvent.click(screen.getByRole('button', { name: 'Copy JSON' })) expect(await screen.findByRole('status', { name: 'JSON copied' })).not.toBeNull() await waitFor(() => { - expect(screen.getByTestId('highlighted-json').querySelector('.shiki')).not.toBeNull() + expect(screen.getByTestId('highlighted-json').querySelector('.json-view-code')).not.toBeNull() }) fireEvent.click(events[0]!) fireEvent.click(screen.getByRole('tab', { name: 'JSON' })) diff --git a/apps/local/src/components/json-view.tsx b/apps/local/src/components/json-view.tsx index 5ae09ba69..f926c046a 100644 --- a/apps/local/src/components/json-view.tsx +++ b/apps/local/src/components/json-view.tsx @@ -1,21 +1,12 @@ -import { createHighlighterCore } from '@shikijs/core' -import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript' -import json from '@shikijs/langs/json' -import githubDark from '@shikijs/themes/github-dark' -import githubLight from '@shikijs/themes/github-light' +import { language } from '@twinkleplop/json' import { Check, Copy } from 'lucide-react' -import { useEffect, useState } from 'react' import { useCopyToClipboard } from '@uidotdev/usehooks' type JsonViewProps = { code: string } -const highlighter = createHighlighterCore({ - engine: createJavaScriptRegexEngine(), - langs: [json], - themes: [githubLight, githubDark], -}) +const highlight = language() function formatJson(code: string) { try { @@ -25,72 +16,43 @@ function formatJson(code: string) { } } -/** Render JSON with Shiki only after its containing event has been expanded. */ +/** A string token immediately followed by `:` is an object key. */ +function highlightJson(code: string) { + return highlight(code, { + class_name: 'json-view-code', + has_classes: true, + token: (type, _start, end) => { + if (type === 'string' && code[end] === ':') { + return { class: 'tok-key' } + } + }, + }) +} + +/** Render JSON with Twinkleplop after its containing event has been expanded. */ export function JsonView({ code }: JsonViewProps) { - const [html, setHtml] = useState() const [copiedText, copyToClipboard] = useCopyToClipboard() const formattedCode = formatJson(code) + const html = highlightJson(formattedCode) const copied = copiedText === code - useEffect(() => { - let disposed = false - - void highlighter - .then((instance) => - instance.codeToHtml(formattedCode, { - lang: 'json', - themes: { - light: 'github-light', - dark: 'github-dark', - }, - defaultColor: false, - }) - ) - .then((result) => { - if (!disposed) { - setHtml(result) - } - }) - .catch(() => { - // Keep the readable, escaped source fallback if syntax highlighting cannot load. - }) - - return () => { - disposed = true - } - }, [formattedCode]) - const copyJson = () => { void copyToClipboard(code) } - const copyButton = ( - - ) - - if (!html) { - return ( -
-
{copyButton}
- {copied ? JSON copied : null} -
-          {formattedCode}
-        
-
- ) - } - return (
-
{copyButton}
+
+ +
{copied ? JSON copied : null}
=5.7.2' + '@twinkleplop/core@0.2.1': + resolution: {integrity: sha512-e/5EokljcDDsGvh55n0QO+HXDMVbpUhkNpg/QCWlY0Igw4a0qTB7fbJWtFrcRhBqfg+H7Wtw07A6U8YADm9J/A==} + + '@twinkleplop/json@0.1.4': + resolution: {integrity: sha512-lal3aivE56eP+i5JhK4eQAN1vOKbyft2Mbbc86+RrZwynN4f9MylQxwJbw/o1dTt5OY0ewO3UGtPaR5XCmctJA==} + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -7769,6 +7766,12 @@ snapshots: dependencies: typescript: 5.9.3 + '@twinkleplop/core@0.2.1': {} + + '@twinkleplop/json@0.1.4': + dependencies: + '@twinkleplop/core': 0.2.1 + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1