mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-15 03:08:56 +02:00
Une pièce jointe n'affichait que son nom de fichier, et une image posée par l'IA sortait à sa taille NATIVE — .chatimg portait la classe mais aucune règle CSS, donc une capture remplissait la colonne et poussait le texte hors de vue. - pièce jointe image : vignette dans le composeur (objectURL local, rien n'est encore parti au serveur) et tuile 200x160 dans le message envoyé, hydratée comme les captures (fetch authentifié + blob:) ; - images de l'IA plafonnées à 280px de haut ; - clic sur n'importe quelle image = loupe plein écran (Échap ou clic sur le fond pour fermer). La source est relue AU CLIC, l'hydratation remplaçant le src par un blob: après coup. align-items:flex-start sur .msg-files au passage : une pastille texte voisine d'une vignette s'étirait à sa hauteur et, arrondie à 999px, devenait un gros ovale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
2.7 KiB
JavaScript
59 lines
2.7 KiB
JavaScript
// ===== Loupe d'image =======================================================
|
||
// Les images du fil sont PLAFONNÉES (CSS .chatimg / .msg-img) : sans plafond,
|
||
// une capture sortait à sa taille native et mangeait tout l'écran, ce qui rendait
|
||
// la conversation illisible. Le plafond ne se paie donc pas en information : un
|
||
// clic rouvre l'image ici, aussi grande que la fenêtre le permet.
|
||
//
|
||
// On réutilise la source DÉJÀ chargée par la vignette (blob: hydraté par
|
||
// hydrateImages, ou objectURL local d'une pièce jointe en attente) : rien n'est
|
||
// re-téléchargé, et ça marche derrière la clé de pilotage — qu'une balise <img>
|
||
// ne sait pas porter, d'où le blob: en premier lieu.
|
||
function lightboxEl(){
|
||
let el=document.getElementById('lightbox');
|
||
if(el) return el;
|
||
el=document.createElement('div');
|
||
el.id='lightbox';
|
||
el.innerHTML='<button type="button" class="lb-x" title="fermer" aria-label="fermer">×</button>'
|
||
+'<img alt=""><div class="lb-name"></div>';
|
||
// Clic sur le FOND (ou sur le ×) = fermer ; clic sur l'image = ne rien faire,
|
||
// sinon on referme dès qu'on veut la regarder de près.
|
||
el.onclick=e=>{ if(e.target===el || e.target.classList.contains('lb-x')) closeLightbox(); };
|
||
document.body.appendChild(el);
|
||
return el;
|
||
}
|
||
// Capture (3e argument à true) : sans elle, Échap serait d'abord vu par les
|
||
// écouteurs des modales déjà en place et la loupe ne se fermerait pas.
|
||
function _lbKey(e){
|
||
if(e.key==='Escape'){ e.preventDefault(); e.stopPropagation(); closeLightbox(); }
|
||
}
|
||
function openLightbox(src, name){
|
||
if(!src) return;
|
||
const el=lightboxEl(), img=el.querySelector('img');
|
||
img.src=src;
|
||
img.alt=name||'';
|
||
el.querySelector('.lb-name').textContent=name||'';
|
||
showModal(el);
|
||
document.addEventListener('keydown', _lbKey, true);
|
||
}
|
||
function closeLightbox(){
|
||
const el=document.getElementById('lightbox'); if(!el) return;
|
||
document.removeEventListener('keydown', _lbKey, true);
|
||
hideModal(el);
|
||
// La source n'est lâchée qu'APRÈS la transition : la vider tout de suite
|
||
// laissait un cadre vide clignoter pendant la fermeture.
|
||
setTimeout(()=>{
|
||
if(!el.classList.contains('show')) el.querySelector('img').removeAttribute('src');
|
||
}, 200);
|
||
}
|
||
// Rend une image cliquable (idempotent : renderBody reconstruit le DOM à chaque
|
||
// delta du streaming, et hydrateImages nous rappelle sur les mêmes éléments).
|
||
//
|
||
// On lit currentSrc/src AU CLIC et non à la liaison : l'hydratation remplace la
|
||
// source par un blob: après coup, et figer l'URL /api/chat/image ici rouvrirait
|
||
// une image que le navigateur n'a pas le droit de charger seul.
|
||
function bindZoom(img, name){
|
||
if(!img || img.dataset.zoom) return;
|
||
img.dataset.zoom='1';
|
||
img.addEventListener('click', ()=>openLightbox(img.currentSrc||img.src, name||img.alt||''));
|
||
}
|