update browser extension and app
This commit is contained in:
@@ -25,19 +25,67 @@ A ShareX-style browser extension for **Chrome, Brave, Edge, Opera, and Firefox**
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
### Chrome, Brave, Edge, Opera (Chromium)
|
||||
### Chrome, Brave, Edge, Opera (Chromium) — Permanent by default
|
||||
|
||||
1. Open your browser and navigate to `chrome://extensions` (or `edge://extensions` in Edge).
|
||||
2. Enable **Developer mode** (toggle in the top-right corner).
|
||||
3. Click **Load unpacked** in the top-left menu.
|
||||
2. Enable **Developer mode** (toggle switch in top-right).
|
||||
3. Click **Load unpacked**.
|
||||
4. Select the `browser-extension` folder in this repository (`/home/kibi/Projects/f0ckm-uploader/browser-extension`).
|
||||
5. Pin the **f0ckm Uploader** icon to your toolbar for easy access!
|
||||
5. *Note*: In Chromium browsers, loading unpacked extensions **remains installed permanently** across browser restarts as long as the folder location is not moved or deleted.
|
||||
|
||||
---
|
||||
|
||||
### Mozilla Firefox
|
||||
|
||||
1. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`.
|
||||
Firefox restricts unsigned extensions from being permanently installed in standard release builds by default. Choose one of the following methods for permanent installation:
|
||||
|
||||
#### Method 1: System Policy (`policies.json`) — Recommended for standard Firefox on Linux
|
||||
|
||||
1. Zip the extension directory:
|
||||
```bash
|
||||
cd /home/kibi/Projects/f0ckm-uploader/browser-extension
|
||||
zip -r /tmp/f0ckm-uploader.xpi *
|
||||
```
|
||||
2. Create or edit `/etc/firefox/policies/policies.json` with superuser privileges:
|
||||
```json
|
||||
{
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"f0ckm-uploader@local": {
|
||||
"installation_mode": "force_installed",
|
||||
"install_url": "file:///tmp/f0ckm-uploader.xpi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
3. Restart Firefox. The extension will be permanently installed.
|
||||
|
||||
#### Method 2: Firefox Developer Edition / Nightly / ESR / LibreWolf / Waterfox
|
||||
|
||||
1. Open `about:config` in the address bar and accept the risk.
|
||||
2. Search for `xpinstall.signatures.required` and toggle it to **`false`**.
|
||||
3. Package the extension as `.xpi`:
|
||||
```bash
|
||||
cd /home/kibi/Projects/f0ckm-uploader/browser-extension
|
||||
zip -r ~/f0ckm-uploader.xpi *
|
||||
```
|
||||
4. Open `about:addons`, click the gear icon (⚙️) -> **Install Add-on From File...** and select `f0ckm-uploader.xpi`.
|
||||
|
||||
#### Method 3: Self-Signing via Mozilla Add-ons Hub (AMO) — Works on standard Firefox release
|
||||
|
||||
1. Zip the `browser-extension` directory contents.
|
||||
2. Go to the [Mozilla Add-on Developer Hub](https://addons.mozilla.org/en-US/developers/).
|
||||
3. Submit a new add-on and choose **"On your own"** (Self-distribution / unlisted).
|
||||
4. Mozilla's automated scanner will sign your `.xpi` file within a minute.
|
||||
5. Download your signed `.xpi` file and open it in Firefox (`Ctrl+O` or drag and drop into `about:addons`) to install permanently.
|
||||
|
||||
#### Method 4: Temporary Installation (Development mode)
|
||||
|
||||
1. Navigate to `about:debugging#/runtime/this-firefox`.
|
||||
2. Click **Load Temporary Add-on...**
|
||||
3. Select `manifest.json` from the `browser-extension` folder.
|
||||
*(Note: Temporary add-ons automatically unload when Firefox closes).*
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -122,6 +122,73 @@ chrome.commands.onCommand.addListener(async (command) => {
|
||||
}
|
||||
});
|
||||
|
||||
function normalizeApiUrl(url) {
|
||||
if (!url) return "";
|
||||
url = url.trim().replace(/^['"]|['"]$/g, "");
|
||||
if (!url) return "";
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
let path = parsed.pathname.replace(/\/+$/, "");
|
||||
if (!path || path === "") {
|
||||
return `${parsed.origin}/api/v2/upload`;
|
||||
} else if (path.endsWith("/api/v2")) {
|
||||
return `${parsed.origin}/upload`;
|
||||
} else if (!path.endsWith("/upload")) {
|
||||
return `${parsed.origin}/api/v2/upload`;
|
||||
}
|
||||
return url;
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyToClipboard(text) {
|
||||
if (!text) return;
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function testConnection(apiUrl, apiKey) {
|
||||
if (!apiUrl || !apiKey) {
|
||||
return { success: false, msg: "API URL and API Key are required." };
|
||||
}
|
||||
const normUrl = normalizeApiUrl(apiUrl);
|
||||
try {
|
||||
const res = await fetch(normUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": apiKey,
|
||||
"Authorization": `Bearer ${apiKey}`
|
||||
},
|
||||
credentials: "omit",
|
||||
body: JSON.stringify({ api_key: apiKey, key: apiKey })
|
||||
});
|
||||
if (res.status === 200) {
|
||||
return { success: true, msg: "Connection successful! (HTTP 200)" };
|
||||
} else if (res.status === 401) {
|
||||
return { success: false, msg: "Unauthorized: Invalid API Key." };
|
||||
} else if (res.status === 400 || res.status === 422) {
|
||||
return { success: true, msg: "Connection successful! (API key verified)" };
|
||||
} else {
|
||||
let text = `HTTP ${res.status}`;
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.msg || json.error || json.message) text = json.msg || json.error || json.message;
|
||||
} catch (e) {}
|
||||
return { success: false, msg: text };
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, msg: err.message || "Connection failed. Check server URL and network." };
|
||||
}
|
||||
}
|
||||
|
||||
async function processUpload(targetUrl) {
|
||||
if (!targetUrl) return;
|
||||
|
||||
@@ -160,45 +227,69 @@ async function processUpload(targetUrl) {
|
||||
try {
|
||||
if (!settings.apiUrl || !settings.apiKey) {
|
||||
showNotification("Upload Error", "API URL and API Key must be configured in extension options.", "error");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedUrl = normalizeApiUrl(settings.apiUrl);
|
||||
|
||||
const payload = {
|
||||
url: targetUrl,
|
||||
tags: settings.tags || "url,upload",
|
||||
visibility: settings.visibility || "0"
|
||||
visibility: settings.visibility || "0",
|
||||
api_key: settings.apiKey,
|
||||
key: settings.apiKey
|
||||
};
|
||||
|
||||
if (settings.rating && settings.rating !== "default") {
|
||||
if (settings.rating && settings.rating !== "default" && settings.rating !== "") {
|
||||
const ratingMap = { s: "sfw", q: "nsfw", e: "nsfl", sfw: "sfw", nsfw: "nsfw", nsfl: "nsfl" };
|
||||
payload.rating = ratingMap[settings.rating.toLowerCase()] || settings.rating;
|
||||
}
|
||||
if (settings.isOc) payload.is_oc = "1";
|
||||
|
||||
const uploadRes = await fetch(settings.apiUrl, {
|
||||
const uploadRes = await fetch(normalizedUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": settings.apiKey
|
||||
"X-Api-Key": settings.apiKey,
|
||||
"Authorization": `Bearer ${settings.apiKey}`
|
||||
},
|
||||
credentials: "omit",
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!uploadRes.ok) {
|
||||
let errorMsg = `HTTP Error ${uploadRes.status}`;
|
||||
try {
|
||||
const errJson = await uploadRes.json();
|
||||
if (errJson.msg || errJson.error || errJson.message) {
|
||||
errorMsg = errJson.msg || errJson.error || errJson.message;
|
||||
}
|
||||
} catch (e) {}
|
||||
showNotification("Upload Failed", errorMsg, "error");
|
||||
return null;
|
||||
}
|
||||
|
||||
const json = await uploadRes.json();
|
||||
|
||||
if (json.success && (json.url || json.file_url || json.direct_url || json.target_url || json.file)) {
|
||||
const postUrl = json.url || json.post_url || "";
|
||||
const directUrl = json.file_url || json.direct_url || json.target_url || json.file || "";
|
||||
const resultUrl = (settings.urlType === "direct" && directUrl) ? directUrl : (postUrl || directUrl);
|
||||
const postUrl = json.url || json.post_url || json.link || (json.data && (json.data.url || json.data.post_url)) || "";
|
||||
const directUrl = json.file_url || json.direct_url || json.target_url || json.file || (json.data && (json.data.file_url || json.data.direct_url)) || "";
|
||||
const resultUrl = (settings.urlType === "direct" && directUrl) ? directUrl : (postUrl || directUrl);
|
||||
|
||||
showNotification("Upload Successful!", `Link: ${resultUrl}`, "success");
|
||||
if (resultUrl) {
|
||||
await copyToClipboard(resultUrl);
|
||||
showNotification("Upload Successful!", `Link copied to clipboard: ${resultUrl}`, "success");
|
||||
return resultUrl;
|
||||
} else if (json.success) {
|
||||
showNotification("Upload Successful!", "Upload processed successfully.", "success");
|
||||
return "success";
|
||||
} else {
|
||||
const msg = json.msg || "Upload failed";
|
||||
const msg = json.msg || json.error || "Upload failed";
|
||||
showNotification("Upload Failed", msg, "error");
|
||||
return null;
|
||||
}
|
||||
} catch (err) {
|
||||
showNotification("Upload Error", err.message || "An error occurred", "error");
|
||||
showNotification("Upload Error", err.message || "An error occurred during network request", "error");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,5 +329,10 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
sendResponse({ status: "ok", resultUrl: resUrl || "" });
|
||||
});
|
||||
return true;
|
||||
} else if (request.action === "test_connection") {
|
||||
testConnection(request.apiUrl, request.apiKey).then((res) => {
|
||||
sendResponse(res);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
"manifest_version": 3,
|
||||
"name": "f0ckm Uploader",
|
||||
"version": "2.0",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "f0ckm-uploader@local",
|
||||
"strict_min_version": "109.0"
|
||||
}
|
||||
},
|
||||
"description": "Upload images, media, links, and URLs directly to f0ckm from context menu or toolbar.",
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
<label for="apiKey">API Key</label>
|
||||
<input type="password" id="apiKey" placeholder="Enter your secret API key">
|
||||
</div>
|
||||
<div class="field-row" style="margin-top: 10px;">
|
||||
<button type="button" id="btn-test-connection" class="btn-secondary" style="padding: 8px 16px; border-radius: 6px; cursor: pointer;">Test Connection</button>
|
||||
<span id="test-toast" class="toast hidden" style="margin-left: 10px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
|
||||
@@ -9,6 +9,29 @@ const DEFAULT_SETTINGS = {
|
||||
urlType: "post"
|
||||
};
|
||||
|
||||
function normalizeApiUrl(url) {
|
||||
if (!url) return "";
|
||||
url = url.trim().replace(/^['"]|['"]$/g, "");
|
||||
if (!url) return "";
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
let path = parsed.pathname.replace(/\/+$/, "");
|
||||
if (!path || path === "") {
|
||||
return `${parsed.origin}/api/v2/upload`;
|
||||
} else if (path.endsWith("/api/v2")) {
|
||||
return `${parsed.origin}/upload`;
|
||||
} else if (!path.endsWith("/upload")) {
|
||||
return `${parsed.origin}/api/v2/upload`;
|
||||
}
|
||||
return url;
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const form = document.getElementById("settings-form");
|
||||
const modeDesktop = document.getElementById("mode-desktop");
|
||||
@@ -21,6 +44,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const isOc = document.getElementById("isOc");
|
||||
const urlType = document.getElementById("urlType");
|
||||
const saveToast = document.getElementById("save-toast");
|
||||
const btnTest = document.getElementById("btn-test-connection");
|
||||
const testToast = document.getElementById("test-toast");
|
||||
|
||||
// Load existing settings
|
||||
chrome.storage.sync.get(DEFAULT_SETTINGS, (items) => {
|
||||
@@ -42,9 +67,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// Save settings on form submit
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const rawUrl = apiUrl.value.trim();
|
||||
const normUrl = normalizeApiUrl(rawUrl);
|
||||
if (normUrl && normUrl !== rawUrl) {
|
||||
apiUrl.value = normUrl;
|
||||
}
|
||||
|
||||
const newSettings = {
|
||||
uploadMode: modeDirect.checked ? "direct" : "desktop",
|
||||
apiUrl: apiUrl.value.trim(),
|
||||
apiUrl: normUrl || rawUrl,
|
||||
apiKey: apiKey.value.trim(),
|
||||
rating: rating.value,
|
||||
visibility: visibility.value,
|
||||
@@ -60,4 +91,39 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}, 2500);
|
||||
});
|
||||
});
|
||||
|
||||
// Test Connection button
|
||||
if (btnTest) {
|
||||
btnTest.addEventListener("click", () => {
|
||||
const url = apiUrl.value.trim();
|
||||
const key = apiKey.value.trim();
|
||||
if (!url || !key) {
|
||||
if (testToast) {
|
||||
testToast.textContent = "Please enter API URL and API Key.";
|
||||
testToast.style.color = "#ff4444";
|
||||
testToast.classList.remove("hidden");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
btnTest.disabled = true;
|
||||
btnTest.textContent = "Testing...";
|
||||
if (testToast) testToast.classList.add("hidden");
|
||||
|
||||
chrome.runtime.sendMessage({ action: "test_connection", apiUrl: url, apiKey: key }, (res) => {
|
||||
btnTest.disabled = false;
|
||||
btnTest.textContent = "Test Connection";
|
||||
if (testToast) {
|
||||
testToast.classList.remove("hidden");
|
||||
if (res && res.success) {
|
||||
testToast.textContent = "✓ " + res.msg;
|
||||
testToast.style.color = "#00e676";
|
||||
} else {
|
||||
testToast.textContent = "❌ " + (res ? res.msg : "Test failed");
|
||||
testToast.style.color = "#ff4444";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,12 +20,79 @@ html, body {
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 14px;
|
||||
background: #191b22;
|
||||
border-bottom: 1px solid #282a36;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
background: #282c3f;
|
||||
}
|
||||
|
||||
.api-config-card {
|
||||
background: #191b26;
|
||||
border: 1px solid #0066ff44;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.api-config-card.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.config-header {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #4da6ff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.api-config-card .field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.api-config-card label {
|
||||
font-size: 10px;
|
||||
color: #a0a6b8;
|
||||
}
|
||||
|
||||
.api-config-card input {
|
||||
background: #12131a;
|
||||
border: 1px solid #282c3f;
|
||||
color: #ffffff;
|
||||
padding: 6px 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.config-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.popup-toast {
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<img src="../icons/icon32.png" alt="f0ckm Logo" class="brand-icon">
|
||||
<span class="brand-name">f0ckm Uploader</span>
|
||||
</div>
|
||||
<button id="btn-open-options" class="icon-btn" title="Open Settings Options">⚙️</button>
|
||||
</div>
|
||||
|
||||
<div class="mode-selector">
|
||||
@@ -26,6 +27,24 @@
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div id="api-config-card" class="api-config-card hidden">
|
||||
<div class="config-header">
|
||||
<span>⚙️ Direct API Credentials</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="popup-api-url">API URL:</label>
|
||||
<input type="url" id="popup-api-url" placeholder="https://f0ckm.com/api/v2/upload">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="popup-api-key">API Key:</label>
|
||||
<input type="password" id="popup-api-key" placeholder="Secret API key">
|
||||
</div>
|
||||
<div class="config-actions">
|
||||
<button id="btn-save-api-config" class="btn btn-sm btn-primary">Save API Info</button>
|
||||
<button id="btn-popup-test" class="btn btn-sm btn-accent">Test</button>
|
||||
<span id="popup-api-toast" class="popup-toast"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="active-tab-card" class="active-tab-card">
|
||||
<div class="card-top">
|
||||
<span id="site-badge" class="site-badge">🌐 Web Page</span>
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
function normalizeApiUrl(url) {
|
||||
if (!url) return "";
|
||||
url = url.trim().replace(/^['"]|['"]$/g, "");
|
||||
if (!url) return "";
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
let path = parsed.pathname.replace(/\/+$/, "");
|
||||
if (!path || path === "") {
|
||||
return `${parsed.origin}/api/v2/upload`;
|
||||
} else if (path.endsWith("/api/v2")) {
|
||||
return `${parsed.origin}/upload`;
|
||||
} else if (!path.endsWith("/upload")) {
|
||||
return `${parsed.origin}/api/v2/upload`;
|
||||
}
|
||||
return url;
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
function initPopup() {
|
||||
const modeDesktop = document.getElementById("mode-desktop");
|
||||
const modeDirect = document.getElementById("mode-direct");
|
||||
const btnOpenOptions = document.getElementById("btn-open-options");
|
||||
const apiConfigCard = document.getElementById("api-config-card");
|
||||
const popupApiUrl = document.getElementById("popup-api-url");
|
||||
const popupApiKey = document.getElementById("popup-api-key");
|
||||
const btnSaveApiConfig = document.getElementById("btn-save-api-config");
|
||||
const btnPopupTest = document.getElementById("btn-popup-test");
|
||||
const popupApiToast = document.getElementById("popup-api-toast");
|
||||
|
||||
const btnUploadTab = document.getElementById("btn-upload-tab");
|
||||
const btnUploadText = document.getElementById("btn-upload-text");
|
||||
const btnUploadUrl = document.getElementById("btn-upload-url");
|
||||
@@ -17,26 +48,115 @@ function initPopup() {
|
||||
|
||||
let activeTabUrl = "";
|
||||
|
||||
// 1. Load settings asynchronously without blocking UI initialization
|
||||
try {
|
||||
chrome.storage.sync.get({ uploadMode: "desktop" }, (settings) => {
|
||||
if (chrome.runtime.lastError || !settings) return;
|
||||
if (settings.uploadMode === "direct") {
|
||||
if (modeDirect) modeDirect.checked = true;
|
||||
if (btnOpenOptions) {
|
||||
btnOpenOptions.addEventListener("click", () => {
|
||||
if (chrome.runtime.openOptionsPage) {
|
||||
chrome.runtime.openOptionsPage();
|
||||
} else {
|
||||
if (modeDesktop) modeDesktop.checked = true;
|
||||
window.open(chrome.runtime.getURL("options/options.html"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateModeUI(mode) {
|
||||
if (mode === "direct") {
|
||||
if (modeDirect) modeDirect.checked = true;
|
||||
if (apiConfigCard) apiConfigCard.classList.remove("hidden");
|
||||
} else {
|
||||
if (modeDesktop) modeDesktop.checked = true;
|
||||
if (apiConfigCard) apiConfigCard.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Load settings & drafts asynchronously without blocking UI initialization
|
||||
try {
|
||||
chrome.storage.sync.get({ uploadMode: "desktop", apiUrl: "", apiKey: "", draftUrl: "" }, (settings) => {
|
||||
if (chrome.runtime.lastError || !settings) return;
|
||||
if (popupApiUrl) popupApiUrl.value = settings.apiUrl || "";
|
||||
if (popupApiKey) popupApiKey.value = settings.apiKey || "";
|
||||
if (urlInput && settings.draftUrl) urlInput.value = settings.draftUrl;
|
||||
updateModeUI(settings.uploadMode);
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
// Auto-save on input so values are never lost if popup closes when copying/switching tabs
|
||||
if (popupApiUrl) {
|
||||
popupApiUrl.addEventListener("input", () => {
|
||||
chrome.storage.sync.set({ apiUrl: popupApiUrl.value });
|
||||
});
|
||||
}
|
||||
if (popupApiKey) {
|
||||
popupApiKey.addEventListener("input", () => {
|
||||
chrome.storage.sync.set({ apiKey: popupApiKey.value });
|
||||
});
|
||||
}
|
||||
if (urlInput) {
|
||||
urlInput.addEventListener("input", () => {
|
||||
chrome.storage.sync.set({ draftUrl: urlInput.value });
|
||||
});
|
||||
}
|
||||
|
||||
if (modeDesktop) {
|
||||
modeDesktop.addEventListener("change", () => {
|
||||
chrome.storage.sync.set({ uploadMode: "desktop" });
|
||||
chrome.storage.sync.set({ uploadMode: "desktop" }, () => {
|
||||
updateModeUI("desktop");
|
||||
});
|
||||
});
|
||||
}
|
||||
if (modeDirect) {
|
||||
modeDirect.addEventListener("change", () => {
|
||||
chrome.storage.sync.set({ uploadMode: "direct" });
|
||||
chrome.storage.sync.set({ uploadMode: "direct" }, () => {
|
||||
updateModeUI("direct");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (btnSaveApiConfig) {
|
||||
btnSaveApiConfig.addEventListener("click", () => {
|
||||
const rawUrl = popupApiUrl ? popupApiUrl.value.trim() : "";
|
||||
const key = popupApiKey ? popupApiKey.value.trim() : "";
|
||||
const normUrl = normalizeApiUrl(rawUrl);
|
||||
if (popupApiUrl && normUrl) popupApiUrl.value = normUrl;
|
||||
|
||||
chrome.storage.sync.set({ apiUrl: normUrl || rawUrl, apiKey: key }, () => {
|
||||
if (popupApiToast) {
|
||||
popupApiToast.textContent = "Saved!";
|
||||
popupApiToast.style.color = "#00e676";
|
||||
setTimeout(() => { popupApiToast.textContent = ""; }, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (btnPopupTest) {
|
||||
btnPopupTest.addEventListener("click", () => {
|
||||
const url = popupApiUrl ? popupApiUrl.value.trim() : "";
|
||||
const key = popupApiKey ? popupApiKey.value.trim() : "";
|
||||
if (!url || !key) {
|
||||
if (popupApiToast) {
|
||||
popupApiToast.textContent = "Enter URL & Key!";
|
||||
popupApiToast.style.color = "#ff4444";
|
||||
}
|
||||
return;
|
||||
}
|
||||
btnPopupTest.disabled = true;
|
||||
if (popupApiToast) {
|
||||
popupApiToast.textContent = "Testing...";
|
||||
popupApiToast.style.color = "#4da6ff";
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({ action: "test_connection", apiUrl: url, apiKey: key }, (res) => {
|
||||
btnPopupTest.disabled = false;
|
||||
if (popupApiToast) {
|
||||
if (res && res.success) {
|
||||
popupApiToast.textContent = "✓ OK!";
|
||||
popupApiToast.style.color = "#00e676";
|
||||
} else {
|
||||
popupApiToast.textContent = "❌ " + (res ? res.msg : "Failed");
|
||||
popupApiToast.style.color = "#ff4444";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,7 +290,7 @@ function initPopup() {
|
||||
if (response && response.resultUrl) {
|
||||
showSuccessResult(response.resultUrl);
|
||||
} else {
|
||||
showStatus("URL Upload queued! Link copied to clipboard.", false);
|
||||
showStatus("Upload failed or queued. Check notification / settings.", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user