4.8 KiB
4.8 KiB
#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 <img>
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 extensionsIMAGE_EXTENSIONS,AUDIO_EXTENSIONS,VIDEO_EXTENSIONS(+ helpersis_image/is_audio/is_video/is_media/media_mime_type). Socle réutilisé par #109.attachment_indexer.pyetapi_file_view()ne dupliquent plus la liste.- Les extensions image sont intégrées à
SUPPORTED_EXTENSIONS(indexer.py) avec une branche binaire :_scan_vaultet_index_single_file_syncindexent nom / taille / mtime et ne lisent jamais les octets (content: "",content_preview: ""). Le TF-IDF reste donc propre et aucuneUnicodeDecodeErrorne pollue les logs. - Le reindex watchdog suit automatiquement (même filtre d'extensions).
- Le filtre
ext:png/ext:jpgde la recherche avancée est opérationnel dès lors que les images entrent dans l'index. /api/dashboardexposeimage_count(par vault) ettotal_images(global), séparés dufile_countgénéral.
B. Affichage standalone (correctif)
api_file_view()génère désormaissrc="/api/image/{vault}?path=…"(chemin URL-encodé) au lieu de/raw.viewer.jsutilise le même endpoint (plus de bouton « Plein écran » cassé).- Sécurité SVG :
/api/image(et le repli de/api/media/.../thumb) ajouteContent-Security-Policy: sandboxpour 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<img>, 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 avecpillow>=10.0, mise en cache sous<OBSIGATE_DATA_DIR>/.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 vianaturalWidth/Height, taille, type MIME, chemin, date), lightbox plein écran (fondrgba(0,0,0,.9),Échappour quitter) ; EXT_ICONS: extensions image → icône Lucideimage;- compatible Split View (#75) : rendu dans
getContentArea()du panneau actif.
E. Tests
tests/test_image_api.py: octets + MIME sur/api/image, en-têtesandboxdes SVG, URL/api/imagedans le HTML deapi_file_view, encodage des chemins accentués, miniatures WebP + repli SVG + refus non-image.tests/test_image_indexing.py: image présente danslist_directoryetpath_index, indexée aveccontent == "", pertinence watchdog, compteurs dashboard, filtreext:png.tests/frontend/image-viewer.test.mjs: helpers purs (clampImageZoom,isImagePath,buildImageUrl) + vérifications statiques (zoom/pan/nav, absence de/rawdans la visionneuse, CSS, icônes).tests/e2e/image-viewer.spec.js: ouverture d'une image depuis l'arborescence, réponse/api/imageenimage/png, zoom molette, navigation par la pellicule (fixturestest_vault/sample-image.png+sample-vector.svg).
Limitations connues
- HEIC/HEIF (iPhone) : non décodables par les navigateurs → hors scope ;
pillow-heifenvisagé 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.