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