Files
flowdeck/extension/popup.js
T
bruno 0b251649e5
FlowDeck CI / lint (push) Successful in 1m11s
FlowDeck CI / test (push) Failing after 8m32s
FlowDeck CI / docker (push) Skipped
feat: v6.2.0 Web Clipper — extension navigateur (capture article/selection/bookmark/screenshot)
- Service app/services/web_clipper.py: sanitize HTML, html->blocks, extraction article, creation page workspace-aware, rate limit 50/h, device registration
- Router app/routers/web_clipper.py: POST /api/v2/web-clipper/clip, GET /status, POST /auth/verify, GET/DELETE /devices, GET /extensions (download page), auth via session ou Bearer (api_tokens / extension_devices)
- Migration 19: extension_devices + extension_clips (+ indexes)
- Extension Manifest V3: content.js (floating button, selection), background.js (clip + contextMenus), popup.html/js, clipper.css, icons
- Settings UI: onglet Extensions (liste devices, revoke, test clip, liens download), page /extensions
- Tests: 16 tests web_clipper (sanitize, blocks, article/bookmark/selection/screenshot, bearer, rate-limit, devices, extensions page)
- Bump version 6.1.0 -> 6.2.0
2026-09-19 23:26:03 -04:00

77 lines
3.1 KiB
JavaScript

// Popup logic
const $ = (id) => document.getElementById(id);
async function load() {
const cfg = await chrome.runtime.sendMessage({ action: "getConfig" });
$("serverUrl").value = cfg.serverUrl || "http://localhost:8080";
$("token").value = cfg.token || "";
}
async function save() {
const serverUrl = $("serverUrl").value.trim() || "http://localhost:8080";
const token = $("token").value.trim();
// deviceId preserved
const cfg = await chrome.runtime.sendMessage({ action: "getConfig" });
cfg.serverUrl = serverUrl.replace(/\/$/, "");
cfg.token = token;
await chrome.runtime.sendMessage({ action: "setConfig", config: cfg });
show("Saved ✓", "ok");
}
async function verify() {
await save();
const res = await chrome.runtime.sendMessage({ action: "auth" });
if (res && res.authenticated) show(`Connected as ${res.user?.login || "user"} — ${res.devices} device(s)`, "ok");
else if (res && res.authenticated === false) show("Not authenticated — check token / server URL", "err");
else show(JSON.stringify(res).slice(0, 200), "warn");
}
function show(msg, kind) {
const el = $("status");
el.textContent = msg;
el.className = "status " + kind;
}
async function doClip(type) {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab) return;
// Get page data via scripting
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (clipType) => {
const sel = window.getSelection();
const html = document.documentElement.outerHTML;
const selectionHtml = (() => {
if (!sel || sel.rangeCount === 0) return "";
const d = document.createElement("div");
d.appendChild(sel.getRangeAt(0).cloneContents());
return d.innerHTML;
})();
return {
url: location.href,
title: document.title,
html: html,
content_type: sel && sel.toString().trim().length > 20 ? "selection" : clipType,
selection_html: selectionHtml,
metadata: {
og_description: document.querySelector('meta[property="og:description"]')?.content || document.querySelector('meta[name="description"]')?.content || ""
}
};
},
args: [type]
});
const resp = await chrome.runtime.sendMessage({ action: "clip", data: result });
if (resp && resp.status === "ok") show(`Clipped → page ${resp.page_id} ✓`, "ok");
else show(resp.detail || resp.error || "Clip failed", "err");
}
$("save").addEventListener("click", save);
$("verify").addEventListener("click", verify);
$("clipArticle").addEventListener("click", () => doClip("article"));
$("clipBookmark").addEventListener("click", () => doClip("bookmark"));
$("clipSelection").addEventListener("click", () => doClip("selection"));
$("openFlowDeck").addEventListener("click", (e) => { e.preventDefault(); chrome.tabs.create({ url: $("serverUrl").value || "http://localhost:8080" }); });
$("openExtensions").addEventListener("click", (e) => { e.preventDefault(); chrome.tabs.create({ url: ($("serverUrl").value || "http://localhost:8080") + "/extensions" }); });
load();