@gxp-dev/tools 2.0.63 → 2.0.65
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.
- package/README.md +32 -31
- package/bin/gx-devtools.js +74 -54
- package/bin/lib/cli.js +23 -21
- package/bin/lib/commands/add-dependency.js +366 -325
- package/bin/lib/commands/assets.js +137 -139
- package/bin/lib/commands/build.js +169 -174
- package/bin/lib/commands/datastore.js +181 -183
- package/bin/lib/commands/dev.js +127 -131
- package/bin/lib/commands/extensions.js +147 -149
- package/bin/lib/commands/extract-config.js +73 -67
- package/bin/lib/commands/index.js +12 -12
- package/bin/lib/commands/init.js +342 -240
- package/bin/lib/commands/publish.js +69 -75
- package/bin/lib/commands/socket.js +69 -69
- package/bin/lib/commands/ssl.js +14 -14
- package/bin/lib/constants.js +10 -24
- package/bin/lib/tui/App.tsx +761 -705
- package/bin/lib/tui/components/AIPanel.tsx +191 -171
- package/bin/lib/tui/components/CommandInput.tsx +394 -343
- package/bin/lib/tui/components/GeminiPanel.tsx +175 -151
- package/bin/lib/tui/components/Header.tsx +23 -21
- package/bin/lib/tui/components/LogPanel.tsx +244 -220
- package/bin/lib/tui/components/TabBar.tsx +50 -48
- package/bin/lib/tui/components/WelcomeScreen.tsx +126 -71
- package/bin/lib/tui/index.tsx +37 -39
- package/bin/lib/tui/services/AIService.ts +518 -462
- package/bin/lib/tui/services/ExtensionService.ts +140 -129
- package/bin/lib/tui/services/GeminiService.ts +367 -337
- package/bin/lib/tui/services/ServiceManager.ts +344 -322
- package/bin/lib/tui/services/SocketService.ts +168 -168
- package/bin/lib/tui/services/ViteService.ts +88 -88
- package/bin/lib/tui/services/index.ts +47 -22
- package/bin/lib/utils/ai-scaffold.js +291 -280
- package/bin/lib/utils/extract-config.js +157 -140
- package/bin/lib/utils/files.js +82 -86
- package/bin/lib/utils/index.js +7 -7
- package/bin/lib/utils/paths.js +34 -34
- package/bin/lib/utils/prompts.js +194 -169
- package/bin/lib/utils/ssl.js +79 -81
- package/browser-extensions/README.md +0 -1
- package/browser-extensions/chrome/background.js +244 -237
- package/browser-extensions/chrome/content.js +32 -29
- package/browser-extensions/chrome/devtools.html +7 -7
- package/browser-extensions/chrome/devtools.js +19 -19
- package/browser-extensions/chrome/inspector.js +802 -767
- package/browser-extensions/chrome/manifest.json +71 -63
- package/browser-extensions/chrome/panel.html +674 -636
- package/browser-extensions/chrome/panel.js +722 -712
- package/browser-extensions/chrome/popup.html +586 -543
- package/browser-extensions/chrome/popup.js +282 -244
- package/browser-extensions/chrome/rules.json +1 -1
- package/browser-extensions/chrome/test-chrome.html +216 -136
- package/browser-extensions/chrome/test-mixed-content.html +284 -189
- package/browser-extensions/chrome/test-uri-pattern.html +221 -198
- package/browser-extensions/firefox/README.md +9 -6
- package/browser-extensions/firefox/background.js +221 -218
- package/browser-extensions/firefox/content.js +55 -52
- package/browser-extensions/firefox/debug-errors.html +386 -228
- package/browser-extensions/firefox/debug-https.html +153 -105
- package/browser-extensions/firefox/devtools.html +7 -7
- package/browser-extensions/firefox/devtools.js +23 -20
- package/browser-extensions/firefox/inspector.js +802 -767
- package/browser-extensions/firefox/manifest.json +68 -68
- package/browser-extensions/firefox/panel.html +674 -636
- package/browser-extensions/firefox/panel.js +722 -712
- package/browser-extensions/firefox/popup.html +572 -535
- package/browser-extensions/firefox/popup.js +281 -236
- package/browser-extensions/firefox/test-gramercy.html +170 -125
- package/browser-extensions/firefox/test-imports.html +59 -55
- package/browser-extensions/firefox/test-masking.html +231 -140
- package/browser-extensions/firefox/test-uri-pattern.html +221 -198
- package/dist/tui/App.d.ts +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +154 -150
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/AIPanel.d.ts.map +1 -1
- package/dist/tui/components/AIPanel.js +42 -35
- package/dist/tui/components/AIPanel.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +92 -62
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/GeminiPanel.d.ts.map +1 -1
- package/dist/tui/components/GeminiPanel.js +37 -30
- package/dist/tui/components/GeminiPanel.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +1 -1
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +26 -24
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.d.ts +2 -2
- package/dist/tui/components/TabBar.d.ts.map +1 -1
- package/dist/tui/components/TabBar.js +11 -11
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/components/WelcomeScreen.d.ts.map +1 -1
- package/dist/tui/components/WelcomeScreen.js +6 -6
- package/dist/tui/components/WelcomeScreen.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/services/AIService.d.ts +2 -2
- package/dist/tui/services/AIService.d.ts.map +1 -1
- package/dist/tui/services/AIService.js +165 -125
- package/dist/tui/services/AIService.js.map +1 -1
- package/dist/tui/services/ExtensionService.d.ts +1 -1
- package/dist/tui/services/ExtensionService.d.ts.map +1 -1
- package/dist/tui/services/ExtensionService.js +33 -26
- package/dist/tui/services/ExtensionService.js.map +1 -1
- package/dist/tui/services/GeminiService.d.ts +1 -1
- package/dist/tui/services/GeminiService.d.ts.map +1 -1
- package/dist/tui/services/GeminiService.js +87 -76
- package/dist/tui/services/GeminiService.js.map +1 -1
- package/dist/tui/services/ServiceManager.d.ts +3 -3
- package/dist/tui/services/ServiceManager.d.ts.map +1 -1
- package/dist/tui/services/ServiceManager.js +72 -58
- package/dist/tui/services/ServiceManager.js.map +1 -1
- package/dist/tui/services/SocketService.d.ts.map +1 -1
- package/dist/tui/services/SocketService.js +32 -32
- package/dist/tui/services/SocketService.js.map +1 -1
- package/dist/tui/services/ViteService.d.ts.map +1 -1
- package/dist/tui/services/ViteService.js +26 -28
- package/dist/tui/services/ViteService.js.map +1 -1
- package/dist/tui/services/index.d.ts +6 -6
- package/dist/tui/services/index.d.ts.map +1 -1
- package/dist/tui/services/index.js +6 -6
- package/dist/tui/services/index.js.map +1 -1
- package/mcp/gxp-api-server.js +83 -81
- package/package.json +109 -93
- package/runtime/PortalContainer.vue +258 -234
- package/runtime/dev-tools/DevToolsModal.vue +153 -155
- package/runtime/dev-tools/LayoutSwitcher.vue +144 -140
- package/runtime/dev-tools/MockDataEditor.vue +456 -433
- package/runtime/dev-tools/SocketSimulator.vue +379 -371
- package/runtime/dev-tools/StoreInspector.vue +517 -455
- package/runtime/dev-tools/index.js +5 -5
- package/runtime/fallback-layouts/PrivateLayout.vue +2 -2
- package/runtime/fallback-layouts/PublicLayout.vue +2 -2
- package/runtime/fallback-layouts/SystemLayout.vue +2 -2
- package/runtime/gxpStringsPlugin.js +159 -134
- package/runtime/index.html +17 -19
- package/runtime/main.js +24 -22
- package/runtime/mock-api/auth-middleware.js +15 -15
- package/runtime/mock-api/image-generator.js +46 -46
- package/runtime/mock-api/index.js +55 -55
- package/runtime/mock-api/response-generator.js +116 -105
- package/runtime/mock-api/route-generator.js +107 -84
- package/runtime/mock-api/socket-triggers.js +94 -93
- package/runtime/mock-api/spec-loader.js +79 -80
- package/runtime/package.json +3 -0
- package/runtime/server.js +68 -68
- package/runtime/stores/gxpPortalConfigStore.js +204 -186
- package/runtime/stores/index.js +2 -2
- package/runtime/vite-inspector-plugin.js +858 -707
- package/runtime/vite-source-tracker-plugin.js +132 -113
- package/runtime/vite.config.js +191 -139
- package/scripts/launch-chrome.js +41 -41
- package/scripts/pack-chrome.js +38 -39
- package/socket-events/AiSessionMessageCreated.json +17 -17
- package/socket-events/SocialStreamPostCreated.json +23 -23
- package/socket-events/SocialStreamPostVariantCompleted.json +22 -22
- package/template/.claude/agents/gxp-developer.md +100 -99
- package/template/.claude/settings.json +7 -7
- package/template/AGENTS.md +30 -23
- package/template/GEMINI.md +20 -20
- package/template/README.md +70 -53
- package/template/app-manifest.json +2 -4
- package/template/configuration.json +10 -10
- package/template/default-styling.css +1 -1
- package/template/index.html +18 -20
- package/template/main.js +24 -22
- package/template/src/DemoPage.vue +415 -362
- package/template/src/Plugin.vue +76 -85
- package/template/src/stores/index.js +3 -3
- package/template/src/stores/test-data.json +164 -172
- package/template/theme-layouts/AdditionalStyling.css +50 -50
- package/template/theme-layouts/PrivateLayout.vue +8 -12
- package/template/theme-layouts/PublicLayout.vue +8 -12
- package/template/theme-layouts/SystemLayout.vue +8 -12
- package/template/vite.extend.js +45 -0
- package/template/vite.config.js +0 -409
|
@@ -13,299 +13,306 @@
|
|
|
13
13
|
|
|
14
14
|
// Check if we're in the page context (already injected) vs content script context
|
|
15
15
|
// In page context, neither 'browser' nor 'chrome' runtime APIs are available
|
|
16
|
-
const isContentScriptContext =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
const isContentScriptContext =
|
|
17
|
+
(typeof browser !== "undefined" && browser.runtime) ||
|
|
18
|
+
(typeof chrome !== "undefined" && chrome.runtime)
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
isContentScriptContext &&
|
|
22
|
+
typeof window.__gxpInspectorInjected === "undefined"
|
|
23
|
+
) {
|
|
24
|
+
// We're in the content script context - inject into page
|
|
25
|
+
const runtime = typeof browser !== "undefined" ? browser : chrome
|
|
26
|
+
const script = document.createElement("script")
|
|
27
|
+
script.src = runtime.runtime.getURL("inspector.js")
|
|
28
|
+
script.onload = function () {
|
|
29
|
+
this.remove()
|
|
30
|
+
}
|
|
31
|
+
;(document.head || document.documentElement).appendChild(script)
|
|
32
|
+
|
|
33
|
+
// Also set up message relay from page to extension
|
|
34
|
+
window.addEventListener("message", (event) => {
|
|
35
|
+
if (event.source !== window) return
|
|
36
|
+
if (event.data?.type === "GXP_INSPECTOR_MESSAGE") {
|
|
37
|
+
runtime.runtime.sendMessage(event.data.payload)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Mark that content script has run (for content script context)
|
|
42
|
+
window.__gxpInspectorContentScriptLoaded = true
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
// Mark as injected (for both contexts)
|
|
42
|
-
window.__gxpInspectorInjected = true
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
46
|
+
window.__gxpInspectorInjected = true
|
|
47
|
+
;(function () {
|
|
48
|
+
"use strict"
|
|
49
|
+
|
|
50
|
+
// If gxpInspector already exists, we're done (prevent double init)
|
|
51
|
+
if (window.gxpInspector) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Configuration
|
|
56
|
+
const DEV_SERVER_URL = "https://localhost:3060"
|
|
57
|
+
const API_PREFIX = "/__gxp-inspector"
|
|
58
|
+
|
|
59
|
+
// State
|
|
60
|
+
let inspectorEnabled = false
|
|
61
|
+
let highlightOverlay = null
|
|
62
|
+
let inspectorPanel = null
|
|
63
|
+
let selectedElement = null
|
|
64
|
+
let hoveredElement = null
|
|
65
|
+
let selectionHighlight = null // Persistent highlight for selected element
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// API Communication
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
async function apiCall(endpoint, options = {}) {
|
|
72
|
+
const url = `${DEV_SERVER_URL}${API_PREFIX}${endpoint}`
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
...options,
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
...options.headers,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
return await response.json()
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("[GxP Inspector] API Error:", error)
|
|
84
|
+
return { success: false, error: error.message }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function ping() {
|
|
89
|
+
return apiCall("/ping")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function extractString(data) {
|
|
93
|
+
return apiCall("/extract-string", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
body: JSON.stringify(data),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function getStrings() {
|
|
100
|
+
return apiCall("/strings")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================
|
|
104
|
+
// Vue Component Detection
|
|
105
|
+
// ============================================================
|
|
106
|
+
|
|
107
|
+
function getVueInstance(el) {
|
|
108
|
+
// Vue 3 detection
|
|
109
|
+
if (el.__vueParentComponent) {
|
|
110
|
+
return el.__vueParentComponent
|
|
111
|
+
}
|
|
112
|
+
// Walk up to find Vue component
|
|
113
|
+
let current = el
|
|
114
|
+
while (current) {
|
|
115
|
+
if (current.__vueParentComponent) {
|
|
116
|
+
return current.__vueParentComponent
|
|
117
|
+
}
|
|
118
|
+
current = current.parentElement
|
|
119
|
+
}
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getComponentInfo(vueInstance) {
|
|
124
|
+
if (!vueInstance) return null
|
|
125
|
+
|
|
126
|
+
const type = vueInstance.type
|
|
127
|
+
const name =
|
|
128
|
+
type?.name ||
|
|
129
|
+
type?.__name ||
|
|
130
|
+
type?.__file?.split("/").pop()?.replace(".vue", "") ||
|
|
131
|
+
"Anonymous"
|
|
132
|
+
const file = type?.__file || null
|
|
133
|
+
|
|
134
|
+
// Helper to safely serialize a value (handles circular refs, functions, etc.)
|
|
135
|
+
function safeSerialize(value) {
|
|
136
|
+
if (value === null || value === undefined) return value
|
|
137
|
+
if (typeof value === "function") return "[Function]"
|
|
138
|
+
if (typeof value !== "object") return value
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Try JSON stringify/parse to get a clean copy
|
|
142
|
+
return JSON.parse(JSON.stringify(value))
|
|
143
|
+
} catch {
|
|
144
|
+
// If that fails, return a string representation
|
|
145
|
+
if (Array.isArray(value)) return `[Array(${value.length})]`
|
|
146
|
+
return "{...}"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Get props
|
|
151
|
+
const props = {}
|
|
152
|
+
if (vueInstance.props) {
|
|
153
|
+
Object.keys(vueInstance.props).forEach((key) => {
|
|
154
|
+
props[key] = safeSerialize(vueInstance.props[key])
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get component data/state
|
|
159
|
+
const data = {}
|
|
160
|
+
if (vueInstance.setupState) {
|
|
161
|
+
Object.keys(vueInstance.setupState).forEach((key) => {
|
|
162
|
+
const value = vueInstance.setupState[key]
|
|
163
|
+
if (typeof value !== "function") {
|
|
164
|
+
data[key] = safeSerialize(value)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { name, file, props, data }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getTextContent(el) {
|
|
173
|
+
// Get direct text content, excluding child elements
|
|
174
|
+
const texts = []
|
|
175
|
+
el.childNodes.forEach((node) => {
|
|
176
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
177
|
+
const text = node.textContent.trim()
|
|
178
|
+
if (text) texts.push(text)
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
return texts
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if an element has a gxp-string attribute (indicating it's already extracted)
|
|
186
|
+
*/
|
|
187
|
+
function getGxpStringKey(el) {
|
|
188
|
+
return el?.getAttribute?.("gxp-string") || null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check for a data-gxp-expr attribute on the element
|
|
193
|
+
* The vite-source-tracker-plugin adds this attribute in dev mode
|
|
194
|
+
* @param {Element} el - The element to check
|
|
195
|
+
* @returns {string|null} - The source expression or null
|
|
196
|
+
*/
|
|
197
|
+
function getGxpSourceExpression(el) {
|
|
198
|
+
if (!el || !el.getAttribute) return null
|
|
199
|
+
return el.getAttribute("data-gxp-expr") || null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get text content with gxp-string attribute info
|
|
204
|
+
* Returns array of objects: { text, gxpStringKey, isExtracted, sourceExpression, isDynamic }
|
|
205
|
+
*/
|
|
206
|
+
function getTextContentWithAttributes(el) {
|
|
207
|
+
const results = []
|
|
208
|
+
|
|
209
|
+
// Check if this element itself has gxp-string
|
|
210
|
+
const elementKey = getGxpStringKey(el)
|
|
211
|
+
// Check for data-gxp-source attribute (injected by vite plugin for {{ expressions }})
|
|
212
|
+
const sourceExpression = getGxpSourceExpression(el)
|
|
213
|
+
|
|
214
|
+
el.childNodes.forEach((node) => {
|
|
215
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
216
|
+
const text = node.textContent.trim()
|
|
217
|
+
if (text) {
|
|
218
|
+
results.push({
|
|
219
|
+
text: text,
|
|
220
|
+
gxpStringKey: elementKey,
|
|
221
|
+
isExtracted: elementKey !== null,
|
|
222
|
+
sourceExpression: sourceExpression,
|
|
223
|
+
isDynamic: sourceExpression !== null,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return results
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Find all child elements with gxp-string attributes
|
|
234
|
+
*/
|
|
235
|
+
function findChildGxpStrings(el) {
|
|
236
|
+
const strings = []
|
|
237
|
+
const elements = el.querySelectorAll("[gxp-string]")
|
|
238
|
+
|
|
239
|
+
elements.forEach((child) => {
|
|
240
|
+
const key = child.getAttribute("gxp-string")
|
|
241
|
+
const text = child.textContent.trim()
|
|
242
|
+
if (key && text) {
|
|
243
|
+
strings.push({
|
|
244
|
+
key: key,
|
|
245
|
+
text: text,
|
|
246
|
+
element: child.tagName.toLowerCase(),
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
return strings
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Analyze text content to detect if it comes from getString() calls
|
|
256
|
+
* Returns an array of text info objects with type: 'raw' or 'getString'
|
|
257
|
+
*/
|
|
258
|
+
function analyzeTextContent(el, vueInstance) {
|
|
259
|
+
const textInfos = []
|
|
260
|
+
const texts = getTextContent(el)
|
|
261
|
+
|
|
262
|
+
// Get the component's source info to help detect getString usage
|
|
263
|
+
const componentFile = vueInstance?.type?.__file || null
|
|
264
|
+
|
|
265
|
+
// Try to detect getString calls by checking if this element's text
|
|
266
|
+
// is likely from a getString call. We look for patterns in the rendered output
|
|
267
|
+
// and can cross-reference with the app-manifest.json via API later.
|
|
268
|
+
|
|
269
|
+
texts.forEach((text, index) => {
|
|
270
|
+
// Default to raw text
|
|
271
|
+
const textInfo = {
|
|
272
|
+
text: text,
|
|
273
|
+
type: "raw",
|
|
274
|
+
index: index,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// We'll mark it as potentially from getString if we can detect it
|
|
278
|
+
// The panel will verify against the manifest
|
|
279
|
+
textInfos.push(textInfo)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
return textInfos
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Try to find getString calls in the Vue component by inspecting
|
|
287
|
+
* the component's template bindings (if accessible)
|
|
288
|
+
*/
|
|
289
|
+
function findGetStringCalls(vueInstance, filePath) {
|
|
290
|
+
const getStringCalls = []
|
|
291
|
+
|
|
292
|
+
if (!vueInstance) return getStringCalls
|
|
293
|
+
|
|
294
|
+
// Check setupState for gxpStore reference
|
|
295
|
+
const hasGxpStore = vueInstance.setupState?.gxpStore !== undefined
|
|
296
|
+
|
|
297
|
+
// We can't directly see the template, but we can check for gxpStore usage
|
|
298
|
+
// The actual verification happens on the server side by checking the source file
|
|
299
|
+
|
|
300
|
+
return { hasGxpStore, getStringCalls }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================================
|
|
304
|
+
// Overlay UI
|
|
305
|
+
// ============================================================
|
|
306
|
+
|
|
307
|
+
function createHighlightOverlay() {
|
|
308
|
+
if (highlightOverlay) return highlightOverlay
|
|
309
|
+
|
|
310
|
+
highlightOverlay = document.createElement("div")
|
|
311
|
+
highlightOverlay.id = "gxp-inspector-highlight"
|
|
312
|
+
|
|
313
|
+
const style = document.createElement("style")
|
|
314
|
+
style.id = "gxp-highlight-style"
|
|
315
|
+
style.textContent = `
|
|
309
316
|
/* Pointer cursor when in selection mode */
|
|
310
317
|
body.gxp-inspector-selecting,
|
|
311
318
|
body.gxp-inspector-selecting * {
|
|
@@ -334,53 +341,53 @@ window.__gxpInspectorInjected = true;
|
|
|
334
341
|
border-radius: 3px 3px 0 0;
|
|
335
342
|
white-space: nowrap;
|
|
336
343
|
}
|
|
337
|
-
|
|
344
|
+
`
|
|
338
345
|
|
|
339
|
-
|
|
346
|
+
highlightOverlay.innerHTML = `<div class="gxp-highlight-label"></div>`
|
|
340
347
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
348
|
+
if (!document.getElementById("gxp-highlight-style")) {
|
|
349
|
+
document.head.appendChild(style)
|
|
350
|
+
}
|
|
351
|
+
document.body.appendChild(highlightOverlay)
|
|
352
|
+
return highlightOverlay
|
|
353
|
+
}
|
|
347
354
|
|
|
348
|
-
|
|
349
|
-
|
|
355
|
+
function updateHighlight(el) {
|
|
356
|
+
if (!el || !highlightOverlay) return
|
|
350
357
|
|
|
351
|
-
|
|
352
|
-
|
|
358
|
+
const rect = el.getBoundingClientRect()
|
|
359
|
+
const label = highlightOverlay.querySelector(".gxp-highlight-label")
|
|
353
360
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
// Position the highlight overlay directly
|
|
362
|
+
highlightOverlay.style.display = "block"
|
|
363
|
+
highlightOverlay.style.left = `${rect.left}px`
|
|
364
|
+
highlightOverlay.style.top = `${rect.top}px`
|
|
365
|
+
highlightOverlay.style.width = `${rect.width}px`
|
|
366
|
+
highlightOverlay.style.height = `${rect.height}px`
|
|
360
367
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
368
|
+
// Build label: Component::element::gxp-string-key
|
|
369
|
+
label.textContent = buildElementLabel(el)
|
|
370
|
+
}
|
|
364
371
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
372
|
+
function hideHighlight() {
|
|
373
|
+
if (highlightOverlay) {
|
|
374
|
+
highlightOverlay.style.display = "none"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
370
377
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
378
|
+
// ============================================================
|
|
379
|
+
// Selection Highlight (persistent border on selected element)
|
|
380
|
+
// ============================================================
|
|
374
381
|
|
|
375
|
-
|
|
376
|
-
|
|
382
|
+
function createSelectionHighlight() {
|
|
383
|
+
if (selectionHighlight) return selectionHighlight
|
|
377
384
|
|
|
378
|
-
|
|
379
|
-
|
|
385
|
+
selectionHighlight = document.createElement("div")
|
|
386
|
+
selectionHighlight.id = "gxp-inspector-selection"
|
|
380
387
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
388
|
+
const style = document.createElement("style")
|
|
389
|
+
style.id = "gxp-selection-style"
|
|
390
|
+
style.textContent = `
|
|
384
391
|
#gxp-inspector-selection {
|
|
385
392
|
position: fixed;
|
|
386
393
|
pointer-events: none;
|
|
@@ -423,61 +430,65 @@ window.__gxpInspectorInjected = true;
|
|
|
423
430
|
inset 0 0 30px rgba(97, 218, 251, 0.15);
|
|
424
431
|
}
|
|
425
432
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
433
|
+
`
|
|
434
|
+
|
|
435
|
+
selectionHighlight.innerHTML = `<div class="gxp-selection-label"></div>`
|
|
436
|
+
|
|
437
|
+
if (!document.getElementById("gxp-selection-style")) {
|
|
438
|
+
document.head.appendChild(style)
|
|
439
|
+
}
|
|
440
|
+
document.body.appendChild(selectionHighlight)
|
|
441
|
+
return selectionHighlight
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function updateSelectionHighlight(el) {
|
|
445
|
+
if (!el) {
|
|
446
|
+
hideSelectionHighlight()
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
createSelectionHighlight()
|
|
451
|
+
const rect = el.getBoundingClientRect()
|
|
452
|
+
const label = selectionHighlight.querySelector(".gxp-selection-label")
|
|
453
|
+
|
|
454
|
+
// Position the main overlay element directly
|
|
455
|
+
selectionHighlight.style.display = "block"
|
|
456
|
+
selectionHighlight.style.left = `${rect.left}px`
|
|
457
|
+
selectionHighlight.style.top = `${rect.top}px`
|
|
458
|
+
selectionHighlight.style.width = `${rect.width}px`
|
|
459
|
+
selectionHighlight.style.height = `${rect.height}px`
|
|
460
|
+
|
|
461
|
+
// Build label: Component::element::gxp-string-key
|
|
462
|
+
label.textContent = buildElementLabel(el)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function hideSelectionHighlight() {
|
|
466
|
+
if (selectionHighlight) {
|
|
467
|
+
selectionHighlight.style.display = "none"
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Update selection highlight position on scroll/resize
|
|
472
|
+
function updateSelectionPosition() {
|
|
473
|
+
if (
|
|
474
|
+
selectedElement &&
|
|
475
|
+
selectionHighlight &&
|
|
476
|
+
selectionHighlight.style.display !== "none"
|
|
477
|
+
) {
|
|
478
|
+
updateSelectionHighlight(selectedElement)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ============================================================
|
|
483
|
+
// Inspector Panel
|
|
484
|
+
// ============================================================
|
|
485
|
+
|
|
486
|
+
function createInspectorPanel() {
|
|
487
|
+
if (inspectorPanel) return inspectorPanel
|
|
488
|
+
|
|
489
|
+
inspectorPanel = document.createElement("div")
|
|
490
|
+
inspectorPanel.id = "gxp-inspector-panel"
|
|
491
|
+
inspectorPanel.innerHTML = `
|
|
481
492
|
<div class="gxp-panel-header">
|
|
482
493
|
<span class="gxp-panel-title">GxP Component Inspector</span>
|
|
483
494
|
<button class="gxp-panel-close">×</button>
|
|
@@ -516,10 +527,10 @@ window.__gxpInspectorInjected = true;
|
|
|
516
527
|
<pre class="gxp-props-content"></pre>
|
|
517
528
|
</div>
|
|
518
529
|
</div>
|
|
519
|
-
|
|
530
|
+
`
|
|
520
531
|
|
|
521
|
-
|
|
522
|
-
|
|
532
|
+
const style = document.createElement("style")
|
|
533
|
+
style.textContent = `
|
|
523
534
|
#gxp-inspector-panel {
|
|
524
535
|
position: fixed;
|
|
525
536
|
bottom: 20px;
|
|
@@ -698,390 +709,414 @@ window.__gxpInspectorInjected = true;
|
|
|
698
709
|
max-height: 150px;
|
|
699
710
|
margin: 0;
|
|
700
711
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
712
|
+
`
|
|
713
|
+
|
|
714
|
+
document.head.appendChild(style)
|
|
715
|
+
document.body.appendChild(inspectorPanel)
|
|
716
|
+
|
|
717
|
+
// Event handlers
|
|
718
|
+
inspectorPanel
|
|
719
|
+
.querySelector(".gxp-panel-close")
|
|
720
|
+
.addEventListener("click", () => {
|
|
721
|
+
disableInspector()
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
inspectorPanel
|
|
725
|
+
.querySelector(".gxp-extract-button")
|
|
726
|
+
.addEventListener("click", handleExtract)
|
|
727
|
+
|
|
728
|
+
return inspectorPanel
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function updatePanel(el) {
|
|
732
|
+
if (!inspectorPanel || !el) return
|
|
733
|
+
|
|
734
|
+
const vueInstance = getVueInstance(el)
|
|
735
|
+
const info = getComponentInfo(vueInstance)
|
|
736
|
+
const texts = getTextContent(el)
|
|
737
|
+
|
|
738
|
+
// Update component info
|
|
739
|
+
const nameEl = inspectorPanel.querySelector(".gxp-component-name")
|
|
740
|
+
const fileEl = inspectorPanel.querySelector(".gxp-component-file")
|
|
741
|
+
|
|
742
|
+
if (info) {
|
|
743
|
+
nameEl.textContent = `<${info.name}>`
|
|
744
|
+
fileEl.textContent = info.file || "Unknown file"
|
|
745
|
+
} else {
|
|
746
|
+
nameEl.textContent = `<${el.tagName.toLowerCase()}>`
|
|
747
|
+
fileEl.textContent = "Not a Vue component"
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Update strings list
|
|
751
|
+
const stringsSection = inspectorPanel.querySelector(".gxp-strings-section")
|
|
752
|
+
const stringsList = inspectorPanel.querySelector(".gxp-strings-list")
|
|
753
|
+
|
|
754
|
+
if (texts.length > 0) {
|
|
755
|
+
stringsSection.style.display = "block"
|
|
756
|
+
stringsList.innerHTML = texts
|
|
757
|
+
.map(
|
|
758
|
+
(text) => `
|
|
742
759
|
<div class="gxp-string-item" data-text="${escapeHtml(text)}">
|
|
743
760
|
<span class="gxp-string-text">${escapeHtml(text)}</span>
|
|
744
761
|
<button class="gxp-string-extract">Extract</button>
|
|
745
762
|
</div>
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
763
|
+
`,
|
|
764
|
+
)
|
|
765
|
+
.join("")
|
|
766
|
+
|
|
767
|
+
// Add click handlers
|
|
768
|
+
stringsList.querySelectorAll(".gxp-string-item").forEach((item) => {
|
|
769
|
+
item
|
|
770
|
+
.querySelector(".gxp-string-extract")
|
|
771
|
+
.addEventListener("click", (e) => {
|
|
772
|
+
e.stopPropagation()
|
|
773
|
+
showExtractForm(item.dataset.text, info?.file)
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
} else {
|
|
777
|
+
stringsSection.style.display = "none"
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Update props section
|
|
781
|
+
const propsSection = inspectorPanel.querySelector(".gxp-props-section")
|
|
782
|
+
const propsContent = inspectorPanel.querySelector(".gxp-props-content")
|
|
783
|
+
|
|
784
|
+
if (info && Object.keys(info.props).length > 0) {
|
|
785
|
+
propsSection.style.display = "block"
|
|
786
|
+
propsContent.textContent = JSON.stringify(info.props, null, 2)
|
|
787
|
+
} else {
|
|
788
|
+
propsSection.style.display = "none"
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function showExtractForm(text, filePath) {
|
|
793
|
+
const extractSection = inspectorPanel.querySelector(".gxp-extract-section")
|
|
794
|
+
const textInput = inspectorPanel.querySelector(".gxp-extract-text")
|
|
795
|
+
const keyInput = inspectorPanel.querySelector(".gxp-extract-key")
|
|
796
|
+
const fileInput = inspectorPanel.querySelector(".gxp-extract-file")
|
|
797
|
+
const statusEl = inspectorPanel.querySelector(".gxp-extract-status")
|
|
798
|
+
|
|
799
|
+
extractSection.style.display = "block"
|
|
800
|
+
textInput.value = text
|
|
801
|
+
keyInput.value = textToKey(text)
|
|
802
|
+
fileInput.value = filePath || ""
|
|
803
|
+
statusEl.style.display = "none"
|
|
804
|
+
statusEl.className = "gxp-extract-status"
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async function handleExtract() {
|
|
808
|
+
const textInput = inspectorPanel.querySelector(".gxp-extract-text")
|
|
809
|
+
const keyInput = inspectorPanel.querySelector(".gxp-extract-key")
|
|
810
|
+
const fileInput = inspectorPanel.querySelector(".gxp-extract-file")
|
|
811
|
+
const button = inspectorPanel.querySelector(".gxp-extract-button")
|
|
812
|
+
const statusEl = inspectorPanel.querySelector(".gxp-extract-status")
|
|
813
|
+
|
|
814
|
+
const text = textInput.value
|
|
815
|
+
const key = keyInput.value
|
|
816
|
+
const filePath = fileInput.value
|
|
817
|
+
|
|
818
|
+
if (!text || !key || !filePath) {
|
|
819
|
+
statusEl.textContent = "All fields are required"
|
|
820
|
+
statusEl.className = "gxp-extract-status error"
|
|
821
|
+
return
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
button.disabled = true
|
|
825
|
+
button.textContent = "Extracting..."
|
|
826
|
+
|
|
827
|
+
try {
|
|
828
|
+
const result = await extractString({
|
|
829
|
+
text,
|
|
830
|
+
key,
|
|
831
|
+
filePath,
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
if (result.success) {
|
|
835
|
+
statusEl.textContent = `Success! Added getString('${key}') to ${filePath}`
|
|
836
|
+
statusEl.className = "gxp-extract-status success"
|
|
837
|
+
} else {
|
|
838
|
+
statusEl.textContent = result.error || "Extraction failed"
|
|
839
|
+
statusEl.className = "gxp-extract-status error"
|
|
840
|
+
}
|
|
841
|
+
} catch (error) {
|
|
842
|
+
statusEl.textContent = error.message
|
|
843
|
+
statusEl.className = "gxp-extract-status error"
|
|
844
|
+
} finally {
|
|
845
|
+
button.disabled = false
|
|
846
|
+
button.textContent = "Extract to getString()"
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ============================================================
|
|
851
|
+
// Event Handlers
|
|
852
|
+
// ============================================================
|
|
853
|
+
|
|
854
|
+
function handleMouseMove(e) {
|
|
855
|
+
if (!inspectorEnabled) return
|
|
856
|
+
|
|
857
|
+
const el = e.target
|
|
858
|
+
if (
|
|
859
|
+
el === highlightOverlay ||
|
|
860
|
+
highlightOverlay?.contains(el) ||
|
|
861
|
+
el === inspectorPanel ||
|
|
862
|
+
inspectorPanel?.contains(el)
|
|
863
|
+
) {
|
|
864
|
+
return
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (el !== hoveredElement) {
|
|
868
|
+
hoveredElement = el
|
|
869
|
+
updateHighlight(el)
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function handleClick(e) {
|
|
874
|
+
if (!inspectorEnabled) return
|
|
875
|
+
|
|
876
|
+
const el = e.target
|
|
877
|
+
if (
|
|
878
|
+
el === highlightOverlay ||
|
|
879
|
+
highlightOverlay?.contains(el) ||
|
|
880
|
+
el === inspectorPanel ||
|
|
881
|
+
inspectorPanel?.contains(el) ||
|
|
882
|
+
el === selectionHighlight ||
|
|
883
|
+
selectionHighlight?.contains(el)
|
|
884
|
+
) {
|
|
885
|
+
return
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
e.preventDefault()
|
|
889
|
+
e.stopPropagation()
|
|
890
|
+
|
|
891
|
+
selectedElement = el
|
|
892
|
+
// Store for DevTools panel access via eval
|
|
893
|
+
window.__gxpSelectedElement = el
|
|
894
|
+
|
|
895
|
+
updatePanel(el)
|
|
896
|
+
|
|
897
|
+
// Show persistent selection highlight
|
|
898
|
+
updateSelectionHighlight(el)
|
|
899
|
+
|
|
900
|
+
// Hide hover highlight
|
|
901
|
+
hideHighlight()
|
|
902
|
+
|
|
903
|
+
// Disable inspector (selection mode) after selecting
|
|
904
|
+
// This prevents accidental re-selection on next click
|
|
905
|
+
disableInspector()
|
|
906
|
+
|
|
907
|
+
// Send selection to background for DevTools panel
|
|
908
|
+
sendElementToDevTools(el)
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function sendElementToDevTools(el) {
|
|
912
|
+
const vueInstance = getVueInstance(el)
|
|
913
|
+
const info = getComponentInfo(vueInstance)
|
|
914
|
+
const texts = getTextContent(el)
|
|
915
|
+
const textsWithAttrs = getTextContentWithAttributes(el)
|
|
916
|
+
const childGxpStrings = findChildGxpStrings(el)
|
|
917
|
+
|
|
918
|
+
// Check if the element itself has gxp-string
|
|
919
|
+
const gxpStringKey = getGxpStringKey(el)
|
|
920
|
+
// Check for data-gxp-source attribute (injected by vite plugin)
|
|
921
|
+
const sourceExpression = getGxpSourceExpression(el)
|
|
922
|
+
|
|
923
|
+
const data = {
|
|
924
|
+
tagName: el.tagName.toLowerCase(),
|
|
925
|
+
component: info,
|
|
926
|
+
texts: texts,
|
|
927
|
+
textsWithAttributes: textsWithAttrs,
|
|
928
|
+
childGxpStrings: childGxpStrings,
|
|
929
|
+
gxpStringKey: gxpStringKey,
|
|
930
|
+
isExtracted: gxpStringKey !== null,
|
|
931
|
+
sourceExpression: sourceExpression,
|
|
932
|
+
isDynamic: sourceExpression !== null,
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Send to content script via postMessage (since we're in page context)
|
|
936
|
+
// The content script will relay to the background script
|
|
937
|
+
window.postMessage(
|
|
938
|
+
{
|
|
939
|
+
type: "GXP_INSPECTOR_MESSAGE",
|
|
940
|
+
payload: {
|
|
941
|
+
type: "elementSelected",
|
|
942
|
+
data: data,
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
"*",
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function handleKeyDown(e) {
|
|
950
|
+
// Escape to disable inspector
|
|
951
|
+
if (e.key === "Escape" && inspectorEnabled) {
|
|
952
|
+
disableInspector()
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Ctrl+Shift+I to toggle inspector
|
|
956
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "I") {
|
|
957
|
+
e.preventDefault()
|
|
958
|
+
toggleInspector()
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// ============================================================
|
|
963
|
+
// Public API
|
|
964
|
+
// ============================================================
|
|
965
|
+
|
|
966
|
+
function enableInspector() {
|
|
967
|
+
if (inspectorEnabled) return
|
|
968
|
+
|
|
969
|
+
inspectorEnabled = true
|
|
970
|
+
createHighlightOverlay()
|
|
971
|
+
createInspectorPanel()
|
|
972
|
+
|
|
973
|
+
// Add selecting class for pointer cursor
|
|
974
|
+
document.body.classList.add("gxp-inspector-selecting")
|
|
975
|
+
|
|
976
|
+
// Hide previous selection highlight when entering selection mode
|
|
977
|
+
hideSelectionHighlight()
|
|
978
|
+
selectedElement = null
|
|
979
|
+
window.__gxpSelectedElement = null
|
|
980
|
+
|
|
981
|
+
document.addEventListener("mousemove", handleMouseMove, true)
|
|
982
|
+
document.addEventListener("click", handleClick, true)
|
|
983
|
+
window.addEventListener("scroll", updateSelectionPosition, true)
|
|
984
|
+
window.addEventListener("resize", updateSelectionPosition)
|
|
985
|
+
|
|
986
|
+
console.log("[GxP Inspector] Enabled - Hover over elements to inspect")
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function disableInspector() {
|
|
990
|
+
if (!inspectorEnabled) return
|
|
991
|
+
|
|
992
|
+
inspectorEnabled = false
|
|
993
|
+
hideHighlight()
|
|
994
|
+
|
|
995
|
+
// Remove selecting class for pointer cursor
|
|
996
|
+
document.body.classList.remove("gxp-inspector-selecting")
|
|
997
|
+
|
|
998
|
+
if (inspectorPanel) {
|
|
999
|
+
inspectorPanel.remove()
|
|
1000
|
+
inspectorPanel = null
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
document.removeEventListener("mousemove", handleMouseMove, true)
|
|
1004
|
+
document.removeEventListener("click", handleClick, true)
|
|
1005
|
+
// Keep scroll/resize listeners for selection highlight position updates
|
|
1006
|
+
// They will be removed when a new selection starts
|
|
1007
|
+
|
|
1008
|
+
// Don't clear selectedElement here - we want to keep the selection
|
|
1009
|
+
hoveredElement = null
|
|
1010
|
+
|
|
1011
|
+
console.log("[GxP Inspector] Disabled - Selection preserved")
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Clear selection completely (called when user wants to deselect)
|
|
1015
|
+
function clearSelection() {
|
|
1016
|
+
hideSelectionHighlight()
|
|
1017
|
+
selectedElement = null
|
|
1018
|
+
window.__gxpSelectedElement = null
|
|
1019
|
+
window.removeEventListener("scroll", updateSelectionPosition, true)
|
|
1020
|
+
window.removeEventListener("resize", updateSelectionPosition)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function toggleInspector() {
|
|
1024
|
+
if (inspectorEnabled) {
|
|
1025
|
+
disableInspector()
|
|
1026
|
+
} else {
|
|
1027
|
+
enableInspector()
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// ============================================================
|
|
1032
|
+
// Utility Functions
|
|
1033
|
+
// ============================================================
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Build a descriptive label for an element
|
|
1037
|
+
* Format: ComponentName::element::gxp-string-key
|
|
1038
|
+
* Examples:
|
|
1039
|
+
* - DemoPage::h1::welcome_title
|
|
1040
|
+
* - DemoPage::h1 (no gxp-string)
|
|
1041
|
+
* - div::gxp-string-key (no Vue component)
|
|
1042
|
+
* - div (plain element)
|
|
1043
|
+
*/
|
|
1044
|
+
function buildElementLabel(el) {
|
|
1045
|
+
const parts = []
|
|
1046
|
+
|
|
1047
|
+
// Get Vue component name
|
|
1048
|
+
const vueInstance = getVueInstance(el)
|
|
1049
|
+
const info = getComponentInfo(vueInstance)
|
|
1050
|
+
if (info && info.name) {
|
|
1051
|
+
parts.push(info.name)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Add element tag name
|
|
1055
|
+
parts.push(el.tagName.toLowerCase())
|
|
1056
|
+
|
|
1057
|
+
// Check for gxp-string attribute
|
|
1058
|
+
const gxpStringKey = el.getAttribute("gxp-string")
|
|
1059
|
+
if (gxpStringKey) {
|
|
1060
|
+
parts.push(gxpStringKey)
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Check for gxp-src attribute (for images/assets)
|
|
1064
|
+
const gxpSrcKey = el.getAttribute("gxp-src")
|
|
1065
|
+
if (gxpSrcKey && !gxpStringKey) {
|
|
1066
|
+
parts.push(gxpSrcKey)
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
return parts.join("::")
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function textToKey(text) {
|
|
1073
|
+
return text
|
|
1074
|
+
.toLowerCase()
|
|
1075
|
+
.replace(/[^a-z0-9\s]/g, "")
|
|
1076
|
+
.replace(/\s+/g, "_")
|
|
1077
|
+
.substring(0, 40)
|
|
1078
|
+
.replace(/_+$/, "")
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function escapeHtml(text) {
|
|
1082
|
+
const div = document.createElement("div")
|
|
1083
|
+
div.textContent = text
|
|
1084
|
+
return div.innerHTML
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// ============================================================
|
|
1088
|
+
// Initialize
|
|
1089
|
+
// ============================================================
|
|
1090
|
+
|
|
1091
|
+
// Add keyboard listener
|
|
1092
|
+
document.addEventListener("keydown", handleKeyDown)
|
|
1093
|
+
|
|
1094
|
+
// Expose API
|
|
1095
|
+
window.gxpInspector = {
|
|
1096
|
+
enable: enableInspector,
|
|
1097
|
+
disable: disableInspector,
|
|
1098
|
+
toggle: toggleInspector,
|
|
1099
|
+
isEnabled: () => inspectorEnabled,
|
|
1100
|
+
clearSelection: clearSelection,
|
|
1101
|
+
ping: ping,
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Listen for messages from content script (which relays from popup/background)
|
|
1105
|
+
window.addEventListener("message", (event) => {
|
|
1106
|
+
if (event.source !== window) return
|
|
1107
|
+
if (event.data?.type === "GXP_INSPECTOR_ACTION") {
|
|
1108
|
+
const action = event.data.action
|
|
1109
|
+
if (action === "toggleInspector") {
|
|
1110
|
+
toggleInspector()
|
|
1111
|
+
} else if (action === "enable") {
|
|
1112
|
+
enableInspector()
|
|
1113
|
+
} else if (action === "disable") {
|
|
1114
|
+
disableInspector()
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
})
|
|
1118
|
+
|
|
1119
|
+
console.log(
|
|
1120
|
+
"[GxP Inspector] Loaded in page context. Press Ctrl+Shift+I to toggle inspector.",
|
|
1121
|
+
)
|
|
1122
|
+
})()
|