@antigenic-oss/paint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +178 -0
- package/NOTICE +4 -0
- package/README.md +180 -0
- package/bin/paint.js +266 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +19 -0
- package/package.json +81 -0
- package/postcss.config.mjs +8 -0
- package/public/dev-editor-inspector.js +1872 -0
- package/src/app/api/claude/analyze/route.ts +319 -0
- package/src/app/api/claude/apply/route.ts +185 -0
- package/src/app/api/claude/pick-folder/route.ts +64 -0
- package/src/app/api/claude/scan/route.ts +221 -0
- package/src/app/api/claude/status/route.ts +55 -0
- package/src/app/api/project/scan/route.ts +634 -0
- package/src/app/api/project-scan/css-variables/route.ts +238 -0
- package/src/app/api/project-scan/route.ts +40 -0
- package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
- package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
- package/src/app/docs/DocsClient.tsx +322 -0
- package/src/app/docs/layout.tsx +7 -0
- package/src/app/docs/page.tsx +855 -0
- package/src/app/globals.css +176 -0
- package/src/app/layout.tsx +19 -0
- package/src/app/page.tsx +46 -0
- package/src/bridge/api-handlers.ts +885 -0
- package/src/bridge/proxy-handler.ts +329 -0
- package/src/bridge/server.ts +113 -0
- package/src/components/BreakpointTabs.tsx +72 -0
- package/src/components/ChangeSummaryModal.tsx +267 -0
- package/src/components/ConnectModal.tsx +994 -0
- package/src/components/Editor.tsx +90 -0
- package/src/components/PageSelector.tsx +208 -0
- package/src/components/PreviewFrame.tsx +299 -0
- package/src/components/ProjectFolderBanner.tsx +91 -0
- package/src/components/ResponsiveToolbar.tsx +222 -0
- package/src/components/TargetSelector.tsx +243 -0
- package/src/components/TopBar.tsx +315 -0
- package/src/components/common/CollapsibleSection.tsx +36 -0
- package/src/components/common/ColorPicker.tsx +920 -0
- package/src/components/common/EditablePre.tsx +136 -0
- package/src/components/common/ErrorBoundary.tsx +65 -0
- package/src/components/common/ResizablePanel.tsx +83 -0
- package/src/components/common/ScanAnimation.tsx +76 -0
- package/src/components/common/ToastContainer.tsx +97 -0
- package/src/components/common/UnitInput.tsx +77 -0
- package/src/components/common/VariableColorPicker.tsx +622 -0
- package/src/components/left-panel/AddElementPanel.tsx +237 -0
- package/src/components/left-panel/ComponentsPanel.tsx +609 -0
- package/src/components/left-panel/IconSidebar.tsx +99 -0
- package/src/components/left-panel/LayerNode.tsx +874 -0
- package/src/components/left-panel/LayerSearch.tsx +23 -0
- package/src/components/left-panel/LayersPanel.tsx +52 -0
- package/src/components/left-panel/LeftPanel.tsx +122 -0
- package/src/components/left-panel/PagesPanel.tsx +114 -0
- package/src/components/left-panel/icons.tsx +162 -0
- package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
- package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
- package/src/components/right-panel/ElementLogBox.tsx +248 -0
- package/src/components/right-panel/PanelTabs.tsx +83 -0
- package/src/components/right-panel/RightPanel.tsx +41 -0
- package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
- package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
- package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
- package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
- package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
- package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
- package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
- package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
- package/src/components/right-panel/claude/DiffCard.tsx +130 -0
- package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
- package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
- package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
- package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
- package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
- package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
- package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
- package/src/components/right-panel/design/BorderSection.tsx +161 -0
- package/src/components/right-panel/design/CSSRawView.tsx +412 -0
- package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
- package/src/components/right-panel/design/DesignPanel.tsx +275 -0
- package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
- package/src/components/right-panel/design/GradientEditor.tsx +726 -0
- package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
- package/src/components/right-panel/design/PositionSection.tsx +865 -0
- package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
- package/src/components/right-panel/design/SVGSection.tsx +361 -0
- package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
- package/src/components/right-panel/design/SizeSection.tsx +183 -0
- package/src/components/right-panel/design/TextSection.tsx +719 -0
- package/src/components/right-panel/design/icons.tsx +948 -0
- package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
- package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
- package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
- package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
- package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
- package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
- package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
- package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
- package/src/hooks/useBridge.ts +95 -0
- package/src/hooks/useChangeTracker.ts +563 -0
- package/src/hooks/useClaudeAPI.ts +118 -0
- package/src/hooks/useDOMTree.ts +25 -0
- package/src/hooks/useKeyboardShortcuts.ts +76 -0
- package/src/hooks/usePostMessage.ts +589 -0
- package/src/hooks/useProjectScan.ts +204 -0
- package/src/hooks/useResizable.ts +20 -0
- package/src/hooks/useSelectedElement.ts +51 -0
- package/src/hooks/useTargetUrl.ts +81 -0
- package/src/inspector/DOMTraverser.ts +71 -0
- package/src/inspector/ElementSelector.ts +23 -0
- package/src/inspector/HoverHighlighter.ts +54 -0
- package/src/inspector/SelectionHighlighter.ts +27 -0
- package/src/inspector/StyleExtractor.ts +19 -0
- package/src/inspector/inspector.ts +17 -0
- package/src/inspector/messaging.ts +30 -0
- package/src/lib/apiBase.ts +15 -0
- package/src/lib/classifyElement.ts +430 -0
- package/src/lib/claude-bin.ts +197 -0
- package/src/lib/claude-stream.ts +158 -0
- package/src/lib/clientProjectScanner.ts +344 -0
- package/src/lib/componentMatcher.ts +156 -0
- package/src/lib/constants.ts +573 -0
- package/src/lib/cssVariableUtils.ts +409 -0
- package/src/lib/diffParser.ts +206 -0
- package/src/lib/folderPicker.ts +84 -0
- package/src/lib/gradientParser.ts +160 -0
- package/src/lib/projectScanner.ts +355 -0
- package/src/lib/promptBuilder.ts +402 -0
- package/src/lib/shadowParser.ts +124 -0
- package/src/lib/tailwindClassParser.ts +248 -0
- package/src/lib/textShadowUtils.ts +106 -0
- package/src/lib/utils.ts +299 -0
- package/src/lib/validatePath.ts +40 -0
- package/src/proxy.ts +92 -0
- package/src/server/terminal-server.ts +104 -0
- package/src/store/changeSlice.ts +288 -0
- package/src/store/claudeSlice.ts +222 -0
- package/src/store/componentSlice.ts +90 -0
- package/src/store/consoleSlice.ts +51 -0
- package/src/store/cssVariableSlice.ts +94 -0
- package/src/store/elementSlice.ts +78 -0
- package/src/store/index.ts +35 -0
- package/src/store/terminalSlice.ts +30 -0
- package/src/store/treeSlice.ts +69 -0
- package/src/store/uiSlice.ts +327 -0
- package/src/types/changelog.ts +49 -0
- package/src/types/claude.ts +131 -0
- package/src/types/component.ts +49 -0
- package/src/types/cssVariables.ts +18 -0
- package/src/types/element.ts +21 -0
- package/src/types/file-system-access.d.ts +27 -0
- package/src/types/gradient.ts +12 -0
- package/src/types/messages.ts +392 -0
- package/src/types/shadow.ts +8 -0
- package/src/types/tree.ts +9 -0
- package/tsconfig.json +42 -0
- package/tsconfig.server.json +12 -0
|
@@ -0,0 +1,1872 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pAInt Inspector — Standalone Script
|
|
3
|
+
*
|
|
4
|
+
* Add this script to your project to enable pAInt inspection.
|
|
5
|
+
* Works with both local and Vercel-hosted editors automatically.
|
|
6
|
+
*
|
|
7
|
+
* Next.js App Router:
|
|
8
|
+
* <script src="http://localhost:4000/dev-editor-inspector.js" />
|
|
9
|
+
* (place inside <body> in layout.tsx, after all other content)
|
|
10
|
+
*
|
|
11
|
+
* Plain HTML:
|
|
12
|
+
* <script src="http://localhost:4000/dev-editor-inspector.js"></script>
|
|
13
|
+
* (place before </body>)
|
|
14
|
+
*
|
|
15
|
+
* The script auto-detects the editor origin from its src attribute.
|
|
16
|
+
* If detection fails, it discovers the editor via handshake.
|
|
17
|
+
* It does nothing when the page is not loaded inside an iframe.
|
|
18
|
+
*/
|
|
19
|
+
;(function () {
|
|
20
|
+
// Iframe guard — script does nothing if not loaded inside an iframe
|
|
21
|
+
if (window.parent === window) return
|
|
22
|
+
|
|
23
|
+
// --- Origin Detection ---
|
|
24
|
+
// Determine the editor (parent) window's origin for postMessage.
|
|
25
|
+
// Strategy: try same-origin access first (works when loaded via proxy),
|
|
26
|
+
// then fall back to '*' (works for cross-origin / direct URL loading).
|
|
27
|
+
// Note: postMessage does NOT throw on origin mismatch — it silently
|
|
28
|
+
// drops the message. So we must detect the correct origin upfront.
|
|
29
|
+
var parentOrigin = '*'
|
|
30
|
+
try {
|
|
31
|
+
// Same-origin: can access parent's location directly
|
|
32
|
+
parentOrigin = window.parent.location.origin
|
|
33
|
+
} catch (e) {
|
|
34
|
+
// Cross-origin: can't access parent's origin.
|
|
35
|
+
// Use '*' — safe for localhost dev tool use case.
|
|
36
|
+
parentOrigin = '*'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Track whether the editor has acknowledged our connection
|
|
40
|
+
var connected = false
|
|
41
|
+
|
|
42
|
+
function send(message) {
|
|
43
|
+
try {
|
|
44
|
+
window.parent.postMessage(message, parentOrigin)
|
|
45
|
+
} catch (e) {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- Console Interception (no DOM dependency, runs immediately) ---
|
|
49
|
+
var originalConsole = {
|
|
50
|
+
log: console.log,
|
|
51
|
+
info: console.info,
|
|
52
|
+
warn: console.warn,
|
|
53
|
+
error: console.error,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function serializeArg(arg) {
|
|
57
|
+
if (arg === null) return 'null'
|
|
58
|
+
if (arg === undefined) return 'undefined'
|
|
59
|
+
if (typeof arg === 'string') return arg
|
|
60
|
+
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg)
|
|
61
|
+
if (arg instanceof Error) return arg.stack || arg.message || String(arg)
|
|
62
|
+
try {
|
|
63
|
+
return JSON.stringify(arg)
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return String(arg)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function interceptConsole(level) {
|
|
70
|
+
console[level] = function () {
|
|
71
|
+
var args = []
|
|
72
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
73
|
+
args.push(serializeArg(arguments[i]))
|
|
74
|
+
}
|
|
75
|
+
originalConsole[level].apply(console, arguments)
|
|
76
|
+
send({
|
|
77
|
+
type: 'CONSOLE_MESSAGE',
|
|
78
|
+
payload: { level: level, args: args, timestamp: Date.now() },
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interceptConsole('log')
|
|
84
|
+
interceptConsole('info')
|
|
85
|
+
interceptConsole('warn')
|
|
86
|
+
interceptConsole('error')
|
|
87
|
+
|
|
88
|
+
window.onerror = function (message, source, line, column) {
|
|
89
|
+
send({
|
|
90
|
+
type: 'CONSOLE_MESSAGE',
|
|
91
|
+
payload: {
|
|
92
|
+
level: 'error',
|
|
93
|
+
args: [String(message)],
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
source: source || undefined,
|
|
96
|
+
line: line || undefined,
|
|
97
|
+
column: column || undefined,
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
window.addEventListener('unhandledrejection', function (e) {
|
|
103
|
+
var reason = e.reason
|
|
104
|
+
var msg =
|
|
105
|
+
reason instanceof Error
|
|
106
|
+
? reason.stack || reason.message || String(reason)
|
|
107
|
+
: String(reason)
|
|
108
|
+
send({
|
|
109
|
+
type: 'CONSOLE_MESSAGE',
|
|
110
|
+
payload: {
|
|
111
|
+
level: 'error',
|
|
112
|
+
args: ['Unhandled Promise Rejection: ' + msg],
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// --- Inspector initialization (requires document.body) ---
|
|
119
|
+
function initInspector() {
|
|
120
|
+
function kebabToCamel(str) {
|
|
121
|
+
return str.replace(/-([a-z])/g, function (m, c) {
|
|
122
|
+
return c.toUpperCase()
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function camelToKebab(str) {
|
|
127
|
+
return str.replace(/[A-Z]/g, function (c) {
|
|
128
|
+
return '-' + c.toLowerCase()
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function generateSelectorPath(element) {
|
|
133
|
+
var parts = []
|
|
134
|
+
var current = element
|
|
135
|
+
while (current && current !== document.documentElement) {
|
|
136
|
+
var selector = current.tagName.toLowerCase()
|
|
137
|
+
if (current.id) {
|
|
138
|
+
selector += '#' + CSS.escape(current.id)
|
|
139
|
+
parts.unshift(selector)
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
if (current.className && typeof current.className === 'string') {
|
|
143
|
+
var classes = current.className.trim().split(/\s+/).filter(Boolean)
|
|
144
|
+
if (classes.length > 0) {
|
|
145
|
+
selector +=
|
|
146
|
+
'.' +
|
|
147
|
+
classes
|
|
148
|
+
.map(function (c) {
|
|
149
|
+
return CSS.escape(c)
|
|
150
|
+
})
|
|
151
|
+
.join('.')
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
var parent = current.parentElement
|
|
155
|
+
if (parent) {
|
|
156
|
+
var siblings = Array.from(parent.children).filter(function (c) {
|
|
157
|
+
return c.tagName === current.tagName
|
|
158
|
+
})
|
|
159
|
+
if (siblings.length > 1) {
|
|
160
|
+
var index = siblings.indexOf(current) + 1
|
|
161
|
+
selector += ':nth-of-type(' + index + ')'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
parts.unshift(selector)
|
|
165
|
+
current = current.parentElement
|
|
166
|
+
}
|
|
167
|
+
return parts.join(' > ')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function serializeTree(element) {
|
|
171
|
+
if (!element || element.nodeType !== 1) return null
|
|
172
|
+
var tagName = element.tagName.toLowerCase()
|
|
173
|
+
if (tagName === 'script' || tagName === 'style' || tagName === 'link')
|
|
174
|
+
return null
|
|
175
|
+
var children = []
|
|
176
|
+
for (var i = 0; i < element.children.length; i++) {
|
|
177
|
+
var child = serializeTree(element.children[i])
|
|
178
|
+
if (child) children.push(child)
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
id: generateSelectorPath(element),
|
|
182
|
+
tagName: tagName,
|
|
183
|
+
className:
|
|
184
|
+
element.className && typeof element.className === 'string'
|
|
185
|
+
? element.className
|
|
186
|
+
: null,
|
|
187
|
+
elementId: element.id || null,
|
|
188
|
+
children: children,
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getComputedStylesForElement(el) {
|
|
193
|
+
var computed = window.getComputedStyle(el)
|
|
194
|
+
var props = [
|
|
195
|
+
'width',
|
|
196
|
+
'height',
|
|
197
|
+
'min-width',
|
|
198
|
+
'min-height',
|
|
199
|
+
'max-width',
|
|
200
|
+
'max-height',
|
|
201
|
+
'overflow',
|
|
202
|
+
'margin-top',
|
|
203
|
+
'margin-right',
|
|
204
|
+
'margin-bottom',
|
|
205
|
+
'margin-left',
|
|
206
|
+
'padding-top',
|
|
207
|
+
'padding-right',
|
|
208
|
+
'padding-bottom',
|
|
209
|
+
'padding-left',
|
|
210
|
+
'font-family',
|
|
211
|
+
'font-size',
|
|
212
|
+
'font-weight',
|
|
213
|
+
'line-height',
|
|
214
|
+
'letter-spacing',
|
|
215
|
+
'text-align',
|
|
216
|
+
'text-decoration',
|
|
217
|
+
'text-transform',
|
|
218
|
+
'color',
|
|
219
|
+
'border-width',
|
|
220
|
+
'border-style',
|
|
221
|
+
'border-color',
|
|
222
|
+
'border-radius',
|
|
223
|
+
'border-top-left-radius',
|
|
224
|
+
'border-top-right-radius',
|
|
225
|
+
'border-bottom-right-radius',
|
|
226
|
+
'border-bottom-left-radius',
|
|
227
|
+
'background-color',
|
|
228
|
+
'background-image',
|
|
229
|
+
'opacity',
|
|
230
|
+
'display',
|
|
231
|
+
'flex-direction',
|
|
232
|
+
'justify-content',
|
|
233
|
+
'align-items',
|
|
234
|
+
'flex-wrap',
|
|
235
|
+
'gap',
|
|
236
|
+
'column-gap',
|
|
237
|
+
'row-gap',
|
|
238
|
+
'grid-template-columns',
|
|
239
|
+
'grid-template-rows',
|
|
240
|
+
'grid-auto-flow',
|
|
241
|
+
'justify-items',
|
|
242
|
+
'vertical-align',
|
|
243
|
+
'position',
|
|
244
|
+
'top',
|
|
245
|
+
'right',
|
|
246
|
+
'bottom',
|
|
247
|
+
'left',
|
|
248
|
+
'z-index',
|
|
249
|
+
'box-sizing',
|
|
250
|
+
'fill',
|
|
251
|
+
'stroke',
|
|
252
|
+
]
|
|
253
|
+
var styles = {}
|
|
254
|
+
for (var i = 0; i < props.length; i++) {
|
|
255
|
+
styles[kebabToCamel(props[i])] = computed.getPropertyValue(props[i])
|
|
256
|
+
}
|
|
257
|
+
return styles
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function scanCSSVariableDefinitions() {
|
|
261
|
+
var definitions = {}
|
|
262
|
+
var rootStyles = window.getComputedStyle(document.documentElement)
|
|
263
|
+
var FRAMEWORK_PREFIXES = [
|
|
264
|
+
'--tw-',
|
|
265
|
+
'--next-',
|
|
266
|
+
'--radix-',
|
|
267
|
+
'--chakra-',
|
|
268
|
+
'--mantine-',
|
|
269
|
+
'--mui-',
|
|
270
|
+
'--framer-',
|
|
271
|
+
'--sb-',
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
function extractFromRules(rules) {
|
|
275
|
+
for (var ri = 0; ri < rules.length; ri++) {
|
|
276
|
+
var rule = rules[ri]
|
|
277
|
+
// Recurse into @media and other grouping rules
|
|
278
|
+
if (rule.cssRules) {
|
|
279
|
+
extractFromRules(rule.cssRules)
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
if (!rule.style) continue
|
|
283
|
+
for (var pi = 0; pi < rule.style.length; pi++) {
|
|
284
|
+
var prop = rule.style[pi]
|
|
285
|
+
if (prop.indexOf('--') === 0) {
|
|
286
|
+
var rawVal = rule.style.getPropertyValue(prop).trim()
|
|
287
|
+
var resolved = rootStyles.getPropertyValue(prop).trim()
|
|
288
|
+
definitions[prop] = {
|
|
289
|
+
value: rawVal,
|
|
290
|
+
resolvedValue: resolved || rawVal,
|
|
291
|
+
selector: rule.selectorText || '',
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Primary: check for <style data-design-tokens> or <link data-design-tokens>
|
|
299
|
+
var taggedSheets = []
|
|
300
|
+
for (var si = 0; si < document.styleSheets.length; si++) {
|
|
301
|
+
var sheet = document.styleSheets[si]
|
|
302
|
+
if (
|
|
303
|
+
sheet.ownerNode &&
|
|
304
|
+
sheet.ownerNode.hasAttribute &&
|
|
305
|
+
sheet.ownerNode.hasAttribute('data-design-tokens')
|
|
306
|
+
) {
|
|
307
|
+
taggedSheets.push(sheet)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (taggedSheets.length > 0) {
|
|
312
|
+
// Explicit mode: scan only tagged sheets
|
|
313
|
+
for (var ti = 0; ti < taggedSheets.length; ti++) {
|
|
314
|
+
var taggedRules
|
|
315
|
+
try {
|
|
316
|
+
taggedRules = taggedSheets[ti].cssRules || taggedSheets[ti].rules
|
|
317
|
+
} catch (e) {
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
if (taggedRules) extractFromRules(taggedRules)
|
|
321
|
+
}
|
|
322
|
+
return { definitions: definitions, isExplicit: true }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Fallback: scan all sheets
|
|
326
|
+
for (var fi = 0; fi < document.styleSheets.length; fi++) {
|
|
327
|
+
var fallbackSheet = document.styleSheets[fi]
|
|
328
|
+
var fallbackRules
|
|
329
|
+
try {
|
|
330
|
+
fallbackRules = fallbackSheet.cssRules || fallbackSheet.rules
|
|
331
|
+
} catch (e) {
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
if (fallbackRules) extractFromRules(fallbackRules)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check for <meta name="design-tokens-prefix"> allowlist
|
|
338
|
+
var metaEl = document.querySelector('meta[name="design-tokens-prefix"]')
|
|
339
|
+
var metaPrefixes = null
|
|
340
|
+
if (metaEl) {
|
|
341
|
+
var content = metaEl.getAttribute('content')
|
|
342
|
+
if (content) {
|
|
343
|
+
metaPrefixes = content.split(',')
|
|
344
|
+
for (var mpi = 0; mpi < metaPrefixes.length; mpi++) {
|
|
345
|
+
metaPrefixes[mpi] = metaPrefixes[mpi].trim()
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Filter definitions
|
|
351
|
+
var filtered = {}
|
|
352
|
+
var keys = Object.keys(definitions)
|
|
353
|
+
for (var ki = 0; ki < keys.length; ki++) {
|
|
354
|
+
var key = keys[ki]
|
|
355
|
+
if (metaPrefixes) {
|
|
356
|
+
// Meta allowlist mode: only keep variables matching specified prefixes
|
|
357
|
+
var allowed = false
|
|
358
|
+
for (var api = 0; api < metaPrefixes.length; api++) {
|
|
359
|
+
if (key.indexOf(metaPrefixes[api]) === 0) {
|
|
360
|
+
allowed = true
|
|
361
|
+
break
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (allowed) filtered[key] = definitions[key]
|
|
365
|
+
} else {
|
|
366
|
+
// Default fallback: filter out known framework prefixes
|
|
367
|
+
var isFramework = false
|
|
368
|
+
for (var fpi = 0; fpi < FRAMEWORK_PREFIXES.length; fpi++) {
|
|
369
|
+
if (key.indexOf(FRAMEWORK_PREFIXES[fpi]) === 0) {
|
|
370
|
+
isFramework = true
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!isFramework) filtered[key] = definitions[key]
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return { definitions: filtered, isExplicit: false }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function detectCSSVariablesOnElement(el) {
|
|
382
|
+
var usages = {}
|
|
383
|
+
if (el.style) {
|
|
384
|
+
for (var si = 0; si < el.style.length; si++) {
|
|
385
|
+
var inlineProp = el.style[si]
|
|
386
|
+
var inlineVal = el.style.getPropertyValue(inlineProp)
|
|
387
|
+
if (inlineVal && inlineVal.indexOf('var(') >= 0) {
|
|
388
|
+
usages[inlineProp] = inlineVal.trim()
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (var shi = 0; shi < document.styleSheets.length; shi++) {
|
|
393
|
+
var sheet = document.styleSheets[shi]
|
|
394
|
+
var rules
|
|
395
|
+
try {
|
|
396
|
+
rules = sheet.cssRules || sheet.rules
|
|
397
|
+
} catch (e) {
|
|
398
|
+
continue
|
|
399
|
+
}
|
|
400
|
+
if (!rules) continue
|
|
401
|
+
for (var ri = 0; ri < rules.length; ri++) {
|
|
402
|
+
var rule = rules[ri]
|
|
403
|
+
if (!rule.selectorText || !rule.style) continue
|
|
404
|
+
var matches = false
|
|
405
|
+
try {
|
|
406
|
+
matches = el.matches(rule.selectorText)
|
|
407
|
+
} catch (e) {
|
|
408
|
+
continue
|
|
409
|
+
}
|
|
410
|
+
if (!matches) continue
|
|
411
|
+
for (var pi = 0; pi < rule.style.length; pi++) {
|
|
412
|
+
var prop = rule.style[pi]
|
|
413
|
+
var val = rule.style.getPropertyValue(prop)
|
|
414
|
+
if (val && val.indexOf('var(') >= 0) {
|
|
415
|
+
if (!usages[prop]) {
|
|
416
|
+
usages[prop] = val.trim()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return usages
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Selection highlight
|
|
426
|
+
var selectionOverlay = document.createElement('div')
|
|
427
|
+
selectionOverlay.style.cssText =
|
|
428
|
+
'position:fixed;pointer-events:none;z-index:999997;border:2px solid #4a9eff;display:none;'
|
|
429
|
+
document.body.appendChild(selectionOverlay)
|
|
430
|
+
|
|
431
|
+
// Hover highlight — dashed green border + element name label
|
|
432
|
+
var hoverOverlay = document.createElement('div')
|
|
433
|
+
hoverOverlay.style.cssText =
|
|
434
|
+
'position:fixed;pointer-events:none;z-index:999996;border:1px dashed #4ade80;display:none;transition:top 0.04s,left 0.04s,width 0.04s,height 0.04s;'
|
|
435
|
+
document.body.appendChild(hoverOverlay)
|
|
436
|
+
|
|
437
|
+
var hoverLabel = document.createElement('div')
|
|
438
|
+
hoverLabel.style.cssText =
|
|
439
|
+
'position:absolute;top:-18px;left:-1px;padding:1px 6px;font-size:10px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;line-height:14px;color:#fff;background:#1D3F23;border-radius:3px 3px 0 0;white-space:nowrap;pointer-events:none;'
|
|
440
|
+
hoverOverlay.appendChild(hoverLabel)
|
|
441
|
+
|
|
442
|
+
var hoveredElement = null
|
|
443
|
+
|
|
444
|
+
function getElementLabel(el) {
|
|
445
|
+
var tag = el.tagName.toLowerCase()
|
|
446
|
+
if (el.id) return tag + '#' + el.id
|
|
447
|
+
var cls = el.className
|
|
448
|
+
if (cls && typeof cls === 'string') {
|
|
449
|
+
var classes = cls.trim().split(/\s+/)
|
|
450
|
+
// Prefer c- prefixed class for the label
|
|
451
|
+
for (var i = 0; i < classes.length; i++) {
|
|
452
|
+
if (classes[i].indexOf('c-') === 0) return tag + '.' + classes[i]
|
|
453
|
+
}
|
|
454
|
+
if (classes[0]) return tag + '.' + classes[0]
|
|
455
|
+
}
|
|
456
|
+
return tag
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
document.addEventListener('mousemove', function (e) {
|
|
460
|
+
if (!selectionModeEnabled) {
|
|
461
|
+
hoverOverlay.style.display = 'none'
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
var el = document.elementFromPoint(e.clientX, e.clientY)
|
|
465
|
+
if (
|
|
466
|
+
!el ||
|
|
467
|
+
el === hoverOverlay ||
|
|
468
|
+
el === selectionOverlay ||
|
|
469
|
+
el === hoverLabel
|
|
470
|
+
)
|
|
471
|
+
return
|
|
472
|
+
if (el === selectedElement) {
|
|
473
|
+
hoverOverlay.style.display = 'none'
|
|
474
|
+
hoveredElement = null
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
if (el === hoveredElement) return
|
|
478
|
+
hoveredElement = el
|
|
479
|
+
var rect = el.getBoundingClientRect()
|
|
480
|
+
hoverOverlay.style.display = 'block'
|
|
481
|
+
hoverOverlay.style.top = rect.top + 'px'
|
|
482
|
+
hoverOverlay.style.left = rect.left + 'px'
|
|
483
|
+
hoverOverlay.style.width = rect.width + 'px'
|
|
484
|
+
hoverOverlay.style.height = rect.height + 'px'
|
|
485
|
+
hoverLabel.textContent = getElementLabel(el)
|
|
486
|
+
if (rect.top < 20) {
|
|
487
|
+
hoverLabel.style.top = 'auto'
|
|
488
|
+
hoverLabel.style.bottom = '-18px'
|
|
489
|
+
hoverLabel.style.borderRadius = '0 0 3px 3px'
|
|
490
|
+
} else {
|
|
491
|
+
hoverLabel.style.top = '-18px'
|
|
492
|
+
hoverLabel.style.bottom = 'auto'
|
|
493
|
+
hoverLabel.style.borderRadius = '3px 3px 0 0'
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
var selectedElement = null
|
|
498
|
+
var selectionModeEnabled = true
|
|
499
|
+
|
|
500
|
+
// Click selection — when selection mode is on, intercept clicks to select elements.
|
|
501
|
+
// When off, let clicks through so links and buttons work normally.
|
|
502
|
+
document.addEventListener(
|
|
503
|
+
'click',
|
|
504
|
+
function (e) {
|
|
505
|
+
if (!selectionModeEnabled) return
|
|
506
|
+
|
|
507
|
+
// If text editing is active, clicking outside commits the edit
|
|
508
|
+
if (textEditingActive) {
|
|
509
|
+
var clickedEl = document.elementFromPoint(e.clientX, e.clientY)
|
|
510
|
+
if (clickedEl !== textEditTarget) {
|
|
511
|
+
e.preventDefault()
|
|
512
|
+
e.stopPropagation()
|
|
513
|
+
commitTextEdit()
|
|
514
|
+
}
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
e.preventDefault()
|
|
519
|
+
e.stopPropagation()
|
|
520
|
+
var el = document.elementFromPoint(e.clientX, e.clientY)
|
|
521
|
+
if (
|
|
522
|
+
!el ||
|
|
523
|
+
el === selectionOverlay ||
|
|
524
|
+
el === hoverOverlay ||
|
|
525
|
+
el === hoverLabel
|
|
526
|
+
)
|
|
527
|
+
return
|
|
528
|
+
hoverOverlay.style.display = 'none'
|
|
529
|
+
selectElement(el)
|
|
530
|
+
},
|
|
531
|
+
true,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
function selectElement(el) {
|
|
535
|
+
// Don't select elements when selection mode is disabled (preview mode)
|
|
536
|
+
if (!selectionModeEnabled) return
|
|
537
|
+
selectedElement = el
|
|
538
|
+
var rect = el.getBoundingClientRect()
|
|
539
|
+
selectionOverlay.style.display = 'block'
|
|
540
|
+
selectionOverlay.style.top = rect.top + 'px'
|
|
541
|
+
selectionOverlay.style.left = rect.left + 'px'
|
|
542
|
+
selectionOverlay.style.width = rect.width + 'px'
|
|
543
|
+
selectionOverlay.style.height = rect.height + 'px'
|
|
544
|
+
|
|
545
|
+
var attrs = {}
|
|
546
|
+
for (var ai = 0; ai < el.attributes.length; ai++) {
|
|
547
|
+
var attr = el.attributes[ai]
|
|
548
|
+
attrs[attr.name] = attr.value
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
var text = (el.innerText || '').substring(0, 500) || null
|
|
552
|
+
var varUsages = detectCSSVariablesOnElement(el)
|
|
553
|
+
|
|
554
|
+
send({
|
|
555
|
+
type: 'ELEMENT_SELECTED',
|
|
556
|
+
payload: {
|
|
557
|
+
selectorPath: generateSelectorPath(el),
|
|
558
|
+
tagName: el.tagName.toLowerCase(),
|
|
559
|
+
className:
|
|
560
|
+
el.className && typeof el.className === 'string'
|
|
561
|
+
? el.className
|
|
562
|
+
: null,
|
|
563
|
+
id: el.id || null,
|
|
564
|
+
attributes: attrs,
|
|
565
|
+
innerText: text,
|
|
566
|
+
computedStyles: getComputedStylesForElement(el),
|
|
567
|
+
cssVariableUsages: varUsages,
|
|
568
|
+
boundingRect: {
|
|
569
|
+
x: rect.x,
|
|
570
|
+
y: rect.y,
|
|
571
|
+
width: rect.width,
|
|
572
|
+
height: rect.height,
|
|
573
|
+
top: rect.top,
|
|
574
|
+
right: rect.right,
|
|
575
|
+
bottom: rect.bottom,
|
|
576
|
+
left: rect.left,
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function clearSelection() {
|
|
583
|
+
selectedElement = null
|
|
584
|
+
selectionOverlay.style.display = 'none'
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function updateSelectionOverlay() {
|
|
588
|
+
if (!selectedElement || selectionOverlay.style.display === 'none') return
|
|
589
|
+
var rect = selectedElement.getBoundingClientRect()
|
|
590
|
+
selectionOverlay.style.top = rect.top + 'px'
|
|
591
|
+
selectionOverlay.style.left = rect.left + 'px'
|
|
592
|
+
selectionOverlay.style.width = rect.width + 'px'
|
|
593
|
+
selectionOverlay.style.height = rect.height + 'px'
|
|
594
|
+
}
|
|
595
|
+
window.addEventListener('scroll', updateSelectionOverlay, true)
|
|
596
|
+
window.addEventListener('resize', updateSelectionOverlay, true)
|
|
597
|
+
|
|
598
|
+
// --- Inline Text Editing ---
|
|
599
|
+
var textEditingActive = false
|
|
600
|
+
var originalTextContent = null
|
|
601
|
+
var textEditTarget = null
|
|
602
|
+
var SKIP_TEXT_EDIT_TAGS = {
|
|
603
|
+
INPUT: 1,
|
|
604
|
+
TEXTAREA: 1,
|
|
605
|
+
SELECT: 1,
|
|
606
|
+
IMG: 1,
|
|
607
|
+
VIDEO: 1,
|
|
608
|
+
IFRAME: 1,
|
|
609
|
+
SVG: 1,
|
|
610
|
+
svg: 1,
|
|
611
|
+
CANVAS: 1,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function commitTextEdit() {
|
|
615
|
+
if (!textEditingActive || !textEditTarget) return
|
|
616
|
+
var newText = textEditTarget.textContent || ''
|
|
617
|
+
var el = textEditTarget
|
|
618
|
+
var selectorPath = generateSelectorPath(el)
|
|
619
|
+
|
|
620
|
+
el.contentEditable = 'false'
|
|
621
|
+
el.style.removeProperty('outline')
|
|
622
|
+
el.style.removeProperty('outline-offset')
|
|
623
|
+
el.style.removeProperty('min-width')
|
|
624
|
+
textEditingActive = false
|
|
625
|
+
textEditTarget = null
|
|
626
|
+
|
|
627
|
+
observer.observe(document.body, {
|
|
628
|
+
childList: true,
|
|
629
|
+
subtree: true,
|
|
630
|
+
attributes: true,
|
|
631
|
+
attributeFilter: ['class', 'id', 'style'],
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
selectionOverlay.style.display = 'block'
|
|
635
|
+
updateSelectionOverlay()
|
|
636
|
+
|
|
637
|
+
if (newText !== originalTextContent) {
|
|
638
|
+
send({
|
|
639
|
+
type: 'TEXT_CHANGED',
|
|
640
|
+
payload: {
|
|
641
|
+
selectorPath: selectorPath,
|
|
642
|
+
originalText: originalTextContent || '',
|
|
643
|
+
newText: newText,
|
|
644
|
+
},
|
|
645
|
+
})
|
|
646
|
+
}
|
|
647
|
+
originalTextContent = null
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function cancelTextEdit() {
|
|
651
|
+
if (!textEditingActive || !textEditTarget) return
|
|
652
|
+
textEditTarget.textContent = originalTextContent
|
|
653
|
+
textEditTarget.contentEditable = 'false'
|
|
654
|
+
textEditTarget.style.removeProperty('outline')
|
|
655
|
+
textEditTarget.style.removeProperty('outline-offset')
|
|
656
|
+
textEditTarget.style.removeProperty('min-width')
|
|
657
|
+
textEditingActive = false
|
|
658
|
+
textEditTarget = null
|
|
659
|
+
originalTextContent = null
|
|
660
|
+
|
|
661
|
+
observer.observe(document.body, {
|
|
662
|
+
childList: true,
|
|
663
|
+
subtree: true,
|
|
664
|
+
attributes: true,
|
|
665
|
+
attributeFilter: ['class', 'id', 'style'],
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
selectionOverlay.style.display = 'block'
|
|
669
|
+
updateSelectionOverlay()
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Find the best editable text target from a starting element.
|
|
673
|
+
// Walks down through wrappers to find a leaf text node,
|
|
674
|
+
// or returns the element itself if it has direct text content.
|
|
675
|
+
// Handles: <button><svg/>Submit</button>, <a><svg/><span>text</span><svg/></a>, etc.
|
|
676
|
+
function findTextTarget(el) {
|
|
677
|
+
if (!el || SKIP_TEXT_EDIT_TAGS[el.tagName]) return null
|
|
678
|
+
// Leaf node with text — ideal target
|
|
679
|
+
if (el.children.length === 0) {
|
|
680
|
+
return el.textContent && el.textContent.trim() ? el : null
|
|
681
|
+
}
|
|
682
|
+
// Single child — recurse into it (common: <button><span>text</span></button>)
|
|
683
|
+
if (el.children.length === 1) {
|
|
684
|
+
var child = el.children[0]
|
|
685
|
+
if (SKIP_TEXT_EDIT_TAGS[child.tagName]) {
|
|
686
|
+
return hasDirectTextNodes(el) ? el : null
|
|
687
|
+
}
|
|
688
|
+
return findTextTarget(child)
|
|
689
|
+
}
|
|
690
|
+
// Multiple children — find the single non-skippable text-bearing child
|
|
691
|
+
// (handles: <a><svg/><span>Book a Call</span><svg/></a>)
|
|
692
|
+
var textChild = null
|
|
693
|
+
for (var i = 0; i < el.children.length; i++) {
|
|
694
|
+
var ch = el.children[i]
|
|
695
|
+
if (SKIP_TEXT_EDIT_TAGS[ch.tagName]) continue
|
|
696
|
+
if (ch.textContent && ch.textContent.trim()) {
|
|
697
|
+
if (textChild) {
|
|
698
|
+
textChild = null
|
|
699
|
+
break
|
|
700
|
+
} // ambiguous — multiple text children
|
|
701
|
+
textChild = ch
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (textChild) return findTextTarget(textChild)
|
|
705
|
+
// Fall back: allow editing if element has direct text nodes
|
|
706
|
+
return hasDirectTextNodes(el) ? el : null
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function hasDirectTextNodes(el) {
|
|
710
|
+
for (var i = 0; i < el.childNodes.length; i++) {
|
|
711
|
+
if (
|
|
712
|
+
el.childNodes[i].nodeType === 3 &&
|
|
713
|
+
el.childNodes[i].textContent.trim()
|
|
714
|
+
) {
|
|
715
|
+
return true
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
document.addEventListener(
|
|
722
|
+
'dblclick',
|
|
723
|
+
function (e) {
|
|
724
|
+
if (!selectionModeEnabled) return
|
|
725
|
+
e.preventDefault()
|
|
726
|
+
e.stopPropagation()
|
|
727
|
+
|
|
728
|
+
var el = document.elementFromPoint(e.clientX, e.clientY)
|
|
729
|
+
// If the selection overlay is in the way, temporarily hide it
|
|
730
|
+
// and re-probe to get the actual element underneath
|
|
731
|
+
if (el === selectionOverlay || el === hoverOverlay) {
|
|
732
|
+
var prevSel = selectionOverlay.style.display
|
|
733
|
+
var prevHov = hoverOverlay.style.display
|
|
734
|
+
selectionOverlay.style.display = 'none'
|
|
735
|
+
hoverOverlay.style.display = 'none'
|
|
736
|
+
el = document.elementFromPoint(e.clientX, e.clientY)
|
|
737
|
+
selectionOverlay.style.display = prevSel
|
|
738
|
+
hoverOverlay.style.display = prevHov
|
|
739
|
+
}
|
|
740
|
+
if (!el) return
|
|
741
|
+
if (SKIP_TEXT_EDIT_TAGS[el.tagName]) return
|
|
742
|
+
|
|
743
|
+
// Find the best editable text target (may walk down into children)
|
|
744
|
+
el = findTextTarget(el)
|
|
745
|
+
if (!el) return
|
|
746
|
+
|
|
747
|
+
var text = el.textContent
|
|
748
|
+
if (text === null || text === undefined) return
|
|
749
|
+
|
|
750
|
+
textEditingActive = true
|
|
751
|
+
textEditTarget = el
|
|
752
|
+
originalTextContent = text
|
|
753
|
+
|
|
754
|
+
observer.disconnect()
|
|
755
|
+
|
|
756
|
+
selectionOverlay.style.display = 'none'
|
|
757
|
+
|
|
758
|
+
el.contentEditable = 'true'
|
|
759
|
+
el.style.setProperty('outline', '2px solid #4a9eff', 'important')
|
|
760
|
+
el.style.setProperty('outline-offset', '2px', 'important')
|
|
761
|
+
el.style.setProperty('min-width', '20px', 'important')
|
|
762
|
+
el.focus()
|
|
763
|
+
|
|
764
|
+
var range = document.createRange()
|
|
765
|
+
range.selectNodeContents(el)
|
|
766
|
+
var sel = window.getSelection()
|
|
767
|
+
sel.removeAllRanges()
|
|
768
|
+
sel.addRange(range)
|
|
769
|
+
},
|
|
770
|
+
true,
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
document.addEventListener(
|
|
774
|
+
'keydown',
|
|
775
|
+
function (e) {
|
|
776
|
+
if (textEditingActive) {
|
|
777
|
+
e.stopPropagation()
|
|
778
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
779
|
+
e.preventDefault()
|
|
780
|
+
commitTextEdit()
|
|
781
|
+
} else if (e.key === 'Escape') {
|
|
782
|
+
e.preventDefault()
|
|
783
|
+
cancelTextEdit()
|
|
784
|
+
}
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Delete selected element (Delete or Backspace when not editing text)
|
|
789
|
+
if (
|
|
790
|
+
(e.key === 'Delete' || e.key === 'Backspace') &&
|
|
791
|
+
selectionModeEnabled &&
|
|
792
|
+
selectedElement
|
|
793
|
+
) {
|
|
794
|
+
e.preventDefault()
|
|
795
|
+
e.stopPropagation()
|
|
796
|
+
var delEl = selectedElement
|
|
797
|
+
var computed = window.getComputedStyle(delEl)
|
|
798
|
+
var origDisplay = computed.getPropertyValue('display')
|
|
799
|
+
var delSelector = generateSelectorPath(delEl)
|
|
800
|
+
|
|
801
|
+
var delAttrs = {}
|
|
802
|
+
for (var dai = 0; dai < delEl.attributes.length; dai++) {
|
|
803
|
+
var da = delEl.attributes[dai]
|
|
804
|
+
delAttrs[da.name] = da.value
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Hide the element
|
|
808
|
+
delEl.style.setProperty('display', 'none', 'important')
|
|
809
|
+
selectionOverlay.style.display = 'none'
|
|
810
|
+
selectedElement = null
|
|
811
|
+
|
|
812
|
+
send({
|
|
813
|
+
type: 'ELEMENT_DELETED',
|
|
814
|
+
payload: {
|
|
815
|
+
selectorPath: delSelector,
|
|
816
|
+
originalDisplay: origDisplay,
|
|
817
|
+
tagName: delEl.tagName.toLowerCase(),
|
|
818
|
+
className:
|
|
819
|
+
delEl.className && typeof delEl.className === 'string'
|
|
820
|
+
? delEl.className
|
|
821
|
+
: null,
|
|
822
|
+
elementId: delEl.id || null,
|
|
823
|
+
innerText: (delEl.innerText || '').substring(0, 500) || null,
|
|
824
|
+
attributes: delAttrs,
|
|
825
|
+
computedStyles: getComputedStylesForElement(delEl),
|
|
826
|
+
},
|
|
827
|
+
})
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
true,
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
// --- Component Detection ---
|
|
834
|
+
var SEMANTIC_COMPONENTS = {
|
|
835
|
+
button: 'Button',
|
|
836
|
+
nav: 'Navigation',
|
|
837
|
+
input: 'Input',
|
|
838
|
+
header: 'Header',
|
|
839
|
+
footer: 'Footer',
|
|
840
|
+
dialog: 'Dialog',
|
|
841
|
+
a: 'Link',
|
|
842
|
+
img: 'Image',
|
|
843
|
+
form: 'Form',
|
|
844
|
+
select: 'Select',
|
|
845
|
+
textarea: 'Textarea',
|
|
846
|
+
table: 'Table',
|
|
847
|
+
aside: 'Sidebar',
|
|
848
|
+
main: 'Main Content',
|
|
849
|
+
section: 'Section',
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
var ARIA_ROLE_MAP = {
|
|
853
|
+
button: 'Button',
|
|
854
|
+
navigation: 'Navigation',
|
|
855
|
+
tab: 'Tab',
|
|
856
|
+
tablist: 'Tab List',
|
|
857
|
+
dialog: 'Dialog',
|
|
858
|
+
alert: 'Alert',
|
|
859
|
+
menu: 'Menu',
|
|
860
|
+
menuitem: 'Menu Item',
|
|
861
|
+
search: 'Search',
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
var CLASS_PATTERNS = [
|
|
865
|
+
[/\bbtn\b/i, 'Button'],
|
|
866
|
+
[/\bcard\b/i, 'Card'],
|
|
867
|
+
[/\bmodal\b/i, 'Modal'],
|
|
868
|
+
[/\bdropdown\b/i, 'Dropdown'],
|
|
869
|
+
[/\bbadge\b/i, 'Badge'],
|
|
870
|
+
[/\bnav\b/i, 'Navigation'],
|
|
871
|
+
[/\balert\b/i, 'Alert'],
|
|
872
|
+
[/\btabs?\b/i, 'Tab'],
|
|
873
|
+
]
|
|
874
|
+
|
|
875
|
+
function detectCPrefixComponent(el) {
|
|
876
|
+
var cls = el.className
|
|
877
|
+
if (!cls || typeof cls !== 'string') return null
|
|
878
|
+
var classes = cls.trim().split(/\s+/)
|
|
879
|
+
for (var i = 0; i < classes.length; i++) {
|
|
880
|
+
if (classes[i].indexOf('c-') === 0 && classes[i].length > 2) {
|
|
881
|
+
var raw = classes[i].substring(2)
|
|
882
|
+
// Convert kebab-case to Title Case (e.g. "nav-bar" → "Nav Bar")
|
|
883
|
+
var name = raw
|
|
884
|
+
.split('-')
|
|
885
|
+
.map(function (part) {
|
|
886
|
+
return part.charAt(0).toUpperCase() + part.slice(1)
|
|
887
|
+
})
|
|
888
|
+
.join(' ')
|
|
889
|
+
return { name: name, method: 'c-prefix' }
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return null
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function detectSingleComponent(el) {
|
|
896
|
+
var tag = el.tagName.toLowerCase()
|
|
897
|
+
var dataComp = el.getAttribute('data-component')
|
|
898
|
+
if (dataComp) return { name: dataComp, method: 'data-attribute' }
|
|
899
|
+
// Detect c- prefixed class identifiers (e.g. "c-header" → "Header")
|
|
900
|
+
var cPrefix = detectCPrefixComponent(el)
|
|
901
|
+
if (cPrefix) return cPrefix
|
|
902
|
+
if (tag.indexOf('-') >= 0) return { name: tag, method: 'custom-element' }
|
|
903
|
+
if (SEMANTIC_COMPONENTS[tag])
|
|
904
|
+
return { name: SEMANTIC_COMPONENTS[tag], method: 'semantic-html' }
|
|
905
|
+
var role = el.getAttribute('role')
|
|
906
|
+
if (role && ARIA_ROLE_MAP[role])
|
|
907
|
+
return { name: ARIA_ROLE_MAP[role], method: 'aria-role' }
|
|
908
|
+
var cls = el.className
|
|
909
|
+
if (cls && typeof cls === 'string') {
|
|
910
|
+
for (var pi = 0; pi < CLASS_PATTERNS.length; pi++) {
|
|
911
|
+
if (CLASS_PATTERNS[pi][0].test(cls))
|
|
912
|
+
return { name: CLASS_PATTERNS[pi][1], method: 'class-pattern' }
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return null
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
var SIZE_SUFFIXES = ['xs', 'sm', 'md', 'lg', 'xl', '2xl']
|
|
919
|
+
var COLOR_SUFFIXES = [
|
|
920
|
+
'primary',
|
|
921
|
+
'secondary',
|
|
922
|
+
'success',
|
|
923
|
+
'danger',
|
|
924
|
+
'warning',
|
|
925
|
+
'info',
|
|
926
|
+
'light',
|
|
927
|
+
'dark',
|
|
928
|
+
]
|
|
929
|
+
var STATE_SUFFIXES = [
|
|
930
|
+
'active',
|
|
931
|
+
'disabled',
|
|
932
|
+
'loading',
|
|
933
|
+
'selected',
|
|
934
|
+
'checked',
|
|
935
|
+
]
|
|
936
|
+
|
|
937
|
+
function detectClassVariants(el) {
|
|
938
|
+
var groups = []
|
|
939
|
+
var cls = el.className
|
|
940
|
+
if (!cls || typeof cls !== 'string') return groups
|
|
941
|
+
var classes = cls.trim().split(/\s+/).filter(Boolean)
|
|
942
|
+
var prefixes = {}
|
|
943
|
+
for (var ci = 0; ci < classes.length; ci++) {
|
|
944
|
+
var parts = classes[ci].split('-')
|
|
945
|
+
if (parts.length >= 2) {
|
|
946
|
+
var base = parts[0]
|
|
947
|
+
var suffix = parts.slice(1).join('-')
|
|
948
|
+
if (!prefixes[base])
|
|
949
|
+
prefixes[base] = { currentClass: classes[ci], suffix: suffix }
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
for (var base in prefixes) {
|
|
953
|
+
if (!prefixes.hasOwnProperty(base)) continue
|
|
954
|
+
var sizeOpts = [],
|
|
955
|
+
colorOpts = [],
|
|
956
|
+
stateOpts = []
|
|
957
|
+
var currentClass = prefixes[base].currentClass
|
|
958
|
+
for (var si = 0; si < document.styleSheets.length; si++) {
|
|
959
|
+
var sheet = document.styleSheets[si]
|
|
960
|
+
var rules
|
|
961
|
+
try {
|
|
962
|
+
rules = sheet.cssRules || sheet.rules
|
|
963
|
+
} catch (e) {
|
|
964
|
+
continue
|
|
965
|
+
}
|
|
966
|
+
if (!rules) continue
|
|
967
|
+
for (var ri = 0; ri < rules.length; ri++) {
|
|
968
|
+
var rule = rules[ri]
|
|
969
|
+
if (!rule.selectorText) continue
|
|
970
|
+
var escapedBase = base.replace(/[-\/\\^*+?.()|[\]]/g, '\\$&')
|
|
971
|
+
var match = rule.selectorText.match(
|
|
972
|
+
new RegExp('\\.' + escapedBase + '-([\\w-]+)'),
|
|
973
|
+
)
|
|
974
|
+
if (!match) continue
|
|
975
|
+
var foundSuffix = match[1]
|
|
976
|
+
var foundClass = base + '-' + foundSuffix
|
|
977
|
+
if (foundClass === currentClass) continue
|
|
978
|
+
var opt = {
|
|
979
|
+
label: foundSuffix,
|
|
980
|
+
className: foundClass,
|
|
981
|
+
removeClassNames: [currentClass],
|
|
982
|
+
pseudoState: null,
|
|
983
|
+
pseudoStyles: null,
|
|
984
|
+
}
|
|
985
|
+
if (SIZE_SUFFIXES.indexOf(foundSuffix) >= 0) {
|
|
986
|
+
sizeOpts.push(opt)
|
|
987
|
+
} else if (COLOR_SUFFIXES.indexOf(foundSuffix) >= 0) {
|
|
988
|
+
colorOpts.push(opt)
|
|
989
|
+
} else if (STATE_SUFFIXES.indexOf(foundSuffix) >= 0) {
|
|
990
|
+
stateOpts.push(opt)
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
var currentSuffix = prefixes[base].suffix
|
|
995
|
+
var currentOpt = {
|
|
996
|
+
label: currentSuffix,
|
|
997
|
+
className: currentClass,
|
|
998
|
+
removeClassNames: [],
|
|
999
|
+
pseudoState: null,
|
|
1000
|
+
pseudoStyles: null,
|
|
1001
|
+
}
|
|
1002
|
+
if (sizeOpts.length > 0) {
|
|
1003
|
+
sizeOpts.unshift(currentOpt)
|
|
1004
|
+
var seen = {}
|
|
1005
|
+
sizeOpts = sizeOpts.filter(function (o) {
|
|
1006
|
+
if (seen[o.className]) return false
|
|
1007
|
+
seen[o.className] = true
|
|
1008
|
+
return true
|
|
1009
|
+
})
|
|
1010
|
+
if (sizeOpts.length >= 2) {
|
|
1011
|
+
for (var soi = 0; soi < sizeOpts.length; soi++) {
|
|
1012
|
+
sizeOpts[soi].removeClassNames = [currentClass]
|
|
1013
|
+
}
|
|
1014
|
+
sizeOpts[0].removeClassNames = []
|
|
1015
|
+
groups.push({
|
|
1016
|
+
groupName: 'Size',
|
|
1017
|
+
type: 'class',
|
|
1018
|
+
options: sizeOpts,
|
|
1019
|
+
activeIndex: 0,
|
|
1020
|
+
})
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (colorOpts.length > 0) {
|
|
1024
|
+
colorOpts.unshift(currentOpt)
|
|
1025
|
+
var seenC = {}
|
|
1026
|
+
colorOpts = colorOpts.filter(function (o) {
|
|
1027
|
+
if (seenC[o.className]) return false
|
|
1028
|
+
seenC[o.className] = true
|
|
1029
|
+
return true
|
|
1030
|
+
})
|
|
1031
|
+
if (colorOpts.length >= 2) {
|
|
1032
|
+
for (var coi = 0; coi < colorOpts.length; coi++) {
|
|
1033
|
+
colorOpts[coi].removeClassNames = [currentClass]
|
|
1034
|
+
}
|
|
1035
|
+
colorOpts[0].removeClassNames = []
|
|
1036
|
+
groups.push({
|
|
1037
|
+
groupName: 'Color',
|
|
1038
|
+
type: 'class',
|
|
1039
|
+
options: colorOpts,
|
|
1040
|
+
activeIndex: 0,
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (stateOpts.length > 0) {
|
|
1045
|
+
stateOpts.unshift(currentOpt)
|
|
1046
|
+
var seenS = {}
|
|
1047
|
+
stateOpts = stateOpts.filter(function (o) {
|
|
1048
|
+
if (seenS[o.className]) return false
|
|
1049
|
+
seenS[o.className] = true
|
|
1050
|
+
return true
|
|
1051
|
+
})
|
|
1052
|
+
if (stateOpts.length >= 2) {
|
|
1053
|
+
for (var stoi = 0; stoi < stateOpts.length; stoi++) {
|
|
1054
|
+
stateOpts[stoi].removeClassNames = [currentClass]
|
|
1055
|
+
}
|
|
1056
|
+
stateOpts[0].removeClassNames = []
|
|
1057
|
+
groups.push({
|
|
1058
|
+
groupName: 'State',
|
|
1059
|
+
type: 'class',
|
|
1060
|
+
options: stateOpts,
|
|
1061
|
+
activeIndex: 0,
|
|
1062
|
+
})
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return groups
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function detectPseudoVariants(el) {
|
|
1070
|
+
try {
|
|
1071
|
+
var visualProps = [
|
|
1072
|
+
'color',
|
|
1073
|
+
'backgroundColor',
|
|
1074
|
+
'borderColor',
|
|
1075
|
+
'opacity',
|
|
1076
|
+
'transform',
|
|
1077
|
+
'boxShadow',
|
|
1078
|
+
'textDecoration',
|
|
1079
|
+
'outline',
|
|
1080
|
+
]
|
|
1081
|
+
var defaultStyles = window.getComputedStyle(el)
|
|
1082
|
+
var pseudos = ['hover', 'focus', 'active']
|
|
1083
|
+
var options = [
|
|
1084
|
+
{
|
|
1085
|
+
label: 'default',
|
|
1086
|
+
className: null,
|
|
1087
|
+
removeClassNames: null,
|
|
1088
|
+
pseudoState: null,
|
|
1089
|
+
pseudoStyles: null,
|
|
1090
|
+
},
|
|
1091
|
+
]
|
|
1092
|
+
for (var pi = 0; pi < pseudos.length; pi++) {
|
|
1093
|
+
var pseudo = pseudos[pi]
|
|
1094
|
+
var pseudoStyles = window.getComputedStyle(el, ':' + pseudo)
|
|
1095
|
+
var diffs = {}
|
|
1096
|
+
var hasDiff = false
|
|
1097
|
+
for (var vi = 0; vi < visualProps.length; vi++) {
|
|
1098
|
+
var prop = visualProps[vi]
|
|
1099
|
+
var defaultVal = defaultStyles.getPropertyValue(prop)
|
|
1100
|
+
var pseudoVal = pseudoStyles.getPropertyValue(prop)
|
|
1101
|
+
if (defaultVal !== pseudoVal && pseudoVal) {
|
|
1102
|
+
diffs[prop] = pseudoVal
|
|
1103
|
+
hasDiff = true
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (hasDiff) {
|
|
1107
|
+
options.push({
|
|
1108
|
+
label: pseudo,
|
|
1109
|
+
className: null,
|
|
1110
|
+
removeClassNames: null,
|
|
1111
|
+
pseudoState: pseudo,
|
|
1112
|
+
pseudoStyles: diffs,
|
|
1113
|
+
})
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (options.length >= 2) {
|
|
1117
|
+
return [
|
|
1118
|
+
{
|
|
1119
|
+
groupName: 'Pseudo States',
|
|
1120
|
+
type: 'pseudo',
|
|
1121
|
+
options: options,
|
|
1122
|
+
activeIndex: 0,
|
|
1123
|
+
},
|
|
1124
|
+
]
|
|
1125
|
+
}
|
|
1126
|
+
} catch (e) {}
|
|
1127
|
+
return []
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function scanForComponents(rootElement) {
|
|
1131
|
+
var allElements = rootElement.querySelectorAll('*')
|
|
1132
|
+
var results = []
|
|
1133
|
+
var batchSize = 50
|
|
1134
|
+
var index = 0
|
|
1135
|
+
var scheduleNext =
|
|
1136
|
+
typeof requestIdleCallback === 'function'
|
|
1137
|
+
? function (cb) {
|
|
1138
|
+
requestIdleCallback(cb)
|
|
1139
|
+
}
|
|
1140
|
+
: function (cb) {
|
|
1141
|
+
setTimeout(cb, 0)
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function processBatch() {
|
|
1145
|
+
var end = Math.min(index + batchSize, allElements.length)
|
|
1146
|
+
for (var i = index; i < end; i++) {
|
|
1147
|
+
var el = allElements[i]
|
|
1148
|
+
var detection = detectSingleComponent(el)
|
|
1149
|
+
if (detection) {
|
|
1150
|
+
var rect = el.getBoundingClientRect()
|
|
1151
|
+
var text = (el.innerText || '').substring(0, 50) || null
|
|
1152
|
+
var childCount = 0
|
|
1153
|
+
var childEls = el.querySelectorAll('*')
|
|
1154
|
+
for (var ci = 0; ci < childEls.length; ci++) {
|
|
1155
|
+
if (detectSingleComponent(childEls[ci])) childCount++
|
|
1156
|
+
}
|
|
1157
|
+
results.push({
|
|
1158
|
+
selectorPath: generateSelectorPath(el),
|
|
1159
|
+
name: detection.name,
|
|
1160
|
+
tagName: el.tagName.toLowerCase(),
|
|
1161
|
+
detectionMethod: detection.method,
|
|
1162
|
+
className:
|
|
1163
|
+
el.className && typeof el.className === 'string'
|
|
1164
|
+
? el.className
|
|
1165
|
+
: null,
|
|
1166
|
+
elementId: el.id || null,
|
|
1167
|
+
innerText: text,
|
|
1168
|
+
boundingRect: {
|
|
1169
|
+
top: rect.top,
|
|
1170
|
+
left: rect.left,
|
|
1171
|
+
width: rect.width,
|
|
1172
|
+
height: rect.height,
|
|
1173
|
+
},
|
|
1174
|
+
variants: detectClassVariants(el).concat(
|
|
1175
|
+
detectPseudoVariants(el),
|
|
1176
|
+
),
|
|
1177
|
+
childComponentCount: childCount,
|
|
1178
|
+
})
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
index = end
|
|
1182
|
+
if (index < allElements.length) {
|
|
1183
|
+
scheduleNext(processBatch)
|
|
1184
|
+
} else {
|
|
1185
|
+
send({
|
|
1186
|
+
type: 'COMPONENTS_DETECTED',
|
|
1187
|
+
payload: { components: results },
|
|
1188
|
+
})
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (allElements.length === 0) {
|
|
1192
|
+
send({ type: 'COMPONENTS_DETECTED', payload: { components: [] } })
|
|
1193
|
+
} else {
|
|
1194
|
+
processBatch()
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// MutationObserver for DOM changes
|
|
1199
|
+
var mutationPending = false
|
|
1200
|
+
var previousTreeJSON = ''
|
|
1201
|
+
var observer = new MutationObserver(function () {
|
|
1202
|
+
if (mutationPending) return
|
|
1203
|
+
mutationPending = true
|
|
1204
|
+
requestAnimationFrame(function () {
|
|
1205
|
+
mutationPending = false
|
|
1206
|
+
var tree = serializeTree(document.body)
|
|
1207
|
+
if (!tree) return
|
|
1208
|
+
var treeJSON = JSON.stringify(tree)
|
|
1209
|
+
if (treeJSON === previousTreeJSON) return
|
|
1210
|
+
previousTreeJSON = treeJSON
|
|
1211
|
+
var removedSelectors = []
|
|
1212
|
+
if (selectedElement && !document.body.contains(selectedElement)) {
|
|
1213
|
+
removedSelectors.push(generateSelectorPath(selectedElement))
|
|
1214
|
+
clearSelection()
|
|
1215
|
+
}
|
|
1216
|
+
send({
|
|
1217
|
+
type: 'DOM_UPDATED',
|
|
1218
|
+
payload: { tree: tree, removedSelectors: removedSelectors },
|
|
1219
|
+
})
|
|
1220
|
+
})
|
|
1221
|
+
})
|
|
1222
|
+
observer.observe(document.body, {
|
|
1223
|
+
childList: true,
|
|
1224
|
+
subtree: true,
|
|
1225
|
+
attributes: true,
|
|
1226
|
+
attributeFilter: ['class', 'id', 'style'],
|
|
1227
|
+
})
|
|
1228
|
+
|
|
1229
|
+
// --- Drag-and-drop from Add Element palette ---
|
|
1230
|
+
var VOID_TAGS_DND = {
|
|
1231
|
+
img: 1,
|
|
1232
|
+
input: 1,
|
|
1233
|
+
br: 1,
|
|
1234
|
+
hr: 1,
|
|
1235
|
+
area: 1,
|
|
1236
|
+
base: 1,
|
|
1237
|
+
col: 1,
|
|
1238
|
+
embed: 1,
|
|
1239
|
+
link: 1,
|
|
1240
|
+
meta: 1,
|
|
1241
|
+
param: 1,
|
|
1242
|
+
source: 1,
|
|
1243
|
+
track: 1,
|
|
1244
|
+
wbr: 1,
|
|
1245
|
+
}
|
|
1246
|
+
var dropIndicator = document.createElement('div')
|
|
1247
|
+
dropIndicator.style.cssText =
|
|
1248
|
+
'position:fixed;pointer-events:none;z-index:2147483645;border:2px dashed #4a9eff;background:rgba(74,158,255,0.08);display:none;transition:top 0.08s,left 0.08s,width 0.08s,height 0.08s;'
|
|
1249
|
+
document.body.appendChild(dropIndicator)
|
|
1250
|
+
|
|
1251
|
+
document.addEventListener(
|
|
1252
|
+
'dragover',
|
|
1253
|
+
function (e) {
|
|
1254
|
+
if (!e.dataTransfer || !e.dataTransfer.types.indexOf) return
|
|
1255
|
+
if (
|
|
1256
|
+
e.dataTransfer.types.indexOf('application/x-dev-editor-element') ===
|
|
1257
|
+
-1
|
|
1258
|
+
)
|
|
1259
|
+
return
|
|
1260
|
+
e.preventDefault()
|
|
1261
|
+
e.dataTransfer.dropEffect = 'copy'
|
|
1262
|
+
var dropTarget = document.elementFromPoint(e.clientX, e.clientY)
|
|
1263
|
+
if (!dropTarget || dropTarget === dropIndicator) return
|
|
1264
|
+
if (dropTarget.id && dropTarget.id.indexOf('dev-editor') === 0) return
|
|
1265
|
+
var rect = dropTarget.getBoundingClientRect()
|
|
1266
|
+
dropIndicator.style.top = rect.top + 'px'
|
|
1267
|
+
dropIndicator.style.left = rect.left + 'px'
|
|
1268
|
+
dropIndicator.style.width = rect.width + 'px'
|
|
1269
|
+
dropIndicator.style.height = rect.height + 'px'
|
|
1270
|
+
dropIndicator.style.display = 'block'
|
|
1271
|
+
},
|
|
1272
|
+
true,
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
document.addEventListener(
|
|
1276
|
+
'dragleave',
|
|
1277
|
+
function (e) {
|
|
1278
|
+
if (
|
|
1279
|
+
e.relatedTarget === null ||
|
|
1280
|
+
e.relatedTarget === document.documentElement
|
|
1281
|
+
) {
|
|
1282
|
+
dropIndicator.style.display = 'none'
|
|
1283
|
+
}
|
|
1284
|
+
},
|
|
1285
|
+
true,
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
document.addEventListener(
|
|
1289
|
+
'drop',
|
|
1290
|
+
function (e) {
|
|
1291
|
+
dropIndicator.style.display = 'none'
|
|
1292
|
+
if (!e.dataTransfer) return
|
|
1293
|
+
var raw = e.dataTransfer.getData('application/x-dev-editor-element')
|
|
1294
|
+
if (!raw) return
|
|
1295
|
+
e.preventDefault()
|
|
1296
|
+
e.stopPropagation()
|
|
1297
|
+
try {
|
|
1298
|
+
var data = JSON.parse(raw)
|
|
1299
|
+
var dropEl = document.elementFromPoint(e.clientX, e.clientY)
|
|
1300
|
+
if (!dropEl) return
|
|
1301
|
+
if (dropEl.id && dropEl.id.indexOf('dev-editor') === 0) return
|
|
1302
|
+
var targetParent = dropEl
|
|
1303
|
+
var insertMode = 'child'
|
|
1304
|
+
if (VOID_TAGS_DND[dropEl.tagName.toLowerCase()]) {
|
|
1305
|
+
targetParent = dropEl.parentElement || document.body
|
|
1306
|
+
insertMode = 'after'
|
|
1307
|
+
}
|
|
1308
|
+
var newEl = document.createElement(data.tag)
|
|
1309
|
+
newEl.setAttribute('data-dev-editor-inserted', 'true')
|
|
1310
|
+
if (data.placeholderText) newEl.textContent = data.placeholderText
|
|
1311
|
+
if (data.defaultStyles) {
|
|
1312
|
+
var dndDS = data.defaultStyles
|
|
1313
|
+
for (var dndDSKey in dndDS) {
|
|
1314
|
+
if (dndDS.hasOwnProperty(dndDSKey))
|
|
1315
|
+
newEl.style.setProperty(dndDSKey, dndDS[dndDSKey])
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
if (insertMode === 'after' && dropEl.nextSibling) {
|
|
1319
|
+
targetParent.insertBefore(newEl, dropEl.nextSibling)
|
|
1320
|
+
} else {
|
|
1321
|
+
targetParent.appendChild(newEl)
|
|
1322
|
+
}
|
|
1323
|
+
var newSelector = generateSelectorPath(newEl)
|
|
1324
|
+
var newIndex = Array.from(targetParent.children).indexOf(newEl)
|
|
1325
|
+
send({
|
|
1326
|
+
type: 'ELEMENT_INSERTED',
|
|
1327
|
+
payload: {
|
|
1328
|
+
selectorPath: newSelector,
|
|
1329
|
+
parentSelectorPath: generateSelectorPath(targetParent),
|
|
1330
|
+
tagName: data.tag,
|
|
1331
|
+
insertionIndex: newIndex,
|
|
1332
|
+
placeholderText: data.placeholderText || '',
|
|
1333
|
+
defaultStyles: data.defaultStyles || undefined,
|
|
1334
|
+
},
|
|
1335
|
+
})
|
|
1336
|
+
} catch (err) {}
|
|
1337
|
+
},
|
|
1338
|
+
true,
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
// --- Handle messages from editor ---
|
|
1342
|
+
window.addEventListener('message', function (e) {
|
|
1343
|
+
// Accept messages from detected parentOrigin, or any origin if using wildcard
|
|
1344
|
+
if (parentOrigin !== '*' && e.origin !== parentOrigin) return
|
|
1345
|
+
|
|
1346
|
+
var msg = e.data
|
|
1347
|
+
if (!msg || !msg.type) return
|
|
1348
|
+
|
|
1349
|
+
// Lock in the parent origin from first valid editor message
|
|
1350
|
+
if (parentOrigin === '*' && msg.type) {
|
|
1351
|
+
parentOrigin = e.origin
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Mark as connected (stops INSPECTOR_READY retries)
|
|
1355
|
+
if (!connected) {
|
|
1356
|
+
connected = true
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
switch (msg.type) {
|
|
1360
|
+
case 'SELECT_ELEMENT': {
|
|
1361
|
+
try {
|
|
1362
|
+
var el = document.querySelector(msg.payload.selectorPath)
|
|
1363
|
+
if (el) {
|
|
1364
|
+
selectElement(el)
|
|
1365
|
+
el.scrollIntoView({ block: 'center', behavior: 'instant' })
|
|
1366
|
+
}
|
|
1367
|
+
} catch (err) {}
|
|
1368
|
+
break
|
|
1369
|
+
}
|
|
1370
|
+
case 'PREVIEW_CHANGE': {
|
|
1371
|
+
try {
|
|
1372
|
+
var target = document.querySelector(msg.payload.selectorPath)
|
|
1373
|
+
if (target) {
|
|
1374
|
+
var cssProp = camelToKebab(msg.payload.property)
|
|
1375
|
+
requestAnimationFrame(function () {
|
|
1376
|
+
target.style.setProperty(
|
|
1377
|
+
cssProp,
|
|
1378
|
+
msg.payload.value,
|
|
1379
|
+
'important',
|
|
1380
|
+
)
|
|
1381
|
+
})
|
|
1382
|
+
}
|
|
1383
|
+
} catch (err) {}
|
|
1384
|
+
break
|
|
1385
|
+
}
|
|
1386
|
+
case 'REVERT_CHANGE': {
|
|
1387
|
+
try {
|
|
1388
|
+
var target2 = document.querySelector(msg.payload.selectorPath)
|
|
1389
|
+
if (target2)
|
|
1390
|
+
target2.style.removeProperty(camelToKebab(msg.payload.property))
|
|
1391
|
+
} catch (err) {}
|
|
1392
|
+
break
|
|
1393
|
+
}
|
|
1394
|
+
case 'REVERT_ALL': {
|
|
1395
|
+
var allElements = document.querySelectorAll('[style]')
|
|
1396
|
+
allElements.forEach(function (el) {
|
|
1397
|
+
el.removeAttribute('style')
|
|
1398
|
+
})
|
|
1399
|
+
break
|
|
1400
|
+
}
|
|
1401
|
+
case 'SET_SELECTION_MODE': {
|
|
1402
|
+
selectionModeEnabled = !!msg.payload.enabled
|
|
1403
|
+
if (!selectionModeEnabled) {
|
|
1404
|
+
selectionOverlay.style.display = 'none'
|
|
1405
|
+
hoverOverlay.style.display = 'none'
|
|
1406
|
+
hoveredElement = null
|
|
1407
|
+
selectedElement = null
|
|
1408
|
+
}
|
|
1409
|
+
break
|
|
1410
|
+
}
|
|
1411
|
+
case 'HIDE_HOVER': {
|
|
1412
|
+
hoverOverlay.style.display = 'none'
|
|
1413
|
+
hoveredElement = null
|
|
1414
|
+
break
|
|
1415
|
+
}
|
|
1416
|
+
case 'HIDE_SELECTION_OVERLAY': {
|
|
1417
|
+
selectionOverlay.style.display = 'none'
|
|
1418
|
+
break
|
|
1419
|
+
}
|
|
1420
|
+
case 'SHOW_SELECTION_OVERLAY': {
|
|
1421
|
+
if (selectionModeEnabled && selectedElement) {
|
|
1422
|
+
var sr = selectedElement.getBoundingClientRect()
|
|
1423
|
+
selectionOverlay.style.top = sr.top + 'px'
|
|
1424
|
+
selectionOverlay.style.left = sr.left + 'px'
|
|
1425
|
+
selectionOverlay.style.width = sr.width + 'px'
|
|
1426
|
+
selectionOverlay.style.height = sr.height + 'px'
|
|
1427
|
+
selectionOverlay.style.display = 'block'
|
|
1428
|
+
}
|
|
1429
|
+
break
|
|
1430
|
+
}
|
|
1431
|
+
case 'SET_BREAKPOINT': {
|
|
1432
|
+
break
|
|
1433
|
+
}
|
|
1434
|
+
case 'REQUEST_DOM_TREE': {
|
|
1435
|
+
var tree = serializeTree(document.body)
|
|
1436
|
+
if (tree) send({ type: 'DOM_TREE', payload: { tree: tree } })
|
|
1437
|
+
break
|
|
1438
|
+
}
|
|
1439
|
+
case 'REQUEST_PAGE_LINKS': {
|
|
1440
|
+
var links = []
|
|
1441
|
+
var seen = {}
|
|
1442
|
+
var anchors = document.querySelectorAll('a[href]')
|
|
1443
|
+
for (var ai = 0; ai < anchors.length; ai++) {
|
|
1444
|
+
var rawHref = anchors[ai].getAttribute('href') || ''
|
|
1445
|
+
var linkText = (anchors[ai].textContent || '').trim()
|
|
1446
|
+
var resolved
|
|
1447
|
+
try {
|
|
1448
|
+
resolved = new URL(rawHref, window.location.origin)
|
|
1449
|
+
} catch (e) {
|
|
1450
|
+
continue
|
|
1451
|
+
}
|
|
1452
|
+
if (resolved.origin !== window.location.origin) continue
|
|
1453
|
+
var linkPath = resolved.pathname
|
|
1454
|
+
if (linkPath.indexOf('/api/') === 0 || linkPath === '') continue
|
|
1455
|
+
if (!linkPath.startsWith('/')) continue
|
|
1456
|
+
if (seen[linkPath]) continue
|
|
1457
|
+
seen[linkPath] = true
|
|
1458
|
+
links.push({ href: linkPath, text: linkText })
|
|
1459
|
+
}
|
|
1460
|
+
send({ type: 'PAGE_LINKS', payload: { links: links } })
|
|
1461
|
+
break
|
|
1462
|
+
}
|
|
1463
|
+
case 'REQUEST_CSS_VARIABLES': {
|
|
1464
|
+
var result = scanCSSVariableDefinitions()
|
|
1465
|
+
send({
|
|
1466
|
+
type: 'CSS_VARIABLES',
|
|
1467
|
+
payload: {
|
|
1468
|
+
definitions: result.definitions,
|
|
1469
|
+
isExplicit: result.isExplicit,
|
|
1470
|
+
},
|
|
1471
|
+
})
|
|
1472
|
+
break
|
|
1473
|
+
}
|
|
1474
|
+
case 'REQUEST_COMPONENTS': {
|
|
1475
|
+
try {
|
|
1476
|
+
var compRoot = document.body
|
|
1477
|
+
if (msg.payload && msg.payload.rootSelectorPath) {
|
|
1478
|
+
var rootEl = document.querySelector(msg.payload.rootSelectorPath)
|
|
1479
|
+
if (rootEl) compRoot = rootEl
|
|
1480
|
+
}
|
|
1481
|
+
scanForComponents(compRoot)
|
|
1482
|
+
} catch (err) {}
|
|
1483
|
+
break
|
|
1484
|
+
}
|
|
1485
|
+
case 'APPLY_VARIANT': {
|
|
1486
|
+
try {
|
|
1487
|
+
var avEl = document.querySelector(msg.payload.selectorPath)
|
|
1488
|
+
if (avEl) {
|
|
1489
|
+
if (msg.payload.type === 'class') {
|
|
1490
|
+
if (msg.payload.removeClassNames) {
|
|
1491
|
+
for (
|
|
1492
|
+
var rci = 0;
|
|
1493
|
+
rci < msg.payload.removeClassNames.length;
|
|
1494
|
+
rci++
|
|
1495
|
+
) {
|
|
1496
|
+
avEl.classList.remove(msg.payload.removeClassNames[rci])
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (msg.payload.addClassName) {
|
|
1500
|
+
avEl.classList.add(msg.payload.addClassName)
|
|
1501
|
+
}
|
|
1502
|
+
} else if (msg.payload.type === 'pseudo') {
|
|
1503
|
+
if (msg.payload.revertPseudo && msg.payload.pseudoStyles) {
|
|
1504
|
+
var revertKeys = Object.keys(msg.payload.pseudoStyles)
|
|
1505
|
+
for (var rki = 0; rki < revertKeys.length; rki++) {
|
|
1506
|
+
avEl.style.removeProperty(revertKeys[rki])
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (msg.payload.pseudoStyles && !msg.payload.revertPseudo) {
|
|
1510
|
+
var psKeys = Object.keys(msg.payload.pseudoStyles)
|
|
1511
|
+
for (var psi = 0; psi < psKeys.length; psi++) {
|
|
1512
|
+
avEl.style.setProperty(
|
|
1513
|
+
psKeys[psi],
|
|
1514
|
+
msg.payload.pseudoStyles[psKeys[psi]],
|
|
1515
|
+
'important',
|
|
1516
|
+
)
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
var avStyles = getComputedStylesForElement(avEl)
|
|
1521
|
+
var avVarUsages = detectCSSVariablesOnElement(avEl)
|
|
1522
|
+
var avRect = avEl.getBoundingClientRect()
|
|
1523
|
+
send({
|
|
1524
|
+
type: 'VARIANT_APPLIED',
|
|
1525
|
+
payload: {
|
|
1526
|
+
selectorPath: msg.payload.selectorPath,
|
|
1527
|
+
computedStyles: avStyles,
|
|
1528
|
+
cssVariableUsages: avVarUsages,
|
|
1529
|
+
boundingRect: {
|
|
1530
|
+
top: avRect.top,
|
|
1531
|
+
left: avRect.left,
|
|
1532
|
+
width: avRect.width,
|
|
1533
|
+
height: avRect.height,
|
|
1534
|
+
},
|
|
1535
|
+
},
|
|
1536
|
+
})
|
|
1537
|
+
}
|
|
1538
|
+
} catch (err) {}
|
|
1539
|
+
break
|
|
1540
|
+
}
|
|
1541
|
+
case 'REVERT_VARIANT': {
|
|
1542
|
+
try {
|
|
1543
|
+
var rvEl = document.querySelector(msg.payload.selectorPath)
|
|
1544
|
+
if (rvEl) {
|
|
1545
|
+
if (msg.payload.removeClassName) {
|
|
1546
|
+
rvEl.classList.remove(msg.payload.removeClassName)
|
|
1547
|
+
}
|
|
1548
|
+
if (msg.payload.restoreClassName) {
|
|
1549
|
+
rvEl.classList.add(msg.payload.restoreClassName)
|
|
1550
|
+
}
|
|
1551
|
+
if (msg.payload.revertPseudo && msg.payload.pseudoProperties) {
|
|
1552
|
+
for (
|
|
1553
|
+
var rppi = 0;
|
|
1554
|
+
rppi < msg.payload.pseudoProperties.length;
|
|
1555
|
+
rppi++
|
|
1556
|
+
) {
|
|
1557
|
+
rvEl.style.removeProperty(msg.payload.pseudoProperties[rppi])
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
} catch (err) {}
|
|
1562
|
+
break
|
|
1563
|
+
}
|
|
1564
|
+
case 'SET_TEXT_CONTENT': {
|
|
1565
|
+
try {
|
|
1566
|
+
var stEl = document.querySelector(msg.payload.selectorPath)
|
|
1567
|
+
if (stEl) stEl.textContent = msg.payload.text
|
|
1568
|
+
} catch (err) {}
|
|
1569
|
+
break
|
|
1570
|
+
}
|
|
1571
|
+
case 'REVERT_TEXT_CONTENT': {
|
|
1572
|
+
try {
|
|
1573
|
+
var rtEl = document.querySelector(msg.payload.selectorPath)
|
|
1574
|
+
if (rtEl) rtEl.textContent = msg.payload.originalText
|
|
1575
|
+
} catch (err) {}
|
|
1576
|
+
break
|
|
1577
|
+
}
|
|
1578
|
+
case 'DELETE_ELEMENT': {
|
|
1579
|
+
try {
|
|
1580
|
+
var deEl = document.querySelector(msg.payload.selectorPath)
|
|
1581
|
+
if (deEl) {
|
|
1582
|
+
var deComputed = window.getComputedStyle(deEl)
|
|
1583
|
+
var deOrigDisplay = deComputed.getPropertyValue('display')
|
|
1584
|
+
var deSelector = msg.payload.selectorPath
|
|
1585
|
+
var deAttrs = {}
|
|
1586
|
+
for (var deai = 0; deai < deEl.attributes.length; deai++) {
|
|
1587
|
+
var dea = deEl.attributes[deai]
|
|
1588
|
+
deAttrs[dea.name] = dea.value
|
|
1589
|
+
}
|
|
1590
|
+
deEl.style.setProperty('display', 'none', 'important')
|
|
1591
|
+
if (selectedElement === deEl) {
|
|
1592
|
+
selectionOverlay.style.display = 'none'
|
|
1593
|
+
selectedElement = null
|
|
1594
|
+
}
|
|
1595
|
+
send({
|
|
1596
|
+
type: 'ELEMENT_DELETED',
|
|
1597
|
+
payload: {
|
|
1598
|
+
selectorPath: deSelector,
|
|
1599
|
+
originalDisplay: deOrigDisplay,
|
|
1600
|
+
tagName: deEl.tagName.toLowerCase(),
|
|
1601
|
+
className:
|
|
1602
|
+
deEl.className && typeof deEl.className === 'string'
|
|
1603
|
+
? deEl.className
|
|
1604
|
+
: null,
|
|
1605
|
+
elementId: deEl.id || null,
|
|
1606
|
+
innerText: (deEl.innerText || '').substring(0, 500) || null,
|
|
1607
|
+
attributes: deAttrs,
|
|
1608
|
+
computedStyles: getComputedStylesForElement(deEl),
|
|
1609
|
+
},
|
|
1610
|
+
})
|
|
1611
|
+
}
|
|
1612
|
+
} catch (err) {}
|
|
1613
|
+
break
|
|
1614
|
+
}
|
|
1615
|
+
case 'REVERT_DELETE': {
|
|
1616
|
+
try {
|
|
1617
|
+
var rdEl = document.querySelector(msg.payload.selectorPath)
|
|
1618
|
+
if (rdEl) {
|
|
1619
|
+
rdEl.style.removeProperty('display')
|
|
1620
|
+
}
|
|
1621
|
+
} catch (err) {}
|
|
1622
|
+
break
|
|
1623
|
+
}
|
|
1624
|
+
case 'INSERT_ELEMENT': {
|
|
1625
|
+
try {
|
|
1626
|
+
var ieParent = document.querySelector(
|
|
1627
|
+
msg.payload.parentSelectorPath,
|
|
1628
|
+
)
|
|
1629
|
+
if (ieParent) {
|
|
1630
|
+
var IE_VOID = {
|
|
1631
|
+
img: 1,
|
|
1632
|
+
input: 1,
|
|
1633
|
+
br: 1,
|
|
1634
|
+
hr: 1,
|
|
1635
|
+
area: 1,
|
|
1636
|
+
base: 1,
|
|
1637
|
+
col: 1,
|
|
1638
|
+
embed: 1,
|
|
1639
|
+
link: 1,
|
|
1640
|
+
meta: 1,
|
|
1641
|
+
param: 1,
|
|
1642
|
+
source: 1,
|
|
1643
|
+
track: 1,
|
|
1644
|
+
wbr: 1,
|
|
1645
|
+
}
|
|
1646
|
+
var ieTarget = ieParent
|
|
1647
|
+
var ieMode = 'child'
|
|
1648
|
+
if (IE_VOID[ieParent.tagName.toLowerCase()]) {
|
|
1649
|
+
ieTarget = ieParent.parentElement || document.body
|
|
1650
|
+
ieMode = 'after'
|
|
1651
|
+
}
|
|
1652
|
+
var ieNew = document.createElement(msg.payload.tagName)
|
|
1653
|
+
ieNew.setAttribute('data-dev-editor-inserted', 'true')
|
|
1654
|
+
if (msg.payload.placeholderText)
|
|
1655
|
+
ieNew.textContent = msg.payload.placeholderText
|
|
1656
|
+
if (msg.payload.defaultStyles) {
|
|
1657
|
+
var ieDS = msg.payload.defaultStyles
|
|
1658
|
+
for (var ieDSKey in ieDS) {
|
|
1659
|
+
if (ieDS.hasOwnProperty(ieDSKey))
|
|
1660
|
+
ieNew.style.setProperty(ieDSKey, ieDS[ieDSKey])
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (ieMode === 'after' && ieParent.nextSibling) {
|
|
1664
|
+
ieTarget.insertBefore(ieNew, ieParent.nextSibling)
|
|
1665
|
+
} else {
|
|
1666
|
+
ieTarget.appendChild(ieNew)
|
|
1667
|
+
}
|
|
1668
|
+
var ieSelector = generateSelectorPath(ieNew)
|
|
1669
|
+
var ieIndex = Array.from(ieTarget.children).indexOf(ieNew)
|
|
1670
|
+
send({
|
|
1671
|
+
type: 'ELEMENT_INSERTED',
|
|
1672
|
+
payload: {
|
|
1673
|
+
selectorPath: ieSelector,
|
|
1674
|
+
parentSelectorPath: generateSelectorPath(ieTarget),
|
|
1675
|
+
tagName: msg.payload.tagName,
|
|
1676
|
+
insertionIndex: ieIndex,
|
|
1677
|
+
placeholderText: msg.payload.placeholderText || '',
|
|
1678
|
+
defaultStyles: msg.payload.defaultStyles || undefined,
|
|
1679
|
+
},
|
|
1680
|
+
})
|
|
1681
|
+
}
|
|
1682
|
+
} catch (err) {}
|
|
1683
|
+
break
|
|
1684
|
+
}
|
|
1685
|
+
case 'REMOVE_INSERTED_ELEMENT': {
|
|
1686
|
+
try {
|
|
1687
|
+
var rieEl = document.querySelector(msg.payload.selectorPath)
|
|
1688
|
+
if (rieEl && rieEl.parentElement) {
|
|
1689
|
+
rieEl.parentElement.removeChild(rieEl)
|
|
1690
|
+
}
|
|
1691
|
+
} catch (err) {}
|
|
1692
|
+
break
|
|
1693
|
+
}
|
|
1694
|
+
case 'MOVE_ELEMENT': {
|
|
1695
|
+
try {
|
|
1696
|
+
var meEl = document.querySelector(msg.payload.selectorPath)
|
|
1697
|
+
var meNewParent = document.querySelector(
|
|
1698
|
+
msg.payload.newParentSelectorPath,
|
|
1699
|
+
)
|
|
1700
|
+
if (
|
|
1701
|
+
meEl &&
|
|
1702
|
+
meNewParent &&
|
|
1703
|
+
meEl !== meNewParent &&
|
|
1704
|
+
!meNewParent.contains(meEl) === false
|
|
1705
|
+
) {
|
|
1706
|
+
// Allow move even if newParent is a descendant check
|
|
1707
|
+
}
|
|
1708
|
+
if (meEl && meNewParent && meEl !== meNewParent) {
|
|
1709
|
+
// Prevent moving an element into its own descendant
|
|
1710
|
+
if (meEl.contains(meNewParent)) break
|
|
1711
|
+
var meOldParent = meEl.parentElement
|
|
1712
|
+
var meOldIndex = meOldParent
|
|
1713
|
+
? Array.from(meOldParent.children).indexOf(meEl)
|
|
1714
|
+
: -1
|
|
1715
|
+
var meOldParentSelector = meOldParent
|
|
1716
|
+
? generateSelectorPath(meOldParent)
|
|
1717
|
+
: ''
|
|
1718
|
+
// Remove from current position
|
|
1719
|
+
if (meOldParent) meOldParent.removeChild(meEl)
|
|
1720
|
+
// Insert at new position
|
|
1721
|
+
var meNewIndex = msg.payload.newIndex
|
|
1722
|
+
var meChildren = meNewParent.children
|
|
1723
|
+
if (meNewIndex >= 0 && meNewIndex < meChildren.length) {
|
|
1724
|
+
meNewParent.insertBefore(meEl, meChildren[meNewIndex])
|
|
1725
|
+
} else {
|
|
1726
|
+
meNewParent.appendChild(meEl)
|
|
1727
|
+
}
|
|
1728
|
+
var meNewSelector = generateSelectorPath(meEl)
|
|
1729
|
+
var meActualIndex = Array.from(meNewParent.children).indexOf(meEl)
|
|
1730
|
+
send({
|
|
1731
|
+
type: 'ELEMENT_MOVED',
|
|
1732
|
+
payload: {
|
|
1733
|
+
selectorPath: msg.payload.selectorPath,
|
|
1734
|
+
newSelectorPath: meNewSelector,
|
|
1735
|
+
oldParentSelectorPath: meOldParentSelector,
|
|
1736
|
+
newParentSelectorPath: msg.payload.newParentSelectorPath,
|
|
1737
|
+
oldIndex: meOldIndex,
|
|
1738
|
+
newIndex: meActualIndex,
|
|
1739
|
+
},
|
|
1740
|
+
})
|
|
1741
|
+
// Update selection overlay if selected element moved
|
|
1742
|
+
if (selectedElement === meEl) {
|
|
1743
|
+
updateSelectionOverlay()
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
} catch (err) {}
|
|
1747
|
+
break
|
|
1748
|
+
}
|
|
1749
|
+
case 'REVERT_MOVE_ELEMENT': {
|
|
1750
|
+
try {
|
|
1751
|
+
var rmEl = document.querySelector(msg.payload.selectorPath)
|
|
1752
|
+
var rmOldParent = document.querySelector(
|
|
1753
|
+
msg.payload.oldParentSelectorPath,
|
|
1754
|
+
)
|
|
1755
|
+
if (rmEl && rmOldParent) {
|
|
1756
|
+
var rmCurrentParent = rmEl.parentElement
|
|
1757
|
+
if (rmCurrentParent) rmCurrentParent.removeChild(rmEl)
|
|
1758
|
+
var rmOldIndex = msg.payload.oldIndex
|
|
1759
|
+
var rmChildren = rmOldParent.children
|
|
1760
|
+
if (rmOldIndex >= 0 && rmOldIndex < rmChildren.length) {
|
|
1761
|
+
rmOldParent.insertBefore(rmEl, rmChildren[rmOldIndex])
|
|
1762
|
+
} else {
|
|
1763
|
+
rmOldParent.appendChild(rmEl)
|
|
1764
|
+
}
|
|
1765
|
+
if (selectedElement === rmEl) {
|
|
1766
|
+
updateSelectionOverlay()
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
} catch (err) {}
|
|
1770
|
+
break
|
|
1771
|
+
}
|
|
1772
|
+
case 'HEARTBEAT': {
|
|
1773
|
+
send({ type: 'HEARTBEAT_RESPONSE' })
|
|
1774
|
+
break
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
})
|
|
1778
|
+
|
|
1779
|
+
// --- SPA Navigation Detection ---
|
|
1780
|
+
var lastPathname = window.location.pathname
|
|
1781
|
+
|
|
1782
|
+
function notifyNavigationIfChanged() {
|
|
1783
|
+
var currentPath = window.location.pathname
|
|
1784
|
+
if (currentPath !== lastPathname) {
|
|
1785
|
+
lastPathname = currentPath
|
|
1786
|
+
send({ type: 'PAGE_NAVIGATE', payload: { path: currentPath } })
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
window.addEventListener('popstate', notifyNavigationIfChanged)
|
|
1791
|
+
|
|
1792
|
+
if (window.navigation) {
|
|
1793
|
+
window.navigation.addEventListener(
|
|
1794
|
+
'navigatesuccess',
|
|
1795
|
+
notifyNavigationIfChanged,
|
|
1796
|
+
)
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// --- Animation reveal ---
|
|
1800
|
+
// Many pages use animation libraries (Framer Motion, GSAP, etc.) that set
|
|
1801
|
+
// elements to opacity:0 + translateY via inline styles as an initial state,
|
|
1802
|
+
// then animate them on scroll or mount. When loaded through the proxy,
|
|
1803
|
+
// these animations may not fire (hydration issues, missing scroll context).
|
|
1804
|
+
// This function detects such hidden elements and forces them visible so the
|
|
1805
|
+
// editor can inspect and edit all content.
|
|
1806
|
+
function revealAnimationHidden() {
|
|
1807
|
+
var revealed = 0
|
|
1808
|
+
var els = document.querySelectorAll('[style]')
|
|
1809
|
+
for (var ri = 0; ri < els.length; ri++) {
|
|
1810
|
+
var rel = els[ri]
|
|
1811
|
+
var rstyle = rel.getAttribute('style') || ''
|
|
1812
|
+
// Only target elements where opacity is explicitly set to 0 via inline style
|
|
1813
|
+
// and optionally have a translateY transform (animation initial state pattern)
|
|
1814
|
+
if (!/opacity\s*:\s*0\b/.test(rstyle)) continue
|
|
1815
|
+
// Skip elements intentionally hidden (display:none, visibility:hidden)
|
|
1816
|
+
var rcs = window.getComputedStyle(rel)
|
|
1817
|
+
if (rcs.display === 'none' || rcs.visibility === 'hidden') continue
|
|
1818
|
+
// Force visible with a smooth transition
|
|
1819
|
+
rel.style.setProperty(
|
|
1820
|
+
'transition',
|
|
1821
|
+
'opacity 0.3s ease, transform 0.3s ease',
|
|
1822
|
+
'important',
|
|
1823
|
+
)
|
|
1824
|
+
rel.style.setProperty('opacity', '1', 'important')
|
|
1825
|
+
// Also clear translateY transforms that are part of the animation initial state
|
|
1826
|
+
if (
|
|
1827
|
+
/translate[YX]\s*\(/.test(rstyle) ||
|
|
1828
|
+
/matrix\s*\(/.test(rcs.transform)
|
|
1829
|
+
) {
|
|
1830
|
+
rel.style.setProperty('transform', 'none', 'important')
|
|
1831
|
+
}
|
|
1832
|
+
revealed++
|
|
1833
|
+
}
|
|
1834
|
+
return revealed
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// Run reveal after a delay to give animations a chance to fire naturally.
|
|
1838
|
+
// If they don't fire (proxy context), this catches the stuck elements.
|
|
1839
|
+
// Re-run a few times to catch dynamically rendered content.
|
|
1840
|
+
var revealAttempts = 0
|
|
1841
|
+
var revealTimer = setInterval(function () {
|
|
1842
|
+
revealAnimationHidden()
|
|
1843
|
+
revealAttempts++
|
|
1844
|
+
if (revealAttempts >= 5) clearInterval(revealTimer)
|
|
1845
|
+
}, 800)
|
|
1846
|
+
|
|
1847
|
+
// --- Signal ready with retry ---
|
|
1848
|
+
// Send INSPECTOR_READY immediately, then retry every second until editor responds.
|
|
1849
|
+
// This handles race conditions where the editor isn't listening yet.
|
|
1850
|
+
send({ type: 'INSPECTOR_READY' })
|
|
1851
|
+
var readyRetryCount = 0
|
|
1852
|
+
var readyInterval = setInterval(function () {
|
|
1853
|
+
if (connected) {
|
|
1854
|
+
clearInterval(readyInterval)
|
|
1855
|
+
return
|
|
1856
|
+
}
|
|
1857
|
+
readyRetryCount++
|
|
1858
|
+
if (readyRetryCount >= 30) {
|
|
1859
|
+
clearInterval(readyInterval)
|
|
1860
|
+
return
|
|
1861
|
+
}
|
|
1862
|
+
send({ type: 'INSPECTOR_READY' })
|
|
1863
|
+
}, 1000)
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// --- Bootstrap: wait for document.body before initializing ---
|
|
1867
|
+
if (document.readyState === 'loading') {
|
|
1868
|
+
document.addEventListener('DOMContentLoaded', initInspector)
|
|
1869
|
+
} else {
|
|
1870
|
+
initInspector()
|
|
1871
|
+
}
|
|
1872
|
+
})()
|