@gxp-dev/tools 2.0.5 → 2.0.6

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 (169) hide show
  1. package/dist/tui/App.d.ts +13 -0
  2. package/dist/tui/App.d.ts.map +1 -0
  3. package/dist/tui/App.js +480 -0
  4. package/dist/tui/App.js.map +1 -0
  5. package/dist/tui/components/CommandInput.d.ts +13 -0
  6. package/dist/tui/components/CommandInput.d.ts.map +1 -0
  7. package/dist/tui/components/CommandInput.js +180 -0
  8. package/dist/tui/components/CommandInput.js.map +1 -0
  9. package/dist/tui/components/GeminiPanel.d.ts +7 -0
  10. package/dist/tui/components/GeminiPanel.d.ts.map +1 -0
  11. package/dist/tui/components/GeminiPanel.js +99 -0
  12. package/dist/tui/components/GeminiPanel.js.map +1 -0
  13. package/dist/tui/components/Header.d.ts +6 -0
  14. package/dist/tui/components/Header.d.ts.map +1 -0
  15. package/dist/tui/components/Header.js +6 -0
  16. package/dist/tui/components/Header.js.map +1 -0
  17. package/dist/tui/components/LogPanel.d.ts +8 -0
  18. package/dist/tui/components/LogPanel.d.ts.map +1 -0
  19. package/dist/tui/components/LogPanel.js +89 -0
  20. package/dist/tui/components/LogPanel.js.map +1 -0
  21. package/dist/tui/components/TabBar.d.ts +9 -0
  22. package/dist/tui/components/TabBar.d.ts.map +1 -0
  23. package/dist/tui/components/TabBar.js +23 -0
  24. package/dist/tui/components/TabBar.js.map +1 -0
  25. package/dist/tui/components/WelcomeScreen.d.ts +2 -0
  26. package/dist/tui/components/WelcomeScreen.d.ts.map +1 -0
  27. package/dist/tui/components/WelcomeScreen.js +14 -0
  28. package/dist/tui/components/WelcomeScreen.js.map +1 -0
  29. package/dist/tui/index.d.ts +8 -0
  30. package/dist/tui/index.d.ts.map +1 -0
  31. package/dist/tui/index.js +47 -0
  32. package/dist/tui/index.js.map +1 -0
  33. package/dist/tui/package.json +1 -0
  34. package/dist/tui/services/ExtensionService.d.ts +11 -0
  35. package/dist/tui/services/ExtensionService.d.ts.map +1 -0
  36. package/dist/tui/services/ExtensionService.js +101 -0
  37. package/dist/tui/services/ExtensionService.js.map +1 -0
  38. package/dist/tui/services/GeminiService.d.ts +40 -0
  39. package/dist/tui/services/GeminiService.d.ts.map +1 -0
  40. package/dist/tui/services/GeminiService.js +327 -0
  41. package/dist/tui/services/GeminiService.js.map +1 -0
  42. package/dist/tui/services/ServiceManager.d.ts +40 -0
  43. package/dist/tui/services/ServiceManager.d.ts.map +1 -0
  44. package/dist/tui/services/ServiceManager.js +283 -0
  45. package/dist/tui/services/ServiceManager.js.map +1 -0
  46. package/dist/tui/services/SocketService.d.ts +19 -0
  47. package/dist/tui/services/SocketService.d.ts.map +1 -0
  48. package/dist/tui/services/SocketService.js +163 -0
  49. package/dist/tui/services/SocketService.js.map +1 -0
  50. package/dist/tui/services/ViteService.d.ts +8 -0
  51. package/dist/tui/services/ViteService.d.ts.map +1 -0
  52. package/dist/tui/services/ViteService.js +89 -0
  53. package/dist/tui/services/ViteService.js.map +1 -0
  54. package/dist/tui/services/index.d.ts +6 -0
  55. package/dist/tui/services/index.d.ts.map +1 -0
  56. package/dist/tui/services/index.js +6 -0
  57. package/dist/tui/services/index.js.map +1 -0
  58. package/package.json +6 -1
  59. package/.github/workflows/npm-publish.yml +0 -48
  60. package/CLAUDE.md +0 -400
  61. package/REFACTOR_PLAN.md +0 -194
  62. package/browser-extensions/README.md +0 -1
  63. package/browser-extensions/chrome/background.js +0 -857
  64. package/browser-extensions/chrome/content.js +0 -51
  65. package/browser-extensions/chrome/devtools.html +0 -9
  66. package/browser-extensions/chrome/devtools.js +0 -23
  67. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  68. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  69. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  70. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  71. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  72. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  73. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  74. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  75. package/browser-extensions/chrome/inspector.js +0 -1087
  76. package/browser-extensions/chrome/manifest.json +0 -70
  77. package/browser-extensions/chrome/panel.html +0 -638
  78. package/browser-extensions/chrome/panel.js +0 -862
  79. package/browser-extensions/chrome/popup.html +0 -399
  80. package/browser-extensions/chrome/popup.js +0 -515
  81. package/browser-extensions/chrome/rules.json +0 -1
  82. package/browser-extensions/chrome/test-chrome.html +0 -145
  83. package/browser-extensions/chrome/test-mixed-content.html +0 -190
  84. package/browser-extensions/chrome/test-uri-pattern.html +0 -199
  85. package/browser-extensions/firefox/README.md +0 -134
  86. package/browser-extensions/firefox/background.js +0 -804
  87. package/browser-extensions/firefox/content.js +0 -120
  88. package/browser-extensions/firefox/debug-errors.html +0 -229
  89. package/browser-extensions/firefox/debug-https.html +0 -113
  90. package/browser-extensions/firefox/devtools.html +0 -9
  91. package/browser-extensions/firefox/devtools.js +0 -24
  92. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  93. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  94. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  95. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  96. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  97. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  98. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  99. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  100. package/browser-extensions/firefox/inspector.js +0 -1087
  101. package/browser-extensions/firefox/manifest.json +0 -67
  102. package/browser-extensions/firefox/panel.html +0 -638
  103. package/browser-extensions/firefox/panel.js +0 -862
  104. package/browser-extensions/firefox/popup.html +0 -525
  105. package/browser-extensions/firefox/popup.js +0 -536
  106. package/browser-extensions/firefox/test-gramercy.html +0 -126
  107. package/browser-extensions/firefox/test-imports.html +0 -58
  108. package/browser-extensions/firefox/test-masking.html +0 -147
  109. package/browser-extensions/firefox/test-uri-pattern.html +0 -199
  110. package/docs/DOCUSAURUS_IMPORT.md +0 -378
  111. package/docs/_category_.json +0 -8
  112. package/docs/app-manifest.md +0 -272
  113. package/docs/building-for-platform.md +0 -315
  114. package/docs/dev-tools.md +0 -291
  115. package/docs/getting-started.md +0 -180
  116. package/docs/gxp-store.md +0 -305
  117. package/docs/index.md +0 -44
  118. package/runtime/PortalContainer.vue +0 -326
  119. package/runtime/dev-tools/DevToolsModal.vue +0 -217
  120. package/runtime/dev-tools/LayoutSwitcher.vue +0 -221
  121. package/runtime/dev-tools/MockDataEditor.vue +0 -621
  122. package/runtime/dev-tools/SocketSimulator.vue +0 -562
  123. package/runtime/dev-tools/StoreInspector.vue +0 -644
  124. package/runtime/dev-tools/index.js +0 -6
  125. package/runtime/gxpStringsPlugin.js +0 -428
  126. package/runtime/index.html +0 -22
  127. package/runtime/main.js +0 -32
  128. package/runtime/mock-api/auth-middleware.js +0 -97
  129. package/runtime/mock-api/image-generator.js +0 -221
  130. package/runtime/mock-api/index.js +0 -197
  131. package/runtime/mock-api/response-generator.js +0 -394
  132. package/runtime/mock-api/route-generator.js +0 -323
  133. package/runtime/mock-api/socket-triggers.js +0 -371
  134. package/runtime/mock-api/spec-loader.js +0 -300
  135. package/runtime/server.js +0 -180
  136. package/runtime/stores/gxpPortalConfigStore.js +0 -554
  137. package/runtime/stores/index.js +0 -6
  138. package/runtime/vite-inspector-plugin.js +0 -749
  139. package/runtime/vite-source-tracker-plugin.js +0 -232
  140. package/runtime/vite.config.js +0 -402
  141. package/scripts/launch-chrome.js +0 -90
  142. package/scripts/pack-chrome.js +0 -91
  143. package/socket-events/AiSessionMessageCreated.json +0 -18
  144. package/socket-events/SocialStreamPostCreated.json +0 -24
  145. package/socket-events/SocialStreamPostVariantCompleted.json +0 -23
  146. package/template/README.md +0 -332
  147. package/template/app-manifest.json +0 -32
  148. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  149. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  150. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  151. package/template/dev-assets/images/icon-placeholder.png +0 -0
  152. package/template/dev-assets/images/logo-placeholder.png +0 -0
  153. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  154. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  155. package/template/env.example +0 -51
  156. package/template/gitignore +0 -53
  157. package/template/index.html +0 -22
  158. package/template/main.js +0 -28
  159. package/template/src/DemoPage.vue +0 -459
  160. package/template/src/Plugin.vue +0 -38
  161. package/template/src/stores/index.js +0 -9
  162. package/template/src/stores/test-data.json +0 -173
  163. package/template/theme-layouts/AdditionalStyling.css +0 -0
  164. package/template/theme-layouts/PrivateLayout.vue +0 -39
  165. package/template/theme-layouts/PublicLayout.vue +0 -39
  166. package/template/theme-layouts/SystemLayout.vue +0 -39
  167. package/template/vite.config.js +0 -333
  168. package/tsconfig.tui.json +0 -21
  169. package/vite.config.js +0 -164
@@ -1,1087 +0,0 @@
1
- /**
2
- * GxP Component Inspector
3
- *
4
- * Browser extension content script that provides:
5
- * - Component highlighting on hover
6
- * - Vue component detection
7
- * - String extraction panel
8
- * - File editing via Vite dev server API
9
- *
10
- * This script injects itself into the page's main world so that
11
- * DevTools panel can access window.gxpInspector via eval().
12
- */
13
-
14
- // Check if we're in the page context (already injected) vs content script context
15
- // In page context, neither 'browser' nor 'chrome' runtime APIs are available
16
- const isContentScriptContext = (typeof browser !== 'undefined' && browser.runtime) ||
17
- (typeof chrome !== 'undefined' && chrome.runtime);
18
-
19
- if (isContentScriptContext && typeof window.__gxpInspectorInjected === 'undefined') {
20
- // We're in the content script context - inject into page
21
- const runtime = typeof browser !== 'undefined' ? browser : chrome;
22
- const script = document.createElement('script');
23
- script.src = runtime.runtime.getURL('inspector.js');
24
- script.onload = function() {
25
- this.remove();
26
- };
27
- (document.head || document.documentElement).appendChild(script);
28
-
29
- // Also set up message relay from page to extension
30
- window.addEventListener('message', (event) => {
31
- if (event.source !== window) return;
32
- if (event.data?.type === 'GXP_INSPECTOR_MESSAGE') {
33
- runtime.runtime.sendMessage(event.data.payload);
34
- }
35
- });
36
-
37
- // Mark that content script has run (for content script context)
38
- window.__gxpInspectorContentScriptLoaded = true;
39
- }
40
-
41
- // Mark as injected (for both contexts)
42
- window.__gxpInspectorInjected = true;
43
-
44
- (function () {
45
- 'use strict';
46
-
47
- // If gxpInspector already exists, we're done (prevent double init)
48
- if (window.gxpInspector) {
49
- return;
50
- }
51
-
52
- // Configuration
53
- const DEV_SERVER_URL = 'https://localhost:3060';
54
- const API_PREFIX = '/__gxp-inspector';
55
-
56
- // State
57
- let inspectorEnabled = false;
58
- let highlightOverlay = null;
59
- let inspectorPanel = null;
60
- let selectedElement = null;
61
- let hoveredElement = null;
62
- let selectionHighlight = null; // Persistent highlight for selected element
63
-
64
- // ============================================================
65
- // API Communication
66
- // ============================================================
67
-
68
- async function apiCall(endpoint, options = {}) {
69
- const url = `${DEV_SERVER_URL}${API_PREFIX}${endpoint}`;
70
- try {
71
- const response = await fetch(url, {
72
- ...options,
73
- headers: {
74
- 'Content-Type': 'application/json',
75
- ...options.headers
76
- }
77
- });
78
- return await response.json();
79
- } catch (error) {
80
- console.error('[GxP Inspector] API Error:', error);
81
- return { success: false, error: error.message };
82
- }
83
- }
84
-
85
- async function ping() {
86
- return apiCall('/ping');
87
- }
88
-
89
- async function extractString(data) {
90
- return apiCall('/extract-string', {
91
- method: 'POST',
92
- body: JSON.stringify(data)
93
- });
94
- }
95
-
96
- async function getStrings() {
97
- return apiCall('/strings');
98
- }
99
-
100
- // ============================================================
101
- // Vue Component Detection
102
- // ============================================================
103
-
104
- function getVueInstance(el) {
105
- // Vue 3 detection
106
- if (el.__vueParentComponent) {
107
- return el.__vueParentComponent;
108
- }
109
- // Walk up to find Vue component
110
- let current = el;
111
- while (current) {
112
- if (current.__vueParentComponent) {
113
- return current.__vueParentComponent;
114
- }
115
- current = current.parentElement;
116
- }
117
- return null;
118
- }
119
-
120
- function getComponentInfo(vueInstance) {
121
- if (!vueInstance) return null;
122
-
123
- const type = vueInstance.type;
124
- const name = type?.name || type?.__name || type?.__file?.split('/').pop()?.replace('.vue', '') || 'Anonymous';
125
- const file = type?.__file || null;
126
-
127
- // Helper to safely serialize a value (handles circular refs, functions, etc.)
128
- function safeSerialize(value) {
129
- if (value === null || value === undefined) return value;
130
- if (typeof value === 'function') return '[Function]';
131
- if (typeof value !== 'object') return value;
132
-
133
- try {
134
- // Try JSON stringify/parse to get a clean copy
135
- return JSON.parse(JSON.stringify(value));
136
- } catch {
137
- // If that fails, return a string representation
138
- if (Array.isArray(value)) return `[Array(${value.length})]`;
139
- return '{...}';
140
- }
141
- }
142
-
143
- // Get props
144
- const props = {};
145
- if (vueInstance.props) {
146
- Object.keys(vueInstance.props).forEach(key => {
147
- props[key] = safeSerialize(vueInstance.props[key]);
148
- });
149
- }
150
-
151
- // Get component data/state
152
- const data = {};
153
- if (vueInstance.setupState) {
154
- Object.keys(vueInstance.setupState).forEach(key => {
155
- const value = vueInstance.setupState[key];
156
- if (typeof value !== 'function') {
157
- data[key] = safeSerialize(value);
158
- }
159
- });
160
- }
161
-
162
- return { name, file, props, data };
163
- }
164
-
165
- function getTextContent(el) {
166
- // Get direct text content, excluding child elements
167
- const texts = [];
168
- el.childNodes.forEach(node => {
169
- if (node.nodeType === Node.TEXT_NODE) {
170
- const text = node.textContent.trim();
171
- if (text) texts.push(text);
172
- }
173
- });
174
- return texts;
175
- }
176
-
177
- /**
178
- * Check if an element has a gxp-string attribute (indicating it's already extracted)
179
- */
180
- function getGxpStringKey(el) {
181
- return el?.getAttribute?.('gxp-string') || null;
182
- }
183
-
184
- /**
185
- * Check for a data-gxp-expr attribute on the element
186
- * The vite-source-tracker-plugin adds this attribute in dev mode
187
- * @param {Element} el - The element to check
188
- * @returns {string|null} - The source expression or null
189
- */
190
- function getGxpSourceExpression(el) {
191
- if (!el || !el.getAttribute) return null;
192
- return el.getAttribute('data-gxp-expr') || null;
193
- }
194
-
195
- /**
196
- * Get text content with gxp-string attribute info
197
- * Returns array of objects: { text, gxpStringKey, isExtracted, sourceExpression, isDynamic }
198
- */
199
- function getTextContentWithAttributes(el) {
200
- const results = [];
201
-
202
- // Check if this element itself has gxp-string
203
- const elementKey = getGxpStringKey(el);
204
- // Check for data-gxp-source attribute (injected by vite plugin for {{ expressions }})
205
- const sourceExpression = getGxpSourceExpression(el);
206
-
207
- el.childNodes.forEach(node => {
208
- if (node.nodeType === Node.TEXT_NODE) {
209
- const text = node.textContent.trim();
210
- if (text) {
211
- results.push({
212
- text: text,
213
- gxpStringKey: elementKey,
214
- isExtracted: elementKey !== null,
215
- sourceExpression: sourceExpression,
216
- isDynamic: sourceExpression !== null
217
- });
218
- }
219
- }
220
- });
221
-
222
- return results;
223
- }
224
-
225
- /**
226
- * Find all child elements with gxp-string attributes
227
- */
228
- function findChildGxpStrings(el) {
229
- const strings = [];
230
- const elements = el.querySelectorAll('[gxp-string]');
231
-
232
- elements.forEach(child => {
233
- const key = child.getAttribute('gxp-string');
234
- const text = child.textContent.trim();
235
- if (key && text) {
236
- strings.push({
237
- key: key,
238
- text: text,
239
- element: child.tagName.toLowerCase()
240
- });
241
- }
242
- });
243
-
244
- return strings;
245
- }
246
-
247
- /**
248
- * Analyze text content to detect if it comes from getString() calls
249
- * Returns an array of text info objects with type: 'raw' or 'getString'
250
- */
251
- function analyzeTextContent(el, vueInstance) {
252
- const textInfos = [];
253
- const texts = getTextContent(el);
254
-
255
- // Get the component's source info to help detect getString usage
256
- const componentFile = vueInstance?.type?.__file || null;
257
-
258
- // Try to detect getString calls by checking if this element's text
259
- // is likely from a getString call. We look for patterns in the rendered output
260
- // and can cross-reference with the app-manifest.json via API later.
261
-
262
- texts.forEach((text, index) => {
263
- // Default to raw text
264
- const textInfo = {
265
- text: text,
266
- type: 'raw',
267
- index: index
268
- };
269
-
270
- // We'll mark it as potentially from getString if we can detect it
271
- // The panel will verify against the manifest
272
- textInfos.push(textInfo);
273
- });
274
-
275
- return textInfos;
276
- }
277
-
278
- /**
279
- * Try to find getString calls in the Vue component by inspecting
280
- * the component's template bindings (if accessible)
281
- */
282
- function findGetStringCalls(vueInstance, filePath) {
283
- const getStringCalls = [];
284
-
285
- if (!vueInstance) return getStringCalls;
286
-
287
- // Check setupState for gxpStore reference
288
- const hasGxpStore = vueInstance.setupState?.gxpStore !== undefined;
289
-
290
- // We can't directly see the template, but we can check for gxpStore usage
291
- // The actual verification happens on the server side by checking the source file
292
-
293
- return { hasGxpStore, getStringCalls };
294
- }
295
-
296
- // ============================================================
297
- // Overlay UI
298
- // ============================================================
299
-
300
- function createHighlightOverlay() {
301
- if (highlightOverlay) return highlightOverlay;
302
-
303
- highlightOverlay = document.createElement('div');
304
- highlightOverlay.id = 'gxp-inspector-highlight';
305
-
306
- const style = document.createElement('style');
307
- style.id = 'gxp-highlight-style';
308
- style.textContent = `
309
- /* Pointer cursor when in selection mode */
310
- body.gxp-inspector-selecting,
311
- body.gxp-inspector-selecting * {
312
- cursor: crosshair !important;
313
- }
314
- #gxp-inspector-highlight {
315
- position: fixed;
316
- pointer-events: none;
317
- z-index: 999999;
318
- display: none;
319
- border: 2px dashed #f59e0b;
320
- background: rgba(245, 158, 11, 0.1);
321
- border-radius: 4px;
322
- box-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
323
- }
324
- #gxp-inspector-highlight .gxp-highlight-label {
325
- position: absolute;
326
- top: -24px;
327
- left: -2px;
328
- background: #f59e0b;
329
- color: #1e1e1e;
330
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
331
- font-size: 11px;
332
- font-weight: 600;
333
- padding: 2px 8px;
334
- border-radius: 3px 3px 0 0;
335
- white-space: nowrap;
336
- }
337
- `;
338
-
339
- highlightOverlay.innerHTML = `<div class="gxp-highlight-label"></div>`;
340
-
341
- if (!document.getElementById('gxp-highlight-style')) {
342
- document.head.appendChild(style);
343
- }
344
- document.body.appendChild(highlightOverlay);
345
- return highlightOverlay;
346
- }
347
-
348
- function updateHighlight(el) {
349
- if (!el || !highlightOverlay) return;
350
-
351
- const rect = el.getBoundingClientRect();
352
- const label = highlightOverlay.querySelector('.gxp-highlight-label');
353
-
354
- // Position the highlight overlay directly
355
- highlightOverlay.style.display = 'block';
356
- highlightOverlay.style.left = `${rect.left}px`;
357
- highlightOverlay.style.top = `${rect.top}px`;
358
- highlightOverlay.style.width = `${rect.width}px`;
359
- highlightOverlay.style.height = `${rect.height}px`;
360
-
361
- // Build label: Component::element::gxp-string-key
362
- label.textContent = buildElementLabel(el);
363
- }
364
-
365
- function hideHighlight() {
366
- if (highlightOverlay) {
367
- highlightOverlay.style.display = 'none';
368
- }
369
- }
370
-
371
- // ============================================================
372
- // Selection Highlight (persistent border on selected element)
373
- // ============================================================
374
-
375
- function createSelectionHighlight() {
376
- if (selectionHighlight) return selectionHighlight;
377
-
378
- selectionHighlight = document.createElement('div');
379
- selectionHighlight.id = 'gxp-inspector-selection';
380
-
381
- const style = document.createElement('style');
382
- style.id = 'gxp-selection-style';
383
- style.textContent = `
384
- #gxp-inspector-selection {
385
- position: fixed;
386
- pointer-events: none;
387
- z-index: 999998;
388
- display: none;
389
- border: 3px solid #61dafb;
390
- background: rgba(97, 218, 251, 0.1);
391
- border-radius: 4px;
392
- box-shadow: 0 0 0 1px rgba(97, 218, 251, 0.3),
393
- 0 0 12px 3px rgba(97, 218, 251, 0.5),
394
- 0 0 24px 6px rgba(97, 218, 251, 0.25),
395
- inset 0 0 20px rgba(97, 218, 251, 0.1);
396
- animation: gxp-selection-pulse 2s ease-in-out infinite;
397
- }
398
- #gxp-inspector-selection .gxp-selection-label {
399
- position: absolute;
400
- top: -26px;
401
- left: -3px;
402
- background: #61dafb;
403
- color: #1e1e1e;
404
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
405
- font-size: 11px;
406
- font-weight: 600;
407
- padding: 3px 10px;
408
- border-radius: 4px 4px 0 0;
409
- white-space: nowrap;
410
- box-shadow: 0 0 10px rgba(97, 218, 251, 0.6);
411
- }
412
- @keyframes gxp-selection-pulse {
413
- 0%, 100% {
414
- box-shadow: 0 0 0 1px rgba(97, 218, 251, 0.3),
415
- 0 0 12px 3px rgba(97, 218, 251, 0.5),
416
- 0 0 24px 6px rgba(97, 218, 251, 0.25),
417
- inset 0 0 20px rgba(97, 218, 251, 0.1);
418
- }
419
- 50% {
420
- box-shadow: 0 0 0 2px rgba(97, 218, 251, 0.5),
421
- 0 0 20px 5px rgba(97, 218, 251, 0.7),
422
- 0 0 40px 10px rgba(97, 218, 251, 0.35),
423
- inset 0 0 30px rgba(97, 218, 251, 0.15);
424
- }
425
- }
426
- `;
427
-
428
- selectionHighlight.innerHTML = `<div class="gxp-selection-label"></div>`;
429
-
430
- if (!document.getElementById('gxp-selection-style')) {
431
- document.head.appendChild(style);
432
- }
433
- document.body.appendChild(selectionHighlight);
434
- return selectionHighlight;
435
- }
436
-
437
- function updateSelectionHighlight(el) {
438
- if (!el) {
439
- hideSelectionHighlight();
440
- return;
441
- }
442
-
443
- createSelectionHighlight();
444
- const rect = el.getBoundingClientRect();
445
- const label = selectionHighlight.querySelector('.gxp-selection-label');
446
-
447
- // Position the main overlay element directly
448
- selectionHighlight.style.display = 'block';
449
- selectionHighlight.style.left = `${rect.left}px`;
450
- selectionHighlight.style.top = `${rect.top}px`;
451
- selectionHighlight.style.width = `${rect.width}px`;
452
- selectionHighlight.style.height = `${rect.height}px`;
453
-
454
- // Build label: Component::element::gxp-string-key
455
- label.textContent = buildElementLabel(el);
456
- }
457
-
458
- function hideSelectionHighlight() {
459
- if (selectionHighlight) {
460
- selectionHighlight.style.display = 'none';
461
- }
462
- }
463
-
464
- // Update selection highlight position on scroll/resize
465
- function updateSelectionPosition() {
466
- if (selectedElement && selectionHighlight && selectionHighlight.style.display !== 'none') {
467
- updateSelectionHighlight(selectedElement);
468
- }
469
- }
470
-
471
- // ============================================================
472
- // Inspector Panel
473
- // ============================================================
474
-
475
- function createInspectorPanel() {
476
- if (inspectorPanel) return inspectorPanel;
477
-
478
- inspectorPanel = document.createElement('div');
479
- inspectorPanel.id = 'gxp-inspector-panel';
480
- inspectorPanel.innerHTML = `
481
- <div class="gxp-panel-header">
482
- <span class="gxp-panel-title">GxP Component Inspector</span>
483
- <button class="gxp-panel-close">&times;</button>
484
- </div>
485
- <div class="gxp-panel-content">
486
- <div class="gxp-panel-section gxp-component-info">
487
- <div class="gxp-section-title">Component</div>
488
- <div class="gxp-component-name">Click on an element to inspect</div>
489
- <div class="gxp-component-file"></div>
490
- </div>
491
- <div class="gxp-panel-section gxp-strings-section" style="display: none;">
492
- <div class="gxp-section-title">Text Content</div>
493
- <div class="gxp-strings-list"></div>
494
- </div>
495
- <div class="gxp-panel-section gxp-extract-section" style="display: none;">
496
- <div class="gxp-section-title">Extract String</div>
497
- <div class="gxp-extract-form">
498
- <div class="gxp-form-group">
499
- <label>Text:</label>
500
- <input type="text" class="gxp-extract-text" readonly>
501
- </div>
502
- <div class="gxp-form-group">
503
- <label>Key:</label>
504
- <input type="text" class="gxp-extract-key" placeholder="e.g., welcome_title">
505
- </div>
506
- <div class="gxp-form-group">
507
- <label>File:</label>
508
- <input type="text" class="gxp-extract-file" readonly>
509
- </div>
510
- <button class="gxp-extract-button">Extract to getString()</button>
511
- </div>
512
- <div class="gxp-extract-status"></div>
513
- </div>
514
- <div class="gxp-panel-section gxp-props-section" style="display: none;">
515
- <div class="gxp-section-title">Props</div>
516
- <pre class="gxp-props-content"></pre>
517
- </div>
518
- </div>
519
- `;
520
-
521
- const style = document.createElement('style');
522
- style.textContent = `
523
- #gxp-inspector-panel {
524
- position: fixed;
525
- bottom: 20px;
526
- right: 20px;
527
- width: 380px;
528
- max-height: 500px;
529
- background: #1e1e1e;
530
- border: 1px solid #3d3d3d;
531
- border-radius: 8px;
532
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
533
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
534
- font-size: 13px;
535
- color: #e0e0e0;
536
- z-index: 999998;
537
- overflow: hidden;
538
- display: flex;
539
- flex-direction: column;
540
- }
541
- .gxp-panel-header {
542
- display: flex;
543
- justify-content: space-between;
544
- align-items: center;
545
- padding: 12px 16px;
546
- background: #2d2d2d;
547
- border-bottom: 1px solid #3d3d3d;
548
- }
549
- .gxp-panel-title {
550
- font-weight: 600;
551
- color: #61dafb;
552
- }
553
- .gxp-panel-close {
554
- background: none;
555
- border: none;
556
- color: #888;
557
- font-size: 20px;
558
- cursor: pointer;
559
- padding: 0;
560
- line-height: 1;
561
- }
562
- .gxp-panel-close:hover {
563
- color: #ff6b6b;
564
- }
565
- .gxp-panel-content {
566
- padding: 16px;
567
- overflow-y: auto;
568
- flex: 1;
569
- }
570
- .gxp-panel-section {
571
- margin-bottom: 16px;
572
- }
573
- .gxp-section-title {
574
- font-size: 11px;
575
- font-weight: 600;
576
- text-transform: uppercase;
577
- color: #888;
578
- margin-bottom: 8px;
579
- }
580
- .gxp-component-name {
581
- font-size: 15px;
582
- font-weight: 600;
583
- color: #61dafb;
584
- margin-bottom: 4px;
585
- }
586
- .gxp-component-file {
587
- font-size: 11px;
588
- color: #888;
589
- word-break: break-all;
590
- }
591
- .gxp-strings-list {
592
- display: flex;
593
- flex-direction: column;
594
- gap: 8px;
595
- }
596
- .gxp-string-item {
597
- display: flex;
598
- justify-content: space-between;
599
- align-items: center;
600
- padding: 8px 10px;
601
- background: #2d2d2d;
602
- border-radius: 4px;
603
- cursor: pointer;
604
- }
605
- .gxp-string-item:hover {
606
- background: #3d3d3d;
607
- }
608
- .gxp-string-text {
609
- flex: 1;
610
- overflow: hidden;
611
- text-overflow: ellipsis;
612
- white-space: nowrap;
613
- }
614
- .gxp-string-extract {
615
- background: #61dafb;
616
- color: #1e1e1e;
617
- border: none;
618
- padding: 4px 10px;
619
- border-radius: 3px;
620
- font-size: 11px;
621
- font-weight: 600;
622
- cursor: pointer;
623
- margin-left: 8px;
624
- }
625
- .gxp-string-extract:hover {
626
- background: #4fc3f7;
627
- }
628
- .gxp-extract-form {
629
- display: flex;
630
- flex-direction: column;
631
- gap: 10px;
632
- }
633
- .gxp-form-group {
634
- display: flex;
635
- flex-direction: column;
636
- gap: 4px;
637
- }
638
- .gxp-form-group label {
639
- font-size: 11px;
640
- color: #888;
641
- }
642
- .gxp-form-group input {
643
- background: #2d2d2d;
644
- border: 1px solid #3d3d3d;
645
- border-radius: 4px;
646
- padding: 8px 10px;
647
- color: #e0e0e0;
648
- font-size: 12px;
649
- }
650
- .gxp-form-group input:focus {
651
- outline: none;
652
- border-color: #61dafb;
653
- }
654
- .gxp-extract-button {
655
- background: #28a745;
656
- color: white;
657
- border: none;
658
- padding: 10px 16px;
659
- border-radius: 4px;
660
- font-size: 13px;
661
- font-weight: 600;
662
- cursor: pointer;
663
- margin-top: 8px;
664
- }
665
- .gxp-extract-button:hover {
666
- background: #218838;
667
- }
668
- .gxp-extract-button:disabled {
669
- background: #6c757d;
670
- cursor: not-allowed;
671
- }
672
- .gxp-extract-status {
673
- margin-top: 10px;
674
- padding: 10px;
675
- border-radius: 4px;
676
- font-size: 12px;
677
- display: none;
678
- }
679
- .gxp-extract-status.success {
680
- display: block;
681
- background: #28a74520;
682
- border: 1px solid #28a745;
683
- color: #28a745;
684
- }
685
- .gxp-extract-status.error {
686
- display: block;
687
- background: #dc354520;
688
- border: 1px solid #dc3545;
689
- color: #dc3545;
690
- }
691
- .gxp-props-content {
692
- background: #2d2d2d;
693
- padding: 10px;
694
- border-radius: 4px;
695
- font-size: 11px;
696
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
697
- overflow-x: auto;
698
- max-height: 150px;
699
- margin: 0;
700
- }
701
- `;
702
-
703
- document.head.appendChild(style);
704
- document.body.appendChild(inspectorPanel);
705
-
706
- // Event handlers
707
- inspectorPanel.querySelector('.gxp-panel-close').addEventListener('click', () => {
708
- disableInspector();
709
- });
710
-
711
- inspectorPanel.querySelector('.gxp-extract-button').addEventListener('click', handleExtract);
712
-
713
- return inspectorPanel;
714
- }
715
-
716
- function updatePanel(el) {
717
- if (!inspectorPanel || !el) return;
718
-
719
- const vueInstance = getVueInstance(el);
720
- const info = getComponentInfo(vueInstance);
721
- const texts = getTextContent(el);
722
-
723
- // Update component info
724
- const nameEl = inspectorPanel.querySelector('.gxp-component-name');
725
- const fileEl = inspectorPanel.querySelector('.gxp-component-file');
726
-
727
- if (info) {
728
- nameEl.textContent = `<${info.name}>`;
729
- fileEl.textContent = info.file || 'Unknown file';
730
- } else {
731
- nameEl.textContent = `<${el.tagName.toLowerCase()}>`;
732
- fileEl.textContent = 'Not a Vue component';
733
- }
734
-
735
- // Update strings list
736
- const stringsSection = inspectorPanel.querySelector('.gxp-strings-section');
737
- const stringsList = inspectorPanel.querySelector('.gxp-strings-list');
738
-
739
- if (texts.length > 0) {
740
- stringsSection.style.display = 'block';
741
- stringsList.innerHTML = texts.map(text => `
742
- <div class="gxp-string-item" data-text="${escapeHtml(text)}">
743
- <span class="gxp-string-text">${escapeHtml(text)}</span>
744
- <button class="gxp-string-extract">Extract</button>
745
- </div>
746
- `).join('');
747
-
748
- // Add click handlers
749
- stringsList.querySelectorAll('.gxp-string-item').forEach(item => {
750
- item.querySelector('.gxp-string-extract').addEventListener('click', (e) => {
751
- e.stopPropagation();
752
- showExtractForm(item.dataset.text, info?.file);
753
- });
754
- });
755
- } else {
756
- stringsSection.style.display = 'none';
757
- }
758
-
759
- // Update props section
760
- const propsSection = inspectorPanel.querySelector('.gxp-props-section');
761
- const propsContent = inspectorPanel.querySelector('.gxp-props-content');
762
-
763
- if (info && Object.keys(info.props).length > 0) {
764
- propsSection.style.display = 'block';
765
- propsContent.textContent = JSON.stringify(info.props, null, 2);
766
- } else {
767
- propsSection.style.display = 'none';
768
- }
769
- }
770
-
771
- function showExtractForm(text, filePath) {
772
- const extractSection = inspectorPanel.querySelector('.gxp-extract-section');
773
- const textInput = inspectorPanel.querySelector('.gxp-extract-text');
774
- const keyInput = inspectorPanel.querySelector('.gxp-extract-key');
775
- const fileInput = inspectorPanel.querySelector('.gxp-extract-file');
776
- const statusEl = inspectorPanel.querySelector('.gxp-extract-status');
777
-
778
- extractSection.style.display = 'block';
779
- textInput.value = text;
780
- keyInput.value = textToKey(text);
781
- fileInput.value = filePath || '';
782
- statusEl.style.display = 'none';
783
- statusEl.className = 'gxp-extract-status';
784
- }
785
-
786
- async function handleExtract() {
787
- const textInput = inspectorPanel.querySelector('.gxp-extract-text');
788
- const keyInput = inspectorPanel.querySelector('.gxp-extract-key');
789
- const fileInput = inspectorPanel.querySelector('.gxp-extract-file');
790
- const button = inspectorPanel.querySelector('.gxp-extract-button');
791
- const statusEl = inspectorPanel.querySelector('.gxp-extract-status');
792
-
793
- const text = textInput.value;
794
- const key = keyInput.value;
795
- const filePath = fileInput.value;
796
-
797
- if (!text || !key || !filePath) {
798
- statusEl.textContent = 'All fields are required';
799
- statusEl.className = 'gxp-extract-status error';
800
- return;
801
- }
802
-
803
- button.disabled = true;
804
- button.textContent = 'Extracting...';
805
-
806
- try {
807
- const result = await extractString({
808
- text,
809
- key,
810
- filePath
811
- });
812
-
813
- if (result.success) {
814
- statusEl.textContent = `Success! Added getString('${key}') to ${filePath}`;
815
- statusEl.className = 'gxp-extract-status success';
816
- } else {
817
- statusEl.textContent = result.error || 'Extraction failed';
818
- statusEl.className = 'gxp-extract-status error';
819
- }
820
- } catch (error) {
821
- statusEl.textContent = error.message;
822
- statusEl.className = 'gxp-extract-status error';
823
- } finally {
824
- button.disabled = false;
825
- button.textContent = 'Extract to getString()';
826
- }
827
- }
828
-
829
- // ============================================================
830
- // Event Handlers
831
- // ============================================================
832
-
833
- function handleMouseMove(e) {
834
- if (!inspectorEnabled) return;
835
-
836
- const el = e.target;
837
- if (el === highlightOverlay || highlightOverlay?.contains(el) ||
838
- el === inspectorPanel || inspectorPanel?.contains(el)) {
839
- return;
840
- }
841
-
842
- if (el !== hoveredElement) {
843
- hoveredElement = el;
844
- updateHighlight(el);
845
- }
846
- }
847
-
848
- function handleClick(e) {
849
- if (!inspectorEnabled) return;
850
-
851
- const el = e.target;
852
- if (el === highlightOverlay || highlightOverlay?.contains(el) ||
853
- el === inspectorPanel || inspectorPanel?.contains(el) ||
854
- el === selectionHighlight || selectionHighlight?.contains(el)) {
855
- return;
856
- }
857
-
858
- e.preventDefault();
859
- e.stopPropagation();
860
-
861
- selectedElement = el;
862
- // Store for DevTools panel access via eval
863
- window.__gxpSelectedElement = el;
864
-
865
- updatePanel(el);
866
-
867
- // Show persistent selection highlight
868
- updateSelectionHighlight(el);
869
-
870
- // Hide hover highlight
871
- hideHighlight();
872
-
873
- // Disable inspector (selection mode) after selecting
874
- // This prevents accidental re-selection on next click
875
- disableInspector();
876
-
877
- // Send selection to background for DevTools panel
878
- sendElementToDevTools(el);
879
- }
880
-
881
- function sendElementToDevTools(el) {
882
- const vueInstance = getVueInstance(el);
883
- const info = getComponentInfo(vueInstance);
884
- const texts = getTextContent(el);
885
- const textsWithAttrs = getTextContentWithAttributes(el);
886
- const childGxpStrings = findChildGxpStrings(el);
887
-
888
- // Check if the element itself has gxp-string
889
- const gxpStringKey = getGxpStringKey(el);
890
- // Check for data-gxp-source attribute (injected by vite plugin)
891
- const sourceExpression = getGxpSourceExpression(el);
892
-
893
- const data = {
894
- tagName: el.tagName.toLowerCase(),
895
- component: info,
896
- texts: texts,
897
- textsWithAttributes: textsWithAttrs,
898
- childGxpStrings: childGxpStrings,
899
- gxpStringKey: gxpStringKey,
900
- isExtracted: gxpStringKey !== null,
901
- sourceExpression: sourceExpression,
902
- isDynamic: sourceExpression !== null
903
- };
904
-
905
- // Send to content script via postMessage (since we're in page context)
906
- // The content script will relay to the background script
907
- window.postMessage({
908
- type: 'GXP_INSPECTOR_MESSAGE',
909
- payload: {
910
- type: 'elementSelected',
911
- data: data
912
- }
913
- }, '*');
914
- }
915
-
916
- function handleKeyDown(e) {
917
- // Escape to disable inspector
918
- if (e.key === 'Escape' && inspectorEnabled) {
919
- disableInspector();
920
- }
921
-
922
- // Ctrl+Shift+I to toggle inspector
923
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'I') {
924
- e.preventDefault();
925
- toggleInspector();
926
- }
927
- }
928
-
929
- // ============================================================
930
- // Public API
931
- // ============================================================
932
-
933
- function enableInspector() {
934
- if (inspectorEnabled) return;
935
-
936
- inspectorEnabled = true;
937
- createHighlightOverlay();
938
- createInspectorPanel();
939
-
940
- // Add selecting class for pointer cursor
941
- document.body.classList.add('gxp-inspector-selecting');
942
-
943
- // Hide previous selection highlight when entering selection mode
944
- hideSelectionHighlight();
945
- selectedElement = null;
946
- window.__gxpSelectedElement = null;
947
-
948
- document.addEventListener('mousemove', handleMouseMove, true);
949
- document.addEventListener('click', handleClick, true);
950
- window.addEventListener('scroll', updateSelectionPosition, true);
951
- window.addEventListener('resize', updateSelectionPosition);
952
-
953
- console.log('[GxP Inspector] Enabled - Hover over elements to inspect');
954
- }
955
-
956
- function disableInspector() {
957
- if (!inspectorEnabled) return;
958
-
959
- inspectorEnabled = false;
960
- hideHighlight();
961
-
962
- // Remove selecting class for pointer cursor
963
- document.body.classList.remove('gxp-inspector-selecting');
964
-
965
- if (inspectorPanel) {
966
- inspectorPanel.remove();
967
- inspectorPanel = null;
968
- }
969
-
970
- document.removeEventListener('mousemove', handleMouseMove, true);
971
- document.removeEventListener('click', handleClick, true);
972
- // Keep scroll/resize listeners for selection highlight position updates
973
- // They will be removed when a new selection starts
974
-
975
- // Don't clear selectedElement here - we want to keep the selection
976
- hoveredElement = null;
977
-
978
- console.log('[GxP Inspector] Disabled - Selection preserved');
979
- }
980
-
981
- // Clear selection completely (called when user wants to deselect)
982
- function clearSelection() {
983
- hideSelectionHighlight();
984
- selectedElement = null;
985
- window.__gxpSelectedElement = null;
986
- window.removeEventListener('scroll', updateSelectionPosition, true);
987
- window.removeEventListener('resize', updateSelectionPosition);
988
- }
989
-
990
- function toggleInspector() {
991
- if (inspectorEnabled) {
992
- disableInspector();
993
- } else {
994
- enableInspector();
995
- }
996
- }
997
-
998
- // ============================================================
999
- // Utility Functions
1000
- // ============================================================
1001
-
1002
- /**
1003
- * Build a descriptive label for an element
1004
- * Format: ComponentName::element::gxp-string-key
1005
- * Examples:
1006
- * - DemoPage::h1::welcome_title
1007
- * - DemoPage::h1 (no gxp-string)
1008
- * - div::gxp-string-key (no Vue component)
1009
- * - div (plain element)
1010
- */
1011
- function buildElementLabel(el) {
1012
- const parts = [];
1013
-
1014
- // Get Vue component name
1015
- const vueInstance = getVueInstance(el);
1016
- const info = getComponentInfo(vueInstance);
1017
- if (info && info.name) {
1018
- parts.push(info.name);
1019
- }
1020
-
1021
- // Add element tag name
1022
- parts.push(el.tagName.toLowerCase());
1023
-
1024
- // Check for gxp-string attribute
1025
- const gxpStringKey = el.getAttribute('gxp-string');
1026
- if (gxpStringKey) {
1027
- parts.push(gxpStringKey);
1028
- }
1029
-
1030
- // Check for gxp-src attribute (for images/assets)
1031
- const gxpSrcKey = el.getAttribute('gxp-src');
1032
- if (gxpSrcKey && !gxpStringKey) {
1033
- parts.push(gxpSrcKey);
1034
- }
1035
-
1036
- return parts.join('::');
1037
- }
1038
-
1039
- function textToKey(text) {
1040
- return text
1041
- .toLowerCase()
1042
- .replace(/[^a-z0-9\s]/g, '')
1043
- .replace(/\s+/g, '_')
1044
- .substring(0, 40)
1045
- .replace(/_+$/, '');
1046
- }
1047
-
1048
- function escapeHtml(text) {
1049
- const div = document.createElement('div');
1050
- div.textContent = text;
1051
- return div.innerHTML;
1052
- }
1053
-
1054
- // ============================================================
1055
- // Initialize
1056
- // ============================================================
1057
-
1058
- // Add keyboard listener
1059
- document.addEventListener('keydown', handleKeyDown);
1060
-
1061
- // Expose API
1062
- window.gxpInspector = {
1063
- enable: enableInspector,
1064
- disable: disableInspector,
1065
- toggle: toggleInspector,
1066
- isEnabled: () => inspectorEnabled,
1067
- clearSelection: clearSelection,
1068
- ping: ping
1069
- };
1070
-
1071
- // Listen for messages from content script (which relays from popup/background)
1072
- window.addEventListener('message', (event) => {
1073
- if (event.source !== window) return;
1074
- if (event.data?.type === 'GXP_INSPECTOR_ACTION') {
1075
- const action = event.data.action;
1076
- if (action === 'toggleInspector') {
1077
- toggleInspector();
1078
- } else if (action === 'enable') {
1079
- enableInspector();
1080
- } else if (action === 'disable') {
1081
- disableInspector();
1082
- }
1083
- }
1084
- });
1085
-
1086
- console.log('[GxP Inspector] Loaded in page context. Press Ctrl+Shift+I to toggle inspector.');
1087
- })();