dbviewer 0.7.10 → 0.7.11

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/assets/images/dbviewer/emoji-favicon.txt +1 -0
  4. data/app/assets/images/dbviewer/favicon.ico +4 -0
  5. data/app/assets/images/dbviewer/favicon.png +4 -0
  6. data/app/assets/images/dbviewer/favicon.svg +10 -0
  7. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +38 -42
  8. data/app/assets/javascripts/dbviewer/error_handler.js +58 -0
  9. data/app/assets/javascripts/dbviewer/home.js +25 -34
  10. data/app/assets/javascripts/dbviewer/layout.js +100 -129
  11. data/app/assets/javascripts/dbviewer/query.js +309 -246
  12. data/app/assets/javascripts/dbviewer/sidebar.js +170 -183
  13. data/app/assets/javascripts/dbviewer/utility.js +124 -0
  14. data/app/assets/stylesheets/dbviewer/application.css +8 -146
  15. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +0 -34
  16. data/app/assets/stylesheets/dbviewer/logs.css +0 -11
  17. data/app/assets/stylesheets/dbviewer/query.css +21 -9
  18. data/app/assets/stylesheets/dbviewer/table.css +49 -131
  19. data/app/controllers/concerns/dbviewer/database_operations/connection_management.rb +90 -0
  20. data/app/controllers/concerns/dbviewer/database_operations/data_export.rb +31 -0
  21. data/app/controllers/concerns/dbviewer/database_operations/database_information.rb +54 -0
  22. data/app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb +37 -0
  23. data/app/controllers/concerns/dbviewer/database_operations/query_operations.rb +37 -0
  24. data/app/controllers/concerns/dbviewer/database_operations/relationship_management.rb +175 -0
  25. data/app/controllers/concerns/dbviewer/database_operations/table_operations.rb +46 -0
  26. data/app/controllers/concerns/dbviewer/database_operations.rb +4 -9
  27. data/app/controllers/dbviewer/api/tables_controller.rb +12 -0
  28. data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +0 -15
  29. data/app/controllers/dbviewer/tables_controller.rb +4 -33
  30. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +1 -1
  31. data/app/views/dbviewer/tables/query.html.erb +21 -6
  32. data/app/views/dbviewer/tables/show.html.erb +2 -2
  33. data/app/views/layouts/dbviewer/application.html.erb +12 -3
  34. data/config/routes.rb +2 -2
  35. data/lib/dbviewer/database/manager.rb +2 -2
  36. data/lib/dbviewer/datatable/query_operations.rb +1 -17
  37. data/lib/dbviewer/engine.rb +29 -0
  38. data/lib/dbviewer/version.rb +1 -1
  39. metadata +15 -10
  40. data/app/controllers/concerns/dbviewer/connection_management.rb +0 -88
  41. data/app/controllers/concerns/dbviewer/data_export.rb +0 -32
  42. data/app/controllers/concerns/dbviewer/database_information.rb +0 -62
  43. data/app/controllers/concerns/dbviewer/datatable_support.rb +0 -47
  44. data/app/controllers/concerns/dbviewer/pagination_concern.rb +0 -34
  45. data/app/controllers/concerns/dbviewer/query_operations.rb +0 -28
  46. data/app/controllers/concerns/dbviewer/relationship_management.rb +0 -173
  47. data/app/controllers/concerns/dbviewer/table_operations.rb +0 -56
@@ -1,277 +1,340 @@
1
- import * as monaco from "https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/+esm";
2
-
3
- // Helper function to decode HTML entities
4
- function decodeHTMLEntities(text) {
5
- const textarea = document.createElement("textarea");
6
- textarea.innerHTML = text;
7
- return textarea.value;
8
- }
9
-
10
- // Get initial query value from a data attribute to avoid string escaping issues
11
- const initialQueryEncoded = document
12
- .getElementById("monaco-editor")
13
- .getAttribute("data-initial-query");
14
- const initialQuery = decodeHTMLEntities(initialQueryEncoded);
15
-
16
- // Determine initial theme based on document theme
17
- const initialTheme =
18
- document.documentElement.getAttribute("data-bs-theme") === "dark"
19
- ? "vs-dark"
20
- : "vs";
21
-
22
- // Initialize Monaco Editor with SQL syntax highlighting
23
- const editor = monaco.editor.create(document.getElementById("monaco-editor"), {
24
- value: initialQuery || "",
25
- language: "sql",
26
- theme: initialTheme,
27
- automaticLayout: true, // Resize automatically
28
- minimap: { enabled: true },
29
- scrollBeyondLastLine: false,
30
- lineNumbers: "on",
31
- renderLineHighlight: "all",
32
- tabSize: 2,
33
- wordWrap: "on",
34
- formatOnPaste: true,
35
- formatOnType: true,
36
- autoIndent: "full",
37
- folding: true,
38
- glyphMargin: false,
39
- suggestOnTriggerCharacters: true,
40
- fixedOverflowWidgets: true,
41
- quickSuggestions: {
42
- other: true,
43
- comments: true,
44
- strings: true,
45
- },
46
- suggest: {
47
- showKeywords: true,
48
- showSnippets: true,
49
- preview: true,
50
- showIcons: true,
51
- maxVisibleSuggestions: 12,
52
- },
53
- });
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ // Validate that required utility scripts have loaded
3
+ if (!window.DBViewer || !DBViewer.Utility || !DBViewer.ErrorHandler) {
4
+ console.error(
5
+ "Required DBViewer scripts not loaded. Please check utility.js and error_handler.js."
6
+ );
7
+ return;
8
+ }
54
9
 
55
- // Theme change listener
56
- document.addEventListener("dbviewerThemeChanged", (event) => {
57
- const newTheme = event.detail.theme === "dark" ? "vs-dark" : "vs";
58
- monaco.editor.setTheme(newTheme);
10
+ // Destructure the needed functions for easier access
11
+ const { decodeHTMLEntities, ThemeManager } = DBViewer.Utility;
12
+ const { displayError } = DBViewer.ErrorHandler;
13
+
14
+ // Get initial query value from a data attribute to avoid string escaping issues
15
+ const initialQueryEncoded = document
16
+ .getElementById("monaco-editor")
17
+ ?.getAttribute("data-initial-query");
18
+ const initialQuery = initialQueryEncoded
19
+ ? decodeHTMLEntities(initialQueryEncoded)
20
+ : "";
21
+
22
+ // Use RequireJS to load Monaco
23
+ require(["vs/editor/editor.main"], function () {
24
+ const initialTheme =
25
+ ThemeManager.getCurrentTheme() === "dark" ? "vs-dark" : "vs";
26
+
27
+ const editorContainer = document.getElementById("monaco-editor");
28
+ if (!editorContainer) {
29
+ throw new Error("Monaco editor container not found");
30
+ }
59
31
 
60
- // Update editor container border color
61
- const editorContainer = document.querySelector(".monaco-editor-container");
62
- if (editorContainer) {
63
- editorContainer.style.borderColor =
64
- event.detail.theme === "dark" ? "#495057" : "#ced4da";
65
- }
32
+ const editor = monaco.editor.create(editorContainer, {
33
+ value: initialQuery || "",
34
+ language: "sql",
35
+ theme: initialTheme,
36
+ automaticLayout: true, // Resize automatically
37
+ minimap: { enabled: true },
38
+ scrollBeyondLastLine: false,
39
+ lineNumbers: "on",
40
+ renderLineHighlight: "all",
41
+ tabSize: 2,
42
+ wordWrap: "on",
43
+ formatOnPaste: true,
44
+ formatOnType: true,
45
+ autoIndent: "full",
46
+ folding: true,
47
+ glyphMargin: false,
48
+ suggestOnTriggerCharacters: true,
49
+ fixedOverflowWidgets: true,
50
+ quickSuggestions: {
51
+ other: true,
52
+ comments: true,
53
+ strings: true,
54
+ },
55
+ suggest: {
56
+ showKeywords: true,
57
+ showSnippets: true,
58
+ preview: true,
59
+ showIcons: true,
60
+ maxVisibleSuggestions: 12,
61
+ },
62
+ });
66
63
 
67
- // Update status bar styling based on theme
68
- updateStatusBarTheme(event.detail.theme);
64
+ // Theme change listener
65
+ document.addEventListener("dbviewerThemeChanged", (event) => {
66
+ const newTheme = event.detail.theme === "dark" ? "vs-dark" : "vs";
67
+ monaco.editor.setTheme(newTheme);
68
+
69
+ // Update editor container border color
70
+ const editorContainer = document.querySelector(
71
+ ".monaco-editor-container"
72
+ );
73
+ editorContainer.style.borderColor =
74
+ event.detail.theme === "dark" ? "#495057" : "#ced4da";
75
+
76
+ // Update status bar styling based on theme
77
+ updateStatusBarTheme(event.detail.theme);
78
+
79
+ // Update example query buttons
80
+ const exampleQueries = document.querySelectorAll(".example-query");
81
+ exampleQueries.forEach((query) => {
82
+ if (event.detail.theme === "dark") {
83
+ query.style.borderColor = "#495057";
84
+ if (!query.classList.contains("btn-primary")) {
85
+ query.style.color = "#f8f9fa";
86
+ }
87
+ } else {
88
+ query.style.borderColor = "#ced4da";
89
+ if (!query.classList.contains("btn-primary")) {
90
+ query.style.color = "";
91
+ }
92
+ }
93
+ });
94
+ });
69
95
 
70
- // Update example query buttons
71
- const exampleQueries = document.querySelectorAll(".example-query");
72
- exampleQueries.forEach((query) => {
73
- if (event.detail.theme === "dark") {
74
- query.style.borderColor = "#495057";
75
- if (!query.classList.contains("btn-primary")) {
76
- query.style.color = "#f8f9fa";
77
- }
78
- } else {
79
- query.style.borderColor = "#ced4da";
80
- if (!query.classList.contains("btn-primary")) {
81
- query.style.color = "";
96
+ /**
97
+ * Update the status bar styling based on current theme
98
+ * @param {string} theme - 'dark' or 'light'
99
+ */
100
+ function updateStatusBarTheme(theme) {
101
+ const statusBar = document.querySelector(".monaco-status-bar");
102
+ if (!statusBar) return;
103
+
104
+ if (theme === "dark") {
105
+ statusBar.style.backgroundColor = "#343a40";
106
+ statusBar.style.borderColor = "#495057";
107
+ statusBar.style.color = "#adb5bd";
108
+ } else {
109
+ statusBar.style.backgroundColor = "#f8f9fa";
110
+ statusBar.style.borderColor = "#ced4da";
111
+ statusBar.style.color = "#6c757d";
82
112
  }
83
113
  }
84
- });
85
- });
86
114
 
87
- function updateStatusBarTheme(theme) {
88
- const statusBar = document.querySelector(".monaco-status-bar");
89
- if (!statusBar) return;
90
-
91
- if (theme === "dark") {
92
- statusBar.style.backgroundColor = "#343a40";
93
- statusBar.style.borderColor = "#495057";
94
- statusBar.style.color = "#adb5bd";
95
- } else {
96
- statusBar.style.backgroundColor = "#f8f9fa";
97
- statusBar.style.borderColor = "#ced4da";
98
- statusBar.style.color = "#6c757d";
99
- }
100
- }
101
-
102
- // Set up SQL intellisense with table/column completions
103
- const tableName = document.getElementById("table_name").value;
104
- const columns = JSON.parse(document.getElementById("columns_data").value);
105
-
106
- // Register SQL completion providers
107
- monaco.languages.registerCompletionItemProvider("sql", {
108
- provideCompletionItems: function (model, position) {
109
- const textUntilPosition = model.getValueInRange({
110
- startLineNumber: position.lineNumber,
111
- startColumn: 1,
112
- endLineNumber: position.lineNumber,
113
- endColumn: position.column,
115
+ const tableName = document.getElementById("table_name").value;
116
+ const columns = JSON.parse(document.getElementById("columns_data").value);
117
+
118
+ // Register SQL completion providers
119
+ monaco.languages.registerCompletionItemProvider("sql", {
120
+ provideCompletionItems: function () {
121
+ const suggestions = [];
122
+
123
+ // Add table name suggestion
124
+ suggestions.push({
125
+ label: tableName,
126
+ kind: monaco.languages.CompletionItemKind.Class,
127
+ insertText: tableName,
128
+ detail: "Table name",
129
+ });
130
+
131
+ // Add column name suggestions
132
+ columns.forEach((col) => {
133
+ suggestions.push({
134
+ label: col.name,
135
+ kind: monaco.languages.CompletionItemKind.Field,
136
+ insertText: col.name,
137
+ detail: `Column (${col.type})`,
138
+ });
139
+ });
140
+
141
+ // Add common SQL keywords
142
+ const keywords = [
143
+ { label: "SELECT", insertText: "SELECT " },
144
+ { label: "FROM", insertText: "FROM " },
145
+ { label: "WHERE", insertText: "WHERE " },
146
+ { label: "ORDER BY", insertText: "ORDER BY " },
147
+ { label: "GROUP BY", insertText: "GROUP BY " },
148
+ { label: "HAVING", insertText: "HAVING " },
149
+ { label: "LIMIT", insertText: "LIMIT " },
150
+ { label: "JOIN", insertText: "JOIN " },
151
+ { label: "LEFT JOIN", insertText: "LEFT JOIN " },
152
+ { label: "INNER JOIN", insertText: "INNER JOIN " },
153
+ ];
154
+
155
+ keywords.forEach((kw) => {
156
+ suggestions.push({
157
+ label: kw.label,
158
+ kind: monaco.languages.CompletionItemKind.Keyword,
159
+ insertText: kw.insertText,
160
+ });
161
+ });
162
+
163
+ return { suggestions };
164
+ },
114
165
  });
115
166
 
116
- const suggestions = [];
167
+ // Handle form submission - transfer content to hidden input before submitting
168
+ const sqlForm = document.getElementById("sql-query-form");
169
+ sqlForm.addEventListener("submit", function (event) {
170
+ event.preventDefault();
171
+ const queryInput = document.getElementById("query-input");
117
172
 
118
- // Add table name suggestion
119
- suggestions.push({
120
- label: tableName,
121
- kind: monaco.languages.CompletionItemKind.Class,
122
- insertText: tableName,
123
- detail: "Table name",
124
- });
173
+ queryInput.value = editor.getValue();
174
+ const statusInfo = document.querySelector(
175
+ ".monaco-status-bar .status-info"
176
+ );
177
+ if (statusInfo) statusInfo.textContent = "Executing query...";
125
178
 
126
- // Add column name suggestions
127
- columns.forEach((col) => {
128
- suggestions.push({
129
- label: col.name,
130
- kind: monaco.languages.CompletionItemKind.Field,
131
- insertText: col.name,
132
- detail: `Column (${col.type})`,
133
- });
179
+ this.submit();
134
180
  });
135
181
 
136
- // Add common SQL keywords
137
- const keywords = [
138
- { label: "SELECT", insertText: "SELECT " },
139
- { label: "FROM", insertText: "FROM " },
140
- { label: "WHERE", insertText: "WHERE " },
141
- { label: "ORDER BY", insertText: "ORDER BY " },
142
- { label: "GROUP BY", insertText: "GROUP BY " },
143
- { label: "HAVING", insertText: "HAVING " },
144
- { label: "LIMIT", insertText: "LIMIT " },
145
- { label: "JOIN", insertText: "JOIN " },
146
- { label: "LEFT JOIN", insertText: "LEFT JOIN " },
147
- { label: "INNER JOIN", insertText: "INNER JOIN " },
148
- ];
149
-
150
- keywords.forEach((kw) => {
151
- suggestions.push({
152
- label: kw.label,
153
- kind: monaco.languages.CompletionItemKind.Keyword,
154
- insertText: kw.insertText,
182
+ const exampleQueries = document.querySelectorAll(".example-query");
183
+ exampleQueries.forEach((example) => {
184
+ example.style.cursor = "pointer";
185
+ example.addEventListener("click", () => {
186
+ const query = decodeHTMLEntities(example.textContent);
187
+ editor.setValue(query);
188
+ editor.focus();
189
+
190
+ const statusInfo = document.querySelector(
191
+ ".monaco-status-bar .status-info"
192
+ );
193
+ statusInfo.textContent = "Example query loaded";
194
+
195
+ setTimeout(() => {
196
+ const position = editor.getPosition();
197
+ if (position) {
198
+ statusInfo.textContent = `Ln ${position.lineNumber}, Col ${position.column}`;
199
+ } else {
200
+ statusInfo.textContent = "Ready";
201
+ }
202
+ }, 2000);
155
203
  });
156
204
  });
157
205
 
158
- return { suggestions };
159
- },
160
- });
206
+ // Setup editor keybindings if editor was initialized successfully
207
+ editor.addCommand(
208
+ monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
209
+ function () {
210
+ const queryValue = editor.getValue();
211
+ const queryInput = document.getElementById("query-input");
212
+ const sqlForm = document.getElementById("sql-query-form");
161
213
 
162
- // Handle form submission - transfer content to hidden input before submitting
163
- document
164
- .getElementById("sql-query-form")
165
- .addEventListener("submit", function (event) {
166
- // Stop the form from submitting immediately
167
- event.preventDefault();
214
+ queryInput.value = queryValue;
168
215
 
169
- // Get the query value from the editor and set it to the hidden input
170
- const queryValue = editor.getValue();
171
- document.getElementById("query-input").value = queryValue;
216
+ // Update status to indicate submission
217
+ const statusInfo = document.querySelector(
218
+ ".monaco-status-bar .status-info"
219
+ );
220
+ if (statusInfo) statusInfo.textContent = "Executing query...";
172
221
 
173
- // Now manually submit the form
174
- this.submit();
175
- });
222
+ sqlForm.submit();
223
+ }
224
+ );
225
+
226
+ // Add SELECT * action
227
+ editor.addAction({
228
+ id: "insert-select-all",
229
+ label: "Insert SELECT * FROM statement",
230
+ keybindings: [
231
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyS,
232
+ ],
233
+ run: function () {
234
+ try {
235
+ editor.trigger("keyboard", "type", {
236
+ text: `SELECT * FROM ${tableName} LIMIT 100`,
237
+ });
238
+ } catch (error) {
239
+ console.error("Error inserting SELECT statement:", error);
240
+ }
241
+ return null;
242
+ },
243
+ });
176
244
 
177
- // Make example queries clickable
178
- document.querySelectorAll(".example-query").forEach((example) => {
179
- example.style.cursor = "pointer";
180
- example.addEventListener("click", () => {
181
- const query = decodeHTMLEntities(example.textContent);
182
- editor.setValue(query);
183
- editor.focus();
184
- });
185
- });
245
+ // Add WHERE clause action
246
+ editor.addAction({
247
+ id: "insert-where",
248
+ label: "Insert WHERE clause",
249
+ keybindings: [
250
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyW,
251
+ ],
252
+ run: function () {
253
+ editor.trigger("keyboard", "type", { text: " WHERE " });
254
+ return null;
255
+ },
256
+ });
186
257
 
187
- // Setup editor keybindings
188
- editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
189
- // Get the query value from the editor and set it to the hidden input
190
- const queryValue = editor.getValue();
191
- document.getElementById("query-input").value = queryValue;
258
+ // Add toggle table structure action
259
+ editor.addAction({
260
+ id: "toggle-table-structure",
261
+ label: "Toggle Table Structure Reference",
262
+ keybindings: [
263
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyT,
264
+ ],
265
+ run: function () {
266
+ const tableStructureContent = document.getElementById(
267
+ "tableStructureContent"
268
+ );
269
+ if (!tableStructureContent) {
270
+ throw new Error("Table structure content element not found");
271
+ }
272
+
273
+ bootstrap.Collapse.getOrCreateInstance(tableStructureContent).toggle();
274
+ return null;
275
+ },
276
+ });
192
277
 
193
- // Submit the form
194
- document.getElementById("sql-query-form").submit();
195
- });
278
+ const statusBarDiv = document.createElement("div");
279
+ statusBarDiv.className = "monaco-status-bar";
280
+ statusBarDiv.innerHTML = `<div class="status-info">Ready</div>
281
+ <div class="column-info">Table: ${tableName} (${columns.length} columns)</div>`;
282
+ editorContainer.after(statusBarDiv);
196
283
 
197
- // Add keyboard shortcuts for common SQL statements
198
- editor.addAction({
199
- id: "insert-select-all",
200
- label: "Insert SELECT * FROM statement",
201
- keybindings: [
202
- monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyS,
203
- ],
204
- run: function () {
205
- editor.trigger("keyboard", "type", {
206
- text: `SELECT * FROM ${tableName} LIMIT 100`,
284
+ // Apply initial theme to status bar
285
+ updateStatusBarTheme(ThemeManager.getCurrentTheme());
286
+
287
+ // Update status bar with cursor position
288
+ editor.onDidChangeCursorPosition((e) => {
289
+ const position = `Ln ${e.position.lineNumber}, Col ${e.position.column}`;
290
+ const statusInfo = statusBarDiv.querySelector(".status-info");
291
+ if (statusInfo) statusInfo.textContent = position;
207
292
  });
208
- return null;
209
- },
210
- });
211
293
 
212
- editor.addAction({
213
- id: "insert-where",
214
- label: "Insert WHERE clause",
215
- keybindings: [
216
- monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyW,
217
- ],
218
- run: function () {
219
- editor.trigger("keyboard", "type", { text: " WHERE " });
220
- return null;
221
- },
222
- });
294
+ // Focus the editor when page loads
295
+ window.addEventListener("load", () => {
296
+ editor.focus();
297
+ });
223
298
 
224
- editor.addAction({
225
- id: "toggle-table-structure",
226
- label: "Toggle Table Structure Reference",
227
- keybindings: [
228
- monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyT,
229
- ],
230
- run: function () {
231
- // Use Bootstrap's collapse API to toggle
232
- bootstrap.Collapse.getOrCreateInstance(
233
- document.getElementById("tableStructureContent")
234
- ).toggle();
235
- return null;
236
- },
237
- });
299
+ // Toggle icon when table structure collapses or expands
300
+ const tableStructureContent = document.getElementById(
301
+ "tableStructureContent"
302
+ );
238
303
 
239
- // Create a status bar showing cursor position and columns info
240
- const statusBarDiv = document.createElement("div");
241
- statusBarDiv.className = "monaco-status-bar";
242
- statusBarDiv.innerHTML = `<div class="status-info">Ready</div>
243
- <div class="column-info">Table: ${tableName} (${columns.length} columns)</div>`;
244
- document.getElementById("monaco-editor").after(statusBarDiv);
245
-
246
- // Apply initial theme to status bar
247
- const currentTheme =
248
- document.documentElement.getAttribute("data-bs-theme") || "light";
249
- updateStatusBarTheme(currentTheme);
250
-
251
- // Update status bar with cursor position
252
- editor.onDidChangeCursorPosition((e) => {
253
- const position = `Ln ${e.position.lineNumber}, Col ${e.position.column}`;
254
- statusBarDiv.querySelector(".status-info").textContent = position;
255
- });
304
+ tableStructureContent.addEventListener("show.bs.collapse", function () {
305
+ const icon = document.querySelector("#tableStructureHeader button i");
306
+ icon.classList.replace("bi-chevron-down", "bi-chevron-up");
307
+ });
256
308
 
257
- // Focus the editor when page loads
258
- window.addEventListener("load", () => {
259
- editor.focus();
260
- });
309
+ tableStructureContent.addEventListener("hide.bs.collapse", function () {
310
+ const icon = document.querySelector("#tableStructureHeader button i");
311
+ icon.classList.replace("bi-chevron-up", "bi-chevron-down");
312
+ });
261
313
 
262
- // Toggle icon when table structure collapses or expands
263
- document
264
- .getElementById("tableStructureContent")
265
- .addEventListener("show.bs.collapse", function () {
266
- document
267
- .querySelector("#tableStructureHeader button i")
268
- .classList.replace("bi-chevron-down", "bi-chevron-up");
269
- });
314
+ // Add error recovery mechanism
315
+ window.addEventListener("error", function (e) {
316
+ // Only handle Monaco editor related errors
317
+ if (e.message.includes("monaco") || e.message.includes("editor")) {
318
+ displayError(
319
+ "query-container",
320
+ "Editor Error",
321
+ "An error occurred in the SQL editor",
322
+ "Please refresh the page to restore full functionality"
323
+ );
324
+ }
325
+ });
270
326
 
271
- document
272
- .getElementById("tableStructureContent")
273
- .addEventListener("hide.bs.collapse", function () {
274
- document
275
- .querySelector("#tableStructureHeader button i")
276
- .classList.replace("bi-chevron-up", "bi-chevron-down");
327
+ // Global error handler for Monaco editor errors
328
+ window.addEventListener("error", function (e) {
329
+ // Only handle Monaco editor related errors
330
+ if (e.message.includes("monaco") || e.message.includes("editor")) {
331
+ displayError(
332
+ "query-container",
333
+ "Editor Error",
334
+ "An error occurred in the SQL editor",
335
+ "Please refresh the page to restore full functionality"
336
+ );
337
+ }
338
+ });
277
339
  });
340
+ });