adding browser extension and some QoL
This commit is contained in:
58
browser-extension/README.md
Normal file
58
browser-extension/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# f0ckm Browser Integration Addon
|
||||
|
||||
A ShareX-style browser extension for **Chrome, Brave, Edge, Opera, and Firefox** that allows you to upload images, media, links, and URLs directly to **f0ckm** from any web browser context menu or toolbar popup.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Right-Click Context Menu (ShareX Style)**:
|
||||
- **Upload image to f0ckm** (on any image)
|
||||
- **Upload media to f0ckm** (on video/audio elements)
|
||||
- **Upload link with f0ckm** (on links)
|
||||
- **Upload selected URL with f0ckm** (on highlighted text URLs)
|
||||
- **Upload page URL to f0ckm** (on page background)
|
||||
- **Dual Upload Modes**:
|
||||
- **Desktop App Mode (`f0ckm://`)** *(Recommended)*: Hands off downloads and uploads to the KDE desktop system tray app. Gives you live system tray digit progress (`0`–`99`%), Spectacle notifications, audio sounds, and local history gallery.
|
||||
- **Direct API Mode**: Uploads directly from the browser background page using API URL and API Key without needing the tray app running.
|
||||
- **Toolbar Popup Interface**:
|
||||
- Upload current active tab URL with one click.
|
||||
- Paste any URL for quick upload.
|
||||
- Quick toggle between Desktop App Mode and Direct API Mode.
|
||||
- **Global Keyboard Shortcut**: `Alt+Shift+U` to immediately upload current page URL.
|
||||
|
||||
---
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
### Chrome, Brave, Edge, Opera (Chromium)
|
||||
|
||||
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.
|
||||
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!
|
||||
|
||||
### Mozilla Firefox
|
||||
|
||||
1. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`.
|
||||
2. Click **Load Temporary Add-on...**
|
||||
3. Select `manifest.json` from the `browser-extension` folder.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Right-Click Context Menu**: Right-click any image, video, audio link, or page -> select **Upload to f0ckm**.
|
||||
2. **Toolbar Popup**: Click the f0ckm icon in your browser toolbar to upload the active tab or paste a URL.
|
||||
3. **Keyboard Shortcut**: Press `Alt+Shift+U` on any tab.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Click **Settings** (or right-click extension icon -> Options) to configure:
|
||||
- **Upload Mode**: Desktop App (f0ckm://) vs Direct API.
|
||||
- **API Endpoint URL**: Default `https://your-f0ckm-instance.com/api/v2/upload`.
|
||||
- **API Key**: Secret API key for direct API mode.
|
||||
- **Default Rating / Tags / Visibility / OC / Copied Link Format**.
|
||||
242
browser-extension/background.js
Normal file
242
browser-extension/background.js
Normal file
@@ -0,0 +1,242 @@
|
||||
// f0ckm Uploader - Background Service Worker
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
uploadMode: "desktop", // "desktop" (f0ckm://) or "direct" (API)
|
||||
apiUrl: "https://f0ckm.com/api/v2/upload",
|
||||
apiKey: "",
|
||||
rating: "",
|
||||
tags: "",
|
||||
visibility: "0",
|
||||
isOc: false,
|
||||
urlType: "post"
|
||||
};
|
||||
|
||||
async function getSettings() {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.sync.get(DEFAULT_SETTINGS, (items) => {
|
||||
resolve(items || DEFAULT_SETTINGS);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const badgeCache = {};
|
||||
|
||||
function updateBadge(tabId, url) {
|
||||
if (!tabId) return;
|
||||
const isWeb = url && (url.startsWith("http://") || url.startsWith("https://"));
|
||||
const isYT = isWeb && (url.includes("youtube.com") || url.includes("youtu.be"));
|
||||
const targetText = isWeb ? "1" : "";
|
||||
const targetColor = isYT ? "#ff0000" : "#0066ff";
|
||||
|
||||
const cacheKey = `${targetText}:${targetColor}`;
|
||||
if (badgeCache[tabId] === cacheKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
badgeCache[tabId] = cacheKey;
|
||||
|
||||
try {
|
||||
if (targetText) {
|
||||
chrome.action.setBadgeText({ text: targetText, tabId: tabId });
|
||||
chrome.action.setBadgeBackgroundColor({ color: targetColor, tabId: tabId });
|
||||
} else {
|
||||
chrome.action.setBadgeText({ text: "", tabId: tabId });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
chrome.tabs.onActivated.addListener(async (activeInfo) => {
|
||||
try {
|
||||
const tab = await chrome.tabs.get(activeInfo.tabId);
|
||||
if (tab) updateBadge(activeInfo.tabId, tab.url);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (tab && (changeInfo.status === "complete" || changeInfo.url)) {
|
||||
updateBadge(tabId, tab.url);
|
||||
}
|
||||
});
|
||||
|
||||
function setupContextMenus() {
|
||||
chrome.contextMenus.removeAll(() => {
|
||||
chrome.contextMenus.create({
|
||||
id: "f0ckm_upload_image",
|
||||
title: "Upload image to f0ckm",
|
||||
contexts: ["image"]
|
||||
});
|
||||
chrome.contextMenus.create({
|
||||
id: "f0ckm_upload_video",
|
||||
title: "Upload media to f0ckm",
|
||||
contexts: ["video", "audio"]
|
||||
});
|
||||
chrome.contextMenus.create({
|
||||
id: "f0ckm_upload_link",
|
||||
title: "Upload link with f0ckm",
|
||||
contexts: ["link"]
|
||||
});
|
||||
chrome.contextMenus.create({
|
||||
id: "f0ckm_upload_selection",
|
||||
title: "Upload selected URL with f0ckm",
|
||||
contexts: ["selection"]
|
||||
});
|
||||
chrome.contextMenus.create({
|
||||
id: "f0ckm_upload_page",
|
||||
title: "Upload page URL to f0ckm",
|
||||
contexts: ["page"]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
setupContextMenus();
|
||||
});
|
||||
|
||||
chrome.runtime.onStartup.addListener(() => {
|
||||
setupContextMenus();
|
||||
});
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
||||
let targetUrl = "";
|
||||
if (info.menuItemId === "f0ckm_upload_image" || info.menuItemId === "f0ckm_upload_video") {
|
||||
targetUrl = info.srcUrl || info.linkUrl;
|
||||
} else if (info.menuItemId === "f0ckm_upload_link") {
|
||||
targetUrl = info.linkUrl;
|
||||
} else if (info.menuItemId === "f0ckm_upload_selection") {
|
||||
targetUrl = info.selectionText ? info.selectionText.trim() : "";
|
||||
} else if (info.menuItemId === "f0ckm_upload_page") {
|
||||
targetUrl = info.pageUrl || (tab ? tab.url : "");
|
||||
}
|
||||
|
||||
if (targetUrl) {
|
||||
await processUpload(targetUrl);
|
||||
}
|
||||
});
|
||||
|
||||
chrome.commands.onCommand.addListener(async (command) => {
|
||||
if (command === "upload-page") {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (tabs && tabs.length > 0 && tabs[0].url) {
|
||||
await processUpload(tabs[0].url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function processUpload(targetUrl) {
|
||||
if (!targetUrl) return;
|
||||
|
||||
const settings = await getSettings();
|
||||
|
||||
if (settings.uploadMode === "desktop") {
|
||||
// 1. Try local loopback HTTP API first (Direct desktop handoff)
|
||||
const localApiUrl = "http://127.0.0.1:18739/upload?url=" + encodeURIComponent(targetUrl);
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
||||
const res = await fetch(localApiUrl, { signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (res.ok) {
|
||||
return;
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
// 2. Fallback: Custom protocol scheme in active tab
|
||||
const customUrl = "f0ckm://upload?url=" + encodeURIComponent(targetUrl);
|
||||
try {
|
||||
chrome.tabs.create({ url: customUrl, active: true }, (newTab) => {
|
||||
if (newTab && newTab.id) {
|
||||
setTimeout(() => {
|
||||
chrome.tabs.remove(newTab.id).catch(() => {});
|
||||
}, 4500);
|
||||
}
|
||||
});
|
||||
} catch (e) {}
|
||||
} else {
|
||||
// Direct API Upload mode -> POST JSON payload with url to f0ckm API!
|
||||
showNotification("Uploading...", `Uploading ${targetUrl.substring(0, 45)}...`, "uploading");
|
||||
|
||||
try {
|
||||
if (!settings.apiUrl || !settings.apiKey) {
|
||||
showNotification("Upload Error", "API URL and API Key must be configured in extension options.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
url: targetUrl,
|
||||
tags: settings.tags || "url,upload",
|
||||
visibility: settings.visibility || "0"
|
||||
};
|
||||
|
||||
if (settings.rating && settings.rating !== "default") {
|
||||
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, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": settings.apiKey
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
showNotification("Upload Successful!", `Link: ${resultUrl}`, "success");
|
||||
return resultUrl;
|
||||
} else {
|
||||
const msg = json.msg || "Upload failed";
|
||||
showNotification("Upload Failed", msg, "error");
|
||||
}
|
||||
} catch (err) {
|
||||
showNotification("Upload Error", err.message || "An error occurred", "error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showNotification(title, message, iconType = "info") {
|
||||
if (chrome.notifications) {
|
||||
chrome.notifications.create({
|
||||
type: "basic",
|
||||
iconUrl: "icons/icon128.png",
|
||||
title: title,
|
||||
message: message,
|
||||
priority: 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === "get_active_tab") {
|
||||
chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
|
||||
if (tabs && tabs.length > 0 && tabs[0].url) {
|
||||
sendResponse({ tab: tabs[0] });
|
||||
} else {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs2) => {
|
||||
if (tabs2 && tabs2.length > 0 && tabs2[0].url) {
|
||||
sendResponse({ tab: tabs2[0] });
|
||||
} else {
|
||||
chrome.tabs.query({ active: true }, (tabs3) => {
|
||||
sendResponse({ tab: (tabs3 && tabs3.length > 0) ? tabs3[0] : null });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else if (request.action === "upload_url") {
|
||||
processUpload(request.url).then((resUrl) => {
|
||||
sendResponse({ status: "ok", resultUrl: resUrl || "" });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
46
browser-extension/manifest.chrome.json
Normal file
46
browser-extension/manifest.chrome.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "f0ckm Uploader",
|
||||
"version": "2.0",
|
||||
"description": "Upload images, media, links, and URLs directly to f0ckm from context menu or toolbar.",
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"storage",
|
||||
"notifications",
|
||||
"activeTab",
|
||||
"clipboardWrite"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "popup/popup.html",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options/options.html",
|
||||
"open_in_tab": true
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"commands": {
|
||||
"upload-page": {
|
||||
"suggested_key": {
|
||||
"default": "Alt+Shift+U"
|
||||
},
|
||||
"description": "Upload current page URL to f0ckm"
|
||||
}
|
||||
}
|
||||
}
|
||||
50
browser-extension/manifest.json
Normal file
50
browser-extension/manifest.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "f0ckm Uploader",
|
||||
"version": "2.0",
|
||||
"description": "Upload images, media, links, and URLs directly to f0ckm from context menu or toolbar.",
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"storage",
|
||||
"notifications",
|
||||
"activeTab",
|
||||
"tabs",
|
||||
"clipboardWrite"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "popup/popup.html",
|
||||
"default_title": "f0ckm Uploader",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"default_area": "navbar"
|
||||
},
|
||||
|
||||
"options_ui": {
|
||||
"page": "options/options.html",
|
||||
"open_in_tab": true
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"commands": {
|
||||
"upload-page": {
|
||||
"suggested_key": {
|
||||
"default": "Alt+Shift+U"
|
||||
},
|
||||
"description": "Upload current page URL to f0ckm"
|
||||
}
|
||||
}
|
||||
}
|
||||
197
browser-extension/options/options.css
Normal file
197
browser-extension/options/options.css
Normal file
@@ -0,0 +1,197 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #0f1015;
|
||||
color: #dce0ed;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 40px 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
background: #161822;
|
||||
border: 1px solid #242738;
|
||||
border-radius: 12px;
|
||||
padding: 28px 32px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #242738;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 13px;
|
||||
color: #7a829a;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #4da6ff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
background: #1c1e2d;
|
||||
border: 1px solid #2a2e44;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.radio-label:hover {
|
||||
border-color: #3d4464;
|
||||
}
|
||||
|
||||
.radio-label input[type="radio"] {
|
||||
margin-top: 3px;
|
||||
accent-color: #0066ff;
|
||||
}
|
||||
|
||||
.radio-content strong {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.radio-content p {
|
||||
font-size: 12px;
|
||||
color: #8c93aa;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-bottom: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.field label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #b0b6cc;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="url"],
|
||||
input[type="password"],
|
||||
select {
|
||||
background: #12131b;
|
||||
border: 1px solid #292d42;
|
||||
color: #ffffff;
|
||||
padding: 9px 12px;
|
||||
border-radius: 7px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: #0066ff;
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #dce0ed;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #0066ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-top: 28px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #242738;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: linear-gradient(135deg, #0066ff, #0044cc);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background: linear-gradient(135deg, #1a75ff, #0052e6);
|
||||
box-shadow: 0 4px 12px rgba(0, 102, 255, 0.3);
|
||||
}
|
||||
|
||||
.toast {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #2ed573;
|
||||
}
|
||||
|
||||
.toast.hidden {
|
||||
display: none;
|
||||
}
|
||||
103
browser-extension/options/options.html
Normal file
103
browser-extension/options/options.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>f0ckm Extension Options</title>
|
||||
<link rel="stylesheet" href="options.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="../icons/icon48.png" alt="f0ckm Icon" class="logo">
|
||||
<div>
|
||||
<h1>f0ckm Browser Integration Settings</h1>
|
||||
<p class="subtitle">Configure browser extension upload modes and API settings</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="settings-form">
|
||||
<div class="section">
|
||||
<h2>Integration Mode</h2>
|
||||
<div class="field-group">
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="uploadMode" value="desktop" id="mode-desktop">
|
||||
<div class="radio-content">
|
||||
<strong>Desktop Application Integration (f0ckm://)</strong>
|
||||
<p>Delegates downloads & uploads directly to the KDE system tray app. Provides live tray digit icons, Spectacle notifications, sound effects, and local gallery history.</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="uploadMode" value="direct" id="mode-direct">
|
||||
<div class="radio-content">
|
||||
<strong>Direct Browser Extension API Upload</strong>
|
||||
<p>Uploads files directly from the browser background page using API URL and API Key set below.</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>API & Server Configuration</h2>
|
||||
<div class="field">
|
||||
<label for="apiUrl">API Endpoint URL</label>
|
||||
<input type="url" id="apiUrl" placeholder="https://your-f0ckm-instance.com/api/v2/upload">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="apiKey">API Key</label>
|
||||
<input type="password" id="apiKey" placeholder="Enter your secret API key">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Default Upload Preferences</h2>
|
||||
<div class="grid-2">
|
||||
<div class="field">
|
||||
<label for="rating">Default Rating</label>
|
||||
<select id="rating">
|
||||
<option value="">Default (Untagged)</option>
|
||||
<option value="sfw">Safe (sfw)</option>
|
||||
<option value="nsfw">Questionable (nsfw)</option>
|
||||
<option value="nsfl">Explicit (nsfl)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="visibility">Default Visibility</label>
|
||||
<select id="visibility">
|
||||
<option value="0">Public (0)</option>
|
||||
<option value="1">Unlisted (1)</option>
|
||||
<option value="2">Private (2)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="tags">Default Tags</label>
|
||||
<input type="text" id="tags" placeholder="e.g. browser, web, media">
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="isOc">
|
||||
<span>Mark uploads as Original Content (OC)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="urlType">Copied Link Format</label>
|
||||
<select id="urlType">
|
||||
<option value="post">f0ckm Post URL (Viewer page)</option>
|
||||
<option value="direct">Direct File Link (Raw image/media file)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" id="btn-save" class="btn-save">Save Settings</button>
|
||||
<span id="save-toast" class="toast hidden">✓ Settings saved successfully!</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
63
browser-extension/options/options.js
Normal file
63
browser-extension/options/options.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const DEFAULT_SETTINGS = {
|
||||
uploadMode: "desktop",
|
||||
apiUrl: "https://f0ckm.com/api/v2/upload",
|
||||
apiKey: "",
|
||||
rating: "s",
|
||||
tags: "",
|
||||
visibility: "0",
|
||||
isOc: false,
|
||||
urlType: "post"
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const form = document.getElementById("settings-form");
|
||||
const modeDesktop = document.getElementById("mode-desktop");
|
||||
const modeDirect = document.getElementById("mode-direct");
|
||||
const apiUrl = document.getElementById("apiUrl");
|
||||
const apiKey = document.getElementById("apiKey");
|
||||
const rating = document.getElementById("rating");
|
||||
const visibility = document.getElementById("visibility");
|
||||
const tags = document.getElementById("tags");
|
||||
const isOc = document.getElementById("isOc");
|
||||
const urlType = document.getElementById("urlType");
|
||||
const saveToast = document.getElementById("save-toast");
|
||||
|
||||
// Load existing settings
|
||||
chrome.storage.sync.get(DEFAULT_SETTINGS, (items) => {
|
||||
if (items.uploadMode === "direct") {
|
||||
modeDirect.checked = true;
|
||||
} else {
|
||||
modeDesktop.checked = true;
|
||||
}
|
||||
|
||||
apiUrl.value = items.apiUrl || "";
|
||||
apiKey.value = items.apiKey || "";
|
||||
rating.value = items.rating || "s";
|
||||
visibility.value = items.visibility !== undefined ? String(items.visibility) : "0";
|
||||
tags.value = items.tags || "";
|
||||
isOc.checked = !!items.isOc;
|
||||
urlType.value = items.urlType || "post";
|
||||
});
|
||||
|
||||
// Save settings on form submit
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const newSettings = {
|
||||
uploadMode: modeDirect.checked ? "direct" : "desktop",
|
||||
apiUrl: apiUrl.value.trim(),
|
||||
apiKey: apiKey.value.trim(),
|
||||
rating: rating.value,
|
||||
visibility: visibility.value,
|
||||
tags: tags.value.trim(),
|
||||
isOc: isOc.checked,
|
||||
urlType: urlType.value
|
||||
};
|
||||
|
||||
chrome.storage.sync.set(newSettings, () => {
|
||||
saveToast.classList.remove("hidden");
|
||||
setTimeout(() => {
|
||||
saveToast.classList.add("hidden");
|
||||
}, 2500);
|
||||
});
|
||||
});
|
||||
});
|
||||
317
browser-extension/popup/popup.css
Normal file
317
browser-extension/popup/popup.css
Normal file
@@ -0,0 +1,317 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 340px;
|
||||
min-width: 340px;
|
||||
min-height: 240px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #121318;
|
||||
color: #e2e4ec;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px 14px;
|
||||
background: #191b22;
|
||||
border-bottom: 1px solid #282a36;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
color: #ffffff;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
background: #181920;
|
||||
padding: 4px;
|
||||
margin: 10px 14px 4px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #242634;
|
||||
}
|
||||
|
||||
.mode-option {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mode-option input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mode-pill {
|
||||
display: block;
|
||||
padding: 6px 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #8c92a4;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.mode-option input:checked + .mode-pill {
|
||||
background: #2b2f42;
|
||||
color: #4da6ff;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 12px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.active-tab-card {
|
||||
background: #191b26;
|
||||
border: 1px solid #282c3f;
|
||||
border-radius: 9px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.site-badge {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: #0066ff22;
|
||||
color: #4da6ff;
|
||||
border: 1px solid #0066ff55;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.site-badge.youtube {
|
||||
background: #ff000022;
|
||||
color: #ff4d4d;
|
||||
border-color: #ff000055;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab-url-preview {
|
||||
font-size: 10px;
|
||||
color: #727a90;
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background: #111218;
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 9px 12px;
|
||||
border-radius: 7px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 11px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0066ff, #0044cc);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(0, 102, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #1a75ff, #0052e6);
|
||||
box-shadow: 0 4px 14px rgba(0, 102, 255, 0.5);
|
||||
}
|
||||
|
||||
.btn-primary.youtube-style {
|
||||
background: linear-gradient(135deg, #e60000, #b30000);
|
||||
box-shadow: 0 2px 10px rgba(230, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary.youtube-style:hover {
|
||||
background: linear-gradient(135deg, #ff1a1a, #cc0000);
|
||||
box-shadow: 0 4px 14px rgba(230, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.btn-accent {
|
||||
background: #252836;
|
||||
color: #4da6ff;
|
||||
border: 1px solid #363b50;
|
||||
}
|
||||
|
||||
.btn-accent:hover {
|
||||
background: #2e3346;
|
||||
color: #80c0ff;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 8px;
|
||||
font-size: 11px;
|
||||
width: auto;
|
||||
background: #0066ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: #555c70;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.divider::before, .divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
border-bottom: 1px solid #222532;
|
||||
}
|
||||
|
||||
.divider span {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
flex: 1;
|
||||
background: #181a24;
|
||||
border: 1px solid #282c3d;
|
||||
color: #ffffff;
|
||||
padding: 8px 10px;
|
||||
border-radius: 7px;
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
border-color: #0066ff;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: #181a24;
|
||||
border: 1px solid #252838;
|
||||
border-radius: 7px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-card.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #2e3448;
|
||||
border-top-color: #0066ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 11px;
|
||||
color: #a0a6b8;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.result-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.result-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-link {
|
||||
flex: 1;
|
||||
font-size: 11px;
|
||||
color: #4da6ff;
|
||||
text-decoration: underline;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 220px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-link:hover {
|
||||
color: #80c0ff;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px 14px;
|
||||
background: #15161d;
|
||||
border-top: 1px solid #20222d;
|
||||
font-size: 10px;
|
||||
color: #5b6276;
|
||||
}
|
||||
61
browser-extension/popup/popup.html
Normal file
61
browser-extension/popup/popup.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>f0ckm Uploader</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
<script src="popup.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="brand">
|
||||
<img src="../icons/icon32.png" alt="f0ckm Logo" class="brand-icon">
|
||||
<span class="brand-name">f0ckm Uploader</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mode-selector">
|
||||
<label class="mode-option">
|
||||
<input type="radio" name="uploadMode" value="desktop" id="mode-desktop">
|
||||
<span class="mode-pill">Desktop App (f0ckm://)</span>
|
||||
</label>
|
||||
<label class="mode-option">
|
||||
<input type="radio" name="uploadMode" value="direct" id="mode-direct">
|
||||
<span class="mode-pill">Direct API</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div id="active-tab-card" class="active-tab-card">
|
||||
<div class="card-top">
|
||||
<span id="site-badge" class="site-badge">🌐 Web Page</span>
|
||||
<span id="tab-title" class="tab-title">Loading current tab...</span>
|
||||
</div>
|
||||
<div id="tab-url-preview" class="tab-url-preview">https://...</div>
|
||||
<button id="btn-upload-tab" class="btn btn-primary btn-lg">
|
||||
<span class="btn-icon">🚀</span> <span id="btn-upload-text">Upload to f0ckm API</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider"><span>OR PASTE CUSTOM URL</span></div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="url" id="url-input" placeholder="https://youtube.com/watch?v=..." autocomplete="off" spellcheck="false">
|
||||
<button id="btn-upload-url" class="btn btn-accent">Upload URL</button>
|
||||
</div>
|
||||
|
||||
<div id="status-card" class="status-card hidden">
|
||||
<div id="status-spinner" class="spinner hidden"></div>
|
||||
<div id="status-text" class="status-text"></div>
|
||||
<div id="result-container" class="result-container hidden">
|
||||
<a id="result-link" href="#" target="_blank" class="result-link">https://...</a>
|
||||
<button id="btn-copy-result" class="btn btn-sm">Copy Link</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>f0ckm Integration v2.0</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
184
browser-extension/popup/popup.js
Normal file
184
browser-extension/popup/popup.js
Normal file
@@ -0,0 +1,184 @@
|
||||
function initPopup() {
|
||||
const modeDesktop = document.getElementById("mode-desktop");
|
||||
const modeDirect = document.getElementById("mode-direct");
|
||||
const btnUploadTab = document.getElementById("btn-upload-tab");
|
||||
const btnUploadText = document.getElementById("btn-upload-text");
|
||||
const btnUploadUrl = document.getElementById("btn-upload-url");
|
||||
const urlInput = document.getElementById("url-input");
|
||||
const statusCard = document.getElementById("status-card");
|
||||
const statusSpinner = document.getElementById("status-spinner");
|
||||
const statusText = document.getElementById("status-text");
|
||||
const resultContainer = document.getElementById("result-container");
|
||||
const resultLink = document.getElementById("result-link");
|
||||
const btnCopyResult = document.getElementById("btn-copy-result");
|
||||
const siteBadge = document.getElementById("site-badge");
|
||||
const tabTitle = document.getElementById("tab-title");
|
||||
const tabUrlPreview = document.getElementById("tab-url-preview");
|
||||
|
||||
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;
|
||||
} else {
|
||||
if (modeDesktop) modeDesktop.checked = true;
|
||||
}
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
if (modeDesktop) {
|
||||
modeDesktop.addEventListener("change", () => {
|
||||
chrome.storage.sync.set({ uploadMode: "desktop" });
|
||||
});
|
||||
}
|
||||
if (modeDirect) {
|
||||
modeDirect.addEventListener("change", () => {
|
||||
chrome.storage.sync.set({ uploadMode: "direct" });
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Query Tab Info immediately
|
||||
try {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (tabs && tabs[0]) {
|
||||
populateTabInfo(tabs[0]);
|
||||
} else {
|
||||
chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs2) => {
|
||||
if (tabs2 && tabs2[0]) {
|
||||
populateTabInfo(tabs2[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
function populateTabInfo(tab) {
|
||||
if (!tab) return;
|
||||
activeTabUrl = tab.url || tab.pendingUrl || "";
|
||||
if (tabTitle) tabTitle.textContent = tab.title || "Active Page";
|
||||
if (tabUrlPreview) tabUrlPreview.textContent = activeTabUrl || "No URL available";
|
||||
|
||||
const isYouTube = activeTabUrl.includes("youtube.com") || activeTabUrl.includes("youtu.be");
|
||||
if (isYouTube) {
|
||||
if (siteBadge) {
|
||||
siteBadge.textContent = "▶ YouTube";
|
||||
siteBadge.classList.add("youtube");
|
||||
}
|
||||
if (btnUploadText) btnUploadText.textContent = "Upload YouTube Video to f0ckm";
|
||||
if (btnUploadTab) btnUploadTab.classList.add("youtube-style");
|
||||
} else {
|
||||
if (siteBadge) {
|
||||
siteBadge.textContent = "🌐 Web Page";
|
||||
siteBadge.classList.remove("youtube");
|
||||
}
|
||||
if (btnUploadText) btnUploadText.textContent = "Upload Page URL to f0ckm";
|
||||
if (btnUploadTab) btnUploadTab.classList.remove("youtube-style");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Upload Active Tab Button Click Handler
|
||||
if (btnUploadTab) {
|
||||
btnUploadTab.addEventListener("click", () => {
|
||||
if (activeTabUrl && (activeTabUrl.startsWith("http://") || activeTabUrl.startsWith("https://"))) {
|
||||
triggerUpload(activeTabUrl);
|
||||
} else {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const tab = tabs ? tabs[0] : null;
|
||||
const url = tab ? (tab.url || tab.pendingUrl) : "";
|
||||
if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
|
||||
activeTabUrl = url;
|
||||
triggerUpload(url);
|
||||
} else {
|
||||
showStatus("Could not get active tab URL.", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Custom pasted URL Upload Button Click Handler
|
||||
if (btnUploadUrl) {
|
||||
btnUploadUrl.addEventListener("click", () => {
|
||||
const url = urlInput ? urlInput.value.trim() : "";
|
||||
if (url) {
|
||||
triggerUpload(url);
|
||||
} else {
|
||||
showStatus("Please enter a valid URL.", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (urlInput) {
|
||||
urlInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && btnUploadUrl) {
|
||||
btnUploadUrl.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCopyResult) {
|
||||
btnCopyResult.addEventListener("click", () => {
|
||||
const linkUrl = resultLink ? resultLink.href : "";
|
||||
if (linkUrl && linkUrl !== "#" && !linkUrl.endsWith("#")) {
|
||||
navigator.clipboard.writeText(linkUrl);
|
||||
btnCopyResult.textContent = "Copied!";
|
||||
setTimeout(() => {
|
||||
btnCopyResult.textContent = "Copy Link";
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showStatus(text, loading = false) {
|
||||
if (!statusCard) return;
|
||||
statusCard.classList.remove("hidden");
|
||||
if (statusText) statusText.textContent = text;
|
||||
if (loading) {
|
||||
if (statusSpinner) statusSpinner.classList.remove("hidden");
|
||||
if (resultContainer) resultContainer.classList.add("hidden");
|
||||
} else {
|
||||
if (statusSpinner) statusSpinner.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccessResult(finalUrl) {
|
||||
if (!statusCard) return;
|
||||
statusCard.classList.remove("hidden");
|
||||
if (statusSpinner) statusSpinner.classList.add("hidden");
|
||||
if (statusText) statusText.textContent = "Upload Successful! Click link to open:";
|
||||
if (resultLink) {
|
||||
resultLink.href = finalUrl;
|
||||
resultLink.textContent = finalUrl;
|
||||
}
|
||||
if (resultContainer) resultContainer.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function triggerUpload(url) {
|
||||
const currentMode = (modeDirect && modeDirect.checked) ? "direct" : "desktop";
|
||||
|
||||
if (currentMode === "desktop") {
|
||||
showStatus("Sending URL to f0ckm desktop app...", true);
|
||||
chrome.runtime.sendMessage({ action: "upload_url", url: url }, () => {
|
||||
showStatus("URL sent to f0ckm app! Check system tray / notifications.", false);
|
||||
});
|
||||
} else {
|
||||
showStatus("Sending URL to f0ckm API...", true);
|
||||
chrome.runtime.sendMessage({ action: "upload_url", url: url }, (response) => {
|
||||
if (response && response.resultUrl) {
|
||||
showSuccessResult(response.resultUrl);
|
||||
} else {
|
||||
showStatus("URL Upload queued! Link copied to clipboard.", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initPopup);
|
||||
} else {
|
||||
initPopup();
|
||||
}
|
||||
Reference in New Issue
Block a user