// ==UserScript== // @name Universal URL Highlighter with Conditional In-Page Highlighting // @namespace http://tampermonkey.net/ // @version 1.9 // @description Highlights URLs on any webpage, lists them in a toggleable panel with citation counts, conditionally highlights frequent links in-page, and provides export functionality. Excludes links in header and footer, as well as parent URLs of the current page. Hover previews for links. // @author MorganGeek // @match *://*/* // @exclude *zoemp.be* // @exclude *duckduckgo* // @exclude *search* // @exclude *forum* // @exclude *chatgpt* // @exclude *google.com* // @exclude *tampermonkey* // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @connect * // @run-at document-end // ==/UserScript== (function () { 'use strict'; // Configuration of colors based on frequency const COLORS = { low: '#d1e7dd', // Light Green medium: '#fff3cd', // Light Yellow high: '#f8d7da' // Light Red }; // Create a toggle button to show/hide the panel const toggleButton = document.createElement('button'); toggleButton.textContent = 'Show URLs'; toggleButton.style.position = 'fixed'; toggleButton.style.bottom = '10px'; toggleButton.style.right = '10px'; toggleButton.style.zIndex = 1000; toggleButton.style.padding = '10px 15px'; toggleButton.style.backgroundColor = '#007bff'; toggleButton.style.color = '#fff'; toggleButton.style.border = 'none'; toggleButton.style.borderRadius = '5px'; toggleButton.style.cursor = 'pointer'; document.body.appendChild(toggleButton); // Create a floating panel to display the URLs (hidden by default) const panel = document.createElement('div'); panel.id = 'url-panel'; panel.style.position = 'fixed'; panel.style.top = '10px'; panel.style.right = '10px'; panel.style.width = '350px'; panel.style.backgroundColor = '#fff'; panel.style.border = '1px solid #ccc'; panel.style.borderRadius = '5px'; panel.style.padding = '10px'; panel.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.2)'; panel.style.maxHeight = '80vh'; panel.style.overflowY = 'auto'; panel.style.zIndex = 1000; panel.style.display = 'none'; // Hidden by default panel.innerHTML = `

URLs

`; document.body.appendChild(panel); // Create a tooltip for previews const tooltip = document.createElement('div'); tooltip.id = 'url-tooltip'; tooltip.style.position = 'absolute'; tooltip.style.backgroundColor = '#fff'; tooltip.style.border = '1px solid #ccc'; tooltip.style.borderRadius = '5px'; tooltip.style.padding = '10px'; tooltip.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.2)'; tooltip.style.maxWidth = '300px'; tooltip.style.display = 'none'; tooltip.style.zIndex = 1001; document.body.appendChild(tooltip); // Data structure to store URLs with their metadata const urlData = {}; // Store original background colors to restore later const originalColors = new Map(); // Function to escape CSS selectors function escapeSelector(selector) { return CSS.escape(selector); } // Helper function to check if an element is inside header or footer function isInHeaderOrFooter(element) { let parent = element.parentElement; while (parent) { if (parent.tagName.toLowerCase() === 'header' || parent.tagName.toLowerCase() === 'footer') { return true; } parent = parent.parentElement; } return false; } // Function to generate all parent URLs of the current page function getParentUrls(currentUrl) { const parents = []; const urlObj = new URL(currentUrl); const pathname = urlObj.pathname; const pathSegments = pathname.split('/').filter(segment => segment.length > 0); while (pathSegments.length > 0) { pathSegments.pop(); const parentPath = '/' + pathSegments.join('/') + '/'; parents.push(`${urlObj.origin}${parentPath}`); } parents.push(`${urlObj.origin}/`); // Add the base origin return parents; } // Get current page's URL and its parent URLs const currentPageUrl = window.location.href; const parentUrls = new Set(getParentUrls(currentPageUrl)); // Extract URLs from the page and collect data without coloring const links = document.querySelectorAll('a[href]'); links.forEach(link => { if (isInHeaderOrFooter(link)) { // Ignore links inside header or footer return; } const url = link.href.trim(); // Exclude URLs that are parent URLs of the current page if (parentUrls.has(url)) { return; } if (!urlData[url]) { urlData[url] = { url: url, count: 1 }; // Add the URL to the list in the panel const listItem = document.createElement('li'); listItem.innerHTML = `${url} 1`; listItem.style.marginBottom = '5px'; listItem.style.wordWrap = 'break-word'; listItem.style.cursor = 'pointer'; listItem.dataset.url = url; panel.querySelector('#url-list').appendChild(listItem); } else { urlData[url].count += 1; // Update the counter in the panel const existingItem = panel.querySelector(`li[data-url="${escapeSelector(url)}"] span:last-child`); if (existingItem) { existingItem.textContent = urlData[url].count; } } // Add hover preview functionality to the links link.addEventListener('mouseover', () => { GM_xmlhttpRequest({ method: 'GET', url: url, onload: response => { tooltip.innerHTML = ''; const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, 'text/html'); const title = doc.querySelector('title') ? doc.querySelector('title').innerText : 'No Title'; const description = doc.querySelector('meta[name="description"]') ? doc.querySelector('meta[name="description"]').content : 'No Description'; tooltip.innerHTML = `${title}

${description}

`; tooltip.style.display = 'block'; }, onerror: () => { tooltip.innerHTML = 'Preview unavailable'; tooltip.style.display = 'block'; } }); const rect = link.getBoundingClientRect(); tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`; tooltip.style.left = `${rect.left + window.scrollX}px`; }); link.addEventListener('mouseout', () => { tooltip.style.display = 'none'; }); }); // Function to update the panel display based on filters function updatePanel() { const sortBy = panel.querySelector('#sort-select').value; const limit = panel.querySelector('#limit-select').value; // Convert the object to an array for sorting let urlsArray = Object.values(urlData); // Sort based on the selected criteria if (sortBy === 'frequency') { urlsArray.sort((a, b) => b.count - a.count); } else if (sortBy === 'name') { urlsArray.sort((a, b) => a.url.localeCompare(b.url)); } // Apply the limit if necessary if (limit !== 'all') { const limitNumber = parseInt(limit); urlsArray = urlsArray.slice(0, limitNumber); } const urlList = panel.querySelector('#url-list'); urlList.innerHTML = ''; // Clear the current list // Determine frequencies for color coding const counts = urlsArray.map(item => item.count); const maxCount = Math.max(...counts); const minCount = Math.min(...counts); urlsArray.forEach(item => { const listItem = document.createElement('li'); listItem.style.marginBottom = '5px'; listItem.style.wordWrap = 'break-word'; listItem.style.cursor = 'pointer'; listItem.dataset.url = item.url; // Set color based on frequency let color; const ratio = (item.count - minCount) / (maxCount - minCount + 1); if (ratio > 0.66) { color = COLORS.high; } else if (ratio > 0.33) { color = COLORS.medium; } else { color = COLORS.low; } listItem.style.backgroundColor = color; // Create content with URL and citation count listItem.innerHTML = ` ${item.url} ${item.count} `; // Add click event to copy the URL listItem.addEventListener('click', () => { GM_setClipboard(item.url, 'text'); alert('URL copied to clipboard!'); }); urlList.appendChild(listItem); }); } // Function to highlight links in the page based on frequency function highlightLinksInPage(urlsArray) { // Apply color coding based on frequency urlsArray.forEach(item => { const linksToHighlight = document.querySelectorAll(`a[href="${escapeSelector(item.url)}"]:not(header a, footer a)`); linksToHighlight.forEach(link => { // Store original background color if not already stored if (!originalColors.has(link)) { originalColors.set(link, link.style.backgroundColor); } let color; const ratio = (item.count - Math.min(...urlsArray.map(u => u.count))) / (Math.max(...urlsArray.map(u => u.count)) - Math.min(...urlsArray.map(u => u.count)) + 1); if (ratio > 0.66) { color = COLORS.high; } else if (ratio > 0.33) { color = COLORS.medium; } else { color = COLORS.low; } link.style.backgroundColor = color; }); }); } // Function to remove highlights from links in the page function removeHighlights() { originalColors.forEach((color, link) => { link.style.backgroundColor = color || ''; }); originalColors.clear(); } // Initialize the panel updatePanel(); // Listen for changes in the sort and limit selectors panel.querySelector('#sort-select').addEventListener('change', updatePanel); panel.querySelector('#limit-select').addEventListener('change', updatePanel); // Handle the toggle button to show/hide the panel and manage in-page highlighting toggleButton.addEventListener('click', () => { if (panel.style.display === 'none') { panel.style.display = 'block'; toggleButton.textContent = 'Hide URLs'; highlightLinksInPage(Object.values(urlData)); } else { panel.style.display = 'none'; toggleButton.textContent = 'Show URLs'; removeHighlights(); } }); // Handle the "Copy URLs" button panel.querySelector('#copy-urls').addEventListener('click', () => { const urlArray = Object.keys(urlData); GM_setClipboard(urlArray.join('\n'), 'text'); alert('URLs copied to clipboard!'); }); })();