URLやドメイン名(先頭の "h" が抜けている場合も含む)をクリック可能なリンクに変換する。ただし、入力中のフォーム領域は除外する。
当前为
// ==UserScript==
// @name Convert Any links to Clickable Links
// @namespace kdroidwin.hatenablog.com/
// @version 1.4
// @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;
// ノードが input または textarea なら true
if (node.nodeName === 'INPUT' || node.nodeName === 'TEXTAREA') return true;
// contenteditable 属性が設定されているかチェック
if (node.hasAttribute && node.hasAttribute('contenteditable') && node.getAttribute('contenteditable') !== 'false') {
return true;
}
return false;
}
function convertTextToLinks() {
// フォーム要素内は除外するため、walker で対象ノードを選ぶときに、親要素が入力可能な要素でないかもチェックする
const pattern = /(h?ttps?:\/\/[^\s]+|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})/g;
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: node => {
// 親要素または祖先に input/textarea/contenteditable 要素があるかチェック
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 parts = node.nodeValue.split(pattern);
parts.forEach(part => {
const text = part.trim();
if (/^ttps:\/\//i.test(text)) {
const link = document.createElement('a');
link.href = 'h' + text;
link.textContent = text;
link.target = '_blank';
fragment.appendChild(link);
} else if (/^https?:\/\//i.test(text)) {
const link = document.createElement('a');
link.href = text;
link.textContent = text;
link.target = '_blank';
fragment.appendChild(link);
} else if (/(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}/.test(text)) {
const link = document.createElement('a');
link.href = 'https://' + text;
link.textContent = text;
link.target = '_blank';
fragment.appendChild(link);
} else {
// ※ここでは元の部分(trim していない)をそのままテキストとして追加
fragment.appendChild(document.createTextNode(part));
}
});
node.parentNode.replaceChild(fragment, node);
});
}
// 初回実行
convertTextToLinks();
// DOM の変更がある場合に再度実行する
new MutationObserver(convertTextToLinks).observe(document.body, { childList: true, subtree: true });
})();