URLやドメイン名(先頭の"h"が抜けている場合も含む)をクリック可能なリンクに変換する
当前为
// ==UserScript==
// @name Convert Any links to Clickable Links
// @namespace kdroidwin.hatenablog.com/
// @version 1.2
// @description URLやドメイン名(先頭の"h"が抜けている場合も含む)をクリック可能なリンクに変換する
// @author Kdroidwin
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
function convertTextToLinks() {
// 先頭の "h" が抜けているケースも考慮してマッチ
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 => {
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)) {
// ドメインのみの場合は「https://」を補完
const link = document.createElement('a');
link.href = 'https://' + text;
link.textContent = text;
link.target = '_blank';
fragment.appendChild(link);
} else {
// マッチしなかった部分はそのままテキストノードへ
fragment.appendChild(document.createTextNode(part));
}
});
node.parentNode.replaceChild(fragment, node);
});
}
convertTextToLinks();
new MutationObserver(convertTextToLinks).observe(document.body, { childList: true, subtree: true });
})();