@mongoosejs/studio 0.1.19 → 0.2.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 (40) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +4 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +28 -22
  3. package/backend/actions/Model/getDocument.js +2 -1
  4. package/backend/actions/Model/getDocuments.js +3 -2
  5. package/backend/actions/Model/getDocumentsStream.js +3 -2
  6. package/backend/helpers/evaluateFilter.js +38 -1
  7. package/backend/helpers/getRefFromSchemaType.js +5 -0
  8. package/express.js +4 -2
  9. package/frontend/public/app.js +704 -405
  10. package/frontend/public/index.html +1 -1
  11. package/frontend/public/style.css +1 -1
  12. package/frontend/public/tw.css +81 -62
  13. package/frontend/src/_util/document-search-autocomplete.js +229 -0
  14. package/frontend/src/chat/chat-message-script/chat-message-script.html +27 -20
  15. package/frontend/src/chat/chat.html +20 -17
  16. package/frontend/src/chat/chat.js +2 -0
  17. package/frontend/src/document/document.css +1 -8
  18. package/frontend/src/document/document.html +202 -164
  19. package/frontend/src/document/document.js +1 -0
  20. package/frontend/src/document-details/document-details.html +1 -11
  21. package/frontend/src/document-details/document-details.js +43 -1
  22. package/frontend/src/document-details/document-property/document-property.html +4 -4
  23. package/frontend/src/index.js +36 -15
  24. package/frontend/src/json-node/json-node.html +118 -0
  25. package/frontend/src/json-node/json-node.js +272 -0
  26. package/frontend/src/list-array/list-array.html +15 -3
  27. package/frontend/src/list-array/list-array.js +21 -3
  28. package/frontend/src/list-default/list-default.js +2 -2
  29. package/frontend/src/list-json/json-node.html +1 -1
  30. package/frontend/src/list-json/list-json.html +1 -1
  31. package/frontend/src/list-json/list-json.js +11 -248
  32. package/frontend/src/list-subdocument/list-subdocument.html +13 -4
  33. package/frontend/src/list-subdocument/list-subdocument.js +11 -6
  34. package/frontend/src/models/document-search/document-search.html +1 -1
  35. package/frontend/src/models/document-search/document-search.js +22 -116
  36. package/frontend/src/models/models.css +5 -15
  37. package/frontend/src/models/models.html +34 -34
  38. package/frontend/src/models/models.js +1 -1
  39. package/frontend/src/navbar/navbar.html +15 -6
  40. package/package.json +3 -3
@@ -5,7 +5,7 @@
5
5
  <meta content="width=device-width, initial-scale=1" name="viewport">
6
6
  <meta name="msapplication-TileColor" />
7
7
  <meta name="theme-color" content="#ffffff"/>
8
- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans"/>
8
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter"/>
9
9
  <link rel="stylesheet" type="text/css" href="style.css"/>
10
10
  <link rel="stylesheet" href="https://unpkg.com/prismjs@1.29.0/themes/prism.css"/>
11
11
  <link rel="stylesheet" href="tw.css">
@@ -2,7 +2,7 @@ body {
2
2
  min-height: 100%;
3
3
  margin: 0;
4
4
  padding: 0;
5
- font-family: "Open Sans", Helvetica, Arial, FreeSans, sans-serif;
5
+ font-family: "Inter", Helvetica, Arial, FreeSans, sans-serif;
6
6
  }
7
7
 
8
8
  a {
@@ -731,6 +731,11 @@ video {
731
731
  margin: 1rem;
732
732
  }
733
733
 
734
+ .\!my-0 {
735
+ margin-top: 0px !important;
736
+ margin-bottom: 0px !important;
737
+ }
738
+
734
739
  .-mx-4 {
735
740
  margin-left: -1rem;
736
741
  margin-right: -1rem;
@@ -756,6 +761,11 @@ video {
756
761
  margin-right: auto;
757
762
  }
758
763
 
764
+ .my-1 {
765
+ margin-top: 0.25rem;
766
+ margin-bottom: 0.25rem;
767
+ }
768
+
759
769
  .my-4 {
760
770
  margin-top: 1rem;
761
771
  margin-bottom: 1rem;
@@ -924,6 +934,10 @@ video {
924
934
  height: 90vh !important;
925
935
  }
926
936
 
937
+ .h-10 {
938
+ height: 2.5rem;
939
+ }
940
+
927
941
  .h-12 {
928
942
  height: 3rem;
929
943
  }
@@ -984,10 +998,6 @@ video {
984
998
  height: 100%;
985
999
  }
986
1000
 
987
- .h-px {
988
- height: 1px;
989
- }
990
-
991
1001
  .max-h-40 {
992
1002
  max-height: 10rem;
993
1003
  }
@@ -1000,10 +1010,6 @@ video {
1000
1010
  max-height: 50vh;
1001
1011
  }
1002
1012
 
1003
- .max-h-\[6\.5em\] {
1004
- max-height: 6.5em;
1005
- }
1006
-
1007
1013
  .min-h-0 {
1008
1014
  min-height: 0px;
1009
1015
  }
@@ -1104,8 +1110,8 @@ video {
1104
1110
  max-width: 64rem;
1105
1111
  }
1106
1112
 
1107
- .max-w-\[30em\] {
1108
- max-width: 30em;
1113
+ .max-w-7xl {
1114
+ max-width: 80rem;
1109
1115
  }
1110
1116
 
1111
1117
  .max-w-\[calc\(100vw-3rem\)\] {
@@ -1116,10 +1122,6 @@ video {
1116
1122
  max-width: calc(100vw - 4rem);
1117
1123
  }
1118
1124
 
1119
- .max-w-xs {
1120
- max-width: 20rem;
1121
- }
1122
-
1123
1125
  .flex-1 {
1124
1126
  flex: 1 1 0%;
1125
1127
  }
@@ -1200,6 +1202,10 @@ video {
1200
1202
  animation: spin 1s linear infinite;
1201
1203
  }
1202
1204
 
1205
+ .cursor-auto {
1206
+ cursor: auto;
1207
+ }
1208
+
1203
1209
  .cursor-not-allowed {
1204
1210
  cursor: not-allowed;
1205
1211
  }
@@ -1338,12 +1344,6 @@ video {
1338
1344
  margin-bottom: calc(1rem * var(--tw-space-y-reverse));
1339
1345
  }
1340
1346
 
1341
- .space-y-6 > :not([hidden]) ~ :not([hidden]) {
1342
- --tw-space-y-reverse: 0;
1343
- margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
1344
- margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
1345
- }
1346
-
1347
1347
  .divide-y > :not([hidden]) ~ :not([hidden]) {
1348
1348
  --tw-divide-y-reverse: 0;
1349
1349
  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
@@ -1403,6 +1403,10 @@ video {
1403
1403
  white-space: nowrap;
1404
1404
  }
1405
1405
 
1406
+ .text-ellipsis {
1407
+ text-overflow: ellipsis;
1408
+ }
1409
+
1406
1410
  .whitespace-nowrap {
1407
1411
  white-space: nowrap;
1408
1412
  }
@@ -1543,6 +1547,11 @@ video {
1543
1547
  border-color: rgb(239 68 68 / var(--tw-border-opacity));
1544
1548
  }
1545
1549
 
1550
+ .border-slate-100 {
1551
+ --tw-border-opacity: 1;
1552
+ border-color: rgb(241 245 249 / var(--tw-border-opacity));
1553
+ }
1554
+
1546
1555
  .border-transparent {
1547
1556
  border-color: transparent;
1548
1557
  }
@@ -1557,6 +1566,11 @@ video {
1557
1566
  border-left-color: rgb(59 130 246 / var(--tw-border-opacity));
1558
1567
  }
1559
1568
 
1569
+ .\!bg-zinc-50 {
1570
+ --tw-bg-opacity: 1 !important;
1571
+ background-color: rgb(250 250 250 / var(--tw-bg-opacity)) !important;
1572
+ }
1573
+
1560
1574
  .bg-amber-100 {
1561
1575
  --tw-bg-opacity: 1;
1562
1576
  background-color: rgb(254 243 199 / var(--tw-bg-opacity));
@@ -1637,14 +1651,14 @@ video {
1637
1651
  background-color: rgb(75 85 99 / var(--tw-bg-opacity));
1638
1652
  }
1639
1653
 
1640
- .bg-gray-800 {
1654
+ .bg-gray-700 {
1641
1655
  --tw-bg-opacity: 1;
1642
- background-color: rgb(31 41 55 / var(--tw-bg-opacity));
1656
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity));
1643
1657
  }
1644
1658
 
1645
- .bg-green-300 {
1659
+ .bg-gray-800 {
1646
1660
  --tw-bg-opacity: 1;
1647
- background-color: rgb(134 239 172 / var(--tw-bg-opacity));
1661
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity));
1648
1662
  }
1649
1663
 
1650
1664
  .bg-green-50 {
@@ -1652,29 +1666,19 @@ video {
1652
1666
  background-color: rgb(240 253 244 / var(--tw-bg-opacity));
1653
1667
  }
1654
1668
 
1655
- .bg-green-500 {
1656
- --tw-bg-opacity: 1;
1657
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
1658
- }
1659
-
1660
1669
  .bg-green-600 {
1661
1670
  --tw-bg-opacity: 1;
1662
1671
  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
1663
1672
  }
1664
1673
 
1665
- .bg-pink-600 {
1666
- --tw-bg-opacity: 1;
1667
- background-color: rgb(219 39 119 / var(--tw-bg-opacity));
1668
- }
1669
-
1670
1674
  .bg-red-100 {
1671
1675
  --tw-bg-opacity: 1;
1672
1676
  background-color: rgb(254 226 226 / var(--tw-bg-opacity));
1673
1677
  }
1674
1678
 
1675
- .bg-red-300 {
1679
+ .bg-red-400 {
1676
1680
  --tw-bg-opacity: 1;
1677
- background-color: rgb(252 165 165 / var(--tw-bg-opacity));
1681
+ background-color: rgb(248 113 113 / var(--tw-bg-opacity));
1678
1682
  }
1679
1683
 
1680
1684
  .bg-red-50 {
@@ -1692,6 +1696,16 @@ video {
1692
1696
  background-color: rgb(220 38 38 / var(--tw-bg-opacity));
1693
1697
  }
1694
1698
 
1699
+ .bg-slate-100 {
1700
+ --tw-bg-opacity: 1;
1701
+ background-color: rgb(241 245 249 / var(--tw-bg-opacity));
1702
+ }
1703
+
1704
+ .bg-slate-50 {
1705
+ --tw-bg-opacity: 1;
1706
+ background-color: rgb(248 250 252 / var(--tw-bg-opacity));
1707
+ }
1708
+
1695
1709
  .bg-slate-500 {
1696
1710
  --tw-bg-opacity: 1;
1697
1711
  background-color: rgb(100 116 139 / var(--tw-bg-opacity));
@@ -1716,6 +1730,11 @@ video {
1716
1730
  background-color: rgb(229 234 255 / var(--tw-bg-opacity));
1717
1731
  }
1718
1732
 
1733
+ .bg-ultramarine-200 {
1734
+ --tw-bg-opacity: 1;
1735
+ background-color: rgb(206 218 255 / var(--tw-bg-opacity));
1736
+ }
1737
+
1719
1738
  .bg-ultramarine-600 {
1720
1739
  --tw-bg-opacity: 1;
1721
1740
  background-color: rgb(24 35 255 / var(--tw-bg-opacity));
@@ -1736,9 +1755,9 @@ video {
1736
1755
  background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1737
1756
  }
1738
1757
 
1739
- .bg-yellow-300 {
1758
+ .bg-yellow-400 {
1740
1759
  --tw-bg-opacity: 1;
1741
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
1760
+ background-color: rgb(250 204 21 / var(--tw-bg-opacity));
1742
1761
  }
1743
1762
 
1744
1763
  .bg-opacity-40 {
@@ -1749,6 +1768,10 @@ video {
1749
1768
  --tw-bg-opacity: 0.6;
1750
1769
  }
1751
1770
 
1771
+ .p-0 {
1772
+ padding: 0px;
1773
+ }
1774
+
1752
1775
  .p-1 {
1753
1776
  padding: 0.25rem;
1754
1777
  }
@@ -1910,10 +1933,6 @@ video {
1910
1933
  padding-top: 1rem;
1911
1934
  }
1912
1935
 
1913
- .pt-\[1px\] {
1914
- padding-top: 1px;
1915
- }
1916
-
1917
1936
  .text-left {
1918
1937
  text-align: left;
1919
1938
  }
@@ -2398,6 +2417,11 @@ video {
2398
2417
  border-color: rgb(209 213 219 / var(--tw-border-opacity));
2399
2418
  }
2400
2419
 
2420
+ .hover\:border-slate-300:hover {
2421
+ --tw-border-opacity: 1;
2422
+ border-color: rgb(203 213 225 / var(--tw-border-opacity));
2423
+ }
2424
+
2401
2425
  .hover\:border-l-blue-600:hover {
2402
2426
  --tw-border-opacity: 1;
2403
2427
  border-left-color: rgb(37 99 235 / var(--tw-border-opacity));
@@ -2463,26 +2487,11 @@ video {
2463
2487
  background-color: rgb(220 252 231 / var(--tw-bg-opacity));
2464
2488
  }
2465
2489
 
2466
- .hover\:bg-green-300:hover {
2467
- --tw-bg-opacity: 1;
2468
- background-color: rgb(134 239 172 / var(--tw-bg-opacity));
2469
- }
2470
-
2471
2490
  .hover\:bg-green-500:hover {
2472
2491
  --tw-bg-opacity: 1;
2473
2492
  background-color: rgb(34 197 94 / var(--tw-bg-opacity));
2474
2493
  }
2475
2494
 
2476
- .hover\:bg-green-600:hover {
2477
- --tw-bg-opacity: 1;
2478
- background-color: rgb(22 163 74 / var(--tw-bg-opacity));
2479
- }
2480
-
2481
- .hover\:bg-green-700:hover {
2482
- --tw-bg-opacity: 1;
2483
- background-color: rgb(21 128 61 / var(--tw-bg-opacity));
2484
- }
2485
-
2486
2495
  .hover\:bg-pink-100:hover {
2487
2496
  --tw-bg-opacity: 1;
2488
2497
  background-color: rgb(252 231 243 / var(--tw-bg-opacity));
@@ -2538,6 +2547,11 @@ video {
2538
2547
  background-color: rgb(206 218 255 / var(--tw-bg-opacity));
2539
2548
  }
2540
2549
 
2550
+ .hover\:bg-ultramarine-50:hover {
2551
+ --tw-bg-opacity: 1;
2552
+ background-color: rgb(241 245 255 / var(--tw-bg-opacity));
2553
+ }
2554
+
2541
2555
  .hover\:bg-ultramarine-500:hover {
2542
2556
  --tw-bg-opacity: 1;
2543
2557
  background-color: rgb(63 83 255 / var(--tw-bg-opacity));
@@ -2588,6 +2602,12 @@ video {
2588
2602
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2589
2603
  }
2590
2604
 
2605
+ .hover\:shadow-sm:hover {
2606
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
2607
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
2608
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2609
+ }
2610
+
2591
2611
  .focus\:z-10:focus {
2592
2612
  z-index: 10;
2593
2613
  }
@@ -2663,11 +2683,6 @@ video {
2663
2683
  --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity));
2664
2684
  }
2665
2685
 
2666
- .focus\:ring-green-500:focus {
2667
- --tw-ring-opacity: 1;
2668
- --tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity));
2669
- }
2670
-
2671
2686
  .focus\:ring-red-500:focus {
2672
2687
  --tw-ring-opacity: 1;
2673
2688
  --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity));
@@ -2923,6 +2938,10 @@ video {
2923
2938
  gap: 0.75rem;
2924
2939
  }
2925
2940
 
2941
+ .md\:gap-6 {
2942
+ gap: 1.5rem;
2943
+ }
2944
+
2926
2945
  .md\:px-0 {
2927
2946
  padding-left: 0px;
2928
2947
  padding-right: 0px;
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ const { Trie } = require('../models/trie');
4
+
5
+ const QUERY_SELECTORS = [
6
+ '$eq',
7
+ '$ne',
8
+ '$gt',
9
+ '$gte',
10
+ '$lt',
11
+ '$lte',
12
+ '$in',
13
+ '$nin',
14
+ '$exists',
15
+ '$regex',
16
+ '$options',
17
+ '$text',
18
+ '$search',
19
+ '$and',
20
+ '$or',
21
+ '$nor',
22
+ '$not',
23
+ '$elemMatch',
24
+ '$size',
25
+ '$all',
26
+ '$type',
27
+ '$expr',
28
+ '$jsonSchema',
29
+ '$mod'
30
+ ];
31
+
32
+ const VALUE_HELPERS = [
33
+ 'objectIdRange',
34
+ 'ObjectId',
35
+ 'Date',
36
+ 'Math'
37
+ ];
38
+
39
+ // Helpers that are function calls and should have () added
40
+ const FUNCTION_HELPERS = new Set([
41
+ 'objectIdRange',
42
+ 'ObjectId',
43
+ 'Date'
44
+ ]);
45
+
46
+ function buildAutocompleteTrie(schemaPaths) {
47
+ const trie = new Trie();
48
+ // Query operators can appear as field names in nested objects, so add with both roles
49
+ trie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
50
+ trie.bulkInsert(QUERY_SELECTORS, 5, 'fieldName');
51
+ trie.bulkInsert(VALUE_HELPERS, 5, 'value');
52
+
53
+ if (Array.isArray(schemaPaths) && schemaPaths.length > 0) {
54
+ const paths = schemaPaths
55
+ .map(path => path?.path)
56
+ .filter(path => typeof path === 'string' && path.length > 0);
57
+ for (const path of schemaPaths) {
58
+ if (path.schema) {
59
+ paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
60
+ }
61
+ }
62
+ trie.bulkInsert(paths, 10, 'fieldName');
63
+ }
64
+
65
+ return trie;
66
+ }
67
+
68
+ function getAutocompleteContext(searchText, cursorPos) {
69
+ const before = searchText.slice(0, cursorPos);
70
+
71
+ // Check if we're in a field name context (after { or ,)
72
+ // This takes precedence over value context to handle cases like { _id: { $gt
73
+ const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
74
+ if (fieldMatch) {
75
+ const token = fieldMatch[1];
76
+ return {
77
+ token,
78
+ role: 'fieldName',
79
+ startPos: cursorPos - token.length
80
+ };
81
+ }
82
+
83
+ // Check if we're in a value context (after a colon)
84
+ // Match the last colon followed by optional whitespace and capture everything after
85
+ const valueMatch = before.match(/:\s*([^\s,\}\]:]*)$/);
86
+ if (valueMatch) {
87
+ const token = valueMatch[1];
88
+ return {
89
+ token,
90
+ role: 'value',
91
+ startPos: cursorPos - token.length
92
+ };
93
+ }
94
+
95
+ return null;
96
+ }
97
+
98
+ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
99
+ const context = getAutocompleteContext(searchText, cursorPos);
100
+
101
+ if (!context) {
102
+ return [];
103
+ }
104
+
105
+ const { token, role } = context;
106
+
107
+ // Extract the actual term without quotes
108
+ const leadingQuoteMatch = token.match(/^["']/);
109
+ const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
110
+ ? token[token.length - 1]
111
+ : '';
112
+ const term = token
113
+ .replace(/^["']/, '')
114
+ .replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
115
+ .trim();
116
+
117
+ if (!term) {
118
+ return [];
119
+ }
120
+
121
+ const primarySuggestions = trie.getSuggestions(term, 10, role);
122
+ const suggestionsSet = new Set(primarySuggestions);
123
+
124
+ // Add schema path suggestions for field names
125
+ if (role === 'fieldName' && Array.isArray(schemaPaths) && schemaPaths.length > 0) {
126
+ for (const schemaPath of schemaPaths) {
127
+ const path = schemaPath?.path;
128
+ if (
129
+ typeof path === 'string' &&
130
+ path.startsWith(`${term}.`) &&
131
+ !suggestionsSet.has(path)
132
+ ) {
133
+ suggestionsSet.add(path);
134
+ if (suggestionsSet.size >= 10) {
135
+ break;
136
+ }
137
+ }
138
+ }
139
+ }
140
+
141
+ let suggestions = Array.from(suggestionsSet);
142
+
143
+ // Preserve quotes if present
144
+ if (leadingQuoteMatch) {
145
+ const leadingQuote = leadingQuoteMatch[0];
146
+ suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
147
+ }
148
+ if (trailingQuoteMatch) {
149
+ suggestions = suggestions.map(suggestion =>
150
+ suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
151
+ );
152
+ }
153
+
154
+ return suggestions;
155
+ }
156
+
157
+ function applySuggestion(searchText, cursorPos, suggestion) {
158
+ const before = searchText.slice(0, cursorPos);
159
+ const after = searchText.slice(cursorPos);
160
+
161
+ // Check if we're in a value context
162
+ const valueMatch = before.match(/:\s*([^\s,\}\]:]*)$/);
163
+ if (valueMatch) {
164
+ const token = valueMatch[1];
165
+ const start = cursorPos - token.length;
166
+ let replacement = suggestion;
167
+ let cursorOffset = replacement.length;
168
+
169
+ // Add parentheses for function helpers and position cursor inside
170
+ if (FUNCTION_HELPERS.has(suggestion)) {
171
+ replacement = `${suggestion}()`;
172
+ cursorOffset = suggestion.length + 1; // Position cursor between ()
173
+ }
174
+
175
+ return {
176
+ text: searchText.slice(0, start) + replacement + after,
177
+ newCursorPos: start + cursorOffset
178
+ };
179
+ }
180
+
181
+ // Check if we're in a field name context
182
+ const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
183
+ if (fieldMatch) {
184
+ const token = fieldMatch[1];
185
+ const start = cursorPos - token.length;
186
+ let replacement = suggestion;
187
+
188
+ const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
189
+ const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
190
+ const colonNeeded = !/^\s*:/.test(after);
191
+
192
+ // If suggestion already has quotes, use it as-is
193
+ const suggestionHasQuotes = (suggestion.startsWith('"') || suggestion.startsWith('\'')) &&
194
+ (suggestion.endsWith('"') || suggestion.endsWith('\''));
195
+ if (suggestionHasQuotes) {
196
+ replacement = suggestion;
197
+ } else {
198
+ if (leadingQuote && !replacement.startsWith(leadingQuote)) {
199
+ replacement = `${leadingQuote}${replacement}`;
200
+ }
201
+ if (trailingQuote && !replacement.endsWith(trailingQuote)) {
202
+ replacement = `${replacement}${trailingQuote}`;
203
+ }
204
+ }
205
+
206
+ // Only insert : if we know the user isn't entering in a nested path
207
+ // If suggestion has full quotes or user typed both quotes, add colon
208
+ if (colonNeeded && (suggestionHasQuotes || !leadingQuote || trailingQuote)) {
209
+ replacement = `${replacement}:`;
210
+ }
211
+
212
+ return {
213
+ text: searchText.slice(0, start) + replacement + after,
214
+ newCursorPos: start + replacement.length
215
+ };
216
+ }
217
+
218
+ return null;
219
+ }
220
+
221
+ module.exports = {
222
+ buildAutocompleteTrie,
223
+ getAutocompleteContext,
224
+ getAutocompleteSuggestions,
225
+ applySuggestion,
226
+ QUERY_SELECTORS,
227
+ VALUE_HELPERS,
228
+ FUNCTION_HELPERS
229
+ };
@@ -1,26 +1,28 @@
1
- <div class="relative border rounded bg-gray-100 text-black text-sm overflow-hidden">
2
- <div class="flex border-b pt-[1px] text-xs font-medium bg-gray-200">
1
+ <div class="relative border rounded bg-white my-1 text-black text-sm overflow-hidden">
2
+ <div class="flex border-b py-1 pl-1 text-xs font-medium bg-white">
3
3
  <button
4
- class="px-3 py-1 border-r border-gray-300 hover:bg-green-300"
5
- :class="{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}"
4
+ class="px-4 py-1 border-r border-gray-300 text-gray-700 font-semibold transition-colors duration-200 focus:outline-none"
5
+ :class="[
6
+ 'rounded-l-md',
7
+ activeTab === 'code'
8
+ ? 'bg-gray-700 text-white shadow'
9
+ : 'bg-gray-100 hover:bg-gray-200 text-gray-600'
10
+ ]"
6
11
  @click="activeTab = 'code'">
7
12
  Code
8
13
  </button>
9
14
  <button
10
- class="px-3 py-1 hover:bg-green-300"
11
- :class="{'bg-green-300': activeTab === 'output'}"
15
+ class="px-4 py-1 text-gray-700 font-semibold transition-colors duration-200 focus:outline-none"
16
+ :class="[
17
+ 'rounded-r-md',
18
+ activeTab === 'output'
19
+ ? 'bg-gray-700 text-white shadow'
20
+ : 'bg-gray-100 hover:bg-gray-200 text-gray-600'
21
+ ]"
12
22
  @click="activeTab = 'output'">
13
23
  Output
14
24
  </button>
15
25
  <div class="ml-auto mr-1 flex">
16
- <button
17
- v-if="activeTab === 'output'"
18
- class="px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center"
19
- @click="copyOutput">
20
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
21
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
22
- </svg>
23
- </button>
24
26
  <button
25
27
  v-if="activeTab === 'output'"
26
28
  class="px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center"
@@ -39,9 +41,9 @@
39
41
  </button>
40
42
  <async-button
41
43
  v-if="!isEditing"
42
- class="px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400"
44
+ class="px-2 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white border-none rounded cursor-pointer transition-colors disabled:bg-gray-400"
43
45
  @click="executeScript">
44
- Execute
46
+ Run
45
47
  </async-button>
46
48
  <div class="relative ml-1" ref="dropdown">
47
49
  <button
@@ -59,6 +61,11 @@
59
61
  @click="openCreateDashboardModal(); showDropdown = false">
60
62
  Create Dashboard
61
63
  </button>
64
+ <button
65
+ class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
66
+ @click="copyOutput(); showDropdown = false">
67
+ Copy Output As Text
68
+ </button>
62
69
  <button
63
70
  v-if="canOverwriteDashboard"
64
71
  class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
@@ -75,25 +82,25 @@
75
82
  </div>
76
83
  </div>
77
84
 
78
- <div class="p-3 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto" v-show="activeTab === 'code'">
85
+ <div class="p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto" v-show="activeTab === 'code'">
79
86
  <div v-if="isEditing" class="flex flex-col space-y-2">
80
87
  <div class="border border-gray-200">
81
88
  <textarea ref="scriptEditor" class="w-full h-[45vh]" @input="handleScriptInput"></textarea>
82
89
  </div>
83
- <div class="flex justify-end gap-2">
90
+ <div class="flex justify-end gap-2 pb-2">
84
91
  <button
85
92
  class="px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors"
86
93
  @click.stop="cancelEditing">
87
94
  Cancel
88
95
  </button>
89
96
  <async-button
90
- class="px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400"
97
+ class="px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400"
91
98
  @click="executeScript">
92
99
  Execute
93
100
  </async-button>
94
101
  </div>
95
102
  </div>
96
- <pre v-else class="whitespace-pre-wrap"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
103
+ <pre v-else class="whitespace-pre-wrap !my-0 !bg-zinc-50"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
97
104
  </div>
98
105
 
99
106
  <div class="p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative" v-show="activeTab === 'output'">