# 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). ## `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.