您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
URLやドメイン名(先頭の "h" が抜けている場合も含む)をクリック可能なリンクに変換する。ただし、入力中のフォーム領域は除外する。
当前为
- // ==UserScript==
- // @name Convert Any links to Clickable Links
- // @namespace kdroidwin.hatenablog.com
- // @version 1.5
- // @description URLやドメイン名(先頭の "h" が抜けている場合も含む)をクリック可能なリンクに変換する。ただし、入力中のフォーム領域は除外する。
- // @author K
- // @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) {
- const pattern = /(h?ttps?:\/\/[^\s]+|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})/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 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 {
- fragment.appendChild(document.createTextNode(part));
- }
- });
- 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 の変更を監視(変更があったら 300ms 後に実行)
- const observer = new MutationObserver(debounce(mutations => {
- for (const mutation of mutations) {
- for (const node of mutation.addedNodes) {
- if (node.nodeType === 1) {
- convertTextToLinks(node);
- }
- }
- }
- }, 300));
- observer.observe(document.body, { childList: true, subtree: true });
- })();