DOI to Google Scholar

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         DOI to Google Scholar
// @namespace    https://violentmonkey.github.io/
// @version      1.6
// @description  Converts DOI links and plain text DOIs directly to Google Scholar links
// @author       Bui Quoc Dung
// @match        *://*/*
// @exclude      *://scholar.google.com/*
// @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,}\/[^\s"'<>\]}]+)/gi;

  /**
   * Chuyển một DOI (URL hoặc plain text) thành link Google Scholar
   */
  function convertDOIToScholarURL(doi) {
    const cleanDOI = doi.replace(/[.,;:]$/, '');
    const doiPart = cleanDOI.startsWith('http') ?
      cleanDOI.split('doi.org/')[1] || cleanDOI :
      cleanDOI;
    return `${SCHOLAR_URL}${encodeURIComponent(doiPart)}`;
  }

  /**
   * Xử lý các liên kết DOI, thêm sự kiện click để mở trên Google Scholar trong tab mới
   */
  function handleDOILinks() {
    const doiLinkSelectors = [
      'a[href*="doi.org/"]',
      'a[href*="dx.doi.org/"]'
    ].join(',');

    document.querySelectorAll(doiLinkSelectors).forEach(link => {
      if (!link.dataset.scholarProcessed) {
        link.dataset.scholarProcessed = 'true';
        link.addEventListener('click', e => {
          e.preventDefault();
          window.open(convertDOIToScholarURL(link.href), '_blank');
        });
      }
    });
  }

  /**
   * Tìm DOI trong text node và chuyển thành liên kết Google Scholar (mở tab mới)
   */
  function convertPlainTextDOIsToLinks() {
    const walker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_TEXT,
      null,
      false
    );

    const nodesToProcess = [];
    const safeTestRegex = /(?:\b|^|\s)(10\.\d{4,}\/[^\s"'<>\]}]+)/i;

    while (walker.nextNode()) {
      const node = walker.currentNode;
      if (safeTestRegex.test(node.textContent)) {
        nodesToProcess.push(node);
      }
    }

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

      let match;
      DOI_REGEX.lastIndex = 0;
      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 = convertDOIToScholarURL(match[1]);
        link.target = '_blank'; // mở trong tab mới
        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 để tránh xử lý quá nhiều khi DOM thay đổi liên tục
   */
  function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => func(...args), wait);
    };
  }

  /**
   * Xử lý cả liên kết và text chứa DOI
   */
  function processDOIs() {
    handleDOILinks();
    convertPlainTextDOIsToLinks();
  }

  /**
   * Theo dõi các thay đổi động trên trang
   */
  function observeDynamicChanges() {
    const debouncedProcess = debounce(processDOIs, 250);
    const observer = new MutationObserver(debouncedProcess);
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  }

  /**
   * Khởi chạy script
   */
  function init() {
    processDOIs();
    observeDynamicChanges();
  }

  init();
})();