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