diff --git a/tampermonkey/universal-url-highlighter.js b/tampermonkey/universal-url-highlighter.js new file mode 100644 index 0000000..5c57614 --- /dev/null +++ b/tampermonkey/universal-url-highlighter.js @@ -0,0 +1,333 @@ +// ==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* +// @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 = ` +
${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!'); + }); + +})();