Formatting wiht prettier
This commit is contained in:
parent
8bf25954a3
commit
cf8216da00
|
@ -0,0 +1,3 @@
|
||||||
|
src/_locales/
|
||||||
|
.github/
|
||||||
|
.gitea/
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
|
@ -11,34 +11,42 @@ A browser extension that redirects YouTube, Twitter, TikTok... requests to alter
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Translate
|
## Translate
|
||||||
|
|
||||||
<a href="https://hosted.weblate.org/projects/libredirect/extension">
|
<a href="https://hosted.weblate.org/projects/libredirect/extension">
|
||||||
<img src ="./img/weblate.svg">
|
<img src ="./img/weblate.svg">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Install [Node.js](https://nodejs.org/)
|
Install [Node.js](https://nodejs.org/)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/libredirect/browser_extension
|
git clone https://github.com/libredirect/browser_extension
|
||||||
cd browser_extension
|
cd browser_extension
|
||||||
npm install
|
npm install
|
||||||
npm run html # Generates html using Pug
|
npm run html # Generates html using Pug
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run on Firefox
|
#### Run on Firefox
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start
|
npm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Build a zip package for Firefox
|
#### Build a zip package for Firefox
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Install the zip package on Firefox (temporarily)
|
#### Install the zip package on Firefox (temporarily)
|
||||||
|
|
||||||
1. Type in the address bar: `about:debugging#/runtime/this-firefox`
|
1. Type in the address bar: `about:debugging#/runtime/this-firefox`
|
||||||
2. Press `Load Temporary Add-on...`
|
2. Press `Load Temporary Add-on...`
|
||||||
3. Select `libredirect-VERSION.zip` from `web-ext-artifacts` folder
|
3. Select `libredirect-VERSION.zip` from `web-ext-artifacts` folder
|
||||||
|
|
||||||
#### Install the zip package on Firefox ESR, Developer Edition, Nightly
|
#### Install the zip package on Firefox ESR, Developer Edition, Nightly
|
||||||
|
|
||||||
1. Type in the address bar: `about:config`
|
1. Type in the address bar: `about:config`
|
||||||
2. Set `xpinstall.signatures.required` to `false`
|
2. Set `xpinstall.signatures.required` to `false`
|
||||||
3. Type in the address bar: `about:addons`
|
3. Type in the address bar: `about:addons`
|
||||||
|
@ -46,6 +54,7 @@ npm run build
|
||||||
5. Select `libredirect-VERSION.zip` from `web-ext-artifacts` folder
|
5. Select `libredirect-VERSION.zip` from `web-ext-artifacts` folder
|
||||||
|
|
||||||
#### Run on Chromium
|
#### Run on Chromium
|
||||||
|
|
||||||
1. Open `chrome://extensions`
|
1. Open `chrome://extensions`
|
||||||
2. Enable `dev mode`
|
2. Enable `dev mode`
|
||||||
3. Select `load unpacked extension`
|
3. Select `load unpacked extension`
|
||||||
|
|
74
package.json
74
package.json
|
@ -1,39 +1,39 @@
|
||||||
{
|
{
|
||||||
"name": "libredirect",
|
"name": "libredirect",
|
||||||
"description": "Redirects YouTube, Twitter, TikTok and more to privacy friendly frontends.",
|
"description": "Redirects YouTube, Twitter, TikTok and more to privacy friendly frontends.",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.13.1",
|
"node": ">=16.13.1",
|
||||||
"npm": ">=8.1.2"
|
"npm": ">=8.1.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "web-ext run",
|
"start": "web-ext run",
|
||||||
"nightly": "web-ext run --firefox=/home/esmail/software/firefox_nightly/firefox",
|
"nightly": "web-ext run --firefox=/home/esmail/software/firefox_nightly/firefox",
|
||||||
"android": "web-ext run -t firefox-android --adb-device emulator-5554 --firefox-apk org.mozilla.fenix",
|
"android": "web-ext run -t firefox-android --adb-device emulator-5554 --firefox-apk org.mozilla.fenix",
|
||||||
"build": "web-ext build",
|
"build": "web-ext build",
|
||||||
"test": "web-ext lint",
|
"test": "web-ext lint",
|
||||||
"html": "pug --basedir ./ --obj ./src/config.json src/pages/options/index.pug --out src/pages/options/ && pug --basedir ./ --obj ./src/config.json src/pages/popup/popup.pug --out src/pages/popup/"
|
"html": "pug --basedir ./ --obj ./src/config.json src/pages/options/index.pug --out src/pages/options/ && pug --basedir ./ --obj ./src/config.json src/pages/popup/popup.pug --out src/pages/popup/"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/libredirect/libredirect.git"
|
"url": "git+https://github.com/libredirect/libredirect.git"
|
||||||
},
|
},
|
||||||
"author": "LibRedirect",
|
"author": "LibRedirect",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/libredirect/libredirect/issues"
|
"url": "https://github.com/libredirect/libredirect/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://libredirect.github.io",
|
"homepage": "https://libredirect.github.io",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"pug-cli": "^1.0.0-alpha6",
|
"pug-cli": "^1.0.0-alpha6",
|
||||||
"web-ext": "^7.2.0"
|
"web-ext": "^7.2.0"
|
||||||
},
|
},
|
||||||
"webExt": {
|
"webExt": {
|
||||||
"sourceDir": "./src/",
|
"sourceDir": "./src/",
|
||||||
"run": {
|
"run": {
|
||||||
"browserConsole": true
|
"browserConsole": true
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"overwriteDest": true
|
"overwriteDest": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
window.browser = window.browser || window.chrome
|
window.browser = window.browser || window.chrome
|
||||||
|
|
||||||
function localisePage() {
|
function localisePage() {
|
||||||
/**
|
/**
|
||||||
* @param {string} tag
|
* @param {string} tag
|
||||||
*/
|
*/
|
||||||
function getMessage(tag) {
|
function getMessage(tag) {
|
||||||
return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => {
|
return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => {
|
||||||
return v1 ? browser.i18n.getMessage(v1) : null
|
return v1 ? browser.i18n.getMessage(v1) : null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = document.querySelectorAll("[data-localise]")
|
const elements = document.querySelectorAll("[data-localise]")
|
||||||
for (let i in elements)
|
for (let i in elements)
|
||||||
if (elements.hasOwnProperty(i)) {
|
if (elements.hasOwnProperty(i)) {
|
||||||
const obj = elements[i]
|
const obj = elements[i]
|
||||||
const tag = obj.getAttribute("data-localise").toString()
|
const tag = obj.getAttribute("data-localise").toString()
|
||||||
const msg = getMessage(tag)
|
const msg = getMessage(tag)
|
||||||
if (msg && msg !== tag) obj.textContent = msg
|
if (msg && msg !== tag) obj.textContent = msg
|
||||||
}
|
}
|
||||||
|
|
||||||
const placeholders = document.querySelectorAll("[data-localise-placeholder]")
|
const placeholders = document.querySelectorAll("[data-localise-placeholder]")
|
||||||
for (let i in placeholders)
|
for (let i in placeholders)
|
||||||
if (placeholders.hasOwnProperty(i)) {
|
if (placeholders.hasOwnProperty(i)) {
|
||||||
const obj = placeholders[i]
|
const obj = placeholders[i]
|
||||||
const tag = obj.getAttribute("data-localise-placeholder").toString()
|
const tag = obj.getAttribute("data-localise-placeholder").toString()
|
||||||
const msg = getMessage(tag)
|
const msg = getMessage(tag)
|
||||||
if (msg && msg !== tag) obj.placeholder = msg
|
if (msg && msg !== tag) obj.placeholder = msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
localisePage,
|
localisePage,
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@ window.browser = window.browser || window.chrome
|
||||||
* @returns {T}
|
* @returns {T}
|
||||||
*/
|
*/
|
||||||
function getRandomInstance(instances) {
|
function getRandomInstance(instances) {
|
||||||
return instances[~~(instances.length * Math.random())]
|
return instances[~~(instances.length * Math.random())]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,27 +14,28 @@ function getRandomInstance(instances) {
|
||||||
* @returns {T}
|
* @returns {T}
|
||||||
*/
|
*/
|
||||||
function getNextInstance(currentInstanceUrl, instances) {
|
function getNextInstance(currentInstanceUrl, instances) {
|
||||||
const currentInstanceIndex = instances.indexOf(currentInstanceUrl);
|
const currentInstanceIndex = instances.indexOf(currentInstanceUrl)
|
||||||
if (currentInstanceIndex === -1) return getRandomInstance(instances);
|
if (currentInstanceIndex === -1) return getRandomInstance(instances)
|
||||||
const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length;
|
const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length
|
||||||
return instances[nextInstanceIndex];
|
return instances[nextInstanceIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
*/
|
*/
|
||||||
function camelCase(str) {
|
function camelCase(str) {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {URL} url
|
* @param {URL} url
|
||||||
*/
|
*/
|
||||||
function protocolHost(url) {
|
function protocolHost(url) {
|
||||||
if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}`
|
if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}`
|
||||||
if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io")) // workaround
|
if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io"))
|
||||||
return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
|
// workaround
|
||||||
return `${url.protocol}//${url.host}`
|
return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
|
||||||
|
return `${url.protocol}//${url.host}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,14 +60,14 @@ function protocolHost(url) {
|
||||||
* @returns {Promise<Config>}
|
* @returns {Promise<Config>}
|
||||||
*/
|
*/
|
||||||
function getConfig() {
|
function getConfig() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
fetch("/config.json")
|
fetch("/config.json")
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
resolve(JSON.parse(json))
|
resolve(JSON.parse(json))
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,106 +79,108 @@ function getConfig() {
|
||||||
* @returns {Promise<Object.<string, Option | string[]>>}
|
* @returns {Promise<Object.<string, Option | string[]>>}
|
||||||
*/
|
*/
|
||||||
function getOptions() {
|
function getOptions() {
|
||||||
return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options)))
|
return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options)))
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPingCache() {
|
function getPingCache() {
|
||||||
return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {})))
|
return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {})))
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBlacklist(options) {
|
function getBlacklist(options) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let url
|
let url
|
||||||
if (options.fetchInstances == 'github') url = 'https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json'
|
if (options.fetchInstances == "github")
|
||||||
else if (options.fetchInstances == 'codeberg') url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json'
|
url = "https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json"
|
||||||
else return resolve('disabled')
|
else if (options.fetchInstances == "codeberg")
|
||||||
const http = new XMLHttpRequest()
|
url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json"
|
||||||
http.open("GET", url, true)
|
else return resolve("disabled")
|
||||||
http.onreadystatechange = () => {
|
const http = new XMLHttpRequest()
|
||||||
if (http.status === 200 && http.readyState == XMLHttpRequest.DONE)
|
http.open("GET", url, true)
|
||||||
resolve(JSON.parse(http.responseText))
|
http.onreadystatechange = () => {
|
||||||
}
|
if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) resolve(JSON.parse(http.responseText))
|
||||||
http.onerror = () => resolve()
|
}
|
||||||
http.ontimeout = () => resolve()
|
http.onerror = () => resolve()
|
||||||
http.send(null)
|
http.ontimeout = () => resolve()
|
||||||
})
|
http.send(null)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getList(options) {
|
function getList(options) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let url
|
let url
|
||||||
if (options.fetchInstances == 'github') url = 'https://raw.githubusercontent.com/libredirect/instances/main/data.json'
|
if (options.fetchInstances == "github")
|
||||||
else if (options.fetchInstances == 'codeberg') url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json'
|
url = "https://raw.githubusercontent.com/libredirect/instances/main/data.json"
|
||||||
else return resolve('disabled')
|
else if (options.fetchInstances == "codeberg")
|
||||||
const http = new XMLHttpRequest()
|
url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json"
|
||||||
http.open("GET", url, true)
|
else return resolve("disabled")
|
||||||
http.onreadystatechange = () => {
|
const http = new XMLHttpRequest()
|
||||||
if (http.status === 200 && http.readyState == XMLHttpRequest.DONE)
|
http.open("GET", url, true)
|
||||||
return resolve(JSON.parse(http.responseText))
|
http.onreadystatechange = () => {
|
||||||
}
|
if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) return resolve(JSON.parse(http.responseText))
|
||||||
http.onerror = () => resolve()
|
}
|
||||||
http.ontimeout = () => resolve()
|
http.onerror = () => resolve()
|
||||||
http.send(null)
|
http.ontimeout = () => resolve()
|
||||||
})
|
http.send(null)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} href
|
* @param {string} href
|
||||||
*/
|
*/
|
||||||
function pingOnce(href) {
|
function pingOnce(href) {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
let started
|
let started
|
||||||
let http = new XMLHttpRequest()
|
let http = new XMLHttpRequest()
|
||||||
http.timeout = 5000
|
http.timeout = 5000
|
||||||
http.ontimeout = () => resolve(5000)
|
http.ontimeout = () => resolve(5000)
|
||||||
http.onerror = () => resolve()
|
http.onerror = () => resolve()
|
||||||
http.onreadystatechange = () => {
|
http.onreadystatechange = () => {
|
||||||
if (http.readyState == 2) {
|
if (http.readyState == 2) {
|
||||||
if (http.status == 200) {
|
if (http.status == 200) {
|
||||||
let ended = new Date().getTime()
|
let ended = new Date().getTime()
|
||||||
http.abort()
|
http.abort()
|
||||||
resolve(ended - started)
|
resolve(ended - started)
|
||||||
} else {
|
} else {
|
||||||
resolve(5000 + http.status)
|
resolve(5000 + http.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.open("GET", `${href}?_=${new Date().getTime()}`, true)
|
http.open("GET", `${href}?_=${new Date().getTime()}`, true)
|
||||||
started = new Date().getTime()
|
started = new Date().getTime()
|
||||||
http.send(null)
|
http.send(null)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} href
|
* @param {string} href
|
||||||
*/
|
*/
|
||||||
function ping(href) {
|
function ping(href) {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
let average = 0
|
let average = 0
|
||||||
let time
|
let time
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
time = await pingOnce(href)
|
time = await pingOnce(href)
|
||||||
if (i == 0) continue
|
if (i == 0) continue
|
||||||
if (time >= 5000) {
|
if (time >= 5000) {
|
||||||
resolve(time)
|
resolve(time)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
average += time
|
average += time
|
||||||
}
|
}
|
||||||
average = parseInt(average / 3)
|
average = parseInt(average / 3)
|
||||||
resolve(average)
|
resolve(average)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getRandomInstance,
|
getRandomInstance,
|
||||||
getNextInstance,
|
getNextInstance,
|
||||||
protocolHost,
|
protocolHost,
|
||||||
getList,
|
getList,
|
||||||
getBlacklist,
|
getBlacklist,
|
||||||
camelCase,
|
camelCase,
|
||||||
getConfig,
|
getConfig,
|
||||||
getOptions,
|
getOptions,
|
||||||
getPingCache,
|
getPingCache,
|
||||||
ping,
|
ping,
|
||||||
}
|
}
|
||||||
|
|
2156
src/config.json
2156
src/config.json
File diff suppressed because it is too large
Load Diff
|
@ -1,91 +1,82 @@
|
||||||
{
|
{
|
||||||
"name": "__MSG_extensionName__",
|
"name": "__MSG_extensionName__",
|
||||||
"description": "__MSG_extensionDescription__",
|
"description": "__MSG_extensionDescription__",
|
||||||
"version": "2.8.5",
|
"version": "2.8.5",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "7esoorv3@alefvanoon.anonaddy.me",
|
"id": "7esoorv3@alefvanoon.anonaddy.me",
|
||||||
"strict_min_version": "89.0"
|
"strict_min_version": "89.0"
|
||||||
},
|
},
|
||||||
"gecko_android": {
|
"gecko_android": {
|
||||||
"strict_min_version": "113.0"
|
"strict_min_version": "113.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"page": "pages/background/background.html",
|
"page": "pages/background/background.html",
|
||||||
"persistent": true
|
"persistent": true
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "assets/images/libredirect-16.png",
|
"16": "assets/images/libredirect-16.png",
|
||||||
"32": "assets/images/libredirect-32.png",
|
"32": "assets/images/libredirect-32.png",
|
||||||
"48": "assets/images/libredirect-48.png",
|
"48": "assets/images/libredirect-48.png",
|
||||||
"128": "assets/images/libredirect-128.png"
|
"128": "assets/images/libredirect-128.png"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": ["webRequest", "webRequestBlocking", "storage", "clipboardWrite", "contextMenus", "<all_urls>"],
|
||||||
"webRequest",
|
"optional_permissions": ["bookmarks"],
|
||||||
"webRequestBlocking",
|
"browser_action": {
|
||||||
"storage",
|
"default_title": "__MSG_extensionName__",
|
||||||
"clipboardWrite",
|
"browser_style": false,
|
||||||
"contextMenus",
|
"default_popup": "pages/popup/popup.html",
|
||||||
"<all_urls>"
|
"default_icon": {
|
||||||
],
|
"16": "assets/images/libredirect-16.png",
|
||||||
"optional_permissions": [
|
"32": "assets/images/libredirect-32.png",
|
||||||
"bookmarks"
|
"48": "assets/images/libredirect-48.png",
|
||||||
],
|
"128": "assets/images/libredirect-128.png"
|
||||||
"browser_action": {
|
}
|
||||||
"default_title": "__MSG_extensionName__",
|
},
|
||||||
"browser_style": false,
|
"options_ui": {
|
||||||
"default_popup": "pages/popup/popup.html",
|
"page": "pages/options/index.html",
|
||||||
"default_icon": {
|
"browser_style": false,
|
||||||
"16": "assets/images/libredirect-16.png",
|
"open_in_tab": true
|
||||||
"32": "assets/images/libredirect-32.png",
|
},
|
||||||
"48": "assets/images/libredirect-48.png",
|
"chrome_settings_overrides": {
|
||||||
"128": "assets/images/libredirect-128.png"
|
"search_provider": {
|
||||||
}
|
"name": "__MSG_extensionName__",
|
||||||
},
|
"keyword": "@libredirect",
|
||||||
"options_ui": {
|
"favicon_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/assets/images/libredirect-16.png",
|
||||||
"page": "pages/options/index.html",
|
"search_url": "https://search.libredirect.invalid/?q={searchTerms}",
|
||||||
"browser_style": false,
|
"encoding": "UTF-8",
|
||||||
"open_in_tab": true
|
"is_default": false
|
||||||
},
|
}
|
||||||
"chrome_settings_overrides": {
|
},
|
||||||
"search_provider": {
|
"commands": {
|
||||||
"name": "__MSG_extensionName__",
|
"switchInstance": {
|
||||||
"keyword": "@libredirect",
|
"suggested_key": {
|
||||||
"favicon_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/assets/images/libredirect-16.png",
|
"default": "Alt+Shift+L"
|
||||||
"search_url": "https://search.libredirect.invalid/?q={searchTerms}",
|
},
|
||||||
"encoding": "UTF-8",
|
"description": "__MSG_switchInstance__"
|
||||||
"is_default": false
|
},
|
||||||
}
|
"copyRaw": {
|
||||||
},
|
"suggested_key": {
|
||||||
"commands": {
|
"default": "Alt+Shift+C"
|
||||||
"switchInstance": {
|
},
|
||||||
"suggested_key": {
|
"description": "Copies the original link. Ex: Copies the original twitter link while in the nitter website"
|
||||||
"default": "Alt+Shift+L"
|
},
|
||||||
},
|
"reverse": {
|
||||||
"description": "__MSG_switchInstance__"
|
"suggested_key": {
|
||||||
},
|
"default": "Alt+Shift+O"
|
||||||
"copyRaw": {
|
},
|
||||||
"suggested_key": {
|
"description": "Redirect to the original link. Ex: Redirects to the original twitter link while in the nitter website"
|
||||||
"default": "Alt+Shift+C"
|
},
|
||||||
},
|
"redirect": {
|
||||||
"description": "Copies the original link. Ex: Copies the original twitter link while in the nitter website"
|
"suggested_key": {
|
||||||
},
|
"default": "Alt+Shift+R"
|
||||||
"reverse": {
|
},
|
||||||
"suggested_key": {
|
"description": "Redirect link. Ex: Redirects original twitter link to nitter"
|
||||||
"default": "Alt+Shift+O"
|
}
|
||||||
},
|
},
|
||||||
"description": "Redirect to the original link. Ex: Redirects to the original twitter link while in the nitter website"
|
"default_locale": "en",
|
||||||
},
|
"update_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/updates/updates.xml",
|
||||||
"redirect": {
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAroWDSoSRZ1scj+eJRrvnhJbrqXTKnhQuxs6+AJg16sqr0bsMdFV+MSY4i4xnK+K5WOYkBliWXgUyk/wzicoAjOnSJddrL/Md4FuWHI2NVIkrlsLOrYkygi5OLqGPajRH/w8Cdmg7KzEpXe/OnYV0/qS8li8huEdTzdeLdhfbiVl1j3DOr4OJALQ7mPeeNFHFo/oVQ+OkSezWLezA5jUGfhtzPYV6u1TXzX7lCi8E/BbDbwkvvXOMcjXCv08kjdLOY2djCA2a6zr0xAb3q8DlexAMZ8vMof7AQRFtBKhLc9n9VFoipMMdBOVQQj/eIcRILBrmkcZNnJxFKiHNJ+NcZQIDAQAB"
|
||||||
"suggested_key": {
|
|
||||||
"default": "Alt+Shift+R"
|
|
||||||
},
|
|
||||||
"description": "Redirect link. Ex: Redirects original twitter link to nitter"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default_locale": "en",
|
|
||||||
"update_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/updates/updates.xml",
|
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAroWDSoSRZ1scj+eJRrvnhJbrqXTKnhQuxs6+AJg16sqr0bsMdFV+MSY4i4xnK+K5WOYkBliWXgUyk/wzicoAjOnSJddrL/Md4FuWHI2NVIkrlsLOrYkygi5OLqGPajRH/w8Cdmg7KzEpXe/OnYV0/qS8li8huEdTzdeLdhfbiVl1j3DOr4OJALQ7mPeeNFHFo/oVQ+OkSezWLezA5jUGfhtzPYV6u1TXzX7lCi8E/BbDbwkvvXOMcjXCv08kjdLOY2djCA2a6zr0xAb3q8DlexAMZ8vMof7AQRFtBKhLc9n9VFoipMMdBOVQQj/eIcRILBrmkcZNnJxFKiHNJ+NcZQIDAQAB"
|
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script type="module" src="background.js"></script>
|
<script type="module" src="background.js"></script>
|
||||||
</head>
|
</head>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -7,310 +7,324 @@ const isChrome = browser.runtime.getBrowserInfo === undefined
|
||||||
window.browser = window.browser || window.chrome
|
window.browser = window.browser || window.chrome
|
||||||
|
|
||||||
browser.runtime.onInstalled.addListener(async details => {
|
browser.runtime.onInstalled.addListener(async details => {
|
||||||
if (details.previousVersion != browser.runtime.getManifest().version) {
|
if (details.previousVersion != browser.runtime.getManifest().version) {
|
||||||
// ^Used to prevent this running when debugging with auto-reload
|
// ^Used to prevent this running when debugging with auto-reload
|
||||||
if (details.reason == "install") {
|
if (details.reason == "install") {
|
||||||
if (!(await utils.getOptions())) {
|
if (!(await utils.getOptions())) {
|
||||||
await servicesHelper.initDefaults()
|
await servicesHelper.initDefaults()
|
||||||
}
|
}
|
||||||
browser.runtime.openOptionsPage()
|
browser.runtime.openOptionsPage()
|
||||||
}
|
} else if (details.reason == "update") {
|
||||||
else if (details.reason == "update") {
|
if (details.previousVersion == "2.5.2") {
|
||||||
if (details.previousVersion == '2.5.2') {
|
await servicesHelper.upgradeOptions()
|
||||||
await servicesHelper.upgradeOptions()
|
await servicesHelper.processUpdate()
|
||||||
await servicesHelper.processUpdate()
|
} else {
|
||||||
} else {
|
await servicesHelper.processUpdate()
|
||||||
await servicesHelper.processUpdate()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let tabIdRedirects = {}
|
let tabIdRedirects = {}
|
||||||
|
|
||||||
// true == Always redirect, false == Never redirect, null/undefined == follow options for services
|
// true == Always redirect, false == Never redirect, null/undefined == follow options for services
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
details => {
|
details => {
|
||||||
const url = new URL(details.url)
|
const url = new URL(details.url)
|
||||||
const old_href = url.href
|
const old_href = url.href
|
||||||
if (new RegExp(/^chrome-extension:\/{2}.*\/instances\/.*.json$/).test(url.href) && details.type == "xmlhttprequest") return
|
if (new RegExp(/^chrome-extension:\/{2}.*\/instances\/.*.json$/).test(url.href) && details.type == "xmlhttprequest")
|
||||||
let initiator
|
return
|
||||||
try {
|
let initiator
|
||||||
if (details.originUrl) initiator = new URL(details.originUrl)
|
try {
|
||||||
else if (details.initiator && details.initiator !== "null") initiator = new URL(details.initiator)
|
if (details.originUrl) initiator = new URL(details.originUrl)
|
||||||
} catch {
|
else if (details.initiator && details.initiator !== "null") initiator = new URL(details.initiator)
|
||||||
return null
|
} catch {
|
||||||
}
|
return null
|
||||||
if (tabIdRedirects[details.tabId] == false) return null
|
}
|
||||||
let newUrl = servicesHelper.redirect(url, details.type, initiator, tabIdRedirects[details.tabId], details.incognito)
|
if (tabIdRedirects[details.tabId] == false) return null
|
||||||
|
let newUrl = servicesHelper.redirect(url, details.type, initiator, tabIdRedirects[details.tabId], details.incognito)
|
||||||
|
|
||||||
if (details.frameAncestors && details.frameAncestors.length > 0 && servicesHelper.isException(new URL(details.frameAncestors[0].url))) newUrl = null
|
if (
|
||||||
|
details.frameAncestors &&
|
||||||
|
details.frameAncestors.length > 0 &&
|
||||||
|
servicesHelper.isException(new URL(details.frameAncestors[0].url))
|
||||||
|
)
|
||||||
|
newUrl = null
|
||||||
|
|
||||||
if (servicesHelper.isException(url)) {
|
if (servicesHelper.isException(url)) {
|
||||||
if (details.type == "main_frame")
|
if (details.type == "main_frame") newUrl = "BYPASSTAB"
|
||||||
newUrl = "BYPASSTAB"
|
else return null
|
||||||
else
|
}
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newUrl) {
|
if (!newUrl) {
|
||||||
const match = url.href.match(/^https?:\/{2}.*\.libredirect\.invalid.*/)
|
const match = url.href.match(/^https?:\/{2}.*\.libredirect\.invalid.*/)
|
||||||
if (match) {
|
if (match) {
|
||||||
browser.tabs.update({
|
browser.tabs.update({
|
||||||
url: browser.runtime.getURL(`/pages/messages/no_instance.html`)
|
url: browser.runtime.getURL(`/pages/messages/no_instance.html`),
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
if (newUrl === "CANCEL") {
|
if (newUrl === "CANCEL") {
|
||||||
console.log(`Canceled ${url}`)
|
console.log(`Canceled ${url}`)
|
||||||
return { cancel: true }
|
return { cancel: true }
|
||||||
}
|
}
|
||||||
if (newUrl === "BYPASSTAB") {
|
if (newUrl === "BYPASSTAB") {
|
||||||
console.log(`Bypassed ${details.tabId} ${url}`)
|
console.log(`Bypassed ${details.tabId} ${url}`)
|
||||||
if (tabIdRedirects[details.tabId] != false) tabIdRedirects[details.tabId] = false
|
if (tabIdRedirects[details.tabId] != false) tabIdRedirects[details.tabId] = false
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
console.log("Redirecting", old_href, "=>", newUrl)
|
console.log("Redirecting", old_href, "=>", newUrl)
|
||||||
return { redirectUrl: newUrl }
|
return { redirectUrl: newUrl }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
{ urls: ["<all_urls>"] },
|
{ urls: ["<all_urls>"] },
|
||||||
["blocking"]
|
["blocking"]
|
||||||
)
|
)
|
||||||
|
|
||||||
browser.tabs.onRemoved.addListener(tabId => {
|
browser.tabs.onRemoved.addListener(tabId => {
|
||||||
if (tabIdRedirects[tabId] != undefined) {
|
if (tabIdRedirects[tabId] != undefined) {
|
||||||
delete tabIdRedirects[tabId]
|
delete tabIdRedirects[tabId]
|
||||||
console.log(`Removed tab ${tabId} from tabIdRedirects`)
|
console.log(`Removed tab ${tabId} from tabIdRedirects`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
browser.commands.onCommand.addListener(async command => {
|
browser.commands.onCommand.addListener(async command => {
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case "switchInstance": {
|
case "switchInstance": {
|
||||||
const newUrl = await servicesHelper.switchInstance(url)
|
const newUrl = await servicesHelper.switchInstance(url)
|
||||||
if (newUrl) browser.tabs.update({ url: newUrl })
|
if (newUrl) browser.tabs.update({ url: newUrl })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "copyRaw":
|
case "copyRaw":
|
||||||
servicesHelper.copyRaw(url)
|
servicesHelper.copyRaw(url)
|
||||||
break
|
break
|
||||||
case "redirect":
|
case "redirect":
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
||||||
tabIdRedirects[tabs[0].id] = true
|
tabIdRedirects[tabs[0].id] = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "reverse":
|
case "reverse":
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
const newUrl = await servicesHelper.reverse(url)
|
const newUrl = await servicesHelper.reverse(url)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
||||||
tabIdRedirects[tabs[0].id] = false
|
tabIdRedirects[tabs[0].id] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
browser.contextMenus.create({ id: "settingsTab", title: browser.i18n.getMessage("settings"), contexts: ["browser_action"] })
|
browser.contextMenus.create({
|
||||||
browser.contextMenus.create({ id: "switchInstanceTab", title: browser.i18n.getMessage("switchInstance"), contexts: ["browser_action"] })
|
id: "settingsTab",
|
||||||
browser.contextMenus.create({ id: "copyReverseTab", title: 'Copy Original', contexts: ["browser_action"] })
|
title: browser.i18n.getMessage("settings"),
|
||||||
browser.contextMenus.create({ id: "redirectTab", title: 'Redirect', contexts: ["browser_action"] })
|
contexts: ["browser_action"],
|
||||||
browser.contextMenus.create({ id: "reverseTab", title: 'Redirect To Original', contexts: ["browser_action"] })
|
})
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: "switchInstanceTab",
|
||||||
|
title: browser.i18n.getMessage("switchInstance"),
|
||||||
|
contexts: ["browser_action"],
|
||||||
|
})
|
||||||
|
browser.contextMenus.create({ id: "copyReverseTab", title: "Copy Original", contexts: ["browser_action"] })
|
||||||
|
browser.contextMenus.create({ id: "redirectTab", title: "Redirect", contexts: ["browser_action"] })
|
||||||
|
browser.contextMenus.create({ id: "reverseTab", title: "Redirect To Original", contexts: ["browser_action"] })
|
||||||
|
|
||||||
browser.contextMenus.create({ id: "redirectLink", title: 'Redirect', contexts: ["link"] })
|
browser.contextMenus.create({ id: "redirectLink", title: "Redirect", contexts: ["link"] })
|
||||||
browser.contextMenus.create({ id: "redirectLinkInNewTab", title: 'Redirect In New Tab', contexts: ["link"] })
|
browser.contextMenus.create({ id: "redirectLinkInNewTab", title: "Redirect In New Tab", contexts: ["link"] })
|
||||||
browser.contextMenus.create({ id: "reverseLink", title: 'Redirect To Original', contexts: ["link"] })
|
browser.contextMenus.create({ id: "reverseLink", title: "Redirect To Original", contexts: ["link"] })
|
||||||
browser.contextMenus.create({ id: "reverseLinkInNewTab", title: 'Redirect To Original In New Tab', contexts: ["link"] })
|
browser.contextMenus.create({ id: "reverseLinkInNewTab", title: "Redirect To Original In New Tab", contexts: ["link"] })
|
||||||
browser.contextMenus.create({ id: "copyReverseLink", title: 'Copy Original', contexts: ["link"] })
|
browser.contextMenus.create({ id: "copyReverseLink", title: "Copy Original", contexts: ["link"] })
|
||||||
browser.contextMenus.create({ id: "bypassLink", title: 'Bypass', contexts: ["link"] })
|
browser.contextMenus.create({ id: "bypassLink", title: "Bypass", contexts: ["link"] })
|
||||||
browser.contextMenus.create({ id: "bypassLinkInNewTab", title: 'Bypass In New Tab', contexts: ["link"] })
|
browser.contextMenus.create({ id: "bypassLinkInNewTab", title: "Bypass In New Tab", contexts: ["link"] })
|
||||||
|
|
||||||
if (!isChrome) {
|
if (!isChrome) {
|
||||||
browser.contextMenus.create({ id: "redirectBookmark", title: 'Redirect', contexts: ["bookmark"] })
|
browser.contextMenus.create({ id: "redirectBookmark", title: "Redirect", contexts: ["bookmark"] })
|
||||||
browser.contextMenus.create({ id: "redirectBookmarkInNewTab", title: 'Redirect In New Tab', contexts: ["bookmark"] })
|
browser.contextMenus.create({ id: "redirectBookmarkInNewTab", title: "Redirect In New Tab", contexts: ["bookmark"] })
|
||||||
browser.contextMenus.create({ id: "reverseBookmark", title: 'Redirect To Original', contexts: ["bookmark"] })
|
browser.contextMenus.create({ id: "reverseBookmark", title: "Redirect To Original", contexts: ["bookmark"] })
|
||||||
browser.contextMenus.create({ id: "reverseBookmarkInNewTab", title: 'Redirect To Original In New Tab', contexts: ["bookmark"] })
|
browser.contextMenus.create({
|
||||||
browser.contextMenus.create({ id: "copyReverseBookmark", title: 'Copy Original', contexts: ["bookmark"] })
|
id: "reverseBookmarkInNewTab",
|
||||||
browser.contextMenus.create({ id: "bypassBookmark", title: 'Bypass', contexts: ["bookmark"] })
|
title: "Redirect To Original In New Tab",
|
||||||
browser.contextMenus.create({ id: "bypassBookmarkInNewTab", title: 'Bypass In New Tab', contexts: ["bookmark"] })
|
contexts: ["bookmark"],
|
||||||
|
})
|
||||||
|
browser.contextMenus.create({ id: "copyReverseBookmark", title: "Copy Original", contexts: ["bookmark"] })
|
||||||
|
browser.contextMenus.create({ id: "bypassBookmark", title: "Bypass", contexts: ["bookmark"] })
|
||||||
|
browser.contextMenus.create({ id: "bypassBookmarkInNewTab", title: "Bypass In New Tab", contexts: ["bookmark"] })
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.contextMenus.onClicked.addListener(async (info) => {
|
browser.contextMenus.onClicked.addListener(async info => {
|
||||||
switch (info.menuItemId) {
|
switch (info.menuItemId) {
|
||||||
case 'switchInstanceTab': {
|
case "switchInstanceTab": {
|
||||||
const url = new URL(info.pageUrl)
|
const url = new URL(info.pageUrl)
|
||||||
const newUrl = await servicesHelper.switchInstance(url)
|
const newUrl = await servicesHelper.switchInstance(url)
|
||||||
if (newUrl) browser.tabs.update({ url: newUrl })
|
if (newUrl) browser.tabs.update({ url: newUrl })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'settingsTab':
|
case "settingsTab":
|
||||||
browser.runtime.openOptionsPage()
|
browser.runtime.openOptionsPage()
|
||||||
return
|
return
|
||||||
case 'copyReverseTab':
|
case "copyReverseTab":
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
servicesHelper.copyRaw(url)
|
servicesHelper.copyRaw(url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case 'reverseTab':
|
case "reverseTab":
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
const newUrl = await servicesHelper.reverse(url)
|
const newUrl = await servicesHelper.reverse(url)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
||||||
tabIdRedirects[tabs[0].id] = false
|
tabIdRedirects[tabs[0].id] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case 'redirectTab':
|
case "redirectTab":
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
|
||||||
tabIdRedirects[tabs[0].id] = true
|
tabIdRedirects[tabs[0].id] = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case 'copyReverseLink': {
|
case "copyReverseLink": {
|
||||||
const url = new URL(info.linkUrl)
|
const url = new URL(info.linkUrl)
|
||||||
await servicesHelper.copyRaw(url)
|
await servicesHelper.copyRaw(url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'redirectLink':
|
case "redirectLink":
|
||||||
case 'redirectLinkInNewTab': {
|
case "redirectLinkInNewTab": {
|
||||||
const url = new URL(info.linkUrl)
|
const url = new URL(info.linkUrl)
|
||||||
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
if (info.menuItemId == "redirectLink") browser.tabs.update({ url: newUrl })
|
if (info.menuItemId == "redirectLink") browser.tabs.update({ url: newUrl })
|
||||||
else browser.tabs.create({ url: newUrl })
|
else browser.tabs.create({ url: newUrl })
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'reverseLink':
|
case "reverseLink":
|
||||||
case 'reverseLinkInNewTab': {
|
case "reverseLinkInNewTab": {
|
||||||
const url = new URL(info.linkUrl)
|
const url = new URL(info.linkUrl)
|
||||||
const newUrl = await servicesHelper.reverse(url)
|
const newUrl = await servicesHelper.reverse(url)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
if (info.menuItemId == "reverseLink") {
|
if (info.menuItemId == "reverseLink") {
|
||||||
browser.tabs.update({ url: newUrl }, tab => {
|
browser.tabs.update({ url: newUrl }, tab => {
|
||||||
tabIdRedirects[tab.id] = false
|
tabIdRedirects[tab.id] = false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
browser.tabs.create({ url: newUrl }, tab => {
|
browser.tabs.create({ url: newUrl }, tab => {
|
||||||
tabIdRedirects[tab.id] = false
|
tabIdRedirects[tab.id] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'bypassLink':
|
case "bypassLink":
|
||||||
case 'bypassLinkInNewTab': {
|
case "bypassLinkInNewTab": {
|
||||||
const url = new URL(info.linkUrl)
|
const url = new URL(info.linkUrl)
|
||||||
if (info.menuItemId == "bypassLink") {
|
if (info.menuItemId == "bypassLink") {
|
||||||
browser.tabs.update({ url: url.href }, tab => {
|
browser.tabs.update({ url: url.href }, tab => {
|
||||||
tabIdRedirects[tab.id] = false
|
tabIdRedirects[tab.id] = false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
browser.tabs.create({ url: url.href }, tab => {
|
browser.tabs.create({ url: url.href }, tab => {
|
||||||
tabIdRedirects[tab.id] = false
|
tabIdRedirects[tab.id] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'copyReverseBookmark':
|
case "copyReverseBookmark":
|
||||||
browser.bookmarks.get(info.bookmarkId, bookmarks => {
|
browser.bookmarks.get(info.bookmarkId, bookmarks => {
|
||||||
const url = new URL(bookmarks[0].url)
|
const url = new URL(bookmarks[0].url)
|
||||||
servicesHelper.copyRaw(url)
|
servicesHelper.copyRaw(url)
|
||||||
});
|
})
|
||||||
return
|
return
|
||||||
case 'redirectBookmark':
|
case "redirectBookmark":
|
||||||
case 'redirectBookmarkInNewTab':
|
case "redirectBookmarkInNewTab":
|
||||||
browser.bookmarks.get(info.bookmarkId, bookmarks => {
|
browser.bookmarks.get(info.bookmarkId, bookmarks => {
|
||||||
const url = new URL(bookmarks[0].url)
|
const url = new URL(bookmarks[0].url)
|
||||||
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
if (info.menuItemId == 'redirectBookmark') browser.tabs.update({ url: newUrl })
|
if (info.menuItemId == "redirectBookmark") browser.tabs.update({ url: newUrl })
|
||||||
else browser.tabs.create({ url: newUrl })
|
else browser.tabs.create({ url: newUrl })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case 'reverseBookmark':
|
case "reverseBookmark":
|
||||||
case 'reverseBookmarkInNewTab':
|
case "reverseBookmarkInNewTab":
|
||||||
browser.bookmarks.get(info.bookmarkId, async bookmarks => {
|
browser.bookmarks.get(info.bookmarkId, async bookmarks => {
|
||||||
const url = new URL(bookmarks[0].url)
|
const url = new URL(bookmarks[0].url)
|
||||||
const newUrl = await servicesHelper.reverse(url)
|
const newUrl = await servicesHelper.reverse(url)
|
||||||
if (newUrl) {
|
if (newUrl) {
|
||||||
if (info.menuItemId == "reverseBookmark") {
|
if (info.menuItemId == "reverseBookmark") {
|
||||||
browser.tabs.update({ url: newUrl }, tab => {
|
browser.tabs.update({ url: newUrl }, tab => {
|
||||||
tabIdRedirects[tab.id] = false
|
tabIdRedirects[tab.id] = false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
browser.tabs.create({ url: newUrl }, tab => {
|
browser.tabs.create({ url: newUrl }, tab => {
|
||||||
tabIdRedirects[tab.id] = false
|
tabIdRedirects[tab.id] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case 'bypassBookmark':
|
case "bypassBookmark":
|
||||||
case 'bypassBookmarkInNewTab':
|
case "bypassBookmarkInNewTab":
|
||||||
browser.bookmarks.get(info.bookmarkId, async bookmarks => {
|
browser.bookmarks.get(info.bookmarkId, async bookmarks => {
|
||||||
const url = new URL(bookmarks[0].url)
|
const url = new URL(bookmarks[0].url)
|
||||||
if (info.menuItemId == "bypassBookmark") {
|
if (info.menuItemId == "bypassBookmark") {
|
||||||
browser.tabs.update({ url: url.href }, tab => tabIdRedirects[tab.id] = false)
|
browser.tabs.update({ url: url.href }, tab => (tabIdRedirects[tab.id] = false))
|
||||||
} else {
|
} else {
|
||||||
browser.tabs.create({ url: url.href }, tab => tabIdRedirects[tab.id] = false)
|
browser.tabs.create({ url: url.href }, tab => (tabIdRedirects[tab.id] = false))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
if (request == "reverseTab") {
|
if (request == "reverseTab") {
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
const newUrl = await servicesHelper.reverse(url)
|
const newUrl = await servicesHelper.reverse(url)
|
||||||
if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => tabIdRedirects[tabs[0].id] = false)
|
if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => (tabIdRedirects[tabs[0].id] = false))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
} else if (request == "redirectTab") {
|
||||||
else if (request == "redirectTab") {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
if (tabs[0].url) {
|
||||||
if (tabs[0].url) {
|
const url = new URL(tabs[0].url)
|
||||||
const url = new URL(tabs[0].url)
|
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
||||||
const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
|
if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => (tabIdRedirects[tabs[0].id] = true))
|
||||||
if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => tabIdRedirects[tabs[0].id] = true)
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
})
|
})
|
|
@ -1,26 +1,24 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="UTF-8" />
|
||||||
<meta charset="UTF-8">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<link href="../stylesheets/styles.css" rel="stylesheet" />
|
||||||
<link href="../stylesheets/styles.css" rel="stylesheet">
|
|
||||||
<title>No instances found</title>
|
<title>No instances found</title>
|
||||||
<style>
|
<style>
|
||||||
#body {
|
#body {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="body">
|
<div id="body">
|
||||||
<h1>You have no instance selected for this frontend</h1>
|
<h1>You have no instance selected for this frontend</h1>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,19 +1,19 @@
|
||||||
import utils from "../../assets/javascripts/utils.js"
|
import utils from "../../assets/javascripts/utils.js"
|
||||||
|
|
||||||
let config,
|
let config,
|
||||||
options,
|
options,
|
||||||
blacklist,
|
blacklist,
|
||||||
redirects,
|
redirects,
|
||||||
divs = {}
|
divs = {}
|
||||||
|
|
||||||
for (const a of document.getElementById("links").getElementsByTagName("a")) {
|
for (const a of document.getElementById("links").getElementsByTagName("a")) {
|
||||||
if (!a.href.includes("https://")) {
|
if (!a.href.includes("https://")) {
|
||||||
a.addEventListener("click", e => {
|
a.addEventListener("click", e => {
|
||||||
const path = a.getAttribute("href").replace("#", "")
|
const path = a.getAttribute("href").replace("#", "")
|
||||||
loadPage(path)
|
loadPage(path)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config = await utils.getConfig()
|
config = await utils.getConfig()
|
||||||
|
@ -23,180 +23,180 @@ options = await utils.getOptions()
|
||||||
* @param {string} service
|
* @param {string} service
|
||||||
*/
|
*/
|
||||||
async function changeFrontendsSettings(service) {
|
async function changeFrontendsSettings(service) {
|
||||||
options = await utils.getOptions()
|
options = await utils.getOptions()
|
||||||
const opacityDiv = document.getElementById(`${service}-opacity`)
|
const opacityDiv = document.getElementById(`${service}-opacity`)
|
||||||
if (document.getElementById(`${service}-enabled`).checked) {
|
if (document.getElementById(`${service}-enabled`).checked) {
|
||||||
opacityDiv.style.pointerEvents = 'auto'
|
opacityDiv.style.pointerEvents = "auto"
|
||||||
opacityDiv.style.opacity = 1
|
opacityDiv.style.opacity = 1
|
||||||
opacityDiv.style.userSelect = 'auto'
|
opacityDiv.style.userSelect = "auto"
|
||||||
} else {
|
} else {
|
||||||
opacityDiv.style.pointerEvents = 'none'
|
opacityDiv.style.pointerEvents = "none"
|
||||||
opacityDiv.style.opacity = 0.4
|
opacityDiv.style.opacity = 0.4
|
||||||
opacityDiv.style.userSelect = 'none'
|
opacityDiv.style.userSelect = "none"
|
||||||
}
|
}
|
||||||
for (const frontend in config.services[service].frontends) {
|
for (const frontend in config.services[service].frontends) {
|
||||||
if (config.services[service].frontends[frontend].instanceList) {
|
if (config.services[service].frontends[frontend].instanceList) {
|
||||||
const frontendDiv = document.getElementById(frontend)
|
const frontendDiv = document.getElementById(frontend)
|
||||||
if (typeof divs[service].frontend !== "undefined") {
|
if (typeof divs[service].frontend !== "undefined") {
|
||||||
if (
|
if (
|
||||||
frontend == divs[service].frontend.value
|
frontend == divs[service].frontend.value ||
|
||||||
||
|
(config.services[service].frontends[divs[service].frontend.value].desktopApp &&
|
||||||
(config.services[service].frontends[divs[service].frontend.value].desktopApp && divs[service].embedFrontend && frontend == divs[service].embedFrontend.value)
|
divs[service].embedFrontend &&
|
||||||
) {
|
frontend == divs[service].embedFrontend.value)
|
||||||
frontendDiv.style.display = ""
|
) {
|
||||||
if (config.services[service].frontends[frontend].localhost === true) {
|
frontendDiv.style.display = ""
|
||||||
document.getElementById(`${service}-instance-div`).style.display = ""
|
if (config.services[service].frontends[frontend].localhost === true) {
|
||||||
|
document.getElementById(`${service}-instance-div`).style.display = ""
|
||||||
|
|
||||||
if (options[service].instance == "localhost") {
|
if (options[service].instance == "localhost") {
|
||||||
frontendDiv.style.display = "none"
|
frontendDiv.style.display = "none"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById(`${service}-instance-div`).style.display = "none"
|
document.getElementById(`${service}-instance-div`).style.display = "none"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
frontendDiv.style.display = "none"
|
frontendDiv.style.display = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (document.getElementById(`${service}-redirectType`)) {
|
if (document.getElementById(`${service}-redirectType`)) {
|
||||||
const frontend = options[service].frontend
|
const frontend = options[service].frontend
|
||||||
if (config.services[service].frontends[frontend].embeddable) {
|
if (config.services[service].frontends[frontend].embeddable) {
|
||||||
document.getElementById(`${service}-redirectType`).innerHTML = `
|
document.getElementById(`${service}-redirectType`).innerHTML = `
|
||||||
<option value="both" data-localise="__MSG_both__">both</options>
|
<option value="both" data-localise="__MSG_both__">both</options>
|
||||||
<option value="sub_frame" data-localise="__MSG_onlyEmbedded__">Only Embedded</option>
|
<option value="sub_frame" data-localise="__MSG_onlyEmbedded__">Only Embedded</option>
|
||||||
<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
|
<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
|
||||||
`
|
`
|
||||||
}
|
} else if (
|
||||||
else if (config.services[service].frontends[frontend].desktopApp && Object.values(config.services[service].frontends).some(frontend => frontend.embeddable)) {
|
config.services[service].frontends[frontend].desktopApp &&
|
||||||
document.getElementById(`${service}-redirectType`).innerHTML = `
|
Object.values(config.services[service].frontends).some(frontend => frontend.embeddable)
|
||||||
|
) {
|
||||||
|
document.getElementById(`${service}-redirectType`).innerHTML = `
|
||||||
<option value="both" data-localise="__MSG_both__">both</options>
|
<option value="both" data-localise="__MSG_both__">both</options>
|
||||||
<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
|
<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
|
||||||
`
|
`
|
||||||
if (options[service].redirectType == "sub_frame") {
|
if (options[service].redirectType == "sub_frame") {
|
||||||
options[service].redirectType = "main_frame"
|
options[service].redirectType = "main_frame"
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById(`${service}-redirectType`).innerHTML =
|
document.getElementById(`${service}-redirectType`).innerHTML =
|
||||||
'<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>'
|
'<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>'
|
||||||
options[service].redirectType = "main_frame"
|
options[service].redirectType = "main_frame"
|
||||||
|
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
}
|
}
|
||||||
document.getElementById(`${service}-redirectType`).value = options[service].redirectType
|
document.getElementById(`${service}-redirectType`).value = options[service].redirectType
|
||||||
if (config.services[service].frontends[frontend].desktopApp && options[service].redirectType != "main_frame") {
|
if (config.services[service].frontends[frontend].desktopApp && options[service].redirectType != "main_frame") {
|
||||||
document.getElementById(`${service}-embedFrontend-div`).style.display = ''
|
document.getElementById(`${service}-embedFrontend-div`).style.display = ""
|
||||||
document.getElementById(divs[service].embedFrontend.value).style.display = ''
|
document.getElementById(divs[service].embedFrontend.value).style.display = ""
|
||||||
}
|
} else if (
|
||||||
else if (config.services[service].frontends[frontend].desktopApp && options[service].redirectType == "main_frame") {
|
config.services[service].frontends[frontend].desktopApp &&
|
||||||
document.getElementById(`${service}-embedFrontend-div`).style.display = 'none'
|
options[service].redirectType == "main_frame"
|
||||||
document.getElementById(divs[service].embedFrontend.value).style.display = 'none'
|
) {
|
||||||
} else {
|
document.getElementById(`${service}-embedFrontend-div`).style.display = "none"
|
||||||
document.getElementById(`${service}-embedFrontend-div`).style.display = 'none'
|
document.getElementById(divs[service].embedFrontend.value).style.display = "none"
|
||||||
}
|
} else {
|
||||||
}
|
document.getElementById(`${service}-embedFrontend-div`).style.display = "none"
|
||||||
const frontend_name_element = document.getElementById(`${service}_page`).getElementsByClassName("frontend_name")[0]
|
}
|
||||||
frontend_name_element.href = config.services[service].frontends[divs[service].frontend.value].url
|
}
|
||||||
|
const frontend_name_element = document.getElementById(`${service}_page`).getElementsByClassName("frontend_name")[0]
|
||||||
|
frontend_name_element.href = config.services[service].frontends[divs[service].frontend.value].url
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
*/
|
*/
|
||||||
async function loadPage(path) {
|
async function loadPage(path) {
|
||||||
options = await utils.getOptions()
|
options = await utils.getOptions()
|
||||||
for (const section of document.getElementById("pages").getElementsByTagName("section")) section.style.display = "none"
|
for (const section of document.getElementById("pages").getElementsByTagName("section")) section.style.display = "none"
|
||||||
document.getElementById(`${path}_page`).style.display = "block"
|
document.getElementById(`${path}_page`).style.display = "block"
|
||||||
|
|
||||||
for (const element of document.getElementsByClassName("title")) {
|
for (const element of document.getElementsByClassName("title")) {
|
||||||
const a = element.getElementsByTagName('a')[0]
|
const a = element.getElementsByTagName("a")[0]
|
||||||
if (a.getAttribute("href") == `#${path}`) {
|
if (a.getAttribute("href") == `#${path}`) {
|
||||||
element.classList.add("selected")
|
element.classList.add("selected")
|
||||||
} else {
|
} else {
|
||||||
element.classList.remove("selected")
|
element.classList.remove("selected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const service in config.services) {
|
for (const service in config.services) {
|
||||||
if (options[service].enabled) {
|
if (options[service].enabled) {
|
||||||
document.getElementById(`${service}-link`).style.opacity = 1
|
document.getElementById(`${service}-link`).style.opacity = 1
|
||||||
} else {
|
} else {
|
||||||
document.getElementById(`${service}-link`).style.opacity = 0.4
|
document.getElementById(`${service}-link`).style.opacity = 0.4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.history.pushState({ id: "100" }, "Page 2", `/pages/options/index.html#${path}`)
|
window.history.pushState({ id: "100" }, "Page 2", `/pages/options/index.html#${path}`)
|
||||||
|
|
||||||
if (path != 'general') {
|
if (path != "general") {
|
||||||
const service = path;
|
const service = path
|
||||||
divs[service] = {}
|
divs[service] = {}
|
||||||
for (const option in config.services[service].options) {
|
for (const option in config.services[service].options) {
|
||||||
divs[service][option] = document.getElementById(`${service}-${option}`)
|
divs[service][option] = document.getElementById(`${service}-${option}`)
|
||||||
if (typeof config.services[service].options[option] == "boolean") divs[service][option].checked = options[service][option]
|
if (typeof config.services[service].options[option] == "boolean")
|
||||||
else divs[service][option].value = options[service][option]
|
divs[service][option].checked = options[service][option]
|
||||||
divs[service][option].addEventListener("change", async () => {
|
else divs[service][option].value = options[service][option]
|
||||||
let options = await utils.getOptions()
|
divs[service][option].addEventListener("change", async () => {
|
||||||
if (typeof config.services[service].options[option] == "boolean")
|
let options = await utils.getOptions()
|
||||||
options[service][option] = divs[service][option].checked
|
if (typeof config.services[service].options[option] == "boolean")
|
||||||
else
|
options[service][option] = divs[service][option].checked
|
||||||
options[service][option] = divs[service][option].value
|
else options[service][option] = divs[service][option].value
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
changeFrontendsSettings(service)
|
changeFrontendsSettings(service)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
changeFrontendsSettings(service)
|
changeFrontendsSettings(service)
|
||||||
|
|
||||||
blacklist = await utils.getBlacklist(options)
|
blacklist = await utils.getBlacklist(options)
|
||||||
redirects = await utils.getList(options)
|
redirects = await utils.getList(options)
|
||||||
for (const frontend in config.services[service].frontends) {
|
for (const frontend in config.services[service].frontends) {
|
||||||
if (config.services[service].frontends[frontend].instanceList) {
|
if (config.services[service].frontends[frontend].instanceList) {
|
||||||
if (redirects == 'disabled' || blacklist == 'disabled') {
|
if (redirects == "disabled" || blacklist == "disabled") {
|
||||||
document.getElementById(frontend).getElementsByClassName('clearnet')[0].style.display = 'none'
|
document.getElementById(frontend).getElementsByClassName("clearnet")[0].style.display = "none"
|
||||||
document.getElementById(frontend).getElementsByClassName('ping')[0].style.display = 'none'
|
document.getElementById(frontend).getElementsByClassName("ping")[0].style.display = "none"
|
||||||
}
|
} else if (!redirects || !blacklist) {
|
||||||
else if (!redirects || !blacklist) {
|
document
|
||||||
document.getElementById(frontend)
|
.getElementById(frontend)
|
||||||
.getElementsByClassName('clearnet')[0]
|
.getElementsByClassName("clearnet")[0]
|
||||||
.getElementsByClassName("checklist")[0]
|
.getElementsByClassName("checklist")[0]
|
||||||
.getElementsByClassName('loading')[0]
|
.getElementsByClassName("loading")[0].innerHTML = "Could not fetch instances."
|
||||||
.innerHTML = 'Could not fetch instances.'
|
} else {
|
||||||
}
|
createList(frontend)
|
||||||
else {
|
}
|
||||||
createList(frontend)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const frontend in config.services[service].frontends) {
|
for (const frontend in config.services[service].frontends) {
|
||||||
if (config.services[service].frontends[frontend].instanceList) {
|
if (config.services[service].frontends[frontend].instanceList) {
|
||||||
processCustomInstances(frontend)
|
processCustomInstances(frontend)
|
||||||
document.getElementById(`ping-${frontend}`).addEventListener("click", async () => {
|
document.getElementById(`ping-${frontend}`).addEventListener("click", async () => {
|
||||||
document.getElementById(`ping-${frontend}`).getElementsByTagName('x')[0].innerHTML = "Pinging..."
|
document.getElementById(`ping-${frontend}`).getElementsByTagName("x")[0].innerHTML = "Pinging..."
|
||||||
await ping(frontend)
|
await ping(frontend)
|
||||||
document.getElementById(`ping-${frontend}`).getElementsByTagName('x')[0].innerHTML = "Ping instances"
|
document.getElementById(`ping-${frontend}`).getElementsByTagName("x")[0].innerHTML = "Ping instances"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calcCustomInstances(frontend) {
|
async function calcCustomInstances(frontend) {
|
||||||
let options = await utils.getOptions()
|
let options = await utils.getOptions()
|
||||||
let customInstances = options[frontend]
|
let customInstances = options[frontend]
|
||||||
const pingCache = await utils.getPingCache()
|
const pingCache = await utils.getPingCache()
|
||||||
|
|
||||||
document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].innerHTML = customInstances
|
document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].innerHTML = customInstances
|
||||||
.map(
|
.map(x => {
|
||||||
x => {
|
const time = pingCache[x]
|
||||||
const time = pingCache[x];
|
if (time) {
|
||||||
if (time) {
|
var { color, text } = processTime(time)
|
||||||
var { color, text } = processTime(time);
|
}
|
||||||
}
|
const timeText = time ? `<span class="ping" style="color:${color};">${text}</span>` : ""
|
||||||
const timeText = time
|
const custom = isCustomInstance(frontend, x) ? "" : `<span>custom</span>`
|
||||||
? `<span class="ping" style="color:${color};">${text}</span>`
|
return `<div>
|
||||||
: "";
|
|
||||||
const custom = isCustomInstance(frontend, x) ? "" : `<span>custom</span>`
|
|
||||||
return `<div>
|
|
||||||
<x>
|
<x>
|
||||||
<a href="${x}" target="_blank">${x}</a>
|
<a href="${x}" target="_blank">${x}</a>
|
||||||
${timeText}
|
${timeText}
|
||||||
|
@ -209,87 +209,97 @@ async function calcCustomInstances(frontend) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>`
|
<hr>`
|
||||||
})
|
})
|
||||||
.join("\n")
|
.join("\n")
|
||||||
for (const item of customInstances) {
|
for (const item of customInstances) {
|
||||||
document.getElementById(frontend).getElementsByClassName(`clear-${item}`)[0].addEventListener("click", async () => {
|
document
|
||||||
const index = customInstances.indexOf(item)
|
.getElementById(frontend)
|
||||||
if (index > -1) customInstances.splice(index, 1)
|
.getElementsByClassName(`clear-${item}`)[0]
|
||||||
options = await utils.getOptions()
|
.addEventListener("click", async () => {
|
||||||
options[frontend] = customInstances
|
const index = customInstances.indexOf(item)
|
||||||
browser.storage.local.set({ options }, async () => {
|
if (index > -1) customInstances.splice(index, 1)
|
||||||
calcCustomInstances(frontend)
|
options = await utils.getOptions()
|
||||||
createList(frontend)
|
options[frontend] = customInstances
|
||||||
})
|
browser.storage.local.set({ options }, async () => {
|
||||||
})
|
calcCustomInstances(frontend)
|
||||||
}
|
createList(frontend)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processCustomInstances(frontend) {
|
async function processCustomInstances(frontend) {
|
||||||
calcCustomInstances(frontend)
|
calcCustomInstances(frontend)
|
||||||
document.getElementById(frontend).getElementsByClassName("custom-instance-form")[0].addEventListener("submit", async event => {
|
document
|
||||||
event.preventDefault()
|
.getElementById(frontend)
|
||||||
let options = await utils.getOptions()
|
.getElementsByClassName("custom-instance-form")[0]
|
||||||
let customInstances = options[frontend]
|
.addEventListener("submit", async event => {
|
||||||
let frontendCustomInstanceInput = document.getElementById(frontend).getElementsByClassName("custom-instance")[0]
|
event.preventDefault()
|
||||||
try {
|
let options = await utils.getOptions()
|
||||||
var url = new URL(frontendCustomInstanceInput.value)
|
let customInstances = options[frontend]
|
||||||
} catch (error) {
|
let frontendCustomInstanceInput = document.getElementById(frontend).getElementsByClassName("custom-instance")[0]
|
||||||
return
|
try {
|
||||||
}
|
var url = new URL(frontendCustomInstanceInput.value)
|
||||||
let protocolHostVar = utils.protocolHost(url)
|
} catch (error) {
|
||||||
if (frontendCustomInstanceInput.validity.valid) {
|
return
|
||||||
if (!customInstances.includes(protocolHostVar)) {
|
}
|
||||||
customInstances.push(protocolHostVar)
|
let protocolHostVar = utils.protocolHost(url)
|
||||||
options = await utils.getOptions()
|
if (frontendCustomInstanceInput.validity.valid) {
|
||||||
options[frontend] = customInstances
|
if (!customInstances.includes(protocolHostVar)) {
|
||||||
browser.storage.local.set({ options }, () => {
|
customInstances.push(protocolHostVar)
|
||||||
frontendCustomInstanceInput.value = ""
|
options = await utils.getOptions()
|
||||||
calcCustomInstances(frontend)
|
options[frontend] = customInstances
|
||||||
})
|
browser.storage.local.set({ options }, () => {
|
||||||
}
|
frontendCustomInstanceInput.value = ""
|
||||||
}
|
calcCustomInstances(frontend)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} frontend
|
* @param {string} frontend
|
||||||
*/
|
*/
|
||||||
async function createList(frontend) {
|
async function createList(frontend) {
|
||||||
const pingCache = await utils.getPingCache()
|
const pingCache = await utils.getPingCache()
|
||||||
const options = await utils.getOptions()
|
const options = await utils.getOptions()
|
||||||
for (const network in config.networks) {
|
for (const network in config.networks) {
|
||||||
const checklist = document.getElementById(frontend).getElementsByClassName(network)[0].getElementsByClassName("checklist")[0]
|
const checklist = document
|
||||||
|
.getElementById(frontend)
|
||||||
|
.getElementsByClassName(network)[0]
|
||||||
|
.getElementsByClassName("checklist")[0]
|
||||||
|
|
||||||
if (!redirects[frontend]) {
|
if (!redirects[frontend]) {
|
||||||
checklist.innerHTML = '<div class="block block-option">No instances found.</div>'
|
checklist.innerHTML = '<div class="block block-option">No instances found.</div>'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const instances = redirects[frontend][network]
|
const instances = redirects[frontend][network]
|
||||||
if (!instances || instances.length === 0) continue
|
if (!instances || instances.length === 0) continue
|
||||||
|
|
||||||
document.getElementById(frontend).getElementsByClassName("custom-instance")[0].placeholder = redirects[frontend].clearnet[0]
|
document.getElementById(frontend).getElementsByClassName("custom-instance")[0].placeholder =
|
||||||
|
redirects[frontend].clearnet[0]
|
||||||
|
|
||||||
instances.sort((a, b) => blacklist.cloudflare.includes(a) && !blacklist.cloudflare.includes(b))
|
instances.sort((a, b) => blacklist.cloudflare.includes(a) && !blacklist.cloudflare.includes(b))
|
||||||
const content = instances
|
const content = instances.map(x => {
|
||||||
.map(x => {
|
const cloudflare = blacklist.cloudflare.includes(x)
|
||||||
const cloudflare = blacklist.cloudflare.includes(x) ?
|
? `<a target="_blank" href="https://libredirect.github.io/docs.html#instances">
|
||||||
`<a target="_blank" href="https://libredirect.github.io/docs.html#instances">
|
|
||||||
<span style="color:red;">cloudflare</span>
|
<span style="color:red;">cloudflare</span>
|
||||||
</a>` : ""
|
</a>`
|
||||||
|
: ""
|
||||||
|
|
||||||
let time = pingCache[x]
|
let time = pingCache[x]
|
||||||
let timeText = ""
|
let timeText = ""
|
||||||
if (time) {
|
if (time) {
|
||||||
const { color, text } = processTime(time)
|
const { color, text } = processTime(time)
|
||||||
timeText = `<span class="ping" style="color:${color};">${text}</span>`
|
timeText = `<span class="ping" style="color:${color};">${text}</span>`
|
||||||
}
|
}
|
||||||
|
|
||||||
const chosen = options[frontend].includes(x) ? `<span style="color:grey;">chosen</span>` : ""
|
const chosen = options[frontend].includes(x) ? `<span style="color:grey;">chosen</span>` : ""
|
||||||
|
|
||||||
const warnings = [cloudflare, timeText, chosen].join(" ")
|
const warnings = [cloudflare, timeText, chosen].join(" ")
|
||||||
return `<div class="frontend">
|
return `<div class="frontend">
|
||||||
<x>
|
<x>
|
||||||
<a href="${x}" target="_blank">${x}</a>
|
<a href="${x}" target="_blank">${x}</a>
|
||||||
${warnings}
|
${warnings}
|
||||||
|
@ -300,30 +310,29 @@ async function createList(frontend) {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
|
|
||||||
checklist.innerHTML = [
|
checklist.innerHTML = [
|
||||||
`<div class="block block-option">
|
`<div class="block block-option">
|
||||||
<label>${utils.camelCase(network)}</label>
|
<label>${utils.camelCase(network)}</label>
|
||||||
</div>`,
|
</div>`,
|
||||||
...content,
|
...content,
|
||||||
"<br>"
|
"<br>",
|
||||||
].join("\n<hr>\n")
|
].join("\n<hr>\n")
|
||||||
|
|
||||||
for (const instance of instances) {
|
for (const instance of instances) {
|
||||||
checklist.getElementsByClassName(`add-${instance}`)[0]
|
checklist.getElementsByClassName(`add-${instance}`)[0].addEventListener("click", async () => {
|
||||||
.addEventListener("click", async () => {
|
let options = await utils.getOptions()
|
||||||
let options = await utils.getOptions()
|
if (!options[frontend].includes(instance)) {
|
||||||
if (!options[frontend].includes(instance)) {
|
options[frontend].push(instance)
|
||||||
options[frontend].push(instance)
|
browser.storage.local.set({ options }, () => {
|
||||||
browser.storage.local.set({ options }, () => {
|
calcCustomInstances(frontend)
|
||||||
calcCustomInstances(frontend)
|
createList(frontend)
|
||||||
createList(frontend)
|
})
|
||||||
})
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const r = window.location.href.match(/#(.*)/)
|
const r = window.location.href.match(/#(.*)/)
|
||||||
|
@ -334,59 +343,57 @@ else loadPage("general")
|
||||||
* @param {string} frontend
|
* @param {string} frontend
|
||||||
*/
|
*/
|
||||||
async function ping(frontend) {
|
async function ping(frontend) {
|
||||||
const instanceElements = [
|
const instanceElements = [
|
||||||
...document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].getElementsByTagName('x'),
|
...document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].getElementsByTagName("x"),
|
||||||
...document.getElementById(frontend).getElementsByClassName('clearnet')[0].getElementsByTagName('x')
|
...document.getElementById(frontend).getElementsByClassName("clearnet")[0].getElementsByTagName("x"),
|
||||||
]
|
]
|
||||||
|
|
||||||
let pingCache = await utils.getPingCache()
|
let pingCache = await utils.getPingCache()
|
||||||
let redundancyList = {}
|
let redundancyList = {}
|
||||||
for (const element of instanceElements) {
|
for (const element of instanceElements) {
|
||||||
let span = element.getElementsByClassName('ping')[0]
|
let span = element.getElementsByClassName("ping")[0]
|
||||||
if (!span) span = document.createElement('span')
|
if (!span) span = document.createElement("span")
|
||||||
span.classList = ['ping']
|
span.classList = ["ping"]
|
||||||
span.innerHTML = '<span style="color:lightblue">pinging...</span>'
|
span.innerHTML = '<span style="color:lightblue">pinging...</span>'
|
||||||
element.appendChild(span)
|
element.appendChild(span)
|
||||||
const href = element.getElementsByTagName('a')[0].href
|
const href = element.getElementsByTagName("a")[0].href
|
||||||
const innerHTML = element.getElementsByTagName('a')[0].innerHTML
|
const innerHTML = element.getElementsByTagName("a")[0].innerHTML
|
||||||
const time = redundancyList[innerHTML] ?? await utils.ping(href)
|
const time = redundancyList[innerHTML] ?? (await utils.ping(href))
|
||||||
const { color, text } = processTime(time)
|
const { color, text } = processTime(time)
|
||||||
span.innerHTML = `<span style="color:${color};">${text}</span>`
|
span.innerHTML = `<span style="color:${color};">${text}</span>`
|
||||||
pingCache[innerHTML] = time
|
pingCache[innerHTML] = time
|
||||||
redundancyList[innerHTML] = time
|
redundancyList[innerHTML] = time
|
||||||
|
|
||||||
browser.storage.local.set({ pingCache })
|
browser.storage.local.set({ pingCache })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} time
|
* @param {number} time
|
||||||
*/
|
*/
|
||||||
function processTime(time) {
|
function processTime(time) {
|
||||||
let text
|
let text
|
||||||
let color
|
let color
|
||||||
if (time < 5000) {
|
if (time < 5000) {
|
||||||
text = `${time}ms`
|
text = `${time}ms`
|
||||||
if (time <= 1000) color = "green"
|
if (time <= 1000) color = "green"
|
||||||
else if (time <= 2000) color = "orange"
|
else if (time <= 2000) color = "orange"
|
||||||
}
|
} else if (time >= 5000) {
|
||||||
else if (time >= 5000) {
|
color = "red"
|
||||||
color = "red"
|
if (time == 5000) text = "5000ms+"
|
||||||
if (time == 5000) text = "5000ms+"
|
if (time > 5000) text = `Error: ${time - 5000}`
|
||||||
if (time > 5000) text = `Error: ${time - 5000}`
|
} else {
|
||||||
}
|
color = "red"
|
||||||
else {
|
text = "Server not found"
|
||||||
color = "red"
|
}
|
||||||
text = 'Server not found'
|
return { color, text }
|
||||||
}
|
|
||||||
return { color, text }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCustomInstance(frontend, instance) {
|
function isCustomInstance(frontend, instance) {
|
||||||
for (const network in config.networks) {
|
for (const network in config.networks) {
|
||||||
if (!redirects[frontend]) return false;
|
if (!redirects[frontend]) return false
|
||||||
const instances = redirects[frontend][network]
|
const instances = redirects[frontend][network]
|
||||||
if (instances.includes(instance)) return true
|
if (instances.includes(instance)) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
|
@ -5,46 +5,46 @@ import utils from "../../assets/javascripts/utils.js"
|
||||||
import servicesHelper from "../../assets/javascripts/services.js"
|
import servicesHelper from "../../assets/javascripts/services.js"
|
||||||
|
|
||||||
if (!(await utils.getOptions())) {
|
if (!(await utils.getOptions())) {
|
||||||
await servicesHelper.initDefaults()
|
await servicesHelper.initDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeTheme() {
|
async function changeTheme() {
|
||||||
switch ((await utils.getOptions()).theme) {
|
switch ((await utils.getOptions()).theme) {
|
||||||
case "dark":
|
case "dark":
|
||||||
document.body.classList.add("dark-theme")
|
document.body.classList.add("dark-theme")
|
||||||
document.body.classList.remove("light-theme")
|
document.body.classList.remove("light-theme")
|
||||||
for (const element of document.body.getElementsByClassName('dark')) {
|
for (const element of document.body.getElementsByClassName("dark")) {
|
||||||
element.style.display = 'none';
|
element.style.display = "none"
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "light":
|
case "light":
|
||||||
document.body.classList.add("light-theme")
|
document.body.classList.add("light-theme")
|
||||||
document.body.classList.remove("dark-theme")
|
document.body.classList.remove("dark-theme")
|
||||||
for (const element of document.body.getElementsByClassName('light')) {
|
for (const element of document.body.getElementsByClassName("light")) {
|
||||||
element.style.display = 'none';
|
element.style.display = "none"
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (matchMedia("(prefers-color-scheme: light)").matches) {
|
if (matchMedia("(prefers-color-scheme: light)").matches) {
|
||||||
document.body.classList.add("light-theme")
|
document.body.classList.add("light-theme")
|
||||||
document.body.classList.remove("dark-theme")
|
document.body.classList.remove("dark-theme")
|
||||||
for (const element of document.body.getElementsByClassName('light')) {
|
for (const element of document.body.getElementsByClassName("light")) {
|
||||||
element.style.display = 'none';
|
element.style.display = "none"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.body.classList.add("dark-theme")
|
document.body.classList.add("dark-theme")
|
||||||
document.body.classList.remove("light-theme")
|
document.body.classList.remove("light-theme")
|
||||||
for (const element of document.body.getElementsByClassName('dark')) {
|
for (const element of document.body.getElementsByClassName("dark")) {
|
||||||
element.style.display = 'none';
|
element.style.display = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTheme()
|
changeTheme()
|
||||||
if (["ar", "iw", "ku", "fa", "ur"].includes(browser.i18n.getUILanguage())) {
|
if (["ar", "iw", "ku", "fa", "ur"].includes(browser.i18n.getUILanguage())) {
|
||||||
document.getElementsByTagName("body")[0].classList.add("rtl")
|
document.getElementsByTagName("body")[0].classList.add("rtl")
|
||||||
document.getElementsByTagName("body")[0].dir = "rtl"
|
document.getElementsByTagName("body")[0].dir = "rtl"
|
||||||
}
|
}
|
||||||
localise.localisePage()
|
localise.localisePage()
|
||||||
|
|
||||||
|
|
|
@ -7,29 +7,29 @@ import servicesHelper from "../../../assets/javascripts/services.js"
|
||||||
const isChrome = browser.runtime.getBrowserInfo === undefined
|
const isChrome = browser.runtime.getBrowserInfo === undefined
|
||||||
|
|
||||||
async function setOption(option, type, event) {
|
async function setOption(option, type, event) {
|
||||||
let options = await utils.getOptions()
|
let options = await utils.getOptions()
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "select":
|
case "select":
|
||||||
options[option] = event.target.options[event.target.options.selectedIndex].value
|
options[option] = event.target.options[event.target.options.selectedIndex].value
|
||||||
break;
|
break
|
||||||
case "checkbox":
|
case "checkbox":
|
||||||
options[option] = event.target.checked
|
options[option] = event.target.checked
|
||||||
break;
|
break
|
||||||
case "range":
|
case "range":
|
||||||
options[option] = event.target.value
|
options[option] = event.target.value
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportSettingsElement = document.getElementById("export-settings")
|
const exportSettingsElement = document.getElementById("export-settings")
|
||||||
async function exportSettings() {
|
async function exportSettings() {
|
||||||
const options = await utils.getOptions()
|
const options = await utils.getOptions()
|
||||||
options.version = browser.runtime.getManifest().version
|
options.version = browser.runtime.getManifest().version
|
||||||
let resultString = JSON.stringify(options, null, " ")
|
let resultString = JSON.stringify(options, null, " ")
|
||||||
exportSettingsElement.href = "data:application/json;base64," + btoa(resultString)
|
exportSettingsElement.href = "data:application/json;base64," + btoa(resultString)
|
||||||
exportSettingsElement.download = `libredirect-settings-v${options.version}.json`
|
exportSettingsElement.download = `libredirect-settings-v${options.version}.json`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exportSettings()
|
exportSettings()
|
||||||
document.getElementById("general_page").onclick = exportSettings
|
document.getElementById("general_page").onclick = exportSettings
|
||||||
|
@ -37,35 +37,32 @@ document.getElementById("general_page").onclick = exportSettings
|
||||||
const importSettingsElement = document.getElementById("import-settings")
|
const importSettingsElement = document.getElementById("import-settings")
|
||||||
const importSettingsElementText = document.getElementById("import_settings_text")
|
const importSettingsElementText = document.getElementById("import_settings_text")
|
||||||
importSettingsElement.addEventListener("change", () => {
|
importSettingsElement.addEventListener("change", () => {
|
||||||
function importError() {
|
function importError() {
|
||||||
const oldHTML = importSettingsElementText.innerHTML
|
const oldHTML = importSettingsElementText.innerHTML
|
||||||
importSettingsElementText.innerHTML = '<span style="color:red;">Error!</span>'
|
importSettingsElementText.innerHTML = '<span style="color:red;">Error!</span>'
|
||||||
setTimeout(() => (importSettingsElementText.innerHTML = oldHTML), 1000)
|
setTimeout(() => (importSettingsElementText.innerHTML = oldHTML), 1000)
|
||||||
}
|
}
|
||||||
importSettingsElementText.innerHTML = "..."
|
importSettingsElementText.innerHTML = "..."
|
||||||
let file = importSettingsElement.files[0]
|
let file = importSettingsElement.files[0]
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.readAsText(file)
|
reader.readAsText(file)
|
||||||
reader.onload = async () => {
|
reader.onload = async () => {
|
||||||
const data = JSON.parse(reader.result)
|
const data = JSON.parse(reader.result)
|
||||||
if (
|
if ("theme" in data && data.version == browser.runtime.getManifest().version) {
|
||||||
"theme" in data
|
browser.storage.local.clear(async () => {
|
||||||
&& data.version == browser.runtime.getManifest().version
|
browser.storage.local.set({ options: data }, () => {
|
||||||
) {
|
location.reload()
|
||||||
browser.storage.local.clear(async () => {
|
})
|
||||||
browser.storage.local.set({ options: data }, () => {
|
})
|
||||||
location.reload()
|
} else {
|
||||||
})
|
console.log("incompatible settings")
|
||||||
})
|
importError()
|
||||||
} else {
|
}
|
||||||
console.log("incompatible settings")
|
}
|
||||||
importError()
|
reader.onerror = error => {
|
||||||
}
|
console.log("error", error)
|
||||||
}
|
importError()
|
||||||
reader.onerror = error => {
|
}
|
||||||
console.log("error", error)
|
|
||||||
importError()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const exportSettingsSync = document.getElementById("export-settings-sync")
|
const exportSettingsSync = document.getElementById("export-settings-sync")
|
||||||
|
@ -73,58 +70,57 @@ const importSettingsSync = document.getElementById("import-settings-sync")
|
||||||
const importSettingsSyncText = document.getElementById("import_settings_sync_text")
|
const importSettingsSyncText = document.getElementById("import_settings_sync_text")
|
||||||
|
|
||||||
exportSettingsSync.addEventListener("click", async () => {
|
exportSettingsSync.addEventListener("click", async () => {
|
||||||
let options = await utils.getOptions()
|
let options = await utils.getOptions()
|
||||||
options.version = browser.runtime.getManifest().version
|
options.version = browser.runtime.getManifest().version
|
||||||
browser.storage.sync.set({ options }, () => location.reload())
|
browser.storage.sync.set({ options }, () => location.reload())
|
||||||
})
|
})
|
||||||
|
|
||||||
importSettingsSync.addEventListener("click", () => {
|
importSettingsSync.addEventListener("click", () => {
|
||||||
function importError() {
|
function importError() {
|
||||||
importSettingsSyncText.innerHTML = '<span style="color:red;">Error!</span>'
|
importSettingsSyncText.innerHTML = '<span style="color:red;">Error!</span>'
|
||||||
setTimeout(() => (importSettingsSyncText.innerHTML = oldHTML), 1000)
|
setTimeout(() => (importSettingsSyncText.innerHTML = oldHTML), 1000)
|
||||||
}
|
}
|
||||||
const oldHTML = importSettingsSyncText.innerHTML
|
const oldHTML = importSettingsSyncText.innerHTML
|
||||||
importSettingsSyncText.innerHTML = "..."
|
importSettingsSyncText.innerHTML = "..."
|
||||||
browser.storage.sync.get({ options }, r => {
|
browser.storage.sync.get({ options }, r => {
|
||||||
const options = r.options
|
const options = r.options
|
||||||
if (options.version == browser.runtime.getManifest().version) {
|
if (options.version == browser.runtime.getManifest().version) {
|
||||||
browser.storage.local.set({ options }, () => location.reload())
|
browser.storage.local.set({ options }, () => location.reload())
|
||||||
} else {
|
} else {
|
||||||
importError()
|
importError()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const resetSettings = document.getElementById("reset-settings")
|
const resetSettings = document.getElementById("reset-settings")
|
||||||
resetSettings.addEventListener("click", async () => {
|
resetSettings.addEventListener("click", async () => {
|
||||||
resetSettings.innerHTML = "..."
|
resetSettings.innerHTML = "..."
|
||||||
await servicesHelper.initDefaults()
|
await servicesHelper.initDefaults()
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchInstancesElement = document.getElementById('fetch-instances')
|
const fetchInstancesElement = document.getElementById("fetch-instances")
|
||||||
fetchInstancesElement.addEventListener('change', event => {
|
fetchInstancesElement.addEventListener("change", event => {
|
||||||
setOption('fetchInstances', 'select', event)
|
setOption("fetchInstances", "select", event)
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
const redirectOnlyInIncognitoElement = document.getElementById('redirectOnlyInIncognito')
|
const redirectOnlyInIncognitoElement = document.getElementById("redirectOnlyInIncognito")
|
||||||
redirectOnlyInIncognitoElement.addEventListener('change', event => {
|
redirectOnlyInIncognitoElement.addEventListener("change", event => {
|
||||||
setOption('redirectOnlyInIncognito', 'checkbox', event)
|
setOption("redirectOnlyInIncognito", "checkbox", event)
|
||||||
})
|
})
|
||||||
|
|
||||||
const bookmarksMenuElement = document.getElementById('bookmarksMenu')
|
const bookmarksMenuElement = document.getElementById("bookmarksMenu")
|
||||||
bookmarksMenuElement.addEventListener('change', async event => {
|
bookmarksMenuElement.addEventListener("change", async event => {
|
||||||
if (event.target.checked)
|
if (event.target.checked)
|
||||||
browser.permissions.request({ permissions: ["bookmarks"] }, r => bookmarksMenuElement.checked = r)
|
browser.permissions.request({ permissions: ["bookmarks"] }, r => (bookmarksMenuElement.checked = r))
|
||||||
else
|
else browser.permissions.remove({ permissions: ["bookmarks"] }, r => (bookmarksMenuElement.checked = !r))
|
||||||
browser.permissions.remove({ permissions: ["bookmarks"] }, r => bookmarksMenuElement.checked = !r)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let themeElement = document.getElementById("theme")
|
let themeElement = document.getElementById("theme")
|
||||||
themeElement.addEventListener("change", event => {
|
themeElement.addEventListener("change", event => {
|
||||||
setOption("theme", "select", event)
|
setOption("theme", "select", event)
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
let nameCustomInstanceInput = document.getElementById("exceptions-custom-instance")
|
let nameCustomInstanceInput = document.getElementById("exceptions-custom-instance")
|
||||||
|
@ -134,40 +130,44 @@ let instanceType = "url"
|
||||||
let config = await utils.getConfig()
|
let config = await utils.getConfig()
|
||||||
|
|
||||||
for (const service in config.services) {
|
for (const service in config.services) {
|
||||||
document.getElementById(service).addEventListener("change", async event => {
|
document.getElementById(service).addEventListener("change", async event => {
|
||||||
let options = await utils.getOptions()
|
let options = await utils.getOptions()
|
||||||
if (event.target.checked && !options.popupServices.includes(service)) options.popupServices.push(service)
|
if (event.target.checked && !options.popupServices.includes(service)) options.popupServices.push(service)
|
||||||
else if (options.popupServices.includes(service)) {
|
else if (options.popupServices.includes(service)) {
|
||||||
var index = options.popupServices.indexOf(service)
|
var index = options.popupServices.indexOf(service)
|
||||||
if (index !== -1) options.popupServices.splice(index, 1)
|
if (index !== -1) options.popupServices.splice(index, 1)
|
||||||
}
|
}
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = await utils.getOptions()
|
let options = await utils.getOptions()
|
||||||
themeElement.value = options.theme
|
themeElement.value = options.theme
|
||||||
fetchInstancesElement.value = options.fetchInstances
|
fetchInstancesElement.value = options.fetchInstances
|
||||||
redirectOnlyInIncognitoElement.checked = options.redirectOnlyInIncognito
|
redirectOnlyInIncognitoElement.checked = options.redirectOnlyInIncognito
|
||||||
browser.permissions.contains({ permissions: ["bookmarks"] }, r => bookmarksMenuElement.checked = r)
|
browser.permissions.contains({ permissions: ["bookmarks"] }, r => (bookmarksMenuElement.checked = r))
|
||||||
for (const service in config.services) document.getElementById(service).checked = options.popupServices.includes(service)
|
for (const service in config.services)
|
||||||
|
document.getElementById(service).checked = options.popupServices.includes(service)
|
||||||
|
|
||||||
instanceTypeElement.addEventListener("change", event => {
|
instanceTypeElement.addEventListener("change", event => {
|
||||||
instanceType = event.target.options[instanceTypeElement.selectedIndex].value
|
instanceType = event.target.options[instanceTypeElement.selectedIndex].value
|
||||||
if (instanceType == "url") {
|
if (instanceType == "url") {
|
||||||
nameCustomInstanceInput.setAttribute("type", "url")
|
nameCustomInstanceInput.setAttribute("type", "url")
|
||||||
nameCustomInstanceInput.setAttribute("placeholder", "https://www.google.com")
|
nameCustomInstanceInput.setAttribute("placeholder", "https://www.google.com")
|
||||||
} else if (instanceType == "regex") {
|
} else if (instanceType == "regex") {
|
||||||
nameCustomInstanceInput.setAttribute("type", "text")
|
nameCustomInstanceInput.setAttribute("type", "text")
|
||||||
nameCustomInstanceInput.setAttribute("placeholder", "https?://(www.|)youtube.com/")
|
nameCustomInstanceInput.setAttribute("placeholder", "https?://(www.|)youtube.com/")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let exceptionsCustomInstances = options.exceptions
|
let exceptionsCustomInstances = options.exceptions
|
||||||
function calcExceptionsCustomInstances() {
|
function calcExceptionsCustomInstances() {
|
||||||
document.getElementById("exceptions-custom-checklist").innerHTML = [...exceptionsCustomInstances.url, ...exceptionsCustomInstances.regex]
|
document.getElementById("exceptions-custom-checklist").innerHTML = [
|
||||||
.map(
|
...exceptionsCustomInstances.url,
|
||||||
x => `<div>
|
...exceptionsCustomInstances.regex,
|
||||||
|
]
|
||||||
|
.map(
|
||||||
|
x => `<div>
|
||||||
${x}
|
${x}
|
||||||
<button class="add" id="clear-${x}">
|
<button class="add" id="clear-${x}">
|
||||||
<svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"
|
<svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"
|
||||||
|
@ -177,40 +177,40 @@ function calcExceptionsCustomInstances() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>`
|
<hr>`
|
||||||
)
|
)
|
||||||
.join("\n")
|
.join("\n")
|
||||||
|
|
||||||
for (const x of [...exceptionsCustomInstances.url, ...exceptionsCustomInstances.regex]) {
|
for (const x of [...exceptionsCustomInstances.url, ...exceptionsCustomInstances.regex]) {
|
||||||
document.getElementById(`clear-${x}`).addEventListener("click", async () => {
|
document.getElementById(`clear-${x}`).addEventListener("click", async () => {
|
||||||
let index
|
let index
|
||||||
index = exceptionsCustomInstances.url.indexOf(x)
|
index = exceptionsCustomInstances.url.indexOf(x)
|
||||||
if (index > -1) exceptionsCustomInstances.url.splice(index, 1)
|
if (index > -1) exceptionsCustomInstances.url.splice(index, 1)
|
||||||
else {
|
else {
|
||||||
index = exceptionsCustomInstances.regex.indexOf(x)
|
index = exceptionsCustomInstances.regex.indexOf(x)
|
||||||
if (index > -1) exceptionsCustomInstances.regex.splice(index, 1)
|
if (index > -1) exceptionsCustomInstances.regex.splice(index, 1)
|
||||||
}
|
}
|
||||||
options = await utils.getOptions()
|
options = await utils.getOptions()
|
||||||
options.exceptions = exceptionsCustomInstances
|
options.exceptions = exceptionsCustomInstances
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
calcExceptionsCustomInstances()
|
calcExceptionsCustomInstances()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
calcExceptionsCustomInstances()
|
calcExceptionsCustomInstances()
|
||||||
document.getElementById("custom-exceptions-instance-form").addEventListener("submit", async event => {
|
document.getElementById("custom-exceptions-instance-form").addEventListener("submit", async event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
let val
|
let val
|
||||||
if (instanceType == "url" && nameCustomInstanceInput.validity.valid) {
|
if (instanceType == "url" && nameCustomInstanceInput.validity.valid) {
|
||||||
val = nameCustomInstanceInput.value
|
val = nameCustomInstanceInput.value
|
||||||
if (!exceptionsCustomInstances.url.includes(val)) exceptionsCustomInstances.url.push(val)
|
if (!exceptionsCustomInstances.url.includes(val)) exceptionsCustomInstances.url.push(val)
|
||||||
} else if (instanceType == "regex") {
|
} else if (instanceType == "regex") {
|
||||||
val = nameCustomInstanceInput.value
|
val = nameCustomInstanceInput.value
|
||||||
if (val.trim() != "" && !exceptionsCustomInstances.regex.includes(val)) exceptionsCustomInstances.regex.push(val)
|
if (val.trim() != "" && !exceptionsCustomInstances.regex.includes(val)) exceptionsCustomInstances.regex.push(val)
|
||||||
}
|
}
|
||||||
if (val) {
|
if (val) {
|
||||||
options = await utils.getOptions()
|
options = await utils.getOptions()
|
||||||
options.exceptions = exceptionsCustomInstances
|
options.exceptions = exceptionsCustomInstances
|
||||||
browser.storage.local.set({ options }, () => nameCustomInstanceInput.value = "")
|
browser.storage.local.set({ options }, () => (nameCustomInstanceInput.value = ""))
|
||||||
}
|
}
|
||||||
calcExceptionsCustomInstances()
|
calcExceptionsCustomInstances()
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,15 +5,15 @@ import servicesHelper from "../../assets/javascripts/services.js"
|
||||||
import utils from "../../assets/javascripts/utils.js"
|
import utils from "../../assets/javascripts/utils.js"
|
||||||
|
|
||||||
document.getElementById("more-options").href = browser.runtime.getURL("pages/options/index.html")
|
document.getElementById("more-options").href = browser.runtime.getURL("pages/options/index.html")
|
||||||
document.getElementById("more-options").setAttribute('target', '_blank')
|
document.getElementById("more-options").setAttribute("target", "_blank")
|
||||||
|
|
||||||
await browser.runtime.getPlatformInfo(r => {
|
await browser.runtime.getPlatformInfo(r => {
|
||||||
switch (r.os) {
|
switch (r.os) {
|
||||||
case "fuchsia":
|
case "fuchsia":
|
||||||
case "ios":
|
case "ios":
|
||||||
case "android":
|
case "android":
|
||||||
document.getElementsByTagName("html")[0].classList.add("mobile")
|
document.getElementsByTagName("html")[0].classList.add("mobile")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const allSites = document.getElementById("all_sites")
|
const allSites = document.getElementById("all_sites")
|
||||||
|
@ -24,94 +24,96 @@ const config = await utils.getConfig()
|
||||||
const divs = {}
|
const divs = {}
|
||||||
|
|
||||||
for (const service in config.services) {
|
for (const service in config.services) {
|
||||||
divs[service] = {}
|
divs[service] = {}
|
||||||
|
|
||||||
divs[service].all = allSites.getElementsByClassName(service)[0]
|
divs[service].all = allSites.getElementsByClassName(service)[0]
|
||||||
divs[service].current = currSite.getElementsByClassName(service)[0]
|
divs[service].current = currSite.getElementsByClassName(service)[0]
|
||||||
|
|
||||||
divs[service].all_toggle = allSites.getElementsByClassName(`${service}-enabled`)[0]
|
divs[service].all_toggle = allSites.getElementsByClassName(`${service}-enabled`)[0]
|
||||||
divs[service].all_toggle.addEventListener("change", async () => {
|
divs[service].all_toggle.addEventListener("change", async () => {
|
||||||
const options = await utils.getOptions()
|
const options = await utils.getOptions()
|
||||||
options[service].enabled = divs[service].all_toggle.checked
|
options[service].enabled = divs[service].all_toggle.checked
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
})
|
})
|
||||||
|
|
||||||
allSites.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
|
allSites.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
|
browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
divs[service].current_toggle = currSite.getElementsByClassName(`${service}-enabled`)[0]
|
divs[service].current_toggle = currSite.getElementsByClassName(`${service}-enabled`)[0]
|
||||||
divs[service].current_toggle.addEventListener("change", async () => {
|
divs[service].current_toggle.addEventListener("change", async () => {
|
||||||
const options = await utils.getOptions()
|
const options = await utils.getOptions()
|
||||||
options[service].enabled = divs[service].current_toggle.checked
|
options[service].enabled = divs[service].current_toggle.checked
|
||||||
browser.storage.local.set({ options })
|
browser.storage.local.set({ options })
|
||||||
})
|
})
|
||||||
|
|
||||||
currSite.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
|
currSite.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const url = new URL(tabs[0].url)
|
const url = new URL(tabs[0].url)
|
||||||
browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
|
browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
|
||||||
// Set visibility of control buttons
|
// Set visibility of control buttons
|
||||||
if (tabs[0].url) {
|
if (tabs[0].url) {
|
||||||
const hr = document.getElementById("hr")
|
const hr = document.getElementById("hr")
|
||||||
var url = new URL(tabs[0].url)
|
var url = new URL(tabs[0].url)
|
||||||
servicesHelper.switchInstance(url).then(r => {
|
servicesHelper.switchInstance(url).then(r => {
|
||||||
if (r) {
|
if (r) {
|
||||||
document.getElementById("change_instance_div").style.display = ""
|
document.getElementById("change_instance_div").style.display = ""
|
||||||
hr.style.display = ""
|
hr.style.display = ""
|
||||||
document.getElementById("change_instance").addEventListener("click", async () =>
|
document
|
||||||
browser.tabs.update({ url: await servicesHelper.switchInstance(url) })
|
.getElementById("change_instance")
|
||||||
)
|
.addEventListener("click", async () => browser.tabs.update({ url: await servicesHelper.switchInstance(url) }))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
servicesHelper.reverse(url).then(r => {
|
servicesHelper.reverse(url).then(r => {
|
||||||
if (r) {
|
if (r) {
|
||||||
hr.style.display = ""
|
hr.style.display = ""
|
||||||
|
|
||||||
document.getElementById("copy_original_div").style.display = ""
|
document.getElementById("copy_original_div").style.display = ""
|
||||||
document.getElementById("copy_original").addEventListener("click", () => servicesHelper.copyRaw(url))
|
document.getElementById("copy_original").addEventListener("click", () => servicesHelper.copyRaw(url))
|
||||||
|
|
||||||
document.getElementById("redirect_to_original_div").style.display = ""
|
document.getElementById("redirect_to_original_div").style.display = ""
|
||||||
document.getElementById("redirect_to_original").addEventListener("click", () => browser.runtime.sendMessage("reverseTab"))
|
document
|
||||||
}
|
.getElementById("redirect_to_original")
|
||||||
})
|
.addEventListener("click", () => browser.runtime.sendMessage("reverseTab"))
|
||||||
servicesHelper.redirectAsync(url, "main_frame", null, true).then(r => {
|
}
|
||||||
if (r) {
|
})
|
||||||
document.getElementById("redirect_div").style.display = ""
|
servicesHelper.redirectAsync(url, "main_frame", null, true).then(r => {
|
||||||
hr.style.display = ""
|
if (r) {
|
||||||
document.getElementById("redirect").addEventListener("click", () => browser.runtime.sendMessage("redirectTab"))
|
document.getElementById("redirect_div").style.display = ""
|
||||||
}
|
hr.style.display = ""
|
||||||
})
|
document.getElementById("redirect").addEventListener("click", () => browser.runtime.sendMessage("redirectTab"))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const options = await utils.getOptions()
|
const options = await utils.getOptions()
|
||||||
|
|
||||||
// Set visibility of all service buttons
|
// Set visibility of all service buttons
|
||||||
for (const service of options.popupServices) {
|
for (const service of options.popupServices) {
|
||||||
divs[service].all.classList.remove("hide")
|
divs[service].all.classList.remove("hide")
|
||||||
divs[service].all_toggle.checked = options[service].enabled
|
divs[service].all_toggle.checked = options[service].enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set visibility of current page service button
|
// Set visibility of current page service button
|
||||||
if (url) {
|
if (url) {
|
||||||
const service = await servicesHelper.computeService(url)
|
const service = await servicesHelper.computeService(url)
|
||||||
if (service) {
|
if (service) {
|
||||||
divs[service].all.classList.add("hide")
|
divs[service].all.classList.add("hide")
|
||||||
divs[service].current.classList.remove("hide")
|
divs[service].current.classList.remove("hide")
|
||||||
divs[service].current_toggle.checked = options[service].enabled
|
divs[service].current_toggle.checked = options[service].enabled
|
||||||
currentSiteDivider.style.display = ""
|
currentSiteDivider.style.display = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -1,65 +1,65 @@
|
||||||
body {
|
body {
|
||||||
width: 270px;
|
width: 270px;
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button svg {
|
.button svg {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-button {
|
.bottom-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space {
|
.space {
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
height: 23px;
|
height: 23px;
|
||||||
width: 46px;
|
width: 46px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block label {
|
div.block label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block label:hover {
|
div.block label:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block div {
|
div.block div {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile body {
|
html.mobile body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile div.block label {
|
html.mobile div.block label {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile .button svg {
|
html.mobile .button svg {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
|
@ -1,450 +1,449 @@
|
||||||
body {
|
body {
|
||||||
--text: #fff;
|
--text: #fff;
|
||||||
--bg-main: #121212;
|
--bg-main: #121212;
|
||||||
--bg-secondary: #202020;
|
--bg-secondary: #202020;
|
||||||
--active: #fbc117;
|
--active: #fbc117;
|
||||||
--danger: #f04141;
|
--danger: #f04141;
|
||||||
--light-grey: #c3c3c3;
|
--light-grey: #c3c3c3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter";
|
font-family: "Inter";
|
||||||
src: url("Inter-VariableFont_slnt,wght.ttf");
|
src: url("Inter-VariableFont_slnt,wght.ttf");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Vazirmatn";
|
font-family: "Vazirmatn";
|
||||||
src: url("Vazirmatn-VariableFont_wght.ttf");
|
src: url("Vazirmatn-VariableFont_wght.ttf");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: "Inter";
|
font-family: "Inter";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-color: var(--bg-main);
|
background-color: var(--bg-main);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
body * {
|
body * {
|
||||||
font-family: "Inter";
|
font-family: "Inter";
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl {
|
body.rtl {
|
||||||
font-family: "Vazirmatn";
|
font-family: "Vazirmatn";
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl * {
|
body.rtl * {
|
||||||
font-family: "Vazirmatn";
|
font-family: "Vazirmatn";
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block input[type="checkbox"] {
|
div.block input[type="checkbox"] {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: min-content;
|
width: min-content;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
transition: .1s;
|
transition: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title:hover {
|
.title:hover {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title:hover a {
|
.title:hover a {
|
||||||
color: var(--active);
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
img,
|
img,
|
||||||
svg {
|
svg {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
width: 26px;
|
width: 26px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl img,
|
body.rtl img,
|
||||||
body.rtl svg {
|
body.rtl svg {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="url"],
|
input[type="url"],
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
select {
|
select {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #767676;
|
border-color: #767676;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="url"],
|
input[type="url"],
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:invalid {
|
input:invalid {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button svg {
|
.button svg {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.block-option {
|
section.block-option {
|
||||||
width: 750px;
|
width: 750px;
|
||||||
margin: 0 50px;
|
margin: 0 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.block-option h2 {
|
section.block-option h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.option {
|
body.option {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
width: 1160px;
|
width: 1160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.links {
|
section.links {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
max-height: 1030px;
|
max-height: 1030px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.links div {
|
section.links div {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--active);
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
section.links a {
|
section.links a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: white;
|
color: white;
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.links a:hover,
|
section.links a:hover,
|
||||||
section.links .selected {
|
section.links .selected {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.links .selected a {
|
section.links .selected a {
|
||||||
color: var(--active);
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
height: 2px;
|
height: 2px;
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
background-color: rgb(77, 77, 77);
|
background-color: rgb(77, 77, 77);
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block {
|
div.block {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block-option {
|
div.block-option {
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block-option label {
|
div.block-option label {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block-option h1 {
|
div.block-option h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block-option div {
|
div.block-option div {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block input[type="checkbox"] {
|
div.block input[type="checkbox"] {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background-color: var(--light-grey);
|
background-color: var(--light-grey);
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block input[type="checkbox"]:checked {
|
div.block input[type="checkbox"]:checked {
|
||||||
background-color: var(--active);
|
background-color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block input[type="checkbox"]::before {
|
div.block input[type="checkbox"]::before {
|
||||||
content: "";
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2.5px;
|
top: 2.5px;
|
||||||
left: 3.5px;
|
left: 3.5px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl div.block input[type="checkbox"]::before {
|
body.rtl div.block input[type="checkbox"]::before {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 4px;
|
right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.block input[type="checkbox"]:checked::before {
|
div.block input[type="checkbox"]:checked::before {
|
||||||
left: 24px;
|
left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl div.block input[type="checkbox"]:checked::before {
|
body.rtl div.block input[type="checkbox"]:checked::before {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.buttons {
|
div.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-duration: 0.1s;
|
transition-duration: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
color: var(--active);
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button svg {
|
.button svg {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover svg {
|
.button:hover svg {
|
||||||
color: var(--active);
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-inline {
|
.button-inline {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 7.5px 0;
|
margin: 7.5px 0;
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:active {
|
.button:active {
|
||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
button svg {
|
button svg {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.checklist div {
|
div.checklist div {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 5px 15px;
|
margin: 5px 15px;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.checklist a {
|
div.checklist a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.checklist a:hover {
|
div.checklist a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.custom-checklist x a {
|
div.custom-checklist x a {
|
||||||
color: var(--active);
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.add {
|
button.add {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.light-theme {
|
body.light-theme {
|
||||||
--text: black;
|
--text: black;
|
||||||
--bg-main: white;
|
--bg-main: white;
|
||||||
--bg-secondary: #e4e4e4;
|
--bg-secondary: #e4e4e4;
|
||||||
--active: #fb9817;
|
--active: #fb9817;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.light-theme select {
|
body.light-theme select {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.light-theme a {
|
body.light-theme a {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.light-theme a:hover {
|
body.light-theme a:hover {
|
||||||
color: var(--active)
|
color: var(--active);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body div section {
|
body div section {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
select:disabled {
|
select:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:disabled {
|
input:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1250px) {
|
@media (max-width: 1250px) {
|
||||||
body.option {
|
body.option {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 40px 0px;
|
padding: 40px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.links {
|
section.links {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
padding: 0 55px;
|
padding: 0 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.block-option {
|
section.block-option {
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.checklist div x {
|
div.checklist div x {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile img,
|
html.mobile img,
|
||||||
html.mobile svg {
|
html.mobile svg {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile div.block {
|
html.mobile div.block {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile div.block input[type="checkbox"] {
|
html.mobile div.block input[type="checkbox"] {
|
||||||
width: 58px;
|
width: 58px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile div.block input[type="checkbox"]::before {
|
html.mobile div.block input[type="checkbox"]::before {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
left: 3.5px;
|
left: 3.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile div.block input[type="checkbox"]:checked::before {
|
html.mobile div.block input[type="checkbox"]:checked::before {
|
||||||
left: 30px;
|
left: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile body.option {
|
html.mobile body.option {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html.mobile section.links {
|
html.mobile section.links {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 55px;
|
padding: 0 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.mobile section.block-option {
|
html.mobile section.block-option {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
|
@ -47,7 +47,7 @@ Bandcamp - [https://thorwegian.bandcamp.com/track/just-because](https://thorwegi
|
||||||
|
|
||||||
Instructables - [https://instructables.com/DIY-Arduino-Obstacle-Avoiding-Car-at-Home/](https://instructables.com/DIY-Arduino-Obstacle-Avoiding-Car-at-Home/)
|
Instructables - [https://instructables.com/DIY-Arduino-Obstacle-Avoiding-Car-at-Home/](https://instructables.com/DIY-Arduino-Obstacle-Avoiding-Car-at-Home/)
|
||||||
|
|
||||||
Web archive - [https://web.archive.org/web/20230131222432if_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html](https://web.archive.org/web/20230131222432if_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html)
|
Web archive - [https://web.archive.org/web/20230131222432if\_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html](https://web.archive.org/web/20230131222432if_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue