@matdata/yasgui 5.3.0 → 5.5.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.
@@ -2,6 +2,7 @@ import { addClass, removeClass } from "@matdata/yasgui-utils";
2
2
  import "./TabSettingsModal.scss";
3
3
  import Tab from "./Tab";
4
4
  import * as ConfigExportImport from "./ConfigExportImport";
5
+ import { VERSION } from "./version";
5
6
 
6
7
  // Theme toggle icons
7
8
  const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
@@ -110,44 +111,63 @@ export default class TabSettingsModal {
110
111
 
111
112
  this.modalContent.appendChild(header);
112
113
 
113
- // Body with tabs
114
+ // Body with sidebar navigation
114
115
  const body = document.createElement("div");
115
116
  addClass(body, "modalBody");
116
117
 
117
- const tabsContainer = document.createElement("div");
118
- addClass(tabsContainer, "modalTabs");
118
+ // Sidebar navigation
119
+ const sidebar = document.createElement("div");
120
+ addClass(sidebar, "modalSidebar");
119
121
 
120
122
  const requestTab = document.createElement("button");
121
123
  requestTab.textContent = "Request";
122
- addClass(requestTab, "modalTabButton", "active");
124
+ addClass(requestTab, "modalNavButton", "active");
123
125
  requestTab.onclick = () => this.switchTab("request");
124
126
 
127
+ const endpointsTab = document.createElement("button");
128
+ endpointsTab.textContent = "SPARQL Endpoints";
129
+ addClass(endpointsTab, "modalNavButton");
130
+ endpointsTab.onclick = () => this.switchTab("endpoints");
131
+
125
132
  const prefixTab = document.createElement("button");
126
133
  prefixTab.textContent = "Prefixes";
127
- addClass(prefixTab, "modalTabButton");
134
+ addClass(prefixTab, "modalNavButton");
128
135
  prefixTab.onclick = () => this.switchTab("prefix");
129
136
 
130
137
  const editorTab = document.createElement("button");
131
138
  editorTab.textContent = "Editor";
132
- addClass(editorTab, "modalTabButton");
139
+ addClass(editorTab, "modalNavButton");
133
140
  editorTab.onclick = () => this.switchTab("editor");
134
141
 
135
- const endpointsTab = document.createElement("button");
136
- endpointsTab.textContent = "Endpoint Buttons";
137
- addClass(endpointsTab, "modalTabButton");
138
- endpointsTab.onclick = () => this.switchTab("endpoints");
139
-
140
142
  const importExportTab = document.createElement("button");
141
143
  importExportTab.textContent = "Import/Export";
142
- addClass(importExportTab, "modalTabButton");
144
+ addClass(importExportTab, "modalNavButton");
143
145
  importExportTab.onclick = () => this.switchTab("importexport");
144
146
 
145
- tabsContainer.appendChild(requestTab);
146
- tabsContainer.appendChild(prefixTab);
147
- tabsContainer.appendChild(editorTab);
148
- tabsContainer.appendChild(endpointsTab);
149
- tabsContainer.appendChild(importExportTab);
150
- body.appendChild(tabsContainer);
147
+ const shortcutsTab = document.createElement("button");
148
+ shortcutsTab.textContent = "Keyboard Shortcuts";
149
+ addClass(shortcutsTab, "modalNavButton");
150
+ shortcutsTab.onclick = () => this.switchTab("shortcuts");
151
+
152
+ const aboutTab = document.createElement("button");
153
+ aboutTab.textContent = "About";
154
+ addClass(aboutTab, "modalNavButton");
155
+ aboutTab.onclick = () => this.switchTab("about");
156
+
157
+ sidebar.appendChild(requestTab);
158
+ sidebar.appendChild(endpointsTab);
159
+ sidebar.appendChild(prefixTab);
160
+ sidebar.appendChild(editorTab);
161
+ sidebar.appendChild(importExportTab);
162
+ sidebar.appendChild(shortcutsTab);
163
+ sidebar.appendChild(aboutTab);
164
+
165
+ // Content area container
166
+ const contentArea = document.createElement("div");
167
+ addClass(contentArea, "modalContentArea");
168
+
169
+ body.appendChild(sidebar);
170
+ body.appendChild(contentArea);
151
171
 
152
172
  // Tab content containers
153
173
  const requestContent = document.createElement("div");
@@ -155,6 +175,11 @@ export default class TabSettingsModal {
155
175
  requestContent.id = "request-content";
156
176
  this.drawRequestSettings(requestContent);
157
177
 
178
+ const endpointsContent = document.createElement("div");
179
+ addClass(endpointsContent, "modalTabContent");
180
+ endpointsContent.id = "endpoints-content";
181
+ this.drawEndpointsSettings(endpointsContent);
182
+
158
183
  const prefixContent = document.createElement("div");
159
184
  addClass(prefixContent, "modalTabContent");
160
185
  prefixContent.id = "prefix-content";
@@ -165,21 +190,28 @@ export default class TabSettingsModal {
165
190
  editorContent.id = "editor-content";
166
191
  this.drawEditorSettings(editorContent);
167
192
 
168
- const endpointsContent = document.createElement("div");
169
- addClass(endpointsContent, "modalTabContent");
170
- endpointsContent.id = "endpoints-content";
171
- this.drawEndpointButtonsSettings(endpointsContent);
172
-
173
193
  const importExportContent = document.createElement("div");
174
194
  addClass(importExportContent, "modalTabContent");
175
195
  importExportContent.id = "importexport-content";
176
196
  this.drawImportExportSettings(importExportContent);
177
197
 
178
- body.appendChild(requestContent);
179
- body.appendChild(prefixContent);
180
- body.appendChild(editorContent);
181
- body.appendChild(endpointsContent);
182
- body.appendChild(importExportContent);
198
+ const shortcutsContent = document.createElement("div");
199
+ addClass(shortcutsContent, "modalTabContent");
200
+ shortcutsContent.id = "shortcuts-content";
201
+ this.drawKeyboardShortcuts(shortcutsContent);
202
+
203
+ const aboutContent = document.createElement("div");
204
+ addClass(aboutContent, "modalTabContent");
205
+ aboutContent.id = "about-content";
206
+ this.drawAboutSettings(aboutContent);
207
+
208
+ contentArea.appendChild(requestContent);
209
+ contentArea.appendChild(endpointsContent);
210
+ contentArea.appendChild(prefixContent);
211
+ contentArea.appendChild(editorContent);
212
+ contentArea.appendChild(importExportContent);
213
+ contentArea.appendChild(shortcutsContent);
214
+ contentArea.appendChild(aboutContent);
183
215
 
184
216
  this.modalContent.appendChild(body);
185
217
 
@@ -207,16 +239,18 @@ export default class TabSettingsModal {
207
239
  }
208
240
 
209
241
  private switchTab(tabName: string) {
210
- const buttons = this.modalContent.querySelectorAll(".modalTabButton");
242
+ const buttons = this.modalContent.querySelectorAll(".modalNavButton");
211
243
  const contents = this.modalContent.querySelectorAll(".modalTabContent");
212
244
 
213
245
  buttons.forEach((btn, index) => {
214
246
  if (
215
247
  (tabName === "request" && index === 0) ||
216
- (tabName === "prefix" && index === 1) ||
217
- (tabName === "editor" && index === 2) ||
218
- (tabName === "endpoints" && index === 3) ||
219
- (tabName === "importexport" && index === 4)
248
+ (tabName === "endpoints" && index === 1) ||
249
+ (tabName === "prefix" && index === 2) ||
250
+ (tabName === "editor" && index === 3) ||
251
+ (tabName === "importexport" && index === 4) ||
252
+ (tabName === "shortcuts" && index === 5) ||
253
+ (tabName === "about" && index === 6)
220
254
  ) {
221
255
  addClass(btn as HTMLElement, "active");
222
256
  } else {
@@ -378,6 +412,398 @@ export default class TabSettingsModal {
378
412
  constructValidationSection.appendChild(constructValidationCheckboxContainer);
379
413
  constructValidationSection.appendChild(constructValidationHelp);
380
414
  container.appendChild(constructValidationSection);
415
+
416
+ // Code Snippets Bar Visibility Section
417
+ const snippetsBarSection = document.createElement("div");
418
+ addClass(snippetsBarSection, "settingsSection");
419
+
420
+ const snippetsBarCheckboxContainer = document.createElement("div");
421
+ addClass(snippetsBarCheckboxContainer, "checkboxContainer");
422
+
423
+ const snippetsBarCheckbox = document.createElement("input");
424
+ snippetsBarCheckbox.type = "checkbox";
425
+ snippetsBarCheckbox.id = "showSnippetsBar";
426
+ snippetsBarCheckbox.checked = yasqe.getSnippetsBarVisible();
427
+
428
+ const snippetsBarLabel = document.createElement("label");
429
+ snippetsBarLabel.htmlFor = "showSnippetsBar";
430
+ snippetsBarLabel.textContent = "Show code snippets bar";
431
+
432
+ const snippetsBarHelp = document.createElement("div");
433
+ snippetsBarHelp.textContent =
434
+ "Display the code snippets bar above the editor for quick insertion of common SPARQL patterns.";
435
+ addClass(snippetsBarHelp, "settingsHelp");
436
+ snippetsBarHelp.style.marginTop = "5px";
437
+
438
+ snippetsBarCheckboxContainer.appendChild(snippetsBarCheckbox);
439
+ snippetsBarCheckboxContainer.appendChild(snippetsBarLabel);
440
+
441
+ snippetsBarSection.appendChild(snippetsBarCheckboxContainer);
442
+ snippetsBarSection.appendChild(snippetsBarHelp);
443
+ container.appendChild(snippetsBarSection);
444
+ }
445
+
446
+ private drawEndpointsSettings(container: HTMLElement) {
447
+ const section = document.createElement("div");
448
+ addClass(section, "settingsSection");
449
+
450
+ const label = document.createElement("label");
451
+ label.textContent = "SPARQL Endpoints";
452
+ addClass(label, "settingsLabel");
453
+
454
+ const help = document.createElement("div");
455
+ help.textContent =
456
+ "Manage your SPARQL endpoints. Each endpoint can have its own authentication and be displayed as a quick-switch button.";
457
+ addClass(help, "settingsHelp");
458
+
459
+ section.appendChild(label);
460
+ section.appendChild(help);
461
+
462
+ // List of endpoints
463
+ const endpointsList = document.createElement("div");
464
+ addClass(endpointsList, "endpointsTable");
465
+ this.renderEndpointsList(endpointsList);
466
+ section.appendChild(endpointsList);
467
+
468
+ container.appendChild(section);
469
+ }
470
+
471
+ private renderEndpointsList(container: HTMLElement) {
472
+ container.innerHTML = "";
473
+ const configs = this.tab.yasgui.persistentConfig.getEndpointConfigs();
474
+
475
+ // Create table (even if empty, we'll show add form)
476
+ const table = document.createElement("table");
477
+ addClass(table, "endpointsTableElement");
478
+
479
+ // Header
480
+ const thead = document.createElement("thead");
481
+ const headerRow = document.createElement("tr");
482
+ const headers = ["Endpoint", "Label", "Button", "Authentication", "Actions"];
483
+ headers.forEach((h) => {
484
+ const th = document.createElement("th");
485
+ th.textContent = h;
486
+ headerRow.appendChild(th);
487
+ });
488
+ thead.appendChild(headerRow);
489
+ table.appendChild(thead);
490
+
491
+ // Body
492
+ const tbody = document.createElement("tbody");
493
+
494
+ if (configs.length === 0) {
495
+ // Show empty message in table
496
+ const emptyRow = document.createElement("tr");
497
+ const emptyCell = document.createElement("td");
498
+ emptyCell.colSpan = 5;
499
+ emptyCell.textContent = "No endpoints yet. Add one below or access an endpoint to have it automatically tracked.";
500
+ addClass(emptyCell, "emptyMessage");
501
+ emptyCell.style.textAlign = "center";
502
+ emptyCell.style.padding = "20px";
503
+ emptyRow.appendChild(emptyCell);
504
+ tbody.appendChild(emptyRow);
505
+ }
506
+
507
+ configs.forEach((config, index) => {
508
+ const row = document.createElement("tr");
509
+
510
+ // Endpoint column
511
+ const endpointCell = document.createElement("td");
512
+ endpointCell.textContent = config.endpoint;
513
+ endpointCell.title = config.endpoint;
514
+ addClass(endpointCell, "endpointCell");
515
+ row.appendChild(endpointCell);
516
+
517
+ // Label column (editable)
518
+ const labelCell = document.createElement("td");
519
+ const labelInput = document.createElement("input");
520
+ labelInput.type = "text";
521
+ labelInput.value = config.label || "";
522
+ labelInput.placeholder = "Optional label";
523
+ addClass(labelInput, "endpointLabelInput");
524
+ labelInput.onchange = () => {
525
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(config.endpoint, {
526
+ label: labelInput.value.trim() || undefined,
527
+ });
528
+ this.renderEndpointsList(container);
529
+ this.tab.refreshEndpointButtons();
530
+ };
531
+ labelCell.appendChild(labelInput);
532
+ row.appendChild(labelCell);
533
+
534
+ // Show as Button checkbox (requires label)
535
+ const buttonCell = document.createElement("td");
536
+ const buttonCheckbox = document.createElement("input");
537
+ buttonCheckbox.type = "checkbox";
538
+ buttonCheckbox.checked = !!config.showAsButton;
539
+ buttonCheckbox.disabled = !config.label;
540
+ buttonCheckbox.setAttribute(
541
+ "aria-label",
542
+ config.label ? "Show this endpoint as a quick-switch button" : "Add a label first to enable button",
543
+ );
544
+ buttonCheckbox.title = config.label
545
+ ? "Show this endpoint as a quick-switch button"
546
+ : "Add a label first to enable button";
547
+ buttonCheckbox.onchange = () => {
548
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(config.endpoint, {
549
+ showAsButton: buttonCheckbox.checked,
550
+ });
551
+ this.tab.refreshEndpointButtons();
552
+ };
553
+ buttonCell.appendChild(buttonCheckbox);
554
+ addClass(buttonCell, "centerCell");
555
+ row.appendChild(buttonCell);
556
+
557
+ // Authentication column
558
+ const authCell = document.createElement("td");
559
+ const authButton = document.createElement("button");
560
+ authButton.type = "button";
561
+ addClass(authButton, "configureAuthButton");
562
+ if (config.authentication) {
563
+ authButton.textContent = "✓ Configured";
564
+ addClass(authButton, "authenticated");
565
+ } else {
566
+ authButton.textContent = "Configure";
567
+ }
568
+ authButton.onclick = () => this.showAuthenticationModal(config.endpoint);
569
+ authCell.appendChild(authButton);
570
+ addClass(authCell, "centerCell");
571
+ row.appendChild(authCell);
572
+
573
+ // Actions column
574
+ const actionsCell = document.createElement("td");
575
+ const deleteButton = document.createElement("button");
576
+ deleteButton.type = "button";
577
+ deleteButton.textContent = "Delete";
578
+ addClass(deleteButton, "deleteEndpointButton");
579
+ deleteButton.onclick = () => {
580
+ if (confirm(`Delete endpoint "${config.endpoint}"?`)) {
581
+ this.tab.yasgui.persistentConfig.deleteEndpointConfig(config.endpoint);
582
+ this.renderEndpointsList(container);
583
+ this.tab.refreshEndpointButtons();
584
+ }
585
+ };
586
+ actionsCell.appendChild(deleteButton);
587
+ addClass(actionsCell, "centerCell");
588
+ row.appendChild(actionsCell);
589
+
590
+ tbody.appendChild(row);
591
+ });
592
+ table.appendChild(tbody);
593
+ container.appendChild(table);
594
+
595
+ // Add endpoint form
596
+ const addForm = document.createElement("div");
597
+ addClass(addForm, "addEndpointForm");
598
+
599
+ const addFormTitle = document.createElement("div");
600
+ addFormTitle.textContent = "Add New Endpoint";
601
+ addClass(addFormTitle, "addFormTitle");
602
+ addForm.appendChild(addFormTitle);
603
+
604
+ const formInputs = document.createElement("div");
605
+ addClass(formInputs, "addFormInputs");
606
+
607
+ const endpointInput = document.createElement("input");
608
+ endpointInput.type = "url";
609
+ endpointInput.placeholder = "Endpoint URL (e.g., https://dbpedia.org/sparql)";
610
+ addClass(endpointInput, "addEndpointInput");
611
+ formInputs.appendChild(endpointInput);
612
+
613
+ const addButton = document.createElement("button");
614
+ addButton.type = "button";
615
+ addButton.textContent = "+ Add Endpoint";
616
+ addClass(addButton, "addEndpointButton");
617
+ addButton.onclick = () => {
618
+ const endpoint = endpointInput.value.trim();
619
+
620
+ if (!endpoint) {
621
+ alert("Please enter an endpoint URL.");
622
+ return;
623
+ }
624
+
625
+ // Validate URL format
626
+ // Check for supported protocol first
627
+ if (!/^https?:\/\//i.test(endpoint)) {
628
+ alert("Endpoint URL must start with http:// or https://");
629
+ return;
630
+ }
631
+ try {
632
+ new URL(endpoint);
633
+ } catch (e) {
634
+ // Show the error message if available, otherwise a generic one
635
+ alert(e instanceof Error && e.message ? "Malformed URL: " + e.message : "Please enter a valid URL.");
636
+ return;
637
+ }
638
+
639
+ // Check if endpoint already exists
640
+ const existing = this.tab.yasgui.persistentConfig.getEndpointConfig(endpoint);
641
+ if (existing) {
642
+ alert("This endpoint is already in the list.");
643
+ return;
644
+ }
645
+
646
+ // Add the endpoint
647
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {});
648
+
649
+ // Clear input
650
+ endpointInput.value = "";
651
+
652
+ // Refresh list
653
+ this.renderEndpointsList(container);
654
+ this.tab.refreshEndpointButtons();
655
+ };
656
+ formInputs.appendChild(addButton);
657
+
658
+ addForm.appendChild(formInputs);
659
+ container.appendChild(addForm);
660
+ }
661
+
662
+ private showAuthenticationModal(endpoint: string) {
663
+ const config = this.tab.yasgui.persistentConfig.getEndpointConfig(endpoint);
664
+ const existingAuth = config?.authentication;
665
+
666
+ // Create modal overlay
667
+ const authModalOverlay = document.createElement("div");
668
+ addClass(authModalOverlay, "authModalOverlay");
669
+ authModalOverlay.onclick = () => authModalOverlay.remove();
670
+
671
+ // Create modal content
672
+ const authModal = document.createElement("div");
673
+ addClass(authModal, "authModal");
674
+ authModal.onclick = (e) => e.stopPropagation();
675
+
676
+ // Header
677
+ const header = document.createElement("div");
678
+ addClass(header, "authModalHeader");
679
+ const title = document.createElement("h3");
680
+ title.textContent = "Configure Authentication";
681
+ const subtitle = document.createElement("div");
682
+ subtitle.textContent = endpoint;
683
+ addClass(subtitle, "authModalSubtitle");
684
+ header.appendChild(title);
685
+ header.appendChild(subtitle);
686
+ authModal.appendChild(header);
687
+
688
+ // Body
689
+ const body = document.createElement("div");
690
+ addClass(body, "authModalBody");
691
+
692
+ // Auth type (for now only basic, but designed for future)
693
+ const typeSection = document.createElement("div");
694
+ addClass(typeSection, "authModalSection");
695
+ const typeLabel = document.createElement("label");
696
+ typeLabel.textContent = "Authentication Type";
697
+ const typeSelect = document.createElement("select");
698
+ const basicOption = document.createElement("option");
699
+ basicOption.value = "basic";
700
+ basicOption.textContent = "HTTP Basic Authentication";
701
+ typeSelect.appendChild(basicOption);
702
+ // Future: Add OAuth, Bearer Token, etc.
703
+ typeSection.appendChild(typeLabel);
704
+ typeSection.appendChild(typeSelect);
705
+ body.appendChild(typeSection);
706
+
707
+ // Username
708
+ const usernameSection = document.createElement("div");
709
+ addClass(usernameSection, "authModalSection");
710
+ const usernameLabel = document.createElement("label");
711
+ usernameLabel.textContent = "Username";
712
+ const usernameInput = document.createElement("input");
713
+ usernameInput.type = "text";
714
+ usernameInput.placeholder = "Enter username";
715
+ usernameInput.value = existingAuth?.username || "";
716
+ usernameInput.autocomplete = "username";
717
+ usernameSection.appendChild(usernameLabel);
718
+ usernameSection.appendChild(usernameInput);
719
+ body.appendChild(usernameSection);
720
+
721
+ // Password
722
+ const passwordSection = document.createElement("div");
723
+ addClass(passwordSection, "authModalSection");
724
+ const passwordLabel = document.createElement("label");
725
+ passwordLabel.textContent = "Password";
726
+ const passwordInput = document.createElement("input");
727
+ passwordInput.type = "password";
728
+ passwordInput.placeholder = "Enter password";
729
+ passwordInput.value = existingAuth?.password || "";
730
+ passwordInput.autocomplete = "current-password";
731
+ passwordSection.appendChild(passwordLabel);
732
+ passwordSection.appendChild(passwordInput);
733
+ body.appendChild(passwordSection);
734
+
735
+ // Security notice
736
+ const securityNotice = document.createElement("div");
737
+ addClass(securityNotice, "authSecurityNotice");
738
+ securityNotice.innerHTML = `
739
+ <strong>⚠️ Security Notice:</strong>
740
+ <ul>
741
+ <li>Credentials are stored in browser localStorage</li>
742
+ <li>Only use with HTTPS endpoints</li>
743
+ <li>Be cautious when using on shared computers</li>
744
+ </ul>
745
+ `;
746
+ body.appendChild(securityNotice);
747
+
748
+ authModal.appendChild(body);
749
+
750
+ // Footer
751
+ const footer = document.createElement("div");
752
+ addClass(footer, "authModalFooter");
753
+
754
+ const removeButton = document.createElement("button");
755
+ removeButton.textContent = "Remove Authentication";
756
+ removeButton.type = "button";
757
+ addClass(removeButton, "authRemoveButton");
758
+ removeButton.onclick = () => {
759
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
760
+ authentication: undefined,
761
+ });
762
+ authModalOverlay.remove();
763
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
764
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
765
+ };
766
+ if (!existingAuth) {
767
+ removeButton.disabled = true;
768
+ }
769
+
770
+ const cancelButton = document.createElement("button");
771
+ cancelButton.textContent = "Cancel";
772
+ cancelButton.type = "button";
773
+ addClass(cancelButton, "authCancelButton");
774
+ cancelButton.onclick = () => authModalOverlay.remove();
775
+
776
+ const saveButton = document.createElement("button");
777
+ saveButton.textContent = "Save";
778
+ saveButton.type = "button";
779
+ addClass(saveButton, "authSaveButton");
780
+ saveButton.onclick = () => {
781
+ const username = usernameInput.value.trim();
782
+ const password = passwordInput.value;
783
+
784
+ if (username && password) {
785
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
786
+ authentication: {
787
+ type: "basic",
788
+ username,
789
+ password,
790
+ },
791
+ });
792
+ authModalOverlay.remove();
793
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
794
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
795
+ } else {
796
+ alert("Please enter both username and password.");
797
+ }
798
+ };
799
+
800
+ footer.appendChild(removeButton);
801
+ footer.appendChild(cancelButton);
802
+ footer.appendChild(saveButton);
803
+ authModal.appendChild(footer);
804
+
805
+ authModalOverlay.appendChild(authModal);
806
+ document.body.appendChild(authModalOverlay);
381
807
  }
382
808
 
383
809
  private drawRequestSettings(container: HTMLElement) {
@@ -499,6 +925,10 @@ export default class TabSettingsModal {
499
925
  if (constructValidationCheckbox) {
500
926
  yasqe.setCheckConstructVariables(constructValidationCheckbox.checked);
501
927
  }
928
+ const snippetsBarCheckbox = document.getElementById("showSnippetsBar") as HTMLInputElement;
929
+ if (snippetsBarCheckbox) {
930
+ yasqe.setSnippetsBarVisible(snippetsBarCheckbox.checked);
931
+ }
502
932
  yasqe.saveQuery();
503
933
  }
504
934
 
@@ -516,6 +946,9 @@ export default class TabSettingsModal {
516
946
  this.tab.setRequestConfig(updates);
517
947
  }
518
948
 
949
+ // Note: Authentication is now handled per-endpoint in the Endpoints tab,
950
+ // not per-tab anymore. No need to save it here.
951
+
519
952
  // Refresh endpoint buttons to show any changes
520
953
  this.tab.refreshEndpointButtons();
521
954
 
@@ -614,116 +1047,8 @@ export default class TabSettingsModal {
614
1047
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
615
1048
  }
616
1049
 
617
- private drawEndpointButtonsSettings(container: HTMLElement) {
618
- const section = document.createElement("div");
619
- addClass(section, "settingsSection");
620
-
621
- const label = document.createElement("label");
622
- label.textContent = "Custom Endpoint Buttons";
623
- addClass(label, "settingsLabel");
624
-
625
- const help = document.createElement("div");
626
- help.textContent = "Add custom endpoint buttons that will appear next to the endpoint textbox.";
627
- addClass(help, "settingsHelp");
628
-
629
- section.appendChild(label);
630
- section.appendChild(help);
631
-
632
- // List of existing buttons
633
- const buttonsList = document.createElement("div");
634
- addClass(buttonsList, "endpointButtonsList");
635
- this.renderEndpointButtonsList(buttonsList);
636
- section.appendChild(buttonsList);
637
-
638
- // Form to add new button
639
- const addForm = document.createElement("div");
640
- addClass(addForm, "addEndpointButtonForm");
641
-
642
- const labelInput = document.createElement("input");
643
- labelInput.type = "text";
644
- labelInput.placeholder = "Button label (e.g., DBpedia)";
645
- addClass(labelInput, "endpointButtonLabelInput");
646
-
647
- const endpointInput = document.createElement("input");
648
- endpointInput.type = "url";
649
- endpointInput.placeholder = "Endpoint URL (e.g., https://dbpedia.org/sparql)";
650
- addClass(endpointInput, "endpointButtonEndpointInput");
651
-
652
- const addButton = document.createElement("button");
653
- addButton.textContent = "+ Add Button";
654
- addClass(addButton, "addEndpointButton");
655
- addButton.type = "button";
656
- addButton.onclick = () => {
657
- const labelValue = labelInput.value.trim();
658
- const endpointValue = endpointInput.value.trim();
659
-
660
- if (!labelValue || !endpointValue) {
661
- alert("Please enter both a label and an endpoint URL.");
662
- return;
663
- }
664
-
665
- // Add to persistent config
666
- const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
667
- currentButtons.push({ label: labelValue, endpoint: endpointValue });
668
- this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
669
-
670
- // Clear inputs
671
- labelInput.value = "";
672
- endpointInput.value = "";
673
-
674
- // Refresh list
675
- this.renderEndpointButtonsList(buttonsList);
676
- };
677
-
678
- addForm.appendChild(labelInput);
679
- addForm.appendChild(endpointInput);
680
- addForm.appendChild(addButton);
681
- section.appendChild(addForm);
682
-
683
- container.appendChild(section);
684
- }
685
-
686
- private renderEndpointButtonsList(container: HTMLElement) {
687
- container.innerHTML = "";
688
- const customButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
689
-
690
- if (customButtons.length === 0) {
691
- const emptyMsg = document.createElement("div");
692
- emptyMsg.textContent = "No custom buttons yet. Add one below.";
693
- addClass(emptyMsg, "emptyMessage");
694
- container.appendChild(emptyMsg);
695
- return;
696
- }
697
-
698
- customButtons.forEach((btn, index) => {
699
- const item = document.createElement("div");
700
- addClass(item, "endpointButtonItem");
701
-
702
- const labelSpan = document.createElement("span");
703
- labelSpan.textContent = `${btn.label}`;
704
- addClass(labelSpan, "buttonLabel");
705
-
706
- const endpointSpan = document.createElement("span");
707
- endpointSpan.textContent = btn.endpoint;
708
- addClass(endpointSpan, "buttonEndpoint");
709
-
710
- const removeBtn = document.createElement("button");
711
- removeBtn.textContent = "×";
712
- addClass(removeBtn, "removeButton");
713
- removeBtn.type = "button";
714
- removeBtn.onclick = () => {
715
- const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
716
- currentButtons.splice(index, 1);
717
- this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
718
- this.renderEndpointButtonsList(container);
719
- };
720
-
721
- item.appendChild(labelSpan);
722
- item.appendChild(endpointSpan);
723
- item.appendChild(removeBtn);
724
- container.appendChild(item);
725
- });
726
- }
1050
+ // Old endpoint buttons functionality has been merged into drawEndpointsSettings
1051
+ // Keeping this for reference if needed for backwards compatibility
727
1052
 
728
1053
  private getThemeToggleIcon(): string {
729
1054
  const currentTheme = this.tab.yasgui.getTheme();
@@ -885,6 +1210,112 @@ export default class TabSettingsModal {
885
1210
  container.appendChild(importSection);
886
1211
  }
887
1212
 
1213
+ private drawKeyboardShortcuts(container: HTMLElement) {
1214
+ const shortcutsData = [
1215
+ {
1216
+ category: "Query Editor (YASQE)",
1217
+ shortcuts: [
1218
+ { keys: ["Ctrl+Enter", "Cmd+Enter"], description: "Execute query" },
1219
+ { keys: ["Ctrl+Space", "Cmd+Space"], description: "Trigger autocomplete" },
1220
+ { keys: ["Ctrl+S", "Cmd+S"], description: "Save query to local storage" },
1221
+ { keys: ["Ctrl+Shift+F", "Cmd+Shift+F"], description: "Format query" },
1222
+ { keys: ["Ctrl+/", "Cmd+/"], description: "Toggle comment on selected lines" },
1223
+ { keys: ["Ctrl+Shift+D", "Cmd+Shift+D"], description: "Duplicate current line" },
1224
+ { keys: ["Ctrl+Shift+K", "Cmd+Shift+K"], description: "Delete current line" },
1225
+ { keys: ["Esc"], description: "Remove focus from editor" },
1226
+ { keys: ["Ctrl+Click"], description: "Explore URI connections (on URI)" },
1227
+ ],
1228
+ },
1229
+ {
1230
+ category: "Fullscreen",
1231
+ shortcuts: [
1232
+ { keys: ["F11"], description: "Toggle YASQE (editor) fullscreen" },
1233
+ { keys: ["F10"], description: "Toggle YASR (results) fullscreen" },
1234
+ { keys: ["F9"], description: "Switch between YASQE and YASR fullscreen" },
1235
+ { keys: ["Esc"], description: "Exit fullscreen mode" },
1236
+ ],
1237
+ },
1238
+ ];
1239
+
1240
+ shortcutsData.forEach((section) => {
1241
+ const sectionEl = document.createElement("div");
1242
+ addClass(sectionEl, "shortcutsSection");
1243
+
1244
+ const categoryLabel = document.createElement("h3");
1245
+ categoryLabel.textContent = section.category;
1246
+ addClass(categoryLabel, "shortcutsCategory");
1247
+ sectionEl.appendChild(categoryLabel);
1248
+
1249
+ const table = document.createElement("table");
1250
+ addClass(table, "shortcutsTable");
1251
+ table.setAttribute("role", "table");
1252
+ table.setAttribute("aria-label", `${section.category} keyboard shortcuts`);
1253
+
1254
+ // Add table caption for screen readers
1255
+ const caption = document.createElement("caption");
1256
+ caption.textContent = `${section.category} keyboard shortcuts`;
1257
+ caption.style.position = "absolute";
1258
+ caption.style.left = "-10000px";
1259
+ caption.style.width = "1px";
1260
+ caption.style.height = "1px";
1261
+ caption.style.overflow = "hidden";
1262
+ table.appendChild(caption);
1263
+
1264
+ // Add thead with proper headers
1265
+ const thead = document.createElement("thead");
1266
+ const headerRow = document.createElement("tr");
1267
+
1268
+ const keysHeader = document.createElement("th");
1269
+ keysHeader.textContent = "Keys";
1270
+ keysHeader.setAttribute("scope", "col");
1271
+ addClass(keysHeader, "shortcutsKeysHeader");
1272
+ headerRow.appendChild(keysHeader);
1273
+
1274
+ const descHeader = document.createElement("th");
1275
+ descHeader.textContent = "Description";
1276
+ descHeader.setAttribute("scope", "col");
1277
+ addClass(descHeader, "shortcutsDescHeader");
1278
+ headerRow.appendChild(descHeader);
1279
+
1280
+ thead.appendChild(headerRow);
1281
+ table.appendChild(thead);
1282
+
1283
+ // Add tbody
1284
+ const tbody = document.createElement("tbody");
1285
+
1286
+ section.shortcuts.forEach((shortcut) => {
1287
+ const row = document.createElement("tr");
1288
+
1289
+ const keysCell = document.createElement("td");
1290
+ addClass(keysCell, "shortcutsKeys");
1291
+ shortcut.keys.forEach((key, index) => {
1292
+ if (index > 0) {
1293
+ const separator = document.createElement("span");
1294
+ separator.textContent = " / ";
1295
+ addClass(separator, "shortcutsSeparator");
1296
+ keysCell.appendChild(separator);
1297
+ }
1298
+ const kbd = document.createElement("kbd");
1299
+ kbd.textContent = key;
1300
+ keysCell.appendChild(kbd);
1301
+ });
1302
+ row.appendChild(keysCell);
1303
+
1304
+ const descCell = document.createElement("td");
1305
+ addClass(descCell, "shortcutsDescription");
1306
+ descCell.textContent = shortcut.description;
1307
+ row.appendChild(descCell);
1308
+
1309
+ tbody.appendChild(row);
1310
+ });
1311
+
1312
+ table.appendChild(tbody);
1313
+
1314
+ sectionEl.appendChild(table);
1315
+ container.appendChild(sectionEl);
1316
+ });
1317
+ }
1318
+
888
1319
  private async importConfiguration(turtleContent: string) {
889
1320
  try {
890
1321
  const parsedConfig = ConfigExportImport.parseFromTurtle(turtleContent);
@@ -940,6 +1371,108 @@ export default class TabSettingsModal {
940
1371
  }, 5000);
941
1372
  }
942
1373
 
1374
+ private drawAboutSettings(container: HTMLElement) {
1375
+ // About Section
1376
+ const aboutSection = document.createElement("div");
1377
+ addClass(aboutSection, "settingsSection", "aboutSection");
1378
+
1379
+ // YASGUI Title and Version
1380
+ const titleContainer = document.createElement("div");
1381
+ addClass(titleContainer, "aboutTitle");
1382
+
1383
+ const title = document.createElement("h3");
1384
+ title.textContent = "YASGUI";
1385
+ addClass(title, "aboutMainTitle");
1386
+
1387
+ const versionBadge = document.createElement("span");
1388
+ versionBadge.textContent = `v${VERSION}`;
1389
+ addClass(versionBadge, "versionBadge");
1390
+
1391
+ titleContainer.appendChild(title);
1392
+ titleContainer.appendChild(versionBadge);
1393
+ aboutSection.appendChild(titleContainer);
1394
+
1395
+ // Subtitle
1396
+ const subtitle = document.createElement("p");
1397
+ subtitle.textContent = "Yet Another SPARQL GUI";
1398
+ addClass(subtitle, "aboutSubtitle");
1399
+ aboutSection.appendChild(subtitle);
1400
+
1401
+ // Links Section
1402
+ const linksSection = document.createElement("div");
1403
+ addClass(linksSection, "aboutLinks");
1404
+
1405
+ // Documentation Link
1406
+ const docsLink = this.createAboutLink(
1407
+ "📚 Documentation",
1408
+ "https://yasgui-doc.matdata.eu/docs/",
1409
+ "View the complete documentation and guides",
1410
+ );
1411
+ linksSection.appendChild(docsLink);
1412
+
1413
+ // Release Notes Link
1414
+ const releasesLink = this.createAboutLink(
1415
+ "📝 Release Notes",
1416
+ "https://github.com/Matdata-eu/Yasgui/releases",
1417
+ "See what's new in the latest releases",
1418
+ );
1419
+ linksSection.appendChild(releasesLink);
1420
+
1421
+ // Issues/Support Link
1422
+ const issuesLink = this.createAboutLink(
1423
+ "🐛 Report Issues & Get Support",
1424
+ "https://github.com/Matdata-eu/Yasgui/issues",
1425
+ "Report bugs, request features, or ask for help",
1426
+ );
1427
+ linksSection.appendChild(issuesLink);
1428
+
1429
+ aboutSection.appendChild(linksSection);
1430
+
1431
+ // Footer info
1432
+ const footerInfo = document.createElement("div");
1433
+ addClass(footerInfo, "aboutFooter");
1434
+
1435
+ const paragraph1 = document.createElement("p");
1436
+ paragraph1.textContent = "YASGUI is an open-source project maintained by ";
1437
+ const matdataLink = document.createElement("a");
1438
+ matdataLink.href = "https://matdata.eu";
1439
+ matdataLink.target = "_blank";
1440
+ matdataLink.rel = "noopener noreferrer";
1441
+ matdataLink.textContent = "Matdata";
1442
+ paragraph1.appendChild(matdataLink);
1443
+ paragraph1.appendChild(document.createTextNode("."));
1444
+
1445
+ const paragraph2 = document.createElement("p");
1446
+ paragraph2.textContent = "Licensed under the MIT License.";
1447
+
1448
+ footerInfo.appendChild(paragraph1);
1449
+ footerInfo.appendChild(paragraph2);
1450
+ aboutSection.appendChild(footerInfo);
1451
+
1452
+ container.appendChild(aboutSection);
1453
+ }
1454
+
1455
+ private createAboutLink(label: string, url: string, description: string): HTMLElement {
1456
+ const linkContainer = document.createElement("div");
1457
+ addClass(linkContainer, "aboutLinkItem");
1458
+
1459
+ const link = document.createElement("a");
1460
+ link.href = url;
1461
+ link.target = "_blank";
1462
+ link.rel = "noopener noreferrer";
1463
+ link.textContent = label;
1464
+ addClass(link, "aboutLink");
1465
+
1466
+ const desc = document.createElement("p");
1467
+ desc.textContent = description;
1468
+ addClass(desc, "aboutLinkDescription");
1469
+
1470
+ linkContainer.appendChild(link);
1471
+ linkContainer.appendChild(desc);
1472
+
1473
+ return linkContainer;
1474
+ }
1475
+
943
1476
  public destroy() {
944
1477
  if (this.modalOverlay && this.modalOverlay.parentNode) {
945
1478
  this.modalOverlay.parentNode.removeChild(this.modalOverlay);