@matdata/yasqe 5.9.0 → 5.10.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/__tests__/share-test.d.ts +1 -0
- package/build/ts/src/__tests__/share-test.js +202 -0
- package/build/ts/src/__tests__/share-test.js.map +1 -0
- package/build/ts/src/index.d.ts +3 -0
- package/build/ts/src/index.js +176 -49
- package/build/ts/src/index.js.map +1 -1
- package/build/ts/src/sparql.d.ts +3 -0
- package/build/ts/src/sparql.js +176 -16
- package/build/ts/src/sparql.js.map +1 -1
- package/build/yasqe.min.css +1 -1
- package/build/yasqe.min.css.map +3 -3
- package/build/yasqe.min.js +84 -78
- package/build/yasqe.min.js.map +3 -3
- package/package.json +1 -1
- package/src/__tests__/share-test.ts +237 -0
- package/src/index.ts +214 -65
- package/src/scss/buttons.scss +107 -4
- package/src/sparql.ts +232 -20
package/package.json
CHANGED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Share Functionality Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
|
|
8
|
+
describe("Share Functionality", () => {
|
|
9
|
+
describe("Shell String Escaping", () => {
|
|
10
|
+
it("should escape single quotes for shell commands", () => {
|
|
11
|
+
const input = "test'value";
|
|
12
|
+
const escaped = input.replace(/'/g, "'\\''");
|
|
13
|
+
expect(escaped).to.equal("test'\\''value");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should handle strings with multiple single quotes", () => {
|
|
17
|
+
const input = "it's a 'test'";
|
|
18
|
+
const escaped = input.replace(/'/g, "'\\''");
|
|
19
|
+
expect(escaped).to.equal("it'\\''s a '\\''test'\\''");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("PowerShell String Escaping", () => {
|
|
24
|
+
it("should escape backticks, double quotes, and dollar signs", () => {
|
|
25
|
+
const input = 'test"value$var`tick';
|
|
26
|
+
const escaped = input.replace(/`/g, "``").replace(/"/g, '`"').replace(/\$/g, "`$");
|
|
27
|
+
expect(escaped).to.equal('test`"value`$var``tick');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should handle strings with multiple special characters", () => {
|
|
31
|
+
const input = '$test`"value"';
|
|
32
|
+
const escaped = input.replace(/`/g, "``").replace(/"/g, '`"').replace(/\$/g, "`$");
|
|
33
|
+
expect(escaped).to.equal('`$test```"value`"');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("URL Normalization", () => {
|
|
38
|
+
it("should detect absolute URLs", () => {
|
|
39
|
+
const url = "https://example.com/sparql";
|
|
40
|
+
expect(url.indexOf("http")).to.equal(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should detect relative URLs", () => {
|
|
44
|
+
const url = "/sparql";
|
|
45
|
+
expect(url.indexOf("http")).to.equal(-1);
|
|
46
|
+
expect(url.indexOf("/")).to.equal(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should handle relative path normalization", () => {
|
|
50
|
+
const pathname = "/app/editor";
|
|
51
|
+
const relativePath = "sparql";
|
|
52
|
+
|
|
53
|
+
let basePath = pathname;
|
|
54
|
+
if (!basePath.endsWith("/")) {
|
|
55
|
+
const lastSlashIndex = basePath.lastIndexOf("/");
|
|
56
|
+
basePath = lastSlashIndex >= 0 ? basePath.substring(0, lastSlashIndex + 1) : "/";
|
|
57
|
+
}
|
|
58
|
+
const result = basePath + relativePath;
|
|
59
|
+
|
|
60
|
+
expect(result).to.equal("/app/sparql");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should handle pathname ending with slash", () => {
|
|
64
|
+
const pathname = "/app/";
|
|
65
|
+
const relativePath = "sparql";
|
|
66
|
+
|
|
67
|
+
let basePath = pathname;
|
|
68
|
+
if (!basePath.endsWith("/")) {
|
|
69
|
+
const lastSlashIndex = basePath.lastIndexOf("/");
|
|
70
|
+
basePath = lastSlashIndex >= 0 ? basePath.substring(0, lastSlashIndex + 1) : "/";
|
|
71
|
+
}
|
|
72
|
+
const result = basePath + relativePath;
|
|
73
|
+
|
|
74
|
+
expect(result).to.equal("/app/sparql");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("Command Generation Format", () => {
|
|
79
|
+
it("should format cURL commands with proper line breaks", () => {
|
|
80
|
+
const segments = [
|
|
81
|
+
"curl",
|
|
82
|
+
"'https://example.com/sparql'",
|
|
83
|
+
"--data",
|
|
84
|
+
"'query=SELECT'",
|
|
85
|
+
"-X",
|
|
86
|
+
"POST",
|
|
87
|
+
"-H",
|
|
88
|
+
"'Authorization: Bearer token'",
|
|
89
|
+
];
|
|
90
|
+
const curlString = segments.join(" \\\n ");
|
|
91
|
+
|
|
92
|
+
expect(curlString).to.include("curl");
|
|
93
|
+
expect(curlString).to.include("\\\n");
|
|
94
|
+
expect(curlString).to.include("https://example.com/sparql");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should format PowerShell commands with proper structure", () => {
|
|
98
|
+
const lines = [
|
|
99
|
+
"$params = @{",
|
|
100
|
+
' Uri = "https://example.com/sparql"',
|
|
101
|
+
' Method = "Post"',
|
|
102
|
+
" Headers = @{",
|
|
103
|
+
' "Accept" = "application/sparql-results+json"',
|
|
104
|
+
' "Authorization" = "Bearer token"',
|
|
105
|
+
" }",
|
|
106
|
+
' ContentType = "application/x-www-form-urlencoded"',
|
|
107
|
+
' Body = "query=SELECT"',
|
|
108
|
+
' OutFile = "result.json"',
|
|
109
|
+
"}",
|
|
110
|
+
"",
|
|
111
|
+
"Invoke-WebRequest @params",
|
|
112
|
+
];
|
|
113
|
+
const psString = lines.join("\n");
|
|
114
|
+
|
|
115
|
+
expect(psString).to.include("$params");
|
|
116
|
+
expect(psString).to.include("Invoke-WebRequest");
|
|
117
|
+
expect(psString).to.include("Headers");
|
|
118
|
+
expect(psString).to.include("OutFile");
|
|
119
|
+
expect(psString).to.include("Accept");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should format wget commands with proper line breaks", () => {
|
|
123
|
+
const segments = [
|
|
124
|
+
"wget",
|
|
125
|
+
"'https://example.com/sparql'",
|
|
126
|
+
"--body-data",
|
|
127
|
+
"'query=SELECT'",
|
|
128
|
+
"--method",
|
|
129
|
+
"POST",
|
|
130
|
+
"--header",
|
|
131
|
+
"'Authorization: Bearer token'",
|
|
132
|
+
"-O -",
|
|
133
|
+
];
|
|
134
|
+
const wgetString = segments.join(" \\\n ");
|
|
135
|
+
|
|
136
|
+
expect(wgetString).to.include("wget");
|
|
137
|
+
expect(wgetString).to.include("\\\n");
|
|
138
|
+
expect(wgetString).to.include("--body-data");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("URL Normalization", () => {
|
|
143
|
+
it("should handle absolute URLs", () => {
|
|
144
|
+
const url = "https://example.com/sparql";
|
|
145
|
+
expect(url.indexOf("http")).to.equal(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should detect relative URLs", () => {
|
|
149
|
+
const url = "/sparql";
|
|
150
|
+
expect(url.indexOf("http")).to.equal(-1);
|
|
151
|
+
expect(url.indexOf("/")).to.equal(0);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should detect relative paths", () => {
|
|
155
|
+
const url = "sparql";
|
|
156
|
+
expect(url.indexOf("http")).to.equal(-1);
|
|
157
|
+
expect(url.indexOf("/")).to.not.equal(0);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("Authentication Detection Logic", () => {
|
|
162
|
+
it("should detect Authorization header", () => {
|
|
163
|
+
const headers = { Authorization: "Bearer token" };
|
|
164
|
+
expect(headers["Authorization"]).to.exist;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should detect API key headers by name", () => {
|
|
168
|
+
const headerName = "X-API-Key";
|
|
169
|
+
const lowerHeader = headerName.toLowerCase();
|
|
170
|
+
expect(lowerHeader).to.include("key");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should detect token headers by name", () => {
|
|
174
|
+
const headerName = "X-Auth-Token";
|
|
175
|
+
const lowerHeader = headerName.toLowerCase();
|
|
176
|
+
expect(lowerHeader).to.include("token");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should detect auth headers by name", () => {
|
|
180
|
+
const headerName = "X-Custom-Auth";
|
|
181
|
+
const lowerHeader = headerName.toLowerCase();
|
|
182
|
+
expect(lowerHeader).to.include("auth");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should not detect non-auth headers", () => {
|
|
186
|
+
const headerName = "Content-Type";
|
|
187
|
+
const lowerHeader = headerName.toLowerCase();
|
|
188
|
+
expect(lowerHeader).to.not.include("key");
|
|
189
|
+
expect(lowerHeader).to.not.include("token");
|
|
190
|
+
expect(lowerHeader).to.not.include("auth");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("String Escaping", () => {
|
|
195
|
+
it("should escape double quotes in PowerShell", () => {
|
|
196
|
+
const value = 'test"value';
|
|
197
|
+
const escaped = value.replace(/"/g, '`"');
|
|
198
|
+
expect(escaped).to.equal('test`"value');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should handle single quotes in shell commands", () => {
|
|
202
|
+
const value = "test'value";
|
|
203
|
+
const wrapped = `'${value}'`;
|
|
204
|
+
expect(wrapped).to.equal("'test'value'");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("HTTP Method Handling", () => {
|
|
209
|
+
it("should use POST for SPARQL updates", () => {
|
|
210
|
+
const queryMode: string = "update";
|
|
211
|
+
const method = queryMode === "update" ? "POST" : "GET";
|
|
212
|
+
expect(method).to.equal("POST");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should default to configured method for SPARQL queries", () => {
|
|
216
|
+
const queryMode: string = "query";
|
|
217
|
+
const method = queryMode === "update" ? "POST" : "GET";
|
|
218
|
+
expect(method).to.equal("GET");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("Query String Formatting", () => {
|
|
223
|
+
it("should format query parameters", () => {
|
|
224
|
+
const params = { query: "SELECT * WHERE { ?s ?p ?o }", format: "json" };
|
|
225
|
+
const keys = Object.keys(params);
|
|
226
|
+
expect(keys).to.include("query");
|
|
227
|
+
expect(keys).to.include("format");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should handle URL encoding", () => {
|
|
231
|
+
const query = "SELECT * WHERE { ?s ?p ?o }";
|
|
232
|
+
const encoded = encodeURIComponent(query);
|
|
233
|
+
expect(encoded).to.include("SELECT");
|
|
234
|
+
expect(encoded).to.not.include(" ");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,12 @@ import CodeMirror from "./CodeMirror";
|
|
|
18
18
|
import { YasqeAjaxConfig } from "./sparql";
|
|
19
19
|
import { spfmt } from "sparql-formatter";
|
|
20
20
|
|
|
21
|
+
// Toast notification timing constants
|
|
22
|
+
const TOAST_DEFAULT_DURATION = 3000; // 3 seconds
|
|
23
|
+
const TOAST_WARNING_DURATION = 4000; // 4 seconds for warnings
|
|
24
|
+
const TOAST_WARNING_DELAY = 500; // Delay before showing auth warning
|
|
25
|
+
const TOAST_FADEOUT_DURATION = 300; // Fade out animation duration
|
|
26
|
+
|
|
21
27
|
export interface Yasqe {
|
|
22
28
|
on(eventName: "query", handler: (instance: Yasqe, req: Request, abortController?: AbortController) => void): void;
|
|
23
29
|
off(eventName: "query", handler: (instance: Yasqe, req: Request, abortController?: AbortController) => void): void;
|
|
@@ -262,87 +268,217 @@ export class Yasqe extends CodeMirror {
|
|
|
262
268
|
let popup: HTMLDivElement | undefined = document.createElement("div");
|
|
263
269
|
popup.className = "yasqe_sharePopup";
|
|
264
270
|
buttons.appendChild(popup);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
+
|
|
272
|
+
// Toast notification element for warnings
|
|
273
|
+
let toastElement: HTMLDivElement | undefined;
|
|
274
|
+
let toastTimeout: number | undefined;
|
|
275
|
+
|
|
276
|
+
const showToast = (
|
|
277
|
+
message: string,
|
|
278
|
+
duration: number = TOAST_DEFAULT_DURATION,
|
|
279
|
+
type: "info" | "warning" = "info",
|
|
280
|
+
) => {
|
|
281
|
+
// Remove existing toast if any
|
|
282
|
+
if (toastElement) {
|
|
283
|
+
toastElement.remove();
|
|
284
|
+
}
|
|
285
|
+
// Clear existing timeout
|
|
286
|
+
if (toastTimeout !== undefined) {
|
|
287
|
+
clearTimeout(toastTimeout);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
toastElement = document.createElement("div");
|
|
291
|
+
toastElement.className = type === "warning" ? "yasqe_toast yasqe_toast-warning" : "yasqe_toast";
|
|
292
|
+
|
|
293
|
+
// Add warning icon for warning toasts
|
|
294
|
+
if (type === "warning") {
|
|
295
|
+
const iconWrapper = document.createElement("span");
|
|
296
|
+
iconWrapper.className = "yasqe_toast-icon";
|
|
297
|
+
const icon = drawSvgStringAsElement(imgs.warning);
|
|
298
|
+
iconWrapper.appendChild(icon);
|
|
299
|
+
toastElement.appendChild(iconWrapper);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const messageSpan = document.createElement("span");
|
|
303
|
+
messageSpan.className = "yasqe_toast-message";
|
|
304
|
+
messageSpan.textContent = message;
|
|
305
|
+
toastElement.appendChild(messageSpan);
|
|
306
|
+
|
|
307
|
+
document.body.appendChild(toastElement);
|
|
308
|
+
|
|
309
|
+
// Auto-remove after duration
|
|
310
|
+
toastTimeout = window.setTimeout(() => {
|
|
311
|
+
if (toastElement) {
|
|
312
|
+
toastElement.classList.add("yasqe_toast-fadeout");
|
|
313
|
+
setTimeout(() => {
|
|
314
|
+
toastElement?.remove();
|
|
315
|
+
toastElement = undefined;
|
|
316
|
+
}, TOAST_FADEOUT_DURATION);
|
|
271
317
|
}
|
|
272
|
-
},
|
|
273
|
-
true,
|
|
274
|
-
);
|
|
275
|
-
var input = document.createElement("input");
|
|
276
|
-
input.type = "text";
|
|
277
|
-
input.value = this.config.createShareableLink(this);
|
|
278
|
-
|
|
279
|
-
input.onfocus = function () {
|
|
280
|
-
input.select();
|
|
318
|
+
}, duration);
|
|
281
319
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
320
|
+
|
|
321
|
+
// Create event listener that can be removed
|
|
322
|
+
const closePopupHandler = (event: MouseEvent) => {
|
|
323
|
+
if (popup && event.target !== popup && !popup.contains(<any>event.target)) {
|
|
324
|
+
popup.remove();
|
|
325
|
+
popup = undefined;
|
|
326
|
+
// Clean up toast when popup closes
|
|
327
|
+
if (toastElement) {
|
|
328
|
+
toastElement.remove();
|
|
329
|
+
toastElement = undefined;
|
|
330
|
+
}
|
|
331
|
+
if (toastTimeout !== undefined) {
|
|
332
|
+
clearTimeout(toastTimeout);
|
|
333
|
+
}
|
|
334
|
+
// Remove this event listener to prevent memory leak
|
|
335
|
+
document.body.removeEventListener("click", closePopupHandler, true);
|
|
336
|
+
}
|
|
286
337
|
};
|
|
338
|
+
|
|
339
|
+
document.body.addEventListener("click", closePopupHandler, true);
|
|
340
|
+
|
|
287
341
|
popup.innerHTML = "";
|
|
288
342
|
|
|
289
|
-
|
|
290
|
-
|
|
343
|
+
// Helper function to copy text to clipboard
|
|
344
|
+
const copyToClipboard = async (text: string, buttonText: string, hasAuth: boolean = false) => {
|
|
345
|
+
try {
|
|
346
|
+
// Check if Clipboard API is available
|
|
347
|
+
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
|
348
|
+
// Fallback for older browsers or non-secure contexts
|
|
349
|
+
const textArea = document.createElement("textarea");
|
|
350
|
+
textArea.value = text;
|
|
351
|
+
textArea.style.position = "fixed";
|
|
352
|
+
textArea.style.left = "-999999px";
|
|
353
|
+
document.body.appendChild(textArea);
|
|
354
|
+
textArea.select();
|
|
355
|
+
try {
|
|
356
|
+
document.execCommand("copy");
|
|
357
|
+
document.body.removeChild(textArea);
|
|
358
|
+
showToast(`${buttonText} copied to clipboard!`);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
document.body.removeChild(textArea);
|
|
361
|
+
throw new Error("Copy command not supported");
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
await navigator.clipboard.writeText(text);
|
|
365
|
+
showToast(`${buttonText} copied to clipboard!`);
|
|
366
|
+
}
|
|
291
367
|
|
|
292
|
-
|
|
368
|
+
// Show warning if credentials are included
|
|
369
|
+
if (hasAuth) {
|
|
370
|
+
setTimeout(() => {
|
|
371
|
+
showToast(
|
|
372
|
+
"Warning: Authentication credentials included in copied content",
|
|
373
|
+
TOAST_WARNING_DURATION,
|
|
374
|
+
"warning",
|
|
375
|
+
);
|
|
376
|
+
}, TOAST_WARNING_DELAY);
|
|
377
|
+
}
|
|
378
|
+
} catch (err) {
|
|
379
|
+
console.error("Failed to copy to clipboard:", err);
|
|
380
|
+
showToast("Failed to copy to clipboard. Please copy manually.", 2000);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
293
383
|
|
|
294
|
-
|
|
384
|
+
// Create title
|
|
385
|
+
const title = document.createElement("div");
|
|
386
|
+
title.className = "yasqe_sharePopup_title";
|
|
387
|
+
title.textContent = "Share Query";
|
|
388
|
+
popup.appendChild(title);
|
|
389
|
+
|
|
390
|
+
// Create button container
|
|
391
|
+
const buttonContainer = document.createElement("div");
|
|
392
|
+
buttonContainer.className = "yasqe_sharePopup_buttons";
|
|
393
|
+
popup.appendChild(buttonContainer);
|
|
394
|
+
|
|
395
|
+
// URL button
|
|
396
|
+
const urlBtn = document.createElement("button");
|
|
397
|
+
urlBtn.innerText = "Copy URL";
|
|
398
|
+
urlBtn.className = "yasqe_btn yasqe_btn-sm yasqe_shareBtn";
|
|
399
|
+
buttonContainer.appendChild(urlBtn);
|
|
400
|
+
urlBtn.onclick = async () => {
|
|
401
|
+
const url = this.config.createShareableLink(this);
|
|
402
|
+
await copyToClipboard(url, "URL");
|
|
403
|
+
};
|
|
295
404
|
|
|
296
|
-
//
|
|
297
|
-
const popupInputButtons: HTMLButtonElement[] = [];
|
|
405
|
+
// URL Shorten button (if configured)
|
|
298
406
|
const createShortLink = this.config.createShortLink;
|
|
299
407
|
if (createShortLink) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
input.replaceWith(errSpan);
|
|
325
|
-
},
|
|
326
|
-
);
|
|
408
|
+
const shortenBtn = document.createElement("button");
|
|
409
|
+
shortenBtn.innerText = "Shorten URL";
|
|
410
|
+
shortenBtn.className = "yasqe_btn yasqe_btn-sm yasqe_shareBtn";
|
|
411
|
+
buttonContainer.appendChild(shortenBtn);
|
|
412
|
+
shortenBtn.onclick = async () => {
|
|
413
|
+
shortenBtn.disabled = true;
|
|
414
|
+
shortenBtn.innerText = "Shortening...";
|
|
415
|
+
try {
|
|
416
|
+
const longUrl = this.config.createShareableLink(this);
|
|
417
|
+
const shortUrl = await createShortLink(this, longUrl);
|
|
418
|
+
await copyToClipboard(shortUrl, "Shortened URL");
|
|
419
|
+
shortenBtn.innerText = "Shorten URL";
|
|
420
|
+
shortenBtn.disabled = false;
|
|
421
|
+
} catch (err) {
|
|
422
|
+
shortenBtn.innerText = "Shorten URL";
|
|
423
|
+
shortenBtn.disabled = false;
|
|
424
|
+
let errorMsg = "Failed to shorten URL";
|
|
425
|
+
if (typeof err === "string" && err.length !== 0) {
|
|
426
|
+
errorMsg = err;
|
|
427
|
+
} else if ((err as any).message && (err as any).message.length !== 0) {
|
|
428
|
+
errorMsg = (err as any).message;
|
|
429
|
+
}
|
|
430
|
+
showToast(errorMsg, 3000);
|
|
431
|
+
}
|
|
327
432
|
};
|
|
328
433
|
}
|
|
329
434
|
|
|
435
|
+
// cURL button
|
|
330
436
|
const curlBtn = document.createElement("button");
|
|
331
|
-
|
|
332
|
-
curlBtn.
|
|
333
|
-
curlBtn
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
437
|
+
curlBtn.innerText = "Copy cURL";
|
|
438
|
+
curlBtn.className = "yasqe_btn yasqe_btn-sm yasqe_shareBtn";
|
|
439
|
+
buttonContainer.appendChild(curlBtn);
|
|
440
|
+
curlBtn.onclick = async () => {
|
|
441
|
+
const curlString = this.getAsCurlString();
|
|
442
|
+
const hasAuth = this.hasAuthenticationCredentials();
|
|
443
|
+
await copyToClipboard(curlString, "cURL command", hasAuth);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// PowerShell button
|
|
447
|
+
const psBtn = document.createElement("button");
|
|
448
|
+
psBtn.innerText = "Copy PowerShell";
|
|
449
|
+
psBtn.className = "yasqe_btn yasqe_btn-sm yasqe_shareBtn";
|
|
450
|
+
buttonContainer.appendChild(psBtn);
|
|
451
|
+
psBtn.onclick = async () => {
|
|
452
|
+
const psString = this.getAsPowerShellString();
|
|
453
|
+
const hasAuth = this.hasAuthenticationCredentials();
|
|
454
|
+
await copyToClipboard(psString, "PowerShell command", hasAuth);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// wget button
|
|
458
|
+
const wgetBtn = document.createElement("button");
|
|
459
|
+
wgetBtn.innerText = "Copy wget";
|
|
460
|
+
wgetBtn.className = "yasqe_btn yasqe_btn-sm yasqe_shareBtn";
|
|
461
|
+
buttonContainer.appendChild(wgetBtn);
|
|
462
|
+
wgetBtn.onclick = async () => {
|
|
463
|
+
const wgetString = this.getAsWgetString();
|
|
464
|
+
const hasAuth = this.hasAuthenticationCredentials();
|
|
465
|
+
await copyToClipboard(wgetString, "wget command", hasAuth);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Position popup after layout is complete
|
|
469
|
+
const positionPopup = () => {
|
|
470
|
+
if (!popup) return;
|
|
471
|
+
const svgPos = svgShare.getBoundingClientRect();
|
|
472
|
+
popup.style.top = svgShare.offsetTop + svgPos.height + "px";
|
|
473
|
+
popup.style.left = svgShare.offsetLeft + svgShare.clientWidth - popup.clientWidth + "px";
|
|
340
474
|
};
|
|
341
475
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
476
|
+
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
|
477
|
+
window.requestAnimationFrame(positionPopup);
|
|
478
|
+
} else {
|
|
479
|
+
// Fallback for environments without requestAnimationFrame
|
|
480
|
+
setTimeout(positionPopup, 0);
|
|
481
|
+
}
|
|
346
482
|
};
|
|
347
483
|
}
|
|
348
484
|
/**
|
|
@@ -1398,6 +1534,19 @@ export class Yasqe extends CodeMirror {
|
|
|
1398
1534
|
return Sparql.getAsCurlString(this, config);
|
|
1399
1535
|
}
|
|
1400
1536
|
|
|
1537
|
+
public getAsPowerShellString(config?: Sparql.YasqeAjaxConfig): string {
|
|
1538
|
+
return Sparql.getAsPowerShellString(this, config);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
public getAsWgetString(config?: Sparql.YasqeAjaxConfig): string {
|
|
1542
|
+
return Sparql.getAsWgetString(this, config);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
public hasAuthenticationCredentials(config?: Sparql.YasqeAjaxConfig): boolean {
|
|
1546
|
+
const ajaxConfig = Sparql.getAjaxConfig(this, config);
|
|
1547
|
+
return ajaxConfig ? Sparql.hasAuthenticationCredentials(ajaxConfig) : false;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1401
1550
|
public abortQuery() {
|
|
1402
1551
|
if (this.req) {
|
|
1403
1552
|
if (this.abortController) {
|