# #108 — Support complet des images (arborescence, visionneuse, indexation) > **Version livrée :** 2.17.0 · **Statut :** ✅ · **Impact :** 🟡 > **Zone :** backend (`indexer`, `main`, `media_types`, `media_thumbs`) + frontend > (`viewer.js`, `utils.js`, `style.css`). ## Contexte Seul l'affichage *inline* dans un document markdown (`![[image.png]]`) fonctionnait. L'image isolée était **invisible dans l'arborescence** (filtrée par `SUPPORTED_EXTENSIONS`) et son **affichage standalone était cassé** : le `` généré par `api_file_view()` pointait vers `/api/file/{vault}/raw`, un endpoint qui renvoie du **JSON** (`FileRawResponse`) et non des octets d'image. ## Ce qui a été livré ### A. Arborescence & indexation - **`backend/media_types.py`** (nouveau) : source unique des extensions `IMAGE_EXTENSIONS`, `AUDIO_EXTENSIONS`, `VIDEO_EXTENSIONS` (+ helpers `is_image`/`is_audio`/`is_video`/`is_media`/`media_mime_type`). Socle réutilisé par #109. `attachment_indexer.py` et `api_file_view()` ne dupliquent plus la liste. - Les extensions image sont intégrées à `SUPPORTED_EXTENSIONS` (`indexer.py`) **avec une branche binaire** : `_scan_vault` et `_index_single_file_sync` indexent **nom / taille / mtime** et ne lisent **jamais** les octets (`content: ""`, `content_preview: ""`). Le TF-IDF reste donc propre et aucune `UnicodeDecodeError` ne pollue les logs. - Le reindex **watchdog** suit automatiquement (même filtre d'extensions). - Le filtre `ext:png` / `ext:jpg` de la recherche avancée est opérationnel dès lors que les images entrent dans l'index. - `/api/dashboard` expose `image_count` (par vault) et `total_images` (global), séparés du `file_count` général. ### B. Affichage standalone (correctif) - `api_file_view()` génère désormais `src="/api/image/{vault}?path=…"` (chemin URL-encodé) au lieu de `/raw`. - `viewer.js` utilise le même endpoint (plus de bouton « Plein écran » cassé). - **Sécurité SVG** : `/api/image` (et le repli de `/api/media/.../thumb`) ajoute `Content-Security-Policy: sandbox` pour les `.svg`, ce qui empêche l'exécution du JavaScript embarqué quand le fichier est ouvert directement dans un onglet (XSS same-origin). Dans une balise ``, l'en-tête est sans effet. Le middleware de sécurité ne remplace plus une politique stricte posée par une route. ### C. Miniatures - `GET /api/media/{vault}/thumb?path=…&size=…` : miniature **WebP** générée avec `pillow>=10.0`, mise en cache sous `/.obsigate-cache/thumbs/{sha1}.webp`. La clé de cache embarque **mtime + taille**, donc toute édition invalide naturellement la vignette. - Génération dans un thread (`run_in_executor`) avec **timeout 2 s** ; repli sur l'original en cas d'échec. SVG : l'original est servi tel quel (Pillow ne décode pas le SVG) ; GIF/WebP animés : première frame. ### D. Visionneuse `renderImageViewer()` (`frontend/js/viewer.js`) remplace l'ancien rendu minimal : - image centrée `object-fit: contain` ; **zoom molette 0,1×–8×**, **pan au glisser** (Pointer Events), **double-clic = réinitialisation**, raccourcis `+` / `-` / `0` ; - boutons +/−/reset et **badge de zoom** ; - **navigation ←/→** entre les images du même dossier (via `/api/browse`) et **pellicule de miniatures** (`/api/media/.../thumb`, `loading="lazy"`) ; - barre d'outils : « Ouvrir l'original » (nouvel onglet `/api/image`), téléchargement, **panneau métadonnées** repliable (dimensions via `naturalWidth/Height`, taille, type MIME, chemin, date), **lightbox** plein écran (fond `rgba(0,0,0,.9)`, `Échap` pour quitter) ; - `EXT_ICONS` : extensions image → icône Lucide `image` ; - compatible Split View (#75) : rendu dans `getContentArea()` du panneau actif. ### E. Tests - `tests/test_image_api.py` : octets + MIME sur `/api/image`, en-tête `sandbox` des SVG, URL `/api/image` dans le HTML de `api_file_view`, encodage des chemins accentués, miniatures WebP + repli SVG + refus non-image. - `tests/test_image_indexing.py` : image présente dans `list_directory` et `path_index`, indexée avec `content == ""`, pertinence watchdog, compteurs dashboard, filtre `ext:png`. - `tests/frontend/image-viewer.test.mjs` : helpers purs (`clampImageZoom`, `isImagePath`, `buildImageUrl`) + vérifications statiques (zoom/pan/nav, absence de `/raw` dans la visionneuse, CSS, icônes). - `tests/e2e/image-viewer.spec.js` : ouverture d'une image depuis l'arborescence, réponse `/api/image` en `image/png`, zoom molette, navigation par la pellicule (fixtures `test_vault/sample-image.png` + `sample-vector.svg`). ## Limitations connues - **HEIC/HEIF** (iPhone) : non décodables par les navigateurs → hors scope ; `pillow-heif` envisagé en v2. - Le SVG passe par l'original (pas de rendu bitmap côté serveur) : les miniatures de dossiers SVG ne sont pas générées.