Greasy Fork 还支持 简体中文。

Convert Any Links to Clickable Links

すべてのリンクをクリックできるリンクにかえる。

目前為 2025-03-14 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Convert Any Links to Clickable Links
  3. // @namespace kdroidwin.hatenablog.com
  4. // @version 2.7
  5. // @description すべてのリンクをクリックできるリンクにかえる。
  6. // @author Kdroidwin
  7. // @match *://*/*
  8. // @exclude *://github.com/*
  9. // @exclude *://chat.openai.com/*
  10. // @exclude *://blog.hatena.ne.jp/*
  11. // @grant none
  12. // @license GPL-3.0
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. const urlPattern = /\b(?:h?ttps?:\/\/[^\s<>"]+|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s<>"]*)?)\b/g;
  19.  
  20. function convertTextToLinks(node) {
  21. if (node.nodeType !== 3 || !urlPattern.test(node.nodeValue)) return;
  22.  
  23. const parent = node.parentNode;
  24. if (parent.tagName === 'A' || parent.matches('input, textarea, [contenteditable]')) return;
  25.  
  26. const frag = document.createDocumentFragment();
  27. let lastIndex = 0;
  28. node.nodeValue.replace(urlPattern, (match, offset) => {
  29. frag.appendChild(document.createTextNode(node.nodeValue.slice(lastIndex, offset)));
  30.  
  31. const a = document.createElement('a');
  32. a.href = match.startsWith('ttp') ? 'h' + match : match.includes('://') ? match : 'https://' + match;
  33. a.textContent = match;
  34. a.target = '_blank';
  35. frag.appendChild(a);
  36.  
  37. lastIndex = offset + match.length;
  38. });
  39.  
  40. frag.appendChild(document.createTextNode(node.nodeValue.slice(lastIndex)));
  41. parent.replaceChild(frag, node);
  42. }
  43.  
  44. function debounce(func, delay) {
  45. let timer;
  46. return (...args) => {
  47. clearTimeout(timer);
  48. timer = setTimeout(() => func(...args), delay);
  49. };
  50. }
  51.  
  52. const observer = new MutationObserver(debounce(mutations => {
  53. for (const mutation of mutations) {
  54. if (mutation.type === 'childList') {
  55. for (const node of mutation.addedNodes) {
  56. if (node.nodeType === 1) {
  57. for (const textNode of node.childNodes) {
  58. convertTextToLinks(textNode);
  59. }
  60. }
  61. }
  62. } else if (mutation.type === 'characterData') {
  63. convertTextToLinks(mutation.target);
  64. }
  65. }
  66. }, 500));
  67.  
  68. observer.observe(document.body, { childList: true, subtree: true, characterData: true });
  69.  
  70. document.querySelectorAll('*').forEach(el => {
  71. for (const node of el.childNodes) {
  72. convertTextToLinks(node);
  73. }
  74. });
  75. })();