@matdata/yasqe 4.6.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/build/ts/grammar/tokenizer.d.ts +37 -0
  3. package/build/ts/src/CodeMirror.d.ts +21 -0
  4. package/build/ts/src/autocompleters/classes.d.ts +3 -0
  5. package/build/ts/src/autocompleters/index.d.ts +39 -0
  6. package/build/ts/src/autocompleters/prefixes.d.ts +3 -0
  7. package/build/ts/src/autocompleters/properties.d.ts +3 -0
  8. package/build/ts/src/autocompleters/variables.d.ts +3 -0
  9. package/build/ts/src/defaults.d.ts +75 -0
  10. package/build/ts/src/imgs.d.ts +7 -0
  11. package/build/ts/src/index.d.ts +303 -0
  12. package/build/ts/src/prefixFold.d.ts +8 -0
  13. package/build/ts/src/prefixUtils.d.ts +9 -0
  14. package/build/ts/src/sparql.d.ts +20 -0
  15. package/build/ts/src/tokenUtils.d.ts +4 -0
  16. package/build/ts/src/tooltip.d.ts +2 -0
  17. package/build/ts/src/trie.d.ts +13 -0
  18. package/build/yasqe.html +108 -0
  19. package/build/yasqe.min.css +2 -0
  20. package/build/yasqe.min.css.map +1 -0
  21. package/build/yasqe.min.js +3 -0
  22. package/build/yasqe.min.js.LICENSE.txt +3 -0
  23. package/build/yasqe.min.js.map +1 -0
  24. package/grammar/README.md +12 -0
  25. package/grammar/_tokenizer-table.js +4776 -0
  26. package/grammar/build.sh +2 -0
  27. package/grammar/sparql11-grammar.pl +834 -0
  28. package/grammar/sparqljs-browser-min.js +4535 -0
  29. package/grammar/tokenizer.ts +729 -0
  30. package/grammar/util/gen_ll1.pl +37 -0
  31. package/grammar/util/gen_sparql11.pl +11 -0
  32. package/grammar/util/ll1.pl +175 -0
  33. package/grammar/util/output_to_javascript.pl +75 -0
  34. package/grammar/util/prune.pl +49 -0
  35. package/grammar/util/rewrite.pl +104 -0
  36. package/package.json +40 -0
  37. package/src/CodeMirror.ts +54 -0
  38. package/src/autocompleters/classes.ts +32 -0
  39. package/src/autocompleters/index.ts +346 -0
  40. package/src/autocompleters/prefixes.ts +130 -0
  41. package/src/autocompleters/properties.ts +28 -0
  42. package/src/autocompleters/show-hint.scss +38 -0
  43. package/src/autocompleters/variables.ts +52 -0
  44. package/src/defaults.ts +149 -0
  45. package/src/imgs.ts +14 -0
  46. package/src/index.ts +1089 -0
  47. package/src/prefixFold.ts +93 -0
  48. package/src/prefixUtils.ts +65 -0
  49. package/src/scss/buttons.scss +275 -0
  50. package/src/scss/codemirrorMods.scss +36 -0
  51. package/src/scss/yasqe.scss +89 -0
  52. package/src/sparql.ts +215 -0
  53. package/src/tokenUtils.ts +121 -0
  54. package/src/tooltip.ts +31 -0
  55. package/src/trie.ts +238 -0
package/src/index.ts ADDED
@@ -0,0 +1,1089 @@
1
+ require("./scss/yasqe.scss");
2
+ require("./scss/buttons.scss");
3
+ import { findFirstPrefixLine } from "./prefixFold";
4
+ import { getPrefixesFromQuery, addPrefixes, removePrefixes, Prefixes } from "./prefixUtils";
5
+ import { getPreviousNonWsToken, getNextNonWsToken, getCompleteToken } from "./tokenUtils";
6
+ import * as sparql11Mode from "../grammar/tokenizer";
7
+ import { Storage as YStorage } from "@matdata/yasgui-utils";
8
+ import * as queryString from "query-string";
9
+ import tooltip from "./tooltip";
10
+ import { drawSvgStringAsElement, addClass, removeClass } from "@matdata/yasgui-utils";
11
+ import * as Sparql from "./sparql";
12
+ import * as imgs from "./imgs";
13
+ import * as Autocompleter from "./autocompleters";
14
+ import { merge, escape } from "lodash-es";
15
+
16
+ import getDefaults from "./defaults";
17
+ import CodeMirror from "./CodeMirror";
18
+ import { YasqeAjaxConfig } from "./sparql";
19
+
20
+ export interface Yasqe {
21
+ on(eventName: "query", handler: (instance: Yasqe, req: Request, abortController?: AbortController) => void): void;
22
+ off(eventName: "query", handler: (instance: Yasqe, req: Request, abortController?: AbortController) => void): void;
23
+ on(eventName: "queryAbort", handler: (instance: Yasqe, req: Request) => void): void;
24
+ off(eventName: "queryAbort", handler: (instance: Yasqe, req: Request) => void): void;
25
+ on(eventName: "queryResponse", handler: (instance: Yasqe, response: any, duration: number) => void): void;
26
+ off(eventName: "queryResponse", handler: (instance: Yasqe, response: any, duration: number) => void): void;
27
+ showHint: (conf: HintConfig) => void;
28
+ on(eventName: "error", handler: (instance: Yasqe) => void): void;
29
+ off(eventName: "error", handler: (instance: Yasqe) => void): void;
30
+ on(eventName: "blur", handler: (instance: Yasqe) => void): void;
31
+ off(eventName: "blur", handler: (instance: Yasqe) => void): void;
32
+ on(eventName: "queryBefore", handler: (instance: Yasqe, config: YasqeAjaxConfig) => void): void;
33
+ off(eventName: "queryBefore", handler: (instance: Yasqe, config: YasqeAjaxConfig) => void): void;
34
+ on(eventName: "queryResults", handler: (instance: Yasqe, results: any, duration: number) => void): void;
35
+ off(eventName: "queryResults", handler: (instance: Yasqe, results: any, duration: number) => void): void;
36
+ on(eventName: "autocompletionShown", handler: (instance: Yasqe, widget: any) => void): void;
37
+ off(eventName: "autocompletionShown", handler: (instance: Yasqe, widget: any) => void): void;
38
+ on(eventName: "autocompletionClose", handler: (instance: Yasqe) => void): void;
39
+ off(eventName: "autocompletionClose", handler: (instance: Yasqe) => void): void;
40
+ on(eventName: "resize", handler: (instance: Yasqe, newSize: string) => void): void;
41
+ off(eventName: "resize", handler: (instance: Yasqe, newSize: string) => void): void;
42
+ on(eventName: string, handler: () => void): void;
43
+ }
44
+
45
+ export class Yasqe extends CodeMirror {
46
+ private static storageNamespace = "triply";
47
+ public autocompleters: { [name: string]: Autocompleter.Completer | undefined } = {};
48
+ private prevQueryValid = false;
49
+ public queryValid = true;
50
+ public lastQueryDuration: number | undefined;
51
+ private req: Request | undefined;
52
+ private abortController: AbortController | undefined;
53
+ private queryStatus: "valid" | "error" | undefined;
54
+ private queryBtn: HTMLButtonElement | undefined;
55
+ private fullscreenBtn: HTMLButtonElement | undefined;
56
+ private isFullscreen: boolean = false;
57
+ private resizeWrapper?: HTMLDivElement;
58
+ public rootEl: HTMLDivElement;
59
+ public storage: YStorage;
60
+ public config: Config;
61
+ public persistentConfig: PersistentConfig | undefined;
62
+ constructor(parent: HTMLElement, conf: PartialConfig = {}) {
63
+ super();
64
+ if (!parent) throw new Error("No parent passed as argument. Dont know where to draw YASQE");
65
+ this.rootEl = document.createElement("div");
66
+ this.rootEl.className = "yasqe";
67
+ parent.appendChild(this.rootEl);
68
+ this.config = merge({}, Yasqe.defaults, conf);
69
+ //inherit codemirror props
70
+ const cm = (CodeMirror as any)(this.rootEl, this.config);
71
+ //Assign our functions to the cm object. This is needed, as some functions (like the ctrl-enter callback)
72
+ //get the original cm as argument, and not yasqe
73
+ for (const key of Object.getOwnPropertyNames(Yasqe.prototype)) {
74
+ cm[key] = (<any>Yasqe.prototype)[key].bind(this);
75
+ }
76
+ //Also assign the codemirror functions to our object, so we can easily use those
77
+ Object.assign(this, CodeMirror.prototype, cm);
78
+
79
+ //Do some post processing
80
+ this.storage = new YStorage(Yasqe.storageNamespace);
81
+ this.drawButtons();
82
+ const storageId = this.getStorageId();
83
+ // this.getWrapperElement
84
+ if (storageId) {
85
+ const persConf = this.storage.get<any>(storageId);
86
+ if (persConf && typeof persConf === "string") {
87
+ this.persistentConfig = { query: persConf, editorHeight: this.config.editorHeight }; // Migrate to object based localstorage
88
+ } else {
89
+ this.persistentConfig = persConf;
90
+ }
91
+ if (!this.persistentConfig)
92
+ this.persistentConfig = { query: this.getValue(), editorHeight: this.config.editorHeight };
93
+ if (this.persistentConfig && this.persistentConfig.query) this.setValue(this.persistentConfig.query);
94
+ }
95
+ this.config.autocompleters.forEach((c) => this.enableCompleter(c).then(() => {}, console.warn));
96
+ if (this.config.consumeShareLink) {
97
+ this.config.consumeShareLink(this);
98
+ //and: add a hash listener!
99
+ window.addEventListener("hashchange", this.handleHashChange);
100
+ }
101
+ this.checkSyntax();
102
+ // Size codemirror to the
103
+ if (this.persistentConfig && this.persistentConfig.editorHeight) {
104
+ this.getWrapperElement().style.height = this.persistentConfig.editorHeight;
105
+ } else if (this.config.editorHeight) {
106
+ this.getWrapperElement().style.height = this.config.editorHeight;
107
+ }
108
+
109
+ if (this.config.resizeable) this.drawResizer();
110
+ if (this.config.collapsePrefixesOnLoad) this.collapsePrefixes(true);
111
+ this.registerEventListeners();
112
+ }
113
+ private handleHashChange = () => {
114
+ this.config.consumeShareLink?.(this);
115
+ };
116
+ private handleChange() {
117
+ this.checkSyntax();
118
+ this.updateQueryButton();
119
+ }
120
+ private handleBlur() {
121
+ this.saveQuery();
122
+ }
123
+ private handleChanges() {
124
+ // e.g. handle blur
125
+ this.checkSyntax();
126
+ this.updateQueryButton();
127
+ }
128
+ private handleCursorActivity() {
129
+ this.autocomplete(true);
130
+ }
131
+ private handleQuery(_yasqe: Yasqe, req: Request, abortController?: AbortController) {
132
+ this.req = req;
133
+ this.abortController = abortController;
134
+ this.updateQueryButton();
135
+ }
136
+ private handleQueryResponse(_yasqe: Yasqe, _response: any, duration: number) {
137
+ this.lastQueryDuration = duration;
138
+ this.req = undefined;
139
+ this.updateQueryButton();
140
+ }
141
+ private handleQueryAbort(_yasqe: Yasqe, _req: Request) {
142
+ this.req = undefined;
143
+ this.updateQueryButton();
144
+ }
145
+
146
+ private registerEventListeners() {
147
+ /**
148
+ * Register listeners
149
+ */
150
+ this.on("change", this.handleChange);
151
+ this.on("blur", this.handleBlur);
152
+ this.on("changes", this.handleChanges);
153
+ this.on("cursorActivity", this.handleCursorActivity);
154
+
155
+ this.on("query", this.handleQuery);
156
+ this.on("queryResponse", this.handleQueryResponse);
157
+ this.on("queryAbort", this.handleQueryAbort);
158
+ }
159
+
160
+ private unregisterEventListeners() {
161
+ this.off("change" as any, this.handleChange);
162
+ this.off("blur", this.handleBlur);
163
+ this.off("changes" as any, this.handleChanges);
164
+ this.off("cursorActivity" as any, this.handleCursorActivity);
165
+
166
+ this.off("query", this.handleQuery);
167
+ this.off("queryResponse", this.handleQueryResponse);
168
+ this.off("queryAbort", this.handleQueryAbort);
169
+ }
170
+ /**
171
+ * Generic IDE functions
172
+ */
173
+ public emit(event: string, ...data: any[]) {
174
+ CodeMirror.signal(this, event, this, ...data);
175
+ }
176
+
177
+ public getStorageId(getter?: Config["persistenceId"]): string | undefined {
178
+ const persistenceId = getter || this.config.persistenceId;
179
+ if (!persistenceId) return undefined;
180
+ if (typeof persistenceId === "string") return persistenceId;
181
+ return persistenceId(this);
182
+ }
183
+ private drawButtons() {
184
+ const buttons = document.createElement("div");
185
+ buttons.className = "yasqe_buttons";
186
+ this.getWrapperElement().appendChild(buttons);
187
+
188
+ if (this.config.pluginButtons) {
189
+ const pluginButtons = this.config.pluginButtons();
190
+ if (!pluginButtons) return;
191
+ if (Array.isArray(pluginButtons)) {
192
+ for (const button of pluginButtons) {
193
+ buttons.append(button);
194
+ }
195
+ } else {
196
+ buttons.appendChild(pluginButtons);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * draw share link button
202
+ */
203
+ if (this.config.createShareableLink) {
204
+ var svgShare = drawSvgStringAsElement(imgs.share);
205
+ const shareLinkWrapper = document.createElement("button");
206
+ shareLinkWrapper.className = "yasqe_share";
207
+ shareLinkWrapper.title = "Share query";
208
+ shareLinkWrapper.setAttribute("aria-label", "Share query");
209
+ shareLinkWrapper.appendChild(svgShare);
210
+ buttons.appendChild(shareLinkWrapper);
211
+ shareLinkWrapper.addEventListener("click", (event: MouseEvent) => showSharePopup(event));
212
+ shareLinkWrapper.addEventListener("keydown", (event: KeyboardEvent) => {
213
+ if (event.code === "Enter") {
214
+ showSharePopup(event);
215
+ }
216
+ });
217
+
218
+ const showSharePopup = (event: MouseEvent | KeyboardEvent) => {
219
+ event.stopPropagation();
220
+ let popup: HTMLDivElement | undefined = document.createElement("div");
221
+ popup.className = "yasqe_sharePopup";
222
+ buttons.appendChild(popup);
223
+ document.body.addEventListener(
224
+ "click",
225
+ (event) => {
226
+ if (popup && event.target !== popup && !popup.contains(<any>event.target)) {
227
+ popup.remove();
228
+ popup = undefined;
229
+ }
230
+ },
231
+ true,
232
+ );
233
+ var input = document.createElement("input");
234
+ input.type = "text";
235
+ input.value = this.config.createShareableLink(this);
236
+
237
+ input.onfocus = function () {
238
+ input.select();
239
+ };
240
+ // Work around Chrome's little problem
241
+ input.onmouseup = function () {
242
+ // $this.unbind("mouseup");
243
+ return false;
244
+ };
245
+ popup.innerHTML = "";
246
+
247
+ var inputWrapper = document.createElement("div");
248
+ inputWrapper.className = "inputWrapper";
249
+
250
+ inputWrapper.appendChild(input);
251
+
252
+ popup.appendChild(inputWrapper);
253
+
254
+ // We need to track which buttons are drawn here since the two implementations don't play nice together
255
+ const popupInputButtons: HTMLButtonElement[] = [];
256
+ const createShortLink = this.config.createShortLink;
257
+ if (createShortLink) {
258
+ popup.className = popup.className += " enableShort";
259
+ const shortBtn = document.createElement("button");
260
+ popupInputButtons.push(shortBtn);
261
+ shortBtn.innerHTML = "Shorten";
262
+ shortBtn.className = "yasqe_btn yasqe_btn-sm shorten";
263
+ popup.appendChild(shortBtn);
264
+ shortBtn.onclick = () => {
265
+ popupInputButtons.forEach((button) => (button.disabled = true));
266
+ createShortLink(this, input.value).then(
267
+ (value) => {
268
+ input.value = value;
269
+ input.focus();
270
+ },
271
+ (err) => {
272
+ const errSpan = document.createElement("span");
273
+ errSpan.className = "shortlinkErr";
274
+ // Throwing a string or an object should work
275
+ let textContent = "An error has occurred";
276
+ if (typeof err === "string" && err.length !== 0) {
277
+ textContent = err;
278
+ } else if (err.message && err.message.length !== 0) {
279
+ textContent = err.message;
280
+ }
281
+ errSpan.textContent = textContent;
282
+ input.replaceWith(errSpan);
283
+ },
284
+ );
285
+ };
286
+ }
287
+
288
+ const curlBtn = document.createElement("button");
289
+ popupInputButtons.push(curlBtn);
290
+ curlBtn.innerText = "cURL";
291
+ curlBtn.className = "yasqe_btn yasqe_btn-sm curl";
292
+ popup.appendChild(curlBtn);
293
+ curlBtn.onclick = () => {
294
+ popupInputButtons.forEach((button) => (button.disabled = true));
295
+ input.value = this.getAsCurlString();
296
+ input.focus();
297
+ popup?.appendChild(curlBtn);
298
+ };
299
+
300
+ const svgPos = svgShare.getBoundingClientRect();
301
+ popup.style.top = svgShare.offsetTop + svgPos.height + "px";
302
+ popup.style.left = svgShare.offsetLeft + svgShare.clientWidth - popup.clientWidth + "px";
303
+ input.focus();
304
+ };
305
+ }
306
+ /**
307
+ * Draw query btn
308
+ */
309
+ if (this.config.showQueryButton) {
310
+ this.queryBtn = document.createElement("button");
311
+ addClass(this.queryBtn, "yasqe_queryButton");
312
+
313
+ /**
314
+ * Add busy/valid/error btns
315
+ */
316
+ const queryEl = drawSvgStringAsElement(imgs.query);
317
+ addClass(queryEl, "queryIcon");
318
+ this.queryBtn.appendChild(queryEl);
319
+
320
+ const warningIcon = drawSvgStringAsElement(imgs.warning);
321
+ addClass(warningIcon, "warningIcon");
322
+ this.queryBtn.appendChild(warningIcon);
323
+
324
+ this.queryBtn.onclick = () => {
325
+ if (this.config.queryingDisabled) return; // Don't do anything
326
+ if (this.req) {
327
+ this.abortQuery();
328
+ } else {
329
+ this.query().catch(() => {}); //catch this to avoid unhandled rejection
330
+ }
331
+ };
332
+ this.queryBtn.title = "Run query";
333
+ this.queryBtn.setAttribute("aria-label", "Run query");
334
+
335
+ buttons.appendChild(this.queryBtn);
336
+ this.updateQueryButton();
337
+ }
338
+
339
+ /**
340
+ * Draw fullscreen btn
341
+ */
342
+ this.fullscreenBtn = document.createElement("button");
343
+ addClass(this.fullscreenBtn, "yasqe_fullscreenButton");
344
+ const fullscreenIcon = drawSvgStringAsElement(imgs.fullscreen);
345
+ addClass(fullscreenIcon, "fullscreenIcon");
346
+ this.fullscreenBtn.appendChild(fullscreenIcon);
347
+ const fullscreenExitIcon = drawSvgStringAsElement(imgs.fullscreenExit);
348
+ addClass(fullscreenExitIcon, "fullscreenExitIcon");
349
+ this.fullscreenBtn.appendChild(fullscreenExitIcon);
350
+ this.fullscreenBtn.onclick = () => {
351
+ this.toggleFullscreen();
352
+ };
353
+ this.fullscreenBtn.title = "Toggle fullscreen (F11)";
354
+ this.fullscreenBtn.setAttribute("aria-label", "Toggle fullscreen");
355
+ buttons.appendChild(this.fullscreenBtn);
356
+ }
357
+ public toggleFullscreen() {
358
+ this.isFullscreen = !this.isFullscreen;
359
+ if (this.isFullscreen) {
360
+ addClass(this.rootEl, "fullscreen");
361
+ this.fullscreenBtn?.setAttribute("title", "Exit fullscreen (F11)");
362
+ } else {
363
+ removeClass(this.rootEl, "fullscreen");
364
+ this.fullscreenBtn?.setAttribute("title", "Toggle fullscreen (F11)");
365
+ }
366
+ this.refresh();
367
+ }
368
+ public getIsFullscreen() {
369
+ return this.isFullscreen;
370
+ }
371
+ private drawResizer() {
372
+ if (this.resizeWrapper) return;
373
+ this.resizeWrapper = document.createElement("div");
374
+ addClass(this.resizeWrapper, "resizeWrapper");
375
+ const chip = document.createElement("div");
376
+ addClass(chip, "resizeChip");
377
+ this.resizeWrapper.appendChild(chip);
378
+ this.resizeWrapper.addEventListener("mousedown", this.initDrag, false);
379
+ this.resizeWrapper.addEventListener("dblclick", this.expandEditor);
380
+ this.rootEl.appendChild(this.resizeWrapper);
381
+ }
382
+ private initDrag() {
383
+ document.documentElement.addEventListener("mousemove", this.doDrag, false);
384
+ document.documentElement.addEventListener("mouseup", this.stopDrag, false);
385
+ }
386
+ private calculateDragOffset(event: MouseEvent, rootEl: HTMLElement) {
387
+ let parentOffset = 0;
388
+ // offsetParent is, at the time of writing, a working draft. see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
389
+ if (rootEl.offsetParent) parentOffset = (rootEl.offsetParent as HTMLElement).offsetTop;
390
+ let scrollOffset = 0;
391
+ let parentElement = rootEl.parentElement;
392
+ while (parentElement) {
393
+ scrollOffset += parentElement.scrollTop;
394
+ parentElement = parentElement.parentElement;
395
+ }
396
+ return event.clientY - parentOffset - this.rootEl.offsetTop + scrollOffset;
397
+ }
398
+ private doDrag(event: MouseEvent) {
399
+ this.getWrapperElement().style.height = this.calculateDragOffset(event, this.rootEl) + "px";
400
+ }
401
+ private stopDrag() {
402
+ document.documentElement.removeEventListener("mousemove", this.doDrag, false);
403
+ document.documentElement.removeEventListener("mouseup", this.stopDrag, false);
404
+ this.emit("resize", this.getWrapperElement().style.height);
405
+ if (this.getStorageId() && this.persistentConfig) {
406
+ // If there is no storage id there is no persistency wanted
407
+ this.persistentConfig.editorHeight = this.getWrapperElement().style.height;
408
+ this.saveQuery();
409
+ }
410
+ // Refresh the editor to make sure the 'hidden' lines are rendered
411
+ this.refresh();
412
+ }
413
+ public duplicateLine() {
414
+ const cur = this.getDoc().getCursor();
415
+ if (cur) {
416
+ const line = this.getDoc().getLine(cur.line);
417
+ this.getDoc().replaceRange(line + "\n" + line, { ch: 0, line: cur.line }, { ch: line.length, line: cur.line });
418
+ }
419
+ }
420
+ private updateQueryButton(status?: "valid" | "error") {
421
+ if (!this.queryBtn) return;
422
+
423
+ /**
424
+ * Set query status (valid vs invalid)
425
+ */
426
+ if (this.config.queryingDisabled) {
427
+ addClass(this.queryBtn, "query_disabled");
428
+ this.queryBtn.title = this.config.queryingDisabled;
429
+ } else {
430
+ removeClass(this.queryBtn, "query_disabled");
431
+ this.queryBtn.title = "Run query";
432
+ this.queryBtn.setAttribute("aria-label", "Run query");
433
+ }
434
+ if (!status) {
435
+ status = this.queryValid ? "valid" : "error";
436
+ }
437
+ if (status != this.queryStatus) {
438
+ //reset query status classnames
439
+ removeClass(this.queryBtn, "query_" + this.queryStatus);
440
+ addClass(this.queryBtn, "query_" + status);
441
+ this.queryStatus = status;
442
+ }
443
+
444
+ /**
445
+ * Set/remove spinner if needed
446
+ */
447
+ if (this.req && this.queryBtn.className.indexOf("busy") < 0) {
448
+ this.queryBtn.className = this.queryBtn.className += " busy";
449
+ }
450
+ if (!this.req && this.queryBtn.className.indexOf("busy") >= 0) {
451
+ this.queryBtn.className = this.queryBtn.className.replace("busy", "");
452
+ }
453
+ }
454
+ public handleLocalStorageQuotaFull(_e: any) {
455
+ console.warn("Localstorage quota exceeded. Clearing all queries");
456
+ Yasqe.clearStorage();
457
+ }
458
+
459
+ public saveQuery() {
460
+ const storageId = this.getStorageId();
461
+ if (!storageId || !this.persistentConfig) return;
462
+ this.persistentConfig.query = this.getValue();
463
+ this.storage.set(storageId, this.persistentConfig, this.config.persistencyExpire, this.handleLocalStorageQuotaFull);
464
+ }
465
+
466
+ /**
467
+ * Get SPARQL query props
468
+ */
469
+ public getQueryType() {
470
+ return this.getOption("queryType");
471
+ }
472
+ public getQueryMode(): "update" | "query" {
473
+ switch (this.getQueryType()) {
474
+ case "INSERT":
475
+ case "DELETE":
476
+ case "LOAD":
477
+ case "CLEAR":
478
+ case "CREATE":
479
+ case "DROP":
480
+ case "COPY":
481
+ case "MOVE":
482
+ case "ADD":
483
+ return "update";
484
+ default:
485
+ return "query";
486
+ }
487
+ }
488
+ public getVariablesFromQuery() {
489
+ //Use precise here. We want to be sure we use the most up to date state. If we're
490
+ //not, we might get outdated info from the current query (creating loops such
491
+ //as https://github.com/TriplyDB/YASGUI/issues/84)
492
+ //on caveat: this function won't work when query is invalid (i.e. when typing)
493
+ const token: Token = this.getTokenAt(
494
+ { line: this.getDoc().lastLine(), ch: this.getDoc().getLine(this.getDoc().lastLine()).length },
495
+ true,
496
+ );
497
+ const vars: string[] = [];
498
+ for (var v in token.state.variables) {
499
+ vars.push(v);
500
+ }
501
+ return vars.sort();
502
+ }
503
+
504
+ /**
505
+ * Sparql-related tasks
506
+ */
507
+ private autoformatSelection(start: number, end: number): string {
508
+ var text = this.getValue();
509
+ text = text.substring(start, end);
510
+ return Yasqe.autoformatString(text);
511
+ }
512
+ public static autoformatString(text: string): string {
513
+ var breakAfterArray = [
514
+ ["keyword", "ws", "string-2", "ws", "variable-3"], // i.e. prefix declaration
515
+ ["keyword", "ws", "variable-3"], // i.e. base
516
+ ];
517
+
518
+ var breakBeforeCharacters = ["}"];
519
+
520
+ var getBreakType = function (stringVal: string) {
521
+ //first check the characters to break after
522
+ if (stringVal === "{") return 1;
523
+ if (stringVal === ".") return 1;
524
+ if (stringVal === ";") {
525
+ //it shouldnt be part of a group concat though.
526
+ //To check this case, we need to check the previous char type in the stacktrace
527
+ if (stackTrace.length > 2 && stackTrace[stackTrace.length - 2] === "punc") return 0;
528
+ return 1;
529
+ }
530
+
531
+ //Now check which arrays to break after
532
+ for (var i = 0; i < breakAfterArray.length; i++) {
533
+ if (stackTrace.valueOf().toString() === breakAfterArray[i].valueOf().toString()) {
534
+ return 1;
535
+ }
536
+ }
537
+ for (var i = 0; i < breakBeforeCharacters.length; i++) {
538
+ // don't want to issue 'breakbefore' AND 'breakafter', so check
539
+ // current line
540
+ if (currentLine.trim() !== "" && stringVal == breakBeforeCharacters[i]) {
541
+ return -1;
542
+ }
543
+ }
544
+ return 0;
545
+ };
546
+ var formattedQuery = "";
547
+ var currentLine = "";
548
+ var stackTrace: string[] = [];
549
+ (<any>Yasqe).runMode(text, "sparql11", function (stringVal: string, type: string) {
550
+ stackTrace.push(type);
551
+ var breakType = getBreakType(stringVal);
552
+ if (breakType != 0) {
553
+ if (breakType == 1) {
554
+ formattedQuery += stringVal + "\n";
555
+ currentLine = "";
556
+ } else {
557
+ // (-1)
558
+ formattedQuery += "\n" + stringVal;
559
+ currentLine = stringVal;
560
+ }
561
+ stackTrace = [];
562
+ } else {
563
+ currentLine += stringVal;
564
+ formattedQuery += stringVal;
565
+ }
566
+ if (stackTrace.length == 1 && stackTrace[0] == "sp-ws") stackTrace = [];
567
+ });
568
+ return formattedQuery.replace(/\n\s*\n/g, "\n").trim();
569
+ }
570
+
571
+ public commentLines() {
572
+ var startLine = this.getDoc().getCursor("start").line;
573
+ var endLine = this.getDoc().getCursor("end").line;
574
+ var min = Math.min(startLine, endLine);
575
+ var max = Math.max(startLine, endLine);
576
+
577
+ // if all lines start with #, remove this char. Otherwise add this char
578
+ var linesAreCommented = true;
579
+ for (var i = min; i <= max; i++) {
580
+ var line = this.getDoc().getLine(i);
581
+ if (line.length == 0 || line.substring(0, 1) != "#") {
582
+ linesAreCommented = false;
583
+ break;
584
+ }
585
+ }
586
+ for (var i = min; i <= max; i++) {
587
+ if (linesAreCommented) {
588
+ // lines are commented, so remove comments
589
+ this.getDoc().replaceRange(
590
+ "",
591
+ {
592
+ line: i,
593
+ ch: 0,
594
+ },
595
+ {
596
+ line: i,
597
+ ch: 1,
598
+ },
599
+ );
600
+ } else {
601
+ // Not all lines are commented, so add comments
602
+ this.getDoc().replaceRange("#", {
603
+ line: i,
604
+ ch: 0,
605
+ });
606
+ }
607
+ }
608
+ }
609
+
610
+ public autoformat() {
611
+ if (!this.getDoc().somethingSelected()) this.execCommand("selectAll");
612
+ const from = this.getDoc().getCursor("start");
613
+
614
+ var to: Position = {
615
+ line: this.getDoc().getCursor("end").line,
616
+ ch: this.getDoc().getSelection().length,
617
+ };
618
+ var absStart = this.getDoc().indexFromPos(from);
619
+ var absEnd = this.getDoc().indexFromPos(to);
620
+ // Insert additional line breaks where necessary according to the
621
+ // mode's syntax
622
+
623
+ const res = this.autoformatSelection(absStart, absEnd);
624
+
625
+ // Replace and auto-indent the range
626
+ this.operation(() => {
627
+ this.getDoc().replaceRange(res, from, to);
628
+ var startLine = this.getDoc().posFromIndex(absStart).line;
629
+ var endLine = this.getDoc().posFromIndex(absStart + res.length).line;
630
+ for (var i = startLine; i <= endLine; i++) {
631
+ this.indentLine(i, "smart");
632
+ }
633
+ });
634
+ }
635
+ //values in the form of {?var: 'value'}, or [{?var: 'value'}]
636
+ public getQueryWithValues(values: string | { [varName: string]: string } | Array<{ [varName: string]: string }>) {
637
+ if (!values) return this.getValue();
638
+ var injectString: string;
639
+ if (typeof values === "string") {
640
+ injectString = values;
641
+ } else {
642
+ //start building inject string
643
+ if (!(values instanceof Array)) values = [values];
644
+ var variables = values.reduce(function (vars, valueObj) {
645
+ for (var v in valueObj) {
646
+ vars[v] = v;
647
+ }
648
+ return vars;
649
+ }, {});
650
+ var varArray: string[] = [];
651
+ for (var v in variables) {
652
+ varArray.push(v);
653
+ }
654
+
655
+ if (!varArray.length) return this.getValue();
656
+ //ok, we've got enough info to start building the string now
657
+ injectString = "VALUES (" + varArray.join(" ") + ") {\n";
658
+ values.forEach(function (valueObj) {
659
+ injectString += "( ";
660
+ varArray.forEach(function (variable) {
661
+ injectString += valueObj[variable] || "UNDEF";
662
+ });
663
+ injectString += " )\n";
664
+ });
665
+ injectString += "}\n";
666
+ }
667
+ if (!injectString) return this.getValue();
668
+
669
+ var newQuery = "";
670
+ var injected = false;
671
+ var gotSelect = false;
672
+ (<any>Yasqe).runMode(
673
+ this.getValue(),
674
+ "sparql11",
675
+ function (stringVal: string, className: string, _row: number, _col: number, _state: TokenizerState) {
676
+ if (className === "keyword" && stringVal.toLowerCase() === "select") gotSelect = true;
677
+ newQuery += stringVal;
678
+ if (gotSelect && !injected && className === "punc" && stringVal === "{") {
679
+ injected = true;
680
+ //start injecting
681
+ newQuery += "\n" + injectString;
682
+ }
683
+ },
684
+ );
685
+ return newQuery;
686
+ }
687
+
688
+ public getValueWithoutComments() {
689
+ var cleanedQuery = "";
690
+ (<any>Yasqe).runMode(this.getValue(), "sparql11", function (stringVal: string, className: string) {
691
+ if (className != "comment") {
692
+ cleanedQuery += stringVal;
693
+ }
694
+ });
695
+ return cleanedQuery;
696
+ }
697
+
698
+ public setCheckSyntaxErrors(isEnabled: boolean) {
699
+ this.config.syntaxErrorCheck = isEnabled;
700
+ this.checkSyntax();
701
+ }
702
+ public checkSyntax() {
703
+ this.queryValid = true;
704
+
705
+ this.clearGutter("gutterErrorBar");
706
+
707
+ var state: TokenizerState;
708
+ for (var l = 0; l < this.getDoc().lineCount(); ++l) {
709
+ var precise = false;
710
+ if (!this.prevQueryValid) {
711
+ // we don't want cached information in this case, otherwise the
712
+ // previous error sign might still show up,
713
+ // even though the syntax error might be gone already
714
+ precise = true;
715
+ }
716
+
717
+ var token: Token = this.getTokenAt(
718
+ {
719
+ line: l,
720
+ ch: this.getDoc().getLine(l).length,
721
+ },
722
+ precise,
723
+ );
724
+ var state = token.state;
725
+ this.setOption("queryType", state.queryType);
726
+ if (state.OK == false) {
727
+ if (!this.config.syntaxErrorCheck) {
728
+ //the library we use already marks everything as being an error. Overwrite this class attribute.
729
+ const els = this.getWrapperElement().querySelectorAll(".sp-error");
730
+ for (let i = 0; i < els.length; i++) {
731
+ var el: any = els[i];
732
+ if (el.style) el.style.color = "black";
733
+ }
734
+ //we don't want the gutter error, so return
735
+ return;
736
+ }
737
+ const warningEl = drawSvgStringAsElement(imgs.warning);
738
+ if (state.errorMsg) {
739
+ tooltip(this, warningEl, escape(token.state.errorMsg));
740
+ } else if (state.possibleCurrent && state.possibleCurrent.length > 0) {
741
+ var expectedEncoded: string[] = [];
742
+ state.possibleCurrent.forEach(function (expected) {
743
+ expectedEncoded.push("<strong style='text-decoration:underline'>" + escape(expected) + "</strong>");
744
+ });
745
+ tooltip(this, warningEl, "This line is invalid. Expected: " + expectedEncoded.join(", "));
746
+ }
747
+ // warningEl.style.marginTop = "2px";
748
+ // warningEl.style.marginLeft = "2px";
749
+ warningEl.className = "parseErrorIcon";
750
+ this.setGutterMarker(l, "gutterErrorBar", warningEl);
751
+
752
+ this.queryValid = false;
753
+ break;
754
+ }
755
+ }
756
+ }
757
+ /**
758
+ * Token management
759
+ */
760
+
761
+ public getCompleteToken(token?: Token, cur?: Position): Token {
762
+ return getCompleteToken(this, token, cur);
763
+ }
764
+ public getPreviousNonWsToken(line: number, token: Token): Token {
765
+ return getPreviousNonWsToken(this, line, token);
766
+ }
767
+ public getNextNonWsToken(lineNumber: number, charNumber?: number): Token | undefined {
768
+ return getNextNonWsToken(this, lineNumber, charNumber);
769
+ }
770
+ /**
771
+ * Notification management
772
+ */
773
+ private notificationEls: { [key: string]: HTMLDivElement } = {};
774
+
775
+ /**
776
+ * Shows notification
777
+ * @param key reference to the notification
778
+ * @param message the message to display
779
+ */
780
+ public showNotification(key: string, message: string) {
781
+ if (!this.notificationEls[key]) {
782
+ // We create one wrapper for each notification, since there is no interactivity with the container (yet) we don't need to keep a reference
783
+ const notificationContainer = document.createElement("div");
784
+ addClass(notificationContainer, "notificationContainer");
785
+ this.getWrapperElement().appendChild(notificationContainer);
786
+
787
+ // Create the actual notification element
788
+ this.notificationEls[key] = document.createElement("div");
789
+ addClass(this.notificationEls[key], "notification", "notif_" + key);
790
+ notificationContainer.appendChild(this.notificationEls[key]);
791
+ }
792
+ // Hide others
793
+ for (const notificationId in this.notificationEls) {
794
+ if (notificationId !== key) this.hideNotification(notificationId);
795
+ }
796
+ const el = this.notificationEls[key];
797
+ addClass(el, "active");
798
+ el.innerText = message;
799
+ }
800
+ /**
801
+ * Hides notification
802
+ * @param key the identifier of the notification to hide
803
+ */
804
+ public hideNotification(key: string) {
805
+ if (this.notificationEls[key]) {
806
+ removeClass(this.notificationEls[key], "active");
807
+ }
808
+ }
809
+
810
+ /**
811
+ * Autocompleter management
812
+ */
813
+ public enableCompleter(name: string): Promise<void> {
814
+ if (!Yasqe.Autocompleters[name])
815
+ return Promise.reject(new Error("Autocompleter " + name + " is not a registered autocompleter"));
816
+ if (this.config.autocompleters.indexOf(name) < 0) this.config.autocompleters.push(name);
817
+ const autocompleter = (this.autocompleters[name] = new Autocompleter.Completer(this, Yasqe.Autocompleters[name]));
818
+ return autocompleter.initialize();
819
+ }
820
+ public disableCompleter(name: string) {
821
+ this.config.autocompleters = this.config.autocompleters.filter((a) => a !== name);
822
+ this.autocompleters[name] = undefined;
823
+ }
824
+ public autocomplete(fromAutoShow = false) {
825
+ if (this.getDoc().somethingSelected()) return;
826
+
827
+ for (let i in this.config.autocompleters) {
828
+ const autocompleter = this.autocompleters[this.config.autocompleters[i]];
829
+ if (!autocompleter || !autocompleter.autocomplete(fromAutoShow)) continue;
830
+ }
831
+ }
832
+
833
+ /**
834
+ * Prefix management
835
+ */
836
+ public collapsePrefixes(collapse = true) {
837
+ const firstPrefixLine = findFirstPrefixLine(this);
838
+ if (firstPrefixLine === undefined) return; //nothing to collapse
839
+ this.foldCode(firstPrefixLine, (<any>CodeMirror).fold.prefix, collapse ? "fold" : "unfold");
840
+ }
841
+
842
+ public getPrefixesFromQuery(): Prefixes {
843
+ return getPrefixesFromQuery(this);
844
+ }
845
+ public addPrefixes(prefixes: string | Prefixes): void {
846
+ return addPrefixes(this, prefixes);
847
+ }
848
+ public removePrefixes(prefixes: Prefixes): void {
849
+ return removePrefixes(this, prefixes);
850
+ }
851
+ public updateWidget() {
852
+ if (
853
+ (this as any).cursorCoords &&
854
+ (this as any).state.completionActive &&
855
+ (this as any).state.completionActive.widget
856
+ ) {
857
+ const newTop: string = (this as any).cursorCoords(null).bottom;
858
+ (this as any).state.completionActive.widget.hints.style.top = newTop + "px";
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Querying
864
+ */
865
+ public query(config?: Sparql.YasqeAjaxConfig) {
866
+ if (this.config.queryingDisabled) return Promise.reject("Querying is disabled.");
867
+ // Abort previous request
868
+ this.abortQuery();
869
+ return Sparql.executeQuery(this, config);
870
+ }
871
+ public getUrlParams() {
872
+ //first try hash
873
+ let urlParams: queryString.ParsedQuery = {};
874
+ if (window.location.hash.length > 1) {
875
+ //firefox does some decoding if we're using window.location.hash (e.g. the + sign in contentType settings)
876
+ //Don't want this. So simply get the hash string ourselves
877
+ urlParams = queryString.parse(location.hash);
878
+ }
879
+ if ((!urlParams || !("query" in urlParams)) && window.location.search.length > 1) {
880
+ //ok, then just try regular url params
881
+ urlParams = queryString.parse(window.location.search);
882
+ }
883
+ return urlParams;
884
+ }
885
+ public configToQueryParams(): queryString.ParsedQuery {
886
+ //extend existing link, so first fetch current arguments
887
+ var urlParams: any = {};
888
+ if (window.location.hash.length > 1) urlParams = queryString.parse(window.location.hash);
889
+ urlParams["query"] = this.getValue();
890
+ return urlParams;
891
+ }
892
+ public queryParamsToConfig(params: queryString.ParsedQuery) {
893
+ if (params && params.query && typeof params.query === "string") {
894
+ this.setValue(params.query);
895
+ }
896
+ }
897
+
898
+ public getAsCurlString(config?: Sparql.YasqeAjaxConfig): string {
899
+ return Sparql.getAsCurlString(this, config);
900
+ }
901
+
902
+ public abortQuery() {
903
+ if (this.req) {
904
+ if (this.abortController) {
905
+ this.abortController.abort();
906
+ }
907
+ this.emit("queryAbort", this, this.req);
908
+ }
909
+ }
910
+
911
+ public expandEditor() {
912
+ this.setSize(null, "100%");
913
+ }
914
+
915
+ public destroy() {
916
+ // Abort running query
917
+ this.abortQuery();
918
+ this.unregisterEventListeners();
919
+ this.resizeWrapper?.removeEventListener("mousedown", this.initDrag, false);
920
+ this.resizeWrapper?.removeEventListener("dblclick", this.expandEditor);
921
+ for (const autocompleter in this.autocompleters) {
922
+ this.disableCompleter(autocompleter);
923
+ }
924
+ window.removeEventListener("hashchange", this.handleHashChange);
925
+ this.rootEl.remove();
926
+ }
927
+
928
+ /**
929
+ * Statics
930
+ */
931
+ static Sparql = Sparql;
932
+ static runMode = (<any>CodeMirror).runMode;
933
+ static clearStorage() {
934
+ const storage = new YStorage(Yasqe.storageNamespace);
935
+ storage.removeNamespace();
936
+ }
937
+
938
+ static Autocompleters: { [name: string]: Autocompleter.CompleterConfig } = {};
939
+ static registerAutocompleter(value: Autocompleter.CompleterConfig, enable = true) {
940
+ const name = value.name;
941
+ Yasqe.Autocompleters[name] = value;
942
+ if (enable && Yasqe.defaults.autocompleters.indexOf(name) < 0) Yasqe.defaults.autocompleters.push(name);
943
+ }
944
+ static defaults = getDefaults();
945
+ static forkAutocompleter(
946
+ fromCompleter: string,
947
+ newCompleter: { name: string } & Partial<Autocompleter.CompleterConfig>,
948
+ enable = true,
949
+ ) {
950
+ if (!Yasqe.Autocompleters[fromCompleter]) throw new Error("Autocompleter " + fromCompleter + " does not exist");
951
+ if (!newCompleter?.name) {
952
+ throw new Error("Expected a name for newly registered autocompleter");
953
+ }
954
+ const name = newCompleter.name;
955
+ Yasqe.Autocompleters[name] = { ...Yasqe.Autocompleters[fromCompleter], ...newCompleter };
956
+
957
+ if (enable && Yasqe.defaults.autocompleters.indexOf(name) < 0) Yasqe.defaults.autocompleters.push(name);
958
+ }
959
+ }
960
+ (<any>Object).assign(CodeMirror.prototype, Yasqe.prototype);
961
+
962
+ export type TokenizerState = sparql11Mode.State;
963
+ export type Position = CodeMirror.Position;
964
+ export type Token = CodeMirror.Token;
965
+
966
+ export interface HintList {
967
+ list: Hint[];
968
+ from: Position;
969
+ to: Position;
970
+ }
971
+ export interface Hint {
972
+ text: string;
973
+ displayText?: string;
974
+ className?: string;
975
+ render?: (el: HTMLElement, self: Hint, data: any) => void;
976
+ from?: Position;
977
+ to?: Position;
978
+ }
979
+
980
+ export type HintFn = { async?: boolean } & (() => Promise<HintList> | HintList);
981
+ export interface HintConfig {
982
+ completeOnSingleClick?: boolean;
983
+ container?: HTMLElement;
984
+ closeCharacters?: RegExp;
985
+ completeSingle?: boolean;
986
+ // A hinting function, as specified above. It is possible to set the async property on a hinting function to true, in which case it will be called with arguments (cm, callback, ?options), and the completion interface will only be popped up when the hinting function calls the callback, passing it the object holding the completions. The hinting function can also return a promise, and the completion interface will only be popped when the promise resolves. By default, hinting only works when there is no selection. You can give a hinting function a supportsSelection property with a truthy value to indicate that it supports selections.
987
+ hint: HintFn;
988
+
989
+ // Whether the pop-up should be horizontally aligned with the start of the word (true, default), or with the cursor (false).
990
+ alignWithWord?: boolean;
991
+ // When enabled (which is the default), the pop-up will close when the editor is unfocused.
992
+ closeOnUnfocus?: boolean;
993
+ // Allows you to provide a custom key map of keys to be active when the pop-up is active. The handlers will be called with an extra argument, a handle to the completion menu, which has moveFocus(n), setFocus(n), pick(), and close() methods (see the source for details), that can be used to change the focused element, pick the current element or close the menu. Additionally menuSize() can give you access to the size of the current dropdown menu, length give you the number of available completions, and data give you full access to the completion returned by the hinting function.
994
+ customKeys?: any;
995
+
996
+ // Like customKeys above, but the bindings will be added to the set of default bindings, instead of replacing them.
997
+ extraKeys?: {
998
+ [key: string]: (
999
+ yasqe: Yasqe,
1000
+ event: {
1001
+ close: () => void;
1002
+ data: {
1003
+ from: Position;
1004
+ to: Position;
1005
+ list: Hint[];
1006
+ };
1007
+ length: number;
1008
+ menuSize: () => void;
1009
+ moveFocus: (movement: number) => void;
1010
+ pick: () => void;
1011
+ setFocus: (index: number) => void;
1012
+ },
1013
+ ) => void;
1014
+ };
1015
+ }
1016
+ export interface RequestConfig<Y> {
1017
+ queryArgument: string | ((yasqe: Y) => string) | undefined;
1018
+ endpoint: string | ((yasqe: Y) => string);
1019
+ method: "POST" | "GET" | ((yasqe: Y) => "POST" | "GET");
1020
+ acceptHeaderGraph: string | ((yasqe: Y) => string);
1021
+ acceptHeaderSelect: string | ((yasqe: Y) => string);
1022
+ acceptHeaderUpdate: string | ((yasqe: Y) => string);
1023
+ namedGraphs: string[] | ((yasqe: Y) => string[]);
1024
+ defaultGraphs: string[] | ((yasqe: Y) => []);
1025
+ args: Array<{ name: string; value: string }> | ((yasqe: Y) => Array<{ name: string; value: string }>);
1026
+ headers: { [key: string]: string } | ((yasqe: Y) => { [key: string]: string });
1027
+ withCredentials: boolean | ((yasqe: Y) => boolean);
1028
+ adjustQueryBeforeRequest: ((yasqe: Y) => string) | false;
1029
+ }
1030
+ export type PlainRequestConfig = {
1031
+ [K in keyof RequestConfig<any>]: Exclude<RequestConfig<any>[K], Function>;
1032
+ };
1033
+ export type PartialConfig = {
1034
+ [P in keyof Config]?: Config[P] extends object ? Partial<Config[P]> : Config[P];
1035
+ };
1036
+ export interface Config extends Partial<CodeMirror.EditorConfiguration> {
1037
+ mode: string;
1038
+ collapsePrefixesOnLoad: boolean;
1039
+ syntaxErrorCheck: boolean;
1040
+ /**
1041
+ * Show a button with which users can create a link to this query. Set this value to null to disable this functionality.
1042
+ * By default, this feature is enabled, and the only the query value is appended to the link.
1043
+ * ps. This function should return an object which is parseable by jQuery.param (http://api.jquery.com/jQuery.param/)
1044
+ */
1045
+ createShareableLink: (yasqe: Yasqe) => string;
1046
+ createShortLink: ((yasqe: Yasqe, longLink: string) => Promise<string>) | undefined;
1047
+ consumeShareLink: ((yasqe: Yasqe) => void) | undefined | null;
1048
+ /**
1049
+ * Change persistency settings for the YASQE query value. Setting the values
1050
+ * to null, will disable persistancy: nothing is stored between browser
1051
+ * sessions Setting the values to a string (or a function which returns a
1052
+ * string), will store the query in localstorage using the specified string.
1053
+ * By default, the ID is dynamically generated using the closest dom ID, to avoid collissions when using multiple YASQE items on one
1054
+ * page
1055
+ */
1056
+ persistenceId: ((yasqe: Yasqe) => string) | string | undefined | null;
1057
+ persistencyExpire: number; //seconds
1058
+ showQueryButton: boolean;
1059
+ requestConfig: RequestConfig<Yasqe> | ((yasqe: Yasqe) => RequestConfig<Yasqe>);
1060
+ pluginButtons: (() => HTMLElement[] | HTMLElement) | undefined;
1061
+ //Addon specific addon ts defs, or missing props from codemirror conf
1062
+ highlightSelectionMatches: { showToken?: RegExp; annotateScrollbar?: boolean };
1063
+ tabMode: string;
1064
+ foldGutter: any; //This should be of type boolean, or an object. However, setting it to any to avoid
1065
+ //ts complaining about incorrectly extending, as the cm def only defined it has having a boolean type.
1066
+ matchBrackets: boolean;
1067
+ autocompleters: string[];
1068
+ hintConfig: Partial<HintConfig>;
1069
+ resizeable: boolean;
1070
+ editorHeight: string;
1071
+ queryingDisabled: string | undefined; // The string will be the message displayed when hovered
1072
+ prefixCcApi: string; // the suggested default prefixes URL API getter
1073
+ }
1074
+ export interface PersistentConfig {
1075
+ query: string;
1076
+ editorHeight: string;
1077
+ }
1078
+ // export var _Yasqe = _Yasqe;
1079
+
1080
+ //add missing static functions, added by e.g. addons
1081
+ // declare function runMode(text:string, mode:any, out:any):void
1082
+
1083
+ //Need to assign our prototype to codemirror's, as some of the callbacks (e.g. the keymap opts)
1084
+ //give us a cm doc, instead of a yasqe + cm doc
1085
+ Autocompleter.completers.forEach((c) => {
1086
+ Yasqe.registerAutocompleter(c);
1087
+ });
1088
+
1089
+ export default Yasqe;