// Content script for Crawl4AI Assistant class SchemaBuilder { constructor() { this.mode = null; this.container = null; this.fields = []; this.overlay = null; this.toolbar = null; this.highlightBox = null; this.selectedElements = new Set(); this.isPaused = false; this.codeModal = null; this.handleMouseMove = this.handleMouseMove.bind(this); this.handleClick = this.handleClick.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this); } start() { this.mode = 'container'; this.createOverlay(); this.createToolbar(); this.attachEventListeners(); this.updateToolbar(); } stop() { this.detachEventListeners(); this.overlay?.remove(); this.toolbar?.remove(); this.highlightBox?.remove(); this.removeAllHighlights(); this.mode = null; this.container = null; this.fields = []; this.selectedElements.clear(); } createOverlay() { // Create highlight box this.highlightBox = document.createElement('div'); this.highlightBox.className = 'c4ai-highlight-box'; document.body.appendChild(this.highlightBox); } createToolbar() { this.toolbar = document.createElement('div'); this.toolbar.className = 'c4ai-toolbar'; this.toolbar.innerHTML = `
`; document.body.appendChild(this.toolbar); // Add event listeners for toolbar buttons document.getElementById('c4ai-pause').addEventListener('click', () => this.togglePause()); document.getElementById('c4ai-generate').addEventListener('click', () => this.stopAndGenerate()); document.getElementById('c4ai-close').addEventListener('click', () => this.stop()); // Make toolbar draggable this.makeDraggable(this.toolbar); } attachEventListeners() { document.addEventListener('mousemove', this.handleMouseMove, true); document.addEventListener('click', this.handleClick, true); document.addEventListener('keydown', this.handleKeyPress, true); } detachEventListeners() { document.removeEventListener('mousemove', this.handleMouseMove, true); document.removeEventListener('click', this.handleClick, true); document.removeEventListener('keydown', this.handleKeyPress, true); } handleMouseMove(e) { if (this.isPaused) return; const element = document.elementFromPoint(e.clientX, e.clientY); if (element && !this.isOurElement(element)) { this.highlightElement(element); } } handleClick(e) { if (this.isPaused) return; const element = e.target; if (this.isOurElement(element)) { return; } e.preventDefault(); e.stopPropagation(); if (this.mode === 'container') { this.selectContainer(element); } else if (this.mode === 'field') { this.selectField(element); } } handleKeyPress(e) { if (e.key === 'Escape') { this.stop(); } } isOurElement(element) { return element.classList.contains('c4ai-highlight-box') || element.classList.contains('c4ai-toolbar') || element.closest('.c4ai-toolbar') || element.closest('.c4ai-field-dialog') || element.closest('.c4ai-code-modal'); } makeDraggable(element) { let isDragging = false; let startX, startY, initialX, initialY; const titlebar = element.querySelector('.c4ai-toolbar-titlebar'); titlebar.addEventListener('mousedown', (e) => { // Don't drag if clicking on buttons if (e.target.classList.contains('c4ai-dot')) return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = element.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; element.style.transition = 'none'; titlebar.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; element.style.left = `${initialX + deltaX}px`; element.style.top = `${initialY + deltaY}px`; element.style.right = 'auto'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; element.style.transition = ''; titlebar.style.cursor = 'grab'; } }); } togglePause() { this.isPaused = !this.isPaused; const pauseBtn = document.getElementById('c4ai-pause'); if (this.isPaused) { pauseBtn.innerHTML = ' Resume'; pauseBtn.classList.add('c4ai-paused'); this.highlightBox.style.display = 'none'; } else { pauseBtn.innerHTML = ' Pause'; pauseBtn.classList.remove('c4ai-paused'); } } stopAndGenerate() { if (!this.container || this.fields.length === 0) { alert('Please select a container and at least one field before generating code.'); return; } const code = this.generateCode(); this.showCodeModal(code); } highlightElement(element) { const rect = element.getBoundingClientRect(); this.highlightBox.style.cssText = ` left: ${rect.left + window.scrollX}px; top: ${rect.top + window.scrollY}px; width: ${rect.width}px; height: ${rect.height}px; display: block; `; if (this.mode === 'container') { this.highlightBox.className = 'c4ai-highlight-box c4ai-container-mode'; } else { this.highlightBox.className = 'c4ai-highlight-box c4ai-field-mode'; } } selectContainer(element) { // Remove previous container highlight if (this.container) { this.container.element.classList.remove('c4ai-selected-container'); } this.container = { element: element, html: element.outerHTML, selector: this.generateSelector(element), tagName: element.tagName.toLowerCase() }; element.classList.add('c4ai-selected-container'); this.mode = 'field'; this.updateToolbar(); this.updateStats(); } selectField(element) { // Don't select the container itself if (element === this.container.element) { return; } // Check if already selected - if so, deselect it if (this.selectedElements.has(element)) { this.deselectField(element); return; } // Must be inside the container if (!this.container.element.contains(element)) { return; } this.showFieldDialog(element); } deselectField(element) { // Remove from fields array this.fields = this.fields.filter(f => f.element !== element); // Remove from selected elements set this.selectedElements.delete(element); // Remove visual selection element.classList.remove('c4ai-selected-field'); // Update UI this.updateToolbar(); this.updateStats(); } showFieldDialog(element) { const dialog = document.createElement('div'); dialog.className = 'c4ai-field-dialog'; const rect = element.getBoundingClientRect(); dialog.style.cssText = ` left: ${rect.left + window.scrollX}px; top: ${rect.bottom + window.scrollY + 10}px; `; dialog.innerHTML = `