Files
williamandClaude Sonnet 5 ecd483f352
Build and deploy / test-python (push) Successful in 7m48s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / lint-python (push) Successful in 5m44s
Build and deploy / lint-js (push) Failing after 1m52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 5m5s
Implemente un systeme de pages pour le support de formation
Retour utilisateur : "il faut implementer un systeme de page". Un
support est desormais compose de PLUSIEURS pages (_document_pages),
chacune un document independant affiche seul sur le canevas -- chaque
element appartient a exactement une page via page_id (document_engine/
elements/*, routes/document/document_element_add.py/document_render.py
revalident desormais un page_id explicite). Migration automatique et
silencieuse pour les supports crees avant cette fonctionnalite
(db/supports/ensure_document_pages_schema.py, meme convention que les
ensure_X_schema.py existants) : leurs elements deviennent tous les
enfants d'une "Page 1" creee a la volee, aucune perte de contenu.

Nouveau paquet document_engine/pages/ (add/list/get/rename/move/delete)
et 4 routes dediees (routes/document/document_page_*.py) -- supprimer
la DERNIERE page restante est refuse (garde-fou pose a la route, meme
decoupage que routes/game/screens/screen_delete.py cote jeu, jamais
dans la fonction bas niveau).

Cote editeur : une bande d'ONGLETS au-dessus du canevas (jamais un
panneau lateral, choix explicite de l'utilisateur) -- clic pour changer
de page, double-clic pour renommer (contenteditable), glisser pour
reordonner, "+" pour ajouter, "x" pour supprimer. Changer de page vide
la pile Annuler/Retablir (une commande empilee sur une autre page n'a
plus de sens). Mode Apercu : navigation Page precedente/suivante avec
indicateur "Page X / N" (choix explicite : page par page, pas de
defilement continu), jamais affichee s'il n'y a qu'une seule page.

Verifie : suite pytest complete (702 tests, dont 14 nouveaux pour les
routes de pages), simulation DOM reelle (jsdom, 25 assertions couvrant
tout le cycle de vie cote client -- creation/bascule/renommage/
reordonnancement/suppression de page, portee correcte des elements par
page, pile Annuler/Retablir videe au changement de page, pilule de
navigation en Apercu), et un test de fumee HTTP reel contre le serveur
de dev en marche (creation/ajout d'element/rendu/renommage/suppression
d'une page, refus de supprimer la derniere page, page inconnue -> 404).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 13:24:23 +02:00

59 lines
2.8 KiB
Python

from db.supports import connect_support, ensure_document_pages_schema
def move_document_element(slug: str, element_id: int, new_parent_id: int | None, new_index: int) -> None:
"""Réinsertion réelle d'un élément — appelée par l'algorithme de
glisser-déposer du moteur de layout (voir docs/plan/PLAN.md) : dépose
dans un nouveau groupe de frères (nouvelle rangée, ou top-niveau) à
une position précise, pas juste "monter/descendre" d'un cran. Renumérote
intégralement les deux groupes concernés (ancien et nouveau, ou un seul
si inchangé) plutôt que de décaler un par un, 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, voir static/document/js/document-editor.js)."""
ensure_document_pages_schema(slug)
conn = connect_support(slug)
row = conn.execute("SELECT parent_id FROM _document_elements WHERE id = ?", (element_id,)).fetchone()
if not row:
conn.close()
return
old_parent_id = row["parent_id"]
if old_parent_id == new_parent_id:
siblings = [
r["id"]
for r in conn.execute(
"SELECT id FROM _document_elements WHERE parent_id IS ? AND id != ? ORDER BY order_index",
(old_parent_id, element_id),
).fetchall()
]
siblings.insert(max(0, min(new_index, len(siblings))), element_id)
for index, sibling_id in enumerate(siblings):
conn.execute("UPDATE _document_elements SET order_index = ? WHERE id = ?", (index, sibling_id))
else:
old_siblings = [
r["id"]
for r in conn.execute(
"SELECT id FROM _document_elements WHERE parent_id IS ? AND id != ? ORDER BY order_index",
(old_parent_id, element_id),
).fetchall()
]
for index, sibling_id in enumerate(old_siblings):
conn.execute("UPDATE _document_elements SET order_index = ? WHERE id = ?", (index, sibling_id))
new_siblings = [
r["id"]
for r in conn.execute(
"SELECT id FROM _document_elements WHERE parent_id IS ? ORDER BY order_index",
(new_parent_id,),
).fetchall()
]
new_siblings.insert(max(0, min(new_index, len(new_siblings))), element_id)
conn.execute("UPDATE _document_elements SET parent_id = ? WHERE id = ?", (new_parent_id, element_id))
for index, sibling_id in enumerate(new_siblings):
conn.execute("UPDATE _document_elements SET order_index = ? WHERE id = ?", (index, sibling_id))
conn.commit()
conn.close()