feat: add element inspector with chat integration
- Implement element inspector tool for preview iframe with hover/click detection - Add inspector panel UI to display element details and styles - Integrate selected elements into chat messages for reference - Style improvements for chat messages and scroll behavior - Add inspector script injection to preview iframe - Support element selection and context in chat prompts -Redesign Messgaes, Workbench and Header for a more refined look allowing more workspace in view
This commit is contained in:
292
public/inspector-script.js
Normal file
292
public/inspector-script.js
Normal file
@@ -0,0 +1,292 @@
|
||||
(function() {
|
||||
let isInspectorActive = false;
|
||||
let inspectorStyle = null;
|
||||
let currentHighlight = null;
|
||||
|
||||
// Function to get relevant styles
|
||||
function getRelevantStyles(element) {
|
||||
const computedStyles = window.getComputedStyle(element);
|
||||
const relevantProps = [
|
||||
'display', 'position', 'width', 'height', 'margin', 'padding',
|
||||
'border', 'background', 'color', 'font-size', 'font-family',
|
||||
'text-align', 'flex-direction', 'justify-content', 'align-items'
|
||||
];
|
||||
|
||||
const styles = {};
|
||||
relevantProps.forEach(prop => {
|
||||
const value = computedStyles.getPropertyValue(prop);
|
||||
if (value) styles[prop] = value;
|
||||
});
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
// Function to create a readable element selector
|
||||
function createReadableSelector(element) {
|
||||
let selector = element.tagName.toLowerCase();
|
||||
|
||||
// Add ID if present
|
||||
if (element.id) {
|
||||
selector += `#${element.id}`;
|
||||
}
|
||||
|
||||
// Add classes if present
|
||||
let className = '';
|
||||
if (element.className) {
|
||||
if (typeof element.className === 'string') {
|
||||
className = element.className;
|
||||
} else if (element.className.baseVal !== undefined) {
|
||||
className = element.className.baseVal;
|
||||
} else {
|
||||
className = element.className.toString();
|
||||
}
|
||||
|
||||
if (className.trim()) {
|
||||
const classes = className.trim().split(/\s+/).slice(0, 3); // Limit to first 3 classes
|
||||
selector += `.${classes.join('.')}`;
|
||||
}
|
||||
}
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
// Function to create element display text
|
||||
function createElementDisplayText(element) {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
let displayText = `<${tagName}`;
|
||||
|
||||
// Add ID attribute
|
||||
if (element.id) {
|
||||
displayText += ` id="${element.id}"`;
|
||||
}
|
||||
|
||||
// Add class attribute (limit to first 3 classes for readability)
|
||||
let className = '';
|
||||
if (element.className) {
|
||||
if (typeof element.className === 'string') {
|
||||
className = element.className;
|
||||
} else if (element.className.baseVal !== undefined) {
|
||||
className = element.className.baseVal;
|
||||
} else {
|
||||
className = element.className.toString();
|
||||
}
|
||||
|
||||
if (className.trim()) {
|
||||
const classes = className.trim().split(/\s+/);
|
||||
const displayClasses = classes.length > 3 ?
|
||||
classes.slice(0, 3).join(' ') + '...' :
|
||||
classes.join(' ');
|
||||
displayText += ` class="${displayClasses}"`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add other important attributes
|
||||
const importantAttrs = ['type', 'name', 'href', 'src', 'alt', 'title'];
|
||||
importantAttrs.forEach(attr => {
|
||||
const value = element.getAttribute(attr);
|
||||
if (value) {
|
||||
const truncatedValue = value.length > 30 ? value.substring(0, 30) + '...' : value;
|
||||
displayText += ` ${attr}="${truncatedValue}"`;
|
||||
}
|
||||
});
|
||||
|
||||
displayText += '>';
|
||||
|
||||
// Add text content preview for certain elements
|
||||
const textElements = ['span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'button', 'a', 'label'];
|
||||
if (textElements.includes(tagName) && element.textContent) {
|
||||
const textPreview = element.textContent.trim().substring(0, 50);
|
||||
if (textPreview) {
|
||||
displayText += textPreview.length < element.textContent.trim().length ?
|
||||
textPreview + '...' : textPreview;
|
||||
}
|
||||
}
|
||||
|
||||
displayText += `</${tagName}>`;
|
||||
|
||||
return displayText;
|
||||
}
|
||||
|
||||
// Function to create element info
|
||||
function createElementInfo(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
tagName: element.tagName,
|
||||
className: getElementClassName(element),
|
||||
id: element.id || '',
|
||||
textContent: element.textContent?.slice(0, 100) || '',
|
||||
styles: getRelevantStyles(element),
|
||||
rect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
top: rect.top,
|
||||
left: rect.left
|
||||
},
|
||||
// Add new readable formats
|
||||
selector: createReadableSelector(element),
|
||||
displayText: createElementDisplayText(element),
|
||||
elementPath: getElementPath(element)
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to get element class name consistently
|
||||
function getElementClassName(element) {
|
||||
if (!element.className) return '';
|
||||
|
||||
if (typeof element.className === 'string') {
|
||||
return element.className;
|
||||
} else if (element.className.baseVal !== undefined) {
|
||||
return element.className.baseVal;
|
||||
} else {
|
||||
return element.className.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get element path (breadcrumb)
|
||||
function getElementPath(element) {
|
||||
const path = [];
|
||||
let current = element;
|
||||
|
||||
while (current && current !== document.body && current !== document.documentElement) {
|
||||
let pathSegment = current.tagName.toLowerCase();
|
||||
|
||||
if (current.id) {
|
||||
pathSegment += `#${current.id}`;
|
||||
} else if (current.className) {
|
||||
const className = getElementClassName(current);
|
||||
if (className.trim()) {
|
||||
const firstClass = className.trim().split(/\s+/)[0];
|
||||
pathSegment += `.${firstClass}`;
|
||||
}
|
||||
}
|
||||
|
||||
path.unshift(pathSegment);
|
||||
current = current.parentElement;
|
||||
|
||||
// Limit path length
|
||||
if (path.length >= 5) break;
|
||||
}
|
||||
|
||||
return path.join(' > ');
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
function handleMouseMove(e) {
|
||||
if (!isInspectorActive) return;
|
||||
|
||||
const target = e.target;
|
||||
if (!target || target === document.body || target === document.documentElement) return;
|
||||
|
||||
// Remove previous highlight
|
||||
if (currentHighlight) {
|
||||
currentHighlight.classList.remove('inspector-highlight');
|
||||
}
|
||||
|
||||
// Add highlight to current element
|
||||
target.classList.add('inspector-highlight');
|
||||
currentHighlight = target;
|
||||
|
||||
const elementInfo = createElementInfo(target);
|
||||
|
||||
// Send message to parent
|
||||
window.parent.postMessage({
|
||||
type: 'INSPECTOR_HOVER',
|
||||
elementInfo: elementInfo
|
||||
}, '*');
|
||||
}
|
||||
|
||||
function handleClick(e) {
|
||||
if (!isInspectorActive) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.target;
|
||||
if (!target || target === document.body || target === document.documentElement) return;
|
||||
|
||||
const elementInfo = createElementInfo(target);
|
||||
|
||||
// Send message to parent
|
||||
window.parent.postMessage({
|
||||
type: 'INSPECTOR_CLICK',
|
||||
elementInfo: elementInfo
|
||||
}, '*');
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
if (!isInspectorActive) return;
|
||||
|
||||
// Remove highlight
|
||||
if (currentHighlight) {
|
||||
currentHighlight.classList.remove('inspector-highlight');
|
||||
currentHighlight = null;
|
||||
}
|
||||
|
||||
// Send message to parent
|
||||
window.parent.postMessage({
|
||||
type: 'INSPECTOR_LEAVE'
|
||||
}, '*');
|
||||
}
|
||||
|
||||
// Function to activate/deactivate inspector
|
||||
function setInspectorActive(active) {
|
||||
isInspectorActive = active;
|
||||
|
||||
if (active) {
|
||||
// Add inspector styles
|
||||
if (!inspectorStyle) {
|
||||
inspectorStyle = document.createElement('style');
|
||||
inspectorStyle.textContent = `
|
||||
.inspector-active * {
|
||||
cursor: crosshair !important;
|
||||
}
|
||||
.inspector-highlight {
|
||||
outline: 2px solid #3b82f6 !important;
|
||||
outline-offset: -2px !important;
|
||||
background-color: rgba(59, 130, 246, 0.1) !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(inspectorStyle);
|
||||
}
|
||||
|
||||
document.body.classList.add('inspector-active');
|
||||
|
||||
// Add event listeners
|
||||
document.addEventListener('mousemove', handleMouseMove, true);
|
||||
document.addEventListener('click', handleClick, true);
|
||||
document.addEventListener('mouseleave', handleMouseLeave, true);
|
||||
} else {
|
||||
document.body.classList.remove('inspector-active');
|
||||
|
||||
// Remove highlight
|
||||
if (currentHighlight) {
|
||||
currentHighlight.classList.remove('inspector-highlight');
|
||||
currentHighlight = null;
|
||||
}
|
||||
|
||||
// Remove event listeners
|
||||
document.removeEventListener('mousemove', handleMouseMove, true);
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
document.removeEventListener('mouseleave', handleMouseLeave, true);
|
||||
|
||||
// Remove styles
|
||||
if (inspectorStyle) {
|
||||
inspectorStyle.remove();
|
||||
inspectorStyle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for messages from parent
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.data.type === 'INSPECTOR_ACTIVATE') {
|
||||
setInspectorActive(event.data.active);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-inject if inspector is already active
|
||||
window.parent.postMessage({ type: 'INSPECTOR_READY' }, '*');
|
||||
})();
|
||||
Reference in New Issue
Block a user