-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrs.js
More file actions
144 lines (125 loc) · 5.66 KB
/
Copy pathrs.js
File metadata and controls
144 lines (125 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// ==UserScript==
// @name Linkify homeTaskItem
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Ищет в элементах с классом "homeTaskItem" текстовые http/https ссылки и заменяет их на <a> теги.
// @author Ты
// @match *://*/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// Регулярное выражение для ссылок (чаще всего корректно)
const urlRegex = /https?:\/\/[^\s<>"'`]+/gi;
// Создаёт элемент <a> из найденной строки ссылки
function makeLinkNode(url) {
const a = document.createElement('a');
a.href = url;
a.textContent = url;
a.target = '_blank';
a.rel = 'noopener noreferrer';
return a;
}
// Обрабатывает один текстовый узел: заменяет все ссылки в нём на узлы <a>
function replaceLinksInTextNode(textNode) {
const text = textNode.nodeValue;
if (!text || !urlRegex.test(text)) return;
// Восстановим regex (т.к. тест сдвигает флаг)
urlRegex.lastIndex = 0;
const frag = document.createDocumentFragment();
let lastIndex = 0;
let match;
while ((match = urlRegex.exec(text)) !== null) {
const url = match[0];
const idx = match.index;
// текст до ссылки
if (idx > lastIndex) {
frag.appendChild(document.createTextNode(text.slice(lastIndex, idx)));
}
// сам линк
frag.appendChild(makeLinkNode(url));
lastIndex = idx + url.length;
}
// остаток текста
if (lastIndex < text.length) {
frag.appendChild(document.createTextNode(text.slice(lastIndex)));
}
// заменяем текстовый узел на фрагмент
textNode.parentNode.replaceChild(frag, textNode);
}
// Обрабатываем конкретный элемент .homeTaskItem: только текстовые узлы (не затрагиваем уже существующие <a>)
function processHomeTaskItem(el) {
// Если в элементе нет потенциальной ссылки — быстро выходим
if (!el.textContent || !/https?:\/\//i.test(el.textContent)) return;
// Создаём TreeWalker, который возвращает только текстовые узлы,
// но пропускает текст внутри существующих <a>
const walker = document.createTreeWalker(
el,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
// если предок — <a>, пропускаем
for (let p = node.parentNode; p && p !== el; p = p.parentNode) {
if (p.nodeType === 1 && p.tagName.toLowerCase() === 'a') {
return NodeFilter.FILTER_REJECT;
}
}
// принимаем текстовый узел
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
const textNodes = [];
let tn;
while ((tn = walker.nextNode())) {
textNodes.push(tn);
}
textNodes.forEach(replaceLinksInTextNode);
}
// Найти все текущие элементы и обработать их
function scanAndProcessAll() {
document.querySelectorAll('.homeTaskItem').forEach(processHomeTaskItem);
}
// Следим за динамически добавленным контентом (например, SPA)
const observer = new MutationObserver(mutations => {
for (const m of mutations) {
// новые элементы
if (m.addedNodes && m.addedNodes.length) {
m.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
// если добавлен сам .homeTaskItem или внутри него есть такие элементы
if (node.classList && node.classList.contains('homeTaskItem')) {
processHomeTaskItem(node);
} else {
node.querySelectorAll && node.querySelectorAll('.homeTaskItem').forEach(processHomeTaskItem);
}
});
}
// если изменился текст внутри существующего .homeTaskItem
if (m.type === 'characterData' && m.target) {
// ищем ближайший предок с нужным классом
let parent = m.target.parentNode;
while (parent && parent !== document.body) {
if (parent.classList && parent.classList.contains('homeTaskItem')) {
processHomeTaskItem(parent);
break;
}
parent = parent.parentNode;
}
}
}
});
observer.observe(document.documentElement || document.body, {
childList: true,
subtree: true,
characterData: true
});
// стартовый проход
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', scanAndProcessAll);
} else {
scanAndProcessAll();
}
})();