diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0983094d3e..803f2525c8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -22,3 +22,6 @@ jobs: - name: Run Prettier Check run: pnpm format:check + + - name: Run Playwright + run: pnpm test diff --git a/package.json b/package.json index f8fad50692..19372b5c1f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "repository": "graphql/graphql.github.io website", "private": true, - "packageManager": "pnpm@9.15.9", + "packageManager": "pnpm@10.15.0", "scripts": { "analyze": "ANALYZE=true next build", "build": "next build && next-image-export-optimizer", @@ -22,9 +22,17 @@ "validate:snippets": "node scripts/validate-snippets.js" }, "dependencies": { + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/commands": "^6.3.3", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/language": "^6.10.0", + "@codemirror/lint": "^6.8.5", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.24.0", "@graphql-tools/schema": "10.0.25", "@headlessui/react": "^2.2.4", "@igorkowalczyk/is-browser": "^5.1.0", + "@lezer/highlight": "^1.2.1", "@next/bundle-analyzer": "^15.4.5", "@radix-ui/react-radio-group": "^1.2.2", "@sparticuz/chromium": "^138.0.2", @@ -34,8 +42,7 @@ "autoprefixer": "^10.4.20", "calendar-link": "^2.10.0", "clsx": "^2.1.1", - "codemirror": "^5.65.19", - "codemirror-graphql": "1.3.2", + "cm6-graphql": "^0.2.1", "date-fns": "^2.30.0", "fast-glob": "^3.3.3", "github-slugger": "2.0.0", @@ -46,9 +53,8 @@ "leaflet": "^1.9.4", "lucide-react": "^0.469.0", "markdown-to-jsx": "^7.7.2", - "marked": "5.1.2", "motion": "^12.11.0", - "next": "^14.2.22", + "next": "^14.2.32", "next-image-export-optimizer": "^1.18.0", "next-query-params": "^5.0.1", "next-sitemap": "^4.2.3", @@ -88,7 +94,7 @@ "@types/codemirror": "5.60.16", "@types/hast": "3.0.4", "@types/node": "^22.10.5", - "@types/react": "^18.3.18", + "@types/react": "^18.3.23", "@types/rss": "0.0.32", "@types/string-similarity": "^4.0.2", "@typescript-eslint/eslint-plugin": "7.18.0", @@ -106,7 +112,7 @@ "remark-lint-first-heading-level": "3.1.2", "remark-lint-heading-increment": "3.1.2", "tsx": "^4.19.4", - "typescript": "^5.8.3" + "typescript": "^5.9.2" }, "browserslist": [ "chrome >0 and last 2.5 years", @@ -122,6 +128,9 @@ "nextra": "patches/nextra.patch", "nextra-theme-docs": "patches/nextra-theme-docs.patch", "mermaid-isomorphic": "patches/mermaid-isomorphic.patch" - } + }, + "onlyBuiltDependencies": [ + "esbuild" + ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d0939e77a..58f507aaba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,19 +6,40 @@ settings: patchedDependencies: mermaid-isomorphic: - hash: tt5abewtk4kmqtpqfe2f44hnpa + hash: fccadc7038719bcf9dc12a573655719edaf7ea8246bd144c660191d05b38c637 path: patches/mermaid-isomorphic.patch nextra: - hash: 6ssvmycplmexou7j5jtwzrnhmy + hash: ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d path: patches/nextra.patch nextra-theme-docs: - hash: 3vafyhtna4g6ghudqadsxldexi + hash: db05bf9d86002253cd072795bad24938011273dded5221e22840e1c2e439b3e5 path: patches/nextra-theme-docs.patch importers: .: dependencies: + '@codemirror/autocomplete': + specifier: ^6.18.6 + version: 6.18.6 + '@codemirror/commands': + specifier: ^6.3.3 + version: 6.8.1 + '@codemirror/lang-json': + specifier: ^6.0.1 + version: 6.0.2 + '@codemirror/language': + specifier: ^6.10.0 + version: 6.11.3 + '@codemirror/lint': + specifier: ^6.8.5 + version: 6.8.5 + '@codemirror/state': + specifier: ^6.4.0 + version: 6.5.2 + '@codemirror/view': + specifier: ^6.24.0 + version: 6.38.1 '@graphql-tools/schema': specifier: 10.0.25 version: 10.0.25(graphql@16.10.0) @@ -28,6 +49,9 @@ importers: '@igorkowalczyk/is-browser': specifier: ^5.1.0 version: 5.1.0(tailwindcss@3.4.17) + '@lezer/highlight': + specifier: ^1.2.1 + version: 1.2.1 '@next/bundle-analyzer': specifier: ^15.4.5 version: 15.5.0 @@ -55,12 +79,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 - codemirror: - specifier: ^5.65.19 - version: 5.65.20 - codemirror-graphql: - specifier: 1.3.2 - version: 1.3.2(@codemirror/language@0.20.2)(codemirror@5.65.20)(graphql@16.10.0) + cm6-graphql: + specifier: ^0.2.1 + version: 0.2.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.3)(@codemirror/lint@6.8.5)(@codemirror/state@6.5.2)(@codemirror/view@6.38.1)(@lezer/highlight@1.2.1)(graphql@16.10.0) date-fns: specifier: ^2.30.0 version: 2.30.0 @@ -91,14 +112,11 @@ importers: markdown-to-jsx: specifier: ^7.7.2 version: 7.7.13(react@18.3.1) - marked: - specifier: 5.1.2 - version: 5.1.2 motion: specifier: ^12.11.0 version: 12.23.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: ^14.2.22 + specifier: ^14.2.32 version: 14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-image-export-optimizer: specifier: ^1.18.0 @@ -114,10 +132,10 @@ importers: version: 3.0.1(less-loader@12.3.0(less@4.4.1))(less@4.4.1)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) nextra: specifier: 3.3.1 - version: 3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + version: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) nextra-theme-docs: specifier: 3.3.1 - version: 3.3.1(patch_hash=3vafyhtna4g6ghudqadsxldexi)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.3.1(patch_hash=db05bf9d86002253cd072795bad24938011273dded5221e22840e1c2e439b3e5)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) numbro: specifier: 2.5.0 version: 2.5.0 @@ -181,10 +199,6 @@ importers: use-query-params: specifier: ^2.2.1 version: 2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optionalDependencies: - playwright: - specifier: ^1.54.2 - version: 1.55.0 devDependencies: '@graphql-eslint/eslint-plugin': specifier: 4.4.0 @@ -208,7 +222,7 @@ importers: specifier: ^22.10.5 version: 22.17.2 '@types/react': - specifier: ^18.3.18 + specifier: ^18.3.23 version: 18.3.24 '@types/rss': specifier: 0.0.32 @@ -262,8 +276,12 @@ importers: specifier: ^4.19.4 version: 4.20.4 typescript: - specifier: ^5.8.3 + specifier: ^5.9.2 version: 5.9.2 + optionalDependencies: + playwright: + specifier: ^1.54.2 + version: 1.55.0 packages: @@ -867,14 +885,26 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - '@codemirror/language@0.20.2': - resolution: {integrity: sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==} + '@codemirror/autocomplete@6.18.6': + resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} + + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} - '@codemirror/state@0.20.1': - resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==} + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} - '@codemirror/view@0.20.7': - resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==} + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/lint@6.8.5': + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.38.1': + resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==} '@corex/deepmerge@4.0.43': resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==} @@ -1394,14 +1424,20 @@ packages: '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} - '@lezer/common@0.16.1': - resolution: {integrity: sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==} + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} - '@lezer/highlight@0.16.0': - resolution: {integrity: sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==} + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} - '@lezer/lr@0.16.3': - resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==} + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} '@mdx-js/mdx@3.1.0': resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} @@ -2537,15 +2573,16 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - codemirror-graphql@1.3.2: - resolution: {integrity: sha512-glwFsEVlH5TvxjSKGymZ1sNy37f3Mes58CB4fXOd0zy9+JzDL08Wti1b5ycy4vFZYghMDK1/Or/zRSjMAGtC2w==} + cm6-graphql@0.2.1: + resolution: {integrity: sha512-FIAFHn6qyiXChTz3Pml0NgTM8LyyXs8QfP2iPG7MLA8Xi83WuVlkGG5PDs+DDeEVabHkLIZmcyNngQlxLXKk6A==} peerDependencies: - '@codemirror/language': ^0.20.0 - codemirror: ^5.65.3 - graphql: ^15.5.0 || ^16.0.0 - - codemirror@5.65.20: - resolution: {integrity: sha512-i5dLDDxwkFCbhjvL2pNjShsojoL3XHyDwsGv1jqETUoW+lzpBKKqNTUWgQwVAOa0tUm4BwekT455ujafi8payA==} + '@codemirror/autocomplete': ^6.0.0 + '@codemirror/language': ^6.0.0 + '@codemirror/lint': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/highlight': ^1.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 codsen-utils@1.6.8: resolution: {integrity: sha512-8GESpsIuEsprHKuTWen1KO221gxyA+PDUXSZq0ywo5G5IcqmPtqTOw5BAljQUz2GdOe8TcWcH896qYb8gdxHFA==} @@ -2630,6 +2667,9 @@ packages: typescript: optional: true + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-inspect@1.0.1: resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} engines: {node: '>=16.0.0'} @@ -4006,11 +4046,6 @@ packages: engines: {node: '>= 20'} hasBin: true - marked@5.1.2: - resolution: {integrity: sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==} - engines: {node: '>= 16'} - hasBin: true - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -6606,20 +6641,48 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@codemirror/language@0.20.2': + '@codemirror/autocomplete@6.18.6': dependencies: - '@codemirror/state': 0.20.1 - '@codemirror/view': 0.20.7 - '@lezer/common': 0.16.1 - '@lezer/highlight': 0.16.0 - '@lezer/lr': 0.16.3 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.1': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/json': 1.0.3 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 style-mod: 4.1.2 - '@codemirror/state@0.20.1': {} + '@codemirror/lint@6.8.5': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + crelt: 1.0.6 - '@codemirror/view@0.20.7': + '@codemirror/state@6.5.2': dependencies: - '@codemirror/state': 0.20.1 + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.38.1': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 style-mod: 4.1.2 w3c-keyname: 2.2.8 @@ -7153,15 +7216,23 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lezer/common@0.16.1': {} + '@lezer/common@1.2.3': {} + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 - '@lezer/highlight@0.16.0': + '@lezer/json@1.0.3': dependencies: - '@lezer/common': 0.16.1 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 - '@lezer/lr@0.16.3': + '@lezer/lr@1.4.2': dependencies: - '@lezer/common': 0.16.1 + '@lezer/common': 1.2.3 + + '@marijn/find-cluster-break@1.0.2': {} '@mdx-js/mdx@3.1.0(acorn@8.15.0)': dependencies: @@ -8394,15 +8465,17 @@ snapshots: clsx@2.1.1: {} - codemirror-graphql@1.3.2(@codemirror/language@0.20.2)(codemirror@5.65.20)(graphql@16.10.0): + cm6-graphql@0.2.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.3)(@codemirror/lint@6.8.5)(@codemirror/state@6.5.2)(@codemirror/view@6.38.1)(@lezer/highlight@1.2.1)(graphql@16.10.0): dependencies: - '@codemirror/language': 0.20.2 - codemirror: 5.65.20 + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.8.5 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/highlight': 1.2.1 graphql: 16.10.0 graphql-language-service: 5.5.0(graphql@16.10.0) - codemirror@5.65.20: {} - codsen-utils@1.6.8: dependencies: rfdc: 1.4.1 @@ -8483,6 +8556,8 @@ snapshots: optionalDependencies: typescript: 5.9.2 + crelt@1.0.6: {} + cross-inspect@1.0.1: dependencies: tslib: 2.8.1 @@ -10097,8 +10172,6 @@ snapshots: marked@16.2.0: {} - marked@5.1.2: {} - math-intrinsics@1.1.0: {} mathjax-full@3.2.2: @@ -10302,7 +10375,7 @@ snapshots: merge2@1.4.1: {} - mermaid-isomorphic@3.0.4(patch_hash=tt5abewtk4kmqtpqfe2f44hnpa)(playwright@1.55.0): + mermaid-isomorphic@3.0.4(patch_hash=fccadc7038719bcf9dc12a573655719edaf7ea8246bd144c660191d05b38c637)(playwright@1.55.0): dependencies: '@fortawesome/fontawesome-free': 6.7.2 mermaid: 11.10.0 @@ -10765,7 +10838,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.3.1(patch_hash=3vafyhtna4g6ghudqadsxldexi)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.3.1(patch_hash=db05bf9d86002253cd072795bad24938011273dded5221e22840e1c2e439b3e5)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 @@ -10773,13 +10846,13 @@ snapshots: flexsearch: 0.7.43 next: 14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + nextra: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.25.76 - nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2): + nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.24)(acorn@8.15.0)(next@14.2.32(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2): dependencies: '@formatjs/intl-localematcher': 0.5.10 '@headlessui/react': 2.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11372,7 +11445,7 @@ snapshots: '@types/hast': 3.0.4 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - mermaid-isomorphic: 3.0.4(patch_hash=tt5abewtk4kmqtpqfe2f44hnpa)(playwright@1.55.0) + mermaid-isomorphic: 3.0.4(patch_hash=fccadc7038719bcf9dc12a573655719edaf7ea8246bd144c660191d05b38c637)(playwright@1.55.0) mini-svg-data-uri: 1.4.4 space-separated-tokens: 2.0.2 unified: 11.0.5 diff --git a/scripts/sync-sched/speakers.json b/scripts/sync-sched/speakers.json index e5021cd76c..42ecdf690b 100644 --- a/scripts/sync-sched/speakers.json +++ b/scripts/sync-sched/speakers.json @@ -156,7 +156,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282673957 + "~syncedDetailsAt": 1756487485564 }, { "username": "ajhingran", @@ -1573,7 +1573,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487482717 }, { "username": "jens63", @@ -1719,7 +1719,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "kamilkisiela", @@ -1749,7 +1749,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "kbahl", @@ -2116,7 +2116,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "marion84", @@ -2246,7 +2246,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "matteo.collina1", @@ -2266,7 +2266,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "mauricio.montalvo.guzman", @@ -2282,7 +2282,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583283 + "~syncedDetailsAt": 1756487482717 }, { "username": "meenakshi.dhanani1", @@ -2357,7 +2357,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583283 + "~syncedDetailsAt": 1756487482717 }, { "username": "michael.astle", @@ -2442,7 +2442,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583283 + "~syncedDetailsAt": 1756487482717 }, { "username": "patrick.arminio", @@ -2550,7 +2550,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "rachit_sengupta", @@ -2665,7 +2665,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583284 + "~syncedDetailsAt": 1756487482717 }, { "username": "robrichard87", @@ -2681,7 +2681,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583284 + "~syncedDetailsAt": 1756487482717 }, { "username": "ruben.cagnie", @@ -2712,7 +2712,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583284 + "~syncedDetailsAt": 1756487482717 }, { "username": "saihaj", @@ -2802,7 +2802,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "sasha177", @@ -2965,7 +2965,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "spencer211", @@ -3002,7 +3002,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583284 + "~syncedDetailsAt": 1756487482717 }, { "username": "stefan239", @@ -3288,7 +3288,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282583284 + "~syncedDetailsAt": 1756487482717 }, { "username": "vincent.desmares", @@ -3338,7 +3338,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1756282583284 + "~syncedDetailsAt": 1756487482717 }, { "username": "watson17", @@ -3354,7 +3354,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1756282610669 + "~syncedDetailsAt": 1756487485564 }, { "username": "x65han", diff --git a/src/_design-system/mdx-components/index.tsx b/src/_design-system/mdx-components/index.tsx index bec4faee2c..b3876d35be 100644 --- a/src/_design-system/mdx-components/index.tsx +++ b/src/_design-system/mdx-components/index.tsx @@ -1,7 +1,19 @@ +import { Pre, PreProps } from "../../components/pre" import { getMdxHeadings } from "./get-mdx-headings" import { MdxLink } from "./mdx-link" +const MdxPre = (props: PreProps) => { + return ( +
+  )
+}
+
 export const mdxComponents = {
   a: MdxLink,
+  pre: MdxPre,
   ...getMdxHeadings(),
 }
diff --git a/src/_design-system/syntax/dark.json b/src/_design-system/syntax/dark.json
index 6897a3fe5b..a3d613278a 100644
--- a/src/_design-system/syntax/dark.json
+++ b/src/_design-system/syntax/dark.json
@@ -1,5 +1,5 @@
 {
-  "name": "k-colorable dark, based on GitHub Dark",
+  "name": "k-colorable-dark",
   "type": "dark",
   "semanticHighlighting": true,
   "colors": {
@@ -191,7 +191,7 @@
     {
       "scope": ["comment", "punctuation.definition.comment", "string.comment"],
       "settings": {
-        "foreground": "#6a737d"
+        "foreground": "#737373"
       }
     },
     {
diff --git a/src/_design-system/syntax/light.json b/src/_design-system/syntax/light.json
index 781f2ea182..a6b8f371ff 100644
--- a/src/_design-system/syntax/light.json
+++ b/src/_design-system/syntax/light.json
@@ -1,5 +1,5 @@
 {
-  "name": "k-colorable light, based on Min Light",
+  "name": "k-colorable-light",
   "type": "light",
   "semanticHighlighting": true,
   "colors": {
@@ -159,12 +159,6 @@
         "foreground": "#2b5581"
       }
     },
-    {
-      "scope": ["comment", "string.quoted.docstring.multi"],
-      "settings": {
-        "foreground": "#c2c3c5"
-      }
-    },
     {
       "scope": [
         "constant.numeric",
@@ -322,6 +316,46 @@
       "settings": {
         "foreground": "#990069"
       }
+    },
+    {
+      "scope": ["meta.diff.header.git"],
+      "settings": {
+        "fontStyle": "underline bold"
+      }
+    },
+    {
+      "scope": ["meta.diff.index.git", "meta.diff.range"],
+      "settings": {
+        "foreground": "#6E7557",
+        "fontStyle": "italic"
+      }
+    },
+    {
+      "scope": ["meta.diff.header.from-file"],
+      "settings": { "foreground": "#BD0026", "fontStyle": "italic" }
+    },
+    {
+      "scope": ["meta.diff.header.to-file"],
+      "settings": { "foreground": "#558900", "fontStyle": "italic" }
+    },
+    {
+      "scope": ["markup.deleted.diff"],
+      "settings": { "foreground": "#B31D28" }
+    },
+    {
+      "scope": ["markup.inserted.diff"],
+      "settings": { "foreground": "#558900" }
+    },
+    {
+      "scope": [
+        "comment",
+        "string.quoted.docstring.multi",
+        "punctuation.definition.comment"
+      ],
+      "settings": {
+        "foreground": "#9FA88A",
+        "fontStyle": "italic"
+      }
     }
   ]
 }
diff --git a/src/codemirror.less b/src/codemirror.less
deleted file mode 100644
index f1cdee39d9..0000000000
--- a/src/codemirror.less
+++ /dev/null
@@ -1,896 +0,0 @@
-@import "variables.less";
-
-/* Code Mirror */
-
-/* BASICS */
-
-.CodeMirror {
-  /* Set height, width, borders, and global font properties here */
-  height: 300px;
-  .code-font();
-}
-
-/* PADDING */
-
-.CodeMirror-lines {
-  padding: 4px 0; /* Vertical padding around content */
-}
-.CodeMirror pre {
-  padding: 0 4px; /* Horizontal padding of content */
-}
-
-.CodeMirror-scrollbar-filler,
-.CodeMirror-gutter-filler {
-  background-color: white; /* The little square between H and V scrollbars */
-}
-
-/* GUTTER */
-
-.CodeMirror-gutters {
-  border-right: 1px solid #ddd;
-  background-color: #f7f7f7;
-  white-space: nowrap;
-}
-.CodeMirror-linenumbers {
-}
-.CodeMirror-linenumber {
-  padding: 0 3px 0 5px;
-  min-width: 20px;
-  text-align: right;
-  color: #999;
-  white-space: nowrap;
-}
-
-.CodeMirror-guttermarker {
-  color: black;
-}
-.CodeMirror-guttermarker-subtle {
-  color: #999;
-}
-
-/* CURSOR */
-
-.CodeMirror-cursor {
-  border-left: 1px solid black;
-  border-right: none;
-  width: 0;
-}
-/* Shown when moving in bi-directional text */
-.CodeMirror div.CodeMirror-secondarycursor {
-  border-left: 1px solid silver;
-}
-.cm-fat-cursor .CodeMirror-cursor {
-  width: auto;
-  border: 0;
-  background: #7e7;
-}
-.cm-fat-cursor div.CodeMirror-cursors {
-  z-index: 1;
-}
-
-.cm-animate-fat-cursor {
-  width: auto;
-  border: 0;
-  -webkit-animation: blink 1.06s steps(1) infinite;
-  -moz-animation: blink 1.06s steps(1) infinite;
-  animation: blink 1.06s steps(1) infinite;
-  background-color: #7e7;
-}
-@-moz-keyframes blink {
-  0% {
-  }
-  50% {
-    background-color: transparent;
-  }
-  100% {
-  }
-}
-@-webkit-keyframes blink {
-  0% {
-  }
-  50% {
-    background-color: transparent;
-  }
-  100% {
-  }
-}
-@keyframes blink {
-  0% {
-  }
-  50% {
-    background-color: transparent;
-  }
-  100% {
-  }
-}
-
-/* Can style cursor different in overwrite (non-insert) mode */
-.CodeMirror-overwrite .CodeMirror-cursor {
-}
-
-.cm-tab {
-  display: inline-block;
-  text-decoration: inherit;
-}
-
-.CodeMirror-ruler {
-  border-left: 1px solid #ccc;
-  position: absolute;
-}
-
-/* DEFAULT THEME */
-
-.cm-s-default .cm-header {
-  color: blue;
-}
-.cm-s-default .cm-quote {
-  color: #090;
-}
-.cm-negative {
-  color: #d44;
-}
-.cm-positive {
-  color: #292;
-}
-.cm-header,
-.cm-strong {
-  font-weight: bold;
-}
-.cm-em {
-  font-style: italic;
-}
-.cm-link {
-  text-decoration: underline;
-}
-.cm-strikethrough {
-  text-decoration: line-through;
-}
-
-.cm-s-default .cm-keyword {
-  color: #708;
-}
-.cm-s-default .cm-atom {
-  color: #219;
-}
-.cm-s-default .cm-number {
-  color: #164;
-}
-.cm-s-default .cm-def {
-  color: #00f;
-}
-.cm-s-default .cm-variable,
-.cm-s-default .cm-punctuation,
-.cm-s-default .cm-property,
-.cm-s-default .cm-operator {
-}
-.cm-s-default .cm-variable-2 {
-  color: #05a;
-}
-.cm-s-default .cm-variable-3 {
-  color: #085;
-}
-.cm-s-default .cm-comment {
-  color: #a50;
-}
-.cm-s-default .cm-string {
-  color: #a11;
-}
-.cm-s-default .cm-string-2 {
-  color: #f50;
-}
-.cm-s-default .cm-meta {
-  color: #555;
-}
-.cm-s-default .cm-qualifier {
-  color: #555;
-}
-.cm-s-default .cm-builtin {
-  color: #30a;
-}
-.cm-s-default .cm-bracket {
-  color: #997;
-}
-.cm-s-default .cm-tag {
-  color: #170;
-}
-.cm-s-default .cm-attribute {
-  color: #00c;
-}
-.cm-s-default .cm-hr {
-  color: #999;
-}
-.cm-s-default .cm-link {
-  color: #00c;
-}
-
-.cm-s-default .cm-error {
-  color: #f00;
-}
-.cm-invalidchar {
-  color: #f00;
-}
-
-.CodeMirror-composing {
-  border-bottom: 2px solid;
-}
-
-/* Default styles for common addons */
-
-div.CodeMirror span.CodeMirror-matchingbracket {
-  color: #0f0;
-}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {
-  color: #f22;
-}
-.CodeMirror-matchingtag {
-  background: rgba(255, 150, 0, 0.3);
-}
-.CodeMirror-activeline-background {
-  background: #e8f2ff;
-}
-
-/* STOP */
-
-/* The rest of this file contains styles related to the mechanics of
-   the editor. You probably shouldn't touch them. */
-
-.CodeMirror {
-  position: relative;
-  overflow: hidden;
-  background: white;
-}
-
-.CodeMirror-scroll {
-  overflow: scroll !important; /* Things will break if this is overridden */
-  /* 30px is the magic margin used to hide the element's real scrollbars */
-  /* See overflow: hidden in .CodeMirror */
-  margin-bottom: -30px;
-  margin-right: -30px;
-  padding-bottom: 30px;
-  height: 100%;
-  outline: none; /* Prevent dragging from highlighting the element */
-  position: relative;
-}
-.CodeMirror-sizer {
-  position: relative;
-  border-right: 30px solid transparent;
-}
-
-/* The fake, visible scrollbars. Used to force redraw during scrolling
-   before actuall scrolling happens, thus preventing shaking and
-   flickering artifacts. */
-.CodeMirror-vscrollbar,
-.CodeMirror-hscrollbar,
-.CodeMirror-scrollbar-filler,
-.CodeMirror-gutter-filler {
-  position: absolute;
-  z-index: 6;
-  display: none;
-}
-.CodeMirror-scroll {
-  overflow: hidden;
-}
-.CodeMirror-scrollbar-filler {
-  right: 0;
-  bottom: 0;
-}
-.CodeMirror-gutter-filler {
-  left: 0;
-  bottom: 0;
-}
-
-.CodeMirror-gutters {
-  position: absolute;
-  left: 0;
-  top: 0;
-  z-index: 3;
-}
-.CodeMirror-gutter {
-  white-space: normal;
-  height: 100%;
-  display: inline-block;
-  margin-bottom: -30px;
-  /* Hack to make IE7 behave */
-  *zoom: 1;
-  *display: inline;
-}
-.CodeMirror-gutter-wrapper {
-  position: absolute;
-  z-index: 4;
-  background: none !important;
-  border: none !important;
-}
-.CodeMirror-gutter-background {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  z-index: 4;
-}
-.CodeMirror-gutter-elt {
-  position: absolute;
-  cursor: default;
-  z-index: 4;
-}
-.CodeMirror-gutter-wrapper {
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  user-select: none;
-}
-
-.CodeMirror-lines {
-  cursor: text;
-  min-height: 1px; /* prevents collapsing before first draw */
-}
-.CodeMirror pre {
-  /* Reset some styles that the rest of the page might have set */
-  -moz-border-radius: 0;
-  -webkit-border-radius: 0;
-  border-radius: 0;
-  border-width: 0;
-  background: transparent;
-  font-family: inherit;
-  font-size: inherit;
-  margin: 0;
-  white-space: pre;
-  word-wrap: normal;
-  line-height: inherit;
-  color: inherit;
-  z-index: 2;
-  position: relative;
-  overflow: visible;
-  -webkit-tap-highlight-color: transparent;
-}
-.CodeMirror-wrap pre {
-  word-wrap: break-word;
-  white-space: pre-wrap;
-  word-break: normal;
-}
-
-.CodeMirror-linebackground {
-  position: absolute;
-  left: 0;
-  right: 0;
-  top: 0;
-  bottom: 0;
-  z-index: 0;
-}
-
-.CodeMirror-linewidget {
-  position: relative;
-  z-index: 2;
-  overflow: auto;
-}
-
-.CodeMirror-widget {
-}
-
-.CodeMirror-code {
-  outline: none;
-}
-
-/* Force content-box sizing for the elements where we expect it */
-.CodeMirror-scroll,
-.CodeMirror-sizer,
-.CodeMirror-gutter,
-.CodeMirror-gutters,
-.CodeMirror-linenumber {
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-}
-
-.CodeMirror-measure {
-  position: absolute;
-  width: 100%;
-  height: 0;
-  overflow: hidden;
-  visibility: hidden;
-}
-
-.CodeMirror-cursor {
-  position: absolute;
-}
-.CodeMirror-measure pre {
-  position: static;
-}
-
-div.CodeMirror-cursors {
-  visibility: hidden;
-  position: relative;
-  z-index: 3;
-}
-div.CodeMirror-dragcursors {
-  visibility: visible;
-}
-
-.CodeMirror-focused div.CodeMirror-cursors {
-  visibility: visible;
-}
-
-.CodeMirror-selected {
-  background: #d9d9d9;
-}
-.CodeMirror-focused .CodeMirror-selected {
-  background: #d7d4f0;
-}
-.CodeMirror-crosshair {
-  cursor: crosshair;
-}
-.CodeMirror-line::selection,
-.CodeMirror-line > span::selection,
-.CodeMirror-line > span > span::selection {
-  background: #d7d4f0;
-}
-.CodeMirror-line::-moz-selection,
-.CodeMirror-line > span::-moz-selection,
-.CodeMirror-line > span > span::-moz-selection {
-  background: #d7d4f0;
-}
-
-.cm-searching {
-  background: #ffa;
-  background: rgba(255, 255, 0, 0.4);
-}
-
-/* IE7 hack to prevent it from returning funny offsetTops on the spans */
-.CodeMirror span {
-  *vertical-align: text-bottom;
-}
-
-/* Used to force a border model for a node */
-.cm-force-border {
-  padding-right: 0.1px;
-}
-
-@media print {
-  /* Hide the cursor when printing */
-  .CodeMirror div.CodeMirror-cursors {
-    visibility: hidden;
-  }
-}
-
-/* See issue #2901 */
-.cm-tab-wrap-hack:after {
-  content: "";
-}
-
-/* Help users use markselection to safely style text background */
-span.CodeMirror-selectedtext {
-  background: none;
-}
-
-/* Fold */
-
-.CodeMirror-foldmarker {
-  color: blue;
-  text-shadow:
-    #b9f 1px 1px 2px,
-    #b9f -1px -1px 2px,
-    #b9f 1px -1px 2px,
-    #b9f -1px 1px 2px;
-  font-family: arial;
-  line-height: 0.3;
-  cursor: pointer;
-}
-.CodeMirror-foldgutter {
-  width: 0.7em;
-}
-.CodeMirror-foldgutter-open,
-.CodeMirror-foldgutter-folded {
-  cursor: pointer;
-}
-.CodeMirror-foldgutter-open:after {
-  content: "\25BE";
-}
-.CodeMirror-foldgutter-folded:after {
-  content: "\25B8";
-}
-
-/* Fold override */
-
-.CodeMirror-foldmarker {
-  border-radius: 4px;
-  background: #08f;
-  background: -webkit-linear-gradient(#43a8ff, #0f83e8);
-  background: linear-gradient(#43a8ff, #0f83e8);
-
-  color: white;
-  -webkit-box-shadow:
-    0 1px 1px rgba(0, 0, 0, 0.2),
-    inset 0 0 0 1px rgba(0, 0, 0, 0.1);
-  -moz-box-shadow:
-    0 1px 1px rgba(0, 0, 0, 0.2),
-    inset 0 0 0 1px rgba(0, 0, 0, 0.1);
-  box-shadow:
-    0 1px 1px rgba(0, 0, 0, 0.2),
-    inset 0 0 0 1px rgba(0, 0, 0, 0.1);
-  font-family: arial;
-  line-height: 0;
-  padding: 0px 4px 1px;
-  font-size: 12px;
-  margin: 0 3px;
-  text-shadow: 0 -1px rgba(0, 0, 0, 0.1);
-}
-
-/* Lint */
-
-/* The lint marker gutter */
-.CodeMirror-lint-markers {
-  width: 16px;
-}
-
-.CodeMirror-lint-tooltip {
-  background-color: infobackground;
-  border: 1px solid black;
-  border-radius: 4px 4px 4px 4px;
-  .code-font();
-  overflow: hidden;
-  padding: 2px 5px;
-  position: fixed;
-  white-space: pre;
-  white-space: pre-wrap;
-  z-index: 100;
-  max-width: 600px;
-  opacity: 0;
-  transition: opacity 0.4s;
-  -moz-transition: opacity 0.4s;
-  -webkit-transition: opacity 0.4s;
-  -o-transition: opacity 0.4s;
-  -ms-transition: opacity 0.4s;
-}
-
-.CodeMirror-lint-mark-error,
-.CodeMirror-lint-mark-warning {
-  background-position: left bottom;
-  background-repeat: repeat-x;
-}
-
-.CodeMirror-lint-mark-error {
-  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
-}
-
-.CodeMirror-lint-mark-warning {
-  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
-}
-
-.CodeMirror-lint-marker-error,
-.CodeMirror-lint-marker-warning {
-  background-position: center center;
-  background-repeat: no-repeat;
-  cursor: pointer;
-  display: inline-block;
-  height: 16px;
-  width: 16px;
-  vertical-align: middle;
-  position: relative;
-}
-
-.CodeMirror-lint-message-error,
-.CodeMirror-lint-message-warning {
-  padding-left: 18px;
-  background-position: top left;
-  background-repeat: no-repeat;
-}
-
-.CodeMirror-lint-marker-error,
-.CodeMirror-lint-message-error {
-  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
-}
-
-.CodeMirror-lint-marker-warning,
-.CodeMirror-lint-message-warning {
-  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
-}
-
-.CodeMirror-lint-marker-multiple {
-  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
-  background-repeat: no-repeat;
-  background-position: right bottom;
-  width: 100%;
-  height: 100%;
-}
-
-/* Hint */
-
-.CodeMirror-hints {
-  background: white;
-  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  .code-font();
-  list-style: none;
-  margin: 0;
-  margin-left: -6px;
-  max-height: 14.5em;
-  overflow-y: auto;
-  overflow: hidden;
-  padding: 0;
-  position: absolute;
-  z-index: 10;
-}
-
-.CodeMirror-hints-wrapper {
-  background: white;
-  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  margin-left: -6px;
-  position: absolute;
-  z-index: 10;
-}
-
-.CodeMirror-hints-wrapper .CodeMirror-hints {
-  -webkit-box-shadow: none;
-  -moz-box-shadow: none;
-  box-shadow: none;
-  position: relative;
-  margin-left: 0;
-  z-index: 0;
-}
-
-.CodeMirror-hint {
-  border-top: solid 1px #f7f7f7;
-  color: #333;
-  cursor: pointer;
-  margin: 0;
-  max-width: 300px;
-  overflow: hidden;
-  padding: 2px 6px;
-  white-space: pre;
-}
-
-li.CodeMirror-hint-active {
-  background-color: #08f;
-  border-top-color: white;
-  color: white;
-}
-
-.CodeMirror-hint-information {
-  border-top: solid 1px #c0c0c0;
-  max-width: 300px;
-  padding: 4px 6px;
-  position: relative;
-  z-index: 1;
-}
-
-.CodeMirror-hint-information:first-child {
-  border-bottom: solid 1px #c0c0c0;
-  border-top: none;
-  margin-bottom: -1px;
-}
-
-/* Custom typeahead */
-
-.CodeMirror-hint-information .content {
-  -webkit-box-orient: vertical;
-  display: -webkit-box;
-  .body-font(@size: 15px);
-  -webkit-line-clamp: 3;
-  max-height: 48px;
-  overflow: hidden;
-  text-overflow: -o-ellipsis-lastline;
-}
-
-.CodeMirror-hint-information .content p:first-child {
-  margin-top: 0;
-}
-
-.CodeMirror-hint-information .content p:last-child {
-  margin-bottom: 0;
-}
-
-.CodeMirror-hint-information .infoType {
-  color: #30a;
-  margin-right: 0.5em;
-  display: inline;
-}
-
-/* Lint override */
-
-div.CodeMirror-lint-tooltip {
-  background-color: white;
-  color: #333;
-  border: 0;
-  border-radius: 2px;
-  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
-  .body-font(@size: 15px);
-  padding: 6px 10px;
-  opacity: 0;
-  transition: opacity 0.15s;
-  -moz-transition: opacity 0.15s;
-  -webkit-transition: opacity 0.15s;
-  -o-transition: opacity 0.15s;
-  -ms-transition: opacity 0.15s;
-}
-
-div.CodeMirror-lint-message-error,
-div.CodeMirror-lint-message-warning {
-  padding-left: 23px;
-}
-
-/* Brackets override */
-
-div.CodeMirror span.CodeMirror-matchingbracket {
-  color: #555;
-  text-decoration: underline;
-}
-
-div.CodeMirror span.CodeMirror-nonmatchingbracket {
-  color: #f00;
-}
-
-/* Theme */
-
-/* COLORS */
-
-/* Comment */
-.cm-comment {
-  color: #999;
-}
-
-/* Punctuation */
-.cm-punctuation {
-  color: #555;
-}
-
-/* Keyword */
-.cm-keyword {
-  color: #b11a04;
-}
-
-/* OperationName, FragmentName */
-.cm-def {
-  color: #d2054e;
-}
-
-/* FieldName */
-.cm-property {
-  color: #1f61a0;
-}
-
-/* FieldAlias */
-.cm-qualifier {
-  color: #1c92a9;
-}
-
-/* ArgumentName and ObjectFieldName */
-.cm-attribute {
-  color: #8b2bb9;
-}
-
-/* Number */
-.cm-number {
-  color: #2882f9;
-}
-
-/* String */
-.cm-string {
-  color: #d64292;
-}
-
-/* Boolean */
-.cm-builtin {
-  color: #d47509;
-}
-
-/* EnumValue */
-.cm-string-2 {
-  color: #0b7fc7;
-}
-
-/* Variable */
-.cm-variable {
-  color: #397d13;
-}
-
-/* Directive */
-.cm-meta {
-  color: #b33086;
-}
-
-/* Type */
-.cm-atom {
-  color: #ca9800;
-}
-
-/* CM override */
-
-.CodeMirror {
-  .code-font();
-}
-
-.miniGraphiQL {
-  margin: 28px 0;
-  color: #333;
-  width: 100%;
-  display: -webkit-flex;
-  display: flex;
-  -webkit-flex-direction: row;
-  flex-direction: row;
-  position: relative;
-
-  background: white;
-  box-shadow:
-    inset 0 0 0 1px #eee,
-    inset 4px 0 0 #eee;
-  border-radius: 3px;
-  margin-left: -4px;
-
-  .editor-name {
-    position: sticky;
-    display: block;
-    padding: 0.5rem 0.75rem;
-    border-bottom: 1px solid rgb(209, 213, 219);
-    background-color: rgb(243, 244, 246);
-    color: rgb(55, 65, 81);
-    font-size: 0.8rem;
-    font-weight: 600;
-
-    .dark & {
-      border-bottom-color: rgb(23, 23, 23);
-      background-color: rgb(8, 8, 8);
-      color: rgb(209, 213, 219);
-      font-weight: 400;
-    }
-  }
-}
-
-.query-editor .CodeMirror {
-  height: auto;
-  min-height: 100px;
-  margin: 0px 7px 35px;
-  background: none;
-}
-
-.query-editor {
-  width: 50%;
-}
-
-.hasVariables {
-  width: 50%;
-
-  .query-editor {
-    width: auto;
-  }
-
-  .query-editor .CodeMirror {
-    margin-bottom: 21px;
-  }
-
-  .variable-editor .CodeMirror {
-    height: auto;
-    min-height: 30px;
-    margin: 0 7px;
-    background: none;
-  }
-}
-
-.result-window {
-  /*background: #fbfafa;*/
-  box-shadow: inset 5px 0px 4px -3px rgba(0, 0, 0, 0.2);
-
-  position: absolute;
-  left: 50%;
-  top: 0;
-  bottom: 0;
-  height: 100%;
-  right: 0;
-
-  box-shadow: inset 0 0 0 1px #eee;
-  border-radius: 3px;
-}
-
-.result-window .CodeMirror {
-  background: none;
-  height: 100%;
-  padding-bottom: 45px;
-  margin: 0 7px;
-  box-sizing: border-box;
-}
-
-.query-editor,
-.variable-editor,
-.result-window {
-  overflow: hidden;
-}
diff --git a/src/components/index-page/graphql-advantages/precision.tsx b/src/components/index-page/graphql-advantages/precision.tsx
index fdbe24d1bb..214f507d5b 100644
--- a/src/components/index-page/graphql-advantages/precision.tsx
+++ b/src/components/index-page/graphql-advantages/precision.tsx
@@ -96,19 +96,19 @@ export function PrecisionFigure() {
       className="nextra-codeblocks flex w-full max-w-[100vw] bg-gradient-to-b from-transparent to-sec-lighter px-[14px] py-[30px] *:w-1/2 dark:to-sec-darker/25 max-[380px]:px-0 sm:max-w-[calc(100vw-32px)] xl:px-[46px] max-[380px]:[&_:is(.rounded-t-md,pre)]:rounded-none [&_pre]:!h-48"
       aria-hidden
     >
-      
+      
         {"{"}
         {"\n  "}
-        {"hero"}
+        {"hero"}
         {" {"}
-        
+        
           {"\n    name"}
         
         {"\n    height\n    mass".split("").map((char, i) => (
           
             {char === "\n" ? 
: char}
diff --git a/src/components/index-page/how-it-works.tsx b/src/components/index-page/how-it-works.tsx index e20aea999f..20842ac2e1 100644 --- a/src/components/index-page/how-it-works.tsx +++ b/src/components/index-page/how-it-works.tsx @@ -12,6 +12,13 @@ export function HowItWorks() {

A GraphQL Query

    } /> + + {/* TODO: Instead of importing CodeB and CodeC, we'll refactor MiniGraphiQL and dynamically import it here. + Required changes: + - [ ] Move VariableEditor and QueryEditor to separate files. + - [ ] Import them here with the raw code snippets. + + */} } /> } />
diff --git a/src/components/index-page/quotes-from-the-industry/index.tsx b/src/components/index-page/quotes-from-the-industry/index.tsx index 026bcdd3e2..03395ff537 100644 --- a/src/components/index-page/quotes-from-the-industry/index.tsx +++ b/src/components/index-page/quotes-from-the-industry/index.tsx @@ -4,7 +4,7 @@ import { } from "@/app/conf/2025/components/testimonials" import { SectionLabel } from "@/app/conf/_design-system/section-label" -import mateoCollina from "./mateo-collina.webp" +import matteoCollina from "./matteo-collina.webp" const testimonials: Testimonial[] = [ { @@ -13,7 +13,7 @@ const testimonials: Testimonial[] = [ author: { name: "Matteo Collina", role: "Platformatic, Co-Founder & CTO", - avatar: mateoCollina.src, + avatar: matteoCollina.src, }, }, { @@ -35,32 +35,11 @@ const testimonials: Testimonial[] = [ }, { quote: - "GraphQL is evolving to new use cases every day and it's really a competitive advantage to experience them first hand with everyone that matters.", + "The rich ecosystem of powerful tooling enables companies and organizations to tap into the GraphQL hivemind, delivering delightful, long-lived APIs rapidly without sacrificing performance or scalability. From solo developers to distributed teams in huge companies, GraphQL has proven itself time and time again - if you need a solid, performant, and low-maintenance API for your mobile and web apps, look no further!", author: { - name: "Vincent Desmares", - role: "Teamstarter, CTO", - avatar: - "https://avatars.sched.co/d/cc/21066875/avatar.jpg.320x320px.jpg?f80", - }, - }, - { - quote: - "GraphQL is evolving to new use cases every day and it's really a competitive advantage to experience them first hand with everyone that matters.", - author: { - name: "Vincent Desmares", - role: "Teamstarter, CTO", - avatar: - "https://avatars.sched.co/d/cc/21066875/avatar.jpg.320x320px.jpg?f80", - }, - }, - { - quote: - "GraphQL is evolving to new use cases every day and it's really a competitive advantage to experience them first hand with everyone that matters.", - author: { - name: "Vincent Desmares", - role: "Teamstarter, CTO", - avatar: - "https://avatars.sched.co/d/cc/21066875/avatar.jpg.320x320px.jpg?f80", + name: "Benjie Gillam", + role: "Graphile, Director", + avatar: "https://avatars.sched.co/b/99/18743846/avatar.jpg.320x320px.jpg", }, }, ] diff --git a/src/components/index-page/quotes-from-the-industry/mateo-collina.webp b/src/components/index-page/quotes-from-the-industry/matteo-collina.webp similarity index 100% rename from src/components/index-page/quotes-from-the-industry/mateo-collina.webp rename to src/components/index-page/quotes-from-the-industry/matteo-collina.webp diff --git a/src/components/interactive-code-block/codemirror-theme.tsx b/src/components/interactive-code-block/codemirror-theme.tsx new file mode 100644 index 0000000000..622e66cdff --- /dev/null +++ b/src/components/interactive-code-block/codemirror-theme.tsx @@ -0,0 +1,136 @@ +import { EditorView } from "@codemirror/view" +import { Extension } from "@codemirror/state" +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language" +import { tags as t } from "@lezer/highlight" + +export const editorTheme = EditorView.theme({ + "&": { + color: "var(--cm-foreground)", + backgroundColor: "var(--cm-background)", + }, + + ".cm-content": { + caretColor: "var(--cm-cursor)", + }, + + ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--cm-cursor)" }, + "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": + { backgroundColor: "var(--cm-selection)" }, + + ".cm-panels": { + backgroundColor: "var(--cm-background)", + color: "var(--cm-foreground)", + }, + ".cm-panels.cm-panels-top": { + borderBottom: "2px solid var(--cm-gutter-border)", + }, + ".cm-panels.cm-panels-bottom": { + borderTop: "2px solid var(--cm-gutter-border)", + }, + + ".cm-searchMatch": { + backgroundColor: "#72a1ff59", + outline: "1px solid #457dff", + }, + ".cm-searchMatch.cm-searchMatch-selected": { + backgroundColor: "#6199ff2f", + }, + + ".cm-activeLine": { backgroundColor: "rgba(255, 255, 255, 0.05)" }, + ".cm-selectionMatch": { backgroundColor: "#aafe661a" }, + + "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { + backgroundColor: "#bad0f847", + }, + + ".cm-gutters": { + backgroundColor: "var(--cm-gutter-background)", + color: "var(--cm-line-number)", + border: "none", + borderRight: "1px solid var(--cm-gutter-border)", + }, + + ".cm-activeLineGutter": { + backgroundColor: "var(--cm-gutter-background)", + }, + + ".cm-foldPlaceholder": { + backgroundColor: "transparent", + border: "none", + color: "#ddd", + }, + + ".cm-tooltip": { + border: "none", + backgroundColor: "var(--cm-hints-background)", + color: "var(--cm-hints-foreground)", + }, + ".cm-tooltip .cm-tooltip-arrow:before": { + borderTopColor: "transparent", + borderBottomColor: "transparent", + }, + ".cm-tooltip .cm-tooltip-arrow:after": { + borderTopColor: "var(--cm-hints-background)", + borderBottomColor: "var(--cm-hints-background)", + }, + ".cm-tooltip-autocomplete": { + "& > ul > li[aria-selected]": { + backgroundColor: "var(--cm-hints-active-background)", + color: "var(--cm-hints-active-foreground)", + }, + }, +}) + +export const syntaxTheme = HighlightStyle.define([ + { tag: t.keyword, class: "cm-keyword" }, + { + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + class: "cm-def", + }, + { tag: [t.function(t.variableName), t.labelName], class: "cm-variable" }, + { tag: [t.color, t.constant(t.name), t.standard(t.name)], class: "cm-atom" }, + { tag: [t.definition(t.name), t.separator], class: "cm-def" }, + { + tag: [ + t.typeName, + t.className, + t.number, + t.changed, + t.annotation, + t.modifier, + t.self, + t.namespace, + ], + class: "cm-atom", + }, + { + tag: [ + t.operator, + t.operatorKeyword, + t.url, + t.escape, + t.regexp, + t.link, + t.special(t.string), + ], + class: "cm-punctuation", + }, + { tag: [t.meta, t.comment], class: "cm-comment" }, + { tag: t.strong, fontWeight: "bold" }, + { tag: t.emphasis, fontStyle: "italic" }, + { tag: t.strikethrough, textDecoration: "line-through" }, + { tag: t.link, class: "cm-comment", textDecoration: "underline" }, + { tag: t.heading, fontWeight: "bold", class: "cm-def" }, + { tag: [t.atom, t.bool, t.special(t.variableName)], class: "cm-atom" }, + { tag: [t.processingInstruction, t.string, t.inserted], class: "cm-string" }, + { tag: t.invalid, class: "cm-invalidchar" }, + { + tag: t.punctuation, + class: "cm-punctuation", + }, +]) + +export const codeMirrorThemeExtension: Extension = [ + editorTheme, + syntaxHighlighting(syntaxTheme), +] diff --git a/src/components/interactive-code-block/get-variable-to-type.tsx b/src/components/interactive-code-block/get-variable-to-type.tsx new file mode 100644 index 0000000000..dc2924395d --- /dev/null +++ b/src/components/interactive-code-block/get-variable-to-type.tsx @@ -0,0 +1,30 @@ +import { GraphQLSchema, parse, typeFromAST } from "graphql" + +export function getVariableToType(schema: GraphQLSchema, documentStr: string) { + if (!documentStr || !schema) { + return {} + } + + try { + const documentAST = parse(documentStr) + const variableToType = Object.create(null) + documentAST.definitions.forEach(definition => { + if (definition.kind === "OperationDefinition") { + const variableDefinitions = definition.variableDefinitions + if (variableDefinitions) { + variableDefinitions.forEach(({ variable, type }) => { + const inputType = typeFromAST(schema, type) + if (inputType) { + variableToType[variable.name.value] = inputType + } + }) + } + } + }) + return variableToType + } catch (e) { + // ignore + } + + return {} +} diff --git a/src/components/interactive-code-block/index.tsx b/src/components/interactive-code-block/index.tsx new file mode 100644 index 0000000000..e856fdcd0b --- /dev/null +++ b/src/components/interactive-code-block/index.tsx @@ -0,0 +1,5 @@ +import dynamic from "next/dynamic" + +export const InteractiveCodeBlock = dynamic(() => import("./mini-graphiQL"), { + ssr: true, +}) diff --git a/src/components/interactive-code-block/mini-graphiQL.tsx b/src/components/interactive-code-block/mini-graphiQL.tsx new file mode 100644 index 0000000000..9addc0c6f4 --- /dev/null +++ b/src/components/interactive-code-block/mini-graphiQL.tsx @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { Component } from "react" + +import { graphql, GraphQLSchema } from "graphql" + +import { QueryEditor } from "./query-editor" +import { VariableEditor } from "./variable-editor" +import { ResultViewer } from "./result-viewer" +import { getVariableToType } from "./get-variable-to-type" +import { StarWarsSchema } from "./swapi-schema" +import { UsersSchema } from "./users-schema" +import { CodeBlockLabel } from "@/components/pre/code-block-label" + +export type MiniGraphiQLProps = { + children: string +} + +interface MiniGraphiQLState { + query: string + variables: string + response: string | null + variableToType: Record +} + +const SCHEMA_MAP = { + StarWars: StarWarsSchema, + Users: UsersSchema, +} as const + +type SchemaKey = keyof typeof SCHEMA_MAP + +type Metadata = { + graphiql?: boolean + variables?: unknown + schema?: SchemaKey +} + +export default class MiniGraphiQL extends Component< + MiniGraphiQLProps, + MiniGraphiQLState +> { + // Lifecycle + + _editorQueryID = 0 + + schema: GraphQLSchema + + constructor(props: MiniGraphiQLProps) { + super(props) + + const codeMatch = this.props.children.match(/```graphql\s*\n([\s\S]*?)```/) + const blockContent = codeMatch?.[1] + const [firstLine, ...rest] = (blockContent || "").split("\n") + + const metaMatch = firstLine.match(/^\s*#\s*({.*})\s*$/)?.[1] ?? "{}" + const meta = JSON.parse(metaMatch) as Metadata + + const query = rest.join("\n").replace(/^\s+/, "") + const variables = meta.variables + ? JSON.stringify(meta.variables, null, 2) + : "" + this.schema = SCHEMA_MAP[meta.schema ?? "StarWars"] + + this.state = { + query: query, + variables: variables, + response: null, + variableToType: getVariableToType(this.schema, query), + } + } + + render() { + const editor = ( +
+ + +
+ ) + + return ( +
+ {Object.keys(this.state.variableToType).length > 0 ? ( +
+ {editor} +
+ + void this._runQuery.bind(this)} + /> +
+
+ ) : ( + editor + )} +
+ + +
+
+ ) + } + + componentDidMount() { + this._runQueryFromEditor() + } + + // Private methods + + _runQueryFromEditor() { + this.setState({ + variableToType: getVariableToType(this.schema, this.state.query), + }) + this._runQuery({ manual: true }) + } + + async _runQuery(options: { manual: boolean }) { + this._editorQueryID++ + const queryID = this._editorQueryID + try { + const result = await graphql({ + schema: this.schema, + source: this.state.query, + variableValues: JSON.parse(this.state.variables || "{}"), + }) + + let resultToSerialize: any = result + if (result.errors) { + if (!options.manual) { + // if the query was ran on edit, we display errors on the left side + // so we can just return instead of showing the resulting error + return + } + + // Convert errors to serializable format + const serializedErrors = result.errors.map(error => ({ + message: error.message, + locations: error.locations, + path: error.path, + })) + // Replace errors with serialized version for JSON.stringify + resultToSerialize = { ...result, errors: serializedErrors } + } + + if (queryID === this._editorQueryID) { + this.setState({ response: JSON.stringify(resultToSerialize, null, 2) }) + } + } catch (error) { + if (queryID === this._editorQueryID) { + this.setState({ response: JSON.stringify(error, null, 2) }) + } + } + } + + _handleEditQuery(value: string) { + this.setState({ query: value }) + void this._runQuery({ manual: false }) + } + + _handleEditVariables(value: string) { + this.setState({ variables: value }) + } +} diff --git a/src/components/interactive-code-block/on-has-completion.tsx b/src/components/interactive-code-block/on-has-completion.tsx new file mode 100644 index 0000000000..6bcbf066d4 --- /dev/null +++ b/src/components/interactive-code-block/on-has-completion.tsx @@ -0,0 +1,14 @@ +/** + * Note: This file is retained for compatibility but is no longer used with CodeMirror 6. + * CodeMirror 6 and cm6-graphql handle completions differently and don't require this custom UI. + */ + +export function onHasCompletion( + cm: any, + data: any, + onHintInformationRender?: any, +) { + // This function is no longer used with CodeMirror 6 + // Left as a stub for backwards compatibility + return +} diff --git a/src/components/interactive-code-block/query-editor.tsx b/src/components/interactive-code-block/query-editor.tsx new file mode 100644 index 0000000000..65ec0e2e22 --- /dev/null +++ b/src/components/interactive-code-block/query-editor.tsx @@ -0,0 +1,150 @@ +import { Component } from "react" +import { EditorView, keymap } from "@codemirror/view" +import { EditorState } from "@codemirror/state" +import { history, historyKeymap, defaultKeymap } from "@codemirror/commands" +import { bracketMatching } from "@codemirror/language" +import { + autocompletion, + closeBrackets, + completionKeymap, +} from "@codemirror/autocomplete" +import { graphql, updateSchema } from "cm6-graphql" +import { GraphQLSchema } from "graphql" +import { codeMirrorThemeExtension } from "./codemirror-theme" +import "./syntax-highlighting.css" + +interface QueryEditorProps { + schema?: GraphQLSchema + value?: string + onEdit?: (value: string) => void + runQuery?: () => void + onHintInformationRender?: (el: HTMLElement) => void +} + +/** + * QueryEditor + * + * Maintains an instance of CodeMirror 6 responsible for editing a GraphQL query. + * + * Props: + * + * - schema: A GraphQLSchema instance enabling editor linting and hinting. + * - value: The text of the editor. + * - onEdit: A function called when the editor changes, given the edited text. + * + */ +export class QueryEditor extends Component { + private view: EditorView | null = null + private domNode: HTMLDivElement | null = null + private cachedValue: string + private ignoreChangeEvent = false + + constructor(props: QueryEditorProps) { + super(props) + + // Keep a cached version of the value, this cache will be updated when the + // editor is updated, which can later be used to protect the editor from + // unnecessary updates during the update lifecycle. + this.cachedValue = props.value || "" + } + + /** + * Public API for retrieving the CodeMirror instance from this + * React component. + */ + getCodeMirror() { + return this.view + } + + componentDidMount() { + if (!this.domNode) return + + const runQueryBinding = keymap.of([ + { + key: "Cmd-Enter", + run: () => (this.props.runQuery?.(), true), + }, + { + key: "Ctrl-Enter", + run: () => (this.props.runQuery?.(), true), + }, + ]) + + const state = EditorState.create({ + doc: this.props.value || "", + extensions: [ + history(), + closeBrackets(), + bracketMatching(), + keymap.of([...historyKeymap, ...completionKeymap, ...defaultKeymap]), + runQueryBinding, + codeMirrorThemeExtension, + graphql(this.props.schema, {}), + autocompletion({ + icons: false, + }), + EditorView.updateListener.of(update => { + if (update.docChanged && !this.ignoreChangeEvent) { + this.cachedValue = update.state.doc.toString() + if (this.props.onEdit) { + this.props.onEdit(this.cachedValue) + } + } + }), + ], + }) + + // Create editor view + this.view = new EditorView({ + state, + parent: this.domNode, + }) + } + + componentWillUnmount() { + if (this.view) { + this.view.destroy() + this.view = null + } + } + + componentDidUpdate(prevProps: QueryEditorProps) { + if (!this.view) return + + // Ensure the changes caused by this update are not interpreted as + // user-input changes which could otherwise result in an infinite + // event loop. + this.ignoreChangeEvent = true + + if (this.props.schema !== prevProps.schema && this.props.schema) { + updateSchema(this.view, this.props.schema) + } + + if ( + this.props.value !== prevProps.value && + this.props.value !== this.cachedValue + ) { + this.cachedValue = this.props.value || "" + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: this.props.value || "", + }, + }) + } + + this.ignoreChangeEvent = false + } + + render() { + return ( +
{ + this.domNode = e + }} + /> + ) + } +} diff --git a/src/components/interactive-code-block/result-viewer.tsx b/src/components/interactive-code-block/result-viewer.tsx new file mode 100644 index 0000000000..d6aab400f1 --- /dev/null +++ b/src/components/interactive-code-block/result-viewer.tsx @@ -0,0 +1,81 @@ +import { Component } from "react" +import { EditorView } from "@codemirror/view" +import { EditorState } from "@codemirror/state" +// todo: perhaps custom grammar to match the shiki highlighting? +import { json } from "@codemirror/lang-json" +import { codeMirrorThemeExtension } from "./codemirror-theme" + +import "./syntax-highlighting.css" + +interface ResultViewerProps { + value?: string +} + +/** + * ResultViewer + * + * Maintains an instance of CodeMirror 6 for viewing a GraphQL response. + * + * Props: + * + * - value: The text of the editor. + * + */ +export class ResultViewer extends Component { + private view: EditorView | null = null + private domNode: HTMLDivElement | null = null + + componentDidMount() { + if (!this.domNode) return + + // Create read-only editor state for JSON results + const state = EditorState.create({ + doc: this.props.value || "", + extensions: [ + EditorState.readOnly.of(true), + json(), + codeMirrorThemeExtension, + ], + }) + + // Create editor view + this.view = new EditorView({ + state, + parent: this.domNode, + }) + } + + componentWillUnmount() { + if (this.view) { + this.view.destroy() + this.view = null + } + } + + shouldComponentUpdate(nextProps: ResultViewerProps) { + return this.props.value !== nextProps.value + } + + componentDidUpdate() { + if (!this.view) return + + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: this.props.value || "", + }, + }) + } + + render() { + return ( +
{ + this.domNode = e + }} + /> + ) + } +} diff --git a/src/components/marked/swapi-schema.tsx b/src/components/interactive-code-block/swapi-schema.tsx similarity index 100% rename from src/components/marked/swapi-schema.tsx rename to src/components/interactive-code-block/swapi-schema.tsx diff --git a/src/components/interactive-code-block/syntax-highlighting.css b/src/components/interactive-code-block/syntax-highlighting.css new file mode 100644 index 0000000000..90ff291459 --- /dev/null +++ b/src/components/interactive-code-block/syntax-highlighting.css @@ -0,0 +1,314 @@ +:root { + --cm-comment: #6e7557; + --cm-punctuation: #6e7557; + --cm-keyword: #990069; + --cm-def: hsl(var(--color-pri-base)); + --cm-property: #0e0f0b; + --cm-qualifier: #1c92a9; + --cm-attribute: #8b2bb9; + --cm-number: #1976d2; + --cm-string: #609a06; + --cm-builtin: #1976d2; + --cm-string-2: #0b7fc7; + --cm-variable: #e10198; + --cm-meta: #b33086; + --cm-atom: #1976d2; + + --cm-background: #ffffff; + --cm-foreground: #4f533f; + --cm-gutter-background: #f7f7f7; + --cm-gutter-border: #ddd; + --cm-line-number: #999; + --cm-cursor: #000000; + --cm-selection: #d7d4f0; + --cm-hints-background: hsl(var(--color-neu-0) / 0.8); + --cm-hints-foreground: #080707; + --cm-hints-active-background: hsl(var(--color-pri-base) / 0.1); + --cm-hints-active-foreground: #ffffff; +} + +.dark { + /* Dark theme colors (matching Shiki dark.json) */ + --cm-comment: #737373; + --cm-punctuation: #6e7557; + --cm-keyword: #c2f653; + --cm-def: #dbf6a2; + --cm-property: #dbf6a2; + --cm-qualifier: #1c92a9; + --cm-attribute: #8b2bb9; + --cm-number: #79b8ff; + --cm-string: #9ecbff; + --cm-builtin: #79b8ff; + --cm-string-2: #0b7fc7; + --cm-variable: #dbf6a2; + --cm-meta: #ff99e0; + --cm-atom: #79b8ff; + + /* Editor UI colors - dark theme */ + --cm-background: hsl(var(--color-neu-800) / 0.025); + --cm-foreground: #cfd3c5; + --cm-gutter-background: #1f2425; + --cm-gutter-border: #1b1f20; + --cm-line-number: #737373; + --cm-cursor: #c8e1ff; + --cm-selection: #3392ff44; + --cm-hints-foreground: #e1e4e8; + --cm-hints-active-background: hsl(var(--color-sec-base) / 0.1); + --cm-hints-active-foreground: #ffffff; +} + +/* CodeMirror 6 Editor Base Styles */ +.cm-editor { + font-family: inherit; + font-size: inherit; + background: var(--cm-background); + color: var(--cm-foreground); + position: relative; + + & .cm-content { + padding: 16px 0; + } + + & .cm-line { + padding: 0 0 0 16px; + } + + & .cm-scroller { + line-height: 1.5; + } +} + +.cm-focused { + outline: none; +} + +/* Syntax Highlighting Classes */ +.cm-comment { + color: var(--cm-comment); +} + +.cm-punctuation { + color: var(--cm-punctuation); +} + +.cm-keyword { + color: var(--cm-keyword); +} + +.cm-def { + color: var(--cm-def); +} + +.cm-property { + color: var(--cm-property); +} + +.cm-qualifier { + color: var(--cm-qualifier); +} + +.cm-attribute { + color: var(--cm-attribute); +} + +.cm-number { + color: var(--cm-number); +} + +.cm-string { + color: var(--cm-string); +} + +.cm-builtin { + color: var(--cm-builtin); +} + +.cm-string-2 { + color: var(--cm-string-2); +} + +.cm-variable { + color: var(--cm-variable); +} + +.cm-meta { + color: var(--cm-meta); +} + +.cm-atom { + color: var(--cm-atom); +} + +/* Editor UI Elements */ +.cm-gutters { + border-right: 1px solid var(--cm-gutter-border); + background-color: var(--cm-gutter-background); + white-space: nowrap; +} + +.cm-gutter { + white-space: normal; + height: 100%; + display: inline-block; + margin-bottom: -30px; +} + +.cm-lineNumbers .cm-gutterElement { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: var(--cm-line-number); + white-space: nowrap; +} + +.cm-cursor { + border-left: 1px solid var(--cm-cursor); + border-right: none; + width: 0; +} + +.cm-selectionBackground { + background: var(--cm-selection); +} + +.cm-focused.cm-focused { + outline: none; +} + +.cm-focused .cm-selectionBackground { + background: var(--cm-selection); +} + +/* Autocomplete/Hints */ +.cm-editor .cm-tooltip { + @apply shadow-md backdrop-blur-[6.4px] dark:border-neu-50; + + border: 1px solid hsl(var(--color-neu-100)); + background: var(--cm-hints-background); + color: var(--cm-hints-foreground); + font-family: inherit; + font-size: inherit; + + &.cm-completionInfo-right { + @apply bg-neu-0 shadow-lg; + } +} + +.cm-tooltip-autocomplete { + background: var(--cm-hints-background); + margin-left: -6px; + position: absolute; + z-index: 10; +} + +.cm-completionLabel { + color: var(--cm-hints-foreground); +} + +.cm-editor .cm-completionDetail { + color: var(--cm-def); + font-style: normal; + font-size: 0.8em; +} + +.cm-completionMatchedText { + text-decoration: underline; +} + +.cm-completionIcon { + width: 1em; + height: 1em; + vertical-align: middle; + margin-right: 0.5em; +} + +.cm-tooltip.cm-tooltip-autocomplete > ul > li { + color: var(--cm-hints-foreground); + cursor: pointer; + margin: 0; + max-width: 300px; + overflow: hidden; + white-space: pre; + font-size: 14px; + line-height: 1.5 !important; +} + +.cm-tooltip-autocomplete li:hover { + background-color: var(--cm-hints-active-background); + color: var(--cm-hints-active-foreground); +} + +/* Lint Markers */ +.cm-lintRange { + background-position: left bottom; + background-repeat: repeat-x; +} + +.cm-lintRange-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg=="); +} + +.cm-lintRange-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); +} + +.cm-diagnostic { + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: inline-block; + height: 16px; + width: 16px; + vertical-align: middle; + position: relative; +} + +.cm-diagnostic-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); +} + +.cm-diagnostic-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); +} + +.cm-tooltip-lint { + background-color: var(--cm-hints-background); + color: var(--cm-hints-foreground); + border: none; + border-radius: 2px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); + font-family: inherit; + font-size: inherit; + padding: 6px 10px; + opacity: 0; + transition: opacity 0.15s; +} + +/* Matching brackets */ +.cm-matchingBracket { + color: #555; + text-decoration: underline; +} + +.cm-nonmatchingBracket { + color: #f00; +} + +/* Active line */ +.cm-activeLine { + background-color: rgba(0, 0, 0, 0.05); +} + +.dark .cm-activeLine { + background-color: rgba(255, 255, 255, 0.05); +} + +.cm-searchMatch { + background: #ffa; + background: rgba(255, 255, 0, 0.4); +} + +.cm-searchMatch-selected { + background: #ff8; + background: rgba(255, 255, 0, 0.6); +} diff --git a/src/components/marked/users-schema.ts b/src/components/interactive-code-block/users-schema.ts similarity index 100% rename from src/components/marked/users-schema.ts rename to src/components/interactive-code-block/users-schema.ts diff --git a/src/components/interactive-code-block/variable-editor.tsx b/src/components/interactive-code-block/variable-editor.tsx new file mode 100644 index 0000000000..89d3ab0e13 --- /dev/null +++ b/src/components/interactive-code-block/variable-editor.tsx @@ -0,0 +1,113 @@ +import { Component } from "react" +import { EditorView } from "@codemirror/view" +import { EditorState } from "@codemirror/state" +import { json } from "@codemirror/lang-json" +import { history } from "@codemirror/commands" +import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language" + +interface VariableEditorProps { + value: string + variableToType?: any + onEdit?: (value: string) => void + onRunQuery?: () => void + onHintInformationRender?: (el: HTMLElement) => void +} + +/** + * VariableEditor + * + * An instance of CodeMirror 6 for editing variables defined in QueryEditor. + * + * Props: + * + * - variableToType: A mapping of variable name to GraphQLType. + * - value: The text of the editor. + * - onEdit: A function called when the editor changes, given the edited text. + * + */ +export class VariableEditor extends Component { + private view: EditorView | null = null + private domNode: HTMLDivElement | null = null + private cachedValue: string + private ignoreChangeEvent = false + + constructor(props: VariableEditorProps) { + super(props) + + // Keep a cached version of the value, this cache will be updated when the + // editor is updated, which can later be used to protect the editor from + // unnecessary updates during the update lifecycle. + this.cachedValue = props.value || "" + } + + componentDidMount() { + if (!this.domNode) return + + // Create editor state for JSON (variables are JSON) + const state = EditorState.create({ + doc: this.props.value || "", + extensions: [ + history(), + json(), + syntaxHighlighting(defaultHighlightStyle), + EditorView.updateListener.of(update => { + if (update.docChanged && !this.ignoreChangeEvent) { + this.cachedValue = update.state.doc.toString() + if (this.props.onEdit) { + this.props.onEdit(this.cachedValue) + } + } + }), + ], + }) + + // Create editor view + this.view = new EditorView({ + state, + parent: this.domNode, + }) + } + + componentDidUpdate(prevProps: VariableEditorProps) { + if (!this.view) return + + // Ensure the changes caused by this update are not interpreted as + // user-input changes which could otherwise result in an infinite + // event loop. + this.ignoreChangeEvent = true + + if ( + this.props.value !== prevProps.value && + this.props.value !== this.cachedValue + ) { + this.cachedValue = this.props.value + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: this.props.value || "", + }, + }) + } + + this.ignoreChangeEvent = false + } + + componentWillUnmount() { + if (this.view) { + this.view.destroy() + this.view = null + } + } + + render() { + return ( +
{ + this.domNode = e + }} + /> + ) + } +} diff --git a/src/components/marked/index.tsx b/src/components/marked/index.tsx deleted file mode 100644 index 39730051bc..0000000000 --- a/src/components/marked/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import dynamic from "next/dynamic" -import { StarWarsSchema } from "./swapi-schema" -import { UsersSchema } from "./users-schema" - -const SCHEMA_MAP = { - StarWars: StarWarsSchema, - Users: UsersSchema, -} as const - -type SchemaKey = keyof typeof SCHEMA_MAP - -type Metadata = { - graphiql?: boolean - variables?: unknown - schema?: SchemaKey -} - -const MiniGraphiQL = dynamic(() => import("./mini-graphiQL"), { ssr: true }) - -export function Marked({ children }: { children: string }) { - const codeMatch = children.match(/```graphql\s*\n([\s\S]*?)```/) - const blockContent = codeMatch?.[1] - const [firstLine, ...rest] = (blockContent || "").split("\n") - // First line must contain the metadata JSON comment: # { … } - const metaMatch = firstLine.match(/^\s*#\s*({.*})\s*$/) - - if (!metaMatch) { - throw new Error( - `Invalid GraphiQL metadata JSON: ${firstLine}. MiniGraphQL shouldn't be used here.`, - ) - } - - let meta: Metadata - try { - meta = JSON.parse(metaMatch[1]) as Metadata - } catch { - throw new Error(`Invalid GraphiQL metadata JSON: ${metaMatch[1]}`) - } - - const query = rest.join("\n") - const variables = meta.variables - ? JSON.stringify(meta.variables, null, 2) - : "" - const schema = SCHEMA_MAP[meta.schema ?? "StarWars"] - - return -} diff --git a/src/components/marked/mini-graphiQL.tsx b/src/components/marked/mini-graphiQL.tsx deleted file mode 100644 index 37c1d3e8db..0000000000 --- a/src/components/marked/mini-graphiQL.tsx +++ /dev/null @@ -1,608 +0,0 @@ -// @ts-nocheck -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { Component } from "react" -import { marked } from "marked" - -import { graphql, formatError, parse, typeFromAST } from "graphql" - -export type MiniGraphiQLProps = { - schema: GraphQLSchema - query: string - variables: string -} - -export default class MiniGraphiQL extends Component { - // Lifecycle - - constructor(props) { - super() - const query = props.query.replace(/^\s+/, "") - - // Initialize state - this.state = { - query: query, - variables: props.variables, - response: null, - variableToType: getVariableToType(props.schema, query), - } - - this._editorQueryID = 0 - } - - render() { - const editor = ( - - ) - - return ( -
- {Object.keys(this.state.variableToType).length > 0 ? ( -
- {editor} - -
- ) : ( - editor - )} - -
- ) - } - - componentDidMount() { - this._runQueryFromEditor() - } - - // Private methods - - _runQueryFromEditor() { - this.setState({ - variableToType: getVariableToType(this.props.schema, this.state.query), - }) - this._runQuery() - } - - async _runQuery() { - this._editorQueryID++ - const queryID = this._editorQueryID - try { - const result = await graphql({ - schema: this.props.schema, - source: this.state.query, - variableValues: JSON.parse(this.state.variables || "{}"), - rootValue: this.props.rootValue, - }) - - if (result.errors) { - result.errors = result.errors.map(formatError) - } - - if (queryID === this._editorQueryID) { - this.setState({ response: JSON.stringify(result, null, 2) }) - } - } catch (error) { - if (queryID === this._editorQueryID) { - this.setState({ response: JSON.stringify(error, null, 2) }) - } - } - } - - _handleEditQuery(value) { - this.setState({ query: value }) - } - - _handleEditVariables(value) { - this.setState({ variables: value }) - } -} - -/** - * QueryEditor - * - * Maintains an instance of CodeMirror responsible for editing a GraphQL query. - * - * Props: - * - * - schema: A GraphQLSchema instance enabling editor linting and hinting. - * - value: The text of the editor. - * - onEdit: A function called when the editor changes, given the edited text. - * - */ -class QueryEditor extends Component { - constructor(props) { - super() - - // Keep a cached version of the value, this cache will be updated when the - // editor is updated, which can later be used to protect the editor from - // unnecessary updates during the update lifecycle. - this.cachedValue = props.value || "" - } - - /** - * Public API for retrieving the CodeMirror instance from this - * React component. - */ - getCodeMirror() { - return this.editor - } - - componentDidMount() { - const CodeMirror = require("codemirror") - require("codemirror/addon/hint/show-hint") - require("codemirror/addon/comment/comment") - require("codemirror/addon/edit/matchbrackets") - require("codemirror/addon/edit/closebrackets") - require("codemirror/addon/lint/lint") - require("codemirror/keymap/sublime") - require("codemirror-graphql/hint") - require("codemirror-graphql/lint") - require("codemirror-graphql/mode") - - this.editor = CodeMirror(this.domNode, { - value: this.props.value || "", - viewportMargin: Infinity, - tabSize: 2, - mode: "graphql", - theme: "graphiql", - keyMap: "sublime", - autoCloseBrackets: true, - matchBrackets: true, - showCursorWhenSelecting: true, - lint: { - schema: this.props.schema, - onUpdateLinting: this._didLint.bind(this), - }, - hintOptions: { - schema: this.props.schema, - closeOnUnfocus: true, - completeSingle: false, - }, - extraKeys: { - "Cmd-Space": () => this.editor.showHint({ completeSingle: false }), - "Ctrl-Space": () => this.editor.showHint({ completeSingle: false }), - "Alt-Space": () => this.editor.showHint({ completeSingle: false }), - "Shift-Space": () => this.editor.showHint({ completeSingle: false }), - - "Cmd-Enter": () => { - if (this.props.onRunQuery) { - this.props.onRunQuery() - } - }, - "Ctrl-Enter": () => { - if (this.props.onRunQuery) { - this.props.onRunQuery() - } - }, - - // Editor improvements - "Ctrl-Left": "goSubwordLeft", - "Ctrl-Right": "goSubwordRight", - "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", - }, - }) - - this.editor.on("change", this._onEdit.bind(this)) - this.editor.on("keyup", this._onKeyUp.bind(this)) - this.editor.on("hasCompletion", this._onHasCompletion.bind(this)) - } - - componentWillUnmount() { - this.editor = null - } - - componentDidUpdate(prevProps) { - // Ensure the changes caused by this update are not interpreted as - // user-input changes which could otherwise result in an infinite - // event loop. - this.ignoreChangeEvent = true - if (this.props.schema !== prevProps.schema) { - this.editor.options.lint.schema = this.props.schema - this.editor.options.hintOptions.schema = this.props.schema - CodeMirror.signal(this.editor, "change", this.editor) - } - if ( - this.props.value !== prevProps.value && - this.props.value !== this.cachedValue - ) { - this.cachedValue = this.props.value - this.editor.setValue(this.props.value) - } - this.ignoreChangeEvent = false - } - - _didLint(annotations) { - if (annotations.length === 0) { - this.props.runQuery() - } - } - - _onKeyUp(cm, event) { - const code = event.keyCode - if ( - (code >= 65 && code <= 90) || // letters - (!event.shiftKey && code >= 48 && code <= 57) || // numbers - (event.shiftKey && code === 189) || // underscore - (event.shiftKey && code === 50) || // @ - (event.shiftKey && code === 57) // ( - ) { - this.editor.execCommand("autocomplete") - } - } - - _onEdit() { - if (!this.ignoreChangeEvent) { - this.cachedValue = this.editor.getValue() - if (this.props.onEdit) { - this.props.onEdit(this.cachedValue) - } - } - } - - _onHasCompletion(cm, data) { - onHasCompletion(cm, data, this.props.onHintInformationRender) - } - - render() { - return ( -
(this.domNode = e)}> - Operation -
- ) - } -} - -/** - * ResultViewer - * - * Maintains an instance of CodeMirror for viewing a GraphQL response. - * - * Props: - * - * - value: The text of the editor. - * - */ -class ResultViewer extends Component { - componentDidMount() { - const CodeMirror = require("codemirror") - require("codemirror-graphql/results/mode") - - this.viewer = CodeMirror(this.domNode, { - value: this.props.value || "", - viewportMargin: Infinity, - readOnly: true, - theme: "graphiql", - mode: "graphql-results", - keyMap: "sublime", - extraKeys: { - // Editor improvements - "Ctrl-Left": "goSubwordLeft", - "Ctrl-Right": "goSubwordRight", - "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", - }, - }) - } - - componentWillUnmount() { - this.viewer = null - } - - shouldComponentUpdate(nextProps) { - return this.props.value !== nextProps.value - } - - componentDidUpdate() { - this.viewer.setValue(this.props.value || "") - } - - render() { - return ( -
(this.domNode = e)}> - Response -
- ) - } -} - -/** - * VariableEditor - * - * An instance of CodeMirror for editing variables defined in QueryEditor. - * - * Props: - * - * - variableToType: A mapping of variable name to GraphQLType. - * - value: The text of the editor. - * - onEdit: A function called when the editor changes, given the edited text. - * - */ -class VariableEditor extends Component { - constructor(props) { - super() - - // Keep a cached version of the value, this cache will be updated when the - // editor is updated, which can later be used to protect the editor from - // unnecessary updates during the update lifecycle. - this.cachedValue = props.value || "" - this._onKeyUp = this.onKeyUp.bind(this) - this._onEdit = this.onEdit.bind(this) - this._onHasCompletion = this.onHasCompletion.bind(this) - } - - componentDidMount() { - // Lazily require to ensure requiring GraphiQL outside of a Browser context - // does not produce an error. - const CodeMirror = require("codemirror") - require("codemirror/addon/hint/show-hint") - require("codemirror/addon/edit/matchbrackets") - require("codemirror/addon/edit/closebrackets") - require("codemirror/addon/lint/lint") - require("codemirror/keymap/sublime") - require("codemirror-graphql/variables/hint") - require("codemirror-graphql/variables/lint") - require("codemirror-graphql/variables/mode") - - this.editor = CodeMirror(this.domNode, { - value: this.props.value || "", - viewportMargin: Infinity, - tabSize: 2, - mode: "graphql-variables", - theme: "graphiql", - keyMap: "sublime", - autoCloseBrackets: true, - matchBrackets: true, - showCursorWhenSelecting: true, - lint: { - variableToType: this.props.variableToType, - onUpdateLinting: this._didLint.bind(this), - }, - hintOptions: { - variableToType: this.props.variableToType, - }, - extraKeys: { - "Cmd-Space": () => this.editor.showHint({ completeSingle: false }), - "Ctrl-Space": () => this.editor.showHint({ completeSingle: false }), - "Alt-Space": () => this.editor.showHint({ completeSingle: false }), - "Shift-Space": () => this.editor.showHint({ completeSingle: false }), - - "Cmd-Enter"() { - if (this.props.onRunQuery) { - this.props.onRunQuery() - } - }, - "Ctrl-Enter"() { - if (this.props.onRunQuery) { - this.props.onRunQuery() - } - }, - - // Editor improvements - "Ctrl-Left": "goSubwordLeft", - "Ctrl-Right": "goSubwordRight", - "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", - }, - }) - - this.editor.on("change", this._onEdit) - this.editor.on("keyup", this._onKeyUp) - this.editor.on("hasCompletion", this._onHasCompletion) - } - - componentDidUpdate(prevProps) { - const CodeMirror = require("codemirror") - - // Ensure the changes caused by this update are not interpreted as - // user-input changes which could otherwise result in an infinite - // event loop. - this.ignoreChangeEvent = true - if (this.props.variableToType !== prevProps.variableToType) { - this.editor.options.lint.variableToType = this.props.variableToType - this.editor.options.hintOptions.variableToType = this.props.variableToType - CodeMirror.signal(this.editor, "change", this.editor) - } - if ( - this.props.value !== prevProps.value && - this.props.value !== this.cachedValue - ) { - this.cachedValue = this.props.value - this.editor.setValue(this.props.value) - } - this.ignoreChangeEvent = false - } - - componentWillUnmount() { - this.editor.off("change", this._onEdit) - this.editor.off("keyup", this._onKeyUp) - this.editor.off("hasCompletion", this._onHasCompletion) - this.editor = null - } - - render() { - return ( -
(this.domNode = e)}> - Variables -
- ) - } - - _didLint(annotations) { - if (annotations.length === 0) { - this.props.onRunQuery() - } - } - - onKeyUp(cm, event) { - const code = event.keyCode - if ( - (code >= 65 && code <= 90) || // letters - (!event.shiftKey && code >= 48 && code <= 57) || // numbers - (event.shiftKey && code === 189) || // underscore - (event.shiftKey && code === 222) // " - ) { - this.editor.execCommand("autocomplete") - } - } - - onEdit() { - if (!this.ignoreChangeEvent) { - this.cachedValue = this.editor.getValue() - if (this.props.onEdit) { - this.props.onEdit(this.cachedValue) - } - } - } - - onHasCompletion(cm, data) { - onHasCompletion(cm, data, this.props.onHintInformationRender) - } -} - -/** - * Render a custom UI for CodeMirror's hint which includes additional info - * about the type and description for the selected context. - */ -function onHasCompletion(cm, data, onHintInformationRender) { - const CodeMirror = require("codemirror") - let wrapper - let information - - // When a hint result is selected, we touch the UI. - CodeMirror.on(data, "select", (ctx, el) => { - // Only the first time (usually when the hint UI is first displayed) - // do we create the wrapping node. - if (!wrapper) { - // Wrap the existing hint UI, so we have a place to put information. - const hintsUl = el.parentNode - const container = hintsUl.parentNode - wrapper = document.createElement("div") - container.appendChild(wrapper) - - // CodeMirror vertically inverts the hint UI if there is not enough - // space below the cursor. Since this modified UI appends to the bottom - // of CodeMirror's existing UI, it could cover the cursor. This adjusts - // the positioning of the hint UI to accommodate. - let top = hintsUl.style.top - let bottom = "" - const cursorTop = cm.cursorCoords().top - if (parseInt(top, 10) < cursorTop) { - top = "" - bottom = window.innerHeight - cursorTop + 3 + "px" - } - - // Style the wrapper, remove positioning from hints. Note that usage - // of this option will need to specify CSS to remove some styles from - // the existing hint UI. - wrapper.className = "CodeMirror-hints-wrapper" - wrapper.style.left = hintsUl.style.left - wrapper.style.top = top - wrapper.style.bottom = bottom - hintsUl.style.left = "" - hintsUl.style.top = "" - - // This "information" node will contain the additional info about the - // highlighted typeahead option. - information = document.createElement("div") - information.className = "CodeMirror-hint-information" - if (bottom) { - wrapper.appendChild(information) - wrapper.appendChild(hintsUl) - } else { - wrapper.appendChild(hintsUl) - wrapper.appendChild(information) - } - - // When CodeMirror attempts to remove the hint UI, we detect that it was - // removed from our wrapper and in turn remove the wrapper from the - // original container. - let onRemoveFn - const observer = new MutationObserver(mutationsList => { - for (const mutation of mutationsList) { - // Check if the hintsUl element was removed - if (mutation.removedNodes) { - mutation.removedNodes.forEach(node => { - if (node === hintsUl) { - // Cleanup logic - observer.disconnect() // Stop observing - wrapper.parentNode.removeChild(wrapper) - wrapper = null - information = null - onRemoveFn = null - } - }) - } - } - }) - - // Start observing the wrapper for child node removals - observer.observe(wrapper, { childList: true, subtree: false }) - } - - // Now that the UI has been set up, add info to information. - const description = ctx.description - ? marked(ctx.description, { smartypants: true }) - : "Self descriptive." - const type = ctx.type - ? '' + String(ctx.type) + "" - : "" - information.innerHTML = - '
' + - (description.slice(0, 3) === "

" - ? "

" + type + description.slice(3) - : type + description) + - "

" - - // Additional rendering? - if (onHintInformationRender) { - onHintInformationRender(information) - } - }) -} - -function getVariableToType(schema, documentStr) { - if (!documentStr || !schema) { - return {} - } - - try { - const documentAST = parse(documentStr) - const variableToType = Object.create(null) - documentAST.definitions.forEach(definition => { - if (definition.kind === "OperationDefinition") { - const variableDefinitions = definition.variableDefinitions - if (variableDefinitions) { - variableDefinitions.forEach(({ variable, type }) => { - const inputType = typeFromAST(schema, type) - if (inputType) { - variableToType[variable.name.value] = inputType - } - }) - } - } - }) - return variableToType - } catch (e) { - // ignore - } - - return {} -} diff --git a/src/components/pre/code-block-label.tsx b/src/components/pre/code-block-label.tsx new file mode 100644 index 0000000000..2de78c266e --- /dev/null +++ b/src/components/pre/code-block-label.tsx @@ -0,0 +1,32 @@ +import cn from "clsx" +import type { FC, ReactElement, ReactNode } from "react" + +import classes from "./pre.module.css" + +interface CodeBlockLabelProps { + text: ReactNode + icon?: FC<{ className?: string }> + button?: ReactElement | false + className?: string +} + +export function CodeBlockLabel({ + text, + icon: Icon, + button, + className, +}: CodeBlockLabelProps): ReactElement { + return ( +
+ {Icon && } + {text} + {button} +
+ ) +} diff --git a/src/components/pre/index.tsx b/src/components/pre/index.tsx index 25bf431ff8..949dab6ddc 100644 --- a/src/components/pre/index.tsx +++ b/src/components/pre/index.tsx @@ -5,8 +5,9 @@ import { WordWrapIcon } from "nextra/icons" import { Button, CopyToClipboard } from "nextra/components" import classes from "./pre.module.css" +import { CodeBlockLabel } from "./code-block-label" -interface PreProps extends ComponentPropsWithoutRef<"pre"> { +export interface PreProps extends ComponentPropsWithoutRef<"pre"> { "data-filename"?: string "data-copy"?: "" "data-language"?: string @@ -39,20 +40,16 @@ export function Pre({ return (
{filename && ( -
- {Icon && } - {filename} - {copyButton} -
+ )}
-The examples in this guide are based on [a modified version of the SWAPI GraphQL schema](https://github.com/graphql/graphql.github.io/blob/source/src/components/marked/swapi-schema.tsx). Because these queries are designed for illustrative purposes, they will not run on the full version of the SWAPI GraphQL API due to differences between the two schemas. [You can try the full version of the API here.](https://graphql.org/swapi-graphql/)
+The examples in this guide are based on [a modified version of the SWAPI GraphQL schema](https://github.com/graphql/graphql.github.io/blob/source/src/components/interactive-code-block/swapi-schema.tsx). Because these queries are designed for illustrative purposes, they will not run on the full version of the SWAPI GraphQL API due to differences between the two schemas. [You can try the full version of the API here.](https://graphql.org/swapi-graphql/)
 
 
 ## Next steps
diff --git a/src/remark-graphiql-comment.js b/src/remark-graphiql-comment.js
index 1cce6af551..05d7ed56f2 100644
--- a/src/remark-graphiql-comment.js
+++ b/src/remark-graphiql-comment.js
@@ -1,14 +1,19 @@
 import { visit } from "unist-util-visit"
 
+const MINI_GRAPHIQL_COMPONENT = "InteractiveCodeBlock"
+const MINI_GRAPHIQL_PATH = "@/components/interactive-code-block"
+
 export const remarkGraphiQLComment = () => ast => {
   const nodes = []
 
-  const MINI_GRAPHIQL_COMPONENT = "Marked"
-
   visit(ast, { type: "code", lang: "graphql" }, node => {
+    if ((node.meta || "").split(" ").includes("graphiql")) {
+      nodes.push(node)
+      return
+    }
+
     const [firstLine] = node.value.split("\n")
-    const isGraphiQLComment = /graphiql["']?: ?true/.test(firstLine)
-    if (isGraphiQLComment) {
+    if (/graphiql["']?: ?true/.test(firstLine)) {
       nodes.push(node)
     }
   })
@@ -21,7 +26,7 @@ export const remarkGraphiQLComment = () => ast => {
           body: [
             {
               type: "ImportDeclaration",
-              source: { type: "Literal", value: "@/components/marked" },
+              source: { type: "Literal", value: MINI_GRAPHIQL_PATH },
               specifiers: [
                 {
                   type: "ImportSpecifier",