Discussions : la liste se dessine par pages de 40

Repris d'AJEAN 0.15.6, pour la partie qui nous concerne. Côté serveur, la
liste était déjà légère (l'index ne porte que des métadonnées, aucune
conversation n'est relue) ; c'est le DOM qui coûtait : des centaines de
lignes construites d'un coup à chaque rafraîchissement de la liste.

On en dessine 40, puis la suite quand la ligne « afficher N de plus »
arrive à l'écran (IntersectionObserver, ou clic). La discussion active
reste toujours visible, même au-delà. La recherche filtre toujours la
liste ENTIÈRE, qui reste en mémoire ; changer la requête repart de la
première page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-02 23:05:34 +02:00
1 parent f7a4ee71e7
commit 04137411fa
3 files changed
+64 -2

No files matched your search

+32 -1
View File
@@ -1746,6 +1746,9 @@ html[data-files="1"] #files-btn{color:var(--accent)}
color:var(--dim);letter-spacing:-.01em;overflow:visible}
.side .preset:hover .conv-when{opacity:0}
.conv-empty{font-size:12px;color:var(--dim);padding:8px 11px}
.conv-more{display:block;width:100%;font:inherit;font-size:12px;color:var(--dim);background:none;
border:0;padding:8px 11px;text-align:left;cursor:pointer}
.conv-more:hover{color:var(--text)}
/* Discussion au travail : un anneau qui tourne, à gauche du titre. C'est le seul
élément animé de la liste — il désigne sans ambiguïté le fil sur lequel un
agent est en train de travailler, y compris depuis un autre appareil. */
@@ -9127,6 +9130,12 @@ let CONVS = [], CONV_ACTIVE = '';
// ligne doit déjà s'animer si un agent travaille (page rechargée en cours de
// route, ou ouverte depuis un autre appareil).
let CONV_BUSY = false;
// Liste paginée (AJEAN 0.15.6) : avec des centaines de discussions, construire
// toutes les lignes d'un coup figeait l'ouverture du panneau. On en dessine
// CONV_PAGE, puis la suite quand le bas de la liste devient visible. La
// recherche, elle, filtre toujours TOUTE la liste (CONVS est en mémoire).
const CONV_PAGE = 40;
let CONV_SHOWN = CONV_PAGE, CONV_LAST_Q = '', convMoreObs = null;
async function loadConversations(){
let r;
@@ -9149,6 +9158,7 @@ function renderConversations(){
if(!cont) return;
const q = (document.getElementById('conv-search')?.value || '').trim().toLowerCase();
cont.textContent = '';
if(q !== CONV_LAST_Q){ CONV_LAST_Q = q; CONV_SHOWN = CONV_PAGE; }
const list = q
? CONVS.filter(c => (c.title || 'Nouvelle discussion').toLowerCase().includes(q))
: CONVS;
@@ -9161,7 +9171,10 @@ function renderConversations(){
convSyncBusy();
return;
}
for(const c of list){
// La discussion active reste toujours visible, même loin dans la liste.
const ai = list.findIndex(c => c.id === CONV_ACTIVE);
const shown = Math.max(CONV_SHOWN, ai + 1);
for(const c of list.slice(0, shown)){
const row = document.createElement('div');
row.className = 'preset' + (c.id === CONV_ACTIVE ? ' active' : '');
// L'identifiant est porté par la ligne : convSyncBusy retrouve ainsi CELLE
@@ -9199,10 +9212,28 @@ function renderConversations(){
row.append(info, when, acts);
cont.appendChild(row);
}
if(list.length > shown) convMoreRow(cont, list.length - shown);
syncTopbarTitle();
convSyncBusy();
}
// Ligne « N de plus » en bas de la liste : un clic, ou son arrivée à l'écran
// (IntersectionObserver), dessine la page suivante.
function convMoreRow(cont, left){
const more = document.createElement('button');
more.type = 'button';
more.className = 'conv-more';
more.textContent = 'afficher ' + Math.min(left, CONV_PAGE) + ' de plus (' + left + ' restantes)';
const next = () => { CONV_SHOWN += CONV_PAGE; renderConversations(); };
more.onclick = next;
cont.appendChild(more);
if(convMoreObs) convMoreObs.disconnect();
if(window.IntersectionObserver){
convMoreObs = new IntersectionObserver(es => { if(es.some(e => e.isIntersecting)){ convMoreObs.disconnect(); next(); } });
convMoreObs.observe(more);
}
}
// --- Discussion au travail : anneau qui tourne ------------------------------
// Une seule génération tourne à la fois, et c'est forcément sur la discussion
// OUVERTE (le serveur n'a qu'un fil en mémoire) : l'indicateur va donc sur la
+29 -1
View File
@@ -19,6 +19,12 @@ let CONVS = [], CONV_ACTIVE = '';
// ligne doit déjà s'animer si un agent travaille (page rechargée en cours de
// route, ou ouverte depuis un autre appareil).
let CONV_BUSY = false;
// Liste paginée (AJEAN 0.15.6) : avec des centaines de discussions, construire
// toutes les lignes d'un coup figeait l'ouverture du panneau. On en dessine
// CONV_PAGE, puis la suite quand le bas de la liste devient visible. La
// recherche, elle, filtre toujours TOUTE la liste (CONVS est en mémoire).
const CONV_PAGE = 40;
let CONV_SHOWN = CONV_PAGE, CONV_LAST_Q = '', convMoreObs = null;
async function loadConversations(){
let r;
@@ -41,6 +47,7 @@ function renderConversations(){
if(!cont) return;
const q = (document.getElementById('conv-search')?.value || '').trim().toLowerCase();
cont.textContent = '';
if(q !== CONV_LAST_Q){ CONV_LAST_Q = q; CONV_SHOWN = CONV_PAGE; }
const list = q
? CONVS.filter(c => (c.title || 'Nouvelle discussion').toLowerCase().includes(q))
: CONVS;
@@ -53,7 +60,10 @@ function renderConversations(){
convSyncBusy();
return;
}
for(const c of list){
// La discussion active reste toujours visible, même loin dans la liste.
const ai = list.findIndex(c => c.id === CONV_ACTIVE);
const shown = Math.max(CONV_SHOWN, ai + 1);
for(const c of list.slice(0, shown)){
const row = document.createElement('div');
row.className = 'preset' + (c.id === CONV_ACTIVE ? ' active' : '');
// L'identifiant est porté par la ligne : convSyncBusy retrouve ainsi CELLE
@@ -91,10 +101,28 @@ function renderConversations(){
row.append(info, when, acts);
cont.appendChild(row);
}
if(list.length > shown) convMoreRow(cont, list.length - shown);
syncTopbarTitle();
convSyncBusy();
}
// Ligne « N de plus » en bas de la liste : un clic, ou son arrivée à l'écran
// (IntersectionObserver), dessine la page suivante.
function convMoreRow(cont, left){
const more = document.createElement('button');
more.type = 'button';
more.className = 'conv-more';
more.textContent = 'afficher ' + Math.min(left, CONV_PAGE) + ' de plus (' + left + ' restantes)';
const next = () => { CONV_SHOWN += CONV_PAGE; renderConversations(); };
more.onclick = next;
cont.appendChild(more);
if(convMoreObs) convMoreObs.disconnect();
if(window.IntersectionObserver){
convMoreObs = new IntersectionObserver(es => { if(es.some(e => e.isIntersecting)){ convMoreObs.disconnect(); next(); } });
convMoreObs.observe(more);
}
}
// --- Discussion au travail : anneau qui tourne ------------------------------
// Une seule génération tourne à la fois, et c'est forcément sur la discussion
// OUVERTE (le serveur n'a qu'un fil en mémoire) : l'indicateur va donc sur la
+3
View File
@@ -1711,6 +1711,9 @@ html[data-files="1"] #files-btn{color:var(--accent)}
color:var(--dim);letter-spacing:-.01em;overflow:visible}
.side .preset:hover .conv-when{opacity:0}
.conv-empty{font-size:12px;color:var(--dim);padding:8px 11px}
.conv-more{display:block;width:100%;font:inherit;font-size:12px;color:var(--dim);background:none;
border:0;padding:8px 11px;text-align:left;cursor:pointer}
.conv-more:hover{color:var(--text)}
/* Discussion au travail : un anneau qui tourne, à gauche du titre. C'est le seul
élément animé de la liste — il désigne sans ambiguïté le fil sur lequel un
agent est en train de travailler, y compris depuis un autre appareil. */