@bestcodetools/graphql-playground 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +8 -0
- package/.github/workflows/docker-publish.yml +48 -0
- package/.github/workflows/npm-publish.yml +22 -0
- package/Dockerfile +28 -0
- package/README.md +110 -0
- package/jest.config.js +8 -0
- package/package.json +49 -0
- package/public/components/config-editor.html +38 -0
- package/public/components/field-args.html +16 -0
- package/public/components/field-type.html +27 -0
- package/public/components/headers-editor.html +3 -0
- package/public/components/query-editor.html +3 -0
- package/public/components/response-viewer.html +3 -0
- package/public/components/schema-viewer.html +74 -0
- package/public/components/variables-editor.html +3 -0
- package/public/index.html +114 -0
- package/public/js/components/config-editor.js +122 -0
- package/public/js/components/headers-editor.js +562 -0
- package/public/js/components/query-editor.js +1896 -0
- package/public/js/components/response-viewer.js +201 -0
- package/public/js/components/schema-viewer.js +644 -0
- package/public/js/components/variables-editor.js +1258 -0
- package/public/js/main.js +1016 -0
- package/public/styles/main.css +1313 -0
- package/public/styles/main.css.map +1 -0
- package/public/styles/main.scss +1319 -0
- package/src/middleware.ts +36 -0
- package/src/standalone.ts +14 -0
- package/tests/standalone.test.ts +74 -0
- package/tsconfig.json +71 -0
|
@@ -0,0 +1,1896 @@
|
|
|
1
|
+
((app) => {
|
|
2
|
+
const TEMPLATE_VERSION = '20260409p';
|
|
3
|
+
|
|
4
|
+
app.component('queryEditor', {
|
|
5
|
+
templateUrl: `components/query-editor.html?v=${TEMPLATE_VERSION}`,
|
|
6
|
+
bindings: {
|
|
7
|
+
query: '=',
|
|
8
|
+
schema: '<',
|
|
9
|
+
api: '=?',
|
|
10
|
+
onChange: '&?'
|
|
11
|
+
},
|
|
12
|
+
controller: ['$element', function ($element) {
|
|
13
|
+
const $ctrl = this;
|
|
14
|
+
let editor = null;
|
|
15
|
+
let tooltipElement = null;
|
|
16
|
+
let hideTooltipTimeout = null;
|
|
17
|
+
let resizeHandler = null;
|
|
18
|
+
const graphqlKeywords = [
|
|
19
|
+
'query',
|
|
20
|
+
'mutation',
|
|
21
|
+
'subscription',
|
|
22
|
+
'fragment',
|
|
23
|
+
'on',
|
|
24
|
+
'schema',
|
|
25
|
+
'type',
|
|
26
|
+
'interface',
|
|
27
|
+
'union',
|
|
28
|
+
'enum',
|
|
29
|
+
'input',
|
|
30
|
+
'scalar',
|
|
31
|
+
'directive',
|
|
32
|
+
'implements',
|
|
33
|
+
'extend',
|
|
34
|
+
'true',
|
|
35
|
+
'false',
|
|
36
|
+
'null'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function ensureGraphqlMode() {
|
|
40
|
+
if (typeof window.CodeMirror === 'undefined') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (window.CodeMirror.modes && window.CodeMirror.modes['graphql-playground']) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const operationKeywordPattern = /^(?:query|mutation|subscription|fragment|on)\b/;
|
|
49
|
+
const schemaKeywordPattern = /^(?:schema|type|interface|union|enum|input|scalar|directive|implements|extend)\b/;
|
|
50
|
+
const atomPattern = /^(?:true|false|null)\b/;
|
|
51
|
+
|
|
52
|
+
window.CodeMirror.defineMode('graphql-playground', function () {
|
|
53
|
+
return {
|
|
54
|
+
startState() {
|
|
55
|
+
return {
|
|
56
|
+
inString: false,
|
|
57
|
+
blockString: false,
|
|
58
|
+
blockComment: false,
|
|
59
|
+
expectField: false,
|
|
60
|
+
expectArgument: false,
|
|
61
|
+
expectType: false,
|
|
62
|
+
argumentDepth: 0,
|
|
63
|
+
selectionDepth: 0
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
token(stream, state) {
|
|
67
|
+
if (state.blockComment) {
|
|
68
|
+
if (stream.match(/.*?\*\//)) {
|
|
69
|
+
state.blockComment = false;
|
|
70
|
+
} else {
|
|
71
|
+
stream.skipToEnd();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return 'comment';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (state.blockString) {
|
|
78
|
+
if (stream.match(/.*?"""/)) {
|
|
79
|
+
state.blockString = false;
|
|
80
|
+
} else {
|
|
81
|
+
stream.skipToEnd();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return 'string';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (state.inString) {
|
|
88
|
+
let escaped = false;
|
|
89
|
+
let next;
|
|
90
|
+
|
|
91
|
+
while ((next = stream.next()) != null) {
|
|
92
|
+
if (next === '"' && !escaped) {
|
|
93
|
+
state.inString = false;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
escaped = !escaped && next === '\\';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return 'string';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (stream.eatSpace()) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (stream.match(/^#.*/)) {
|
|
108
|
+
return 'comment';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (stream.match('/*')) {
|
|
112
|
+
state.blockComment = true;
|
|
113
|
+
return 'comment';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (stream.match('"""')) {
|
|
117
|
+
state.blockString = true;
|
|
118
|
+
return 'string';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (stream.peek() === '"') {
|
|
122
|
+
stream.next();
|
|
123
|
+
state.inString = true;
|
|
124
|
+
return 'string';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (stream.match(atomPattern)) {
|
|
128
|
+
return 'atom';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (stream.match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)) {
|
|
132
|
+
return 'number';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (stream.match(/^@[A-Za-z_][A-Za-z0-9_]*/)) {
|
|
136
|
+
return 'meta';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (stream.match(/^\$[A-Za-z_][A-Za-z0-9_]*/)) {
|
|
140
|
+
state.expectArgument = false;
|
|
141
|
+
return 'variable-2';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (stream.match(/^!/)) {
|
|
145
|
+
return 'non-null-modifier';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (stream.match(/^[()\[\]{}]/)) {
|
|
149
|
+
const bracket = stream.current();
|
|
150
|
+
|
|
151
|
+
if (bracket === '(') {
|
|
152
|
+
state.argumentDepth += 1;
|
|
153
|
+
state.expectArgument = true;
|
|
154
|
+
} else if (bracket === ')') {
|
|
155
|
+
state.argumentDepth = Math.max(0, state.argumentDepth - 1);
|
|
156
|
+
state.expectArgument = state.argumentDepth > 0;
|
|
157
|
+
} else if (bracket === '{') {
|
|
158
|
+
state.selectionDepth += 1;
|
|
159
|
+
} else if (bracket === '}') {
|
|
160
|
+
state.selectionDepth = Math.max(0, state.selectionDepth - 1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (bracket === '[' || bracket === ']') {
|
|
164
|
+
return 'list-modifier';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return 'bracket';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (stream.match(/^,/)) {
|
|
171
|
+
if (state.argumentDepth > 0) {
|
|
172
|
+
state.expectArgument = true;
|
|
173
|
+
state.expectType = false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return 'operator';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (stream.match(/^[:=|&!]/)) {
|
|
180
|
+
const operator = stream.current();
|
|
181
|
+
|
|
182
|
+
if (operator === ':') {
|
|
183
|
+
state.expectType = true;
|
|
184
|
+
} else if (operator === '=') {
|
|
185
|
+
state.expectType = false;
|
|
186
|
+
} else if (operator === '|') {
|
|
187
|
+
state.expectType = true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return 'operator';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (stream.match(/^[A-Z][A-Za-z0-9_]*/)) {
|
|
194
|
+
state.expectType = false;
|
|
195
|
+
return 'type-name';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (stream.match(/^[a-z_][A-Za-z0-9_]*/i)) {
|
|
199
|
+
const identifier = stream.current();
|
|
200
|
+
|
|
201
|
+
if (state.expectArgument) {
|
|
202
|
+
state.expectArgument = false;
|
|
203
|
+
return 'variable';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (state.expectField) {
|
|
207
|
+
state.expectField = false;
|
|
208
|
+
return 'def';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (state.selectionDepth === 0 && state.argumentDepth === 0 && operationKeywordPattern.test(identifier)) {
|
|
212
|
+
state.expectField = true;
|
|
213
|
+
state.expectArgument = false;
|
|
214
|
+
state.expectType = false;
|
|
215
|
+
return 'keyword';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (state.selectionDepth === 0 && state.argumentDepth === 0 && schemaKeywordPattern.test(identifier)) {
|
|
219
|
+
state.expectField = false;
|
|
220
|
+
state.expectArgument = false;
|
|
221
|
+
state.expectType = true;
|
|
222
|
+
return 'keyword';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return 'property';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
stream.next();
|
|
229
|
+
return null;
|
|
230
|
+
},
|
|
231
|
+
lineComment: '#'
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function createCompletion(text, meta, renderText, completionType) {
|
|
237
|
+
return {
|
|
238
|
+
text,
|
|
239
|
+
displayText: renderText || text,
|
|
240
|
+
className: completionType ? `cm-hint-${completionType}` : '',
|
|
241
|
+
hint(cm, data, completion) {
|
|
242
|
+
const from = data.from || cm.getCursor();
|
|
243
|
+
const to = data.to || cm.getCursor();
|
|
244
|
+
|
|
245
|
+
if (typeof completion.apply === 'function') {
|
|
246
|
+
completion.apply(cm, from, to, completion);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
cm.replaceRange(completion.text, from, to);
|
|
251
|
+
|
|
252
|
+
const cursorOffset = completion.cursorOffset;
|
|
253
|
+
|
|
254
|
+
if (typeof cursorOffset === 'number') {
|
|
255
|
+
const startIndex = cm.indexFromPos(from);
|
|
256
|
+
cm.setCursor(cm.posFromIndex(startIndex + cursorOffset));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function getNamedType(typeRef) {
|
|
263
|
+
if (!typeRef) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (typeRef.name) {
|
|
268
|
+
return typeRef;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return typeRef.ofType ? getNamedType(typeRef.ofType) : null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function getTypeMap(schema) {
|
|
275
|
+
if (!schema || !Array.isArray(schema.types)) {
|
|
276
|
+
return new Map();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return new Map(schema.types.filter((type) => type && type.name).map((type) => [type.name, type]));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getRootTypeName(schema, operation) {
|
|
283
|
+
if (!schema) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (operation === 'mutation') {
|
|
288
|
+
return schema.mutationType && schema.mutationType.name;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (operation === 'subscription') {
|
|
292
|
+
return schema.subscriptionType && schema.subscriptionType.name;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return schema.queryType && schema.queryType.name;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function getFieldMap(typeRef) {
|
|
299
|
+
if (!typeRef || !Array.isArray(typeRef.fields)) {
|
|
300
|
+
return new Map();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return new Map(typeRef.fields.filter((field) => field && field.name).map((field) => [field.name, field]));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function getFieldByName(typeMap, typeName, fieldName) {
|
|
307
|
+
if (!typeName || !fieldName) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const typeRef = typeMap.get(typeName);
|
|
312
|
+
const fieldMap = getFieldMap(typeRef);
|
|
313
|
+
|
|
314
|
+
return fieldMap.get(fieldName) || null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function typeRefToString(typeRef) {
|
|
318
|
+
if (!typeRef) {
|
|
319
|
+
return '';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (typeRef.kind === 'NON_NULL') {
|
|
323
|
+
return `${typeRefToString(typeRef.ofType)}!`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (typeRef.kind === 'LIST') {
|
|
327
|
+
return `[${typeRefToString(typeRef.ofType)}]`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return typeRef.name || '';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function buildTypeTokens(typeRef) {
|
|
334
|
+
if (!typeRef) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (typeRef.kind === 'LIST') {
|
|
339
|
+
return [
|
|
340
|
+
{ text: '[', className: 'field-type-prefix' },
|
|
341
|
+
...buildTypeTokens(typeRef.ofType),
|
|
342
|
+
{ text: ']', className: 'field-type-suffix' }
|
|
343
|
+
];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (typeRef.kind === 'NON_NULL') {
|
|
347
|
+
return [
|
|
348
|
+
...buildTypeTokens(typeRef.ofType),
|
|
349
|
+
{ text: '!', className: 'field-type-non-null' }
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return [{
|
|
354
|
+
text: typeRef.name || '',
|
|
355
|
+
className: 'field-type'
|
|
356
|
+
}];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function ensureTooltipElement() {
|
|
360
|
+
if (tooltipElement || typeof document === 'undefined') {
|
|
361
|
+
return tooltipElement;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
tooltipElement = document.createElement('div');
|
|
365
|
+
tooltipElement.className = 'schema-tooltip';
|
|
366
|
+
tooltipElement.style.display = 'none';
|
|
367
|
+
document.body.appendChild(tooltipElement);
|
|
368
|
+
return tooltipElement;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function renderTooltip(payload, eventTarget, event) {
|
|
372
|
+
const tooltip = ensureTooltipElement();
|
|
373
|
+
|
|
374
|
+
if (!tooltip || !payload || !eventTarget || typeof eventTarget.getBoundingClientRect !== 'function') {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const rect = eventTarget.getBoundingClientRect();
|
|
379
|
+
const tooltipWidth = 320;
|
|
380
|
+
const gap = 8;
|
|
381
|
+
const maxLeft = Math.max(gap, window.innerWidth - tooltipWidth - gap);
|
|
382
|
+
const anchorLeft = event && typeof event.clientX === 'number' ? event.clientX : rect.left;
|
|
383
|
+
const anchorTop = event && typeof event.clientY === 'number' ? event.clientY : rect.bottom;
|
|
384
|
+
const top = Math.min(window.innerHeight - 120, anchorTop + gap);
|
|
385
|
+
const left = Math.min(anchorLeft, maxLeft);
|
|
386
|
+
const typeTokens = buildTypeTokens(payload.typeRef);
|
|
387
|
+
const nameClass = payload.labelClass || 'field';
|
|
388
|
+
|
|
389
|
+
tooltip.innerHTML = `
|
|
390
|
+
<div class="schema-tooltip-signature">
|
|
391
|
+
<span class="schema-tooltip-name schema-tooltip-name-${nameClass}"></span>
|
|
392
|
+
${Array.isArray(payload.arguments) && payload.arguments.length ? '<span class="schema-tooltip-inline-args"></span>' : ''}
|
|
393
|
+
${typeTokens.length ? '<span>:</span><span class="schema-tooltip-type-tokens"></span>' : ''}
|
|
394
|
+
</div>
|
|
395
|
+
${Array.isArray(payload.usages) && payload.usages.length ? '<div class="schema-tooltip-usage-list"></div>' : ''}
|
|
396
|
+
${payload.description ? '<div class="schema-tooltip-description"></div>' : ''}
|
|
397
|
+
`;
|
|
398
|
+
|
|
399
|
+
const nameElement = tooltip.querySelector('.schema-tooltip-name');
|
|
400
|
+
if (nameElement) {
|
|
401
|
+
nameElement.textContent = payload.label || '';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const typeTokensContainer = tooltip.querySelector('.schema-tooltip-type-tokens');
|
|
405
|
+
if (typeTokensContainer) {
|
|
406
|
+
typeTokens.forEach((token) => {
|
|
407
|
+
const tokenElement = document.createElement('span');
|
|
408
|
+
tokenElement.className = token.className;
|
|
409
|
+
tokenElement.textContent = token.text;
|
|
410
|
+
typeTokensContainer.appendChild(tokenElement);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const descriptionElement = tooltip.querySelector('.schema-tooltip-description');
|
|
415
|
+
if (descriptionElement) {
|
|
416
|
+
descriptionElement.textContent = payload.description || '';
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const argsElement = tooltip.querySelector('.schema-tooltip-inline-args');
|
|
420
|
+
if (argsElement) {
|
|
421
|
+
const openParen = document.createElement('span');
|
|
422
|
+
openParen.textContent = '(';
|
|
423
|
+
argsElement.appendChild(openParen);
|
|
424
|
+
|
|
425
|
+
payload.arguments.forEach((arg, index) => {
|
|
426
|
+
const name = document.createElement('span');
|
|
427
|
+
name.className = 'schema-tooltip-name schema-tooltip-name-argument';
|
|
428
|
+
name.textContent = arg.name;
|
|
429
|
+
argsElement.appendChild(name);
|
|
430
|
+
|
|
431
|
+
const separator = document.createElement('span');
|
|
432
|
+
separator.textContent = ':';
|
|
433
|
+
argsElement.appendChild(separator);
|
|
434
|
+
|
|
435
|
+
const typeContainer = document.createElement('span');
|
|
436
|
+
typeContainer.className = 'schema-tooltip-type-tokens';
|
|
437
|
+
buildTypeTokens(arg.typeRef).forEach((token) => {
|
|
438
|
+
const tokenElement = document.createElement('span');
|
|
439
|
+
tokenElement.className = token.className;
|
|
440
|
+
tokenElement.textContent = token.text;
|
|
441
|
+
typeContainer.appendChild(tokenElement);
|
|
442
|
+
});
|
|
443
|
+
argsElement.appendChild(typeContainer);
|
|
444
|
+
|
|
445
|
+
if (index < payload.arguments.length - 1) {
|
|
446
|
+
const comma = document.createElement('span');
|
|
447
|
+
comma.textContent = ',';
|
|
448
|
+
argsElement.appendChild(comma);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const closeParen = document.createElement('span');
|
|
453
|
+
closeParen.textContent = ')';
|
|
454
|
+
argsElement.appendChild(closeParen);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const usageListElement = tooltip.querySelector('.schema-tooltip-usage-list');
|
|
458
|
+
if (usageListElement) {
|
|
459
|
+
payload.usages.forEach((usage) => {
|
|
460
|
+
const usageItem = document.createElement('div');
|
|
461
|
+
usageItem.className = 'schema-tooltip-usage-item';
|
|
462
|
+
|
|
463
|
+
const fieldName = document.createElement('span');
|
|
464
|
+
fieldName.className = 'schema-tooltip-name schema-tooltip-name-field';
|
|
465
|
+
fieldName.textContent = usage.fieldName;
|
|
466
|
+
usageItem.appendChild(fieldName);
|
|
467
|
+
|
|
468
|
+
if (Array.isArray(usage.arguments) && usage.arguments.length) {
|
|
469
|
+
const inlineArgs = document.createElement('span');
|
|
470
|
+
inlineArgs.className = 'schema-tooltip-inline-args';
|
|
471
|
+
|
|
472
|
+
const openParen = document.createElement('span');
|
|
473
|
+
openParen.className = 'schema-tooltip-muted';
|
|
474
|
+
openParen.textContent = '(';
|
|
475
|
+
inlineArgs.appendChild(openParen);
|
|
476
|
+
|
|
477
|
+
usage.arguments.forEach((arg, index) => {
|
|
478
|
+
const argName = document.createElement('span');
|
|
479
|
+
argName.className = arg.isHighlighted
|
|
480
|
+
? 'schema-tooltip-name schema-tooltip-name-argument'
|
|
481
|
+
: 'schema-tooltip-muted';
|
|
482
|
+
argName.textContent = arg.name;
|
|
483
|
+
inlineArgs.appendChild(argName);
|
|
484
|
+
|
|
485
|
+
const separator = document.createElement('span');
|
|
486
|
+
separator.className = 'schema-tooltip-muted';
|
|
487
|
+
separator.textContent = ':';
|
|
488
|
+
inlineArgs.appendChild(separator);
|
|
489
|
+
|
|
490
|
+
const typeContainer = document.createElement('span');
|
|
491
|
+
typeContainer.className = 'schema-tooltip-type-tokens';
|
|
492
|
+
|
|
493
|
+
buildTypeTokens(arg.typeRef).forEach((token) => {
|
|
494
|
+
const tokenElement = document.createElement('span');
|
|
495
|
+
tokenElement.className = arg.isHighlighted
|
|
496
|
+
? token.className
|
|
497
|
+
: `${token.className} schema-tooltip-muted`;
|
|
498
|
+
tokenElement.textContent = token.text;
|
|
499
|
+
typeContainer.appendChild(tokenElement);
|
|
500
|
+
});
|
|
501
|
+
inlineArgs.appendChild(typeContainer);
|
|
502
|
+
|
|
503
|
+
if (index < usage.arguments.length - 1) {
|
|
504
|
+
const comma = document.createElement('span');
|
|
505
|
+
comma.className = 'schema-tooltip-muted';
|
|
506
|
+
comma.textContent = ',';
|
|
507
|
+
inlineArgs.appendChild(comma);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const closeParen = document.createElement('span');
|
|
512
|
+
closeParen.className = 'schema-tooltip-muted';
|
|
513
|
+
closeParen.textContent = ')';
|
|
514
|
+
inlineArgs.appendChild(closeParen);
|
|
515
|
+
usageItem.appendChild(inlineArgs);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (usage.returnTypeRef) {
|
|
519
|
+
const returnSeparator = document.createElement('span');
|
|
520
|
+
returnSeparator.className = 'schema-tooltip-muted';
|
|
521
|
+
returnSeparator.textContent = ':';
|
|
522
|
+
usageItem.appendChild(returnSeparator);
|
|
523
|
+
|
|
524
|
+
const returnType = document.createElement('span');
|
|
525
|
+
returnType.className = 'schema-tooltip-type-tokens';
|
|
526
|
+
buildTypeTokens(usage.returnTypeRef).forEach((token) => {
|
|
527
|
+
const tokenElement = document.createElement('span');
|
|
528
|
+
tokenElement.className = `${token.className} schema-tooltip-muted`;
|
|
529
|
+
tokenElement.textContent = token.text;
|
|
530
|
+
returnType.appendChild(tokenElement);
|
|
531
|
+
});
|
|
532
|
+
usageItem.appendChild(returnType);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
usageListElement.appendChild(usageItem);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
tooltip.style.top = `${Math.max(gap, top)}px`;
|
|
540
|
+
tooltip.style.left = `${Math.max(gap, left)}px`;
|
|
541
|
+
tooltip.style.display = 'block';
|
|
542
|
+
tooltip.classList.remove('is-visible');
|
|
543
|
+
|
|
544
|
+
if (hideTooltipTimeout) {
|
|
545
|
+
window.clearTimeout(hideTooltipTimeout);
|
|
546
|
+
hideTooltipTimeout = null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
window.requestAnimationFrame(() => {
|
|
550
|
+
if (tooltip) {
|
|
551
|
+
tooltip.classList.add('is-visible');
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function hideTooltip() {
|
|
557
|
+
if (tooltipElement) {
|
|
558
|
+
tooltipElement.classList.remove('is-visible');
|
|
559
|
+
|
|
560
|
+
if (hideTooltipTimeout) {
|
|
561
|
+
window.clearTimeout(hideTooltipTimeout);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
hideTooltipTimeout = window.setTimeout(() => {
|
|
565
|
+
if (tooltipElement) {
|
|
566
|
+
tooltipElement.style.display = 'none';
|
|
567
|
+
}
|
|
568
|
+
hideTooltipTimeout = null;
|
|
569
|
+
}, 300);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function toVariableNameSuffix(typeRef) {
|
|
574
|
+
const typeText = typeRefToString(typeRef);
|
|
575
|
+
|
|
576
|
+
if (!typeText) {
|
|
577
|
+
return 'Value';
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return typeText
|
|
581
|
+
.replace(/[\[\]!]/g, ' ')
|
|
582
|
+
.trim()
|
|
583
|
+
.split(/\s+/)
|
|
584
|
+
.filter(Boolean)
|
|
585
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
586
|
+
.join('') || 'Value';
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function buildOperationArgumentBindings(args, existingVariables) {
|
|
590
|
+
const variableRegistry = new Map();
|
|
591
|
+
const bindings = [];
|
|
592
|
+
|
|
593
|
+
(existingVariables || []).forEach((variable) => {
|
|
594
|
+
if (!variable || !variable.name) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
variableRegistry.set(variable.name, variable.typeString || '');
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
(args || []).forEach((arg) => {
|
|
602
|
+
if (!arg || !arg.name) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const typeString = typeRefToString(arg.type);
|
|
607
|
+
const baseVariableName = arg.name;
|
|
608
|
+
const existingType = variableRegistry.get(baseVariableName);
|
|
609
|
+
let variableName = baseVariableName;
|
|
610
|
+
|
|
611
|
+
if (existingType && existingType !== typeString) {
|
|
612
|
+
variableName = `${baseVariableName}${toVariableNameSuffix(arg.type)}`;
|
|
613
|
+
|
|
614
|
+
let duplicateIndex = 2;
|
|
615
|
+
while (variableRegistry.has(variableName) && variableRegistry.get(variableName) !== typeString) {
|
|
616
|
+
variableName = `${baseVariableName}${toVariableNameSuffix(arg.type)}${duplicateIndex}`;
|
|
617
|
+
duplicateIndex += 1;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (!variableRegistry.has(variableName)) {
|
|
622
|
+
variableRegistry.set(variableName, typeString);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
bindings.push({
|
|
626
|
+
argumentName: arg.name,
|
|
627
|
+
variableName,
|
|
628
|
+
typeString
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
return bindings;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function parseOperationVariables(headerText) {
|
|
636
|
+
const variables = [];
|
|
637
|
+
const variablePattern = /\$([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([!\[\]A-Za-z0-9_]+)/g;
|
|
638
|
+
let match = variablePattern.exec(headerText);
|
|
639
|
+
|
|
640
|
+
while (match) {
|
|
641
|
+
variables.push({
|
|
642
|
+
name: match[1],
|
|
643
|
+
typeString: match[2]
|
|
644
|
+
});
|
|
645
|
+
match = variablePattern.exec(headerText);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return variables;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function resolveOperationVariable(schema, queryText, variableName) {
|
|
652
|
+
const operationHeaderMatch = /(query|mutation|subscription)\b([\s\S]*?)\{/.exec(queryText || '');
|
|
653
|
+
|
|
654
|
+
if (!operationHeaderMatch) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const variables = parseOperationVariables(operationHeaderMatch[2] || '');
|
|
659
|
+
const variable = variables.find((item) => item.name === variableName);
|
|
660
|
+
|
|
661
|
+
if (!variable) {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
label: `$${variable.name}`,
|
|
667
|
+
labelClass: 'argument',
|
|
668
|
+
typeRef: parseTypeRefFromString(variable.typeString, schema),
|
|
669
|
+
usages: collectVariableUsages(schema, queryText, variableName),
|
|
670
|
+
description: ''
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function collectVariableUsages(schema, text, variableName) {
|
|
675
|
+
const typeMap = getTypeMap(schema);
|
|
676
|
+
let operation = 'query';
|
|
677
|
+
let rootTypeName = getRootTypeName(schema, operation);
|
|
678
|
+
const typeStack = rootTypeName ? [rootTypeName] : [];
|
|
679
|
+
let pendingFieldRef = null;
|
|
680
|
+
let currentArgumentField = null;
|
|
681
|
+
let currentArgumentName = null;
|
|
682
|
+
let parenDepth = 0;
|
|
683
|
+
let i = 0;
|
|
684
|
+
const usages = [];
|
|
685
|
+
|
|
686
|
+
while (i < text.length) {
|
|
687
|
+
const char = text[i];
|
|
688
|
+
const nextThree = text.slice(i, i + 3);
|
|
689
|
+
|
|
690
|
+
if (nextThree === '"""') {
|
|
691
|
+
const end = text.indexOf('"""', i + 3);
|
|
692
|
+
i = end === -1 ? text.length : end + 3;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (text.slice(i, i + 2) === '/*') {
|
|
697
|
+
const end = text.indexOf('*/', i + 2);
|
|
698
|
+
i = end === -1 ? text.length : end + 2;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (char === '#') {
|
|
703
|
+
const end = text.indexOf('\n', i + 1);
|
|
704
|
+
i = end === -1 ? text.length : end + 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (char === '"') {
|
|
709
|
+
i += 1;
|
|
710
|
+
while (i < text.length) {
|
|
711
|
+
const stringChar = text[i];
|
|
712
|
+
|
|
713
|
+
if (stringChar === '\\') {
|
|
714
|
+
i += 2;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (stringChar === '"') {
|
|
719
|
+
i += 1;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
i += 1;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (/\s/.test(char)) {
|
|
730
|
+
i += 1;
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (isNameStart(char)) {
|
|
735
|
+
let end = i + 1;
|
|
736
|
+
|
|
737
|
+
while (end < text.length && isNamePart(text[end])) {
|
|
738
|
+
end += 1;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const word = text.slice(i, end);
|
|
742
|
+
|
|
743
|
+
if (word === 'query' || word === 'mutation' || word === 'subscription') {
|
|
744
|
+
operation = word;
|
|
745
|
+
rootTypeName = getRootTypeName(schema, operation);
|
|
746
|
+
typeStack.length = 0;
|
|
747
|
+
if (rootTypeName) {
|
|
748
|
+
typeStack.push(rootTypeName);
|
|
749
|
+
}
|
|
750
|
+
pendingFieldRef = null;
|
|
751
|
+
currentArgumentField = null;
|
|
752
|
+
currentArgumentName = null;
|
|
753
|
+
parenDepth = 0;
|
|
754
|
+
i = end;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (parenDepth > 0 && currentArgumentField) {
|
|
759
|
+
const argumentRef = (currentArgumentField.args || []).find((arg) => arg && arg.name === word);
|
|
760
|
+
|
|
761
|
+
if (argumentRef) {
|
|
762
|
+
currentArgumentName = word;
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
const currentTypeName = typeStack[typeStack.length - 1];
|
|
766
|
+
pendingFieldRef = getFieldByName(typeMap, currentTypeName, word);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
i = end;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (char === '$') {
|
|
774
|
+
let end = i + 1;
|
|
775
|
+
|
|
776
|
+
while (end < text.length && isNamePart(text[end])) {
|
|
777
|
+
end += 1;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const usedVariableName = text.slice(i + 1, end);
|
|
781
|
+
|
|
782
|
+
if (usedVariableName === variableName && currentArgumentField && currentArgumentName) {
|
|
783
|
+
usages.push({
|
|
784
|
+
fieldName: currentArgumentField.name,
|
|
785
|
+
arguments: (currentArgumentField.args || []).map((arg) => ({
|
|
786
|
+
name: arg.name,
|
|
787
|
+
typeRef: arg.type,
|
|
788
|
+
isHighlighted: arg.name === currentArgumentName
|
|
789
|
+
})),
|
|
790
|
+
returnTypeRef: currentArgumentField.type
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
i = end;
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (char === '(') {
|
|
799
|
+
parenDepth += 1;
|
|
800
|
+
if (pendingFieldRef) {
|
|
801
|
+
currentArgumentField = pendingFieldRef;
|
|
802
|
+
}
|
|
803
|
+
i += 1;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (char === ')') {
|
|
808
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
809
|
+
if (parenDepth === 0) {
|
|
810
|
+
currentArgumentField = null;
|
|
811
|
+
currentArgumentName = null;
|
|
812
|
+
}
|
|
813
|
+
i += 1;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (char === '{') {
|
|
818
|
+
if (pendingFieldRef) {
|
|
819
|
+
const childType = getNamedType(pendingFieldRef.type);
|
|
820
|
+
if (childType && childType.name) {
|
|
821
|
+
typeStack.push(childType.name);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
pendingFieldRef = null;
|
|
825
|
+
i += 1;
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (char === '}') {
|
|
830
|
+
if (typeStack.length > 1) {
|
|
831
|
+
typeStack.pop();
|
|
832
|
+
}
|
|
833
|
+
pendingFieldRef = null;
|
|
834
|
+
i += 1;
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (char === ',') {
|
|
839
|
+
currentArgumentName = null;
|
|
840
|
+
i += 1;
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
i += 1;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return usages;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function parseTypeRefFromString(typeString) {
|
|
851
|
+
if (!typeString) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
let index = 0;
|
|
856
|
+
|
|
857
|
+
function parseInner() {
|
|
858
|
+
if (typeString[index] === '[') {
|
|
859
|
+
index += 1;
|
|
860
|
+
const inner = parseInner();
|
|
861
|
+
|
|
862
|
+
if (typeString[index] === ']') {
|
|
863
|
+
index += 1;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
let listType = { kind: 'LIST', ofType: inner };
|
|
867
|
+
|
|
868
|
+
if (typeString[index] === '!') {
|
|
869
|
+
index += 1;
|
|
870
|
+
listType = { kind: 'NON_NULL', ofType: listType };
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return listType;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const nameMatch = /^[A-Za-z_][A-Za-z0-9_]*/.exec(typeString.slice(index));
|
|
877
|
+
|
|
878
|
+
if (!nameMatch) {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
index += nameMatch[0].length;
|
|
883
|
+
let namedType = getTypeMap($ctrl.schema).get(nameMatch[0]) || { kind: 'NAMED', name: nameMatch[0] };
|
|
884
|
+
|
|
885
|
+
if (typeString[index] === '!') {
|
|
886
|
+
index += 1;
|
|
887
|
+
namedType = { kind: 'NON_NULL', ofType: namedType };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return namedType;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return parseInner();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function resolveQueryHoverPayload(cm, position) {
|
|
897
|
+
const token = cm.getTokenAt(position);
|
|
898
|
+
|
|
899
|
+
if (!token || !token.string || !token.type) {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const tokenText = token.string.replace(/^"+|"+$/g, '');
|
|
904
|
+
const textBeforeToken = cm.getValue().slice(0, cm.indexFromPos({ line: position.line, ch: token.start }));
|
|
905
|
+
const context = resolveQueryContext($ctrl.schema, textBeforeToken);
|
|
906
|
+
|
|
907
|
+
if (token.type.indexOf('variable-2') !== -1) {
|
|
908
|
+
return resolveOperationVariable($ctrl.schema, cm.getValue(), tokenText.replace(/^\$/, ''));
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (token.type.indexOf('type-name') !== -1) {
|
|
912
|
+
const schemaType = getTypeMap($ctrl.schema).get(tokenText);
|
|
913
|
+
|
|
914
|
+
if (!schemaType) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return {
|
|
919
|
+
label: schemaType.name,
|
|
920
|
+
labelClass: 'type',
|
|
921
|
+
typeRef: schemaType,
|
|
922
|
+
description: schemaType.description || ''
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (token.type.indexOf('variable') !== -1 && context.inArguments && context.currentArgumentField) {
|
|
927
|
+
const arg = (context.currentArgumentField.args || []).find((item) => item.name === tokenText);
|
|
928
|
+
|
|
929
|
+
if (!arg) {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return {
|
|
934
|
+
label: arg.name,
|
|
935
|
+
labelClass: 'argument',
|
|
936
|
+
typeRef: arg.type,
|
|
937
|
+
description: arg.description || ''
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if ((token.type.indexOf('property') !== -1 || token.type.indexOf('def') !== -1) && context.parentTypeName) {
|
|
942
|
+
const field = getFieldByName(getTypeMap($ctrl.schema), context.parentTypeName, tokenText);
|
|
943
|
+
|
|
944
|
+
if (!field) {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return {
|
|
949
|
+
label: field.name,
|
|
950
|
+
labelClass: 'field',
|
|
951
|
+
typeRef: field.type,
|
|
952
|
+
arguments: Array.isArray(field.args)
|
|
953
|
+
? field.args.map((arg) => ({
|
|
954
|
+
name: arg.name,
|
|
955
|
+
typeRef: arg.type,
|
|
956
|
+
description: arg.description || ''
|
|
957
|
+
}))
|
|
958
|
+
: [],
|
|
959
|
+
description: field.description || ''
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function bindHoverHandlers(cm) {
|
|
967
|
+
const wrapper = cm.getWrapperElement();
|
|
968
|
+
let lastTooltipKey = '';
|
|
969
|
+
let hoverTimer = null;
|
|
970
|
+
|
|
971
|
+
function clearHoverTimer() {
|
|
972
|
+
if (hoverTimer) {
|
|
973
|
+
window.clearTimeout(hoverTimer);
|
|
974
|
+
hoverTimer = null;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
wrapper.addEventListener('mousemove', (event) => {
|
|
979
|
+
const target = event.target;
|
|
980
|
+
|
|
981
|
+
if (!target || !(target instanceof HTMLElement) || !target.closest('.CodeMirror-lines')) {
|
|
982
|
+
clearHoverTimer();
|
|
983
|
+
hideTooltip();
|
|
984
|
+
lastTooltipKey = '';
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const position = cm.coordsChar({ left: event.clientX, top: event.clientY }, 'window');
|
|
989
|
+
const payload = resolveQueryHoverPayload(cm, position);
|
|
990
|
+
|
|
991
|
+
if (!payload) {
|
|
992
|
+
clearHoverTimer();
|
|
993
|
+
hideTooltip();
|
|
994
|
+
lastTooltipKey = '';
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const nextKey = `${payload.label}|${payload.description}|${typeRefToString(payload.typeRef)}`;
|
|
999
|
+
|
|
1000
|
+
if (lastTooltipKey !== nextKey) {
|
|
1001
|
+
clearHoverTimer();
|
|
1002
|
+
hideTooltip();
|
|
1003
|
+
lastTooltipKey = nextKey;
|
|
1004
|
+
hoverTimer = window.setTimeout(() => {
|
|
1005
|
+
renderTooltip(payload, target, event);
|
|
1006
|
+
hoverTimer = null;
|
|
1007
|
+
}, 1000);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
wrapper.addEventListener('mouseleave', () => {
|
|
1013
|
+
clearHoverTimer();
|
|
1014
|
+
lastTooltipKey = '';
|
|
1015
|
+
hideTooltip();
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function isNameStart(char) {
|
|
1020
|
+
return /[A-Za-z_]/.test(char);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function isNamePart(char) {
|
|
1024
|
+
return /[A-Za-z0-9_]/.test(char);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function resolveQueryContext(schema, text) {
|
|
1028
|
+
const typeMap = getTypeMap(schema);
|
|
1029
|
+
let operation = 'query';
|
|
1030
|
+
let rootTypeName = getRootTypeName(schema, operation);
|
|
1031
|
+
const typeStack = rootTypeName ? [rootTypeName] : [];
|
|
1032
|
+
const fieldStack = [];
|
|
1033
|
+
let pendingFieldName = null;
|
|
1034
|
+
let pendingFieldRef = null;
|
|
1035
|
+
let currentArgumentField = null;
|
|
1036
|
+
let parenDepth = 0;
|
|
1037
|
+
let selectionDepth = 0;
|
|
1038
|
+
let lastOperationKeywordIndex = 0;
|
|
1039
|
+
let operationHeader = null;
|
|
1040
|
+
let i = 0;
|
|
1041
|
+
|
|
1042
|
+
while (i < text.length) {
|
|
1043
|
+
const char = text[i];
|
|
1044
|
+
const nextThree = text.slice(i, i + 3);
|
|
1045
|
+
|
|
1046
|
+
if (nextThree === '"""') {
|
|
1047
|
+
const end = text.indexOf('"""', i + 3);
|
|
1048
|
+
i = end === -1 ? text.length : end + 3;
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (text.slice(i, i + 2) === '/*') {
|
|
1053
|
+
const end = text.indexOf('*/', i + 2);
|
|
1054
|
+
i = end === -1 ? text.length : end + 2;
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (char === '#') {
|
|
1059
|
+
const end = text.indexOf('\n', i + 1);
|
|
1060
|
+
i = end === -1 ? text.length : end + 1;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (char === '"') {
|
|
1065
|
+
i += 1;
|
|
1066
|
+
while (i < text.length) {
|
|
1067
|
+
const stringChar = text[i];
|
|
1068
|
+
|
|
1069
|
+
if (stringChar === '\\') {
|
|
1070
|
+
i += 2;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (stringChar === '"') {
|
|
1075
|
+
i += 1;
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
i += 1;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (/\s/.test(char)) {
|
|
1086
|
+
i += 1;
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (char === '$') {
|
|
1091
|
+
i += 1;
|
|
1092
|
+
while (i < text.length && isNamePart(text[i])) {
|
|
1093
|
+
i += 1;
|
|
1094
|
+
}
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (char === '@') {
|
|
1099
|
+
i += 1;
|
|
1100
|
+
while (i < text.length && isNamePart(text[i])) {
|
|
1101
|
+
i += 1;
|
|
1102
|
+
}
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if (isNameStart(char)) {
|
|
1107
|
+
let end = i + 1;
|
|
1108
|
+
|
|
1109
|
+
while (end < text.length && isNamePart(text[end])) {
|
|
1110
|
+
end += 1;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const word = text.slice(i, end);
|
|
1114
|
+
|
|
1115
|
+
if (word === 'query' || word === 'mutation' || word === 'subscription') {
|
|
1116
|
+
operation = word;
|
|
1117
|
+
rootTypeName = getRootTypeName(schema, operation);
|
|
1118
|
+
typeStack.length = 0;
|
|
1119
|
+
fieldStack.length = 0;
|
|
1120
|
+
if (rootTypeName) {
|
|
1121
|
+
typeStack.push(rootTypeName);
|
|
1122
|
+
}
|
|
1123
|
+
pendingFieldName = null;
|
|
1124
|
+
pendingFieldRef = null;
|
|
1125
|
+
currentArgumentField = null;
|
|
1126
|
+
parenDepth = 0;
|
|
1127
|
+
selectionDepth = 0;
|
|
1128
|
+
lastOperationKeywordIndex = i;
|
|
1129
|
+
operationHeader = null;
|
|
1130
|
+
i = end;
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (word === 'fragment' || word === 'on') {
|
|
1135
|
+
i = end;
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (parenDepth === 0) {
|
|
1140
|
+
const currentTypeName = typeStack[typeStack.length - 1];
|
|
1141
|
+
const fieldRef = getFieldByName(typeMap, currentTypeName, word);
|
|
1142
|
+
|
|
1143
|
+
if (fieldRef) {
|
|
1144
|
+
pendingFieldName = word;
|
|
1145
|
+
pendingFieldRef = fieldRef;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
i = end;
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (char === '(') {
|
|
1154
|
+
parenDepth += 1;
|
|
1155
|
+
currentArgumentField = pendingFieldRef;
|
|
1156
|
+
i += 1;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (char === ')') {
|
|
1161
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
1162
|
+
if (parenDepth === 0) {
|
|
1163
|
+
currentArgumentField = null;
|
|
1164
|
+
}
|
|
1165
|
+
i += 1;
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (char === '{') {
|
|
1170
|
+
selectionDepth += 1;
|
|
1171
|
+
|
|
1172
|
+
if (selectionDepth === 1 && lastOperationKeywordIndex <= i) {
|
|
1173
|
+
operationHeader = {
|
|
1174
|
+
operation,
|
|
1175
|
+
keywordIndex: lastOperationKeywordIndex,
|
|
1176
|
+
openBraceIndex: i
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (pendingFieldRef) {
|
|
1181
|
+
const childType = getNamedType(pendingFieldRef.type);
|
|
1182
|
+
|
|
1183
|
+
if (childType && childType.name) {
|
|
1184
|
+
typeStack.push(childType.name);
|
|
1185
|
+
fieldStack.push(pendingFieldRef.name);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
pendingFieldName = null;
|
|
1190
|
+
pendingFieldRef = null;
|
|
1191
|
+
i += 1;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
if (char === '}') {
|
|
1196
|
+
selectionDepth = Math.max(0, selectionDepth - 1);
|
|
1197
|
+
|
|
1198
|
+
if (typeStack.length > 1) {
|
|
1199
|
+
typeStack.pop();
|
|
1200
|
+
fieldStack.pop();
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
pendingFieldName = null;
|
|
1204
|
+
pendingFieldRef = null;
|
|
1205
|
+
i += 1;
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
if (char === ',') {
|
|
1210
|
+
if (parenDepth === 0) {
|
|
1211
|
+
pendingFieldName = null;
|
|
1212
|
+
pendingFieldRef = null;
|
|
1213
|
+
}
|
|
1214
|
+
i += 1;
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
i += 1;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
return {
|
|
1222
|
+
operation,
|
|
1223
|
+
parentTypeName: typeStack[typeStack.length - 1] || rootTypeName,
|
|
1224
|
+
currentArgumentField,
|
|
1225
|
+
inArguments: parenDepth > 0 && Boolean(currentArgumentField),
|
|
1226
|
+
inSelectionSet: selectionDepth > 0 && parenDepth === 0,
|
|
1227
|
+
operationHeader
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function supportsSelectionSet(field) {
|
|
1232
|
+
const namedType = getNamedType(field && field.type);
|
|
1233
|
+
|
|
1234
|
+
return Boolean(namedType && ['OBJECT', 'INTERFACE'].includes(namedType.kind));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function hasRequiredArguments(field) {
|
|
1238
|
+
return Boolean(field && Array.isArray(field.args) && field.args.some((arg) => arg && arg.type && arg.type.kind === 'NON_NULL'));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function buildExpandedSelectionSet(typeRef, indentLevel, visitedTypes) {
|
|
1242
|
+
const namedType = getNamedType(typeRef);
|
|
1243
|
+
const typeMap = getTypeMap($ctrl.schema);
|
|
1244
|
+
const schemaType = namedType ? typeMap.get(namedType.name) : null;
|
|
1245
|
+
|
|
1246
|
+
if (!schemaType || !Array.isArray(schemaType.fields) || !schemaType.fields.length) {
|
|
1247
|
+
return [];
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const nextVisited = new Set(visitedTypes || []);
|
|
1251
|
+
if (namedType && namedType.name) {
|
|
1252
|
+
nextVisited.add(namedType.name);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return schemaType.fields
|
|
1256
|
+
.filter((childField) => childField && childField.name && !hasRequiredArguments(childField))
|
|
1257
|
+
.flatMap((childField) => {
|
|
1258
|
+
if (!supportsSelectionSet(childField)) {
|
|
1259
|
+
return [`${indentLevel}${childField.name}`];
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const childNamedType = getNamedType(childField.type);
|
|
1263
|
+
if (!childNamedType || nextVisited.has(childNamedType.name)) {
|
|
1264
|
+
return [];
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const nestedLines = buildExpandedSelectionSet(childField.type, `${indentLevel} `, nextVisited);
|
|
1268
|
+
|
|
1269
|
+
if (!nestedLines.length) {
|
|
1270
|
+
return [];
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
return [
|
|
1274
|
+
`${indentLevel}${childField.name} {`,
|
|
1275
|
+
...nestedLines,
|
|
1276
|
+
`${indentLevel}}`
|
|
1277
|
+
];
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function buildFieldInsertion(field) {
|
|
1282
|
+
if (!field || !Array.isArray(field.args) || field.args.length === 0) {
|
|
1283
|
+
return {
|
|
1284
|
+
text: field && field.name ? field.name : '',
|
|
1285
|
+
cursorOffset: field && field.name ? field.name.length : 0
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const argsText = field.args.map((arg) => `${arg.name}: `).join(', ');
|
|
1290
|
+
const text = `${field.name}(${argsText})`;
|
|
1291
|
+
|
|
1292
|
+
return {
|
|
1293
|
+
text,
|
|
1294
|
+
cursorOffset: field.name.length + 1
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function buildFieldApply(field) {
|
|
1299
|
+
return function applyFieldCompletion(cm, from, to, completion) {
|
|
1300
|
+
const insertion = buildFieldInsertion(field);
|
|
1301
|
+
const baseText = insertion.text;
|
|
1302
|
+
const operation = completion && completion.operation ? completion.operation : 'query';
|
|
1303
|
+
const shouldWrapInOperation = Boolean(completion && completion.wrapInOperation);
|
|
1304
|
+
const shouldBindOperationVariables = Boolean(completion && completion.bindOperationVariables);
|
|
1305
|
+
const shouldIncludeAllFields = Boolean(completion && completion.includeAllFields);
|
|
1306
|
+
|
|
1307
|
+
if (shouldWrapInOperation) {
|
|
1308
|
+
const currentValue = cm.getValue();
|
|
1309
|
+
const plainText = currentValue
|
|
1310
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
1311
|
+
.replace(/#.*/g, '')
|
|
1312
|
+
.trim();
|
|
1313
|
+
const shouldReplaceWholeEditor = plainText === '';
|
|
1314
|
+
const rootIndent = '';
|
|
1315
|
+
const fieldIndent = ' ';
|
|
1316
|
+
const childIndent = ' ';
|
|
1317
|
+
const argumentBindings = buildOperationArgumentBindings(field.args);
|
|
1318
|
+
const variableDefinitions = argumentBindings.length
|
|
1319
|
+
? ` (${argumentBindings.map((binding) => `$${binding.variableName}: ${binding.typeString}`).join(', ')})`
|
|
1320
|
+
: '';
|
|
1321
|
+
const fieldArguments = argumentBindings.length
|
|
1322
|
+
? `(${argumentBindings.map((binding) => `${binding.argumentName}: $${binding.variableName}`).join(', ')})`
|
|
1323
|
+
: '';
|
|
1324
|
+
const fieldCall = `${field.name}${fieldArguments}`;
|
|
1325
|
+
const expandedLines = shouldIncludeAllFields ? buildExpandedSelectionSet(field.type, childIndent, new Set()) : [];
|
|
1326
|
+
const blockText = supportsSelectionSet(field)
|
|
1327
|
+
? `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall} {\n${expandedLines.length ? `${expandedLines.join('\n')}\n` : `${childIndent}\n`}${fieldIndent}}\n${rootIndent}}`
|
|
1328
|
+
: `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall}\n${rootIndent}}`;
|
|
1329
|
+
const replaceFrom = shouldReplaceWholeEditor ? { line: 0, ch: 0 } : from;
|
|
1330
|
+
const replaceTo = shouldReplaceWholeEditor ? { line: cm.lastLine(), ch: cm.getLine(cm.lastLine()).length } : to;
|
|
1331
|
+
const startIndex = cm.indexFromPos(replaceFrom);
|
|
1332
|
+
const cursorIndex = supportsSelectionSet(field)
|
|
1333
|
+
? startIndex + `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall} {\n${expandedLines.length ? expandedLines[0] : childIndent}`.length
|
|
1334
|
+
: startIndex + `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall}`.length;
|
|
1335
|
+
|
|
1336
|
+
cm.replaceRange(blockText, replaceFrom, replaceTo);
|
|
1337
|
+
cm.setCursor(cm.posFromIndex(cursorIndex));
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (shouldBindOperationVariables && Array.isArray(field.args) && field.args.length) {
|
|
1342
|
+
const currentValue = cm.getValue();
|
|
1343
|
+
const context = resolveQueryContext($ctrl.schema, currentValue.slice(0, cm.indexFromPos(from)));
|
|
1344
|
+
const header = context.operationHeader;
|
|
1345
|
+
const headerText = header ? currentValue.slice(header.keywordIndex, header.openBraceIndex) : '';
|
|
1346
|
+
const existingVariables = parseOperationVariables(headerText);
|
|
1347
|
+
const argumentBindings = buildOperationArgumentBindings(field.args, existingVariables);
|
|
1348
|
+
const fieldArguments = argumentBindings.length
|
|
1349
|
+
? `(${argumentBindings.map((binding) => `${binding.argumentName}: $${binding.variableName}`).join(', ')})`
|
|
1350
|
+
: '';
|
|
1351
|
+
const fieldCall = `${field.name}${fieldArguments}`;
|
|
1352
|
+
|
|
1353
|
+
cm.replaceRange(fieldCall, from, to);
|
|
1354
|
+
|
|
1355
|
+
if (header) {
|
|
1356
|
+
const nextValue = cm.getValue();
|
|
1357
|
+
const updatedHeaderText = nextValue.slice(header.keywordIndex, header.openBraceIndex);
|
|
1358
|
+
const nextExistingVariables = parseOperationVariables(updatedHeaderText);
|
|
1359
|
+
const newBindings = argumentBindings.filter((binding) => !nextExistingVariables.some((variable) => variable.name === binding.variableName));
|
|
1360
|
+
|
|
1361
|
+
if (newBindings.length) {
|
|
1362
|
+
const definitionsText = newBindings.map((binding) => `$${binding.variableName}: ${binding.typeString}`).join(', ');
|
|
1363
|
+
const openParenIndex = updatedHeaderText.indexOf('(');
|
|
1364
|
+
const insertIndex = header.keywordIndex + updatedHeaderText.length;
|
|
1365
|
+
|
|
1366
|
+
if (openParenIndex === -1) {
|
|
1367
|
+
cm.replaceRange(` (${definitionsText})`, cm.posFromIndex(insertIndex), cm.posFromIndex(insertIndex));
|
|
1368
|
+
} else {
|
|
1369
|
+
const closeParenIndex = updatedHeaderText.lastIndexOf(')');
|
|
1370
|
+
const absoluteCloseParenIndex = header.keywordIndex + closeParenIndex;
|
|
1371
|
+
const prefixVariables = updatedHeaderText.slice(openParenIndex + 1, closeParenIndex).trim();
|
|
1372
|
+
const separator = prefixVariables ? ', ' : '';
|
|
1373
|
+
cm.replaceRange(`${separator}${definitionsText}`, cm.posFromIndex(absoluteCloseParenIndex), cm.posFromIndex(absoluteCloseParenIndex));
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (!supportsSelectionSet(field)) {
|
|
1379
|
+
cm.setCursor(cm.posFromIndex(cm.indexFromPos(from) + fieldCall.length));
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const updatedFrom = from;
|
|
1384
|
+
const lineText = cm.getLine(updatedFrom.line) || '';
|
|
1385
|
+
const lineIndent = (lineText.match(/^\s*/) || [''])[0];
|
|
1386
|
+
const indentUnit = cm.getOption('indentUnit') || 2;
|
|
1387
|
+
const childIndent = `${lineIndent}${' '.repeat(indentUnit)}`;
|
|
1388
|
+
const expandedLines = shouldIncludeAllFields ? buildExpandedSelectionSet(field.type, childIndent, new Set()) : [];
|
|
1389
|
+
const blockText = `${fieldCall} {\n${expandedLines.length ? `${expandedLines.join('\n')}\n` : `${childIndent}\n`}${lineIndent}}`;
|
|
1390
|
+
const startIndex = cm.indexFromPos(updatedFrom);
|
|
1391
|
+
const cursorIndex = startIndex + `${fieldCall} {\n${expandedLines.length ? expandedLines[0] : childIndent}`.length;
|
|
1392
|
+
|
|
1393
|
+
cm.replaceRange(blockText, updatedFrom, cm.posFromIndex(startIndex + fieldCall.length));
|
|
1394
|
+
cm.setCursor(cm.posFromIndex(cursorIndex));
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (!supportsSelectionSet(field)) {
|
|
1399
|
+
cm.replaceRange(baseText, from, to);
|
|
1400
|
+
cm.setCursor(cm.posFromIndex(cm.indexFromPos(from) + insertion.cursorOffset));
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const lineText = cm.getLine(from.line) || '';
|
|
1405
|
+
const lineIndent = (lineText.match(/^\s*/) || [''])[0];
|
|
1406
|
+
const indentUnit = cm.getOption('indentUnit') || 2;
|
|
1407
|
+
const childIndent = `${lineIndent}${' '.repeat(indentUnit)}`;
|
|
1408
|
+
const blockText = `${baseText} {\n${childIndent}\n${lineIndent}}`;
|
|
1409
|
+
const startIndex = cm.indexFromPos(from);
|
|
1410
|
+
const cursorIndex = startIndex + `${baseText} {\n${childIndent}`.length;
|
|
1411
|
+
|
|
1412
|
+
cm.replaceRange(blockText, from, to);
|
|
1413
|
+
cm.setCursor(cm.posFromIndex(cursorIndex));
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function buildOperationNameApply(field) {
|
|
1418
|
+
return function applyOperationNameCompletion(cm, from, to) {
|
|
1419
|
+
const operationName = field && field.name ? field.name : '';
|
|
1420
|
+
|
|
1421
|
+
if (!operationName) {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
cm.replaceRange(operationName, from, to);
|
|
1426
|
+
cm.setCursor(cm.posFromIndex(cm.indexFromPos(from) + operationName.length));
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function buildArgInsertion(arg) {
|
|
1431
|
+
return {
|
|
1432
|
+
text: `${arg.name}: `,
|
|
1433
|
+
cursorOffset: arg.name.length + 2
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function getSchemaCompletions(schema) {
|
|
1438
|
+
const completionsByKey = new Map();
|
|
1439
|
+
const rootQueryTypeName = getRootTypeName(schema, 'query');
|
|
1440
|
+
const rootMutationTypeName = getRootTypeName(schema, 'mutation');
|
|
1441
|
+
const rootSubscriptionTypeName = getRootTypeName(schema, 'subscription');
|
|
1442
|
+
|
|
1443
|
+
function pushCompletion(completion) {
|
|
1444
|
+
if (!completion || !completion.text) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
const key = `${completion.text}::${completion.displayText || ''}::${completion.className || ''}`;
|
|
1449
|
+
|
|
1450
|
+
if (!completionsByKey.has(key)) {
|
|
1451
|
+
completionsByKey.set(key, completion);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
graphqlKeywords.forEach((keyword) => {
|
|
1456
|
+
pushCompletion(createCompletion(keyword, 'keyword', keyword, 'keyword'));
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
if (!schema || !Array.isArray(schema.types)) {
|
|
1460
|
+
return Array.from(completionsByKey.values());
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
schema.types.forEach((type) => {
|
|
1464
|
+
if (!type || !type.name || type.name.startsWith('__')) {
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
pushCompletion(createCompletion(type.name, type.kind ? type.kind.toLowerCase() : 'type', `${type.name} (${type.kind})`, 'type'));
|
|
1469
|
+
|
|
1470
|
+
if (Array.isArray(type.fields)) {
|
|
1471
|
+
type.fields.forEach((field) => {
|
|
1472
|
+
if (!field || !field.name) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const insertion = buildFieldInsertion(field);
|
|
1477
|
+
const namedType = getNamedType(field.type);
|
|
1478
|
+
const renderText = namedType ? `${field.name} : ${namedType.name}` : field.name;
|
|
1479
|
+
const completion = createCompletion(insertion.text, 'field', renderText, 'field');
|
|
1480
|
+
completion.cursorOffset = insertion.cursorOffset;
|
|
1481
|
+
|
|
1482
|
+
if (type.name === rootQueryTypeName || type.name === rootMutationTypeName || type.name === rootSubscriptionTypeName) {
|
|
1483
|
+
completion.apply = buildFieldApply(field);
|
|
1484
|
+
completion.wrapInOperation = true;
|
|
1485
|
+
completion.operation = type.name === rootMutationTypeName
|
|
1486
|
+
? 'mutation'
|
|
1487
|
+
: type.name === rootSubscriptionTypeName
|
|
1488
|
+
? 'subscription'
|
|
1489
|
+
: 'query';
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
pushCompletion(completion);
|
|
1493
|
+
|
|
1494
|
+
if (Array.isArray(field.args)) {
|
|
1495
|
+
field.args.forEach((arg) => {
|
|
1496
|
+
if (!arg || !arg.name) {
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const argInsertion = buildArgInsertion(arg);
|
|
1501
|
+
const argType = getNamedType(arg.type);
|
|
1502
|
+
const argRenderText = argType ? `${arg.name}: ${argType.name}` : `${arg.name}:`;
|
|
1503
|
+
const argCompletion = createCompletion(argInsertion.text, 'argument', argRenderText, 'argument');
|
|
1504
|
+
argCompletion.cursorOffset = argInsertion.cursorOffset;
|
|
1505
|
+
pushCompletion(argCompletion);
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (Array.isArray(type.inputFields)) {
|
|
1512
|
+
type.inputFields.forEach((field) => {
|
|
1513
|
+
if (!field || !field.name) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
const insertion = buildArgInsertion(field);
|
|
1518
|
+
const namedType = getNamedType(field.type);
|
|
1519
|
+
const renderText = namedType ? `${field.name}: ${namedType.name}` : `${field.name}:`;
|
|
1520
|
+
const completion = createCompletion(insertion.text, 'input', renderText, 'argument');
|
|
1521
|
+
completion.cursorOffset = insertion.cursorOffset;
|
|
1522
|
+
pushCompletion(completion);
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (Array.isArray(type.enumValues)) {
|
|
1527
|
+
type.enumValues.forEach((value) => {
|
|
1528
|
+
if (!value || !value.name) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
pushCompletion(createCompletion(value.name, 'enum', `${value.name} (enum)`, 'enum'));
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
return Array.from(completionsByKey.values());
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function getFieldCompletionsForType(schema, typeName, options) {
|
|
1541
|
+
const typeMap = getTypeMap(schema);
|
|
1542
|
+
const typeRef = typeMap.get(typeName);
|
|
1543
|
+
|
|
1544
|
+
if (!typeRef || !Array.isArray(typeRef.fields)) {
|
|
1545
|
+
return [];
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
return typeRef.fields
|
|
1549
|
+
.filter((field) => field && field.name)
|
|
1550
|
+
.flatMap((field) => {
|
|
1551
|
+
const insertion = buildFieldInsertion(field);
|
|
1552
|
+
const namedType = getNamedType(field.type);
|
|
1553
|
+
const renderText = namedType ? `${field.name} : ${namedType.name}` : field.name;
|
|
1554
|
+
const completion = createCompletion(insertion.text, 'field', renderText, 'field');
|
|
1555
|
+
completion.cursorOffset = insertion.cursorOffset;
|
|
1556
|
+
completion.apply = buildFieldApply(field);
|
|
1557
|
+
completion.wrapInOperation = Boolean(options && options.wrapInOperation);
|
|
1558
|
+
completion.bindOperationVariables = Boolean(options && options.bindOperationVariables);
|
|
1559
|
+
completion.operation = options && options.operation ? options.operation : 'query';
|
|
1560
|
+
const completions = [completion];
|
|
1561
|
+
|
|
1562
|
+
if (supportsSelectionSet(field)) {
|
|
1563
|
+
const includeAllCompletion = createCompletion(insertion.text, 'field', `* ${renderText} (add all fields)`, 'field');
|
|
1564
|
+
includeAllCompletion.cursorOffset = insertion.cursorOffset;
|
|
1565
|
+
includeAllCompletion.apply = buildFieldApply(field);
|
|
1566
|
+
includeAllCompletion.wrapInOperation = Boolean(options && options.wrapInOperation);
|
|
1567
|
+
includeAllCompletion.bindOperationVariables = Boolean(options && options.bindOperationVariables);
|
|
1568
|
+
includeAllCompletion.operation = options && options.operation ? options.operation : 'query';
|
|
1569
|
+
includeAllCompletion.includeAllFields = true;
|
|
1570
|
+
completions.push(includeAllCompletion);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
return completions;
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function getArgumentCompletions(fieldRef) {
|
|
1578
|
+
if (!fieldRef || !Array.isArray(fieldRef.args)) {
|
|
1579
|
+
return [];
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
return fieldRef.args
|
|
1583
|
+
.filter((arg) => arg && arg.name)
|
|
1584
|
+
.map((arg) => {
|
|
1585
|
+
const insertion = buildArgInsertion(arg);
|
|
1586
|
+
const argType = getNamedType(arg.type);
|
|
1587
|
+
const renderText = argType ? `${arg.name}: ${argType.name}` : `${arg.name}:`;
|
|
1588
|
+
const completion = createCompletion(insertion.text, 'argument', renderText, 'argument');
|
|
1589
|
+
completion.cursorOffset = insertion.cursorOffset;
|
|
1590
|
+
return completion;
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function getContextualCompletions(schema, queryText, cursorIndex) {
|
|
1595
|
+
if (!schema) {
|
|
1596
|
+
return getSchemaCompletions(schema);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const context = resolveQueryContext(schema, queryText.slice(0, cursorIndex));
|
|
1600
|
+
const rootTypeName = getRootTypeName(schema, context.operation);
|
|
1601
|
+
const queryPrefix = queryText.slice(0, cursorIndex);
|
|
1602
|
+
const querySuffix = queryText.slice(cursorIndex);
|
|
1603
|
+
const cleanedPrefix = queryPrefix.replace(/\/\*[\s\S]*?\*\//g, '').replace(/#.*/g, '');
|
|
1604
|
+
const cleanedSuffix = querySuffix.replace(/\/\*[\s\S]*?\*\//g, '').replace(/#.*/g, '');
|
|
1605
|
+
const normalizedPrefix = cleanedPrefix.trim();
|
|
1606
|
+
const normalizedSuffix = cleanedSuffix.trim();
|
|
1607
|
+
const isOperationNameContext = !context.inArguments
|
|
1608
|
+
&& !context.inSelectionSet
|
|
1609
|
+
&& Boolean(rootTypeName)
|
|
1610
|
+
&& /\b(query|mutation|subscription)\s+[A-Za-z_]*$/i.test(cleanedPrefix)
|
|
1611
|
+
&& /^\s*(\([^{}]*\))?\s*\{/.test(cleanedSuffix);
|
|
1612
|
+
const isTopLevelRootSelection = !context.inArguments
|
|
1613
|
+
&& !context.inSelectionSet
|
|
1614
|
+
&& context.parentTypeName
|
|
1615
|
+
&& context.parentTypeName === rootTypeName
|
|
1616
|
+
&& normalizedSuffix === ''
|
|
1617
|
+
&& (normalizedPrefix === '' || /^[A-Za-z_][A-Za-z0-9_]*$/.test(normalizedPrefix));
|
|
1618
|
+
|
|
1619
|
+
if (context.inArguments && context.currentArgumentField) {
|
|
1620
|
+
return getArgumentCompletions(context.currentArgumentField);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
if (context.inSelectionSet && context.parentTypeName) {
|
|
1624
|
+
return getFieldCompletionsForType(schema, context.parentTypeName, {
|
|
1625
|
+
bindOperationVariables: context.parentTypeName === rootTypeName,
|
|
1626
|
+
operation: context.operation
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (isOperationNameContext && rootTypeName) {
|
|
1631
|
+
return getFieldCompletionsForType(schema, rootTypeName, {
|
|
1632
|
+
operation: context.operation
|
|
1633
|
+
}).filter((completion) => !completion.includeAllFields).map((completion) => {
|
|
1634
|
+
const operationName = completion.text.replace(/\(.*/, '');
|
|
1635
|
+
const operationNameCompletion = { ...completion };
|
|
1636
|
+
operationNameCompletion.text = operationName;
|
|
1637
|
+
operationNameCompletion.displayText = operationName;
|
|
1638
|
+
operationNameCompletion.apply = buildOperationNameApply({
|
|
1639
|
+
name: operationName
|
|
1640
|
+
});
|
|
1641
|
+
delete operationNameCompletion.wrapInOperation;
|
|
1642
|
+
delete operationNameCompletion.bindOperationVariables;
|
|
1643
|
+
delete operationNameCompletion.includeAllFields;
|
|
1644
|
+
delete operationNameCompletion.cursorOffset;
|
|
1645
|
+
return operationNameCompletion;
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
if (isTopLevelRootSelection && rootTypeName) {
|
|
1650
|
+
const keywordCompletions = graphqlKeywords
|
|
1651
|
+
.filter((keyword) => ['query', 'mutation', 'subscription'].includes(keyword))
|
|
1652
|
+
.map((keyword) => createCompletion(keyword, 'keyword', keyword, 'keyword'));
|
|
1653
|
+
return [
|
|
1654
|
+
...getFieldCompletionsForType(schema, rootTypeName, {
|
|
1655
|
+
wrapInOperation: true,
|
|
1656
|
+
operation: context.operation
|
|
1657
|
+
}),
|
|
1658
|
+
...keywordCompletions
|
|
1659
|
+
];
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
return getSchemaCompletions(schema);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function getHints(cm) {
|
|
1666
|
+
const cursor = cm.getCursor();
|
|
1667
|
+
const token = cm.getTokenAt(cursor);
|
|
1668
|
+
const tokenStart = (token && typeof token.start === 'number') ? token.start : cursor.ch;
|
|
1669
|
+
const tokenEnd = (token && typeof token.end === 'number') ? token.end : cursor.ch;
|
|
1670
|
+
const rawToken = token && typeof token.string === 'string' ? token.string : '';
|
|
1671
|
+
const normalizedToken = /^[\w_]+$/.test(rawToken) ? rawToken : '';
|
|
1672
|
+
const prefix = normalizedToken.slice(0, Math.max(0, cursor.ch - tokenStart));
|
|
1673
|
+
const from = { line: cursor.line, ch: prefix ? tokenStart : cursor.ch };
|
|
1674
|
+
const to = { line: cursor.line, ch: prefix ? tokenEnd : cursor.ch };
|
|
1675
|
+
const cursorIndex = cm.indexFromPos(cursor);
|
|
1676
|
+
const allCompletions = getContextualCompletions($ctrl.schema, cm.getValue(), cursorIndex);
|
|
1677
|
+
const filtered = allCompletions.filter((completion) => {
|
|
1678
|
+
if (!prefix) {
|
|
1679
|
+
return true;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
return completion.text.toLowerCase().startsWith(prefix.toLowerCase());
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
return {
|
|
1686
|
+
list: filtered.slice(0, 200),
|
|
1687
|
+
from,
|
|
1688
|
+
to
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function triggerAutocomplete(cm) {
|
|
1693
|
+
if (typeof cm.showHint !== 'function') {
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
cm.showHint({
|
|
1698
|
+
completeSingle: false,
|
|
1699
|
+
hint: getHints
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function resetToOperation(operationName) {
|
|
1704
|
+
if (!editor) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
const normalizedOperation = operationName === 'mutation' ? 'mutation' : 'query';
|
|
1709
|
+
const nextValue = `${normalizedOperation} {\n \n}`;
|
|
1710
|
+
|
|
1711
|
+
editor.setValue(nextValue);
|
|
1712
|
+
editor.setCursor({ line: 1, ch: 2 });
|
|
1713
|
+
$ctrl.query = nextValue;
|
|
1714
|
+
$ctrl._lastAppliedQuery = nextValue;
|
|
1715
|
+
$ctrl._lastEditorValue = nextValue;
|
|
1716
|
+
|
|
1717
|
+
if ($ctrl.onChange) {
|
|
1718
|
+
$ctrl.onChange();
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
$ctrl._syncParent();
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
function createEditor() {
|
|
1725
|
+
const textarea = $element[0].querySelector('textarea');
|
|
1726
|
+
|
|
1727
|
+
if (!textarea || typeof window.CodeMirror === 'undefined') {
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
ensureGraphqlMode();
|
|
1732
|
+
|
|
1733
|
+
editor = window.CodeMirror.fromTextArea(textarea, {
|
|
1734
|
+
mode: 'graphql-playground',
|
|
1735
|
+
lineNumbers: true,
|
|
1736
|
+
lineWrapping: true,
|
|
1737
|
+
matchBrackets: true,
|
|
1738
|
+
indentUnit: 2,
|
|
1739
|
+
tabSize: 2,
|
|
1740
|
+
viewportMargin: Infinity,
|
|
1741
|
+
extraKeys: {
|
|
1742
|
+
Tab(cm) {
|
|
1743
|
+
if (cm.state.completionActive) {
|
|
1744
|
+
cm.execCommand('pick');
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (cm.somethingSelected()) {
|
|
1749
|
+
cm.indentSelection('add');
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
cm.replaceSelection(' ', 'end');
|
|
1754
|
+
},
|
|
1755
|
+
'Shift-Tab'(cm) {
|
|
1756
|
+
cm.indentSelection('subtract');
|
|
1757
|
+
},
|
|
1758
|
+
'Ctrl-Space'(cm) {
|
|
1759
|
+
triggerAutocomplete(cm);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
editor.setValue($ctrl.query || '');
|
|
1765
|
+
|
|
1766
|
+
editor.on('change', (instance) => {
|
|
1767
|
+
const nextValue = instance.getValue();
|
|
1768
|
+
|
|
1769
|
+
if ($ctrl.query === nextValue) {
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
$ctrl.query = nextValue;
|
|
1774
|
+
$ctrl._lastEditorValue = nextValue;
|
|
1775
|
+
$ctrl._lastAppliedQuery = nextValue;
|
|
1776
|
+
if ($ctrl.onChange) {
|
|
1777
|
+
$ctrl.onChange();
|
|
1778
|
+
}
|
|
1779
|
+
$ctrl._syncParent();
|
|
1780
|
+
});
|
|
1781
|
+
|
|
1782
|
+
editor.on('inputRead', (cm, change) => {
|
|
1783
|
+
if (!change || change.origin === 'setValue' || typeof cm.showHint !== 'function') {
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
const insertedText = Array.isArray(change.text) ? change.text.join('') : '';
|
|
1788
|
+
|
|
1789
|
+
if (/^[A-Za-z_]$/.test(insertedText)) {
|
|
1790
|
+
triggerAutocomplete(cm);
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
bindHoverHandlers(editor);
|
|
1795
|
+
$ctrl._lastEditorValue = editor.getValue();
|
|
1796
|
+
$ctrl._lastAppliedQuery = editor.getValue();
|
|
1797
|
+
scheduleRefresh();
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function scheduleRefresh() {
|
|
1801
|
+
if (!editor) {
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
window.requestAnimationFrame(() => {
|
|
1806
|
+
if (editor) {
|
|
1807
|
+
editor.refresh();
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
$ctrl._syncParent = function () {
|
|
1813
|
+
const rootScope = $element.scope();
|
|
1814
|
+
|
|
1815
|
+
if (!rootScope) {
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
if (rootScope.$$phase) {
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
rootScope.$applyAsync();
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
$ctrl.$postLink = function () {
|
|
1827
|
+
$ctrl.api = $ctrl.api || {};
|
|
1828
|
+
$ctrl.api.resetToOperation = resetToOperation;
|
|
1829
|
+
$ctrl.api.refresh = scheduleRefresh;
|
|
1830
|
+
createEditor();
|
|
1831
|
+
resizeHandler = () => scheduleRefresh();
|
|
1832
|
+
window.addEventListener('resize', resizeHandler);
|
|
1833
|
+
window.setTimeout(scheduleRefresh, 0);
|
|
1834
|
+
window.setTimeout(scheduleRefresh, 120);
|
|
1835
|
+
};
|
|
1836
|
+
|
|
1837
|
+
$ctrl.$onChanges = function (changes) {
|
|
1838
|
+
if (!editor || !changes.query) {
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
const nextValue = changes.query.currentValue || '';
|
|
1843
|
+
|
|
1844
|
+
if (nextValue === $ctrl._lastAppliedQuery || nextValue === editor.getValue()) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
const cursor = editor.getCursor();
|
|
1849
|
+
editor.setValue(nextValue);
|
|
1850
|
+
editor.setCursor(cursor);
|
|
1851
|
+
$ctrl._lastAppliedQuery = nextValue;
|
|
1852
|
+
$ctrl._lastEditorValue = nextValue;
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1855
|
+
$ctrl.$doCheck = function () {
|
|
1856
|
+
if (!editor) {
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
const nextValue = typeof $ctrl.query === 'string' ? $ctrl.query : '';
|
|
1861
|
+
|
|
1862
|
+
if (nextValue === editor.getValue() || nextValue === $ctrl._lastAppliedQuery) {
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
const cursor = editor.getCursor();
|
|
1867
|
+
editor.setValue(nextValue);
|
|
1868
|
+
editor.setCursor(cursor);
|
|
1869
|
+
$ctrl._lastAppliedQuery = nextValue;
|
|
1870
|
+
$ctrl._lastEditorValue = nextValue;
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
$ctrl.$onDestroy = function () {
|
|
1874
|
+
if (resizeHandler) {
|
|
1875
|
+
window.removeEventListener('resize', resizeHandler);
|
|
1876
|
+
resizeHandler = null;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if (editor) {
|
|
1880
|
+
hideTooltip();
|
|
1881
|
+
editor.toTextArea();
|
|
1882
|
+
editor = null;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
if (tooltipElement && tooltipElement.parentNode) {
|
|
1886
|
+
if (hideTooltipTimeout) {
|
|
1887
|
+
window.clearTimeout(hideTooltipTimeout);
|
|
1888
|
+
hideTooltipTimeout = null;
|
|
1889
|
+
}
|
|
1890
|
+
tooltipElement.parentNode.removeChild(tooltipElement);
|
|
1891
|
+
tooltipElement = null;
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
}]
|
|
1895
|
+
});
|
|
1896
|
+
})(angular.module('app'));
|