Files
Forge-Engine/document_engine/elements/elements.md
T
williamandClaude Sonnet 5 c6e173589f 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>
2026-09-23 11:50:23 +02:00

4.6 KiB

document_engine/elements/

CRUD des éléments d'UNE PAGE d'un support de formation (_document_elements, voir db/supports/create_support.py) — un support est composé de plusieurs pages (voir document_engine/pages/), chaque élément appartient à exactement une page via page_id, jamais partagé entre pages. Un élément est soit un élément de contenu/rangée du flux (parent_id = groupe de frères, toujours dans la MÊME page), soit une forme libre superposée en position absolue (toujours parent_id = NULL).

add_document_element(slug: str, kind: str, page_id: int, parent_id: int | None = None) -> int

Insère un nouvel élément dans page_id, en fin de son groupe de frères (même parent_id, recherché uniquement à l'intérieur de cette page). Pose les attributs de départ via element_default_attributes(kind) (voir document_engine/labels/element_kind_labels.py).

  • Retour : l'id du nouvel élément.
  • Exceptions : aucune levée explicitement ; une connexion invalide (support inexistant) lève l'erreur SQLite sous-jacente.

list_document_elements(slug: str, page_id: int) -> list[dict[str, Any]]

Renvoie tous les éléments de page_id, à plat, triés par (parent_id, order_index) — les éléments top-niveau (parent_id NULL) groupés en premier (tri SQLite : NULL avant toute valeur), puis chaque rangée groupant ses propres enfants. attributes est décodé en dict. Le canevas n'affiche jamais qu'une seule page à la fois, d'où ce filtrage.

  • Retour : liste de dicts (une ligne de table chacun, attributes déjà en JSON décodé).
  • Exceptions : aucune.

get_document_element(slug: str, element_id: int) -> dict[str, Any] | None

Récupère un seul élément par id.

  • Retour : le dict de l'élément, ou None si l'id n'existe pas.
  • Exceptions : aucune.

update_document_element_attributes(slug: str, element_id: int, attributes: dict[str, Any]) -> None

Remplace intégralement le JSON attributes d'un élément — chaque formulaire du panneau Propriétés envoie l'état complet de ses champs, jamais un patch partiel.

  • Retour : aucun.
  • Exceptions : aucune levée explicitement ; un element_id inexistant ne modifie silencieusement aucune ligne (UPDATE sans correspondance).

move_document_element(slug: str, element_id: int, new_parent_id: int | None, new_index: int) -> None

Réinsertion réelle appelée par l'algorithme de glisser-déposer du moteur de layout : dépose l'élément dans un groupe de frères (nouvelle rangée, un groupe existant, ou le top-niveau) à une position précise, puis renumérote intégralement le ou les groupes concernés (ancien et nouveau si le parent change, un seul sinon) pour rester correct même en cas de réordonnancement dans le même groupe. new_parent_id doit toujours désigner un élément de la MÊME page que element_id — jamais vérifié ici (le client ne propose que des cibles de la page actuellement affichée).

  • Retour : aucun.
  • Exceptions : aucune levée explicitement ; un element_id inexistant ne fait rien (retour silencieux après vérification de son existence).

move_document_element_to_page(slug: str, element_id: int, target_page_id: int) -> None

Déplace un élément vers une AUTRE page du même support — utilisé par la pagination automatique (retour utilisateur du 23/09/2026 : quand le contenu déborde d'une page, l'élément en trop est déplacé vers une nouvelle page plutôt que d'y rester tassé). L'élément redevient TOUJOURS top-niveau sur la page cible (parent_id remis à NULL) — une rangée qui existait sur l'ancienne page n'a aucun sens comme enfant d'une rangée de la page cible. Si l'élément déplacé est lui-même une rangée, ses enfants directs (même parent_id) SUIVENT sur la page cible (page_id mis à jour en cascade, parent_id inchangé) — sans cette cascade ils resteraient orphelins d'une page qu'ils n'occupent plus (list_document_elements, filtré par page_id, ne les retrouverait plus). Renumérote les anciens frères après le retrait.

  • Retour : aucun.
  • Exceptions : aucune levée explicitement ; un element_id inexistant ne fait rien (retour silencieux après vérification de son existence).

delete_document_element(slug: str, element_id: int) -> None

Supprime un élément. La contrainte FOREIGN KEY ... ON DELETE CASCADE (voir db/supports/create_support.py) retire automatiquement ses enfants si l'élément supprimé était une rangée.

  • Retour : aucun.
  • Exceptions : aucune levée explicitement ; un element_id inexistant ne modifie silencieusement aucune ligne.