import { createElement } from "react" import { createRoot, type Root } from "react-dom/client" import InputSuggestionsPopup from "~components/InputSuggestionsPopup" import { searchFacts } from "~utils/api" import { isExtensionContextValid } from "~utils/context" let inputDots: Map = new Map() let inputPopups: Map = new Map() let reactRoots: Map = new Map() let currentUrl = window.location.href // Track page navigation and reset popups function checkPageNavigation() { if (window.location.href !== currentUrl) { console.log("Page navigation detected, resetting input popups") currentUrl = window.location.href clearAllInputElements() } } // Input field monitoring function createInputDot(input: HTMLElement, rect: DOMRect) { if (inputDots.has(input)) return const dot = document.createElement("div") dot.style.cssText = ` position: absolute; width: 54px; height: 20px; cursor: pointer; z-index: 10001; opacity: 0; transition: opacity 0.2s ease; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); background: #c15e50; border-radius: 4px; padding: 3px; display: flex; flex-direction: column; align-items: center; justify-content: center; ` // Add the logo SVG with white color and Core text - smaller version for input fields dot.innerHTML = `
Core
` dot.style.top = `${rect.top + window.scrollY + 2}px` dot.style.left = `${rect.right + window.scrollX - 24}px` dot.addEventListener("click", () => showInputPopup(input)) dot.addEventListener("mouseenter", () => showInputPopup(input)) document.body.appendChild(dot) inputDots.set(input, dot) setTimeout(() => { dot.style.opacity = "1" }, 10) } // Facts popup for input fields with React async function showInputPopup(input: HTMLElement) { if (!isExtensionContextValid()) { console.log("Extension context invalid, skipping input popup") return } // Get value from input element, handling both input/textarea and contenteditable let query = "" if ("value" in input && (input as HTMLInputElement).value !== undefined) { query = (input as HTMLInputElement).value.trim() } else { query = input.textContent?.trim() || "" } if (!query || inputPopups.has(input)) return // Create popup container const popup = document.createElement("div") popup.style.cssText = ` position: fixed; z-index: 10002; opacity: 0; transition: opacity 0.2s ease; ` // Position popup const rect = input.getBoundingClientRect() let top = rect.bottom + 5 let left = rect.left if (left + 400 > window.innerWidth) { left = window.innerWidth - 410 } if (left < 10) { left = 10 } if (top + 300 > window.innerHeight) { top = rect.top - 310 } popup.style.top = `${top}px` popup.style.left = `${left}px` document.body.appendChild(popup) inputPopups.set(input, popup) // Create React root const root = createRoot(popup) reactRoots.set(input, root) // Handle fact selection const handleFactSelect = (factText: string) => { if (input) { // Handle both input/textarea and contenteditable elements if ("value" in input && (input as HTMLInputElement).value !== undefined) { const inputEl = input as HTMLInputElement inputEl.value += (inputEl.value ? " " : "") + factText inputEl.dispatchEvent(new Event("input", { bubbles: true })) } else { // For contenteditable elements const currentText = input.textContent || "" input.textContent = currentText + (currentText ? " " : "") + factText input.dispatchEvent(new Event("input", { bubbles: true })) } removeInputPopup(input) } } // Handle popup close const handleClose = () => { removeInputPopup(input) } // Render loading state initially root.render( createElement(InputSuggestionsPopup, { facts: [], isLoading: true, onFactSelect: handleFactSelect, onClose: handleClose }) ) // Show popup setTimeout(() => { popup.style.opacity = "1" }, 10) // Search for facts try { const searchResult = await searchFacts(query) const facts = searchResult?.facts || [] // Update with search results root.render( createElement(InputSuggestionsPopup, { facts, isLoading: false, onFactSelect: handleFactSelect, onClose: handleClose }) ) } catch (error) { console.error("Error searching facts:", error) // Show error state root.render( createElement(InputSuggestionsPopup, { facts: [], isLoading: false, onFactSelect: handleFactSelect, onClose: handleClose }) ) } } // Input field monitoring export function checkInputFields() { // Check for page navigation first checkPageNavigation() const inputs = document.querySelectorAll( "input[type='text'], input[type='search'], input[type='email'], input[type='url'], textarea, [contenteditable='true']" ) inputs.forEach((input) => { const element = input as HTMLElement const inputElement = element as HTMLInputElement | HTMLTextAreaElement // Skip Core extension popup textarea if ( element.id === "memory-text" || element.closest('[style*="z-index: 10002"]') ) { return } let value = "" if (inputElement.value !== undefined) { value = inputElement.value.trim() } else { value = element.textContent?.trim() || "" } const words = value.split(/\s+/).filter((word) => word.length > 0) if (words.length > 1) { const rect = element.getBoundingClientRect() if (rect.width > 0 && rect.height > 0) { createInputDot(element, rect) } } else { removeInputDot(element) removeInputPopup(element) } }) } // Cleanup functions function removeInputDot(input: HTMLElement) { const dot = inputDots.get(input) if (dot) { dot.remove() inputDots.delete(input) } } export function removeInputPopup(input: HTMLElement) { // Clean up React root first const root = reactRoots.get(input) if (root) { root.unmount() reactRoots.delete(input) } // Remove DOM element const popup = inputPopups.get(input) if (popup) { popup.remove() inputPopups.delete(input) } } export function clearAllInputElements() { // Clean up React roots reactRoots.forEach((root) => root.unmount()) reactRoots.clear() // Clean up DOM elements inputDots.forEach((dot) => dot.remove()) inputPopups.forEach((popup) => popup.remove()) inputDots.clear() inputPopups.clear() } // Check if click is on input elements export function isInputElement(target: HTMLElement): boolean { // Check if target is within any popup or dot for (const popup of inputPopups.values()) { if (popup.contains(target)) return true } for (const dot of inputDots.values()) { if (dot.contains(target)) return true } return false } // Handle input popup click outside export function handleInputClickOutside(target: HTMLElement) { inputPopups.forEach((popup, input) => { if (!popup.contains(target) && target !== input) { removeInputPopup(input) } }) } // Close all input popups export function closeAllInputPopups() { inputPopups.forEach((popup, input) => { removeInputPopup(input) }) }