@promakeai/inspector 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -13
- package/dist/hook.d.ts +29 -8
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +57 -10
- package/dist/plugin.d.ts +10 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +215 -101
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -1
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
# @promakeai/inspector
|
|
2
2
|
|
|
3
|
+
[](https://github.com/promakeai/inspector/actions/workflows/build.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@promakeai/inspector)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
Visual element inspector for React apps in iframe with AI prompt support. Perfect for visual editors, page builders, and low-code platforms.
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- 🎯 **Element Selection** - Click to select any element on the page
|
|
8
12
|
- 🔍 **React Component Detection** - Automatically detects React components via Fiber
|
|
9
|
-
- 📝 **
|
|
10
|
-
- 🤖 **AI Prompt Support** -
|
|
13
|
+
- 📝 **Dynamic Content Input** - Show/hide text editing on-demand with `showContentInput()`
|
|
14
|
+
- 🤖 **AI Prompt Support** - Always-visible prompt input for AI modifications
|
|
11
15
|
- 🎨 **Visual Feedback** - Highlight selected elements with overlay
|
|
12
16
|
- 🔒 **Pause Mode** - Lock selection and disable scroll while editing
|
|
13
17
|
- 🌐 **URL Tracking** - Monitor navigation changes in iframe
|
|
14
18
|
- 🐛 **Error Tracking** - Capture JavaScript errors, promise rejections, and console errors
|
|
15
|
-
- 🎨 **Customizable
|
|
19
|
+
- 🎨 **Customizable Theme** - Full color customization for all UI elements
|
|
20
|
+
- 🌍 **Customizable Labels** - Multi-language support
|
|
21
|
+
- 🚫 **Ignore Elements** - Mark elements as non-inspectable with `data-inspector-ignore`
|
|
22
|
+
- 🔍 **Text Node Detection** - Automatically detect if selected element is a text node
|
|
16
23
|
- 📦 **TypeScript** - Full type safety
|
|
17
24
|
|
|
18
25
|
## Installation
|
|
@@ -48,14 +55,21 @@ import { useInspector } from "@promakeai/inspector/hook";
|
|
|
48
55
|
function App() {
|
|
49
56
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
50
57
|
|
|
51
|
-
const { isInspecting, toggleInspector } = useInspector(
|
|
58
|
+
const { isInspecting, toggleInspector, showContentInput } = useInspector(
|
|
52
59
|
iframeRef,
|
|
53
60
|
{
|
|
54
61
|
onElementSelected: (data) => {
|
|
55
62
|
console.log("Element selected:", data);
|
|
63
|
+
console.log("Is text node:", data.isTextNode);
|
|
64
|
+
|
|
56
65
|
// Access component info
|
|
57
66
|
console.log(data.component?.fileName);
|
|
58
67
|
console.log(data.component?.lineNumber);
|
|
68
|
+
|
|
69
|
+
// Show content input if it's a text node
|
|
70
|
+
if (data.isTextNode) {
|
|
71
|
+
showContentInput(true);
|
|
72
|
+
}
|
|
59
73
|
},
|
|
60
74
|
onPromptSubmitted: (data) => {
|
|
61
75
|
console.log("AI Prompt:", data.prompt);
|
|
@@ -87,6 +101,16 @@ function App() {
|
|
|
87
101
|
textPlaceholder: "Enter text...",
|
|
88
102
|
updateText: "Update",
|
|
89
103
|
promptPlaceholder: "Ask AI for changes...",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
// Optional: Custom theme colors
|
|
107
|
+
backgroundColor: "#ffffff",
|
|
108
|
+
textColor: "#111827",
|
|
109
|
+
buttonColor: "#4417db",
|
|
110
|
+
buttonTextColor: "#ffffff",
|
|
111
|
+
inputBackgroundColor: "#f9fafb",
|
|
112
|
+
inputTextColor: "#111827",
|
|
113
|
+
inputBorderColor: "#d1d5db",
|
|
90
114
|
}
|
|
91
115
|
);
|
|
92
116
|
|
|
@@ -96,6 +120,13 @@ function App() {
|
|
|
96
120
|
{isInspecting ? "Stop Inspector" : "Start Inspector"}
|
|
97
121
|
</button>
|
|
98
122
|
|
|
123
|
+
{/* Optional: Manual content input toggle */}
|
|
124
|
+
{isInspecting && (
|
|
125
|
+
<button onClick={() => showContentInput(true)}>
|
|
126
|
+
Show Content Input
|
|
127
|
+
</button>
|
|
128
|
+
)}
|
|
129
|
+
|
|
99
130
|
<iframe ref={iframeRef} src="http://localhost:5173" />
|
|
100
131
|
</div>
|
|
101
132
|
);
|
|
@@ -104,13 +135,13 @@ function App() {
|
|
|
104
135
|
|
|
105
136
|
## API
|
|
106
137
|
|
|
107
|
-
### `useInspector(iframeRef, callbacks?, labels?)`
|
|
138
|
+
### `useInspector(iframeRef, callbacks?, labels?, theme?)`
|
|
108
139
|
|
|
109
140
|
#### Parameters
|
|
110
141
|
|
|
111
142
|
- **iframeRef**: `RefObject<HTMLIFrameElement>` - Reference to the iframe element
|
|
112
143
|
- **callbacks**: `InspectorCallbacks` (optional)
|
|
113
|
-
- `onElementSelected`: Called when an element is selected
|
|
144
|
+
- `onElementSelected`: Called when an element is selected (receives `isTextNode` property)
|
|
114
145
|
- `onPromptSubmitted`: Called when AI prompt is submitted
|
|
115
146
|
- `onTextUpdated`: Called when text content is updated
|
|
116
147
|
- `onUrlChange`: Called when URL changes in iframe
|
|
@@ -120,6 +151,14 @@ function App() {
|
|
|
120
151
|
- `textPlaceholder`: Placeholder for text input
|
|
121
152
|
- `updateText`: Label for update button
|
|
122
153
|
- `promptPlaceholder`: Placeholder for prompt input
|
|
154
|
+
- **theme**: `InspectorTheme` (optional)
|
|
155
|
+
- `backgroundColor`: Box background color (default: `#ffffff`)
|
|
156
|
+
- `textColor`: Text color (default: `#111827`)
|
|
157
|
+
- `buttonColor`: Button background color (default: `#4417db`)
|
|
158
|
+
- `buttonTextColor`: Button text color (default: `#ffffff`)
|
|
159
|
+
- `inputBackgroundColor`: Input background color (default: `#f9fafb`)
|
|
160
|
+
- `inputTextColor`: Input text color (default: `#111827`)
|
|
161
|
+
- `inputBorderColor`: Input border color (default: `#d1d5db`)
|
|
123
162
|
|
|
124
163
|
#### Returns
|
|
125
164
|
|
|
@@ -127,6 +166,7 @@ function App() {
|
|
|
127
166
|
- **toggleInspector**: `(active?: boolean) => void` - Toggle inspection mode
|
|
128
167
|
- **startInspecting**: `() => void` - Start inspecting
|
|
129
168
|
- **stopInspecting**: `() => void` - Stop inspecting
|
|
169
|
+
- **showContentInput**: `(show: boolean) => void` - Show or hide content input dynamically
|
|
130
170
|
|
|
131
171
|
## Types
|
|
132
172
|
|
|
@@ -139,6 +179,8 @@ interface SelectedElementData {
|
|
|
139
179
|
id: string;
|
|
140
180
|
component: ComponentInfo | null;
|
|
141
181
|
position: ElementPosition;
|
|
182
|
+
isTextNode?: boolean; // Whether the element is a text node
|
|
183
|
+
textContent?: string; // Text content of the element (if text node)
|
|
142
184
|
}
|
|
143
185
|
```
|
|
144
186
|
|
|
@@ -186,6 +228,20 @@ interface ErrorData {
|
|
|
186
228
|
}
|
|
187
229
|
```
|
|
188
230
|
|
|
231
|
+
### `InspectorTheme`
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface InspectorTheme {
|
|
235
|
+
backgroundColor?: string; // Box background color
|
|
236
|
+
textColor?: string; // Text color
|
|
237
|
+
buttonColor?: string; // Button background color
|
|
238
|
+
buttonTextColor?: string; // Button text color
|
|
239
|
+
inputBackgroundColor?: string; // Input background color
|
|
240
|
+
inputTextColor?: string; // Input text color
|
|
241
|
+
inputBorderColor?: string; // Input border color
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
189
245
|
## Features in Detail
|
|
190
246
|
|
|
191
247
|
### Element Selection
|
|
@@ -193,26 +249,65 @@ interface ErrorData {
|
|
|
193
249
|
Click any element to select it. The inspector will:
|
|
194
250
|
|
|
195
251
|
- Show a blue highlight overlay
|
|
196
|
-
- Display a control box below/above the element
|
|
252
|
+
- Display a control box below/above the element (always shows prompt input)
|
|
197
253
|
- Pause scrolling
|
|
198
254
|
- Extract React component information
|
|
255
|
+
- Detect if element is a text-only node (`isTextNode`)
|
|
256
|
+
|
|
257
|
+
### Dynamic Content Input
|
|
258
|
+
|
|
259
|
+
The inspector now uses a two-stage approach:
|
|
260
|
+
|
|
261
|
+
1. **Prompt Input (Always Visible)**: Every element shows a prompt input for AI instructions
|
|
262
|
+
2. **Content Input (On-Demand)**: Text editing appears only when you call `showContentInput(true)`
|
|
199
263
|
|
|
200
|
-
|
|
264
|
+
This allows you to:
|
|
201
265
|
|
|
202
|
-
|
|
266
|
+
- Run AI computations after selection
|
|
267
|
+
- Decide programmatically when to show text editing
|
|
268
|
+
- Keep the UI clean and focused
|
|
203
269
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
270
|
+
```typescript
|
|
271
|
+
onElementSelected: (data) => {
|
|
272
|
+
if (data.isTextNode) {
|
|
273
|
+
// Show content input above prompt input
|
|
274
|
+
showContentInput(true);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
```
|
|
207
278
|
|
|
208
279
|
### AI Prompts
|
|
209
280
|
|
|
210
281
|
For any element:
|
|
211
282
|
|
|
283
|
+
- Prompt input is always visible
|
|
212
284
|
- Enter a prompt describing desired changes
|
|
213
285
|
- Click the send button (↑)
|
|
214
286
|
- Prompt + element data sent to parent app
|
|
215
287
|
|
|
288
|
+
### Ignoring Elements
|
|
289
|
+
|
|
290
|
+
Mark elements as non-inspectable using the `data-inspector-ignore` attribute:
|
|
291
|
+
|
|
292
|
+
```jsx
|
|
293
|
+
<button data-inspector-ignore onClick={toggleInspect}>
|
|
294
|
+
Inspector Controls
|
|
295
|
+
</button>
|
|
296
|
+
|
|
297
|
+
<div data-inspector-ignore>
|
|
298
|
+
{/* All children will be ignored too */}
|
|
299
|
+
<button>Button 1</button>
|
|
300
|
+
<button>Button 2</button>
|
|
301
|
+
</div>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
This is perfect for:
|
|
305
|
+
|
|
306
|
+
- Inspector control buttons
|
|
307
|
+
- Floating toolbars
|
|
308
|
+
- Fixed navigation
|
|
309
|
+
- Any UI that shouldn't be inspectable
|
|
310
|
+
|
|
216
311
|
### Component Detection
|
|
217
312
|
|
|
218
313
|
Automatically extracts:
|
|
@@ -243,6 +338,45 @@ Perfect for integrating with error tracking services like Sentry, LogRocket, or
|
|
|
243
338
|
|
|
244
339
|
## Examples
|
|
245
340
|
|
|
341
|
+
### Dynamic Content Input Based on AI Response
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
const { toggleInspector, showContentInput } = useInspector(iframeRef, {
|
|
345
|
+
onElementSelected: async (data) => {
|
|
346
|
+
// Run AI analysis first
|
|
347
|
+
const analysis = await analyzeElement(data);
|
|
348
|
+
|
|
349
|
+
// Show content input only if AI suggests text editing
|
|
350
|
+
if (analysis.shouldShowTextEditor && data.isTextNode) {
|
|
351
|
+
showContentInput(true);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
onPromptSubmitted: async (data) => {
|
|
355
|
+
// Process AI prompt
|
|
356
|
+
const result = await processAIPrompt(data.prompt, data.element);
|
|
357
|
+
|
|
358
|
+
// Optionally show content input after AI processing
|
|
359
|
+
if (result.needsTextInput) {
|
|
360
|
+
showContentInput(true);
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Dark Theme
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
const { toggleInspector } = useInspector(iframeRef, callbacks, labels, {
|
|
370
|
+
backgroundColor: "#1a1a1a",
|
|
371
|
+
textColor: "#ffffff",
|
|
372
|
+
buttonColor: "#00ff00",
|
|
373
|
+
buttonTextColor: "#000000",
|
|
374
|
+
inputBackgroundColor: "#2a2a2a",
|
|
375
|
+
inputTextColor: "#ffffff",
|
|
376
|
+
inputBorderColor: "#444444",
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
246
380
|
### Multi-language Support
|
|
247
381
|
|
|
248
382
|
```typescript
|
|
@@ -250,10 +384,30 @@ const { toggleInspector } = useInspector(iframeRef, callbacks, {
|
|
|
250
384
|
editText: "Metni Düzenle",
|
|
251
385
|
textPlaceholder: "Metin girin...",
|
|
252
386
|
updateText: "Güncelle",
|
|
253
|
-
promptPlaceholder: "
|
|
387
|
+
promptPlaceholder: "Promake'e değişiklik sor...",
|
|
254
388
|
});
|
|
255
389
|
```
|
|
256
390
|
|
|
391
|
+
### Prevent Inspector Buttons from Being Selected
|
|
392
|
+
|
|
393
|
+
```jsx
|
|
394
|
+
function App() {
|
|
395
|
+
return (
|
|
396
|
+
<div>
|
|
397
|
+
{/* These buttons won't be inspectable */}
|
|
398
|
+
<div className="inspector-controls" data-inspector-ignore>
|
|
399
|
+
<button onClick={toggleInspect}>Toggle Inspector</button>
|
|
400
|
+
<button onClick={() => showContentInput(true)}>
|
|
401
|
+
Show Content Input
|
|
402
|
+
</button>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<iframe ref={iframeRef} src="http://localhost:5173" />
|
|
406
|
+
</div>
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
257
411
|
### Open File in Editor
|
|
258
412
|
|
|
259
413
|
```typescript
|
|
@@ -303,6 +457,19 @@ const { toggleInspector } = useInspector(iframeRef, {
|
|
|
303
457
|
- React 18.0+
|
|
304
458
|
- TypeScript (recommended)
|
|
305
459
|
|
|
460
|
+
## CI/CD
|
|
461
|
+
|
|
462
|
+
This project includes GitHub Actions workflow:
|
|
463
|
+
|
|
464
|
+
### Build Workflow (`.github/workflows/build.yml`)
|
|
465
|
+
|
|
466
|
+
Automatically runs on every push and pull request:
|
|
467
|
+
|
|
468
|
+
- ✅ Uses Bun for fast installation and build
|
|
469
|
+
- ✅ Runs `bun install` and `bun run build`
|
|
470
|
+
- ✅ Checks for build artifacts
|
|
471
|
+
- ✅ Uploads build artifacts for review
|
|
472
|
+
|
|
306
473
|
## License
|
|
307
474
|
|
|
308
475
|
MIT
|
package/dist/hook.d.ts
CHANGED
|
@@ -12,11 +12,17 @@
|
|
|
12
12
|
* function App() {
|
|
13
13
|
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
14
14
|
*
|
|
15
|
-
* const { isInspecting, toggleInspector } = useInspector(
|
|
15
|
+
* const { isInspecting, toggleInspector, showContentInput } = useInspector(
|
|
16
16
|
* iframeRef,
|
|
17
17
|
* {
|
|
18
18
|
* onElementSelected: (data) => {
|
|
19
19
|
* console.log('Element selected:', data);
|
|
20
|
+
* console.log('Is text node:', data.isTextNode);
|
|
21
|
+
*
|
|
22
|
+
* // Dynamically show content input for text nodes
|
|
23
|
+
* if (data.isTextNode) {
|
|
24
|
+
* showContentInput(true);
|
|
25
|
+
* }
|
|
20
26
|
* },
|
|
21
27
|
* onPromptSubmitted: (data) => {
|
|
22
28
|
* console.log('AI Prompt:', data.prompt);
|
|
@@ -40,22 +46,37 @@
|
|
|
40
46
|
* textPlaceholder: 'Enter text...',
|
|
41
47
|
* updateText: 'Update',
|
|
42
48
|
* promptPlaceholder: 'Ask AI for changes...'
|
|
49
|
+
* },
|
|
50
|
+
* {
|
|
51
|
+
* // Optional: Custom theme colors (all optional)
|
|
52
|
+
* backgroundColor: '#ffffff',
|
|
53
|
+
* textColor: '#111827',
|
|
54
|
+
* buttonColor: '#4417db',
|
|
55
|
+
* buttonTextColor: '#ffffff',
|
|
56
|
+
* inputBackgroundColor: '#f9fafb',
|
|
57
|
+
* inputTextColor: '#111827',
|
|
58
|
+
* inputBorderColor: '#d1d5db'
|
|
43
59
|
* }
|
|
44
60
|
* );
|
|
45
61
|
*
|
|
46
62
|
* return (
|
|
47
|
-
*
|
|
48
|
-
* <
|
|
49
|
-
* {
|
|
50
|
-
*
|
|
63
|
+
* <>
|
|
64
|
+
* <div data-inspector-ignore>
|
|
65
|
+
* <button onClick={() => toggleInspector()}>
|
|
66
|
+
* Toggle Inspector
|
|
67
|
+
* </button>
|
|
68
|
+
* <button onClick={() => showContentInput(true)}>
|
|
69
|
+
* Show Content Input
|
|
70
|
+
* </button>
|
|
71
|
+
* </div>
|
|
51
72
|
* <iframe ref={iframeRef} src="http://localhost:5173" />
|
|
52
|
-
*
|
|
73
|
+
* </>
|
|
53
74
|
* );
|
|
54
75
|
* }
|
|
55
76
|
* ```
|
|
56
77
|
*/
|
|
57
78
|
import { RefObject } from "react";
|
|
58
|
-
import type { InspectorCallbacks, InspectorLabels, UseInspectorReturn } from "./types";
|
|
59
|
-
export declare function useInspector(iframeRef: RefObject<HTMLIFrameElement>, callbacks?: InspectorCallbacks, labels?: InspectorLabels): UseInspectorReturn;
|
|
79
|
+
import type { InspectorCallbacks, InspectorLabels, InspectorTheme, UseInspectorReturn } from "./types";
|
|
80
|
+
export declare function useInspector(iframeRef: RefObject<HTMLIFrameElement>, callbacks?: InspectorCallbacks, labels?: InspectorLabels, theme?: InspectorTheme): UseInspectorReturn;
|
|
60
81
|
export type * from "./types";
|
|
61
82
|
//# sourceMappingURL=hook.d.ts.map
|
package/dist/hook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AAEH,OAAO,EAAoC,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,kBAAkB,EAMnB,MAAM,SAAS,CAAC;AAoCjB,wBAAgB,YAAY,CAC1B,SAAS,EAAE,SAAS,CAAC,iBAAiB,CAAC,EACvC,SAAS,CAAC,EAAE,kBAAkB,EAC9B,MAAM,CAAC,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,cAAc,GACrB,kBAAkB,CAsJpB;AAGD,mBAAmB,SAAS,CAAC"}
|
package/dist/hook.js
CHANGED
|
@@ -12,11 +12,17 @@
|
|
|
12
12
|
* function App() {
|
|
13
13
|
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
14
14
|
*
|
|
15
|
-
* const { isInspecting, toggleInspector } = useInspector(
|
|
15
|
+
* const { isInspecting, toggleInspector, showContentInput } = useInspector(
|
|
16
16
|
* iframeRef,
|
|
17
17
|
* {
|
|
18
18
|
* onElementSelected: (data) => {
|
|
19
19
|
* console.log('Element selected:', data);
|
|
20
|
+
* console.log('Is text node:', data.isTextNode);
|
|
21
|
+
*
|
|
22
|
+
* // Dynamically show content input for text nodes
|
|
23
|
+
* if (data.isTextNode) {
|
|
24
|
+
* showContentInput(true);
|
|
25
|
+
* }
|
|
20
26
|
* },
|
|
21
27
|
* onPromptSubmitted: (data) => {
|
|
22
28
|
* console.log('AI Prompt:', data.prompt);
|
|
@@ -40,22 +46,37 @@
|
|
|
40
46
|
* textPlaceholder: 'Enter text...',
|
|
41
47
|
* updateText: 'Update',
|
|
42
48
|
* promptPlaceholder: 'Ask AI for changes...'
|
|
49
|
+
* },
|
|
50
|
+
* {
|
|
51
|
+
* // Optional: Custom theme colors (all optional)
|
|
52
|
+
* backgroundColor: '#ffffff',
|
|
53
|
+
* textColor: '#111827',
|
|
54
|
+
* buttonColor: '#4417db',
|
|
55
|
+
* buttonTextColor: '#ffffff',
|
|
56
|
+
* inputBackgroundColor: '#f9fafb',
|
|
57
|
+
* inputTextColor: '#111827',
|
|
58
|
+
* inputBorderColor: '#d1d5db'
|
|
43
59
|
* }
|
|
44
60
|
* );
|
|
45
61
|
*
|
|
46
62
|
* return (
|
|
47
|
-
*
|
|
48
|
-
* <
|
|
49
|
-
* {
|
|
50
|
-
*
|
|
63
|
+
* <>
|
|
64
|
+
* <div data-inspector-ignore>
|
|
65
|
+
* <button onClick={() => toggleInspector()}>
|
|
66
|
+
* Toggle Inspector
|
|
67
|
+
* </button>
|
|
68
|
+
* <button onClick={() => showContentInput(true)}>
|
|
69
|
+
* Show Content Input
|
|
70
|
+
* </button>
|
|
71
|
+
* </div>
|
|
51
72
|
* <iframe ref={iframeRef} src="http://localhost:5173" />
|
|
52
|
-
*
|
|
73
|
+
* </>
|
|
53
74
|
* );
|
|
54
75
|
* }
|
|
55
76
|
* ```
|
|
56
77
|
*/
|
|
57
78
|
import { useEffect, useState, useCallback } from "react";
|
|
58
|
-
export function useInspector(iframeRef, callbacks, labels) {
|
|
79
|
+
export function useInspector(iframeRef, callbacks, labels, theme) {
|
|
59
80
|
const [isInspecting, setIsInspecting] = useState(false);
|
|
60
81
|
/**
|
|
61
82
|
* Send message to iframe
|
|
@@ -83,8 +104,9 @@ export function useInspector(iframeRef, callbacks, labels) {
|
|
|
83
104
|
type: "TOGGLE_INSPECTOR",
|
|
84
105
|
active: newState,
|
|
85
106
|
labels: labels,
|
|
107
|
+
theme: theme,
|
|
86
108
|
});
|
|
87
|
-
}, [isInspecting, sendMessage, labels]);
|
|
109
|
+
}, [isInspecting, sendMessage, labels, theme]);
|
|
88
110
|
/**
|
|
89
111
|
* Start inspecting
|
|
90
112
|
*/
|
|
@@ -97,16 +119,38 @@ export function useInspector(iframeRef, callbacks, labels) {
|
|
|
97
119
|
const stopInspecting = useCallback(() => {
|
|
98
120
|
toggleInspector(false);
|
|
99
121
|
}, [toggleInspector]);
|
|
122
|
+
/**
|
|
123
|
+
* Show or hide content input
|
|
124
|
+
*/
|
|
125
|
+
const showContentInput = useCallback((show) => {
|
|
126
|
+
sendMessage({
|
|
127
|
+
type: "SHOW_CONTENT_INPUT",
|
|
128
|
+
show: show,
|
|
129
|
+
});
|
|
130
|
+
}, [sendMessage]);
|
|
100
131
|
/**
|
|
101
132
|
* Listen for messages from iframe
|
|
102
133
|
*/
|
|
103
134
|
useEffect(() => {
|
|
104
135
|
const handleMessage = (event) => {
|
|
136
|
+
// Parse message if it's a string (JSON stringified)
|
|
137
|
+
let messageData;
|
|
138
|
+
if (typeof event.data === "string") {
|
|
139
|
+
try {
|
|
140
|
+
messageData = JSON.parse(event.data);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return; // Invalid JSON, ignore
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
messageData = event.data;
|
|
148
|
+
}
|
|
105
149
|
// Security: Only handle expected message types
|
|
106
|
-
if (!
|
|
150
|
+
if (!messageData || typeof messageData.type !== "string") {
|
|
107
151
|
return;
|
|
108
152
|
}
|
|
109
|
-
const { type, data } =
|
|
153
|
+
const { type, data } = messageData;
|
|
110
154
|
switch (type) {
|
|
111
155
|
case "INSPECTOR_ELEMENT_SELECTED":
|
|
112
156
|
callbacks?.onElementSelected?.(data);
|
|
@@ -142,6 +186,8 @@ export function useInspector(iframeRef, callbacks, labels) {
|
|
|
142
186
|
sendMessage({
|
|
143
187
|
type: "TOGGLE_INSPECTOR",
|
|
144
188
|
active: false,
|
|
189
|
+
labels: labels,
|
|
190
|
+
theme: theme,
|
|
145
191
|
});
|
|
146
192
|
}
|
|
147
193
|
};
|
|
@@ -151,5 +197,6 @@ export function useInspector(iframeRef, callbacks, labels) {
|
|
|
151
197
|
toggleInspector,
|
|
152
198
|
startInspecting,
|
|
153
199
|
stopInspecting,
|
|
200
|
+
showContentInput,
|
|
154
201
|
};
|
|
155
202
|
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -22,12 +22,20 @@
|
|
|
22
22
|
* Plugin özellikler:
|
|
23
23
|
* - Element seçimi (click-to-select)
|
|
24
24
|
* - React Fiber üzerinden component bilgisi çıkarma
|
|
25
|
-
* - Text
|
|
26
|
-
* -
|
|
25
|
+
* - Text node algılama (isTextNode)
|
|
26
|
+
* - Dinamik content input (showContentInput ile kontrol edilir)
|
|
27
|
+
* - AI prompt gönderme (her zaman görünür)
|
|
28
|
+
* - data-inspector-ignore desteği (belirli elementleri ignore etme)
|
|
27
29
|
* - URL değişikliği takibi
|
|
28
30
|
* - JavaScript hata yakalama (error, promise rejection, console.error)
|
|
31
|
+
* - Özelleştirilebilir tema renkleri
|
|
29
32
|
* - Özelleştirilebilir dil desteği
|
|
30
33
|
*
|
|
34
|
+
* Kullanım:
|
|
35
|
+
* - Elementlere data-inspector-ignore attribute'u ekleyerek inspector'dan gizleyebilirsiniz
|
|
36
|
+
* - Control box her zaman prompt input gösterir
|
|
37
|
+
* - Content input sadece showContentInput(true) ile dinamik olarak eklenir
|
|
38
|
+
*
|
|
31
39
|
* Not: Bu plugin otomatik olarak template/iframe'e enjekte edilir.
|
|
32
40
|
* Ana app'te useInspector hook'u ile kontrol edilir.
|
|
33
41
|
*/
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,wBAAgB,eAAe,IAAI,MAAM,CAyrBxC"}
|
package/dist/plugin.js
CHANGED
|
@@ -22,12 +22,20 @@
|
|
|
22
22
|
* Plugin özellikler:
|
|
23
23
|
* - Element seçimi (click-to-select)
|
|
24
24
|
* - React Fiber üzerinden component bilgisi çıkarma
|
|
25
|
-
* - Text
|
|
26
|
-
* -
|
|
25
|
+
* - Text node algılama (isTextNode)
|
|
26
|
+
* - Dinamik content input (showContentInput ile kontrol edilir)
|
|
27
|
+
* - AI prompt gönderme (her zaman görünür)
|
|
28
|
+
* - data-inspector-ignore desteği (belirli elementleri ignore etme)
|
|
27
29
|
* - URL değişikliği takibi
|
|
28
30
|
* - JavaScript hata yakalama (error, promise rejection, console.error)
|
|
31
|
+
* - Özelleştirilebilir tema renkleri
|
|
29
32
|
* - Özelleştirilebilir dil desteği
|
|
30
33
|
*
|
|
34
|
+
* Kullanım:
|
|
35
|
+
* - Elementlere data-inspector-ignore attribute'u ekleyerek inspector'dan gizleyebilirsiniz
|
|
36
|
+
* - Control box her zaman prompt input gösterir
|
|
37
|
+
* - Content input sadece showContentInput(true) ile dinamik olarak eklenir
|
|
38
|
+
*
|
|
31
39
|
* Not: Bu plugin otomatik olarak template/iframe'e enjekte edilir.
|
|
32
40
|
* Ana app'te useInspector hook'u ile kontrol edilir.
|
|
33
41
|
*/
|
|
@@ -62,6 +70,17 @@ export function inspectorPlugin() {
|
|
|
62
70
|
promptPlaceholder: 'Ask Promake for changes...'
|
|
63
71
|
};
|
|
64
72
|
|
|
73
|
+
// Customizable theme colors
|
|
74
|
+
let theme = {
|
|
75
|
+
backgroundColor: '#ffffff',
|
|
76
|
+
textColor: '#111827',
|
|
77
|
+
buttonColor: '#4417db',
|
|
78
|
+
buttonTextColor: '#ffffff',
|
|
79
|
+
inputBackgroundColor: '#f9fafb',
|
|
80
|
+
inputTextColor: '#111827',
|
|
81
|
+
inputBorderColor: '#d1d5db'
|
|
82
|
+
};
|
|
83
|
+
|
|
65
84
|
// Create overlay for highlighting
|
|
66
85
|
function createOverlay() {
|
|
67
86
|
if (overlay) return overlay;
|
|
@@ -95,6 +114,10 @@ export function inspectorPlugin() {
|
|
|
95
114
|
const isTextElement = element.textContent && element.children.length === 0;
|
|
96
115
|
const currentText = isTextElement ? element.textContent.trim() : '';
|
|
97
116
|
|
|
117
|
+
// Store text info in data attribute for later use
|
|
118
|
+
controlBox.setAttribute('data-is-text-element', isTextElement);
|
|
119
|
+
controlBox.setAttribute('data-current-text', currentText);
|
|
120
|
+
|
|
98
121
|
// Calculate position
|
|
99
122
|
const boxWidth = Math.max(320, Math.min(rect.width, 500));
|
|
100
123
|
const centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
|
|
@@ -102,8 +125,8 @@ export function inspectorPlugin() {
|
|
|
102
125
|
const spaceBelow = viewportHeight - rect.bottom;
|
|
103
126
|
const spaceAbove = rect.top;
|
|
104
127
|
|
|
105
|
-
// Estimate box height
|
|
106
|
-
const estimatedBoxHeight =
|
|
128
|
+
// Estimate box height (only prompt input now, smaller)
|
|
129
|
+
const estimatedBoxHeight = 100;
|
|
107
130
|
|
|
108
131
|
// Show above if not enough space below
|
|
109
132
|
let topPosition;
|
|
@@ -122,7 +145,7 @@ export function inspectorPlugin() {
|
|
|
122
145
|
top: \${topPosition}px;
|
|
123
146
|
left: \${Math.max(10, centerLeft)}px;
|
|
124
147
|
width: \${boxWidth}px;
|
|
125
|
-
background:
|
|
148
|
+
background: \${theme.backgroundColor};
|
|
126
149
|
border: 1px solid #e5e7eb;
|
|
127
150
|
border-radius: 14px;
|
|
128
151
|
padding: 12px;
|
|
@@ -130,80 +153,33 @@ export function inspectorPlugin() {
|
|
|
130
153
|
z-index: 1000000;
|
|
131
154
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
132
155
|
font-size: 14px;
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
gap: 0;
|
|
133
159
|
\`;
|
|
134
160
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (isTextElement) {
|
|
139
|
-
html += \`
|
|
140
|
-
<div style="margin-bottom: 12px;">
|
|
141
|
-
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
142
|
-
<label style="font-weight: 500; color: #111827; font-size: 13px;">
|
|
143
|
-
\${labels.editText}
|
|
144
|
-
</label>
|
|
145
|
-
<button
|
|
146
|
-
id="inspector-close"
|
|
147
|
-
style="width: 32px; height: 32px; background: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 14px; transition: all 0.2s; padding: 0; flex-shrink: 0;"
|
|
148
|
-
onmouseover="this.style.background='#f3f4f6';"
|
|
149
|
-
onmouseout="this.style.background='transparent';"
|
|
150
|
-
title="Close"
|
|
151
|
-
>
|
|
152
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
153
|
-
<path d="M12 4L4 12M4 4L12 12" stroke="#6b7280" stroke-width="2" stroke-linecap="round"/>
|
|
154
|
-
</svg>
|
|
155
|
-
</button>
|
|
156
|
-
</div>
|
|
157
|
-
<textarea
|
|
158
|
-
id="inspector-text-input"
|
|
159
|
-
style="width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 14px; font-size: 14px; color: #111827; background: #f9fafb; transition: all 0.2s; resize: vertical; min-height: 60px; max-height: 200px; font-family: inherit; line-height: 1.5;"
|
|
160
|
-
placeholder="\${labels.textPlaceholder}"
|
|
161
|
-
onfocus="this.style.background='#ffffff'; this.style.borderColor='#9ca3af';"
|
|
162
|
-
onblur="this.style.background='#f9fafb'; this.style.borderColor='#d1d5db';"
|
|
163
|
-
>\${currentText}</textarea>
|
|
164
|
-
</div>
|
|
165
|
-
<div style="margin-bottom: 10px;">
|
|
166
|
-
<button
|
|
167
|
-
id="inspector-text-submit"
|
|
168
|
-
style="width: 100%; padding: 10px 16px; background: #4417db; color: white; border: none; border-radius: 14px; cursor: pointer; font-weight: 500; font-size: 14px; transition: all 0.2s;"
|
|
169
|
-
onmouseover="this.style.background='#3712af';"
|
|
170
|
-
onmouseout="this.style.background='#4417db';"
|
|
171
|
-
title="\${labels.updateText}"
|
|
172
|
-
>
|
|
173
|
-
\${labels.updateText}
|
|
174
|
-
</button>
|
|
175
|
-
</div>
|
|
176
|
-
<div style="border-top: 1px solid #e5e7eb; margin: 12px 0;"></div>
|
|
177
|
-
\`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Prompt input and buttons (side by side)
|
|
181
|
-
html += \`
|
|
182
|
-
<div style="display: flex; gap: 8px; align-items: stretch;">
|
|
161
|
+
// Only prompt input and buttons (always shown)
|
|
162
|
+
let html = \`
|
|
163
|
+
<div id="inspector-prompt-section" style="display: flex; gap: 8px; align-items: stretch;">
|
|
183
164
|
<input
|
|
184
165
|
type="text"
|
|
185
166
|
id="inspector-prompt-input"
|
|
186
|
-
style="flex: 1; padding: 10px 12px; border: 1px solid
|
|
167
|
+
style="flex: 1; padding: 10px 12px; border: 1px solid \${theme.inputBorderColor}; border-radius: 14px; font-size: 14px; color: \${theme.inputTextColor}; background: \${theme.inputBackgroundColor}; transition: all 0.2s;"
|
|
187
168
|
placeholder="\${labels.promptPlaceholder}"
|
|
188
169
|
onfocus="this.style.background='#ffffff'; this.style.borderColor='#9ca3af';"
|
|
189
|
-
onblur="this.style.background='
|
|
170
|
+
onblur="this.style.background='\${theme.inputBackgroundColor}'; this.style.borderColor='\${theme.inputBorderColor}';"
|
|
190
171
|
/>
|
|
191
172
|
<button
|
|
192
173
|
id="inspector-prompt-submit"
|
|
193
|
-
style="width: 44px; height: 44px; padding: 0; background:
|
|
194
|
-
onmouseover="this.style.
|
|
195
|
-
onmouseout="this.style.
|
|
174
|
+
style="width: 44px; height: 44px; padding: 0; background: \${theme.buttonColor}; color: \${theme.buttonTextColor}; border: none; border-radius: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; flex-shrink: 0;"
|
|
175
|
+
onmouseover="this.style.opacity='0.85';"
|
|
176
|
+
onmouseout="this.style.opacity='1';"
|
|
196
177
|
title="Send"
|
|
197
178
|
>
|
|
198
179
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
199
180
|
<path d="M10 15V5M10 5L5 10M10 5L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
200
181
|
</svg>
|
|
201
182
|
</button>
|
|
202
|
-
\`;
|
|
203
|
-
|
|
204
|
-
// Close button next to send button (only for non-text elements)
|
|
205
|
-
if (!isTextElement) {
|
|
206
|
-
html += \`
|
|
207
183
|
<button
|
|
208
184
|
id="inspector-close"
|
|
209
185
|
style="padding: 10px; background: #f3f4f6; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 14px; transition: all 0.2s;"
|
|
@@ -215,10 +191,8 @@ export function inspectorPlugin() {
|
|
|
215
191
|
<path d="M12 4L4 12M4 4L12 12" stroke="#6b7280" stroke-width="2" stroke-linecap="round"/>
|
|
216
192
|
</svg>
|
|
217
193
|
</button>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
html += \`</div>\`;
|
|
194
|
+
</div>
|
|
195
|
+
\`;
|
|
222
196
|
|
|
223
197
|
controlBox.innerHTML = html;
|
|
224
198
|
document.body.appendChild(controlBox);
|
|
@@ -234,13 +208,14 @@ export function inspectorPlugin() {
|
|
|
234
208
|
const prompt = promptInput.value.trim();
|
|
235
209
|
if (prompt) {
|
|
236
210
|
if (window.parent !== window) {
|
|
237
|
-
|
|
211
|
+
const message = JSON.stringify({
|
|
238
212
|
type: 'INSPECTOR_PROMPT_SUBMITTED',
|
|
239
213
|
data: {
|
|
240
214
|
prompt: prompt,
|
|
241
215
|
element: elementData
|
|
242
216
|
}
|
|
243
|
-
}
|
|
217
|
+
});
|
|
218
|
+
window.parent.postMessage(message, '*');
|
|
244
219
|
}
|
|
245
220
|
console.log('Prompt submitted:', elementData);
|
|
246
221
|
|
|
@@ -257,23 +232,83 @@ export function inspectorPlugin() {
|
|
|
257
232
|
}
|
|
258
233
|
});
|
|
259
234
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
235
|
+
// Prevent prompt input events from bubbling
|
|
236
|
+
promptInput.addEventListener('click', (e) => e.stopPropagation());
|
|
237
|
+
|
|
238
|
+
// Close button
|
|
239
|
+
closeBtn.addEventListener('click', (e) => {
|
|
240
|
+
e.stopPropagation();
|
|
241
|
+
unpauseInspection();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Focus prompt input
|
|
245
|
+
setTimeout(() => promptInput.focus(), 100);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Show or hide content input section
|
|
249
|
+
function toggleContentInput(show) {
|
|
250
|
+
if (!controlBox) return;
|
|
251
|
+
|
|
252
|
+
const isTextElement = controlBox.getAttribute('data-is-text-element') === 'true';
|
|
253
|
+
const currentText = controlBox.getAttribute('data-current-text') || '';
|
|
254
|
+
|
|
255
|
+
// Check if content input already exists
|
|
256
|
+
let contentSection = controlBox.querySelector('#inspector-content-section');
|
|
257
|
+
|
|
258
|
+
if (show && !contentSection && isTextElement) {
|
|
259
|
+
// Create content input section
|
|
260
|
+
contentSection = document.createElement('div');
|
|
261
|
+
contentSection.id = 'inspector-content-section';
|
|
262
|
+
contentSection.style.cssText = 'margin-bottom: 12px;';
|
|
263
|
+
|
|
264
|
+
contentSection.innerHTML = \`
|
|
265
|
+
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
266
|
+
<label style="font-weight: 500; color: \${theme.textColor}; font-size: 13px;">
|
|
267
|
+
\${labels.editText}
|
|
268
|
+
</label>
|
|
269
|
+
</div>
|
|
270
|
+
<textarea
|
|
271
|
+
id="inspector-text-input"
|
|
272
|
+
style="width: 100%; padding: 10px 12px; border: 1px solid \${theme.inputBorderColor}; border-radius: 14px; font-size: 14px; color: \${theme.inputTextColor}; background: \${theme.inputBackgroundColor}; transition: all 0.2s; resize: vertical; min-height: 60px; max-height: 200px; font-family: inherit; line-height: 1.5;"
|
|
273
|
+
placeholder="\${labels.textPlaceholder}"
|
|
274
|
+
onfocus="this.style.background='#ffffff'; this.style.borderColor='#9ca3af';"
|
|
275
|
+
onblur="this.style.background='\${theme.inputBackgroundColor}'; this.style.borderColor='\${theme.inputBorderColor}';"
|
|
276
|
+
>\${currentText}</textarea>
|
|
277
|
+
<div style="margin-top: 8px;">
|
|
278
|
+
<button
|
|
279
|
+
id="inspector-text-submit"
|
|
280
|
+
style="width: 100%; padding: 10px 16px; background: \${theme.buttonColor}; color: \${theme.buttonTextColor}; border: none; border-radius: 14px; cursor: pointer; font-weight: 500; font-size: 14px; transition: all 0.2s;"
|
|
281
|
+
onmouseover="this.style.opacity='0.85';"
|
|
282
|
+
onmouseout="this.style.opacity='1';"
|
|
283
|
+
title="\${labels.updateText}"
|
|
284
|
+
>
|
|
285
|
+
\${labels.updateText}
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
<div style="border-top: 1px solid #e5e7eb; margin-top: 12px; margin-bottom: 12px;"></div>
|
|
289
|
+
\`;
|
|
290
|
+
|
|
291
|
+
// Insert at the beginning of controlBox
|
|
292
|
+
const promptSection = controlBox.querySelector('#inspector-prompt-section');
|
|
293
|
+
controlBox.insertBefore(contentSection, promptSection);
|
|
294
|
+
|
|
295
|
+
// Add event listeners
|
|
296
|
+
const textInput = contentSection.querySelector('#inspector-text-input');
|
|
297
|
+
const textSubmit = contentSection.querySelector('#inspector-text-submit');
|
|
264
298
|
|
|
265
299
|
textSubmit.addEventListener('click', (e) => {
|
|
266
300
|
e.stopPropagation();
|
|
267
301
|
const newText = textInput.value;
|
|
268
302
|
if (window.parent !== window) {
|
|
269
|
-
|
|
303
|
+
const message = JSON.stringify({
|
|
270
304
|
type: 'INSPECTOR_TEXT_UPDATED',
|
|
271
305
|
data: {
|
|
272
306
|
text: newText,
|
|
273
307
|
originalText: currentText,
|
|
274
|
-
element:
|
|
308
|
+
element: selectedElementData
|
|
275
309
|
}
|
|
276
|
-
}
|
|
310
|
+
});
|
|
311
|
+
window.parent.postMessage(message, '*');
|
|
277
312
|
}
|
|
278
313
|
console.log('Text updated:', newText);
|
|
279
314
|
|
|
@@ -282,7 +317,8 @@ export function inspectorPlugin() {
|
|
|
282
317
|
});
|
|
283
318
|
|
|
284
319
|
textInput.addEventListener('keypress', (e) => {
|
|
285
|
-
if (e.key === 'Enter') {
|
|
320
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
321
|
+
e.preventDefault();
|
|
286
322
|
e.stopPropagation();
|
|
287
323
|
textSubmit.click();
|
|
288
324
|
}
|
|
@@ -290,19 +326,52 @@ export function inspectorPlugin() {
|
|
|
290
326
|
|
|
291
327
|
// Prevent input events from bubbling
|
|
292
328
|
textInput.addEventListener('click', (e) => e.stopPropagation());
|
|
329
|
+
|
|
330
|
+
// Focus text input
|
|
331
|
+
setTimeout(() => textInput.focus(), 100);
|
|
332
|
+
|
|
333
|
+
// Adjust box height
|
|
334
|
+
adjustControlBoxPosition();
|
|
335
|
+
} else if (!show && contentSection) {
|
|
336
|
+
// Remove content input section
|
|
337
|
+
contentSection.remove();
|
|
338
|
+
|
|
339
|
+
// Refocus prompt input
|
|
340
|
+
const promptInput = controlBox.querySelector('#inspector-prompt-input');
|
|
341
|
+
if (promptInput) {
|
|
342
|
+
setTimeout(() => promptInput.focus(), 100);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Adjust box height
|
|
346
|
+
adjustControlBoxPosition();
|
|
293
347
|
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Adjust control box position based on content
|
|
351
|
+
function adjustControlBoxPosition() {
|
|
352
|
+
if (!controlBox || !selectedElement) return;
|
|
294
353
|
|
|
295
|
-
|
|
296
|
-
|
|
354
|
+
const rect = selectedElement.getBoundingClientRect();
|
|
355
|
+
const boxWidth = Math.max(320, Math.min(rect.width, 500));
|
|
356
|
+
const centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
|
|
357
|
+
const viewportHeight = window.innerHeight;
|
|
358
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
359
|
+
const spaceAbove = rect.top;
|
|
297
360
|
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
e.stopPropagation();
|
|
301
|
-
unpauseInspection();
|
|
302
|
-
});
|
|
361
|
+
// Get actual box height
|
|
362
|
+
const boxHeight = controlBox.offsetHeight;
|
|
303
363
|
|
|
304
|
-
//
|
|
305
|
-
|
|
364
|
+
// Show above if not enough space below
|
|
365
|
+
let topPosition;
|
|
366
|
+
if (spaceBelow < boxHeight + 20 && spaceAbove > spaceBelow) {
|
|
367
|
+
topPosition = rect.top - boxHeight - 10;
|
|
368
|
+
controlBox.setAttribute('data-position', 'above');
|
|
369
|
+
} else {
|
|
370
|
+
topPosition = rect.bottom + 10;
|
|
371
|
+
controlBox.setAttribute('data-position', 'below');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
controlBox.style.top = topPosition + 'px';
|
|
306
375
|
}
|
|
307
376
|
|
|
308
377
|
// Pause inspection
|
|
@@ -400,8 +469,9 @@ export function inspectorPlugin() {
|
|
|
400
469
|
|
|
401
470
|
const rect = element.getBoundingClientRect();
|
|
402
471
|
overlay.style.display = 'block';
|
|
403
|
-
|
|
404
|
-
overlay.style.
|
|
472
|
+
// position: fixed uses viewport coordinates, no need for scroll offset
|
|
473
|
+
overlay.style.top = rect.top + 'px';
|
|
474
|
+
overlay.style.left = rect.left + 'px';
|
|
405
475
|
overlay.style.width = rect.width + 'px';
|
|
406
476
|
overlay.style.height = rect.height + 'px';
|
|
407
477
|
}
|
|
@@ -417,10 +487,13 @@ export function inspectorPlugin() {
|
|
|
417
487
|
function handleMouseMove(e) {
|
|
418
488
|
if (!inspectMode || isPaused) return;
|
|
419
489
|
|
|
420
|
-
// Ignore control box and
|
|
490
|
+
// Ignore control box, overlay, and elements with data-inspector-ignore
|
|
421
491
|
if (e.target.id === 'inspector-control-box' ||
|
|
422
492
|
e.target.closest('#inspector-control-box') ||
|
|
423
|
-
e.target.id === 'inspector-overlay'
|
|
493
|
+
e.target.id === 'inspector-overlay' ||
|
|
494
|
+
e.target.hasAttribute('data-inspector-ignore') ||
|
|
495
|
+
e.target.closest('[data-inspector-ignore]')) {
|
|
496
|
+
clearHighlight();
|
|
424
497
|
return;
|
|
425
498
|
}
|
|
426
499
|
|
|
@@ -428,20 +501,40 @@ export function inspectorPlugin() {
|
|
|
428
501
|
highlightElement(hoveredElement);
|
|
429
502
|
}
|
|
430
503
|
|
|
504
|
+
// Handle scroll - update highlight position
|
|
505
|
+
function handleScroll() {
|
|
506
|
+
if (!inspectMode) return;
|
|
507
|
+
|
|
508
|
+
if (isPaused && selectedElement) {
|
|
509
|
+
// Update overlay for selected element
|
|
510
|
+
highlightElement(selectedElement);
|
|
511
|
+
// Update control box position
|
|
512
|
+
if (controlBox) {
|
|
513
|
+
adjustControlBoxPosition();
|
|
514
|
+
}
|
|
515
|
+
} else if (hoveredElement) {
|
|
516
|
+
// Update overlay for hovered element
|
|
517
|
+
highlightElement(hoveredElement);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
431
521
|
// Handle click
|
|
432
522
|
function handleClick(e) {
|
|
433
523
|
if (!inspectMode) return;
|
|
434
524
|
|
|
435
|
-
// Ignore clicks on control box
|
|
525
|
+
// Ignore clicks on control box and elements with data-inspector-ignore
|
|
436
526
|
if (e.target.id === 'inspector-control-box' ||
|
|
437
|
-
e.target.closest('#inspector-control-box')
|
|
527
|
+
e.target.closest('#inspector-control-box') ||
|
|
528
|
+
e.target.hasAttribute('data-inspector-ignore') ||
|
|
529
|
+
e.target.closest('[data-inspector-ignore]')) {
|
|
438
530
|
return;
|
|
439
531
|
}
|
|
440
532
|
|
|
441
|
-
// If already paused,
|
|
533
|
+
// If already paused, unpause first then ignore this click
|
|
442
534
|
if (isPaused) {
|
|
443
535
|
e.preventDefault();
|
|
444
536
|
e.stopPropagation();
|
|
537
|
+
unpauseInspection();
|
|
445
538
|
return;
|
|
446
539
|
}
|
|
447
540
|
|
|
@@ -452,6 +545,10 @@ export function inspectorPlugin() {
|
|
|
452
545
|
const fiber = getReactFiber(element);
|
|
453
546
|
const componentInfo = fiber ? getComponentInfo(fiber) : null;
|
|
454
547
|
|
|
548
|
+
// Check if element is a text node
|
|
549
|
+
const isTextNode = element.textContent && element.children.length === 0;
|
|
550
|
+
const textContent = isTextNode ? element.textContent.trim() : '';
|
|
551
|
+
|
|
455
552
|
const elementData = {
|
|
456
553
|
tagName: element.tagName,
|
|
457
554
|
className: element.className,
|
|
@@ -462,21 +559,27 @@ export function inspectorPlugin() {
|
|
|
462
559
|
left: element.getBoundingClientRect().left,
|
|
463
560
|
width: element.getBoundingClientRect().width,
|
|
464
561
|
height: element.getBoundingClientRect().height
|
|
465
|
-
}
|
|
562
|
+
},
|
|
563
|
+
isTextNode: isTextNode,
|
|
564
|
+
textContent: textContent
|
|
466
565
|
};
|
|
467
566
|
|
|
468
567
|
// Send info to parent window
|
|
469
568
|
if (window.parent !== window) {
|
|
470
|
-
|
|
569
|
+
// Use JSON.stringify to ensure all properties are serialized correctly
|
|
570
|
+
const message = JSON.stringify({
|
|
471
571
|
type: 'INSPECTOR_ELEMENT_SELECTED',
|
|
472
572
|
data: elementData
|
|
473
|
-
}
|
|
573
|
+
});
|
|
574
|
+
window.parent.postMessage(message, '*');
|
|
474
575
|
}
|
|
475
576
|
|
|
476
577
|
// Log for debugging
|
|
477
578
|
console.log('Element selected:', {
|
|
478
579
|
element,
|
|
479
|
-
componentInfo
|
|
580
|
+
componentInfo,
|
|
581
|
+
isTextNode,
|
|
582
|
+
textContent
|
|
480
583
|
});
|
|
481
584
|
|
|
482
585
|
// Pause inspection and show control box
|
|
@@ -492,6 +595,7 @@ export function inspectorPlugin() {
|
|
|
492
595
|
document.body.style.cursor = 'crosshair';
|
|
493
596
|
document.addEventListener('mousemove', handleMouseMove, true);
|
|
494
597
|
document.addEventListener('click', handleClick, true);
|
|
598
|
+
document.addEventListener('scroll', handleScroll, true);
|
|
495
599
|
} else {
|
|
496
600
|
// Clean up everything
|
|
497
601
|
clearHighlight();
|
|
@@ -499,6 +603,7 @@ export function inspectorPlugin() {
|
|
|
499
603
|
document.body.style.cursor = '';
|
|
500
604
|
document.removeEventListener('mousemove', handleMouseMove, true);
|
|
501
605
|
document.removeEventListener('click', handleClick, true);
|
|
606
|
+
document.removeEventListener('scroll', handleScroll, true);
|
|
502
607
|
}
|
|
503
608
|
}
|
|
504
609
|
|
|
@@ -511,6 +616,13 @@ export function inspectorPlugin() {
|
|
|
511
616
|
if (event.data.labels) {
|
|
512
617
|
labels = { ...labels, ...event.data.labels };
|
|
513
618
|
}
|
|
619
|
+
|
|
620
|
+
// Update theme if provided
|
|
621
|
+
if (event.data.theme) {
|
|
622
|
+
theme = { ...theme, ...event.data.theme };
|
|
623
|
+
}
|
|
624
|
+
} else if (event.data.type === 'SHOW_CONTENT_INPUT') {
|
|
625
|
+
toggleContentInput(event.data.show);
|
|
514
626
|
}
|
|
515
627
|
});
|
|
516
628
|
|
|
@@ -523,7 +635,7 @@ export function inspectorPlugin() {
|
|
|
523
635
|
lastUrl = newUrl;
|
|
524
636
|
|
|
525
637
|
if (window.parent !== window) {
|
|
526
|
-
|
|
638
|
+
const message = JSON.stringify({
|
|
527
639
|
type: 'URL_CHANGED',
|
|
528
640
|
data: {
|
|
529
641
|
url: newUrl,
|
|
@@ -531,7 +643,8 @@ export function inspectorPlugin() {
|
|
|
531
643
|
search: location.search,
|
|
532
644
|
hash: location.hash
|
|
533
645
|
}
|
|
534
|
-
}
|
|
646
|
+
});
|
|
647
|
+
window.parent.postMessage(message, '*');
|
|
535
648
|
}
|
|
536
649
|
}
|
|
537
650
|
}
|
|
@@ -564,10 +677,11 @@ export function inspectorPlugin() {
|
|
|
564
677
|
|
|
565
678
|
function sendError(errorData) {
|
|
566
679
|
if (window.parent !== window) {
|
|
567
|
-
|
|
680
|
+
const message = JSON.stringify({
|
|
568
681
|
type: 'INSPECTOR_ERROR',
|
|
569
682
|
data: errorData
|
|
570
|
-
}
|
|
683
|
+
});
|
|
684
|
+
window.parent.postMessage(message, '*');
|
|
571
685
|
}
|
|
572
686
|
// Use original console.error to avoid infinite loop
|
|
573
687
|
originalConsoleError.call(console, '🔍 Inspector Error:', errorData);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared types for @
|
|
2
|
+
* Shared types for @promakeai/inspector
|
|
3
|
+
*
|
|
4
|
+
* Bu dosya inspector için kullanılan tüm type'ları içerir.
|
|
5
|
+
* Hook ve plugin arasında paylaşılan type'lar burada tanımlanır.
|
|
3
6
|
*/
|
|
4
7
|
export interface ComponentInfo {
|
|
5
8
|
componentName: string;
|
|
@@ -19,6 +22,8 @@ export interface SelectedElementData {
|
|
|
19
22
|
id: string;
|
|
20
23
|
component: ComponentInfo | null;
|
|
21
24
|
position: ElementPosition;
|
|
25
|
+
isTextNode?: boolean;
|
|
26
|
+
textContent?: string;
|
|
22
27
|
}
|
|
23
28
|
export interface UrlChangeData {
|
|
24
29
|
url: string;
|
|
@@ -50,6 +55,18 @@ export interface InspectorLabels {
|
|
|
50
55
|
updateText?: string;
|
|
51
56
|
promptPlaceholder?: string;
|
|
52
57
|
}
|
|
58
|
+
export interface InspectorTheme {
|
|
59
|
+
backgroundColor?: string;
|
|
60
|
+
textColor?: string;
|
|
61
|
+
buttonColor?: string;
|
|
62
|
+
buttonTextColor?: string;
|
|
63
|
+
inputBackgroundColor?: string;
|
|
64
|
+
inputTextColor?: string;
|
|
65
|
+
inputBorderColor?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface ContentInputRequestData {
|
|
68
|
+
show: boolean;
|
|
69
|
+
}
|
|
53
70
|
export interface InspectorCallbacks {
|
|
54
71
|
onElementSelected?: (data: SelectedElementData) => void;
|
|
55
72
|
onUrlChange?: (data: UrlChangeData) => void;
|
|
@@ -62,5 +79,6 @@ export interface UseInspectorReturn {
|
|
|
62
79
|
toggleInspector: (active?: boolean) => void;
|
|
63
80
|
startInspecting: () => void;
|
|
64
81
|
stopInspecting: () => void;
|
|
82
|
+
showContentInput: (show: boolean) => void;
|
|
65
83
|
}
|
|
66
84
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAGD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAGD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAID,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAGD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAC;CACf;AAGD,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACrC;AAGD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,gBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C"}
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promakeai/inspector",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Visual element inspector for React apps in iframe with AI prompt support",
|
|
5
5
|
"author": "Promake",
|
|
6
|
+
"license": "MIT",
|
|
6
7
|
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/promakeai/inspector.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/promakeai/inspector/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/promakeai/inspector#readme",
|
|
7
16
|
"main": "./dist/hook.js",
|
|
8
17
|
"module": "./dist/hook.js",
|
|
9
18
|
"types": "./dist/hook.d.ts",
|
|
@@ -24,7 +33,8 @@
|
|
|
24
33
|
],
|
|
25
34
|
"scripts": {
|
|
26
35
|
"build": "tsc && tsc -p tsconfig.plugin.json",
|
|
27
|
-
"prepublishOnly": "npm run build"
|
|
36
|
+
"prepublishOnly": "npm run build",
|
|
37
|
+
"release": "npm run build && npm publish --access public"
|
|
28
38
|
},
|
|
29
39
|
"keywords": [
|
|
30
40
|
"vite",
|
|
@@ -38,8 +48,8 @@
|
|
|
38
48
|
"prompt"
|
|
39
49
|
],
|
|
40
50
|
"peerDependencies": {
|
|
41
|
-
"react": "
|
|
42
|
-
"vite": "
|
|
51
|
+
"react": ">18.0.0",
|
|
52
|
+
"vite": ">5.0.0"
|
|
43
53
|
},
|
|
44
54
|
"devDependencies": {
|
|
45
55
|
"@types/react": "^18.2.0",
|