URLやドメイン名をクリック可能なリンクに変換(軽量化版)
当前为
// ==UserScript==
// @name Convert Any links to Clickable Links (Lightweight)
// @namespace kdroidwin.hatenablog.com
// @version 2.4
// @description URLやドメイン名をクリック可能なリンクに変換(軽量化版)
// @author Kdroidwin
// @match *://*/*
// @exclude *://github.com/*
// @exclude *://chat.openai.com/*
// @exclude *://blog.hatena.ne.jp/*
// @grant none
// @license GPL-3.0
// ==/UserScript==
(function() {
'use strict';
const urlPattern = /\b(?:h?ttps?:\/\/[^\s<>"]+|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s<>"]*)?)\b/g;
function isEditable(node) {
while (node) {
if (node.nodeName === 'INPUT' || node.nodeName === 'TEXTAREA' ||
(node.getAttribute && node.getAttribute('contenteditable') === 'true')) return true;
node = node.parentNode;
}
return false;
}
function convertTextToLinks(root) {
const textNodes = [];
root.querySelectorAll(':not(a):not(input):not(textarea):not([contenteditable])').forEach(el => {
for (let node of el.childNodes) {
if (node.nodeType === 3 && urlPattern.test(node.nodeValue)) textNodes.push(node);
}
});
textNodes.forEach(node => {
const frag = document.createDocumentFragment();
let lastIndex = 0;
node.nodeValue.replace(urlPattern, (match, offset) => {
frag.appendChild(document.createTextNode(node.nodeValue.slice(lastIndex, offset)));
const a = document.createElement('a');
a.href = match.startsWith('ttp') ? 'h' + match : match.includes('://') ? match : 'https://' + match;
a.textContent = match;
a.target = '_blank';
frag.appendChild(a);
lastIndex = offset + match.length;
});
frag.appendChild(document.createTextNode(node.nodeValue.slice(lastIndex)));
node.parentNode.replaceChild(frag, node);
});
}
const observer = new MutationObserver(() => {
convertTextToLinks(document.body);
});
observer.observe(document.body, { childList: true, subtree: true });
convertTextToLinks(document.body);
})();