diff options
Diffstat (limited to 'src/pages')
66 files changed, 2607 insertions, 1847 deletions
diff --git a/src/pages/background/background.html b/src/pages/background/background.html index 542df18b..787d419e 100644 --- a/src/pages/background/background.html +++ b/src/pages/background/background.html @@ -1,7 +1,7 @@ -<!DOCTYPE html> +<!doctype html> <html> - <head> - <meta charset="utf-8" /> - <script type="module" src="background.js"></script> - </head> + <head> + <meta charset="utf-8" /> + <script type="module" src="background.js"></script> + </head> </html> diff --git a/src/pages/background/background.js b/src/pages/background/background.js index 4b8f1ca2..1fcba190 100644 --- a/src/pages/background/background.js +++ b/src/pages/background/background.js @@ -7,338 +7,377 @@ const isChrome = browser.runtime.getBrowserInfo === undefined window.browser = window.browser || window.chrome browser.runtime.onInstalled.addListener(async details => { - if (details.previousVersion != browser.runtime.getManifest().version) { - // ^Used to prevent this running when debugging with auto-reload - if (details.reason == "install") { - if (!(await utils.getOptions())) { - await servicesHelper.initDefaults() - } - browser.runtime.openOptionsPage() - } - else if (details.reason == "update") { - if (details.previousVersion == '2.5.2') { - await servicesHelper.upgradeOptions() - await servicesHelper.processUpdate() - } else { - await servicesHelper.processUpdate() - } - } - } + if (details.previousVersion != browser.runtime.getManifest().version) { + // ^Used to prevent this running when debugging with auto-reload + if (details.reason == "install") { + if (!(await utils.getOptions())) { + await servicesHelper.initDefaults() + } + } else if (details.reason == "update") { + await servicesHelper.processUpdate() + } + } }) +// true to redirect, false to bypass let tabIdRedirects = {} // true == Always redirect, false == Never redirect, null/undefined == follow options for services browser.webRequest.onBeforeRequest.addListener( - details => { - const url = new URL(details.url) - if (new RegExp(/^chrome-extension:\/{2}.*\/instances\/.*.json$/).test(url.href) && details.type == "xmlhttprequest") return - let initiator - try { - if (details.originUrl) initiator = new URL(details.originUrl) - else if (details.initiator && details.initiator !== "null") initiator = new URL(details.initiator) - } catch { - return null - } - if (tabIdRedirects[details.tabId] == false) return null - let newUrl = servicesHelper.redirect(url, details.type, initiator, tabIdRedirects[details.tabId], details.incognito) + details => { + const old_href = details.url + const url = new URL(details.url) + if (new RegExp(/^chrome-extension:\/{2}.*\/instances\/.*.json$/).test(url.href) && details.type == "xmlhttprequest") + return null - if (details.frameAncestors && details.frameAncestors.length > 0 && servicesHelper.isException(new URL(details.frameAncestors[0].url))) newUrl = null + // if url is previously bypassed + if (tabIdRedirects[details.tabId] == false) return null - if (servicesHelper.isException(url)) { - if (details.type == "main_frame") - newUrl = "BYPASSTAB" - else - return null - } + // Bypass embeds from excepted urls + if ( + details.frameAncestors && + details.frameAncestors.length >= 1 && + servicesHelper.isException(new URL(details.frameAncestors[0].url)) + ) + return null - if (!newUrl) { - const match = url.href.match(/^https?:\/{2}.*\.libredirect\.invalid.*/) - if (match) { - browser.tabs.update({ - url: browser.runtime.getURL(`/pages/messages/no_instance.html`) - }); - } - } + if (servicesHelper.isException(url)) { + if (details.type == "main_frame") { + console.log(`Bypassing ${details.tabId} ${url}`) + tabIdRedirects[details.tabId] = false + } + return null + } - if (newUrl) { - if (newUrl === "CANCEL") { - console.log(`Canceled ${url}`) - return { cancel: true } - } - if (newUrl === "BYPASSTAB") { - console.log(`Bypassed ${details.tabId} ${url}`) - if (tabIdRedirects[details.tabId] != false) tabIdRedirects[details.tabId] = false - return null - } - console.info("Redirecting", url.href, "=>", newUrl) - return { redirectUrl: newUrl } - } - return null - }, - { urls: ["<all_urls>"] }, - ["blocking"] -) + let originUrl + let documentUrl + try { + if (details.originUrl) originUrl = new URL(details.originUrl) + if (details.documentUrl) documentUrl = new URL(details.documentUrl) + } catch { + return null + } -browser.tabs.onRemoved.addListener(tabId => { - if (tabIdRedirects[tabId] != undefined) { - delete tabIdRedirects[tabId] - console.log(`Removed tab ${tabId} from tabIdRedirects`) - } -}) + let newUrl = servicesHelper.redirect( + url, + details.type, + originUrl, + documentUrl, + details.incognito, + tabIdRedirects[details.tabId] + ) -browser.commands.onCommand.addListener(async command => { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - const url = new URL(tabs[0].url) - switch (command) { - case "switchInstance": - const newUrl = await servicesHelper.switchInstance(url) - if (newUrl) browser.tabs.update({ url: newUrl }) - break - case "copyRaw": { - servicesHelper.copyRaw(url) - break - } - case "redirect": { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - const newUrl = servicesHelper.redirect(url, "main_frame", null, true) - if (newUrl) { - browser.tabs.update(tabs[0].id, { url: newUrl }, () => { - tabIdRedirects[tabs[0].id] = true - }) - } - } - }) - break - } - case "reverse": { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - const newUrl = await servicesHelper.reverse(url) - if (newUrl) { - browser.tabs.update(tabs[0].id, { url: newUrl }, () => { - tabIdRedirects[tabs[0].id] = false - }) - } - } - }) - break - } - } - }) -}) + if ( + (newUrl && newUrl.startsWith("https://no-instance.libredirect.invalid")) || + (!newUrl && url.href.startsWith("https://no-instance.libredirect.invalid")) + ) { + newUrl = newUrl ? new URL(newUrl) : url + const frontend = newUrl.searchParams.get("frontend") + const oldUrl = new URL(newUrl.searchParams.get("url")) + const params = new URLSearchParams({ + message: "no_instance", + url: oldUrl, + frontend: frontend, + }) + browser.tabs.update({ + url: browser.runtime.getURL(`/pages/messages/index.html?${params.toString()}`), + }) + return { cancel: true } + } -browser.contextMenus.create({ id: "settingsTab", title: browser.i18n.getMessage("settings"), 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"] }) + if (!newUrl) { + if (url.href.match(/^https?:\/{2}(.*\.)?libredirect\.invalid.*/)) { + const params = new URLSearchParams({ + message: "disabled", + url: url.href, + }) + browser.tabs.update({ + url: browser.runtime.getURL(`/pages/messages/index.html?${params.toString()}`), + }) + return { cancel: true } + } + } -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: "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: "copyReverseLink", title: 'Copy Original', contexts: ["link"] }) -browser.contextMenus.create({ id: "bypassLink", title: 'Bypass', contexts: ["link"] }) -browser.contextMenus.create({ id: "bypassLinkInNewTab", title: 'Bypass In New Tab', contexts: ["link"] }) + if (newUrl === "CANCEL") { + console.log(`Cancelling ${url}`) + return { cancel: true } + } + if (newUrl === "BYPASSTAB") { + console.log(`Bypassing ${details.tabId} ${url}`) + tabIdRedirects[details.tabId] = false + return null + } + if (newUrl) { + console.log("Redirecting", old_href, "=>", newUrl) + return { redirectUrl: newUrl } + } + return null + }, + { urls: ["<all_urls>"] }, + ["blocking"] +) -if (!isChrome) { - 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: "reverseBookmark", title: 'Redirect To Original', contexts: ["bookmark"] }) - browser.contextMenus.create({ id: "reverseBookmarkInNewTab", title: 'Redirect To Original In New Tab', 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.webRequest.onHeadersReceived.addListener( + details => { + if (details.statusCode >= 501 || details.statusCode == 429 || details.statusCode == 403) { + const url = new URL(details.url) + const { service, frontend } = servicesHelper.computeFrontend(url) + if (!service) return + const params = new URLSearchParams({ + message: "server_error", + code: details.statusCode, + url: url.href, + frontend: frontend, + service: service, + }) + setTimeout(() => { + browser.tabs.update({ + url: browser.runtime.getURL(`/pages/messages/index.html?${params.toString()}`), + }) + }, 2000) + } + }, + { urls: ["<all_urls>"] } +) -browser.contextMenus.onClicked.addListener(async (info) => { - switch (info.menuItemId) { - case 'switchInstanceTab': { - const url = new URL(info.pageUrl) - const newUrl = await servicesHelper.switchInstance(url) - if (newUrl) { - browser.tabs.update({ url: newUrl }) - } - return - } - case 'settingsTab': { - browser.runtime.openOptionsPage() - return - } - case 'copyReverseTab': { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - servicesHelper.copyRaw(url) - } - }) - return - } - case 'reverseTab': { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - const newUrl = await servicesHelper.reverse(url) - if (newUrl) { - browser.tabs.update(tabs[0].id, { url: newUrl }, () => { - tabIdRedirects[tabs[0].id] = false - }) - } - } - }) - return - } - case 'redirectTab': { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - const newUrl = servicesHelper.redirect(url, "main_frame", null, true) - if (newUrl) { - browser.tabs.update(tabs[0].id, { url: newUrl }, () => { - tabIdRedirects[tabs[0].id] = true - }) - } - } - }) - return - } +browser.tabs.onRemoved.addListener(tabId => { + if (tabIdRedirects[tabId] != undefined) { + delete tabIdRedirects[tabId] + console.log(`Removed tab ${tabId} from tabIdRedirects`) + } +}) - case 'copyReverseLink': { - const url = new URL(info.linkUrl) - await servicesHelper.copyRaw(url) - return - } - case 'redirectLink': - case 'redirectLinkInNewTab': { - const url = new URL(info.linkUrl) - const newUrl = servicesHelper.redirect(url, "main_frame", null, true) - if (newUrl) { - if (info.menuItemId == "redirectLink") browser.tabs.update({ url: newUrl }) - else browser.tabs.create({ url: newUrl }) - } - return - } - case 'reverseLink': - case 'reverseLinkInNewTab': { - const url = new URL(info.linkUrl) - const newUrl = await servicesHelper.reverse(url) - if (newUrl) { - if (info.menuItemId == "reverseLink") { - browser.tabs.update({ url: newUrl }, tab => { - tabIdRedirects[tab.id] = false - }) - } else { - browser.tabs.create({ url: newUrl }, tab => { - tabIdRedirects[tab.id] = false - }) - } - } - return - } +browser.runtime.getPlatformInfo(r => { + if (r.os != "fuchsia" && r.os != "ios" && r.os != "android") { + browser.commands.onCommand.addListener(async command => { + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + const url = new URL(tabs[0].url) + switch (command) { + case "switchInstance": { + const newUrl = await servicesHelper.switchInstance(url) + if (newUrl) browser.tabs.update({ url: newUrl }) + break + } + case "copyRaw": + servicesHelper.copyRaw(url) + break + case "redirect": + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0].url) { + const url = new URL(tabs[0].url) + const newUrl = servicesHelper.redirect(url, "main_frame", null, null, false, true) + if (newUrl) { + browser.tabs.update(tabs[0].id, { url: newUrl }, () => { + tabIdRedirects[tabs[0].id] = true + }) + } + } + }) + break + case "reverse": + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0].url) { + const url = new URL(tabs[0].url) + const newUrl = await servicesHelper.reverse(url) + if (newUrl) { + browser.tabs.update(tabs[0].id, { url: newUrl }, () => { + tabIdRedirects[tabs[0].id] = false + }) + } + } + }) + break + } + }) + }) - case 'bypassLink': - case 'bypassLinkInNewTab': { - const url = new URL(info.linkUrl) - if (info.menuItemId == "bypassLink") { - browser.tabs.update({ url: url.href }, tab => { - tabIdRedirects[tab.id] = false - }) - } else { - browser.tabs.create({ url: url.href }, tab => { - tabIdRedirects[tab.id] = false - }) - } - return - } + browser.contextMenus.create({ + id: "settingsTab", + title: browser.i18n.getMessage("settings"), + 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"] }) - case 'copyReverseBookmark': { - browser.bookmarks.get(info.bookmarkId, bookmarks => { - const url = new URL(bookmarks[0].url) - servicesHelper.copyRaw(url) - }); - return - } + 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: "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: "copyReverseLink", title: "Copy Original", contexts: ["link"] }) + browser.contextMenus.create({ id: "bypassLink", title: "Bypass", contexts: ["link"] }) + browser.contextMenus.create({ id: "bypassLinkInNewTab", title: "Bypass In New Tab", contexts: ["link"] }) - case 'redirectBookmark': - case 'redirectBookmarkInNewTab': { - browser.bookmarks.get(info.bookmarkId, bookmarks => { - const url = new URL(bookmarks[0].url) - const newUrl = servicesHelper.redirect(url, "main_frame", null, true) - if (newUrl) { - if (info.menuItemId == 'redirectBookmark') browser.tabs.update({ url: newUrl }) - else browser.tabs.create({ url: newUrl }) - } - }) - return - } - case 'reverseBookmark': - case 'reverseBookmarkInNewTab': { - browser.bookmarks.get(info.bookmarkId, async bookmarks => { - const url = new URL(bookmarks[0].url) - const newUrl = await servicesHelper.reverse(url) - if (newUrl) { - if (info.menuItemId == "reverseBookmark") { - browser.tabs.update({ url: newUrl }, tab => { - tabIdRedirects[tab.id] = false - }) - } else { - browser.tabs.create({ url: newUrl }, tab => { - tabIdRedirects[tab.id] = false - }) - } - } - }) - return - } + if (!isChrome) { + 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: "reverseBookmark", title: "Redirect To Original", contexts: ["bookmark"] }) + browser.contextMenus.create({ + id: "reverseBookmarkInNewTab", + title: "Redirect To Original In New Tab", + 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"] }) + } - case 'bypassBookmark': - case 'bypassBookmarkInNewTab': { - browser.bookmarks.get(info.bookmarkId, async bookmarks => { - const url = new URL(bookmarks[0].url) - if (info.menuItemId == "bypassBookmark") { - browser.tabs.update({ url: url.href }, tab => { - tabIdRedirects[tab.id] = false - }) - } else { - browser.tabs.create({ url: url.href }, tab => { - tabIdRedirects[tab.id] = false - }) - } - return - }) - } - } + browser.contextMenus.onClicked.addListener(async info => { + switch (info.menuItemId) { + case "switchInstanceTab": { + const url = new URL(info.pageUrl) + const newUrl = await servicesHelper.switchInstance(url) + if (newUrl) browser.tabs.update({ url: newUrl }) + return + } + case "settingsTab": + browser.runtime.openOptionsPage() + return + case "copyReverseTab": + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0].url) { + const url = new URL(tabs[0].url) + servicesHelper.copyRaw(url) + } + }) + return + case "reverseTab": + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0].url) { + const url = new URL(tabs[0].url) + const newUrl = await servicesHelper.reverse(url) + if (newUrl) { + browser.tabs.update(tabs[0].id, { url: newUrl }, () => { + tabIdRedirects[tabs[0].id] = false + }) + } + } + }) + return + case "redirectTab": + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0].url) { + const url = new URL(tabs[0].url) + const newUrl = servicesHelper.redirect(url, "main_frame", null, null, false, true) + if (newUrl) { + browser.tabs.update(tabs[0].id, { url: newUrl }, () => { + tabIdRedirects[tabs[0].id] = true + }) + } + } + }) + return + case "copyReverseLink": { + const url = new URL(info.linkUrl) + await servicesHelper.copyRaw(url) + return + } + case "redirectLink": + case "redirectLinkInNewTab": { + const url = new URL(info.linkUrl) + const newUrl = servicesHelper.redirect(url, "main_frame", null, null, false, true) + if (newUrl) { + if (info.menuItemId == "redirectLink") browser.tabs.update({ url: newUrl }) + else browser.tabs.create({ url: newUrl }) + } + return + } + case "reverseLink": + case "reverseLinkInNewTab": { + const url = new URL(info.linkUrl) + const newUrl = await servicesHelper.reverse(url) + if (newUrl) { + if (info.menuItemId == "reverseLink") { + browser.tabs.update({ url: newUrl }, tab => { + tabIdRedirects[tab.id] = false + }) + } else { + browser.tabs.create({ url: newUrl }, tab => { + tabIdRedirects[tab.id] = false + }) + } + } + return + } + case "bypassLink": + case "bypassLinkInNewTab": { + const url = new URL(info.linkUrl) + if (info.menuItemId == "bypassLink") { + browser.tabs.update({ url: url.href }, tab => { + tabIdRedirects[tab.id] = false + }) + } else { + browser.tabs.create({ url: url.href }, tab => { + tabIdRedirects[tab.id] = false + }) + } + return + } + case "copyReverseBookmark": + browser.bookmarks.get(info.bookmarkId, bookmarks => { + const url = new URL(bookmarks[0].url) + servicesHelper.copyRaw(url) + }) + return + case "redirectBookmark": + case "redirectBookmarkInNewTab": + browser.bookmarks.get(info.bookmarkId, bookmarks => { + const url = new URL(bookmarks[0].url) + const newUrl = servicesHelper.redirect(url, "main_frame", null, null, false, true) + if (newUrl) { + if (info.menuItemId == "redirectBookmark") browser.tabs.update({ url: newUrl }) + else browser.tabs.create({ url: newUrl }) + } + }) + return + case "reverseBookmark": + case "reverseBookmarkInNewTab": + browser.bookmarks.get(info.bookmarkId, async bookmarks => { + const url = new URL(bookmarks[0].url) + const newUrl = await servicesHelper.reverse(url) + if (newUrl) { + if (info.menuItemId == "reverseBookmark") { + browser.tabs.update({ url: newUrl }, tab => { + tabIdRedirects[tab.id] = false + }) + } else { + browser.tabs.create({ url: newUrl }, tab => { + tabIdRedirects[tab.id] = false + }) + } + } + }) + return + case "bypassBookmark": + case "bypassBookmarkInNewTab": + browser.bookmarks.get(info.bookmarkId, async bookmarks => { + const url = new URL(bookmarks[0].url) + if (info.menuItemId == "bypassBookmark") { + browser.tabs.update({ url: url.href }, tab => (tabIdRedirects[tab.id] = false)) + } else { + browser.tabs.create({ url: url.href }, tab => (tabIdRedirects[tab.id] = false)) + } + return + }) + } + }) + } }) -browser.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request == "reverseTab") { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - const newUrl = await servicesHelper.reverse(url) - if (newUrl) { - browser.tabs.update(tabs[0].id, { url: newUrl }, () => { - tabIdRedirects[tabs[0].id] = false - }) - } - } - }) - } - else if (request == "redirectTab") { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - const newUrl = servicesHelper.redirect(url, "main_frame", null, true) - if (newUrl) { - browser.tabs.update(tabs[0].id, { url: newUrl }, () => { - tabIdRedirects[tabs[0].id] = true - }) - } - } - }) - } -}) \ No newline at end of file +browser.runtime.onMessage.addListener(r => { + if (r.message == "reverse") tabIdRedirects[r.tabId] = false + else if (r.message == "redirect") tabIdRedirects[r.tabId] = true +}) diff --git a/src/pages/components/Button.svelte b/src/pages/components/Button.svelte new file mode 100644 index 00000000..6ae2ba61 --- /dev/null +++ b/src/pages/components/Button.svelte @@ -0,0 +1,35 @@ +<button {...$$restProps} 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:enabled { + color: var(--active); + } + + button:active:enabled { + transform: translateY(1px); + } + + button:disabled { + cursor: not-allowed; + opacity: 0.5; + } + +</style> diff --git a/src/pages/components/Checkbox.svelte b/src/pages/components/Checkbox.svelte new file mode 100644 index 00000000..d42a4f10 --- /dev/null +++ b/src/pages/components/Checkbox.svelte @@ -0,0 +1,52 @@ +<script> + export let checked + export let onChange +</script> + +<input class={document.body.dir} {...$$restProps} 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; + } + + input[type="checkbox"].rtl::before { + left: auto; + right: 3.5px; + } + + input[type="checkbox"].rtl:checked::before { + left: auto; + right: 24px; + } +</style> diff --git a/src/pages/components/Input.svelte b/src/pages/components/Input.svelte new file mode 100644 index 00000000..59e584db --- /dev/null +++ b/src/pages/components/Input.svelte @@ -0,0 +1,46 @@ +<script> + export let value +</script> + +<input + {...$$restProps} + 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); + } + @media (max-width: 715px) { + input { + width: 200px; + } + } +</style> diff --git a/src/pages/components/Label.svelte b/src/pages/components/Label.svelte new file mode 100644 index 00000000..39930cd1 --- /dev/null +++ b/src/pages/components/Label.svelte @@ -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> diff --git a/src/pages/components/Row.svelte b/src/pages/components/Row.svelte new file mode 100644 index 00000000..09246d98 --- /dev/null +++ b/src/pages/components/Row.svelte @@ -0,0 +1,12 @@ +<div {...$$restProps} on:click> + <slot></slot> +</div> + +<style> + div { + justify-content: space-between; + display: flex; + align-items: center; + margin: 20px 0; + } +</style> diff --git a/src/pages/components/Select.svelte b/src/pages/components/Select.svelte new file mode 100644 index 00000000..7829c53e --- /dev/null +++ b/src/pages/components/Select.svelte @@ -0,0 +1,34 @@ +<script> + export let values + export let value + export let onChange + export let ariaLabel +</script> + +<select bind: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> diff --git a/src/pages/stylesheets/Inter-VariableFont_slnt,wght.ttf b/src/pages/fonts/Inter-VariableFont_slnt,wght.ttf index 969a990f..969a990f 100644 --- a/src/pages/stylesheets/Inter-VariableFont_slnt,wght.ttf +++ b/src/pages/fonts/Inter-VariableFont_slnt,wght.ttf Binary files differdiff --git a/src/pages/stylesheets/Vazirmatn-VariableFont_wght.ttf b/src/pages/fonts/Vazirmatn-VariableFont_wght.ttf index f4b97c01..f4b97c01 100644 --- a/src/pages/stylesheets/Vazirmatn-VariableFont_wght.ttf +++ b/src/pages/fonts/Vazirmatn-VariableFont_wght.ttf Binary files differdiff --git a/src/pages/fonts/styles.css b/src/pages/fonts/styles.css new file mode 100644 index 00000000..754543b1 --- /dev/null +++ b/src/pages/fonts/styles.css @@ -0,0 +1,13 @@ +@font-face { + font-family: "Inter"; + src: url("Inter-VariableFont_slnt,wght.ttf"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "Vazirmatn"; + src: url("Vazirmatn-VariableFont_wght.ttf"); + font-weight: normal; + font-style: normal; +} \ No newline at end of file diff --git a/src/pages/icons/AboutIcon.svelte b/src/pages/icons/AboutIcon.svelte new file mode 100644 index 00000000..e113dd68 --- /dev/null +++ b/src/pages/icons/AboutIcon.svelte @@ -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 +> diff --git a/src/pages/icons/AddIcon.svelte b/src/pages/icons/AddIcon.svelte new file mode 100644 index 00000000..ab26f078 --- /dev/null +++ b/src/pages/icons/AddIcon.svelte @@ -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> diff --git a/src/pages/icons/AutoPickIcon.svelte b/src/pages/icons/AutoPickIcon.svelte new file mode 100644 index 00000000..86adfa28 --- /dev/null +++ b/src/pages/icons/AutoPickIcon.svelte @@ -0,0 +1,11 @@ +<svg + {...$$restProps} + xmlns="http://www.w3.org/2000/svg" + height="20px" + viewBox="0 -960 960 960" + width="20px" + fill="currentColor" + ><path + d="M144-144v-178l342-342-54-53 51-51 72 72 110.22-110.22q4.45-4.45 11.11-7.11Q683-816 691-816t15 2.5q7 2.5 12 7.5l87 88q4.55 5.83 7.27 12.64 2.73 6.8 2.73 14.58t-2.66 14.44q-2.67 6.66-7.11 11.1L696-556l72 73-51 51-54-54-341 342H144Zm72-72h76l320-320-75-76-321 320v76Zm424-385 90-91-38-39-91 90 39 40Zm0 0-39-40 39 40Z" + /></svg +> diff --git a/src/pages/icons/CloseIcon.svelte b/src/pages/icons/CloseIcon.svelte new file mode 100644 index 00000000..ddfb29cb --- /dev/null +++ b/src/pages/icons/CloseIcon.svelte @@ -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 +> diff --git a/src/pages/icons/CopyIcon.svelte b/src/pages/icons/CopyIcon.svelte new file mode 100644 index 00000000..37c13f98 --- /dev/null +++ b/src/pages/icons/CopyIcon.svelte @@ -0,0 +1,12 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + height="24px" + width="24px" + viewBox="0 0 24 24" + preserveAspectRatio="xMinYMin meet" + fill="currentColor" +> + <path + d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" + /> +</svg> diff --git a/src/pages/icons/ExportIcon.svelte b/src/pages/icons/ExportIcon.svelte new file mode 100644 index 00000000..d155e5c5 --- /dev/null +++ b/src/pages/icons/ExportIcon.svelte @@ -0,0 +1,12 @@ +<svg + {...$$restProps} + 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> diff --git a/src/pages/icons/GeneralIcon.svelte b/src/pages/icons/GeneralIcon.svelte new file mode 100644 index 00000000..b9429021 --- /dev/null +++ b/src/pages/icons/GeneralIcon.svelte @@ -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> diff --git a/src/pages/icons/ImportIcon.svelte b/src/pages/icons/ImportIcon.svelte new file mode 100644 index 00000000..f64d0ff6 --- /dev/null +++ b/src/pages/icons/ImportIcon.svelte @@ -0,0 +1,12 @@ +<svg + {...$$restProps} + 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> diff --git a/src/pages/icons/PingIcon.svelte b/src/pages/icons/PingIcon.svelte new file mode 100644 index 00000000..34c4a37d --- /dev/null +++ b/src/pages/icons/PingIcon.svelte @@ -0,0 +1,12 @@ +<svg + {...$$restProps} + 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 +> diff --git a/src/pages/icons/RedirectIcon.svelte b/src/pages/icons/RedirectIcon.svelte new file mode 100644 index 00000000..9392762a --- /dev/null +++ b/src/pages/icons/RedirectIcon.svelte @@ -0,0 +1,10 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + height="24px" + width="24px" + viewBox="0 0 24 24" + preserveAspectRatio="xMinYMin meet" + fill="currentColor" +> + <path d="M7 20v-9q0-.825.588-1.413Q8.175 9 9 9h8.2l-1.6-1.6L17 6l4 4-4 4-1.4-1.4 1.6-1.6H9v9Z" /> +</svg> diff --git a/src/pages/icons/RedirectToOriginalIcon.svelte b/src/pages/icons/RedirectToOriginalIcon.svelte new file mode 100644 index 00000000..aad5c48e --- /dev/null +++ b/src/pages/icons/RedirectToOriginalIcon.svelte @@ -0,0 +1,13 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + height="24px" + width="24px" + viewBox="0 0 24 24" + preserveAspectRatio="xMinYMin meet" + fill="currentColor" +> + <path + d="M 17,20 V 11 Q 17,10.175 16.412,9.587 15.825,9 15,9 H 6.8 L 8.4,7.4 7,6 3,10 7,14 8.4,12.6 6.8,11 H 15 v 9 z" + id="path2" + /> +</svg> diff --git a/src/pages/icons/ResetIcon.svelte b/src/pages/icons/ResetIcon.svelte new file mode 100644 index 00000000..6daf57e6 --- /dev/null +++ b/src/pages/icons/ResetIcon.svelte @@ -0,0 +1,16 @@ +<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" + {...$$restProps} +> + <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> diff --git a/src/pages/icons/ServicesIcon.svelte b/src/pages/icons/ServicesIcon.svelte new file mode 100644 index 00000000..ec24259b --- /dev/null +++ b/src/pages/icons/ServicesIcon.svelte @@ -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 +> diff --git a/src/pages/icons/SettingsIcon.svelte b/src/pages/icons/SettingsIcon.svelte new file mode 100644 index 00000000..00798289 --- /dev/null +++ b/src/pages/icons/SettingsIcon.svelte @@ -0,0 +1,15 @@ +<svg + {...$$restProps} + xmlns="http://www.w3.org/2000/svg" + height="24px" + width="24px" + viewBox="0 0 24 24" + preserveAspectRatio="xMinYMin meet" + fill="currentColor" + on:click + on:keydown={null} +> + <path + d="m9.25 22-.4-3.2q-.325-.125-.612-.3-.288-.175-.563-.375L4.7 19.375l-2.75-4.75 2.575-1.95Q4.5 12.5 4.5 12.337v-.675q0-.162.025-.337L1.95 9.375l2.75-4.75 2.975 1.25q.275-.2.575-.375.3-.175.6-.3l.4-3.2h5.5l.4 3.2q.325.125.613.3.287.175.562.375l2.975-1.25 2.75 4.75-2.575 1.95q.025.175.025.337v.675q0 .163-.05.338l2.575 1.95-2.75 4.75-2.95-1.25q-.275.2-.575.375-.3.175-.6.3l-.4 3.2Zm2.8-6.5q1.45 0 2.475-1.025Q15.55 13.45 15.55 12q0-1.45-1.025-2.475Q13.5 8.5 12.05 8.5q-1.475 0-2.488 1.025Q8.55 10.55 8.55 12q0 1.45 1.012 2.475Q10.575 15.5 12.05 15.5Z" + /> +</svg> diff --git a/src/pages/icons/SwitchInstanceIcon.svelte b/src/pages/icons/SwitchInstanceIcon.svelte new file mode 100644 index 00000000..6a1f96ac --- /dev/null +++ b/src/pages/icons/SwitchInstanceIcon.svelte @@ -0,0 +1,15 @@ +<svg + {...$$restProps} + xmlns="http://www.w3.org/2000/svg" + height="24px" + width="24px" + viewBox="0 0 24 24" + preserveAspectRatio="xMinYMin meet" + fill="currentColor" + on:click + on:keydown={null} +> + <path + d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" + /> +</svg> diff --git a/src/pages/messages/index.html b/src/pages/messages/index.html new file mode 100644 index 00000000..8701c152 --- /dev/null +++ b/src/pages/messages/index.html @@ -0,0 +1,14 @@ +<!doctype html> +<html lang="en"> + <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" /> + <title>Settings</title> + <link rel="stylesheet" href="build/bundle.css" /> + <link rel="stylesheet" href="../fonts/styles.css" /> + <script defer src="build/bundle.js"></script> + </head> + + <body></body> +</html> diff --git a/src/pages/messages/no_instance.html b/src/pages/messages/no_instance.html deleted file mode 100644 index 76ec19cf..00000000 --- a/src/pages/messages/no_instance.html +++ /dev/null @@ -1,26 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <meta charset="UTF-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link href="../stylesheets/styles.css" rel="stylesheet"> - <title>No instances found</title> - <style> - #body { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - } - </style> -</head> - -<body> - <div id="body"> - <h1>You have no instance selected for this frontend</h1> - </div> -</body> - -</html> \ No newline at end of file diff --git a/src/pages/messages_src/App.svelte b/src/pages/messages_src/App.svelte new file mode 100644 index 00000000..1c5170dd --- /dev/null +++ b/src/pages/messages_src/App.svelte @@ -0,0 +1,200 @@ +<script> + const browser = window.browser || window.chrome + + import utils from "../../assets/javascripts/utils.js" + import { onDestroy } from "svelte" + import servicesHelper from "../../assets/javascripts/services.js" + import { onMount } from "svelte" + + import { options, config, page } from "./stores" + import Button from "../components/Button.svelte" + import AutoPickIcon from "../icons/AutoPickIcon.svelte" + import SwitchInstanceIcon from "../icons/SwitchInstanceIcon.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) { + await servicesHelper.initDefaults() + opts = await utils.getOptions() + } + options.set(opts) + config.set(await utils.getConfig()) + }) + + let _page + page.subscribe(val => (_page = val)) + + let style + $: if (_options) style = utils.style(_options, window) + + let autoPicking = false + + const params = new URLSearchParams(window.location.search) + const oldUrl = new URL(params.get("url")) + + async function autoPick() { + const frontend = params.get("frontend") + autoPicking = true + const redirects = await utils.getList(_options) + const instances = utils.randomInstances(redirects[frontend]["clearnet"], 5) + const pings = await Promise.all([ + ...instances.map(async instance => { + return [instance, await utils.ping(instance)] + }), + ]) + pings.sort((a, b) => a[1] - b[1]) + _options[frontend].push(pings[0][0]) + options.set(_options) + autoPicking = false + } + + async function autoPickInstance() { + await autoPick() + await redirectUrl() + } + + async function enableService() { + const service = await servicesHelper.computeService(oldUrl) + _options[service].enabled = true + options.set(_options) + await redirectUrl() + } + + async function redirectUrl() { + const newUrl = await servicesHelper.redirectAsync(oldUrl, "main_frame", null, null, false, true) + browser.tabs.update({ url: newUrl }) + } + + async function switchInstance() { + const newUrl = await servicesHelper.switchInstance(oldUrl) + browser.tabs.update({ url: newUrl }) + } + + async function removeInstance() { + const service = await servicesHelper.computeService(oldUrl) + const frontend = params.get("frontend") + const i = _options[frontend].findIndex(instance => oldUrl.href.startsWith(instance)) + _options[frontend].splice(i, 1) + options.set(_options) + const newUrl = await servicesHelper.switchInstance(oldUrl, service) + browser.tabs.update({ url: newUrl }) + } + + async function removeAndAutoPickInstance() { + const service = await servicesHelper.computeService(oldUrl) + + const frontend = params.get("frontend") + const i = _options[frontend].findIndex(instance => oldUrl.href.startsWith(instance)) + _options[frontend].splice(i, 1) + options.set(_options) + await autoPick() + const newUrl = await servicesHelper.switchInstance(oldUrl, service) + browser.tabs.update({ url: newUrl }) + } + + async function addAutoPickInstance() { + await autoPick() + const newUrl = await servicesHelper.switchInstance(oldUrl) + browser.tabs.update({ url: newUrl }) + } +</script> + +{#if _options && _config} + <div class="main" dir="auto" {style}> + {#if params.get("message") == "disabled"} + <div> + <h1>You disabled redirections for this service</h1> + <Button on:click={enableService}> + {browser.i18n.getMessage("enable") || "Enable"} + </Button> + </div> + {:else if params.get("message") == "server_error"} + <!-- https://httpstat.us/403 for testing --> + <div> + <h1>Your selected instance gave out an error: {params.get("code")}</h1> + {#if _options[params.get("frontend")].length > 1} + <Button on:click={switchInstance}> + <SwitchInstanceIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("switchInstance") || "Switch Instance"} + </Button> + <Button on:click={removeInstance}> + <SwitchInstanceIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("removeInstance") || "Remove Instance"} + + + {browser.i18n.getMessage("switchInstance") || "Switch Instance"} + </Button> + {:else} + <Button on:click={addAutoPickInstance} disabled={autoPicking}> + <AutoPickIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("autoPickInstance") || "Auto Pick Instance"} + </Button> + <Button on:click={removeAndAutoPickInstance} disabled={autoPicking}> + <AutoPickIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("removeInstance") || "Remove Instance"} + + + {browser.i18n.getMessage("autoPickInstance") || "Auto Pick Instance"} + </Button> + {/if} + </div> + {:else if params.get("message") == "no_instance"} + <div> + <h1>You have no instance selected for this frontend</h1> + <Button on:click={autoPickInstance} disabled={autoPicking}> + <AutoPickIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("autoPickInstance") || "Auto Pick Instance"} + </Button> + </div> + {/if} + </div> +{:else} + <p>Loading...</p> +{/if} + +<style> + :global(body) { + width: 100vw; + height: 100vh; + margin: 0; + padding: 0; + } + + div.main { + height: 100%; + display: grid; + grid-template-columns: 800px; + margin: 0; + padding-top: 50px; + justify-content: center; + font-family: "Inter", sans-serif; + box-sizing: border-box; + font-size: 16px; + background-color: var(--bg-main); + color: var(--text); + overflow: scroll; + } + + :global(.margin) { + margin-right: 10px; + margin-left: 0; + } + :global(.margin_rtl) { + margin-right: 0; + margin-left: 10px; + } +</style> diff --git a/src/pages/messages_src/main.js b/src/pages/messages_src/main.js new file mode 100644 index 00000000..c4012f4a --- /dev/null +++ b/src/pages/messages_src/main.js @@ -0,0 +1,7 @@ +import App from "./App.svelte" + +const app = new App({ + target: document.body, +}) + +export default app diff --git a/src/pages/messages_src/stores.js b/src/pages/messages_src/stores.js new file mode 100644 index 00000000..782f6064 --- /dev/null +++ b/src/pages/messages_src/stores.js @@ -0,0 +1,5 @@ +import { writable } from "svelte/store" + +export const options = writable(null) +export const config = writable(null) +export const page = writable("general") diff --git a/src/pages/options/index.html b/src/pages/options/index.html new file mode 100644 index 00000000..b197d4a7 --- /dev/null +++ b/src/pages/options/index.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> + +<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"> + <title>Settings</title> + <link rel='stylesheet' href='build/bundle.css'> + <link rel='stylesheet' href='../fonts/styles.css'> + <script defer src='build/bundle.js'></script> +</head> + +<body> +</body> + +</html> \ No newline at end of file diff --git a/src/pages/options/index.js b/src/pages/options/index.js deleted file mode 100644 index fcc51298..00000000 --- a/src/pages/options/index.js +++ /dev/null @@ -1,399 +0,0 @@ -import utils from "../../assets/javascripts/utils.js" - -let config, - options, - 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) - - - for (const frontend in config.services[service].frontends) { - if (config.services[service].frontends[frontend].instanceList) { - processCustomInstances(frontend, document) - 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 () { - const blacklist = await utils.getBlacklist(options) - const 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, config.networks, document, redirects, blacklist) - } - } - } - }() - } -} - -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 => { - let time = pingCache[x] - let timeText = "" - if (time) { - const { color, text } = processTime(time) - timeText = `<span class="ping" style="color:${color};">${text}</span>` - } - return `<div> - <x> - <a href="${x}" target="_blank">${x}</a> - ${timeText} - </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) - const blacklist = await utils.getBlacklist(options) - const redirects = await utils.getList(options) - createList(frontend, config.networks, document, redirects, blacklist) - }) - }) - } -} - -async function processCustomInstances(frontend, document) { - 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] - let url - try { - 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 - * @param {*} networks - * @param {*} document - * @param {*} redirects - * @param {*} blacklist - */ -async function createList(frontend, networks, document, redirects, blacklist) { - const pingCache = await utils.getPingCache() - const options = await utils.getOptions() - for (const network in 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] - - const sortedInstances = instances.sort((a, b) => blacklist.cloudflare.includes(a) && !blacklist.cloudflare.includes(b)) - - const content = sortedInstances - .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, config.networks, document, redirects, blacklist) - }) - } - }) - } - } -} - -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 - } -} diff --git a/src/pages/options/index.pug b/src/pages/options/index.pug deleted file mode 100644 index 4e19b087..00000000 --- a/src/pages/options/index.pug +++ /dev/null @@ -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") - \ No newline at end of file diff --git a/src/pages/options/init.js b/src/pages/options/init.js deleted file mode 100644 index f88c9ef9..00000000 --- a/src/pages/options/init.js +++ /dev/null @@ -1,54 +0,0 @@ -window.browser = window.browser || window.chrome - -import localise from "../../assets/javascripts/localise.js" -import utils from "../../assets/javascripts/utils.js" -import servicesHelper from "../../assets/javascripts/services.js" - -if (!(await utils.getOptions())) { - await servicesHelper.initDefaults() -} - -function changeTheme() { - return new Promise(async resolve => { - switch ((await utils.getOptions()).theme) { - case "dark": - document.body.classList.add("dark-theme") - document.body.classList.remove("light-theme") - for (const element of document.body.getElementsByClassName('dark')) { - element.style.display = 'none'; - } - break - case "light": - document.body.classList.add("light-theme") - document.body.classList.remove("dark-theme") - for (const element of document.body.getElementsByClassName('light')) { - element.style.display = 'none'; - } - break - default: - if (matchMedia("(prefers-color-scheme: light)").matches) { - document.body.classList.add("light-theme") - document.body.classList.remove("dark-theme") - for (const element of document.body.getElementsByClassName('light')) { - element.style.display = 'none'; - } - } else { - document.body.classList.add("dark-theme") - document.body.classList.remove("light-theme") - for (const element of document.body.getElementsByClassName('dark')) { - element.style.display = 'none'; - } - } - } - resolve() - }) -} - -changeTheme() -if (["ar", "iw", "ku", "fa", "ur"].includes(browser.i18n.getUILanguage())) { - document.getElementsByTagName("body")[0].classList.add("rtl") - document.getElementsByTagName("body")[0].dir = "rtl" -} -localise.localisePage() - -window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", changeTheme) diff --git a/src/pages/options/widgets/general.js b/src/pages/options/widgets/general.js deleted file mode 100644 index 6f2852a9..00000000 --- a/src/pages/options/widgets/general.js +++ /dev/null @@ -1,219 +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() - if (type == "select") { - options[option] = event.target.options[event.target.options.selectedIndex].value - } else if (type == "checkbox") { - options[option] = event.target.checked - } else if (type == "range") { - options[option] = event.target.value - } - 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) - bookmarksMenuElement.checked = await browser.permissions.request({ - permissions: ["bookmarks"] - }) - else - bookmarksMenuElement.checked = !await browser.permissions.remove({ - permissions: ["bookmarks"] - }) -}) - -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 -bookmarksMenuElement.checked = await browser.permissions.contains({ permissions: ["bookmarks"] }) -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() -}) diff --git a/src/pages/options/widgets/general.pug b/src/pages/options/widgets/general.pug deleted file mode 100644 index 70316473..00000000 --- a/src/pages/options/widgets/general.pug +++ /dev/null @@ -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" 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") \ No newline at end of file diff --git a/src/pages/options/widgets/services.pug b/src/pages/options/widgets/services.pug deleted file mode 100644 index e08bb001..00000000 --- a/src/pages/options/widgets/services.pug +++ /dev/null @@ -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... \ No newline at end of file diff --git a/src/pages/options_src/App.svelte b/src/pages/options_src/App.svelte new file mode 100644 index 00000000..1c4830bf --- /dev/null +++ b/src/pages/options_src/App.svelte @@ -0,0 +1,101 @@ +<script> + const browser = window.browser || window.chrome + + import General from "./General/General.svelte" + import url from "./url" + 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 } 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) { + await servicesHelper.initDefaults() + opts = await utils.getOptions() + } + options.set(opts) + config.set(await utils.getConfig()) + }) + + let style + $: if (_options) style = utils.style(_options, window) + + const dir = ["ar", "iw", "ku", "fa", "ur"].includes(browser.i18n.getUILanguage()) ? "rtl" : "ltr" + document.body.dir = dir +</script> + +{#if _options && _config} + <div class={dir} {dir} {style}> + <Sidebar /> + {#if !$url.hash || $url.hash == "#general"} + <General /> + {:else if $url.hash.startsWith("#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 800px; + margin: 0; + padding-top: 50px; + justify-content: center; + font-family: "Inter", sans-serif; + box-sizing: border-box; + font-size: 16px; + background-color: var(--bg-main); + color: var(--text); + overflow: scroll; + } + + @media (max-width: 1250px) { + div { + grid-template-columns: auto; + grid-template-rows: min-content auto; + padding-left: 5vw; + padding-right: 5vw; + } + } + + @media (max-width: 715px) { + div { + font-size: 14px; + grid-template-columns: auto; + grid-template-rows: min-content auto; + padding-left: 5vw; + padding-right: 5vw; + } + } +</style> diff --git a/src/pages/options_src/General/Exceptions.svelte b/src/pages/options_src/General/Exceptions.svelte new file mode 100644 index 00000000..7315877d --- /dev/null +++ b/src/pages/options_src/General/Exceptions.svelte @@ -0,0 +1,110 @@ +<script> + const browser = window.browser || window.chrome + + 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> + +<Row> + <Label>{browser.i18n.getMessage("excludeFromRedirecting") || "Excluded from redirecting"}</Label> +</Row> +<div dir="ltr"> + <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> diff --git a/src/pages/options_src/General/General.svelte b/src/pages/options_src/General/General.svelte new file mode 100644 index 00000000..b6ed1b46 --- /dev/null +++ b/src/pages/options_src/General/General.svelte @@ -0,0 +1,98 @@ +<script> + const browser = window.browser || window.chrome + + import Exceptions from "./Exceptions.svelte" + import SettingsButtons from "./SettingsButtons.svelte" + import { options } from "../stores" + import { onDestroy } from "svelte" + import Row from "../../components/Row.svelte" + import Label from "../../components/Label.svelte" + import Select from "../../components/Select.svelte" + import Checkbox from "../../components/Checkbox.svelte" + + let _options + const unsubscribe = options.subscribe(val => (_options = val)) + onDestroy(unsubscribe) + + let disableBookmarks = null + browser.runtime.getPlatformInfo(r => { + switch (r.os) { + case "fuchsia": + case "ios": + case "android": + disableBookmarks = true + break + default: + disableBookmarks = false + } + if (!disableBookmarks) { + browser.permissions.contains({ permissions: ["bookmarks"] }, r => (bookmarksPermission = r)) + } + }) + + let bookmarksPermission + $: if (disableBookmarks !== null && disableBookmarks === false) { + if (bookmarksPermission) { + browser.permissions.request({ permissions: ["bookmarks"] }, r => (bookmarksPermission = r)) + } else { + browser.permissions.remove({ permissions: ["bookmarks"] }) + bookmarksPermission = false + } + } +</script> + +<div> + <Row> + <Label>{browser.i18n.getMessage("theme") || "Theme"}</Label> + <Select + values={[ + { value: "detect", name: browser.i18n.getMessage("auto") || "Auto" }, + { value: "light", name: browser.i18n.getMessage("light") || "Light" }, + { value: "dark", name: browser.i18n.getMessage("dark") || "Dark" }, + ]} + value={_options.theme} + onChange={e => { + _options.theme = e.target.options[e.target.options.selectedIndex].value + options.set(_options) + }} + /> + </Row> + + <Row> + <Label>{browser.i18n.getMessage("fetchPublicInstances") || "Fetch public instances"}</Label> + <Select + value={_options.fetchInstances} + values={[ + { value: "github", name: "GitHub" }, + { value: "codeberg", name: "Codeberg" }, + { value: "disable", name: browser.i18n.getMessage("disable") || "Disable" }, + ]} + onChange={e => { + _options.fetchInstances = e.target.options[e.target.options.selectedIndex].value + options.set(_options) + }} + /> + </Row> + + <Row> + <Label>{browser.i18n.getMessage("redirectOnlyInIncognito") || "Redirect Only in Incognito"}</Label> + <Checkbox + checked={_options.redirectOnlyInIncognito} + onChange={e => { + _options.redirectOnlyInIncognito = e.target.checked + options.set(_options) + }} + /> + </Row> + + {#if disableBookmarks === false} + <Row> + <Label>{browser.i18n.getMessage("bookmarksMenu") || "Bookmarks menu"}</Label> + <Checkbox bind:checked={bookmarksPermission} /> + </Row> + {/if} + + <Exceptions /> + + <SettingsButtons /> +</div> diff --git a/src/pages/options_src/General/SettingsButtons.svelte b/src/pages/options_src/General/SettingsButtons.svelte new file mode 100644 index 00000000..4be747fe --- /dev/null +++ b/src/pages/options_src/General/SettingsButtons.svelte @@ -0,0 +1,112 @@ +<script> + const 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 importSettingsInput + let importSettingsFiles + $: if (importSettingsFiles) { + const reader = new FileReader() + reader.readAsText(importSettingsFiles[0]) + reader.onload = async () => { + let data = JSON.parse(reader.result) + if (data.version != browser.runtime.getManifest().version) { + alert("Importing from a previous version. Be careful") + } + data = await servicesHelper.processUpdate(data) + options.set(data) + } + reader.onerror = error => { + console.log("error", error) + alert("Error!") + } + } + + async function exportSettings() { + _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() + } + + async function exportSettingsSync() { + _options.version = browser.runtime.getManifest().version + browser.storage.sync.set({ options: _options }) + } + + async function importSettingsSync() { + browser.storage.sync.get({ options }, async r => { + let data = r.options + if (data.version != browser.runtime.getManifest().version) { + alert("Importing from a previous version. Be careful") + } + data = await servicesHelper.processUpdate(data) + options.set(data) + }) + } + + async function resetSettings() { + browser.storage.local.clear(async () => { + const data = await servicesHelper.initDefaults() + options.set(data) + }) + } +</script> + +<div class="buttons"> + <Button on:click={() => importSettingsInput.click()}> + <ImportIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("importSettings") || "Import Settings"} + </Button> + <input + type="file" + accept=".json" + style="display: none" + bind:this={importSettingsInput} + bind:files={importSettingsFiles} + /> + + <Button on:click={exportSettings}> + <ExportIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("exportSettings") || "Export Settings"} + </Button> + + <Button on:click={exportSettingsSync}> + <ExportIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("exportSettingsToSync") || "Export Settings to Sync"} + </Button> + + <Button on:click={importSettingsSync}> + <ImportIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("importSettingsFromSync") || "Import Settings from Sync"} + </Button> + + <Button on:click={resetSettings}> + <ResetIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("resetSettings") || "Reset Settings"} + </Button> +</div> + +<style> + :global(.margin) { + margin-right: 10px; + margin-left: 0; + } + :global(.margin_rtl) { + margin-right: 0; + margin-left: 10px; + } +</style> diff --git a/src/pages/options_src/Services/FrontendIcon.svelte b/src/pages/options_src/Services/FrontendIcon.svelte new file mode 100644 index 00000000..4b392676 --- /dev/null +++ b/src/pages/options_src/Services/FrontendIcon.svelte @@ -0,0 +1,41 @@ +<script> + import { onDestroy } from "svelte" + export let details + export let selectedService + import { config, options } from "../stores" + let _options + let _config + + const unsubscribeOptions = options.subscribe(val => (_options = val)) + const unsubscribeConfig = config.subscribe(val => (_config = val)) + onDestroy(() => { + unsubscribeOptions() + unsubscribeConfig() + }) + + let theme + $: if (_options) { + if (_options.theme == "dark") { + theme = "dark" + } else if (_options.theme == "light") { + theme = "light" + } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + theme = "dark" + } else { + theme = "light" + } + } + $: imageType = _config.services[selectedService].frontends[details.value].imageType +</script> + +{#if imageType} + {#if imageType == "svgMono"} + {#if theme == "dark"} + <img src={`/assets/images/${details.value}-icon-light.svg`} alt={details.label} /> + {:else} + <img src={`/assets/images/${details.value}-icon.svg`} alt={details.label} /> + {/if} + {:else} + <img src={`/assets/images/${details.value}-icon.${imageType}`} alt={details.label} /> + {/if} +{/if} diff --git a/src/pages/options_src/Services/Instances.svelte b/src/pages/options_src/Services/Instances.svelte new file mode 100644 index 00000000..4e5d1e7d --- /dev/null +++ b/src/pages/options_src/Services/Instances.svelte @@ -0,0 +1,261 @@ +<script> + const 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 AutoPickIcon from "../../icons/AutoPickIcon.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 + export let selectedFrontend + + 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 + + $: serviceConf = _config.services[selectedService] + + let allInstances = [] + + $: { + allInstances = [] + if (_options[selectedFrontend]) allInstances.push(..._options[selectedFrontend]) + if (redirects && redirects[selectedFrontend]) { + allInstances.push(...redirects[selectedFrontend]["clearnet"]) + } + allInstances = [...new Set(allInstances)] + } + + let pingCache + $: { + if (pingCache) browser.storage.local.set({ pingCache }) + } + + function isCustomInstance(instance) { + if (redirects[selectedFrontend]) { + for (const network in redirects[selectedFrontend]) { + if (redirects[selectedFrontend][network].includes(instance)) return false + } + } + return true + } + + async function pingInstances() { + pingCache = {} + for (const instance of allInstances) { + pingCache[instance] = { color: "lightblue", value: "pinging..." } + const time = await utils.ping(instance) + pingCache[instance] = colorTime(time) + } + } + + async function autoPickInstance() { + const instances = utils.randomInstances(redirects[selectedFrontend]["clearnet"], 5) + const myInstancesCache = [] + for (const instance of instances) { + pingCache[instance] = { color: "lightblue", value: "pinging..." } + const time = await utils.ping(instance) + pingCache[instance] = colorTime(time) + myInstancesCache.push([instance, time]) + } + myInstancesCache.sort((a, b) => a[1] - b[1]) + + _options[selectedFrontend].push(myInstancesCache[0][0]) + options.set(_options) + } + + function colorTime(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[selectedFrontend].includes(instance)) { + _options[selectedFrontend].push(instance) + addInstanceValue = "" + options.set(_options) + } + } +</script> + +{#if serviceConf.frontends[selectedFrontend].instanceList && redirects && blacklist} + <hr /> + + <div> + <Button on:click={pingInstances}> + <PingIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("pingInstances") || "Ping Instances"} + </Button> + <Button on:click={autoPickInstance}> + <AutoPickIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("autoPickInstance") || "Auto Pick Instance"} + </Button> + </div> + + <Row> + <Label>{browser.i18n.getMessage("addYourFavoriteInstances") || "Add your favorite instances"}</Label> + </Row> + <div dir="ltr"> + <Row> + <Input + bind:value={addInstanceValue} + type="url" + placeholder="https://instance.com" + aria-label="Add instance input" + on:keydown={e => e.key === "Enter" && addInstance()} + /> + <button on:click={addInstance} class="add" aria-label="Add the instance"> + <AddIcon /> + </button> + </Row> + + {#each _options[selectedFrontend] 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[selectedFrontend].indexOf(instance) + if (index > -1) { + _options[selectedFrontend].splice(index, 1) + options.set(_options) + } + }} + > + <CloseIcon /> + </button> + </Row> + <hr /> + {/each} + + {#if redirects !== "disabled" && blacklist !== "disabled"} + {#if redirects[selectedFrontend] && redirects[selectedFrontend]["clearnet"]} + {#each Object.entries(_config.networks) as [networkName, network]} + {#if redirects[selectedFrontend] && redirects[selectedFrontend][networkName] && redirects[selectedFrontend][networkName].length > 0} + <Row></Row> + <Row><Label>{network.name}</Label></Row> + <hr /> + {#each redirects[selectedFrontend][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[selectedFrontend].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[selectedFrontend]) { + if (!_options[selectedFrontend].includes(instance)) { + _options[selectedFrontend].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; + word-wrap: anywhere; + } + + a:hover { + text-decoration: underline; + } + + :global(.margin) { + margin-right: 10px; + margin-left: 0; + } + :global(.margin_rtl) { + margin-right: 0; + margin-left: 10px; + } +</style> diff --git a/src/pages/options_src/Services/RedirectType.svelte b/src/pages/options_src/Services/RedirectType.svelte new file mode 100644 index 00000000..69ea2b73 --- /dev/null +++ b/src/pages/options_src/Services/RedirectType.svelte @@ -0,0 +1,102 @@ +<script> + const browser = window.browser || window.chrome + + import { onDestroy } from "svelte" + import SvelteSelect from "svelte-select" + import { options, config } from "../stores" + import Row from "../../components/Row.svelte" + import Label from "../../components/Label.svelte" + import FrontendIcon from "./FrontendIcon.svelte" + import Select from "../../components/Select.svelte" + + 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: browser.i18n.getMessage("both") || "Both" }, + { value: "sub_frame", name: browser.i18n.getMessage("onlyEmbedded") || "Only Embedded" }, + { value: "main_frame", name: browser.i18n.getMessage("onlyNotEmbedded") || "Only Not Embedded" }, + ] + } else if ( + serviceConf.frontends[frontendName].desktopApp && + Object.values(serviceConf.frontends).some(frontend => frontend.embeddable) + ) { + values = [ + { value: "both", name: browser.i18n.getMessage("both") || "Both" }, + { value: "main_frame", name: browser.i18n.getMessage("onlyNotEmbedded") || "Only Not Embedded" }, + ] + if (serviceOptions.redirectType == "sub_frame") { + serviceOptions.redirectType = "main_frame" + options.set(_options) + } + } else { + values = [{ value: "main_frame", name: browser.i18n.getMessage("onlyNotEmbedded") || "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, + label: frontendConf.name, + }) + } + } + } +</script> + +<Row> + <Label>{browser.i18n.getMessage("redirectType") || "Redirect Type"}</Label> + <Select + value={serviceOptions.redirectType} + onChange={e => { + serviceOptions.redirectType = e.target.options[e.target.options.selectedIndex].value + options.set(_options) + }} + {values} + /> +</Row> + +{#if serviceConf.frontends[frontendName].desktopApp && serviceOptions.redirectType != "main_frame"} + <Row> + <Label>{browser.i18n.getMessage("embedFrontend") || "Embed Frontend"}</Label> + <SvelteSelect + clearable={false} + class="svelte_select" + value={serviceOptions.embedFrontend} + on:change={e => { + serviceOptions.embedFrontend = e.detail.value + options.set(_options) + }} + items={embeddableFrontends} + > + <div class="slot" slot="item" let:item> + <FrontendIcon details={item} {selectedService} /> + {item.label} + </div> + <div class="slot" slot="selection" let:selection> + <FrontendIcon details={selection} {selectedService} /> + {selection.label} + </div> + </SvelteSelect> + </Row> +{/if} diff --git a/src/pages/options_src/Services/ServiceIcon.svelte b/src/pages/options_src/Services/ServiceIcon.svelte new file mode 100644 index 00000000..89393cf6 --- /dev/null +++ b/src/pages/options_src/Services/ServiceIcon.svelte @@ -0,0 +1,40 @@ +<script> + import { onDestroy } from "svelte" + export let details + import { config, options } from "../stores" + let _options + let _config + + const unsubscribeOptions = options.subscribe(val => (_options = val)) + const unsubscribeConfig = config.subscribe(val => (_config = val)) + onDestroy(() => { + unsubscribeOptions() + unsubscribeConfig() + }) + + let theme + $: if (_options) { + if (_options.theme == "dark") { + theme = "dark" + } else if (_options.theme == "light") { + theme = "light" + } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + theme = "dark" + } else { + theme = "light" + } + } + $: imageType = _config.services[details.value].imageType +</script> + +{#if imageType} + {#if imageType == "svgMono"} + {#if theme == "dark"} + <img src={`/assets/images/${details.value}-icon-light.svg`} alt={details.label} /> + {:else} + <img src={`/assets/images/${details.value}-icon.svg`} alt={details.label} /> + {/if} + {:else} + <img src={`/assets/images/${details.value}-icon.${imageType}`} alt={details.label} /> + {/if} +{/if} diff --git a/src/pages/options_src/Services/Services.svelte b/src/pages/options_src/Services/Services.svelte new file mode 100644 index 00000000..db2977f9 --- /dev/null +++ b/src/pages/options_src/Services/Services.svelte @@ -0,0 +1,260 @@ +<script> + const browser = window.browser || window.chrome + + import url from "../url" + 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 } from "svelte" + import Instances from "./Instances.svelte" + import SvelteSelect from "svelte-select" + import ServiceIcon from "./ServiceIcon.svelte" + import FrontendIcon from "./FrontendIcon.svelte" + import Checkbox from "../../components/Checkbox.svelte" + + let _options + let _config + + const unsubscribeOptions = options.subscribe(val => (_options = val)) + const unsubscribeConfig = config.subscribe(val => (_config = val)) + onDestroy(() => { + unsubscribeOptions() + unsubscribeConfig() + }) + let selectedService = $url.hash.startsWith("#services:") ? $url.hash.split(":")[1] : "youtube" + let hideServiceSelection = false + let hideFrontendSelection = false + $: serviceConf = _config.services[selectedService] + $: serviceOptions = _options[selectedService] + $: frontendWebsite = serviceConf.frontends[serviceOptions.frontend].url + $: servicesEntries = Object.entries(_config.services) + $: frontendEntries = Object.entries(serviceConf.frontends) +</script> + +<div> + <Row> + <Label> + <a href={serviceConf.url} style="text-decoration: underline;" target="_blank" rel="noopener noreferrer"> + {browser.i18n.getMessage("service") || "Service"} + </a> + </Label> + <div dir="ltr" on:click={() => (hideServiceSelection = true)} on:keydown={null}> + <SvelteSelect + clearable={false} + class="svelte_select" + value={selectedService} + showChevron + on:change={e => { + selectedService = e.detail.value + window.location.hash = `services:${e.detail.value}` + hideServiceSelection = false + }} + on:pointerup={() => (hideServiceSelection = false)} + on:focus={() => (hideServiceSelection = true)} + on:blur={() => (hideServiceSelection = false)} + items={[ + ...servicesEntries.map(([serviceKey, service]) => { + return { value: serviceKey, label: service.name } + }), + ]} + > + <div class={"slot " + (!_options[item.value].enabled && "disabled")} slot="item" let:item> + <ServiceIcon details={item} /> + {item.label} + </div> + <div + class={"slot " + (!_options[selection.value].enabled && !hideServiceSelection && "disabled")} + slot="selection" + let:selection + > + {#if !hideServiceSelection} + <ServiceIcon details={selection} /> + {selection.label} + {:else} + {browser.i18n.getMessage("searchService") || "Search Service"} + {/if} + </div> + <div style="font-size: 10px;" slot="chevron-icon">🮦</div> + </SvelteSelect> + </div> + </Row> + + <hr /> + + <Row> + <Label>{browser.i18n.getMessage("enable") || "Enable"}</Label> + <Checkbox + checked={serviceOptions.enabled} + onChange={e => { + serviceOptions.enabled = e.target.checked + options.set(_options) + }} + /> + </Row> + + <div style={!serviceOptions.enabled && "pointer-events: none;opacity: 0.4;user-select: none;"}> + <Row> + <Label>{browser.i18n.getMessage("showInPopup") || "Show in popup"}</Label> + <Checkbox + 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> + + <Row> + <Label> + <a href={frontendWebsite} style="text-decoration: underline;" target="_blank" rel="noopener noreferrer"> + {browser.i18n.getMessage("frontend") || "Frontend"} + </a> + </Label> + <div dir="ltr" on:click={() => (hideFrontendSelection = true)} on:keydown={null}> + <SvelteSelect + clearable={false} + dir="ltr" + class="svelte_select" + value={serviceOptions.frontend} + showChevron + on:change={e => { + serviceOptions.frontend = e.detail.value + options.set(_options) + hideFrontendSelection = false + }} + on:pointerup={() => (hideServiceSelection = false)} + on:focus={() => (hideFrontendSelection = true)} + on:blur={() => (hideFrontendSelection = false)} + items={[ + ...frontendEntries.map(([frontendId, frontend]) => ({ + value: frontendId, + label: frontend.name, + })), + ]} + > + <div class="slot" slot="item" let:item> + <FrontendIcon details={item} {selectedService} /> + {item.label} + </div> + <div class="slot" slot="selection" let:selection> + {#if !hideFrontendSelection} + <FrontendIcon details={selection} {selectedService} /> + {selection.label} + {:else} + {browser.i18n.getMessage("search_frontend") || "Search Frontend"} + {/if} + </div> + <div style="font-size: 10px;" slot="chevron-icon">🮦</div> + </SvelteSelect> + </div> + </Row> + + <RedirectType {selectedService} /> + + <Row> + <Label>{browser.i18n.getMessage("unsupportedIframesHandling") || "Unsupported embeds handling"}</Label> + <Select + value={serviceOptions.unsupportedUrls} + onChange={e => { + serviceOptions.unsupportedUrls = e.target.options[e.target.options.selectedIndex].value + options.set(_options) + }} + values={[ + { value: "bypass", name: browser.i18n.getMessage("bypass") || "Bypass" }, + { value: "block", name: browser.i18n.getMessage("block") || "Block" }, + ]} + /> + </Row> + + <div style={_options.redirectOnlyInIncognito && "pointer-events: none;opacity: 0.4;user-select: none;"}> + <Row> + <Label>{browser.i18n.getMessage("redirectOnlyInIncognito") || "Redirect Only in Incognito"}</Label> + <Checkbox + checked={serviceOptions.redirectOnlyInIncognito} + onChange={e => { + serviceOptions.redirectOnlyInIncognito = e.target.checked + options.set(_options) + }} + /> + </Row> + </div> + + {#if selectedService == "search"} + <Row> + <Label>{browser.i18n.getMessage("redirectGoogle") || "Redirect Google"}</Label> + <Checkbox + checked={serviceOptions.redirectGoogle} + onChange={e => { + serviceOptions.redirectGoogle = e.target.checked + options.set(_options) + }} + /> + </Row> + <Row> + <Label> + {@html browser.i18n.getMessage("searchHint") || + `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" + target="_blank" + rel="noopener noreferrer" + >here + </a>.`} + </Label> + </Row> + {/if} + + <Instances + {selectedService} + selectedFrontend={!serviceConf.frontends[serviceOptions.frontend].desktopApp || + serviceOptions.redirectType == "main_frame" + ? serviceOptions.frontend + : serviceOptions.embedFrontend} + /> + + <Row></Row> + </div> +</div> + +<style> + :global(.svelte_select) { + font-weight: bold; + --item-padding: 0 10px; + --border: none; + --border-hover: none; + --border-focused: none; + --width: 210px; + --background: var(--bg-secondary); + --list-background: var(--bg-secondary); + --item-is-active-bg: grey; + --item-hover-bg: grey; + --item-is-active-color: var(--text); + --list-max-height: 400px; + --padding: 0 0 0 10px; + --item-color: var(--text); + } + :global(.svelte_select .slot) { + display: flex; + justify-content: start; + align-items: center; + } + + :global(.svelte_select img, .svelte_select svg) { + margin-right: 10px; + margin-left: 0; + height: 26px; + width: 26px; + color: var(--text); + } + + :global(.svelte_select .disabled) { + opacity: 0.4; + } +</style> diff --git a/src/pages/options_src/Sidebar.svelte b/src/pages/options_src/Sidebar.svelte new file mode 100644 index 00000000..6b67581a --- /dev/null +++ b/src/pages/options_src/Sidebar.svelte @@ -0,0 +1,69 @@ +<script> + const browser = window.browser || window.chrome + + import url from "./url" + import GeneralIcon from "../icons/GeneralIcon.svelte" + import ServicesIcon from "../icons/ServicesIcon.svelte" + import AboutIcon from "../icons/AboutIcon.svelte" +</script> + +<div> + <a href="#general" style={$url.hash == "#general" && "color: var(--active);"}> + <GeneralIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("general") || "General"} + </a> + <a href="#services" style={$url.hash == "#services" && "color: var(--active);"}> + <ServicesIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("services") || "Services"} + </a> + <a href="https://libredirect.github.io" target="_blank" rel="noopener noreferrer"> + <AboutIcon class="margin margin_{document.body.dir}" /> + {browser.i18n.getMessage("about") || "About"} + </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; + min-width: max-content; + } + + a:hover { + color: var(--active); + } + + @media (max-width: 1250px) { + div { + flex-direction: row; + justify-content: center; + margin: 0; + } + } + + @media (max-width: 715px) { + a { + margin: 5px; + } + } + + :global(.margin) { + margin-right: 5px; + margin-left: 0; + } + :global(.margin_rtl) { + margin-right: 0; + margin-left: 5px; + } +</style> diff --git a/src/pages/options_src/main.js b/src/pages/options_src/main.js new file mode 100644 index 00000000..c4012f4a --- /dev/null +++ b/src/pages/options_src/main.js @@ -0,0 +1,7 @@ +import App from "./App.svelte" + +const app = new App({ + target: document.body, +}) + +export default app diff --git a/src/pages/options_src/stores.js b/src/pages/options_src/stores.js new file mode 100644 index 00000000..7ae0f8c7 --- /dev/null +++ b/src/pages/options_src/stores.js @@ -0,0 +1,4 @@ +import { writable } from "svelte/store" + +export const options = writable(null) +export const config = writable(null) diff --git a/src/pages/options_src/url.js b/src/pages/options_src/url.js new file mode 100644 index 00000000..010e5b21 --- /dev/null +++ b/src/pages/options_src/url.js @@ -0,0 +1,38 @@ +// https://svelte.dev/repl/5abaac000b164aa1aacc6051d5c4f584?version=3.59.2 + +import { derived, writable } from 'svelte/store' + +export function createUrlStore(ssrUrl) { + // Ideally a bundler constant so that it's tree-shakable + if (typeof window === 'undefined') { + const { subscribe } = writable(ssrUrl) + return { subscribe } + } + + const href = writable(window.location.href) + + const originalPushState = history.pushState + const originalReplaceState = history.replaceState + + const updateHref = () => href.set(window.location.href) + + history.pushState = () => { + originalPushState.apply(this, arguments) + updateHref() + } + + history.replaceState = () => { + originalReplaceState.apply(this, arguments) + updateHref() + } + + window.addEventListener('popstate', updateHref) + window.addEventListener('hashchange', updateHref) + + return { + subscribe: derived(href, ($href) => new URL($href)).subscribe + } +} + +// If you're using in a pure SPA, you can return a store directly and share it everywhere +export default createUrlStore() diff --git a/src/pages/popup/index.html b/src/pages/popup/index.html new file mode 100644 index 00000000..b197d4a7 --- /dev/null +++ b/src/pages/popup/index.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> + +<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"> + <title>Settings</title> + <link rel='stylesheet' href='build/bundle.css'> + <link rel='stylesheet' href='../fonts/styles.css'> + <script defer src='build/bundle.js'></script> +</head> + +<body> +</body> + +</html> \ No newline at end of file diff --git a/src/pages/popup/popup.js b/src/pages/popup/popup.js deleted file mode 100644 index 5eeb18c2..00000000 --- a/src/pages/popup/popup.js +++ /dev/null @@ -1,113 +0,0 @@ -"use strict" -window.browser = window.browser || window.chrome - -import servicesHelper from "../../assets/javascripts/services.js" -import utils from "../../assets/javascripts/utils.js" - -document.getElementById("more-options").addEventListener("click", () => browser.runtime.openOptionsPage()) - -const allSites = document.getElementById("all_sites") -const currSite = document.getElementById("current_site") -const currentSiteDivider = document.getElementById("current_site_divider") - -const config = await utils.getConfig() -const divs = {} - -for (const service in config.services) { - divs[service] = {} - - divs[service].all = allSites.getElementsByClassName(service)[0] - divs[service].current = currSite.getElementsByClassName(service)[0] - - divs[service].all_toggle = allSites.getElementsByClassName(`${service}-enabled`)[0] - divs[service].all_toggle.addEventListener("change", async () => { - const options = await utils.getOptions() - options[service].enabled = divs[service].all_toggle.checked - browser.storage.local.set({ options }) - }) - - allSites.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) }) - } - }) - }) - - divs[service].current_toggle = currSite.getElementsByClassName(`${service}-enabled`)[0] - divs[service].current_toggle.addEventListener("change", async () => { - const options = await utils.getOptions() - options[service].enabled = divs[service].current_toggle.checked - browser.storage.local.set({ options }) - }) - - currSite.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => { - browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0].url) { - const url = new URL(tabs[0].url) - browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) }) - } - }) - }) -} - -browser.tabs.query({ active: true, currentWindow: true }, async tabs => { - let url; - - // Set visibility of control buttons - if (tabs[0].url) { - url = new URL(tabs[0].url) - servicesHelper.switchInstance(url).then(r => { - if (r) { - document.getElementById("change_instance_div").style.display = "" - document.getElementById("change_instance").addEventListener("click", async () => - browser.tabs.update({ url: await servicesHelper.switchInstance(url) }) - ) - } - }) - servicesHelper.copyRaw(url, true).then(r => { - if (r) { - document.getElementById("copy_original_div").style.display = "" - document.getElementById("copy_original").addEventListener("click", () => - servicesHelper.copyRaw(url) - ) - } - }) - servicesHelper.reverse(url).then(r => { - if (r) { - document.getElementById("redirect_to_original_div").style.display = "" - 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 = "" - document.getElementById("redirect").addEventListener("click", () => - browser.runtime.sendMessage("redirectTab") - ) - } - }) - } - - const options = await utils.getOptions() - - // Set visibility of all service buttons - for (const service of options.popupServices) { - divs[service].all.classList.remove("hide") - divs[service].all_toggle.checked = options[service].enabled - } - - // Set visibility of current page service button - if (url) { - const service = await servicesHelper.computeService(url) - if (service) { - divs[service].all.classList.add("hide") - divs[service].current.classList.remove("hide") - divs[service].current_toggle.checked = options[service].enabled - currentSiteDivider.style.display = "" - } - } -}) \ No newline at end of file diff --git a/src/pages/popup/popup.pug b/src/pages/popup/popup.pug deleted file mode 100644 index f145fe5d..00000000 --- a/src/pages/popup/popup.pug +++ /dev/null @@ -1,51 +0,0 @@ -doctype html -html(lang="en") - head - meta(charset="UTF-8") - meta(name="viewport" content="width=device-width, initial-scale=1.0") - link(href="../stylesheets/styles.css" rel="stylesheet") - link(href="./style.css" rel="stylesheet") - body(dir="auto") - div(id="current_site") - include /src/pages/popup/switches - div(id="current_site_divider" style="display: none") - hr - - div(id="all_sites") - include /src/pages/popup/switches - - hr - - div(class="block" id="change_instance_div" style="display: none") - button(class="title button bottom-button" id="change_instance") - label(data-localise="__MSG_switchInstance__") Switch Instance - svg(xmlns="http://www.w3.org/2000/svg" height="26px" width="26px" fill="currentColor") - path(d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z") - - div(class="block" id="copy_original_div" title="Copy the original redirected link" style="display: none") - button(class="title button bottom-button" id="copy_original") - label() Copy Original - svg(xmlns="http://www.w3.org/2000/svg" height="24px" width="24px" fill="currentColor") - path(d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z") - - div(class="block" id="redirect_div" style="display: none") - button(class="title button bottom-button" id="redirect") - label Redirect - svg(xmlns="http://www.w3.org/2000/svg" height="24" width="24" fill="currentColor") - path(d="M7 20v-9q0-.825.588-1.413Q8.175 9 9 9h8.2l-1.6-1.6L17 6l4 4-4 4-1.4-1.4 1.6-1.6H9v9Z") - - div(class="block" id="redirect_to_original_div" style="display: none") - button(class="title button bottom-button" id="redirect_to_original") - label Redirect To Original - svg(xmlns="http://www.w3.org/2000/svg" height="24px" width="24px" fill="currentColor") - path(d="M 17,20 V 11 Q 17,10.175 16.412,9.587 15.825,9 15,9 H 6.8 L 8.4,7.4 7,6 3,10 7,14 8.4,12.6 6.8,11 H 15 v 9 z" id="path2") - - div(class="block") - button(class="title button bottom-button" id="more-options") - label(data-localise="__MSG_settings__") Settings - svg(xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="26px" width="26px" fill="currentColor") - path(d="m9.25 22-.4-3.2q-.325-.125-.612-.3-.288-.175-.563-.375L4.7 19.375l-2.75-4.75 2.575-1.95Q4.5 12.5 4.5 12.337v-.675q0-.162.025-.337L1.95 9.375l2.75-4.75 2.975 1.25q.275-.2.575-.375.3-.175.6-.3l.4-3.2h5.5l.4 3.2q.325.125.613.3.287.175.562.375l2.975-1.25 2.75 4.75-2.575 1.95q.025.175.025.337v.675q0 .163-.05.338l2.575 1.95-2.75 4.75-2.95-1.25q-.275.2-.575.375-.3.175-.6.3l-.4 3.2Zm2.8-6.5q1.45 0 2.475-1.025Q15.55 13.45 15.55 12q0-1.45-1.025-2.475Q13.5 8.5 12.05 8.5q-1.475 0-2.488 1.025Q8.55 10.55 8.55 12q0 1.45 1.012 2.475Q10.575 15.5 12.05 15.5Z") - - div(class="space") - script(type="module" src="../options/init.js") - script(type="module" src="./popup.js") \ No newline at end of file diff --git a/src/pages/popup/style.css b/src/pages/popup/style.css deleted file mode 100644 index 6c258d75..00000000 --- a/src/pages/popup/style.css +++ /dev/null @@ -1,53 +0,0 @@ -body { - width: 270px; - min-height: auto; -} - -html, -body { - margin: 0; -} - -.hide { - display: none !important; -} - -.button { - display: flex; - margin: 0 auto; - justify-content: space-between; -} - -.button svg { - width: 26px; - height: 26px; -} - -.bottom-button { - width: 100%; -} - -.space { - height: 10px; -} - -input { - height: 23px; - width: 46px; -} - - -div.block label { - margin: 0; - font-size: 18px; - font-weight: bold; - max-width: 180px; -} - -div.block label:hover { - cursor: pointer; -} - -div.block div { - display: flex; -} \ No newline at end of file diff --git a/src/pages/popup/switches.pug b/src/pages/popup/switches.pug deleted file mode 100644 index bea107d2..00000000 --- a/src/pages/popup/switches.pug +++ /dev/null @@ -1,14 +0,0 @@ -each _, service in services - div(class=`${service} block hide`) - a(class="title" href=services[service].url) - if services[service].imageType == 'svgMono' - img(class='dark' src=`/assets/images/${service}-icon.svg`) - img(class='light' src=`/assets/images/${service}-icon-light.svg`) - else - img(src=`/assets/images/${service}-icon.${services[service].imageType}`) - label=services[service].name - div - input(class=`${service}-enabled` type="checkbox" aria-label=`toggle ${services[service].name}`) - button(class=`${service}-change_instance title button` aria-label=`change instance for ${services[service].name}`) - svg(xmlns="http://www.w3.org/2000/svg" height="26px" width="26px" fill="currentColor") - path(d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z") \ No newline at end of file diff --git a/src/pages/popup_src/App.svelte b/src/pages/popup_src/App.svelte new file mode 100644 index 00000000..f6699312 --- /dev/null +++ b/src/pages/popup_src/App.svelte @@ -0,0 +1,78 @@ +<script> + const browser = window.browser || window.chrome + + import utils from "../../assets/javascripts/utils.js" + import { onDestroy } from "svelte" + import servicesHelper from "../../assets/javascripts/services.js" + import { onMount } from "svelte" + import Buttons from "./Buttons.svelte" + + import { options, config, page } from "./stores" + + 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) { + await servicesHelper.initDefaults() + opts = await utils.getOptions() + } + options.set(opts) + config.set(await utils.getConfig()) + }) + + let _page + page.subscribe(val => (_page = val)) + + let style + $: if (_options) style = utils.style(_options, window) +</script> + +{#if _options && _config} + <div class="main" dir="auto" {style}> + <Buttons /> + </div> +{:else} + <p>Loading...</p> +{/if} + +<style> + :global(html, body) { + min-width: 260px; + height: min-content; + min-height: auto; + margin: 0; + padding: 0; + box-sizing: border-box; + } + + :global(body) { + margin-top: -20px; + } + + div { + font-weight: bold; + height: 100%; + margin: 0; + padding: 10px; + padding-top: 20px; + font-family: "Inter", sans-serif; + font-size: 16px; + background-color: var(--bg-main); + color: var(--text); + } +</style> diff --git a/src/pages/popup_src/Buttons.svelte b/src/pages/popup_src/Buttons.svelte new file mode 100644 index 00000000..ab5682dc --- /dev/null +++ b/src/pages/popup_src/Buttons.svelte @@ -0,0 +1,145 @@ +<script> + const browser = window.browser || window.chrome + + import Row from "./components/Row.svelte" + import Label from "../components/Label.svelte" + import CopyIcon from "../icons/CopyIcon.svelte" + import RedirectToOriginalIcon from "../icons/RedirectToOriginalIcon.svelte" + import RedirectIcon from "../icons/RedirectIcon.svelte" + import SwitchInstanceIcon from "../icons/SwitchInstanceIcon.svelte" + import SettingsIcon from "../icons/SettingsIcon.svelte" + import { options, config } from "./stores" + import { onDestroy } from "svelte" + import servicesHelper from "../../assets/javascripts/services" + import Switch from "./components/Switch.svelte" + + let _options + let _config + + const unsubscribeOptions = options.subscribe(val => (_options = val)) + const unsubscribeConfig = config.subscribe(val => (_config = val)) + onDestroy(() => { + unsubscribeOptions() + unsubscribeConfig() + }) + + let url + let switchInstance + let redirectToOriginal + let redirect + let currentService + browser.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0].url) { + url = new URL(tabs[0].url) + servicesHelper.switchInstance(url).then(r => (switchInstance = r)) + servicesHelper.reverse(url).then(r => (redirectToOriginal = r)) + servicesHelper.redirectAsync(url, "main_frame", null, null, false, true).then(r => (redirect = r)) + servicesHelper.computeService(url).then(r => (currentService = r)) + } + }) +</script> + +<div class={document.body.dir}> + {#if redirect} + <Row + class="interactive" + on:click={() => { + browser.tabs.query({ active: true, currentWindow: true }, tabs => { + browser.runtime.sendMessage({ message: "redirect", tabId: tabs[0].id }, () => { + browser.tabs.update({ url: redirect }) + }) + }) + }} + > + <Label>{browser.i18n.getMessage("redirect") || "Redirect"}</Label> + <RedirectIcon /> + </Row> + {/if} + + {#if switchInstance} + <Row + class="interactive" + on:click={async () => + browser.tabs.update({ url: switchInstance }, () => { + window.close() + })} + > + <Label>{browser.i18n.getMessage("switchInstance") || "Switch Instance"}</Label> + <SwitchInstanceIcon /> + </Row> + {/if} + + {#if redirectToOriginal} + <Row class="interactive" on:click={() => servicesHelper.copyRaw(url)}> + <Label>{browser.i18n.getMessage("copyOriginal") || "Copy Original"}</Label> + <CopyIcon /> + </Row> + <Row + class="interactive" + on:click={() => { + browser.tabs.query({ active: true, currentWindow: true }, tabs => { + browser.runtime.sendMessage({ message: "reverse", tabId: tabs[0].id }, () => { + browser.tabs.update({ url: redirectToOriginal }) + }) + }) + }} + > + <Label>{browser.i18n.getMessage("redirectToOriginal" || "Redirect to Original")}</Label> + <RedirectToOriginalIcon /> + </Row> + {/if} + + {#if redirect || switchInstance || redirectToOriginal} + <hr /> + {/if} + + {#if currentService} + <Switch serviceKey={currentService} {url} /> + <hr /> + {/if} + + {#each _options.popupServices as serviceKey} + {#if currentService !== serviceKey} + <Switch {serviceKey} {url} /> + {/if} + {/each} + + <hr /> + + <Row + class="interactive" + on:click={() => + browser.tabs.create({ url: browser.runtime.getURL("pages/options/index.html") }, () => { + window.close() + })} + > + <Label>{browser.i18n.getMessage("settings")}</Label> + <SettingsIcon /> + </Row> +</div> + +<style> + :global(.interactive) { + transition: 0.1s; + } + :global(.interactive:hover) { + color: var(--active); + cursor: pointer; + } + :global(.interactive:active) { + transform: translateY(1px); + } + + :global(img, svg) { + margin-right: 5px; + margin-left: 0; + height: 26px; + width: 26px; + color: var(--text); + } + + :global(.rtl img, .rtl svg) { + margin-right: 0; + margin-left: 5px; + } +</style> diff --git a/src/pages/popup_src/components/Row.svelte b/src/pages/popup_src/components/Row.svelte new file mode 100644 index 00000000..a4d78f07 --- /dev/null +++ b/src/pages/popup_src/components/Row.svelte @@ -0,0 +1,13 @@ +<div {...$$props} on:click> + <slot></slot> + </div> + + <style> + div { + justify-content: space-between; + display: flex; + align-items: center; + margin: 10px 0; + } + </style> + \ No newline at end of file diff --git a/src/pages/popup_src/components/ServiceIcon.svelte b/src/pages/popup_src/components/ServiceIcon.svelte new file mode 100644 index 00000000..89393cf6 --- /dev/null +++ b/src/pages/popup_src/components/ServiceIcon.svelte @@ -0,0 +1,40 @@ +<script> + import { onDestroy } from "svelte" + export let details + import { config, options } from "../stores" + let _options + let _config + + const unsubscribeOptions = options.subscribe(val => (_options = val)) + const unsubscribeConfig = config.subscribe(val => (_config = val)) + onDestroy(() => { + unsubscribeOptions() + unsubscribeConfig() + }) + + let theme + $: if (_options) { + if (_options.theme == "dark") { + theme = "dark" + } else if (_options.theme == "light") { + theme = "light" + } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + theme = "dark" + } else { + theme = "light" + } + } + $: imageType = _config.services[details.value].imageType +</script> + +{#if imageType} + {#if imageType == "svgMono"} + {#if theme == "dark"} + <img src={`/assets/images/${details.value}-icon-light.svg`} alt={details.label} /> + {:else} + <img src={`/assets/images/${details.value}-icon.svg`} alt={details.label} /> + {/if} + {:else} + <img src={`/assets/images/${details.value}-icon.${imageType}`} alt={details.label} /> + {/if} +{/if} diff --git a/src/pages/popup_src/components/Switch.svelte b/src/pages/popup_src/components/Switch.svelte new file mode 100644 index 00000000..98f765b7 --- /dev/null +++ b/src/pages/popup_src/components/Switch.svelte @@ -0,0 +1,81 @@ +<script> + const browser = window.browser || window.chrome + + import Checkbox from "../../components/Checkbox.svelte" + import Label from "../../components/Label.svelte" + import SwitchInstanceIcon from "../../icons/SwitchInstanceIcon.svelte" + import Row from "./Row.svelte" + import ServiceIcon from "./ServiceIcon.svelte" + import { onDestroy } from "svelte" + import servicesHelper from "../../../assets/javascripts/services" + import { options, config } from "../stores" + import SettingsIcon from "../../icons/SettingsIcon.svelte" + + let _options + let _config + + const unsubscribeOptions = options.subscribe(val => (_options = val)) + const unsubscribeConfig = config.subscribe(val => (_config = val)) + onDestroy(() => { + unsubscribeOptions() + unsubscribeConfig() + }) + + export let serviceKey + export let url +</script> + +<Row> + <div + class="interactive margin margin_{document.body.dir}" + on:keydown={null} + on:click={() => + browser.tabs.create({ url: _config.services[serviceKey].url }, () => { + window.close() + })} + > + <ServiceIcon details={{ value: serviceKey, label: _config.services[serviceKey].name }} /> + <Label>{_config.services[serviceKey].name}</Label> + </div> + <div> + <Checkbox + class="margin margin_{document.body.dir}" + label="Enable" + checked={_options[serviceKey].enabled} + onChange={e => { + _options[serviceKey].enabled = e.target.checked + options.set(_options) + }} + /> + <SwitchInstanceIcon + class="interactive" + on:click={async () => + browser.tabs.update({ url: await servicesHelper.switchInstance(url, serviceKey) }, () => { + window.close() + })} + /> + <SettingsIcon + class="interactive" + on:click={() => + browser.tabs.create({ url: browser.runtime.getURL(`pages/options/index.html#services:${serviceKey}`) }, () => { + window.close() + })} + /> + </div> +</Row> + +<style> + div { + display: flex; + align-items: center; + } + + :global(.margin) { + margin-right: 5px; + margin-left: 0; + } + :global(.margin_rtl) { + margin-right: 0; + margin-left: 5px; + } +</style> diff --git a/src/pages/popup_src/main.js b/src/pages/popup_src/main.js new file mode 100644 index 00000000..c4012f4a --- /dev/null +++ b/src/pages/popup_src/main.js @@ -0,0 +1,7 @@ +import App from "./App.svelte" + +const app = new App({ + target: document.body, +}) + +export default app diff --git a/src/pages/popup_src/stores.js b/src/pages/popup_src/stores.js new file mode 100644 index 00000000..782f6064 --- /dev/null +++ b/src/pages/popup_src/stores.js @@ -0,0 +1,5 @@ +import { writable } from "svelte/store" + +export const options = writable(null) +export const config = writable(null) +export const page = writable("general") diff --git a/src/pages/stylesheets/styles.css b/src/pages/stylesheets/styles.css deleted file mode 100644 index 2519a05f..00000000 --- a/src/pages/stylesheets/styles.css +++ /dev/null @@ -1,391 +0,0 @@ -body { - --text: #fff; - --bg-main: #121212; - --bg-secondary: #202020; - --active: #fbc117; - --danger: #f04141; - --light-grey: #c3c3c3; -} - -@font-face { - font-family: "Inter"; - src: url("Inter-VariableFont_slnt,wght.ttf"); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: "Vazirmatn"; - src: url("Vazirmatn-VariableFont_wght.ttf"); - font-weight: normal; - font-style: normal; -} - -body { - margin: auto; - padding: 0; - font-family: "Inter"; - font-size: 16px; - background-color: var(--bg-main); - color: var(--text); -} - -body * { - font-family: "Inter"; -} - -body.rtl { - font-family: "Vazirmatn"; -} - -body.rtl * { - font-family: "Vazirmatn"; -} - -div.block input[type="checkbox"] { - appearance: none; - -moz-appearance: none; - -webkit-appearance: none; -} - -.title { - display: flex; - align-items: center; - text-decoration: none; - width: min-content; - color: var(--text); - transition: .1s; -} - -.title:hover { - opacity: 1 !important; -} - -.title:hover a { - color: var(--active); -} - -img, -svg { - margin-right: 10px; - height: 26px; - width: 26px; - color: var(--text); -} - -body.rtl img, -body.rtl svg { - margin-right: 0px; - margin-left: 10px; -} - -input[type="url"], -input[type="text"], -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; -} - -input[type="url"], -input[type="text"] { - width: 400px; - cursor: text; -} - -input:invalid { - color: var(--danger); -} - -.button svg { - height: 18px; - width: 18px; -} - -section.block-option { - width: 750px; - margin: 0 50px; -} - -section.block-option h2 { - margin: 0; -} - -body.option { - display: flex; - padding: 40px; - width: 1160px; -} - -section.links { - display: flex; - flex-wrap: wrap; - flex-direction: column; - width: 350px; - max-height: 930px; -} - -section.links div { - margin: 10px; - width: max-content; -} - -a { - text-decoration: none; - color: var(--text); - transition: 0.1s; -} - -a:hover { - color: var(--active); -} - -section.links a { - display: flex; - align-items: center; - font-size: 18px; - text-decoration: none; - color: white; - transition: 0.1s; -} - -section.links a:hover, -section.links .selected { - opacity: 1 !important; -} - -section.links .selected a { - color: var(--active); -} - -::placeholder { - color: var(--text); - opacity: 0.7; -} - -hr { - height: 2px; - margin: 0 15px; - background-color: rgb(77, 77, 77); - border: none; -} - -div.block { - padding: 0 15px; - justify-content: space-between; - display: flex; - align-items: center; - margin-top: 10px; - margin-bottom: 10px; -} - -div.block-option { - margin: 30px 0; -} - -div.block-option label { - margin-right: 5px; - width: 80%; - min-width: 150px; - font-size: 18px; -} - -div.block-option h1 { - margin: 0; - font-size: 28px; - color: var(--text); -} - -div.block-option div { - text-align: center; -} - -div.block input[type="checkbox"] { - width: 46px; - height: 24px; - background-color: var(--light-grey); - border-radius: 50px; - transition: 0.4s; - cursor: pointer; -} - -div.block input[type="checkbox"]:checked { - background-color: var(--active); -} - -div.block input[type="checkbox"]::before { - content: ""; - display: inline-block; - width: 18px; - height: 18px; - box-sizing: border-box; - position: relative; - top: 2.5px; - left: 3.5px; - background-color: white; - border-radius: 50%; - transition: 0.3s; -} - -body.rtl div.block input[type="checkbox"]::before { - left: auto; - right: 4px; -} - -div.block input[type="checkbox"]:checked::before { - left: 24px; -} - -body.rtl div.block input[type="checkbox"]:checked::before { - left: auto; - right: 24px; -} - -div.buttons { - display: flex; - margin: 0 15px; - margin-bottom: 15px; - margin-top: 15px; - flex-wrap: wrap; - align-items: center; - justify-content: start; -} - -.button { - color: var(--text); - font-size: 16px; - font-weight: bold; - text-decoration: none; - cursor: pointer; - transition-duration: 0.1s; -} - -.button:hover { - color: var(--active); -} - -.button svg { - width: auto; - height: auto; - padding: 0; - margin-right: 5px; -} - -.button:hover svg { - color: var(--active); -} - -.button-inline { - display: inline-flex; - align-items: center; - margin: 7.5px 0; - background-color: var(--bg-secondary); - border-radius: 5px; - padding: 10px; -} - -.button:active { - transform: translateY(1px); -} - -button svg { - color: var(--text); -} - -div.checklist div { - justify-content: space-between; - margin: 5px 15px; - padding: 10px 0; - word-wrap: break-word; - display: flex; -} - -div.checklist a { - text-decoration: none; - color: var(--text); -} - -div.checklist a:hover { - text-decoration: underline; -} - -div.custom-checklist x a { - color: var(--active); -} - -button.add { - background-color: transparent; - border: none; - padding: 0; - margin: 0; - text-decoration: none; - display: inline-block; - cursor: pointer; -} - -body.light-theme { - --text: black; - --bg-main: white; - --bg-secondary: #e4e4e4; - --active: #fb9817; -} - -body.light-theme select { - border: 1px solid black; -} - -body.light-theme a { - color: black; -} - -body.light-theme a:hover { - color: var(--active) -} - -button { - background-color: transparent; - color: var(--text); - border: none; - text-decoration: none; - display: inline-block; - cursor: pointer; - border-radius: 5px; -} - -body div section { - display: none; -} - -select:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -input:disabled { - opacity: 0.6; - cursor: not-allowed; -} - - -@media (max-width: 1250px) { - body.option { - flex-direction: column; - width: 750px; - align-items: center; - } - - section.links { - flex-direction: row; - width: 750px; - padding: 0 55px; - } -} \ No newline at end of file diff --git a/src/pages/widgets/head.pug b/src/pages/widgets/head.pug deleted file mode 100644 index 0c6e9fdb..00000000 --- a/src/pages/widgets/head.pug +++ /dev/null @@ -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") \ No newline at end of file diff --git a/src/pages/widgets/links.pug b/src/pages/widgets/links.pug deleted file mode 100644 index 318c72f9..00000000 --- a/src/pages/widgets/links.pug +++ /dev/null @@ -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 |