Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
QuiteAFancyEmerald
GitHub Repository: QuiteAFancyEmerald/Holy-Unblocker
Path: blob/master/views/assets/js/csel.js
11192 views
1
/* -----------------------------------------------
2
/* Authors: OlyB and Yoct
3
/* GNU Affero General Public License v3.0: https://www.gnu.org/licenses/agpl-3.0.en.html
4
/* Adapted and modified by Yoct.
5
/* Settings Menu
6
/* ----------------------------------------------- */
7
8
// Encase everything in a new scope so that variables are not accidentally
9
// attached to the global scope.
10
(() => {
11
// Determine the expiration date of a new cookie.
12
let date = new Date();
13
date.setFullYear(date.getFullYear() + 100);
14
date = date.toUTCString();
15
16
// Cookies will not be used unless necessary. The localStorage API will be used instead.
17
const storageId = '{{hu-lts}}-storage',
18
storageObject = () => JSON.parse(localStorage.getItem(storageId)) || {},
19
setStorage = (name, value) => {
20
let mainStorage = storageObject();
21
mainStorage[name] = value;
22
localStorage.setItem(storageId, JSON.stringify(mainStorage));
23
},
24
removeStorage = (name) => {
25
let mainStorage = storageObject();
26
delete mainStorage[name];
27
localStorage.setItem(storageId, JSON.stringify(mainStorage));
28
},
29
readStorage = (name) => storageObject()[name],
30
useStorageArgs = (name, func) => func(readStorage(name)),
31
// All cookies should be secure and are intended to work in IFrames.
32
setCookie = (name, value) => {
33
document.cookie =
34
name +
35
`=${encodeURIComponent(value)}; expires=${date}; SameSite=None; Secure;`;
36
},
37
removeCookie = (name) => {
38
document.cookie =
39
name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure;';
40
},
41
readCookie = async (name) => {
42
// Get the first cookie that has the same name.
43
for (let cookie of document.cookie.split('; '))
44
if (!cookie.indexOf(name + '='))
45
// Return the cookie's stored content.
46
return decodeURIComponent(cookie.slice(name.length + 1));
47
},
48
// Customize the page's title.
49
pageTitle = (value) => {
50
let tag =
51
document.getElementsByTagName('title')[0] ||
52
document.createElement('title');
53
tag.innerHTML = value;
54
document.head.appendChild(tag);
55
},
56
// Set the page's favicon to a new URL.
57
pageIcon = (value) => {
58
let tags = document.querySelectorAll("link[rel*='icon']") || [
59
document.createElement('link'),
60
];
61
tags.forEach((element) => {
62
element.rel = 'icon';
63
element.href = value;
64
document.head.appendChild(element);
65
});
66
},
67
// Make a small stylesheet to override a setting from the main stylesheet.
68
pageShowAds = () => {
69
let advertising = document.createElement('style');
70
advertising.id = 'advertising';
71
advertising.innerText = '.ad { display:block; }';
72
(
73
document.head ||
74
document.body ||
75
document.documentElement ||
76
document
77
).appendChild(advertising);
78
},
79
// Remove the stylesheet made by the function above, if it exists.
80
pageHideAds = () => {
81
(document.getElementById('advertising') || new Text()).remove();
82
},
83
offStateKeywords = ['off', 'disabled', 'false', '0'],
84
onStateKeywords = ['on', 'enabled', 'true', '1'],
85
checkBooleanState = (element) => {
86
const state =
87
`${(element.querySelector('input:checked,option:checked') || []).value || element.checked}`.toLowerCase();
88
return (
89
onStateKeywords.includes(state) ||
90
(!offStateKeywords.includes(state) && state)
91
);
92
},
93
classEvent = (targetElementList, eventTrigger, eventConstructor = Event) =>
94
new eventConstructor(eventTrigger, { target: targetElementList[0] }),
95
classUpdateHandler =
96
(targetElementList, stateFunction, manualEvent = false, ...params) =>
97
() => {
98
const state =
99
'function' === typeof stateFunction
100
? `${stateFunction(...params)}`.toLowerCase()
101
: `${stateFunction}`.toLowerCase();
102
[...targetElementList].forEach((updateTarget) => {
103
if (updateTarget.children.length > 0) {
104
let children = updateTarget.querySelectorAll('input,option');
105
const values = Array.from(children, (child) => {
106
const childText = `${child.value}`.toLowerCase();
107
return offStateKeywords.includes(childText)
108
? offStateKeywords.concat(child.value)
109
: onStateKeywords.includes(childText)
110
? onStateKeywords.concat(child.value)
111
: [childText];
112
});
113
const mappedIndex = values.findIndex((possibleValues) =>
114
possibleValues.includes(state)
115
);
116
if ('number' === typeof updateTarget.selectedIndex)
117
updateTarget.selectedIndex = mappedIndex;
118
else children[mappedIndex].checked = true;
119
} else updateTarget.checked = onStateKeywords.includes(state);
120
});
121
let eventTarget = targetElementList[0];
122
if (manualEvent instanceof Event && eventTarget)
123
['input', 'select'].includes(eventTarget.tagName.toLowerCase())
124
? eventTarget.dispatchEvent(manualEvent)
125
: eventTarget.querySelectorAll('input').forEach((child) => {
126
child.dispatchEvent(manualEvent);
127
});
128
},
129
// These titles and icons are used as autofill templates by settings.html.
130
// The icon URLs and tab titles may need to be updated over time.
131
presetIcons = Object.freeze({
132
'': ' \n ',
133
'{{Google}}': 'Google \n https://www.google.com/favicon.ico',
134
'{{Bing}}':
135
'Bing \n https://www.bing.com/sa/simg/favicon-trans-bg-blue-mg-28.ico',
136
'{{Google}} Drive':
137
'Home - Google Drive \n https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png',
138
Gmail:
139
'Inbox - Gmail \n https://ssl.gstatic.com/ui/v1/icons/mail/rfr/gmail.ico',
140
}),
141
defaultTheme = 'dark',
142
// Choose the default transport mode, for proxying, based on the browser.
143
// Firefox is not supported by epoxy yet, which is why this is implemented.
144
defaultMode = '{{libcurl}}',
145
defaultSearch = '{{defaultSearch}}';
146
147
// All code in this block is used by menu items that adjust website settings.
148
149
/* BEGIN WEBSITE SETTINGS */
150
151
if (document.getElementById('csel')) {
152
const attachEventListener = (selector, ...args) =>
153
(
154
document.getElementById(selector) || document.querySelector(selector)
155
).addEventListener(...args),
156
focusElement = document
157
.getElementsByClassName('dropdown-settings')[0]
158
.parentElement.querySelector("a[href='#']");
159
160
// TODO: Add functionality to adapt listeners for the Wisp Transport List.
161
// TODO: Properly comment this code.
162
const attachClassEventListener = (classSelector, ...args) => {
163
const eventTrigger = args[0],
164
selectorList = [...document.getElementsByClassName(classSelector)];
165
selectorList.forEach((element, index) => {
166
let otherElements = [...selectorList];
167
otherElements.splice(index, 1);
168
const elementValue = () =>
169
(element.querySelector('input:checked,option:checked') || []).value ||
170
element.checked;
171
const listeners = ['input', 'select'].includes(
172
element.tagName.toLowerCase()
173
)
174
? [element]
175
: element.querySelectorAll('input');
176
177
listeners.forEach((listener) => {
178
listener.addEventListener(...args);
179
listener.addEventListener(
180
eventTrigger,
181
classUpdateHandler(otherElements, elementValue)
182
);
183
});
184
});
185
};
186
187
attachEventListener('.dropdown-settings .close-settings-btn', 'click', () => {
188
document.activeElement.blur();
189
});
190
191
// Allow users to set a custom title with the UI.
192
attachEventListener('titleform', 'submit', (e) => {
193
e.preventDefault();
194
e = e.target.firstElementChild;
195
if (e.value) {
196
pageTitle(e.value);
197
setStorage('Title', e.value);
198
e.value = '';
199
} else if (confirm('Reset the title to default?')) {
200
// Allow users to reset the title to default if nothing is entered.
201
focusElement.focus();
202
removeStorage('Title');
203
pageTitle('Holy Unblocker LTS');
204
}
205
});
206
207
// Allow users to set a custom favicon with the UI.
208
attachEventListener('iconform', 'submit', (e) => {
209
e.preventDefault();
210
e = e.target.firstElementChild;
211
if (e.value) {
212
pageIcon(e.value);
213
setStorage('Icon', e.value);
214
e.value = '';
215
} else if (confirm('Reset the icon to default?')) {
216
// Allow users to reset the favicon to default if nothing is entered.
217
focusElement.focus();
218
removeStorage('Icon');
219
pageIcon('{{route}}{{assets/ico/favicon.ico}}');
220
}
221
});
222
223
/*
224
225
This is unused in the current settings menu.
226
227
// Allow users to make a new about:blank tab and view the site from there.
228
// An iframe of the current page is inserted into the new tab.
229
attachEventListener("cselab", "click", () => {
230
let win = window.open();
231
let iframe = win.document.createElement("iframe");
232
iframe.style = "width: 100%; height: 100%; border: none; overflow: hidden; margin: 0; padding: 0; position: fixed; top: 0; left: 0";
233
iframe.src = location.href;
234
win.document.body.appendChild(iframe);
235
});
236
*/
237
238
// Provides users with a handy set of title and icon autofill options.
239
attachEventListener('icon-list', 'change', (e) => {
240
let titleform = document.getElementById('titleform'),
241
iconform = document.getElementById('iconform');
242
[titleform.firstElementChild.value, iconform.firstElementChild.value] = (
243
presetIcons[e.target.value] || ' \n '
244
).split(' \n ');
245
});
246
247
attachClassEventListener('search-engine-list', 'change', (e) => {
248
e.target.value === defaultSearch
249
? removeStorage('SearchEngine')
250
: setStorage('SearchEngine', e.target.value);
251
});
252
253
// Allow users to change the Wisp transport mode, for proxying, with the UI.
254
attachClassEventListener('{{wisp-transport}}-list', 'change', (e) => {
255
if (e.target.checked) {
256
let wispTransportList = e.target.closest('.{{wisp-transport}}-list');
257
!wispTransportList.querySelector('input:checked') ||
258
e.target.value === defaultMode
259
? removeStorage('Transport')
260
: setStorage('Transport', e.target.value);
261
262
// Only the libcurl transport mode supports TOR at the moment.
263
let torCheck = document.getElementsByClassName('useonion');
264
if (
265
e.target.value !== 'libcurl' &&
266
checkBooleanState(torCheck[0]) === true
267
)
268
classUpdateHandler(torCheck, 'off', classEvent(torCheck, 'change'))();
269
}
270
});
271
272
attachClassEventListener('theme-list', 'change', (e) => {
273
if (e.target.checked) {
274
let themeList = e.target.closest('.theme-list');
275
if (
276
!themeList.querySelector('input:checked') ||
277
e.target.value === defaultTheme
278
) {
279
const theme = readStorage('Theme');
280
if (theme) document.documentElement.classList.toggle(theme, false);
281
removeStorage('Theme');
282
} else {
283
setStorage('Theme', e.target.value);
284
document.documentElement.classList.toggle(e.target.value, true);
285
}
286
(async () => {
287
const shouldLoad = await new Promise((resolve) => {
288
let tries = 0;
289
const load = () => {
290
if (!document.getElementById('background')) return resolve(false);
291
if ('function' === typeof self.loadFull) {
292
window.removeEventListener('load', load);
293
resolve(true);
294
} else if (tries < 5) {
295
tries++;
296
setTimeout(load, 1000);
297
}
298
};
299
if (document.readyState === 'complete') load();
300
else window.addEventListener('load', load);
301
});
302
if (!shouldLoad) return;
303
await loadFull(tsParticles);
304
const styles = getComputedStyle(document.documentElement);
305
306
await tsParticles.load({
307
id: 'background',
308
options: {
309
background: {
310
color: {
311
value: styles.getPropertyValue('--particles-bg') || '#1d232a',
312
},
313
},
314
fullScreen: {
315
enable: true,
316
zIndex: -1,
317
},
318
detectRetina: true,
319
fpsLimit: 60,
320
interactivity: {
321
events: {
322
resize: {
323
enable: true,
324
},
325
},
326
},
327
particles: {
328
color: {
329
value: styles.getPropertyValue('--particles-color') || '#ffffff',
330
},
331
move: {
332
enable: true,
333
speed: parseFloat(styles.getPropertyValue('--particles-mv-spd')) || 0.3,
334
direction: 'none',
335
outModes: {
336
default: 'out',
337
},
338
},
339
number: {
340
density: {
341
enable: true,
342
area: 800,
343
},
344
value: 100,
345
},
346
opacity: {
347
value: {
348
min: 0.1,
349
max: parseFloat(styles.getPropertyValue('--particles-op-max')) || 0.3,
350
},
351
animation: {
352
enable: true,
353
speed: parseFloat(styles.getPropertyValue('--particles-op-spd')) || 0.3,
354
sync: false,
355
},
356
},
357
shape: {
358
type: 'circle',
359
},
360
size: {
361
value: { min: 1, max: 5 },
362
animation: {
363
enable: true,
364
speed: parseFloat(styles.getPropertyValue('--particles-sz-spd')) || 0.3,
365
sync: false,
366
},
367
},
368
links: {
369
enable: true,
370
distance: 150,
371
color: styles.getPropertyValue('--particles-links') || '#ffffff',
372
opacity: parseFloat(styles.getPropertyValue('--particles-links-opacity')) || 0.4,
373
width: 1,
374
},
375
},
376
pauseOnBlur: true,
377
pauseOnOutsideViewport: true,
378
},
379
});
380
})();
381
}
382
});
383
384
// Allow users to toggle ads with the UI.
385
attachClassEventListener('hideads', 'change', (e) => {
386
if (checkBooleanState(e.target) === true) {
387
pageHideAds();
388
setStorage('HideAds', true);
389
} else {
390
pageShowAds();
391
setStorage('HideAds', false);
392
}
393
});
394
395
/* Allow users to toggle onion routing in Ultraviolet with the UI. Only
396
* the libcurl transport mode supports TOR at the moment, so ensure that
397
* users are aware that they cannot use TOR with other modes.
398
*/
399
attachClassEventListener('useonion', 'change', (e) => {
400
let unselectedModes = document.querySelectorAll(
401
'.{{wisp-transport}}-list input:not([value={{libcurl}}]),.region-list'
402
);
403
const wispTransportList = document.getElementsByClassName(
404
'{{wisp-transport}}-list'
405
),
406
regionList = document.getElementsByClassName('region-list');
407
if (checkBooleanState(e.target) === true) {
408
classUpdateHandler(
409
wispTransportList,
410
'{{libcurl}}',
411
classEvent(wispTransportList, 'change')
412
)();
413
classUpdateHandler(regionList, 'off', classEvent(regionList, 'change'))();
414
unselectedModes.forEach((e) => {
415
e.setAttribute('disabled', 'true');
416
});
417
setStorage('UseSocks5', 'tor');
418
classUpdateHandler(document.getElementsByClassName('useonion'), 'on')();
419
} else {
420
unselectedModes.forEach((e) => {
421
e.removeAttribute('disabled');
422
});
423
424
// Tor will likely never be enabled by default, so removing the cookie
425
// here may be better than setting it to false.
426
removeStorage('UseSocks5');
427
}
428
});
429
430
attachClassEventListener('useac', 'change', (e) => {
431
if (checkBooleanState(e.target) === false) setStorage('UseAC', false);
432
else removeStorage('UseAC');
433
});
434
435
attachClassEventListener('region-list', 'change', (e) => {
436
const isOff = checkBooleanState(e.target) === false;
437
isOff
438
? removeStorage('UseSocks5')
439
: setStorage('UseSocks5', e.target.value);
440
441
// TOR cannot be used at the same time as a regional selection.
442
// This is because they both run on the socks5 protocol.
443
let torCheck = document.getElementsByClassName('useonion');
444
if (!isOff && checkBooleanState(torCheck[0]) === true)
445
classUpdateHandler(torCheck, 'off')();
446
});
447
448
/* The Eruda devtools are an alternative to the Chii devtools.
449
attachClassEventListener('eruda', 'change', (e) => {
450
const enabled = checkBooleanState(e.target) === true;
451
452
if (enabled) {
453
setStorage('ErudaEnabled', true);
454
const moduleLocation = '{{route}}{{eruda/eruda.js}}';
455
456
import(moduleLocation).then((module) => {
457
if (!self.eruda || !self.eruda.init) return;
458
eruda.init();
459
delete eruda;
460
});
461
} else {
462
removeStorage('ErudaEnabled');
463
}
464
});
465
*/
466
}
467
468
/* END WEBSITE SETTINGS */
469
470
/* LOAD USER-SAVED SETTINGS */
471
472
// Load a custom page title and favicon if it was previously stored.
473
useStorageArgs('Title', (s) => {
474
s != undefined && pageTitle(s);
475
});
476
useStorageArgs('Icon', (s) => {
477
s != undefined && pageIcon(s);
478
});
479
480
useStorageArgs('Theme', (s) => {
481
const themeList = document.getElementsByClassName('theme-list');
482
classUpdateHandler(
483
themeList,
484
s || defaultTheme,
485
classEvent(themeList, 'change')
486
)();
487
});
488
489
useStorageArgs('SearchEngine', (s) => {
490
classUpdateHandler(
491
document.getElementsByClassName('search-engine-list'),
492
s || defaultSearch
493
)();
494
});
495
496
// Load the Wisp transport mode that was last used, or use the default.
497
useStorageArgs('Transport', (s) => {
498
classUpdateHandler(
499
document.getElementsByClassName('{{wisp-transport}}-list'),
500
s || defaultMode
501
)();
502
});
503
504
// Ads are disabled by default. Load ads if ads were enabled previously.
505
// Change !== to === here if ads should be enabled by default.
506
useStorageArgs('HideAds', (s) => {
507
if (s !== false) pageHideAds();
508
else {
509
pageShowAds();
510
classUpdateHandler(document.getElementsByClassName('hideads'), 'off')();
511
}
512
});
513
514
// TOR is disabled by default. Enable TOR if it was enabled previously.
515
useStorageArgs('UseSocks5', (s) => {
516
const tor = document.getElementsByClassName('useonion'),
517
regionList = document.getElementsByClassName('region-list');
518
if (s === 'tor') classUpdateHandler(tor, 'on', classEvent(tor, 'change'))();
519
else if ('string' === typeof s) classUpdateHandler(regionList, s)();
520
});
521
522
/*
523
useStorageArgs('ErudaEnabled', (s) => {
524
const erudaSwitch = document.getElementsByClassName('eruda');
525
526
if (s === true || s === 'true') {
527
classUpdateHandler(erudaSwitch, 'on', classEvent(erudaSwitch, 'change'))();
528
}
529
});
530
*/
531
532
useStorageArgs('UseAC', (s) => {
533
if (s === false)
534
classUpdateHandler(document.getElementsByClassName('useac'), 'off')();
535
});
536
})();
537
538