mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-24 23:59:16 -04:00
Improve codemirror typings (#3993)
Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
This commit is contained in:
1 parent
cf2387bbcd
commit
381cf74a19
24 files changed
+612
-421
No files matched your search
@@ -141,6 +141,7 @@ module.exports = {
|
||||
'@typescript-eslint/keyword-spacing': ERROR,
|
||||
'@typescript-eslint/member-delimiter-style': ERROR,
|
||||
'@typescript-eslint/no-empty-function': OFF(UNKNOWN),
|
||||
'@typescript-eslint/no-empty-interface': [ERROR, { 'allowSingleExtends': true }],
|
||||
'@typescript-eslint/no-namespace': [ERROR, { allowDeclarations: true }],
|
||||
'@typescript-eslint/no-redeclare': ERROR,
|
||||
'@typescript-eslint/no-unused-vars': [ERROR, { ignoreRestSiblings: true }],
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { GraphQLInfoOptions } from 'codemirror-graphql/info';
|
||||
|
||||
declare module 'codemirror-graphql/jump' {
|
||||
type ModifiedGraphQLJumpOptions = Omit<GraphQLJumpOptions, 'onClick'> & {
|
||||
onClick: GraphQLInfoOptions['onClick'];
|
||||
};
|
||||
}
|
||||
+73
-2
@@ -1,6 +1,11 @@
|
||||
import 'codemirror';
|
||||
|
||||
import { GraphQLInfoOptions } from 'codemirror-graphql/info';
|
||||
import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
|
||||
import { HandleGetRenderContext, HandleRender } from './common/render';
|
||||
import { NunjucksParsedTag } from './templating/utils';
|
||||
|
||||
type LinkClickCallback = (url: string) => void;
|
||||
|
||||
@@ -18,8 +23,74 @@ interface InsomniaExtensions {
|
||||
declare module 'codemirror' {
|
||||
type CodeMirrorLinkClickCallback = LinkClickCallback;
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
interface Editor extends InsomniaExtensions {}
|
||||
interface EditorFromTextEditor extends InsomniaExtensions {}
|
||||
/* eslint-enable @typescript-eslint/no-empty-interface */
|
||||
interface TextMarker {
|
||||
// This flag is being used internally by codemirror and the fold extension
|
||||
__isFold: boolean;
|
||||
}
|
||||
|
||||
interface Variable {
|
||||
name: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface Snippet {
|
||||
name: string;
|
||||
displayValue: string;
|
||||
value: string | (() => Promise<unknown>);
|
||||
}
|
||||
|
||||
interface EnvironmentAutocompleteOptions {
|
||||
getConstants?: () => string[] | PromiseLike<string[]>;
|
||||
getVariables?: () => Variable[] | PromiseLike<Variable[]>;
|
||||
getSnippets?: () => Snippet[] | PromiseLike<Snippet[]>;
|
||||
getTags?: () => NunjucksParsedTag[] | PromiseLike<NunjucksParsedTag[]>;
|
||||
}
|
||||
|
||||
interface EditorConfiguration {
|
||||
info?: GraphQLInfoOptions;
|
||||
jump?: ModifiedGraphQLJumpOptions;
|
||||
environmentAutocomplete?: EnvironmentAutocompleteOptions;
|
||||
}
|
||||
|
||||
interface Hint {
|
||||
/**
|
||||
* Custom Insomnia Key. Used for checking the type of the hint
|
||||
*/
|
||||
type: 'constant' | 'variable' | 'snippet' | 'tag';
|
||||
/**
|
||||
* Custom Insomnia Key. The segment that matched and produced this hint
|
||||
*/
|
||||
segment: string;
|
||||
/**
|
||||
* Custom Insomnia Key. This value gets displayed in the autocomplete menu.
|
||||
*/
|
||||
displayValue: string;
|
||||
/**
|
||||
* Custom Insomnia Key. The display value of the hint
|
||||
*/
|
||||
comment?: string;
|
||||
/**
|
||||
* Custom Insomnia Key. Used for sorting the hints
|
||||
*/
|
||||
score: number;
|
||||
}
|
||||
|
||||
interface ShowHintOptions {
|
||||
variables?: Variable[];
|
||||
constants?: string[];
|
||||
snippets?: Snippet[];
|
||||
tags?: NunjucksParsedTag[];
|
||||
showAllOnNoMatch?: boolean;
|
||||
}
|
||||
|
||||
interface LintOptions {
|
||||
schema?: GraphQLSchema;
|
||||
}
|
||||
|
||||
interface EditorEventMap {
|
||||
fold: (instance: Editor, from: Position) => void;
|
||||
unfold: (instance: Editor, from: Position) => void;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// @TODO We should make a PR for improving the types of httpsnippet.
|
||||
declare module 'httpsnippet' {
|
||||
export interface HTTPSnippetClient {
|
||||
key: string;
|
||||
title: string;
|
||||
link: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface HTTPSnippetTarget {
|
||||
key: string;
|
||||
title: string;
|
||||
extname: string;
|
||||
default: string;
|
||||
clients: HTTPSnippetClient[];
|
||||
}
|
||||
|
||||
class HTTPSnippet {
|
||||
constructor(data: any);
|
||||
convert(target: string, options?: any): string | false;
|
||||
|
||||
convert(target: string, client?: string, options?: any): string | false;
|
||||
|
||||
static addTarget(target: HTTPSnippetTarget): void;
|
||||
|
||||
static addTargetClient(target: string, client: HTTPSnippetClient): void;
|
||||
|
||||
static extName(target: string): string;
|
||||
static availableTargets(): HTTPSnippetTarget[];
|
||||
}
|
||||
|
||||
export default HTTPSnippet;
|
||||
}
|
||||
@@ -66,6 +66,7 @@ export interface BaseSettings {
|
||||
hasPromptedToMigrateFromDesigner: boolean;
|
||||
hasPromptedOnboarding: boolean;
|
||||
hasPromptedAnalytics: boolean;
|
||||
isVariableUncovered?: boolean;
|
||||
}
|
||||
|
||||
export type Settings = BaseModel & BaseSettings;
|
||||
|
||||
@@ -3,7 +3,9 @@ import './base-imports';
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import clone from 'clone';
|
||||
import CodeMirror, { CodeMirrorLinkClickCallback } from 'codemirror';
|
||||
import CodeMirror, { CodeMirrorLinkClickCallback, ShowHintOptions } from 'codemirror';
|
||||
import { GraphQLInfoOptions } from 'codemirror-graphql/info';
|
||||
import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { json as jsonPrettify } from 'insomnia-prettify';
|
||||
import { query as queryXPath } from 'insomnia-xpath';
|
||||
@@ -40,9 +42,7 @@ const BASE_CODEMIRROR_OPTIONS: CodeMirror.EditorConfiguration = {
|
||||
lineNumbers: true,
|
||||
placeholder: 'Start Typing...',
|
||||
foldGutter: true,
|
||||
height: 'auto',
|
||||
// @ts-expect-error -- TSCONVERSION should be autoRefresh: { delay: 2000 }
|
||||
autoRefresh: 2000,
|
||||
autoRefresh: { delay: 2000 },
|
||||
lineWrapping: true,
|
||||
scrollbarStyle: 'native',
|
||||
lint: true,
|
||||
@@ -89,20 +89,20 @@ export type CodeEditorOnChange = (value: string) => void;
|
||||
interface Props {
|
||||
indentWithTabs?: boolean;
|
||||
onChange?: CodeEditorOnChange;
|
||||
onCursorActivity?: Function;
|
||||
onFocus?: Function;
|
||||
onBlur?: Function;
|
||||
onCursorActivity?: (cm: CodeMirror.EditorFromTextArea) => void;
|
||||
onFocus?: (e: FocusEvent) => void;
|
||||
onBlur?: (e: FocusEvent) => void;
|
||||
onClickLink?: CodeMirrorLinkClickCallback;
|
||||
onKeyDown?: Function;
|
||||
onKeyDown?: (e: KeyboardEvent, value: string) => void;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onPaste?: Function;
|
||||
onPaste?: (e: ClipboardEvent) => void;
|
||||
onCodeMirrorInit?: (editor: CodeMirror.EditorFromTextArea) => void;
|
||||
render?: HandleRender;
|
||||
nunjucksPowerUserMode?: boolean;
|
||||
getRenderContext?: HandleGetRenderContext;
|
||||
getAutocompleteConstants?: Function;
|
||||
getAutocompleteSnippets?: Function;
|
||||
getAutocompleteConstants?: () => string[] | PromiseLike<string[]>;
|
||||
getAutocompleteSnippets?: () => CodeMirror.Snippet[];
|
||||
keyMap?: string;
|
||||
mode?: string;
|
||||
id?: string;
|
||||
@@ -133,10 +133,10 @@ interface Props {
|
||||
debounceMillis?: number;
|
||||
dynamicHeight?: boolean;
|
||||
autoCloseBrackets?: boolean;
|
||||
hintOptions?: Object;
|
||||
lintOptions?: Object;
|
||||
infoOptions?: Object;
|
||||
jumpOptions?: Object;
|
||||
hintOptions?: ShowHintOptions;
|
||||
lintOptions?: any;
|
||||
infoOptions?: GraphQLInfoOptions;
|
||||
jumpOptions?: ModifiedGraphQLJumpOptions;
|
||||
uniquenessKey?: string;
|
||||
isVariableUncovered?: boolean;
|
||||
raw?: boolean;
|
||||
@@ -146,6 +146,14 @@ interface State {
|
||||
filter: string;
|
||||
}
|
||||
|
||||
function isMarkerRange(mark?: CodeMirror.Position | CodeMirror.MarkerRange): mark is CodeMirror.MarkerRange {
|
||||
if (!mark) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.prototype.hasOwnProperty.call(mark, 'from');
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
class CodeEditor extends Component<Props, State> {
|
||||
private _uniquenessKey?: string;
|
||||
@@ -238,7 +246,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
setCursor(ch, line = 0) {
|
||||
setCursor(ch: number, line = 0) {
|
||||
if (this.codeMirror) {
|
||||
if (!this.hasFocus()) {
|
||||
this.focus();
|
||||
@@ -296,7 +304,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
|
||||
getSelectionStart() {
|
||||
if (!this.codeMirror) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const selections = this.codeMirror.listSelections();
|
||||
@@ -310,7 +318,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
|
||||
getSelectionEnd() {
|
||||
if (!this.codeMirror) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const selections = this.codeMirror.listSelections();
|
||||
@@ -348,26 +356,23 @@ class CodeEditor extends Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error this is a (Node & ParentNode) in the types, but I think it's actually supposed to be Element
|
||||
this.codeMirror.getTextArea().parentNode?.setAttribute(name, value);
|
||||
this.codeMirror.getTextArea().parentElement?.setAttribute(name, value);
|
||||
}
|
||||
|
||||
removeAttribute(name) {
|
||||
removeAttribute(name: string) {
|
||||
if (!this.codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error this is a (Node & ParentNode) in the types, but I think it's actually supposed to be Element
|
||||
this.codeMirror.getTextArea().parentNode?.removeAttribute(name);
|
||||
this.codeMirror.getTextArea().parentElement?.removeAttribute(name);
|
||||
}
|
||||
|
||||
getAttribute(name) {
|
||||
getAttribute(name: string) {
|
||||
if (!this.codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error this is a (Node & ParentNode) in the types, but I think it's actually supposed to be Element
|
||||
this.codeMirror.getTextArea().parentNode?.getAttribute(name);
|
||||
this.codeMirror.getTextArea().parentElement?.getAttribute(name);
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
@@ -412,16 +417,20 @@ class CodeEditor extends Component<Props, State> {
|
||||
|
||||
const marks = this.codeMirror
|
||||
.getAllMarks()
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
.filter(c => c.__isFold)
|
||||
.filter(mark => mark.__isFold)
|
||||
.map(mark => {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
const { from, to } = mark.find();
|
||||
const result = mark.find();
|
||||
|
||||
if (isMarkerRange(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
to,
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
editorStates[uniquenessKey] = {
|
||||
scroll: this.codeMirror.getScrollInfo(),
|
||||
selections: this.codeMirror.listSelections(),
|
||||
@@ -472,7 +481,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
const foldOptions = {
|
||||
const foldOptions: CodeMirror.EditorConfiguration['foldOptions'] = {
|
||||
widget: (from, to) => {
|
||||
let count;
|
||||
// Get open / close token
|
||||
@@ -517,9 +526,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
this.codeMirror.on('blur', this._codemirrorBlur);
|
||||
this.codeMirror.on('paste', this._codemirrorPaste);
|
||||
this.codeMirror.on('scroll', this._codemirrorScroll);
|
||||
// @ts-expect-error this event does indeed exist, but is not present on the CodeMirror types and declaration merging doesn't seem to want to allow adding it
|
||||
this.codeMirror.on('fold', this._codemirrorToggleFold);
|
||||
// @ts-expect-error this event does indeed exist, but is not present on the CodeMirror types and declaration merging doesn't seem to want to allow adding it
|
||||
this.codeMirror.on('unfold', this._codemirrorToggleFold);
|
||||
this.codeMirror.on('keyHandled', this._codemirrorKeyHandled);
|
||||
// Prevent these things if we're type === "password"
|
||||
@@ -530,16 +537,19 @@ class CodeEditor extends Component<Props, State> {
|
||||
line: -1,
|
||||
ch: -1,
|
||||
});
|
||||
|
||||
let extraKeys = BASE_CODEMIRROR_OPTIONS.extraKeys;
|
||||
extraKeys = extraKeys && typeof extraKeys !== 'string' ? extraKeys : {};
|
||||
|
||||
this.codeMirror.setOption('extraKeys', {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
...BASE_CODEMIRROR_OPTIONS.extraKeys,
|
||||
...extraKeys,
|
||||
Tab: cm => {
|
||||
// Indent with tabs or spaces
|
||||
// From https://github.com/codemirror/CodeMirror/issues/988#issuecomment-14921785
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add');
|
||||
} else {
|
||||
cm.replaceSelection(this._indentChars(), 'end', '+input');
|
||||
cm.replaceSelection(this._indentChars(), 'end');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -593,7 +603,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
this.codeMirror.on('cursorActivity', this._codemirrorCursorActivity);
|
||||
}
|
||||
|
||||
static _isJSON(mode) {
|
||||
static _isJSON(mode?: string) {
|
||||
if (!mode) {
|
||||
return false;
|
||||
}
|
||||
@@ -601,7 +611,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
return mode.indexOf('json') !== -1;
|
||||
}
|
||||
|
||||
static _isYAML(mode) {
|
||||
static _isYAML(mode?: string) {
|
||||
if (!mode) {
|
||||
return false;
|
||||
}
|
||||
@@ -609,7 +619,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
return mode.indexOf('yaml') !== -1;
|
||||
}
|
||||
|
||||
static _isXML(mode) {
|
||||
static _isXML(mode?: string) {
|
||||
if (!mode) {
|
||||
return false;
|
||||
}
|
||||
@@ -617,7 +627,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
return mode.indexOf('xml') !== -1;
|
||||
}
|
||||
|
||||
static _isEDN(mode) {
|
||||
static _isEDN(mode?: string) {
|
||||
if (!mode) {
|
||||
return false;
|
||||
}
|
||||
@@ -635,11 +645,11 @@ class CodeEditor extends Component<Props, State> {
|
||||
this._prettify(this.codeMirror?.getValue());
|
||||
}
|
||||
|
||||
_prettify(code) {
|
||||
_prettify(code?: string) {
|
||||
this._codemirrorSetValue(code, true);
|
||||
}
|
||||
|
||||
_prettifyJSON(code) {
|
||||
_prettifyJSON(code: string) {
|
||||
try {
|
||||
let jsonString = code;
|
||||
|
||||
@@ -661,7 +671,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
static _prettifyEDN(code) {
|
||||
static _prettifyEDN(code: string) {
|
||||
try {
|
||||
return zprint(code, null);
|
||||
} catch (e) {
|
||||
@@ -669,7 +679,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_prettifyXML(code) {
|
||||
_prettifyXML(code: string) {
|
||||
if (this.props.updateFilter && this.state.filter) {
|
||||
try {
|
||||
const results = queryXPath(code, this.state.filter);
|
||||
@@ -733,11 +743,11 @@ class CodeEditor extends Component<Props, State> {
|
||||
// NOTE: YAML is not valid when indented with Tabs
|
||||
const isYaml = typeof rawMode === 'string' ? rawMode.includes('yaml') : false;
|
||||
const actuallyIndentWithTabs = indentWithTabs && !isYaml;
|
||||
const options: any = {
|
||||
const options: CodeMirror.EditorConfiguration = {
|
||||
readOnly: !!readOnly,
|
||||
placeholder: placeholder || '',
|
||||
mode: mode,
|
||||
tabIndex: typeof tabIndex === 'number' ? tabIndex : null,
|
||||
tabindex: typeof tabIndex === 'number' ? tabIndex : undefined,
|
||||
dragDrop: !noDragDrop,
|
||||
scrollbarStyle: hideScrollbars ? 'null' : 'native',
|
||||
styleActiveLine: !noStyleActiveLine,
|
||||
@@ -761,16 +771,18 @@ class CodeEditor extends Component<Props, State> {
|
||||
options.indentUnit = indentSize;
|
||||
}
|
||||
|
||||
if (!hideGutters && options.lint) {
|
||||
options.gutters.push('CodeMirror-lint-markers');
|
||||
}
|
||||
if (options.gutters && !hideGutters) {
|
||||
if (options.lint) {
|
||||
options.gutters.push('CodeMirror-lint-markers');
|
||||
}
|
||||
|
||||
if (!hideGutters && options.lineNumbers) {
|
||||
options.gutters.push('CodeMirror-linenumbers');
|
||||
if (options.lineNumbers) {
|
||||
options.gutters.push('CodeMirror-linenumbers');
|
||||
}
|
||||
}
|
||||
|
||||
if (!hideGutters && options.foldGutter) {
|
||||
options.gutters.push('CodeMirror-foldgutter');
|
||||
options.gutters?.push('CodeMirror-foldgutter');
|
||||
}
|
||||
|
||||
if (hintOptions) {
|
||||
@@ -795,7 +807,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
|
||||
// Setup the hint options
|
||||
if (getRenderContext || getAutocompleteConstants || getAutocompleteSnippets) {
|
||||
let getVariables: (() => Promise<Object[]>) | undefined;
|
||||
let getVariables: (() => Promise<CodeMirror.Variable[]>) | undefined;
|
||||
let getTags: (() => Promise<NunjucksParsedTag[]>) | undefined;
|
||||
|
||||
if (getRenderContext) {
|
||||
@@ -818,8 +830,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
|
||||
for (const option of firstArg.options || []) {
|
||||
// @ts-expect-error -- TSCONVERSION option.name doesn't exist
|
||||
const optionName = misc.fnOrString(option.displayName, tagDef.args) || option.name;
|
||||
const optionName = misc.fnOrString(option.displayName, tagDef.args);
|
||||
const newDef = clone(tagDef);
|
||||
newDef.displayName = `${tagDef.displayName} ⇒ ${optionName}`;
|
||||
newDef.args[0].defaultValue = option.value;
|
||||
@@ -844,25 +855,26 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
|
||||
// Strip of charset if there is one
|
||||
Object.keys(options).map(key => {
|
||||
this._codemirrorSmartSetOption(key, options[key]);
|
||||
});
|
||||
Object.keys(options).map(key =>
|
||||
this._codemirrorSmartSetOption(
|
||||
key as keyof CodeMirror.EditorConfiguration,
|
||||
options[key]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set option if it's different than in the current Codemirror instance
|
||||
*/
|
||||
_codemirrorSmartSetOption(key, value) {
|
||||
_codemirrorSmartSetOption<K extends keyof CodeMirror.EditorConfiguration>(key: K, value: CodeMirror.EditorConfiguration[K]) {
|
||||
const cm = this.codeMirror;
|
||||
let shouldSetOption = false;
|
||||
|
||||
if (key === 'jump' || key === 'info' || key === 'lint' || key === 'hintOptions') {
|
||||
// Use stringify here because these could be infinitely recursive due to GraphQL
|
||||
// schemas
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
shouldSetOption = JSON.stringify(value) !== JSON.stringify(cm.options[key]);
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
} else if (!deepEqual(value, cm.options[key])) {
|
||||
shouldSetOption = JSON.stringify(value) !== JSON.stringify(cm?.getOption(key));
|
||||
} else if (!deepEqual(value, cm?.getOption(key))) {
|
||||
// Don't set the option if it hasn't changed
|
||||
shouldSetOption = true;
|
||||
}
|
||||
@@ -875,8 +887,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
// Set the option safely. When setting "lint", for example, it can throw an exception
|
||||
// and cause the editor to break.
|
||||
try {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
cm.setOption(key, value);
|
||||
cm?.setOption(key, value);
|
||||
} catch (err) {
|
||||
console.log('Failed to set CodeMirror option', err.message, {
|
||||
key,
|
||||
@@ -885,7 +896,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
static _normalizeMode(mode) {
|
||||
static _normalizeMode(mode?: string) {
|
||||
const mimeType = mode ? mode.split(';')[0] : 'text/plain';
|
||||
|
||||
if (mimeType.includes('graphql-variables')) {
|
||||
@@ -910,15 +921,15 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorCursorActivity(instance) {
|
||||
_codemirrorCursorActivity(instance: CodeMirror.EditorFromTextArea) {
|
||||
if (this.props.onCursorActivity) {
|
||||
this.props.onCursorActivity(instance);
|
||||
}
|
||||
}
|
||||
|
||||
async _codemirrorKeyDown(doc: CodeMirror.EditorFromTextArea, e) {
|
||||
async _codemirrorKeyDown(doc: CodeMirror.EditorFromTextArea, e: KeyboardEvent & {codemirrorIgnore: boolean}) {
|
||||
// Use default tab behaviour if we're told
|
||||
if (this.props.defaultTabBehavior && e.keyCode === TAB_KEY) {
|
||||
if (this.props.defaultTabBehavior && e.code === TAB_KEY.toString()) {
|
||||
e.codemirrorIgnore = true;
|
||||
}
|
||||
|
||||
@@ -933,9 +944,9 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorTriggerCompletionKeyUp(doc, e) {
|
||||
_codemirrorTriggerCompletionKeyUp(doc: CodeMirror.EditorFromTextArea, e: KeyboardEvent) {
|
||||
// Enable graphql completion if we're in that mode
|
||||
if (doc.options.mode === 'graphql') {
|
||||
if (doc.getOption('mode') === 'graphql') {
|
||||
// Only operate on one-letter keys. This will filter out
|
||||
// any special keys (Backspace, Enter, etc)
|
||||
if (e.metaKey || e.ctrlKey || e.altKey || e.key.length > 1) {
|
||||
@@ -958,13 +969,13 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorFocus(_doc, e) {
|
||||
_codemirrorFocus(_doc: CodeMirror.EditorFromTextArea, e: FocusEvent) {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(e);
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorBlur(_doc, e) {
|
||||
_codemirrorBlur(_doc: CodeMirror.EditorFromTextArea, e: FocusEvent) {
|
||||
this._persistState();
|
||||
|
||||
if (this.props.onBlur) {
|
||||
@@ -980,7 +991,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
this._persistState();
|
||||
}
|
||||
|
||||
_codemirrorKeyHandled(_codeMirror, _keyName, event) {
|
||||
_codemirrorKeyHandled(_codeMirror: CodeMirror.EditorFromTextArea, _keyName: string, event: KeyboardEvent) {
|
||||
const { keyMap } = this.props;
|
||||
const { keyCode } = event;
|
||||
const isVimKeyMap = keyMap === EDITOR_KEY_MAP_VIM;
|
||||
@@ -991,7 +1002,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorValueBeforeChange(doc: CodeMirror.Editor, change: CodeMirror.EditorChangeCancellable) {
|
||||
_codemirrorValueBeforeChange(doc: CodeMirror.EditorFromTextArea, change: CodeMirror.EditorChangeCancellable) {
|
||||
const value = this.codeMirror?.getDoc().getValue();
|
||||
|
||||
// If we're in single-line mode, merge all changed lines into one
|
||||
@@ -1004,7 +1015,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
|
||||
// Don't allow non-breaking spaces because they break the GraphQL syntax
|
||||
if (doc.getOption('mode') === 'graphql') {
|
||||
if (doc.getOption('mode') === 'graphql' && change.text.length > 0) {
|
||||
const text = change.text.map(normalizeIrregularWhitespace);
|
||||
|
||||
change.update?.(change.from, change.to, text);
|
||||
@@ -1018,13 +1029,13 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorPaste(_cm, e) {
|
||||
_codemirrorPaste(_cm: CodeMirror.EditorFromTextArea, e: ClipboardEvent) {
|
||||
if (this.props.onPaste) {
|
||||
this.props.onPaste(e);
|
||||
}
|
||||
}
|
||||
|
||||
_codemirrorPreventWhenTypePassword(_cm, e) {
|
||||
_codemirrorPreventWhenTypePassword(_cm: CodeMirror.EditorFromTextArea, e: Event) {
|
||||
const { type } = this.props;
|
||||
|
||||
if (type && type.toLowerCase() === 'password') {
|
||||
@@ -1046,8 +1057,8 @@ class CodeEditor extends Component<Props, State> {
|
||||
// Disable linting if the document reaches a maximum size or is empty
|
||||
const isOverMaxSize = value.length > MAX_SIZE_FOR_LINTING;
|
||||
const shouldLint = isOverMaxSize || value.length === 0 ? false : !this.props.noLint;
|
||||
// @ts-expect-error TSCONVERSION
|
||||
const existingLint = this.codeMirror?.options.lint || false;
|
||||
|
||||
const existingLint = this.codeMirror?.getOption('lint') || false;
|
||||
|
||||
if (shouldLint !== existingLint) {
|
||||
const { lintOptions } = this.props;
|
||||
@@ -1063,7 +1074,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
* @param code the code to set in the editor
|
||||
* @param forcePrettify
|
||||
*/
|
||||
_codemirrorSetValue(code, forcePrettify = false) {
|
||||
_codemirrorSetValue(code = '', forcePrettify = false) {
|
||||
if (typeof code !== 'string') {
|
||||
console.warn('Code editor was passed non-string value', code);
|
||||
return;
|
||||
@@ -1089,10 +1100,10 @@ class CodeEditor extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
this.codeMirror?.setValue(code || '');
|
||||
this.codeMirror?.setValue(code);
|
||||
}
|
||||
|
||||
_handleFilterHistorySelect(filter) {
|
||||
_handleFilterHistorySelect(filter = '') {
|
||||
this._filterInput.value = filter;
|
||||
|
||||
this._setFilter(filter);
|
||||
@@ -1102,7 +1113,7 @@ class CodeEditor extends Component<Props, State> {
|
||||
this._setFilter(e.target.value);
|
||||
}
|
||||
|
||||
_setFilter(filter) {
|
||||
_setFilter(filter = '') {
|
||||
if (this._filterTimeout !== null) {
|
||||
clearTimeout(this._filterTimeout);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'codemirror/addon/mode/overlay';
|
||||
|
||||
import CodeMirror from 'codemirror';
|
||||
import CodeMirror, { EnvironmentAutocompleteOptions, Hint, ShowHintOptions } from 'codemirror';
|
||||
|
||||
import { escapeHTML, escapeRegex, isNotNullOrUndefined } from '../../../../common/misc';
|
||||
import * as models from '../../../../models';
|
||||
import { getDefaultFill } from '../../../../templating/utils';
|
||||
import { getDefaultFill, NunjucksParsedTag } from '../../../../templating/utils';
|
||||
import { isNunjucksMode } from '../modes/nunjucks';
|
||||
|
||||
const NAME_MATCH_FLEXIBLE = /[\w.\][\-/]+$/;
|
||||
const NAME_MATCH = /[\w.\][]+$/;
|
||||
@@ -54,14 +55,14 @@ CodeMirror.defineExtension('closeHintDropdown', function() {
|
||||
this.state.completionActive?.close();
|
||||
});
|
||||
|
||||
CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => {
|
||||
CodeMirror.defineOption('environmentAutocomplete', null, (cm: CodeMirror.EditorFromTextArea, options: EnvironmentAutocompleteOptions) => {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function completeAfter(cm: CodeMirror.Editor, fn, showAllOnNoMatch = false) {
|
||||
async function completeAfter(cm: CodeMirror.EditorFromTextArea, callback?: () => boolean, showAllOnNoMatch = false) {
|
||||
// Bail early if didn't match the callback test
|
||||
if (fn && !fn()) {
|
||||
if (callback && !callback()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,7 +92,6 @@ CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => {
|
||||
// Actually show the hint
|
||||
cm.showHint({
|
||||
// Insomnia-specific options
|
||||
// @ts-expect-error -- TSCONVERSION needs investigation
|
||||
constants: constants || [],
|
||||
variables: variables || [],
|
||||
snippets: snippets || [],
|
||||
@@ -114,37 +114,39 @@ CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => {
|
||||
});
|
||||
}
|
||||
|
||||
function completeIfInVariableName(cm) {
|
||||
function completeIfInVariableName(cm: CodeMirror.EditorFromTextArea) {
|
||||
completeAfter(cm, () => {
|
||||
const cur = cm.getCursor();
|
||||
const pos = CodeMirror.Pos(cur.line, cur.ch - MAX_HINT_LOOK_BACK);
|
||||
const range = cm.getRange(pos, cur);
|
||||
return range.match(COMPLETE_AFTER_WORD);
|
||||
|
||||
return COMPLETE_AFTER_WORD.test(range);
|
||||
});
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
function completeIfAfterTagOrVarOpen(cm) {
|
||||
function completeIfAfterTagOrVarOpen(cm: CodeMirror.EditorFromTextArea) {
|
||||
completeAfter(
|
||||
cm,
|
||||
() => {
|
||||
const cur = cm.getCursor();
|
||||
const pos = CodeMirror.Pos(cur.line, cur.ch - MAX_HINT_LOOK_BACK);
|
||||
const range = cm.getRange(pos, cur);
|
||||
return range.match(COMPLETE_AFTER_CURLIES);
|
||||
|
||||
return COMPLETE_AFTER_CURLIES.test(range);
|
||||
},
|
||||
true,
|
||||
);
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
function completeForce(cm) {
|
||||
completeAfter(cm, null, true);
|
||||
function completeForce(cm: CodeMirror.EditorFromTextArea) {
|
||||
completeAfter(cm, undefined, true);
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
let keydownDebounce: NodeJS.Timeout | null = null;
|
||||
cm.on('keydown', async (cm, e) => {
|
||||
let keydownTimeoutHandle: NodeJS.Timeout | null = null;
|
||||
cm.on('keydown', async (cm: CodeMirror.EditorFromTextArea, e) => {
|
||||
// Close autocomplete on Escape if it's open
|
||||
if (cm.isHintDropdownActive() && e.key === 'Escape') {
|
||||
if (!cm.state.completionActive) {
|
||||
@@ -162,21 +164,21 @@ CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keydownDebounce !== null) {
|
||||
clearTimeout(keydownDebounce);
|
||||
if (keydownTimeoutHandle !== null) {
|
||||
clearTimeout(keydownTimeoutHandle);
|
||||
}
|
||||
const { autocompleteDelay } = await models.settings.getOrCreate();
|
||||
|
||||
if (autocompleteDelay > 0) {
|
||||
keydownDebounce = setTimeout(() => {
|
||||
keydownTimeoutHandle = setTimeout(() => {
|
||||
completeIfInVariableName(cm);
|
||||
}, autocompleteDelay);
|
||||
}
|
||||
});
|
||||
// Clear timeout if we already closed the completion
|
||||
cm.on('endCompletion', () => {
|
||||
if (keydownDebounce !== null) {
|
||||
clearTimeout(keydownDebounce);
|
||||
if (keydownTimeoutHandle !== null) {
|
||||
clearTimeout(keydownTimeoutHandle);
|
||||
}
|
||||
});
|
||||
// Remove keymap if we're already added it
|
||||
@@ -196,12 +198,12 @@ CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => {
|
||||
* @param options
|
||||
* @returns {Promise.<{list: Array, from, to}>}
|
||||
*/
|
||||
function hint(cm, options) {
|
||||
function hint(cm: CodeMirror.EditorFromTextArea, options: ShowHintOptions) {
|
||||
// Add type to all things (except constants, which need to convert to an object)
|
||||
const variablesToMatch = (options.variables || []).map(v => ({ ...v, type: TYPE_VARIABLE }));
|
||||
const snippetsToMatch = (options.snippets || []).map(v => ({ ...v, type: TYPE_SNIPPET }));
|
||||
const tagsToMatch = (options.tags || []).map(v => ({ ...v, type: TYPE_TAG }));
|
||||
const constantsToMatch = (options.constants || []).map(s => ({
|
||||
const variablesToMatch: VariableCompletionItem[] = (options.variables || []).map(v => ({ ...v, type: TYPE_VARIABLE }));
|
||||
const snippetsToMatch: SnippetCompletionItem[] = (options.snippets || []).map(v => ({ ...v, type: TYPE_SNIPPET }));
|
||||
const tagsToMatch: TagCompletionItem[] = (options.tags || []).map(v => ({ ...v, type: TYPE_TAG }));
|
||||
const constantsToMatch: ConstantCompletionItem[] = (options.constants || []).map(s => ({
|
||||
name: s,
|
||||
value: s,
|
||||
displayValue: '',
|
||||
@@ -228,24 +230,27 @@ function hint(cm, options) {
|
||||
const nameSegmentLong = nameMatchLong ? nameMatchLong[0] : fallbackSegment;
|
||||
const nameSegmentFull = previousText;
|
||||
// Actually try to match the list of things
|
||||
const lowPriorityMatches = [];
|
||||
const highPriorityMatches = [];
|
||||
let mode;
|
||||
let lowPriorityMatches: Hint[] = [];
|
||||
let highPriorityMatches: Hint[] = [];
|
||||
|
||||
try {
|
||||
mode = cm.getOption('mode').baseMode;
|
||||
} catch (e) {
|
||||
mode = 'unknown';
|
||||
const modeOption = cm.getOption('mode') ?? 'unknown';
|
||||
|
||||
let mode = 'unknown';
|
||||
|
||||
if (typeof modeOption === 'string') {
|
||||
mode = modeOption;
|
||||
} else if (isNunjucksMode(modeOption)) {
|
||||
mode = modeOption.baseMode;
|
||||
}
|
||||
|
||||
// Match variables
|
||||
if (allowMatchingVariables) {
|
||||
matchSegments(variablesToMatch, nameSegment, TYPE_VARIABLE, MAX_VARIABLES).forEach(m =>
|
||||
lowPriorityMatches.push(m),
|
||||
);
|
||||
matchSegments(variablesToMatch, nameSegmentLong, TYPE_VARIABLE, MAX_VARIABLES).forEach(m =>
|
||||
highPriorityMatches.push(m),
|
||||
);
|
||||
const sortVariableCompletionHints = getCompletionHints(variablesToMatch, nameSegment, TYPE_VARIABLE, MAX_VARIABLES);
|
||||
lowPriorityMatches = [...lowPriorityMatches, ...sortVariableCompletionHints];
|
||||
|
||||
const longVariableCompletionHints = getCompletionHints(variablesToMatch, nameSegmentLong, TYPE_VARIABLE, MAX_VARIABLES);
|
||||
|
||||
highPriorityMatches = [...highPriorityMatches, ...longVariableCompletionHints];
|
||||
}
|
||||
|
||||
// Match constants
|
||||
@@ -262,48 +267,48 @@ function hint(cm, options) {
|
||||
// Remove trailing quotes and spaces
|
||||
if (token.type === 'variable') {
|
||||
// We're inside a JSON key
|
||||
matchSegments(constantsToMatch, segment, TYPE_CONSTANT, MAX_CONSTANTS).forEach(m =>
|
||||
highPriorityMatches.push(m),
|
||||
);
|
||||
const constantCompletionHints = getCompletionHints(constantsToMatch, segment, TYPE_CONSTANT, MAX_CONSTANTS);
|
||||
highPriorityMatches = [...highPriorityMatches, ...constantCompletionHints];
|
||||
|
||||
} else if (
|
||||
token.type === 'invalidchar' ||
|
||||
token.type === 'ws' ||
|
||||
(token.type === 'punctuation' && token.string === '{')
|
||||
) {
|
||||
// We're outside of a JSON key
|
||||
matchSegments(constantsToMatch, segment, TYPE_CONSTANT, MAX_CONSTANTS).forEach(m =>
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
highPriorityMatches.push({ ...m, text: '"' + m.text + '": ' }),
|
||||
);
|
||||
const constantCompletionHints = getCompletionHints(constantsToMatch, segment, TYPE_CONSTANT, MAX_CONSTANTS).map(hint => ({ ...hint, text: '"' + hint.text + '": ' }));
|
||||
|
||||
highPriorityMatches = [...highPriorityMatches, ...constantCompletionHints];
|
||||
}
|
||||
} else {
|
||||
// Otherwise match full segments
|
||||
matchSegments(constantsToMatch, nameSegmentFull, TYPE_CONSTANT, MAX_CONSTANTS).forEach(m =>
|
||||
highPriorityMatches.push(m),
|
||||
);
|
||||
const hints = getCompletionHints(constantsToMatch, nameSegmentFull, TYPE_CONSTANT, MAX_CONSTANTS);
|
||||
|
||||
highPriorityMatches = [...highPriorityMatches, ...hints];
|
||||
}
|
||||
}
|
||||
|
||||
// Match tags
|
||||
if (allowMatchingTags) {
|
||||
matchSegments(tagsToMatch, nameSegment, TYPE_TAG, MAX_TAGS).forEach(m =>
|
||||
lowPriorityMatches.push(m),
|
||||
);
|
||||
matchSegments(tagsToMatch, nameSegmentLong, TYPE_TAG, MAX_TAGS).forEach(m =>
|
||||
highPriorityMatches.push(m),
|
||||
);
|
||||
const lowPriorityTagHints = getCompletionHints(tagsToMatch, nameSegment, TYPE_TAG, MAX_TAGS);
|
||||
|
||||
lowPriorityMatches = [...lowPriorityMatches, ...lowPriorityTagHints];
|
||||
|
||||
const highPriorityTagHints = getCompletionHints(tagsToMatch, nameSegmentLong, TYPE_TAG, MAX_TAGS);
|
||||
|
||||
highPriorityMatches = [...highPriorityMatches, ...highPriorityTagHints];
|
||||
}
|
||||
|
||||
matchSegments(snippetsToMatch, nameSegment, TYPE_SNIPPET, MAX_SNIPPETS).forEach(m =>
|
||||
highPriorityMatches.push(m),
|
||||
);
|
||||
const snippetHints = getCompletionHints(snippetsToMatch, nameSegment, TYPE_SNIPPET, MAX_SNIPPETS);
|
||||
|
||||
highPriorityMatches = [...highPriorityMatches, ...snippetHints];
|
||||
|
||||
const matches = [...highPriorityMatches, ...lowPriorityMatches];
|
||||
// Autocomplete from longest matched segment
|
||||
const segment = highPriorityMatches.length ? nameSegmentLong : nameSegment;
|
||||
const uniqueMatches = matches.reduce(
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
(arr, v) => (arr.find(a => a.text === v.text) ? arr : [...arr, v]),
|
||||
[], // Default value
|
||||
[] as Hint[], // Default value
|
||||
);
|
||||
return {
|
||||
list: uniqueMatches,
|
||||
@@ -313,13 +318,13 @@ function hint(cm, options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the text in the editor when a hint is selected.
|
||||
* Replace the text in the EditorFromTextArea when a hint is selected.
|
||||
* This also makes sure there is whitespace surrounding it
|
||||
* @param cm
|
||||
* @param self
|
||||
* @param data
|
||||
*/
|
||||
async function replaceHintMatch(cm, _self, data) {
|
||||
async function replaceHintMatch(cm: CodeMirror.EditorFromTextArea, _self, data) {
|
||||
if (typeof data.text === 'function') {
|
||||
data.text = await data.text();
|
||||
}
|
||||
@@ -361,79 +366,92 @@ async function replaceHintMatch(cm, _self, data) {
|
||||
cm.replaceRange(`${prefix}${data.text}${suffix}`, from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match against a list of things
|
||||
* @param listOfThings - Can be list of strings or list of {name, value}
|
||||
* @param segment - segment to match against
|
||||
* @param type
|
||||
* @param limit
|
||||
* @returns {Array}
|
||||
*/
|
||||
function matchSegments(listOfThings, segment, type, limit = -1) {
|
||||
if (!Array.isArray(listOfThings)) {
|
||||
console.warn('Autocomplete received items in non-list form', listOfThings);
|
||||
return [];
|
||||
}
|
||||
// @TODO Simplify these types and make the interface stricter and move the transforms in the top level.
|
||||
interface CompletionItemKind {
|
||||
name: string;
|
||||
displayName?: string;
|
||||
displayValue?: string;
|
||||
value?: string | (() => PromiseLike<unknown>);
|
||||
type: typeof TYPE_CONSTANT | typeof TYPE_SNIPPET | typeof TYPE_VARIABLE | typeof TYPE_TAG;
|
||||
}
|
||||
|
||||
const matches = [];
|
||||
interface ConstantCompletionItem extends CompletionItemKind {
|
||||
type: typeof TYPE_CONSTANT;
|
||||
value: string;
|
||||
}
|
||||
|
||||
for (const t of listOfThings) {
|
||||
const name = typeof t === 'string' ? t : t.name;
|
||||
const value = typeof t === 'string' ? '' : t.value;
|
||||
const displayName = t.displayName || name;
|
||||
// Generate the value we'll fill it with
|
||||
let defaultFill;
|
||||
interface VariableCompletionItem extends CompletionItemKind {
|
||||
type: typeof TYPE_VARIABLE;
|
||||
value: string | (() => PromiseLike<unknown>);
|
||||
}
|
||||
|
||||
if (t.type === TYPE_CONSTANT) {
|
||||
defaultFill = t.value;
|
||||
} else if (t.type === TYPE_SNIPPET) {
|
||||
defaultFill = t.value;
|
||||
} else if (t.type === TYPE_VARIABLE) {
|
||||
// Variables fill with variable name, not value (eg. {{ foo }})
|
||||
// TODO: This is extremely confusing and does not make any sense so let's
|
||||
// refactor this to a single unified format for all types
|
||||
defaultFill = t.name;
|
||||
} else if (t.type === TYPE_TAG) {
|
||||
defaultFill = getDefaultFill(t.name, t.args);
|
||||
} else {
|
||||
throw new Error('Unidentified autocomplete type: ' + t.type);
|
||||
interface SnippetCompletionItem extends CompletionItemKind {
|
||||
type: typeof TYPE_SNIPPET;
|
||||
value: string | (() => PromiseLike<unknown>);
|
||||
}
|
||||
|
||||
interface TagCompletionItem extends NunjucksParsedTag, CompletionItemKind {
|
||||
type: typeof TYPE_TAG;
|
||||
}
|
||||
|
||||
type CompletionItem = ConstantCompletionItem | VariableCompletionItem | SnippetCompletionItem | TagCompletionItem;
|
||||
|
||||
function isConstantCompletionItem(item: CompletionItem): item is ConstantCompletionItem {
|
||||
return item.type === TYPE_CONSTANT;
|
||||
}
|
||||
|
||||
function isVariableCompletionItem(item: CompletionItem): item is VariableCompletionItem {
|
||||
return item.type === TYPE_VARIABLE;
|
||||
}
|
||||
|
||||
function isSnippetCompletionItem(item: CompletionItem): item is SnippetCompletionItem {
|
||||
return item.type === TYPE_SNIPPET;
|
||||
}
|
||||
|
||||
function isTagCompletionItem(item: CompletionItem): item is TagCompletionItem {
|
||||
return item.type === TYPE_TAG;
|
||||
}
|
||||
|
||||
function getCompletionHints(completionItems: CompletionItem[], segment: string, type: CompletionItem['type'], limit = -1): Hint[] {
|
||||
const matches: CodeMirror.Hint[] = [];
|
||||
|
||||
for (const item of completionItems) {
|
||||
const name = typeof item === 'string' ? item : item.name;
|
||||
const value = typeof item === 'string' ? '' : item.value ?? '';
|
||||
const displayName = item.displayName || name;
|
||||
let defaultFill = '';
|
||||
|
||||
if (isConstantCompletionItem(item)) {
|
||||
defaultFill = item.value;
|
||||
} else if (isVariableCompletionItem(item) || isSnippetCompletionItem(item)) {
|
||||
defaultFill = item.name;
|
||||
} else if (isTagCompletionItem(item)) {
|
||||
defaultFill = getDefaultFill(item.name, item.args);
|
||||
}
|
||||
|
||||
const matchSegment = segment.toLowerCase();
|
||||
const matchName = displayName.toLowerCase();
|
||||
const matchName = displayName.toLowerCase() || '';
|
||||
|
||||
// Throw away things that don't match
|
||||
if (!matchName.includes(matchSegment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let displayValue = t.displayValue;
|
||||
let displayValue = item.displayValue || '';
|
||||
|
||||
if (typeof displayValue !== 'string' && typeof value !== 'function') {
|
||||
if (typeof item.displayValue !== 'string' && typeof value !== 'function') {
|
||||
displayValue = JSON.stringify(value);
|
||||
}
|
||||
|
||||
matches.push({
|
||||
// Custom Insomnia keys
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
type,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
segment,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
displayValue,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
comment: value,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
displayValue: displayValue,
|
||||
comment: value.toString(),
|
||||
score: name.length,
|
||||
// In case we want to sort by this
|
||||
// CodeMirror
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
text: defaultFill,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
displayText: displayName,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
displayText: displayName || name,
|
||||
render: renderHintMatch,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
hint: replaceHintMatch,
|
||||
});
|
||||
}
|
||||
@@ -447,13 +465,8 @@ function matchSegments(listOfThings, segment, type, limit = -1) {
|
||||
|
||||
/**
|
||||
* Replace all occurrences of string
|
||||
* @param text
|
||||
* @param find
|
||||
* @param prefix
|
||||
* @param suffix
|
||||
* @returns string
|
||||
*/
|
||||
function replaceWithSurround(text, find, prefix, suffix) {
|
||||
function replaceWithSurround(text: string, find: string, prefix: string, suffix: string) {
|
||||
const escapedString = escapeRegex(find);
|
||||
const re = new RegExp(escapedString, 'gi');
|
||||
return text.replace(re, matched => prefix + matched + suffix);
|
||||
@@ -461,17 +474,14 @@ function replaceWithSurround(text, find, prefix, suffix) {
|
||||
|
||||
/**
|
||||
* Render the autocomplete list entry
|
||||
* @param li
|
||||
* @param self
|
||||
* @param data
|
||||
*/
|
||||
function renderHintMatch(li, _self, data) {
|
||||
function renderHintMatch(li: HTMLElement, _allHints: CodeMirror.Hints, hint: Hint) {
|
||||
// Bold the matched text
|
||||
const { displayText, segment, displayValue } = data;
|
||||
const markedName = replaceWithSurround(displayText, segment, '<strong>', '</strong>');
|
||||
const { char, title } = ICONS[data.type];
|
||||
|
||||
const { displayText, segment, type, displayValue } = hint;
|
||||
const markedName = replaceWithSurround(displayText || '', segment, '<strong>', '</strong>');
|
||||
const { char, title } = ICONS[type];
|
||||
let safeValue = '';
|
||||
|
||||
if (isNotNullOrUndefined<string>(displayValue)) {
|
||||
const escaped = escapeHTML(displayValue);
|
||||
safeValue = `
|
||||
@@ -481,7 +491,7 @@ function renderHintMatch(li, _self, data) {
|
||||
`;
|
||||
}
|
||||
|
||||
li.className += ` fancy-hint type--${data.type}`;
|
||||
li.className += ` fancy-hint type--${type}`;
|
||||
li.innerHTML = `
|
||||
<label class="label" title="${title}">${char}</label>
|
||||
<div class="name">${markedName}</div>
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
export function isNunjucksMode(
|
||||
mode: CodeMirror.ModeSpec<unknown>
|
||||
): mode is CodeMirror.ModeSpec<{ baseMode: 'string' }> {
|
||||
return 'baseMode' in mode;
|
||||
}
|
||||
|
||||
CodeMirror.defineMode('nunjucks', (config, parserConfig) => {
|
||||
const baseMode = CodeMirror.getMode(config, parserConfig.baseMode || 'text/plain');
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import React, { Fragment, PureComponent } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { HandleGetRenderContext, HandleRender } from '../../../common/render';
|
||||
import Input from '../base/debounced-input';
|
||||
import CodeEditor from './code-editor';
|
||||
import CodeEditor, { CodeEditorOnChange } from './code-editor';
|
||||
const MODE_INPUT = 'input';
|
||||
const MODE_EDITOR = 'editor';
|
||||
const TYPE_TEXT = 'text';
|
||||
@@ -16,20 +17,21 @@ interface Props {
|
||||
id?: string;
|
||||
type?: string;
|
||||
mode?: string;
|
||||
onBlur?: Function;
|
||||
onKeyDown?: Function;
|
||||
onFocus?: Function;
|
||||
onChange?: Function;
|
||||
onPaste?: Function;
|
||||
render?: Function;
|
||||
getRenderContext?: Function;
|
||||
onBlur?: (e: FocusEvent) => void;
|
||||
onKeyDown?: (e: KeyboardEvent, value?: any) => void;
|
||||
onFocus?: (e: FocusEvent) => void;
|
||||
onChange?: CodeEditorOnChange;
|
||||
onPaste?: (e: ClipboardEvent) => void;
|
||||
render?: HandleRender;
|
||||
getRenderContext?: HandleGetRenderContext;
|
||||
nunjucksPowerUserMode?: boolean;
|
||||
getAutocompleteConstants?: Function | null;
|
||||
getAutocompleteConstants?: () => string[] | PromiseLike<string[]>;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
forceEditor?: boolean;
|
||||
forceInput?: boolean;
|
||||
isVariableUncovered?: boolean;
|
||||
readOnly?: boolean;
|
||||
// TODO(TSCONVERSION) figure out why so many components pass this in yet it isn't used anywhere in this
|
||||
disabled?: boolean;
|
||||
}
|
||||
@@ -218,14 +220,14 @@ class OneLineEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_handleInputBlur() {
|
||||
_handleInputBlur(e: FocusEvent) {
|
||||
// Set focused state
|
||||
this._input?.removeAttribute('data-focused');
|
||||
|
||||
this.props.onBlur?.();
|
||||
this.props.onBlur?.(e);
|
||||
}
|
||||
|
||||
_handleEditorBlur() {
|
||||
_handleEditorBlur(e: FocusEvent) {
|
||||
// Editor was already removed from the DOM, so do nothing
|
||||
if (!this._editor) {
|
||||
return;
|
||||
@@ -244,9 +246,10 @@ class OneLineEditor extends PureComponent<Props, State> {
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
this.props.onBlur?.();
|
||||
this.props.onBlur?.(e);
|
||||
}
|
||||
|
||||
// @TODO Refactor this event handler. The way we search for a parent form node is not stable.
|
||||
_handleKeyDown(e) {
|
||||
// submit form if needed
|
||||
if (e.keyCode === 13) {
|
||||
@@ -382,14 +385,10 @@ class OneLineEditor extends PureComponent<Props, State> {
|
||||
onKeyDown={this._handleKeyDown}
|
||||
onFocus={this._handleEditorFocus}
|
||||
onMouseLeave={this._handleEditorMouseLeave}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
onChange={onChange}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
render={render}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
getRenderContext={getRenderContext}
|
||||
nunjucksPowerUserMode={nunjucksPowerUserMode}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
getAutocompleteConstants={getAutocompleteConstants}
|
||||
className={classnames('editor--single-line', className)}
|
||||
defaultValue={defaultValue}
|
||||
|
||||
@@ -193,7 +193,7 @@ class OAuth1Auth extends PureComponent<Props> {
|
||||
property: string,
|
||||
onChange: (...args: any[]) => any,
|
||||
help: string | null = null,
|
||||
handleAutocomplete: ((...args: any[]) => any) | null = null,
|
||||
handleAutocomplete?: ((...args: any[]) => any)
|
||||
) {
|
||||
const {
|
||||
handleRender,
|
||||
|
||||
@@ -320,7 +320,7 @@ class OAuth2Auth extends PureComponent<Props, State> {
|
||||
property: string,
|
||||
onChange: (...args: any[]) => any,
|
||||
help: string | null = null,
|
||||
handleAutocomplete: ((...args: any[]) => any) | null = null,
|
||||
handleAutocomplete?: (...args: any[]) => any,
|
||||
) {
|
||||
const {
|
||||
handleRender,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { EditorFromTextArea, TextMarker } from 'codemirror';
|
||||
import { EditorFromTextArea, LintOptions, ShowHintOptions, TextMarker } from 'codemirror';
|
||||
import { GraphQLInfoOptions } from 'codemirror-graphql/info';
|
||||
import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump';
|
||||
import electron, { OpenDialogOptions } from 'electron';
|
||||
import { readFileSync } from 'fs';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
import { DefinitionNode, DocumentNode, GraphQLNonNull, GraphQLSchema, NonNullTypeNode, OperationDefinitionNode } from 'graphql';
|
||||
import { parse, print, typeFromAST } from 'graphql';
|
||||
import Maybe from 'graphql/tsutils/Maybe';
|
||||
import { buildClientSchema, getIntrospectionQuery } from 'graphql/utilities';
|
||||
import { json as jsonPrettify } from 'insomnia-prettify';
|
||||
import React, { PureComponent } from 'react';
|
||||
@@ -13,7 +16,7 @@ import ReactDOM from 'react-dom';
|
||||
import { AUTOBIND_CFG, CONTENT_TYPE_JSON, DEBOUNCE_MILLIS } from '../../../../common/constants';
|
||||
import { database as db } from '../../../../common/database';
|
||||
import { markdownToHTML } from '../../../../common/markdown-to-html';
|
||||
import { jsonParseOr } from '../../../../common/misc';
|
||||
import { isNotNullOrUndefined, jsonParseOr } from '../../../../common/misc';
|
||||
import { HandleGetRenderContext, HandleRender } from '../../../../common/render';
|
||||
import * as models from '../../../../models/index';
|
||||
import type { Request } from '../../../../models/request';
|
||||
@@ -37,6 +40,10 @@ if (!explorerContainer) {
|
||||
throw new Error('Failed to find #graphql-explorer-container');
|
||||
}
|
||||
|
||||
function isOperationDefinition(def: DefinitionNode): def is OperationDefinitionNode {
|
||||
return def.kind === 'OperationDefinition';
|
||||
}
|
||||
|
||||
interface GraphQLBody {
|
||||
query: string;
|
||||
variables?: Record<string, any>;
|
||||
@@ -76,7 +83,7 @@ interface State {
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
class GraphQLEditor extends PureComponent<Props, State> {
|
||||
_disabledOperationMarkers: TextMarker[] = [];
|
||||
_documentAST: null | Record<string, any> = null;
|
||||
_documentAST: null | DocumentNode = null;
|
||||
_isMounted = false;
|
||||
_queryEditor: null | EditorFromTextArea = null;
|
||||
_schemaFetchTimeout: NodeJS.Timeout | null = null;
|
||||
@@ -86,13 +93,17 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
|
||||
const body = GraphQLEditor._stringToGraphQL(props.content);
|
||||
this._setDocumentAST(body.query);
|
||||
let automaticFetch;
|
||||
let automaticFetch = true;
|
||||
|
||||
try {
|
||||
// @ts-expect-error -- TSCONVERSION don't parse if the read item is not defined
|
||||
automaticFetch = JSON.parse(window.localStorage.getItem('graphql.automaticFetch'));
|
||||
const automaticFetchStringified = window.localStorage.getItem('graphql.automaticFetch');
|
||||
if (automaticFetchStringified) {
|
||||
automaticFetch = JSON.parse(automaticFetchStringified);
|
||||
}
|
||||
} catch (err) {
|
||||
automaticFetch = true;
|
||||
if (err instanceof Error) {
|
||||
console.warn('Could not parse value of graphql.automaticFetch from localStorage:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
@@ -163,12 +174,16 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
_handleClickReference(reference: ActiveReference, event: React.MouseEvent) {
|
||||
_handleClickReference(reference: Maybe<ActiveReference>, event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
explorerVisible: true,
|
||||
activeReference: reference,
|
||||
});
|
||||
|
||||
if (reference) {
|
||||
this.setState({
|
||||
explorerVisible: true,
|
||||
activeReference: reference,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_handleQueryFocus() {
|
||||
@@ -189,22 +204,25 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
const { _documentAST, _queryEditor } = this;
|
||||
|
||||
if (!_documentAST || !_queryEditor) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const disabledDefinitions = _documentAST.definitions.filter(d => {
|
||||
const name = d.name ? d.name.value : null;
|
||||
return d.kind === 'OperationDefinition' && name !== operationName;
|
||||
});
|
||||
|
||||
// Remove current query highlighting
|
||||
for (const textMarker of this._disabledOperationMarkers) {
|
||||
textMarker.clear();
|
||||
}
|
||||
|
||||
const disabledDefinitionLocations = _documentAST.definitions
|
||||
.filter(isOperationDefinition)
|
||||
.filter(d => {
|
||||
return d.name?.value !== operationName;
|
||||
})
|
||||
.map(definition => definition.loc)
|
||||
.filter(isNotNullOrUndefined);
|
||||
|
||||
// Add "Unhighlight" markers
|
||||
this._disabledOperationMarkers = disabledDefinitions.map(definition => {
|
||||
const { startToken, endToken } = definition.loc;
|
||||
const disabledOperationMarkers = disabledDefinitionLocations.map(location => {
|
||||
const { startToken, endToken } = location;
|
||||
const from = {
|
||||
line: startToken.line - 1,
|
||||
ch: startToken.column - 1,
|
||||
@@ -213,13 +231,13 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
line: endToken.line,
|
||||
ch: endToken.column - 1,
|
||||
};
|
||||
// @ts-expect-error - doc doesn't exist, use getDoc()
|
||||
return _queryEditor.doc.markText(from, to, {
|
||||
|
||||
return _queryEditor.getDoc().markText(from, to, {
|
||||
className: 'cm-gql-disabled',
|
||||
});
|
||||
});
|
||||
|
||||
return null;
|
||||
this._disabledOperationMarkers = disabledOperationMarkers;
|
||||
}
|
||||
|
||||
_handleViewResponse() {
|
||||
@@ -371,27 +389,26 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
_buildVariableTypes(
|
||||
schema: Record<string, any> | null,
|
||||
): Record<string, Record<string, any>> | null {
|
||||
schema: GraphQLSchema | null,
|
||||
): Record<string, GraphQLNonNull<any>> | null {
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const definitions = this._documentAST ? this._documentAST.definitions : [];
|
||||
const variableToType = {};
|
||||
const variableToType: Record<string, GraphQLNonNull<any>> = {};
|
||||
|
||||
for (const { kind, variableDefinitions } of definitions) {
|
||||
if (kind !== 'OperationDefinition') {
|
||||
for (const definition of definitions) {
|
||||
if (!isOperationDefinition(definition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!variableDefinitions) {
|
||||
if (!definition.variableDefinitions) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const { variable, type } of variableDefinitions) {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
const inputType = typeFromAST(schema, type);
|
||||
for (const { variable, type } of definition.variableDefinitions) {
|
||||
const inputType = typeFromAST(schema, type as NonNullTypeNode);
|
||||
|
||||
if (!inputType) {
|
||||
continue;
|
||||
@@ -432,8 +449,8 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
this.setState({
|
||||
automaticFetch,
|
||||
});
|
||||
// @ts-expect-error -- TSCONVERSION convert boolean to string when setting
|
||||
window.localStorage.setItem('graphql.automaticFetch', automaticFetch);
|
||||
|
||||
window.localStorage.setItem('graphql.automaticFetch', automaticFetch.toString());
|
||||
}
|
||||
|
||||
_handlePrettify() {
|
||||
@@ -651,17 +668,48 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
const variableTypes = this._buildVariableTypes(schema);
|
||||
|
||||
// Create portal for GraphQL Explorer
|
||||
const graphQLExplorerPortal = ReactDOM.createPortal(
|
||||
<GraphqlExplorer
|
||||
schema={schema}
|
||||
key={schemaLastFetchTime}
|
||||
visible={explorerVisible}
|
||||
reference={activeReference}
|
||||
handleClose={this._handleCloseExplorer}
|
||||
/>,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
explorerContainer,
|
||||
);
|
||||
let graphQLExplorerPortal: React.ReactPortal | null = null;
|
||||
if (explorerContainer) {
|
||||
graphQLExplorerPortal = ReactDOM.createPortal(
|
||||
<GraphqlExplorer
|
||||
schema={schema}
|
||||
key={schemaLastFetchTime}
|
||||
visible={explorerVisible}
|
||||
reference={activeReference}
|
||||
handleClose={this._handleCloseExplorer}
|
||||
/>,
|
||||
explorerContainer
|
||||
);
|
||||
}
|
||||
|
||||
let graphqlOptions: {
|
||||
hintOptions: ShowHintOptions;
|
||||
infoOptions: GraphQLInfoOptions;
|
||||
jumpOptions: ModifiedGraphQLJumpOptions;
|
||||
lintOptions: LintOptions;
|
||||
} | undefined;
|
||||
|
||||
if (schema) {
|
||||
graphqlOptions = {
|
||||
hintOptions: {
|
||||
schema,
|
||||
completeSingle: false,
|
||||
},
|
||||
infoOptions: {
|
||||
schema,
|
||||
renderDescription: GraphQLEditor.renderMarkdown,
|
||||
onClick: this._handleClickReference,
|
||||
},
|
||||
jumpOptions: {
|
||||
schema,
|
||||
onClick: this._handleClickReference,
|
||||
},
|
||||
lintOptions: {
|
||||
schema,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="graphql-editor">
|
||||
<Dropdown right className="graphql-editor__schema-dropdown margin-bottom-xs">
|
||||
@@ -701,26 +749,6 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
dynamicHeight
|
||||
manualPrettify
|
||||
uniquenessKey={uniquenessKey ? uniquenessKey + '::query' : undefined}
|
||||
hintOptions={{
|
||||
schema: schema || null,
|
||||
completeSingle: false,
|
||||
}}
|
||||
infoOptions={{
|
||||
schema: schema || null,
|
||||
renderDescription: GraphQLEditor.renderMarkdown,
|
||||
onClick: this._handleClickReference,
|
||||
}}
|
||||
jumpOptions={{
|
||||
schema: schema || null,
|
||||
onClick: this._handleClickReference,
|
||||
}}
|
||||
lintOptions={
|
||||
schema
|
||||
? {
|
||||
schema,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
fontSize={settings.editorFontSize}
|
||||
indentSize={settings.editorIndentSize}
|
||||
keyMap={settings.editorKeyMap}
|
||||
@@ -733,6 +761,7 @@ class GraphQLEditor extends PureComponent<Props, State> {
|
||||
mode="graphql"
|
||||
lineWrapping={settings.editorLineWrapping}
|
||||
placeholder=""
|
||||
{...graphqlOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="graphql-editor__schema-error">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { GraphQLSchema, GraphQLType } from 'graphql';
|
||||
import { SchemaReference } from 'codemirror-graphql/utils/SchemaReference';
|
||||
import { GraphQLField, GraphQLNamedType, GraphQLSchema, GraphQLType, isNamedType } from 'graphql';
|
||||
import { GraphQLEnumType } from 'graphql';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { createRef } from 'react';
|
||||
@@ -16,6 +17,46 @@ import GraphQLExplorerSearchResults from './graph-ql-explorer-search-results';
|
||||
import GraphQLExplorerType from './graph-ql-explorer-type';
|
||||
import { ActiveReference, GraphQLFieldWithParentName } from './graph-ql-types';
|
||||
|
||||
function getReferenceInfo(reference: SchemaReference) {
|
||||
let field: GraphQLField<any, any, { [key: string]: any }> | undefined;
|
||||
if ('field' in reference) {
|
||||
field = reference.field;
|
||||
}
|
||||
|
||||
let type: GraphQLType | undefined;
|
||||
if ('type' in reference) {
|
||||
type = reference.type;
|
||||
}
|
||||
|
||||
return { type, field };
|
||||
}
|
||||
|
||||
function isSameFieldAndType(
|
||||
currentType?: GraphQLType,
|
||||
type?: GraphQLType,
|
||||
currentField?: GraphQLFieldWithParentName,
|
||||
field?: GraphQLField<any, any, { [key: string]: any }>
|
||||
) {
|
||||
// @TODO Simplify this function since it's hard to follow along
|
||||
const compare = <
|
||||
T extends GraphQLNamedType | GraphQLFieldWithParentName,
|
||||
U extends GraphQLNamedType | GraphQLFieldWithParentName
|
||||
>(a?: T, b?: U) => (!a && !b) || (a && b && a.name === b.name);
|
||||
|
||||
if (!isNamedType(currentType)) {
|
||||
currentType = undefined;
|
||||
}
|
||||
|
||||
if (!isNamedType(type)) {
|
||||
type = undefined;
|
||||
}
|
||||
|
||||
const isSameType = compare(currentType, type);
|
||||
|
||||
const isSameField = compare(currentField, field);
|
||||
return isSameType && isSameField;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
handleClose: () => void;
|
||||
schema: GraphQLSchema | null;
|
||||
@@ -24,8 +65,8 @@ interface Props {
|
||||
}
|
||||
|
||||
interface HistoryItem {
|
||||
currentType: null | GraphQLType;
|
||||
currentField: null | GraphQLFieldWithParentName;
|
||||
currentType?: GraphQLType;
|
||||
currentField?: GraphQLFieldWithParentName;
|
||||
}
|
||||
|
||||
interface State extends HistoryItem {
|
||||
@@ -39,8 +80,8 @@ const SEARCH_UPDATE_DELAY_IN_MS = 300;
|
||||
class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
history: [],
|
||||
currentType: null,
|
||||
currentField: null,
|
||||
currentType: undefined,
|
||||
currentField: undefined,
|
||||
filter: '',
|
||||
};
|
||||
|
||||
@@ -62,8 +103,8 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
|
||||
_navigateToSchema() {
|
||||
this.setState({
|
||||
currentType: null,
|
||||
currentField: null,
|
||||
currentType: undefined,
|
||||
currentField: undefined,
|
||||
history: this._addToHistory(),
|
||||
});
|
||||
}
|
||||
@@ -71,7 +112,7 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
_handleNavigateType(type: GraphQLType) {
|
||||
this.setState({
|
||||
currentType: type,
|
||||
currentField: null,
|
||||
currentField: undefined,
|
||||
history: this._addToHistory(),
|
||||
});
|
||||
}
|
||||
@@ -89,8 +130,8 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
const last = history[history.length - 1] || null;
|
||||
return {
|
||||
history: history.slice(0, history.length - 1),
|
||||
currentType: last ? last.currentType : null,
|
||||
currentField: last ? last.currentField : null,
|
||||
currentType: last ? last.currentType : undefined,
|
||||
currentField: last ? last.currentField : undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -114,21 +155,16 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (!nextProps.reference) {
|
||||
const { reference } = nextProps;
|
||||
|
||||
if (!reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, field } = nextProps.reference;
|
||||
const { currentType, currentField } = this.state;
|
||||
const { currentField, currentType } = this.state;
|
||||
const { type, field } = getReferenceInfo(reference);
|
||||
|
||||
const compare = <T extends { name: string } | null, U extends { name: string } | null>(a: T, b: U) => (!a && !b) || (a && b && a.name === b.name);
|
||||
|
||||
// @ts-expect-error -- needs generic for `name`
|
||||
const sameType = compare(currentType, type);
|
||||
const sameField = compare(currentField, field);
|
||||
const nothingChanged = sameType && sameField;
|
||||
|
||||
if (nothingChanged) {
|
||||
if (isSameFieldAndType(currentType, type, currentField, field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,8 +172,8 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
|
||||
this.setState({
|
||||
history,
|
||||
currentType: type || null,
|
||||
currentField: field || null,
|
||||
currentType: type,
|
||||
currentField: field,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,8 +204,11 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
if (lastField) {
|
||||
name = lastField.name || 'Unknown';
|
||||
} else if (lastType) {
|
||||
// @ts-expect-error -- needs generic for `name`
|
||||
name = lastType.name || 'Unknown';
|
||||
if (isNamedType(lastType)) {
|
||||
name = lastType.name;
|
||||
} else {
|
||||
name = 'Unknown';
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -271,8 +310,8 @@ class GraphQLExplorer extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
const fieldName = currentField ? currentField.name : null;
|
||||
// @ts-expect-error -- needs generic for `name`
|
||||
const typeName = currentType ? currentType.name : null;
|
||||
|
||||
const typeName = isNamedType(currentType) ? currentType.name : null;
|
||||
const schemaName = schema ? 'Schema' : null;
|
||||
return (
|
||||
<KeydownBinder onKeydown={this._handleKeydown}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLArgument, GraphQLField, GraphQLType } from 'graphql';
|
||||
import { SchemaReference } from 'codemirror-graphql/utils/SchemaReference';
|
||||
import { GraphQLField } from 'graphql';
|
||||
|
||||
type GraphQLFieldAny = GraphQLField<any, any>;
|
||||
|
||||
@@ -11,8 +12,4 @@ export interface GraphQLFieldWithParentName extends GraphQLFieldWithOptionalArgs
|
||||
parentName?: string;
|
||||
}
|
||||
|
||||
export interface ActiveReference {
|
||||
type: GraphQLType | null;
|
||||
argument: GraphQLArgument | null;
|
||||
field: GraphQLFieldWithOptionalArgs | null;
|
||||
}
|
||||
export type ActiveReference = SchemaReference;
|
||||
@@ -212,7 +212,7 @@ class KeyValueEditorRow extends PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_handleBlurDescription(e) {
|
||||
_handleBlurDescription(e: FocusEvent) {
|
||||
if (this.props.onBlurDescription) {
|
||||
this.props.onBlurDescription(this.props.pair, e);
|
||||
}
|
||||
@@ -289,7 +289,6 @@ class KeyValueEditorRow extends PureComponent<Props, State> {
|
||||
>
|
||||
<OneLineEditor
|
||||
ref={this._setDescriptionInputRef}
|
||||
// @ts-expect-error -- TSCONVERSION very strange that one of the `OneLineEditor`s in this file _doesn't_ error with this...
|
||||
readOnly={readOnly}
|
||||
forceInput={forceInput}
|
||||
placeholder={descriptionPlaceholder || 'Description'}
|
||||
@@ -347,7 +346,6 @@ class KeyValueEditorRow extends PureComponent<Props, State> {
|
||||
return (
|
||||
<OneLineEditor
|
||||
ref={ref => { this._valueInput = ref; }}
|
||||
// @ts-expect-error -- TSCONVERSION very strange that one of the `OneLineEditor`s in this file _doesn't_ error with this...
|
||||
readOnly={readOnly}
|
||||
forceInput={forceInput}
|
||||
type={valueInputType || 'text'}
|
||||
@@ -492,7 +490,6 @@ class KeyValueEditorRow extends PureComponent<Props, State> {
|
||||
isVariableUncovered={isVariableUncovered}
|
||||
getAutocompleteConstants={this._handleAutocompleteNames}
|
||||
forceInput={forceInput}
|
||||
// @ts-expect-error -- TSCONVERSION very strange that one of the `OneLineEditor`s in this file _doesn't_ error with this...
|
||||
readOnly={readOnly}
|
||||
onBlur={this._handleBlurName}
|
||||
onChange={this._handleNameChange}
|
||||
|
||||
@@ -16,8 +16,8 @@ interface Props {
|
||||
indentSize: number;
|
||||
keyMap: string;
|
||||
lineWrapping: boolean;
|
||||
handleRender: HandleRender;
|
||||
handleGetRenderContext: HandleGetRenderContext;
|
||||
handleRender?: HandleRender;
|
||||
handleGetRenderContext?: HandleGetRenderContext;
|
||||
nunjucksPowerUserMode: boolean;
|
||||
isVariableUncovered: boolean;
|
||||
placeholder?: string;
|
||||
|
||||
@@ -167,10 +167,8 @@ class CodePromptModal extends PureComponent<Props, State> {
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
onChange={this._handleChange}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
|
||||
handleGetRenderContext={enableRender ? handleGetRenderContext : null}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
|
||||
handleRender={enableRender ? handleRender : null}
|
||||
handleGetRenderContext={enableRender ? handleGetRenderContext : undefined}
|
||||
handleRender={enableRender ? handleRender : undefined}
|
||||
mode={mode}
|
||||
keyMap={editorKeyMap}
|
||||
indentSize={editorIndentSize}
|
||||
@@ -192,10 +190,8 @@ class CodePromptModal extends PureComponent<Props, State> {
|
||||
onChange={this._handleChange}
|
||||
nunjucksPowerUserMode={nunjucksPowerUserMode}
|
||||
isVariableUncovered={isVariableUncovered}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
|
||||
getRenderContext={enableRender ? handleGetRenderContext : null}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
|
||||
render={enableRender ? handleRender : null}
|
||||
getRenderContext={handleGetRenderContext}
|
||||
render={handleRender}
|
||||
mode={mode}
|
||||
keyMap={editorKeyMap}
|
||||
indentSize={editorIndentSize}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import HTTPSnippet, { availableTargets } from 'httpsnippet';
|
||||
import HTTPSnippet, { HTTPSnippetClient, HTTPSnippetTarget } from 'httpsnippet';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { exportHarRequest } from '../../../common/har';
|
||||
import { Request } from '../../../models/request';
|
||||
import { CopyButton } from '../base/copy-button';
|
||||
import { Dropdown, DropdownButton, DropdownItem } from '../base/dropdown';
|
||||
import Link from '../base/link';
|
||||
@@ -13,8 +14,8 @@ import ModalFooter from '../base/modal-footer';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
import CodeEditor from '../codemirror/code-editor';
|
||||
|
||||
const DEFAULT_TARGET = availableTargets().find(t => t.key === 'shell');
|
||||
const DEFAULT_CLIENT = DEFAULT_TARGET.clients.find(t => t.key === 'curl');
|
||||
const DEFAULT_TARGET = HTTPSnippet.availableTargets().find(t => t.key === 'shell') as HTTPSnippetTarget;
|
||||
const DEFAULT_CLIENT = DEFAULT_TARGET?.clients.find(t => t.key === 'curl') as HTTPSnippetClient;
|
||||
const MODE_MAP = {
|
||||
c: 'clike',
|
||||
java: 'clike',
|
||||
@@ -36,32 +37,30 @@ interface Props {
|
||||
|
||||
interface State {
|
||||
cmd: string;
|
||||
request: null;
|
||||
target: string;
|
||||
client: string;
|
||||
request?: Request;
|
||||
target: HTTPSnippetTarget;
|
||||
client: HTTPSnippetClient;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
let client;
|
||||
let target;
|
||||
let target: HTTPSnippetTarget | undefined;
|
||||
let client: HTTPSnippetClient | undefined;
|
||||
|
||||
// Load preferences from localStorage
|
||||
try {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
target = JSON.parse(window.localStorage.getItem('insomnia::generateCode::target'));
|
||||
target = JSON.parse(window.localStorage.getItem('insomnia::generateCode::target') || '') as HTTPSnippetTarget;
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
client = JSON.parse(window.localStorage.getItem('insomnia::generateCode::client'));
|
||||
client = JSON.parse(window.localStorage.getItem('insomnia::generateCode::client') || '') as HTTPSnippetClient;
|
||||
} catch (e) {}
|
||||
|
||||
this.state = {
|
||||
cmd: '',
|
||||
request: null,
|
||||
request: undefined,
|
||||
target: target || DEFAULT_TARGET,
|
||||
client: client || DEFAULT_CLIENT,
|
||||
};
|
||||
@@ -85,13 +84,15 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
_handleClientChange(client) {
|
||||
const { target, request } = this.state;
|
||||
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
this._generateCode(request, target, client);
|
||||
}
|
||||
|
||||
_handleTargetChange(target) {
|
||||
const { target: currentTarget } = this.state;
|
||||
const { target: currentTarget, request } = this.state;
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
if (currentTarget.key === target.key) {
|
||||
// No change
|
||||
return;
|
||||
@@ -99,16 +100,21 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
|
||||
const client = target.clients.find(c => c.key === target.default);
|
||||
|
||||
this._generateCode(this.state.request, target, client);
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
this._generateCode(request, target, client);
|
||||
}
|
||||
|
||||
async _generateCode(request, target, client) {
|
||||
async _generateCode(request: Request, target: HTTPSnippetTarget, client: HTTPSnippetClient) {
|
||||
// Some clients need a content-length for the request to succeed
|
||||
const addContentLength = (TO_ADD_CONTENT_LENGTH[target.key] || []).find(c => c === client.key);
|
||||
const { environmentId } = this.props;
|
||||
const har = await exportHarRequest(request._id, environmentId, addContentLength);
|
||||
// @TODO Should we throw instead?
|
||||
if (!har) return;
|
||||
const snippet = new HTTPSnippet(har);
|
||||
const cmd = snippet.convert(target.key, client.key);
|
||||
const cmd = snippet.convert(target.key, client.key) || '';
|
||||
this.setState({
|
||||
request,
|
||||
cmd,
|
||||
@@ -120,7 +126,7 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
window.localStorage.setItem('insomnia::generateCode::target', JSON.stringify(target));
|
||||
}
|
||||
|
||||
show(request) {
|
||||
show(request: Request) {
|
||||
const { client, target } = this.state;
|
||||
|
||||
this._generateCode(request, target, client);
|
||||
@@ -131,13 +137,11 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
render() {
|
||||
const { cmd, target, client } = this.state;
|
||||
const { editorFontSize, editorIndentSize, editorKeyMap } = this.props;
|
||||
const targets = availableTargets();
|
||||
const targets = HTTPSnippet.availableTargets();
|
||||
// NOTE: Just some extra precautions in case the target is messed up
|
||||
let clients = [];
|
||||
let clients: HTTPSnippetClient[] = [];
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
if (target && Array.isArray(target.clients)) {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
clients = target.clients;
|
||||
}
|
||||
|
||||
@@ -156,7 +160,6 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
<Dropdown outline>
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
{
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
target ? target.title : 'n/a'
|
||||
}
|
||||
<i className="fa fa-caret-down" />
|
||||
@@ -170,24 +173,16 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
|
||||
<Dropdown outline>
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
{
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
client ? client.title : 'n/a'
|
||||
}
|
||||
{client ? client.title : 'n/a'}
|
||||
<i className="fa fa-caret-down" />
|
||||
</DropdownButton>
|
||||
{clients.map(client => (
|
||||
<DropdownItem
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
key={client.key}
|
||||
onClick={this._handleClientChange}
|
||||
value={client}
|
||||
>
|
||||
{
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
client.title
|
||||
}
|
||||
{client.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
@@ -199,7 +194,6 @@ class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
placeholder="Generating code snippet..."
|
||||
className="border-top"
|
||||
key={Date.now()}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
mode={MODE_MAP[target.key] || target.key}
|
||||
ref={this._setEditorRef}
|
||||
fontSize={editorFontSize}
|
||||
|
||||
@@ -112,8 +112,6 @@ class GenerateConfigModal extends PureComponent<Props, State> {
|
||||
lineWrapping={settings.editorLineWrapping}
|
||||
mode={config.mimeType}
|
||||
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be genuine
|
||||
onChange={this._handleChange}
|
||||
readOnly
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
@@ -22,7 +22,7 @@ import KeydownBinder from './keydown-binder';
|
||||
import { showPrompt } from './modals/index';
|
||||
|
||||
interface Props {
|
||||
handleAutocompleteUrls: Function;
|
||||
handleAutocompleteUrls: () => Promise<string[]>;
|
||||
handleGenerateCode: Function;
|
||||
handleGetRenderContext: HandleGetRenderContext;
|
||||
handleImport: Function;
|
||||
@@ -58,7 +58,7 @@ class RequestUrlBar extends PureComponent<Props, State> {
|
||||
currentTimeout: null,
|
||||
};
|
||||
|
||||
_lastPastedText: string | null = null;
|
||||
_lastPastedText?: string;
|
||||
|
||||
_setDropdownRef(n: Dropdown) {
|
||||
this._dropdown = n;
|
||||
@@ -102,7 +102,7 @@ class RequestUrlBar extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
// Reset pasted text cache
|
||||
this._lastPastedText = null;
|
||||
this._lastPastedText = undefined;
|
||||
// Attempt to import the pasted text
|
||||
const importedRequest = await this.props.handleImport(pastedText);
|
||||
|
||||
@@ -113,9 +113,9 @@ class RequestUrlBar extends PureComponent<Props, State> {
|
||||
}, DEBOUNCE_MILLIS);
|
||||
}
|
||||
|
||||
_handleUrlPaste(e: React.ClipboardEvent<HTMLInputElement>) {
|
||||
_handleUrlPaste(e: ClipboardEvent) {
|
||||
// NOTE: We're not actually doing the import here to avoid races with onChange
|
||||
this._lastPastedText = e.clipboardData.getData('text/plain');
|
||||
this._lastPastedText = e.clipboardData?.getData('text/plain');
|
||||
}
|
||||
|
||||
_handleGenerateCode() {
|
||||
|
||||
@@ -437,7 +437,6 @@ class WrapperUnitTest extends PureComponent<Props, State> {
|
||||
lintOptions={WrapperUnitTest.lintOptions}
|
||||
onChange={this._handleUnitTestCodeChange.bind(this, unitTest)}
|
||||
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
isVariableUncovered={settings.isVariableUncovered}
|
||||
mode="javascript"
|
||||
lineWrapping={settings.editorLineWrapping}
|
||||
@@ -473,8 +472,7 @@ class WrapperUnitTest extends PureComponent<Props, State> {
|
||||
bg="surprise"
|
||||
onClick={this._handleRunTests}
|
||||
size="default"
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
disabled={testsRunning}
|
||||
disabled={Boolean(testsRunning)}
|
||||
>
|
||||
{testsRunning ? 'Running... ' : 'Run Tests'}
|
||||
<i className="fa fa-play space-left" />
|
||||
@@ -519,8 +517,7 @@ class WrapperUnitTest extends PureComponent<Props, State> {
|
||||
<DropdownItem
|
||||
stayOpenAfterClick
|
||||
onClick={this._handleRunTests}
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
disabled={testsRunning}
|
||||
disabled={Boolean(testsRunning)}
|
||||
>
|
||||
{testsRunning ? 'Running... ' : 'Run Tests'}
|
||||
</DropdownItem>
|
||||
|
||||
@@ -557,7 +557,11 @@ class App extends PureComponent<AppProps, State> {
|
||||
const har = await exportHarRequest(request._id, environmentId);
|
||||
const snippet = new HTTPSnippet(har);
|
||||
const cmd = snippet.convert('shell', 'curl');
|
||||
clipboard.writeText(cmd);
|
||||
|
||||
// @TODO Should we throw otherwise? What should happen if we cannot find cmd?
|
||||
if (cmd) {
|
||||
clipboard.writeText(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static async _updateRequestGroupMetaByParentId(requestGroupId, patch) {
|
||||
|
||||
Generated
+24
-24
@@ -2225,9 +2225,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/codemirror": {
|
||||
"version": "5.60.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz",
|
||||
"integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==",
|
||||
"version": "5.60.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz",
|
||||
"integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/tern": "*"
|
||||
@@ -2810,9 +2810,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/tern": {
|
||||
"version": "0.23.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.3.tgz",
|
||||
"integrity": "sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w==",
|
||||
"version": "0.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
|
||||
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*"
|
||||
@@ -5696,14 +5696,14 @@
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz",
|
||||
"integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ=="
|
||||
"version": "5.62.3",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
|
||||
"integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
|
||||
},
|
||||
"codemirror-graphql": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.1.tgz",
|
||||
"integrity": "sha512-5ttMpv2kMn99Rmf2aZ5P6/hMd3y11cN8LP/x5MUeF0ipcalZA/GE/OxxXkhV0YJE/uW5QIcPyZDkvtSsGZa23A==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.2.tgz",
|
||||
"integrity": "sha512-D4+BdYa6iQnDlio4mBk1Yap5ROCqEWapSFLkiKGatx/I0dF6euzdwd0um3Ndudw6rFQbNuT7hpcH8tnBO6VOfQ==",
|
||||
"requires": {
|
||||
"graphql-language-service-interface": "^2.8.2",
|
||||
"graphql-language-service-parser": "^1.9.0"
|
||||
@@ -9728,9 +9728,9 @@
|
||||
}
|
||||
},
|
||||
"graphql-language-service-interface": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.3.tgz",
|
||||
"integrity": "sha512-Gh4Q3dlCT1MrZGO0eaz7v31gkp8fh+ig94YH/A+1Th2q+k3RsRqfSJm5tKZ8TJ4rSADZ/dj+hzOpWCGzLyCiHQ==",
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.4.tgz",
|
||||
"integrity": "sha512-myW8z7HOZkYfhYGKDc0URFkTZChp41Po890W92zuBIhGccckgtiWSJGXaLX+r9QAwVIeZhKaNgEacsyvQb1f/g==",
|
||||
"requires": {
|
||||
"graphql-language-service-parser": "^1.9.0",
|
||||
"graphql-language-service-types": "^1.8.0",
|
||||
@@ -9739,22 +9739,22 @@
|
||||
}
|
||||
},
|
||||
"graphql-language-service-parser": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.1.tgz",
|
||||
"integrity": "sha512-GySsDrYxzxu6r1vF282xXDR2KlfVL5aOW7pgc75fF3UFiuqGm/SeoIljNM0mLpRl5KSxo1HNOxhkWoFBoy/h2w==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.2.tgz",
|
||||
"integrity": "sha512-3txms73cJsXDfJQdR5hI83N2rpTuq9FD6aijdrXAeSuI5B60g32DxjelUkt4Ge+2BvBEDLn5ppXlpVYDC9UQHQ==",
|
||||
"requires": {
|
||||
"graphql-language-service-types": "^1.8.0"
|
||||
}
|
||||
},
|
||||
"graphql-language-service-types": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.1.tgz",
|
||||
"integrity": "sha512-IpYS0mEHEmRsFlq+loWCpSYYYizAID7Alri6GoFN1QqUdux+8rp1Tkp2NGsGDpDmm3Dbz5ojmJWzNWQGpuwveA=="
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.2.tgz",
|
||||
"integrity": "sha512-Sj07RHnMwAhEvAt7Jdt1l/x56ZpoNh+V6g+T58CF6GiYqI5l4vXqqRB4d4xHDcNQX98GpJfnf3o8BqPgP3C5Sw=="
|
||||
},
|
||||
"graphql-language-service-utils": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.2.tgz",
|
||||
"integrity": "sha512-hXGd4ARhyD7WTmTwuYmCYo6BcY8FtTp+1JHLaUG0Q63k0NpZTuFuRZ+N7TSP9mcRb7labeozs3DYgaqStsDe1A==",
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.3.tgz",
|
||||
"integrity": "sha512-ydevEZ0AgzEKQF3hiCbLXuS0o7189Ww/T30WtCKCLaRHDYk9Yyb2PZWdhSTWLxYZTaX2TccV6NtFWvzIC7UP3g==",
|
||||
"requires": {
|
||||
"graphql-language-service-types": "^1.8.0",
|
||||
"nullthrows": "^1.0.0"
|
||||
|
||||
@@ -83,8 +83,8 @@
|
||||
"class-autobind-decorator": "^3.0.1",
|
||||
"classnames": "^2.3.1",
|
||||
"clone": "^2.1.0",
|
||||
"codemirror": "^5.62.0",
|
||||
"codemirror-graphql": "^1.0.1",
|
||||
"codemirror": "^5.62.3",
|
||||
"codemirror-graphql": "^1.0.2",
|
||||
"color": "^3.1.2",
|
||||
"deep-equal": "^1.0.1",
|
||||
"electron-context-menu": "^2.2.0",
|
||||
@@ -180,7 +180,7 @@
|
||||
"@types/analytics-node": "^3.1.4",
|
||||
"@types/aws4": "^1.5.1",
|
||||
"@types/clone": "^2.1.0",
|
||||
"@types/codemirror": "^5.60.0",
|
||||
"@types/codemirror": "^5.60.2",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/concurrently": "^6.0.1",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
|
||||
Reference in new issue
Block a user