Pagination automatique : le contenu qui déborde part sur une nouvelle page
Retour utilisateur du 23/09/2026 : "si il n'y a plus de place sur la page il faut automatiquement créer une autre page [et y] coller le contenu et amener l'utilisateur sur la page" — remplace le comportement précédent (overflow:hidden, contenu clipsé, à gérer manuellement). - Nouvelle capacité serveur : document_engine.move_document_element_to_page (+ route POST /document/<slug>/elements/<id>/move-to-page) déplace un élément (et ses enfants de rangée en cascade) vers une AUTRE page — jusqu'ici move_document_element ne gérait que le réordonnancement DANS la même page. - Client : forgeDocCheckPageOverflow, appelée à la fin de CHAQUE forgeDocRefreshCanvas (point d'entrée unique après toute mutation) : mesure le débordement réel (scrollHeight vs clientHeight), trouve le premier élément top-niveau qui dépasse le bas de la page (getBoundingClientRect, tient compte du zoom), déplace cet élément et tout ce qui le suit vers une page neuve, puis y bascule l'utilisateur. Jamais déclenché sur une page mini-jeu (toujours seule sur sa page, aucun débordement pertinent à corriger). Vérifié par un test jsdom dédié (géométrie simulée via getBoundingClientRect/scrollHeight/clientHeight, jsdom n'ayant pas de vrai moteur de mise en page) : ordre des déplacements, page inchangée si le contenu tient, page mini-jeu jamais scindée. 6 nouveaux tests Python (document_engine + route). 711/711 tests passent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0e8c6efb06
commit
c6e173589f
@@ -90,6 +90,14 @@ function forgeDocApiDelete(id) {
|
||||
.then((r) => r.json());
|
||||
}
|
||||
|
||||
function forgeDocApiMoveToPage(id, targetPageId) {
|
||||
return fetch(`/document/${window.forgeDocState.slug}/elements/${id}/move-to-page`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ target_page_id: targetPageId }),
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
|
||||
function forgeDocApiRender() {
|
||||
const url = `/document/${window.forgeDocState.slug}/render?page_id=${window.forgeDocState.activePageId}`;
|
||||
return fetch(url).then((r) => r.json());
|
||||
@@ -109,6 +117,46 @@ function forgeDocApplySelectionClass() {
|
||||
if (dom) dom.classList.add('docSelected');
|
||||
}
|
||||
|
||||
function forgeDocCheckPageOverflow() {
|
||||
// Pagination automatique (retour utilisateur du 23/09/2026 : "si il
|
||||
// n'y a plus de place sur la page il faut automatiquement créer une
|
||||
// autre page [et y] coller le contenu [...] et non [...] adapter la
|
||||
// taille du contenu pour rester sur la page") — jamais sur une page
|
||||
// mini-jeu (toujours seul sur sa page par construction, voir
|
||||
// routes/document/document_element_add.py ; .docPageContent y est
|
||||
// délibérément height:100% donc scrollHeight === clientHeight, aucun
|
||||
// débordement réel n'est possible/pertinent à corriger là).
|
||||
const contentEl = document.getElementById('docPageContent');
|
||||
const pageEl = document.getElementById('docPage');
|
||||
if (!contentEl || !pageEl || contentEl.querySelector('.docMinigame')) return Promise.resolve();
|
||||
if (contentEl.scrollHeight <= contentEl.clientHeight + 1) return Promise.resolve();
|
||||
|
||||
// Le PREMIER élément top-niveau qui dépasse le bas de la page, et tout
|
||||
// ce qui le suit dans l'ordre, part ensemble sur une nouvelle page —
|
||||
// measuré via getBoundingClientRect (tient compte du zoom CSS
|
||||
// transform éventuel), jamais une estimation par hauteur cumulée qui
|
||||
// ignorerait marges/gap réels.
|
||||
const topLevel = forgeDocTopLevel();
|
||||
const pageBottom = pageEl.getBoundingClientRect().bottom;
|
||||
let splitIndex = -1;
|
||||
topLevel.forEach((el, index) => {
|
||||
if (splitIndex !== -1) return;
|
||||
const dom = document.querySelector(`#docPage [data-element-id="${el.id}"]`);
|
||||
if (dom && dom.getBoundingClientRect().bottom > pageBottom + 0.5) splitIndex = index;
|
||||
});
|
||||
if (splitIndex === -1) return Promise.resolve();
|
||||
|
||||
const overflowing = topLevel.slice(splitIndex);
|
||||
return forgeDocApiPageAdd().then((result) => {
|
||||
window.forgeDocState.pages.push({
|
||||
id: result.id, title: result.title, order_index: window.forgeDocState.pages.length,
|
||||
});
|
||||
return overflowing
|
||||
.reduce((chain, el) => chain.then(() => forgeDocApiMoveToPage(el.id, result.id)), Promise.resolve())
|
||||
.then(() => forgeDocSwitchPage(result.id));
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocRefreshCanvas() {
|
||||
return forgeDocApiRender().then((data) => {
|
||||
document.getElementById('docPageContent').innerHTML = data.html;
|
||||
@@ -122,7 +170,7 @@ function forgeDocRefreshCanvas() {
|
||||
window.forgeDocState.selectedId = null;
|
||||
forgeDocRenderProps(null);
|
||||
}
|
||||
});
|
||||
}).then(() => forgeDocCheckPageOverflow());
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user