Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
QuiteAFancyEmerald
GitHub Repository: QuiteAFancyEmerald/Holy-Unblocker
Path: blob/master/views/assets/js/csel.js
5247 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 = /(?:Chrome|AppleWebKit)\//.test(navigator.userAgent)
145
? '{{epoxy}}'
146
: '{{libcurl}}',
147
defaultSearch = '{{defaultSearch}}';
148
149
// All code in this block is used by menu items that adjust website settings.
150
151
/* BEGIN WEBSITE SETTINGS */
152
153
if (document.getElementById('csel')) {
154
const attachEventListener = (selector, ...args) =>
155
(
156
document.getElementById(selector) || document.querySelector(selector)
157
).addEventListener(...args),
158
focusElement = document
159
.getElementsByClassName('dropdown-settings')[0]
160
.parentElement.querySelector("a[href='#']");
161
162
// TODO: Add functionality to adapt listeners for the Wisp Transport List.
163
// TODO: Properly comment this code.
164
const attachClassEventListener = (classSelector, ...args) => {
165
const eventTrigger = args[0],
166
selectorList = [...document.getElementsByClassName(classSelector)];
167
selectorList.forEach((element, index) => {
168
let otherElements = [...selectorList];
169
otherElements.splice(index, 1);
170
const elementValue = () =>
171
(element.querySelector('input:checked,option:checked') || []).value ||
172
element.checked;
173
const listeners = ['input', 'select'].includes(
174
element.tagName.toLowerCase()
175
)
176
? [element]
177
: element.querySelectorAll('input');
178
179
listeners.forEach((listener) => {
180
listener.addEventListener(...args);
181
listener.addEventListener(
182
eventTrigger,
183
classUpdateHandler(otherElements, elementValue)
184
);
185
});
186
});
187
};
188
189
attachEventListener('.dropdown-settings .close-settings-btn', 'click', () => {
190
document.activeElement.blur();
191
});
192
193
// Allow users to set a custom title with the UI.
194
attachEventListener('titleform', 'submit', (e) => {
195
e.preventDefault();
196
e = e.target.firstElementChild;
197
if (e.value) {
198
pageTitle(e.value);
199
setStorage('Title', e.value);
200
e.value = '';
201
} else if (confirm('Reset the title to default?')) {
202
// Allow users to reset the title to default if nothing is entered.
203
focusElement.focus();
204
removeStorage('Title');
205
pageTitle('Holy Unblocker LTS');
206
}
207
});
208
209
// Allow users to set a custom favicon with the UI.
210
attachEventListener('iconform', 'submit', (e) => {
211
e.preventDefault();
212
e = e.target.firstElementChild;
213
if (e.value) {
214
pageIcon(e.value);
215
setStorage('Icon', e.value);
216
e.value = '';
217
} else if (confirm('Reset the icon to default?')) {
218
// Allow users to reset the favicon to default if nothing is entered.
219
focusElement.focus();
220
removeStorage('Icon');
221
pageIcon('{{route}}{{assets/ico/favicon.ico}}');
222
}
223
});
224
225
/*
226
227
This is unused in the current settings menu.
228
229
// Allow users to make a new about:blank tab and view the site from there.
230
// An iframe of the current page is inserted into the new tab.
231
attachEventListener("cselab", "click", () => {
232
let win = window.open();
233
let iframe = win.document.createElement("iframe");
234
iframe.style = "width: 100%; height: 100%; border: none; overflow: hidden; margin: 0; padding: 0; position: fixed; top: 0; left: 0";
235
iframe.src = location.href;
236
win.document.body.appendChild(iframe);
237
});
238
*/
239
240
// Provides users with a handy set of title and icon autofill options.
241
attachEventListener('icon-list', 'change', (e) => {
242
let titleform = document.getElementById('titleform'),
243
iconform = document.getElementById('iconform');
244
[titleform.firstElementChild.value, iconform.firstElementChild.value] = (
245
presetIcons[e.target.value] || ' \n '
246
).split(' \n ');
247
});
248
249
attachClassEventListener('search-engine-list', 'change', (e) => {
250
e.target.value === defaultSearch
251
? removeStorage('SearchEngine')
252
: setStorage('SearchEngine', e.target.value);
253
});
254
255
// Allow users to change the Wisp transport mode, for proxying, with the UI.
256
attachClassEventListener('{{wisp-transport}}-list', 'change', (e) => {
257
if (e.target.checked) {
258
let wispTransportList = e.target.closest('.{{wisp-transport}}-list');
259
!wispTransportList.querySelector('input:checked') ||
260
e.target.value === defaultMode
261
? removeStorage('Transport')
262
: setStorage('Transport', e.target.value);
263
264
// Only the libcurl transport mode supports TOR at the moment.
265
let torCheck = document.getElementsByClassName('useonion');
266
if (
267
e.target.value !== 'libcurl' &&
268
checkBooleanState(torCheck[0]) === true
269
)
270
classUpdateHandler(torCheck, 'off', classEvent(torCheck, 'change'))();
271
}
272
});
273
274
attachClassEventListener('theme-list', 'change', (e) => {
275
if (e.target.checked) {
276
let themeList = e.target.closest('.theme-list');
277
if (
278
!themeList.querySelector('input:checked') ||
279
e.target.value === defaultTheme
280
) {
281
const theme = readStorage('Theme');
282
if (theme) document.documentElement.classList.toggle(theme, false);
283
removeStorage('Theme');
284
} else {
285
setStorage('Theme', e.target.value);
286
document.documentElement.classList.toggle(e.target.value, true);
287
}
288
(async () => {
289
const shouldLoad = await new Promise((resolve) => {
290
let tries = 0;
291
const load = () => {
292
if (!document.getElementById('background')) return resolve(false);
293
if ('function' === typeof self.loadFull) {
294
window.removeEventListener('load', load);
295
resolve(true);
296
} else if (tries < 5) {
297
tries++;
298
setTimeout(load, 1000);
299
}
300
};
301
if (document.readyState === 'complete') load();
302
else window.addEventListener('load', load);
303
});
304
if (!shouldLoad) return;
305
await loadFull(tsParticles);
306
const styles = getComputedStyle(document.documentElement);
307
308
await tsParticles.load({
309
id: 'background',
310
options: {
311
background: {
312
color: {
313
value: styles.getPropertyValue('--particles-bg') || '#1d232a',
314
},
315
},
316
fullScreen: {
317
enable: true,
318
zIndex: -1,
319
},
320
detectRetina: true,
321
fpsLimit: 60,
322
interactivity: {
323
events: {
324
resize: {
325
enable: true,
326
},
327
},
328
},
329
particles: {
330
color: {
331
value: styles.getPropertyValue('--particles-color') || '#ffffff',
332
},
333
move: {
334
enable: true,
335
speed: parseFloat(styles.getPropertyValue('--particles-mv-spd')) || 0.3,
336
direction: 'none',
337
outModes: {
338
default: 'out',
339
},
340
},
341
number: {
342
density: {
343
enable: true,
344
area: 800,
345
},
346
value: 100,
347
},
348
opacity: {
349
value: {
350
min: 0.1,
351
max: parseFloat(styles.getPropertyValue('--particles-op-max')) || 0.3,
352
},
353
animation: {
354
enable: true,
355
speed: parseFloat(styles.getPropertyValue('--particles-op-spd')) || 0.3,
356
sync: false,
357
},
358
},
359
shape: {
360
type: 'circle',
361
},
362
size: {
363
value: { min: 1, max: 5 },
364
animation: {
365
enable: true,
366
speed: parseFloat(styles.getPropertyValue('--particles-sz-spd')) || 0.3,
367
sync: false,
368
},
369
},
370
links: {
371
enable: true,
372
distance: 150,
373
color: styles.getPropertyValue('--particles-links') || '#ffffff',
374
opacity: parseFloat(styles.getPropertyValue('--particles-links-opacity')) || 0.4,
375
width: 1,
376
},
377
},
378
pauseOnBlur: true,
379
pauseOnOutsideViewport: true,
380
},
381
});
382
})();
383
}
384
});
385
386
// Allow users to toggle ads with the UI.
387
attachClassEventListener('hideads', 'change', (e) => {
388
if (checkBooleanState(e.target) === true) {
389
pageHideAds();
390
setStorage('HideAds', true);
391
} else {
392
pageShowAds();
393
setStorage('HideAds', false);
394
}
395
});
396
397
/* Allow users to toggle onion routing in Ultraviolet with the UI. Only
398
* the libcurl transport mode supports TOR at the moment, so ensure that
399
* users are aware that they cannot use TOR with other modes.
400
*/
401
attachClassEventListener('useonion', 'change', (e) => {
402
let unselectedModes = document.querySelectorAll(
403
'.{{wisp-transport}}-list input:not([value={{libcurl}}]),.region-list'
404
);
405
const wispTransportList = document.getElementsByClassName(
406
'{{wisp-transport}}-list'
407
),
408
regionList = document.getElementsByClassName('region-list');
409
if (checkBooleanState(e.target) === true) {
410
classUpdateHandler(
411
wispTransportList,
412
'{{libcurl}}',
413
classEvent(wispTransportList, 'change')
414
)();
415
classUpdateHandler(regionList, 'off', classEvent(regionList, 'change'))();
416
unselectedModes.forEach((e) => {
417
e.setAttribute('disabled', 'true');
418
});
419
setStorage('UseSocks5', 'tor');
420
classUpdateHandler(document.getElementsByClassName('useonion'), 'on')();
421
} else {
422
unselectedModes.forEach((e) => {
423
e.removeAttribute('disabled');
424
});
425
426
// Tor will likely never be enabled by default, so removing the cookie
427
// here may be better than setting it to false.
428
removeStorage('UseSocks5');
429
}
430
});
431
432
attachClassEventListener('useac', 'change', (e) => {
433
if (checkBooleanState(e.target) === false) setStorage('UseAC', false);
434
else removeStorage('UseAC');
435
});
436
437
attachClassEventListener('region-list', 'change', (e) => {
438
const isOff = checkBooleanState(e.target) === false;
439
isOff
440
? removeStorage('UseSocks5')
441
: setStorage('UseSocks5', e.target.value);
442
443
// TOR cannot be used at the same time as a regional selection.
444
// This is because they both run on the socks5 protocol.
445
let torCheck = document.getElementsByClassName('useonion');
446
if (!isOff && checkBooleanState(torCheck[0]) === true)
447
classUpdateHandler(torCheck, 'off')();
448
});
449
450
/* The Eruda devtools are an alternative to the Chii devtools.
451
attachClassEventListener('eruda', 'change', (e) => {
452
const enabled = checkBooleanState(e.target) === true;
453
454
if (enabled) {
455
setStorage('ErudaEnabled', true);
456
const moduleLocation = '{{route}}{{eruda/eruda.js}}';
457
458
import(moduleLocation).then((module) => {
459
if (!self.eruda || !self.eruda.init) return;
460
eruda.init();
461
delete eruda;
462
});
463
} else {
464
removeStorage('ErudaEnabled');
465
}
466
});
467
*/
468
}
469
470
/* END WEBSITE SETTINGS */
471
472
/* LOAD USER-SAVED SETTINGS */
473
474
// Load a custom page title and favicon if it was previously stored.
475
useStorageArgs('Title', (s) => {
476
s != undefined && pageTitle(s);
477
});
478
useStorageArgs('Icon', (s) => {
479
s != undefined && pageIcon(s);
480
});
481
482
useStorageArgs('Theme', (s) => {
483
const themeList = document.getElementsByClassName('theme-list');
484
classUpdateHandler(
485
themeList,
486
s || defaultTheme,
487
classEvent(themeList, 'change')
488
)();
489
});
490
491
useStorageArgs('SearchEngine', (s) => {
492
classUpdateHandler(
493
document.getElementsByClassName('search-engine-list'),
494
s || defaultSearch
495
)();
496
});
497
498
// Load the Wisp transport mode that was last used, or use the default.
499
useStorageArgs('Transport', (s) => {
500
classUpdateHandler(
501
document.getElementsByClassName('{{wisp-transport}}-list'),
502
s || defaultMode
503
)();
504
});
505
506
// Ads are disabled by default. Load ads if ads were enabled previously.
507
// Change !== to === here if ads should be enabled by default.
508
useStorageArgs('HideAds', (s) => {
509
if (s !== false) pageHideAds();
510
else {
511
pageShowAds();
512
classUpdateHandler(document.getElementsByClassName('hideads'), 'off')();
513
}
514
});
515
516
// TOR is disabled by default. Enable TOR if it was enabled previously.
517
useStorageArgs('UseSocks5', (s) => {
518
const tor = document.getElementsByClassName('useonion'),
519
regionList = document.getElementsByClassName('region-list');
520
if (s === 'tor') classUpdateHandler(tor, 'on', classEvent(tor, 'change'))();
521
else if ('string' === typeof s) classUpdateHandler(regionList, s)();
522
});
523
524
/*
525
useStorageArgs('ErudaEnabled', (s) => {
526
const erudaSwitch = document.getElementsByClassName('eruda');
527
528
if (s === true || s === 'true') {
529
classUpdateHandler(erudaSwitch, 'on', classEvent(erudaSwitch, 'change'))();
530
}
531
});
532
*/
533
534
useStorageArgs('UseAC', (s) => {
535
if (s === false)
536
classUpdateHandler(document.getElementsByClassName('useac'), 'off')();
537
});
538
})();
539
540