Theseus 0.3.61: Privacy tidied — country dropdown, VPN row honest, language names in the picker
Privacy › Location is three modes now: Show real, Hide, Manual. Manual reveals a 50-country dropdown whose pick becomes the coordinates navigator.geolocation returns to pages — country-capital granularity, no regions or free-form cities. Old profiles on the retired "Spoof (region)" auto-migrate to Manual + the region's representative country on first open, so nothing breaks. VPN row in Privacy stops opening the wrong add-on: the sidebar now no-ops on a specific panelId that isn't registered (used to silently substitute panels[0], which surfaced Aegis whenever the VPN add-on was disabled), and the row hides itself when vpn:main isn't in the sidebar panel list. Language picker (globe chip menu + Settings › General › Website language) drops the BCP-47 tag from every visible label — the tag surfaces only as the 2-letter chip in the URL bar once picked. "English" is the UK original; the US variant row is retired (same 2-letter chip, ~same text). Ukrainian dropped from the quick list too. "Automatic" reads as the OS language name (Intl.DisplayNames) instead of a raw en-US style tag.
This commit is contained in:
parent
df2b1f57ff
commit
a3fb8917c3
4 changed files with 177 additions and 128 deletions
|
|
@ -723,20 +723,24 @@
|
|||
const langBtn = $("langBtn"), langCode = $("langCode");
|
||||
let systemLocaleCached = "en-US";
|
||||
function short(tag) { return String(tag || "").split("-")[0].toUpperCase().slice(0, 2) || "??"; }
|
||||
function langName(tag) {
|
||||
try { return new Intl.DisplayNames(["en"], { type: "language" }).of(tag) || tag; }
|
||||
catch { return tag; }
|
||||
}
|
||||
function renderLang(s) {
|
||||
if (!s) return;
|
||||
const auto = (s.languageMode || "show") === "show";
|
||||
if (auto) {
|
||||
langBtn.classList.remove("on");
|
||||
langCode.textContent = "AUTO";
|
||||
langBtn.title = `Website language: automatic (OS: ${systemLocaleCached}). Click to change.`;
|
||||
langBtn.title = `Website language: automatic — ${langName(systemLocaleCached)}. Click to change.`;
|
||||
} else {
|
||||
const tag = (s.languageMode === "manual" && s.languageValue) ? s.languageValue
|
||||
: (s.languageMode === "spoof" && s.languageSpoof) ? s.languageSpoof
|
||||
: (s.languageMode === "hide") ? "en-US" : systemLocaleCached;
|
||||
langBtn.classList.add("on");
|
||||
langCode.textContent = short(tag);
|
||||
langBtn.title = `Websites see you in ${tag}. Click to change.`;
|
||||
langBtn.title = `Websites see you in ${langName(tag)}. Click to change.`;
|
||||
}
|
||||
}
|
||||
langBtn.onclick = () => {
|
||||
|
|
|
|||
34
main.js
34
main.js
|
|
@ -463,8 +463,8 @@ const SETTINGS_DEFAULTS = {
|
|||
clearStorageOnQuit: true, // drop localStorage / IndexedDB / service workers / cache API
|
||||
// Anti-fingerprinting — each: show (real) | hide (neutral) | spoof (auto decoy) | manual (user value)
|
||||
timezoneMode: "show", timezoneValue: "Europe/Berlin", // IANA zone for manual
|
||||
languageMode: "show", languageSpoof: "en-US", languageValue: "en-US", // spoof = top-10 pick, manual = free text
|
||||
locationMode: "hide", locationRegion: "europe", locationLat: "40.7128", locationLon: "-74.0060", // spoof by region, or manual coords
|
||||
languageMode: "show", languageSpoof: "en-US", languageValue: "en-GB", // spoof = top-10 pick, manual = free text (English = UK original; US variant retired from the picker)
|
||||
locationMode: "hide", locationRegion: "europe", locationCountry: "DE", locationLat: "40.7128", locationLon: "-74.0060", // manual = pick a country (locationCountry drives lat/lon); legacy spoof/region still handled by effLocation() for old profiles
|
||||
searchEngine: "startpage",// default search engine (built-in id or a custom id)
|
||||
installedEngines: DEFAULT_ENABLED.slice(), // built-in engines added to the user's list (visible in Settings)
|
||||
enabledEngines: DEFAULT_ENABLED.slice(), // subset that's currently toggled on (shown in the toolbar dropdown)
|
||||
|
|
@ -2853,7 +2853,15 @@ function setSidebar(show, panelId) {
|
|||
}
|
||||
if (show) {
|
||||
const wantId = panelId || sidebarActivePanelId || panels[0].panelId;
|
||||
const panel = panels.find((p) => p.panelId === wantId) || panels[0];
|
||||
// A CALLER-supplied panelId that isn't registered used to silently fall
|
||||
// back to panels[0], which surfaced the wrong add-on (Settings › Privacy
|
||||
// › VPN opening Aegis whenever the VPN add-on was disabled). Fall back
|
||||
// only when the id came from restore state; a specific request is a no-op.
|
||||
let panel = panels.find((p) => p.panelId === wantId);
|
||||
if (!panel) {
|
||||
if (panelId) { console.warn(`setSidebar: no panel "${panelId}"; ignoring`); return; }
|
||||
panel = panels[0];
|
||||
}
|
||||
if (sidebarActivePanelId !== panel.panelId) {
|
||||
sidebarActivePanelId = panel.panelId;
|
||||
try { sidebar.webContents.loadFile(panel.pageFile); } catch (e) { console.warn("sidebar loadFile failed:", e?.message); }
|
||||
|
|
@ -4050,9 +4058,12 @@ ipcMain.handle("tab-context-menu-popup", (e, tabId, rect) => {
|
|||
// (languageMode="show", follow OS) or a specific BCP-47 tag
|
||||
// (languageMode="manual", languageValue=<tag>). One truth source, two entry
|
||||
// points. Applied via applyAcceptLanguage() + applyFingerprintAll() below.
|
||||
// Labels are the language's own name in its own script — no BCP-47 tag in
|
||||
// the label. The tag only surfaces as the 2-letter code on the URL-bar chip
|
||||
// once picked. English is the UK original (en-GB); the US variant is 90%+
|
||||
// the same text and shared the "EN" chip code, so it isn't a separate row.
|
||||
const WEBSITE_LANGUAGE_QUICK = [
|
||||
{ tag: "en-US", label: "English (US)" },
|
||||
{ tag: "en-GB", label: "English (UK)" },
|
||||
{ tag: "en-GB", label: "English" },
|
||||
{ tag: "es-ES", label: "Español" },
|
||||
{ tag: "pt-BR", label: "Português (Brasil)" },
|
||||
{ tag: "fr-FR", label: "Français" },
|
||||
|
|
@ -4060,7 +4071,6 @@ const WEBSITE_LANGUAGE_QUICK = [
|
|||
{ tag: "it-IT", label: "Italiano" },
|
||||
{ tag: "nl-NL", label: "Nederlands" },
|
||||
{ tag: "ru-RU", label: "Русский" },
|
||||
{ tag: "uk-UA", label: "Українська" },
|
||||
{ tag: "pl-PL", label: "Polski" },
|
||||
{ tag: "tr-TR", label: "Türkçe" },
|
||||
{ tag: "ar", label: "العربية" },
|
||||
|
|
@ -4087,6 +4097,12 @@ function setWebsiteLanguage(mode, value) {
|
|||
applyAcceptLanguage();
|
||||
try { chrome?.webContents.send("settings-update", settings); } catch {}
|
||||
}
|
||||
// Human-readable name for a BCP-47 tag ("de" → "German") via V8 Intl.
|
||||
// Falls back to the tag itself if the locale isn't recognised.
|
||||
function languageNameFor(tag) {
|
||||
try { return new Intl.DisplayNames(["en"], { type: "language" }).of(tag) || tag; }
|
||||
catch { return tag; }
|
||||
}
|
||||
ipcMain.handle("system-locale", () => app.getLocale() || "en-US");
|
||||
ipcMain.handle("website-language-menu-popup", (e, rect) => {
|
||||
if (chrome && e.sender !== chrome.webContents) throw new Error("website-language-menu-popup: untrusted sender");
|
||||
|
|
@ -4094,11 +4110,11 @@ ipcMain.handle("website-language-menu-popup", (e, rect) => {
|
|||
const isAuto = settings.languageMode === "show";
|
||||
const current = isAuto ? null : (settings.languageValue || "").toLowerCase();
|
||||
const template = [
|
||||
{ label: `Automatic (OS: ${osLoc})${isAuto ? " ✓" : ""}`,
|
||||
{ label: `Automatic (${languageNameFor(osLoc)})${isAuto ? " ✓" : ""}`,
|
||||
click: () => setWebsiteLanguage("show") },
|
||||
{ type: "separator" },
|
||||
...WEBSITE_LANGUAGE_QUICK.map((L) => ({
|
||||
label: `${L.label} — ${L.tag}${current === L.tag.toLowerCase() ? " ✓" : ""}`,
|
||||
label: `${L.label}${current === L.tag.toLowerCase() ? " ✓" : ""}`,
|
||||
click: () => setWebsiteLanguage("manual", L.tag),
|
||||
})),
|
||||
{ type: "separator" },
|
||||
|
|
@ -6099,7 +6115,7 @@ ipcMain.handle("settings-set", (_e, key, val) => {
|
|||
if (key === "theme") applyTheme();
|
||||
if (key === "backgroundThrottle") applyThrottle();
|
||||
if (["timezoneMode", "timezoneValue", "languageMode", "languageSpoof", "languageValue",
|
||||
"locationMode", "locationRegion", "locationLat", "locationLon", "hideMediaDevices"].includes(key)) { applyFingerprintAll(); applyAcceptLanguage(); }
|
||||
"locationMode", "locationRegion", "locationCountry", "locationLat", "locationLon", "hideMediaDevices"].includes(key)) { applyFingerprintAll(); applyAcceptLanguage(); }
|
||||
// Push updates chrome-side reactively so toolbar-size changes (urlBarSize,
|
||||
// searchBoxSize) take effect without a relaunch. Whole settings object
|
||||
// fits in one message and chrome only cares about a handful of fields.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "theseus-navigator",
|
||||
"version": "0.3.60",
|
||||
"version": "0.3.61",
|
||||
"license": "MPL-2.0",
|
||||
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
||||
"author": "Silent Mode",
|
||||
|
|
|
|||
261
settings.html
261
settings.html
|
|
@ -684,57 +684,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="txt"><div class="t">Location</div><div class="d"><b>Hide</b> denies geolocation; <b>Spoof</b> reports a chosen region; <b>Manual</b> reports exact coordinates.</div></div>
|
||||
<div class="txt"><div class="t">Location</div><div class="d"><b>Hide</b> denies geolocation entirely; <b>Manual</b> reports the coordinates of a country you pick, so a page's <code>navigator.geolocation</code> answers with a plausible position instead of your real one.</div></div>
|
||||
<div class="ctl">
|
||||
<select id="locationMode"><option value="show">Show real</option><option value="hide">Hide (block)</option><option value="spoof">Spoof (region)</option><option value="manual">Manual…</option></select>
|
||||
<select id="locationRegion" hidden>
|
||||
<option value="europe">Europe</option>
|
||||
<option value="asia">Asia</option>
|
||||
<option value="north_america">North America</option>
|
||||
<option value="south_america">South America</option>
|
||||
<option value="africa">Africa</option>
|
||||
<option value="middle_east">Middle East</option>
|
||||
<option value="australia">Australia</option>
|
||||
<select id="locationMode"><option value="show">Show real</option><option value="hide">Hide (block)</option><option value="manual">Manual…</option></select>
|
||||
<select id="locationCountry" hidden>
|
||||
<!-- Populated in JS from the COUNTRIES table (kept in one place). -->
|
||||
</select>
|
||||
<!-- Manual location = pick a city from a native select; its lat/lon
|
||||
are looked up client-side (see CITIES below) and written to
|
||||
locationLat / locationLon settings, which drive the
|
||||
navigator.geolocation override in main.js. The raw lat/lon
|
||||
inputs used to sit here but nobody types coordinates by hand;
|
||||
the city picker gives the same override with a single click. -->
|
||||
<select id="locationCity" hidden>
|
||||
<option value="">Pick a city…</option>
|
||||
<option value="london">London</option>
|
||||
<option value="berlin">Berlin</option>
|
||||
<option value="paris">Paris</option>
|
||||
<option value="madrid">Madrid</option>
|
||||
<option value="rome">Rome</option>
|
||||
<option value="moscow">Moscow</option>
|
||||
<option value="istanbul">Istanbul</option>
|
||||
<option value="dubai">Dubai</option>
|
||||
<option value="mumbai">Mumbai</option>
|
||||
<option value="singapore">Singapore</option>
|
||||
<option value="bangkok">Bangkok</option>
|
||||
<option value="shanghai">Shanghai</option>
|
||||
<option value="tokyo">Tokyo</option>
|
||||
<option value="seoul">Seoul</option>
|
||||
<option value="sydney">Sydney</option>
|
||||
<option value="new_york">New York</option>
|
||||
<option value="los_angeles">Los Angeles</option>
|
||||
<option value="chicago">Chicago</option>
|
||||
<option value="toronto">Toronto</option>
|
||||
<option value="mexico_city">Mexico City</option>
|
||||
<option value="sao_paulo">São Paulo</option>
|
||||
<option value="buenos_aires">Buenos Aires</option>
|
||||
<option value="cairo">Cairo</option>
|
||||
<option value="nairobi">Nairobi</option>
|
||||
<option value="johannesburg">Johannesburg</option>
|
||||
<option value="__other__">Other…</option>
|
||||
</select>
|
||||
<div id="locationOther" class="coords" hidden>
|
||||
<input id="locationLatOther" type="number" step="0.0001" placeholder="lat" style="width:100px">
|
||||
<input id="locationLonOther" type="number" step="0.0001" placeholder="lon" style="width:100px">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note">These reduce tracking and hide your IP, but a custom browser can still be fingerprinted. For maximum anonymity, use the Tor Browser.</div>
|
||||
|
|
@ -935,7 +890,19 @@
|
|||
}
|
||||
const refreshAll = () => refresh().then(refreshExtra, refreshExtra);
|
||||
el("torOn").addEventListener("change", () => { if (C.toggleTor) C.toggleTor().catch(() => {}); setTimeout(refreshExtra, 1200); setTimeout(refreshExtra, 4000); });
|
||||
el("vpnRow").addEventListener("click", () => C.openPanel && C.openPanel("vpn:main"));
|
||||
// VPN row: only offer it when the VPN add-on is installed AND its panel
|
||||
// registered. Otherwise clicking it used to open whichever add-on was
|
||||
// first in the sidebar list (usually Aegis). Hide it in that case.
|
||||
(async () => {
|
||||
const vpnRow = el("vpnRow");
|
||||
try {
|
||||
const list = await (C.listAddons ? C.listAddons() : Promise.resolve({}));
|
||||
const panels = (list && list.sidebarPanels) || [];
|
||||
const vpn = panels.find((p) => p.panelId === "vpn:main");
|
||||
if (!vpn) { vpnRow.hidden = true; return; }
|
||||
vpnRow.addEventListener("click", () => C.openPanel && C.openPanel("vpn:main"));
|
||||
} catch { vpnRow.hidden = true; }
|
||||
})();
|
||||
document.addEventListener("section", (e) => { if (String(e.detail).startsWith("privacy")) refreshAll(); });
|
||||
refreshAll();
|
||||
})();
|
||||
|
|
@ -973,13 +940,18 @@
|
|||
// same languageMode/languageValue setting the Anti-fingerprinting Language
|
||||
// row edits. "Auto" = languageMode="show" (follow OS); anything else
|
||||
// switches to languageMode="manual" and pins languageValue.
|
||||
// Same list the toolbar globe menu uses (kept in sync with
|
||||
// WEBSITE_LANGUAGE_QUICK in main.js). Language name in its own script;
|
||||
// the BCP-47 tag only surfaces as the 2-letter chip in the URL bar once
|
||||
// picked, never in the picker. English is the UK original (en-GB) —
|
||||
// the US variant is 90%+ the same text and shared "EN" anyway.
|
||||
const WEB_LANG_QUICK = [
|
||||
{ tag: "en-US", label: "English (US)" }, { tag: "en-GB", label: "English (UK)" },
|
||||
{ tag: "en-GB", label: "English" },
|
||||
{ tag: "es-ES", label: "Español (España)" }, { tag: "es-MX", label: "Español (México)" },
|
||||
{ tag: "pt-BR", label: "Português (Brasil)" }, { tag: "pt-PT", label: "Português (Portugal)" },
|
||||
{ tag: "fr-FR", label: "Français" }, { tag: "de-DE", label: "Deutsch" },
|
||||
{ tag: "it-IT", label: "Italiano" }, { tag: "nl-NL", label: "Nederlands" },
|
||||
{ tag: "ru-RU", label: "Русский" }, { tag: "uk-UA", label: "Українська" },
|
||||
{ tag: "ru-RU", label: "Русский" },
|
||||
{ tag: "pl-PL", label: "Polski" }, { tag: "cs-CZ", label: "Čeština" },
|
||||
{ tag: "sv-SE", label: "Svenska" }, { tag: "fi-FI", label: "Suomi" },
|
||||
{ tag: "tr-TR", label: "Türkçe" }, { tag: "el-GR", label: "Ελληνικά" },
|
||||
|
|
@ -989,6 +961,13 @@
|
|||
{ tag: "ko-KR", label: "한국어" }, { tag: "vi-VN", label: "Tiếng Việt" },
|
||||
{ tag: "th-TH", label: "ไทย" }, { tag: "id-ID", label: "Bahasa Indonesia" },
|
||||
];
|
||||
// "English" for a locale tag ("en-US" → "English", "de-DE" → "German") via
|
||||
// browser Intl. Used for the "Automatic (…)" label so it reads as a
|
||||
// language name rather than a raw tag.
|
||||
const langNameFor = (tag) => {
|
||||
try { return new Intl.DisplayNames(["en"], { type: "language" }).of(tag) || tag; }
|
||||
catch { return tag; }
|
||||
};
|
||||
(async () => {
|
||||
const webLangPick = document.getElementById("webLangPick");
|
||||
const webLangOther = document.getElementById("webLangOther");
|
||||
|
|
@ -997,8 +976,10 @@
|
|||
let osLoc = "en-US";
|
||||
try { osLoc = (C.systemLocale && await C.systemLocale()) || "en-US"; } catch {}
|
||||
// Build options: "Automatic" first, then the quick list, then "Other…".
|
||||
const opts = [`<option value="__auto__">Automatic (OS: ${osLoc})</option>`,
|
||||
...WEB_LANG_QUICK.map((L) => `<option value="${L.tag}">${L.label} — ${L.tag}</option>`),
|
||||
// No BCP-47 tag in visible labels — the tag surfaces only as the 2-letter
|
||||
// chip in the URL bar.
|
||||
const opts = [`<option value="__auto__">Automatic (${langNameFor(osLoc)})</option>`,
|
||||
...WEB_LANG_QUICK.map((L) => `<option value="${L.tag}">${L.label}</option>`),
|
||||
`<option value="__other__">Other…</option>`];
|
||||
webLangPick.innerHTML = opts.join("");
|
||||
const paint = (cur) => {
|
||||
|
|
@ -1006,15 +987,15 @@
|
|||
if (mode !== "manual") {
|
||||
webLangPick.value = "__auto__"; webLangOther.hidden = true;
|
||||
hint.innerHTML = mode === "show"
|
||||
? "Following your operating system. Also switchable from the globe icon in the address bar."
|
||||
? `Following your operating system (${langNameFor(osLoc)}). Also switchable from the globe icon in the address bar.`
|
||||
: `Anti-fingerprinting is currently overriding this (mode: <b>${mode}</b>). Change it under <b>Privacy → Anti-fingerprinting</b>.`;
|
||||
return;
|
||||
}
|
||||
const tag = cur.languageValue || "en-US";
|
||||
const tag = cur.languageValue || "en-GB";
|
||||
const known = new Set(WEB_LANG_QUICK.map((L) => L.tag));
|
||||
if (known.has(tag)) { webLangPick.value = tag; webLangOther.hidden = true; }
|
||||
else { webLangPick.value = "__other__"; webLangOther.hidden = false; webLangOther.value = tag; }
|
||||
hint.textContent = `Websites see you in ${tag}. Also switchable from the globe icon in the address bar.`;
|
||||
hint.textContent = `Websites see you in ${langNameFor(tag)}. Also switchable from the globe icon in the address bar.`;
|
||||
};
|
||||
paint(s);
|
||||
webLangPick.addEventListener("change", () => {
|
||||
|
|
@ -1310,68 +1291,116 @@
|
|||
lngMode.value = s.languageMode || "show";
|
||||
applyLng();
|
||||
lngMode.addEventListener("change", () => { C.set("languageMode", lngMode.value); applyLng(); });
|
||||
// location: region dropdown when spoofing, city dropdown when manual.
|
||||
// Manual writes lat/lon into settings via the CITIES map below; the raw
|
||||
// coord inputs were dropped from the UI (nobody types them by hand).
|
||||
const locMode = document.getElementById("locationMode");
|
||||
const locRegion = document.getElementById("locationRegion");
|
||||
const locCity = document.getElementById("locationCity");
|
||||
locRegion.value = s.locationRegion || "europe";
|
||||
locRegion.addEventListener("change", () => C.set("locationRegion", locRegion.value));
|
||||
// Location manual reveals the city select; picking "Other…" reveals a
|
||||
// lat/lon pair, whose values are pushed to locationLat/Lon settings.
|
||||
const locOther = document.getElementById("locationOther");
|
||||
const locLatOther = document.getElementById("locationLatOther");
|
||||
const locLonOther = document.getElementById("locationLonOther");
|
||||
const applyLoc = () => {
|
||||
locRegion.hidden = locMode.value !== "spoof";
|
||||
locCity.hidden = locMode.value !== "manual";
|
||||
locOther.hidden = locMode.value !== "manual" || locCity.value !== "__other__";
|
||||
// Location: three modes — Show real / Hide / Manual (pick a country).
|
||||
// Manual writes the country's representative lat/lon into locationLat +
|
||||
// locationLon, which is what navigator.geolocation returns to pages via
|
||||
// the override in main.js. Legacy `spoof` (region) and free-form city
|
||||
// picks are gone from the UI; effLocation() in main still handles a
|
||||
// saved `spoof` mode so old profiles keep working until they change it.
|
||||
// COUNTRIES: ISO-3166 alpha-2 → [ label, lat, lon ]. Coord is the
|
||||
// country's capital (or biggest city) so pages that geocode the position
|
||||
// land in the right country. Alphabetised for the dropdown.
|
||||
const COUNTRIES = {
|
||||
AR: ["Argentina", -34.6037, -58.3816],
|
||||
AT: ["Austria", 48.2082, 16.3738],
|
||||
AU: ["Australia", -33.8688, 151.2093],
|
||||
BE: ["Belgium", 50.8503, 4.3517],
|
||||
BR: ["Brazil", -23.5505, -46.6333],
|
||||
CA: ["Canada", 43.6532, -79.3832],
|
||||
CH: ["Switzerland", 47.3769, 8.5417],
|
||||
CL: ["Chile", -33.4489, -70.6693],
|
||||
CN: ["China", 31.2304, 121.4737],
|
||||
CO: ["Colombia", 4.7110, -74.0721],
|
||||
CZ: ["Czechia", 50.0755, 14.4378],
|
||||
DE: ["Germany", 52.5200, 13.4050],
|
||||
DK: ["Denmark", 55.6761, 12.5683],
|
||||
EG: ["Egypt", 30.0444, 31.2357],
|
||||
ES: ["Spain", 40.4168, -3.7038],
|
||||
FI: ["Finland", 60.1699, 24.9384],
|
||||
FR: ["France", 48.8566, 2.3522],
|
||||
GB: ["United Kingdom", 51.5074, -0.1278],
|
||||
GR: ["Greece", 37.9838, 23.7275],
|
||||
HK: ["Hong Kong", 22.3193, 114.1694],
|
||||
ID: ["Indonesia", -6.2088, 106.8456],
|
||||
IE: ["Ireland", 53.3498, -6.2603],
|
||||
IL: ["Israel", 32.0853, 34.7818],
|
||||
IN: ["India", 19.0760, 72.8777],
|
||||
IT: ["Italy", 41.9028, 12.4964],
|
||||
JP: ["Japan", 35.6762, 139.6503],
|
||||
KE: ["Kenya", -1.2921, 36.8219],
|
||||
KR: ["South Korea", 37.5665, 126.9780],
|
||||
MA: ["Morocco", 33.5731, -7.5898],
|
||||
MX: ["Mexico", 19.4326, -99.1332],
|
||||
MY: ["Malaysia", 3.1390, 101.6869],
|
||||
NG: ["Nigeria", 6.5244, 3.3792],
|
||||
NL: ["Netherlands", 52.3676, 4.9041],
|
||||
NO: ["Norway", 59.9139, 10.7522],
|
||||
NZ: ["New Zealand", -36.8485, 174.7633],
|
||||
PE: ["Peru", -12.0464, -77.0428],
|
||||
PH: ["Philippines", 14.5995, 120.9842],
|
||||
PL: ["Poland", 52.2297, 21.0122],
|
||||
PT: ["Portugal", 38.7223, -9.1393],
|
||||
RU: ["Russia", 55.7558, 37.6173],
|
||||
SA: ["Saudi Arabia", 24.7136, 46.6753],
|
||||
SE: ["Sweden", 59.3293, 18.0686],
|
||||
SG: ["Singapore", 1.3521, 103.8198],
|
||||
TH: ["Thailand", 13.7563, 100.5018],
|
||||
TR: ["Turkey", 41.0082, 28.9784],
|
||||
TW: ["Taiwan", 25.0330, 121.5654],
|
||||
UA: ["Ukraine", 50.4501, 30.5234],
|
||||
US: ["United States", 40.7128, -74.0060],
|
||||
VN: ["Vietnam", 10.8231, 106.6297],
|
||||
ZA: ["South Africa", -26.2041, 28.0473],
|
||||
};
|
||||
locMode.value = s.locationMode || "show";
|
||||
// Legacy region → representative country (Spoof mode is retired but
|
||||
// migrating old profiles keeps them roughly where they were).
|
||||
const REGION_TO_COUNTRY = {
|
||||
europe: "DE", asia: "JP", north_america: "US", south_america: "BR",
|
||||
africa: "KE", middle_east: "AE", australia: "AU",
|
||||
};
|
||||
const locMode = document.getElementById("locationMode");
|
||||
const locCountry = document.getElementById("locationCountry");
|
||||
// Build the country dropdown from the map above.
|
||||
locCountry.innerHTML = Object.entries(COUNTRIES)
|
||||
.sort((a, b) => a[1][0].localeCompare(b[1][0]))
|
||||
.map(([code, [name]]) => `<option value="${code}">${name}</option>`)
|
||||
.join("");
|
||||
// Restore prior selection: prefer an explicit locationCountry; otherwise
|
||||
// recover the country whose coords match the saved lat/lon; else Germany.
|
||||
const initialCountry = (() => {
|
||||
if (s.locationCountry && COUNTRIES[s.locationCountry]) return s.locationCountry;
|
||||
const la = Number(s.locationLat), lo = Number(s.locationLon);
|
||||
for (const [code, [, x, y]] of Object.entries(COUNTRIES)) {
|
||||
if (Math.abs(x - la) < 0.05 && Math.abs(y - lo) < 0.05) return code;
|
||||
}
|
||||
return "DE";
|
||||
})();
|
||||
locCountry.value = initialCountry;
|
||||
// Migrate legacy spoof/region on this render pass so the mode dropdown
|
||||
// has a valid value and the settings file is normalised on next save.
|
||||
const initialMode = (() => {
|
||||
if (s.locationMode === "spoof") {
|
||||
const code = REGION_TO_COUNTRY[s.locationRegion] || "DE";
|
||||
const [, la, lo] = COUNTRIES[code];
|
||||
C.set("locationCountry", code);
|
||||
C.set("locationLat", String(la));
|
||||
C.set("locationLon", String(lo));
|
||||
C.set("locationMode", "manual");
|
||||
locCountry.value = code;
|
||||
return "manual";
|
||||
}
|
||||
return s.locationMode || "show";
|
||||
})();
|
||||
const applyLoc = () => { locCountry.hidden = locMode.value !== "manual"; };
|
||||
locMode.value = initialMode;
|
||||
applyLoc();
|
||||
locMode.addEventListener("change", () => { C.set("locationMode", locMode.value); applyLoc(); });
|
||||
locCity.addEventListener("change", applyLoc);
|
||||
// Prefill Other lat/lon inputs from saved settings.
|
||||
locLatOther.value = s.locationLat ?? "";
|
||||
locLonOther.value = s.locationLon ?? "";
|
||||
const saveOtherCoords = () => {
|
||||
const la = String(locLatOther.value).trim();
|
||||
const lo = String(locLonOther.value).trim();
|
||||
if (la !== "") C.set("locationLat", la);
|
||||
if (lo !== "") C.set("locationLon", lo);
|
||||
C.set("locationCity", "__other__");
|
||||
};
|
||||
locLatOther.addEventListener("change", saveOtherCoords);
|
||||
locLonOther.addEventListener("change", saveOtherCoords);
|
||||
// If the previously-saved city was "__other__", set the select to it so the
|
||||
// Other inputs stay visible on reload.
|
||||
if (s.locationCity === "__other__") locCity.value = "__other__";
|
||||
// Picking a city writes its lat/lon to settings — that's what
|
||||
// navigator.geolocation returns to pages once "manual" mode is active.
|
||||
const CITIES = {
|
||||
london:[51.5074,-0.1278], berlin:[52.52,13.405], paris:[48.8566,2.3522],
|
||||
madrid:[40.4168,-3.7038], rome:[41.9028,12.4964], moscow:[55.7558,37.6173],
|
||||
istanbul:[41.0082,28.9784], dubai:[25.2048,55.2708], mumbai:[19.076,72.8777],
|
||||
singapore:[1.3521,103.8198], bangkok:[13.7563,100.5018], shanghai:[31.2304,121.4737],
|
||||
tokyo:[35.6762,139.6503], seoul:[37.5665,126.978], sydney:[-33.8688,151.2093],
|
||||
new_york:[40.7128,-74.006], los_angeles:[34.0522,-118.2437], chicago:[41.8781,-87.6298],
|
||||
toronto:[43.6532,-79.3832], mexico_city:[19.4326,-99.1332], sao_paulo:[-23.5505,-46.6333],
|
||||
buenos_aires:[-34.6037,-58.3816], cairo:[30.0444,31.2357], nairobi:[-1.2921,36.8219],
|
||||
johannesburg:[-26.2041,28.0473],
|
||||
};
|
||||
// Restore prior city selection when possible by matching stored lat/lon.
|
||||
const restoreCity = () => {
|
||||
const la = Number(s.locationLat), lo = Number(s.locationLon);
|
||||
for (const [key, [x, y]] of Object.entries(CITIES))
|
||||
if (Math.abs(x - la) < 0.01 && Math.abs(y - lo) < 0.01) { locCity.value = key; return; }
|
||||
};
|
||||
restoreCity();
|
||||
locCity.addEventListener("change", () => {
|
||||
const c = CITIES[locCity.value]; if (!c) return;
|
||||
C.set("locationLat", String(c[0]));
|
||||
C.set("locationLon", String(c[1]));
|
||||
C.set("locationCity", locCity.value); // save the pick so we can restore it later
|
||||
locCountry.addEventListener("change", () => {
|
||||
const code = locCountry.value;
|
||||
const c = COUNTRIES[code]; if (!c) return;
|
||||
C.set("locationCountry", code);
|
||||
C.set("locationLat", String(c[1]));
|
||||
C.set("locationLon", String(c[2]));
|
||||
});
|
||||
|
||||
// Storage: "Clear all now" wipes everything the toggles cover, without
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue