From 82d252610a9978b053debfff89e8c323884effa1 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:25:45 +0200 Subject: [PATCH 01/28] Render MiniGraphiQL with `graphiql` string in node.meta --- src/remark-graphiql-comment.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/remark-graphiql-comment.js b/src/remark-graphiql-comment.js index 1cce6af551..8524cb27c1 100644 --- a/src/remark-graphiql-comment.js +++ b/src/remark-graphiql-comment.js @@ -6,9 +6,13 @@ export const remarkGraphiQLComment = () => ast => { 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) } }) From 9adbb19cdc49c21a0f58a722da931db714d45258 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:26:49 +0200 Subject: [PATCH 02/28] Don't validate in Marked: not its responsibility --- src/components/marked/index.tsx | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/components/marked/index.tsx b/src/components/marked/index.tsx index 39730051bc..07e9eb5df9 100644 --- a/src/components/marked/index.tsx +++ b/src/components/marked/index.tsx @@ -21,21 +21,9 @@ 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 metaMatch = firstLine.match(/^\s*#\s*({.*})\s*$/)?.[1] ?? "{}" + const meta = JSON.parse(metaMatch) as Metadata const query = rest.join("\n") const variables = meta.variables From 68ec6578bd53dc3587b2497366e6c78ccdfc1edf Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:27:29 +0200 Subject: [PATCH 03/28] Change interactive code snippet colors --- src/codemirror.less | 200 +++++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 97 deletions(-) diff --git a/src/codemirror.less b/src/codemirror.less index f1cdee39d9..4c72f04b02 100644 --- a/src/codemirror.less +++ b/src/codemirror.less @@ -8,6 +8,8 @@ /* Set height, width, borders, and global font properties here */ height: 300px; .code-font(); + background: var(--cm-background); + color: var(--cm-foreground); } /* PADDING */ @@ -21,14 +23,16 @@ .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ + background-color: var( + --cm-background + ); /* The little square between H and V scrollbars */ } /* GUTTER */ .CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; + border-right: 1px solid var(--cm-gutter-border); + background-color: var(--cm-gutter-background); white-space: nowrap; } .CodeMirror-linenumbers { @@ -37,7 +41,7 @@ padding: 0 3px 0 5px; min-width: 20px; text-align: right; - color: #999; + color: var(--cm-line-number); white-space: nowrap; } @@ -51,7 +55,7 @@ /* CURSOR */ .CodeMirror-cursor { - border-left: 1px solid black; + border-left: 1px solid var(--cm-cursor); border-right: none; width: 0; } @@ -146,63 +150,6 @@ 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; } @@ -237,7 +184,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { .CodeMirror { position: relative; overflow: hidden; - background: white; + background: var(--cm-background); } .CodeMirror-scroll { @@ -407,10 +354,10 @@ div.CodeMirror-dragcursors { } .CodeMirror-selected { - background: #d9d9d9; + background: var(--cm-selection); } .CodeMirror-focused .CodeMirror-selected { - background: #d7d4f0; + background: var(--cm-selection); } .CodeMirror-crosshair { cursor: crosshair; @@ -418,12 +365,12 @@ div.CodeMirror-dragcursors { .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { - background: #d7d4f0; + background: var(--cm-selection); } .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { - background: #d7d4f0; + background: var(--cm-selection); } .cm-searching { @@ -592,7 +539,7 @@ span.CodeMirror-selectedtext { /* Hint */ .CodeMirror-hints { - background: white; + background: var(--cm-hints-background); -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); @@ -609,7 +556,7 @@ span.CodeMirror-selectedtext { } .CodeMirror-hints-wrapper { - background: white; + background: var(--cm-hints-background); -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); @@ -629,7 +576,7 @@ span.CodeMirror-selectedtext { .CodeMirror-hint { border-top: solid 1px #f7f7f7; - color: #333; + color: var(--cm-hints-foreground); cursor: pointer; margin: 0; max-width: 300px; @@ -639,9 +586,9 @@ span.CodeMirror-selectedtext { } li.CodeMirror-hint-active { - background-color: #08f; - border-top-color: white; - color: white; + background-color: var(--cm-hints-active-background); + border-top-color: var(--cm-hints-active-background); + color: var(--cm-hints-active-foreground); } .CodeMirror-hint-information { @@ -687,8 +634,8 @@ li.CodeMirror-hint-active { /* Lint override */ div.CodeMirror-lint-tooltip { - background-color: white; - color: #333; + background-color: var(--cm-hints-background); + color: var(--cm-hints-foreground); border: 0; border-radius: 2px; -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); @@ -724,74 +671,139 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { /* COLORS */ +/* CSS Custom Properties for Theme Colors */ +:root { + /* Light theme colors (matching Shiki light.json) */ + --cm-comment: #c2c3c5; + --cm-punctuation: #6e7557; + --cm-keyword: #e10198; + --cm-keyword-graphql: #990069; + --cm-def: #0e0f0b; + --cm-property: #0e0f0b; + --cm-qualifier: #1c92a9; + --cm-attribute: #8b2bb9; + --cm-number: #1976d2; + --cm-string: #2b5581; + --cm-builtin: #1976d2; + --cm-string-2: #0b7fc7; + --cm-variable: #e10198; + --cm-meta: #b33086; + --cm-atom: #1976d2; + + /* Editor UI colors - light theme */ + --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: #ffffff; + --cm-hints-foreground: #333; + --cm-hints-active-background: #08f; + --cm-hints-active-foreground: #ffffff; +} + +.dark { + /* Dark theme colors (matching Shiki dark.json) */ + --cm-comment: #6a737d; + --cm-punctuation: #6e7557; + --cm-keyword: #c2f653; + --cm-keyword-graphql: #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: #24292c; + --cm-foreground: #cfd3c5; + --cm-gutter-background: #1f2425; + --cm-gutter-border: #1b1f20; + --cm-line-number: #6a737d; + --cm-cursor: #c8e1ff; + --cm-selection: #3392ff44; + --cm-hints-background: #24292c; + --cm-hints-foreground: #e1e4e8; + --cm-hints-active-background: #044289; + --cm-hints-active-foreground: #ffffff; +} + /* Comment */ .cm-comment { - color: #999; + color: var(--cm-comment); } /* Punctuation */ .cm-punctuation { - color: #555; + color: var(--cm-punctuation); } /* Keyword */ .cm-keyword { - color: #b11a04; + color: var(--cm-keyword); } /* OperationName, FragmentName */ .cm-def { - color: #d2054e; + color: var(--cm-def); } /* FieldName */ .cm-property { - color: #1f61a0; + color: var(--cm-property); } /* FieldAlias */ .cm-qualifier { - color: #1c92a9; + color: var(--cm-qualifier); } /* ArgumentName and ObjectFieldName */ .cm-attribute { - color: #8b2bb9; + color: var(--cm-attribute); } /* Number */ .cm-number { - color: #2882f9; + color: var(--cm-number); } /* String */ .cm-string { - color: #d64292; + color: var(--cm-string); } /* Boolean */ .cm-builtin { - color: #d47509; + color: var(--cm-builtin); } /* EnumValue */ .cm-string-2 { - color: #0b7fc7; + color: var(--cm-string-2); } /* Variable */ .cm-variable { - color: #397d13; + color: var(--cm-variable); } /* Directive */ .cm-meta { - color: #b33086; + color: var(--cm-meta); } /* Type */ .cm-atom { - color: #ca9800; + color: var(--cm-atom); } /* CM override */ @@ -810,10 +822,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { flex-direction: row; position: relative; - background: white; - box-shadow: - inset 0 0 0 1px #eee, - inset 4px 0 0 #eee; + background: transparent; + border-radius: 3px; margin-left: -4px; @@ -867,9 +877,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { } .result-window { - /*background: #fbfafa;*/ - box-shadow: inset 5px 0px 4px -3px rgba(0, 0, 0, 0.2); - position: absolute; left: 50%; top: 0; @@ -877,7 +884,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { height: 100%; right: 0; - box-shadow: inset 0 0 0 1px #eee; border-radius: 3px; } From 994750c12565e7e9eea980f92f4d5c9853651711 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:28:02 +0200 Subject: [PATCH 04/28] Remove global .miniGraphiQL class --- src/globals.css | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/globals.css b/src/globals.css index 432e4f3ef1..d89c79e8a7 100644 --- a/src/globals.css +++ b/src/globals.css @@ -112,15 +112,6 @@ div[id^="headlessui-menu-items"] { } } -/*._max-w-\[90rem\] {*/ -/* !* TODO: maybe add to nextra as option to configure width? *!*/ -/* @apply container;*/ -/*}*/ - -.miniGraphiQL { - @apply !rounded-md border-2 !shadow-none dark:border-neutral-900 dark:!bg-neutral-900/10 dark:brightness-200; -} - .result-window { @apply !rounded-none border-l-2 !shadow-none dark:border-neutral-900; } From 66d6d3d3a3d4a03667270fbdb054c3173f12ed63 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:44:33 +0200 Subject: [PATCH 05/28] Refactor MiniGraphiQL to separate files --- .../marked/get-variable-to-type.tsx | 30 + src/components/marked/mini-graphiQL.tsx | 518 +----------------- src/components/marked/on-has-completion.tsx | 102 ++++ src/components/marked/query-editor.tsx | 157 ++++++ src/components/marked/result-viewer.tsx | 54 ++ src/components/marked/variable-editor.tsx | 167 ++++++ 6 files changed, 528 insertions(+), 500 deletions(-) create mode 100644 src/components/marked/get-variable-to-type.tsx create mode 100644 src/components/marked/on-has-completion.tsx create mode 100644 src/components/marked/query-editor.tsx create mode 100644 src/components/marked/result-viewer.tsx create mode 100644 src/components/marked/variable-editor.tsx diff --git a/src/components/marked/get-variable-to-type.tsx b/src/components/marked/get-variable-to-type.tsx new file mode 100644 index 0000000000..dc2924395d --- /dev/null +++ b/src/components/marked/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/marked/mini-graphiQL.tsx b/src/components/marked/mini-graphiQL.tsx index 37c1d3e8db..bdc3ee0d7d 100644 --- a/src/components/marked/mini-graphiQL.tsx +++ b/src/components/marked/mini-graphiQL.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck /** * Copyright (c) 2015, Facebook, Inc. * All rights reserved. @@ -10,7 +9,9 @@ import { Component } from "react" import { marked } from "marked" -import { graphql, formatError, parse, typeFromAST } from "graphql" +import { graphql, formatError, GraphQLSchema } from "graphql" + +import { QueryEditor } from "./query-editor" export type MiniGraphiQLProps = { schema: GraphQLSchema @@ -18,11 +19,23 @@ export type MiniGraphiQLProps = { variables: string } -export default class MiniGraphiQL extends Component { +interface MiniGraphiQLState { + query: string + variables: string + response: string | null + variableToType: Record +} + +export default class MiniGraphiQL extends Component< + MiniGraphiQLProps, + MiniGraphiQLState +> { // Lifecycle - constructor(props) { - super() + _editorQueryID = 0 + + constructor(props: MiniGraphiQLProps) { + super(props) const query = props.query.replace(/^\s+/, "") // Initialize state @@ -32,8 +45,6 @@ export default class MiniGraphiQL extends Component { response: null, variableToType: getVariableToType(props.schema, query), } - - this._editorQueryID = 0 } render() { @@ -113,496 +124,3 @@ export default class MiniGraphiQL extends Component { 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/marked/on-has-completion.tsx b/src/components/marked/on-has-completion.tsx new file mode 100644 index 0000000000..b3b18f9d6a --- /dev/null +++ b/src/components/marked/on-has-completion.tsx @@ -0,0 +1,102 @@ +/** + * Render a custom UI for CodeMirror's hint which includes additional info + * about the type and description for the selected context. + */ +export 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) + } + }) +} + diff --git a/src/components/marked/query-editor.tsx b/src/components/marked/query-editor.tsx new file mode 100644 index 0000000000..d7a31d0ca5 --- /dev/null +++ b/src/components/marked/query-editor.tsx @@ -0,0 +1,157 @@ +import { Component } from "react"; + +/** + * 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. + * + */ +export 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", // <-- here? + 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 +
+ ) + } +} diff --git a/src/components/marked/result-viewer.tsx b/src/components/marked/result-viewer.tsx new file mode 100644 index 0000000000..57328dc2d9 --- /dev/null +++ b/src/components/marked/result-viewer.tsx @@ -0,0 +1,54 @@ +/** + * ResultViewer + * + * Maintains an instance of CodeMirror for viewing a GraphQL response. + * + * Props: + * + * - value: The text of the editor. + * + */ +export class ResultViewer extends Component { + componentDidMount() { + // TODO: I don't think this does anything other than a static import in Next.js. + // We should use a dynamic import function if we want codemirror loaded lazily. + 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 +
+ ) + } +} diff --git a/src/components/marked/variable-editor.tsx b/src/components/marked/variable-editor.tsx new file mode 100644 index 0000000000..46c44cbd9b --- /dev/null +++ b/src/components/marked/variable-editor.tsx @@ -0,0 +1,167 @@ +import type CodeMirror from "codemirror"; +import { Component } from "react" +import { onHasCompletion } from "./on-has-completion"; + +interface VariableEditorProps { + value: string; +} + +/** + * 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. + * + */ +export class VariableEditor extends Component { + cachedValue: string + + _onKeyUp: (cm: CodeMirror.DocOrEditor, event: any) => void + _onEdit: () => void + _onHasCompletion: (cm: CodeMirror.DocOrEditor, event: any) => void + + 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 || "" + 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) + } +} From c7661169e4dfd751c26049400fb04f7636d8aebd Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:44:46 +0200 Subject: [PATCH 06/28] Add a TODO comment --- src/components/index-page/how-it-works.tsx | 7 +++++++ 1 file changed, 7 insertions(+) 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. + + */} } /> } />
From 4c682413b18bc5d3e5d87934541b94bbb7a33e35 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 21 Aug 2025 18:55:08 +0200 Subject: [PATCH 07/28] Run Playwright on CI --- .github/workflows/check.yml | 3 +++ 1 file changed, 3 insertions(+) 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 From 744a6b1273181f3aa357109e25c3aa62663a0b4b Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 22 Aug 2025 03:59:00 +0200 Subject: [PATCH 08/28] wip --- src/codemirror.less | 50 ++++- src/components/index-page/hero/index.tsx | 5 +- src/components/marked/mini-graphiQL.tsx | 25 ++- src/components/marked/on-has-completion.tsx | 103 +---------- src/components/marked/query-editor.tsx | 193 ++++++++++---------- src/components/marked/result-viewer.tsx | 86 ++++++--- src/components/marked/variable-editor.tsx | 184 +++++++------------ 7 files changed, 299 insertions(+), 347 deletions(-) diff --git a/src/codemirror.less b/src/codemirror.less index 4c72f04b02..d004835fb9 100644 --- a/src/codemirror.less +++ b/src/codemirror.less @@ -1,9 +1,40 @@ @import "variables.less"; -/* Code Mirror */ +/* Code Mirror 6 & Compatibility */ -/* BASICS */ +/* BASICS - CodeMirror 6 */ +.cm-editor { + /* Set height, width, borders, and global font properties here */ + height: 300px; + .code-font(); + background: var(--cm-background); + color: var(--cm-foreground); + position: relative; + overflow: hidden; +} + +.cm-scroller { + overflow: auto; + position: relative; +} +.cm-content { + min-height: 1px; + cursor: text; + padding: 4px 0; +} + +.cm-line { + padding: 0 4px; + line-height: inherit; + color: inherit; +} + +.cm-focused { + outline: none; +} + +/* Compatibility aliases for CodeMirror 5 */ .CodeMirror { /* Set height, width, borders, and global font properties here */ height: 300px; @@ -808,7 +839,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { /* CM override */ -.CodeMirror { +.CodeMirror, +.cm-editor { .code-font(); } @@ -846,7 +878,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { } } -.query-editor .CodeMirror { +.query-editor .CodeMirror, +.query-editor .cm-editor { height: auto; min-height: 100px; margin: 0px 7px 35px; @@ -864,11 +897,13 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { width: auto; } - .query-editor .CodeMirror { + .query-editor .CodeMirror, + .query-editor .cm-editor { margin-bottom: 21px; } - .variable-editor .CodeMirror { + .variable-editor .CodeMirror, + .variable-editor .cm-editor { height: auto; min-height: 30px; margin: 0 7px; @@ -887,7 +922,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket { border-radius: 3px; } -.result-window .CodeMirror { +.result-window .CodeMirror, +.result-window .cm-editor { background: none; height: 100%; padding-bottom: 45px; diff --git a/src/components/index-page/hero/index.tsx b/src/components/index-page/hero/index.tsx index ee935df106..4e1d100614 100644 --- a/src/components/index-page/hero/index.tsx +++ b/src/components/index-page/hero/index.tsx @@ -29,8 +29,9 @@ export function Hero() {
- +
diff --git a/src/components/marked/mini-graphiQL.tsx b/src/components/marked/mini-graphiQL.tsx index bdc3ee0d7d..0652fc9c8d 100644 --- a/src/components/marked/mini-graphiQL.tsx +++ b/src/components/marked/mini-graphiQL.tsx @@ -7,16 +7,19 @@ */ import { Component } from "react" -import { marked } from "marked" -import { graphql, formatError, GraphQLSchema } from "graphql" +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" export type MiniGraphiQLProps = { schema: GraphQLSchema query: string variables: string + rootValue?: any } interface MiniGraphiQLState { @@ -73,7 +76,7 @@ export default class MiniGraphiQL extends Component< ) : ( editor )} - + ) } @@ -102,12 +105,20 @@ export default class MiniGraphiQL extends Component< rootValue: this.props.rootValue, }) + let resultToSerialize: any = result if (result.errors) { - result.errors = result.errors.map(formatError) + // 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(result, null, 2) }) + this.setState({ response: JSON.stringify(resultToSerialize, null, 2) }) } } catch (error) { if (queryID === this._editorQueryID) { @@ -116,11 +127,11 @@ export default class MiniGraphiQL extends Component< } } - _handleEditQuery(value) { + _handleEditQuery(value: string) { this.setState({ query: value }) } - _handleEditVariables(value) { + _handleEditVariables(value: string) { this.setState({ variables: value }) } } diff --git a/src/components/marked/on-has-completion.tsx b/src/components/marked/on-has-completion.tsx index b3b18f9d6a..c411628553 100644 --- a/src/components/marked/on-has-completion.tsx +++ b/src/components/marked/on-has-completion.tsx @@ -1,102 +1,11 @@ /** - * Render a custom UI for CodeMirror's hint which includes additional info - * about the type and description for the selected context. + * 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, 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) - } - }) +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/marked/query-editor.tsx b/src/components/marked/query-editor.tsx index d7a31d0ca5..134b500af9 100644 --- a/src/components/marked/query-editor.tsx +++ b/src/components/marked/query-editor.tsx @@ -1,9 +1,25 @@ -import { Component } from "react"; +import { Component } from "react" +import { EditorView, keymap } from "@codemirror/view" +import { EditorState } from "@codemirror/state" +import { history, historyKeymap, defaultKeymap } from "@codemirror/commands" +import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language" +import { autocompletion, completionKeymap } from "@codemirror/autocomplete" +import { graphql, updateSchema } from "cm6-graphql" +import { GraphQLSchema } from "graphql" + +interface QueryEditorProps { + schema?: GraphQLSchema + value?: string + onEdit?: (value: string) => void + onRunQuery?: () => void + runQuery?: () => void + onHintInformationRender?: (el: HTMLElement) => void +} /** * QueryEditor * - * Maintains an instance of CodeMirror responsible for editing a GraphQL query. + * Maintains an instance of CodeMirror 6 responsible for editing a GraphQL query. * * Props: * @@ -12,9 +28,14 @@ import { Component } from "react"; * - onEdit: A function called when the editor changes, given the edited text. * */ -export class QueryEditor extends Component { - constructor(props) { - super() +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 @@ -27,129 +48,115 @@ export class QueryEditor extends Component { * React component. */ getCodeMirror() { - return this.editor + return this.view } 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", // <-- here? - 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 }), + if (!this.domNode) return - "Cmd-Enter": () => { + // Create key bindings + const runQueryBinding = keymap.of([ + { + key: "Cmd-Enter", + run: () => { if (this.props.onRunQuery) { this.props.onRunQuery() } + return true }, - "Ctrl-Enter": () => { + }, + { + key: "Ctrl-Enter", + run: () => { if (this.props.onRunQuery) { this.props.onRunQuery() } + return true }, - - // Editor improvements - "Ctrl-Left": "goSubwordLeft", - "Ctrl-Right": "goSubwordRight", - "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", }, + ]) + + // Create editor state + const state = EditorState.create({ + doc: this.props.value || "", + extensions: [ + history(), + keymap.of([...historyKeymap, ...completionKeymap, ...defaultKeymap]), + runQueryBinding, + syntaxHighlighting(defaultHighlightStyle), + graphql(this.props.schema), + autocompletion(), + EditorView.updateListener.of(update => { + if (update.docChanged && !this.ignoreChangeEvent) { + this.cachedValue = update.state.doc.toString() + if (this.props.onEdit) { + this.props.onEdit(this.cachedValue) + } + } + }), + EditorView.theme({ + ".cm-editor": { + fontSize: "inherit", + fontFamily: "inherit", + }, + ".cm-focused": { + outline: "none", + }, + }), + ], }) - this.editor.on("change", this._onEdit.bind(this)) - this.editor.on("keyup", this._onKeyUp.bind(this)) - this.editor.on("hasCompletion", this._onHasCompletion.bind(this)) + // Create editor view + this.view = new EditorView({ + state, + parent: this.domNode, + }) } componentWillUnmount() { - this.editor = null + if (this.view) { + this.view.destroy() + this.view = null + } } - componentDidUpdate(prevProps) { + 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.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() + if (this.props.schema !== prevProps.schema && this.props.schema) { + updateSchema(this.view, this.props.schema) } - } - _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.props.value !== prevProps.value && + this.props.value !== this.cachedValue ) { - this.editor.execCommand("autocomplete") - } - } - - _onEdit() { - if (!this.ignoreChangeEvent) { - this.cachedValue = this.editor.getValue() - if (this.props.onEdit) { - this.props.onEdit(this.cachedValue) - } + this.cachedValue = this.props.value || "" + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: this.props.value || "", + }, + }) } - } - _onHasCompletion(cm, data) { - onHasCompletion(cm, data, this.props.onHintInformationRender) + this.ignoreChangeEvent = false } render() { return ( -
(this.domNode = e)}> +
{ + this.domNode = e + }} + > Operation
) diff --git a/src/components/marked/result-viewer.tsx b/src/components/marked/result-viewer.tsx index 57328dc2d9..e920d28d6d 100644 --- a/src/components/marked/result-viewer.tsx +++ b/src/components/marked/result-viewer.tsx @@ -1,52 +1,88 @@ +import { Component } from "react" +import { EditorView } from "@codemirror/view" +import { EditorState } from "@codemirror/state" +import { json } from "@codemirror/lang-json" +import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language" + +interface ResultViewerProps { + value?: string +} + /** * ResultViewer * - * Maintains an instance of CodeMirror for viewing a GraphQL response. + * Maintains an instance of CodeMirror 6 for viewing a GraphQL response. * * Props: * * - value: The text of the editor. * */ -export class ResultViewer extends Component { +export class ResultViewer extends Component { + private view: EditorView | null = null + private domNode: HTMLDivElement | null = null + componentDidMount() { - // TODO: I don't think this does anything other than a static import in Next.js. - // We should use a dynamic import function if we want codemirror loaded lazily. - 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", - }, + 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), + EditorView.editable.of(false), + json(), + syntaxHighlighting(defaultHighlightStyle), + EditorView.theme({ + ".cm-editor": { + fontSize: "inherit", + fontFamily: "inherit", + }, + ".cm-focused": { + outline: "none", + }, + }), + ], + }) + + // Create editor view + this.view = new EditorView({ + state, + parent: this.domNode, }) } componentWillUnmount() { - this.viewer = null + if (this.view) { + this.view.destroy() + this.view = null + } } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps: ResultViewerProps) { return this.props.value !== nextProps.value } componentDidUpdate() { - this.viewer.setValue(this.props.value || "") + 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)}> +
{ + this.domNode = e + }} + > Response
) diff --git a/src/components/marked/variable-editor.tsx b/src/components/marked/variable-editor.tsx index 46c44cbd9b..f46e473a41 100644 --- a/src/components/marked/variable-editor.tsx +++ b/src/components/marked/variable-editor.tsx @@ -1,15 +1,22 @@ -import type CodeMirror from "codemirror"; -import { Component } from "react" -import { onHasCompletion } from "./on-has-completion"; +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 for editing variables defined in QueryEditor. + * An instance of CodeMirror 6 for editing variables defined in QueryEditor. * * Props: * @@ -19,11 +26,10 @@ interface VariableEditorProps { * */ export class VariableEditor extends Component { - cachedValue: string - - _onKeyUp: (cm: CodeMirror.DocOrEditor, event: any) => void - _onEdit: () => void - _onHasCompletion: (cm: CodeMirror.DocOrEditor, event: any) => void + private view: EditorView | null = null; + private domNode: HTMLDivElement | null = null; + private cachedValue: string; + private ignoreChangeEvent = false; constructor(props: VariableEditorProps) { super(props); @@ -31,137 +37,83 @@ export class VariableEditor extends Component { // 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) + this.cachedValue = props.value || ""; } 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() + 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); + } } - }, - "Ctrl-Enter"() { - if (this.props.onRunQuery) { - this.props.onRunQuery() + }), + EditorView.theme({ + ".cm-editor": { + fontSize: "inherit", + fontFamily: "inherit" + }, + ".cm-focused": { + outline: "none" } - }, - - // 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) + }) + ] + }); + + // Create editor view + this.view = new EditorView({ + state, + parent: this.domNode + }); } - componentDidUpdate(prevProps) { - const CodeMirror = require("codemirror") + 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.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) - } + this.ignoreChangeEvent = true; + if ( this.props.value !== prevProps.value && this.props.value !== this.cachedValue ) { - this.cachedValue = this.props.value - this.editor.setValue(this.props.value) + this.cachedValue = this.props.value; + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: this.props.value || "" + } + }); } - this.ignoreChangeEvent = false + + 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 + if (this.view) { + this.view.destroy(); + this.view = null; + } } render() { return ( -
(this.domNode = e)}> +
{ 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) + ); } } From c6568d83f185034bb357409854ee71ceb4086305 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 22 Aug 2025 05:41:40 +0200 Subject: [PATCH 09/28] color codemirrors properly --- src/{codemirror.less => codemirror-old.less} | 0 .../graphql-advantages/precision.tsx | 8 +- src/components/marked/codemirror-one-dark.tsx | 142 ++++++++ src/components/marked/mini-graphiQL.tsx | 15 +- src/components/marked/query-editor.tsx | 46 +-- src/components/marked/result-viewer.tsx | 20 +- src/components/marked/syntax-highlighting.css | 322 ++++++++++++++++++ src/pages/_app.tsx | 2 - 8 files changed, 500 insertions(+), 55 deletions(-) rename src/{codemirror.less => codemirror-old.less} (100%) create mode 100644 src/components/marked/codemirror-one-dark.tsx create mode 100644 src/components/marked/syntax-highlighting.css diff --git a/src/codemirror.less b/src/codemirror-old.less similarity index 100% rename from src/codemirror.less rename to src/codemirror-old.less 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/marked/codemirror-one-dark.tsx b/src/components/marked/codemirror-one-dark.tsx new file mode 100644 index 0000000000..4b13c05d62 --- /dev/null +++ b/src/components/marked/codemirror-one-dark.tsx @@ -0,0 +1,142 @@ +import { EditorView } from "@codemirror/view" +import { Extension } from "@codemirror/state" +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language" +import { tags as t } from "@lezer/highlight" + +/// The editor theme styles for One Dark using CSS custom properties. +export const oneDarkTheme = 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)", + }, + }, + }, + { dark: true }, +) + +/// The highlighting style for code using CSS custom properties. +export const oneDarkHighlightStyle = 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", + }, +]) + +/// Extension to enable the One Dark theme using CSS custom properties. +export const oneDark: Extension = [ + oneDarkTheme, + syntaxHighlighting(oneDarkHighlightStyle), +] diff --git a/src/components/marked/mini-graphiQL.tsx b/src/components/marked/mini-graphiQL.tsx index 0652fc9c8d..9a88067066 100644 --- a/src/components/marked/mini-graphiQL.tsx +++ b/src/components/marked/mini-graphiQL.tsx @@ -62,7 +62,7 @@ export default class MiniGraphiQL extends Component< ) return ( -
+
{Object.keys(this.state.variableToType).length > 0 ? (
{editor} @@ -91,10 +91,10 @@ export default class MiniGraphiQL extends Component< this.setState({ variableToType: getVariableToType(this.props.schema, this.state.query), }) - this._runQuery() + this._runQuery({ manual: true }) } - async _runQuery() { + async _runQuery(options: { manual: boolean }) { this._editorQueryID++ const queryID = this._editorQueryID try { @@ -107,11 +107,17 @@ export default class MiniGraphiQL extends Component< 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 + path: error.path, })) // Replace errors with serialized version for JSON.stringify resultToSerialize = { ...result, errors: serializedErrors } @@ -129,6 +135,7 @@ export default class MiniGraphiQL extends Component< _handleEditQuery(value: string) { this.setState({ query: value }) + void this._runQuery({ manual: false }) } _handleEditVariables(value: string) { diff --git a/src/components/marked/query-editor.tsx b/src/components/marked/query-editor.tsx index 134b500af9..508efa1394 100644 --- a/src/components/marked/query-editor.tsx +++ b/src/components/marked/query-editor.tsx @@ -2,16 +2,21 @@ import { Component } from "react" import { EditorView, keymap } from "@codemirror/view" import { EditorState } from "@codemirror/state" import { history, historyKeymap, defaultKeymap } from "@codemirror/commands" -import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language" -import { autocompletion, completionKeymap } from "@codemirror/autocomplete" +import { bracketMatching } from "@codemirror/language" +import { + autocompletion, + closeBrackets, + completionKeymap, +} from "@codemirror/autocomplete" import { graphql, updateSchema } from "cm6-graphql" import { GraphQLSchema } from "graphql" +import { oneDark } from "./codemirror-one-dark" +import "./syntax-highlighting.css" interface QueryEditorProps { schema?: GraphQLSchema value?: string onEdit?: (value: string) => void - onRunQuery?: () => void runQuery?: () => void onHintInformationRender?: (el: HTMLElement) => void } @@ -54,38 +59,30 @@ export class QueryEditor extends Component { componentDidMount() { if (!this.domNode) return - // Create key bindings const runQueryBinding = keymap.of([ { key: "Cmd-Enter", - run: () => { - if (this.props.onRunQuery) { - this.props.onRunQuery() - } - return true - }, + run: () => (this.props.runQuery?.(), true), }, { key: "Ctrl-Enter", - run: () => { - if (this.props.onRunQuery) { - this.props.onRunQuery() - } - return true - }, + run: () => (this.props.runQuery?.(), true), }, ]) - // Create editor state const state = EditorState.create({ doc: this.props.value || "", extensions: [ history(), + closeBrackets(), + bracketMatching(), keymap.of([...historyKeymap, ...completionKeymap, ...defaultKeymap]), runQueryBinding, - syntaxHighlighting(defaultHighlightStyle), - graphql(this.props.schema), - autocompletion(), + oneDark, + graphql(this.props.schema, {}), + autocompletion({ + icons: false, + }), EditorView.updateListener.of(update => { if (update.docChanged && !this.ignoreChangeEvent) { this.cachedValue = update.state.doc.toString() @@ -94,15 +91,6 @@ export class QueryEditor extends Component { } } }), - EditorView.theme({ - ".cm-editor": { - fontSize: "inherit", - fontFamily: "inherit", - }, - ".cm-focused": { - outline: "none", - }, - }), ], }) diff --git a/src/components/marked/result-viewer.tsx b/src/components/marked/result-viewer.tsx index e920d28d6d..2cf5e52007 100644 --- a/src/components/marked/result-viewer.tsx +++ b/src/components/marked/result-viewer.tsx @@ -2,7 +2,9 @@ import { Component } from "react" import { EditorView } from "@codemirror/view" import { EditorState } from "@codemirror/state" import { json } from "@codemirror/lang-json" -import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language" +import { oneDark } from "./codemirror-one-dark" + +import "./syntax-highlighting.css" interface ResultViewerProps { value?: string @@ -28,21 +30,7 @@ export class ResultViewer extends Component { // Create read-only editor state for JSON results const state = EditorState.create({ doc: this.props.value || "", - extensions: [ - EditorState.readOnly.of(true), - EditorView.editable.of(false), - json(), - syntaxHighlighting(defaultHighlightStyle), - EditorView.theme({ - ".cm-editor": { - fontSize: "inherit", - fontFamily: "inherit", - }, - ".cm-focused": { - outline: "none", - }, - }), - ], + extensions: [EditorState.readOnly.of(true), json(), oneDark], }) // Create editor view diff --git a/src/components/marked/syntax-highlighting.css b/src/components/marked/syntax-highlighting.css new file mode 100644 index 0000000000..79df5aeef2 --- /dev/null +++ b/src/components/marked/syntax-highlighting.css @@ -0,0 +1,322 @@ +/* CodeMirror 6 Syntax Highlighting with Light/Dark Mode Support */ + +/* CSS Custom Properties for Theme Colors */ +:root { + /* Light theme colors (matching Shiki light.json) */ + --cm-comment: #c2c3c5; + --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: #2b5581; + --cm-builtin: #1976d2; + --cm-string-2: #0b7fc7; + --cm-variable: #e10198; + --cm-meta: #b33086; + --cm-atom: #1976d2; + + /* Editor UI colors - light theme */ + --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: #ffffff; + --cm-hints-foreground: #333; + --cm-hints-active-background: #08f; + --cm-hints-active-foreground: #ffffff; +} + +.dark { + /* Dark theme colors (matching Shiki dark.json) */ + --cm-comment: #6a737d; + --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: #24292c; + --cm-foreground: #cfd3c5; + --cm-gutter-background: #1f2425; + --cm-gutter-border: #1b1f20; + --cm-line-number: #6a737d; + --cm-cursor: #c8e1ff; + --cm-selection: #3392ff44; + --cm-hints-background: #24292c; + --cm-hints-foreground: #e1e4e8; + --cm-hints-active-background: #044289; + --cm-hints-active-foreground: #ffffff; +} + +/* CodeMirror 6 Editor Base Styles */ +.cm-editor { + height: 300px; + font-family: inherit; + font-size: inherit; + background: var(--cm-background); + color: var(--cm-foreground); + position: relative; + overflow: hidden; +} + +.cm-scroller { + overflow: auto; + position: relative; +} + +.cm-content { + min-height: 1px; + cursor: text; + padding: 4px 0; +} + +.cm-line { + padding: 0 4px; + line-height: inherit; + color: inherit; +} + +.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-selectionBackground { + background: var(--cm-selection); +} + +/* Autocomplete/Hints */ +.cm-tooltip { + background: 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; +} + +.cm-tooltip-autocomplete { + background: var(--cm-hints-background); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); + margin-left: -6px; + position: absolute; + z-index: 10; +} + +.cm-completionLabel { + color: var(--cm-hints-foreground); +} + +.cm-completionDetail { + color: var(--cm-comment); +} + +.cm-completionMatchedText { + text-decoration: underline; +} + +.cm-completionIcon { + width: 1em; + height: 1em; + vertical-align: middle; + margin-right: 0.5em; +} + +.cm-tooltip-autocomplete li { + border-top: solid 1px #f7f7f7; + color: var(--cm-hints-foreground); + cursor: pointer; + margin: 0; + max-width: 300px; + overflow: hidden; + padding: 2px 6px; + white-space: pre; +} + +.dark .cm-tooltip-autocomplete li { + border-top-color: #333; +} + +.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/pages/_app.tsx b/src/pages/_app.tsx index bb88615211..e922453488 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,12 +1,10 @@ import type { AppProps } from "next/app" -import { Roboto_Flex, Roboto_Mono } from "next/font/google" import { useRouter } from "next/router" import { useEffect } from "react" import { NewFontsStyleTag } from "@/app/fonts" import "@/globals.css" -import "@/codemirror.less" import "@/app/colors.css" const gaId = process.env.NEXT_PUBLIC_GA_ID From 4e6d1474516bf1dfd86b90223d75383cab96ca97 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 22 Aug 2025 06:36:25 +0200 Subject: [PATCH 10/28] Style codemirrors properly --- package.json | 13 +- pnpm-lock.yaml | 324 +++++++++++------- src/components/marked/codemirror-one-dark.tsx | 133 ++++--- src/components/marked/mini-graphiQL.tsx | 2 +- src/components/marked/query-editor.tsx | 2 +- src/components/marked/result-viewer.tsx | 1 + src/components/marked/syntax-highlighting.css | 65 ++-- src/components/marked/variable-editor.tsx | 89 ++--- src/components/pre/index.tsx | 3 +- 9 files changed, 360 insertions(+), 272 deletions(-) diff --git a/package.json b/package.json index b50ebe9a81..a1088b834e 100644 --- a/package.json +++ b/package.json @@ -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", @@ -48,7 +55,7 @@ "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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b524ad2fd7..de80d0353b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,27 @@ 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.4.5 @@ -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 @@ -98,26 +119,26 @@ importers: specifier: ^12.11.0 version: 12.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: ^14.2.22 - version: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^14.2.32 + version: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-image-export-optimizer: specifier: ^1.18.0 - version: 1.18.0(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 1.18.0(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) next-query-params: specifier: ^5.0.1 - version: 5.0.1(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(use-query-params@2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 5.0.1(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(use-query-params@2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 4.2.3(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) next-with-less: specifier: ^3.0.1 - version: 3.0.1(less-loader@12.2.0(less@4.2.1))(less@4.2.1)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 3.0.1(less-loader@12.2.0(less@4.2.1))(less@4.2.1)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.23)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) + version: 3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) nextra-theme-docs: specifier: 3.3.1 - version: 3.3.1(patch_hash=6nt5xhmisiwamimp7yowa7aoxq)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.3.1(patch_hash=6nt5xhmisiwamimp7yowa7aoxq)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) numbro: specifier: 2.5.0 version: 2.5.0 @@ -878,14 +899,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/state@0.20.1': - resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==} + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} - '@codemirror/view@0.20.7': - resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==} + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@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==} @@ -1386,14 +1419,20 @@ packages: '@kamilkisiela/fast-url-parser@1.1.4': resolution: {integrity: sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==} - '@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@0.16.0': - resolution: {integrity: sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==} + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} - '@lezer/lr@0.16.3': - resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==} + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@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.0.1': resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} @@ -1483,62 +1522,62 @@ packages: '@next/env@13.5.6': resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} - '@next/env@14.2.29': - resolution: {integrity: sha512-UzgLR2eBfhKIQt0aJ7PWH7XRPYw7SXz0Fpzdl5THjUnvxy4kfBk9OU4RNPNiETewEEtaBcExNFNn1QWH8wQTjg==} + '@next/env@14.2.32': + resolution: {integrity: sha512-n9mQdigI6iZ/DF6pCTwMKeWgF2e8lg7qgt5M7HXMLtyhZYMnf/u905M18sSpPmHL9MKp9JHo56C6jrD2EvWxng==} '@next/eslint-plugin-next@15.3.3': resolution: {integrity: sha512-VKZJEiEdpKkfBmcokGjHu0vGDG+8CehGs90tBEy/IDoDDKGngeyIStt2MmE5FYNyU9BhgR7tybNWTAJY/30u+Q==} - '@next/swc-darwin-arm64@14.2.29': - resolution: {integrity: sha512-wWtrAaxCVMejxPHFb1SK/PVV1WDIrXGs9ki0C/kUM8ubKHQm+3hU9MouUywCw8Wbhj3pewfHT2wjunLEr/TaLA==} + '@next/swc-darwin-arm64@14.2.32': + resolution: {integrity: sha512-osHXveM70zC+ilfuFa/2W6a1XQxJTvEhzEycnjUaVE8kpUS09lDpiDDX2YLdyFCzoUbvbo5r0X1Kp4MllIOShw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.29': - resolution: {integrity: sha512-7Z/jk+6EVBj4pNLw/JQrvZVrAh9Bv8q81zCFSfvTMZ51WySyEHWVpwCEaJY910LyBftv2F37kuDPQm0w9CEXyg==} + '@next/swc-darwin-x64@14.2.32': + resolution: {integrity: sha512-P9NpCAJuOiaHHpqtrCNncjqtSBi1f6QUdHK/+dNabBIXB2RUFWL19TY1Hkhu74OvyNQEYEzzMJCMQk5agjw1Qg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.29': - resolution: {integrity: sha512-o6hrz5xRBwi+G7JFTHc+RUsXo2lVXEfwh4/qsuWBMQq6aut+0w98WEnoNwAwt7hkEqegzvazf81dNiwo7KjITw==} + '@next/swc-linux-arm64-gnu@14.2.32': + resolution: {integrity: sha512-v7JaO0oXXt6d+cFjrrKqYnR2ubrD+JYP7nQVRZgeo5uNE5hkCpWnHmXm9vy3g6foMO8SPwL0P3MPw1c+BjbAzA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.29': - resolution: {integrity: sha512-9i+JEHBOVgqxQ92HHRFlSW1EQXqa/89IVjtHgOqsShCcB/ZBjTtkWGi+SGCJaYyWkr/lzu51NTMCfKuBf7ULNw==} + '@next/swc-linux-arm64-musl@14.2.32': + resolution: {integrity: sha512-tA6sIKShXtSJBTH88i0DRd6I9n3ZTirmwpwAqH5zdJoQF7/wlJXR8DkPmKwYl5mFWhEKr5IIa3LfpMW9RRwKmQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.29': - resolution: {integrity: sha512-B7JtMbkUwHijrGBOhgSQu2ncbCYq9E7PZ7MX58kxheiEOwdkM+jGx0cBb+rN5AeqF96JypEppK6i/bEL9T13lA==} + '@next/swc-linux-x64-gnu@14.2.32': + resolution: {integrity: sha512-7S1GY4TdnlGVIdeXXKQdDkfDysoIVFMD0lJuVVMeb3eoVjrknQ0JNN7wFlhCvea0hEk0Sd4D1hedVChDKfV2jw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.29': - resolution: {integrity: sha512-yCcZo1OrO3aQ38B5zctqKU1Z3klOohIxug6qdiKO3Q3qNye/1n6XIs01YJ+Uf+TdpZQ0fNrOQI2HrTLF3Zprnw==} + '@next/swc-linux-x64-musl@14.2.32': + resolution: {integrity: sha512-OHHC81P4tirVa6Awk6eCQ6RBfWl8HpFsZtfEkMpJ5GjPsJ3nhPe6wKAJUZ/piC8sszUkAgv3fLflgzPStIwfWg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.29': - resolution: {integrity: sha512-WnrfeOEtTVidI9Z6jDLy+gxrpDcEJtZva54LYC0bSKQqmyuHzl0ego+v0F/v2aXq0am67BRqo/ybmmt45Tzo4A==} + '@next/swc-win32-arm64-msvc@14.2.32': + resolution: {integrity: sha512-rORQjXsAFeX6TLYJrCG5yoIDj+NKq31Rqwn8Wpn/bkPNy5rTHvOXkW8mLFonItS7QC6M+1JIIcLe+vOCTOYpvg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.29': - resolution: {integrity: sha512-vkcriFROT4wsTdSeIzbxaZjTNTFKjSYmLd8q/GVH3Dn8JmYjUKOuKXHK8n+lovW/kdcpIvydO5GtN+It2CvKWA==} + '@next/swc-win32-ia32-msvc@14.2.32': + resolution: {integrity: sha512-jHUeDPVHrgFltqoAqDB6g6OStNnFxnc7Aks3p0KE0FbwAvRg6qWKYF5mSTdCTxA3axoSAUwxYdILzXJfUwlHhA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.29': - resolution: {integrity: sha512-iPPwUEKnVs7pwR0EBLJlwxLD7TTHWS/AoVZx1l9ZQzfQciqaFEr5AlYzA2uB6Fyby1IF18t4PL0nTpB+k4Tzlw==} + '@next/swc-win32-x64-msvc@14.2.32': + resolution: {integrity: sha512-2N0lSoU4GjfLSO50wvKpMQgKd4HdI2UHEhQPPPnlgfBJlOgJxkjpkYBqzk08f1gItBB6xF/n+ykso2hgxuydsA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2417,9 +2456,6 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001721: - resolution: {integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==} - caniuse-lite@1.0.30001726: resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} @@ -2490,15 +2526,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==} @@ -2586,6 +2623,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'} @@ -2806,6 +2846,9 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debounce-promise@3.1.2: + resolution: {integrity: sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -3353,11 +3396,11 @@ packages: peerDependencies: graphql: '*' - graphql-language-service@5.2.0: - resolution: {integrity: sha512-o/ZgTS0pBxWm3hSF4+6GwiV1//DxzoLWEbS38+jqpzzy1d/QXBidwQuVYTOksclbtOJZ3KR/tZ8fi/tI6VpVMg==} + graphql-language-service@5.5.0: + resolution: {integrity: sha512-9EvWrLLkF6Y5e29/2cmFoAO6hBPPAZlCyjznmpR11iFtRydfkss+9m6x+htA8h7YznGam+TtJwS6JuwoWWgb2Q==} hasBin: true peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 graphql-ws@5.16.0: resolution: {integrity: sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==} @@ -4344,8 +4387,8 @@ packages: less-loader: '>= 7.0.0' next: '>= 11.0.1' - next@14.2.29: - resolution: {integrity: sha512-s98mCOMOWLGGpGOfgKSnleXLuegvvH415qtRZXpSp00HeEgdmrxmwL9cgKU+h4XrhB16zEI5d/7BnkS3ATInsA==} + next@14.2.32: + resolution: {integrity: sha512-fg5g0GZ7/nFc09X8wLe6pNSU8cLWbLRG3TZzPJ1BJvi2s9m7eF991se67wliM9kR5yLHRkyGKU49MMx58s3LJg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -6555,20 +6598,48 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@codemirror/language@0.20.2': + '@codemirror/autocomplete@6.18.6': + dependencies: + '@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/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 + '@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 @@ -7063,15 +7134,23 @@ snapshots: '@kamilkisiela/fast-url-parser@1.1.4': {} - '@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.0.1': dependencies: @@ -7167,37 +7246,37 @@ snapshots: '@next/env@13.5.6': {} - '@next/env@14.2.29': {} + '@next/env@14.2.32': {} '@next/eslint-plugin-next@15.3.3': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@14.2.29': + '@next/swc-darwin-arm64@14.2.32': optional: true - '@next/swc-darwin-x64@14.2.29': + '@next/swc-darwin-x64@14.2.32': optional: true - '@next/swc-linux-arm64-gnu@14.2.29': + '@next/swc-linux-arm64-gnu@14.2.32': optional: true - '@next/swc-linux-arm64-musl@14.2.29': + '@next/swc-linux-arm64-musl@14.2.32': optional: true - '@next/swc-linux-x64-gnu@14.2.29': + '@next/swc-linux-x64-gnu@14.2.32': optional: true - '@next/swc-linux-x64-musl@14.2.29': + '@next/swc-linux-x64-musl@14.2.32': optional: true - '@next/swc-win32-arm64-msvc@14.2.29': + '@next/swc-win32-arm64-msvc@14.2.32': optional: true - '@next/swc-win32-ia32-msvc@14.2.29': + '@next/swc-win32-ia32-msvc@14.2.32': optional: true - '@next/swc-win32-x64-msvc@14.2.29': + '@next/swc-win32-x64-msvc@14.2.32': optional: true '@nodelib/fs.scandir@2.1.5': @@ -8161,8 +8240,6 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001721: {} - caniuse-lite@1.0.30001726: {} ccount@2.0.1: {} @@ -8238,14 +8315,16 @@ 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.2.0(graphql@16.10.0) - - codemirror@5.65.20: {} + graphql-language-service: 5.5.0(graphql@16.10.0) codsen-utils@1.6.8: dependencies: @@ -8327,6 +8406,8 @@ snapshots: optionalDependencies: typescript: 5.8.3 + crelt@1.0.6: {} + cross-inspect@1.0.1: dependencies: tslib: 2.8.1 @@ -8583,6 +8664,8 @@ snapshots: dayjs@1.11.13: {} + debounce-promise@3.1.2: {} + debounce@1.2.1: {} debug@4.3.4: @@ -9293,8 +9376,9 @@ snapshots: arrify: 1.0.1 graphql: 16.10.0 - graphql-language-service@5.2.0(graphql@16.10.0): + graphql-language-service@5.5.0(graphql@16.10.0): dependencies: + debounce-promise: 3.1.2 graphql: 16.10.0 nullthrows: 1.1.1 vscode-languageserver-types: 3.17.5 @@ -10585,81 +10669,81 @@ snapshots: negotiator@1.0.0: {} - next-image-export-optimizer@1.18.0(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + next-image-export-optimizer@1.18.0(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 sharp: 0.33.5 typescript: 5.8.3 - next-query-params@5.0.1(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(use-query-params@2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + next-query-params@5.0.1(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(use-query-params@2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: - next: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 tslib: 2.8.1 use-query-params: 2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-sitemap@4.2.3(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + next-sitemap@4.2.3(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.6 fast-glob: 3.3.3 minimist: 1.2.8 - next: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next-with-less@3.0.1(less-loader@12.2.0(less@4.2.1))(less@4.2.1)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + next-with-less@3.0.1(less-loader@12.2.0(less@4.2.1))(less@4.2.1)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: clone-deep: 4.0.1 less: 4.2.1 less-loader: 12.2.0(less@4.2.1) - next: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 14.2.29 + '@next/env': 14.2.32 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001721 + caniuse-lite: 1.0.30001726 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.29 - '@next/swc-darwin-x64': 14.2.29 - '@next/swc-linux-arm64-gnu': 14.2.29 - '@next/swc-linux-arm64-musl': 14.2.29 - '@next/swc-linux-x64-gnu': 14.2.29 - '@next/swc-linux-x64-musl': 14.2.29 - '@next/swc-win32-arm64-msvc': 14.2.29 - '@next/swc-win32-ia32-msvc': 14.2.29 - '@next/swc-win32-x64-msvc': 14.2.29 + '@next/swc-darwin-arm64': 14.2.32 + '@next/swc-darwin-x64': 14.2.32 + '@next/swc-linux-arm64-gnu': 14.2.32 + '@next/swc-linux-arm64-musl': 14.2.32 + '@next/swc-linux-x64-gnu': 14.2.32 + '@next/swc-linux-x64-musl': 14.2.32 + '@next/swc-win32-arm64-msvc': 14.2.32 + '@next/swc-win32-ia32-msvc': 14.2.32 + '@next/swc-win32-x64-msvc': 14.2.32 '@playwright/test': 1.54.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.3.1(patch_hash=6nt5xhmisiwamimp7yowa7aoxq)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.3.1(patch_hash=6nt5xhmisiwamimp7yowa7aoxq)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 escape-string-regexp: 5.0.0 flexsearch: 0.7.43 - next: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.23)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) + nextra: 3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.22.4 - nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3): + nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3): dependencies: '@formatjs/intl-localematcher': 0.5.10 '@headlessui/react': 2.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -10682,7 +10766,7 @@ snapshots: mdast-util-gfm: 3.0.0 mdast-util-to-hast: 13.2.0 negotiator: 1.0.0 - next: 14.2.29(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) p-limit: 6.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) diff --git a/src/components/marked/codemirror-one-dark.tsx b/src/components/marked/codemirror-one-dark.tsx index 4b13c05d62..dac08387ad 100644 --- a/src/components/marked/codemirror-one-dark.tsx +++ b/src/components/marked/codemirror-one-dark.tsx @@ -4,86 +4,83 @@ import { HighlightStyle, syntaxHighlighting } from "@codemirror/language" import { tags as t } from "@lezer/highlight" /// The editor theme styles for One Dark using CSS custom properties. -export const oneDarkTheme = EditorView.theme( - { - "&": { - color: "var(--cm-foreground)", - backgroundColor: "var(--cm-background)", - }, +export const oneDarkTheme = EditorView.theme({ + "&": { + color: "var(--cm-foreground)", + backgroundColor: "var(--cm-background)", + }, - ".cm-content": { - caretColor: "var(--cm-cursor)", - }, + ".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-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-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-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-activeLine": { backgroundColor: "rgba(255, 255, 255, 0.05)" }, + ".cm-selectionMatch": { backgroundColor: "#aafe661a" }, - "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { - backgroundColor: "#bad0f847", - }, + "&.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-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-activeLineGutter": { + backgroundColor: "var(--cm-gutter-background)", + }, - ".cm-foldPlaceholder": { - backgroundColor: "transparent", - border: "none", - color: "#ddd", - }, + ".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)", - }, + ".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)", }, }, - { dark: true }, -) +}) /// The highlighting style for code using CSS custom properties. export const oneDarkHighlightStyle = HighlightStyle.define([ diff --git a/src/components/marked/mini-graphiQL.tsx b/src/components/marked/mini-graphiQL.tsx index 9a88067066..4aeac691e4 100644 --- a/src/components/marked/mini-graphiQL.tsx +++ b/src/components/marked/mini-graphiQL.tsx @@ -62,7 +62,7 @@ export default class MiniGraphiQL extends Component< ) return ( -
+
{Object.keys(this.state.variableToType).length > 0 ? (
{editor} diff --git a/src/components/marked/query-editor.tsx b/src/components/marked/query-editor.tsx index 508efa1394..5ccde9744d 100644 --- a/src/components/marked/query-editor.tsx +++ b/src/components/marked/query-editor.tsx @@ -140,7 +140,7 @@ export class QueryEditor extends Component { render() { return (
{ this.domNode = e }} diff --git a/src/components/marked/result-viewer.tsx b/src/components/marked/result-viewer.tsx index 2cf5e52007..ad0015946a 100644 --- a/src/components/marked/result-viewer.tsx +++ b/src/components/marked/result-viewer.tsx @@ -1,6 +1,7 @@ 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 { oneDark } from "./codemirror-one-dark" diff --git a/src/components/marked/syntax-highlighting.css b/src/components/marked/syntax-highlighting.css index 79df5aeef2..32eedba190 100644 --- a/src/components/marked/syntax-highlighting.css +++ b/src/components/marked/syntax-highlighting.css @@ -26,9 +26,9 @@ --cm-line-number: #999; --cm-cursor: #000000; --cm-selection: #d7d4f0; - --cm-hints-background: #ffffff; - --cm-hints-foreground: #333; - --cm-hints-active-background: #08f; + --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; } @@ -50,45 +50,37 @@ --cm-atom: #79b8ff; /* Editor UI colors - dark theme */ - --cm-background: #24292c; + --cm-background: #000; --cm-foreground: #cfd3c5; --cm-gutter-background: #1f2425; --cm-gutter-border: #1b1f20; --cm-line-number: #6a737d; --cm-cursor: #c8e1ff; --cm-selection: #3392ff44; - --cm-hints-background: #24292c; --cm-hints-foreground: #e1e4e8; - --cm-hints-active-background: #044289; + --cm-hints-active-background: hsl(var(--color-sec-base) / 0.1); --cm-hints-active-foreground: #ffffff; } /* CodeMirror 6 Editor Base Styles */ .cm-editor { - height: 300px; font-family: inherit; font-size: inherit; background: var(--cm-background); color: var(--cm-foreground); position: relative; - overflow: hidden; -} -.cm-scroller { - overflow: auto; - position: relative; -} + & .cm-content { + padding: 16px 0; + } -.cm-content { - min-height: 1px; - cursor: text; - padding: 4px 0; -} + & .cm-line { + padding: 0 0 0 16px; + } -.cm-line { - padding: 0 4px; - line-height: inherit; - color: inherit; + & .cm-scroller { + line-height: 1.5; + } } .cm-focused { @@ -189,19 +181,22 @@ } /* Autocomplete/Hints */ -.cm-tooltip { +.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); - border: none; - border-radius: 2px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); font-family: inherit; font-size: inherit; + + &.cm-completionInfo-right { + @apply bg-neu-0 shadow-lg; + } } .cm-tooltip-autocomplete { background: var(--cm-hints-background); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); margin-left: -6px; position: absolute; z-index: 10; @@ -211,8 +206,10 @@ color: var(--cm-hints-foreground); } -.cm-completionDetail { - color: var(--cm-comment); +.cm-editor .cm-completionDetail { + color: var(--cm-def); + font-style: normal; + font-size: 0.8em; } .cm-completionMatchedText { @@ -226,19 +223,15 @@ margin-right: 0.5em; } -.cm-tooltip-autocomplete li { - border-top: solid 1px #f7f7f7; +.cm-tooltip.cm-tooltip-autocomplete > ul > li { color: var(--cm-hints-foreground); cursor: pointer; margin: 0; max-width: 300px; overflow: hidden; - padding: 2px 6px; white-space: pre; -} - -.dark .cm-tooltip-autocomplete li { - border-top-color: #333; + font-size: 14px; + line-height: 1.5 !important; } .cm-tooltip-autocomplete li:hover { diff --git a/src/components/marked/variable-editor.tsx b/src/components/marked/variable-editor.tsx index f46e473a41..88a50434fa 100644 --- a/src/components/marked/variable-editor.tsx +++ b/src/components/marked/variable-editor.tsx @@ -1,16 +1,16 @@ -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"; +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; + value: string + variableToType?: any + onEdit?: (value: string) => void + onRunQuery?: () => void + onHintInformationRender?: (el: HTMLElement) => void } /** @@ -26,22 +26,22 @@ interface VariableEditorProps { * */ export class VariableEditor extends Component { - private view: EditorView | null = null; - private domNode: HTMLDivElement | null = null; - private cachedValue: string; - private ignoreChangeEvent = false; + private view: EditorView | null = null + private domNode: HTMLDivElement | null = null + private cachedValue: string + private ignoreChangeEvent = false constructor(props: VariableEditorProps) { - super(props); + 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 || ""; + this.cachedValue = props.value || "" } componentDidMount() { - if (!this.domNode) return; + if (!this.domNode) return // Create editor state for JSON (variables are JSON) const state = EditorState.create({ @@ -50,70 +50,75 @@ export class VariableEditor extends Component { history(), json(), syntaxHighlighting(defaultHighlightStyle), - EditorView.updateListener.of((update) => { + EditorView.updateListener.of(update => { if (update.docChanged && !this.ignoreChangeEvent) { - this.cachedValue = update.state.doc.toString(); + this.cachedValue = update.state.doc.toString() if (this.props.onEdit) { - this.props.onEdit(this.cachedValue); + this.props.onEdit(this.cachedValue) } } }), EditorView.theme({ ".cm-editor": { fontSize: "inherit", - fontFamily: "inherit" + fontFamily: "inherit", }, ".cm-focused": { - outline: "none" - } - }) - ] - }); + outline: "none", + }, + }), + ], + }) // Create editor view this.view = new EditorView({ state, - parent: this.domNode - }); + parent: this.domNode, + }) } componentDidUpdate(prevProps: VariableEditorProps) { - if (!this.view) return; + 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; - + this.ignoreChangeEvent = true + if ( this.props.value !== prevProps.value && this.props.value !== this.cachedValue ) { - this.cachedValue = this.props.value; + this.cachedValue = this.props.value this.view.dispatch({ changes: { from: 0, to: this.view.state.doc.length, - insert: this.props.value || "" - } - }); + insert: this.props.value || "", + }, + }) } - - this.ignoreChangeEvent = false; + + this.ignoreChangeEvent = false } componentWillUnmount() { if (this.view) { - this.view.destroy(); - this.view = null; + this.view.destroy() + this.view = null } } render() { return ( -
{ this.domNode = e; }}> +
{ + this.domNode = e + }} + > Variables
- ); + ) } } diff --git a/src/components/pre/index.tsx b/src/components/pre/index.tsx index 25bf431ff8..0c0409df83 100644 --- a/src/components/pre/index.tsx +++ b/src/components/pre/index.tsx @@ -39,6 +39,7 @@ export function Pre({ return (
{filename && ( + // TODO: Extract this as a component for import in MiniGraphiQL
Date: Fri, 29 Aug 2025 17:04:41 +0200 Subject: [PATCH 11/28] Upgrade pnpm from 9.15.9 to 10.15.0 --- package.json | 2 +- pnpm-lock.yaml | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index a1088b834e..65392161b6 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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de80d0353b..f6adc564c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,13 +6,13 @@ 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: 6nt5xhmisiwamimp7yowa7aoxq + hash: d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521 path: patches/nextra-theme-docs.patch importers: @@ -135,10 +135,10 @@ importers: version: 3.0.1(less-loader@12.2.0(less@4.2.1))(less@4.2.1)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) + version: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) nextra-theme-docs: specifier: 3.3.1 - version: 3.3.1(patch_hash=6nt5xhmisiwamimp7yowa7aoxq)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.3.1(patch_hash=d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) numbro: specifier: 2.5.0 version: 2.5.0 @@ -199,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.54.2 devDependencies: '@graphql-eslint/eslint-plugin': specifier: 4.4.0 @@ -282,6 +278,10 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 + optionalDependencies: + playwright: + specifier: ^1.54.2 + version: 1.54.2 packages: @@ -10257,7 +10257,7 @@ snapshots: merge2@1.4.1: {} - mermaid-isomorphic@3.0.4(patch_hash=tt5abewtk4kmqtpqfe2f44hnpa)(playwright@1.54.2): + mermaid-isomorphic@3.0.4(patch_hash=fccadc7038719bcf9dc12a573655719edaf7ea8246bd144c660191d05b38c637)(playwright@1.54.2): dependencies: '@fortawesome/fontawesome-free': 6.7.2 mermaid: 11.7.0 @@ -10729,7 +10729,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.3.1(patch_hash=6nt5xhmisiwamimp7yowa7aoxq)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.3.1(patch_hash=d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 @@ -10737,13 +10737,13 @@ snapshots: flexsearch: 0.7.43 next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) + nextra: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.22.4 - nextra@3.3.1(patch_hash=6ssvmycplmexou7j5jtwzrnhmy)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3): + nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3): dependencies: '@formatjs/intl-localematcher': 0.5.10 '@headlessui/react': 2.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11274,7 +11274,7 @@ snapshots: '@types/hast': 3.0.4 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.0 - mermaid-isomorphic: 3.0.4(patch_hash=tt5abewtk4kmqtpqfe2f44hnpa)(playwright@1.54.2) + mermaid-isomorphic: 3.0.4(patch_hash=fccadc7038719bcf9dc12a573655719edaf7ea8246bd144c660191d05b38c637)(playwright@1.54.2) mini-svg-data-uri: 1.4.4 space-separated-tokens: 2.0.2 unified: 11.0.5 From f155b2f8a50f81b7c7d65f9a17c2e3496b94bacc Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 29 Aug 2025 17:04:47 +0200 Subject: [PATCH 12/28] Update 2025 conference schedule and speaker details --- scripts/sync-sched/schedule-2025.json | 182 ++++++++++++++++---------- scripts/sync-sched/speakers.json | 58 ++++---- 2 files changed, 144 insertions(+), 96 deletions(-) diff --git a/scripts/sync-sched/schedule-2025.json b/scripts/sync-sched/schedule-2025.json index 35ac508bec..0648035366 100644 --- a/scripts/sync-sched/schedule-2025.json +++ b/scripts/sync-sched/schedule-2025.json @@ -79,7 +79,7 @@ "event_start": "2025-09-08 09:00", "event_end": "2025-09-08 09:05", "event_type": "Keynote Sessions", - "goers": "3", + "goers": "4", "seats": "0", "invite_only": "N", "venue": "Grote Zaal - 2nd Floor", @@ -125,7 +125,7 @@ "event_start": "2025-09-08 09:05", "event_end": "2025-09-08 09:15", "event_type": "Keynote Sessions", - "goers": "4", + "goers": "5", "seats": "0", "invite_only": "N", "venue": "Grote Zaal - 2nd Floor", @@ -323,7 +323,7 @@ "event_end": "2025-09-08 10:15", "event_type": "Keynote Sessions", "description": "As developers build with AI agents, we face a challenge: how do we provide these agents with reliable, flexible access to our distributed data? GraphQL's graph-based approach makes it the ideal language for AI. Join Matt DeBergalis, CEO and Co-founder of Apollo GraphQL, to explore how \"thinking in graphs\" fundamentally transforms API orchestration from procedural code to declarative queries – creating the composable data layer that AI-driven applications require.", - "goers": "3", + "goers": "4", "seats": "0", "invite_only": "N", "venue": "Grote Zaal - 2nd Floor", @@ -482,7 +482,7 @@ "event_key": "929631", "active": "Y", "pinned": "N", - "name": "Sponsored Session: Schema Design Patterns: Leveraging Existing REST APIs for Rapid GraphQL Adoption - Speaker To Be Announced", + "name": "Sponsored Session: Schema Design Patterns: Leveraging Existing REST APIs for Rapid GraphQL Adoption - Michael Watson, Apollo GraphQL", "event_start": "2025-09-08 10:45", "event_end": "2025-09-08 11:15", "event_type": "Developer Experience", @@ -494,6 +494,15 @@ "audience": "Any", "id": "9ef7eaa509478085ff75215c2b664f23", "venue_id": "2152800", + "speakers": [ + { + "username": "watson17", + "id": "19024254", + "name": "Michael Watson", + "company": "Apollo GraphQL", + "custom_order": 0 + } + ], "event_start_year": "2025", "event_start_month": "September", "event_start_month_short": "Sep", @@ -607,6 +616,52 @@ "end_time": "11:15:00", "event_subtype": "Schema evolution" }, + { + "event_key": "925253", + "active": "Y", + "pinned": "N", + "name": "Offset Pagination Is Dead! Meet Relative Cursors - Michael Staib, ChilliCream", + "event_start": "2025-09-08 11:25", + "event_end": "2025-09-08 11:35", + "event_type": "Developer Experience", + "description": "What if you could keep traditional UI pagination concepts, but with the performance and reliability of cursor-based pagination? In this lightning talk, you’ll learn how relative cursors enable fast, consistent pagination while preserving familiar UX patterns like “jump to page.” It’s a smarter, more robust approach to navigating data—ideal for modern APIs and real-world apps.", + "goers": "1", + "seats": "0", + "invite_only": "N", + "venue": "IJzaal - 5th Floor", + "audience": "Advanced", + "id": "73b37145c961856b3c857568d0739a9f", + "venue_id": "2152806", + "speakers": [ + { + "username": "michael_staib.23xujj9p", + "id": "14900031", + "name": "Michael Staib", + "company": "ChilliCream", + "custom_order": 0 + } + ], + "event_start_year": "2025", + "event_start_month": "September", + "event_start_month_short": "Sep", + "event_start_day": "8", + "event_start_weekday": "Monday", + "event_start_weekday_short": "Mon", + "event_start_time": "11:25", + "event_end_year": "2025", + "event_end_month": "September", + "event_end_month_short": "Sep", + "event_end_day": "8", + "event_end_weekday": "Monday", + "event_end_weekday_short": "Mon", + "event_end_time": "11:35", + "start_date": "2025-09-08", + "start_time": "11:25:00", + "start_time_ts": 1757323500, + "end_date": "2025-09-08", + "end_time": "11:35:00", + "event_subtype": "Patterns and community trends" + }, { "event_key": "929626", "active": "Y", @@ -614,7 +669,7 @@ "name": "Panel Discussion: APIs for AIs - Kewei Qu, Meta; Fredrik Björk, Grafbase; Boris Besemer, Vercel; Moderated by Stephen Spalding, Netflix", "event_start": "2025-09-08 11:25", "event_end": "2025-09-08 11:55", - "event_type": "AI / LLMs", + "event_type": "GraphQL in Production", "description": "APIs give AI superpowers. The MCP protocol, introduced less than a year ago, has quickly become a popular method for connecting large language models (LLMs) with the outside world. But where does GraphQL fit in? It's structured, typed, and introspectable... so what's the catch?\n\nJoin our panel of experts as we delve into how to leverage GraphQL effectively and safely in AI applications. We'll discuss the trade-offs, potential pitfalls, and share insights into best practices and strategies. This interactive discussion will explore how combining GraphQL with MCP can unlock new superpowers for AI, offering attendees a chance to engage with thought leaders and gain valuable perspectives.", "goers": "2", "seats": "0", @@ -680,53 +735,7 @@ "start_time_ts": 1757323500, "end_date": "2025-09-08", "end_time": "11:55:00", - "event_subtype": "" - }, - { - "event_key": "925253", - "active": "Y", - "pinned": "N", - "name": "Offset Pagination Is Dead! Meet Relative Cursors - Michael Staib, ChilliCream", - "event_start": "2025-09-08 11:25", - "event_end": "2025-09-08 11:35", - "event_type": "Developer Experience", - "description": "What if you could keep traditional UI pagination concepts, but with the performance and reliability of cursor-based pagination? In this lightning talk, you’ll learn how relative cursors enable fast, consistent pagination while preserving familiar UX patterns like “jump to page.” It’s a smarter, more robust approach to navigating data—ideal for modern APIs and real-world apps.", - "goers": "1", - "seats": "0", - "invite_only": "N", - "venue": "IJzaal - 5th Floor", - "audience": "Advanced", - "id": "73b37145c961856b3c857568d0739a9f", - "venue_id": "2152806", - "speakers": [ - { - "username": "michael_staib.23xujj9p", - "id": "14900031", - "name": "Michael Staib", - "company": "ChilliCream", - "custom_order": 0 - } - ], - "event_start_year": "2025", - "event_start_month": "September", - "event_start_month_short": "Sep", - "event_start_day": "8", - "event_start_weekday": "Monday", - "event_start_weekday_short": "Mon", - "event_start_time": "11:25", - "event_end_year": "2025", - "event_end_month": "September", - "event_end_month_short": "Sep", - "event_end_day": "8", - "event_end_weekday": "Monday", - "event_end_weekday_short": "Mon", - "event_end_time": "11:35", - "start_date": "2025-09-08", - "start_time": "11:25:00", - "start_time_ts": 1757323500, - "end_date": "2025-09-08", - "end_time": "11:35:00", - "event_subtype": "Patterns and community trends" + "event_subtype": "AI / LLMs" }, { "event_key": "924672", @@ -778,7 +787,7 @@ "event_key": "925279", "active": "Y", "pinned": "N", - "name": "See the Graph in GraphQL: Graph Visualization in Action - Ivan Goncharov, ApolloGraphQL", + "name": "See the Graph in GraphQL: Graph Visualization in Action - Ivan Goncharov, KeenEthics", "event_start": "2025-09-08 11:45", "event_end": "2025-09-08 11:55", "event_type": "Developer Experience", @@ -795,7 +804,7 @@ "username": "ivan.goncharov.ua", "id": "23096422", "name": "Ivan Goncharov", - "company": "ApolloGraphQL", + "company": "KeenEthics", "custom_order": 0 } ], @@ -1054,6 +1063,52 @@ "end_time": "14:15:00", "event_subtype": "Documentation" }, + { + "event_key": "929645", + "active": "Y", + "pinned": "N", + "name": "Streamlining Data Collection and Entity Management for Amazon's Buyer Abuse Prevention Team - Adam Cervantes, Amazon", + "event_start": "2025-09-08 13:45", + "event_end": "2025-09-08 14:15", + "event_type": "GraphQL in Production", + "description": "The Buyer Abuse Prevention team is responsible for preventing returns abuse on Amazon.com. We do this by leveraging multiple streams of data to help make accurate decisions that minimize friction to our good customers. Enrichment of entities, for example, orders, is often distributed across multiple APIs, which makes collecting and organizing large sets of data inefficient and inflexible.\n\nTo simplify development, we built a GraphQL API to consolidate the collection and storage of data that allowed us to break the dependency on API results and design our storage around entities in a way that was optimal for our business. Chaining API calls now only takes place within a single API without need for code replication. Swapping out the underlying API for specific fields no longer requires code refactoring as the shape of the entity remained the same. The schema is well-connected which allows for different entry points but ultimately arrives at the same data without needing to reinvent the wheel.\n\nWe can now focus on developing a schema and set of entities that match our business needs, without risk of major refactoring when a dependent API changes.", + "goers": "0", + "seats": "0", + "invite_only": "N", + "venue": "Studio - 5th Floor", + "audience": "Beginner", + "id": "2f80d25265c00f9c5133f80cdcc71618", + "venue_id": "2152809", + "speakers": [ + { + "username": "adam427", + "id": "3141026", + "name": "Adam Cervantes", + "company": "Amazon", + "custom_order": 0 + } + ], + "event_start_year": "2025", + "event_start_month": "September", + "event_start_month_short": "Sep", + "event_start_day": "8", + "event_start_weekday": "Monday", + "event_start_weekday_short": "Mon", + "event_start_time": "13:45", + "event_end_year": "2025", + "event_end_month": "September", + "event_end_month_short": "Sep", + "event_end_day": "8", + "event_end_weekday": "Monday", + "event_end_weekday_short": "Mon", + "event_end_time": "14:15", + "start_date": "2025-09-08", + "start_time": "13:45:00", + "start_time_ts": 1757331900, + "end_date": "2025-09-08", + "end_time": "14:15:00", + "event_subtype": "Case studies" + }, { "event_key": "882751", "active": "Y", @@ -3633,7 +3688,7 @@ "event_end": "2025-09-10 12:25", "event_type": "Developer Experience", "description": "One of the great achievements of GraphQL is composable, full-stack type safety: a strongly-typed schema, against which one writes client components, and from which minimal yet sufficient queries are generated. This seamless flow from database to UI, with immediate feedback, compile-time guarantees and great performance, represents an unmatched DevEx breakthrough.\n \nBut what if we use a full-stack client (like Isograph aims to be)? Or use a rich client in combination with a Hasura, Prisma or PostGraphile, and effectively write components against an SQL schema? Have both the GraphQL schema and its operation language become mere implementation details?\n \nIn this talk, I'll explore how GraphQL's apparent disappearance into tooling actually represents its ultimate victory. Even as GraphQL-the-syntax fades from view, its architectural innovations—fragment composability, full-stack type safety, document merging, persisted operations—become the invisible foundation of modern development.\n \nThe best way to honor this legacy isn't to protect its syntax — it's to let its principles be reborn in new forms, evolving as our tools evolve, making app development better for years to come.", - "goers": "0", + "goers": "1", "seats": "0", "invite_only": "N", "venue": "Grote Zaal - 2nd Floor", @@ -3802,7 +3857,7 @@ "event_key": "923860", "active": "Y", "pinned": "N", - "name": "Reintroducing Apollo Client: V4 and Beyond - Jerel Miller & Lenz Weber-Tronic, Apollo GraphQL", + "name": "Reintroducing Apollo Client: V4 and Beyond - Lenz Weber-Tronic, Apollo GraphQL", "event_start": "2025-09-10 13:40", "event_end": "2025-09-10 14:10", "event_type": "Developer Experience", @@ -3815,19 +3870,12 @@ "id": "efe5aee612551209ba413d57d3ddbb4e", "venue_id": "2152800", "speakers": [ - { - "username": "jerel.miller", - "id": "23098762", - "name": "Jerel Miller", - "company": "Apollo GraphQL", - "custom_order": 0 - }, { "username": "mail1232", "id": "23098771", "name": "Lenz Weber-Tronic", "company": "Apollo GraphQL", - "custom_order": 1 + "custom_order": 0 } ], "event_start_year": "2025", @@ -4350,7 +4398,7 @@ "name": "LLMs + GraphQL + MCP: A Blueprint for Scalable AI Tooling - Erik Wrede, Strawberry-GraphQL & Thore Koritzius, Independent", "event_start": "2025-09-10 15:50", "event_end": "2025-09-10 16:20", - "event_type": "AI / LLMs", + "event_type": "GraphQL in Production", "description": "Plugging an LLM into GraphQL sounds simple—until it drowns in thousands of fields, types, and connections. Most models today can’t reason effectively over large APIs without brittle prompt hacks or hardcoded shortcuts.\n\nModel Context Protocol (MCP) is the cutting-edge solution for enabling seamless, dynamic interactions between LLMs and external tooling. It standardizes the way models interact with various tools, breaking down barriers between APIs and AI systems.\n\nIn this talk, you’ll discover how to turn any GraphQL endpoint into an MCP-compatible server with minimal overhead. Reuse your existing GraphQL infrastructure to avoid reinventing authorization, schema management, and validation enabling scalable, robust LLM integrations. We’ll compare existing tools and automated schema discovery against hand-crafted mappers based on benchmarks of public GraphQL APIs. Join us to learn about our experiences and recommendations for your next GenAI project, powered by GraphQL.", "goers": "0", "seats": "0", @@ -4394,7 +4442,7 @@ "start_time_ts": 1757512200, "end_date": "2025-09-10", "end_time": "16:20:00", - "event_subtype": "" + "event_subtype": "AI / LLMs" }, { "event_key": "929627", @@ -4534,4 +4582,4 @@ "event_subtype": "", "description": "" } -] \ No newline at end of file +] diff --git a/scripts/sync-sched/speakers.json b/scripts/sync-sched/speakers.json index 33a352f124..7cdfce3f16 100644 --- a/scripts/sync-sched/speakers.json +++ b/scripts/sync-sched/speakers.json @@ -65,7 +65,7 @@ "company": "Airbnb", "position": "Viaduct Platform Lead", "name": "Adam Miskiewicz", - "about": "Adam is a Senior Staff Software Engineer at Airbnb, where he leads platform architecture with a focus on performance, developer experience, and large-scale GraphQL systems. He’s one of the core architects behind Viaduct, Airbnb’s multi-tenant GraphQL platform, and is known for turning complex ideas into scalable, developer-friendly infrastructure. When he's not deep in Kotlin or shaping deployment strategy, he's probably pushing teams to move faster — safely.", + "about": "Adam is a Principal Software Engineer at Airbnb, where he leads platform architecture with a focus on performance, developer experience, and large-scale GraphQL systems. He’s one of the core architects behind Viaduct, Airbnb’s multi-tenant GraphQL platform, and is known for turning complex ideas into scalable, developer-friendly infrastructure. When he's not deep in Kotlin or shaping deployment strategy, he's probably pushing teams to move faster — safely.", "location": "", "url": "", "avatar": "//avatars.sched.co/8/82/23352721/avatar.jpg.320x320px.jpg?bfd", @@ -108,6 +108,21 @@ ], "~syncedDetailsAt": 1749497543087 }, + { + "username": "adam427", + "company": "Amazon", + "position": "Software Development Engineer", + "name": "Adam Cervantes", + "about": "Adam is currently a software development engineer at Amazon's Buyer Abuse Prevention team. For the last 4 years, Adam has been helping prevent returns abuse on Amazon.com. One of his projects was to consolidate the data the team needs into a single, cohesive, GraphQL API.", + "location": "", + "url": "", + "avatar": "//avatars.sched.co/b/ad/3141026/avatar.jpg.320x320px.jpg?90d", + "socialurls": [], + "_years": [ + 2025 + ], + "~syncedDetailsAt": 1756236819315 + }, { "username": "aditi_rajawat", "company": "Intuit", @@ -979,7 +994,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819315 }, { "username": "dotansimha", @@ -1377,12 +1392,12 @@ }, { "username": "ivan.goncharov.ua", - "company": "ApolloGraphQL", - "position": "Staff Software Engineer", + "company": "KeenEthics", + "position": "Head of R&D", "name": "Ivan Goncharov", "about": "Ivan has been active in the GraphQL community since its early days and is currently a member of the GraphQL Technical Steering Committee.", - "location": "", - "url": "", + "location": "Lviv", + "url": "https://apis.guru", "avatar": "//avatars.sched.co/d/6f/23096422/avatar.jpg.320x320px.jpg?958", "socialurls": [], "_years": [ @@ -1528,7 +1543,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819315 }, { "username": "jem28", @@ -1574,21 +1589,6 @@ ], "~syncedDetailsAt": 1749505650884 }, - { - "username": "jerel.miller", - "company": "Apollo GraphQL", - "position": "Principal Software Engineer", - "name": "Jerel Miller", - "about": "Jerel is a Colorado native with a brief stint in Portland Oregon. He loves to code and learn about all sorts of programming patterns. He is an avid Denver Broncos fan and loves to play the bass.", - "location": "", - "url": "", - "avatar": "//avatars.sched.co/6/6e/23098762/avatar.jpg.320x320px.jpg?2ea", - "socialurls": [], - "_years": [ - 2025 - ], - "~syncedDetailsAt": 1754078014495 - }, { "username": "jesperrasmussen", "company": "The LEGO Group", @@ -1646,7 +1646,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819315 }, { "username": "jordaneldredge", @@ -1685,7 +1685,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819315 }, { "username": "juancarlosjr97", @@ -1739,7 +1739,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819316 }, { "username": "kbahl", @@ -1917,7 +1917,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819316 }, { "username": "ldebruijn", @@ -1953,7 +1953,7 @@ 2024, 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819316 }, { "username": "lee_byron.25krdom6", @@ -2007,7 +2007,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819315 }, { "username": "lyonwj1", @@ -2071,7 +2071,7 @@ "_years": [ 2025 ], - "~syncedDetailsAt": 1754078014495 + "~syncedDetailsAt": 1756236819315 }, { "username": "mansi.mittal", From 584cdc4a43558025cc9126af6a45b3951cd8935b Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 29 Aug 2025 17:04:57 +0200 Subject: [PATCH 13/28] Improve syntax highlighting for comments and diffs --- src/_design-system/syntax/light.json | 48 +++++++++-- .../2025/components/testimonials/index.tsx | 2 +- src/app/conf/_api/sched-client.tsx | 15 +++- .../index-page/quotes-from-the-industry.tsx | 77 ------------------ .../quotes-from-the-industry/index.tsx | 64 +++++++++++++++ .../matteo-collina.webp | Bin 0 -> 30032 bytes src/components/marked/syntax-highlighting.css | 9 +- 7 files changed, 122 insertions(+), 93 deletions(-) delete mode 100644 src/components/index-page/quotes-from-the-industry.tsx create mode 100644 src/components/index-page/quotes-from-the-industry/index.tsx create mode 100644 src/components/index-page/quotes-from-the-industry/matteo-collina.webp diff --git a/src/_design-system/syntax/light.json b/src/_design-system/syntax/light.json index 781f2ea182..3e9cd10d5c 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/app/conf/2025/components/testimonials/index.tsx b/src/app/conf/2025/components/testimonials/index.tsx index ae03d84ecd..3aeead8ad8 100644 --- a/src/app/conf/2025/components/testimonials/index.tsx +++ b/src/app/conf/2025/components/testimonials/index.tsx @@ -7,7 +7,7 @@ import maskBlur from "./mask.webp" export interface TestimonialsProps extends React.HTMLAttributes {} export interface Testimonial { - quote: string + quote: React.ReactNode author: { name: string role: string diff --git a/src/app/conf/_api/sched-client.tsx b/src/app/conf/_api/sched-client.tsx index 68d7d1daa6..d2f1c16757 100644 --- a/src/app/conf/_api/sched-client.tsx +++ b/src/app/conf/_api/sched-client.tsx @@ -191,7 +191,20 @@ function preprocessDescription(description: string | undefined | null): string { // respecting
  • and tags doesn't make sense, because speakers don't use them consistently // we'll improve how the descriptions look later down the tree in the session details page - return stripHtml(res).result + return stripHtml(res, { + ignoreTags: [ + "a", + "b", + "i", + "em", + "strong", + "code", + "pre", + "ul", + "ol", + "li", + ], + }).result } function shapeSpeaker(user: SchedSpeaker): SchedSpeaker { diff --git a/src/components/index-page/quotes-from-the-industry.tsx b/src/components/index-page/quotes-from-the-industry.tsx deleted file mode 100644 index 3b1951aef2..0000000000 --- a/src/components/index-page/quotes-from-the-industry.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - TestimonialsList, - type Testimonial, -} from "@/app/conf/2025/components/testimonials" -import { SectionLabel } from "@/app/conf/_design-system/section-label" - -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.", - 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", - }, - }, - { - 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", - }, - }, -] - -export function QuotesFromTheIndustry() { - return ( -
    -
    - - Quotes from the industry - -

    - Loved by world‑class devs -

    -
    - -
    - ) -} diff --git a/src/components/index-page/quotes-from-the-industry/index.tsx b/src/components/index-page/quotes-from-the-industry/index.tsx new file mode 100644 index 0000000000..03395ff537 --- /dev/null +++ b/src/components/index-page/quotes-from-the-industry/index.tsx @@ -0,0 +1,64 @@ +import { + TestimonialsList, + type Testimonial, +} from "@/app/conf/2025/components/testimonials" +import { SectionLabel } from "@/app/conf/_design-system/section-label" + +import matteoCollina from "./matteo-collina.webp" + +const testimonials: Testimonial[] = [ + { + quote: + "GraphQL gives us enterprise performance with start-up agility: streamlined queries, lean payloads, live updates, and lightning-fast responses help our customers focus on building their applications, not building around them.", + author: { + name: "Matteo Collina", + role: "Platformatic, Co-Founder & CTO", + avatar: matteoCollina.src, + }, + }, + { + quote: ( + <> + GraphQL is the best developer tool for creating and managing performant + APIs at scale, both for producers and consumers. It can query any source + and expose anything back, including real time data. It gives + understanding of your API usage that no other + spec can provide. + + ), + author: { + name: "Uri Goldshtein", + role: "The Guild, Founder & CEO", + avatar: + "https://avatars.sched.co/8/2b/14900013/avatar.jpg.320x320px.jpg?9f1", + }, + }, + { + quote: + "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: "Benjie Gillam", + role: "Graphile, Director", + avatar: "https://avatars.sched.co/b/99/18743846/avatar.jpg.320x320px.jpg", + }, + }, +] + +export function QuotesFromTheIndustry() { + return ( +
    +
    + + Quotes from the industry + +

    + Loved by world‑class devs +

    +
    + +
    + ) +} diff --git a/src/components/index-page/quotes-from-the-industry/matteo-collina.webp b/src/components/index-page/quotes-from-the-industry/matteo-collina.webp new file mode 100644 index 0000000000000000000000000000000000000000..06c9138051516e93765335e12a7069e32c510735 GIT binary patch literal 30032 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 literal 0 HcmV?d00001 diff --git a/src/components/marked/syntax-highlighting.css b/src/components/marked/syntax-highlighting.css index 32eedba190..6b45456426 100644 --- a/src/components/marked/syntax-highlighting.css +++ b/src/components/marked/syntax-highlighting.css @@ -1,9 +1,5 @@ -/* CodeMirror 6 Syntax Highlighting with Light/Dark Mode Support */ - -/* CSS Custom Properties for Theme Colors */ :root { - /* Light theme colors (matching Shiki light.json) */ - --cm-comment: #c2c3c5; + --cm-comment: #6e7557; --cm-punctuation: #6e7557; --cm-keyword: #990069; --cm-def: hsl(var(--color-pri-base)); @@ -11,14 +7,13 @@ --cm-qualifier: #1c92a9; --cm-attribute: #8b2bb9; --cm-number: #1976d2; - --cm-string: #2b5581; + --cm-string: #609a06; --cm-builtin: #1976d2; --cm-string-2: #0b7fc7; --cm-variable: #e10198; --cm-meta: #b33086; --cm-atom: #1976d2; - /* Editor UI colors - light theme */ --cm-background: #ffffff; --cm-foreground: #4f533f; --cm-gutter-background: #f7f7f7; From 40d8c122aa71ecd8aa4d5a11b9080a987d28324a Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 29 Aug 2025 17:11:54 +0200 Subject: [PATCH 14/28] Remove focus outline --- src/components/marked/syntax-highlighting.css | 4 ++++ src/components/marked/variable-editor.tsx | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/marked/syntax-highlighting.css b/src/components/marked/syntax-highlighting.css index 6b45456426..63641a58ff 100644 --- a/src/components/marked/syntax-highlighting.css +++ b/src/components/marked/syntax-highlighting.css @@ -171,6 +171,10 @@ background: var(--cm-selection); } +.cm-focused.cm-focused { + outline: none; +} + .cm-focused .cm-selectionBackground { background: var(--cm-selection); } diff --git a/src/components/marked/variable-editor.tsx b/src/components/marked/variable-editor.tsx index 88a50434fa..9b750b7f9b 100644 --- a/src/components/marked/variable-editor.tsx +++ b/src/components/marked/variable-editor.tsx @@ -58,15 +58,6 @@ export class VariableEditor extends Component { } } }), - EditorView.theme({ - ".cm-editor": { - fontSize: "inherit", - fontFamily: "inherit", - }, - ".cm-focused": { - outline: "none", - }, - }), ], }) From f94ddd3611ba8386bb4308a585d600fe2dac47ab Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 29 Aug 2025 17:19:54 +0200 Subject: [PATCH 15/28] Extract CodeBlockLabel --- src/codemirror-old.less | 938 ------------------------ src/components/pre/code-block-label.tsx | 29 + src/components/pre/index.tsx | 13 +- src/components/pre/pre.module.css | 4 +- 4 files changed, 33 insertions(+), 951 deletions(-) delete mode 100644 src/codemirror-old.less create mode 100644 src/components/pre/code-block-label.tsx diff --git a/src/codemirror-old.less b/src/codemirror-old.less deleted file mode 100644 index d004835fb9..0000000000 --- a/src/codemirror-old.less +++ /dev/null @@ -1,938 +0,0 @@ -@import "variables.less"; - -/* Code Mirror 6 & Compatibility */ - -/* BASICS - CodeMirror 6 */ -.cm-editor { - /* Set height, width, borders, and global font properties here */ - height: 300px; - .code-font(); - background: var(--cm-background); - color: var(--cm-foreground); - position: relative; - overflow: hidden; -} - -.cm-scroller { - overflow: auto; - position: relative; -} - -.cm-content { - min-height: 1px; - cursor: text; - padding: 4px 0; -} - -.cm-line { - padding: 0 4px; - line-height: inherit; - color: inherit; -} - -.cm-focused { - outline: none; -} - -/* Compatibility aliases for CodeMirror 5 */ -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - height: 300px; - .code-font(); - background: var(--cm-background); - color: var(--cm-foreground); -} - -/* 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: var( - --cm-background - ); /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid var(--cm-gutter-border); - background-color: var(--cm-gutter-background); - white-space: nowrap; -} -.CodeMirror-linenumbers { -} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: var(--cm-line-number); - white-space: nowrap; -} - -.CodeMirror-guttermarker { - color: black; -} -.CodeMirror-guttermarker-subtle { - color: #999; -} - -/* CURSOR */ - -.CodeMirror-cursor { - border-left: 1px solid var(--cm-cursor); - 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-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: var(--cm-background); -} - -.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: var(--cm-selection); -} -.CodeMirror-focused .CodeMirror-selected { - background: var(--cm-selection); -} -.CodeMirror-crosshair { - cursor: crosshair; -} -.CodeMirror-line::selection, -.CodeMirror-line > span::selection, -.CodeMirror-line > span > span::selection { - background: var(--cm-selection); -} -.CodeMirror-line::-moz-selection, -.CodeMirror-line > span::-moz-selection, -.CodeMirror-line > span > span::-moz-selection { - background: var(--cm-selection); -} - -.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: var(--cm-hints-background); - -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: var(--cm-hints-background); - -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: var(--cm-hints-foreground); - cursor: pointer; - margin: 0; - max-width: 300px; - overflow: hidden; - padding: 2px 6px; - white-space: pre; -} - -li.CodeMirror-hint-active { - background-color: var(--cm-hints-active-background); - border-top-color: var(--cm-hints-active-background); - color: var(--cm-hints-active-foreground); -} - -.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: var(--cm-hints-background); - color: var(--cm-hints-foreground); - 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 */ - -/* CSS Custom Properties for Theme Colors */ -:root { - /* Light theme colors (matching Shiki light.json) */ - --cm-comment: #c2c3c5; - --cm-punctuation: #6e7557; - --cm-keyword: #e10198; - --cm-keyword-graphql: #990069; - --cm-def: #0e0f0b; - --cm-property: #0e0f0b; - --cm-qualifier: #1c92a9; - --cm-attribute: #8b2bb9; - --cm-number: #1976d2; - --cm-string: #2b5581; - --cm-builtin: #1976d2; - --cm-string-2: #0b7fc7; - --cm-variable: #e10198; - --cm-meta: #b33086; - --cm-atom: #1976d2; - - /* Editor UI colors - light theme */ - --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: #ffffff; - --cm-hints-foreground: #333; - --cm-hints-active-background: #08f; - --cm-hints-active-foreground: #ffffff; -} - -.dark { - /* Dark theme colors (matching Shiki dark.json) */ - --cm-comment: #6a737d; - --cm-punctuation: #6e7557; - --cm-keyword: #c2f653; - --cm-keyword-graphql: #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: #24292c; - --cm-foreground: #cfd3c5; - --cm-gutter-background: #1f2425; - --cm-gutter-border: #1b1f20; - --cm-line-number: #6a737d; - --cm-cursor: #c8e1ff; - --cm-selection: #3392ff44; - --cm-hints-background: #24292c; - --cm-hints-foreground: #e1e4e8; - --cm-hints-active-background: #044289; - --cm-hints-active-foreground: #ffffff; -} - -/* Comment */ -.cm-comment { - color: var(--cm-comment); -} - -/* Punctuation */ -.cm-punctuation { - color: var(--cm-punctuation); -} - -/* Keyword */ -.cm-keyword { - color: var(--cm-keyword); -} - -/* OperationName, FragmentName */ -.cm-def { - color: var(--cm-def); -} - -/* FieldName */ -.cm-property { - color: var(--cm-property); -} - -/* FieldAlias */ -.cm-qualifier { - color: var(--cm-qualifier); -} - -/* ArgumentName and ObjectFieldName */ -.cm-attribute { - color: var(--cm-attribute); -} - -/* Number */ -.cm-number { - color: var(--cm-number); -} - -/* String */ -.cm-string { - color: var(--cm-string); -} - -/* Boolean */ -.cm-builtin { - color: var(--cm-builtin); -} - -/* EnumValue */ -.cm-string-2 { - color: var(--cm-string-2); -} - -/* Variable */ -.cm-variable { - color: var(--cm-variable); -} - -/* Directive */ -.cm-meta { - color: var(--cm-meta); -} - -/* Type */ -.cm-atom { - color: var(--cm-atom); -} - -/* CM override */ - -.CodeMirror, -.cm-editor { - .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: transparent; - - 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, -.query-editor .cm-editor { - 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, - .query-editor .cm-editor { - margin-bottom: 21px; - } - - .variable-editor .CodeMirror, - .variable-editor .cm-editor { - height: auto; - min-height: 30px; - margin: 0 7px; - background: none; - } -} - -.result-window { - position: absolute; - left: 50%; - top: 0; - bottom: 0; - height: 100%; - right: 0; - - border-radius: 3px; -} - -.result-window .CodeMirror, -.result-window .cm-editor { - 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/pre/code-block-label.tsx b/src/components/pre/code-block-label.tsx new file mode 100644 index 0000000000..1a59d8fc4d --- /dev/null +++ b/src/components/pre/code-block-label.tsx @@ -0,0 +1,29 @@ +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 +} + +export function CodeBlockLabel({ + text, + icon: Icon, + button, +}: CodeBlockLabelProps): ReactElement { + return ( +
    + {Icon && } + {text} + {button} +
    + ) +} diff --git a/src/components/pre/index.tsx b/src/components/pre/index.tsx index 0c0409df83..025f1f9cd6 100644 --- a/src/components/pre/index.tsx +++ b/src/components/pre/index.tsx @@ -5,6 +5,7 @@ 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"> { "data-filename"?: string @@ -39,17 +40,7 @@ export function Pre({ return (
    {filename && ( - // TODO: Extract this as a component for import in MiniGraphiQL -
    - {Icon && } - {filename} - {copyButton} -
    + )}
    Date: Fri, 29 Aug 2025 17:23:46 +0200
    Subject: [PATCH 16/28] Remove unused marked dependency
    
    ---
     package.json   |  1 -
     pnpm-lock.yaml | 10 ----------
     2 files changed, 11 deletions(-)
    
    diff --git a/package.json b/package.json
    index 65392161b6..35aeeaf8fe 100644
    --- a/package.json
    +++ b/package.json
    @@ -53,7 +53,6 @@
         "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.32",
         "next-image-export-optimizer": "^1.18.0",
    diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
    index f6adc564c0..af269e438e 100644
    --- a/pnpm-lock.yaml
    +++ b/pnpm-lock.yaml
    @@ -112,9 +112,6 @@ 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.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
    @@ -4024,11 +4021,6 @@ packages:
         engines: {node: '>= 18'}
         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'}
    @@ -10023,8 +10015,6 @@ snapshots:
     
       marked@15.0.12: {}
     
    -  marked@5.1.2: {}
    -
       math-intrinsics@1.1.0: {}
     
       mathjax-full@3.2.2:
    
    From 5e3a31ebbb6045cc3639f80a90d6dffd07b35b3e Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Fri, 29 Aug 2025 17:34:44 +0200
    Subject: [PATCH 17/28] Rename `marked` to `interactive-code-block`
    
    ---
     .../codemirror-one-dark.tsx                                | 0
     .../get-variable-to-type.tsx                               | 0
     .../{marked => interactive-code-block}/index.tsx           | 0
     .../{marked => interactive-code-block}/mini-graphiQL.tsx   | 0
     .../on-has-completion.tsx                                  | 0
     .../{marked => interactive-code-block}/query-editor.tsx    | 0
     .../{marked => interactive-code-block}/result-viewer.tsx   | 0
     .../{marked => interactive-code-block}/swapi-schema.tsx    | 0
     .../syntax-highlighting.css                                | 0
     .../{marked => interactive-code-block}/users-schema.ts     | 0
     .../{marked => interactive-code-block}/variable-editor.tsx | 0
     src/pages/learn/index.mdx                                  | 2 +-
     src/remark-graphiql-comment.js                             | 7 ++++---
     13 files changed, 5 insertions(+), 4 deletions(-)
     rename src/components/{marked => interactive-code-block}/codemirror-one-dark.tsx (100%)
     rename src/components/{marked => interactive-code-block}/get-variable-to-type.tsx (100%)
     rename src/components/{marked => interactive-code-block}/index.tsx (100%)
     rename src/components/{marked => interactive-code-block}/mini-graphiQL.tsx (100%)
     rename src/components/{marked => interactive-code-block}/on-has-completion.tsx (100%)
     rename src/components/{marked => interactive-code-block}/query-editor.tsx (100%)
     rename src/components/{marked => interactive-code-block}/result-viewer.tsx (100%)
     rename src/components/{marked => interactive-code-block}/swapi-schema.tsx (100%)
     rename src/components/{marked => interactive-code-block}/syntax-highlighting.css (100%)
     rename src/components/{marked => interactive-code-block}/users-schema.ts (100%)
     rename src/components/{marked => interactive-code-block}/variable-editor.tsx (100%)
    
    diff --git a/src/components/marked/codemirror-one-dark.tsx b/src/components/interactive-code-block/codemirror-one-dark.tsx
    similarity index 100%
    rename from src/components/marked/codemirror-one-dark.tsx
    rename to src/components/interactive-code-block/codemirror-one-dark.tsx
    diff --git a/src/components/marked/get-variable-to-type.tsx b/src/components/interactive-code-block/get-variable-to-type.tsx
    similarity index 100%
    rename from src/components/marked/get-variable-to-type.tsx
    rename to src/components/interactive-code-block/get-variable-to-type.tsx
    diff --git a/src/components/marked/index.tsx b/src/components/interactive-code-block/index.tsx
    similarity index 100%
    rename from src/components/marked/index.tsx
    rename to src/components/interactive-code-block/index.tsx
    diff --git a/src/components/marked/mini-graphiQL.tsx b/src/components/interactive-code-block/mini-graphiQL.tsx
    similarity index 100%
    rename from src/components/marked/mini-graphiQL.tsx
    rename to src/components/interactive-code-block/mini-graphiQL.tsx
    diff --git a/src/components/marked/on-has-completion.tsx b/src/components/interactive-code-block/on-has-completion.tsx
    similarity index 100%
    rename from src/components/marked/on-has-completion.tsx
    rename to src/components/interactive-code-block/on-has-completion.tsx
    diff --git a/src/components/marked/query-editor.tsx b/src/components/interactive-code-block/query-editor.tsx
    similarity index 100%
    rename from src/components/marked/query-editor.tsx
    rename to src/components/interactive-code-block/query-editor.tsx
    diff --git a/src/components/marked/result-viewer.tsx b/src/components/interactive-code-block/result-viewer.tsx
    similarity index 100%
    rename from src/components/marked/result-viewer.tsx
    rename to src/components/interactive-code-block/result-viewer.tsx
    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/marked/syntax-highlighting.css b/src/components/interactive-code-block/syntax-highlighting.css
    similarity index 100%
    rename from src/components/marked/syntax-highlighting.css
    rename to src/components/interactive-code-block/syntax-highlighting.css
    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/marked/variable-editor.tsx b/src/components/interactive-code-block/variable-editor.tsx
    similarity index 100%
    rename from src/components/marked/variable-editor.tsx
    rename to src/components/interactive-code-block/variable-editor.tsx
    diff --git a/src/pages/learn/index.mdx b/src/pages/learn/index.mdx
    index ada9acfe38..2a2003033c 100644
    --- a/src/pages/learn/index.mdx
    +++ b/src/pages/learn/index.mdx
    @@ -95,7 +95,7 @@ The best way to learn GraphQL is to start writing queries. The query editors use
     ```
     
     
    -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 8524cb27c1..e1d30cb312 100644
    --- a/src/remark-graphiql-comment.js
    +++ b/src/remark-graphiql-comment.js
    @@ -1,10 +1,11 @@
     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)
    @@ -25,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",
    
    From fe7993194f4ceb5bfafeafb8bb6993dc3dc1329d Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Fri, 29 Aug 2025 19:09:37 +0200
    Subject: [PATCH 18/28] Update codemirror styles
    
    ---
     package.json                                  |   4 +-
     pnpm-lock.yaml                                | 118 +++++++++---------
     src/_design-system/mdx-components/index.tsx   |  12 ++
     src/_design-system/syntax/dark.json           |   4 +-
     ...rror-one-dark.tsx => codemirror-theme.tsx} |  13 +-
     .../interactive-code-block/index.tsx          |  36 +-----
     .../interactive-code-block/mini-graphiQL.tsx  |  95 ++++++++++----
     .../interactive-code-block/query-editor.tsx   |   8 +-
     .../interactive-code-block/result-viewer.tsx  |  12 +-
     .../syntax-highlighting.css                   |   6 +-
     .../variable-editor.tsx                       |   4 +-
     src/components/pre/code-block-label.tsx       |   5 +-
     src/components/pre/index.tsx                  |  11 +-
     src/globals.css                               |   8 --
     src/remark-graphiql-comment.js                |   2 +-
     15 files changed, 179 insertions(+), 159 deletions(-)
     rename src/components/interactive-code-block/{codemirror-one-dark.tsx => codemirror-theme.tsx} (89%)
    
    diff --git a/package.json b/package.json
    index 35aeeaf8fe..fec2b6e008 100644
    --- a/package.json
    +++ b/package.json
    @@ -93,7 +93,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",
    @@ -111,7 +111,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",
    diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
    index af269e438e..715684f6ce 100644
    --- a/pnpm-lock.yaml
    +++ b/pnpm-lock.yaml
    @@ -132,10 +132,10 @@ importers:
             version: 3.0.1(less-loader@12.2.0(less@4.2.1))(less@4.2.1)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
           nextra:
             specifier: 3.3.1
    -        version: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3)
    +        version: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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=d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
    +        version: 3.3.1(patch_hash=d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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
    @@ -199,7 +199,7 @@ importers:
         devDependencies:
           '@graphql-eslint/eslint-plugin':
             specifier: 4.4.0
    -        version: 4.4.0(@types/node@22.10.5)(eslint@8.57.1)(graphql@16.10.0)(typescript@5.8.3)
    +        version: 4.4.0(@types/node@22.10.5)(eslint@8.57.1)(graphql@16.10.0)(typescript@5.9.2)
           '@next/eslint-plugin-next':
             specifier: ^15.3.3
             version: 15.3.3
    @@ -208,7 +208,7 @@ importers:
             version: 1.54.2
           '@svgr/webpack':
             specifier: ^8.1.0
    -        version: 8.1.0(typescript@5.8.3)
    +        version: 8.1.0(typescript@5.9.2)
           '@types/codemirror':
             specifier: 5.60.16
             version: 5.60.16
    @@ -219,7 +219,7 @@ importers:
             specifier: ^22.10.5
             version: 22.10.5
           '@types/react':
    -        specifier: ^18.3.18
    +        specifier: ^18.3.23
             version: 18.3.23
           '@types/rss':
             specifier: 0.0.32
    @@ -229,10 +229,10 @@ importers:
             version: 4.0.2
           '@typescript-eslint/eslint-plugin':
             specifier: 7.18.0
    -        version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)
    +        version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)
           '@typescript-eslint/parser':
             specifier: 7.18.0
    -        version: 7.18.0(eslint@8.57.1)(typescript@5.8.3)
    +        version: 7.18.0(eslint@8.57.1)(typescript@5.9.2)
           eslint:
             specifier: 8.57.1
             version: 8.57.1
    @@ -273,8 +273,8 @@ importers:
             specifier: ^4.19.4
             version: 4.19.4
           typescript:
    -        specifier: ^5.8.3
    -        version: 5.8.3
    +        specifier: ^5.9.2
    +        version: 5.9.2
         optionalDependencies:
           playwright:
             specifier: ^1.54.2
    @@ -5467,8 +5467,8 @@ packages:
       typedarray@0.0.6:
         resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
     
    -  typescript@5.8.3:
    -    resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
    +  typescript@5.9.2:
    +    resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
         engines: {node: '>=14.17'}
         hasBin: true
     
    @@ -6775,7 +6775,7 @@ snapshots:
     
       '@fortawesome/fontawesome-free@6.7.2': {}
     
    -  '@graphql-eslint/eslint-plugin@4.4.0(@types/node@22.10.5)(eslint@8.57.1)(graphql@16.10.0)(typescript@5.8.3)':
    +  '@graphql-eslint/eslint-plugin@4.4.0(@types/node@22.10.5)(eslint@8.57.1)(graphql@16.10.0)(typescript@5.9.2)':
         dependencies:
           '@graphql-tools/code-file-loader': 8.1.10(graphql@16.10.0)
           '@graphql-tools/graphql-tag-pluck': 8.3.9(graphql@16.10.0)
    @@ -6784,7 +6784,7 @@ snapshots:
           eslint: 8.57.1
           fast-glob: 3.3.3
           graphql: 16.10.0
    -      graphql-config: 5.1.3(@types/node@22.10.5)(graphql@16.10.0)(typescript@5.8.3)
    +      graphql-config: 5.1.3(@types/node@22.10.5)(graphql@16.10.0)(typescript@5.9.2)
           graphql-depth-limit: 1.1.0(graphql@16.10.0)
           lodash.lowercase: 4.3.0
         transitivePeerDependencies:
    @@ -7494,10 +7494,10 @@ snapshots:
     
       '@shikijs/core@1.2.1': {}
     
    -  '@shikijs/twoslash@1.2.1(typescript@5.8.3)':
    +  '@shikijs/twoslash@1.2.1(typescript@5.9.2)':
         dependencies:
           '@shikijs/core': 1.2.1
    -      twoslash: 0.2.5(typescript@5.8.3)
    +      twoslash: 0.2.5(typescript@5.9.2)
         transitivePeerDependencies:
           - supports-color
           - typescript
    @@ -7554,12 +7554,12 @@ snapshots:
           '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.0)
           '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.0)
     
    -  '@svgr/core@8.1.0(typescript@5.8.3)':
    +  '@svgr/core@8.1.0(typescript@5.9.2)':
         dependencies:
           '@babel/core': 7.26.0
           '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0)
           camelcase: 6.3.0
    -      cosmiconfig: 8.3.6(typescript@5.8.3)
    +      cosmiconfig: 8.3.6(typescript@5.9.2)
           snake-case: 3.0.4
         transitivePeerDependencies:
           - supports-color
    @@ -7570,35 +7570,35 @@ snapshots:
           '@babel/types': 7.26.3
           entities: 4.5.0
     
    -  '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))':
    +  '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.2))':
         dependencies:
           '@babel/core': 7.26.0
           '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0)
    -      '@svgr/core': 8.1.0(typescript@5.8.3)
    +      '@svgr/core': 8.1.0(typescript@5.9.2)
           '@svgr/hast-util-to-babel-ast': 8.0.0
           svg-parser: 2.0.4
         transitivePeerDependencies:
           - supports-color
     
    -  '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))(typescript@5.8.3)':
    +  '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.9.2))(typescript@5.9.2)':
         dependencies:
    -      '@svgr/core': 8.1.0(typescript@5.8.3)
    -      cosmiconfig: 8.3.6(typescript@5.8.3)
    +      '@svgr/core': 8.1.0(typescript@5.9.2)
    +      cosmiconfig: 8.3.6(typescript@5.9.2)
           deepmerge: 4.3.1
           svgo: 3.3.2
         transitivePeerDependencies:
           - typescript
     
    -  '@svgr/webpack@8.1.0(typescript@5.8.3)':
    +  '@svgr/webpack@8.1.0(typescript@5.9.2)':
         dependencies:
           '@babel/core': 7.26.0
           '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.0)
           '@babel/preset-env': 7.26.0(@babel/core@7.26.0)
           '@babel/preset-react': 7.26.3(@babel/core@7.26.0)
           '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0)
    -      '@svgr/core': 8.1.0(typescript@5.8.3)
    -      '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
    -      '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))(typescript@5.8.3)
    +      '@svgr/core': 8.1.0(typescript@5.9.2)
    +      '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2))
    +      '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2))(typescript@5.9.2)
         transitivePeerDependencies:
           - supports-color
           - typescript
    @@ -7861,34 +7861,34 @@ snapshots:
         dependencies:
           '@types/node': 22.10.5
     
    -  '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)':
    +  '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)':
         dependencies:
           '@eslint-community/regexpp': 4.10.0
    -      '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
    +      '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2)
           '@typescript-eslint/scope-manager': 7.18.0
    -      '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
    -      '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
    +      '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2)
    +      '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2)
           '@typescript-eslint/visitor-keys': 7.18.0
           eslint: 8.57.1
           graphemer: 1.4.0
           ignore: 5.3.1
           natural-compare: 1.4.0
    -      ts-api-utils: 1.3.0(typescript@5.8.3)
    +      ts-api-utils: 1.3.0(typescript@5.9.2)
         optionalDependencies:
    -      typescript: 5.8.3
    +      typescript: 5.9.2
         transitivePeerDependencies:
           - supports-color
     
    -  '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3)':
    +  '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2)':
         dependencies:
           '@typescript-eslint/scope-manager': 7.18.0
           '@typescript-eslint/types': 7.18.0
    -      '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3)
    +      '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2)
           '@typescript-eslint/visitor-keys': 7.18.0
           debug: 4.3.4
           eslint: 8.57.1
         optionalDependencies:
    -      typescript: 5.8.3
    +      typescript: 5.9.2
         transitivePeerDependencies:
           - supports-color
     
    @@ -7897,21 +7897,21 @@ snapshots:
           '@typescript-eslint/types': 7.18.0
           '@typescript-eslint/visitor-keys': 7.18.0
     
    -  '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.8.3)':
    +  '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)':
         dependencies:
    -      '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3)
    -      '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
    +      '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2)
    +      '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2)
           debug: 4.3.4
           eslint: 8.57.1
    -      ts-api-utils: 1.3.0(typescript@5.8.3)
    +      ts-api-utils: 1.3.0(typescript@5.9.2)
         optionalDependencies:
    -      typescript: 5.8.3
    +      typescript: 5.9.2
         transitivePeerDependencies:
           - supports-color
     
       '@typescript-eslint/types@7.18.0': {}
     
    -  '@typescript-eslint/typescript-estree@7.18.0(typescript@5.8.3)':
    +  '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.2)':
         dependencies:
           '@typescript-eslint/types': 7.18.0
           '@typescript-eslint/visitor-keys': 7.18.0
    @@ -7920,18 +7920,18 @@ snapshots:
           is-glob: 4.0.3
           minimatch: 9.0.5
           semver: 7.6.0
    -      ts-api-utils: 1.3.0(typescript@5.8.3)
    +      ts-api-utils: 1.3.0(typescript@5.9.2)
         optionalDependencies:
    -      typescript: 5.8.3
    +      typescript: 5.9.2
         transitivePeerDependencies:
           - supports-color
     
    -  '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.8.3)':
    +  '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)':
         dependencies:
           '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1)
           '@typescript-eslint/scope-manager': 7.18.0
           '@typescript-eslint/types': 7.18.0
    -      '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3)
    +      '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2)
           eslint: 8.57.1
         transitivePeerDependencies:
           - supports-color
    @@ -8389,14 +8389,14 @@ snapshots:
         dependencies:
           layout-base: 2.0.1
     
    -  cosmiconfig@8.3.6(typescript@5.8.3):
    +  cosmiconfig@8.3.6(typescript@5.9.2):
         dependencies:
           import-fresh: 3.3.0
           js-yaml: 4.1.0
           parse-json: 5.2.0
           path-type: 4.0.0
         optionalDependencies:
    -      typescript: 5.8.3
    +      typescript: 5.9.2
     
       crelt@1.0.6: {}
     
    @@ -9342,7 +9342,7 @@ snapshots:
     
       graphemer@1.4.0: {}
     
    -  graphql-config@5.1.3(@types/node@22.10.5)(graphql@16.10.0)(typescript@5.8.3):
    +  graphql-config@5.1.3(@types/node@22.10.5)(graphql@16.10.0)(typescript@5.9.2):
         dependencies:
           '@graphql-tools/graphql-file-loader': 8.0.8(graphql@16.10.0)
           '@graphql-tools/json-file-loader': 8.0.8(graphql@16.10.0)
    @@ -9350,7 +9350,7 @@ snapshots:
           '@graphql-tools/merge': 9.1.1(graphql@16.10.0)
           '@graphql-tools/url-loader': 8.0.20(@types/node@22.10.5)(graphql@16.10.0)
           '@graphql-tools/utils': 10.9.1(graphql@16.10.0)
    -      cosmiconfig: 8.3.6(typescript@5.8.3)
    +      cosmiconfig: 8.3.6(typescript@5.9.2)
           graphql: 16.10.0
           jiti: 2.4.2
           minimatch: 9.0.5
    @@ -10664,7 +10664,7 @@ snapshots:
           next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
           react: 18.3.1
           sharp: 0.33.5
    -      typescript: 5.8.3
    +      typescript: 5.9.2
     
       next-query-params@5.0.1(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(use-query-params@2.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
         dependencies:
    @@ -10719,7 +10719,7 @@ snapshots:
           - '@babel/core'
           - babel-plugin-macros
     
    -  nextra-theme-docs@3.3.1(patch_hash=d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
    +  nextra-theme-docs@3.3.1(patch_hash=d2bc2e328040a21c2c3cb0757d2399ad1148a4a11c106f78af83b947e06f3521)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
           clsx: 2.1.1
    @@ -10727,20 +10727,20 @@ snapshots:
           flexsearch: 0.7.43
           next: 14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3)
    +      nextra: 3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.22.4
     
    -  nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.8.3):
    +  nextra@3.3.1(patch_hash=ea67bba83484b83abd3764c3d267a635474008733ff1efe84d2a16d02fddc42d)(@types/react@18.3.23)(next@14.2.32(@babel/core@7.26.0)(@playwright/test@1.54.2)(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.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
           '@mdx-js/mdx': 3.0.1
           '@mdx-js/react': 3.0.1(@types/react@18.3.23)(react@18.3.1)
           '@napi-rs/simple-git': 0.1.16
    -      '@shikijs/twoslash': 1.2.1(typescript@5.8.3)
    +      '@shikijs/twoslash': 1.2.1(typescript@5.9.2)
           '@theguild/remark-mermaid': 0.1.3(react@18.3.1)
           '@theguild/remark-npm2yarn': 0.3.3
           better-react-mathjax: 2.0.3(react@18.3.1)
    @@ -11922,9 +11922,9 @@ snapshots:
     
       trough@2.2.0: {}
     
    -  ts-api-utils@1.3.0(typescript@5.8.3):
    +  ts-api-utils@1.3.0(typescript@5.9.2):
         dependencies:
    -      typescript: 5.8.3
    +      typescript: 5.9.2
     
       ts-dedent@2.2.0: {}
     
    @@ -11943,11 +11943,11 @@ snapshots:
     
       twoslash-protocol@0.2.5: {}
     
    -  twoslash@0.2.5(typescript@5.8.3):
    +  twoslash@0.2.5(typescript@5.9.2):
         dependencies:
           '@typescript/vfs': 1.5.0
           twoslash-protocol: 0.2.5
    -      typescript: 5.8.3
    +      typescript: 5.9.2
         transitivePeerDependencies:
           - supports-color
     
    @@ -11994,7 +11994,7 @@ snapshots:
     
       typedarray@0.0.6: {}
     
    -  typescript@5.8.3: {}
    +  typescript@5.9.2: {}
     
       ufo@1.6.1: {}
     
    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..8057389657 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/components/interactive-code-block/codemirror-one-dark.tsx b/src/components/interactive-code-block/codemirror-theme.tsx
    similarity index 89%
    rename from src/components/interactive-code-block/codemirror-one-dark.tsx
    rename to src/components/interactive-code-block/codemirror-theme.tsx
    index dac08387ad..622e66cdff 100644
    --- a/src/components/interactive-code-block/codemirror-one-dark.tsx
    +++ b/src/components/interactive-code-block/codemirror-theme.tsx
    @@ -3,8 +3,7 @@ import { Extension } from "@codemirror/state"
     import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"
     import { tags as t } from "@lezer/highlight"
     
    -/// The editor theme styles for One Dark using CSS custom properties.
    -export const oneDarkTheme = EditorView.theme({
    +export const editorTheme = EditorView.theme({
       "&": {
         color: "var(--cm-foreground)",
         backgroundColor: "var(--cm-background)",
    @@ -82,8 +81,7 @@ export const oneDarkTheme = EditorView.theme({
       },
     })
     
    -/// The highlighting style for code using CSS custom properties.
    -export const oneDarkHighlightStyle = HighlightStyle.define([
    +export const syntaxTheme = HighlightStyle.define([
       { tag: t.keyword, class: "cm-keyword" },
       {
         tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
    @@ -132,8 +130,7 @@ export const oneDarkHighlightStyle = HighlightStyle.define([
       },
     ])
     
    -/// Extension to enable the One Dark theme using CSS custom properties.
    -export const oneDark: Extension = [
    -  oneDarkTheme,
    -  syntaxHighlighting(oneDarkHighlightStyle),
    +export const codeMirrorThemeExtension: Extension = [
    +  editorTheme,
    +  syntaxHighlighting(syntaxTheme),
     ]
    diff --git a/src/components/interactive-code-block/index.tsx b/src/components/interactive-code-block/index.tsx
    index 07e9eb5df9..e856fdcd0b 100644
    --- a/src/components/interactive-code-block/index.tsx
    +++ b/src/components/interactive-code-block/index.tsx
    @@ -1,35 +1,5 @@
     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")
    -
    -  const metaMatch = firstLine.match(/^\s*#\s*({.*})\s*$/)?.[1] ?? "{}"
    -  const meta = JSON.parse(metaMatch) as Metadata
    -
    -  const query = rest.join("\n")
    -  const variables = meta.variables
    -    ? JSON.stringify(meta.variables, null, 2)
    -    : ""
    -  const schema = SCHEMA_MAP[meta.schema ?? "StarWars"]
    -
    -  return 
    -}
    +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
    index 4aeac691e4..9addc0c6f4 100644
    --- a/src/components/interactive-code-block/mini-graphiQL.tsx
    +++ b/src/components/interactive-code-block/mini-graphiQL.tsx
    @@ -14,12 +14,12 @@ 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 = {
    -  schema: GraphQLSchema
    -  query: string
    -  variables: string
    -  rootValue?: any
    +  children: string
     }
     
     interface MiniGraphiQLState {
    @@ -29,6 +29,19 @@ interface MiniGraphiQLState {
       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
    @@ -37,46 +50,77 @@ export default class MiniGraphiQL extends Component<
     
       _editorQueryID = 0
     
    +  schema: GraphQLSchema
    +
       constructor(props: MiniGraphiQLProps) {
         super(props)
    -    const query = props.query.replace(/^\s+/, "")
     
    -    // Initialize state
    +    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: props.variables,
    +      variables: variables,
           response: null,
    -      variableToType: getVariableToType(props.schema, query),
    +      variableToType: getVariableToType(this.schema, query),
         }
       }
     
       render() {
         const editor = (
    -      
    +      
    + + +
    ) return (
    {Object.keys(this.state.variableToType).length > 0 ? ( -
    +
    {editor} - +
    + + void this._runQuery.bind(this)} + /> +
    ) : ( editor )} - +
    + + +
    ) } @@ -89,7 +133,7 @@ export default class MiniGraphiQL extends Component< _runQueryFromEditor() { this.setState({ - variableToType: getVariableToType(this.props.schema, this.state.query), + variableToType: getVariableToType(this.schema, this.state.query), }) this._runQuery({ manual: true }) } @@ -99,10 +143,9 @@ export default class MiniGraphiQL extends Component< const queryID = this._editorQueryID try { const result = await graphql({ - schema: this.props.schema, + schema: this.schema, source: this.state.query, variableValues: JSON.parse(this.state.variables || "{}"), - rootValue: this.props.rootValue, }) let resultToSerialize: any = result diff --git a/src/components/interactive-code-block/query-editor.tsx b/src/components/interactive-code-block/query-editor.tsx index 5ccde9744d..65ec0e2e22 100644 --- a/src/components/interactive-code-block/query-editor.tsx +++ b/src/components/interactive-code-block/query-editor.tsx @@ -10,7 +10,7 @@ import { } from "@codemirror/autocomplete" import { graphql, updateSchema } from "cm6-graphql" import { GraphQLSchema } from "graphql" -import { oneDark } from "./codemirror-one-dark" +import { codeMirrorThemeExtension } from "./codemirror-theme" import "./syntax-highlighting.css" interface QueryEditorProps { @@ -78,7 +78,7 @@ export class QueryEditor extends Component { bracketMatching(), keymap.of([...historyKeymap, ...completionKeymap, ...defaultKeymap]), runQueryBinding, - oneDark, + codeMirrorThemeExtension, graphql(this.props.schema, {}), autocompletion({ icons: false, @@ -144,9 +144,7 @@ export class QueryEditor extends Component { ref={e => { this.domNode = e }} - > - Operation -
    + /> ) } } diff --git a/src/components/interactive-code-block/result-viewer.tsx b/src/components/interactive-code-block/result-viewer.tsx index ad0015946a..d6aab400f1 100644 --- a/src/components/interactive-code-block/result-viewer.tsx +++ b/src/components/interactive-code-block/result-viewer.tsx @@ -3,7 +3,7 @@ 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 { oneDark } from "./codemirror-one-dark" +import { codeMirrorThemeExtension } from "./codemirror-theme" import "./syntax-highlighting.css" @@ -31,7 +31,11 @@ export class ResultViewer extends Component { // Create read-only editor state for JSON results const state = EditorState.create({ doc: this.props.value || "", - extensions: [EditorState.readOnly.of(true), json(), oneDark], + extensions: [ + EditorState.readOnly.of(true), + json(), + codeMirrorThemeExtension, + ], }) // Create editor view @@ -71,9 +75,7 @@ export class ResultViewer extends Component { ref={e => { this.domNode = e }} - > - Response -
    + /> ) } } diff --git a/src/components/interactive-code-block/syntax-highlighting.css b/src/components/interactive-code-block/syntax-highlighting.css index 63641a58ff..90ff291459 100644 --- a/src/components/interactive-code-block/syntax-highlighting.css +++ b/src/components/interactive-code-block/syntax-highlighting.css @@ -29,7 +29,7 @@ .dark { /* Dark theme colors (matching Shiki dark.json) */ - --cm-comment: #6a737d; + --cm-comment: #737373; --cm-punctuation: #6e7557; --cm-keyword: #c2f653; --cm-def: #dbf6a2; @@ -45,11 +45,11 @@ --cm-atom: #79b8ff; /* Editor UI colors - dark theme */ - --cm-background: #000; + --cm-background: hsl(var(--color-neu-800) / 0.025); --cm-foreground: #cfd3c5; --cm-gutter-background: #1f2425; --cm-gutter-border: #1b1f20; - --cm-line-number: #6a737d; + --cm-line-number: #737373; --cm-cursor: #c8e1ff; --cm-selection: #3392ff44; --cm-hints-foreground: #e1e4e8; diff --git a/src/components/interactive-code-block/variable-editor.tsx b/src/components/interactive-code-block/variable-editor.tsx index 9b750b7f9b..89d3ab0e13 100644 --- a/src/components/interactive-code-block/variable-editor.tsx +++ b/src/components/interactive-code-block/variable-editor.tsx @@ -107,9 +107,7 @@ export class VariableEditor extends Component { ref={e => { this.domNode = e }} - > - Variables -
  • + /> ) } } diff --git a/src/components/pre/code-block-label.tsx b/src/components/pre/code-block-label.tsx index 1a59d8fc4d..2de78c266e 100644 --- a/src/components/pre/code-block-label.tsx +++ b/src/components/pre/code-block-label.tsx @@ -7,18 +7,21 @@ 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 && } diff --git a/src/components/pre/index.tsx b/src/components/pre/index.tsx index 025f1f9cd6..949dab6ddc 100644 --- a/src/components/pre/index.tsx +++ b/src/components/pre/index.tsx @@ -7,7 +7,7 @@ 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 @@ -40,11 +40,16 @@ export function Pre({ return (
    {filename && ( - + )}
     ast => {
    
    From f69ad01ab2b2bb1144b020de0307fd3f4df831b0 Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Fri, 29 Aug 2025 19:12:08 +0200
    Subject: [PATCH 19/28] Allow running esbuild builds
    
    ---
     package.json | 5 ++++-
     1 file changed, 4 insertions(+), 1 deletion(-)
    
    diff --git a/package.json b/package.json
    index 8a74079254..19372b5c1f 100644
    --- a/package.json
    +++ b/package.json
    @@ -128,6 +128,9 @@
           "nextra": "patches/nextra.patch",
           "nextra-theme-docs": "patches/nextra-theme-docs.patch",
           "mermaid-isomorphic": "patches/mermaid-isomorphic.patch"
    -    }
    +    },
    +    "onlyBuiltDependencies": [
    +      "esbuild"
    +    ]
       }
     }
    
    From bc98a9ee960f4d597f21ce6b2da4ece923153b30 Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Fri, 29 Aug 2025 19:14:20 +0200
    Subject: [PATCH 20/28] Remove duplicate image
    
    ---
     .../quotes-from-the-industry/mateo-collina.webp | Bin 30032 -> 0 bytes
     1 file changed, 0 insertions(+), 0 deletions(-)
     delete mode 100644 src/components/index-page/quotes-from-the-industry/mateo-collina.webp
    
    diff --git a/src/components/index-page/quotes-from-the-industry/mateo-collina.webp b/src/components/index-page/quotes-from-the-industry/mateo-collina.webp
    deleted file mode 100644
    index 06c9138051516e93765335e12a7069e32c510735..0000000000000000000000000000000000000000
    GIT binary patch
    literal 0
    HcmV?d00001
    
    literal 30032
    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
    
    
    From 122e5c5410b510dd75b9fe914ae967688c966e27 Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Fri, 29 Aug 2025 19:28:30 +0200
    Subject: [PATCH 21/28] Format
    
    ---
     .../interactive-code-block/on-has-completion.tsx         | 9 ++++++---
     1 file changed, 6 insertions(+), 3 deletions(-)
    
    diff --git a/src/components/interactive-code-block/on-has-completion.tsx b/src/components/interactive-code-block/on-has-completion.tsx
    index c411628553..6bcbf066d4 100644
    --- a/src/components/interactive-code-block/on-has-completion.tsx
    +++ b/src/components/interactive-code-block/on-has-completion.tsx
    @@ -3,9 +3,12 @@
      * CodeMirror 6 and cm6-graphql handle completions differently and don't require this custom UI.
      */
     
    -export function onHasCompletion(cm: any, data: any, onHintInformationRender?: any) {
    +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;
    +  return
     }
    -
    
    From b10178aec3416122b051ff3e0eb8ca2d4359cf95 Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Sat, 30 Aug 2025 11:54:58 +0200
    Subject: [PATCH 22/28] Change syntax theme names to avoid a bug
    
    ---
     src/_design-system/syntax/dark.json  | 2 +-
     src/_design-system/syntax/light.json | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/src/_design-system/syntax/dark.json b/src/_design-system/syntax/dark.json
    index 8057389657..a3d613278a 100644
    --- a/src/_design-system/syntax/dark.json
    +++ b/src/_design-system/syntax/dark.json
    @@ -1,5 +1,5 @@
     {
    -  "name": "k-colorable dark",
    +  "name": "k-colorable-dark",
       "type": "dark",
       "semanticHighlighting": true,
       "colors": {
    diff --git a/src/_design-system/syntax/light.json b/src/_design-system/syntax/light.json
    index 3e9cd10d5c..a6b8f371ff 100644
    --- a/src/_design-system/syntax/light.json
    +++ b/src/_design-system/syntax/light.json
    @@ -1,5 +1,5 @@
     {
    -  "name": "k-colorable light",
    +  "name": "k-colorable-light",
       "type": "light",
       "semanticHighlighting": true,
       "colors": {
    
    From 55b3e0ca58fffac936ad36393b9f766a1485bda8 Mon Sep 17 00:00:00 2001
    From: Piotr Monwid-Olechnowicz 
    Date: Sat, 30 Aug 2025 12:48:57 +0200
    Subject: [PATCH 23/28] Fix heights and properly chain handlers
    
    ---
     .../interactive-code-block/mini-graphiQL.tsx        | 13 ++++++++-----
     .../interactive-code-block/query-editor.tsx         |  2 +-
     .../interactive-code-block/result-viewer.tsx        |  2 +-
     .../interactive-code-block/variable-editor.tsx      |  2 +-
     4 files changed, 11 insertions(+), 8 deletions(-)
    
    diff --git a/src/components/interactive-code-block/mini-graphiQL.tsx b/src/components/interactive-code-block/mini-graphiQL.tsx
    index 9addc0c6f4..ac8004aa89 100644
    --- a/src/components/interactive-code-block/mini-graphiQL.tsx
    +++ b/src/components/interactive-code-block/mini-graphiQL.tsx
    @@ -78,7 +78,7 @@ export default class MiniGraphiQL extends Component<
     
       render() {
         const editor = (
    -      
    +
    { + void this._runQuery({ manual: false }) + }) } _handleEditVariables(value: string) { - this.setState({ variables: value }) + this.setState({ variables: value }, () => { + void this._runQuery({ manual: false }) + }) } } diff --git a/src/components/interactive-code-block/query-editor.tsx b/src/components/interactive-code-block/query-editor.tsx index 65ec0e2e22..b5f975497c 100644 --- a/src/components/interactive-code-block/query-editor.tsx +++ b/src/components/interactive-code-block/query-editor.tsx @@ -140,7 +140,7 @@ export class QueryEditor extends Component { 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 index d6aab400f1..032424c44b 100644 --- a/src/components/interactive-code-block/result-viewer.tsx +++ b/src/components/interactive-code-block/result-viewer.tsx @@ -71,7 +71,7 @@ export class ResultViewer extends Component { render() { return (
    { this.domNode = e }} diff --git a/src/components/interactive-code-block/variable-editor.tsx b/src/components/interactive-code-block/variable-editor.tsx index 89d3ab0e13..91ce332e6b 100644 --- a/src/components/interactive-code-block/variable-editor.tsx +++ b/src/components/interactive-code-block/variable-editor.tsx @@ -103,7 +103,7 @@ export class VariableEditor extends Component { render() { return (
    { this.domNode = e }} From 4ddcc263b61dd96f1a39dbc246a980d9ee9ba657 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sat, 30 Aug 2025 13:08:11 +0200 Subject: [PATCH 24/28] Fix tests for new editor --- .gitignore | 2 ++ src/globals.css | 42 ---------------------------- test/e2e/graphql-interactive.spec.ts | 23 ++++++++------- 3 files changed, 13 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 2d422a0b51..2c2af0a81e 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,5 @@ public/sitemap.xml out/ tsconfig.tsbuildinfo + +playwright-report/ diff --git a/src/globals.css b/src/globals.css index b8dbc95f47..a59e0b6d45 100644 --- a/src/globals.css +++ b/src/globals.css @@ -112,52 +112,10 @@ div[id^="headlessui-menu-items"] { } } -.CodeMirror-selected { - @apply !bg-primary/50 dark:bg-primary/40; -} - -.CodeMirror-cursor { - @apply dark:border-white; -} - -.CodeMirror { - @apply p-2; -} - ::selection { @apply bg-pri-base/25 dark:bg-pri-light/50; } -@media (prefers-color-scheme: dark) { - body { - /*background: linear-gradient(*/ - /* 125deg,*/ - /* rgba(234, 117, 195, 0.3) -10%,*/ - /* rgba(234, 117, 195, 0) 8%*/ - /* ),*/ - /* linear-gradient(*/ - /* -125deg,*/ - /* rgba(234, 117, 195, 0.3) -10%,*/ - /* rgba(234, 117, 195, 0) 8%*/ - /* ),*/ - /* linear-gradient(0deg, #05031c, #05031c);*/ - /*@apply bg-gradient-to-b from-primary/10 to-[#05031c]*/ - - /*background:*/ - /* !*linear-gradient(0deg, #05031c, #05031c),*!*/ - /* linear-gradient(*/ - /* 125.93deg,*/ - /* rgba(234, 117, 195, 0.7) -10.45%,*/ - /* rgba(234, 117, 195, 0) 28.49%*/ - /* ),*/ - /* linear-gradient(*/ - /* -125.93deg,*/ - /* rgba(234, 117, 195, 0.7) -33.25%,*/ - /* rgba(234, 117, 195, 0) 26.45%*/ - /* );*/ - } -} - .donts-images ul { gap: 0 3em; @apply flex flex-wrap; diff --git a/test/e2e/graphql-interactive.spec.ts b/test/e2e/graphql-interactive.spec.ts index 990db07467..84946b81cd 100644 --- a/test/e2e/graphql-interactive.spec.ts +++ b/test/e2e/graphql-interactive.spec.ts @@ -5,9 +5,9 @@ test.describe("interactive examples", () => { page, }) => { await page.goto("/learn") - await page.waitForSelector(".CodeMirror", { timeout: 10000 }) + await page.waitForSelector(".cm-editor", { timeout: 10000 }) - const editors = page.locator(".miniGraphiQL") + const editors = page.locator(".cm-editor") let heroEditor: Locator | null = null for (let i = 0; i < (await editors.count()); i++) { @@ -23,12 +23,7 @@ test.describe("interactive examples", () => { throw new Error("Could not find hero GraphQL editor") } - const codeMirrorEditor = heroEditor.locator(".CodeMirror").first() - await expect(codeMirrorEditor).toBeVisible() - - await codeMirrorEditor.click() - - const codeLines = codeMirrorEditor.locator(".CodeMirror-line") + const codeLines = heroEditor.locator(".cm-line") // Find the line containing "name" and click after it for (let i = 0; i < (await codeLines.count()); i++) { @@ -47,20 +42,24 @@ test.describe("interactive examples", () => { await page.keyboard.type("ap") await page.keyboard.press("Control+Space") - const autoCompleteMenu = page.locator(".CodeMirror-hints") + const autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) const appearsInSuggestion = page - .locator(".CodeMirror-hints li") + .locator(".cm-completionLabel") .filter({ hasText: "appearsIn" }) + expect(page.locator(".cm-completionDetail").first()).toHaveText( + "[Episode]!", + ) + if (await appearsInSuggestion.isVisible()) { await appearsInSuggestion.click() } else { await page.keyboard.press("Enter") } - const resultViewer = heroEditor.locator(".result-window") + const resultViewer = page.locator(".result-window").first() await expect(resultViewer).toBeVisible() await expect @@ -91,7 +90,7 @@ test.describe("interactive examples", () => { await page.waitForLoadState("networkidle") // Find the mutation example that has GraphiQL enabled - const editors = page.locator(".miniGraphiQL") + const editors = page.locator(".cm-editor") let mutationEditor: Locator | null = null for (let i = 0; i < (await editors.count()); i++) { From 812b8b450eac8d6fb88bac0f17e5c7b99bc36462 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sat, 30 Aug 2025 13:16:42 +0200 Subject: [PATCH 25/28] Run Playwright on CI --- .github/workflows/check.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 803f2525c8..a64f745516 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,5 +23,18 @@ jobs: - name: Run Prettier Check run: pnpm format:check - - name: Run Playwright - run: pnpm test + # per the docs: "caching browser binaries is not recommended, + # since the amount of time it takes to restore the cache is + # comparable to the time it takes to download the binaries" + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + - name: Run Playwright tests + run: npx playwright test + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 From a843f6b13f7d5d21931706c02ead253b52351839 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 1 Sep 2025 15:15:17 +0200 Subject: [PATCH 26/28] Use the same colors for VariableEditor --- src/components/interactive-code-block/variable-editor.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/interactive-code-block/variable-editor.tsx b/src/components/interactive-code-block/variable-editor.tsx index 91ce332e6b..73fe6a5e7c 100644 --- a/src/components/interactive-code-block/variable-editor.tsx +++ b/src/components/interactive-code-block/variable-editor.tsx @@ -3,7 +3,8 @@ 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" +import { syntaxHighlighting } from "@codemirror/language" +import { codeMirrorThemeExtension } from "./codemirror-theme" interface VariableEditorProps { value: string @@ -49,7 +50,7 @@ export class VariableEditor extends Component { extensions: [ history(), json(), - syntaxHighlighting(defaultHighlightStyle), + codeMirrorThemeExtension, EditorView.updateListener.of(update => { if (update.docChanged && !this.ignoreChangeEvent) { this.cachedValue = update.state.doc.toString() From ebd8a4d01f71d96e8cbeb711bff8ff72c2f983dc Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 1 Sep 2025 15:18:35 +0200 Subject: [PATCH 27/28] Use the same font for interactive code blocks --- src/components/interactive-code-block/syntax-highlighting.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/interactive-code-block/syntax-highlighting.css b/src/components/interactive-code-block/syntax-highlighting.css index 90ff291459..62580fb102 100644 --- a/src/components/interactive-code-block/syntax-highlighting.css +++ b/src/components/interactive-code-block/syntax-highlighting.css @@ -57,7 +57,6 @@ --cm-hints-active-foreground: #ffffff; } -/* CodeMirror 6 Editor Base Styles */ .cm-editor { font-family: inherit; font-size: inherit; @@ -75,6 +74,7 @@ & .cm-scroller { line-height: 1.5; + font-family: var(--font-mono); } } From 0dfd34de77586a591e47f53d244da03b968c8f96 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 1 Sep 2025 15:23:49 +0200 Subject: [PATCH 28/28] Match the coloring on t.separator [,:] between Lezer and Shiki --- src/components/interactive-code-block/codemirror-theme.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/interactive-code-block/codemirror-theme.tsx b/src/components/interactive-code-block/codemirror-theme.tsx index 622e66cdff..a6708358d7 100644 --- a/src/components/interactive-code-block/codemirror-theme.tsx +++ b/src/components/interactive-code-block/codemirror-theme.tsx @@ -112,6 +112,7 @@ export const syntaxTheme = HighlightStyle.define([ t.regexp, t.link, t.special(t.string), + t.separator, ], class: "cm-punctuation", },