feat(content-curation): exclude parent URLs and toggle link highlighting
This commit is contained in:
parent
3f142c0258
commit
32f613278e
333
tampermonkey/universal-url-highlighter.js
Normal file
333
tampermonkey/universal-url-highlighter.js
Normal file
@ -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 = `
|
||||
<h4>URLs</h4>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label for="sort-select">Sort by:</label>
|
||||
<select id="sort-select" style="width: 100%; padding: 5px; margin-top: 5px;">
|
||||
<option value="frequency">Frequency</option>
|
||||
<option value="name">Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<ul id="url-list" style="margin: 0; padding: 0; list-style: none;"></ul>
|
||||
<div style="margin-top: 10px;">
|
||||
<label for="limit-select">Show:</label>
|
||||
<select id="limit-select" style="width: 100%; padding: 5px; margin-top: 5px;">
|
||||
<option value="all">All</option>
|
||||
<option value="10">Top 10</option>
|
||||
<option value="20">Top 20</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="copy-urls" style="margin-top: 10px; display: block; width: 100%; padding: 10px; background-color: #007bff; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Copy URLs</button>
|
||||
`;
|
||||
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 = `<span>${url}</span> <span style="float: right; font-weight: bold;">1</span>`;
|
||||
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 = `<strong>${title}</strong><p>${description}</p>`;
|
||||
tooltip.style.display = 'block';
|
||||
},
|
||||
onerror: () => {
|
||||
tooltip.innerHTML = '<strong>Preview unavailable</strong>';
|
||||
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 = `
|
||||
<span>${item.url}</span>
|
||||
<span style="float: right; font-weight: bold;">${item.count}</span>
|
||||
`;
|
||||
|
||||
// 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!');
|
||||
});
|
||||
|
||||
})();
|
Loading…
Reference in New Issue
Block a user