From 49065048d780f8b7381063e33d4c7dcce2607cb9 Mon Sep 17 00:00:00 2001 From: ankur22 Date: Wed, 4 Dec 2024 12:20:35 +0000 Subject: [PATCH] Add copy from selector engine Copy the selector from the open window with the element hovering over the element. --- common/js/selector_engine.js | 183 ++++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 77 deletions(-) diff --git a/common/js/selector_engine.js b/common/js/selector_engine.js index f7c7ea97e..762118dcf 100644 --- a/common/js/selector_engine.js +++ b/common/js/selector_engine.js @@ -29,70 +29,69 @@ // 5. Fallback to XPath return generateXPath(element); -} - -// Helper function to compute the role (explicit or implicit) -function getRole(element) { - // Check for explicit role - if (element.hasAttribute('role')) { - return element.getAttribute('role'); - } - - // Implicit role mapping - const implicitRoles = { - button: ['button', "input[type='button']", "input[type='submit']", "input[type='reset']"], - link: ['a[href]'], - checkbox: ["input[type='checkbox']"], - heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], - dialog: ['dialog'], - img: ['img[alt]'], - textbox: ["input[type='text']", 'textarea'], - radio: ["input[type='radio']"], - // Add more implicit roles if needed - }; + } - for (const [role, selectors] of Object.entries(implicitRoles)) { - for (const selector of selectors) { - if (element.matches(selector)) { - return role; - } - } - } + // Helper function to compute the role (explicit or implicit) + function getRole(element) { + // Check for explicit role + if (element.hasAttribute('role')) { + return element.getAttribute('role'); + } - return null; -} + // Implicit role mapping + const implicitRoles = { + button: ['button', "input[type='button']", "input[type='submit']", "input[type='reset']"], + link: ['a[href]'], + checkbox: ["input[type='checkbox']"], + heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + dialog: ['dialog'], + img: ['img[alt]'], + textbox: ["input[type='text']", 'textarea'], + radio: ["input[type='radio']"], + // Add more implicit roles if needed + }; + + for (const [role, selectors] of Object.entries(implicitRoles)) { + for (const selector of selectors) { + if (element.matches(selector)) { + return role; + } + } + } -// Helper function to compute the accessible name of an element -function getAccessibleName(element) { - // Prefer aria-label or aria-labelledby - if (element.hasAttribute('aria-label')) { - return element.getAttribute('aria-label'); - } - if (element.hasAttribute('aria-labelledby')) { - const labelId = element.getAttribute('aria-labelledby'); - const labelElement = element.ownerDocument.getElementById(labelId); - return labelElement ? labelElement.textContent.trim() : ''; - } - // Use text content as a fallback - return element.textContent.trim(); -} + return null; + } -// Helper function to generate XPath as a fallback -function generateXPath(element) { - if (element.id) { - return `//*[@id="${element.id}"]`; - } - const siblings = Array.from(element.parentNode.children).filter( - (el) => el.nodeName === element.nodeName - ); - const index = siblings.indexOf(element) + 1; - const tagName = element.nodeName.toLowerCase(); - if (element.parentNode === document) { - return `/${tagName}[${index}]`; - } - return `${generateXPath(element.parentNode)}/${tagName}[${index}]`; -} + // Helper function to compute the accessible name of an element + function getAccessibleName(element) { + // Prefer aria-label or aria-labelledby + if (element.hasAttribute('aria-label')) { + return element.getAttribute('aria-label'); + } + if (element.hasAttribute('aria-labelledby')) { + const labelId = element.getAttribute('aria-labelledby'); + const labelElement = element.ownerDocument.getElementById(labelId); + return labelElement ? labelElement.textContent.trim() : ''; + } + // Use text content as a fallback + return element.textContent.trim(); + } + // Helper function to generate XPath as a fallback + function generateXPath(element) { + if (element.id) { + return `//*[@id="${element.id}"]`; + } + const siblings = Array.from(element.parentNode.children).filter( + (el) => el.nodeName === element.nodeName + ); + const index = siblings.indexOf(element) + 1; + const tagName = element.nodeName.toLowerCase(); + if (element.parentNode === document) { + return `/${tagName}[${index}]`; + } + return `${generateXPath(element.parentNode)}/${tagName}[${index}]`; + } // Highlight and Selector Display let lastHighlightedElement = null; @@ -107,29 +106,59 @@ function generateXPath(element) { selectorOverlay.style.zIndex = '9999'; document.body.appendChild(selectorOverlay); + // Helper to copy text to clipboard + function copyToClipboard(text) { + navigator.clipboard.writeText(text).then(() => { + console.log(`Copied to clipboard: ${text}`); + showTemporaryMessage('Copied!', selectorOverlay); + }).catch((err) => { + console.error('Failed to copy text: ', err); + showTemporaryMessage('Failed to copy', selectorOverlay); + }); + } + + // Show a temporary message in the overlay + function showTemporaryMessage(message, overlay) { + const originalText = overlay.textContent; + overlay.textContent = message; + setTimeout(() => { + overlay.textContent = originalText; + }, 1000); // Reset after 1 second + } + + // Highlight the element and show selector function highlightElement(event) { - if (lastHighlightedElement) { - lastHighlightedElement.style.outline = ''; - } - const element = event.target; - element.style.outline = '2px solid #FF671D'; - lastHighlightedElement = element; - - const selector = findBestSelector(element); - const rect = element.getBoundingClientRect(); - selectorOverlay.textContent = selector; - selectorOverlay.style.top = `${rect.top + window.scrollY}px`; - selectorOverlay.style.left = `${rect.left + window.scrollX}px`; + if (lastHighlightedElement) { + lastHighlightedElement.style.outline = ''; + } + const element = event.target; + element.style.outline = '2px solid #FF671D'; + lastHighlightedElement = element; + + const selector = findBestSelector(element); + const rect = element.getBoundingClientRect(); + selectorOverlay.textContent = selector; + selectorOverlay.style.top = `${rect.top + window.scrollY}px`; + selectorOverlay.style.left = `${rect.left + window.scrollX}px`; + + // Copy to clipboard on Command + C + document.onkeydown = (e) => { + if (e.metaKey && e.key === 'c') { // Press Command + C + e.preventDefault(); + copyToClipboard(selector); + } + }; } function removeHighlight() { - if (lastHighlightedElement) { - lastHighlightedElement.style.outline = ''; - lastHighlightedElement = null; - } - selectorOverlay.textContent = ''; + if (lastHighlightedElement) { + lastHighlightedElement.style.outline = ''; + lastHighlightedElement = null; + } + selectorOverlay.textContent = ''; + document.onkeydown = null; // Remove keydown listener } document.addEventListener('mouseover', highlightElement); document.addEventListener('mouseout', removeHighlight); -})(); +})(); \ No newline at end of file