DOI to Google Scholar

Converts DOI links and plain text DOIs directly to Google Scholar links

// ==UserScript==
// @name         DOI to Google Scholar
// @namespace    https://violentmonkey.github.io/
// @version      1.4
// @description  Converts DOI links and plain text DOIs directly to Google Scholar links
// @author       Bui Quoc Dung
// @match        *://*/*
// @license      AGPL-3.0-or-later
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    const SCHOLAR_URL = 'https://scholar.google.com/scholar?hl=en&as_sdt=0%2C5&q=';
    const DOI_REGEX = /(?:\b|^|\s)(10\.\d{4,}(?:\/[a-zA-Z0-9._-]+)+)(?=\b|$|\s|[,.;)])/g;

    const convertDOI = doi => {
        const cleanDOI = doi.replace(/[.,;:]$/, '');
        const doiPart = cleanDOI.startsWith('http') ?
            cleanDOI.split('doi.org/')[1] || cleanDOI :
            cleanDOI;
        return `${SCHOLAR_URL}${encodeURIComponent(doiPart)}`;
    };

    const processLinks = () => {
        const doiSelectors = [
            'a[href*="doi.org/"]',
            'a[href*="dx.doi.org/"]'
        ].join(',');

        document.querySelectorAll(doiSelectors).forEach(link => {
            if (!link.dataset.scholarProcessed) {
                link.dataset.scholarProcessed = 'true';
                link.addEventListener('click', e => {
                    e.preventDefault();
                    window.location.href = convertDOI(link.href);
                });
            }
        });
    };

    const processText = () => {
        const walk = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            null,
            false
        );

        const nodesToProcess = [];
        while (walk.nextNode()) {
            if (DOI_REGEX.test(walk.currentNode.textContent)) {
                nodesToProcess.push(walk.currentNode);
            }
        }

        nodesToProcess.forEach(node => {
            const fragment = document.createDocumentFragment();
            let lastIndex = 0;
            const text = node.textContent;
            DOI_REGEX.lastIndex = 0;

            let match;
            while ((match = DOI_REGEX.exec(text)) !== null) {
                if (match.index > lastIndex) {
                    fragment.appendChild(
                        document.createTextNode(text.substring(lastIndex, match.index))
                    );
                }

                const link = document.createElement('a');
                link.href = convertDOI(match[1]); // Direct Google Scholar link
                link.textContent = match[1];
                link.style.color = '#1a0dab';
                link.style.textDecoration = 'underline';
                fragment.appendChild(link);

                lastIndex = match.index + match[0].length;
            }

            if (lastIndex < text.length) {
                fragment.appendChild(
                    document.createTextNode(text.substring(lastIndex))
                );
            }

            node.replaceWith(fragment);
        });
    };

    // Debounce and observer logic (unchanged)
    const debounce = (func, wait) => {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    };

    const debouncedProcess = debounce(() => {
        processLinks();
        processText();
    }, 250);

    const observer = new MutationObserver(debouncedProcess);
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Initial processing
    processLinks();
    processText();
})();