Fix lint errors and show docs explorer on Cmd click

This commit is contained in:
Gregory Schier
2025-07-14 14:52:16 -07:00
parent 6f1fd7a254
commit 0c60d190af
48 changed files with 454 additions and 199 deletions

133
package-lock.json generated
View File

@@ -7839,6 +7839,19 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fault": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz",
"integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fd-slicer": { "node_modules/fd-slicer": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -8030,6 +8043,14 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/format-graphql": { "node_modules/format-graphql": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/format-graphql/-/format-graphql-1.5.0.tgz", "resolved": "https://registry.npmjs.org/format-graphql/-/format-graphql-1.5.0.tgz",
@@ -8987,6 +9008,29 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"ccount": "^2.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"mdast-util-to-hast": "^13.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"stringify-entities": "^4.0.0",
"zwitch": "^2.0.4"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-jsx-runtime": { "node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@@ -9074,6 +9118,16 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/html-void-elements": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/http-reasons": { "node_modules/http-reasons": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz",
@@ -10885,6 +10939,36 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/mdast-util-frontmatter": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz",
"integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"escape-string-regexp": "^5.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0",
"micromark-extension-frontmatter": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mdast-util-gfm": { "node_modules/mdast-util-gfm": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
@@ -11259,6 +11343,22 @@
"micromark-util-types": "^2.0.0" "micromark-util-types": "^2.0.0"
} }
}, },
"node_modules/micromark-extension-frontmatter": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz",
"integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==",
"license": "MIT",
"dependencies": {
"fault": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm": { "node_modules/micromark-extension-gfm": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
@@ -14587,6 +14687,37 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/rehype-stringify": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz",
"integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"hast-util-to-html": "^9.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-frontmatter": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz",
"integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-frontmatter": "^2.0.0",
"micromark-extension-frontmatter": "^2.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-gfm": { "node_modules/remark-gfm": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
@@ -18626,6 +18757,8 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-pdf": "^10.0.1", "react-pdf": "^10.0.1",
"react-use": "^17.6.0", "react-use": "^17.6.0",
"rehype-stringify": "^10.0.1",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"uuid": "^11.1.0", "uuid": "^11.1.0",

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -1,4 +1,4 @@
import { PluginDefinition } from '@yaakapp/api'; import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = { export const plugin: PluginDefinition = {
authentication: { authentication: {

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -1,4 +1,4 @@
import { PluginDefinition } from '@yaakapp/api'; import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = { export const plugin: PluginDefinition = {
authentication: { authentication: {

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"jsonwebtoken": "^9.0.2" "jsonwebtoken": "^9.0.2"

View File

@@ -1,4 +1,4 @@
import { PluginDefinition } from '@yaakapp/api'; import type { PluginDefinition } from '@yaakapp/api';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
const algorithms = [ const algorithms = [
@@ -20,49 +20,49 @@ const algorithms = [
const defaultAlgorithm = algorithms[0]; const defaultAlgorithm = algorithms[0];
export const plugin: PluginDefinition = { export const plugin: PluginDefinition = {
authentication: { authentication: {
name: 'jwt', name: 'jwt',
label: 'JWT Bearer', label: 'JWT Bearer',
shortLabel: 'JWT', shortLabel: 'JWT',
args: [ args: [
{ {
type: 'select', type: 'select',
name: 'algorithm', name: 'algorithm',
label: 'Algorithm', label: 'Algorithm',
hideLabel: true, hideLabel: true,
defaultValue: defaultAlgorithm, defaultValue: defaultAlgorithm,
options: algorithms.map(value => ({ label: value === 'none' ? 'None' : value, value })), options: algorithms.map((value) => ({ label: value === 'none' ? 'None' : value, value })),
}, },
{ {
type: 'text', type: 'text',
name: 'secret', name: 'secret',
label: 'Secret or Private Key', label: 'Secret or Private Key',
password: true, password: true,
optional: true, optional: true,
multiLine: true, multiLine: true,
}, },
{ {
type: 'checkbox', type: 'checkbox',
name: 'secretBase64', name: 'secretBase64',
label: 'Secret is base64 encoded', label: 'Secret is base64 encoded',
}, },
{ {
type: 'editor', type: 'editor',
name: 'payload', name: 'payload',
label: 'Payload', label: 'Payload',
language: 'json', language: 'json',
defaultValue: '{\n "foo": "bar"\n}', defaultValue: '{\n "foo": "bar"\n}',
placeholder: '{ }', placeholder: '{ }',
}, },
], ],
async onApply(_ctx, { values }) { async onApply(_ctx, { values }) {
const { algorithm, secret: _secret, secretBase64, payload } = values; const { algorithm, secret: _secret, secretBase64, payload } = values;
const secret = secretBase64 ? Buffer.from(`${_secret}`, 'base64') : `${_secret}`; const secret = secretBase64 ? Buffer.from(`${_secret}`, 'base64') : `${_secret}`;
const token = jwt.sign(`${payload}`, secret, { algorithm: algorithm as any }); const token = jwt.sign(`${payload}`, secret, {
const value = `Bearer ${token}`; algorithm: algorithm as (typeof algorithms)[number],
return { setHeaders: [{ name: 'Authorization', value }] }; });
} const value = `Bearer ${token}`;
, return { setHeaders: [{ name: 'Authorization', value }] };
}, },
} },
; };

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -1,4 +1,4 @@
import { Context } from '@yaakapp/api'; import type { Context } from '@yaakapp/api';
export async function storeToken( export async function storeToken(
ctx: Context, ctx: Context,

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"jsonpath-plus": "^10.3.0" "jsonpath-plus": "^10.3.0"

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.9.8", "@xmldom/xmldom": "^0.9.8",

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"shell-quote": "^1.8.1" "shell-quote": "^1.8.1"

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"yaml": "^2.4.2" "yaml": "^2.4.2"

View File

@@ -4,11 +4,11 @@ export function convertSyntax(variable: string): string {
return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}'); return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}');
} }
export function isJSObject(obj: any) { export function isJSObject(obj: unknown) {
return Object.prototype.toString.call(obj) === '[object Object]'; return Object.prototype.toString.call(obj) === '[object Object]';
} }
export function isJSString(obj: any) { export function isJSString(obj: unknown) {
return Object.prototype.toString.call(obj) === '[object String]'; return Object.prototype.toString.call(obj) === '[object String]';
} }

View File

@@ -1,4 +1,4 @@
import { Context, PluginDefinition } from '@yaakapp/api'; import type { Context, PluginDefinition } from '@yaakapp/api';
import YAML from 'yaml'; import YAML from 'yaml';
import { deleteUndefinedAttrs, isJSObject } from './common'; import { deleteUndefinedAttrs, isJSObject } from './common';
import { convertInsomniaV4 } from './v4'; import { convertInsomniaV4 } from './v4';
@@ -15,16 +15,18 @@ export const plugin: PluginDefinition = {
}; };
export function convertInsomnia(contents: string) { export function convertInsomnia(contents: string) {
let parsed: any; let parsed: unknown;
try { try {
parsed = JSON.parse(contents); parsed = JSON.parse(contents);
} catch (e) { } catch {
// Fall through
} }
try { try {
parsed = parsed ?? YAML.parse(contents); parsed = parsed ?? YAML.parse(contents);
} catch (e) { } catch {
// Fall through
} }
if (!isJSObject(parsed)) return null; if (!isJSObject(parsed)) return null;

View File

@@ -1,7 +1,8 @@
import { PartialImportResources } from '@yaakapp/api'; /* eslint-disable @typescript-eslint/no-explicit-any */
import type { PartialImportResources } from '@yaakapp/api';
import { convertId, convertSyntax, isJSObject } from './common'; import { convertId, convertSyntax, isJSObject } from './common';
export function convertInsomniaV4(parsed: Record<string, any>) { export function convertInsomniaV4(parsed: any) {
if (!Array.isArray(parsed.resources)) return null; if (!Array.isArray(parsed.resources)) return null;
const resources: PartialImportResources = { const resources: PartialImportResources = {
@@ -14,7 +15,9 @@ export function convertInsomniaV4(parsed: Record<string, any>) {
}; };
// Import workspaces // Import workspaces
const workspacesToImport = parsed.resources.filter(r => isJSObject(r) && r._type === 'workspace'); const workspacesToImport = parsed.resources.filter(
(r: any) => isJSObject(r) && r._type === 'workspace',
);
for (const w of workspacesToImport) { for (const w of workspacesToImport) {
resources.workspaces.push({ resources.workspaces.push({
id: convertId(w._id), id: convertId(w._id),
@@ -40,13 +43,9 @@ export function convertInsomniaV4(parsed: Record<string, any>) {
resources.folders.push(importFolder(child, w._id)); resources.folders.push(importFolder(child, w._id));
nextFolder(child._id); nextFolder(child._id);
} else if (child._type === 'request') { } else if (child._type === 'request') {
resources.httpRequests.push( resources.httpRequests.push(importHttpRequest(child, w._id));
importHttpRequest(child, w._id),
);
} else if (child._type === 'grpc_request') { } else if (child._type === 'grpc_request') {
resources.grpcRequests.push( resources.grpcRequests.push(importGrpcRequest(child, w._id));
importGrpcRequest(child, w._id),
);
} }
} }
}; };
@@ -64,10 +63,7 @@ export function convertInsomniaV4(parsed: Record<string, any>) {
return { resources }; return { resources };
} }
function importHttpRequest( function importHttpRequest(r: any, workspaceId: string): PartialImportResources['httpRequests'][0] {
r: any,
workspaceId: string,
): PartialImportResources['httpRequests'][0] {
let bodyType: string | null = null; let bodyType: string | null = null;
let body = {}; let body = {};
if (r.body.mimeType === 'application/octet-stream') { if (r.body.mimeType === 'application/octet-stream') {
@@ -141,10 +137,7 @@ function importHttpRequest(
}; };
} }
function importGrpcRequest( function importGrpcRequest(r: any, workspaceId: string): PartialImportResources['grpcRequests'][0] {
r: any,
workspaceId: string,
): PartialImportResources['grpcRequests'][0] {
const parts = r.protoMethodName.split('/').filter((p: any) => p !== ''); const parts = r.protoMethodName.split('/').filter((p: any) => p !== '');
const service = parts[0] ?? null; const service = parts[0] ?? null;
const method = parts[1] ?? null; const method = parts[1] ?? null;
@@ -186,13 +179,18 @@ function importFolder(f: any, workspaceId: string): PartialImportResources['fold
}; };
} }
function importEnvironment(e: any, workspaceId: string, isParent?: boolean): PartialImportResources['environments'][0] { function importEnvironment(
e: any,
workspaceId: string,
isParent?: boolean,
): PartialImportResources['environments'][0] {
return { return {
id: convertId(e._id), id: convertId(e._id),
createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined, createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined,
updatedAt: e.modified ? new Date(e.modified).toISOString().replace('Z', '') : undefined, updatedAt: e.modified ? new Date(e.modified).toISOString().replace('Z', '') : undefined,
workspaceId: convertId(workspaceId), workspaceId: convertId(workspaceId),
// @ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
sortPriority: e.metaSortKey, // Will be added to Yaak later sortPriority: e.metaSortKey, // Will be added to Yaak later
base: isParent ?? e.parentId === workspaceId, base: isParent ?? e.parentId === workspaceId,
model: 'environment', model: 'environment',

View File

@@ -1,8 +1,16 @@
import { PartialImportResources } from '@yaakapp/api'; /* eslint-disable @typescript-eslint/no-explicit-any */
import type { PartialImportResources } from '@yaakapp/api';
import { convertId, convertSyntax, isJSObject } from './common'; import { convertId, convertSyntax, isJSObject } from './common';
export function convertInsomniaV5(parsed: Record<string, any>) { export function convertInsomniaV5(parsed: any) {
if (!Array.isArray(parsed.collection)) return null; // Assert parsed is object
if (parsed == null || typeof parsed !== 'object') {
return null;
}
if (!('collection' in parsed) || !Array.isArray(parsed.collection)) {
return null;
}
const resources: PartialImportResources = { const resources: PartialImportResources = {
environments: [], environments: [],
@@ -14,7 +22,7 @@ export function convertInsomniaV5(parsed: Record<string, any>) {
}; };
// Import workspaces // Import workspaces
const meta: Record<string, any> = parsed.meta ?? {}; const meta = ('meta' in parsed ? parsed.meta : {}) as Record<string, any>;
resources.workspaces.push({ resources.workspaces.push({
id: convertId(meta.id ?? 'collection'), id: convertId(meta.id ?? 'collection'),
createdAt: meta.created ? new Date(meta.created).toISOString().replace('Z', '') : undefined, createdAt: meta.created ? new Date(meta.created).toISOString().replace('Z', '') : undefined,
@@ -36,17 +44,11 @@ export function convertInsomniaV5(parsed: Record<string, any>) {
resources.folders.push(importFolder(child, meta.id, parentId)); resources.folders.push(importFolder(child, meta.id, parentId));
nextFolder(child.children, child.meta.id); nextFolder(child.children, child.meta.id);
} else if (child.method) { } else if (child.method) {
resources.httpRequests.push( resources.httpRequests.push(importHttpRequest(child, meta.id, parentId));
importHttpRequest(child, meta.id, parentId),
);
} else if (child.protoFileId) { } else if (child.protoFileId) {
resources.grpcRequests.push( resources.grpcRequests.push(importGrpcRequest(child, meta.id, parentId));
importGrpcRequest(child, meta.id, parentId),
);
} else if (child.url) { } else if (child.url) {
resources.websocketRequests.push( resources.websocketRequests.push(importWebsocketRequest(child, meta.id, parentId));
importWebsocketRequest(child, meta.id, parentId),
);
} }
} }
}; };
@@ -219,7 +221,11 @@ function importAuthentication(r: any) {
return { authenticationType, authentication } as const; return { authenticationType, authentication } as const;
} }
function importFolder(f: any, workspaceId: string, parentId: string): PartialImportResources['folders'][0] { function importFolder(
f: any,
workspaceId: string,
parentId: string,
): PartialImportResources['folders'][0] {
const id = f.meta?.id ?? f._id; const id = f.meta?.id ?? f._id;
const created = f.meta?.created ?? f.created; const created = f.meta?.created ?? f.created;
const updated = f.meta?.modified ?? f.updated; const updated = f.meta?.modified ?? f.updated;
@@ -238,8 +244,11 @@ function importFolder(f: any, workspaceId: string, parentId: string): PartialImp
}; };
} }
function importEnvironment(
function importEnvironment(e: any, workspaceId: string, isParent?: boolean): PartialImportResources['environments'][0] { e: any,
workspaceId: string,
isParent?: boolean,
): PartialImportResources['environments'][0] {
const id = e.meta?.id ?? e._id; const id = e.meta?.id ?? e._id;
const created = e.meta?.created ?? e.created; const created = e.meta?.created ?? e.created;
const updated = e.meta?.modified ?? e.updated; const updated = e.meta?.modified ?? e.updated;
@@ -251,7 +260,8 @@ function importEnvironment(e: any, workspaceId: string, isParent?: boolean): Par
updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined, updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined,
workspaceId: convertId(workspaceId), workspaceId: convertId(workspaceId),
public: !e.isPrivate, public: !e.isPrivate,
// @ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
sortPriority: sortKey, // Will be added to Yaak later sortPriority: sortKey, // Will be added to Yaak later
base: isParent ?? e.parentId === workspaceId, base: isParent ?? e.parentId === workspaceId,
model: 'environment', model: 'environment',

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"openapi-to-postmanv2": "^5.0.0", "openapi-to-postmanv2": "^5.0.0",

View File

@@ -8,6 +8,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -1,11 +1,11 @@
import { Environment, PluginDefinition } from '@yaakapp/api'; import type { Environment, PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = { export const plugin: PluginDefinition = {
importer: { importer: {
name: 'Yaak', name: 'Yaak',
description: 'Yaak official format', description: 'Yaak official format',
onImport(_ctx, args) { onImport(_ctx, args) {
return migrateImport(args.text) as any; return migrateImport(args.text);
}, },
}, },
}; };
@@ -14,7 +14,7 @@ export function migrateImport(contents: string) {
let parsed; let parsed;
try { try {
parsed = JSON.parse(contents); parsed = JSON.parse(contents);
} catch (err) { } catch {
return undefined; return undefined;
} }
@@ -69,6 +69,6 @@ export function migrateImport(contents: string) {
return { resources: parsed.resources }; // Should already be in the correct format return { resources: parsed.resources }; // Should already be in the correct format
} }
function isJSObject(obj: any) { function isJSObject(obj: unknown) {
return Object.prototype.toString.call(obj) === '[object Object]'; return Object.prototype.toString.call(obj) === '[object Object]';
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -1,4 +1,4 @@
import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = { export const plugin: PluginDefinition = {
templateFunctions: [ templateFunctions: [

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -1,4 +1,4 @@
import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
import { createHash, createHmac } from 'node:crypto'; import { createHash, createHmac } from 'node:crypto';
const algorithms = ['md5', 'sha1', 'sha256', 'sha512'] as const; const algorithms = ['md5', 'sha1', 'sha256', 'sha512'] as const;

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"jsonpath-plus": "^10.3.0" "jsonpath-plus": "^10.3.0"

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"jsonpath-plus": "^10.3.0", "jsonpath-plus": "^10.3.0",

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"uuid": "^11.1.0" "uuid": "^11.1.0"

View File

@@ -7,7 +7,7 @@ export const plugin: PluginDefinition = {
name: 'uuid.v1', name: 'uuid.v1',
description: 'Generate a UUID V1', description: 'Generate a UUID V1',
args: [], args: [],
async onRender(_ctx: Context, _args: CallTemplateFunctionArgs): Promise<string | null> { async onRender(): Promise<string | null> {
return v1(); return v1();
}, },
}, },
@@ -32,7 +32,7 @@ export const plugin: PluginDefinition = {
name: 'uuid.v4', name: 'uuid.v4',
description: 'Generate a UUID V4', description: 'Generate a UUID V4',
args: [], args: [],
async onRender(_ctx: Context, _args: CallTemplateFunctionArgs): Promise<string | null> { async onRender(): Promise<string | null> {
return v4(); return v4();
}, },
}, },
@@ -68,7 +68,7 @@ export const plugin: PluginDefinition = {
name: 'uuid.v7', name: 'uuid.v7',
description: 'Generate a UUID V7', description: 'Generate a UUID V7',
args: [], args: [],
async onRender(_ctx: Context, _args: CallTemplateFunctionArgs): Promise<string | null> { async onRender(): Promise<string | null> {
return v7(); return v7();
}, },
}, },

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.9.8", "@xmldom/xmldom": "^0.9.8",

View File

@@ -7,6 +7,6 @@
"scripts": { "scripts": {
"build": "yaakcli build", "build": "yaakcli build",
"dev": "yaakcli dev", "dev": "yaakcli dev",
"lint": "tsc --noEmit" "lint": "eslint . --ext .ts,.tsx"
} }
} }

View File

@@ -3,11 +3,11 @@ import classNames from 'classnames';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import React from 'react'; import React from 'react';
import { showGraphQLDocExplorerAtom } from '../atoms/graphqlSchemaAtom';
import { useCurrentGraphQLSchema } from '../hooks/useIntrospectGraphQL'; import { useCurrentGraphQLSchema } from '../hooks/useIntrospectGraphQL';
import type { SlotProps } from './core/SplitLayout'; import type { SlotProps } from './core/SplitLayout';
import { SplitLayout } from './core/SplitLayout'; import { SplitLayout } from './core/SplitLayout';
import { GraphQLDocsExplorer } from './GraphQLDocsExplorer'; import { GraphQLDocsExplorer } from './graphql/GraphQLDocsExplorer';
import { showGraphQLDocExplorerAtom } from './graphql/graphqlAtoms';
import { HttpRequestPane } from './HttpRequestPane'; import { HttpRequestPane } from './HttpRequestPane';
import { HttpResponsePane } from './HttpResponsePane'; import { HttpResponsePane } from './HttpResponsePane';
@@ -17,7 +17,6 @@ interface Props {
} }
export function HttpRequestLayout({ activeRequest, style }: Props) { export function HttpRequestLayout({ activeRequest, style }: Props) {
const { bodyType } = activeRequest;
const showGraphQLDocExplorer = useAtomValue(showGraphQLDocExplorerAtom); const showGraphQLDocExplorer = useAtomValue(showGraphQLDocExplorerAtom);
const graphQLSchema = useCurrentGraphQLSchema(activeRequest); const graphQLSchema = useCurrentGraphQLSchema(activeRequest);
@@ -39,11 +38,11 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
/> />
); );
if (bodyType === 'graphql' && showGraphQLDocExplorer && graphQLSchema != null) { if (activeRequest.bodyType === 'graphql' && showGraphQLDocExplorer && graphQLSchema != null) {
return ( return (
<SplitLayout <SplitLayout
name="graphql_layout" name="graphql_layout"
defaultRatio={1/3} defaultRatio={1 / 3}
firstSlot={requestResponseSplit} firstSlot={requestResponseSplit}
secondSlot={({ style, orientation }) => ( secondSlot={({ style, orientation }) => (
<GraphQLDocsExplorer <GraphQLDocsExplorer

View File

@@ -47,13 +47,13 @@ import type { TabItem } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText'; import { EmptyStateText } from './EmptyStateText';
import { FormMultipartEditor } from './FormMultipartEditor'; import { FormMultipartEditor } from './FormMultipartEditor';
import { FormUrlencodedEditor } from './FormUrlencodedEditor'; import { FormUrlencodedEditor } from './FormUrlencodedEditor';
import { GraphQLEditor } from './GraphQLEditor';
import { HeadersEditor } from './HeadersEditor'; import { HeadersEditor } from './HeadersEditor';
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor'; import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
import { MarkdownEditor } from './MarkdownEditor'; import { MarkdownEditor } from './MarkdownEditor';
import { RequestMethodDropdown } from './RequestMethodDropdown'; import { RequestMethodDropdown } from './RequestMethodDropdown';
import { UrlBar } from './UrlBar'; import { UrlBar } from './UrlBar';
import { UrlParametersEditor } from './UrlParameterEditor'; import { UrlParametersEditor } from './UrlParameterEditor';
import { GraphQLEditor } from './graphql/GraphQLEditor';
interface Props { interface Props {
style: CSSProperties; style: CSSProperties;

View File

@@ -6,7 +6,9 @@ import {
} from '@codemirror/autocomplete'; } from '@codemirror/autocomplete';
import { history, historyKeymap } from '@codemirror/commands'; import { history, historyKeymap } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { markdown } from '@codemirror/lang-markdown'; import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import type { LanguageSupport } from '@codemirror/language'; import type { LanguageSupport } from '@codemirror/language';
import { import {
codeFolding, codeFolding,
@@ -35,15 +37,15 @@ import {
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import type { EnvironmentVariable } from '@yaakapp-internal/models'; import type { EnvironmentVariable } from '@yaakapp-internal/models';
import { graphql } from 'cm6-graphql'; import { graphql } from 'cm6-graphql';
import { renderMarkdown } from '../../../lib/markdown';
import { pluralizeCount } from '../../../lib/pluralize'; import { pluralizeCount } from '../../../lib/pluralize';
import { showInGraphQLDocsExplorer } from '../../graphql/useGraphQLDocsExplorer';
import type { EditorProps } from './Editor'; import type { EditorProps } from './Editor';
import { pairs } from './pairs/extension';
import { text } from './text/extension'; import { text } from './text/extension';
import type { TwigCompletionOption } from './twig/completion'; import type { TwigCompletionOption } from './twig/completion';
import { twig } from './twig/extension'; import { twig } from './twig/extension';
import { pathParametersPlugin } from './twig/pathParameters'; import { pathParametersPlugin } from './twig/pathParameters';
import { json } from '@codemirror/lang-json';
import { xml } from '@codemirror/lang-xml';
import { pairs } from './pairs/extension';
import { url } from './url/extension'; import { url } from './url/extension';
export const syntaxHighlightStyle = HighlightStyle.define([ export const syntaxHighlightStyle = HighlightStyle.define([
@@ -123,7 +125,22 @@ export function getLanguageExtension({
// GraphQL is a special exception // GraphQL is a special exception
if (language === 'graphql') { if (language === 'graphql') {
return [graphql(), extraExtensions]; return [
graphql(undefined, {
async onCompletionInfoRender(gqlCompletionItem): Promise<Node | null> {
if (!gqlCompletionItem.documentation) return null;
const innerHTML = await renderMarkdown(gqlCompletionItem.documentation);
const span = document.createElement('span');
span.innerHTML = innerHTML;
return span;
},
onShowInDocs(field, type, parentType) {
console.log("SHOW IN DOCS", field);
showInGraphQLDocsExplorer(field, type, parentType).catch(console.error);
},
}),
extraExtensions,
];
} }
const base_ = syntaxExtensions[language ?? 'text'] ?? text(); const base_ = syntaxExtensions[language ?? 'text'] ?? text();
@@ -229,7 +246,6 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
} }
}, },
}), }),
EditorState.allowMultipleSelections.of(true),
indentOnInput(), indentOnInput(),
rectangularSelection(), rectangularSelection(),
crosshairCursor(), crosshairCursor(),

View File

@@ -64,7 +64,7 @@ export function SplitLayout({
} }
const size = useContainerSize(containerRef); const size = useContainerSize(containerRef);
const verticalBasedOnSize = size.width < STACK_VERTICAL_WIDTH; const verticalBasedOnSize = size.width !== 0 && size.width < STACK_VERTICAL_WIDTH;
const vertical = layout !== 'horizontal' && (layout === 'vertical' || verticalBasedOnSize); const vertical = layout !== 'horizontal' && (layout === 'vertical' || verticalBasedOnSize);
const styles = useMemo<CSSProperties>(() => { const styles = useMemo<CSSProperties>(() => {
@@ -142,31 +142,25 @@ export function SplitLayout({
[width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth], [width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth],
); );
const containerQueryReady = size.width > 0 || size.height > 0;
return ( return (
<div <div
ref={containerRef} ref={containerRef}
style={styles} style={styles}
className={classNames(className, 'grid w-full h-full overflow-hidden')} className={classNames(className, 'grid w-full h-full overflow-hidden')}
> >
{containerQueryReady && ( {firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
{secondSlot && (
<> <>
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })} <ResizeHandle
{secondSlot && ( style={areaD}
<> isResizing={isResizing}
<ResizeHandle className={classNames(vertical ? '-translate-y-1.5' : '-translate-x-1.5')}
style={areaD} onResizeStart={handleResizeStart}
isResizing={isResizing} onReset={handleReset}
className={classNames(vertical ? '-translate-y-1.5' : '-translate-x-1.5')} side={vertical ? 'top' : 'left'}
onResizeStart={handleResizeStart} justify="center"
onReset={handleReset} />
side={vertical ? 'top' : 'left'} {secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
justify="center"
/>
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
</>
)}
</> </>
)} )}
</div> </div>

View File

@@ -23,17 +23,18 @@ import {
} from 'graphql'; } from 'graphql';
import type { CSSProperties, HTMLAttributes, KeyboardEvent, ReactNode } from 'react'; import type { CSSProperties, HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
import { Fragment, memo, useCallback, useMemo, useRef, useState } from 'react'; import { Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
import { showGraphQLDocExplorerAtom } from '../atoms/graphqlSchemaAtom'; import { useClickOutside } from '../../hooks/useClickOutside';
import { useClickOutside } from '../hooks/useClickOutside'; import { useContainerSize } from '../../hooks/useContainerQuery';
import { useContainerSize } from '../hooks/useContainerQuery'; import { useDebouncedValue } from '../../hooks/useDebouncedValue';
import { useDebouncedValue } from '../hooks/useDebouncedValue'; import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import { useStateWithDeps } from '../hooks/useStateWithDeps'; import { jotaiStore } from '../../lib/jotai';
import { jotaiStore } from '../lib/jotai'; import { CountBadge } from '../core/CountBadge';
import { CountBadge } from './core/CountBadge'; import { Icon } from '../core/Icon';
import { Icon } from './core/Icon'; import { IconButton } from '../core/IconButton';
import { IconButton } from './core/IconButton'; import { PlainInput } from '../core/PlainInput';
import { PlainInput } from './core/PlainInput'; import { Markdown } from '../Markdown';
import { Markdown } from './Markdown'; import { showGraphQLDocExplorerAtom } from './graphqlAtoms';
import { useGraphQLDocsExplorerEvent } from './useGraphQLDocsExplorer';
interface Props { interface Props {
style?: CSSProperties; style?: CSSProperties;
@@ -58,6 +59,17 @@ export const GraphQLDocsExplorer = memo(function GraphQLDocsExplorer({
const mutType = schema.getMutationType(); const mutType = schema.getMutationType();
const subType = schema.getSubscriptionType(); const subType = schema.getSubscriptionType();
useGraphQLDocsExplorerEvent('gql_docs_explorer.show_in_docs', ({ field }) => {
walkTypeGraph(schema, null, (t) => {
if (t.name === field) {
setActiveItem(toExplorerItem(t, null));
return false;
} else {
return true;
}
});
});
const qryItem: ExplorerItem = qryType ? { kind: 'type', type: qryType, from: null } : null; const qryItem: ExplorerItem = qryType ? { kind: 'type', type: qryType, from: null } : null;
const mutItem: ExplorerItem = mutType ? { kind: 'type', type: mutType, from: null } : null; const mutItem: ExplorerItem = mutType ? { kind: 'type', type: mutType, from: null } : null;
const subItem: ExplorerItem = subType ? { kind: 'type', type: subType, from: null } : null; const subItem: ExplorerItem = subType ? { kind: 'type', type: subType, from: null } : null;
@@ -642,22 +654,20 @@ function GqlSchemaSearch({
const results = useMemo(() => { const results = useMemo(() => {
const results: SearchResult[] = []; const results: SearchResult[] = [];
walkTypeGraph( walkTypeGraph(schema, currentItem?.type ?? null, (type, from, depth) => {
currentItem?.type ?? null, if (type === currentItem?.type) {
(type, from, depth) => { return true; // Skip the current type and continue
if (type === currentItem?.type) { }
return null; // Remove the current type from results
}
const match = fuzzyMatch(type.name, debouncedValue); const match = fuzzyMatch(type.name, debouncedValue);
if (match == null) { if (match == null) {
// Do nothing // Do nothing
} else { } else {
results.push({ name: type.name, type, score: match.score, from, depth }); results.push({ name: type.name, type, score: match.score, from, depth });
} }
},
schema, return true; // Continue searching
); });
results.sort((a, b) => { results.sort((a, b) => {
if (value == '') { if (value == '') {
if (a.name.startsWith('_') && !b.name.startsWith('_')) { if (a.name.startsWith('_') && !b.name.startsWith('_')) {
@@ -831,13 +841,13 @@ function DocMarkdown({ children, className }: { children: string | null; classNa
} }
function walkTypeGraph( function walkTypeGraph(
schema: GraphQLSchema,
start: GraphQLType | GraphQLField<any, any> | GraphQLInputField | null, start: GraphQLType | GraphQLField<any, any> | GraphQLInputField | null,
cb: ( cb: (
type: GraphQLNamedType | GraphQLField<any, any> | GraphQLInputField, type: GraphQLNamedType | GraphQLField<any, any> | GraphQLInputField,
from: GraphQLNamedType | null, from: GraphQLNamedType | null,
path: string[], path: string[],
) => void, ) => boolean,
schema: GraphQLSchema,
) { ) {
const visited = new Set<string>(); const visited = new Set<string>();
const queue: Array<{ const queue: Array<{
@@ -867,7 +877,8 @@ function walkTypeGraph(
if (visited.has(name)) continue; if (visited.has(name)) continue;
visited.add(name); visited.add(name);
cb(current, from, path); const cont = cb(current, from, path);
if (!cont) break;
if (isObjectType(current) || isInterfaceType(current)) { if (isObjectType(current) || isInterfaceType(current)) {
for (const field of Object.values(current.getFields())) { for (const field of Object.values(current.getFields())) {

View File

@@ -6,19 +6,19 @@ import { formatSdl } from 'format-graphql';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef } from 'react';
import { useLocalStorage } from 'react-use'; import { useLocalStorage } from 'react-use';
import { showGraphQLDocExplorerAtom } from '../atoms/graphqlSchemaAtom'; import { useIntrospectGraphQL } from '../../hooks/useIntrospectGraphQL';
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL'; import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import { useStateWithDeps } from '../hooks/useStateWithDeps'; import { showDialog } from '../../lib/dialog';
import { showDialog } from '../lib/dialog'; import { Banner } from '../core/Banner';
import { Banner } from './core/Banner'; import { Button } from '../core/Button';
import { Button } from './core/Button'; import type { DropdownItem } from '../core/Dropdown';
import type { DropdownItem } from './core/Dropdown'; import { Dropdown } from '../core/Dropdown';
import { Dropdown } from './core/Dropdown'; import type { EditorProps } from '../core/Editor/Editor';
import type { EditorProps } from './core/Editor/Editor'; import { Editor } from '../core/Editor/Editor';
import { Editor } from './core/Editor/Editor'; import { FormattedError } from '../core/FormattedError';
import { FormattedError } from './core/FormattedError'; import { Icon } from '../core/Icon';
import { Icon } from './core/Icon'; import { Separator } from '../core/Separator';
import { Separator } from './core/Separator'; import { showGraphQLDocExplorerAtom } from './graphqlAtoms';
type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> & { type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> & {
baseRequest: HttpRequest; baseRequest: HttpRequest;

View File

@@ -1,3 +1,3 @@
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { atomWithKVStorage } from '../../lib/atoms/atomWithKVStorage';
export const showGraphQLDocExplorerAtom = atomWithKVStorage<boolean>('show_graphql_docs', false); export const showGraphQLDocExplorerAtom = atomWithKVStorage<boolean>('show_graphql_docs', false);

View File

@@ -0,0 +1,63 @@
import EventEmitter from 'eventemitter3';
import type { DependencyList } from 'react';
import { useEffect } from 'react';
import { jotaiStore } from '../../lib/jotai';
import { sleep } from '../../lib/sleep';
import { showGraphQLDocExplorerAtom } from './graphqlAtoms';
type EventDataMap = {
'gql_docs_explorer.show_in_docs': { field?: string; type?: string; parentType?: string };
'gql_docs_explorer.focus_tab': undefined;
};
export function useGraphQLDocsExplorerEvent<
Event extends keyof EventDataMap,
Data extends EventDataMap[Event],
>(event: Event, fn: (data: Data) => void, deps?: DependencyList) {
useEffect(() => {
emitter.on(event, fn);
return () => {
emitter.off(event, fn);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}
export async function showInGraphQLDocsExplorer(
field: string | undefined,
type: string | undefined,
parentType: string | undefined,
) {
const isVisible = jotaiStore.get(showGraphQLDocExplorerAtom);
if (!isVisible) {
// Show and give some time for the explorer to start listening for events
jotaiStore.set(showGraphQLDocExplorerAtom, true);
await sleep(100);
}
emitter.emit('gql_docs_explorer.show_in_docs', { field, type, parentType });
}
const emitter = new (class GraphQLDocsExplorerEventEmitter {
#emitter: EventEmitter = new EventEmitter();
emit<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
event: Event,
data: Data,
) {
this.#emitter.emit(event, data);
}
on<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
event: Event,
fn: (data: Data) => void,
) {
this.#emitter.on(event, fn);
}
off<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
event: Event,
fn: (data: Data) => void,
) {
this.#emitter.off(event, fn);
}
})();

27
src-web/lib/markdown.ts Normal file
View File

@@ -0,0 +1,27 @@
import rehypeStringify from 'rehype-stringify';
import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import { unified } from 'unified';
const renderer = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype, {
// handlers: {
// link: (state, node, parent) => {
// return node;
// },
// },
})
.use(rehypeStringify);
export async function renderMarkdown(md: string): Promise<string> {
try {
const r = await renderer.process(md);
return r.toString();
} catch (err) {
console.log('FAILED TO RENDER MARKDOWN', err);
return 'error';
}
}

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"dev": "vite dev --force", "dev": "vite dev --force",
"build": "vite build", "build": "vite build",
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx" "lint": "eslint . --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"@codemirror/commands": "^6.8.1", "@codemirror/commands": "^6.8.1",
@@ -61,6 +61,8 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-pdf": "^10.0.1", "react-pdf": "^10.0.1",
"react-use": "^17.6.0", "react-use": "^17.6.0",
"rehype-stringify": "^10.0.1",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"uuid": "^11.1.0", "uuid": "^11.1.0",