@matdata/yasgui 5.11.0 → 5.13.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 (53) hide show
  1. package/build/ts/src/PersistentConfig.d.ts +4 -0
  2. package/build/ts/src/PersistentConfig.js +12 -0
  3. package/build/ts/src/PersistentConfig.js.map +1 -1
  4. package/build/ts/src/Tab.js +2 -1
  5. package/build/ts/src/Tab.js.map +1 -1
  6. package/build/ts/src/TabSettingsModal.js +114 -0
  7. package/build/ts/src/TabSettingsModal.js.map +1 -1
  8. package/build/ts/src/ThemeManager.d.ts +3 -0
  9. package/build/ts/src/ThemeManager.js +11 -1
  10. package/build/ts/src/ThemeManager.js.map +1 -1
  11. package/build/ts/src/index.d.ts +1 -0
  12. package/build/ts/src/index.js +2 -0
  13. package/build/ts/src/index.js.map +1 -1
  14. package/build/ts/src/queryManagement/QueryBrowser.js +27 -6
  15. package/build/ts/src/queryManagement/QueryBrowser.js.map +1 -1
  16. package/build/ts/src/queryManagement/SaveManagedQueryModal.js +3 -3
  17. package/build/ts/src/queryManagement/SaveManagedQueryModal.js.map +1 -1
  18. package/build/ts/src/queryManagement/backends/BitbucketProviderClient.js +2 -2
  19. package/build/ts/src/queryManagement/backends/BitbucketProviderClient.js.map +1 -1
  20. package/build/ts/src/queryManagement/backends/GiteaProviderClient.js +2 -2
  21. package/build/ts/src/queryManagement/backends/GiteaProviderClient.js.map +1 -1
  22. package/build/ts/src/queryManagement/backends/GithubProviderClient.js +2 -2
  23. package/build/ts/src/queryManagement/backends/GithubProviderClient.js.map +1 -1
  24. package/build/ts/src/queryManagement/backends/GitlabProviderClient.js +2 -2
  25. package/build/ts/src/queryManagement/backends/GitlabProviderClient.js.map +1 -1
  26. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.js +4 -4
  27. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.js.map +1 -1
  28. package/build/ts/src/queryManagement/normalizeQueryFilename.js +2 -2
  29. package/build/ts/src/queryManagement/normalizeQueryFilename.js.map +1 -1
  30. package/build/ts/src/version.d.ts +1 -1
  31. package/build/ts/src/version.js +1 -1
  32. package/build/yasgui.min.css +1 -1
  33. package/build/yasgui.min.css.map +3 -3
  34. package/build/yasgui.min.js +162 -162
  35. package/build/yasgui.min.js.map +3 -3
  36. package/package.json +1 -1
  37. package/src/PersistentConfig.ts +15 -0
  38. package/src/Tab.ts +3 -2
  39. package/src/TabSettingsModal.scss +12 -0
  40. package/src/TabSettingsModal.ts +144 -0
  41. package/src/ThemeManager.ts +18 -1
  42. package/src/github-dark-theme.scss +192 -0
  43. package/src/index.ts +4 -0
  44. package/src/queryManagement/QueryBrowser.ts +34 -7
  45. package/src/queryManagement/SaveManagedQueryModal.ts +3 -3
  46. package/src/queryManagement/backends/BitbucketProviderClient.ts +2 -2
  47. package/src/queryManagement/backends/GiteaProviderClient.ts +2 -2
  48. package/src/queryManagement/backends/GithubProviderClient.ts +2 -2
  49. package/src/queryManagement/backends/GitlabProviderClient.ts +2 -2
  50. package/src/queryManagement/backends/InMemoryWorkspaceBackend.ts +4 -4
  51. package/src/queryManagement/normalizeQueryFilename.ts +4 -2
  52. package/src/themes.scss +43 -43
  53. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@matdata/yasgui",
3
3
  "description": "Yet Another SPARQL GUI",
4
- "version": "5.11.0",
4
+ "version": "5.13.0",
5
5
  "main": "build/yasgui.min.js",
6
6
  "types": "build/ts/src/index.d.ts",
7
7
  "license": "MIT",
@@ -19,6 +19,8 @@ export interface PersistedJson {
19
19
  orientation?: "vertical" | "horizontal";
20
20
  showSnippetsBar?: boolean;
21
21
  disabledDevButtons?: string[]; // Endpoints of developer-configured buttons that are disabled by user
22
+ codeMirrorThemeLight?: string; // User-selected CodeMirror theme for light mode
23
+ codeMirrorThemeDark?: string; // User-selected CodeMirror theme for dark mode
22
24
  }
23
25
  function getDefaults(): PersistedJson {
24
26
  return {
@@ -182,6 +184,19 @@ export default class PersistentConfig {
182
184
  this.toStorage();
183
185
  }
184
186
 
187
+ public getCodeMirrorTheme(mode: "light" | "dark"): string | undefined {
188
+ return mode === "dark" ? this.persistedJson.codeMirrorThemeDark : this.persistedJson.codeMirrorThemeLight;
189
+ }
190
+
191
+ public setCodeMirrorTheme(mode: "light" | "dark", theme: string) {
192
+ if (mode === "dark") {
193
+ this.persistedJson.codeMirrorThemeDark = theme;
194
+ } else {
195
+ this.persistedJson.codeMirrorThemeLight = theme;
196
+ }
197
+ this.toStorage();
198
+ }
199
+
185
200
  // New endpoint configuration methods
186
201
  public getEndpointConfigs(): EndpointConfig[] {
187
202
  return this.persistedJson.endpointConfigs || [];
package/src/Tab.ts CHANGED
@@ -1216,9 +1216,10 @@ export class Tab extends EventEmitter {
1216
1216
  }
1217
1217
 
1218
1218
  private initYasqe() {
1219
- // Set theme based on current yasgui theme
1219
+ // Set theme based on stored preference for current mode
1220
1220
  const currentTheme = this.yasgui.getTheme();
1221
- const cmTheme = currentTheme === "dark" ? "material-palenight" : "default";
1221
+ const storedCmTheme = this.yasgui.persistentConfig.getCodeMirrorTheme(currentTheme);
1222
+ const cmTheme = storedCmTheme || (currentTheme === "dark" ? "github-dark" : "default");
1222
1223
 
1223
1224
  const yasqeConf: Partial<YasqeConfig> = {
1224
1225
  ...this.yasgui.config.yasqe,
@@ -160,6 +160,18 @@
160
160
  font-style: italic;
161
161
  }
162
162
 
163
+ .settingsLink {
164
+ display: inline-block;
165
+ margin-top: 5px;
166
+ font-size: 13px;
167
+ color: var(--yasgui-accent-color, #337ab7);
168
+ text-decoration: none;
169
+
170
+ &:hover {
171
+ text-decoration: underline;
172
+ }
173
+ }
174
+
163
175
  .settingsField {
164
176
  margin-bottom: 10px;
165
177
  }
@@ -358,6 +358,121 @@ export default class TabSettingsModal {
358
358
  return;
359
359
  }
360
360
 
361
+ // Available CodeMirror themes
362
+ const themes = [
363
+ { value: "default", label: "Default" },
364
+ { value: "github-dark", label: "GitHub Dark Default" },
365
+ { value: "material-palenight", label: "Material Palenight" },
366
+ { value: "monokai", label: "Monokai" },
367
+ { value: "dracula", label: "Dracula" },
368
+ { value: "nord", label: "Nord" },
369
+ { value: "solarized dark", label: "Solarized Dark" },
370
+ { value: "solarized light", label: "Solarized Light" },
371
+ { value: "twilight", label: "Twilight" },
372
+ { value: "material", label: "Material" },
373
+ { value: "cobalt", label: "Cobalt" },
374
+ { value: "darcula", label: "Darcula" },
375
+ { value: "gruvbox-dark", label: "Gruvbox Dark" },
376
+ { value: "oceanic-next", label: "Oceanic Next" },
377
+ { value: "material-darker", label: "Material Darker" },
378
+ { value: "blackboard", label: "Blackboard" },
379
+ { value: "base16-dark", label: "Base16 Dark" },
380
+ { value: "base16-light", label: "Base16 Light" },
381
+ { value: "eclipse", label: "Eclipse" },
382
+ { value: "elegant", label: "Elegant" },
383
+ { value: "idea", label: "IntelliJ IDEA" },
384
+ { value: "mbo", label: "MBO" },
385
+ { value: "neat", label: "Neat" },
386
+ { value: "neo", label: "Neo" },
387
+ { value: "night", label: "Night" },
388
+ { value: "paraiso-dark", label: "Paraiso Dark" },
389
+ { value: "paraiso-light", label: "Paraiso Light" },
390
+ { value: "pastel-on-dark", label: "Pastel on Dark" },
391
+ { value: "rubyblue", label: "Ruby Blue" },
392
+ { value: "the-matrix", label: "The Matrix" },
393
+ { value: "tomorrow-night-bright", label: "Tomorrow Night Bright" },
394
+ { value: "tomorrow-night-eighties", label: "Tomorrow Night 80s" },
395
+ { value: "vibrant-ink", label: "Vibrant Ink" },
396
+ { value: "xq-dark", label: "XQ Dark" },
397
+ { value: "xq-light", label: "XQ Light" },
398
+ ];
399
+
400
+ // Light Mode Theme Section
401
+ const themeLightSection = document.createElement("div");
402
+ addClass(themeLightSection, "settingsSection");
403
+
404
+ const themeLightLabel = document.createElement("label");
405
+ themeLightLabel.textContent = "Editor Theme (Light Mode)";
406
+ addClass(themeLightLabel, "settingsLabel");
407
+
408
+ const themeLightHelp = document.createElement("div");
409
+ themeLightHelp.textContent = "Syntax highlighting theme when using light mode.";
410
+ addClass(themeLightHelp, "settingsHelp");
411
+
412
+ const themeLightSelect = document.createElement("select");
413
+ themeLightSelect.id = "codeMirrorThemeLightSelect";
414
+ addClass(themeLightSelect, "settingsSelect");
415
+
416
+ themes.forEach((theme) => {
417
+ const option = document.createElement("option");
418
+ option.value = theme.value;
419
+ option.textContent = theme.label;
420
+ themeLightSelect.appendChild(option);
421
+ });
422
+
423
+ const storedLightTheme = this.tab.yasgui.persistentConfig.getCodeMirrorTheme("light");
424
+ themeLightSelect.value = storedLightTheme || "default";
425
+
426
+ themeLightSection.appendChild(themeLightLabel);
427
+ themeLightSection.appendChild(themeLightHelp);
428
+ themeLightSection.appendChild(themeLightSelect);
429
+ container.appendChild(themeLightSection);
430
+
431
+ // Dark Mode Theme Section
432
+ const themeDarkSection = document.createElement("div");
433
+ addClass(themeDarkSection, "settingsSection");
434
+
435
+ const themeDarkLabel = document.createElement("label");
436
+ themeDarkLabel.textContent = "Editor Theme (Dark Mode)";
437
+ addClass(themeDarkLabel, "settingsLabel");
438
+
439
+ const themeDarkHelp = document.createElement("div");
440
+ themeDarkHelp.textContent = "Syntax highlighting theme when using dark mode.";
441
+ addClass(themeDarkHelp, "settingsHelp");
442
+
443
+ const themeDarkSelect = document.createElement("select");
444
+ themeDarkSelect.id = "codeMirrorThemeDarkSelect";
445
+ addClass(themeDarkSelect, "settingsSelect");
446
+
447
+ themes.forEach((theme) => {
448
+ const option = document.createElement("option");
449
+ option.value = theme.value;
450
+ option.textContent = theme.label;
451
+ themeDarkSelect.appendChild(option);
452
+ });
453
+
454
+ const storedDarkTheme = this.tab.yasgui.persistentConfig.getCodeMirrorTheme("dark");
455
+ themeDarkSelect.value = storedDarkTheme || "github-dark";
456
+
457
+ themeDarkSection.appendChild(themeDarkLabel);
458
+ themeDarkSection.appendChild(themeDarkHelp);
459
+ themeDarkSection.appendChild(themeDarkSelect);
460
+ container.appendChild(themeDarkSection);
461
+
462
+ // Theme reference link
463
+ const themeReferenceSection = document.createElement("div");
464
+ addClass(themeReferenceSection, "settingsSection");
465
+
466
+ const themeReferenceLink = document.createElement("a");
467
+ themeReferenceLink.href = "https://codemirror.net/5/demo/theme.html";
468
+ themeReferenceLink.target = "_blank";
469
+ themeReferenceLink.rel = "noopener noreferrer";
470
+ themeReferenceLink.textContent = "Preview CodeMirror themes →";
471
+ addClass(themeReferenceLink, "settingsLink");
472
+
473
+ themeReferenceSection.appendChild(themeReferenceLink);
474
+ container.appendChild(themeReferenceSection);
475
+
361
476
  // Formatter Type Section
362
477
  const formatterSection = document.createElement("div");
363
478
  addClass(formatterSection, "settingsSection");
@@ -1355,10 +1470,39 @@ export default class TabSettingsModal {
1355
1470
  // Save editor settings
1356
1471
  const yasqe = this.tab.getYasqe();
1357
1472
  if (yasqe && yasqe.persistentConfig) {
1473
+ const codeMirrorThemeLightSelect = document.getElementById("codeMirrorThemeLightSelect") as HTMLSelectElement;
1474
+ const codeMirrorThemeDarkSelect = document.getElementById("codeMirrorThemeDarkSelect") as HTMLSelectElement;
1358
1475
  const formatterSelect = document.getElementById("formatterTypeSelect") as HTMLSelectElement;
1359
1476
  const autoformatCheckbox = document.getElementById("autoformatOnQuery") as HTMLInputElement;
1360
1477
  const constructValidationCheckbox = document.getElementById("checkConstructVariables") as HTMLInputElement;
1361
1478
 
1479
+ // Save CodeMirror themes for both light and dark modes
1480
+ if (codeMirrorThemeLightSelect) {
1481
+ const selectedTheme = codeMirrorThemeLightSelect.value;
1482
+ this.tab.yasgui.persistentConfig.setCodeMirrorTheme("light", selectedTheme);
1483
+ }
1484
+
1485
+ if (codeMirrorThemeDarkSelect) {
1486
+ const selectedTheme = codeMirrorThemeDarkSelect.value;
1487
+ this.tab.yasgui.persistentConfig.setCodeMirrorTheme("dark", selectedTheme);
1488
+ }
1489
+
1490
+ // Apply the appropriate theme based on current mode
1491
+ const currentMode = this.tab.yasgui.getTheme();
1492
+ const themeToApply =
1493
+ currentMode === "dark"
1494
+ ? codeMirrorThemeDarkSelect?.value || "github-dark"
1495
+ : codeMirrorThemeLightSelect?.value || "default";
1496
+
1497
+ // Apply theme to all CodeMirror instances
1498
+ const cmElements = document.querySelectorAll(".CodeMirror");
1499
+ cmElements.forEach((element) => {
1500
+ const cm = (element as any).CodeMirror;
1501
+ if (cm && cm.setOption) {
1502
+ cm.setOption("theme", themeToApply);
1503
+ }
1504
+ });
1505
+
1362
1506
  if (formatterSelect) {
1363
1507
  yasqe.persistentConfig.formatterType = formatterSelect.value as "sparql-formatter" | "legacy";
1364
1508
  }
@@ -1,4 +1,5 @@
1
1
  import { Storage as YStorage } from "@matdata/yasgui-utils";
2
+ import PersistentConfig from "./PersistentConfig";
2
3
 
3
4
  export type Theme = "light" | "dark";
4
5
 
@@ -7,6 +8,7 @@ export class ThemeManager {
7
8
  private storage: YStorage;
8
9
  private currentTheme: Theme;
9
10
  private rootElement: HTMLElement;
11
+ private persistentConfig?: PersistentConfig;
10
12
 
11
13
  constructor(rootElement: HTMLElement) {
12
14
  this.storage = new YStorage("yasgui");
@@ -15,6 +17,13 @@ export class ThemeManager {
15
17
  this.applyTheme(this.currentTheme);
16
18
  }
17
19
 
20
+ /**
21
+ * Set PersistentConfig for accessing CodeMirror theme preference
22
+ */
23
+ public setPersistentConfig(persistentConfig: PersistentConfig): void {
24
+ this.persistentConfig = persistentConfig;
25
+ }
26
+
18
27
  /**
19
28
  * Get the currently active theme
20
29
  */
@@ -61,7 +70,15 @@ export class ThemeManager {
61
70
  * Update CodeMirror theme for all Yasqe editors
62
71
  */
63
72
  private updateCodeMirrorTheme(theme: Theme): void {
64
- const cmTheme = theme === "dark" ? "material-palenight" : "default";
73
+ // Get user's stored CodeMirror theme preference for the current mode, or use default
74
+ let cmTheme: string;
75
+ if (this.persistentConfig) {
76
+ const storedTheme = this.persistentConfig.getCodeMirrorTheme(theme);
77
+ cmTheme = storedTheme || (theme === "dark" ? "github-dark" : "default");
78
+ } else {
79
+ // Fallback if persistentConfig not yet available
80
+ cmTheme = theme === "dark" ? "github-dark" : "default";
81
+ }
65
82
 
66
83
  // Find all CodeMirror instances within the root element
67
84
  const cmElements = this.rootElement.querySelectorAll(".CodeMirror");
@@ -0,0 +1,192 @@
1
+ // GitHub Dark Default CodeMirror Theme
2
+ // Based on GitHub's dark default color scheme
3
+
4
+ .cm-s-github-dark.CodeMirror {
5
+ background-color: #0d1117;
6
+ color: #c9d1d9;
7
+ }
8
+
9
+ // Selection styles - using GitHub's subtle blue highlight
10
+ .cm-s-github-dark div.CodeMirror-selected {
11
+ background: rgba(56, 138, 253, 0.5) !important;
12
+ }
13
+
14
+ .cm-s-github-dark.CodeMirror ::selection {
15
+ background: rgba(56, 138, 253, 0.308) !important;
16
+ }
17
+
18
+ .cm-s-github-dark.CodeMirror ::-moz-selection {
19
+ background: rgba(56, 138, 253, 0.5) !important;
20
+ }
21
+
22
+ .cm-s-github-dark .CodeMirror-line::selection,
23
+ .cm-s-github-dark .CodeMirror-line > span::selection,
24
+ .cm-s-github-dark .CodeMirror-line > span > span::selection {
25
+ background: rgba(56, 138, 253, 0.5) !important;
26
+ }
27
+
28
+ .cm-s-github-dark .CodeMirror-line::-moz-selection,
29
+ .cm-s-github-dark .CodeMirror-line > span::-moz-selection,
30
+ .cm-s-github-dark .CodeMirror-line > span > span::-moz-selection {
31
+ background: rgba(56, 138, 253, 0.5) !important;
32
+ }
33
+
34
+ .cm-s-github-dark.CodeMirror-focused div.CodeMirror-selected {
35
+ background: rgba(56, 138, 253, 0.5) !important;
36
+ }
37
+
38
+ .cm-s-github-dark .CodeMirror-gutters {
39
+ background: #0d1117;
40
+ border-right: 1px solid #30363d;
41
+ }
42
+
43
+ .cm-s-github-dark .CodeMirror-guttermarker {
44
+ color: #c9d1d9;
45
+ }
46
+
47
+ .cm-s-github-dark .CodeMirror-guttermarker-subtle {
48
+ color: #6e7681;
49
+ }
50
+
51
+ .cm-s-github-dark .CodeMirror-linenumber {
52
+ color: #6e7681;
53
+ }
54
+
55
+ .cm-s-github-dark .CodeMirror-cursor {
56
+ border-left: 1px solid #c9d1d9;
57
+ }
58
+
59
+ // SPARQL syntax highlighting - GitHub Dark Default colors
60
+ .cm-s-github-dark span.cm-keyword {
61
+ color: #ff7b72; // Keywords (SELECT, WHERE, PREFIX, etc.)
62
+ }
63
+
64
+ .cm-s-github-dark span.cm-atom {
65
+ color: #f3f9ff; // Variables (?var, $var)
66
+ }
67
+
68
+ .cm-s-github-dark span.cm-string {
69
+ color: #a5d6ff; // Literals in quotes
70
+ }
71
+
72
+ .cm-s-github-dark span.cm-string-2 {
73
+ color: #5784ff; // Prefix namespace (rdf:, foaf:)
74
+ }
75
+
76
+ .cm-s-github-dark span.cm-variable-3 {
77
+ color: #1b9b8e; // URIs in angle brackets
78
+ }
79
+
80
+ .cm-s-github-dark span.cm-number {
81
+ color: #4faaf9; // Numbers
82
+ }
83
+
84
+ .cm-s-github-dark span.cm-comment {
85
+ color: #696e74; // Comments
86
+ font-style: italic;
87
+ }
88
+
89
+ .cm-s-github-dark span.cm-meta {
90
+ color: #ffa657; // Metadata
91
+ }
92
+
93
+ .cm-s-github-dark span.cm-operator {
94
+ color: #ff7b72; // Operators
95
+ }
96
+
97
+ .cm-s-github-dark span.cm-qualifier {
98
+ color: #79c0ff; // Qualifiers
99
+ }
100
+
101
+ .cm-s-github-dark span.cm-property {
102
+ color: #79c0ff; // Properties
103
+ }
104
+
105
+ .cm-s-github-dark span.cm-variable {
106
+ color: #c9d1d9; // Default variables
107
+ }
108
+
109
+ .cm-s-github-dark span.cm-variable-2 {
110
+ color: #79c0ff;
111
+ }
112
+
113
+ .cm-s-github-dark span.cm-def {
114
+ color: #d2a8ff; // Definitions
115
+ }
116
+
117
+ .cm-s-github-dark span.cm-tag {
118
+ color: #7ee787; // Tags
119
+ }
120
+
121
+ .cm-s-github-dark span.cm-attribute {
122
+ color: #79c0ff; // Attributes
123
+ }
124
+
125
+ .cm-s-github-dark span.cm-builtin {
126
+ color: #ffa657; // Built-in functions
127
+ }
128
+
129
+ .cm-s-github-dark span.cm-bracket {
130
+ color: #c9d1d9; // Brackets (fallback)
131
+ }
132
+
133
+ // Rainbow bracket colors
134
+ .cm-s-github-dark span.cm-bracket-level-0 {
135
+ color: #ffd700; // Gold
136
+ }
137
+
138
+ .cm-s-github-dark span.cm-bracket-level-1 {
139
+ color: #d2a8ff; // Purple
140
+ }
141
+
142
+ .cm-s-github-dark span.cm-bracket-level-2 {
143
+ color: #d85896; // Blue
144
+ }
145
+
146
+ .cm-s-github-dark span.cm-bracket-level-3 {
147
+ color: #229922; // Green
148
+ }
149
+
150
+ .cm-s-github-dark span.cm-bracket-paren {
151
+ color: #c9d1d9; // Constant color for () and []
152
+ }
153
+
154
+ .cm-s-github-dark span.cm-bracket-mismatch {
155
+ color: #ff7b72; // Red for mismatched brackets
156
+ }
157
+
158
+ .cm-s-github-dark span.cm-error {
159
+ color: #f85149;
160
+ border-bottom: 2px dotted #f85149;
161
+ }
162
+
163
+ // Active line
164
+ .cm-s-github-dark .CodeMirror-activeline-background {
165
+ background: rgba(110, 118, 129, 0.1);
166
+ }
167
+
168
+ // Matching brackets
169
+ .cm-s-github-dark .CodeMirror-matchingbracket {
170
+ color: #7ee787 !important;
171
+ background-color: rgba(46, 160, 67, 0.15);
172
+ text-decoration: underline;
173
+ }
174
+
175
+ .cm-s-github-dark .CodeMirror-nonmatchingbracket {
176
+ color: #ff7b72 !important;
177
+ background-color: rgba(248, 81, 73, 0.15);
178
+ }
179
+
180
+ // Search highlighting
181
+ .cm-s-github-dark .CodeMirror-searching {
182
+ background-color: rgba(255, 206, 0, 0.4);
183
+ }
184
+
185
+ // Override match highlight to use subtle selection color
186
+ .cm-s-github-dark .cm-matchhighlight {
187
+ background-color: rgba(56, 138, 253, 0.5) !important;
188
+ }
189
+
190
+ .cm-s-github-dark .CodeMirror-focused .CodeMirror-selected {
191
+ background: rgba(56, 138, 253, 0.5) !important;
192
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ import { ThemeManager, Theme } from "./ThemeManager";
19
19
  import QueryBrowser from "./queryManagement/QueryBrowser";
20
20
  import "./index.scss";
21
21
  import "./themes.scss";
22
+ import "./github-dark-theme.scss";
22
23
  import "../../yasr/src/scss/global.scss";
23
24
  import "codemirror/theme/material-palenight.css";
24
25
 
@@ -171,6 +172,9 @@ export class Yasgui extends EventEmitter {
171
172
  this.themeManager.listenToSystemTheme();
172
173
  this.persistentConfig = new PersistentConfig(this);
173
174
 
175
+ // Set persistentConfig reference in ThemeManager so it can access CodeMirror theme preference
176
+ this.themeManager.setPersistentConfig(this.persistentConfig);
177
+
174
178
  // Load persisted showSnippetsBar if available
175
179
  const persistedShowSnippetsBar = this.persistentConfig.getShowSnippetsBar();
176
180
  if (persistedShowSnippetsBar !== undefined) {
@@ -3,7 +3,7 @@ import { addClass, removeClass } from "@matdata/yasgui-utils";
3
3
  import type { WorkspaceConfig, FolderEntry } from "./types";
4
4
  import { filterFolderEntriesByName } from "./browserFilter";
5
5
  import { getWorkspaceBackend } from "./backends/getWorkspaceBackend";
6
- import { asWorkspaceBackendError } from "./backends/errors";
6
+ import { asWorkspaceBackendError, isWorkspaceBackendError, WorkspaceBackendError } from "./backends/errors";
7
7
  import { getEndpointToAutoSwitch } from "./openManagedQuery";
8
8
  import { hashQueryText } from "./textHash";
9
9
  import type { BackendType, VersionRef, ManagedTabMetadata } from "./types";
@@ -36,7 +36,7 @@ export default class QueryBrowser {
36
36
  private expandedFolderIds = new Set<string>();
37
37
  private folderEntriesById = new Map<string, FolderEntry[]>();
38
38
  private folderLoadingById = new Set<string>();
39
- private folderErrorById = new Map<string, string>();
39
+ private folderErrorById = new Map<string, WorkspaceBackendError>();
40
40
 
41
41
  private debouncedSearchHandle?: number;
42
42
 
@@ -315,7 +315,7 @@ export default class QueryBrowser {
315
315
  this.folderEntriesById.set(key, entries);
316
316
  } catch (e) {
317
317
  const err = asWorkspaceBackendError(e);
318
- this.folderErrorById.set(key, err.message || "Failed to load folder");
318
+ this.folderErrorById.set(key, err);
319
319
  this.folderEntriesById.set(key, []);
320
320
  } finally {
321
321
  this.folderLoadingById.delete(key);
@@ -325,6 +325,22 @@ export default class QueryBrowser {
325
325
  private formatStatusError(err: Error, workspaceType: WorkspaceConfig["type"]) {
326
326
  const message = err.message || "Unknown error";
327
327
 
328
+ // Check for authentication/authorization errors
329
+ if (isWorkspaceBackendError(err)) {
330
+ if (err.code === "AUTH_FAILED") {
331
+ return `⚠️ Authentication failed. Please check your workspace configuration and credentials in Settings → Workspaces.`;
332
+ }
333
+ if (err.code === "FORBIDDEN") {
334
+ return `⚠️ Access forbidden. Please verify your ${workspaceType === "git" ? "token permissions" : "endpoint authentication"} in Settings → Workspaces.`;
335
+ }
336
+ if (err.code === "NETWORK_ERROR") {
337
+ return `⚠️ Unable to connect to workspace. Please check your workspace configuration and network connection.`;
338
+ }
339
+ if (err.code === "NOT_FOUND") {
340
+ return `⚠️ Workspace not found. Please verify your workspace configuration in Settings → Workspaces.`;
341
+ }
342
+ }
343
+
328
344
  if (message.includes("No GitProviderClient configured")) {
329
345
  return "Git workspaces require a supported provider. Supported: GitHub, GitLab, Bitbucket Cloud (bitbucket.org), and Gitea. For self-hosted/enterprise instances, configure a git workspace 'provider' and/or 'apiBaseUrl'.";
330
346
  }
@@ -333,7 +349,8 @@ export default class QueryBrowser {
333
349
  return "This workspace backend is not supported yet.";
334
350
  }
335
351
 
336
- return message;
352
+ // Generic error with helpful suggestion
353
+ return `⚠️ Failed to load workspace: ${message}. Please check your workspace configuration and authentication in Settings → Workspaces.`;
337
354
  }
338
355
 
339
356
  private clearList() {
@@ -923,7 +940,7 @@ export default class QueryBrowser {
923
940
  addClass(error, "yasgui-query-browser__tree-meta");
924
941
  addClass(error, "yasgui-query-browser__tree-meta--error");
925
942
  error.setAttribute("style", makeIndentStyle(depth + 1));
926
- error.textContent = err;
943
+ error.textContent = this.formatStatusError(err, backend.type);
927
944
  this.listEl.appendChild(error);
928
945
  }
929
946
 
@@ -1041,6 +1058,15 @@ export default class QueryBrowser {
1041
1058
  if (runId !== this.refreshRunId) return;
1042
1059
 
1043
1060
  const rootKey = this.folderKey(undefined);
1061
+
1062
+ // Check if root folder failed to load
1063
+ const rootError = this.folderErrorById.get(rootKey);
1064
+ if (rootError) {
1065
+ this.setStatus(this.formatStatusError(rootError, workspace.type));
1066
+ this.clearList();
1067
+ return;
1068
+ }
1069
+
1044
1070
  const rootEntries = this.folderEntriesById.get(rootKey) || [];
1045
1071
 
1046
1072
  const expandedIds = Array.from(this.expandedFolderIds).sort();
@@ -1053,8 +1079,9 @@ export default class QueryBrowser {
1053
1079
  .sort()
1054
1080
  .join("|");
1055
1081
  const loading = this.folderLoadingById.has(key) ? "1" : "0";
1056
- const err = this.folderErrorById.get(key) || "";
1057
- return `${key}:${loading}:${err}:${entryPart}`;
1082
+ const err = this.folderErrorById.get(key);
1083
+ const errStr = err ? `${err.code}:${err.message}` : "";
1084
+ return `${key}:${loading}:${errStr}:${entryPart}`;
1058
1085
  })
1059
1086
  .join(";");
1060
1087
 
@@ -185,7 +185,7 @@ export default class SaveManagedQueryModal {
185
185
  this.nameEl.addEventListener("input", () => {
186
186
  if (this.filenameTouched) return;
187
187
  const suggested = this.suggestFilenameFromName(this.nameEl.value);
188
- if (suggested) this.filenameEl.value = suggested.replace(/\.sparql$/i, "");
188
+ if (suggested) this.filenameEl.value = suggested.replace(/\.(rq|sparql)$/i, "");
189
189
  });
190
190
 
191
191
  this.filenameEl = document.createElement("input");
@@ -373,7 +373,7 @@ export default class SaveManagedQueryModal {
373
373
  let resolvedFilename = filename;
374
374
 
375
375
  if (isGit) {
376
- resolvedFilename = resolvedFilename.replace(/\.sparql$/i, "");
376
+ resolvedFilename = resolvedFilename.replace(/\.(rq|sparql)$/i, "");
377
377
  if (!resolvedFilename) {
378
378
  window.alert("Please enter a filename");
379
379
  return;
@@ -446,7 +446,7 @@ export default class SaveManagedQueryModal {
446
446
 
447
447
  const defaultFilename =
448
448
  defaults?.filename ?? (defaultName ? (this.suggestFilenameFromName(defaultName) ?? "") : "");
449
- this.filenameEl.value = defaultFilename.replace(/\.sparql$/i, "");
449
+ this.filenameEl.value = defaultFilename.replace(/\.(rq|sparql)$/i, "");
450
450
  this.messageEl.value = defaults?.message ?? "";
451
451
 
452
452
  this.folderPickerOpen = false;
@@ -217,9 +217,9 @@ export class BitbucketProviderClient extends BaseGitProviderClient {
217
217
  }
218
218
 
219
219
  if (v.type === "commit_file") {
220
- if (!/\.sparql$/i.test(name)) continue;
220
+ if (!/\.(rq|sparql)$/i.test(name)) continue;
221
221
  const id = relPath ? this.joinPath(relPath, name) : name;
222
- const label = name.replace(/\.sparql$/i, "");
222
+ const label = name.replace(/\.(rq|sparql)$/i, "");
223
223
  entries.push({ kind: "query", id, label, parentId: relPath || undefined });
224
224
  }
225
225
  }
@@ -137,9 +137,9 @@ export class GiteaProviderClient extends BaseGitProviderClient {
137
137
  }
138
138
 
139
139
  if (item.type === "file") {
140
- if (!/\.sparql$/i.test(item.name)) continue;
140
+ if (!/\.(rq|sparql)$/i.test(item.name)) continue;
141
141
  const id = relPath ? this.joinPath(relPath, item.name) : item.name;
142
- const label = item.name.replace(/\.sparql$/i, "");
142
+ const label = item.name.replace(/\.(rq|sparql)$/i, "");
143
143
  entries.push({ kind: "query", id, label, parentId: relPath || undefined });
144
144
  }
145
145
  }
@@ -148,11 +148,11 @@ export class GithubProviderClient extends BaseGitProviderClient {
148
148
  }
149
149
 
150
150
  if (item.type === "file") {
151
- if (!/\.sparql$/i.test(item.name)) continue;
151
+ if (!/\.(rq|sparql)$/i.test(item.name)) continue;
152
152
  const id = config.rootPath
153
153
  ? item.path.slice(this.joinPath(config.rootPath).length).replace(/^\//, "")
154
154
  : item.path;
155
- const label = item.name.replace(/\.sparql$/i, "");
155
+ const label = item.name.replace(/\.(rq|sparql)$/i, "");
156
156
  entries.push({ kind: "query", id, label, parentId: relPath || undefined });
157
157
  }
158
158
  }
@@ -160,9 +160,9 @@ export class GitlabProviderClient extends BaseGitProviderClient {
160
160
  }
161
161
 
162
162
  if (item.type === "blob") {
163
- if (!/\.sparql$/i.test(item.name)) continue;
163
+ if (!/\.(rq|sparql)$/i.test(item.name)) continue;
164
164
  const id = relPath ? this.joinPath(relPath, item.name) : item.name;
165
- const label = item.name.replace(/\.sparql$/i, "");
165
+ const label = item.name.replace(/\.(rq|sparql)$/i, "");
166
166
  entries.push({ kind: "query", id, label, parentId: relPath || undefined });
167
167
  }
168
168
  }