Highlights quoted text with customizable colors, styles, and features
// ==UserScript==
// @name π¬ Quote Highlighter
// @version 1.3
// @description Highlights quoted text with customizable colors, styles, and features
// @author Misspent
// @namespace ChatGPT / Grok AI
// @icon https://i.imgur.com/bhdcGzd.png
// @match *://*/*
// @exclude /^https?:\/\/(www\.)?(x\.com|youtube\.com|github\.com|twitter\.com|steam\.com|search.brave\.com|twitch\.tv)(\/.*)?$/
// @grant GM_addStyle
// @run-at document-idle
// @license MIT
// ==/UserScript==
// https://www.iconsdb.com/icons/preview/orange/quote-xxl.png
(function () {
'use strict';
// ================== CONFIGURATION ==================
const CONFIG = {
// Highlight appearance
textColor: '#EA9D9C', // #b92b27 | #EA9D9C | #b92b27
backgroundColor: 'rgba(234, 157, 156, 0.12)',
borderRadius: '0px',
padding: '0 2px',
// fontWeight: '500',
// Enable subtle underline instead of bg
underline: false,
// Ignore short quoted text
minimumLength: 2,
// Prevent absurdly large matches
maximumLength: 500,
// Startup delay
startupDelay: 1000,
// Ignore these tags entirely
ignoredTags: new Set([
'SCRIPT',
'STYLE',
'TEXTAREA',
'INPUT',
'CODE',
'PRE',
'NOSCRIPT',
'OPTION',
// Mine
'a',
'video',
'[href]',
'.flair',
'.subtitle',
'#subtitle',
'#ace-editor',
'.no-highlight',
'.flair-content',
'#TitleClipboard',
'clipboard-notification'
]),
// Ignore editable areas
ignoreContentEditable: true,
// MutationObserver debounce
observerDebounce: 150,
// Debug mode
debug: false,
// Your regex
quoteRegex:
/(?<!\w)(["ββββ'β`"])(.*?)(["ββββ'β`.])(?!\w)/g // /(?<!\w)(["ββββ'β`"])(.*?)(["ββββ'β`.])(?!\w)/g
};
// ================== STYLES ==================
GM_addStyle(`
.quote-highlight {
color: ${CONFIG.textColor} !important;
background: ${CONFIG.underline ? 'transparent' : CONFIG.backgroundColor};
border-radius: ${CONFIG.borderRadius};
padding: ${CONFIG.padding};
font-weight: ${CONFIG.fontWeight};
transition: all 0.15s ease;
cursor: text;
}
.quote-highlight:hover {
filter: brightness(1.08);
}
${
CONFIG.underline
? `
.quote-highlight {
text-decoration: underline;
text-decoration-color: ${CONFIG.textColor};
text-decoration-thickness: 2px;
}
`
: ''
}
`);
// ================== HELPERS ==================
function log(...args) {
if (CONFIG.debug) {
console.log('[QuoteHighlighter]', ...args);
}
}
function shouldSkipNode(node) {
if (!node || !node.parentNode) return true;
const parent = node.parentNode;
if (parent.classList?.contains('quote-highlight')) {
return true;
}
if (CONFIG.ignoredTags.has(parent.nodeName)) {
return true;
}
if (
CONFIG.ignoreContentEditable &&
parent.closest('[contenteditable="true"]')
) {
return true;
}
return false;
}
// ================== MAIN HIGHLIGHT FUNCTION ==================
function processTextNode(textNode) {
if (shouldSkipNode(textNode)) return;
const text = textNode.nodeValue;
if (!text || !text.trim()) return;
CONFIG.quoteRegex.lastIndex = 0;
const matches = [...text.matchAll(CONFIG.quoteRegex)];
if (!matches.length) return;
const fragment = document.createDocumentFragment();
let lastIndex = 0;
for (const match of matches) {
const fullMatch = match[0];
if (
fullMatch.length < CONFIG.minimumLength ||
fullMatch.length > CONFIG.maximumLength
) {
continue;
}
const start = match.index;
// Normal text before match
if (start > lastIndex) {
fragment.appendChild(
document.createTextNode(
text.slice(lastIndex, start)
)
);
}
// Highlighted quote
const span = document.createElement('span');
span.className = 'quote-highlight';
span.textContent = fullMatch;
// Tooltip
span.title = 'Quoted from Script';
fragment.appendChild(span);
lastIndex = start + fullMatch.length;
}
// Remaining text
if (lastIndex < text.length) {
fragment.appendChild(
document.createTextNode(text.slice(lastIndex))
);
}
// Replace only if something changed
if (fragment.childNodes.length) {
textNode.parentNode.replaceChild(fragment, textNode);
}
}
// ================== TREE WALKER ==================
function scan(root = document.body) {
const walker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
null
);
const textNodes = [];
while (walker.nextNode()) {
textNodes.push(walker.currentNode);
}
for (const node of textNodes) {
processTextNode(node);
}
log(`Processed ${textNodes.length} text nodes`);
}
// ================== LIVE PAGE SUPPORT ==================
let debounceTimer;
const observer = new MutationObserver((mutations) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.TEXT_NODE) {
processTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE) {
scan(node);
}
}
}
}, CONFIG.observerDebounce);
});
// ================== START ==================
function init() {
log('Initializing...');
setTimeout(() => {
scan(document.body);
observer.observe(document.body, {
childList: true,
subtree: true
});
log('Observer started');
}, CONFIG.startupDelay);
}
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// ================== OPTIONAL GLOBAL API (Useful for debugging in DevTools) ==================
window.QuoteHighlighter = {
rescan: () => scan(document.body),
disconnect: () => observer.disconnect(),
config: CONFIG
};
})();