Migrating UI to Svelte
|
@ -7,3 +7,4 @@ package-lock.json
|
||||||
src/pages/options/index.html
|
src/pages/options/index.html
|
||||||
src/pages/popup/popup.html
|
src/pages/popup/popup.html
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
src/pages/options/build
|
13
package.json
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "libredirect",
|
"name": "libredirect",
|
||||||
|
"type": "module",
|
||||||
"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",
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
"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": "rollup -c"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -24,8 +25,16 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://libredirect.github.io",
|
"homepage": "https://libredirect.github.io",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"prettier": "3.3.3",
|
||||||
"pug-cli": "^1.0.0-alpha6",
|
"pug-cli": "^1.0.0-alpha6",
|
||||||
"web-ext": "^7.2.0"
|
"web-ext": "^7.2.0",
|
||||||
|
"@rollup/plugin-commonjs": "^24.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
||||||
|
"@rollup/plugin-terser": "^0.4.0",
|
||||||
|
"rollup": "^3.15.0",
|
||||||
|
"rollup-plugin-css-only": "^4.3.0",
|
||||||
|
"rollup-plugin-svelte": "^7.1.2",
|
||||||
|
"svelte": "^3.55.0"
|
||||||
},
|
},
|
||||||
"webExt": {
|
"webExt": {
|
||||||
"sourceDir": "./src/",
|
"sourceDir": "./src/",
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import svelte from "rollup-plugin-svelte"
|
||||||
|
import commonjs from "@rollup/plugin-commonjs"
|
||||||
|
import terser from "@rollup/plugin-terser"
|
||||||
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
|
import css from "rollup-plugin-css-only"
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "src/pages/src/main.js",
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: "iife",
|
||||||
|
name: "app",
|
||||||
|
file: "src/pages/options/build/bundle.js",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
compilerOptions: {
|
||||||
|
dev: !production,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
css({ output: "bundle.css" }),
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ["svelte"],
|
||||||
|
exportConditions: ["svelte"],
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
// production && terser(),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,399 +0,0 @@
|
||||||
import utils from "../../assets/javascripts/utils.js"
|
|
||||||
|
|
||||||
let config,
|
|
||||||
options,
|
|
||||||
blacklist,
|
|
||||||
redirects,
|
|
||||||
divs = {}
|
|
||||||
|
|
||||||
for (const a of document.getElementById("links").getElementsByTagName("a")) {
|
|
||||||
if (!a.href.includes("https://")) {
|
|
||||||
a.addEventListener("click", e => {
|
|
||||||
const path = a.getAttribute("href").replace("#", "")
|
|
||||||
loadPage(path)
|
|
||||||
e.preventDefault()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config = await utils.getConfig()
|
|
||||||
options = await utils.getOptions()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} service
|
|
||||||
*/
|
|
||||||
async function changeFrontendsSettings(service) {
|
|
||||||
options = await utils.getOptions()
|
|
||||||
const opacityDiv = document.getElementById(`${service}-opacity`)
|
|
||||||
if (document.getElementById(`${service}-enabled`).checked) {
|
|
||||||
opacityDiv.style.pointerEvents = "auto"
|
|
||||||
opacityDiv.style.opacity = 1
|
|
||||||
opacityDiv.style.userSelect = "auto"
|
|
||||||
} else {
|
|
||||||
opacityDiv.style.pointerEvents = "none"
|
|
||||||
opacityDiv.style.opacity = 0.4
|
|
||||||
opacityDiv.style.userSelect = "none"
|
|
||||||
}
|
|
||||||
for (const frontend in config.services[service].frontends) {
|
|
||||||
if (config.services[service].frontends[frontend].instanceList) {
|
|
||||||
const frontendDiv = document.getElementById(frontend)
|
|
||||||
if (typeof divs[service].frontend !== "undefined") {
|
|
||||||
if (
|
|
||||||
frontend == divs[service].frontend.value ||
|
|
||||||
(config.services[service].frontends[divs[service].frontend.value].desktopApp &&
|
|
||||||
divs[service].embedFrontend &&
|
|
||||||
frontend == divs[service].embedFrontend.value)
|
|
||||||
) {
|
|
||||||
frontendDiv.style.display = ""
|
|
||||||
if (config.services[service].frontends[frontend].localhost === true) {
|
|
||||||
document.getElementById(`${service}-instance-div`).style.display = ""
|
|
||||||
|
|
||||||
if (options[service].instance == "localhost") {
|
|
||||||
frontendDiv.style.display = "none"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById(`${service}-instance-div`).style.display = "none"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
frontendDiv.style.display = "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (document.getElementById(`${service}-redirectType`)) {
|
|
||||||
const frontend = options[service].frontend
|
|
||||||
if (config.services[service].frontends[frontend].embeddable) {
|
|
||||||
document.getElementById(`${service}-redirectType`).innerHTML = `
|
|
||||||
<option value="both" data-localise="__MSG_both__">both</options>
|
|
||||||
<option value="sub_frame" data-localise="__MSG_onlyEmbedded__">Only Embedded</option>
|
|
||||||
<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
|
|
||||||
`
|
|
||||||
} else if (
|
|
||||||
config.services[service].frontends[frontend].desktopApp &&
|
|
||||||
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="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
|
|
||||||
`
|
|
||||||
if (options[service].redirectType == "sub_frame") {
|
|
||||||
options[service].redirectType = "main_frame"
|
|
||||||
browser.storage.local.set({ options })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById(`${service}-redirectType`).innerHTML =
|
|
||||||
'<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>'
|
|
||||||
options[service].redirectType = "main_frame"
|
|
||||||
|
|
||||||
browser.storage.local.set({ options })
|
|
||||||
}
|
|
||||||
document.getElementById(`${service}-redirectType`).value = options[service].redirectType
|
|
||||||
if (config.services[service].frontends[frontend].desktopApp && options[service].redirectType != "main_frame") {
|
|
||||||
document.getElementById(`${service}-embedFrontend-div`).style.display = ""
|
|
||||||
document.getElementById(divs[service].embedFrontend.value).style.display = ""
|
|
||||||
} else if (
|
|
||||||
config.services[service].frontends[frontend].desktopApp &&
|
|
||||||
options[service].redirectType == "main_frame"
|
|
||||||
) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} path
|
|
||||||
*/
|
|
||||||
async function loadPage(path) {
|
|
||||||
options = await utils.getOptions()
|
|
||||||
for (const section of document.getElementById("pages").getElementsByTagName("section")) section.style.display = "none"
|
|
||||||
document.getElementById(`${path}_page`).style.display = "block"
|
|
||||||
|
|
||||||
for (const element of document.getElementsByClassName("title")) {
|
|
||||||
const a = element.getElementsByTagName("a")[0]
|
|
||||||
if (a.getAttribute("href") == `#${path}`) {
|
|
||||||
element.classList.add("selected")
|
|
||||||
} else {
|
|
||||||
element.classList.remove("selected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const service in config.services) {
|
|
||||||
if (options[service].enabled) {
|
|
||||||
document.getElementById(`${service}-link`).style.opacity = 1
|
|
||||||
} else {
|
|
||||||
document.getElementById(`${service}-link`).style.opacity = 0.4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.history.pushState({ id: "100" }, "Page 2", `/pages/options/index.html#${path}`)
|
|
||||||
|
|
||||||
if (path != "general") {
|
|
||||||
const service = path
|
|
||||||
divs[service] = {}
|
|
||||||
for (const option in config.services[service].options) {
|
|
||||||
divs[service][option] = document.getElementById(`${service}-${option}`)
|
|
||||||
if (typeof config.services[service].options[option] == "boolean")
|
|
||||||
divs[service][option].checked = options[service][option]
|
|
||||||
else divs[service][option].value = options[service][option]
|
|
||||||
divs[service][option].addEventListener("change", async () => {
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
if (typeof config.services[service].options[option] == "boolean")
|
|
||||||
options[service][option] = divs[service][option].checked
|
|
||||||
else options[service][option] = divs[service][option].value
|
|
||||||
browser.storage.local.set({ options })
|
|
||||||
changeFrontendsSettings(service)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
changeFrontendsSettings(service)
|
|
||||||
|
|
||||||
blacklist = await utils.getBlacklist(options)
|
|
||||||
redirects = await utils.getList(options)
|
|
||||||
for (const frontend in config.services[service].frontends) {
|
|
||||||
if (config.services[service].frontends[frontend].instanceList) {
|
|
||||||
if (redirects == "disabled" || blacklist == "disabled") {
|
|
||||||
document.getElementById(frontend).getElementsByClassName("clearnet")[0].style.display = "none"
|
|
||||||
document.getElementById(frontend).getElementsByClassName("ping")[0].style.display = "none"
|
|
||||||
} else if (!redirects || !blacklist) {
|
|
||||||
document
|
|
||||||
.getElementById(frontend)
|
|
||||||
.getElementsByClassName("clearnet")[0]
|
|
||||||
.getElementsByClassName("checklist")[0]
|
|
||||||
.getElementsByClassName("loading")[0].innerHTML = "Could not fetch instances."
|
|
||||||
} else {
|
|
||||||
createList(frontend)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const frontend in config.services[service].frontends) {
|
|
||||||
if (config.services[service].frontends[frontend].instanceList) {
|
|
||||||
processCustomInstances(frontend)
|
|
||||||
document.getElementById(`ping-${frontend}`).addEventListener("click", async () => {
|
|
||||||
document.getElementById(`ping-${frontend}`).getElementsByTagName("x")[0].innerHTML = "Pinging..."
|
|
||||||
await ping(frontend)
|
|
||||||
document.getElementById(`ping-${frontend}`).getElementsByTagName("x")[0].innerHTML = "Ping instances"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calcCustomInstances(frontend) {
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
let customInstances = options[frontend]
|
|
||||||
const pingCache = await utils.getPingCache()
|
|
||||||
|
|
||||||
document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].innerHTML = customInstances
|
|
||||||
.map(x => {
|
|
||||||
const time = pingCache[x]
|
|
||||||
if (time) {
|
|
||||||
var { color, text } = processTime(time)
|
|
||||||
}
|
|
||||||
const timeText = time ? `<span class="ping" style="color:${color};">${text}</span>` : ""
|
|
||||||
const custom = isCustomInstance(frontend, x) ? "" : `<span>custom</span>`
|
|
||||||
return `<div>
|
|
||||||
<x>
|
|
||||||
<a href="${x}" target="_blank">${x}</a>
|
|
||||||
${timeText}
|
|
||||||
${custom}
|
|
||||||
</x>
|
|
||||||
<button class="add clear-${x}">
|
|
||||||
<svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
|
|
||||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr>`
|
|
||||||
})
|
|
||||||
.join("\n")
|
|
||||||
for (const item of customInstances) {
|
|
||||||
document
|
|
||||||
.getElementById(frontend)
|
|
||||||
.getElementsByClassName(`clear-${item}`)[0]
|
|
||||||
.addEventListener("click", async () => {
|
|
||||||
const index = customInstances.indexOf(item)
|
|
||||||
if (index > -1) customInstances.splice(index, 1)
|
|
||||||
options = await utils.getOptions()
|
|
||||||
options[frontend] = customInstances
|
|
||||||
browser.storage.local.set({ options }, async () => {
|
|
||||||
calcCustomInstances(frontend)
|
|
||||||
createList(frontend)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processCustomInstances(frontend) {
|
|
||||||
calcCustomInstances(frontend)
|
|
||||||
document
|
|
||||||
.getElementById(frontend)
|
|
||||||
.getElementsByClassName("custom-instance-form")[0]
|
|
||||||
.addEventListener("submit", async event => {
|
|
||||||
event.preventDefault()
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
let customInstances = options[frontend]
|
|
||||||
let frontendCustomInstanceInput = document.getElementById(frontend).getElementsByClassName("custom-instance")[0]
|
|
||||||
try {
|
|
||||||
var url = new URL(frontendCustomInstanceInput.value)
|
|
||||||
} catch (error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let protocolHostVar = utils.protocolHost(url)
|
|
||||||
if (frontendCustomInstanceInput.validity.valid) {
|
|
||||||
if (!customInstances.includes(protocolHostVar)) {
|
|
||||||
customInstances.push(protocolHostVar)
|
|
||||||
options = await utils.getOptions()
|
|
||||||
options[frontend] = customInstances
|
|
||||||
browser.storage.local.set({ options }, () => {
|
|
||||||
frontendCustomInstanceInput.value = ""
|
|
||||||
calcCustomInstances(frontend)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} frontend
|
|
||||||
*/
|
|
||||||
async function createList(frontend) {
|
|
||||||
const pingCache = await utils.getPingCache()
|
|
||||||
const options = await utils.getOptions()
|
|
||||||
for (const network in config.networks) {
|
|
||||||
const checklist = document
|
|
||||||
.getElementById(frontend)
|
|
||||||
.getElementsByClassName(network)[0]
|
|
||||||
.getElementsByClassName("checklist")[0]
|
|
||||||
|
|
||||||
if (!redirects[frontend]) {
|
|
||||||
checklist.innerHTML = '<div class="block block-option">No instances found.</div>'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const instances = redirects[frontend][network]
|
|
||||||
if (!instances || instances.length === 0) continue
|
|
||||||
|
|
||||||
document.getElementById(frontend).getElementsByClassName("custom-instance")[0].placeholder =
|
|
||||||
redirects[frontend].clearnet[0]
|
|
||||||
|
|
||||||
instances.sort((a, b) => blacklist.cloudflare.includes(a) && !blacklist.cloudflare.includes(b))
|
|
||||||
const content = instances.map(x => {
|
|
||||||
const cloudflare = blacklist.cloudflare.includes(x)
|
|
||||||
? `<a target="_blank" href="https://libredirect.github.io/docs.html#instances">
|
|
||||||
<span style="color:red;">cloudflare</span>
|
|
||||||
</a>`
|
|
||||||
: ""
|
|
||||||
|
|
||||||
let time = pingCache[x]
|
|
||||||
let timeText = ""
|
|
||||||
if (time) {
|
|
||||||
const { color, text } = processTime(time)
|
|
||||||
timeText = `<span class="ping" style="color:${color};">${text}</span>`
|
|
||||||
}
|
|
||||||
|
|
||||||
const chosen = options[frontend].includes(x) ? `<span style="color:grey;">chosen</span>` : ""
|
|
||||||
|
|
||||||
const warnings = [cloudflare, timeText, chosen].join(" ")
|
|
||||||
return `<div class="frontend">
|
|
||||||
<x>
|
|
||||||
<a href="${x}" target="_blank">${x}</a>
|
|
||||||
${warnings}
|
|
||||||
</x>
|
|
||||||
<button class="add add-${x}">
|
|
||||||
<svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
|
|
||||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>`
|
|
||||||
})
|
|
||||||
|
|
||||||
checklist.innerHTML = [
|
|
||||||
`<div class="block block-option">
|
|
||||||
<label>${utils.camelCase(network)}</label>
|
|
||||||
</div>`,
|
|
||||||
...content,
|
|
||||||
"<br>",
|
|
||||||
].join("\n<hr>\n")
|
|
||||||
|
|
||||||
for (const instance of instances) {
|
|
||||||
checklist.getElementsByClassName(`add-${instance}`)[0].addEventListener("click", async () => {
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
if (!options[frontend].includes(instance)) {
|
|
||||||
options[frontend].push(instance)
|
|
||||||
browser.storage.local.set({ options }, () => {
|
|
||||||
calcCustomInstances(frontend)
|
|
||||||
createList(frontend)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const r = window.location.href.match(/#(.*)/)
|
|
||||||
if (r) loadPage(r[1])
|
|
||||||
else loadPage("general")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} frontend
|
|
||||||
*/
|
|
||||||
async function ping(frontend) {
|
|
||||||
const instanceElements = [
|
|
||||||
...document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].getElementsByTagName("x"),
|
|
||||||
...document.getElementById(frontend).getElementsByClassName("clearnet")[0].getElementsByTagName("x"),
|
|
||||||
]
|
|
||||||
|
|
||||||
let pingCache = await utils.getPingCache()
|
|
||||||
let redundancyList = {}
|
|
||||||
for (const element of instanceElements) {
|
|
||||||
let span = element.getElementsByClassName("ping")[0]
|
|
||||||
if (!span) span = document.createElement("span")
|
|
||||||
span.classList = ["ping"]
|
|
||||||
span.innerHTML = '<span style="color:lightblue">pinging...</span>'
|
|
||||||
element.appendChild(span)
|
|
||||||
const href = element.getElementsByTagName("a")[0].href
|
|
||||||
const innerHTML = element.getElementsByTagName("a")[0].innerHTML
|
|
||||||
const time = redundancyList[innerHTML] ?? (await utils.ping(href))
|
|
||||||
const { color, text } = processTime(time)
|
|
||||||
span.innerHTML = `<span style="color:${color};">${text}</span>`
|
|
||||||
pingCache[innerHTML] = time
|
|
||||||
redundancyList[innerHTML] = time
|
|
||||||
|
|
||||||
browser.storage.local.set({ pingCache })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} time
|
|
||||||
*/
|
|
||||||
function processTime(time) {
|
|
||||||
let text
|
|
||||||
let color
|
|
||||||
if (time < 5000) {
|
|
||||||
text = `${time}ms`
|
|
||||||
if (time <= 1000) color = "green"
|
|
||||||
else if (time <= 2000) color = "orange"
|
|
||||||
} else if (time >= 5000) {
|
|
||||||
color = "red"
|
|
||||||
if (time == 5000) text = "5000ms+"
|
|
||||||
if (time > 5000) text = `Error: ${time - 5000}`
|
|
||||||
} else {
|
|
||||||
color = "red"
|
|
||||||
text = "Server not found"
|
|
||||||
}
|
|
||||||
return { color, text }
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCustomInstance(frontend, instance) {
|
|
||||||
for (const network in config.networks) {
|
|
||||||
if (!redirects[frontend]) return false
|
|
||||||
const instances = redirects[frontend][network]
|
|
||||||
if (instances.includes(instance)) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
doctype html
|
|
||||||
html(id="elementToShowWithJavaScript" lang="en")
|
|
||||||
include /src/pages/widgets/head
|
|
||||||
body(class="option" dir="auto")
|
|
||||||
include /src/pages/widgets/links
|
|
||||||
div#pages
|
|
||||||
include /src/pages/options/widgets/general
|
|
||||||
include /src/pages/options/widgets/services
|
|
||||||
script(type="module" src="./index.js")
|
|
||||||
|
|
|
@ -1,216 +0,0 @@
|
||||||
"use strict"
|
|
||||||
window.browser = window.browser || window.chrome
|
|
||||||
|
|
||||||
import utils from "../../../assets/javascripts/utils.js"
|
|
||||||
import servicesHelper from "../../../assets/javascripts/services.js"
|
|
||||||
|
|
||||||
const isChrome = browser.runtime.getBrowserInfo === undefined
|
|
||||||
|
|
||||||
async function setOption(option, type, event) {
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
switch (type) {
|
|
||||||
case "select":
|
|
||||||
options[option] = event.target.options[event.target.options.selectedIndex].value
|
|
||||||
break
|
|
||||||
case "checkbox":
|
|
||||||
options[option] = event.target.checked
|
|
||||||
break
|
|
||||||
case "range":
|
|
||||||
options[option] = event.target.value
|
|
||||||
break
|
|
||||||
}
|
|
||||||
browser.storage.local.set({ options })
|
|
||||||
}
|
|
||||||
|
|
||||||
const exportSettingsElement = document.getElementById("export-settings")
|
|
||||||
async function exportSettings() {
|
|
||||||
const options = await utils.getOptions()
|
|
||||||
options.version = browser.runtime.getManifest().version
|
|
||||||
let resultString = JSON.stringify(options, null, " ")
|
|
||||||
exportSettingsElement.href = "data:application/json;base64," + btoa(resultString)
|
|
||||||
exportSettingsElement.download = `libredirect-settings-v${options.version}.json`
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exportSettings()
|
|
||||||
document.getElementById("general_page").onclick = exportSettings
|
|
||||||
|
|
||||||
const importSettingsElement = document.getElementById("import-settings")
|
|
||||||
const importSettingsElementText = document.getElementById("import_settings_text")
|
|
||||||
importSettingsElement.addEventListener("change", () => {
|
|
||||||
function importError() {
|
|
||||||
const oldHTML = importSettingsElementText.innerHTML
|
|
||||||
importSettingsElementText.innerHTML = '<span style="color:red;">Error!</span>'
|
|
||||||
setTimeout(() => (importSettingsElementText.innerHTML = oldHTML), 1000)
|
|
||||||
}
|
|
||||||
importSettingsElementText.innerHTML = "..."
|
|
||||||
let file = importSettingsElement.files[0]
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.readAsText(file)
|
|
||||||
reader.onload = async () => {
|
|
||||||
const data = JSON.parse(reader.result)
|
|
||||||
if ("theme" in data && data.version == browser.runtime.getManifest().version) {
|
|
||||||
browser.storage.local.clear(async () => {
|
|
||||||
browser.storage.local.set({ options: data }, () => {
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log("incompatible settings")
|
|
||||||
importError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.onerror = error => {
|
|
||||||
console.log("error", error)
|
|
||||||
importError()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const exportSettingsSync = document.getElementById("export-settings-sync")
|
|
||||||
const importSettingsSync = document.getElementById("import-settings-sync")
|
|
||||||
const importSettingsSyncText = document.getElementById("import_settings_sync_text")
|
|
||||||
|
|
||||||
exportSettingsSync.addEventListener("click", async () => {
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
options.version = browser.runtime.getManifest().version
|
|
||||||
browser.storage.sync.set({ options }, () => location.reload())
|
|
||||||
})
|
|
||||||
|
|
||||||
importSettingsSync.addEventListener("click", () => {
|
|
||||||
function importError() {
|
|
||||||
importSettingsSyncText.innerHTML = '<span style="color:red;">Error!</span>'
|
|
||||||
setTimeout(() => (importSettingsSyncText.innerHTML = oldHTML), 1000)
|
|
||||||
}
|
|
||||||
const oldHTML = importSettingsSyncText.innerHTML
|
|
||||||
importSettingsSyncText.innerHTML = "..."
|
|
||||||
browser.storage.sync.get({ options }, r => {
|
|
||||||
const options = r.options
|
|
||||||
if (options.version == browser.runtime.getManifest().version) {
|
|
||||||
browser.storage.local.set({ options }, () => location.reload())
|
|
||||||
} else {
|
|
||||||
importError()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const resetSettings = document.getElementById("reset-settings")
|
|
||||||
resetSettings.addEventListener("click", async () => {
|
|
||||||
resetSettings.innerHTML = "..."
|
|
||||||
await servicesHelper.initDefaults()
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchInstancesElement = document.getElementById("fetch-instances")
|
|
||||||
fetchInstancesElement.addEventListener("change", event => {
|
|
||||||
setOption("fetchInstances", "select", event)
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
|
|
||||||
const redirectOnlyInIncognitoElement = document.getElementById("redirectOnlyInIncognito")
|
|
||||||
redirectOnlyInIncognitoElement.addEventListener("change", event => {
|
|
||||||
setOption("redirectOnlyInIncognito", "checkbox", event)
|
|
||||||
})
|
|
||||||
|
|
||||||
const bookmarksMenuElement = document.getElementById("bookmarksMenu")
|
|
||||||
bookmarksMenuElement.addEventListener("change", async event => {
|
|
||||||
if (event.target.checked)
|
|
||||||
browser.permissions.request({ permissions: ["bookmarks"] }, r => (bookmarksMenuElement.checked = r))
|
|
||||||
else browser.permissions.remove({ permissions: ["bookmarks"] }, r => (bookmarksMenuElement.checked = !r))
|
|
||||||
})
|
|
||||||
|
|
||||||
let themeElement = document.getElementById("theme")
|
|
||||||
themeElement.addEventListener("change", event => {
|
|
||||||
setOption("theme", "select", event)
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
|
|
||||||
let nameCustomInstanceInput = document.getElementById("exceptions-custom-instance")
|
|
||||||
let instanceTypeElement = document.getElementById("exceptions-custom-instance-type")
|
|
||||||
let instanceType = "url"
|
|
||||||
|
|
||||||
let config = await utils.getConfig()
|
|
||||||
|
|
||||||
for (const service in config.services) {
|
|
||||||
document.getElementById(service).addEventListener("change", async event => {
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
if (event.target.checked && !options.popupServices.includes(service)) options.popupServices.push(service)
|
|
||||||
else if (options.popupServices.includes(service)) {
|
|
||||||
var index = options.popupServices.indexOf(service)
|
|
||||||
if (index !== -1) options.popupServices.splice(index, 1)
|
|
||||||
}
|
|
||||||
browser.storage.local.set({ options })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = await utils.getOptions()
|
|
||||||
themeElement.value = options.theme
|
|
||||||
fetchInstancesElement.value = options.fetchInstances
|
|
||||||
redirectOnlyInIncognitoElement.checked = options.redirectOnlyInIncognito
|
|
||||||
browser.permissions.contains({ permissions: ["bookmarks"] }, r => (bookmarksMenuElement.checked = r))
|
|
||||||
for (const service in config.services)
|
|
||||||
document.getElementById(service).checked = options.popupServices.includes(service)
|
|
||||||
|
|
||||||
instanceTypeElement.addEventListener("change", event => {
|
|
||||||
instanceType = event.target.options[instanceTypeElement.selectedIndex].value
|
|
||||||
if (instanceType == "url") {
|
|
||||||
nameCustomInstanceInput.setAttribute("type", "url")
|
|
||||||
nameCustomInstanceInput.setAttribute("placeholder", "https://www.google.com")
|
|
||||||
} else if (instanceType == "regex") {
|
|
||||||
nameCustomInstanceInput.setAttribute("type", "text")
|
|
||||||
nameCustomInstanceInput.setAttribute("placeholder", "https?://(www.|)youtube.com/")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let exceptionsCustomInstances = options.exceptions
|
|
||||||
function calcExceptionsCustomInstances() {
|
|
||||||
document.getElementById("exceptions-custom-checklist").innerHTML = [
|
|
||||||
...exceptionsCustomInstances.url,
|
|
||||||
...exceptionsCustomInstances.regex,
|
|
||||||
]
|
|
||||||
.map(
|
|
||||||
x => `<div>
|
|
||||||
${x}
|
|
||||||
<button class="add" id="clear-${x}">
|
|
||||||
<svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"
|
|
||||||
fill="currentColor">
|
|
||||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr>`
|
|
||||||
)
|
|
||||||
.join("\n")
|
|
||||||
|
|
||||||
for (const x of [...exceptionsCustomInstances.url, ...exceptionsCustomInstances.regex]) {
|
|
||||||
document.getElementById(`clear-${x}`).addEventListener("click", async () => {
|
|
||||||
let index
|
|
||||||
index = exceptionsCustomInstances.url.indexOf(x)
|
|
||||||
if (index > -1) exceptionsCustomInstances.url.splice(index, 1)
|
|
||||||
else {
|
|
||||||
index = exceptionsCustomInstances.regex.indexOf(x)
|
|
||||||
if (index > -1) exceptionsCustomInstances.regex.splice(index, 1)
|
|
||||||
}
|
|
||||||
options = await utils.getOptions()
|
|
||||||
options.exceptions = exceptionsCustomInstances
|
|
||||||
browser.storage.local.set({ options })
|
|
||||||
calcExceptionsCustomInstances()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
calcExceptionsCustomInstances()
|
|
||||||
document.getElementById("custom-exceptions-instance-form").addEventListener("submit", async event => {
|
|
||||||
event.preventDefault()
|
|
||||||
let val
|
|
||||||
if (instanceType == "url" && nameCustomInstanceInput.validity.valid) {
|
|
||||||
val = nameCustomInstanceInput.value
|
|
||||||
if (!exceptionsCustomInstances.url.includes(val)) exceptionsCustomInstances.url.push(val)
|
|
||||||
} else if (instanceType == "regex") {
|
|
||||||
val = nameCustomInstanceInput.value
|
|
||||||
if (val.trim() != "" && !exceptionsCustomInstances.regex.includes(val)) exceptionsCustomInstances.regex.push(val)
|
|
||||||
}
|
|
||||||
if (val) {
|
|
||||||
options = await utils.getOptions()
|
|
||||||
options.exceptions = exceptionsCustomInstances
|
|
||||||
browser.storage.local.set({ options }, () => (nameCustomInstanceInput.value = ""))
|
|
||||||
}
|
|
||||||
calcExceptionsCustomInstances()
|
|
||||||
})
|
|
|
@ -1,88 +0,0 @@
|
||||||
section(class="block-option" id="general_page")
|
|
||||||
div(class="block block-option")
|
|
||||||
h1(data-localise="__MSG_general__") General
|
|
||||||
hr
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(data-localise="__MSG_theme__") Theme
|
|
||||||
select(id="theme" aria-label="select theme")
|
|
||||||
option(value="detect" data-localise="__MSG_auto__") Auto
|
|
||||||
option(value="light" data-localise="__MSG_light__") Light
|
|
||||||
option(value="dark" data-localise="__MSG_dark__") Dark
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(data-localise="__MSG_fetchPublicInstances__") Fetch public instances
|
|
||||||
select(id="fetch-instances" aria-label="Select fetch public instances")
|
|
||||||
option(value="github") GitHub
|
|
||||||
option(value="codeberg") Codeberg
|
|
||||||
option(value="disable" data-localise="__MSG_disable__") Disable
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for='redirectOnlyInIncognito' data-localise="__MSG_redirectOnlyInIncognito__") Redirect Only in Incognito
|
|
||||||
input(id='redirectOnlyInIncognito' type="checkbox")
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for='bookmarksMenu' data-localise="__MSG_bookmarksMenu__") Bookmarks menu
|
|
||||||
input(id='bookmarksMenu' type="checkbox")
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(data-localise="__MSG_excludeFromRedirecting__") Excluded from redirecting
|
|
||||||
|
|
||||||
form(id="custom-exceptions-instance-form")
|
|
||||||
div(class="block block-option")
|
|
||||||
div(class="block" style="padding: 0")
|
|
||||||
input(id="exceptions-custom-instance" placeholder="https://www.google.com" type="url" aria-label="Add url exception input")
|
|
||||||
|
|
|
||||||
select(id="exceptions-custom-instance-type")
|
|
||||||
option(value="url") URL
|
|
||||||
option(value="regex") Regex
|
|
||||||
|
|
|
||||||
button(class="add" id="exceptions-add-instance" type="submit" aria-label="Add the url exception")
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor")
|
|
||||||
path(d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z")
|
|
||||||
|
|
||||||
hr
|
|
||||||
|
|
||||||
div(class="checklist" id="exceptions-custom-checklist")
|
|
||||||
|
|
||||||
div(class="buttons")
|
|
||||||
label(class="button button-inline" id="import_settings_text" for="import-settings")
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
|
|
||||||
path(d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z")
|
|
||||||
|
|
|
||||||
x(data-localise="__MSG_importSettings__") Import Settings
|
|
||||||
input(id="import-settings" type="file" accept=".json" style="display: none")
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
a(class="button button-inline" id="export-settings")
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
|
|
||||||
path(d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z")
|
|
||||||
|
|
|
||||||
x(data-localise="__MSG_exportSettings__") Export Settings
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
button(class="button button-inline" id="export-settings-sync")
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
|
|
||||||
path(d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z")
|
|
||||||
|
|
|
||||||
x() Export Settings to Sync
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
button(class="button button-inline" id="import-settings-sync")
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
|
|
||||||
path(d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z")
|
|
||||||
|
|
|
||||||
x(id="import_settings_sync_text") Import Settings from Sync
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
button(class="button button-inline" id="reset-settings")
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
|
|
||||||
path(d="M12,5V2L8,6l4,4V7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02c3.95-0.49,7-3.85,7-7.93C20,8.58,16.42,5,12,5z")
|
|
||||||
path(d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z")
|
|
||||||
x(data-localise="__MSG_resetSettings__") Reset Settings
|
|
||||||
|
|
||||||
script(type="module" src="./widgets/general.js")
|
|
|
@ -1,83 +0,0 @@
|
||||||
each val, service in services
|
|
||||||
section(class="block-option" id=service+"_page")
|
|
||||||
div(class="block block-option")
|
|
||||||
h1
|
|
||||||
a(target="_blank" href=services[service].url)=services[service].name
|
|
||||||
|
|
||||||
hr
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for=`${service}-enabled` data-localise="__MSG_enable__") Enable
|
|
||||||
input(id=`${service}-enabled` type="checkbox")
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for=service data-localise="__MSG_showInPopup__") Show in popup
|
|
||||||
input(id=service type="checkbox")
|
|
||||||
|
|
||||||
div(id=service+"-opacity")
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for=`${service}-frontend`)
|
|
||||||
a(class="frontend_name" target="_blank" data-localise="__MSG_frontend__") Frontend
|
|
||||||
select(id=`${service}-frontend`)
|
|
||||||
each val, frontend in services[service].frontends
|
|
||||||
option(value=frontend)=services[service].frontends[frontend].name
|
|
||||||
|
|
||||||
div(class="block block-option" id=service+"-instance-div")
|
|
||||||
label(for=`${service}-instance`) Instance Type
|
|
||||||
select(id=`${service}-instance`)
|
|
||||||
option(value="localhost") localhost
|
|
||||||
option(value="public") public instances
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for=`${service}-redirectType` data-localise="__MSG_redirectType__") Redirect Type
|
|
||||||
select(id=`${service}-redirectType`)
|
|
||||||
|
|
||||||
|
|
||||||
div(id=`${service}-embedFrontend-div` class="block block-option")
|
|
||||||
label(for=`${service}-embedFrontend` data-localise="__MSG_embedFrontend__") Embed Frontend
|
|
||||||
select(id=`${service}-embedFrontend`)
|
|
||||||
each val, frontend in services[service].frontends
|
|
||||||
if services[service].frontends[frontend].embeddable && services[service].frontends[frontend].instanceList
|
|
||||||
option(value=frontend)=services[service].frontends[frontend].name
|
|
||||||
|
|
||||||
|
|
||||||
div(class="block block-option")
|
|
||||||
label(for=`${service}-unsupportedUrls` data-localise="__MSG_unsupportedIframesHandling__") Unsupported iframes handling
|
|
||||||
select(id=`${service}-unsupportedUrls`)
|
|
||||||
option(value="bypass") bypass
|
|
||||||
option(value="block") block
|
|
||||||
|
|
||||||
if (service == 'search')
|
|
||||||
div(class="block block-option")
|
|
||||||
label Set LibRedirect as Default Search Engine. For how to do in chromium browsers, click <a href="https://libredirect.github.io/docs.html#search_engine_chromium">here</a>.
|
|
||||||
|
|
||||||
|
|
||||||
each val, frontend in services[service].frontends
|
|
||||||
if services[service].frontends[frontend].instanceList
|
|
||||||
div(id=frontend dir="ltr")
|
|
||||||
hr
|
|
||||||
div(dir="auto" class="block block-option")
|
|
||||||
label(data-localise="__MSG_addYourFavoriteInstances__") Add your favorite instances
|
|
||||||
|
|
||||||
form(class="custom-instance-form")
|
|
||||||
div(class="block block-option")
|
|
||||||
input(class="custom-instance" type="url" placeholder="https://instance.com" aria-label="Add instance input")
|
|
||||||
button(class="add add-instance" type="submit" aria-label="Add the instance")
|
|
||||||
svg(xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor")
|
|
||||||
path(d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z")
|
|
||||||
|
|
||||||
div(class="checklist custom-checklist")
|
|
||||||
|
|
||||||
div(class="ping block")
|
|
||||||
button(class="button button-inline" id=`ping-${frontend}`)
|
|
||||||
svg(xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor")
|
|
||||||
path(d="M10.45 15.5q.6.6 1.55.587.95-.012 1.4-.687L19 7l-8.4 5.6q-.675.45-.712 1.375-.038.925.562 1.525ZM12 4q1.475 0 2.838.412Q16.2 4.825 17.4 5.65l-1.9 1.2q-.825-.425-1.712-.637Q12.9 6 12 6 8.675 6 6.338 8.337 4 10.675 4 14q0 1.05.287 2.075Q4.575 17.1 5.1 18h13.8q.575-.95.838-1.975Q20 15 20 13.9q0-.9-.212-1.75-.213-.85-.638-1.65l1.2-1.9q.75 1.175 1.188 2.5.437 1.325.462 2.75.025 1.425-.325 2.725-.35 1.3-1.025 2.475-.275.45-.75.7-.475.25-1 .25H5.1q-.525 0-1-.25t-.75-.7q-.65-1.125-1-2.387Q2 15.4 2 14q0-2.075.788-3.888.787-1.812 2.15-3.175Q6.3 5.575 8.125 4.787 9.95 4 12 4Zm.175 7.825Z")
|
|
||||||
|
|
|
||||||
x() Ping instances
|
|
||||||
|
|
||||||
each val, network in networks
|
|
||||||
div(class=network)
|
|
||||||
div(class="checklist")
|
|
||||||
if (network == 'clearnet')
|
|
||||||
div(class="block block-option loading") Loading...
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
<script>
|
||||||
|
let browser = window.browser || window.chrome
|
||||||
|
|
||||||
|
import General from "./General/General.svelte"
|
||||||
|
import utils from "../../assets/javascripts/utils.js"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
import servicesHelper from "../../assets/javascripts/services.js"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import Sidebar from "./Sidebar.svelte"
|
||||||
|
import { options, config, page } from "./stores"
|
||||||
|
import Services from "./Services/Services.svelte"
|
||||||
|
|
||||||
|
let _options
|
||||||
|
const unsubscribeOptions = options.subscribe(val => {
|
||||||
|
if (val) {
|
||||||
|
_options = val
|
||||||
|
browser.storage.local.set({ options: val })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let _config
|
||||||
|
const unsubscribeConfig = config.subscribe(val => (_config = val))
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
unsubscribeOptions()
|
||||||
|
unsubscribeConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
let opts = await utils.getOptions()
|
||||||
|
if (!opts) {
|
||||||
|
console.log("init defulats")
|
||||||
|
await servicesHelper.initDefaults()
|
||||||
|
opts = await utils.getOptions()
|
||||||
|
}
|
||||||
|
options.set(opts)
|
||||||
|
config.set(await utils.getConfig())
|
||||||
|
})
|
||||||
|
|
||||||
|
let _page
|
||||||
|
page.subscribe(val => (_page = val))
|
||||||
|
|
||||||
|
const dark = {
|
||||||
|
text: "#fff",
|
||||||
|
bgMain: "#121212",
|
||||||
|
bgSecondary: "#202020",
|
||||||
|
active: "#fbc117",
|
||||||
|
danger: "#f04141",
|
||||||
|
lightGrey: "#c3c3c3",
|
||||||
|
}
|
||||||
|
const light = {
|
||||||
|
text: "black",
|
||||||
|
bgMain: "white",
|
||||||
|
bgSecondary: "#e4e4e4",
|
||||||
|
active: "#fb9817",
|
||||||
|
danger: "#f04141",
|
||||||
|
lightGrey: "#c3c3c3",
|
||||||
|
}
|
||||||
|
let cssVariables
|
||||||
|
$: if (_options) {
|
||||||
|
if (_options.theme == "dark") {
|
||||||
|
cssVariables = dark
|
||||||
|
} else if (_options.theme == "light") {
|
||||||
|
cssVariables = light
|
||||||
|
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
|
cssVariables = dark
|
||||||
|
} else {
|
||||||
|
cssVariables = light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if _options && _config}
|
||||||
|
<div
|
||||||
|
class="main"
|
||||||
|
dir="auto"
|
||||||
|
style="
|
||||||
|
--text: {cssVariables.text};
|
||||||
|
--bg-main: {cssVariables.bgMain};
|
||||||
|
--bg-secondary: {cssVariables.bgSecondary};
|
||||||
|
--active: {cssVariables.active};
|
||||||
|
--danger: {cssVariables.danger};
|
||||||
|
--light-grey: {cssVariables.lightGrey};"
|
||||||
|
>
|
||||||
|
<Sidebar />
|
||||||
|
{#if _page == "general"}
|
||||||
|
<General />
|
||||||
|
{:else if _page == "services"}
|
||||||
|
<Services />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>Loading...</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(body) {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content 700px;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 50px;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: "Inter";
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: var(--bg-main);
|
||||||
|
color: var(--text);
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,108 @@
|
||||||
|
<script>
|
||||||
|
import Row from "../components/Row.svelte"
|
||||||
|
import Select from "../components/Select.svelte"
|
||||||
|
import AddIcon from "../icons/AddIcon.svelte"
|
||||||
|
import CloseIcon from "../icons/CloseIcon.svelte"
|
||||||
|
import Input from "../components/Input.svelte"
|
||||||
|
import Label from "../components/Label.svelte"
|
||||||
|
import { options, config } from "../stores"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
let _options
|
||||||
|
let _config
|
||||||
|
|
||||||
|
const unsubscribeOptions = options.subscribe(val => (_options = val))
|
||||||
|
const unsubscribeConfig = config.subscribe(val => (_config = val))
|
||||||
|
onDestroy(() => {
|
||||||
|
unsubscribeOptions()
|
||||||
|
unsubscribeConfig()
|
||||||
|
})
|
||||||
|
let inputType = "url"
|
||||||
|
let inputValue = ""
|
||||||
|
|
||||||
|
$: inputPlaceholder = inputType == "url" ? "https://www.google.com" : "https?://(www.|)youtube.com/"
|
||||||
|
|
||||||
|
function removeException(exception) {
|
||||||
|
let index
|
||||||
|
index = _options.exceptions.url.indexOf(exception)
|
||||||
|
if (index > -1) {
|
||||||
|
_options.exceptions.url.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
index = _options.exceptions.regex.indexOf(exception)
|
||||||
|
if (index > -1) _options.exceptions.regex.splice(index, 1)
|
||||||
|
}
|
||||||
|
options.set(_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addException() {
|
||||||
|
let valid = false
|
||||||
|
if (inputType == "url" && /^(ftp|http|https):\/\/[^ "]+$/.test(inputValue)) {
|
||||||
|
valid = true
|
||||||
|
if (!_options.exceptions.url.includes(inputValue)) {
|
||||||
|
_options.exceptions.url.push(inputValue)
|
||||||
|
}
|
||||||
|
} else if (inputType == "regex") {
|
||||||
|
valid = true
|
||||||
|
if (!_options.exceptions.regex.includes(inputValue)) {
|
||||||
|
_options.exceptions.regex.push(inputValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
options.set(_options)
|
||||||
|
inputValue = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="block block-option">
|
||||||
|
<Row>
|
||||||
|
<Label>Excluded from redirecting</Label>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder={inputPlaceholder}
|
||||||
|
aria-label="Add url exception input"
|
||||||
|
bind:value={inputValue}
|
||||||
|
on:keydown={e => {
|
||||||
|
if (e.key === "Enter") addException()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
bind:value={inputType}
|
||||||
|
values={[
|
||||||
|
{ value: "url", name: "URL" },
|
||||||
|
{ value: "regex", name: "Regex" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button class="add" on:click={addException} aria-label="Add the url exception">
|
||||||
|
<AddIcon />
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<hr />
|
||||||
|
<div class="checklist">
|
||||||
|
{#each [..._options.exceptions.url, ..._options.exceptions.regex] as exception}
|
||||||
|
<Row>
|
||||||
|
{exception}
|
||||||
|
<button class="add" on:click={() => removeException(exception)}>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<hr />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.add {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script>
|
||||||
|
let browser = window.browser || window.chrome
|
||||||
|
|
||||||
|
import Exceptions from "./Exceptions.svelte"
|
||||||
|
import SettingsButtons from "./SettingsButtons.svelte"
|
||||||
|
import RowSelect from "../components/RowSelect.svelte"
|
||||||
|
import Checkbox from "../components/RowCheckbox.svelte"
|
||||||
|
import { options } from "../stores"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
let _options
|
||||||
|
const unsubscribe = options.subscribe(val => (_options = val))
|
||||||
|
onDestroy(unsubscribe)
|
||||||
|
|
||||||
|
let bookmarksPermission
|
||||||
|
browser.permissions.contains({ permissions: ["bookmarks"] }, r => (bookmarksPermission = r))
|
||||||
|
$: if (bookmarksPermission) {
|
||||||
|
browser.permissions.request({ permissions: ["bookmarks"] }, r => (bookmarksPermission = r))
|
||||||
|
} else {
|
||||||
|
browser.permissions.remove({ permissions: ["bookmarks"] })
|
||||||
|
bookmarksPermission = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<RowSelect
|
||||||
|
label="Theme"
|
||||||
|
values={[
|
||||||
|
{ value: "detect", name: "Auto" },
|
||||||
|
{ value: "light", name: "Light" },
|
||||||
|
{ value: "dark", name: "Dark" },
|
||||||
|
]}
|
||||||
|
value={_options.theme}
|
||||||
|
onChange={e => {
|
||||||
|
_options["theme"] = e.target.options[e.target.options.selectedIndex].value
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
ariaLabel="select theme"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RowSelect
|
||||||
|
label="Fetch public instances"
|
||||||
|
value={_options.fetchInstances}
|
||||||
|
onChange={e => {
|
||||||
|
_options["fetchInstances"] = e.target.options[e.target.options.selectedIndex].value
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
ariaLabel="Select fetch public instances"
|
||||||
|
values={[
|
||||||
|
{ value: "github", name: "GitHub" },
|
||||||
|
{ value: "codeberg", name: "Codeberg" },
|
||||||
|
{ value: "disable", name: "Disable" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
label="Redirect Only in Incognito"
|
||||||
|
checked={_options.redirectOnlyInIncognito}
|
||||||
|
onChange={e => {
|
||||||
|
_options["redirectOnlyInIncognito"] = e.target.checked
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox label="Bookmarks menu" bind:checked={bookmarksPermission} />
|
||||||
|
|
||||||
|
<Exceptions opts={_options} />
|
||||||
|
|
||||||
|
<SettingsButtons opts={_options} />
|
||||||
|
</div>
|
|
@ -0,0 +1,118 @@
|
||||||
|
<script>
|
||||||
|
let browser = window.browser || window.chrome
|
||||||
|
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
import Button from "../components/Button.svelte"
|
||||||
|
import ExportIcon from "../icons/ExportIcon.svelte"
|
||||||
|
import ImportIcon from "../icons/ImportIcon.svelte"
|
||||||
|
import ResetIcon from "../icons/ResetIcon.svelte"
|
||||||
|
import { options } from "../stores"
|
||||||
|
import servicesHelper from "../../../assets/javascripts/services.js"
|
||||||
|
import utils from "../../../assets/javascripts/utils.js"
|
||||||
|
|
||||||
|
let _options
|
||||||
|
const unsubscribe = options.subscribe(val => (_options = val))
|
||||||
|
onDestroy(unsubscribe)
|
||||||
|
|
||||||
|
let disableButtons = false
|
||||||
|
|
||||||
|
let importSettingsInput
|
||||||
|
let importSettingsFiles
|
||||||
|
$: if (importSettingsFiles) {
|
||||||
|
disableButtons = true
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.readAsText(importSettingsFiles[0])
|
||||||
|
reader.onload = async () => {
|
||||||
|
const data = JSON.parse(reader.result)
|
||||||
|
if ("theme" in data && data.version == browser.runtime.getManifest().version) {
|
||||||
|
browser.storage.local.clear(async () => {
|
||||||
|
console.log("clearing")
|
||||||
|
options.set(data)
|
||||||
|
disableButtons = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log("incompatible settings")
|
||||||
|
alert("Incompatible settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.onerror = error => {
|
||||||
|
console.log("error", error)
|
||||||
|
alert("Error!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportSettings() {
|
||||||
|
disableButtons = true
|
||||||
|
_options.version = browser.runtime.getManifest().version
|
||||||
|
const resultString = JSON.stringify(_options, null, " ")
|
||||||
|
const anchor = document.createElement("a")
|
||||||
|
anchor.href = "data:application/json;base64," + btoa(resultString)
|
||||||
|
anchor.download = `libredirect-settings-v${_options.version}.json`
|
||||||
|
anchor.click()
|
||||||
|
disableButtons = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportSettingsSync() {
|
||||||
|
disableButtons = true
|
||||||
|
_options.version = browser.runtime.getManifest().version
|
||||||
|
await servicesHelper.initDefaults()
|
||||||
|
browser.storage.sync.set({ options: _options })
|
||||||
|
disableButtons = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importSettingsSync() {
|
||||||
|
disableButtons = true
|
||||||
|
browser.storage.sync.get({ options }, r => {
|
||||||
|
const optionsSync = r.options
|
||||||
|
if (optionsSync.version == browser.runtime.getManifest().version) {
|
||||||
|
options.set(optionsSync)
|
||||||
|
} else {
|
||||||
|
alert("Error")
|
||||||
|
}
|
||||||
|
disableButtons = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetSettings() {
|
||||||
|
disableButtons = true
|
||||||
|
browser.storage.local.clear(async () => {
|
||||||
|
await servicesHelper.initDefaults()
|
||||||
|
options.set(await utils.getOptions())
|
||||||
|
disableButtons = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<Button on:click={() => importSettingsInput.click()} disabled={disableButtons}>
|
||||||
|
<ImportIcon />
|
||||||
|
<x data-localise="__MSG_importSettings__">Import Settings</x>
|
||||||
|
</Button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
style="display: none"
|
||||||
|
bind:this={importSettingsInput}
|
||||||
|
bind:files={importSettingsFiles}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button on:click={exportSettings} disabled={disableButtons}>
|
||||||
|
<ExportIcon />
|
||||||
|
<x data-localise="__MSG_exportSettings__">Export Settings</x>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button on:click={exportSettingsSync} disabled={disableButtons}>
|
||||||
|
<ExportIcon />
|
||||||
|
<x>Export Settings to Sync</x>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button on:click={importSettingsSync} disabled={disableButtons}>
|
||||||
|
<ImportIcon />
|
||||||
|
<x>Import Settings from Sync</x>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button on:click={resetSettings} disabled={disableButtons}>
|
||||||
|
<ResetIcon />
|
||||||
|
<x>Reset Settings</x>
|
||||||
|
</Button>
|
||||||
|
</div>
|
|
@ -0,0 +1,234 @@
|
||||||
|
<script>
|
||||||
|
let browser = window.browser || window.chrome
|
||||||
|
|
||||||
|
import Button from "../components/Button.svelte"
|
||||||
|
import AddIcon from "../icons/AddIcon.svelte"
|
||||||
|
import { options, config } from "../stores"
|
||||||
|
import PingIcon from "../icons/PingIcon.svelte"
|
||||||
|
import Row from "../components/Row.svelte"
|
||||||
|
import Input from "../components/Input.svelte"
|
||||||
|
import Label from "../components/Label.svelte"
|
||||||
|
import CloseIcon from "../icons/CloseIcon.svelte"
|
||||||
|
import { onDestroy, onMount } from "svelte"
|
||||||
|
import utils from "../../../assets/javascripts/utils"
|
||||||
|
|
||||||
|
export let selectedService
|
||||||
|
|
||||||
|
let _options
|
||||||
|
let _config
|
||||||
|
|
||||||
|
const unsubscribeOptions = options.subscribe(val => (_options = val))
|
||||||
|
const unsubscribeConfig = config.subscribe(val => (_config = val))
|
||||||
|
onDestroy(() => {
|
||||||
|
unsubscribeOptions()
|
||||||
|
unsubscribeConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
let blacklist
|
||||||
|
let redirects
|
||||||
|
|
||||||
|
$: serviceOptions = _options[selectedService]
|
||||||
|
$: serviceConf = _config.services[selectedService]
|
||||||
|
|
||||||
|
let allInstances = []
|
||||||
|
|
||||||
|
$: {
|
||||||
|
allInstances = []
|
||||||
|
if (_options[serviceOptions.frontend]) allInstances.push(..._options[serviceOptions.frontend])
|
||||||
|
if (redirects && redirects[serviceOptions.frontend]) {
|
||||||
|
for (const network in redirects[serviceOptions.frontend]) {
|
||||||
|
allInstances.push(...redirects[serviceOptions.frontend][network])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pingCache
|
||||||
|
$: {
|
||||||
|
if (pingCache) browser.storage.local.set({ pingCache })
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCustomInstance(instance) {
|
||||||
|
if (redirects[serviceOptions.frontend]) {
|
||||||
|
for (const network in redirects[serviceOptions.frontend]) {
|
||||||
|
if (redirects[serviceOptions.frontend][network].includes(instance)) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pingInstances() {
|
||||||
|
pingCache = {}
|
||||||
|
for (const instance of allInstances) {
|
||||||
|
console.log("pinging...", instance)
|
||||||
|
pingCache[instance] = { color: "lightblue", value: "pinging..." }
|
||||||
|
const time = await utils.ping(instance)
|
||||||
|
pingCache[instance] = processTime(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function processTime(time) {
|
||||||
|
let value
|
||||||
|
let color
|
||||||
|
if (time < 5000) {
|
||||||
|
value = `${time}ms`
|
||||||
|
if (time <= 1000) color = "green"
|
||||||
|
else if (time <= 2000) color = "orange"
|
||||||
|
} else if (time >= 5000) {
|
||||||
|
color = "red"
|
||||||
|
if (time == 5000) value = "5000ms+"
|
||||||
|
if (time > 5000) value = `Error: ${time - 5000}`
|
||||||
|
} else {
|
||||||
|
color = "red"
|
||||||
|
value = "Server not found"
|
||||||
|
}
|
||||||
|
return { color, value }
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
blacklist = await utils.getBlacklist(_options)
|
||||||
|
redirects = await utils.getList(_options)
|
||||||
|
pingCache = await utils.getPingCache()
|
||||||
|
})
|
||||||
|
|
||||||
|
let addInstanceValue
|
||||||
|
function addInstance() {
|
||||||
|
const instance = utils.protocolHost(new URL(addInstanceValue))
|
||||||
|
if (!_options[serviceOptions.frontend].includes(instance)) {
|
||||||
|
_options[serviceOptions.frontend].push(instance)
|
||||||
|
addInstanceValue = ""
|
||||||
|
options.set(_options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if serviceConf.frontends[serviceOptions.frontend].instanceList && redirects && blacklist}
|
||||||
|
<hr />
|
||||||
|
<div dir="ltr">
|
||||||
|
<div class="ping">
|
||||||
|
<Button on:click={pingInstances}>
|
||||||
|
<PingIcon />
|
||||||
|
Ping Instances
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Label>Add your favorite instances</Label>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Input
|
||||||
|
bind:value={addInstanceValue}
|
||||||
|
type="url"
|
||||||
|
placeholder="https://instance.com"
|
||||||
|
aria-label="Add instance input"
|
||||||
|
on:keydown={e => {
|
||||||
|
if (e.key === "Enter") addInstance()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button on:click={addInstance} class="add" aria-label="Add the instance">
|
||||||
|
<AddIcon />
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{#each _options[serviceOptions.frontend] as instance}
|
||||||
|
<Row>
|
||||||
|
<span>
|
||||||
|
<a href={instance} target="_blank" rel="noopener noreferrer">{instance}</a>
|
||||||
|
{#if isCustomInstance(instance)}
|
||||||
|
<span style="color:grey">custom</span>
|
||||||
|
{/if}
|
||||||
|
{#if pingCache && pingCache[instance]}
|
||||||
|
<span style="color:{pingCache[instance].color}">{pingCache[instance].value}</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="add"
|
||||||
|
aria-label="Remove Instance"
|
||||||
|
on:click={() => {
|
||||||
|
const index = _options[serviceOptions.frontend].indexOf(instance)
|
||||||
|
if (index > -1) {
|
||||||
|
_options[serviceOptions.frontend].splice(index, 1)
|
||||||
|
options.set(_options)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<hr />
|
||||||
|
{/each}
|
||||||
|
<Row></Row>
|
||||||
|
|
||||||
|
{#if redirects !== "disabled" && blacklist !== "disabled"}
|
||||||
|
{#if redirects[serviceOptions.frontend] && redirects[serviceOptions.frontend]["clearnet"]}
|
||||||
|
{#each Object.entries(_config.networks) as [networkName, network]}
|
||||||
|
{#if redirects[serviceOptions.frontend] && redirects[serviceOptions.frontend][networkName]}
|
||||||
|
<Row><Label>{network.name}</Label></Row>
|
||||||
|
<hr />
|
||||||
|
{#each redirects[serviceOptions.frontend][networkName] as instance}
|
||||||
|
<Row>
|
||||||
|
<span>
|
||||||
|
<a href={instance} target="_blank" rel="noopener noreferrer">{instance}</a>
|
||||||
|
{#if blacklist.cloudflare.includes(instance)}
|
||||||
|
<a
|
||||||
|
href="https://libredirect.github.io/docs.html#instances"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style="color:red;"
|
||||||
|
>
|
||||||
|
cloudflare
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{#if _options[serviceOptions.frontend].includes(instance)}
|
||||||
|
<span style="color:grey">chosen</span>
|
||||||
|
{/if}
|
||||||
|
{#if pingCache && pingCache[instance]}
|
||||||
|
<span style="color:{pingCache[instance].color}">{pingCache[instance].value}</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="add"
|
||||||
|
aria-label="Add instance"
|
||||||
|
on:click={() => {
|
||||||
|
if (_options[serviceOptions.frontend]) {
|
||||||
|
if (!_options[serviceOptions.frontend].includes(instance)) {
|
||||||
|
_options[serviceOptions.frontend].push(instance)
|
||||||
|
options.set(_options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<hr />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<Row><Label>No instances found.</Label></Row>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.add {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,84 @@
|
||||||
|
<script>
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
import RowSelect from "../components/RowSelect.svelte"
|
||||||
|
import { options, config } from "../stores"
|
||||||
|
|
||||||
|
let _options
|
||||||
|
let _config
|
||||||
|
|
||||||
|
const unsubscribeOptions = options.subscribe(val => (_options = val))
|
||||||
|
const unsubscribeConfig = config.subscribe(val => (_config = val))
|
||||||
|
onDestroy(() => {
|
||||||
|
unsubscribeOptions()
|
||||||
|
unsubscribeConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
export let selectedService
|
||||||
|
|
||||||
|
$: serviceConf = _config.services[selectedService]
|
||||||
|
$: serviceOptions = _options[selectedService]
|
||||||
|
$: frontendName = _options[selectedService].frontend
|
||||||
|
|
||||||
|
let values
|
||||||
|
$: if (serviceConf.frontends[frontendName].embeddable) {
|
||||||
|
values = [
|
||||||
|
{ value: "both", name: "Both" },
|
||||||
|
{ value: "sub_frame", name: "Only Embedded" },
|
||||||
|
{ value: "main_frame", name: "Only Not Embedded" },
|
||||||
|
]
|
||||||
|
} else if (
|
||||||
|
serviceConf.frontends[frontendName].desktopApp &&
|
||||||
|
Object.values(serviceConf.frontends).some(frontend => frontend.embeddable)
|
||||||
|
) {
|
||||||
|
values = [
|
||||||
|
{ value: "both", name: "both" },
|
||||||
|
{ value: "main_frame", name: "Only Not Embedded" },
|
||||||
|
]
|
||||||
|
if (serviceOptions.redirectType == "sub_frame") {
|
||||||
|
serviceOptions.redirectType = "main_frame"
|
||||||
|
options.set(_options)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
values = [{ value: "main_frame", name: "Only Not Embedded" }]
|
||||||
|
serviceOptions.redirectType = "main_frame"
|
||||||
|
options.set(_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
let embeddableFrontends = []
|
||||||
|
$: (() => {
|
||||||
|
if (serviceConf) {
|
||||||
|
embeddableFrontends = []
|
||||||
|
for (const [frontendId, frontendConf] of Object.entries(serviceConf.frontends)) {
|
||||||
|
if (frontendConf.embeddable && frontendConf.instanceList) {
|
||||||
|
embeddableFrontends.push({
|
||||||
|
value: frontendId,
|
||||||
|
name: frontendConf.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<RowSelect
|
||||||
|
label="Redirect Type"
|
||||||
|
value={serviceOptions.redirectType}
|
||||||
|
onChange={e => {
|
||||||
|
serviceOptions.redirectType = e.target.options[e.target.options.selectedIndex].value
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
{values}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if serviceConf.frontends[frontendName].desktopApp && serviceOptions.redirectType != "main_frame"}
|
||||||
|
<RowSelect
|
||||||
|
label="Embed Frontend"
|
||||||
|
value={_options.embedFrontend}
|
||||||
|
onChange={e => {
|
||||||
|
serviceOptions.embedFrontend = e.target.options[e.target.options.selectedIndex].value
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
values={embeddableFrontends}
|
||||||
|
/>
|
||||||
|
{/if}
|
|
@ -0,0 +1,119 @@
|
||||||
|
<script>
|
||||||
|
let browser = window.browser || window.chrome
|
||||||
|
|
||||||
|
import Checkbox from "../components/RowCheckbox.svelte"
|
||||||
|
import RowSelect from "../components/RowSelect.svelte"
|
||||||
|
import Row from "../components/Row.svelte"
|
||||||
|
import Label from "../components/Label.svelte"
|
||||||
|
import Select from "../components/Select.svelte"
|
||||||
|
import { options, config } from "../stores"
|
||||||
|
import RedirectType from "./RedirectType.svelte"
|
||||||
|
import { onDestroy, onMount } from "svelte"
|
||||||
|
import Instances from "./Instances.svelte"
|
||||||
|
|
||||||
|
let _options
|
||||||
|
let _config
|
||||||
|
|
||||||
|
const unsubscribeOptions = options.subscribe(val => (_options = val))
|
||||||
|
const unsubscribeConfig = config.subscribe(val => (_config = val))
|
||||||
|
onDestroy(() => {
|
||||||
|
unsubscribeOptions()
|
||||||
|
unsubscribeConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
let selectedService = "youtube"
|
||||||
|
|
||||||
|
$: serviceConf = _config.services[selectedService]
|
||||||
|
$: serviceOptions = _options[selectedService]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Row>
|
||||||
|
<Label>
|
||||||
|
Service:
|
||||||
|
<a href={serviceConf.url} target="_blank" rel="noopener noreferrer">{serviceConf.url}</a>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedService}
|
||||||
|
values={[
|
||||||
|
...Object.entries(_config.services).map(([serviceId, service]) => {
|
||||||
|
return { value: serviceId, name: service.name }
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
onChange={e => (selectedService = e.target.options[e.target.options.selectedIndex].value)}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
label="Enable"
|
||||||
|
checked={serviceOptions.enabled}
|
||||||
|
onChange={e => {
|
||||||
|
serviceOptions.enabled = e.target.checked
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={!serviceOptions.enabled && "pointer-events: none;opacity: 0.4;user-select: none;"}>
|
||||||
|
<Checkbox
|
||||||
|
label="Show in popup"
|
||||||
|
checked={_options.popupServices.includes(selectedService)}
|
||||||
|
onChange={e => {
|
||||||
|
if (e.target.checked && !_options.popupServices.includes(selectedService)) {
|
||||||
|
_options.popupServices.push(selectedService)
|
||||||
|
} else if (_options.popupServices.includes(selectedService)) {
|
||||||
|
const index = _options.popupServices.indexOf(selectedService)
|
||||||
|
if (index !== -1) _options.popupServices.splice(index, 1)
|
||||||
|
}
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Label>
|
||||||
|
Frontend:
|
||||||
|
<a href={serviceConf.frontends[serviceOptions.frontend].url} target="_blank" rel="noopener noreferrer"
|
||||||
|
>{serviceConf.frontends[serviceOptions.frontend].url}</a
|
||||||
|
>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={serviceOptions.frontend}
|
||||||
|
values={[
|
||||||
|
...Object.entries(serviceConf.frontends).map(([frontendId, frontend]) => ({
|
||||||
|
value: frontendId,
|
||||||
|
name: frontend.name,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
onChange={e => {
|
||||||
|
serviceOptions.frontend = e.target.options[e.target.options.selectedIndex].value
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<RedirectType {selectedService} />
|
||||||
|
|
||||||
|
<RowSelect
|
||||||
|
label="Unsupported iframes handling"
|
||||||
|
value={serviceOptions.unsupportedUrls}
|
||||||
|
onChange={e => {
|
||||||
|
serviceOptions.unsupportedUrls = e.target.options[e.target.options.selectedIndex].value
|
||||||
|
options.set(_options)
|
||||||
|
}}
|
||||||
|
values={[
|
||||||
|
{ value: "bypass", name: "Bypass" },
|
||||||
|
{ value: "block", name: "Block" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if selectedService == "search"}
|
||||||
|
<div>
|
||||||
|
Set LibRedirect as Default Search Engine. For how to do in chromium browsers, click
|
||||||
|
<a href="https://libredirect.github.io/docs.html#search_engine_chromium">here</a>.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Instances {selectedService} />
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script>
|
||||||
|
import { page } from "./stores"
|
||||||
|
import GeneralIcon from "./icons/GeneralIcon.svelte"
|
||||||
|
import ServicesIcon from "./icons/ServicesIcon.svelte"
|
||||||
|
import AboutIcon from "./icons/AboutIcon.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="#general" on:click={() => page.set("general")} style={$page == "general" && "color: var(--active);"}>
|
||||||
|
<GeneralIcon style="margin-right: 5px" />
|
||||||
|
<span data-localise="__MSG_general__">General</span>
|
||||||
|
</a>
|
||||||
|
<a href="#services" on:click={() => page.set("services")} style={$page == "services" && "color: var(--active);"}>
|
||||||
|
<ServicesIcon style="margin-right: 5px" />
|
||||||
|
<span data-localise="__MSG_general__">Services</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://libredirect.github.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
<AboutIcon style="margin-right: 5px" />
|
||||||
|
<span data-localise="__MSG_about__">About</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text);
|
||||||
|
transition: 0.1s;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--active);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<button on:click {...$$props}>
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
color: var(--text);
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition-duration: 0.1s;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 7.5px 0;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
color: var(--active);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
export let checked
|
||||||
|
export let onChange
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input bind:checked on:change={onChange} type="checkbox" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input[type="checkbox"] {
|
||||||
|
appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 46px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
border-radius: 50px;
|
||||||
|
transition: 0.4s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
background-color: var(--active);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
left: 3.5px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked::before {
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* body.rtl div.block input[type="checkbox"]::before {
|
||||||
|
left: auto;
|
||||||
|
right: 4px;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* body.rtl div.block input[type="checkbox"]:checked::before {
|
||||||
|
left: auto;
|
||||||
|
right: 24px;
|
||||||
|
} */
|
||||||
|
</style>
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
{...$$props}
|
||||||
|
bind:value
|
||||||
|
on:blur
|
||||||
|
on:change
|
||||||
|
on:click
|
||||||
|
on:contextmenu
|
||||||
|
on:focus
|
||||||
|
on:keydown
|
||||||
|
on:keypress
|
||||||
|
on:keyup
|
||||||
|
on:mouseover
|
||||||
|
on:mouseenter
|
||||||
|
on:mouseleave
|
||||||
|
on:paste
|
||||||
|
on:input
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
font-weight: bold;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
width: 400px;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline-color: var(--active);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline-color: var(--active);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<span>
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
span {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span :global(a) {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span :global(a:hover) {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div {...$$props}>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script>
|
||||||
|
import Row from "./Row.svelte"
|
||||||
|
import Checkbox from "./Checkbox.svelte"
|
||||||
|
import Label from "./Label.svelte"
|
||||||
|
|
||||||
|
export let label
|
||||||
|
export let checked
|
||||||
|
export let onChange
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Label>{label}</Label>
|
||||||
|
<Checkbox bind:checked {onChange} />
|
||||||
|
</Row>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
import Row from "./Row.svelte"
|
||||||
|
import Select from "./Select.svelte"
|
||||||
|
import Label from "./Label.svelte"
|
||||||
|
|
||||||
|
export let label
|
||||||
|
export let values
|
||||||
|
export let value
|
||||||
|
export let onChange
|
||||||
|
export let ariaLabel
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Label>{label}</Label>
|
||||||
|
<Select {value} {values} {onChange} {ariaLabel} />
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script>
|
||||||
|
export let values
|
||||||
|
export let value
|
||||||
|
export let onChange
|
||||||
|
export let ariaLabel
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select bind:value={value} on:change={onChange} aria-label={ariaLabel} on:change on:contextmenu on:input>
|
||||||
|
{#each values as option}
|
||||||
|
<option value={option.value}>{option.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
select {
|
||||||
|
font-weight: bold;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #767676;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
max-width: 500px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg
|
||||||
|
{...$$props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
width="24px"
|
||||||
|
fill="currentColor"
|
||||||
|
><path
|
||||||
|
d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"
|
||||||
|
/></svg
|
||||||
|
>
|
After Width: | Height: | Size: 577 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
|
||||||
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 166 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="currentColor"
|
||||||
|
><path d="m291-240-51-51 189-189-189-189 51-51 189 189 189-189 51 51-189 189 189 189-51 51-189-189-189 189Z" /></svg
|
||||||
|
>
|
After Width: | Height: | Size: 233 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 301 B |
|
@ -0,0 +1,13 @@
|
||||||
|
<svg
|
||||||
|
{...$$props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
enable-background="new 0 0 24 24"
|
||||||
|
height="26px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="26px"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 274 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M10.45 15.5q.6.6 1.55.587.95-.012 1.4-.687L19 7l-8.4 5.6q-.675.45-.712 1.375-.038.925.562 1.525ZM12 4q1.475 0 2.838.412Q16.2 4.825 17.4 5.65l-1.9 1.2q-.825-.425-1.712-.637Q12.9 6 12 6 8.675 6 6.338 8.337 4 10.675 4 14q0 1.05.287 2.075Q4.575 17.1 5.1 18h13.8q.575-.95.838-1.975Q20 15 20 13.9q0-.9-.212-1.75-.213-.85-.638-1.65l1.2-1.9q.75 1.175 1.188 2.5.437 1.325.462 2.75.025 1.425-.325 2.725-.35 1.3-1.025 2.475-.275.45-.75.7-.475.25-1 .25H5.1q-.525 0-1-.25t-.75-.7q-.65-1.125-1-2.387Q2 15.4 2 14q0-2.075.788-3.888.787-1.812 2.15-3.175Q6.3 5.575 8.125 4.787 9.95 4 12 4Zm.175 7.825Z"
|
||||||
|
/></svg
|
||||||
|
>
|
After Width: | Height: | Size: 720 B |
|
@ -0,0 +1,15 @@
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
enable-background="new 0 0 24 24"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12,5V2L8,6l4,4V7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02c3.95-0.49,7-3.85,7-7.93C20,8.58,16.42,5,12,5z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 436 B |
|
@ -0,0 +1,11 @@
|
||||||
|
<svg
|
||||||
|
{...$$props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
width="24px"
|
||||||
|
><path
|
||||||
|
d="m240-120 240-240 240 240H240ZM80-280v-480q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v480q0 33-23.5 56.5T800-200H680v-80h120v-480H160v480h120v80H160q-33 0-56.5-23.5T80-280Zm400-200Z"
|
||||||
|
/></svg
|
||||||
|
>
|
After Width: | Height: | Size: 354 B |
|
@ -0,0 +1,7 @@
|
||||||
|
import App from "./App.svelte"
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
export const options = writable(null)
|
||||||
|
export const config = writable(null)
|
||||||
|
export const page = writable("general")
|
|
@ -1,7 +0,0 @@
|
||||||
head
|
|
||||||
meta(charset="utf-8")
|
|
||||||
meta(name="viewport" content="width=device-width, initial-scale=1")
|
|
||||||
link(rel="icon" type="image/x-icon" href="../../../assets/images/libredirect.svg")
|
|
||||||
link(href="../stylesheets/styles.css" rel="stylesheet")
|
|
||||||
title Settings
|
|
||||||
script(type="module" src="./init.js")
|
|
|
@ -1,22 +0,0 @@
|
||||||
section(class="links" id="links")
|
|
||||||
div(class="title")
|
|
||||||
a(href="#general")
|
|
||||||
include /src/assets/images/general-icon.svg
|
|
||||||
span(data-localise="__MSG_general__") General
|
|
||||||
|
|
||||||
each val, key in services
|
|
||||||
div(class="title" id=`${key}-link`)
|
|
||||||
a(href="#"+key)
|
|
||||||
if services[key].imageType == 'svgMono'
|
|
||||||
img(class='dark' src=`/assets/images/${key}-icon.svg`)
|
|
||||||
img(class='light' src=`/assets/images/${key}-icon-light.svg`)
|
|
||||||
else
|
|
||||||
img(src=`/assets/images/${key}-icon.${services[key].imageType}`)
|
|
||||||
span=services[key].name
|
|
||||||
|
|
||||||
div(class="title")
|
|
||||||
a(target="_blank" href="https://libredirect.github.io")
|
|
||||||
img(class='dark' src="/assets/images/about-icon.svg")
|
|
||||||
img(class='light' src="/assets/images/about-icon-light.svg")
|
|
||||||
|
|
||||||
span(data-localise="__MSG_about__") About
|
|