diff --git a/.changeset/add-on-prettify-callback.md b/.changeset/add-on-prettify-callback.md new file mode 100644 index 0000000000..40640e5571 --- /dev/null +++ b/.changeset/add-on-prettify-callback.md @@ -0,0 +1,6 @@ +--- +"@graphiql/react": minor +"graphiql": minor +--- + +Add support for `onPrettifyQuery` callback to enable customised query formatting diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index b96ec5eaae..cf19b2ee3e 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -204,14 +204,23 @@ export function useMergeQuery({ caller }: UseMergeQueryArgs = {}) { }, [queryEditor, schema]); } -type UsePrettifyEditorsArgs = { +export type UsePrettifyEditorsArgs = { /** * This is only meant to be used internally in `@graphiql/react`. */ caller?: Function; + /** + * Invoked when the prettify callback is invoked + * @param query The current value of the query editor. + * @returns {string} The formatted query + */ + onPrettifyQuery?: (query: string) => string; }; -export function usePrettifyEditors({ caller }: UsePrettifyEditorsArgs = {}) { +export function usePrettifyEditors({ + caller, + onPrettifyQuery, +}: UsePrettifyEditorsArgs = {}) { const { queryEditor, headerEditor, variableEditor } = useEditorContext({ nonNull: true, caller: caller || usePrettifyEditors, @@ -252,13 +261,15 @@ export function usePrettifyEditors({ caller }: UsePrettifyEditorsArgs = {}) { if (queryEditor) { const editorContent = queryEditor.getValue(); - const prettifiedEditorContent = print(parse(editorContent)); + const prettifiedEditorContent = onPrettifyQuery + ? onPrettifyQuery(editorContent) + : print(parse(editorContent)); if (prettifiedEditorContent !== editorContent) { queryEditor.setValue(prettifiedEditorContent); } } - }, [queryEditor, variableEditor, headerEditor]); + }, [queryEditor, variableEditor, headerEditor, onPrettifyQuery]); } export type UseAutoCompleteLeafsArgs = { diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index 48c8aec4f9..f3c931d08f 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -39,6 +39,7 @@ import { useCompletion, useCopyQuery, UseCopyQueryArgs, + UsePrettifyEditorsArgs, useKeyMap, useMergeQuery, usePrettifyEditors, @@ -52,7 +53,8 @@ import { import { normalizeWhitespace } from './whitespace'; export type UseQueryEditorArgs = WriteableEditorProps & - Pick & { + Pick & + Pick & { /** * Invoked when a reference to the GraphQL schema (type or field) is clicked * as part of the editor or one of its tooltips. @@ -74,6 +76,7 @@ export function useQueryEditor( onClickReference, onCopyQuery, onEdit, + onPrettifyQuery, readOnly = false, }: UseQueryEditorArgs = {}, caller?: Function, @@ -101,7 +104,10 @@ export function useQueryEditor( const plugin = usePluginContext(); const copy = useCopyQuery({ caller: caller || useQueryEditor, onCopyQuery }); const merge = useMergeQuery({ caller: caller || useQueryEditor }); - const prettify = usePrettifyEditors({ caller: caller || useQueryEditor }); + const prettify = usePrettifyEditors({ + caller: caller || useQueryEditor, + onPrettifyQuery, + }); const ref = useRef(null); const codeMirrorRef = useRef(); diff --git a/packages/graphiql/cypress/e2e/prettify.cy.ts b/packages/graphiql/cypress/e2e/prettify.cy.ts index d82fd5c50b..ba8e3e16ad 100644 --- a/packages/graphiql/cypress/e2e/prettify.cy.ts +++ b/packages/graphiql/cypress/e2e/prettify.cy.ts @@ -1,8 +1,9 @@ import { version } from 'graphql'; + let describeOrSkip = describe.skip; // hard to account for the extra \n between 15/16 so these only run for 16 for now -if (version.includes('16')) { +if (parseInt(version, 10) > 15) { describeOrSkip = describe; } @@ -25,6 +26,26 @@ const brokenQuery = 'longDescriptionType {id}}'; const brokenVariables = '"a": 1}'; describeOrSkip('GraphiQL Prettify', () => { + describe('onPrettifyQuery', () => { + const rawQuery = '{ test\n\nid }'; + const resultQuery = '{ test id }'; + + it('should work while click on prettify button', () => { + cy.visit(`/?query=${rawQuery}&onPrettifyQuery=true`); + cy.clickPrettify(); + cy.assertHasValues({ query: resultQuery }); + }); + + it('should work while click on key map short cut', () => { + cy.visit(`/?query=${rawQuery}&onPrettifyQuery=true`); + cy.get('.graphiql-query-editor textarea').type('{shift}{ctrl}P', { + force: true, + }); + cy.get('.graphiql-query-editor textarea').type('{esc}'); + cy.assertHasValues({ query: resultQuery }); + }); + }); + it('Regular prettification', () => { cy.visitWithOp({ query: uglyQuery, variablesString: uglyVariables }); diff --git a/packages/graphiql/resources/renderExample.js b/packages/graphiql/resources/renderExample.js index f23510297a..9a81c2ca4a 100644 --- a/packages/graphiql/resources/renderExample.js +++ b/packages/graphiql/resources/renderExample.js @@ -47,6 +47,10 @@ function confirmCloseTab(index) { return confirm(`Are you sure you want to close tab with index ${index}?`); } +function onPrettifyQuery(query) { + return query.replaceAll(/([ \n])+/g, ' '); +} + function updateURL() { const newSearch = Object.entries(parameters) .filter(([_key, value]) => value) @@ -93,6 +97,8 @@ root.render( inputValueDeprecation: !graphqlVersion.includes('15.5'), confirmCloseTab: parameters.confirmCloseTab === 'true' ? confirmCloseTab : undefined, + onPrettifyQuery: + parameters.onPrettifyQuery === 'true' ? onPrettifyQuery : undefined, onTabChange, forcedTheme: parameters.forcedTheme, defaultQuery: parameters.defaultQuery, diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx index 174fb6b02f..db6dda3abc 100644 --- a/packages/graphiql/src/components/GraphiQL.tsx +++ b/packages/graphiql/src/components/GraphiQL.tsx @@ -189,7 +189,7 @@ type AddSuffix, Suffix extends string> = { export type GraphiQLInterfaceProps = WriteableEditorProps & AddSuffix, 'Query'> & - Pick & + Pick & AddSuffix, 'Variables'> & AddSuffix, 'Headers'> & Pick & { @@ -328,8 +328,13 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) { const { logo = , - // @ts-expect-error -- Prop exists but hidden for users - toolbar = , + toolbar = ( +