"use strict"; // License: MIT import { ALL_LANGS, _, getCurrentLanguage, localize, saveCustomLocale, } from "../lib/i18n"; import { Prefs, PrefWatcher } from "../lib/prefs"; import { hostToDomain } from "../lib/util"; import { filters } from "../lib/filters"; import {Limits} from "../lib/manager/limits"; // eslint-disable-next-line no-unused-vars import ModalDialog, { ModalButton } from "../uikit/lib/modal"; import { TYPE_LINK, TYPE_MEDIA } from "../lib/constants"; import { iconForPath, visible } from "../lib/windowutils"; import { VirtualTable } from "../uikit/lib/table"; import { Icons } from "./icons"; import { $ } from "./winutil"; import { runtime, storage } from "../lib/browser"; const ICON_BASE_SIZE = 16; class UIPref extends PrefWatcher { id: string; pref: string; elem: T; constructor(id: string, pref: string) { super(pref); this.id = id; this.pref = pref; this.elem = $(`#${id}`); if (!this.elem) { throw new Error(`Invalid id: ${id}`); } } async save(value: any) { await Prefs.set(this.pref, value); } } class BoolPref extends UIPref { constructor(id: string, pref: string) { super(id, pref); this.elem.addEventListener("change", this.change.bind(this)); } change() { this.save(!!this.elem.checked); } changed(prefs: any, key: string, value: any) { this.elem.checked = !!value; return super.changed(prefs, key, value); } } class IntPref extends UIPref { constructor(id: string, pref: string) { super(id, pref); this.elem.addEventListener("change", this.change.bind(this)); } change() { if (!this.elem.checkValidity()) { return; } this.save(this.elem.value); } changed(prefs: any, key: string, value: any) { this.elem.value = value; return super.changed(prefs, key, value); } } class OptionPref extends UIPref { options: HTMLInputElement[]; constructor(id: string, pref: string) { super(id, pref); this.options = Array.from(this.elem.querySelectorAll(`*[name='${id}']`)); this.options.forEach(o => { o.addEventListener("change", () => this.change()); }); } change() { const opt = this.options.find(e => e.checked); if (opt && opt.value) { this.save(opt.value); } } changed(prefs: any, key: string, value: any) { const opt = this.options.find(e => e.value === value); if (opt) { opt.checked = true; } return super.changed(prefs, key, value); } } class CreateFilterDialog extends ModalDialog { label: HTMLInputElement; expr: HTMLInputElement; link: HTMLInputElement; media: HTMLInputElement; getContent() { const rv = $("#create-filter-template"). content.cloneNode(true) as DocumentFragment; this.label = $("#filter-create-label", rv); this.expr = $("#filter-create-expr", rv); this.link = $("#filter-create-type-link", rv); this.media = $("#filter-create-type-media", rv); return rv; } get buttons() { return [ { title: _("create-filter"), value: "ok", default: true }, { title: _("cancel"), value: "cancel", dismiss: true } ]; } shown() { this.label.focus(); } done(b: ModalButton) { if (!b || !b.default) { return super.done(b); } const label = this.label.value.trim(); const expr = this.expr.value.trim(); let type = 0; if (this.link.checked) { type |= TYPE_LINK; } if (this.media.checked) { type |= TYPE_MEDIA; } let valid = true; if (!label) { valid = false; this.label.setCustomValidity(_("cannot-be-empty")); } else { this.label.setCustomValidity(""); } if (!expr) { valid = false; this.expr.setCustomValidity(_("cannot-be-empty")); } else { this.expr.setCustomValidity(""); } if (!type) { valid = false; this.link.setCustomValidity(_("filter-at-least-one")); this.media.setCustomValidity(_("filter-at-least-one")); } else { this.link.setCustomValidity(""); this.media.setCustomValidity(""); } if (!valid) { return undefined; } filters().then(async filters => { await filters.create(label, expr, type); }).catch(console.error); return super.done(b); } async show() { await super.show(); } } class FiltersUI extends VirtualTable { filters: any[]; icons: Icons; edit: { label: HTMLInputElement; expr: HTMLInputElement; link: HTMLInputElement; media: HTMLInputElement; filter: any; row: number; }; ignoreNext: boolean; constructor() { super("#filters", null); this.filters = []; this.icons = new Icons($("#icons")); const filter: any = null; this.edit = { label: $("#filter-edit-label"), expr: $("#filter-edit-expr"), link: $("#filter-edit-type-link"), media: $("#filter-edit-type-media"), filter, row: filter, }; this.edit.label.addEventListener("input", () => { if (!this.edit.filter) { return; } if (!this.edit.label.checkValidity() || this.edit.label.value.length <= 0) { return; } this.edit.filter.label = this.edit.label.value; this.ignoreNext = true; this.saveFilter(this.edit.filter, this.edit.row); }, true); this.edit.expr.addEventListener("input", () => { if (!this.edit.filter) { return; } if (!this.edit.expr.checkValidity() || this.edit.expr.value.length <= 0) { return; } this.edit.filter.expr = this.edit.expr.value; this.ignoreNext = true; this.saveFilter(this.edit.filter, this.edit.row); }, true); const updateTypes = () => { if (!this.edit.filter) { return; } const link = this.edit.link.checked ? TYPE_LINK : 0; const media = this.edit.media.checked ? TYPE_MEDIA : 0; const type = link | media; if (!type) { return; } this.edit.filter.type = type; this.ignoreNext = true; this.saveFilter(this.edit.filter, this.edit.row); }; this.edit.link.addEventListener("change", updateTypes); this.edit.media.addEventListener("change", updateTypes); this.on("selection-changed", () => { this.edit.filter = null; if (this.selection.empty) { this.resetEdits(); return; } this.edit.row = this.selection.first; const f = this.edit.filter = this.filters[this.edit.row]; if (!this.edit.filter) { this.resetEdits(); return; } $("#filter-edit").classList.remove("hidden"); this.edit.label.value = f.label; this.edit.expr.value = f.expr; this.edit.link.checked = !!(f.type & TYPE_LINK); this.edit.media.checked = !!(f.type & TYPE_MEDIA); if (this.edit.filter.custom) { $("#filter-delete").classList.remove("hidden"); $("#filter-reset").classList.add("hidden"); } else { $("#filter-delete").classList.add("hidden"); $("#filter-reset").classList.remove("hidden"); } }); $("#filter-delete").addEventListener("click", () => { if (!this.edit.filter) { return; } this.edit.filter.delete(). then(this.reload.bind(this)). catch(console.error); }); $("#filter-reset").addEventListener("click", () => { if (!this.edit.filter) { return; } this.edit.filter.reset(). then(this.reload.bind(this)). catch(console.error); }); this.reload().catch(console.error); $("#filter-create-button").addEventListener("click", () => { new CreateFilterDialog().show().catch(console.error); }); filters().then(filters => { filters.on("changed", () => { this.reload().catch(console.error); }); }); } async reload() { if (this.ignoreNext) { return; } this.ignoreNext = false; this.resetEdits(); this.filters = (await filters()).all; this.init(); this.invalidate(); } resetEdits() { this.edit.label.value = ""; this.edit.expr.value = ""; this.edit.link.checked = false; this.edit.media.checked = false; $("#filter-delete").classList.add("hidden"); $("#filter-reset").classList.add("hidden"); $("#filter-edit").classList.add("hidden"); } async saveFilter(filter: any, row: any) { try { this.invalidateRow(row); await filter.save(); } catch (ex) { console.error(ex); } } get rowCount() { return this.filters.length; } getCellIcon(rowid: number, colid: number) { if (!colid) { const f = this.filters[rowid]; if (!f) { return null; } const icon = iconForPath(`file${f.icon ? `.${f.icon}` : ""}`, ICON_BASE_SIZE); return this.icons.get(icon); } return null; } getCellText(rowid: number, colid: number) { const f = this.filters[rowid]; if (!f) { return null; } switch (colid) { case 0: return f.label; case 1: return f.expr; case 2: return [TYPE_LINK, TYPE_MEDIA]. map(t => f.type & t ? _(`filter-type-${t === TYPE_LINK ? "link" : "media"}`) : 0). filter(e => e). join(", "); default: return ""; } } } class LimitsUI extends VirtualTable { limits: any[]; edit: { limit: any; domain: HTMLInputElement; conlimited: HTMLInputElement; conunlimited: HTMLInputElement; conlimit: HTMLInputElement; save: HTMLButtonElement; delete: HTMLButtonElement; row: number; }; constructor() { super("#limits", null); this.limits = []; Limits.on("changed", () => { this.limits = Array.from(Limits); this.invalidate(); this.resetEdits(); }); Limits.load().then(() => { this.limits = Array.from(Limits); this.invalidate(); }); this.edit = { limit: null, domain: $("#limit-edit-domain"), conlimited: $("#limit-edit-concurrent-limited"), conunlimited: $("#limit-edit-concurrent-unlimited"), conlimit: $("#limit-edit-concurrent-limit"), save: $("#limit-save"), delete: $("#limit-delete"), row: -1, }; this.on("selection-changed", () => { this.edit.limit = null; if (this.selection.empty) { this.resetEdits(); return; } this.edit.row = this.selection.first; const l = this.edit.limit = this.limits[this.edit.row]; if (!l) { this.resetEdits(); return; } $("#limit-edit").classList.remove("hidden"); this.edit.domain.value = l.domain; this.edit.domain.setAttribute("readonly", "readonly"); if (l.concurrent <= 0) { this.edit.conunlimited.checked = true; this.edit.conlimit.value = "3"; } else { this.edit.conlimited.checked = true; this.edit.conlimit.value = l.concurrent; } if (l.domain === "*") { this.edit.delete.classList.add("hidden"); } else { this.edit.delete.classList.remove("hidden"); } }); $("#limit-create").addEventListener("click", () => { this.selection.clear(); this.resetEdits(); this.edit.delete.classList.add("hidden"); $("#limit-edit").classList.remove("hidden"); this.edit.domain.focus(); }); this.edit.save.addEventListener("click", () => { let domain; try { if (this.edit.domain.value !== "*") { domain = hostToDomain(this.edit.domain.value); } else { domain = "*"; } if (!domain) { this.edit.domain.setCustomValidity( _("invalid-domain-pref")); return; } } catch (ex) { console.error(ex.message, ex.stack, ex); this.edit.domain.setCustomValidity( _("invalid-domain-pref")); this.edit.domain.setCustomValidity(ex.message || ex.toString()); return; } if (this.edit.conlimited.checked && !this.edit.conlimit.checkValidity()) { return; } const concurrent = this.edit.conunlimited.checked ? -1 : parseInt(this.edit.conlimit.value, 10); Limits.saveEntry(domain, { domain, concurrent }); }); this.edit.delete.addEventListener("click", () => { if (!this.edit.limit) { return; } Limits.delete(this.edit.limit.domain); }); } resetEdits() { this.edit.limit = null; this.edit.domain.removeAttribute("readonly"); this.edit.domain.value = ""; this.edit.domain.setCustomValidity(""); this.edit.conunlimited.checked = true; this.edit.conlimit.value = "3"; this.edit.delete.classList.add("hidden"); $("#limit-edit").classList.add("hidden"); } get rowCount() { return this.limits.length; } getCellText(rowid: number, colid: number) { const f = this.limits[rowid]; if (!f) { return null; } switch (colid) { case 0: return f.domain; case 1: return f.concurrent <= 0 ? _("unlimited") : f.concurrent; default: return ""; } } } addEventListener("DOMContentLoaded", async () => { await localize(document.documentElement); // General new BoolPref("pref-global-turbo", "global-turbo"); new BoolPref("pref-queue-notification", "queue-notification"); new BoolPref("pref-finish-notification", "finish-notification"); new BoolPref("pref-hide-context", "hide-context"); new BoolPref("pref-tooltip", "tooltip"); new BoolPref("pref-open-manager-on-queue", "open-manager-on-queue"); new BoolPref("pref-text-links", "text-links"); new BoolPref("pref-add-paused", "add-paused"); new BoolPref("pref-show-urls", "show-urls"); new BoolPref("pref-remove-missing-on-init", "remove-missing-on-init"); new OptionPref("pref-conflict-action", "conflict-action"); $("#reset-confirmations").addEventListener("click", async () => { for (const k of Prefs) { if (!k.startsWith("confirmations.")) { continue; } await Prefs.reset(k); } await ModalDialog.inform( _("information.title"), _("reset-confirmations.done"), _("ok")); }); $("#reset-layout").addEventListener("click", async () => { for (const k of Prefs) { if (!k.startsWith("tree-config-")) { continue; } await Prefs.reset(k); } for (const k of Prefs) { if (!k.startsWith("window-state-")) { continue; } await Prefs.reset(k); } await ModalDialog.inform( _("information.title"), _("reset-layouts.done"), _("ok")); }); const langs = $("#languages"); const currentLang = getCurrentLanguage(); for (const [code, lang] of ALL_LANGS.entries()) { const langEl = document.createElement("option"); langEl.textContent = lang; langEl.value = code; if (code === currentLang) { langEl.selected = true; } langs.appendChild(langEl); } langs.addEventListener("change", async () => { await storage.sync.set({language: langs.value}); if (langs.value === currentLang) { return; } // eslint-disable-next-line max-len if (confirm("Changing the selected translation requires restarting the extension.\nDo you want to restart the extension now?")) { runtime.reload(); } }); // Filters visible("#filters").then(() => new FiltersUI()); // Network new IntPref("pref-concurrent-downloads", "concurrent"); visible("#limits").then(() => new LimitsUI()); const customLocale = $("#customLocale"); $("#loadCustomLocale").addEventListener("click", () => { customLocale.click(); }); $("#clearCustomLocale").addEventListener("click", () => { saveCustomLocale(undefined); runtime.reload(); }); customLocale.addEventListener("change", async () => { if (!customLocale.files || !customLocale.files.length) { return; } const [file] = customLocale.files; if (!file || file.size > (5 << 20)) { return; } try { const text = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { resolve(reader.result as string); }; reader.onerror = reject; reader.readAsText(file); }); saveCustomLocale(text); if (confirm("Imported your file.\nWant to relaod the extension now?")) { runtime.reload(); } } catch (ex) { console.error(ex); alert(`Could not load your translation file:\n${ex.toString()}`); } }); });