您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
URLやドメイン名(先頭の "h" が抜けている場合も含む)をクリック可能なリンクに変換する。ただし、入力中のフォーム領域は除外する。
当前为
// ==UserScript== // @name Convert Any links to Clickable Links // @namespace kdroidwin.hatenablog.com // @version 2.2 // @description URLやドメイン名(先頭の "h" が抜けている場合も含む)をクリック可能なリンクに変換する。ただし、入力中のフォーム領域は除外する。 // @author Kdroidwin // @match *://*/* // @exclude *://github.com/* // @exclude *://chat.openai.com/* // @exclude *://blog.hatena.ne.jp/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; function isEditable(node) { if (!node) return false; if (node.nodeName === 'INPUT' || node.nodeName === 'TEXTAREA') return true; if (node.hasAttribute && node.hasAttribute('contenteditable') && node.getAttribute('contenteditable') !== 'false') { return true; } return false; } function convertTextToLinks(root = document.body) { // 修正したURLパターン: パスを含むURLをしっかり認識 const pattern = /(h?ttps?:\/\/[^\s]+|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?)/g; const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode: node => { let current = node.parentNode; while (current) { if (isEditable(current)) return NodeFilter.FILTER_REJECT; current = current.parentNode; } if (node.parentNode && node.parentNode.tagName !== 'A' && pattern.test(node.nodeValue)) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } }); let nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); nodes.forEach(node => { const fragment = document.createDocumentFragment(); const matches = node.nodeValue.match(pattern); if (!matches) return; let lastIndex = 0; matches.forEach(match => { const matchIndex = node.nodeValue.indexOf(match, lastIndex); if (matchIndex > lastIndex) { fragment.appendChild(document.createTextNode(node.nodeValue.substring(lastIndex, matchIndex))); } const link = document.createElement('a'); // 先頭の h が抜けている場合も補完 if (/^ttps:\/\//i.test(match)) { link.href = 'h' + match; } else if (/^https?:\/\//i.test(match)) { link.href = match; } else { link.href = 'https://' + match; } link.textContent = match; link.target = '_blank'; fragment.appendChild(link); lastIndex = matchIndex + match.length; }); if (lastIndex < node.nodeValue.length) { fragment.appendChild(document.createTextNode(node.nodeValue.substring(lastIndex))); } node.parentNode.replaceChild(fragment, node); }); } function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // 初回実行 convertTextToLinks(); // DOM の変更を監視(変更があったら 500ms 後に実行) const observer = new MutationObserver(debounce(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1) { convertTextToLinks(node); } } } }, 500)); observer.observe(document.body, { childList: true, subtree: true }); })();