update browser extension and app

This commit is contained in:
2026-08-13 17:21:58 +02:00
parent 7e723ae821
commit 9050faf0a4
14 changed files with 809 additions and 97 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);
}
});
}