From ab0a791cc0d75efe05ca8b6f9da8e21271fbf309 Mon Sep 17 00:00:00 2001 From: "netop://ウィビ" Date: Sun, 26 Apr 2026 21:30:43 -0700 Subject: adds awesome new GraphiQL renderer and an example --- source/graphiql/markup.ts | 309 ---------------------------------------------- source/graphiql/render.ts | 239 +++++++++++------------------------ 2 files changed, 69 insertions(+), 479 deletions(-) delete mode 100755 source/graphiql/markup.ts (limited to 'source/graphiql') diff --git a/source/graphiql/markup.ts b/source/graphiql/markup.ts deleted file mode 100755 index 6bacbf3..0000000 --- a/source/graphiql/markup.ts +++ /dev/null @@ -1,309 +0,0 @@ - - - -/*** EXPORT ------------------------------------------- ***/ - -export const getLoadingMarkup = () => ({ - container: ` - - -
- - -
Loading - GraphQL Playground -
-
- `, - script: ` - const loadingWrapper = document.getElementById("loading-wrapper"); - - if (loadingWrapper) - loadingWrapper.classList.add("fadeOut"); - ` -}); diff --git a/source/graphiql/render.ts b/source/graphiql/render.ts index a0e4dc2..7adec57 100755 --- a/source/graphiql/render.ts +++ b/source/graphiql/render.ts @@ -3,213 +3,112 @@ /*** IMPORT ------------------------------------------- ***/ +import { default as dedent } from "@netopwibby/dedent"; import { filterXSS } from "xss"; /*** UTILITY ------------------------------------------ ***/ -import { getLoadingMarkup } from "./markup.ts"; +const filter = (val: string) => filterXSS(val, { + stripIgnoreTag: true, + stripIgnoreTagBody: ["script"], + whiteList: {} +}); -const CONFIG_ID = "playground-config"; -const loading = getLoadingMarkup(); - -const filter = (val: string) => { - return filterXSS(val, { - stripIgnoreTag: true, - stripIgnoreTagBody: ["script"], - whiteList: {} - }); -} - -const getCdnMarkup = ({ cdnUrl = "//cdn.jsdelivr.net/npm", faviconUrl, version }: { - cdnUrl?: string - faviconUrl?: string | null +const buildAssetUrl = ({ cdnUrl, suffix, version }: { + cdnUrl: string + suffix: string version?: string -}) => { - const buildCDNUrl = (packageName: string, suffix: string) => - filter(`${cdnUrl}/${packageName}${version ? `@${version}` : ""}/${suffix}` || ""); - - return ` - - ${typeof faviconUrl === "string" ? `` : ""} - ${faviconUrl === undefined ? `` : ""} - - `; -} - -const renderConfig = (config: unknown) => { - return filterXSS(`
${JSON.stringify(config)}
`, { - whiteList: { div: ["id"] } - }); -}; +}) => filter(`${cdnUrl}/@eeeooolll/graphiql${version ? `@${version}` : ""}/dist/${suffix}`); /*** EXPORT ------------------------------------------- ***/ -export interface MiddlewareOptions { - codeTheme?: EditorColours; - config?: { [key: string]: unknown; }; - endpoint?: string; - env?: "electron" | "react"; - schema?: IntrospectionResult; - settings?: ISettings; - subscriptionEndpoint?: string; - tabs?: Tab[]; - workspaceName?: string; -} - -export type CursorShape = "line" | "block" | "underline"; -export type Theme = "dark" | "light"; - -export interface ISettings { - "editor.cursorShape": CursorShape; - "editor.fontFamily": string; - "editor.fontSize": number; - "editor.reuseHeaders": boolean; - "editor.theme": Theme; - "general.betaUpdates": boolean; - "request.credentials": string; - "request.globalHeaders": { [key: string]: string }; - "schema.polling.enable": boolean; - "schema.polling.endpointFilter": string; - "schema.polling.interval": number; - "tracing.hideTracingResponse": boolean; - "tracing.tracingSupported": boolean; -} - -export interface EditorColours { - atom: string; - attribute: string; - builtin: string; - comment: string; - cursorColor: string; - def: string; - editorBackground: string; - keyword: string; - leftDrawerBackground: string; - meta: string; - number: string; - property: string; - punctuation: string; - qualifier: string; - resultBackground: string; - rightDrawerBackground: string; - selection: string; - string: string; - string2: string; - variable: string; - ws: string; -} - -export interface IntrospectionResult { - __schema: { [key: string]: unknown; }; -} - -export interface RenderPageOptions extends MiddlewareOptions { +export interface RenderPageOptions { + /** Base CDN. Defaults to jsDelivr. */ cdnUrl?: string; - env?: "electron" | "react"; + /** GraphQL endpoint the embedded fetcher posts to. */ + endpoint?: string; + /** Optional ``. `null` skips, `undefined` falls back to default. */ faviconUrl?: string | null; + /** Document ``. */ title?: string; + /** Pin `@eeeooolll/graphiql` to a specific version on the CDN. */ version?: string; } -export interface Tab { - endpoint: string; - headers?: { [key: string]: string }; - name?: string; - query: string; - responses?: string[]; - variables?: string; -} - /** - * Renders the GraphQL Playground HTML shell. + * Renders the GraphiQL HTML shell. + * + * Loads the prebuilt IIFE bundle from `@eeeooolll/graphiql` (registered as + * `window.EolGraphiQL`) and mounts it against `endpoint`. * * Usually called indirectly via `GraphQLHTTP({ graphiql: true })`; invoke it - * directly if you need to embed the Playground in a custom route. + * directly if you need to embed GraphiQL in a custom route. */ -export function renderPlaygroundPage(options: RenderPageOptions) { - const extendedOptions: - & Partial<{ - canSaveConfig: boolean - configString: string - }> - & RenderPageOptions = { - ...options, - canSaveConfig: false - }; - - if (options.config) - extendedOptions.configString = JSON.stringify(options.config, null, 2); - - if (!extendedOptions.endpoint && !extendedOptions.configString) - console.warn("WARNING: You did not provide an endpoint and do not have a .graphqlconfig. Make sure you have at least one of them."); - else if (extendedOptions.endpoint) - extendedOptions.endpoint = filter(extendedOptions.endpoint || ""); - - return ` +export function renderPlaygroundPage(options: RenderPageOptions): string { + const { + cdnUrl = "//cdn.jsdelivr.net/npm", + endpoint = "/graphql", + faviconUrl, + title = "GraphiQL", + version + } = options; + + const safeEndpoint = filter(endpoint); + const safeTitle = filter(title); + const scriptUrl = buildAssetUrl({ cdnUrl, suffix: "standalone.js", version }); + const styleUrl = buildAssetUrl({ cdnUrl, suffix: "standalone.css", version }); + + const faviconLink = + faviconUrl === null ? + "" : + typeof faviconUrl === "string" ? + `<link rel="shortcut icon" href="${filter(faviconUrl)}"/>` : + `<link rel="shortcut icon" href="${buildAssetUrl({ cdnUrl, suffix: "favicon.svg", version })}"/>`; + + return dedent` <!DOCTYPE html> <html lang="en"> <head> - <meta charset=utf-8/> - <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui"/> - <link href="https://brick.a.ssl.fastly.net/Open+Sans:300,400,600,700/Source+Code+Pro:400,700" rel="stylesheet"/> - <title>${extendedOptions.title || "GraphQL Playground"} - ${extendedOptions.env === "react" || extendedOptions.env === "electron" ? "" : getCdnMarkup(extendedOptions)} - + + + ${safeTitle} + ${faviconLink} + - + - ${loading.container} - ${renderConfig(extendedOptions)} -
+ +
+ + -- cgit v1.2.3