Greasy Fork is available in English.

Webpage text Highlighter

Highlight text, erase, export, and format. Tap eraser to remove single, Long-press eraser to remove ALL. No alert messages.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         Webpage text Highlighter
// @namespace    http://tampermonkey.net/
// @version      24.05.2026.19
// @description  Highlight text, erase, export, and format. Tap eraser to remove single, Long-press eraser to remove ALL. No alert messages.
// @author       Sspuramgemi
// @match        *://*/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let container = null;
    let formatMenu = null;
    let currentDocumentCtx = null;

    const highlightColors = [
        { name: 'Yellow', color: '#ffeb3b', gradient: 'linear-gradient(yellow, yellow)', text: 'black' },
        { name: 'Green',  color: '#00ff00', gradient: 'linear-gradient(#00ff00, #00ff00)', text: 'black' },
        { name: 'Red',    color: '#ff3333', gradient: 'linear-gradient(#ff3333, #ff3333)', text: 'white' },
        { name: 'Blue',   color: '#00ccff', gradient: 'linear-gradient(#00ccff, #00ccff)', text: 'black' }
    ];

    function handleSelection() {
        if (!container) return;
        const selection = window.getSelection();
        if (selection && selection.toString().trim().length > 0) {
            container.style.display = 'flex';
        } else {
            container.style.display = 'none';
            if (formatMenu) formatMenu.style.display = 'none';
        }
    }

    function applyHighlight(colorConfig) {
        const selection = window.getSelection();
        if (!selection || !selection.rangeCount) return;
        const range = selection.getRangeAt(0);
        
        if (range.toString().trim().length === 0) return;
        
        const highlightNode = document.createElement('span');
        highlightNode.setAttribute('data-tm-highlight', colorConfig.color);
        highlightNode.setAttribute('data-tm-textcolor-val', colorConfig.text);
        highlightNode.style.background = colorConfig.gradient;
        highlightNode.style.backgroundColor = colorConfig.color;
        highlightNode.style.color = colorConfig.text;
        
        try {
            range.surroundContents(highlightNode);
            selection.removeAllRanges();
            if (container) container.style.display = 'none';
        } catch (e) {
            console.warn("Can't highlight across multiple complex elements");
        }
    }

    function applyTextColor(colorConfig) {
        const selection = window.getSelection();
        if (!selection || !selection.rangeCount) return;
        const range = selection.getRangeAt(0);
        
        if (range.toString().trim().length === 0) return;
        
        const node = document.createElement('span');
        node.setAttribute('data-tm-textcolor', colorConfig.color);
        node.style.color = colorConfig.color;
        try {
            range.surroundContents(node);
            selection.removeAllRanges();
            if (container) container.style.display = 'none';
        } catch (e) {
            console.warn("Can't apply text color across multiple complex elements");
        }
    }

    function applyCustomFormat(tagName) {
        const selection = window.getSelection();
        if (!selection || !selection.rangeCount) return;
        const range = selection.getRangeAt(0);
        
        if (range.toString().trim().length === 0) return;
        
        const node = document.createElement(tagName);
        node.setAttribute('data-tm-format', tagName);
        try {
            range.surroundContents(node);
            selection.removeAllRanges();
            if (container) container.style.display = 'none';
        } catch (e) {
            console.warn("Can't format across multiple complex elements");
        }
    }

    function removeHighlight() {
        const selection = window.getSelection();
        if (!selection || !selection.rangeCount) return;
        
        let node = selection.getRangeAt(0).startContainer;
        let formatNode = null;
        
        while (node && node !== document.body && node !== document.documentElement) {
            if (node.nodeType === 1 && (
                node.hasAttribute('data-tm-highlight') ||
                node.hasAttribute('data-tm-textcolor') ||
                node.hasAttribute('data-tm-format') ||
                ['b','u','i','em','span'].includes(node.tagName.toLowerCase())
            )) {
                formatNode = node;
                break;
            }
            node = node.parentNode;
        }
        
        if (formatNode) {
            const parent = formatNode.parentNode;
            while (formatNode.firstChild) {
                parent.insertBefore(formatNode.firstChild, formatNode);
            }
            formatNode.remove();
            selection.removeAllRanges();
            parent.normalize();
            if (container) container.style.display = 'none';
        }
    }

    function removeAllHighlights() {
        const elements = document.querySelectorAll(
            'span[data-tm-highlight], span[data-tm-textcolor], [data-tm-format], b, i, u, em, strong'
        );
        
        if (elements.length === 0) return;
        
        elements.forEach(el => {
            if (el.hasAttribute('data-tm-highlight') || 
                el.hasAttribute('data-tm-textcolor') || 
                el.hasAttribute('data-tm-format')) {
                
                const parent = el.parentNode;
                while (el.firstChild) {
                    parent.insertBefore(el.firstChild, el);
                }
                el.remove();
            }
            else if (['b', 'i', 'u', 'em', 'strong'].includes(el.tagName.toLowerCase())) {
                if (!el.hasAttribute('class') && !el.hasAttribute('style')) {
                    const parent = el.parentNode;
                    while (el.firstChild) {
                        parent.insertBefore(el.firstChild, el);
                    }
                    el.remove();
                }
            }
        });
        
        document.body.normalize();
        if (container) container.style.display = 'none';
    }

    function getFormattedMarkdown(node) {
        let text = "";
        node.childNodes.forEach(child => {
            if (child.nodeType === Node.TEXT_NODE) {
                text += child.textContent;
            } else if (child.nodeType === Node.ELEMENT_NODE) {
                let inner = getFormattedMarkdown(child);
                let tag = child.tagName.toLowerCase();
                
                if (tag === 'b' || child.getAttribute('data-tm-format') === 'b') inner = `**${inner}**`;
                if (tag === 'i' || tag === 'em' || child.getAttribute('data-tm-format') === 'i') inner = `*${inner}*`;
                if (tag === 'u' || child.getAttribute('data-tm-format') === 'u') inner = `<u>${inner}</u>`;
                if (child.hasAttribute('data-tm-textcolor')) {
                    inner = `<span style="color:${child.getAttribute('data-tm-textcolor')}">${inner}</span>`;
                }
                if (child.hasAttribute('data-tm-highlight')) {
                    const bgColor = child.getAttribute('data-tm-highlight');
                    const textColor = child.getAttribute('data-tm-textcolor-val') || 'black';
                    inner = `<mark style="background:${bgColor};color:${textColor}">${inner}</mark>`;
                }
                text += inner;
            }
        });
        return text;
    }

    function getAllFormattedElements() {
        const elements = document.querySelectorAll(
            'span[data-tm-highlight], span[data-tm-textcolor], [data-tm-format]'
        );
        
        return Array.from(elements).filter(el => {
            let parent = el.parentNode;
            while (parent && parent !== document.body && parent !== document.documentElement) {
                if (
                    parent.hasAttribute('data-tm-highlight') || 
                    parent.hasAttribute('data-tm-textcolor') || 
                    parent.hasAttribute('data-tm-format')
                ) {
                    return false; 
                }
                parent = parent.parentNode;
            }
            return true;
        });
    }

    function exportHighlightsToText() {
        const elements = getAllFormattedElements();
        if (elements.length === 0) return;
        
        const text = elements.map(el => getFormattedMarkdown(el)).join('\n\n').trim();
        const blob = new Blob([text], { type: 'text/plain' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `highlights_export_${Date.now()}.txt`;
        link.click();
        URL.revokeObjectURL(link.href);
    }

    function exportHighlightsToClipboard() {
        const elements = getAllFormattedElements();
        if (elements.length === 0) return;
        
        const text = elements.map(el => getFormattedMarkdown(el)).join('\n\n').trim();
        navigator.clipboard.writeText(text).catch(() => {});
    }

    function parseFormattedElements() {
        const elements = getAllFormattedElements();
        if (elements.length === 0) return null;

        return elements.map(el => {
            let items = [];
            function parse(node, currentStyles) {
                node.childNodes.forEach(child => {
                    let styles = { ...currentStyles };
                    if (child.nodeType === Node.TEXT_NODE) {
                        if (child.textContent.trim().length > 0) {
                            items.push({ text: child.textContent, styles });
                        }
                    } else if (child.nodeType === Node.ELEMENT_NODE) {
                        let tag = child.tagName.toLowerCase();
                        if (tag === 'b' || child.getAttribute('data-tm-format') === 'b') styles.bold = true;
                        if (tag === 'i' || child.getAttribute('data-tm-format') === 'i') styles.italic = true;
                        if (tag === 'u' || child.getAttribute('data-tm-format') === 'u') styles.underline = true;
                        if (child.hasAttribute('data-tm-highlight')) {
                            styles.highlight = child.getAttribute('data-tm-highlight');
                            styles.textColor = child.getAttribute('data-tm-textcolor-val') || 'black';
                        }
                        if (child.hasAttribute('data-tm-textcolor')) {
                            styles.textColor = child.getAttribute('data-tm-textcolor');
                        }
                        parse(child, styles);
                    }
                });
            }
            
            let rootStyles = { bold: false, italic: false, underline: false, highlight: null, textColor: '#000000' };
            let rootTag = el.tagName.toLowerCase();
            if (rootTag === 'b' || el.getAttribute('data-tm-format') === 'b') rootStyles.bold = true;
            if (rootTag === 'i' || el.getAttribute('data-tm-format') === 'i') rootStyles.italic = true;
            if (rootTag === 'u' || el.getAttribute('data-tm-format') === 'u') rootStyles.underline = true;
            if (el.hasAttribute('data-tm-highlight')) {
                rootStyles.highlight = el.getAttribute('data-tm-highlight');
                rootStyles.textColor = el.getAttribute('data-tm-textcolor-val') || 'black';
            }
            if (el.hasAttribute('data-tm-textcolor')) {
                rootStyles.textColor = el.getAttribute('data-tm-textcolor');
            }
            
            parse(el, rootStyles);
            return items;
        });
    }

    function exportToImage(customWidth) {
        const elements = getAllFormattedElements();
        if (elements.length === 0) return;

        const paragraphs = elements.map(el => {
            let items = [];
            function parse(node, currentStyles) {
                node.childNodes.forEach(child => {
                    let styles = { ...currentStyles };
                    if (child.nodeType === Node.TEXT_NODE) {
                        if (child.textContent.trim().length > 0) {
                            items.push({ text: child.textContent, styles });
                        }
                    } else if (child.nodeType === Node.ELEMENT_NODE) {
                        let tag = child.tagName.toLowerCase();
                        if (tag === 'b' || child.getAttribute('data-tm-format') === 'b') styles.bold = true;
                        if (tag === 'i' || child.getAttribute('data-tm-format') === 'i') styles.italic = true;
                        if (tag === 'u' || child.getAttribute('data-tm-format') === 'u') styles.underline = true;
                        if (child.hasAttribute('data-tm-highlight')) {
                            styles.highlight = child.getAttribute('data-tm-highlight');
                            styles.textColor = child.getAttribute('data-tm-textcolor-val') || 'black';
                        }
                        if (child.hasAttribute('data-tm-textcolor')) {
                            styles.textColor = child.getAttribute('data-tm-textcolor');
                        }
                        parse(child, styles);
                    }
                });
            }
            
            let rootStyles = { bold: false, italic: false, underline: false, highlight: null, textColor: '#000000' };
            let rootTag = el.tagName.toLowerCase();
            if (rootTag === 'b' || el.getAttribute('data-tm-format') === 'b') rootStyles.bold = true;
            if (rootTag === 'i' || el.getAttribute('data-tm-format') === 'i') rootStyles.italic = true;
            if (rootTag === 'u' || el.getAttribute('data-tm-format') === 'u') rootStyles.underline = true;
            if (el.hasAttribute('data-tm-highlight')) {
                rootStyles.highlight = el.getAttribute('data-tm-highlight');
                rootStyles.textColor = el.getAttribute('data-tm-textcolor-val') || 'black';
            }
            if (el.hasAttribute('data-tm-textcolor')) {
                rootStyles.textColor = el.getAttribute('data-tm-textcolor');
            }
            
            parse(el, rootStyles);
            return items;
        });

        const baseWidth = customWidth;
        const padding = 40;
        const maxTextWidth = baseWidth - (padding * 2);
        const fontSize = 16;
        const lineHeight = 23;
        const scaleFactor = 3;

        const calcCanvas = document.createElement('canvas');
        const calcCtx = calcCanvas.getContext('2d');
        calcCtx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;

        let layoutLines = [];
        paragraphs.forEach(para => {
            let currentLine = [];
            let spaceRemaining = maxTextWidth;

            para.forEach(chunk => {
                let fontMod = "";
                if (chunk.styles.italic) fontMod += "italic ";
                if (chunk.styles.bold) fontMod += "bold ";
                calcCtx.font = `${fontMod.trim() || 'normal'} ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;

                let words = chunk.text.split(/(\s+)/);
                
                words.forEach(word => {
                    if (word.length === 0) return;
                    let wordWidth = calcCtx.measureText(word).width;
                    if (wordWidth <= spaceRemaining) {
                        currentLine.push({ text: word, width: wordWidth, styles: chunk.styles });
                        spaceRemaining -= wordWidth;
                    } else {
                        if (currentLine.length > 0) {
                            layoutLines.push(currentLine);
                        }
                        currentLine = [{ text: word, width: wordWidth, styles: chunk.styles }];
                        spaceRemaining = maxTextWidth - wordWidth;
                    }
                });
            });
            if (currentLine.length > 0) layoutLines.push(currentLine);
            layoutLines.push([]);
        });

        if (layoutLines.length > 0 && layoutLines[layoutLines.length - 1].length === 0) layoutLines.pop();

        const baseHeight = (layoutLines.length * lineHeight) + (padding * 2);

        const exportCanvas = document.createElement('canvas');
        exportCanvas.width = baseWidth * scaleFactor;
        exportCanvas.height = baseHeight * scaleFactor;
        
        const ctx = exportCanvas.getContext('2d');
        ctx.scale(scaleFactor, scaleFactor);

        ctx.fillStyle = "#ffffff";
        ctx.fillRect(0, 0, baseWidth, baseHeight);
        ctx.textBaseline = "alphabetic";

        let currentY = padding + fontSize;
        layoutLines.forEach(line => {
            if (line.length === 0) {
                currentY += lineHeight * 0.4;
                return;
            }

            let currentX = padding;
            
            line.forEach(item => {
                if (item.styles.highlight) {
                    ctx.fillStyle = item.styles.highlight;
                    ctx.fillRect(currentX, currentY - (fontSize * 1.05), item.width + 0.5, lineHeight + 5);
                }
                currentX += item.width;
            });

            currentX = padding;
            line.forEach(item => {
                let fontMod = "";
                if (item.styles.italic) fontMod += "italic ";
                if (item.styles.bold) fontMod += "bold ";
                ctx.font = `${fontMod.trim() || 'normal'} ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;
                ctx.fillStyle = item.styles.textColor || '#000000';

                ctx.fillText(item.text, currentX, currentY);

                if (item.styles.underline) {
                    ctx.strokeStyle = item.styles.textColor || '#000000';
                    ctx.lineWidth = 1.5;
                    ctx.beginPath();
                    ctx.moveTo(currentX, currentY + 3);
                    ctx.lineTo(currentX + item.width, currentY + 3);
                    ctx.stroke();
                }
                currentX += item.width;
            });

            currentY += lineHeight;
        });

        try {
            const link = document.createElement('a');
            const widthLabel = customWidth === 390 ? 'mobile' : 'desktop';
            link.download = `highlights_${widthLabel}_${Date.now()}.png`;
            link.href = exportCanvas.toDataURL('image/png', 1.0);
            link.click();
        } catch (e) {
            console.error("Canvas export error:", e);
        }
    }

    function exportMobileView() {
        exportToImage(390);
    }

    function exportDesktopView() {
        exportToImage(750);
    }

    function styleButton(btn, color = '#eeeeee', gradient = null, dashed = false) {
        btn.style.cssText = `
            padding: 6px !important;
            border-radius: 50% !important;
            background: ${gradient || color} !important;
            background-color: ${color} !important;
            border: ${dashed ? '2px dashed #cc0000' : '1px solid #000000'} !important;
            font-size: 16px !important;
            cursor: pointer !important;
            box-shadow: 0 1px 3px rgba(0,0,0,0.3) !important;
            transition: transform 0.1s ease !important;
            width: 36px !important;
            height: 36px !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            margin: 0 !important;
            touch-action: manipulation !important;
        `;
        
        btn.addEventListener('mouseenter', () => btn.style.transform = 'scale(1.15)');
        btn.addEventListener('mouseleave', () => btn.style.transform = 'scale(1.0)');
    }

    function checkAndInit() {
        const targetElement = document.getElementById('tm-highlight-container');
        
        if (!targetElement || currentDocumentCtx !== document) {
            if (targetElement) targetElement.remove();
            if (formatMenu && formatMenu.remove) formatMenu.remove();
            
            container = document.createElement('div');
            container.id = 'tm-highlight-container';
            container.style.cssText = `
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 2147483647;
                display: none;
                flex-direction: column;
                gap: 8px;
            `;

            highlightColors.forEach(colorConfig => {
                const btn = document.createElement('button');
                btn.innerText = '🖍️';
                btn.title = `Highlight ${colorConfig.name} (Hold to change Text Color)`;
                styleButton(btn, colorConfig.color, colorConfig.gradient);

                let pressTimer = null;
                let isLongPress = false;

                btn.addEventListener('pointerdown', (e) => {
                    e.preventDefault(); e.stopPropagation();
                    isLongPress = false;
                    pressTimer = setTimeout(() => {
                        isLongPress = true;
                        applyTextColor(colorConfig);
                    }, 500);
                });

                btn.addEventListener('pointerup', (e) => {
                    e.preventDefault(); e.stopPropagation();
                    clearTimeout(pressTimer);
                    if (!isLongPress) {
                        applyHighlight(colorConfig);
                    }
                });

                btn.addEventListener('pointerleave', () => {
                    clearTimeout(pressTimer);
                });

                container.appendChild(btn);
            });

            const eraserBtn = document.createElement('button');
            eraserBtn.innerText = '🧹';
            eraserBtn.title = 'Tap to remove highlight/format | Long-press to remove ALL';
            styleButton(eraserBtn, '#ffffff', '#ffffff', true);
            
            let eraserPressTimer = null;
            let eraserIsLongPress = false;
            
            eraserBtn.addEventListener('pointerdown', (e) => {
                e.preventDefault(); e.stopPropagation();
                eraserIsLongPress = false;
                eraserPressTimer = setTimeout(() => {
                    eraserIsLongPress = true;
                    removeAllHighlights();
                }, 500);
            });
            
            eraserBtn.addEventListener('pointerup', (e) => {
                e.preventDefault(); e.stopPropagation();
                clearTimeout(eraserPressTimer);
                if (!eraserIsLongPress) {
                    removeHighlight();
                }
            });
            
            eraserBtn.addEventListener('pointerleave', () => {
                clearTimeout(eraserPressTimer);
            });
            
            container.appendChild(eraserBtn);

            const textBtn = document.createElement('button');
            textBtn.innerText = '📄';
            textBtn.title = 'Export to text';
            styleButton(textBtn);
            textBtn.addEventListener('click', exportHighlightsToText);
            container.appendChild(textBtn);

            const imgBtn = document.createElement('button');
            imgBtn.innerText = '🖼️';
            imgBtn.title = 'Tap for Mobile (390px) | Long-press for Desktop (800px)';
            styleButton(imgBtn, '#e0e0e0');
            
            let imgPressTimer = null;
            let imgIsLongPress = false;
            
            imgBtn.addEventListener('pointerdown', (e) => {
                e.preventDefault(); e.stopPropagation();
                imgIsLongPress = false;
                imgPressTimer = setTimeout(() => {
                    imgIsLongPress = true;
                    exportDesktopView();
                }, 500);
            });
            
            imgBtn.addEventListener('pointerup', (e) => {
                e.preventDefault(); e.stopPropagation();
                clearTimeout(imgPressTimer);
                if (!imgIsLongPress) {
                    exportMobileView();
                }
            });
            
            imgBtn.addEventListener('pointerleave', () => {
                clearTimeout(imgPressTimer);
            });
            
            container.appendChild(imgBtn);

            const clipBtn = document.createElement('button');
            clipBtn.innerText = '📋';
            clipBtn.title = 'Copy to clipboard';
            styleButton(clipBtn);
            clipBtn.addEventListener('click', exportHighlightsToClipboard);
            container.appendChild(clipBtn);

            const formatBtn = document.createElement('button');
            formatBtn.innerText = '🔤';
            formatBtn.title = 'Bold / Italic / Underline';
            styleButton(formatBtn);
            container.appendChild(formatBtn);

            formatMenu = document.createElement('div');
            formatMenu.style.cssText = `
                position: fixed;
                bottom: 80px;
                right: 70px;
                z-index: 2147483647;
                display: none;
                flex-direction: column; 
                gap: 6px;
                background: #ffffff;
                border: 1px solid #000000;
                padding: 6px;
                border-radius: 6px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.3);
            `;
            document.body.appendChild(formatMenu);

            function createFormatOption(label, tagName) {
                const btn = document.createElement('button');
                btn.innerText = label;
                styleButton(btn);
                btn.addEventListener('click', () => {
                    applyCustomFormat(tagName);
                    formatMenu.style.display = 'none';
                });
                formatMenu.appendChild(btn);
            }

            createFormatOption('B', 'b');
            createFormatOption('I', 'i');
            createFormatOption('U', 'u');

            formatBtn.addEventListener('click', (e) => {
                e.preventDefault(); e.stopPropagation();
                formatMenu.style.display = formatMenu.style.display === 'none' ? 'flex' : 'none';
            });

            document.addEventListener('click', (e) => {
                if (formatMenu && formatMenu.style.display === 'flex') {
                    if (!formatMenu.contains(e.target) && e.target !== formatBtn) {
                        formatMenu.style.display = 'none';
                    }
                }
            });

            if (document.body) {
                document.body.appendChild(container);
            } else if (document.documentElement) {
                document.documentElement.appendChild(container);
            }

            document.removeEventListener('selectionchange', handleSelection);
            document.addEventListener('selectionchange', handleSelection);
            document.removeEventListener('pointerup', handleSelection);
            document.addEventListener('pointerup', handleSelection);

            currentDocumentCtx = document;
        }
    }

    checkAndInit();
    setInterval(checkAndInit, 600);
})();