@matdata/yasgui 5.4.0 → 5.6.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/build/ts/src/ConfigExportImport.js +3 -0
- package/build/ts/src/ConfigExportImport.js.map +1 -1
- package/build/ts/src/PersistentConfig.d.ts +10 -1
- package/build/ts/src/PersistentConfig.js +39 -0
- package/build/ts/src/PersistentConfig.js.map +1 -1
- package/build/ts/src/Tab.d.ts +1 -1
- package/build/ts/src/Tab.js +76 -78
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +3 -2
- package/build/ts/src/TabSettingsModal.js +453 -121
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/defaults.js +1 -1
- package/build/ts/src/defaults.js.map +1 -1
- package/build/ts/src/index.d.ts +18 -5
- package/build/ts/src/index.js +4 -1
- package/build/ts/src/index.js.map +1 -1
- package/build/ts/src/version.d.ts +1 -1
- package/build/ts/src/version.js +1 -1
- package/build/yasgui.min.css +1 -1
- package/build/yasgui.min.css.map +3 -3
- package/build/yasgui.min.js +128 -121
- package/build/yasgui.min.js.map +3 -3
- package/package.json +1 -1
- package/src/ConfigExportImport.ts +3 -0
- package/src/PersistentConfig.ts +54 -2
- package/src/Tab.ts +147 -106
- package/src/TabSettingsModal.scss +385 -16
- package/src/TabSettingsModal.ts +564 -150
- package/src/defaults.ts +1 -1
- package/src/endpointSelect.scss +12 -0
- package/src/index.ts +31 -3
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -304,6 +304,9 @@ export function parseFromTurtle(turtle: string): Partial<PersistedJson> {
|
|
|
304
304
|
headers: {},
|
|
305
305
|
withCredentials: false,
|
|
306
306
|
adjustQueryBeforeRequest: false,
|
|
307
|
+
basicAuth: undefined,
|
|
308
|
+
bearerAuth: undefined,
|
|
309
|
+
apiKeyAuth: undefined,
|
|
307
310
|
},
|
|
308
311
|
yasr: {
|
|
309
312
|
settings: {},
|
package/src/PersistentConfig.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Storage as YStorage } from "@matdata/yasgui-utils";
|
|
2
|
-
import Yasgui, { EndpointButton } from "./";
|
|
2
|
+
import Yasgui, { EndpointButton, EndpointConfig } from "./";
|
|
3
3
|
import * as Tab from "./Tab";
|
|
4
4
|
export var storageNamespace = "triply";
|
|
5
5
|
export interface PersistedJson {
|
|
@@ -10,9 +10,11 @@ export interface PersistedJson {
|
|
|
10
10
|
lastClosedTab: { index: number; tab: Tab.PersistedJson } | undefined;
|
|
11
11
|
prefixes?: string;
|
|
12
12
|
autoCaptureEnabled?: boolean;
|
|
13
|
-
customEndpointButtons?: EndpointButton[];
|
|
13
|
+
customEndpointButtons?: EndpointButton[]; // Legacy, kept for backwards compatibility
|
|
14
|
+
endpointConfigs?: EndpointConfig[]; // New endpoint-based storage with auth
|
|
14
15
|
theme?: "light" | "dark";
|
|
15
16
|
orientation?: "vertical" | "horizontal";
|
|
17
|
+
showSnippetsBar?: boolean;
|
|
16
18
|
}
|
|
17
19
|
function getDefaults(): PersistedJson {
|
|
18
20
|
return {
|
|
@@ -24,6 +26,7 @@ function getDefaults(): PersistedJson {
|
|
|
24
26
|
prefixes: "",
|
|
25
27
|
autoCaptureEnabled: true,
|
|
26
28
|
customEndpointButtons: [],
|
|
29
|
+
endpointConfigs: [],
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -163,6 +166,55 @@ export default class PersistentConfig {
|
|
|
163
166
|
this.persistedJson.customEndpointButtons = buttons;
|
|
164
167
|
this.toStorage();
|
|
165
168
|
}
|
|
169
|
+
|
|
170
|
+
public getShowSnippetsBar(): boolean | undefined {
|
|
171
|
+
return this.persistedJson.showSnippetsBar;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public setShowSnippetsBar(show: boolean) {
|
|
175
|
+
this.persistedJson.showSnippetsBar = show;
|
|
176
|
+
this.toStorage();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// New endpoint configuration methods
|
|
180
|
+
public getEndpointConfigs(): EndpointConfig[] {
|
|
181
|
+
return this.persistedJson.endpointConfigs || [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public setEndpointConfigs(configs: EndpointConfig[]) {
|
|
185
|
+
this.persistedJson.endpointConfigs = configs;
|
|
186
|
+
this.toStorage();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public addOrUpdateEndpoint(endpoint: string, updates: Partial<Omit<EndpointConfig, "endpoint">>) {
|
|
190
|
+
const configs = this.getEndpointConfigs();
|
|
191
|
+
const existingIndex = configs.findIndex((c) => c.endpoint === endpoint);
|
|
192
|
+
|
|
193
|
+
if (existingIndex >= 0) {
|
|
194
|
+
// Update existing endpoint
|
|
195
|
+
const merged = { ...configs[existingIndex], ...updates };
|
|
196
|
+
if ("authentication" in updates && updates.authentication === undefined) {
|
|
197
|
+
delete merged.authentication;
|
|
198
|
+
}
|
|
199
|
+
configs[existingIndex] = merged;
|
|
200
|
+
} else {
|
|
201
|
+
// Add new endpoint
|
|
202
|
+
configs.push({ endpoint, ...updates });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.setEndpointConfigs(configs);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public getEndpointConfig(endpoint: string): EndpointConfig | undefined {
|
|
209
|
+
const configs = this.getEndpointConfigs();
|
|
210
|
+
return configs.find((c) => c.endpoint === endpoint);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public deleteEndpointConfig(endpoint: string) {
|
|
214
|
+
const configs = this.getEndpointConfigs();
|
|
215
|
+
const filtered = configs.filter((c) => c.endpoint !== endpoint);
|
|
216
|
+
this.setEndpointConfigs(filtered);
|
|
217
|
+
}
|
|
166
218
|
public static clear() {
|
|
167
219
|
const storage = new YStorage(storageNamespace);
|
|
168
220
|
storage.removeNamespace();
|
package/src/Tab.ts
CHANGED
|
@@ -20,9 +20,11 @@ const VERTICAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24" class="svgImg">
|
|
|
20
20
|
<rect x="2" y="2" width="20" height="8" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
21
21
|
<rect x="2" y="12" width="20" height="10" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
22
22
|
</svg>`;
|
|
23
|
+
|
|
23
24
|
export interface PersistedJsonYasr extends YasrPersistentConfig {
|
|
24
25
|
responseSummary: Parser.ResponseSummary;
|
|
25
26
|
}
|
|
27
|
+
|
|
26
28
|
export interface PersistedJson {
|
|
27
29
|
name: string;
|
|
28
30
|
id: string;
|
|
@@ -37,6 +39,7 @@ export interface PersistedJson {
|
|
|
37
39
|
requestConfig: YasguiRequestConfig;
|
|
38
40
|
orientation?: "vertical" | "horizontal";
|
|
39
41
|
}
|
|
42
|
+
|
|
40
43
|
export interface Tab {
|
|
41
44
|
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
42
45
|
|
|
@@ -59,6 +62,7 @@ export interface Tab {
|
|
|
59
62
|
on(event: "autocompletionClose", listener: (tab: Tab) => void): this;
|
|
60
63
|
emit(event: "autocompletionClose", tab: Tab): boolean;
|
|
61
64
|
}
|
|
65
|
+
|
|
62
66
|
export class Tab extends EventEmitter {
|
|
63
67
|
private persistentJson: PersistedJson;
|
|
64
68
|
public yasgui: Yasgui;
|
|
@@ -73,6 +77,7 @@ export class Tab extends EventEmitter {
|
|
|
73
77
|
private settingsModal?: TabSettingsModal;
|
|
74
78
|
private currentOrientation: "vertical" | "horizontal";
|
|
75
79
|
private orientationToggleButton?: HTMLButtonElement;
|
|
80
|
+
|
|
76
81
|
constructor(yasgui: Yasgui, conf: PersistedJson) {
|
|
77
82
|
super();
|
|
78
83
|
if (!conf || conf.id === undefined) throw new Error("Expected a valid configuration to initialize tab with");
|
|
@@ -80,15 +85,19 @@ export class Tab extends EventEmitter {
|
|
|
80
85
|
this.persistentJson = conf;
|
|
81
86
|
this.currentOrientation = this.yasgui.config.orientation || "vertical";
|
|
82
87
|
}
|
|
88
|
+
|
|
83
89
|
public name() {
|
|
84
90
|
return this.persistentJson.name;
|
|
85
91
|
}
|
|
92
|
+
|
|
86
93
|
public getPersistedJson() {
|
|
87
94
|
return this.persistentJson;
|
|
88
95
|
}
|
|
96
|
+
|
|
89
97
|
public getId() {
|
|
90
98
|
return this.persistentJson.id;
|
|
91
99
|
}
|
|
100
|
+
|
|
92
101
|
private draw() {
|
|
93
102
|
if (this.rootEl) return; //aready drawn
|
|
94
103
|
this.rootEl = document.createElement("div");
|
|
@@ -128,10 +137,12 @@ export class Tab extends EventEmitter {
|
|
|
128
137
|
this.initYasr();
|
|
129
138
|
this.yasgui._setPanel(this.persistentJson.id, this.rootEl);
|
|
130
139
|
}
|
|
140
|
+
|
|
131
141
|
public hide() {
|
|
132
142
|
removeClass(this.rootEl, "active");
|
|
133
143
|
this.detachKeyboardListeners();
|
|
134
144
|
}
|
|
145
|
+
|
|
135
146
|
public show() {
|
|
136
147
|
this.draw();
|
|
137
148
|
addClass(this.rootEl, "active");
|
|
@@ -200,9 +211,11 @@ export class Tab extends EventEmitter {
|
|
|
200
211
|
private detachKeyboardListeners() {
|
|
201
212
|
document.removeEventListener("keydown", this.handleKeyDown);
|
|
202
213
|
}
|
|
214
|
+
|
|
203
215
|
public select() {
|
|
204
216
|
this.yasgui.selectTabId(this.persistentJson.id);
|
|
205
217
|
}
|
|
218
|
+
|
|
206
219
|
public close() {
|
|
207
220
|
this.detachKeyboardListeners();
|
|
208
221
|
if (this.yasqe) this.yasqe.abortQuery();
|
|
@@ -222,12 +235,14 @@ export class Tab extends EventEmitter {
|
|
|
222
235
|
this.yasgui.tabElements.get(this.persistentJson.id).delete();
|
|
223
236
|
delete this.yasgui._tabs[this.persistentJson.id];
|
|
224
237
|
}
|
|
238
|
+
|
|
225
239
|
public getQuery() {
|
|
226
240
|
if (!this.yasqe) {
|
|
227
241
|
throw new Error("Cannot get value from uninitialized editor");
|
|
228
242
|
}
|
|
229
243
|
return this.yasqe?.getValue();
|
|
230
244
|
}
|
|
245
|
+
|
|
231
246
|
public setQuery(query: string) {
|
|
232
247
|
if (!this.yasqe) {
|
|
233
248
|
throw new Error("Cannot set value for uninitialized editor");
|
|
@@ -237,12 +252,14 @@ export class Tab extends EventEmitter {
|
|
|
237
252
|
this.emit("change", this, this.persistentJson);
|
|
238
253
|
return this;
|
|
239
254
|
}
|
|
255
|
+
|
|
240
256
|
public getRequestConfig() {
|
|
241
257
|
return this.persistentJson.requestConfig;
|
|
242
258
|
}
|
|
259
|
+
|
|
243
260
|
private initControlbar() {
|
|
244
|
-
this.initEndpointSelectField();
|
|
245
261
|
this.initOrientationToggle();
|
|
262
|
+
this.initEndpointSelectField();
|
|
246
263
|
this.initEndpointButtons();
|
|
247
264
|
if (this.yasgui.config.endpointInfo && this.controlBarEl) {
|
|
248
265
|
this.controlBarEl.appendChild(this.yasgui.config.endpointInfo());
|
|
@@ -313,12 +330,15 @@ export class Tab extends EventEmitter {
|
|
|
313
330
|
}
|
|
314
331
|
}
|
|
315
332
|
}
|
|
333
|
+
|
|
316
334
|
public getYasqe() {
|
|
317
335
|
return this.yasqe;
|
|
318
336
|
}
|
|
337
|
+
|
|
319
338
|
public getYasr() {
|
|
320
339
|
return this.yasr;
|
|
321
340
|
}
|
|
341
|
+
|
|
322
342
|
private initTabSettingsMenu() {
|
|
323
343
|
if (!this.controlBarEl) throw new Error("Need to initialize wrapper elements before drawing tab settings");
|
|
324
344
|
this.settingsModal = new TabSettingsModal(this, this.controlBarEl);
|
|
@@ -359,10 +379,19 @@ export class Tab extends EventEmitter {
|
|
|
359
379
|
// Clear existing buttons
|
|
360
380
|
this.endpointButtonsContainer.innerHTML = "";
|
|
361
381
|
|
|
362
|
-
//
|
|
382
|
+
// Get config buttons (for backwards compatibility)
|
|
363
383
|
const configButtons = this.yasgui.config.endpointButtons || [];
|
|
384
|
+
|
|
385
|
+
// Get endpoint configs where showAsButton is true
|
|
386
|
+
const endpointConfigs = this.yasgui.persistentConfig.getEndpointConfigs();
|
|
387
|
+
const endpointButtons = endpointConfigs
|
|
388
|
+
.filter((config) => config.showAsButton && config.label)
|
|
389
|
+
.map((config) => ({ endpoint: config.endpoint, label: config.label! }));
|
|
390
|
+
|
|
391
|
+
// Also include legacy custom buttons for backwards compatibility
|
|
364
392
|
const customButtons = this.yasgui.persistentConfig.getCustomEndpointButtons();
|
|
365
|
-
|
|
393
|
+
|
|
394
|
+
const allButtons = [...configButtons, ...endpointButtons, ...customButtons];
|
|
366
395
|
|
|
367
396
|
if (allButtons.length === 0) {
|
|
368
397
|
// Hide container if no buttons
|
|
@@ -388,40 +417,32 @@ export class Tab extends EventEmitter {
|
|
|
388
417
|
});
|
|
389
418
|
}
|
|
390
419
|
|
|
391
|
-
private checkEndpointForCors(endpoint: string) {
|
|
392
|
-
if (this.yasgui.config.corsProxy && !(endpoint in Yasgui.corsEnabled)) {
|
|
393
|
-
const askUrl = new URL(endpoint);
|
|
394
|
-
askUrl.searchParams.append("query", "ASK {?x ?y ?z}");
|
|
395
|
-
fetch(askUrl.toString())
|
|
396
|
-
.then(() => {
|
|
397
|
-
Yasgui.corsEnabled[endpoint] = true;
|
|
398
|
-
})
|
|
399
|
-
.catch((e) => {
|
|
400
|
-
// CORS error throws `TypeError: NetworkError when attempting to fetch resource.`
|
|
401
|
-
Yasgui.corsEnabled[endpoint] = e instanceof TypeError ? false : true;
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
420
|
public setEndpoint(endpoint: string, endpointHistory?: string[]) {
|
|
406
421
|
if (endpoint) endpoint = endpoint.trim();
|
|
407
422
|
if (endpointHistory && !eq(endpointHistory, this.yasgui.persistentConfig.getEndpointHistory())) {
|
|
408
423
|
this.yasgui.emit("endpointHistoryChange", this.yasgui, endpointHistory);
|
|
409
424
|
}
|
|
410
|
-
this.checkEndpointForCors(endpoint); //little cost in checking this as we're caching the check results
|
|
411
425
|
|
|
412
426
|
if (this.persistentJson.requestConfig.endpoint !== endpoint) {
|
|
413
427
|
this.persistentJson.requestConfig.endpoint = endpoint;
|
|
414
428
|
this.emit("change", this, this.persistentJson);
|
|
415
429
|
this.emit("endpointChange", this, endpoint);
|
|
430
|
+
|
|
431
|
+
// Auto-track this endpoint in endpoint configs (if not already present)
|
|
432
|
+
if (endpoint && !this.yasgui.persistentConfig.getEndpointConfig(endpoint)) {
|
|
433
|
+
this.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {});
|
|
434
|
+
}
|
|
416
435
|
}
|
|
417
436
|
if (this.endpointSelect instanceof EndpointSelect) {
|
|
418
437
|
this.endpointSelect.setEndpoint(endpoint, endpointHistory);
|
|
419
438
|
}
|
|
420
439
|
return this;
|
|
421
440
|
}
|
|
441
|
+
|
|
422
442
|
public getEndpoint(): string {
|
|
423
443
|
return getAsValue(this.persistentJson.requestConfig.endpoint, this.yasgui);
|
|
424
444
|
}
|
|
445
|
+
|
|
425
446
|
/**
|
|
426
447
|
* Updates the position of the Tab's contextmenu
|
|
427
448
|
* Useful for when being scrolled
|
|
@@ -429,21 +450,26 @@ export class Tab extends EventEmitter {
|
|
|
429
450
|
public updateContextMenu(): void {
|
|
430
451
|
this.getTabListEl().redrawContextMenu();
|
|
431
452
|
}
|
|
453
|
+
|
|
432
454
|
public getShareableLink(baseURL?: string): string {
|
|
433
455
|
return shareLink.createShareLink(baseURL || window.location.href, this);
|
|
434
456
|
}
|
|
457
|
+
|
|
435
458
|
public getShareObject() {
|
|
436
459
|
return shareLink.createShareConfig(this);
|
|
437
460
|
}
|
|
461
|
+
|
|
438
462
|
private getTabListEl(): TabListEl {
|
|
439
463
|
return this.yasgui.tabElements.get(this.persistentJson.id);
|
|
440
464
|
}
|
|
465
|
+
|
|
441
466
|
public setName(newName: string) {
|
|
442
467
|
this.getTabListEl().rename(newName);
|
|
443
468
|
this.persistentJson.name = newName;
|
|
444
469
|
this.emit("change", this, this.persistentJson);
|
|
445
470
|
return this;
|
|
446
471
|
}
|
|
472
|
+
|
|
447
473
|
public hasResults() {
|
|
448
474
|
return !!this.yasr?.results;
|
|
449
475
|
}
|
|
@@ -451,10 +477,12 @@ export class Tab extends EventEmitter {
|
|
|
451
477
|
public getName() {
|
|
452
478
|
return this.persistentJson.name;
|
|
453
479
|
}
|
|
480
|
+
|
|
454
481
|
public query(): Promise<any> {
|
|
455
482
|
if (!this.yasqe) return Promise.reject(new Error("No yasqe editor initialized"));
|
|
456
483
|
return this.yasqe.query();
|
|
457
484
|
}
|
|
485
|
+
|
|
458
486
|
public setRequestConfig(requestConfig: Partial<YasguiRequestConfig>) {
|
|
459
487
|
this.persistentJson.requestConfig = {
|
|
460
488
|
...this.persistentJson.requestConfig,
|
|
@@ -464,6 +492,47 @@ export class Tab extends EventEmitter {
|
|
|
464
492
|
this.emit("change", this, this.persistentJson);
|
|
465
493
|
}
|
|
466
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Get authentication configuration for the current endpoint
|
|
497
|
+
* This retrieves auth from the endpoint-based storage
|
|
498
|
+
*/
|
|
499
|
+
private getAuthForCurrentEndpoint() {
|
|
500
|
+
const endpoint = this.getEndpoint();
|
|
501
|
+
if (!endpoint) return undefined;
|
|
502
|
+
|
|
503
|
+
const endpointConfig = this.yasgui.persistentConfig.getEndpointConfig(endpoint);
|
|
504
|
+
if (!endpointConfig || !endpointConfig.authentication) return undefined;
|
|
505
|
+
|
|
506
|
+
// Convert endpoint auth to requestConfig format
|
|
507
|
+
const auth = endpointConfig.authentication;
|
|
508
|
+
if (auth.type === "basic") {
|
|
509
|
+
return {
|
|
510
|
+
type: "basic" as const,
|
|
511
|
+
config: {
|
|
512
|
+
username: auth.username,
|
|
513
|
+
password: auth.password,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
} else if (auth.type === "bearer") {
|
|
517
|
+
return {
|
|
518
|
+
type: "bearer" as const,
|
|
519
|
+
config: {
|
|
520
|
+
token: auth.token,
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
} else if (auth.type === "apiKey") {
|
|
524
|
+
return {
|
|
525
|
+
type: "apiKey" as const,
|
|
526
|
+
config: {
|
|
527
|
+
headerName: auth.headerName,
|
|
528
|
+
apiKey: auth.apiKey,
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
|
|
467
536
|
/**
|
|
468
537
|
* The Yasgui configuration object may contain a custom request config
|
|
469
538
|
* This request config object can contain getter functions, or plain json
|
|
@@ -503,6 +572,8 @@ export class Tab extends EventEmitter {
|
|
|
503
572
|
persistenceId: null, //yasgui handles persistent storing
|
|
504
573
|
consumeShareLink: null, //not handled by this tab, but by parent yasgui instance
|
|
505
574
|
createShareableLink: () => this.getShareableLink(),
|
|
575
|
+
// Use global showSnippetsBar setting if it exists
|
|
576
|
+
showSnippetsBar: this.yasgui.config.showSnippetsBar !== false,
|
|
506
577
|
requestConfig: () => {
|
|
507
578
|
const processedReqConfig: YasguiRequestConfig = {
|
|
508
579
|
//setting defaults
|
|
@@ -524,18 +595,20 @@ export class Tab extends EventEmitter {
|
|
|
524
595
|
//The adjustQueryBeforeRequest is meant to be a function though, so let's copy that as is
|
|
525
596
|
adjustQueryBeforeRequest: this.yasgui.config.requestConfig.adjustQueryBeforeRequest,
|
|
526
597
|
};
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
598
|
+
|
|
599
|
+
// Inject authentication from endpoint-based storage
|
|
600
|
+
// Only inject endpoint-based auth if the corresponding auth type is not already set
|
|
601
|
+
const endpointAuth = this.getAuthForCurrentEndpoint();
|
|
602
|
+
if (endpointAuth) {
|
|
603
|
+
if (endpointAuth.type === "basic" && typeof processedReqConfig.basicAuth === "undefined") {
|
|
604
|
+
processedReqConfig.basicAuth = endpointAuth.config;
|
|
605
|
+
} else if (endpointAuth.type === "bearer" && typeof processedReqConfig.bearerAuth === "undefined") {
|
|
606
|
+
processedReqConfig.bearerAuth = endpointAuth.config;
|
|
607
|
+
} else if (endpointAuth.type === "apiKey" && typeof processedReqConfig.apiKeyAuth === "undefined") {
|
|
608
|
+
processedReqConfig.apiKeyAuth = endpointAuth.config;
|
|
609
|
+
}
|
|
538
610
|
}
|
|
611
|
+
|
|
539
612
|
return processedReqConfig as PlainRequestConfig;
|
|
540
613
|
},
|
|
541
614
|
};
|
|
@@ -564,6 +637,7 @@ export class Tab extends EventEmitter {
|
|
|
564
637
|
// Add Ctrl+Click handler for URIs
|
|
565
638
|
this.attachYasqeMouseHandler();
|
|
566
639
|
}
|
|
640
|
+
|
|
567
641
|
private destroyYasqe() {
|
|
568
642
|
// As Yasqe extends of CM instead of eventEmitter, it doesn't expose the removeAllListeners function, so we should unregister all events manually
|
|
569
643
|
this.yasqe?.off("blur", this.handleYasqeBlur);
|
|
@@ -578,12 +652,14 @@ export class Tab extends EventEmitter {
|
|
|
578
652
|
this.yasqe?.destroy();
|
|
579
653
|
this.yasqe = undefined;
|
|
580
654
|
}
|
|
655
|
+
|
|
581
656
|
handleYasqeBlur = (yasqe: Yasqe) => {
|
|
582
657
|
this.persistentJson.yasqe.value = yasqe.getValue();
|
|
583
658
|
// Capture prefixes from query if auto-capture is enabled
|
|
584
659
|
this.settingsModal?.capturePrefixesFromQuery();
|
|
585
660
|
this.emit("change", this, this.persistentJson);
|
|
586
661
|
};
|
|
662
|
+
|
|
587
663
|
handleYasqeQuery = (yasqe: Yasqe) => {
|
|
588
664
|
//the blur event might not have fired (e.g. when pressing ctrl-enter). So, we'd like to persist the query as well if needed
|
|
589
665
|
if (yasqe.getValue() !== this.persistentJson.yasqe.value) {
|
|
@@ -592,6 +668,7 @@ export class Tab extends EventEmitter {
|
|
|
592
668
|
}
|
|
593
669
|
this.emit("query", this);
|
|
594
670
|
};
|
|
671
|
+
|
|
595
672
|
handleYasqeQueryAbort = () => {
|
|
596
673
|
this.emit("queryAbort", this);
|
|
597
674
|
// Hide loading indicator in Yasr
|
|
@@ -599,6 +676,7 @@ export class Tab extends EventEmitter {
|
|
|
599
676
|
this.yasr.hideLoading();
|
|
600
677
|
}
|
|
601
678
|
};
|
|
679
|
+
|
|
602
680
|
handleYasqeQueryBefore = () => {
|
|
603
681
|
this.emit("queryBefore", this);
|
|
604
682
|
// Show loading indicator in Yasr
|
|
@@ -606,16 +684,20 @@ export class Tab extends EventEmitter {
|
|
|
606
684
|
this.yasr.showLoading();
|
|
607
685
|
}
|
|
608
686
|
};
|
|
687
|
+
|
|
609
688
|
handleYasqeResize = (_yasqe: Yasqe, newSize: string) => {
|
|
610
689
|
this.persistentJson.yasqe.editorHeight = newSize;
|
|
611
690
|
this.emit("change", this, this.persistentJson);
|
|
612
691
|
};
|
|
692
|
+
|
|
613
693
|
handleAutocompletionShown = (_yasqe: Yasqe, widget: string) => {
|
|
614
694
|
this.emit("autocompletionShown", this, widget);
|
|
615
695
|
};
|
|
696
|
+
|
|
616
697
|
handleAutocompletionClose = (_yasqe: Yasqe) => {
|
|
617
698
|
this.emit("autocompletionClose", this);
|
|
618
699
|
};
|
|
700
|
+
|
|
619
701
|
handleQueryResponse = (_yasqe: Yasqe, response: any, duration: number) => {
|
|
620
702
|
this.emit("queryResponse", this);
|
|
621
703
|
if (!this.yasr) throw new Error("Resultset visualizer not initialized. Cannot draw results");
|
|
@@ -692,7 +774,8 @@ WHERE {
|
|
|
692
774
|
} LIMIT 1000`;
|
|
693
775
|
|
|
694
776
|
// Execute query in background without changing editor content
|
|
695
|
-
|
|
777
|
+
// Note: void operator is intentional - errors are handled in the catch block of executeBackgroundQuery
|
|
778
|
+
void this.executeBackgroundQuery(constructQuery);
|
|
696
779
|
};
|
|
697
780
|
|
|
698
781
|
private async executeBackgroundQuery(query: string) {
|
|
@@ -703,75 +786,21 @@ WHERE {
|
|
|
703
786
|
this.yasr.showLoading();
|
|
704
787
|
this.emit("queryBefore", this);
|
|
705
788
|
|
|
706
|
-
//
|
|
707
|
-
const
|
|
708
|
-
const config = typeof requestConfig === "function" ? requestConfig(this.yasqe) : requestConfig;
|
|
709
|
-
|
|
710
|
-
if (!config.endpoint) {
|
|
711
|
-
throw new Error("No endpoint configured");
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const endpoint = typeof config.endpoint === "function" ? config.endpoint(this.yasqe) : config.endpoint;
|
|
715
|
-
const method = typeof config.method === "function" ? config.method(this.yasqe) : config.method || "POST";
|
|
716
|
-
const headers = typeof config.headers === "function" ? config.headers(this.yasqe) : config.headers || {};
|
|
717
|
-
|
|
718
|
-
// Prepare request
|
|
719
|
-
const searchParams = new URLSearchParams();
|
|
720
|
-
searchParams.append("query", query);
|
|
721
|
-
|
|
722
|
-
// Add any additional args
|
|
723
|
-
if (config.args && Array.isArray(config.args)) {
|
|
724
|
-
config.args.forEach((arg: any) => {
|
|
725
|
-
if (arg.name && arg.value) {
|
|
726
|
-
searchParams.append(arg.name, arg.value);
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
}
|
|
789
|
+
// Track query execution time
|
|
790
|
+
const startTime = Date.now();
|
|
730
791
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
792
|
+
// Use yasqe's executeQuery with custom query and accept header
|
|
793
|
+
const queryResponse = await Yasqe.Sparql.executeQuery(
|
|
794
|
+
this.yasqe,
|
|
795
|
+
undefined, // Use default config
|
|
796
|
+
{
|
|
797
|
+
customQuery: query,
|
|
798
|
+
customAccept: "text/turtle",
|
|
736
799
|
},
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
let url = endpoint;
|
|
740
|
-
if (method === "POST") {
|
|
741
|
-
fetchOptions.headers = {
|
|
742
|
-
...fetchOptions.headers,
|
|
743
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
744
|
-
};
|
|
745
|
-
fetchOptions.body = searchParams.toString();
|
|
746
|
-
} else {
|
|
747
|
-
const urlObj = new URL(endpoint);
|
|
748
|
-
searchParams.forEach((value, key) => {
|
|
749
|
-
urlObj.searchParams.append(key, value);
|
|
750
|
-
});
|
|
751
|
-
url = urlObj.toString();
|
|
752
|
-
}
|
|
800
|
+
);
|
|
753
801
|
|
|
754
|
-
const startTime = Date.now();
|
|
755
|
-
const response = await fetch(url, fetchOptions);
|
|
756
802
|
const duration = Date.now() - startTime;
|
|
757
803
|
|
|
758
|
-
if (!response.ok) {
|
|
759
|
-
throw new Error(`Query failed: ${response.statusText}`);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
const result = await response.text();
|
|
763
|
-
|
|
764
|
-
// Create a query response object similar to what Yasqe produces
|
|
765
|
-
// This includes headers so the Parser can detect the content type
|
|
766
|
-
const queryResponse = {
|
|
767
|
-
ok: response.ok,
|
|
768
|
-
status: response.status,
|
|
769
|
-
statusText: response.statusText,
|
|
770
|
-
headers: response.headers,
|
|
771
|
-
type: response.type,
|
|
772
|
-
content: result,
|
|
773
|
-
};
|
|
774
|
-
|
|
775
804
|
// Set the response in Yasr
|
|
776
805
|
this.yasr.setResponse(queryResponse, duration);
|
|
777
806
|
|
|
@@ -787,10 +816,17 @@ WHERE {
|
|
|
787
816
|
console.error("Background query failed:", error);
|
|
788
817
|
if (this.yasr) {
|
|
789
818
|
this.yasr.hideLoading();
|
|
790
|
-
// Set error response
|
|
819
|
+
// Set error response with detailed HTTP status if available
|
|
820
|
+
const errorObj: any = error;
|
|
821
|
+
let errorText = error instanceof Error ? error.message : String(error);
|
|
822
|
+
|
|
791
823
|
this.yasr.setResponse(
|
|
792
824
|
{
|
|
793
|
-
error:
|
|
825
|
+
error: {
|
|
826
|
+
status: errorObj.status,
|
|
827
|
+
statusText: errorObj.statusText || (error instanceof Error ? error.name : undefined),
|
|
828
|
+
text: errorText,
|
|
829
|
+
},
|
|
794
830
|
},
|
|
795
831
|
0,
|
|
796
832
|
);
|
|
@@ -866,6 +902,7 @@ WHERE {
|
|
|
866
902
|
this.emit("change", this, this.persistentJson);
|
|
867
903
|
});
|
|
868
904
|
}
|
|
905
|
+
|
|
869
906
|
destroy() {
|
|
870
907
|
this.removeAllListeners();
|
|
871
908
|
this.settingsModal?.destroy();
|
|
@@ -875,6 +912,7 @@ WHERE {
|
|
|
875
912
|
this.yasr = undefined;
|
|
876
913
|
this.destroyYasqe();
|
|
877
914
|
}
|
|
915
|
+
|
|
878
916
|
public static getDefaults(yasgui?: Yasgui): PersistedJson {
|
|
879
917
|
return {
|
|
880
918
|
yasqe: {
|
|
@@ -904,11 +942,21 @@ const safeEndpoint = (endpoint: string): string => {
|
|
|
904
942
|
|
|
905
943
|
function getCorsErrorRenderer(tab: Tab) {
|
|
906
944
|
return async (error: Parser.ErrorSummary): Promise<HTMLElement | undefined> => {
|
|
945
|
+
// Only show CORS/mixed-content warning for actual network failures (no status code)
|
|
946
|
+
// AND when querying HTTP from HTTPS
|
|
907
947
|
if (!error.status) {
|
|
908
|
-
// Only show this custom error if
|
|
909
948
|
const shouldReferToHttp =
|
|
910
949
|
new URL(tab.getEndpoint()).protocol === "http:" && window.location.protocol === "https:";
|
|
911
|
-
|
|
950
|
+
|
|
951
|
+
// Check if this looks like a network error (not just missing status)
|
|
952
|
+
const isNetworkError =
|
|
953
|
+
!error.text ||
|
|
954
|
+
error.text.indexOf("Request has been terminated") >= 0 ||
|
|
955
|
+
error.text.indexOf("Failed to fetch") >= 0 ||
|
|
956
|
+
error.text.indexOf("NetworkError") >= 0 ||
|
|
957
|
+
error.text.indexOf("Network request failed") >= 0;
|
|
958
|
+
|
|
959
|
+
if (shouldReferToHttp && isNetworkError) {
|
|
912
960
|
const errorEl = document.createElement("div");
|
|
913
961
|
const errorSpan = document.createElement("p");
|
|
914
962
|
errorSpan.innerHTML = `You are trying to query an HTTP endpoint (<a href="${safeEndpoint(
|
|
@@ -917,14 +965,7 @@ function getCorsErrorRenderer(tab: Tab) {
|
|
|
917
965
|
tab.getEndpoint(),
|
|
918
966
|
)}</a>) from an HTTP<strong>S</strong> website (<a href="${safeEndpoint(window.location.href)}">${safeEndpoint(
|
|
919
967
|
window.location.href,
|
|
920
|
-
)}</a>).<br>This
|
|
921
|
-
if (tab.yasgui.config.nonSslDomain) {
|
|
922
|
-
const errorLink = document.createElement("p");
|
|
923
|
-
errorLink.innerHTML = `As a workaround, you can use the HTTP version of Yasgui instead: <a href="${tab.getShareableLink(
|
|
924
|
-
tab.yasgui.config.nonSslDomain,
|
|
925
|
-
)}" target="_blank">${tab.yasgui.config.nonSslDomain}</a>`;
|
|
926
|
-
errorSpan.appendChild(errorLink);
|
|
927
|
-
}
|
|
968
|
+
)}</a>).<br>This can be blocked in modern browsers, see <a target="_blank" rel="noopener noreferrer" href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy">https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy</a>. See also <a href="https://yasgui-doc.matdata.eu/docs/user-guide#querying-local-endpoints">the YasGUI documentation</a> for possible workarounds.`;
|
|
928
969
|
errorEl.appendChild(errorSpan);
|
|
929
970
|
return errorEl;
|
|
930
971
|
}
|