@matdata/yasr 5.2.0 → 5.4.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/README.md +128 -144
- package/build/ts/src/plugins/table/__tests__/table-test.d.ts +1 -0
- package/build/ts/src/plugins/table/__tests__/table-test.js +190 -0
- package/build/ts/src/plugins/table/__tests__/table-test.js.map +1 -0
- package/build/ts/src/plugins/table/index.d.ts +11 -3
- package/build/ts/src/plugins/table/index.js +138 -35
- package/build/ts/src/plugins/table/index.js.map +1 -1
- package/build/yasr.min.css +1 -1
- package/build/yasr.min.css.map +3 -3
- package/build/yasr.min.js +35 -32
- package/build/yasr.min.js.map +3 -3
- package/package.json +1 -1
- package/src/bin/takeScreenshot.js +5 -6
- package/src/bindingsToCsv.ts +1 -1
- package/src/parsers/xml.ts +1 -1
- package/src/plugins/table/__tests__/table-test.ts +246 -0
- package/src/plugins/table/column-resizer.d.ts +29 -0
- package/src/plugins/table/index.scss +32 -19
- package/src/plugins/table/index.ts +157 -37
|
@@ -28,7 +28,9 @@ export interface PluginConfig {
|
|
|
28
28
|
export interface PersistentConfig {
|
|
29
29
|
pageSize?: number;
|
|
30
30
|
compact?: boolean;
|
|
31
|
-
|
|
31
|
+
isCompactView?: boolean;
|
|
32
|
+
showUriPrefixes?: boolean;
|
|
33
|
+
showDatatypes?: boolean;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
type DataRow = [number, ...(Parser.BindingValue | "")[]];
|
|
@@ -48,7 +50,9 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
48
50
|
private tableFilterField: HTMLInputElement | undefined;
|
|
49
51
|
private tableSizeField: HTMLSelectElement | undefined;
|
|
50
52
|
private tableCompactSwitch: HTMLInputElement | undefined;
|
|
51
|
-
private
|
|
53
|
+
private tableCompactViewSwitch: HTMLInputElement | undefined;
|
|
54
|
+
private tableUriPrefixSwitch: HTMLInputElement | undefined;
|
|
55
|
+
private tableDatatypeSwitch: HTMLInputElement | undefined;
|
|
52
56
|
private tableResizer:
|
|
53
57
|
| {
|
|
54
58
|
reset: (options: {
|
|
@@ -57,7 +61,7 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
57
61
|
partialRefresh?: boolean;
|
|
58
62
|
headerOnly?: boolean;
|
|
59
63
|
}) => void;
|
|
60
|
-
onResize: () =>
|
|
64
|
+
onResize: () => void;
|
|
61
65
|
}
|
|
62
66
|
| undefined;
|
|
63
67
|
public helpReference = "https://docs.triply.cc/yasgui/#table";
|
|
@@ -109,11 +113,40 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
109
113
|
return bindings.map((binding, rowId) => [rowId + 1, ...vars.map((variable) => binding[variable] ?? "")]);
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
private getMarkdownTable(): string {
|
|
117
|
+
if (!this.yasr.results) return "";
|
|
118
|
+
const bindings = this.yasr.results.getBindings();
|
|
119
|
+
if (!bindings) return "";
|
|
120
|
+
const vars = this.yasr.results.getVariables();
|
|
121
|
+
|
|
122
|
+
// Helper to escape special characters in markdown (backslashes and pipes)
|
|
123
|
+
const escapeMarkdown = (str: string) => str.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
124
|
+
|
|
125
|
+
// Helper to get plain text value from binding
|
|
126
|
+
const getPlainValue = (binding: Parser.BindingValue | ""): string => {
|
|
127
|
+
if (binding === "") return "";
|
|
128
|
+
return escapeMarkdown(binding.value);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Create header row
|
|
132
|
+
let markdown = "| " + vars.map(escapeMarkdown).join(" | ") + " |\n";
|
|
133
|
+
// Create separator row
|
|
134
|
+
markdown += "| " + vars.map(() => "---").join(" | ") + " |\n";
|
|
135
|
+
// Create data rows
|
|
136
|
+
bindings.forEach((binding) => {
|
|
137
|
+
const row = vars.map((variable) => getPlainValue(binding[variable] ?? ""));
|
|
138
|
+
markdown += "| " + row.join(" | ") + " |\n";
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return markdown;
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
private getUriLinkFromBinding(binding: Parser.BindingValue, prefixes?: { [key: string]: string }) {
|
|
113
145
|
const href = sanitize(binding.value);
|
|
114
146
|
let visibleString = href;
|
|
115
147
|
let prefixed = false;
|
|
116
|
-
if (
|
|
148
|
+
// Apply URI prefixing if enabled (default true)
|
|
149
|
+
if (this.persistentConfig.showUriPrefixes !== false && prefixes) {
|
|
117
150
|
for (const prefixLabel in prefixes) {
|
|
118
151
|
if (visibleString.indexOf(prefixes[prefixLabel]) == 0) {
|
|
119
152
|
visibleString = prefixLabel + ":" + href.substring(prefixes[prefixLabel].length);
|
|
@@ -146,11 +179,14 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
146
179
|
// Return now when in compact mode.
|
|
147
180
|
if (this.persistentConfig.compact) return stringRepresentation;
|
|
148
181
|
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
182
|
+
// Show datatypes if enabled (default true)
|
|
183
|
+
if (this.persistentConfig.showDatatypes !== false) {
|
|
184
|
+
if (literalBinding["xml:lang"]) {
|
|
185
|
+
stringRepresentation = `"${stringRepresentation}"<sup>@${literalBinding["xml:lang"]}</sup>`;
|
|
186
|
+
} else if (literalBinding.datatype) {
|
|
187
|
+
const dataType = this.getUriLinkFromBinding({ type: "uri", value: literalBinding.datatype }, prefixes);
|
|
188
|
+
stringRepresentation = `"${stringRepresentation}"<sup>^^${dataType}</sup>`;
|
|
189
|
+
}
|
|
154
190
|
}
|
|
155
191
|
return stringRepresentation;
|
|
156
192
|
}
|
|
@@ -220,18 +256,18 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
220
256
|
this.tableEl.style.removeProperty("width");
|
|
221
257
|
this.tableEl.style.width = this.tableEl.clientWidth + "px";
|
|
222
258
|
const widths = Array.from(this.tableEl.querySelectorAll("th")).map((h) => h.offsetWidth - 26);
|
|
223
|
-
this.tableResizer = new ColumnResizer
|
|
259
|
+
this.tableResizer = new ColumnResizer(this.tableEl, {
|
|
224
260
|
widths: this.persistentConfig.compact === true ? widths : [this.getSizeFirstColumn(), ...widths.slice(1)],
|
|
225
261
|
partialRefresh: true,
|
|
226
|
-
onResize: this.persistentConfig.
|
|
262
|
+
onResize: this.persistentConfig.isCompactView !== false && this.setEllipsisHandlers,
|
|
227
263
|
headerOnly: true,
|
|
228
264
|
});
|
|
229
265
|
// DataTables uses the rendered style to decide the widths of columns.
|
|
230
|
-
// Before a draw remove the
|
|
231
|
-
if (this.persistentConfig.
|
|
266
|
+
// Before a draw remove the compactTable styling
|
|
267
|
+
if (this.persistentConfig.isCompactView !== false) {
|
|
232
268
|
this.dataTable?.on("preDraw", () => {
|
|
233
269
|
this.tableResizer?.reset({ disable: true });
|
|
234
|
-
removeClass(this.tableEl, "
|
|
270
|
+
removeClass(this.tableEl, "compactTable");
|
|
235
271
|
this.tableEl?.style.removeProperty("width");
|
|
236
272
|
this.tableEl?.style.setProperty("width", this.tableEl.clientWidth + "px");
|
|
237
273
|
return true; // Indicate it should re-render
|
|
@@ -252,8 +288,8 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
252
288
|
onResize: this.setEllipsisHandlers,
|
|
253
289
|
headerOnly: true,
|
|
254
290
|
});
|
|
255
|
-
// Re-add the
|
|
256
|
-
addClass(this.tableEl, "
|
|
291
|
+
// Re-add the compact styling
|
|
292
|
+
addClass(this.tableEl, "compactTable");
|
|
257
293
|
// Check if cells need the ellipsisHandlers
|
|
258
294
|
this.setEllipsisHandlers();
|
|
259
295
|
});
|
|
@@ -261,8 +297,8 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
261
297
|
|
|
262
298
|
this.drawControls();
|
|
263
299
|
// Draw again but with the events
|
|
264
|
-
if (this.persistentConfig.
|
|
265
|
-
addClass(this.tableEl, "
|
|
300
|
+
if (this.persistentConfig.isCompactView !== false) {
|
|
301
|
+
addClass(this.tableEl, "compactTable");
|
|
266
302
|
this.setEllipsisHandlers();
|
|
267
303
|
}
|
|
268
304
|
// if (this.tableEl.clientWidth > width) this.tableEl.parentElement?.style.setProperty("overflow", "hidden");
|
|
@@ -307,13 +343,53 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
307
343
|
this.draw(this.persistentConfig);
|
|
308
344
|
this.yasr.storePluginConfig("table", this.persistentConfig);
|
|
309
345
|
};
|
|
310
|
-
private
|
|
346
|
+
private handleSetCompactViewToggle = (event: Event) => {
|
|
347
|
+
// Store in persistentConfig
|
|
348
|
+
this.persistentConfig.isCompactView = (event.target as HTMLInputElement).checked;
|
|
349
|
+
// Update the table
|
|
350
|
+
this.draw(this.persistentConfig);
|
|
351
|
+
this.yasr.storePluginConfig("table", this.persistentConfig);
|
|
352
|
+
};
|
|
353
|
+
private handleSetUriPrefixToggle = (event: Event) => {
|
|
354
|
+
// Store in persistentConfig
|
|
355
|
+
this.persistentConfig.showUriPrefixes = (event.target as HTMLInputElement).checked;
|
|
356
|
+
// Update the table
|
|
357
|
+
this.draw(this.persistentConfig);
|
|
358
|
+
this.yasr.storePluginConfig("table", this.persistentConfig);
|
|
359
|
+
};
|
|
360
|
+
private handleSetDatatypeToggle = (event: Event) => {
|
|
311
361
|
// Store in persistentConfig
|
|
312
|
-
this.persistentConfig.
|
|
362
|
+
this.persistentConfig.showDatatypes = (event.target as HTMLInputElement).checked;
|
|
313
363
|
// Update the table
|
|
314
364
|
this.draw(this.persistentConfig);
|
|
315
365
|
this.yasr.storePluginConfig("table", this.persistentConfig);
|
|
316
366
|
};
|
|
367
|
+
private handleCopyMarkdown = async (event: Event) => {
|
|
368
|
+
const markdown = this.getMarkdownTable();
|
|
369
|
+
const button = event.target as HTMLButtonElement;
|
|
370
|
+
|
|
371
|
+
// Prevent multiple rapid clicks
|
|
372
|
+
if (button.disabled) return;
|
|
373
|
+
button.disabled = true;
|
|
374
|
+
|
|
375
|
+
const originalText = "Copy as Markdown";
|
|
376
|
+
try {
|
|
377
|
+
await navigator.clipboard.writeText(markdown);
|
|
378
|
+
// Provide visual feedback
|
|
379
|
+
button.textContent = "Copied!";
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
button.textContent = originalText;
|
|
382
|
+
button.disabled = false;
|
|
383
|
+
}, 2000);
|
|
384
|
+
} catch (err) {
|
|
385
|
+
// Show user-friendly error
|
|
386
|
+
button.textContent = "Copy failed";
|
|
387
|
+
setTimeout(() => {
|
|
388
|
+
button.textContent = originalText;
|
|
389
|
+
button.disabled = false;
|
|
390
|
+
}, 2000);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
317
393
|
/**
|
|
318
394
|
* Draws controls on each update
|
|
319
395
|
*/
|
|
@@ -339,21 +415,53 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
339
415
|
this.tableCompactSwitch.defaultChecked = !!this.persistentConfig.compact;
|
|
340
416
|
this.tableControls.appendChild(toggleWrapper);
|
|
341
417
|
|
|
342
|
-
//
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
addClass(
|
|
348
|
-
|
|
349
|
-
addClass(
|
|
350
|
-
|
|
351
|
-
this.
|
|
352
|
-
|
|
353
|
-
this.
|
|
354
|
-
|
|
355
|
-
this.
|
|
356
|
-
this.tableControls.appendChild(
|
|
418
|
+
// Compact view switch
|
|
419
|
+
const compactViewToggleWrapper = document.createElement("div");
|
|
420
|
+
const compactViewSwitchComponent = document.createElement("label");
|
|
421
|
+
const compactViewTextComponent = document.createElement("span");
|
|
422
|
+
compactViewTextComponent.innerText = "Compact";
|
|
423
|
+
addClass(compactViewTextComponent, "label");
|
|
424
|
+
compactViewSwitchComponent.appendChild(compactViewTextComponent);
|
|
425
|
+
addClass(compactViewSwitchComponent, "switch");
|
|
426
|
+
compactViewToggleWrapper.appendChild(compactViewSwitchComponent);
|
|
427
|
+
this.tableCompactViewSwitch = document.createElement("input");
|
|
428
|
+
compactViewSwitchComponent.addEventListener("change", this.handleSetCompactViewToggle);
|
|
429
|
+
this.tableCompactViewSwitch.type = "checkbox";
|
|
430
|
+
compactViewSwitchComponent.appendChild(this.tableCompactViewSwitch);
|
|
431
|
+
this.tableCompactViewSwitch.defaultChecked = this.persistentConfig.isCompactView !== false;
|
|
432
|
+
this.tableControls.appendChild(compactViewToggleWrapper);
|
|
433
|
+
|
|
434
|
+
// URI Prefix switch
|
|
435
|
+
const uriPrefixToggleWrapper = document.createElement("div");
|
|
436
|
+
const uriPrefixSwitchComponent = document.createElement("label");
|
|
437
|
+
const uriPrefixTextComponent = document.createElement("span");
|
|
438
|
+
uriPrefixTextComponent.innerText = "Prefixes";
|
|
439
|
+
addClass(uriPrefixTextComponent, "label");
|
|
440
|
+
uriPrefixSwitchComponent.appendChild(uriPrefixTextComponent);
|
|
441
|
+
addClass(uriPrefixSwitchComponent, "switch");
|
|
442
|
+
uriPrefixToggleWrapper.appendChild(uriPrefixSwitchComponent);
|
|
443
|
+
this.tableUriPrefixSwitch = document.createElement("input");
|
|
444
|
+
uriPrefixSwitchComponent.addEventListener("change", this.handleSetUriPrefixToggle);
|
|
445
|
+
this.tableUriPrefixSwitch.type = "checkbox";
|
|
446
|
+
uriPrefixSwitchComponent.appendChild(this.tableUriPrefixSwitch);
|
|
447
|
+
this.tableUriPrefixSwitch.defaultChecked = this.persistentConfig.showUriPrefixes !== false;
|
|
448
|
+
this.tableControls.appendChild(uriPrefixToggleWrapper);
|
|
449
|
+
|
|
450
|
+
// Datatype switch
|
|
451
|
+
const datatypeToggleWrapper = document.createElement("div");
|
|
452
|
+
const datatypeSwitchComponent = document.createElement("label");
|
|
453
|
+
const datatypeTextComponent = document.createElement("span");
|
|
454
|
+
datatypeTextComponent.innerText = "Datatypes";
|
|
455
|
+
addClass(datatypeTextComponent, "label");
|
|
456
|
+
datatypeSwitchComponent.appendChild(datatypeTextComponent);
|
|
457
|
+
addClass(datatypeSwitchComponent, "switch");
|
|
458
|
+
datatypeToggleWrapper.appendChild(datatypeSwitchComponent);
|
|
459
|
+
this.tableDatatypeSwitch = document.createElement("input");
|
|
460
|
+
datatypeSwitchComponent.addEventListener("change", this.handleSetDatatypeToggle);
|
|
461
|
+
this.tableDatatypeSwitch.type = "checkbox";
|
|
462
|
+
datatypeSwitchComponent.appendChild(this.tableDatatypeSwitch);
|
|
463
|
+
this.tableDatatypeSwitch.defaultChecked = this.persistentConfig.showDatatypes !== false;
|
|
464
|
+
this.tableControls.appendChild(datatypeToggleWrapper);
|
|
357
465
|
|
|
358
466
|
// Create table filter
|
|
359
467
|
this.tableFilterField = document.createElement("input");
|
|
@@ -363,6 +471,14 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
363
471
|
this.tableControls.appendChild(this.tableFilterField);
|
|
364
472
|
this.tableFilterField.addEventListener("keyup", this.handleTableSearch);
|
|
365
473
|
|
|
474
|
+
// Create markdown copy button
|
|
475
|
+
const markdownButton = document.createElement("button");
|
|
476
|
+
markdownButton.className = "copyMarkdownBtn";
|
|
477
|
+
markdownButton.textContent = "Copy as Markdown";
|
|
478
|
+
markdownButton.setAttribute("aria-label", "Copy table as markdown");
|
|
479
|
+
markdownButton.addEventListener("click", this.handleCopyMarkdown);
|
|
480
|
+
this.tableControls.appendChild(markdownButton);
|
|
481
|
+
|
|
366
482
|
// Create page wrapper
|
|
367
483
|
const pageSizerWrapper = document.createElement("div");
|
|
368
484
|
pageSizerWrapper.className = "pageSizeWrapper";
|
|
@@ -413,8 +529,12 @@ export default class Table implements Plugin<PluginConfig> {
|
|
|
413
529
|
this.tableSizeField = undefined;
|
|
414
530
|
this.tableCompactSwitch?.removeEventListener("change", this.handleSetCompactToggle);
|
|
415
531
|
this.tableCompactSwitch = undefined;
|
|
416
|
-
this.
|
|
417
|
-
this.
|
|
532
|
+
this.tableCompactViewSwitch?.removeEventListener("change", this.handleSetCompactViewToggle);
|
|
533
|
+
this.tableCompactViewSwitch = undefined;
|
|
534
|
+
this.tableUriPrefixSwitch?.removeEventListener("change", this.handleSetUriPrefixToggle);
|
|
535
|
+
this.tableUriPrefixSwitch = undefined;
|
|
536
|
+
this.tableDatatypeSwitch?.removeEventListener("change", this.handleSetDatatypeToggle);
|
|
537
|
+
this.tableDatatypeSwitch = undefined;
|
|
418
538
|
// Empty controls
|
|
419
539
|
while (this.tableControls?.firstChild) this.tableControls.firstChild.remove();
|
|
420
540
|
this.tableControls?.remove();
|