2026-03-18 12:51:24 +08:00
|
|
|
const DEFAULT_CONFIG = {
|
|
|
|
|
apiBase: "https://polyweather-pro.vercel.app",
|
|
|
|
|
siteBase: "https://polyweather-pro.vercel.app",
|
|
|
|
|
authToken: "",
|
|
|
|
|
selectedCity: ""
|
|
|
|
|
};
|
2026-03-20 18:43:01 +08:00
|
|
|
const locale = String(navigator.language || "en").toLowerCase().startsWith("zh")
|
|
|
|
|
? "zh"
|
|
|
|
|
: "en";
|
|
|
|
|
const I18N = {
|
|
|
|
|
zh: {
|
|
|
|
|
settingsTitle: "PolyWeather 侧边栏设置",
|
|
|
|
|
tokenLabel: "Bearer Token(可选)",
|
|
|
|
|
tokenPlaceholder: "公开模式留空即可;仅当后端返回 401 时再填写。",
|
|
|
|
|
save: "保存",
|
|
|
|
|
test: "测试 /api/cities",
|
|
|
|
|
saved: "已保存。公开模式下 Token 可留空。",
|
|
|
|
|
connectOk: "连接成功,返回城市数",
|
|
|
|
|
tokenOptional: "Token 可留空",
|
|
|
|
|
testFailed: "测试失败",
|
|
|
|
|
backendAuthHint: "说明后端仍要求鉴权;若你要公开插件,请先放开 /api/cities 与 /api/city/*/detail。"
|
|
|
|
|
},
|
|
|
|
|
en: {
|
|
|
|
|
settingsTitle: "PolyWeather Side Panel Settings",
|
|
|
|
|
tokenLabel: "Bearer Token (Optional)",
|
|
|
|
|
tokenPlaceholder: "Leave empty in public mode; only fill it if the backend returns 401.",
|
|
|
|
|
save: "Save",
|
|
|
|
|
test: "Test /api/cities",
|
|
|
|
|
saved: "Saved. Token can be empty in public mode.",
|
|
|
|
|
connectOk: "Connected successfully, city count",
|
|
|
|
|
tokenOptional: "Token can be empty",
|
|
|
|
|
testFailed: "Test failed",
|
|
|
|
|
backendAuthHint: "The backend still requires auth. If the extension should be public, allow /api/cities and /api/city/*/detail."
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function t(key) {
|
|
|
|
|
return I18N[locale][key] || I18N.zh[key] || key;
|
|
|
|
|
}
|
2026-03-18 12:51:24 +08:00
|
|
|
|
|
|
|
|
const apiBaseInput = document.getElementById("apiBaseInput");
|
|
|
|
|
const siteBaseInput = document.getElementById("siteBaseInput");
|
|
|
|
|
const tokenInput = document.getElementById("tokenInput");
|
|
|
|
|
const resultBox = document.getElementById("resultBox");
|
2026-03-20 18:43:01 +08:00
|
|
|
const settingsTitle = document.getElementById("settingsTitle");
|
|
|
|
|
const siteBaseLabel = document.getElementById("siteBaseLabel");
|
|
|
|
|
const apiBaseLabel = document.getElementById("apiBaseLabel");
|
|
|
|
|
const tokenLabel = document.getElementById("tokenLabel");
|
|
|
|
|
const saveBtn = document.getElementById("saveBtn");
|
|
|
|
|
const testBtn = document.getElementById("testBtn");
|
2026-03-18 12:51:24 +08:00
|
|
|
|
|
|
|
|
function normalizeBase(url) {
|
|
|
|
|
return String(url || "").trim().replace(/\/+$/, "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeResult(text) {
|
|
|
|
|
resultBox.textContent = text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getStorage() {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
chrome.storage.sync.get(DEFAULT_CONFIG, (items) => resolve(items));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setStorage(values) {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
chrome.storage.sync.set(values, resolve);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadForm() {
|
|
|
|
|
const cfg = await getStorage();
|
|
|
|
|
apiBaseInput.value = cfg.apiBase || DEFAULT_CONFIG.apiBase;
|
|
|
|
|
siteBaseInput.value = cfg.siteBase || cfg.apiBase || DEFAULT_CONFIG.siteBase;
|
|
|
|
|
tokenInput.value = cfg.authToken || "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveForm() {
|
|
|
|
|
const next = {
|
|
|
|
|
apiBase: normalizeBase(apiBaseInput.value),
|
|
|
|
|
siteBase: normalizeBase(siteBaseInput.value || apiBaseInput.value),
|
|
|
|
|
authToken: String(tokenInput.value || "").trim()
|
|
|
|
|
};
|
|
|
|
|
await setStorage(next);
|
2026-03-20 18:43:01 +08:00
|
|
|
writeResult(t("saved"));
|
2026-03-18 12:51:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function testApi() {
|
|
|
|
|
const apiBase = normalizeBase(apiBaseInput.value);
|
|
|
|
|
const authToken = String(tokenInput.value || "").trim();
|
|
|
|
|
try {
|
|
|
|
|
const headers = { Accept: "application/json" };
|
|
|
|
|
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
|
|
|
|
|
|
|
|
const res = await fetch(`${apiBase}/api/cities`, {
|
|
|
|
|
headers,
|
|
|
|
|
cache: "no-store"
|
|
|
|
|
});
|
|
|
|
|
const text = await res.text();
|
|
|
|
|
let data = null;
|
|
|
|
|
try {
|
|
|
|
|
data = text ? JSON.parse(text) : null;
|
|
|
|
|
} catch (_e) {
|
|
|
|
|
data = text;
|
|
|
|
|
}
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
throw new Error(`HTTP ${res.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
|
|
|
}
|
|
|
|
|
const count = Array.isArray(data)
|
|
|
|
|
? data.length
|
|
|
|
|
: Array.isArray(data?.cities)
|
|
|
|
|
? data.cities.length
|
|
|
|
|
: 0;
|
2026-03-20 18:43:01 +08:00
|
|
|
writeResult(`${t("connectOk")}: ${count} (${t("tokenOptional")})`);
|
2026-03-18 12:51:24 +08:00
|
|
|
} catch (err) {
|
|
|
|
|
const msg = String(err?.message || err || "");
|
|
|
|
|
if (msg.includes("HTTP 401")) {
|
2026-03-20 18:43:01 +08:00
|
|
|
writeResult(`${t("testFailed")}: ${msg}\n${t("backendAuthHint")}`);
|
2026-03-18 12:51:24 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-03-20 18:43:01 +08:00
|
|
|
writeResult(`${t("testFailed")}: ${msg}`);
|
2026-03-18 12:51:24 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 18:43:01 +08:00
|
|
|
function applyStaticTranslations() {
|
|
|
|
|
document.documentElement.lang = locale === "zh" ? "zh-CN" : "en";
|
|
|
|
|
if (settingsTitle) settingsTitle.textContent = t("settingsTitle");
|
|
|
|
|
if (tokenLabel) tokenLabel.textContent = t("tokenLabel");
|
|
|
|
|
if (tokenInput) tokenInput.placeholder = t("tokenPlaceholder");
|
|
|
|
|
if (saveBtn) saveBtn.textContent = t("save");
|
|
|
|
|
if (testBtn) testBtn.textContent = t("test");
|
|
|
|
|
if (siteBaseLabel) siteBaseLabel.textContent = "Site Base URL";
|
|
|
|
|
if (apiBaseLabel) apiBaseLabel.textContent = "API Base URL";
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 12:51:24 +08:00
|
|
|
document.getElementById("saveBtn").addEventListener("click", () => {
|
|
|
|
|
void saveForm();
|
|
|
|
|
});
|
|
|
|
|
document.getElementById("testBtn").addEventListener("click", () => {
|
|
|
|
|
void testApi();
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-20 18:43:01 +08:00
|
|
|
applyStaticTranslations();
|
2026-03-18 12:51:24 +08:00
|
|
|
void loadForm();
|