-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.js
More file actions
156 lines (134 loc) · 4.8 KB
/
content.js
File metadata and controls
156 lines (134 loc) · 4.8 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
145
146
147
148
149
150
151
152
153
154
155
156
// Regex for time + timezone patterns
const timeRegex = /(\d{1,2}:\d{2}|\d{1,2}\s*([ap]m?\.?)?)\s+[\(\[]?(UTC|GMT|PST|PDT|EST|EDT|ET|CST|CDT|CT|MST|MDT|MT|PT|CET|CEST|JST|IST)[\)\]]?/gi;
// Track processed nodes
const processedNodes = new WeakSet();
// Timezone offset mappings
const timezoneMap = {
'PST': -8, 'PDT': -7, 'PT': -8,
'EST': -5, 'EDT': -4, 'ET': -5,
'CST': -6, 'CDT': -5, 'CT': -6,
'MST': -7, 'MDT': -6, 'MT': -7,
'CET': 1, 'CEST': 2, 'JST': 9, 'IST': 5.5
};
// Convert timezone string to local time
function convertToLocalTime(match) {
try {
// Try direct parsing first
let date = new Date(Date.parse(match));
if (!isNaN(date)) {
return date.toLocaleString(undefined, { timeZoneName: "short" });
}
// Manual parsing
const cleanMatch = match.trim().toUpperCase();
let timeStr, timezoneAbbr;
// Extract time and timezone
const cleanedMatch = cleanMatch.replace(/[\(\)\[\]]/g, '');
const parts = cleanedMatch.split(/\s+/);
if (parts.length >= 2) {
timeStr = parts[0];
timezoneAbbr = parts[parts.length - 1];
// Normalize time format
if (timeStr.includes(':')) {
timeStr = timeStr.replace(/(\d{1,2}:\d{2})\s*([ap]m?\.?)?/i, (match, time, ampm) => {
if (ampm) {
const normalized = ampm.toLowerCase().replace(/\./g, '').replace(/^([ap])m?$/, '$1m').toUpperCase();
return `${time} ${normalized}`;
}
return time;
});
} else {
timeStr = timeStr.replace(/(\d{1,2})\s*([ap]m?\.?)?/i, (match, hour, ampm) => {
if (ampm) {
const normalized = ampm.toLowerCase().replace(/\./g, '').replace(/^([ap])m?$/, '$1m').toUpperCase();
return `${hour}:00 ${normalized}`;
}
return `${hour}:00`;
});
}
// Convert timezone to local time
if (timezoneAbbr === 'UTC' || timezoneAbbr === 'GMT') {
const now = new Date();
const timeOnly = new Date(`${now.toDateString()} ${timeStr}`);
if (!isNaN(timeOnly)) {
const utcTime = new Date(timeOnly.getTime() - timeOnly.getTimezoneOffset() * 60000);
return utcTime.toLocaleString(undefined, { timeZoneName: "short" });
}
}
if (timezoneMap[timezoneAbbr]) {
const offsetHours = timezoneMap[timezoneAbbr];
const now = new Date();
const timeOnly = new Date(`${now.toDateString()} ${timeStr}`);
if (!isNaN(timeOnly)) {
const utcTime = new Date(timeOnly.getTime() - (offsetHours * 60 * 60 * 1000) - (timeOnly.getTimezoneOffset() * 60000));
return utcTime.toLocaleString(undefined, { timeZoneName: "short" });
}
}
}
return null;
} catch {
return null;
}
}
// Process text nodes
function processTextNode(node) {
const parent = node.parentNode;
if (!parent || processedNodes.has(node)) return;
const text = node.textContent;
const matches = text.match(timeRegex);
if (!matches) return;
let lastIndex = 0;
const fragment = document.createDocumentFragment();
matches.forEach(match => {
const index = text.indexOf(match, lastIndex);
if (index > lastIndex) {
fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)));
}
const local = convertToLocalTime(match);
if (local) {
const span = document.createElement("span");
span.textContent = match;
// Show time only, no date
const timeOnly = local.split(',')[1]?.trim() || local.split(' ').slice(-3).join(' ');
span.title = timeOnly;
span.style.borderBottom = "1px dotted gray";
fragment.appendChild(span);
} else {
fragment.appendChild(document.createTextNode(match));
}
lastIndex = index + match.length;
});
if (lastIndex < text.length) {
fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
}
parent.replaceChild(fragment, node);
processedNodes.add(node);
}
// DOM walker
function walk(node) {
if (node.nodeType === Node.TEXT_NODE) {
processTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE &&
!["SCRIPT","STYLE","TEXTAREA","IFRAME","INPUT","SELECT","CODE","PRE"].includes(node.tagName)) {
Array.from(node.childNodes).forEach(walk);
}
}
// Initial scan
window.addEventListener("load", () => walk(document.body));
// Watch for dynamic content
let observerTimeout;
const observer = new MutationObserver(mutations => {
clearTimeout(observerTimeout);
observerTimeout = setTimeout(() => {
mutations.forEach(m => {
m.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
walk(node);
}
});
});
}, 100);
});
// Start observer
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
}