@matdata/yasgui 5.5.0 → 5.7.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.
Files changed (38) hide show
  1. package/build/ts/src/ConfigExportImport.js +3 -0
  2. package/build/ts/src/ConfigExportImport.js.map +1 -1
  3. package/build/ts/src/OAuth2Utils.d.ts +18 -0
  4. package/build/ts/src/OAuth2Utils.js +214 -0
  5. package/build/ts/src/OAuth2Utils.js.map +1 -0
  6. package/build/ts/src/PersistentConfig.d.ts +3 -0
  7. package/build/ts/src/PersistentConfig.js +7 -0
  8. package/build/ts/src/PersistentConfig.js.map +1 -1
  9. package/build/ts/src/Tab.d.ts +1 -1
  10. package/build/ts/src/Tab.js +116 -85
  11. package/build/ts/src/Tab.js.map +1 -1
  12. package/build/ts/src/TabSettingsModal.d.ts +1 -0
  13. package/build/ts/src/TabSettingsModal.js +330 -27
  14. package/build/ts/src/TabSettingsModal.js.map +1 -1
  15. package/build/ts/src/defaults.js +1 -1
  16. package/build/ts/src/defaults.js.map +1 -1
  17. package/build/ts/src/index.d.ts +21 -6
  18. package/build/ts/src/index.js +7 -1
  19. package/build/ts/src/index.js.map +1 -1
  20. package/build/ts/src/version.d.ts +1 -1
  21. package/build/ts/src/version.js +1 -1
  22. package/build/yasgui.min.css +1 -1
  23. package/build/yasgui.min.css.map +3 -3
  24. package/build/yasgui.min.js +185 -157
  25. package/build/yasgui.min.js.map +4 -4
  26. package/package.json +3 -2
  27. package/src/ConfigExportImport.ts +3 -0
  28. package/src/OAuth2Utils.ts +315 -0
  29. package/src/PersistentConfig.ts +10 -0
  30. package/src/Tab.ts +191 -111
  31. package/src/TabSettingsModal.scss +70 -3
  32. package/src/TabSettingsModal.ts +400 -30
  33. package/src/defaults.ts +1 -1
  34. package/src/endpointSelect.scss +12 -0
  35. package/src/index.ts +42 -10
  36. package/src/tab.scss +1 -0
  37. package/src/themes.scss +1 -0
  38. package/src/version.ts +1 -1
@@ -63,7 +63,7 @@
63
63
  background: var(--yasgui-bg-primary, white);
64
64
  border-radius: 8px;
65
65
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
66
- max-width: 800px;
66
+ max-width: 1000px;
67
67
  width: 90%;
68
68
  max-height: 90vh;
69
69
  display: flex;
@@ -160,6 +160,13 @@
160
160
 
161
161
  .settingsSection {
162
162
  margin-bottom: 20px;
163
+
164
+ h3 {
165
+ margin: 0 0 12px 0;
166
+ font-size: 16px;
167
+ font-weight: 600;
168
+ color: var(--yasgui-text-primary, #000);
169
+ }
163
170
  }
164
171
 
165
172
  .settingsLabel {
@@ -297,7 +304,8 @@
297
304
  }
298
305
 
299
306
  .primaryButton,
300
- .secondaryButton {
307
+ .secondaryButton,
308
+ .dangerButton {
301
309
  padding: 8px 20px;
302
310
  border: none;
303
311
  border-radius: 4px;
@@ -320,7 +328,21 @@
320
328
  color: var(--yasgui-text-primary, #333);
321
329
 
322
330
  &:hover {
323
- background: var(--yasgui-border-color, #d0d0d0);
331
+ background: var(--yasgui-bg-quaternary, #d0d0d0);
332
+ }
333
+ }
334
+
335
+ .dangerButton {
336
+ background: var(--yasgui-danger-bg, #dc3545);
337
+ color: white;
338
+ margin-top: 10px;
339
+
340
+ &:hover {
341
+ background: var(--yasgui-danger-hover, #c82333);
342
+ }
343
+
344
+ &:active {
345
+ background: var(--yasgui-danger-active, #bd2130);
324
346
  }
325
347
  }
326
348
 
@@ -849,6 +871,24 @@
849
871
  }
850
872
  }
851
873
 
874
+ .authHelpLink {
875
+ padding: 10px 12px;
876
+ background: var(--yasgui-bg-secondary, #f5f5f5);
877
+ border: 1px solid var(--yasgui-border-color, #e0e0e0);
878
+ border-radius: 4px;
879
+ font-size: 13px;
880
+ margin-bottom: 12px;
881
+
882
+ a {
883
+ color: var(--yasgui-accent-color, #337ab7);
884
+ text-decoration: none;
885
+
886
+ &:hover {
887
+ text-decoration: underline;
888
+ }
889
+ }
890
+ }
891
+
852
892
  .authSecurityNotice {
853
893
  padding: 12px;
854
894
  background: var(--yasgui-warning-bg, #fff3cd);
@@ -925,6 +965,33 @@
925
965
  }
926
966
  }
927
967
 
968
+ // OAuth 2.0 specific styling
969
+ .authInputHelp {
970
+ font-size: 12px;
971
+ color: var(--yasgui-text-secondary, #666);
972
+ margin-top: 4px;
973
+ line-height: 1.4;
974
+ }
975
+
976
+ .oauth2TokenStatus {
977
+ padding: 8px 12px;
978
+ border-radius: 4px;
979
+ font-size: 14px;
980
+ font-weight: 500;
981
+
982
+ &.authenticated {
983
+ background: var(--yasgui-success-bg, #d4edda);
984
+ color: var(--yasgui-success-text, #155724);
985
+ border: 1px solid var(--yasgui-success-border, #c3e6cb);
986
+ }
987
+
988
+ &.expired {
989
+ background: var(--yasgui-warning-bg, #fff3cd);
990
+ color: var(--yasgui-warning-text, #856404);
991
+ border: 1px solid var(--yasgui-warning-border, #ffeaa7);
992
+ }
993
+ }
994
+
928
995
  // Add endpoint form styling
929
996
  .addEndpointForm {
930
997
  margin-top: 20px;
@@ -3,6 +3,8 @@ import "./TabSettingsModal.scss";
3
3
  import Tab from "./Tab";
4
4
  import * as ConfigExportImport from "./ConfigExportImport";
5
5
  import { VERSION } from "./version";
6
+ import * as OAuth2Utils from "./OAuth2Utils";
7
+ import PersistentConfig from "./PersistentConfig";
6
8
 
7
9
  // Theme toggle icons
8
10
  const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
@@ -34,6 +36,9 @@ const AcceptHeaderGraphMap: { key: string; value: string }[] = [
34
36
  { key: "TSV", value: "text/tab-separated-values,*/*;q=0.9" },
35
37
  ];
36
38
 
39
+ // Default API Key header name
40
+ const DEFAULT_API_KEY_HEADER = "X-API-Key";
41
+
37
42
  export default class TabSettingsModal {
38
43
  private tab: Tab;
39
44
  private modalOverlay!: HTMLElement;
@@ -89,7 +94,8 @@ export default class TabSettingsModal {
89
94
  // Modal overlay
90
95
  this.modalOverlay = document.createElement("div");
91
96
  addClass(this.modalOverlay, "tabSettingsModalOverlay");
92
- this.modalOverlay.onclick = () => this.close();
97
+ // Removed: this.modalOverlay.onclick = () => this.close();
98
+ // Users must explicitly click 'Save' or 'Cancel' to close the modal
93
99
 
94
100
  // Modal content
95
101
  this.modalContent = document.createElement("div");
@@ -423,7 +429,10 @@ export default class TabSettingsModal {
423
429
  const snippetsBarCheckbox = document.createElement("input");
424
430
  snippetsBarCheckbox.type = "checkbox";
425
431
  snippetsBarCheckbox.id = "showSnippetsBar";
426
- snippetsBarCheckbox.checked = yasqe.getSnippetsBarVisible();
432
+ // Read from global config
433
+ const persistedValue = this.tab.yasgui.persistentConfig.getShowSnippetsBar();
434
+ snippetsBarCheckbox.checked =
435
+ persistedValue !== undefined ? persistedValue : this.tab.yasgui.config.showSnippetsBar !== false;
427
436
 
428
437
  const snippetsBarLabel = document.createElement("label");
429
438
  snippetsBarLabel.htmlFor = "showSnippetsBar";
@@ -689,22 +698,47 @@ export default class TabSettingsModal {
689
698
  const body = document.createElement("div");
690
699
  addClass(body, "authModalBody");
691
700
 
692
- // Auth type (for now only basic, but designed for future)
701
+ // Auth type
693
702
  const typeSection = document.createElement("div");
694
703
  addClass(typeSection, "authModalSection");
695
704
  const typeLabel = document.createElement("label");
696
705
  typeLabel.textContent = "Authentication Type";
697
706
  const typeSelect = document.createElement("select");
707
+
698
708
  const basicOption = document.createElement("option");
699
709
  basicOption.value = "basic";
700
710
  basicOption.textContent = "HTTP Basic Authentication";
701
711
  typeSelect.appendChild(basicOption);
702
- // Future: Add OAuth, Bearer Token, etc.
712
+
713
+ const bearerOption = document.createElement("option");
714
+ bearerOption.value = "bearer";
715
+ bearerOption.textContent = "Bearer Token";
716
+ typeSelect.appendChild(bearerOption);
717
+
718
+ const apiKeyOption = document.createElement("option");
719
+ apiKeyOption.value = "apiKey";
720
+ apiKeyOption.textContent = "API Key (Custom Header)";
721
+ typeSelect.appendChild(apiKeyOption);
722
+
723
+ const oauth2Option = document.createElement("option");
724
+ oauth2Option.value = "oauth2";
725
+ oauth2Option.textContent = "OAuth 2.0";
726
+ typeSelect.appendChild(oauth2Option);
727
+
728
+ // Set the current auth type
729
+ if (existingAuth) {
730
+ typeSelect.value = existingAuth.type;
731
+ }
732
+
703
733
  typeSection.appendChild(typeLabel);
704
734
  typeSection.appendChild(typeSelect);
705
735
  body.appendChild(typeSection);
706
736
 
707
- // Username
737
+ // Basic Auth Fields
738
+ const basicAuthFields = document.createElement("div");
739
+ basicAuthFields.id = "basicAuthFields";
740
+ addClass(basicAuthFields, "authFieldsContainer");
741
+
708
742
  const usernameSection = document.createElement("div");
709
743
  addClass(usernameSection, "authModalSection");
710
744
  const usernameLabel = document.createElement("label");
@@ -712,13 +746,12 @@ export default class TabSettingsModal {
712
746
  const usernameInput = document.createElement("input");
713
747
  usernameInput.type = "text";
714
748
  usernameInput.placeholder = "Enter username";
715
- usernameInput.value = existingAuth?.username || "";
749
+ usernameInput.value = existingAuth?.type === "basic" ? existingAuth.username : "";
716
750
  usernameInput.autocomplete = "username";
717
751
  usernameSection.appendChild(usernameLabel);
718
752
  usernameSection.appendChild(usernameInput);
719
- body.appendChild(usernameSection);
753
+ basicAuthFields.appendChild(usernameSection);
720
754
 
721
- // Password
722
755
  const passwordSection = document.createElement("div");
723
756
  addClass(passwordSection, "authModalSection");
724
757
  const passwordLabel = document.createElement("label");
@@ -726,11 +759,181 @@ export default class TabSettingsModal {
726
759
  const passwordInput = document.createElement("input");
727
760
  passwordInput.type = "password";
728
761
  passwordInput.placeholder = "Enter password";
729
- passwordInput.value = existingAuth?.password || "";
762
+ passwordInput.value = existingAuth?.type === "basic" ? existingAuth.password : "";
730
763
  passwordInput.autocomplete = "current-password";
731
764
  passwordSection.appendChild(passwordLabel);
732
765
  passwordSection.appendChild(passwordInput);
733
- body.appendChild(passwordSection);
766
+ basicAuthFields.appendChild(passwordSection);
767
+
768
+ body.appendChild(basicAuthFields);
769
+
770
+ // Bearer Token Fields
771
+ const bearerAuthFields = document.createElement("div");
772
+ bearerAuthFields.id = "bearerAuthFields";
773
+ addClass(bearerAuthFields, "authFieldsContainer");
774
+ bearerAuthFields.style.display = "none";
775
+
776
+ const tokenSection = document.createElement("div");
777
+ addClass(tokenSection, "authModalSection");
778
+ const tokenLabel = document.createElement("label");
779
+ tokenLabel.textContent = "Bearer Token";
780
+ const tokenInput = document.createElement("input");
781
+ tokenInput.type = "password";
782
+ tokenInput.placeholder = "Enter bearer token";
783
+ tokenInput.autocomplete = "off";
784
+ tokenInput.value = existingAuth?.type === "bearer" ? existingAuth.token : "";
785
+ tokenSection.appendChild(tokenLabel);
786
+ tokenSection.appendChild(tokenInput);
787
+ bearerAuthFields.appendChild(tokenSection);
788
+
789
+ body.appendChild(bearerAuthFields);
790
+
791
+ // API Key Fields
792
+ const apiKeyAuthFields = document.createElement("div");
793
+ apiKeyAuthFields.id = "apiKeyAuthFields";
794
+ addClass(apiKeyAuthFields, "authFieldsContainer");
795
+ apiKeyAuthFields.style.display = "none";
796
+
797
+ const headerNameSection = document.createElement("div");
798
+ addClass(headerNameSection, "authModalSection");
799
+ const headerNameLabel = document.createElement("label");
800
+ headerNameLabel.textContent = "Header Name";
801
+ const headerNameInput = document.createElement("input");
802
+ headerNameInput.type = "text";
803
+ headerNameInput.placeholder = `e.g., ${DEFAULT_API_KEY_HEADER}`;
804
+ headerNameInput.value = existingAuth?.type === "apiKey" ? existingAuth.headerName : DEFAULT_API_KEY_HEADER;
805
+ headerNameSection.appendChild(headerNameLabel);
806
+ headerNameSection.appendChild(headerNameInput);
807
+ apiKeyAuthFields.appendChild(headerNameSection);
808
+
809
+ const apiKeySection = document.createElement("div");
810
+ addClass(apiKeySection, "authModalSection");
811
+ const apiKeyLabel = document.createElement("label");
812
+ apiKeyLabel.textContent = "API Key";
813
+ const apiKeyInput = document.createElement("input");
814
+ apiKeyInput.type = "password";
815
+ apiKeyInput.placeholder = "Enter API key";
816
+ apiKeyInput.autocomplete = "off";
817
+ apiKeyInput.value = existingAuth?.type === "apiKey" ? existingAuth.apiKey : "";
818
+ apiKeySection.appendChild(apiKeyLabel);
819
+ apiKeySection.appendChild(apiKeyInput);
820
+ apiKeyAuthFields.appendChild(apiKeySection);
821
+
822
+ body.appendChild(apiKeyAuthFields);
823
+
824
+ // OAuth 2.0 Fields
825
+ const oauth2AuthFields = document.createElement("div");
826
+ oauth2AuthFields.id = "oauth2AuthFields";
827
+ addClass(oauth2AuthFields, "authFieldsContainer");
828
+ oauth2AuthFields.style.display = "none";
829
+
830
+ const clientIdSection = document.createElement("div");
831
+ addClass(clientIdSection, "authModalSection");
832
+ const clientIdLabel = document.createElement("label");
833
+ clientIdLabel.textContent = "Client ID";
834
+ const clientIdInput = document.createElement("input");
835
+ clientIdInput.type = "text";
836
+ clientIdInput.placeholder = "Enter OAuth 2.0 client ID";
837
+ clientIdInput.value = existingAuth?.type === "oauth2" ? existingAuth.clientId : "";
838
+ clientIdSection.appendChild(clientIdLabel);
839
+ clientIdSection.appendChild(clientIdInput);
840
+ oauth2AuthFields.appendChild(clientIdSection);
841
+
842
+ const authEndpointSection = document.createElement("div");
843
+ addClass(authEndpointSection, "authModalSection");
844
+ const authEndpointLabel = document.createElement("label");
845
+ authEndpointLabel.textContent = "Authorization Endpoint";
846
+ const authEndpointInput = document.createElement("input");
847
+ authEndpointInput.type = "url";
848
+ authEndpointInput.placeholder = "https://provider.com/oauth/authorize";
849
+ authEndpointInput.value = existingAuth?.type === "oauth2" ? existingAuth.authorizationEndpoint : "";
850
+ authEndpointSection.appendChild(authEndpointLabel);
851
+ authEndpointSection.appendChild(authEndpointInput);
852
+ oauth2AuthFields.appendChild(authEndpointSection);
853
+
854
+ const tokenEndpointSection = document.createElement("div");
855
+ addClass(tokenEndpointSection, "authModalSection");
856
+ const tokenEndpointLabel = document.createElement("label");
857
+ tokenEndpointLabel.textContent = "Token Endpoint";
858
+ const tokenEndpointInput = document.createElement("input");
859
+ tokenEndpointInput.type = "url";
860
+ tokenEndpointInput.placeholder = "https://provider.com/oauth/token";
861
+ tokenEndpointInput.value = existingAuth?.type === "oauth2" ? existingAuth.tokenEndpoint : "";
862
+ tokenEndpointSection.appendChild(tokenEndpointLabel);
863
+ tokenEndpointSection.appendChild(tokenEndpointInput);
864
+ oauth2AuthFields.appendChild(tokenEndpointSection);
865
+
866
+ const redirectUriSection = document.createElement("div");
867
+ addClass(redirectUriSection, "authModalSection");
868
+ const redirectUriLabel = document.createElement("label");
869
+ redirectUriLabel.textContent = "Redirect URI (Optional)";
870
+ const redirectUriInput = document.createElement("input");
871
+ redirectUriInput.type = "url";
872
+ redirectUriInput.placeholder = window.location.origin + window.location.pathname;
873
+ redirectUriInput.value = existingAuth?.type === "oauth2" ? existingAuth.redirectUri || "" : "";
874
+ const redirectUriHelp = document.createElement("div");
875
+ redirectUriHelp.textContent = "Leave empty to use current page URL. Must be registered with OAuth provider.";
876
+ addClass(redirectUriHelp, "authInputHelp");
877
+ redirectUriSection.appendChild(redirectUriLabel);
878
+ redirectUriSection.appendChild(redirectUriInput);
879
+ redirectUriSection.appendChild(redirectUriHelp);
880
+ oauth2AuthFields.appendChild(redirectUriSection);
881
+
882
+ const scopeSection = document.createElement("div");
883
+ addClass(scopeSection, "authModalSection");
884
+ const scopeLabel = document.createElement("label");
885
+ scopeLabel.textContent = "Scope (Optional)";
886
+ const scopeInput = document.createElement("input");
887
+ scopeInput.type = "text";
888
+ scopeInput.placeholder = "e.g., read write";
889
+ scopeInput.value = existingAuth?.type === "oauth2" ? existingAuth.scope || "" : "";
890
+ const scopeHelp = document.createElement("div");
891
+ scopeHelp.textContent = "Space-separated list of OAuth 2.0 scopes";
892
+ addClass(scopeHelp, "authInputHelp");
893
+ scopeSection.appendChild(scopeLabel);
894
+ scopeSection.appendChild(scopeInput);
895
+ scopeSection.appendChild(scopeHelp);
896
+ oauth2AuthFields.appendChild(scopeSection);
897
+
898
+ // Show token status if already authenticated
899
+ if (existingAuth?.type === "oauth2" && existingAuth.accessToken) {
900
+ const tokenStatusSection = document.createElement("div");
901
+ addClass(tokenStatusSection, "authModalSection");
902
+ const tokenStatusLabel = document.createElement("label");
903
+ tokenStatusLabel.textContent = "Authentication Status";
904
+ const tokenStatus = document.createElement("div");
905
+ addClass(tokenStatus, "oauth2TokenStatus");
906
+ tokenStatus.innerHTML = "✓ Authenticated";
907
+ addClass(tokenStatus, "authenticated");
908
+ tokenStatusSection.appendChild(tokenStatusLabel);
909
+ tokenStatusSection.appendChild(tokenStatus);
910
+ oauth2AuthFields.appendChild(tokenStatusSection);
911
+ }
912
+
913
+ body.appendChild(oauth2AuthFields);
914
+
915
+ // Function to toggle fields based on auth type
916
+ const toggleAuthFields = () => {
917
+ const authType = typeSelect.value;
918
+ basicAuthFields.style.display = authType === "basic" ? "block" : "none";
919
+ bearerAuthFields.style.display = authType === "bearer" ? "block" : "none";
920
+ apiKeyAuthFields.style.display = authType === "apiKey" ? "block" : "none";
921
+ oauth2AuthFields.style.display = authType === "oauth2" ? "block" : "none";
922
+ };
923
+
924
+ // Set initial visibility
925
+ toggleAuthFields();
926
+
927
+ // Update visibility when auth type changes
928
+ typeSelect.onchange = toggleAuthFields;
929
+
930
+ // Help link
931
+ const helpLink = document.createElement("div");
932
+ addClass(helpLink, "authHelpLink");
933
+ helpLink.innerHTML = `
934
+ 📚 <a href="https://triply.cc/docs/yasgui#authentication" target="_blank" rel="noopener noreferrer">View authentication documentation</a>
935
+ `;
936
+ body.appendChild(helpLink);
734
937
 
735
938
  // Security notice
736
939
  const securityNotice = document.createElement("div");
@@ -755,13 +958,18 @@ export default class TabSettingsModal {
755
958
  removeButton.textContent = "Remove Authentication";
756
959
  removeButton.type = "button";
757
960
  addClass(removeButton, "authRemoveButton");
961
+ // Helper function to close modal and refresh endpoints list
962
+ const closeModalAndRefresh = () => {
963
+ authModalOverlay.remove();
964
+ const endpointsList = this.modalContent.querySelector(".endpointsTable");
965
+ if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
966
+ };
967
+
758
968
  removeButton.onclick = () => {
759
969
  this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
760
970
  authentication: undefined,
761
971
  });
762
- authModalOverlay.remove();
763
- const endpointsList = this.modalContent.querySelector(".endpointsTable");
764
- if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
972
+ closeModalAndRefresh();
765
973
  };
766
974
  if (!existingAuth) {
767
975
  removeButton.disabled = true;
@@ -778,22 +986,125 @@ export default class TabSettingsModal {
778
986
  saveButton.type = "button";
779
987
  addClass(saveButton, "authSaveButton");
780
988
  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.");
989
+ const authType = typeSelect.value;
990
+
991
+ if (authType === "basic") {
992
+ const username = usernameInput.value.trim();
993
+ const password = passwordInput.value;
994
+
995
+ if (username && password) {
996
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
997
+ authentication: {
998
+ type: "basic",
999
+ username,
1000
+ password,
1001
+ },
1002
+ });
1003
+ closeModalAndRefresh();
1004
+ } else {
1005
+ alert("Please enter both username and password.");
1006
+ }
1007
+ } else if (authType === "bearer") {
1008
+ const token = tokenInput.value.trim();
1009
+
1010
+ if (token) {
1011
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
1012
+ authentication: {
1013
+ type: "bearer",
1014
+ token,
1015
+ },
1016
+ });
1017
+ closeModalAndRefresh();
1018
+ } else {
1019
+ alert("Please enter a bearer token.");
1020
+ }
1021
+ } else if (authType === "apiKey") {
1022
+ const headerName = headerNameInput.value.trim();
1023
+ const apiKey = apiKeyInput.value.trim();
1024
+
1025
+ if (headerName && apiKey) {
1026
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
1027
+ authentication: {
1028
+ type: "apiKey",
1029
+ headerName,
1030
+ apiKey,
1031
+ },
1032
+ });
1033
+ closeModalAndRefresh();
1034
+ } else {
1035
+ alert("Please enter both header name and API key.");
1036
+ }
1037
+ } else if (authType === "oauth2") {
1038
+ const clientId = clientIdInput.value.trim();
1039
+ const authorizationEndpoint = authEndpointInput.value.trim();
1040
+ const tokenEndpoint = tokenEndpointInput.value.trim();
1041
+ const redirectUri = redirectUriInput.value.trim() || window.location.origin + window.location.pathname;
1042
+ const scope = scopeInput.value.trim();
1043
+
1044
+ if (!clientId || !authorizationEndpoint || !tokenEndpoint) {
1045
+ alert("Please enter Client ID, Authorization Endpoint, and Token Endpoint.");
1046
+ return;
1047
+ }
1048
+
1049
+ // Check if already authenticated (has access token)
1050
+ if (existingAuth?.type === "oauth2" && existingAuth.accessToken) {
1051
+ // Already has token, just save the configuration updates
1052
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
1053
+ authentication: {
1054
+ type: "oauth2",
1055
+ clientId,
1056
+ authorizationEndpoint,
1057
+ tokenEndpoint,
1058
+ redirectUri,
1059
+ scope,
1060
+ accessToken: existingAuth.accessToken,
1061
+ idToken: existingAuth.idToken,
1062
+ refreshToken: existingAuth.refreshToken,
1063
+ tokenExpiry: existingAuth.tokenExpiry,
1064
+ },
1065
+ });
1066
+ closeModalAndRefresh();
1067
+ return;
1068
+ }
1069
+
1070
+ // Start OAuth 2.0 flow
1071
+ saveButton.disabled = true;
1072
+ saveButton.textContent = "Authenticating...";
1073
+
1074
+ OAuth2Utils.startOAuth2Flow({
1075
+ clientId,
1076
+ authorizationEndpoint,
1077
+ tokenEndpoint,
1078
+ redirectUri,
1079
+ scope,
1080
+ })
1081
+ .then((tokenResponse) => {
1082
+ const tokenExpiry = OAuth2Utils.calculateTokenExpiry(tokenResponse.expires_in);
1083
+
1084
+ this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
1085
+ authentication: {
1086
+ type: "oauth2",
1087
+ clientId,
1088
+ authorizationEndpoint,
1089
+ tokenEndpoint,
1090
+ redirectUri,
1091
+ scope,
1092
+ accessToken: tokenResponse.access_token,
1093
+ idToken: tokenResponse.id_token,
1094
+ refreshToken: tokenResponse.refresh_token,
1095
+ tokenExpiry,
1096
+ },
1097
+ });
1098
+
1099
+ closeModalAndRefresh();
1100
+ alert("OAuth 2.0 authentication successful!");
1101
+ })
1102
+ .catch((error) => {
1103
+ console.error("OAuth 2.0 authentication failed:", error);
1104
+ alert("OAuth 2.0 authentication failed: " + error.message);
1105
+ saveButton.disabled = false;
1106
+ saveButton.textContent = "Save & Authenticate";
1107
+ });
797
1108
  }
798
1109
  };
799
1110
 
@@ -927,7 +1238,23 @@ export default class TabSettingsModal {
927
1238
  }
928
1239
  const snippetsBarCheckbox = document.getElementById("showSnippetsBar") as HTMLInputElement;
929
1240
  if (snippetsBarCheckbox) {
930
- yasqe.setSnippetsBarVisible(snippetsBarCheckbox.checked);
1241
+ // Save globally to config and persistent storage
1242
+ this.tab.yasgui.config.showSnippetsBar = snippetsBarCheckbox.checked;
1243
+ this.tab.yasgui.persistentConfig.setShowSnippetsBar(snippetsBarCheckbox.checked);
1244
+
1245
+ // Apply to all tabs by updating each Yasqe instance's config and refreshing
1246
+ this.tab.yasgui.persistentConfig.getTabs().forEach((tabId: string) => {
1247
+ const tab = this.tab.yasgui.getTab(tabId);
1248
+ if (tab) {
1249
+ const tabYasqe = tab.getYasqe();
1250
+ if (tabYasqe) {
1251
+ // Update the individual Yasqe instance's config
1252
+ tabYasqe.config.showSnippetsBar = snippetsBarCheckbox.checked;
1253
+ // Refresh the snippets bar to reflect the change
1254
+ tabYasqe.refreshSnippetsBar();
1255
+ }
1256
+ }
1257
+ });
931
1258
  }
932
1259
  yasqe.saveQuery();
933
1260
  }
@@ -1450,6 +1777,28 @@ export default class TabSettingsModal {
1450
1777
  aboutSection.appendChild(footerInfo);
1451
1778
 
1452
1779
  container.appendChild(aboutSection);
1780
+
1781
+ // Storage Management Section
1782
+ const storageSection = document.createElement("div");
1783
+ addClass(storageSection, "settingsSection");
1784
+
1785
+ const storageTitle = document.createElement("h3");
1786
+ storageTitle.textContent = "Storage Management";
1787
+ storageSection.appendChild(storageTitle);
1788
+
1789
+ const storageDescription = document.createElement("p");
1790
+ storageDescription.textContent =
1791
+ "Clear all locally stored data including tabs, queries, endpoint configurations, and preferences. This action cannot be undone.";
1792
+ addClass(storageDescription, "settingsHelp");
1793
+ storageSection.appendChild(storageDescription);
1794
+
1795
+ const clearStorageButton = document.createElement("button");
1796
+ clearStorageButton.textContent = "Clear Persistent Storage";
1797
+ addClass(clearStorageButton, "dangerButton");
1798
+ clearStorageButton.onclick = () => this.clearPersistentStorage();
1799
+ storageSection.appendChild(clearStorageButton);
1800
+
1801
+ container.appendChild(storageSection);
1453
1802
  }
1454
1803
 
1455
1804
  private createAboutLink(label: string, url: string, description: string): HTMLElement {
@@ -1473,6 +1822,27 @@ export default class TabSettingsModal {
1473
1822
  return linkContainer;
1474
1823
  }
1475
1824
 
1825
+ private clearPersistentStorage() {
1826
+ const confirmed = confirm(
1827
+ "Are you sure you want to clear all persistent storage?\n\n" +
1828
+ "This will delete:\n" +
1829
+ "- All saved tabs and queries\n" +
1830
+ "- Endpoint configurations and history\n" +
1831
+ "- Authentication settings\n" +
1832
+ "- Editor preferences\n" +
1833
+ "- Prefixes\n\n" +
1834
+ "This action cannot be undone and the page will reload.",
1835
+ );
1836
+
1837
+ if (confirmed) {
1838
+ // Clear persistent storage
1839
+ PersistentConfig.clear();
1840
+
1841
+ // Reload the page to reinitialize with empty storage
1842
+ window.location.reload();
1843
+ }
1844
+ }
1845
+
1476
1846
  public destroy() {
1477
1847
  if (this.modalOverlay && this.modalOverlay.parentNode) {
1478
1848
  this.modalOverlay.parentNode.removeChild(this.modalOverlay);
package/src/defaults.ts CHANGED
@@ -22,7 +22,6 @@ export default function initialize(): Config<CatalogueItem> {
22
22
  return "yagui_" + id;
23
23
  },
24
24
  tabName: "Query",
25
- corsProxy: undefined,
26
25
  persistencyExpire: 60 * 60 * 24 * 30,
27
26
  persistenceLabelResponse: "response",
28
27
  persistenceLabelConfig: "config",
@@ -31,6 +30,7 @@ export default function initialize(): Config<CatalogueItem> {
31
30
  theme: undefined,
32
31
  showThemeToggle: true,
33
32
  orientation: "vertical",
33
+ showSnippetsBar: true,
34
34
  endpointButtons: undefined,
35
35
  endpointCatalogueOptions: {
36
36
  getData: () => {
@@ -4,6 +4,7 @@
4
4
  margin: 4px 0px;
5
5
  border: 2px solid #ccc;
6
6
  width: 100%;
7
+ min-width: 200px;
7
8
  &:hover {
8
9
  border-color: #bbb;
9
10
  }
@@ -125,6 +126,11 @@
125
126
  align-items: center;
126
127
  gap: 4px;
127
128
  margin-left: 4px;
129
+
130
+ // Hide on small screens (mobile)
131
+ @media (max-width: 768px) {
132
+ display: none;
133
+ }
128
134
  }
129
135
 
130
136
  .endpointButton {
@@ -152,5 +158,11 @@
152
158
  outline: 2px solid var(--yasgui-endpoint-button-focus, #5cb3fd);
153
159
  outline-offset: 2px;
154
160
  }
161
+
162
+ // Reduce padding on medium screens
163
+ @media (max-width: 1024px) {
164
+ padding: 4px 8px;
165
+ font-size: 12px;
166
+ }
155
167
  }
156
168
  }