@necrolab/dashboard 0.5.15 → 0.5.17

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 (137) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +70 -566
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +61 -74
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +13 -21
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -22
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +295 -0
  20. package/src/assets/css/main.scss +55 -139
  21. package/src/components/Auth/LoginForm.vue +7 -86
  22. package/src/components/Console/ConsoleToolbar.vue +123 -0
  23. package/src/components/Editors/Account/Account.vue +12 -12
  24. package/src/components/Editors/Account/AccountView.vue +38 -111
  25. package/src/components/Editors/Account/CreateAccount.vue +11 -61
  26. package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
  27. package/src/components/Editors/AdminFileEditor.vue +179 -0
  28. package/src/components/Editors/Profile/CreateProfile.vue +77 -150
  29. package/src/components/Editors/Profile/Profile.vue +20 -21
  30. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  31. package/src/components/Editors/Profile/ProfileView.vue +41 -116
  32. package/src/components/Editors/ProxyFileEditor.vue +86 -0
  33. package/src/components/Editors/TagLabel.vue +16 -55
  34. package/src/components/Editors/TagToggle.vue +20 -8
  35. package/src/components/Filter/Filter.vue +66 -79
  36. package/src/components/Filter/FilterPreview.vue +153 -135
  37. package/src/components/Filter/PriceSortToggle.vue +36 -43
  38. package/src/components/Table/Header.vue +1 -1
  39. package/src/components/Table/Table.vue +45 -51
  40. package/src/components/Tasks/CheckStock.vue +7 -16
  41. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  42. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  43. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  44. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  45. package/src/components/Tasks/EventDetailRow.vue +21 -0
  46. package/src/components/Tasks/MassEdit.vue +6 -16
  47. package/src/components/Tasks/QuickSettings.vue +140 -216
  48. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  49. package/src/components/Tasks/Stats.vue +20 -39
  50. package/src/components/Tasks/Task.vue +64 -270
  51. package/src/components/Tasks/TaskLabel.vue +9 -3
  52. package/src/components/Tasks/TaskView.vue +45 -64
  53. package/src/components/Tasks/Utilities.vue +10 -44
  54. package/src/components/Tasks/ViewTask.vue +23 -107
  55. package/src/components/icons/Close.vue +2 -8
  56. package/src/components/icons/Gear.vue +8 -8
  57. package/src/components/icons/Hash.vue +5 -0
  58. package/src/components/icons/Key.vue +2 -8
  59. package/src/components/icons/Pencil.vue +2 -8
  60. package/src/components/icons/Profile.vue +2 -8
  61. package/src/components/icons/Sell.vue +2 -8
  62. package/src/components/icons/Spinner.vue +4 -7
  63. package/src/components/icons/Wildcard.vue +2 -8
  64. package/src/components/icons/index.js +3 -5
  65. package/src/components/ui/ActionButtonGroup.vue +113 -52
  66. package/src/components/ui/BalanceIndicator.vue +60 -0
  67. package/src/components/ui/EmptyState.vue +24 -0
  68. package/src/components/ui/EnableDisableToggle.vue +23 -0
  69. package/src/components/ui/FormField.vue +49 -49
  70. package/src/components/ui/IconLabel.vue +23 -0
  71. package/src/components/ui/InfoRow.vue +21 -54
  72. package/src/components/ui/Modal.vue +161 -54
  73. package/src/components/ui/Navbar.vue +63 -44
  74. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  75. package/src/components/ui/ReconnectIndicator.vue +111 -124
  76. package/src/components/ui/SectionCard.vue +6 -14
  77. package/src/components/ui/Splash.vue +2 -10
  78. package/src/components/ui/StatusBadge.vue +26 -28
  79. package/src/components/ui/TaskToggle.vue +54 -0
  80. package/src/components/ui/controls/CountryChooser.vue +29 -66
  81. package/src/components/ui/controls/EyeToggle.vue +1 -1
  82. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  83. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  84. package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
  85. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  86. package/src/composables/useCodeEditor.js +117 -0
  87. package/src/composables/useColorMapping.js +15 -0
  88. package/src/composables/useCopyToClipboard.js +1 -1
  89. package/src/composables/useDateFormatting.js +21 -0
  90. package/src/composables/useDeviceDetection.js +14 -0
  91. package/src/composables/useDropdownPosition.js +1 -4
  92. package/src/composables/useDynamicTableHeight.js +31 -0
  93. package/src/composables/useEnableDisable.js +6 -0
  94. package/src/composables/useFilterCSS.js +71 -0
  95. package/src/composables/useFormValidation.js +92 -0
  96. package/src/composables/useGetAllTags.js +9 -0
  97. package/src/composables/useIOSViewportHandling.js +76 -0
  98. package/src/composables/useNotchHandling.js +306 -0
  99. package/src/composables/useRowSelection.js +0 -3
  100. package/src/composables/useTableRender.js +23 -0
  101. package/src/composables/useTicketPricing.js +16 -0
  102. package/src/composables/useWindowDimensions.js +21 -0
  103. package/src/composables/useZoomPrevention.js +96 -0
  104. package/src/constants/tableLayout.js +14 -0
  105. package/src/libs/Filter.js +14 -20
  106. package/src/libs/panzoom.js +1 -5
  107. package/src/libs/utils/array.js +58 -0
  108. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  109. package/src/libs/utils/eventUrl.js +40 -0
  110. package/src/libs/utils/string.js +3 -0
  111. package/src/libs/utils/time.js +20 -0
  112. package/src/libs/utils/validation.js +64 -0
  113. package/src/main.js +0 -2
  114. package/src/stores/connection.js +1 -29
  115. package/src/stores/logger.js +6 -12
  116. package/src/stores/sampleData.js +1 -2
  117. package/src/stores/ui.js +80 -71
  118. package/src/utils/tableHelpers.js +1 -0
  119. package/src/views/Accounts.vue +19 -38
  120. package/src/views/Console.vue +74 -253
  121. package/src/views/Editor.vue +47 -1114
  122. package/src/views/FilterBuilder.vue +190 -461
  123. package/src/views/Login.vue +3 -28
  124. package/src/views/Profiles.vue +17 -32
  125. package/src/views/Tasks.vue +51 -38
  126. package/tailwind.config.js +82 -71
  127. package/workbox-config.cjs +47 -5
  128. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  129. package/exit +0 -209
  130. package/run +0 -177
  131. package/src/assets/css/base/color-fallbacks.scss +0 -10
  132. package/src/assets/img/background.svg.backup +0 -11
  133. package/src/components/icons/SquareCheck.vue +0 -18
  134. package/src/components/icons/SquareUncheck.vue +0 -18
  135. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  136. package/switch-branch.sh +0 -41
  137. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -1,6 +1,5 @@
1
1
  <template>
2
2
  <div>
3
- <!-- Heading -->
4
3
  <div class="page-header" style="padding-bottom: 1.25rem;">
5
4
  <div class="page-header-card">
6
5
  <img src="@/assets/img/pencil.svg" />
@@ -8,203 +7,46 @@
8
7
  </div>
9
8
  </div>
10
9
 
11
- <div class="bg-dark-400 border border-dark-650 rounded shadow-sm p-2">
12
- <!-- Admin Editor Section - Hidden when proxy editor is open -->
13
- <div v-if="ui.profile.admin" class="admin-editor-section" :class="{ 'landscape-hidden': currentProxyList }">
14
- <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
15
- <div class="flex flex-wrap items-center gap-3 w-full">
16
- <div class="w-full lg:w-64 flex flex-shrink-0 dropdown-container z-dropdown-high">
17
- <div class="relative flex-grow">
18
- <Dropdown
19
- class="bg-dark-500 border-2 border-r-0 border-dark-550 admin-file-dropdown w-full"
20
- default="Select a file"
21
- :onClick="(f) => loadFile(f)"
22
- :options="availableFiles"
23
- :allowDefault="false"
24
- :includeAdjacentButtons="true"
25
- rightAmount="right-1" />
26
- </div>
27
- <button
28
- class="refresh-button flex items-center justify-center w-10 h-10 text-white bg-dark-400 rounded-r-lg border-2 border-dark-550 border-l-0"
29
- @click="loadAvailableFiles()"
30
- title="Refresh file list">
31
- <ReloadIcon class="refresh-icon" />
32
- </button>
33
- </div>
34
- <!-- Admin editor buttons moved here -->
35
- <div v-if="textEditorVisible" class="flex gap-1.5 ml-auto">
36
- <button
37
- class="button-default bg-dark-400"
38
- @click="format()"
39
- v-if="isJsonFile()"
40
- title="Format JSON">
41
- <svg
42
- xmlns="http://www.w3.org/2000/svg"
43
- width="16"
44
- height="16"
45
- viewBox="0 0 24 24"
46
- fill="none"
47
- stroke="currentColor"
48
- stroke-width="2"
49
- stroke-linecap="round"
50
- stroke-linejoin="round">
51
- <path d="M21 10H7" />
52
- <path d="M21 6H3" />
53
- <path d="M21 14H3" />
54
- <path d="M21 18H7" />
55
- </svg>
56
- </button>
57
- <button class="button-default bg-dark-400" @click="saveAll" title="Save File">
58
- <svg
59
- xmlns="http://www.w3.org/2000/svg"
60
- width="16"
61
- height="16"
62
- viewBox="0 0 24 24"
63
- fill="none"
64
- stroke="currentColor"
65
- stroke-width="2"
66
- stroke-linecap="round"
67
- stroke-linejoin="round">
68
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
69
- <polyline points="17 21 17 13 7 13 7 21" />
70
- <polyline points="7 3 7 8 15 8" />
71
- </svg>
72
- </button>
73
- <button class="button-default bg-dark-400" @click="closeFile" title="Close File">
74
- <svg
75
- xmlns="http://www.w3.org/2000/svg"
76
- width="16"
77
- height="16"
78
- viewBox="0 0 24 24"
79
- fill="none"
80
- stroke="currentColor"
81
- stroke-width="2"
82
- stroke-linecap="round"
83
- stroke-linejoin="round">
84
- <line x1="18" y1="6" x2="6" y2="18"></line>
85
- <line x1="6" y1="6" x2="18" y2="18"></line>
86
- </svg>
87
- </button>
88
- </div>
89
- </div>
10
+ <div class="bg-dark-400 border-2 rounded-lg shadow-sm p-2" style="border-color: var(--color-border);">
11
+ <AdminFileEditor
12
+ v-if="ui.profile.admin"
13
+ :availableFiles="availableFiles"
14
+ :currentFile="currentFile"
15
+ v-model:content="currentContent"
16
+ :isHidden="!!currentProxyList"
17
+ :logger="ui.logger"
18
+ @load-file="loadFile"
19
+ @refresh-files="loadAvailableFiles"
20
+ @save="saveAll"
21
+ @close="closeFile"
22
+ @format="format" />
90
23
 
91
- <transition name="fade">
92
- <div v-if="textEditorVisible" class="my-3 relative">
93
- <div class="pb-4">
94
- <!-- Syntax Highlighting Editor -->
95
- <div class="editor-container" :class="{ 'has-error': errorMessage }">
96
- <div class="editor-wrapper">
97
- <pre ref="codeDisplay" class="language-json code-highlight"></pre>
98
- <textarea
99
- ref="codeEditor"
100
- v-model="currentContent"
101
- class="code-editor"
102
- spellcheck="false"
103
- @scroll="syncScroll"
104
- @input="highlightCode"
105
- @keydown.tab.prevent="handleTab"></textarea>
106
- </div>
107
- </div>
108
- </div>
109
-
110
- <div v-if="errorMessage" class="error-container">
111
- <div class="error-icon">
112
- <svg
113
- width="16"
114
- height="16"
115
- viewBox="0 0 24 24"
116
- fill="none"
117
- stroke="currentColor"
118
- stroke-width="2">
119
- <circle cx="12" cy="12" r="10" />
120
- <line x1="15" y1="9" x2="9" y2="15" />
121
- <line x1="9" y1="9" x2="15" y2="15" />
122
- </svg>
123
- </div>
124
- <div class="error-content">
125
- <div class="error-title">JSON Syntax Error</div>
126
- <div class="error-text">{{ errorMessage }}</div>
127
- </div>
128
- </div>
129
- </div>
130
- </transition>
131
- </div>
132
-
133
- <!-- Section Divider - Show when both editors are closed -->
134
24
  <div v-if="ui.profile.admin && !textEditorVisible && !currentProxyList" class="section-divider" />
135
25
 
136
- <!-- Proxy Editor Section - Hidden when admin editor is open -->
137
- <div class="proxy-editor-section" :class="{ 'landscape-hidden': textEditorVisible }">
138
- <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Proxy Editor</h5>
139
- <div class="flex flex-wrap items-center gap-3 w-full mb-1">
140
- <div class="w-full lg:w-64 flex flex-shrink-0 dropdown-container z-dropdown">
141
- <div class="relative flex-grow">
142
- <Dropdown
143
- class="bg-dark-500 border-2 border-dark-550 proxy-file-dropdown w-full"
144
- :default="ui.profile.proxyList?.checkout || proxyLists[0]"
145
- :onClick="loadProxies"
146
- :options="proxyLists"
147
- :allowDefault="false"
148
- :includeAdjacentButtons="true"
149
- rightAmount="right-1" />
150
- </div>
151
- </div>
152
- <!-- Proxy save button moved here -->
153
- <div v-if="currentProxyList" class="flex gap-1.5 ml-auto">
154
- <button class="button-default bg-dark-400" @click="saveProxies" title="Save Proxies">
155
- <svg
156
- xmlns="http://www.w3.org/2000/svg"
157
- width="16"
158
- height="16"
159
- viewBox="0 0 24 24"
160
- fill="none"
161
- stroke="currentColor"
162
- stroke-width="2"
163
- stroke-linecap="round"
164
- stroke-linejoin="round">
165
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
166
- <polyline points="17 21 17 13 7 13 7 21" />
167
- <polyline points="7 3 7 8 15 8" />
168
- </svg>
169
- </button>
170
- <button class="button-default bg-dark-400" @click="closeProxyFile" title="Close File">
171
- <svg
172
- xmlns="http://www.w3.org/2000/svg"
173
- width="16"
174
- height="16"
175
- viewBox="0 0 24 24"
176
- fill="none"
177
- stroke="currentColor"
178
- stroke-width="2"
179
- stroke-linecap="round"
180
- stroke-linejoin="round">
181
- <line x1="18" y1="6" x2="6" y2="18"></line>
182
- <line x1="6" y1="6" x2="18" y2="18"></line>
183
- </svg>
184
- </button>
185
- </div>
186
- </div>
187
- <!-- Textarea -->
188
- <transition name="fade">
189
- <div v-if="currentProxyList" class="relative my-3">
190
- <div class="pb-4">
191
- <div class="proxy-editor-container table-component">
192
- <textarea v-model="proxyContent" class="proxy-editor" spellcheck="false"></textarea>
193
- </div>
194
- </div>
195
- </div>
196
- </transition>
197
- </div>
26
+ <ProxyFileEditor
27
+ :proxyLists="proxyLists"
28
+ :currentProxyList="currentProxyList"
29
+ v-model:proxyContent="proxyContent"
30
+ :defaultProxyList="ui.profile.proxyList?.checkout || proxyLists[0]"
31
+ :isHidden="textEditorVisible"
32
+ @load-proxies="loadProxies"
33
+ @save-proxies="saveProxies"
34
+ @close-proxy="closeProxyFile" />
198
35
  </div>
199
36
  </div>
200
37
  </template>
38
+
201
39
  <script setup>
202
- import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
203
- import { DownIcon, ReloadIcon, EditIcon } from "@/components/icons";
40
+ import { ref } from "vue";
204
41
  import { useUIStore } from "@/stores/ui";
205
- import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
206
- import { getProxyLists, getProxyFile } from "@/stores/requests";
42
+ import { getProxyLists, getProxyFile, getQuickConfig, sendQuickConfig } from "@/stores/requests";
207
43
  import { DEBUG } from "@/utils/debug";
44
+ import { useCodeEditor } from "@/composables/useCodeEditor";
45
+ import AdminFileEditor from "@/components/Editors/AdminFileEditor.vue";
46
+ import ProxyFileEditor from "@/components/Editors/ProxyFileEditor.vue";
47
+
48
+ const ui = useUIStore();
49
+ const { formatJSON } = useCodeEditor(ui.logger);
208
50
 
209
51
  const loadFromApi = async (path) => {
210
52
  const res = await fetch(path);
@@ -213,158 +55,29 @@ const loadFromApi = async (path) => {
213
55
 
214
56
  const availableFiles = ref([]);
215
57
  if (DEBUG) availableFiles.value.push("used-codes.json", "proxies.txt", "toMonitor.txt", "test.txt");
58
+
216
59
  const currentFile = ref("");
217
60
  const textEditorVisible = ref(false);
218
61
  const currentContent = ref("");
219
62
  const proxyContent = ref("");
220
63
  const proxyLists = ref(["loading..."]);
221
64
  const currentProxyList = ref("");
222
- const activeModal = computed(() => ui.activeModal);
223
- const ui = useUIStore();
224
-
225
- // References to editor elements
226
- const codeEditor = ref(null);
227
- const codeDisplay = ref(null);
228
65
 
229
66
  let previous = {
230
67
  proxyContent: { list: ui.profile.proxyList, content: "" },
231
68
  currentContent: { file: "", content: "" }
232
69
  };
233
70
 
234
- const errorMessage = computed(() => {
235
- if (!currentFile.value.endsWith(".json")) return;
236
-
237
- let err;
238
- try {
239
- JSON.parse(currentContent.value);
240
- } catch (e) {
241
- err = e;
242
- }
243
- return err?.message;
244
- });
245
-
246
71
  const loadAvailableFiles = async () => (availableFiles.value = await loadFromApi("/api/json-files"));
247
72
 
248
73
  const isJsonFile = () => currentFile.value.endsWith(".json");
249
74
 
250
- // Function to highlight code using Prism
251
- const highlightCode = () => {
252
- if (!codeDisplay.value || !codeEditor.value) return;
253
-
254
- // Get the language based on file extension
255
- let language = "javascript"; // Default language
256
-
257
- if (currentFile.value) {
258
- if (currentFile.value.endsWith(".json")) {
259
- language = "json";
260
- } else if (currentFile.value.endsWith(".js")) {
261
- language = "javascript";
262
- } else if (currentFile.value.endsWith(".txt") || currentFile.value.endsWith(".csv")) {
263
- language = "text";
264
- // Plain text doesn't need highlighting
265
- codeDisplay.value.textContent = currentContent.value || "";
266
- codeDisplay.value.className = "language-text code-highlight";
267
- return;
268
- }
269
- }
270
-
271
- // Ensure Prism is available
272
- if (typeof Prism === "undefined") {
273
- console.error("Prism is not loaded");
274
- return;
275
- }
276
-
277
- // Use requestAnimationFrame for smoother updates
278
- requestAnimationFrame(() => {
279
- try {
280
- // Update the pre element with highlighted HTML
281
- const highlighted = Prism.highlight(currentContent.value || "", Prism.languages[language], language);
282
- codeDisplay.value.innerHTML = highlighted;
283
- codeDisplay.value.className = `language-${language} code-highlight`;
284
- } catch (e) {
285
- console.error("Highlight error:", e);
286
- // Fallback to plain text if highlighting fails
287
- codeDisplay.value.textContent = currentContent.value || "";
288
- }
289
-
290
- // Ensure scroll positions are synced after highlighting
291
- syncScroll();
292
- });
293
- };
294
-
295
- // Function to sync scrolling between textarea and highlighted code
296
- const syncScroll = () => {
297
- if (!codeDisplay.value || !codeEditor.value) return;
298
-
299
- // Synchronize scrolling between the textarea and the highlighted code
300
- requestAnimationFrame(() => {
301
- codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
302
- codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
303
- });
304
- };
305
-
306
- // Function to handle tab key press in the editor
307
- const handleTab = (e) => {
308
- const textarea = codeEditor.value;
309
- const start = textarea.selectionStart;
310
- const end = textarea.selectionEnd;
311
-
312
- // Insert 4 spaces at cursor position
313
- const spaces = " ";
314
- currentContent.value = currentContent.value.substring(0, start) + spaces + currentContent.value.substring(end);
315
-
316
- // Move cursor position after the inserted tab
317
- nextTick(() => {
318
- textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
319
- highlightCode();
320
- });
321
- };
322
-
323
- const format = () => {
324
- try {
325
- if (!isJsonFile()) return;
326
-
327
- const formatted = JSON.stringify(JSON.parse(currentContent.value), null, 4);
328
- currentContent.value = formatted;
329
-
330
- // Apply syntax highlighting after formatting
331
- nextTick(() => {
332
- highlightCode();
333
- });
334
- } catch (e) {
335
- ui.logger.Error("Could not format JSON", e);
336
- }
337
- };
338
-
339
- // Watch for content changes to apply syntax highlighting
340
- watch(currentContent, () => {
341
- nextTick(() => {
342
- highlightCode();
343
- });
344
- });
345
-
346
75
  const loadFile = async (f) => {
347
76
  currentFile.value = f;
348
77
  if (DEBUG) {
349
78
  textEditorVisible.value = true;
350
79
  currentProxyList.value = "";
351
80
  currentContent.value = new Array(100).fill(0, 0, 100).join("\n");
352
-
353
- // Update pre element class based on file type
354
- if (codeDisplay.value) {
355
- if (f.endsWith(".json")) {
356
- codeDisplay.value.className = "language-json";
357
- } else if (f.endsWith(".js")) {
358
- codeDisplay.value.className = "language-javascript";
359
- } else {
360
- codeDisplay.value.className = "language-text";
361
- }
362
- }
363
-
364
- // Apply syntax highlighting after content is set
365
- nextTick(() => {
366
- highlightCode();
367
- });
368
81
  return;
369
82
  }
370
83
 
@@ -374,22 +87,6 @@ const loadFile = async (f) => {
374
87
  currentProxyList.value = "";
375
88
  currentContent.value = text;
376
89
  previous.currentContent = { file: currentFile.value, content: text };
377
-
378
- // Update pre element class based on file type
379
- if (codeDisplay.value) {
380
- if (f.endsWith(".json")) {
381
- codeDisplay.value.className = "language-json";
382
- } else if (f.endsWith(".js")) {
383
- codeDisplay.value.className = "language-javascript";
384
- } else {
385
- codeDisplay.value.className = "language-text";
386
- }
387
- }
388
-
389
- // Apply syntax highlighting after content is loaded
390
- nextTick(() => {
391
- highlightCode();
392
- });
393
90
  };
394
91
 
395
92
  const saveDataToAPI = async (data, endpoint, msg, json = true) => {
@@ -405,7 +102,7 @@ const saveDataToAPI = async (data, endpoint, msg, json = true) => {
405
102
  if (json) {
406
103
  try {
407
104
  j = await res.json();
408
- } catch (e) {
105
+ } catch {
409
106
  ui.showError("Error saving proxies");
410
107
  return;
411
108
  }
@@ -413,7 +110,7 @@ const saveDataToAPI = async (data, endpoint, msg, json = true) => {
413
110
  } else {
414
111
  try {
415
112
  j = await res.text();
416
- } catch (e) {
113
+ } catch {
417
114
  ui.showError("Error saving proxies");
418
115
  return;
419
116
  }
@@ -434,12 +131,21 @@ const saveFile = async () => {
434
131
  };
435
132
  };
436
133
 
134
+ const saveQuickConfig = async () => {
135
+ const content = await getQuickConfig();
136
+ await sendQuickConfig(content);
137
+ };
138
+
437
139
  const saveAll = async () => {
438
140
  if (DEBUG) return;
439
141
  if (currentFile.value && currentContent.value) await saveFile();
440
142
  await saveQuickConfig();
441
143
  };
442
144
 
145
+ const format = () => {
146
+ formatJSON(ref(currentContent), currentFile.value, () => {});
147
+ };
148
+
443
149
  const closeFile = () => {
444
150
  currentFile.value = "";
445
151
  currentContent.value = "";
@@ -474,7 +180,6 @@ const loadProxies = async (list) => {
474
180
  };
475
181
 
476
182
  const saveProxies = async () => {
477
- // if (!DEBUG) return;
478
183
  if (previous.proxyContent.content === proxyContent.value) ui.logger.Info("proxies did not change");
479
184
  else {
480
185
  await saveDataToAPI(
@@ -494,790 +199,18 @@ const loadProxyLists = async () => {
494
199
  else ui.showError("Could not load proxylists");
495
200
  };
496
201
 
497
- // Initialize syntax highlighting
498
- onMounted(() => {
499
- // Apply highlighting when the component is mounted
500
- nextTick(() => {
501
- highlightCode();
502
-
503
- // Ensure scroll synchronization on initial load
504
- if (codeEditor.value && codeDisplay.value) {
505
- syncScroll();
506
- }
507
- });
508
-
509
- // iOS-specific editor fixes with cursor tracking
510
- if (
511
- /iPad|iPhone|iPod/.test(navigator.platform) ||
512
- (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform))
513
- ) {
514
- const trackCursor = (editor) => {
515
- const scrollToCursor = () => {
516
- setTimeout(() => {
517
- const container = editor.closest(".editor-container") || editor.closest(".proxy-editor-container");
518
- if (container) {
519
- // Get cursor position and scroll to it
520
- const lines = editor.value.substring(0, editor.selectionStart).split("\n");
521
- const lineHeight = 24; // Approximate line height
522
- const cursorY = lines.length * lineHeight;
523
- const containerHeight = container.clientHeight;
524
-
525
- // Scroll to keep cursor in view
526
- if (cursorY > container.scrollTop + containerHeight - 60) {
527
- container.scrollTop = cursorY - containerHeight + 60;
528
- }
529
- }
530
- }, 100);
531
- };
532
-
533
- editor.addEventListener("focus", scrollToCursor);
534
- editor.addEventListener("input", scrollToCursor);
535
- editor.addEventListener("click", scrollToCursor);
536
- editor.addEventListener("keyup", scrollToCursor);
537
- };
538
-
539
- nextTick(() => {
540
- if (codeEditor.value) {
541
- trackCursor(codeEditor.value);
542
- }
543
- const proxyEditor = document.querySelector(".proxy-editor");
544
- if (proxyEditor) {
545
- trackCursor(proxyEditor);
546
- }
547
- });
548
- }
549
-
550
- // Watch for editor visibility changes
551
- watch(textEditorVisible, (newValue) => {
552
- if (newValue) {
553
- nextTick(() => {
554
- highlightCode();
555
-
556
- // Focus the editor when it becomes visible
557
- if (codeEditor.value) {
558
- codeEditor.value.focus();
559
- }
560
- });
561
- }
562
- });
563
- });
564
-
565
- // No cleanup needed - global iOS handling takes care of everything
566
-
567
202
  if (ui.profile.admin && !DEBUG) loadAvailableFiles();
568
203
  loadProxyLists();
569
204
  </script>
570
- <style lang="scss" scoped>
571
- /* ==========================================================================
572
- Z-INDEX MANAGEMENT
573
- ========================================================================== */
574
-
575
- .z-dropdown-high {
576
- position: relative;
577
- z-index: 200;
578
- }
579
-
580
- .z-dropdown-high .dropdown-menu,
581
- .z-dropdown-high .option-list {
582
- z-index: 250;
583
- }
584
-
585
- .z-dropdown {
586
- position: relative;
587
- z-index: 100;
588
- }
589
-
590
- .z-dropdown .dropdown-menu,
591
- .z-dropdown .option-list {
592
- z-index: 150;
593
- }
594
-
595
- /* ==========================================================================
596
- ADMIN EDITOR SECTION CONDITIONAL VISIBILITY
597
- ========================================================================== */
598
-
599
- /* Hide admin editor when proxy editor is open - ALL SCREEN SIZES */
600
- .admin-editor-section.landscape-hidden {
601
- display: none !important;
602
- }
603
-
604
- /* ==========================================================================
605
- PROXY EDITOR SECTION CONDITIONAL VISIBILITY
606
- ========================================================================== */
607
-
608
- /* Hide proxy editor when admin editor is open - ALL SCREEN SIZES */
609
- .proxy-editor-section.landscape-hidden {
610
- display: none !important;
611
- }
612
-
613
- /* ==========================================================================
614
- SECTION HEADERS
615
- ========================================================================== */
616
-
617
- /* ==========================================================================
618
- ADMIN FILE DROPDOWN STYLING
619
- ========================================================================== */
620
-
621
- .admin-file-dropdown {
622
- height: 2.5rem !important;
623
- border-top-right-radius: 0 !important;
624
- border-bottom-right-radius: 0 !important;
625
- border-top-left-radius: 0.5rem !important;
626
- border-bottom-left-radius: 0.5rem !important;
627
- }
628
-
629
- .proxy-file-dropdown {
630
- height: 2.5rem !important;
631
- border-radius: 0.5rem !important;
632
- }
633
-
634
- .refresh-button {
635
- height: 2.5rem !important;
636
- transition: all 0.2s ease;
637
-
638
- &:hover {
639
- .refresh-icon {
640
- transform: rotate(180deg);
641
- }
642
- }
643
- }
644
-
645
- .refresh-icon {
646
- transition: transform 0.5s ease;
647
- }
648
-
649
- /* ==========================================================================
650
- EDITOR CONTAINERS
651
- ========================================================================== */
652
-
653
- .editor-container,
654
- .proxy-editor-container {
655
- position: relative;
656
- overflow: hidden;
657
- height: 400px;
658
- max-height: 60vh;
659
- border-radius: 8px;
660
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
661
- background-color: oklch(0.19 0 0);
662
- transition: height 0.3s ease;
663
- }
664
-
665
- .proxy-editor-container {
666
- /* Ensure proxy editor takes full container space */
667
- display: flex;
668
- flex-direction: column;
669
- }
670
-
671
- .proxy-editor-container .pb-4 {
672
- /* Remove bottom padding to maximize space */
673
- padding-bottom: 0 !important;
674
- flex: 1;
675
- display: flex;
676
- flex-direction: column;
677
- }
678
-
679
- .proxy-editor-container .table-component {
680
- /* Make proxy editor container take full height */
681
- flex: 1;
682
- display: flex;
683
- flex-direction: column;
684
- }
685
-
686
- .editor-wrapper {
687
- position: relative;
688
- width: 100%;
689
- height: 100%;
690
- overflow: hidden;
691
- border-radius: 8px;
692
- }
693
-
694
- /* ==========================================================================
695
- EDITOR TEXTAREAS
696
- ========================================================================== */
697
-
698
- .code-editor,
699
- .proxy-editor {
700
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
701
- font-size: 14px;
702
- line-height: 1.6;
703
- tab-size: 4;
704
- background-color: transparent;
705
- border: none;
706
- outline: none;
707
- resize: none;
708
- width: 100%;
709
- height: 100%;
710
- overflow: auto;
711
- -webkit-overflow-scrolling: touch;
712
- padding: 12px;
713
- margin: 0;
714
- box-sizing: border-box;
715
- }
716
-
717
- .code-editor {
718
- /* Make textarea text transparent so syntax highlighting shows through */
719
- color: transparent !important;
720
- z-index: 2;
721
- position: relative;
722
- caret-color: #ffffff; /* Keep cursor visible */
723
-
724
- /* Selection styling */
725
- &::selection {
726
- background-color: rgba(255, 255, 255, 0.2);
727
- }
728
- }
729
-
730
- .proxy-editor {
731
- @apply w-full h-full resize-none focus:outline-none;
732
- color: oklch(0.90 0 0); /* Keep normal text color for proxy editor */
733
- /* Ensure proxy editor takes full height */
734
- flex: 1;
735
- min-height: 100%;
736
- }
737
-
738
- /* ==========================================================================
739
- CODE HIGHLIGHTING
740
- ========================================================================== */
741
-
742
- .code-highlight {
743
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
744
- font-size: 14px;
745
- line-height: 1.6;
746
- tab-size: 4;
747
- /* Remove transparent color - let syntax highlighting show */
748
- color: #f8f8f2 !important;
749
- background: transparent;
750
- border: none;
751
- outline: none;
752
- resize: none;
753
- width: 100%;
754
- height: 100%;
755
- overflow: hidden;
756
- white-space: pre;
757
- pointer-events: none;
758
- z-index: 1;
759
- position: absolute;
760
- top: 0;
761
- left: 0;
762
- padding: 12px;
763
- margin: 0;
764
- box-sizing: border-box;
765
- }
766
-
767
- /* ==========================================================================
768
- PRISM.JS SYNTAX HIGHLIGHTING THEME
769
- ========================================================================== */
770
-
771
- code[class*="language-"],
772
- pre[class*="language-"] {
773
- color: #f8f8f2;
774
- background: none;
775
- text-shadow: 0 1px rgba(0, 0, 0, 0.3);
776
- font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
777
- text-align: left;
778
- white-space: pre;
779
- word-spacing: normal;
780
- word-break: normal;
781
- word-wrap: normal;
782
- line-height: 1.5;
783
- tab-size: 4;
784
- hyphens: none;
785
- }
786
-
787
- pre[class*="language-"] {
788
- padding: 12px;
789
- margin: 0;
790
- overflow: auto;
791
- border-radius: 4px;
792
- font-family: "Menlo", "Monaco", "Courier New", monospace;
793
- font-size: 14px;
794
- line-height: 1.5;
795
- tab-size: 4;
796
- }
797
-
798
- :not(pre) > code[class*="language-"],
799
- pre[class*="language-"] {
800
- background: oklch(0.19 0 0);
801
- white-space: pre;
802
- }
803
-
804
- :not(pre) > code[class*="language-"] {
805
- padding: 0.1em;
806
- border-radius: 0.3em;
807
- white-space: normal;
808
- }
809
-
810
- .token.comment,
811
- .token.prolog,
812
- .token.doctype,
813
- .token.cdata {
814
- color: #676f7d;
815
- font-style: italic;
816
- }
817
-
818
- .token.punctuation {
819
- color: #a9b7c6;
820
- }
821
-
822
- .namespace {
823
- opacity: 0.8;
824
- }
825
-
826
- .token.property,
827
- .token.tag,
828
- .token.constant,
829
- .token.symbol,
830
- .token.deleted {
831
- color: #e06c75;
832
- }
833
-
834
- .token.boolean,
835
- .token.number {
836
- color: #c792ea;
837
- }
838
-
839
- .token.selector,
840
- .token.attr-name,
841
- .token.string,
842
- .token.char,
843
- .token.builtin,
844
- .token.inserted {
845
- color: #98c379;
846
- }
847
-
848
- .token.operator,
849
- .token.entity,
850
- .token.url,
851
- .language-css .token.string,
852
- .style .token.string {
853
- color: #89ddff;
854
- }
855
-
856
- .token.atrule,
857
- .token.attr-value,
858
- .token.keyword {
859
- color: #61afef;
860
- }
861
-
862
- .token.function,
863
- .token.class-name {
864
- color: #ffcb6b;
865
- }
866
-
867
- .token.regex,
868
- .token.important,
869
- .token.variable {
870
- color: #c5e478;
871
- }
872
-
873
- .token.important,
874
- .token.bold {
875
- font-weight: bold;
876
- }
877
-
878
- .token.italic {
879
- font-style: italic;
880
- }
881
-
882
- .token.entity {
883
- cursor: help;
884
- }
885
-
886
- .editor-container:hover {
887
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
888
- transition: box-shadow 0.3s ease;
889
- }
890
-
891
- /* ==========================================================================
892
- ERROR MESSAGES
893
- ========================================================================== */
894
-
895
- .error-container {
896
- @apply flex items-start gap-3 p-4 bg-red-500/10 border border-red-500/20 rounded-lg mt-4 mb-4;
897
-
898
- /* Prevent overflow in landscape mode */
899
- max-width: 100%;
900
- word-wrap: break-word;
901
- overflow-wrap: break-word;
902
- }
903
-
904
- .error-icon {
905
- @apply flex-shrink-0 text-red-400;
906
- }
907
-
908
- .error-content {
909
- @apply flex-1;
910
- min-width: 0; /* Allow text to shrink */
911
- }
912
-
913
- .error-title {
914
- @apply text-red-400 font-medium text-sm mb-1;
915
- }
916
-
917
- .error-text {
918
- @apply text-red-300 text-xs;
919
- word-break: break-word;
920
- }
921
-
922
- /* ==========================================================================
923
- SECTION DIVIDER
924
- ========================================================================== */
925
205
 
206
+ <style lang="scss" scoped>
926
207
  .section-divider {
927
- margin: 24px 0; /* Balanced margin for better centering */
928
- height: 1px;
929
- width: 100%;
930
- position: relative;
931
-
932
- /* Add subtle gradient effect for better visual separation */
933
- background: linear-gradient(90deg, transparent 0%, oklch(0.26 0 0) 20%, oklch(0.26 0 0) 80%, transparent 100%);
934
- border: none;
935
-
936
- /* Add a subtle glow effect */
937
- box-shadow: 0 0 8px rgba(61, 62, 68, 0.3);
208
+ @apply my-8 h-px w-full bg-dark-550;
938
209
  }
939
210
 
940
- /* ==========================================================================
941
- RESPONSIVE LAYOUT UTILITIES
942
- ========================================================================== */
943
-
944
- .flex-responsive-wrap {
945
- @apply flex flex-wrap items-center gap-4;
946
- }
947
-
948
-
949
- /* ==========================================================================
950
- MOBILE RESPONSIVE STYLES (max-width: 767px)
951
- ========================================================================== */
952
-
953
- @media (max-width: 767px) {
954
- /* Layout adjustments - almost no gap between dropdown and buttons */
955
- .flex-responsive-wrap {
956
- @apply flex-col items-stretch !important;
957
- gap: 0px !important; /* Reduced from 2px to eliminate space between dropdown and buttons */
958
- }
959
-
960
- /* Button container - even spacing across full width */
961
- .flex-responsive-wrap > div:last-child {
962
- @apply flex justify-center !important;
963
- }
964
-
965
- /* Editor container heights - fill available space on mobile */
966
- .editor-container,
967
- .proxy-editor-container {
968
- height: calc(100vh - 380px) !important; /* Fill viewport minus header/controls */
969
- max-height: 60vh !important; /* Allow more height */
970
- min-height: 300px !important; /* Ensure minimum usable height */
971
- }
972
-
973
- /* Reduce editor height when JSON error is showing in portrait */
974
- .editor-container.has-error {
975
- height: calc(100vh - 460px) !important; /* Account for error message */
976
- max-height: 50vh !important;
977
- min-height: 250px !important;
978
- }
979
-
980
- /* Error message spacing on mobile */
981
- .error-container {
982
- margin: 8px 0 !important;
983
- padding: 8px !important;
984
- font-size: 11px !important;
985
- }
986
-
987
- .error-title {
988
- font-size: 11px !important;
989
- }
990
-
991
- .error-text {
992
- font-size: 10px !important;
993
- }
994
-
995
- /* Compact editor controls spacing - no margins */
996
- .editor-controls-row,
997
- .proxy-controls-row {
998
- margin-bottom: 0px !important; /* Eliminated margin completely */
999
- }
1000
-
1001
- /* Compact editor containers - reduced margins for more space */
1002
- .my-3 {
1003
- margin: 6px 0 !important; /* Reduced margin between editor and controls */
1004
- }
1005
-
1006
- .pb-4 {
1007
- padding-bottom: 4px !important; /* Reduced padding */
1008
- }
1009
- }
1010
-
1011
- /* ==========================================================================
1012
- TABLET RESPONSIVE STYLES (min-width: 768px)
1013
- ========================================================================== */
1014
-
1015
- @media (min-width: 768px) {
1016
- .flex-responsive-wrap {
1017
- @apply flex-row justify-between items-center !important;
1018
- }
1019
- }
1020
-
1021
- /* ==========================================================================
1022
- DESKTOP RESPONSIVE STYLES (min-width: 1024px)
1023
- ========================================================================== */
1024
-
1025
- @media (min-width: 1024px) {
1026
- .editor-container,
1027
- .proxy-editor-container {
1028
- max-height: 60vh;
1029
- }
1030
- }
1031
-
1032
- /* ==========================================================================
1033
- LARGE DESKTOP RESPONSIVE STYLES (min-width: 1280px)
1034
- ========================================================================== */
1035
-
1036
- @media (min-width: 1280px) {
1037
- .editor-container,
1038
- .proxy-editor-container {
1039
- max-height: 70vh;
1040
- }
1041
- }
1042
-
1043
- /* ==========================================================================
1044
- MOBILE LANDSCAPE MODE (max-height: 500px + landscape)
1045
- ========================================================================== */
1046
-
1047
- @media (max-height: 500px) and (orientation: landscape) and (min-width: 568px) {
1048
- /* Truncate card cleanly when admin editor is open */
1049
- .admin-editor-section:not(.landscape-hidden) + .section-divider + .proxy-editor-section.landscape-hidden {
211
+ .landscape-hidden {
212
+ @media (max-width: 1023px) and (orientation: landscape) {
1050
213
  display: none !important;
1051
214
  }
1052
-
1053
- /* Ensure proxy editor takes full space when visible */
1054
- .proxy-editor-container {
1055
- height: 130px !important;
1056
- max-height: 130px !important;
1057
- min-height: 100px !important;
1058
- }
1059
-
1060
- .proxy-editor {
1061
- height: 100% !important;
1062
- min-height: 100% !important;
1063
- }
1064
-
1065
- /* Compact headings */
1066
- h4 {
1067
- font-size: 16px !important;
1068
- margin-bottom: 6px !important;
1069
- padding-top: 1rem !important;
1070
- }
1071
-
1072
- h5 {
1073
- font-size: 14px !important;
1074
- margin-bottom: 4px !important;
1075
- }
1076
-
1077
- /* Section divider */
1078
- .section-divider {
1079
- margin: 6px 0 !important;
1080
- }
1081
-
1082
- /* Editor controls layout */
1083
- .editor-controls-row,
1084
- .proxy-controls-row {
1085
- gap: 6px !important;
1086
- }
1087
-
1088
- /* Hide button text to save space in landscape mode */
1089
- .button-default span {
1090
- display: none !important;
1091
- }
1092
-
1093
- /* Compact editor containers - reduced margins for more space */
1094
- .my-3 {
1095
- margin: 4px 0 !important; /* Reduced from 6px */
1096
- }
1097
-
1098
- .pb-4 {
1099
- padding-bottom: 2px !important; /* Reduced from 4px */
1100
- }
1101
-
1102
- /* Editor container heights - increased to use more available space */
1103
- .editor-container,
1104
- .proxy-editor-container {
1105
- height: 180px !important; /* Increased from 130px */
1106
- max-height: 180px !important;
1107
- min-height: 150px !important; /* Increased from 100px */
1108
- }
1109
-
1110
- /* Reduce editor height when JSON error is showing */
1111
- .editor-container.has-error {
1112
- height: 120px !important; /* Reduced to make room for error message */
1113
- max-height: 120px !important;
1114
- min-height: 100px !important;
1115
- }
1116
-
1117
- /* Ensure proxy editor takes full space when visible - increased height */
1118
- .proxy-editor-container {
1119
- height: 180px !important; /* Increased from 130px */
1120
- max-height: 180px !important;
1121
- min-height: 150px !important;
1122
- }
1123
-
1124
- /* Editor textarea heights - ensure syntax highlighting works */
1125
- .code-editor,
1126
- .proxy-editor {
1127
- font-size: 11px !important;
1128
- line-height: 1.4 !important;
1129
- padding: 6px !important;
1130
- }
1131
-
1132
- /* Syntax highlighting in landscape mode */
1133
- .code-highlight {
1134
- font-size: 11px !important;
1135
- line-height: 1.4 !important;
1136
- padding: 6px !important;
1137
- /* Ensure syntax highlighting is visible in landscape */
1138
- color: #f8f8f2 !important;
1139
- opacity: 1 !important;
1140
- display: block !important;
1141
- }
1142
-
1143
- /* Compact error messages */
1144
- .error-container {
1145
- margin: 2px 0 !important; /* Reduced from 4px */
1146
- padding: 4px 6px !important; /* More compact padding */
1147
- font-size: 9px !important; /* Reduced from 10px */
1148
- border-radius: 4px !important; /* Smaller border radius */
1149
- gap: 6px !important; /* Reduced gap */
1150
- }
1151
-
1152
- .error-icon {
1153
- width: 12px !important;
1154
- height: 12px !important;
1155
- }
1156
-
1157
- .error-icon svg {
1158
- width: 12px !important;
1159
- height: 12px !important;
1160
- }
1161
-
1162
- .error-title {
1163
- font-size: 9px !important; /* Reduced from 10px */
1164
- margin-bottom: 2px !important;
1165
- font-weight: 500 !important;
1166
- }
1167
-
1168
- .error-text {
1169
- font-size: 8px !important; /* Reduced from 9px */
1170
- line-height: 1.2 !important;
1171
- }
1172
215
  }
1173
-
1174
- /* ==========================================================================
1175
- VERY SHORT LANDSCAPE SCREENS (max-height: 400px + landscape)
1176
- ========================================================================== */
1177
-
1178
- @media (max-height: 400px) and (orientation: landscape) {
1179
- .editor-container,
1180
- .proxy-editor-container {
1181
- height: 140px !important; /* Increased from 100px */
1182
- max-height: 140px !important;
1183
- min-height: 120px !important; /* Increased from 80px */
1184
- }
1185
-
1186
- /* Reduce editor height when JSON error is showing in very short landscape */
1187
- .editor-container.has-error {
1188
- height: 90px !important; /* Reduced to make room for error message */
1189
- max-height: 90px !important;
1190
- min-height: 80px !important;
1191
- }
1192
-
1193
- /* Ensure proxy editor takes full space in very short landscape */
1194
- .proxy-editor {
1195
- height: 100% !important;
1196
- min-height: 100% !important;
1197
- font-size: 10px !important;
1198
- line-height: 1.3 !important;
1199
- padding: 4px !important;
1200
- }
1201
-
1202
- .code-editor {
1203
- font-size: 10px !important;
1204
- line-height: 1.3 !important;
1205
- padding: 4px !important;
1206
- }
1207
-
1208
- /* Ensure syntax highlighting works in very short landscape */
1209
- .code-highlight {
1210
- font-size: 10px !important;
1211
- line-height: 1.3 !important;
1212
- padding: 4px !important;
1213
- color: #f8f8f2 !important;
1214
- opacity: 1 !important;
1215
- display: block !important;
1216
- }
1217
-
1218
- /* Ultra-compact error messages for very short landscape */
1219
- .error-container {
1220
- margin: 1px 0 !important;
1221
- padding: 2px 4px !important;
1222
- font-size: 8px !important;
1223
- border-radius: 3px !important;
1224
- gap: 4px !important;
1225
- }
1226
-
1227
- .error-icon {
1228
- width: 10px !important;
1229
- height: 10px !important;
1230
- }
1231
-
1232
- .error-icon svg {
1233
- width: 10px !important;
1234
- height: 10px !important;
1235
- }
1236
-
1237
- .error-title {
1238
- font-size: 8px !important;
1239
- margin-bottom: 1px !important;
1240
- font-weight: 500 !important;
1241
- }
1242
-
1243
- .error-text {
1244
- font-size: 7px !important;
1245
- line-height: 1.1 !important;
1246
- }
1247
- }
1248
-
1249
- /* ==========================================================================
1250
- MOBILE & TABLET - COMPACT EDITOR BUTTONS FOR ONE-ROW LAYOUT
1251
- ========================================================================== */
1252
-
1253
- /* Icon-sized buttons - compact for all screens */
1254
- .button-default {
1255
- @apply h-7 w-7 p-0; // 28x28px on desktop/tablet
1256
- min-width: 28px !important;
1257
- max-width: 28px !important;
1258
- width: 28px !important;
1259
- height: 28px !important;
1260
- padding: 0 !important;
1261
- border-radius: 6px;
1262
-
1263
- svg {
1264
- @apply w-4 h-4; // 16x16px icons
1265
- }
1266
- }
1267
-
1268
- /* Even more compact on mobile */
1269
- @media (max-width: 640px) {
1270
- .button-default {
1271
- @apply h-6 w-6; // 24x24px on mobile
1272
- min-width: 24px !important;
1273
- max-width: 24px !important;
1274
- width: 24px !important;
1275
- height: 24px !important;
1276
-
1277
- svg {
1278
- @apply w-3.5 h-3.5; // 14x14px icons
1279
- }
1280
- }
1281
- }
1282
-
1283
216
  </style>