Improve codemirror typings (#3993)

Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
This commit is contained in:
James GatzandDimitri Mitropoulos authored and GitHub committed 2021-09-08 15:38:07 +00:00
1 parent cf2387bbcd
commit 381cf74a19
24 files changed
+612 -421

No files matched your search

+1
View File
@@ -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 }],
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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> {
&nbsp;&nbsp;
<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) {
+24 -24
View File
@@ -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"
+3 -3
View File
@@ -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",