- Router api_v2.py (~100 endpoints) : tokens, users, workspaces/members, collections, pages, proprietes, vues/dashboards, commentaires/mentions, notifications, favoris/tags/recents, partage/publish, historique, sprints, templates, export/import, forges, recherche FTS, admin, webhooks CRUD - Helpers api_v2_helpers.py : Bearer unifie (sha256/expires_at/extension_devices), scopes hierarchiques read<write<admin, pagination + X-Total-Count, ISO-8601, RFC 7807, idempotence, audit, rate-limit par token - Migration 20 : api_tokens.scopes/expires_at, webhook_deliveries, api_audit_log, idempotency_keys - main.py : handler d'erreurs unifie StarletteHTTPException, /docs + /redoc - config : PUBLIC_API_INSECURE_OK (dev only), API_V2_RATE_LIMIT_PER_TOKEN - OpenAPI docs/openapi-v2.json (402 chemins), tests/test_public_api_v2.py (24) - Docs : CHANGELOG (v6.2.0/6.2.1 clipper + v6.3.0), ROADMAP, API_GUIDE_V6, V6_Web_Clipper, README, ARCHITECTURE, /help - Suite complete 668 verte, ruff OK
591 lines
24 KiB
Markdown
591 lines
24 KiB
Markdown
# V6.2.0 — Web Clipper : Extension Navigateur
|
|
|
|
> **Statut** : COMPLETED — livré v6.2.0 (2026-09-19) + polish v6.2.1 (2026-09-20) sur `main` (`0b25164`, `ea19d1d`)
|
|
> **Date** : 2026-09-15 conception → 2026-09-19 implémentation
|
|
> **Route** : `feat/v6-web-clipper` → `develop` → `main` ✅ mergé
|
|
> **Dépendances** : v4.0.0 Share & Publish, v5.5.0 Embeds & Rich Media, v5.2.0 OAuth (GitHub)
|
|
|
|
---
|
|
|
|
## 1. Vision & objectifs
|
|
|
|
Créer une **extension de navigateur** qui permet aux utilisateurs de capturer du contenu web (articles, pages, images, bookmarks) directement dans FlowDeck. Le Web Clipper agit comme un pont entre le web et FlowDeck, transformant n'importe quelle page web en une page FlowDeck.
|
|
|
|
### Objectifs
|
|
|
|
| Critère | Cible |
|
|
|---------|-------|
|
|
| Navigateurs supportés | Chrome, Firefox, Edge, Safari |
|
|
| Types de capture | Article complet, sélection, bookmark, screenshot |
|
|
| Temps de capture | < 2 s (page simple), < 5 s (page complexe) |
|
|
| Authentification | OAuth via popup (sans mot de passe) |
|
|
| Format d'import | Markdown + liens + images inline |
|
|
| Compatibilité | Fonctionne même si FlowDeck est fermé |
|
|
|
|
---
|
|
|
|
## 2. Architecture
|
|
|
|
### 2.1 Composants
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ NAVIGATEUR │
|
|
│ │
|
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
│ │ EXTENSION (Manifest V3) │ │
|
|
│ │ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ │ │
|
|
│ │ │ Content │ │ Popup UI │ │ Background │ │ │
|
|
│ │ │ Script │ │ (sidebar │ │ Script │ │ │
|
|
│ │ │ (clipping) │ │ panel) │ │ (OAuth + sync) │ │ │
|
|
│ │ └──────┬──────┘ └──────┬──────┘ └────────┬─────────┘ │ │
|
|
│ │ │ │ │ │ │
|
|
│ │ └────────┬───────┘ │ │ │
|
|
│ │ ▼ │ │ │
|
|
│ │ ┌────────────────┐ │ │ │
|
|
│ │ │ Content │ │ │ │
|
|
│ │ │ Extractor │ │ │ │
|
|
│ │ │ (Readability) │ │ │ │
|
|
│ │ └────────┬───────┘ │ │ │
|
|
│ └─────────────────┼──────────────────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ POST /api/v2/web-clipper/clip │ │
|
|
└────────────────────┼─────────────────────────────────────────┘
|
|
│
|
|
┌────────────────────▼─────────────────────────────────────────┐
|
|
│ FASTAPI (Server) │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ routers/web_clipper.py │ │
|
|
│ │ ├─ POST /api/v2/web-clipper/clip — recevoir la clip │ │
|
|
│ │ ├─ GET /api/v2/web-clipper/status — stat OAuth │ │
|
|
│ │ └─ POST /api/v2/web-clipper/auth/verify — vérifier │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ services/web_clipper.py │ │
|
|
│ │ ├─ extract_article() — Readability.js / Mercury Parser │ │
|
|
│ │ ├─ extract_selection() — sélection HTML → Markdown │ │
|
|
│ │ ├─ create_page_from_clip() — créer la page FlowDeck │ │
|
|
│ │ ├─ download_images() — télécharger les images inline │ │
|
|
│ │ └─ generate_thumbnail() — preview image │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Manifest V3 de l'extension
|
|
|
|
### 3.1 `manifest.json`
|
|
|
|
```json
|
|
{
|
|
"manifest_version": 3,
|
|
"name": "FlowDeck Web Clipper",
|
|
"description": "Capturez du contenu web directement dans FlowDeck",
|
|
"version": "1.0.0",
|
|
"permissions": [
|
|
"activeTab",
|
|
"storage",
|
|
"scripting",
|
|
"contextMenus"
|
|
],
|
|
"host_permissions": [
|
|
"<all_urls>",
|
|
"https://flowdeck.local/*",
|
|
"https://flowdeck.dracodev.net/*"
|
|
],
|
|
"background": {
|
|
"service_worker": "background.js"
|
|
},
|
|
"action": {
|
|
"default_popup": "popup.html",
|
|
"default_icon": {
|
|
"16": "icons/icon-16.png",
|
|
"32": "icons/icon-32.png",
|
|
"48": "icons/icon-48.png",
|
|
"128": "icons/icon-128.png"
|
|
}
|
|
},
|
|
"content_scripts": [
|
|
{
|
|
"matches": ["<all_urls>"],
|
|
"js": ["content.js"],
|
|
"css": ["clipper.css"],
|
|
"run_at": "document_idle"
|
|
}
|
|
],
|
|
"web_accessible_resources": [
|
|
{
|
|
"resources": ["reader-mode.js", "readability.js"],
|
|
"matches": ["<all_urls>"]
|
|
}
|
|
],
|
|
"icons": {
|
|
"16": "icons/icon-16.png",
|
|
"32": "icons/icon-32.png",
|
|
"48": "icons/icon-48.png",
|
|
"128": "icons/icon-128.png"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Endpoints serveur
|
|
|
|
### 4.1 Routeur `app/routers/web_clipper.py`
|
|
|
|
```
|
|
POST /api/v2/web-clipper/clip — Recevoir une capture et créer une page
|
|
GET /api/v2/web-clipper/status — Vérifier l'authentification de l'extension
|
|
POST /api/v2/web-clipper/auth/verify — Vérifier le token OAuth de l'extension
|
|
POST /api/v2/web-clipper/auth/callback — OAuth callback pour l'extension
|
|
```
|
|
|
|
### 4.2 Service `app/services/web_clipper.py`
|
|
|
|
```python
|
|
class WebClipperService:
|
|
"""Traitement des captures web."""
|
|
|
|
async def process_clip(self, clip_data: ClipPayload, user_id: int) -> dict:
|
|
"""Traite une capture complète et crée la page FlowDeck.
|
|
|
|
ClipPayload:
|
|
- url: str (URL source)
|
|
- title: str (titre extrait)
|
|
- content: str (HTML ou Markdown)
|
|
- content_type: str ('article' | 'selection' | 'bookmark' | 'screenshot')
|
|
- images: list[dict] (URLs + base64 data)
|
|
- metadata: dict (og:title, og:description, author, date_published)
|
|
- workspace_id: int | None
|
|
- collection_id: int | None
|
|
- parent_page_id: int | None
|
|
"""
|
|
|
|
async def extract_article(self, html: str, url: str) -> dict:
|
|
"""Extrait le contenu principal d'un article via Readability.js."""
|
|
# Retourne: {title, content (HTML), text_content (Markdown), images[]}
|
|
|
|
async def extract_selection(self, html: str, selection_html: str) -> dict:
|
|
"""Extrait la sélection de texte de la page."""
|
|
# Retourne: {content: HTML, text_content: Markdown}
|
|
|
|
async def create_page_from_clip(self, clip_data: dict, user_id: int) -> dict:
|
|
"""Crée une page FlowDeck à partir d'une capture.
|
|
Inclut la création d'images inline (upload vers /api/upload)."""
|
|
|
|
async def download_images(self, image_urls: list[str]) -> list[str]:
|
|
"""Télécharge les images et retourne les URLs locales."""
|
|
```
|
|
|
|
### 4.3 Payload de la capture
|
|
|
|
```json
|
|
{
|
|
"url": "https://example.com/article",
|
|
"title": "Titre de l'article",
|
|
"content": "<article>...contenu HTML...</article>",
|
|
"content_type": "article",
|
|
"content_format": "html",
|
|
"images": [
|
|
{"src": "https://example.com/image1.jpg", "alt": "Description", "base64": null},
|
|
{"src": "https://example.com/image2.png", "alt": null, "base64": "data:image/png;base64,..."}
|
|
],
|
|
"metadata": {
|
|
"og_title": "Titre Open Graph",
|
|
"og_description": "Description de l'article",
|
|
"author": "Auteur",
|
|
"date_published": "2026-09-01",
|
|
"site_name": "Example.com",
|
|
"favicon_url": "https://example.com/favicon.ico"
|
|
},
|
|
"target_workspace_id": 42,
|
|
"target_collection_id": null,
|
|
"target_page_id": null,
|
|
"tags": ["lecture", "important"],
|
|
"create_as_draft": false
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Contenu de l'extension côté client
|
|
|
|
### 5.1 Content Script — Extraction du contenu
|
|
|
|
```javascript
|
|
// content.js — injecté dans toutes les pages
|
|
|
|
class FlowDeckClipper {
|
|
constructor() {
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// 1. Bouton flottant "Clip to FlowDeck"
|
|
this.createFloatButton();
|
|
|
|
// 2. Menu contextuel (clic droit)
|
|
this.createContextMenu();
|
|
|
|
// 3. Keyboard shortcut (Ctrl+Shift+C)
|
|
this.registerShortcut();
|
|
}
|
|
|
|
createFloatButton() {
|
|
const btn = document.createElement('button');
|
|
btn.className = 'fd-clipper-btn';
|
|
btn.innerHTML = '📌 Clip to FlowDeck';
|
|
btn.addEventListener('click', () => this.openClipper());
|
|
document.body.appendChild(btn);
|
|
}
|
|
|
|
async openClipper() {
|
|
const url = window.location.href;
|
|
const html = document.documentElement.outerHTML;
|
|
const selection = window.getSelection().toString();
|
|
|
|
// Envoyer au service worker
|
|
const clipData = { url, html, selection, title: document.title };
|
|
await chrome.runtime.sendMessage({ action: 'clip', data: clipData });
|
|
}
|
|
|
|
createContextMenu() {
|
|
// Cliquez sur une sélection → menu contextuel "Send to FlowDeck"
|
|
document.addEventListener('contextmenu', (e) => {
|
|
const selection = window.getSelection().toString();
|
|
if (selection.length > 0) {
|
|
const menuItem = document.createElement('div');
|
|
menuItem.className = 'fd-context-menu';
|
|
menuItem.innerHTML = '<span>📌 Clip selection to FlowDeck</span>';
|
|
menuItem.addEventListener('click', () => this.clipSelection(selection));
|
|
document.body.appendChild(menuItem);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.2 Content Extractor — Readability.js
|
|
|
|
L'extension embarque une version simplifiée de **Readability.js** (Mozilla) pour extraire le contenu principal des articles :
|
|
|
|
```javascript
|
|
// reader-mode.js — extraction du contenu principal
|
|
// Version embarquée simplifiée basée sur l'algorithme Readability
|
|
|
|
function extractArticle(html) {
|
|
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
// Algorithme : trouver le meilleur candidat basé sur la longueur de texte,
|
|
// les balises <article>, <main>, <div role="article">, etc.
|
|
// Retourne: { title, content, textContent }
|
|
}
|
|
```
|
|
|
|
### 5.3 Background Script — OAuth et Sync
|
|
|
|
```javascript
|
|
// background.js — Service worker de l'extension
|
|
|
|
// Stockage local des tokens et config
|
|
const STORAGE_KEY = 'flowdeck_clipper';
|
|
|
|
chrome.runtime.onInstalled.addListener(() => {
|
|
chrome.storage.local.set({ [STORAGE_KEY]: { authenticated: false } });
|
|
});
|
|
|
|
// ── Gestion des messages ──
|
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
if (message.action === 'clip') {
|
|
handleClip(message.data).then(result => sendResponse(result));
|
|
return true; // async
|
|
}
|
|
if (message.action === 'auth') {
|
|
handleAuth().then(result => sendResponse(result));
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// ── Clip → Serveur ──
|
|
async function handleClip(data) {
|
|
const token = await getToken();
|
|
if (!token || !isAuthenticated()) {
|
|
return { error: 'not_authenticated', requiresAuth: true };
|
|
}
|
|
|
|
const response = await fetch('https://flowdeck.local/api/v2/web-clipper/clip', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
return response.json();
|
|
}
|
|
|
|
// ── OAuth Flow ──
|
|
async function handleAuth() {
|
|
// Popup OAuth → redirect to /auth/sso/callback → receive token
|
|
// Stocker le token dans chrome.storage.local
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Authentification de l'extension
|
|
|
|
### 6.1 OAuth Device Flow ou Popup
|
|
|
|
L'extension utilise le même OAuth2 que le web :
|
|
|
|
```
|
|
Extension → Background Script → Popup Auth
|
|
1. User clique "Connect"
|
|
2. Popup opens → GET /auth/login (avec redirect vers extension)
|
|
3. OAuth callback → token reçu
|
|
4. Token stocké dans chrome.storage.local (encrypted)
|
|
5. Tous les clips sont authentifiés automatiquement
|
|
```
|
|
|
|
### 6.2 Token storage
|
|
|
|
```python
|
|
# Côté serveur : table pour les tokens d'extension
|
|
ALTER TABLE api_tokens ADD COLUMN source TEXT DEFAULT 'web';
|
|
-- Valeurs: 'web', 'clipper', 'mobile', 'api'
|
|
-- Permet de révoquer uniquement les tokens d'extension
|
|
```
|
|
|
|
```sql
|
|
CREATE TABLE extension_tokens (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
device_id TEXT NOT NULL, -- ID unique de l'extension installée
|
|
token_hash TEXT NOT NULL,
|
|
scopes TEXT DEFAULT 'read,write',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
last_used_at TIMESTAMP,
|
|
revoked INTEGER NOT NULL DEFAULT 0,
|
|
UNIQUE(user_id, device_id)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Types de capture supportés
|
|
|
|
### 7.1 Article complet
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ URL: https://example.com/article │
|
|
│ │
|
|
│ ┌─── Article Content ────────────┐ │
|
|
│ │ Title extracted │ │
|
|
│ │ Author: John Doe │ │
|
|
│ │ Date: 2026-09-15 │ │
|
|
│ │ │ │
|
|
│ │ Main article text... │ │
|
|
│ │ │ │
|
|
│ │ ![#image1.jpg] │ │
|
|
│ │ Caption from article │ │
|
|
│ │ │ │
|
|
│ │ ![#image2.png] │ │
|
|
│ └────────────────────────────────┘ │
|
|
│ │
|
|
│ Tags: [lecture, tech] │
|
|
│ Target: Workspace X → Collection Y │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### 7.2 Sélection de texte
|
|
|
|
```
|
|
L'utilisateur sélectionne du texte sur une page → clic droit → "Clip to FlowDeck"
|
|
→ Crée une page FlowDeck avec le texte sélectionné formaté en Markdown
|
|
→ Ajoute un lien vers la page source en bas
|
|
```
|
|
|
|
### 7.3 Bookmark
|
|
|
|
```
|
|
L'utilisateur clique sur le bouton clipper sur une page sans contenu riche
|
|
→ Crée un "bookmark card" (comme v5.5.0 bookmark cards)
|
|
→ URL, titre, favicon, description OG → page FlowDeck
|
|
```
|
|
|
|
### 7.4 Screenshot
|
|
|
|
```
|
|
L'utilisateur sélectionne une zone → capture en screenshot →
|
|
→ Image uploadée dans FlowDeck → page avec l'image + annotation possible
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Interface utilisateur du serveur
|
|
|
|
### 8.1 Extension management dans Settings
|
|
|
|
Nouvelle section dans `settings.html` → onglet **"Extensions"** :
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ Connected Extensions │
|
|
│──────────────────────────────────────────────────────│
|
|
│ FlowDeck Web Clipper │
|
|
│ ├── Device: Chrome — Windows │
|
|
│ ├── Connected: 2026-09-10 14:30 │
|
|
│ ├── Clips this month: 12 │
|
|
│ └── [Revoke Access] │
|
|
│ │
|
|
│ FlowDeck Web Clipper │
|
|
│ ├── Device: Firefox — macOS │
|
|
│ ├── Connected: 2026-09-08 09:15 │
|
|
│ ├── Clips this month: 5 │
|
|
│ └── [Revoke Access] │
|
|
│ │
|
|
│ [Download Chrome Extension] [Download Firefox Add-on] │
|
|
└──────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 8.2 Page de téléchargement : `/extensions`
|
|
|
|
Nouvelle page publique offrant les liens de téléchargement de l'extension :
|
|
|
|
```
|
|
GET /extensions — Page de téléchargement
|
|
GET /extensions/chrome — Chrome Web Store link / CRX download
|
|
GET /extensions/firefox — Firefox Add-on link / XPI download
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Tables de base de données
|
|
|
|
### 9.1 Modifications
|
|
|
|
```sql
|
|
-- Extension tracking
|
|
CREATE TABLE extension_devices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
extension_name TEXT NOT NULL, -- 'chrome', 'firefox', 'edge', 'safari'
|
|
device_id TEXT NOT NULL, -- UUID unique par installation
|
|
device_name TEXT, -- "Chrome — Windows 11"
|
|
token_hash TEXT NOT NULL,
|
|
scopes TEXT DEFAULT 'read,write',
|
|
last_used_at TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
revoked INTEGER NOT NULL DEFAULT 0,
|
|
UNIQUE(user_id, extension_name, device_id)
|
|
);
|
|
|
|
CREATE INDEX idx_ext_devices_user ON extension_devices(user_id);
|
|
CREATE INDEX idx_ext_devices_device ON extension_devices(device_id);
|
|
|
|
-- Extension activity log
|
|
CREATE TABLE extension_clips (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
device_id TEXT NOT NULL,
|
|
clip_type TEXT NOT NULL, -- 'article', 'selection', 'bookmark', 'screenshot'
|
|
source_url TEXT NOT NULL,
|
|
target_page_id INTEGER REFERENCES collection_pages(id),
|
|
target_workspace_id INTEGER,
|
|
title TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_clips_user ON extension_clips(user_id, created_at);
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Sécurité
|
|
|
|
| Menace | Contre-mesure |
|
|
|--------|---------------|
|
|
| Extension malveillante | Device ID unique + token par device, pas de partage de tokens |
|
|
| CSRF via extension | Origin checking dans le server-side validation |
|
|
| Content injection | Sanitisation HTML côté serveur avant création de page |
|
|
| Données sensibles | Pas de cookies envoyés à l'extension, uniquement tokens OAuth |
|
|
| Rate limiting | Max 50 clips/heure par device |
|
|
| Large payload | Max 10 MB par clip (images incluses) |
|
|
|
|
### 10.1 Content Security Policy
|
|
|
|
```python
|
|
# Extension CSP étendu
|
|
# Ajouter dans le CSP de l'app :
|
|
# connect-src: ws: https://flowdeck.local https://api.flowdeck.local
|
|
# Content-Security-Policy header pour les pages d'extension
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Tests
|
|
|
|
| Test | Description | Outil |
|
|
|------|-------------|-------|
|
|
| Clip article | Capturer un article → créer une page FlowDeck | Playwright + HTTP test |
|
|
| Clip sélection | Sélectionner du texte → créer une page avec le texte | Playwright |
|
|
| Clip bookmark | Cliquer sur un bookmark → créer une carte bookmark | Unit test |
|
|
| Auth flow | Extension OAuth → token → clip | Integration test |
|
|
| Image download | Article avec images → images téléchargées et inline | Unit test |
|
|
| Multiple devices | 2 extensions connectées → clips séparés | Integration test |
|
|
| Revoke | Révoquer un device → clips rejetés | Unit test |
|
|
| Rate limit | 51 clips → 429 Too Many Requests | Unit test |
|
|
| Content sanitization | HTML malveillant → page propre créée | Security test |
|
|
| Readability extraction | Page complexe → contenu principal extrait | Unit test |
|
|
|
|
---
|
|
|
|
## 12. Checklist d'implémentation — ✅ LIVRÉ (v6.2.0/6.2.1)
|
|
|
|
1. ✅ **`app/routers/web_clipper.py`** — `POST /clip`, `GET /status`, `POST /auth/verify`, `GET/DELETE /devices` + page `/extensions`
|
|
2. ✅ **`app/services/web_clipper.py`** — `sanitize_html`, `html_to_blocks`, `extract_article`, `create_page_from_clip`, `register_device`, `log_clip`, rate-limit 50/h
|
|
3. ✅ **Migrations DB (19)** — `extension_devices`, `extension_clips` (index `idx_ext_*`)
|
|
4. ✅ **Extraction embarquée** dans le content script (heuristique article/sélection)
|
|
5. ✅ **Extension côté client** — `extension/` + `static/extension/` : manifest.json, content.js, background.js, popup.html/js, clipper.css, icônes, `flowdeck-clipper.zip`
|
|
6. ✅ **Settings UI** — section Extensions dans `settings.html`
|
|
7. ✅ **Page `/extensions`** — téléchargement + liste devices/clips
|
|
8. ✅ **Auth** — session cookie OU Bearer `api_tokens` / `extension_devices` (token montré une fois)
|
|
9. ✅ **Content sanitization** — nettoyage HTML côté serveur (`sanitize_html`)
|
|
10. ✅ **Tests** — `tests/test_web_clipper.py` (16 tests)
|
|
11. ⚠️ **Packaging** — bundle `.zip` servi ; soumission Chrome Web Store / Firefox Add-on **non faite** (distribution manuelle « load unpacked »)
|
|
12. ✅ **Documentation** — page `/extensions`, `/help`, ce guide
|
|
|
|
---
|
|
|
|
## 13. Dépendances Python
|
|
|
|
```
|
|
# requirements.txt additions pour v6.0.0 Web Clipper
|
|
beautifulsoup4>=4.12 # HTML parsing / extraction
|
|
lxml>=4.9 # XML/HTML parser rapide
|
|
html2text>=2024.2.26 # HTML → Markdown conversion
|
|
requests>=2.32 # Download images dans le service
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Références
|
|
|
|
- [Chrome Extension Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/)
|
|
- [Firefox Add-on Development](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons)
|
|
- [Readability.js (Mozilla)](https://github.com/mozilla/readability)
|
|
- [Mercury Parser](https://github.com/postlight/mercury-parser)
|
|
- [OAuth 2.0 Device Flow](https://datatracker.ietf.org/doc/html/rfc8628)
|
|
- `app/services/web_clipper.py` — service de traitement (livré)
|
|
- `app/routers/web_clipper.py` — endpoints clip/auth (livré)
|
|
- `app/templates/settings.html` — section Extensions (livrée)
|
|
- `extension/` + `static/extension/` — bundle navigateur (livré)
|
|
- `docs/API_GUIDE_V6.md` — référence API v2
|
|
- `ROADMAP.md` — v6.2.0 Web Clipper (livré)
|