@matdata/yasgui 5.4.0 → 5.6.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.
@@ -34,6 +34,9 @@ const AcceptHeaderGraphMap: { key: string; value: string }[] = [
34
34
  { key: "TSV", value: "text/tab-separated-values,*/*;q=0.9" },
35
35
  ];
36
36
 
37
+ // Default API Key header name
38
+ const DEFAULT_API_KEY_HEADER = "X-API-Key";
39
+
37
40
  export default class TabSettingsModal {
38
41
  private tab: Tab;
39
42
  private modalOverlay!: HTMLElement;
@@ -111,56 +114,63 @@ export default class TabSettingsModal {
111
114
 
112
115
  this.modalContent.appendChild(header);
113
116
 
114
- // Body with tabs
117
+ // Body with sidebar navigation
115
118
  const body = document.createElement("div");
116
119
  addClass(body, "modalBody");
117
120
 
118
- const tabsContainer = document.createElement("div");
119
- addClass(tabsContainer, "modalTabs");
121
+ // Sidebar navigation
122
+ const sidebar = document.createElement("div");
123
+ addClass(sidebar, "modalSidebar");
120
124
 
121
125
  const requestTab = document.createElement("button");
122
126
  requestTab.textContent = "Request";
123
- addClass(requestTab, "modalTabButton", "active");
127
+ addClass(requestTab, "modalNavButton", "active");
124
128
  requestTab.onclick = () => this.switchTab("request");
125
129
 
130
+ const endpointsTab = document.createElement("button");
131
+ endpointsTab.textContent = "SPARQL Endpoints";
132
+ addClass(endpointsTab, "modalNavButton");
133
+ endpointsTab.onclick = () => this.switchTab("endpoints");
134
+
126
135
  const prefixTab = document.createElement("button");
127
136
  prefixTab.textContent = "Prefixes";
128
- addClass(prefixTab, "modalTabButton");
137
+ addClass(prefixTab, "modalNavButton");
129
138
  prefixTab.onclick = () => this.switchTab("prefix");
130
139
 
131
140
  const editorTab = document.createElement("button");
132
141
  editorTab.textContent = "Editor";
133
- addClass(editorTab, "modalTabButton");
142
+ addClass(editorTab, "modalNavButton");
134
143
  editorTab.onclick = () => this.switchTab("editor");
135
144
 
136
- const endpointsTab = document.createElement("button");
137
- endpointsTab.textContent = "Endpoint Buttons";
138
- addClass(endpointsTab, "modalTabButton");
139
- endpointsTab.onclick = () => this.switchTab("endpoints");
140
-
141
145
  const importExportTab = document.createElement("button");
142
146
  importExportTab.textContent = "Import/Export";
143
- addClass(importExportTab, "modalTabButton");
147
+ addClass(importExportTab, "modalNavButton");
144
148
  importExportTab.onclick = () => this.switchTab("importexport");
145
149
 
146
150
  const shortcutsTab = document.createElement("button");
147
151
  shortcutsTab.textContent = "Keyboard Shortcuts";
148
- addClass(shortcutsTab, "modalTabButton");
152
+ addClass(shortcutsTab, "modalNavButton");
149
153
  shortcutsTab.onclick = () => this.switchTab("shortcuts");
150
154
 
151
155
  const aboutTab = document.createElement("button");
152
156
  aboutTab.textContent = "About";
153
- addClass(aboutTab, "modalTabButton");
157
+ addClass(aboutTab, "modalNavButton");
154
158
  aboutTab.onclick = () => this.switchTab("about");
155
159
 
156
- tabsContainer.appendChild(requestTab);
157
- tabsContainer.appendChild(prefixTab);
158
- tabsContainer.appendChild(editorTab);
159
- tabsContainer.appendChild(endpointsTab);
160
- tabsContainer.appendChild(importExportTab);
161
- tabsContainer.appendChild(shortcutsTab);
162
- tabsContainer.appendChild(aboutTab);
163
- body.appendChild(tabsContainer);
160
+ sidebar.appendChild(requestTab);
161
+ sidebar.appendChild(endpointsTab);
162
+ sidebar.appendChild(prefixTab);
163
+ sidebar.appendChild(editorTab);
164
+ sidebar.appendChild(importExportTab);
165
+ sidebar.appendChild(shortcutsTab);
166
+ sidebar.appendChild(aboutTab);
167
+
168
+ // Content area container
169
+ const contentArea = document.createElement("div");
170
+ addClass(contentArea, "modalContentArea");
171
+
172
+ body.appendChild(sidebar);
173
+ body.appendChild(contentArea);
164
174
 
165
175
  // Tab content containers
166
176
  const requestContent = document.createElement("div");
@@ -168,6 +178,11 @@ export default class TabSettingsModal {
168
178
  requestContent.id = "request-content";
169
179
  this.drawRequestSettings(requestContent);
170
180
 
181
+ const endpointsContent = document.createElement("div");
182
+ addClass(endpointsContent, "modalTabContent");
183
+ endpointsContent.id = "endpoints-content";
184
+ this.drawEndpointsSettings(endpointsContent);
185
+
171
186
  const prefixContent = document.createElement("div");
172
187
  addClass(prefixContent, "modalTabContent");
173
188
  prefixContent.id = "prefix-content";
@@ -178,11 +193,6 @@ export default class TabSettingsModal {
178
193
  editorContent.id = "editor-content";
179
194
  this.drawEditorSettings(editorContent);
180
195
 
181
- const endpointsContent = document.createElement("div");
182
- addClass(endpointsContent, "modalTabContent");
183
- endpointsContent.id = "endpoints-content";
184
- this.drawEndpointButtonsSettings(endpointsContent);
185
-
186
196
  const importExportContent = document.createElement("div");
187
197
  addClass(importExportContent, "modalTabContent");
188
198
  importExportContent.id = "importexport-content";
@@ -198,13 +208,13 @@ export default class TabSettingsModal {
198
208
  aboutContent.id = "about-content";
199
209
  this.drawAboutSettings(aboutContent);
200
210
 
201
- body.appendChild(requestContent);
202
- body.appendChild(prefixContent);
203
- body.appendChild(editorContent);
204
- body.appendChild(endpointsContent);
205
- body.appendChild(importExportContent);
206
- body.appendChild(shortcutsContent);
207
- body.appendChild(aboutContent);
211
+ contentArea.appendChild(requestContent);
212
+ contentArea.appendChild(endpointsContent);
213
+ contentArea.appendChild(prefixContent);
214
+ contentArea.appendChild(editorContent);
215
+ contentArea.appendChild(importExportContent);
216
+ contentArea.appendChild(shortcutsContent);
217
+ contentArea.appendChild(aboutContent);
208
218
 
209
219
  this.modalContent.appendChild(body);
210
220
 
@@ -232,15 +242,15 @@ export default class TabSettingsModal {
232
242
  }
233
243
 
234
244
  private switchTab(tabName: string) {
235
- const buttons = this.modalContent.querySelectorAll(".modalTabButton");
245
+ const buttons = this.modalContent.querySelectorAll(".modalNavButton");
236
246
  const contents = this.modalContent.querySelectorAll(".modalTabContent");
237
247
 
238
248
  buttons.forEach((btn, index) => {
239
249
  if (
240
250
  (tabName === "request" && index === 0) ||
241
- (tabName === "prefix" && index === 1) ||
242
- (tabName === "editor" && index === 2) ||
243
- (tabName === "endpoints" && index === 3) ||
251
+ (tabName === "endpoints" && index === 1) ||
252
+ (tabName === "prefix" && index === 2) ||
253
+ (tabName === "editor" && index === 3) ||
244
254
  (tabName === "importexport" && index === 4) ||
245
255
  (tabName === "shortcuts" && index === 5) ||
246
256
  (tabName === "about" && index === 6)
@@ -416,7 +426,10 @@ export default class TabSettingsModal {
416
426
  const snippetsBarCheckbox = document.createElement("input");
417
427
  snippetsBarCheckbox.type = "checkbox";
418
428
  snippetsBarCheckbox.id = "showSnippetsBar";
419
- snippetsBarCheckbox.checked = yasqe.getSnippetsBarVisible();
429
+ // Read from global config
430
+ const persistedValue = this.tab.yasgui.persistentConfig.getShowSnippetsBar();
431
+ snippetsBarCheckbox.checked =
432
+ persistedValue !== undefined ? persistedValue : this.tab.yasgui.config.showSnippetsBar !== false;
420
433
 
421
434
  const snippetsBarLabel = document.createElement("label");
422
435
  snippetsBarLabel.htmlFor = "showSnippetsBar";
@@ -436,6 +449,496 @@ export default class TabSettingsModal {
436
449
  container.appendChild(snippetsBarSection);
437
450
  }
438
451
 
452
+ private drawEndpointsSettings(container: HTMLElement) {
453
+ const section = document.createElement("div");
454
+ addClass(section, "settingsSection");
455
+
456
+ const label = document.createElement("label");
457
+ label.textContent = "SPARQL Endpoints";
458
+ addClass(label, "settingsLabel");
459
+
460
+ const help = document.createElement("div");
461
+ help.textContent =
462
+ "Manage your SPARQL endpoints. Each endpoint can have its own authentication and be displayed as a quick-switch button.";
463
+ addClass(help, "settingsHelp");
464
+
465
+ section.appendChild(label);
466
+ section.appendChild(help);
467
+
468
+ // List of endpoints
469
+ const endpointsList = document.createElement("div");
470
+ addClass(endpointsList, "endpointsTable");
471
+ this.renderEndpointsList(endpointsList);
472
+ section.appendChild(endpointsList);
473
+
474
+ container.appendChild(section);
475
+ }
476
+
477
+ private renderEndpointsList(container: HTMLElement) {
478
+ container.innerHTML = "";
479
+ const configs = this.tab.yasgui.persistentConfig.getEndpointConfigs();
480
+
481
+ // Create table (even if empty, we'll show add form)
482
+ const table = document.createElement("table");
483
+ addClass(table, "endpointsTableElement");
484
+
485
+ // Header
486
+ const thead = document.createElement("thead");
487
+ const headerRow = document.createElement("tr");
488
+ const headers = ["Endpoint", "Label", "Button", "Authentication", "Actions"];
489
+ headers.forEach((h) => {
490
+ const th = document.createElement("th");
491
+ th.textContent = h;
492
+ headerRow.appendChild(th);
493
+ });
494
+ thead.appendChild(headerRow);
495
+ table.appendChild(thead);
496
+
497
+ // Body
498
+ const tbody = document.createElement("tbody");
499
+
500
+ if (configs.length === 0) {
501
+ // Show empty message in table
502
+ const emptyRow = document.createElement("tr");
503
+ const emptyCell = document.createElement("td");
504
+ emptyCell.colSpan = 5;
505
+ emptyCell.textContent = "No endpoints yet. Add one below or access an endpoint to have it automatically tracked.";
506
+ addClass(emptyCell, "emptyMessage");
507
+ emptyCell.style.textAlign = "center";
508
+ emptyCell.style.padding = "20px";
509
+ emptyRow.appendChild(emptyCell);
510
+ tbody.appendChild(emptyRow);
511
+ }
512
+
513
+ configs.forEach((config, index) => {
514
+ const row = document.createElement("tr");
515
+
516
+ // Endpoint column
517
+ const endpointCell = document.createElement("td");
518
+ endpointCell.textContent = config.endpoint;
519
+ endpointCell.title = config.endpoint;
520
+ addClass(endpointCell, "endpointCell");
521
+ row.appendChild(endpointCell);
522
+
523
+ // Label column (editable)
524
+ const labelCell = document.createElement("td");
525
+ const labelInput = document.createElement("input");
526
+ labelInput.type = "text";
527
+ labelInput.value = config.label || "";
528
+ labelInput.placeholder = "Optional label";
529
+ addClass(labelInput, "endpointLabelInput");
530
+ labelInput.onchange = () => {
531
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(config.endpoint, {
532
+ label: labelInput.value.trim() || undefined,
533
+ });
534
+ this.renderEndpointsList(container);
535
+ this.tab.refreshEndpointButtons();
536
+ };
537
+ labelCell.appendChild(labelInput);
538
+ row.appendChild(labelCell);
539
+
540
+ // Show as Button checkbox (requires label)
541
+ const buttonCell = document.createElement("td");
542
+ const buttonCheckbox = document.createElement("input");
543
+ buttonCheckbox.type = "checkbox";
544
+ buttonCheckbox.checked = !!config.showAsButton;
545
+ buttonCheckbox.disabled = !config.label;
546
+ buttonCheckbox.setAttribute(
547
+ "aria-label",
548
+ config.label ? "Show this endpoint as a quick-switch button" : "Add a label first to enable button",
549
+ );
550
+ buttonCheckbox.title = config.label
551
+ ? "Show this endpoint as a quick-switch button"
552
+ : "Add a label first to enable button";
553
+ buttonCheckbox.onchange = () => {
554
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(config.endpoint, {
555
+ showAsButton: buttonCheckbox.checked,
556
+ });
557
+ this.tab.refreshEndpointButtons();
558
+ };
559
+ buttonCell.appendChild(buttonCheckbox);
560
+ addClass(buttonCell, "centerCell");
561
+ row.appendChild(buttonCell);
562
+
563
+ // Authentication column
564
+ const authCell = document.createElement("td");
565
+ const authButton = document.createElement("button");
566
+ authButton.type = "button";
567
+ addClass(authButton, "configureAuthButton");
568
+ if (config.authentication) {
569
+ authButton.textContent = "✓ Configured";
570
+ addClass(authButton, "authenticated");
571
+ } else {
572
+ authButton.textContent = "Configure";
573
+ }
574
+ authButton.onclick = () => this.showAuthenticationModal(config.endpoint);
575
+ authCell.appendChild(authButton);
576
+ addClass(authCell, "centerCell");
577
+ row.appendChild(authCell);
578
+
579
+ // Actions column
580
+ const actionsCell = document.createElement("td");
581
+ const deleteButton = document.createElement("button");
582
+ deleteButton.type = "button";
583
+ deleteButton.textContent = "Delete";
584
+ addClass(deleteButton, "deleteEndpointButton");
585
+ deleteButton.onclick = () => {
586
+ if (confirm(`Delete endpoint "${config.endpoint}"?`)) {
587
+ this.tab.yasgui.persistentConfig.deleteEndpointConfig(config.endpoint);
588
+ this.renderEndpointsList(container);
589
+ this.tab.refreshEndpointButtons();
590
+ }
591
+ };
592
+ actionsCell.appendChild(deleteButton);
593
+ addClass(actionsCell, "centerCell");
594
+ row.appendChild(actionsCell);
595
+
596
+ tbody.appendChild(row);
597
+ });
598
+ table.appendChild(tbody);
599
+ container.appendChild(table);
600
+
601
+ // Add endpoint form
602
+ const addForm = document.createElement("div");
603
+ addClass(addForm, "addEndpointForm");
604
+
605
+ const addFormTitle = document.createElement("div");
606
+ addFormTitle.textContent = "Add New Endpoint";
607
+ addClass(addFormTitle, "addFormTitle");
608
+ addForm.appendChild(addFormTitle);
609
+
610
+ const formInputs = document.createElement("div");
611
+ addClass(formInputs, "addFormInputs");
612
+
613
+ const endpointInput = document.createElement("input");
614
+ endpointInput.type = "url";
615
+ endpointInput.placeholder = "Endpoint URL (e.g., https://dbpedia.org/sparql)";
616
+ addClass(endpointInput, "addEndpointInput");
617
+ formInputs.appendChild(endpointInput);
618
+
619
+ const addButton = document.createElement("button");
620
+ addButton.type = "button";
621
+ addButton.textContent = "+ Add Endpoint";
622
+ addClass(addButton, "addEndpointButton");
623
+ addButton.onclick = () => {
624
+ const endpoint = endpointInput.value.trim();
625
+
626
+ if (!endpoint) {
627
+ alert("Please enter an endpoint URL.");
628
+ return;
629
+ }
630
+
631
+ // Validate URL format
632
+ // Check for supported protocol first
633
+ if (!/^https?:\/\//i.test(endpoint)) {
634
+ alert("Endpoint URL must start with http:// or https://");
635
+ return;
636
+ }
637
+ try {
638
+ new URL(endpoint);
639
+ } catch (e) {
640
+ // Show the error message if available, otherwise a generic one
641
+ alert(e instanceof Error && e.message ? "Malformed URL: " + e.message : "Please enter a valid URL.");
642
+ return;
643
+ }
644
+
645
+ // Check if endpoint already exists
646
+ const existing = this.tab.yasgui.persistentConfig.getEndpointConfig(endpoint);
647
+ if (existing) {
648
+ alert("This endpoint is already in the list.");
649
+ return;
650
+ }
651
+
652
+ // Add the endpoint
653
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {});
654
+
655
+ // Clear input
656
+ endpointInput.value = "";
657
+
658
+ // Refresh list
659
+ this.renderEndpointsList(container);
660
+ this.tab.refreshEndpointButtons();
661
+ };
662
+ formInputs.appendChild(addButton);
663
+
664
+ addForm.appendChild(formInputs);
665
+ container.appendChild(addForm);
666
+ }
667
+
668
+ private showAuthenticationModal(endpoint: string) {
669
+ const config = this.tab.yasgui.persistentConfig.getEndpointConfig(endpoint);
670
+ const existingAuth = config?.authentication;
671
+
672
+ // Create modal overlay
673
+ const authModalOverlay = document.createElement("div");
674
+ addClass(authModalOverlay, "authModalOverlay");
675
+ authModalOverlay.onclick = () => authModalOverlay.remove();
676
+
677
+ // Create modal content
678
+ const authModal = document.createElement("div");
679
+ addClass(authModal, "authModal");
680
+ authModal.onclick = (e) => e.stopPropagation();
681
+
682
+ // Header
683
+ const header = document.createElement("div");
684
+ addClass(header, "authModalHeader");
685
+ const title = document.createElement("h3");
686
+ title.textContent = "Configure Authentication";
687
+ const subtitle = document.createElement("div");
688
+ subtitle.textContent = endpoint;
689
+ addClass(subtitle, "authModalSubtitle");
690
+ header.appendChild(title);
691
+ header.appendChild(subtitle);
692
+ authModal.appendChild(header);
693
+
694
+ // Body
695
+ const body = document.createElement("div");
696
+ addClass(body, "authModalBody");
697
+
698
+ // Auth type
699
+ const typeSection = document.createElement("div");
700
+ addClass(typeSection, "authModalSection");
701
+ const typeLabel = document.createElement("label");
702
+ typeLabel.textContent = "Authentication Type";
703
+ const typeSelect = document.createElement("select");
704
+
705
+ const basicOption = document.createElement("option");
706
+ basicOption.value = "basic";
707
+ basicOption.textContent = "HTTP Basic Authentication";
708
+ typeSelect.appendChild(basicOption);
709
+
710
+ const bearerOption = document.createElement("option");
711
+ bearerOption.value = "bearer";
712
+ bearerOption.textContent = "Bearer Token";
713
+ typeSelect.appendChild(bearerOption);
714
+
715
+ const apiKeyOption = document.createElement("option");
716
+ apiKeyOption.value = "apiKey";
717
+ apiKeyOption.textContent = "API Key (Custom Header)";
718
+ typeSelect.appendChild(apiKeyOption);
719
+
720
+ // Set the current auth type
721
+ if (existingAuth) {
722
+ typeSelect.value = existingAuth.type;
723
+ }
724
+
725
+ typeSection.appendChild(typeLabel);
726
+ typeSection.appendChild(typeSelect);
727
+ body.appendChild(typeSection);
728
+
729
+ // Basic Auth Fields
730
+ const basicAuthFields = document.createElement("div");
731
+ basicAuthFields.id = "basicAuthFields";
732
+ addClass(basicAuthFields, "authFieldsContainer");
733
+
734
+ const usernameSection = document.createElement("div");
735
+ addClass(usernameSection, "authModalSection");
736
+ const usernameLabel = document.createElement("label");
737
+ usernameLabel.textContent = "Username";
738
+ const usernameInput = document.createElement("input");
739
+ usernameInput.type = "text";
740
+ usernameInput.placeholder = "Enter username";
741
+ usernameInput.value = existingAuth?.type === "basic" ? existingAuth.username : "";
742
+ usernameInput.autocomplete = "username";
743
+ usernameSection.appendChild(usernameLabel);
744
+ usernameSection.appendChild(usernameInput);
745
+ basicAuthFields.appendChild(usernameSection);
746
+
747
+ const passwordSection = document.createElement("div");
748
+ addClass(passwordSection, "authModalSection");
749
+ const passwordLabel = document.createElement("label");
750
+ passwordLabel.textContent = "Password";
751
+ const passwordInput = document.createElement("input");
752
+ passwordInput.type = "password";
753
+ passwordInput.placeholder = "Enter password";
754
+ passwordInput.value = existingAuth?.type === "basic" ? existingAuth.password : "";
755
+ passwordInput.autocomplete = "current-password";
756
+ passwordSection.appendChild(passwordLabel);
757
+ passwordSection.appendChild(passwordInput);
758
+ basicAuthFields.appendChild(passwordSection);
759
+
760
+ body.appendChild(basicAuthFields);
761
+
762
+ // Bearer Token Fields
763
+ const bearerAuthFields = document.createElement("div");
764
+ bearerAuthFields.id = "bearerAuthFields";
765
+ addClass(bearerAuthFields, "authFieldsContainer");
766
+ bearerAuthFields.style.display = "none";
767
+
768
+ const tokenSection = document.createElement("div");
769
+ addClass(tokenSection, "authModalSection");
770
+ const tokenLabel = document.createElement("label");
771
+ tokenLabel.textContent = "Bearer Token";
772
+ const tokenInput = document.createElement("input");
773
+ tokenInput.type = "password";
774
+ tokenInput.placeholder = "Enter bearer token";
775
+ tokenInput.autocomplete = "off";
776
+ tokenInput.value = existingAuth?.type === "bearer" ? existingAuth.token : "";
777
+ tokenSection.appendChild(tokenLabel);
778
+ tokenSection.appendChild(tokenInput);
779
+ bearerAuthFields.appendChild(tokenSection);
780
+
781
+ body.appendChild(bearerAuthFields);
782
+
783
+ // API Key Fields
784
+ const apiKeyAuthFields = document.createElement("div");
785
+ apiKeyAuthFields.id = "apiKeyAuthFields";
786
+ addClass(apiKeyAuthFields, "authFieldsContainer");
787
+ apiKeyAuthFields.style.display = "none";
788
+
789
+ const headerNameSection = document.createElement("div");
790
+ addClass(headerNameSection, "authModalSection");
791
+ const headerNameLabel = document.createElement("label");
792
+ headerNameLabel.textContent = "Header Name";
793
+ const headerNameInput = document.createElement("input");
794
+ headerNameInput.type = "text";
795
+ headerNameInput.placeholder = `e.g., ${DEFAULT_API_KEY_HEADER}`;
796
+ headerNameInput.value = existingAuth?.type === "apiKey" ? existingAuth.headerName : DEFAULT_API_KEY_HEADER;
797
+ headerNameSection.appendChild(headerNameLabel);
798
+ headerNameSection.appendChild(headerNameInput);
799
+ apiKeyAuthFields.appendChild(headerNameSection);
800
+
801
+ const apiKeySection = document.createElement("div");
802
+ addClass(apiKeySection, "authModalSection");
803
+ const apiKeyLabel = document.createElement("label");
804
+ apiKeyLabel.textContent = "API Key";
805
+ const apiKeyInput = document.createElement("input");
806
+ apiKeyInput.type = "password";
807
+ apiKeyInput.placeholder = "Enter API key";
808
+ apiKeyInput.autocomplete = "off";
809
+ apiKeyInput.value = existingAuth?.type === "apiKey" ? existingAuth.apiKey : "";
810
+ apiKeySection.appendChild(apiKeyLabel);
811
+ apiKeySection.appendChild(apiKeyInput);
812
+ apiKeyAuthFields.appendChild(apiKeySection);
813
+
814
+ body.appendChild(apiKeyAuthFields);
815
+
816
+ // Function to toggle fields based on auth type
817
+ const toggleAuthFields = () => {
818
+ const authType = typeSelect.value;
819
+ basicAuthFields.style.display = authType === "basic" ? "block" : "none";
820
+ bearerAuthFields.style.display = authType === "bearer" ? "block" : "none";
821
+ apiKeyAuthFields.style.display = authType === "apiKey" ? "block" : "none";
822
+ };
823
+
824
+ // Set initial visibility
825
+ toggleAuthFields();
826
+
827
+ // Update visibility when auth type changes
828
+ typeSelect.onchange = toggleAuthFields;
829
+
830
+ // Security notice
831
+ const securityNotice = document.createElement("div");
832
+ addClass(securityNotice, "authSecurityNotice");
833
+ securityNotice.innerHTML = `
834
+ <strong>⚠️ Security Notice:</strong>
835
+ <ul>
836
+ <li>Credentials are stored in browser localStorage</li>
837
+ <li>Only use with HTTPS endpoints</li>
838
+ <li>Be cautious when using on shared computers</li>
839
+ </ul>
840
+ `;
841
+ body.appendChild(securityNotice);
842
+
843
+ authModal.appendChild(body);
844
+
845
+ // Footer
846
+ const footer = document.createElement("div");
847
+ addClass(footer, "authModalFooter");
848
+
849
+ const removeButton = document.createElement("button");
850
+ removeButton.textContent = "Remove Authentication";
851
+ removeButton.type = "button";
852
+ addClass(removeButton, "authRemoveButton");
853
+ removeButton.onclick = () => {
854
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
855
+ authentication: undefined,
856
+ });
857
+ authModalOverlay.remove();
858
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
859
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
860
+ };
861
+ if (!existingAuth) {
862
+ removeButton.disabled = true;
863
+ }
864
+
865
+ const cancelButton = document.createElement("button");
866
+ cancelButton.textContent = "Cancel";
867
+ cancelButton.type = "button";
868
+ addClass(cancelButton, "authCancelButton");
869
+ cancelButton.onclick = () => authModalOverlay.remove();
870
+
871
+ const saveButton = document.createElement("button");
872
+ saveButton.textContent = "Save";
873
+ saveButton.type = "button";
874
+ addClass(saveButton, "authSaveButton");
875
+ saveButton.onclick = () => {
876
+ const authType = typeSelect.value;
877
+
878
+ if (authType === "basic") {
879
+ const username = usernameInput.value.trim();
880
+ const password = passwordInput.value;
881
+
882
+ if (username && password) {
883
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
884
+ authentication: {
885
+ type: "basic",
886
+ username,
887
+ password,
888
+ },
889
+ });
890
+ authModalOverlay.remove();
891
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
892
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
893
+ } else {
894
+ alert("Please enter both username and password.");
895
+ }
896
+ } else if (authType === "bearer") {
897
+ const token = tokenInput.value.trim();
898
+
899
+ if (token) {
900
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
901
+ authentication: {
902
+ type: "bearer",
903
+ token,
904
+ },
905
+ });
906
+ authModalOverlay.remove();
907
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
908
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
909
+ } else {
910
+ alert("Please enter a bearer token.");
911
+ }
912
+ } else if (authType === "apiKey") {
913
+ const headerName = headerNameInput.value.trim();
914
+ const apiKey = apiKeyInput.value.trim();
915
+
916
+ if (headerName && apiKey) {
917
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
918
+ authentication: {
919
+ type: "apiKey",
920
+ headerName,
921
+ apiKey,
922
+ },
923
+ });
924
+ authModalOverlay.remove();
925
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
926
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
927
+ } else {
928
+ alert("Please enter both header name and API key.");
929
+ }
930
+ }
931
+ };
932
+
933
+ footer.appendChild(removeButton);
934
+ footer.appendChild(cancelButton);
935
+ footer.appendChild(saveButton);
936
+ authModal.appendChild(footer);
937
+
938
+ authModalOverlay.appendChild(authModal);
939
+ document.body.appendChild(authModalOverlay);
940
+ }
941
+
439
942
  private drawRequestSettings(container: HTMLElement) {
440
943
  // This is a simplified version - you can expand based on TabPanel.ts
441
944
  const reqConfig = this.tab.getRequestConfig();
@@ -557,7 +1060,23 @@ export default class TabSettingsModal {
557
1060
  }
558
1061
  const snippetsBarCheckbox = document.getElementById("showSnippetsBar") as HTMLInputElement;
559
1062
  if (snippetsBarCheckbox) {
560
- yasqe.setSnippetsBarVisible(snippetsBarCheckbox.checked);
1063
+ // Save globally to config and persistent storage
1064
+ this.tab.yasgui.config.showSnippetsBar = snippetsBarCheckbox.checked;
1065
+ this.tab.yasgui.persistentConfig.setShowSnippetsBar(snippetsBarCheckbox.checked);
1066
+
1067
+ // Apply to all tabs by updating each Yasqe instance's config and refreshing
1068
+ this.tab.yasgui.persistentConfig.getTabs().forEach((tabId: string) => {
1069
+ const tab = this.tab.yasgui.getTab(tabId);
1070
+ if (tab) {
1071
+ const tabYasqe = tab.getYasqe();
1072
+ if (tabYasqe) {
1073
+ // Update the individual Yasqe instance's config
1074
+ tabYasqe.config.showSnippetsBar = snippetsBarCheckbox.checked;
1075
+ // Refresh the snippets bar to reflect the change
1076
+ tabYasqe.refreshSnippetsBar();
1077
+ }
1078
+ }
1079
+ });
561
1080
  }
562
1081
  yasqe.saveQuery();
563
1082
  }
@@ -576,6 +1095,9 @@ export default class TabSettingsModal {
576
1095
  this.tab.setRequestConfig(updates);
577
1096
  }
578
1097
 
1098
+ // Note: Authentication is now handled per-endpoint in the Endpoints tab,
1099
+ // not per-tab anymore. No need to save it here.
1100
+
579
1101
  // Refresh endpoint buttons to show any changes
580
1102
  this.tab.refreshEndpointButtons();
581
1103
 
@@ -674,116 +1196,8 @@ export default class TabSettingsModal {
674
1196
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
675
1197
  }
676
1198
 
677
- private drawEndpointButtonsSettings(container: HTMLElement) {
678
- const section = document.createElement("div");
679
- addClass(section, "settingsSection");
680
-
681
- const label = document.createElement("label");
682
- label.textContent = "Custom Endpoint Buttons";
683
- addClass(label, "settingsLabel");
684
-
685
- const help = document.createElement("div");
686
- help.textContent = "Add custom endpoint buttons that will appear next to the endpoint textbox.";
687
- addClass(help, "settingsHelp");
688
-
689
- section.appendChild(label);
690
- section.appendChild(help);
691
-
692
- // List of existing buttons
693
- const buttonsList = document.createElement("div");
694
- addClass(buttonsList, "endpointButtonsList");
695
- this.renderEndpointButtonsList(buttonsList);
696
- section.appendChild(buttonsList);
697
-
698
- // Form to add new button
699
- const addForm = document.createElement("div");
700
- addClass(addForm, "addEndpointButtonForm");
701
-
702
- const labelInput = document.createElement("input");
703
- labelInput.type = "text";
704
- labelInput.placeholder = "Button label (e.g., DBpedia)";
705
- addClass(labelInput, "endpointButtonLabelInput");
706
-
707
- const endpointInput = document.createElement("input");
708
- endpointInput.type = "url";
709
- endpointInput.placeholder = "Endpoint URL (e.g., https://dbpedia.org/sparql)";
710
- addClass(endpointInput, "endpointButtonEndpointInput");
711
-
712
- const addButton = document.createElement("button");
713
- addButton.textContent = "+ Add Button";
714
- addClass(addButton, "addEndpointButton");
715
- addButton.type = "button";
716
- addButton.onclick = () => {
717
- const labelValue = labelInput.value.trim();
718
- const endpointValue = endpointInput.value.trim();
719
-
720
- if (!labelValue || !endpointValue) {
721
- alert("Please enter both a label and an endpoint URL.");
722
- return;
723
- }
724
-
725
- // Add to persistent config
726
- const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
727
- currentButtons.push({ label: labelValue, endpoint: endpointValue });
728
- this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
729
-
730
- // Clear inputs
731
- labelInput.value = "";
732
- endpointInput.value = "";
733
-
734
- // Refresh list
735
- this.renderEndpointButtonsList(buttonsList);
736
- };
737
-
738
- addForm.appendChild(labelInput);
739
- addForm.appendChild(endpointInput);
740
- addForm.appendChild(addButton);
741
- section.appendChild(addForm);
742
-
743
- container.appendChild(section);
744
- }
745
-
746
- private renderEndpointButtonsList(container: HTMLElement) {
747
- container.innerHTML = "";
748
- const customButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
749
-
750
- if (customButtons.length === 0) {
751
- const emptyMsg = document.createElement("div");
752
- emptyMsg.textContent = "No custom buttons yet. Add one below.";
753
- addClass(emptyMsg, "emptyMessage");
754
- container.appendChild(emptyMsg);
755
- return;
756
- }
757
-
758
- customButtons.forEach((btn, index) => {
759
- const item = document.createElement("div");
760
- addClass(item, "endpointButtonItem");
761
-
762
- const labelSpan = document.createElement("span");
763
- labelSpan.textContent = `${btn.label}`;
764
- addClass(labelSpan, "buttonLabel");
765
-
766
- const endpointSpan = document.createElement("span");
767
- endpointSpan.textContent = btn.endpoint;
768
- addClass(endpointSpan, "buttonEndpoint");
769
-
770
- const removeBtn = document.createElement("button");
771
- removeBtn.textContent = "×";
772
- addClass(removeBtn, "removeButton");
773
- removeBtn.type = "button";
774
- removeBtn.onclick = () => {
775
- const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
776
- currentButtons.splice(index, 1);
777
- this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
778
- this.renderEndpointButtonsList(container);
779
- };
780
-
781
- item.appendChild(labelSpan);
782
- item.appendChild(endpointSpan);
783
- item.appendChild(removeBtn);
784
- container.appendChild(item);
785
- });
786
- }
1199
+ // Old endpoint buttons functionality has been merged into drawEndpointsSettings
1200
+ // Keeping this for reference if needed for backwards compatibility
787
1201
 
788
1202
  private getThemeToggleIcon(): string {
789
1203
  const currentTheme = this.tab.yasgui.getTheme();