@symbo.ls/connect 3.2.7
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/build.js +205 -0
- package/dist/assets/1024x1024.png +0 -0
- package/dist/assets/128x128.png +0 -0
- package/dist/assets/144x144.png +0 -0
- package/dist/assets/192x192.png +0 -0
- package/dist/assets/48x48.png +0 -0
- package/dist/assets/512x512.png +0 -0
- package/dist/assets/72x72.png +0 -0
- package/dist/assets/96x96.png +0 -0
- package/dist/assets/active_cursor.png +0 -0
- package/dist/assets/favicon.svg +6 -0
- package/dist/assets/old/144x144.png +0 -0
- package/dist/assets/old/192x192.png +0 -0
- package/dist/assets/old/48x48.png +0 -0
- package/dist/assets/old/48x48_faint.png +0 -0
- package/dist/assets/old/512x512.png +0 -0
- package/dist/assets/old/72x72.png +0 -0
- package/dist/assets/old/96x96.png +0 -0
- package/dist/auth.js +373 -0
- package/dist/content.css +46 -0
- package/dist/content.js +1171 -0
- package/dist/content.js.map +7 -0
- package/dist/devtools.html +7 -0
- package/dist/devtools.js +5 -0
- package/dist/manifest.json +87 -0
- package/dist/page-agent.js +727 -0
- package/dist/panel.css +2239 -0
- package/dist/panel.html +235 -0
- package/dist/panel.js +4973 -0
- package/dist/picker.html +111 -0
- package/dist/picker.js +300 -0
- package/dist/service_worker.js +219 -0
- package/dist/service_worker.js.map +7 -0
- package/dist/settings.css +128 -0
- package/dist/settings.html +26 -0
- package/dist/settings_ui.js +57 -0
- package/dist/settings_ui.js.map +7 -0
- package/package.json +20 -0
- package/src/content.js +104 -0
- package/src/grabber/clean.js +605 -0
- package/src/grabber/computed.js +78 -0
- package/src/grabber/parse.js +268 -0
- package/src/grabber/stylesheets.js +117 -0
- package/src/grabber/utils.js +238 -0
- package/src/service_worker.js +246 -0
- package/src/settings/settings_ui.js +52 -0
- package/src/settings/settings_utils.js +70 -0
- package/static/assets/1024x1024.png +0 -0
- package/static/assets/128x128.png +0 -0
- package/static/assets/144x144.png +0 -0
- package/static/assets/192x192.png +0 -0
- package/static/assets/48x48.png +0 -0
- package/static/assets/512x512.png +0 -0
- package/static/assets/72x72.png +0 -0
- package/static/assets/96x96.png +0 -0
- package/static/assets/active_cursor.png +0 -0
- package/static/assets/favicon.svg +6 -0
- package/static/assets/old/144x144.png +0 -0
- package/static/assets/old/192x192.png +0 -0
- package/static/assets/old/48x48.png +0 -0
- package/static/assets/old/48x48_faint.png +0 -0
- package/static/assets/old/512x512.png +0 -0
- package/static/assets/old/72x72.png +0 -0
- package/static/assets/old/96x96.png +0 -0
- package/static/auth.js +373 -0
- package/static/content.css +46 -0
- package/static/devtools.html +7 -0
- package/static/devtools.js +5 -0
- package/static/manifest.json +56 -0
- package/static/page-agent.js +727 -0
- package/static/panel.css +2239 -0
- package/static/panel.html +235 -0
- package/static/panel.js +4973 -0
- package/static/picker.html +111 -0
- package/static/picker.js +300 -0
- package/static/settings.css +128 -0
- package/static/settings.html +26 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
// Page-agent: runs in page context, provides DOMQL inspection utilities
|
|
2
|
+
// Accessed from DevTools panel via chrome.devtools.inspectedWindow.eval()
|
|
3
|
+
|
|
4
|
+
;(function () {
|
|
5
|
+
'use strict'
|
|
6
|
+
|
|
7
|
+
const REGISTRY_KEYS = new Set([
|
|
8
|
+
'attr', 'style', 'text', 'html', 'data', 'classlist', 'state', 'scope',
|
|
9
|
+
'extend', 'extends', 'children', 'childExtend', 'childExtends', 'childExtendRecursive',
|
|
10
|
+
'props', 'if', 'define', '__name', '__ref', '__hash', '__text',
|
|
11
|
+
'key', 'tag', 'query', 'parent', 'node', 'variables', 'on', 'component', 'context',
|
|
12
|
+
'update', 'set', 'reset', 'remove', 'setProps', 'lookup', 'lookdown',
|
|
13
|
+
'lookdownAll', 'nextElement', 'previousElement', 'updateContent', 'removeContent',
|
|
14
|
+
'getRootState', 'getRootData', 'getRoot', 'getContext', 'setNodeStyles',
|
|
15
|
+
'call', 'parse', 'keys', 'verbose', 'getRef', 'getPath'
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const METHOD_NAMES = [
|
|
19
|
+
'update', 'set', 'reset', 'remove', 'setProps',
|
|
20
|
+
'lookup', 'lookdown', 'lookdownAll',
|
|
21
|
+
'updateContent', 'removeContent',
|
|
22
|
+
'getRootState', 'getRootData', 'getRoot', 'getContext',
|
|
23
|
+
'setNodeStyles', 'call', 'parse', 'keys', 'verbose',
|
|
24
|
+
'nextElement', 'previousElement', 'getRef', 'getPath'
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const STATE_METHOD_NAMES = [
|
|
28
|
+
'update', 'set', 'reset', 'replace', 'quietUpdate',
|
|
29
|
+
'toggle', 'remove', 'add', 'apply', 'setByPath',
|
|
30
|
+
'parse', 'clean', 'destroy'
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
// Get DOMQL element ref from a DOM node
|
|
34
|
+
function getRef (node) {
|
|
35
|
+
if (!node) return null
|
|
36
|
+
if (node.ref) return node.ref
|
|
37
|
+
// Walk up to find nearest DOMQL element
|
|
38
|
+
let current = node
|
|
39
|
+
while (current) {
|
|
40
|
+
if (current.ref) return current.ref
|
|
41
|
+
current = current.parentElement
|
|
42
|
+
}
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Serialize a value safely (handle circular refs, functions, DOM nodes)
|
|
47
|
+
function serialize (val, depth, maxDepth, seen) {
|
|
48
|
+
if (depth > maxDepth) return { __type: 'truncated', value: '...' }
|
|
49
|
+
if (val === null) return null
|
|
50
|
+
if (val === undefined) return { __type: 'undefined' }
|
|
51
|
+
|
|
52
|
+
const type = typeof val
|
|
53
|
+
if (type === 'string' || type === 'number' || type === 'boolean') return val
|
|
54
|
+
if (type === 'function') return { __type: 'function', name: val.name || 'anonymous' }
|
|
55
|
+
if (val instanceof HTMLElement) return { __type: 'node', tag: val.tagName.toLowerCase(), key: val.getAttribute('key') }
|
|
56
|
+
|
|
57
|
+
if (type === 'object') {
|
|
58
|
+
if (seen.has(val)) return { __type: 'circular' }
|
|
59
|
+
seen.add(val)
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(val)) {
|
|
62
|
+
return val.slice(0, 100).map(item => serialize(item, depth + 1, maxDepth, seen))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = {}
|
|
66
|
+
const keys = Object.keys(val)
|
|
67
|
+
for (const k of keys.slice(0, 100)) {
|
|
68
|
+
if (k === 'node' || k === 'parent' || k === 'context' || k === '__element') continue
|
|
69
|
+
try {
|
|
70
|
+
result[k] = serialize(val[k], depth + 1, maxDepth, seen)
|
|
71
|
+
} catch (e) {
|
|
72
|
+
result[k] = { __type: 'error', message: e.message }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (keys.length > 100) result.__truncated = keys.length + ' keys total'
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return String(val)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Trace which component each prop comes from
|
|
83
|
+
function getPropsOrigin (el) {
|
|
84
|
+
const origins = {}
|
|
85
|
+
|
|
86
|
+
function traceExtend (def, depth) {
|
|
87
|
+
if (!def || typeof def !== 'object' || depth > 10) return
|
|
88
|
+
const name = def.__name || def.key || null
|
|
89
|
+
|
|
90
|
+
if (def.props && typeof def.props === 'object') {
|
|
91
|
+
for (const k of Object.keys(def.props)) {
|
|
92
|
+
if (typeof def.props[k] === 'function' || k.startsWith('__')) continue
|
|
93
|
+
if (!origins[k]) origins[k] = name
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check text
|
|
98
|
+
if (def.text !== undefined && !origins.text) {
|
|
99
|
+
origins.text = name
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Recurse into extend chain
|
|
103
|
+
if (def.extend) traceExtend(def.extend, depth + 1)
|
|
104
|
+
if (Array.isArray(def.extends)) {
|
|
105
|
+
for (const ext of def.extends) traceExtend(ext, depth + 1)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Walk from the element itself
|
|
110
|
+
if (el.__ref) {
|
|
111
|
+
// The element's own component name
|
|
112
|
+
const selfName = el.__ref.__name || el.component || el.key
|
|
113
|
+
|
|
114
|
+
// Check if the element defines its own props
|
|
115
|
+
if (el.__ref.__extend) traceExtend(el.__ref.__extend, 0)
|
|
116
|
+
if (el.__ref.__extends && Array.isArray(el.__ref.__extends)) {
|
|
117
|
+
for (const ext of el.__ref.__extends) traceExtend(ext, 0)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Mark direct props (set on this instance) as "self"
|
|
121
|
+
if (el.props && typeof el.props === 'object') {
|
|
122
|
+
for (const k of Object.keys(el.props)) {
|
|
123
|
+
if (typeof el.props[k] === 'function' || k.startsWith('__')) continue
|
|
124
|
+
if (!origins[k]) origins[k] = selfName || 'self'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return origins
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get serialized info about a DOMQL element
|
|
133
|
+
function getElementInfo (el) {
|
|
134
|
+
if (!el) return null
|
|
135
|
+
|
|
136
|
+
const info = {
|
|
137
|
+
key: el.key || null,
|
|
138
|
+
tag: el.tag || (el.node && el.node.tagName ? el.node.tagName.toLowerCase() : null),
|
|
139
|
+
path: el.__ref && el.__ref.path ? el.__ref.path : null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// State
|
|
143
|
+
if (el.state && typeof el.state === 'object') {
|
|
144
|
+
info.state = {}
|
|
145
|
+
for (const k of Object.keys(el.state)) {
|
|
146
|
+
if (STATE_METHOD_NAMES.includes(k) || k.startsWith('__') || k === 'parent' || k === 'root') continue
|
|
147
|
+
try {
|
|
148
|
+
info.state[k] = serialize(el.state[k], 0, 3, new WeakSet())
|
|
149
|
+
} catch (e) {
|
|
150
|
+
info.state[k] = { __type: 'error', message: e.message }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Props — collect el.props if it exists as an object
|
|
156
|
+
info.props = {}
|
|
157
|
+
|
|
158
|
+
// Include text as a prop shortcut — el.text is a top-level DOMQL property
|
|
159
|
+
// It can be a string, number, or function (getter). Always try to resolve it.
|
|
160
|
+
var textVal = undefined
|
|
161
|
+
if (el.text !== undefined && el.text !== null) {
|
|
162
|
+
if (typeof el.text === 'function') {
|
|
163
|
+
try { textVal = el.text.call(el, el, el.state, el.context) } catch (e) { /* skip */ }
|
|
164
|
+
} else if (typeof el.text !== 'object') {
|
|
165
|
+
textVal = el.text
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if ((textVal === undefined || textVal === null) && el.props && el.props.text != null && typeof el.props.text !== 'function') {
|
|
169
|
+
textVal = el.props.text
|
|
170
|
+
}
|
|
171
|
+
if ((textVal === undefined || textVal === null) && el.node) {
|
|
172
|
+
// Fallback: read direct text nodes from DOM
|
|
173
|
+
var directText = ''
|
|
174
|
+
for (var ci = 0; ci < el.node.childNodes.length; ci++) {
|
|
175
|
+
if (el.node.childNodes[ci].nodeType === 3) directText += el.node.childNodes[ci].textContent
|
|
176
|
+
}
|
|
177
|
+
if (directText.trim()) textVal = directText.trim()
|
|
178
|
+
}
|
|
179
|
+
if (textVal !== undefined && textVal !== null) {
|
|
180
|
+
info.props.text = serialize(textVal, 0, 3, new WeakSet())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (el.props && typeof el.props === 'object') {
|
|
184
|
+
for (const k of Object.keys(el.props)) {
|
|
185
|
+
if (k === 'text' || k === 'parent' || k === 'root' || k === 'update' || k === 'set' || k === 'reset' || k.startsWith('__')) continue
|
|
186
|
+
if (typeof el.props[k] === 'function') continue
|
|
187
|
+
try {
|
|
188
|
+
info.props[k] = serialize(el.props[k], 0, 3, new WeakSet())
|
|
189
|
+
} catch (e) {
|
|
190
|
+
info.props[k] = { __type: 'error', message: e.message }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Prop origin mapping — trace which component defines each prop
|
|
196
|
+
info.propsOrigin = getPropsOrigin(el)
|
|
197
|
+
|
|
198
|
+
// Children (DOMQL child elements)
|
|
199
|
+
info.children = []
|
|
200
|
+
const childKeys = (el.__ref && el.__ref.__children) || []
|
|
201
|
+
for (const ck of childKeys) {
|
|
202
|
+
if (el[ck] && el[ck].node) {
|
|
203
|
+
info.children.push({
|
|
204
|
+
key: ck,
|
|
205
|
+
tag: el[ck].tag || (el[ck].node ? el[ck].node.tagName.toLowerCase() : null),
|
|
206
|
+
hasChildren: !!(el[ck].__ref && el[ck].__ref.__children && el[ck].__ref.__children.length)
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Also find children by walking own keys for DOMQL elements
|
|
212
|
+
for (const k of Object.keys(el)) {
|
|
213
|
+
if (REGISTRY_KEYS.has(k) || k.startsWith('__') || childKeys.includes(k)) continue
|
|
214
|
+
const val = el[k]
|
|
215
|
+
if (val && typeof val === 'object' && val.node instanceof HTMLElement && val.key) {
|
|
216
|
+
info.children.push({
|
|
217
|
+
key: k,
|
|
218
|
+
tag: val.tag || (val.node ? val.node.tagName.toLowerCase() : null),
|
|
219
|
+
hasChildren: !!(val.__ref && val.__ref.__children && val.__ref.__children.length)
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Original (definition) props — from __ref before function resolution
|
|
225
|
+
info.originalProps = {}
|
|
226
|
+
info.functionProps = {} // props whose original value is a function
|
|
227
|
+
|
|
228
|
+
// Gather original definition values from __ref and extend chain
|
|
229
|
+
function collectOriginal (def, depth) {
|
|
230
|
+
if (!def || typeof def !== 'object' || depth > 10) return
|
|
231
|
+
// Props from definition
|
|
232
|
+
if (def.props && typeof def.props === 'object') {
|
|
233
|
+
for (var pk of Object.keys(def.props)) {
|
|
234
|
+
if (pk.startsWith('__')) continue
|
|
235
|
+
if (!(pk in info.originalProps)) {
|
|
236
|
+
if (typeof def.props[pk] === 'function') {
|
|
237
|
+
info.functionProps[pk] = { name: def.props[pk].name || 'anonymous' }
|
|
238
|
+
// Still store the computed value
|
|
239
|
+
if (el.props && el.props[pk] !== undefined && typeof el.props[pk] !== 'function') {
|
|
240
|
+
info.originalProps[pk] = serialize(el.props[pk], 0, 3, new WeakSet())
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
info.originalProps[pk] = serialize(def.props[pk], 0, 3, new WeakSet())
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Text from definition
|
|
249
|
+
if (def.text !== undefined && !('text' in info.originalProps)) {
|
|
250
|
+
if (typeof def.text === 'function') {
|
|
251
|
+
info.functionProps.text = { name: def.text.name || 'anonymous' }
|
|
252
|
+
} else {
|
|
253
|
+
info.originalProps.text = serialize(def.text, 0, 3, new WeakSet())
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Top-level DOMQL keys that are also "props" in the broad sense
|
|
257
|
+
var domqlKeys = ['tag', 'theme', 'flow', 'wrap', 'display', 'position', 'cursor', 'opacity', 'overflow']
|
|
258
|
+
for (var dk of domqlKeys) {
|
|
259
|
+
if (def[dk] !== undefined && !(dk in info.originalProps)) {
|
|
260
|
+
if (typeof def[dk] === 'function') {
|
|
261
|
+
info.functionProps[dk] = { name: def[dk].name || 'anonymous' }
|
|
262
|
+
} else {
|
|
263
|
+
info.originalProps[dk] = serialize(def[dk], 0, 3, new WeakSet())
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Walk extends
|
|
268
|
+
if (def.extend) collectOriginal(def.extend, depth + 1)
|
|
269
|
+
if (Array.isArray(def.extends)) {
|
|
270
|
+
for (var ext of def.extends) collectOriginal(ext, depth + 1)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Start from the element's own __ref
|
|
275
|
+
if (el.__ref) {
|
|
276
|
+
collectOriginal(el.__ref, 0)
|
|
277
|
+
if (el.__ref.__extend) collectOriginal(el.__ref.__extend, 0)
|
|
278
|
+
if (el.__ref.__extends && Array.isArray(el.__ref.__extends)) {
|
|
279
|
+
for (var re of el.__ref.__extends) collectOriginal(re, 0)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Also check top-level element properties for functions
|
|
284
|
+
var topFuncKeys = ['text', 'tag', 'if', 'data']
|
|
285
|
+
for (var tfk of topFuncKeys) {
|
|
286
|
+
if (typeof el[tfk] === 'function' && !(tfk in info.functionProps)) {
|
|
287
|
+
info.functionProps[tfk] = { name: el[tfk].name || 'anonymous' }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Methods available
|
|
292
|
+
info.methods = METHOD_NAMES.filter(m => typeof el[m] === 'function')
|
|
293
|
+
|
|
294
|
+
// State methods
|
|
295
|
+
if (el.state && typeof el.state === 'object') {
|
|
296
|
+
info.stateMethods = STATE_METHOD_NAMES.filter(m => typeof el.state[m] === 'function')
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// __ref info
|
|
300
|
+
if (el.__ref) {
|
|
301
|
+
info.ref = {}
|
|
302
|
+
for (const k of Object.keys(el.__ref)) {
|
|
303
|
+
if (k === 'root' || k === '__element') continue
|
|
304
|
+
try {
|
|
305
|
+
info.ref[k] = serialize(el.__ref[k], 0, 2, new WeakSet())
|
|
306
|
+
} catch (e) {
|
|
307
|
+
info.ref[k] = { __type: 'error', message: e.message }
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return info
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Build a tree of DOMQL elements starting from root
|
|
316
|
+
function buildTree (el, depth, maxDepth) {
|
|
317
|
+
if (!el || depth > maxDepth) return null
|
|
318
|
+
|
|
319
|
+
const node = {
|
|
320
|
+
key: el.key || '(unknown)',
|
|
321
|
+
tag: el.tag || (el.node && el.node.tagName ? el.node.tagName.toLowerCase() : null),
|
|
322
|
+
children: []
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Collect children
|
|
326
|
+
const childKeys = (el.__ref && el.__ref.__children) || []
|
|
327
|
+
const seen = new Set(childKeys)
|
|
328
|
+
|
|
329
|
+
for (const ck of childKeys) {
|
|
330
|
+
if (ck === '__text' || ck.startsWith('__')) continue
|
|
331
|
+
if (el[ck] && el[ck].node) {
|
|
332
|
+
const child = buildTree(el[ck], depth + 1, maxDepth)
|
|
333
|
+
if (child) node.children.push(child)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Also check own keys for child elements not in __children
|
|
338
|
+
for (const k of Object.keys(el)) {
|
|
339
|
+
if (REGISTRY_KEYS.has(k) || k.startsWith('__') || seen.has(k)) continue
|
|
340
|
+
const val = el[k]
|
|
341
|
+
if (val && typeof val === 'object' && val.node instanceof HTMLElement && val.key) {
|
|
342
|
+
seen.add(k)
|
|
343
|
+
const child = buildTree(val, depth + 1, maxDepth)
|
|
344
|
+
if (child) node.children.push(child)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return node
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Find the root DOMQL element
|
|
352
|
+
function findRoot () {
|
|
353
|
+
// Check document.body.ref first
|
|
354
|
+
if (document.body && document.body.ref) return document.body.ref
|
|
355
|
+
// Check common root selectors
|
|
356
|
+
const candidates = document.querySelectorAll('[key]')
|
|
357
|
+
for (const node of candidates) {
|
|
358
|
+
if (node.ref) {
|
|
359
|
+
// Walk up to find the topmost ref
|
|
360
|
+
let root = node.ref
|
|
361
|
+
while (root.parent && root.parent.node && root.parent.key) {
|
|
362
|
+
root = root.parent
|
|
363
|
+
}
|
|
364
|
+
return root
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return null
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Build a key path from a DOMQL element back to the root
|
|
371
|
+
function getPathToRoot (el) {
|
|
372
|
+
const parts = []
|
|
373
|
+
let current = el
|
|
374
|
+
while (current) {
|
|
375
|
+
if (current.key) parts.unshift(current.key)
|
|
376
|
+
if (!current.parent || !current.parent.key) break
|
|
377
|
+
current = current.parent
|
|
378
|
+
}
|
|
379
|
+
return parts.join('.')
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Highlight overlay management
|
|
383
|
+
let _highlightedNode = null
|
|
384
|
+
let _highlightColor = null
|
|
385
|
+
let _scrollRaf = null
|
|
386
|
+
|
|
387
|
+
function _updateOverlayPosition () {
|
|
388
|
+
if (!_highlightedNode) return
|
|
389
|
+
const overlay = document.getElementById('__domql-highlight__')
|
|
390
|
+
if (!overlay || overlay.style.display === 'none') return
|
|
391
|
+
const rect = _highlightedNode.getBoundingClientRect()
|
|
392
|
+
overlay.style.top = rect.top + 'px'
|
|
393
|
+
overlay.style.left = rect.left + 'px'
|
|
394
|
+
overlay.style.width = rect.width + 'px'
|
|
395
|
+
overlay.style.height = rect.height + 'px'
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function _onScroll () {
|
|
399
|
+
if (_scrollRaf) return
|
|
400
|
+
_scrollRaf = requestAnimationFrame(() => {
|
|
401
|
+
_scrollRaf = null
|
|
402
|
+
_updateOverlayPosition()
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function showHighlight (node, color) {
|
|
407
|
+
let overlay = document.getElementById('__domql-highlight__')
|
|
408
|
+
if (!overlay) {
|
|
409
|
+
overlay = document.createElement('div')
|
|
410
|
+
overlay.id = '__domql-highlight__'
|
|
411
|
+
overlay.style.cssText = `
|
|
412
|
+
position: fixed;
|
|
413
|
+
pointer-events: none;
|
|
414
|
+
z-index: 999999;
|
|
415
|
+
transition: top 0.08s, left 0.08s, width 0.08s, height 0.08s;
|
|
416
|
+
`
|
|
417
|
+
document.body.appendChild(overlay)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
_highlightedNode = node
|
|
421
|
+
_highlightColor = color || 'rgba(66, 133, 244, 0.6)'
|
|
422
|
+
|
|
423
|
+
const rect = node.getBoundingClientRect()
|
|
424
|
+
overlay.style.top = rect.top + 'px'
|
|
425
|
+
overlay.style.left = rect.left + 'px'
|
|
426
|
+
overlay.style.width = rect.width + 'px'
|
|
427
|
+
overlay.style.height = rect.height + 'px'
|
|
428
|
+
overlay.style.background = _highlightColor.replace('0.6', '0.12')
|
|
429
|
+
overlay.style.border = '2px solid ' + _highlightColor
|
|
430
|
+
overlay.style.display = 'block'
|
|
431
|
+
|
|
432
|
+
window.removeEventListener('scroll', _onScroll, true)
|
|
433
|
+
window.addEventListener('scroll', _onScroll, true)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function hideHighlight () {
|
|
437
|
+
const overlay = document.getElementById('__domql-highlight__')
|
|
438
|
+
if (overlay) overlay.style.display = 'none'
|
|
439
|
+
_highlightedNode = null
|
|
440
|
+
window.removeEventListener('scroll', _onScroll, true)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// --- Picker mode ---
|
|
444
|
+
let pickerActive = false
|
|
445
|
+
let pickerHoverHandler = null
|
|
446
|
+
let pickerClickHandler = null
|
|
447
|
+
let pickerKeyHandler = null
|
|
448
|
+
|
|
449
|
+
function startPicker () {
|
|
450
|
+
if (pickerActive) return
|
|
451
|
+
pickerActive = true
|
|
452
|
+
|
|
453
|
+
// Show a tooltip with the DOMQL key
|
|
454
|
+
let tooltip = document.getElementById('__domql-picker-tooltip__')
|
|
455
|
+
if (!tooltip) {
|
|
456
|
+
tooltip = document.createElement('div')
|
|
457
|
+
tooltip.id = '__domql-picker-tooltip__'
|
|
458
|
+
tooltip.style.cssText = `
|
|
459
|
+
position: fixed;
|
|
460
|
+
background: #1e1e1e;
|
|
461
|
+
color: #9cdcfe;
|
|
462
|
+
font-family: 'SF Mono', Menlo, Monaco, monospace;
|
|
463
|
+
font-size: 12px;
|
|
464
|
+
padding: 4px 8px;
|
|
465
|
+
border-radius: 4px;
|
|
466
|
+
border: 1px solid #4285f4;
|
|
467
|
+
pointer-events: none;
|
|
468
|
+
z-index: 1000000;
|
|
469
|
+
display: none;
|
|
470
|
+
white-space: nowrap;
|
|
471
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
|
|
472
|
+
`
|
|
473
|
+
document.body.appendChild(tooltip)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
document.body.style.cursor = 'crosshair'
|
|
477
|
+
|
|
478
|
+
pickerHoverHandler = (e) => {
|
|
479
|
+
const el = getRef(e.target)
|
|
480
|
+
if (el && el.node) {
|
|
481
|
+
showHighlight(el.node, 'rgba(66, 133, 244, 0.6)')
|
|
482
|
+
const path = getPathToRoot(el)
|
|
483
|
+
tooltip.textContent = (el.key || '?') + (el.tag ? ' <' + el.tag + '>' : '') + ' \u2014 ' + path
|
|
484
|
+
tooltip.style.display = 'block'
|
|
485
|
+
// Position tooltip near cursor
|
|
486
|
+
const tx = Math.min(e.clientX + 12, window.innerWidth - tooltip.offsetWidth - 8)
|
|
487
|
+
const ty = Math.min(e.clientY + 16, window.innerHeight - tooltip.offsetHeight - 8)
|
|
488
|
+
tooltip.style.left = tx + 'px'
|
|
489
|
+
tooltip.style.top = ty + 'px'
|
|
490
|
+
} else {
|
|
491
|
+
hideHighlight()
|
|
492
|
+
tooltip.style.display = 'none'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
pickerClickHandler = (e) => {
|
|
497
|
+
e.preventDefault()
|
|
498
|
+
e.stopPropagation()
|
|
499
|
+
e.stopImmediatePropagation()
|
|
500
|
+
|
|
501
|
+
const el = getRef(e.target)
|
|
502
|
+
if (el) {
|
|
503
|
+
const path = getPathToRoot(el)
|
|
504
|
+
const info = getElementInfo(el)
|
|
505
|
+
// Store the pick result for the panel to read
|
|
506
|
+
window.__DOMQL_INSPECTOR__._lastPick = { path, info }
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
stopPicker()
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
pickerKeyHandler = (e) => {
|
|
513
|
+
if (e.key === 'Escape') {
|
|
514
|
+
e.preventDefault()
|
|
515
|
+
stopPicker()
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
document.addEventListener('mousemove', pickerHoverHandler, true)
|
|
520
|
+
document.addEventListener('click', pickerClickHandler, true)
|
|
521
|
+
document.addEventListener('keydown', pickerKeyHandler, true)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function stopPicker () {
|
|
525
|
+
pickerActive = false
|
|
526
|
+
document.body.style.cursor = ''
|
|
527
|
+
hideHighlight()
|
|
528
|
+
|
|
529
|
+
const tooltip = document.getElementById('__domql-picker-tooltip__')
|
|
530
|
+
if (tooltip) tooltip.style.display = 'none'
|
|
531
|
+
|
|
532
|
+
if (pickerHoverHandler) {
|
|
533
|
+
document.removeEventListener('mousemove', pickerHoverHandler, true)
|
|
534
|
+
pickerHoverHandler = null
|
|
535
|
+
}
|
|
536
|
+
if (pickerClickHandler) {
|
|
537
|
+
document.removeEventListener('click', pickerClickHandler, true)
|
|
538
|
+
pickerClickHandler = null
|
|
539
|
+
}
|
|
540
|
+
if (pickerKeyHandler) {
|
|
541
|
+
document.removeEventListener('keydown', pickerKeyHandler, true)
|
|
542
|
+
pickerKeyHandler = null
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Expose global API for the DevTools panel
|
|
547
|
+
window.__DOMQL_INSPECTOR__ = {
|
|
548
|
+
getRef,
|
|
549
|
+
getElementInfo,
|
|
550
|
+
buildTree,
|
|
551
|
+
findRoot,
|
|
552
|
+
serialize,
|
|
553
|
+
getPathToRoot,
|
|
554
|
+
METHOD_NAMES,
|
|
555
|
+
STATE_METHOD_NAMES,
|
|
556
|
+
_lastPick: null,
|
|
557
|
+
|
|
558
|
+
// Get tree from root
|
|
559
|
+
getTree (maxDepth) {
|
|
560
|
+
const root = findRoot()
|
|
561
|
+
if (!root) return null
|
|
562
|
+
return buildTree(root, 0, maxDepth || 8)
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
// Inspect a DOM node and return info + path
|
|
566
|
+
inspectNode (node) {
|
|
567
|
+
const el = getRef(node)
|
|
568
|
+
if (!el) return null
|
|
569
|
+
const info = getElementInfo(el)
|
|
570
|
+
info._path = getPathToRoot(el)
|
|
571
|
+
return info
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
// Navigate to child by key path (e.g., "App.Header.Nav.Link")
|
|
575
|
+
// First part is the root element's own key, remaining parts are children
|
|
576
|
+
navigatePath (path) {
|
|
577
|
+
const root = findRoot()
|
|
578
|
+
if (!root) return null
|
|
579
|
+
const parts = path.split('.')
|
|
580
|
+
let current = root
|
|
581
|
+
// If first part matches root's key, skip it (it refers to root itself)
|
|
582
|
+
var start = 0
|
|
583
|
+
if (parts[0] === root.key) start = 1
|
|
584
|
+
for (var i = start; i < parts.length; i++) {
|
|
585
|
+
if (!current[parts[i]]) return null
|
|
586
|
+
current = current[parts[i]]
|
|
587
|
+
}
|
|
588
|
+
return getElementInfo(current)
|
|
589
|
+
},
|
|
590
|
+
|
|
591
|
+
// Get element ref by path (returns the actual element, not serialized)
|
|
592
|
+
getElementByPath (path) {
|
|
593
|
+
const root = findRoot()
|
|
594
|
+
if (!root) return null
|
|
595
|
+
const parts = path.split('.')
|
|
596
|
+
let current = root
|
|
597
|
+
var start = 0
|
|
598
|
+
if (parts[0] === root.key) start = 1
|
|
599
|
+
for (var i = start; i < parts.length; i++) {
|
|
600
|
+
if (!current[parts[i]]) return null
|
|
601
|
+
current = current[parts[i]]
|
|
602
|
+
}
|
|
603
|
+
return current
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
// Update state value
|
|
607
|
+
updateState (path, key, value) {
|
|
608
|
+
const el = this.getElementByPath(path)
|
|
609
|
+
if (!el || !el.state) return { error: 'Element or state not found' }
|
|
610
|
+
try {
|
|
611
|
+
el.state.update({ [key]: value })
|
|
612
|
+
return { success: true }
|
|
613
|
+
} catch (e) {
|
|
614
|
+
return { error: e.message }
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
// Update prop value — uses setProps to update and re-render
|
|
619
|
+
// text is a top-level DOMQL property, not inside props
|
|
620
|
+
updateProp (path, key, value) {
|
|
621
|
+
const el = this.getElementByPath(path)
|
|
622
|
+
if (!el) return { error: 'Element not found' }
|
|
623
|
+
try {
|
|
624
|
+
if (key === 'text') {
|
|
625
|
+
el.update({ text: value })
|
|
626
|
+
} else if (typeof el.setProps === 'function') {
|
|
627
|
+
el.setProps({ [key]: value })
|
|
628
|
+
} else {
|
|
629
|
+
el.update({ props: { [key]: value } })
|
|
630
|
+
}
|
|
631
|
+
return { success: true }
|
|
632
|
+
} catch (e) {
|
|
633
|
+
return { error: e.message }
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
// Call a method on element
|
|
638
|
+
callMethod (path, method, args) {
|
|
639
|
+
const el = this.getElementByPath(path)
|
|
640
|
+
if (!el) return { error: 'Element not found' }
|
|
641
|
+
if (typeof el[method] !== 'function') return { error: 'Method not found: ' + method }
|
|
642
|
+
try {
|
|
643
|
+
const result = el[method].apply(el, args || [])
|
|
644
|
+
return { success: true, result: serialize(result, 0, 3, new WeakSet()) }
|
|
645
|
+
} catch (e) {
|
|
646
|
+
return { error: e.message }
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
// Call a state method
|
|
651
|
+
callStateMethod (path, method, args) {
|
|
652
|
+
const el = this.getElementByPath(path)
|
|
653
|
+
if (!el || !el.state) return { error: 'Element or state not found' }
|
|
654
|
+
if (typeof el.state[method] !== 'function') return { error: 'State method not found: ' + method }
|
|
655
|
+
try {
|
|
656
|
+
const result = el.state[method].apply(el.state, args || [])
|
|
657
|
+
return { success: true, result: serialize(result, 0, 3, new WeakSet()) }
|
|
658
|
+
} catch (e) {
|
|
659
|
+
return { error: e.message }
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
|
|
663
|
+
// Highlight element by path
|
|
664
|
+
highlight (path) {
|
|
665
|
+
const el = this.getElementByPath(path)
|
|
666
|
+
if (!el || !el.node) return
|
|
667
|
+
showHighlight(el.node)
|
|
668
|
+
},
|
|
669
|
+
|
|
670
|
+
// Remove highlight
|
|
671
|
+
removeHighlight () {
|
|
672
|
+
hideHighlight()
|
|
673
|
+
},
|
|
674
|
+
|
|
675
|
+
// Picker mode
|
|
676
|
+
startPicker () {
|
|
677
|
+
startPicker()
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
stopPicker () {
|
|
681
|
+
stopPicker()
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
isPickerActive () {
|
|
685
|
+
return pickerActive
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
// Get design system from any element's context
|
|
689
|
+
getDesignSystem (el) {
|
|
690
|
+
const target = el || findRoot()
|
|
691
|
+
if (!target) return null
|
|
692
|
+
const ds = (target.context && target.context.designSystem) || target.designSystem
|
|
693
|
+
if (!ds || typeof ds !== 'object') return null
|
|
694
|
+
// Custom serializer that handles DS-specific structures
|
|
695
|
+
const seen = new WeakSet()
|
|
696
|
+
function ser (v, d) {
|
|
697
|
+
if (d > 5) return { __type: 'truncated' }
|
|
698
|
+
if (v === null) return null
|
|
699
|
+
if (v === undefined) return { __type: 'undefined' }
|
|
700
|
+
if (typeof v === 'function') return { __type: 'function', name: v.name || '' }
|
|
701
|
+
if (typeof v !== 'object') return v
|
|
702
|
+
if (v instanceof HTMLElement || v instanceof Node) return { __type: 'node' }
|
|
703
|
+
if (seen.has(v)) return { __type: 'circular' }
|
|
704
|
+
seen.add(v)
|
|
705
|
+
if (Array.isArray(v)) return v.slice(0, 50).map(function (x) { return ser(x, d + 1) })
|
|
706
|
+
var o = {}
|
|
707
|
+
var keys = Object.keys(v)
|
|
708
|
+
for (var i = 0; i < keys.length && i < 200; i++) {
|
|
709
|
+
var k = keys[i]
|
|
710
|
+
if (k === 'parent' || k === 'node' || k === '__element' || k === 'context') continue
|
|
711
|
+
try { o[k] = ser(v[k], d + 1) } catch (e) { /* skip */ }
|
|
712
|
+
}
|
|
713
|
+
return o
|
|
714
|
+
}
|
|
715
|
+
return ser(ds, 0)
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
// Read and clear last pick result
|
|
719
|
+
consumePick () {
|
|
720
|
+
const result = this._lastPick
|
|
721
|
+
this._lastPick = null
|
|
722
|
+
return result
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
console.log('[Symbols Connect] Page agent loaded')
|
|
727
|
+
})()
|