Last active
May 17, 2025 18:37
-
-
Save MarwanShehata/01ffccceff91640563cb32bfbedf26b6 to your computer and use it in GitHub Desktop.
DOM Parent Collector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name DOM Parent Collector | |
// @namespace http://tampermonkey.net/ | |
// @version 0.2 | |
// @description Collect DOM parents of clicked elements and organize them | |
// @author You | |
// @match *://*/* | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @grant GM_addStyle | |
// @grant GM_registerMenuCommand | |
// @grant GM_setClipboard | |
// @grant GM_download | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// Configuration | |
let config = GM_getValue('domParentCollectorConfig', { | |
parentLevel: 5, // Default parent level to grab | |
enableCollection: true, // Collection enabled by default | |
maxStoredItems: 100, // Maximum number of items to store | |
showClickHighlight: true, // Show highlight effect when clicking | |
autoGroupInputs: true, // Auto-group inputs with labels | |
defaultExportFormat: 'json' // Default export format | |
}); | |
// Save config helper | |
function saveConfig() { | |
GM_setValue('domParentCollectorConfig', config); | |
} | |
// Initialize storage if not exists | |
if (!GM_getValue('collectedElements')) { | |
GM_setValue('collectedElements', []); | |
} | |
// Styles for UI elements | |
GM_addStyle(` | |
.dom-collector-ui { | |
position: fixed; | |
bottom: 20px; | |
right: 20px; | |
background: #fff; | |
border: 1px solid #ccc; | |
border-radius: 5px; | |
padding: 10px; | |
z-index: 9999; | |
box-shadow: 0 0 10px rgba(0,0,0,0.2); | |
max-width: 300px; | |
font-family: Arial, sans-serif; | |
font-size: 12px; | |
} | |
.dom-collector-ui button { | |
margin: 5px; | |
padding: 5px 10px; | |
background: #f0f0f0; | |
border: 1px solid #ccc; | |
border-radius: 3px; | |
cursor: pointer; | |
} | |
.dom-collector-ui button:hover { | |
background: #e0e0e0; | |
} | |
.dom-collector-ui h3 { | |
margin-top: 0; | |
margin-bottom: 10px; | |
font-size: 14px; | |
} | |
.dom-collector-counter { | |
display: inline-block; | |
background: #007bff; | |
color: white; | |
border-radius: 50%; | |
width: 20px; | |
height: 20px; | |
text-align: center; | |
line-height: 20px; | |
margin-left: 5px; | |
} | |
.dom-collector-highlight { | |
outline: 2px dashed red !important; | |
background-color: rgba(255, 0, 0, 0.1) !important; | |
transition: all 0.3s ease; | |
} | |
.dom-collector-settings { | |
padding: 10px; | |
background: #f9f9f9; | |
border-top: 1px solid #eee; | |
margin-top: 10px; | |
} | |
.dom-collector-settings label { | |
display: block; | |
margin: 5px 0; | |
} | |
.dom-collector-list { | |
max-height: 200px; | |
overflow-y: auto; | |
border: 1px solid #eee; | |
padding: 5px; | |
margin-top: 10px; | |
} | |
.dom-collector-list-item { | |
padding: 3px; | |
border-bottom: 1px solid #f0f0f0; | |
cursor: pointer; | |
} | |
.dom-collector-list-item:hover { | |
background: #f5f5f5; | |
} | |
.dom-collector-export { | |
padding: 10px; | |
background: #f9f9f9; | |
border-top: 1px solid #eee; | |
margin-top: 10px; | |
display: none; | |
} | |
.dom-collector-export select { | |
width: 100%; | |
margin-bottom: 10px; | |
padding: 5px; | |
} | |
`); | |
// Create UI | |
function createUI() { | |
const uiContainer = document.createElement('div'); | |
uiContainer.className = 'dom-collector-ui'; | |
uiContainer.innerHTML = ` | |
<h3>DOM Parent Collector <span class="dom-collector-counter">0</span></h3> | |
<button id="toggle-collection">Pause Collection</button> | |
<button id="toggle-settings">Settings</button> | |
<button id="view-collected">View Collected</button> | |
<button id="export-collected">Export</button> | |
<button id="clear-collected">Clear All</button> | |
<div id="dom-collector-settings" class="dom-collector-settings" style="display: none;"> | |
<label> | |
Parent Level: | |
<input type="number" id="parent-level" min="1" max="20" value="${config.parentLevel}"> | |
</label> | |
<label> | |
<input type="checkbox" id="show-highlight" ${config.showClickHighlight ? 'checked' : ''}> | |
Show click highlights | |
</label> | |
<label> | |
<input type="checkbox" id="auto-group" ${config.autoGroupInputs ? 'checked' : ''}> | |
Auto-group inputs with labels | |
</label> | |
<label> | |
Max stored items: | |
<input type="number" id="max-stored" min="10" max="1000" value="${config.maxStoredItems}"> | |
</label> | |
<label> | |
Default export format: | |
<select id="default-export-format"> | |
<option value="json" ${config.defaultExportFormat === 'json' ? 'selected' : ''}>JSON</option> | |
<option value="csv" ${config.defaultExportFormat === 'csv' ? 'selected' : ''}>CSV</option> | |
<option value="html" ${config.defaultExportFormat === 'html' ? 'selected' : ''}>HTML</option> | |
<option value="plaintext" ${config.defaultExportFormat === 'plaintext' ? 'selected' : ''}>Plain Text</option> | |
</select> | |
</label> | |
<button id="save-settings">Save Settings</button> | |
</div> | |
<div id="dom-collector-list" class="dom-collector-list" style="display: none;"></div> | |
<div id="dom-collector-export" class="dom-collector-export" style="display: none;"> | |
<label> | |
Export Format: | |
<select id="export-format"> | |
<option value="json">JSON</option> | |
<option value="csv">CSV</option> | |
<option value="html">HTML</option> | |
<option value="plaintext">Plain Text</option> | |
</select> | |
</label> | |
<button id="copy-to-clipboard">Copy to Clipboard</button> | |
<button id="download-file">Download File</button> | |
<button id="cancel-export">Cancel</button> | |
</div> | |
`; | |
document.body.appendChild(uiContainer); | |
// Update counter | |
updateCollectionCounter(); | |
// Event listeners | |
document.getElementById('toggle-collection').addEventListener('click', toggleCollection); | |
document.getElementById('toggle-settings').addEventListener('click', toggleSettings); | |
document.getElementById('view-collected').addEventListener('click', toggleViewCollected); | |
document.getElementById('export-collected').addEventListener('click', toggleExportPanel); | |
document.getElementById('clear-collected').addEventListener('click', clearCollected); | |
document.getElementById('save-settings').addEventListener('click', saveSettings); | |
document.getElementById('copy-to-clipboard').addEventListener('click', copyToClipboard); | |
document.getElementById('download-file').addEventListener('click', downloadFile); | |
document.getElementById('cancel-export').addEventListener('click', cancelExport); | |
} | |
// Toggle collection on/off | |
function toggleCollection() { | |
config.enableCollection = !config.enableCollection; | |
document.getElementById('toggle-collection').textContent = | |
config.enableCollection ? 'Pause Collection' : 'Resume Collection'; | |
saveConfig(); | |
} | |
// Toggle settings panel | |
function toggleSettings() { | |
const settingsDiv = document.getElementById('dom-collector-settings'); | |
const listDiv = document.getElementById('dom-collector-list'); | |
const exportDiv = document.getElementById('dom-collector-export'); | |
if (settingsDiv.style.display === 'none') { | |
settingsDiv.style.display = 'block'; | |
listDiv.style.display = 'none'; | |
exportDiv.style.display = 'none'; | |
} else { | |
settingsDiv.style.display = 'none'; | |
} | |
} | |
// Save settings | |
function saveSettings() { | |
config.parentLevel = parseInt(document.getElementById('parent-level').value, 10) || 5; | |
config.showClickHighlight = document.getElementById('show-highlight').checked; | |
config.autoGroupInputs = document.getElementById('auto-group').checked; | |
config.maxStoredItems = parseInt(document.getElementById('max-stored').value, 10) || 100; | |
config.defaultExportFormat = document.getElementById('default-export-format').value; | |
saveConfig(); | |
document.getElementById('dom-collector-settings').style="display: none;"; | |
} | |
// Toggle view collected items | |
function toggleViewCollected() { | |
const listDiv = document.getElementById('dom-collector-list'); | |
const settingsDiv = document.getElementById('dom-collector-settings'); | |
const exportDiv = document.getElementById('dom-collector-export'); | |
if (listDiv.style.display === 'none') { | |
listDiv.style.display = 'block'; | |
settingsDiv.style.display = 'none'; | |
exportDiv.style.display = 'none'; | |
// Populate the list | |
const collectedElements = GM_getValue('collectedElements', []); | |
listDiv.innerHTML = ''; | |
if (collectedElements.length === 0) { | |
listDiv.innerHTML = '<p>No elements collected yet.</p>'; | |
return; | |
} | |
// Group by tag and sort | |
const groupedElements = {}; | |
collectedElements.forEach(item => { | |
if (!groupedElements[item.parentTag]) { | |
groupedElements[item.parentTag] = []; | |
} | |
groupedElements[item.parentTag].push(item); | |
}); | |
// Create list items | |
Object.keys(groupedElements).sort().forEach(tag => { | |
const items = groupedElements[tag]; | |
const groupHeader = document.createElement('div'); | |
groupHeader.innerHTML = `<strong>${tag} (${items.length})</strong>`; | |
listDiv.appendChild(groupHeader); | |
items.forEach((item, index) => { | |
const listItem = document.createElement('div'); | |
listItem.className = 'dom-collector-list-item'; | |
listItem.textContent = `${index + 1}. ${item.description.substring(0, 50)}${item.description.length > 50 ? '...' : ''}`; | |
listItem.title = item.fullPath; | |
// Add click handler to show full details | |
listItem.addEventListener('click', () => { | |
alert(`Element: ${item.description}\nFull Path: ${item.fullPath}\nClasses: ${item.classes}\nID: ${item.id}`); | |
}); | |
listDiv.appendChild(listItem); | |
}); | |
}); | |
} else { | |
listDiv.style.display = 'none'; | |
} | |
} | |
// Toggle export panel | |
function toggleExportPanel() { | |
const listDiv = document.getElementById('dom-collector-list'); | |
const settingsDiv = document.getElementById('dom-collector-settings'); | |
const exportDiv = document.getElementById('dom-collector-export'); | |
if (exportDiv.style.display === 'none') { | |
exportDiv.style.display = 'block'; | |
settingsDiv.style.display = 'none'; | |
listDiv.style.display = 'none'; | |
// Set the default export format | |
document.getElementById('export-format').value = config.defaultExportFormat; | |
} else { | |
exportDiv.style.display = 'none'; | |
} | |
} | |
// Copy collected elements to clipboard | |
function copyToClipboard() { | |
const format = document.getElementById('export-format').value; | |
const collectedElements = GM_getValue('collectedElements', []); | |
if (collectedElements.length === 0) { | |
alert('No elements collected yet.'); | |
return; | |
} | |
const exportData = formatExportData(collectedElements, format); | |
GM_setClipboard(exportData); | |
alert('Data copied to clipboard!'); | |
} | |
// Download collected elements as a file | |
function downloadFile() { | |
const format = document.getElementById('export-format').value; | |
const collectedElements = GM_getValue('collectedElements', []); | |
if (collectedElements.length === 0) { | |
alert('No elements collected yet.'); | |
return; | |
} | |
const exportData = formatExportData(collectedElements, format); | |
const blob = new Blob([exportData], { type: getMimeType(format) }); | |
const url = URL.createObjectURL(blob); | |
const filename = `dom-parents-${new Date().toISOString().split('T')[0]}.${getFileExtension(format)}`; | |
// Use GM_download if available, otherwise fallback to creating a link | |
if (typeof GM_download !== 'undefined') { | |
GM_download({ | |
url: url, | |
name: filename, | |
saveAs: true | |
}); | |
} else { | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = filename; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
setTimeout(() => URL.revokeObjectURL(url), 100); | |
} | |
} | |
// Cancel export | |
function cancelExport() { | |
document.getElementById('dom-collector-export').style.display = 'none'; | |
} | |
// Format export data based on selected format | |
function formatExportData(data, format) { | |
switch (format) { | |
case 'json': | |
return JSON.stringify(data, null, 2); | |
case 'csv': | |
// Determine all possible keys from all objects | |
const allKeys = new Set(); | |
data.forEach(item => { | |
Object.keys(item).forEach(key => allKeys.add(key)); | |
}); | |
const headers = Array.from(allKeys); | |
let csv = headers.join(',') + '\n'; | |
data.forEach(item => { | |
const row = headers.map(header => { | |
const value = item[header] || ''; | |
// Escape commas and quotes | |
return `"${String(value).replace(/"/g, '""')}"`; | |
}); | |
csv += row.join(',') + '\n'; | |
}); | |
return csv; | |
case 'html': | |
// Create HTML table | |
let html = '<table border="1" cellpadding="5" cellspacing="0">\n<thead>\n<tr>\n'; | |
// Group by parent tag | |
const groupedElements = {}; | |
data.forEach(item => { | |
if (!groupedElements[item.parentTag]) { | |
groupedElements[item.parentTag] = []; | |
} | |
groupedElements[item.parentTag].push(item); | |
}); | |
// Headers | |
html += '<th>Element</th><th>Description</th><th>Parent Tag</th><th>Parent ID</th><th>Classes</th><th>Path</th>\n'; | |
html += '</tr>\n</thead>\n<tbody>\n'; | |
// Table rows | |
Object.keys(groupedElements).sort().forEach(tag => { | |
const items = groupedElements[tag]; | |
items.forEach(item => { | |
html += '<tr>\n'; | |
html += `<td>${escapeHtml(item.elementTag)}</td>\n`; | |
html += `<td>${escapeHtml(item.description)}</td>\n`; | |
html += `<td>${escapeHtml(item.parentTag)}</td>\n`; | |
html += `<td>${escapeHtml(item.parentId)}</td>\n`; | |
html += `<td>${escapeHtml(item.classes)}</td>\n`; | |
html += `<td>${escapeHtml(item.fullPath)}</td>\n`; | |
html += '</tr>\n'; | |
}); | |
}); | |
html += '</tbody>\n</table>'; | |
return html; | |
case 'plaintext': | |
// Simple text format | |
let text = 'DOM PARENT COLLECTOR EXPORT\n'; | |
text += '===========================\n\n'; | |
// Group by parent tag | |
const groupedElementsText = {}; | |
data.forEach(item => { | |
if (!groupedElementsText[item.parentTag]) { | |
groupedElementsText[item.parentTag] = []; | |
} | |
groupedElementsText[item.parentTag].push(item); | |
}); | |
Object.keys(groupedElementsText).sort().forEach(tag => { | |
text += `## ${tag.toUpperCase()} (${groupedElementsText[tag].length} items)\n\n`; | |
groupedElementsText[tag].forEach((item, index) => { | |
text += `${index + 1}. ${item.description}\n`; | |
text += ` - Element: ${item.elementTag}\n`; | |
text += ` - Parent: ${item.parentTag}\n`; | |
text += ` - ID: ${item.parentId}\n`; | |
text += ` - Classes: ${item.classes}\n`; | |
text += ` - Path: ${item.fullPath}\n\n`; | |
}); | |
}); | |
return text; | |
default: | |
return JSON.stringify(data, null, 2); | |
} | |
} | |
// Helper function to escape HTML | |
function escapeHtml(text) { | |
const div = document.createElement('div'); | |
div.textContent = String(text); | |
return div.innerHTML; | |
} | |
// Get file extension based on format | |
function getFileExtension(format) { | |
switch (format) { | |
case 'json': return 'json'; | |
case 'csv': return 'csv'; | |
case 'html': return 'html'; | |
case 'plaintext': return 'txt'; | |
default: return 'txt'; | |
} | |
} | |
// Get MIME type based on format | |
function getMimeType(format) { | |
switch (format) { | |
case 'json': return 'application/json'; | |
case 'csv': return 'text/csv'; | |
case 'html': return 'text/html'; | |
case 'plaintext': return 'text/plain'; | |
default: return 'text/plain'; | |
} | |
} | |
// Clear collected elements | |
function clearCollected() { | |
if (confirm('Are you sure you want to clear all collected elements?')) { | |
GM_setValue('collectedElements', []); | |
updateCollectionCounter(); | |
if (document.getElementById('dom-collector-list').style.display !== 'none') { | |
toggleViewCollected(); // Refresh the list view | |
} | |
} | |
} | |
// Update collection counter | |
function updateCollectionCounter() { | |
const collectedElements = GM_getValue('collectedElements', []); | |
document.querySelector('.dom-collector-counter').textContent = collectedElements.length; | |
} | |
// Find the nth parent of an element | |
function getNthParent(element, n) { | |
let parent = element; | |
for (let i = 0; i < n; i++) { | |
if (parent.parentElement) { | |
parent = parent.parentElement; | |
} else { | |
break; | |
} | |
} | |
return parent; | |
} | |
// Get a simplified path to the element | |
function getElementPath(element) { | |
let path = []; | |
let current = element; | |
while (current && current !== document.documentElement) { | |
let selector = current.tagName.toLowerCase(); | |
if (current.id) { | |
selector += `#${current.id}`; | |
} else if (current.className && typeof current.className === 'string') { | |
selector += `.${current.className.trim().split(/\s+/).join('.')}`; | |
} | |
path.unshift(selector); | |
current = current.parentElement; | |
} | |
return path.join(' > '); | |
} | |
// Find associated label for an input element | |
function findInputLabel(inputElement) { | |
// First check for label with 'for' attribute | |
if (inputElement.id) { | |
const label = document.querySelector(`label[for="${inputElement.id}"]`); | |
if (label) return label; | |
} | |
// Then check for parent label | |
let parent = inputElement.parentElement; | |
while (parent && parent !== document.body) { | |
if (parent.tagName === 'LABEL') { | |
return parent; | |
} | |
parent = parent.parentElement; | |
} | |
// Finally check for adjacent label (common pattern) | |
const previousSibling = inputElement.previousElementSibling; | |
const nextSibling = inputElement.nextElementSibling; | |
if (previousSibling && previousSibling.tagName === 'LABEL') { | |
return previousSibling; | |
} | |
if (nextSibling && nextSibling.tagName === 'LABEL') { | |
return nextSibling; | |
} | |
return null; | |
} | |
// Get a description of the element | |
function getElementDescription(element) { | |
// For text inputs, return the value or placeholder | |
if (element.tagName === 'INPUT' && element.type === 'text') { | |
return element.value || element.placeholder || 'Text input'; | |
} | |
// For checkboxes, return the checked state | |
if (element.tagName === 'INPUT' && element.type === 'checkbox') { | |
return `Checkbox: ${element.checked ? 'checked' : 'unchecked'}`; | |
} | |
// For buttons, return the text content or value | |
if (element.tagName === 'BUTTON' || (element.tagName === 'INPUT' && | |
(element.type === 'button' || element.type === 'submit'))) { | |
return element.textContent || element.value || 'Button'; | |
} | |
// For select elements, return the selected option text | |
if (element.tagName === 'SELECT' && element.options[element.selectedIndex]) { | |
return `Select: ${element.options[element.selectedIndex].text}`; | |
} | |
// For links, return the text content or href | |
if (element.tagName === 'A') { | |
return element.textContent.trim() || element.href || 'Link'; | |
} | |
// For images, return the alt text or src | |
if (element.tagName === 'IMG') { | |
return element.alt || element.src.split('/').pop() || 'Image'; | |
} | |
// For other elements, return the text content or tag name | |
return element.textContent.trim().substring(0, 100) || | |
`${element.tagName.toLowerCase()}${element.id ? '#' + element.id : ''}`; | |
} | |
// Process clicked element | |
function processClickedElement(event) { | |
if (!config.enableCollection) return; | |
const element = event.target; | |
// Highlight the clicked element if enabled | |
if (config.showClickHighlight) { | |
element.classList.add('dom-collector-highlight'); | |
setTimeout(() => { | |
element.classList.remove('dom-collector-highlight'); | |
}, 1000); | |
} | |
// Get the nth parent | |
const parent = getNthParent(element, config.parentLevel); | |
// Base element data | |
let elementData = { | |
timestamp: Date.now(), | |
elementTag: element.tagName.toLowerCase(), | |
elementType: element.type || 'none', | |
parentTag: parent.tagName.toLowerCase(), | |
parentId: parent.id || 'none', | |
parentClasses: parent.className || 'none', | |
description: getElementDescription(element), | |
fullPath: getElementPath(parent), | |
id: parent.id || 'none', | |
classes: Array.from(parent.classList).join(' ') || 'none' | |
}; | |
// Special handling for inputs | |
if (config.autoGroupInputs && | |
(element.tagName === 'INPUT' || element.tagName === 'SELECT' || | |
element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON')) { | |
const label = findInputLabel(element); | |
if (label) { | |
elementData.label = label.textContent.trim(); | |
elementData.description = `${elementData.label}: ${elementData.description}`; | |
// Also get the nth parent of the label | |
const labelParent = getNthParent(label, config.parentLevel); | |
elementData.labelParentTag = labelParent.tagName.toLowerCase(); | |
elementData.labelParentPath = getElementPath(labelParent); | |
} | |
} | |
// Store the collected element | |
saveCollectedElement(elementData); | |
// Don't block the normal behavior of the page | |
return true; | |
} | |
// Save collected element to storage | |
function saveCollectedElement(elementData) { | |
let collectedElements = GM_getValue('collectedElements', []); | |
// Add the new element | |
collectedElements.push(elementData); | |
// Sort by parent tag | |
collectedElements.sort((a, b) => a.parentTag.localeCompare(b.parentTag)); | |
// Limit to max stored items | |
if (collectedElements.length > config.maxStoredItems) { | |
collectedElements = collectedElements.slice(-config.maxStoredItems); | |
} | |
// Save and update counter | |
GM_setValue('collectedElements', collectedElements); | |
updateCollectionCounter(); | |
} | |
// Add Tampermonkey menu command | |
GM_registerMenuCommand('DOM Parent Collector Settings', () => { | |
document.getElementById('toggle-settings').click(); | |
}); | |
// Function to handle input blur events | |
function handleInputBlur(event) { | |
if (!config.enableCollection) return; | |
const element = event.target; | |
// Get the nth parent | |
const parent = getNthParent(element, config.parentLevel); | |
// Base element data | |
let elementData = { | |
timestamp: Date.now(), | |
elementTag: element.tagName.toLowerCase(), | |
elementType: element.type || 'none', | |
parentTag: parent.tagName.toLowerCase(), | |
parentId: parent.id || 'none', | |
parentClasses: parent.className || 'none', | |
description: `Value: ${element.value}`, | |
fullPath: getElementPath(parent), | |
id: parent.id || 'none', | |
classes: Array.from(parent.classList).join(' ') || 'none' | |
}; | |
// Special handling for inputs | |
if (config.autoGroupInputs) { | |
const label = findInputLabel(element); | |
if (label) { | |
elementData.label = label.textContent.trim(); | |
elementData.description = `${elementData.label}: ${elementData.description}`; | |
// Also get the nth parent of the label | |
const labelParent = getNthParent(label, config.parentLevel); | |
elementData.labelParentTag = labelParent.tagName.toLowerCase(); | |
elementData.labelParentPath = getElementPath(labelParent); | |
} | |
} | |
// Store the collected element | |
saveCollectedElement(elementData); | |
} | |
// Function to handle select change events | |
function handleSelectChange(event) { | |
if (!config.enableCollection) return; | |
const element = event.target; | |
// Get all options and their values | |
const options = Array.from(element.options).map(option => ({ | |
text: option.text, | |
value: option.value, | |
selected: option.selected | |
})); | |
// Create a Set of all values | |
const valuesSet = new Set(options.map(option => option.value)); | |
// Get the nth parent | |
const parent = getNthParent(element, config.parentLevel); | |
// Base element data | |
let elementData = { | |
timestamp: Date.now(), | |
elementTag: element.tagName.toLowerCase(), | |
elementType: element.type || 'none', | |
parentTag: parent.tagName.toLowerCase(), | |
parentId: parent.id || 'none', | |
parentClasses: parent.className || 'none', | |
description: `Selected: ${element.options[element.selectedIndex].text}`, | |
fullPath: getElementPath(parent), | |
id: parent.id || 'none', | |
classes: Array.from(parent.classList).join(' ') || 'none', | |
options: options, | |
values: Array.from(valuesSet), | |
selectedValue: element.value | |
}; | |
// Special handling for inputs | |
if (config.autoGroupInputs) { | |
const label = findInputLabel(element); | |
if (label) { | |
elementData.label = label.textContent.trim(); | |
elementData.description = `${elementData.label}: ${elementData.description}`; | |
// Also get the nth parent of the label | |
const labelParent = getNthParent(label, config.parentLevel); | |
elementData.labelParentTag = labelParent.tagName.toLowerCase(); | |
elementData.labelParentPath = getElementPath(labelParent); | |
} | |
} | |
// Store the collected element | |
saveCollectedElement(elementData); | |
} | |
// Start the script | |
window.addEventListener('load', () => { | |
createUI(); | |
document.addEventListener('click', processClickedElement, true); | |
// Add event listeners for input blur and select change | |
document.querySelectorAll('input, textarea').forEach(input => { | |
input.addEventListener('blur', handleInputBlur); | |
}); | |
document.querySelectorAll('select').forEach(select => { | |
select.addEventListener('change', handleSelectChange); | |
}); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment