"use strict";
(() => {
let recordingState = {
active: false,
template_name: "",
regions: [],
};
let highlightedElement = null;
let selectedElements = new Set();
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
console.log("[Content] Received message:", message.type);
switch (message.type) {
case "start_recording":
startRecording(message.template_name);
sendResponse({ success: true });
break;
case "stop_recording":
stopRecording();
sendResponse({ success: true, regions: recordingState.regions });
break;
case "get_selected_selector":
if (highlightedElement) {
const path = getElementPath(highlightedElement);
sendResponse({
success: true,
css_path: path.css,
xpath_path: path.xpath,
element_text: highlightedElement.textContent?.slice(0, 100),
});
}
else {
sendResponse({ success: false, error: "No element selected" });
}
break;
case "highlight_selector":
highlightElementBySelector(message.selector);
sendResponse({ success: true });
break;
case "clear_highlights":
clearAllHighlights();
sendResponse({ success: true });
break;
case "get_page_html":
sendResponse({
success: true,
html: document.documentElement.outerHTML,
});
break;
default:
sendResponse({ success: false, error: "Unknown message type" });
}
return true; });
function startRecording(templateName) {
recordingState.active = true;
recordingState.template_name = templateName;
recordingState.regions = [];
console.log("[Content] Recording started:", templateName);
showRecordingOverlay();
document.addEventListener("mouseover", onElementHover, true);
document.addEventListener("mousedown", onElementClick, true);
}
function stopRecording() {
recordingState.active = false;
console.log("[Content] Recording stopped");
removeRecordingOverlay();
document.removeEventListener("mouseover", onElementHover, true);
document.removeEventListener("mousedown", onElementClick, true);
clearAllHighlights();
}
function onElementHover(event) {
if (!recordingState.active)
return;
const target = event.target;
if (target === highlightedElement)
return;
if (highlightedElement) {
unhighlightElement(highlightedElement);
}
highlightedElement = target;
highlightElement(target);
}
function onElementClick(event) {
if (!recordingState.active)
return;
event.preventDefault();
event.stopPropagation();
if (!highlightedElement)
return;
const name = prompt('Enter region name (e.g., "product_title"):');
if (!name)
return;
const path = getElementPath(highlightedElement);
const region = {
name,
selector: {
type: "dual",
css: path.css,
xpath: path.xpath,
},
schema: { type: "string" },
transformations: [],
};
recordingState.regions.push(region);
console.log("[Content] Region added:", name);
chrome.runtime.sendMessage({
type: "region_added",
region,
});
}
function highlightElement(element) {
const rect = element.getBoundingClientRect();
const highlight = document.createElement("div");
highlight.className = "stygian-highlight";
highlight.setAttribute("data-stygian", "highlight");
highlight.style.position = "fixed";
highlight.style.top = rect.top + "px";
highlight.style.left = rect.left + "px";
highlight.style.width = rect.width + "px";
highlight.style.height = rect.height + "px";
highlight.style.backgroundColor = "rgba(52, 152, 219, 0.3)";
highlight.style.border = "2px solid #3498db";
highlight.style.borderRadius = "4px";
highlight.style.zIndex = "999998";
highlight.style.pointerEvents = "none";
document.body.appendChild(highlight);
element.__stygian_highlight = highlight;
}
function unhighlightElement(element) {
const highlight = element.__stygian_highlight;
if (highlight) {
highlight.remove();
delete element.__stygian_highlight;
}
}
function clearAllHighlights() {
document.querySelectorAll('[data-stygian="highlight"]').forEach((el) => {
el.remove();
});
highlightedElement = null;
selectedElements.clear();
}
function highlightElementBySelector(selector) {
clearAllHighlights();
try {
const elements = document.querySelectorAll(selector);
elements.forEach((el) => {
highlightElement(el);
selectedElements.add(el);
});
}
catch (e) {
console.error("[Content] Invalid selector:", e);
}
}
function getElementPath(element) {
return {
css: getCSSPath(element),
xpath: getXPathPath(element),
};
}
function getCSSPath(element) {
const path = [];
let el = element;
while (el && el !== document.documentElement) {
let selector = el.tagName.toLowerCase();
if (el.id) {
selector += `#${el.id}`;
path.unshift(selector);
break;
}
if (el.className) {
const classes = el.className
.split(/\s+/)
.filter((c) => c)
.join(".");
if (classes)
selector += `.${classes}`;
}
path.unshift(selector);
el = el.parentElement;
}
return path.join(" > ");
}
function getXPathPath(element) {
const path = [];
let el = element;
while (el && el !== document.documentElement) {
let index = 1;
let sibling = el.previousElementSibling;
while (sibling) {
if (sibling.tagName === el.tagName) {
index++;
}
sibling = sibling.previousElementSibling;
}
const tagName = el.tagName.toLowerCase();
const part = `${tagName}[${index}]`;
path.unshift(part);
el = el.parentElement;
}
return "/" + path.join("/");
}
function showRecordingOverlay() {
const overlay = document.createElement("div");
overlay.id = "stygian-recording-overlay";
overlay.setAttribute("data-stygian", "overlay");
overlay.innerHTML = `
<div style="
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
pointer-events: none;
">
<div style="
background: white;
padding: 12px 20px;
border-radius: 8px;
margin-top: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
pointer-events: auto;
display: flex;
gap: 8px;
align-items: center;
">
<span style="color: #333; font-size: 14px; font-weight: 500;">
🔴 Recording: ${recordingState.template_name}
</span>
<span style="color: #666; font-size: 12px;">
Hover to preview, Click to add region
</span>
</div>
<div style="
color: white;
font-size: 12px;
margin-top: 10px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 12px;
border-radius: 4px;
">
Regions added: <strong>${recordingState.regions.length}</strong>
</div>
</div>
`;
document.body.appendChild(overlay);
const overlayDiv = overlay.querySelector("div");
if (overlayDiv) {
overlayDiv.style.pointerEvents = "auto";
}
}
function removeRecordingOverlay() {
const overlay = document.getElementById("stygian-recording-overlay");
if (overlay) {
overlay.remove();
}
}
console.log("[Content] Stygian Plugin content script loaded");
chrome.runtime.sendMessage({
type: "content_script_ready",
});
})();