@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.
@@ -28,7 +28,9 @@ export interface PluginConfig {
28
28
  export interface PersistentConfig {
29
29
  pageSize?: number;
30
30
  compact?: boolean;
31
- isEllipsed?: boolean;
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 tableEllipseSwitch: HTMLInputElement | undefined;
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 (prefixes) {
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 (literalBinding["xml:lang"]) {
150
- stringRepresentation = `"${stringRepresentation}"<sup>@${literalBinding["xml:lang"]}</sup>`;
151
- } else if (literalBinding.datatype) {
152
- const dataType = this.getUriLinkFromBinding({ type: "uri", value: literalBinding.datatype }, prefixes);
153
- stringRepresentation = `"${stringRepresentation}"<sup>^^${dataType}</sup>`;
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.default(this.tableEl, {
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.isEllipsed !== false && this.setEllipsisHandlers,
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 ellipseTable styling
231
- if (this.persistentConfig.isEllipsed !== false) {
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, "ellipseTable");
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 ellipsis
256
- addClass(this.tableEl, "ellipseTable");
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.isEllipsed !== false) {
265
- addClass(this.tableEl, "ellipseTable");
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 handleSetEllipsisToggle = (event: Event) => {
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.isEllipsed = (event.target as HTMLInputElement).checked;
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
- // Ellipsis switch
343
- const ellipseToggleWrapper = document.createElement("div");
344
- const ellipseSwitchComponent = document.createElement("label");
345
- const ellipseTextComponent = document.createElement("span");
346
- ellipseTextComponent.innerText = "Ellipse";
347
- addClass(ellipseTextComponent, "label");
348
- ellipseSwitchComponent.appendChild(ellipseTextComponent);
349
- addClass(ellipseSwitchComponent, "switch");
350
- ellipseToggleWrapper.appendChild(ellipseSwitchComponent);
351
- this.tableEllipseSwitch = document.createElement("input");
352
- ellipseSwitchComponent.addEventListener("change", this.handleSetEllipsisToggle);
353
- this.tableEllipseSwitch.type = "checkbox";
354
- ellipseSwitchComponent.appendChild(this.tableEllipseSwitch);
355
- this.tableEllipseSwitch.defaultChecked = this.persistentConfig.isEllipsed !== false;
356
- this.tableControls.appendChild(ellipseToggleWrapper);
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.tableEllipseSwitch?.removeEventListener("change", this.handleSetEllipsisToggle);
417
- this.tableEllipseSwitch = undefined;
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();