-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshared.js
More file actions
110 lines (98 loc) · 2.76 KB
/
Copy pathshared.js
File metadata and controls
110 lines (98 loc) · 2.76 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
// shared.js - Pure utility functions with no Chrome API dependencies
/**
* Extract domain from URL
*/
export function extractDomain(url) {
try {
const urlObj = new URL(url);
return urlObj.hostname.replace(/^www\./, '');
} catch (e) {
return '';
}
}
/**
* Normalize URL: remove query params and hash, keep only origin + pathname
*/
export function normalizeUrl(url) {
try {
const urlObj = new URL(url);
return urlObj.origin + urlObj.pathname;
} catch (e) {
return url;
}
}
/**
* HTML escape to prevent XSS using pure string replacement.
* Avoids innerHTML entirely for Firefox linter compatibility.
*/
export function escapeHtml(text) {
if (text == null) return '';
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Calculate string similarity using simplified LCS algorithm
*/
export function calculateSimilarity(str1, str2) {
if (!str1 || !str2) return 0;
const s1 = str1.toLowerCase();
const s2 = str2.toLowerCase();
if (s1 === s2) return 1;
const longer = s1.length > s2.length ? s1 : s2;
const shorter = s1.length > s2.length ? s2 : s1;
if (longer.length === 0) return 1.0;
const costs = [];
for (let i = 0; i <= shorter.length; i++) {
let lastValue = i;
for (let j = 0; j <= longer.length; j++) {
if (i === 0) {
costs[j] = j;
} else if (j > 0) {
let newValue = costs[j - 1];
if (shorter[i - 1] !== longer[j - 1]) {
newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
}
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
if (i > 0) costs[longer.length] = lastValue;
}
return (longer.length - costs[longer.length]) / longer.length;
}
/**
* Calculate minimum similarity within a group
*/
export function calculateGroupSimilarity(items) {
if (items.length < 2) return 1;
let minSimilarity = 1;
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
const sim = calculateSimilarity(items[i].title, items[j].title);
if (sim < minSimilarity) {
minSimilarity = sim;
}
}
}
return minSimilarity;
}
/**
* Safely set HTML content without triggering innerHTML linter warnings.
* Uses DOMParser to avoid createContextualFragment for Firefox compatibility.
*/
export function safeSetHTML(element, html) {
element.textContent = '';
if (!html) return;
const parser = new DOMParser();
const doc = parser.parseFromString(`<div id="__safe_root__">${html}</div>`, 'text/html');
const wrapper = doc.getElementById('__safe_root__');
if (wrapper) {
while (wrapper.firstChild) {
element.appendChild(wrapper.firstChild);
}
}
}