Supprime la couche de formes libres, déplace la navigation de page dans le panneau gauche
Build and deploy / test-python (push) Successful in 7m43s
Build and deploy / test-js (push) Successful in 57s
Build and deploy / lint-python (push) Successful in 5m29s
Build and deploy / lint-js (push) Failing after 1m16s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 5m42s

Formes libres (rectangle/cercle/triangle/trait) retirées de bout en
bout (bibliothèque, rendu, panneau Propriétés, grille d'accroche,
JS/CSS associés) — fonctionnalité non retenue.

La bande de vignettes visuelles des pages au-dessus du canevas est
remplacée par une section "Pages" dans le panneau de gauche (liste
simple : ajouter/renommer/réordonner (haut/bas)/supprimer), à la
place de l'ex-catégorie "Mise en page" de la bibliothèque. La route
document_edit ne rend plus qu'une seule page (celle affichée) au
chargement, au lieu de toutes les pages pour alimenter les anciennes
vignettes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-21 14:40:33 +02:00
co-authored by Claude Sonnet 5
parent 57c4de3d8a
commit 8dc4b35dcf
11 changed files with 220 additions and 597 deletions
+74 -229
View File
@@ -19,14 +19,6 @@ var FORGE_DOC_STYLE_PRESETS = {
legende: 'Légende',
};
// Même raison que FORGE_DOC_STYLE_PRESETS ci-dessus.
// eslint-disable-next-line no-var, vars-on-top
var FORGE_DOC_SHAPE_KINDS = ['rectangle', 'cercle', 'triangle', 'trait'];
function forgeDocIsShapeKind(kind) {
return FORGE_DOC_SHAPE_KINDS.includes(kind);
}
function forgeDocChildrenOf(parentId) {
return window.forgeDocState.elements
.filter((el) => el.parent_id === parentId)
@@ -116,15 +108,6 @@ function forgeDocRefreshCanvas() {
window.forgeDocState.selectedId = null;
forgeDocRenderProps(null);
}
// La vignette miniature de la page ACTIVE (voir .docPageThumbInner,
// static/document/document-editor.css) reflète le MÊME rendu que le
// canevas principal — seule cette page peut avoir changé tant qu'on
// ne l'a pas quittée (voir forgeDocSwitchPage), jamais besoin de
// reconstruire toute la bande de vignettes ici.
const { activePageId } = window.forgeDocState;
window.forgeDocState.pageThumbsHtml[activePageId] = data.html;
const thumbInner = document.querySelector(`.docPageThumbInner[data-page-id="${activePageId}"]`);
if (thumbInner) thumbInner.innerHTML = data.html;
});
}
@@ -236,8 +219,7 @@ function forgeDocUpdateAttributes(id, newAttributes) {
* ------------------------------------------------------------------- */
function forgeDocFindDropTarget(clientX, clientY) {
const candidates = Array.from(document.querySelectorAll('#docPage [data-element-id]'))
.filter((dom) => !forgeDocIsShapeKind(dom.dataset.kind));
const candidates = Array.from(document.querySelectorAll('#docPage [data-element-id]'));
let best = null;
let bestDist = Infinity;
candidates.forEach((dom) => {
@@ -421,10 +403,13 @@ function forgeDocBindCanvasDropZone() {
/* ---------------------------------------------------------------------
* Pages — un support est composé de plusieurs pages (retour utilisateur
* du 21/09/2026 : "il faut implémenter un système de page"), chacune un
* document indépendant affiché SEUL sur le canevas à la fois. Bande
* d'ONGLETS au-dessus du canevas pour créer/renommer (double-clic)/
* réordonner (glisser)/supprimer (voir templates/document/
* document_edit.html) — jamais un panneau latéral (retour utilisateur).
* document indépendant affiché SEUL sur le canevas à la fois. Section
* dédiée dans le panneau GAUCHE (retour utilisateur du 21/09/2026 :
* "une section qui s'ajoute dans le panneau de gauche pour ajouter une
* page et naviguer entre elles", à la place de l'ex-catégorie "Mise en
* page"/formes libres, supprimée) — une simple liste, jamais de vignette
* visuelle (voir templates/document/document_edit.html, .docPageList) :
* créer/renommer (bouton crayon)/réordonner (boutons haut/bas)/supprimer.
* Changer de page VIDE la pile Annuler/Rétablir (voir forgeDocSwitchPage)
* : une commande empilée sur une autre page rejouerait un appel AJAX
* ciblant des éléments qui ne sont plus affichés, aucun sens à la garder.
@@ -484,7 +469,7 @@ function forgeDocSwitchPage(pageId) {
forgeDocUpdateHistoryButtons();
window.forgeDocState.selectedId = null;
forgeDocRenderProps(null);
forgeDocRenderPageTabs();
forgeDocRenderPageList();
forgeDocUpdatePageNavPill();
return forgeDocRefreshCanvas();
}
@@ -494,11 +479,6 @@ function forgeDocAddPage() {
window.forgeDocState.pages.push({
id: result.id, title: result.title, order_index: window.forgeDocState.pages.length,
});
// Vignette vide (aucun élément sur une page neuve) — forgeDocRefreshCanvas
// la remplacera de toute façon juste après via forgeDocSwitchPage, posé
// ici explicitement plutôt que de compter sur le repli `|| ''` de
// forgeDocRenderPageTabs.
window.forgeDocState.pageThumbsHtml[result.id] = '';
return forgeDocSwitchPage(result.id);
});
}
@@ -507,7 +487,7 @@ function forgeDocRenamePage(pageId, title) {
return forgeDocApiPageRename(pageId, title).then((result) => {
const page = window.forgeDocState.pages.find((p) => p.id === pageId);
if (page) page.title = result.title;
forgeDocRenderPageTabs();
forgeDocRenderPageList();
});
}
@@ -522,15 +502,31 @@ function forgeDocDeletePage(pageId) {
return Promise.resolve();
}
window.forgeDocState.pages = window.forgeDocState.pages.filter((p) => p.id !== pageId);
delete window.forgeDocState.pageThumbsHtml[pageId];
if (window.forgeDocState.activePageId === pageId) {
return forgeDocSwitchPage(forgeDocOrderedPages()[0].id);
}
forgeDocRenderPageTabs();
forgeDocRenderPageList();
return Promise.resolve();
});
}
function forgeDocMovePage(pageId, direction) {
const ordered = forgeDocOrderedPages();
const index = ordered.findIndex((p) => p.id === pageId);
const newIndex = direction === 'up' ? index - 1 : index + 1;
if (newIndex < 0 || newIndex >= ordered.length) return;
forgeDocApiPageMove(pageId, newIndex).then(() => {
const moved = window.forgeDocState.pages.find((p) => p.id === pageId);
const rest = window.forgeDocState.pages
.filter((p) => p.id !== pageId)
.sort((a, b) => a.order_index - b.order_index);
rest.splice(newIndex, 0, moved);
rest.forEach((p, i) => { p.order_index = i; });
window.forgeDocState.pages = rest;
forgeDocRenderPageList();
});
}
function forgeDocSelectAllText(el) {
const range = document.createRange();
range.selectNodeContents(el);
@@ -539,9 +535,10 @@ function forgeDocSelectAllText(el) {
selection.addRange(range);
}
function forgeDocStartRenamingPageTab(titleEl) {
const tab = titleEl.closest('.docPageThumbCard');
const pageId = Number(tab.dataset.pageId);
function forgeDocStartRenamingPage(pageId) {
const item = document.querySelector(`.docPageListItem[data-page-id="${pageId}"]`);
if (!item) return;
const titleEl = item.querySelector('.docPageListTitle');
const original = titleEl.textContent;
titleEl.setAttribute('contenteditable', 'true');
titleEl.focus();
@@ -578,107 +575,60 @@ function forgeDocStartRenamingPageTab(titleEl) {
titleEl.addEventListener('keydown', onKeydown);
}
function forgeDocRenderPageTabs() {
const container = document.getElementById('docPageTabs');
function forgeDocRenderPageList() {
const container = document.getElementById('docPageList');
if (!container) return;
const pages = forgeDocOrderedPages();
container.classList.toggle('docPageTabs--single', pages.length <= 1);
// Le contenu de chaque vignette (.docPageThumbInner) vient du CACHE
// client (window.forgeDocState.pageThumbsHtml, alimenté au chargement
// et tenu à jour par forgeDocRefreshCanvas) — jamais ré-échappé ici :
// c'est déjà du HTML de confiance produit par document_engine.
// render_document côté serveur (même contrat que data.html pour le
// canevas principal), pas du texte libre du créateur.
// div + role="button" ici, jamais <button> : le contenu de la vignette
// (pageThumbsHtml) est le HTML réel de la page, qui peut contenir de
// vrais <button> (Suivant/Recommencer d'un mini-jeu) — un <button> dans
// un <button> est invalide et le parseur HTML referme le bouton
// englobant trop tôt, corrompant l'arbre DOM. L'équivalent clavier
// (Entrée/Espace) est posé plus bas ; inert neutralise les vrais
// boutons copiés dans la vignette (jamais atteignables au clavier).
const cardsHtml = pages.map((p) => `
<div class="docPageThumbCard ${p.id === window.forgeDocState.activePageId ? 'is-active' : ''}"
// div + role="button" ici, jamais <button> : la rangée contient de
// vrais <button> d'action (monter/descendre/renommer/supprimer), qu'un
// <button> englobant ne pourrait pas contenir validement. L'équivalent
// clavier (Entrée/Espace) est posé plus bas.
container.innerHTML = pages.map((p, index) => `
<div class="docPageListItem ${p.id === window.forgeDocState.activePageId ? 'is-active' : ''}"
role="button"
tabindex="0"
data-page-id="${p.id}"
draggable="true">
<span class="docPageThumbFrame">
<span class="docPageThumbInner" data-page-id="${p.id}" inert>${window.forgeDocState.pageThumbsHtml[p.id] || ''}</span>
<span class="docPageThumbDelete" data-page-id="${p.id}" title="Supprimer cette page">✕</span>
</span>
<span class="docPageThumbLabel">${forgeDocEscapeHtml(p.title)}</span>
data-page-id="${p.id}">
<span class="docPageListTitle">${forgeDocEscapeHtml(p.title)}</span>
<div class="docPageListActions">
<button type="button" class="docPageListMove" data-dir="up" data-page-id="${p.id}" aria-label="Déplacer vers le haut" title="Déplacer vers le haut"${index === 0 ? ' disabled' : ''}>↑</button>
<button type="button" class="docPageListMove" data-dir="down" data-page-id="${p.id}" aria-label="Déplacer vers le bas" title="Déplacer vers le bas"${index === pages.length - 1 ? ' disabled' : ''}>↓</button>
<button type="button" class="docPageListRename" data-page-id="${p.id}" aria-label="Renommer cette page" title="Renommer cette page">✎</button>
<button type="button" class="docPageListDelete" data-page-id="${p.id}" aria-label="Supprimer cette page" title="Supprimer cette page"${pages.length <= 1 ? ' disabled' : ''}>✕</button>
</div>
</div>
`).join('');
container.innerHTML = `${cardsHtml}<button type="button" class="docPageThumbCard docPageThumbAddCard" id="docPageTabAdd" aria-label="Ajouter une page" title="Ajouter une page"><span class="docPageThumbFrame docPageThumbAddFrame">+</span></button>`;
container.querySelectorAll('.docPageThumbCard[data-page-id]').forEach((card) => {
card.addEventListener('click', (e) => {
if (e.target.classList.contains('docPageThumbDelete')) return;
forgeDocSwitchPage(Number(card.dataset.pageId));
container.querySelectorAll('.docPageListItem[data-page-id]').forEach((item) => {
item.addEventListener('click', (e) => {
if (e.target.closest('.docPageListActions')) return;
forgeDocSwitchPage(Number(item.dataset.pageId));
});
card.addEventListener('keydown', (e) => {
item.addEventListener('keydown', (e) => {
if (e.key !== 'Enter' && e.key !== ' ') return;
e.preventDefault();
forgeDocSwitchPage(Number(card.dataset.pageId));
forgeDocSwitchPage(Number(item.dataset.pageId));
});
});
container.querySelectorAll('.docPageThumbLabel').forEach((labelEl) => {
labelEl.addEventListener('dblclick', (e) => {
container.querySelectorAll('.docPageListMove').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
forgeDocStartRenamingPageTab(labelEl);
forgeDocMovePage(Number(btn.dataset.pageId), btn.dataset.dir);
});
});
container.querySelectorAll('.docPageThumbDelete').forEach((btn) => {
container.querySelectorAll('.docPageListRename').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
forgeDocStartRenamingPage(Number(btn.dataset.pageId));
});
});
container.querySelectorAll('.docPageListDelete').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
forgeDocDeletePage(Number(btn.dataset.pageId));
});
});
document.getElementById('docPageTabAdd').addEventListener('click', () => forgeDocAddPage());
}
function forgeDocBindPageTabsDrag() {
// Délégation sur le CONTENEUR (jamais les vignettes elles-mêmes,
// recréées à chaque forgeDocRenderPageTabs) : un seul jeu de listeners
// pour toute la session, même principe que forgeDocMotsBindGlobalMouseup.
const container = document.getElementById('docPageTabs');
let draggedId = null;
container.addEventListener('dragstart', (e) => {
const card = e.target.closest('.docPageThumbCard');
if (!card || !card.dataset.pageId) return;
draggedId = Number(card.dataset.pageId);
card.classList.add('is-dragging');
e.dataTransfer.effectAllowed = 'move';
});
container.addEventListener('dragend', (e) => {
const card = e.target.closest('.docPageThumbCard');
if (card) card.classList.remove('is-dragging');
});
container.addEventListener('dragover', (e) => {
if (draggedId !== null) e.preventDefault();
});
container.addEventListener('drop', (e) => {
e.preventDefault();
if (draggedId === null) return;
const targetCard = e.target.closest('.docPageThumbCard[data-page-id]');
const ordered = forgeDocOrderedPages();
const newIndex = targetCard
? ordered.findIndex((p) => p.id === Number(targetCard.dataset.pageId))
: ordered.length - 1;
const movedId = draggedId;
draggedId = null;
forgeDocApiPageMove(movedId, newIndex).then(() => {
const moved = window.forgeDocState.pages.find((p) => p.id === movedId);
const rest = window.forgeDocState.pages
.filter((p) => p.id !== movedId)
.sort((a, b) => a.order_index - b.order_index);
rest.splice(newIndex, 0, moved);
rest.forEach((p, i) => { p.order_index = i; });
window.forgeDocState.pages = rest;
forgeDocRenderPageTabs();
});
});
const addBtn = document.getElementById('docPageListAdd');
if (addBtn) addBtn.onclick = () => forgeDocAddPage();
}
function forgeDocBindPageNavPill() {
@@ -694,59 +644,6 @@ function forgeDocBindPageNavPill() {
});
}
/* ---------------------------------------------------------------------
* Formes libres — position absolue, glissées à la souris (pas de HTML5
* DnD, pas de logique de rangée) avec grille d'accroche réelle.
* ------------------------------------------------------------------- */
// Même raison que FORGE_DOC_STYLE_PRESETS en tête de fichier.
// eslint-disable-next-line no-var, vars-on-top
var FORGE_DOC_SNAP_SIZE = 8;
function forgeDocBindShapeDrag(dom, elementId) {
dom.addEventListener('mousedown', (e) => {
if (window.forgeDocState.previewMode) return;
e.preventDefault();
e.stopPropagation();
forgeDocSelectElement(elementId);
const el = window.forgeDocState.elementsById[elementId];
window.forgeDocState.shapeDrag = {
id: elementId,
startX: e.clientX,
startY: e.clientY,
origX: el.attributes.x,
origY: el.attributes.y,
};
document.getElementById('docPage').classList.add('docPage--dragging-shape');
});
}
function forgeDocBindShapeDragGlobalHandlers() {
document.addEventListener('mousemove', (e) => {
const drag = window.forgeDocState.shapeDrag;
if (!drag) return;
const zoomFactor = window.forgeDocState.zoom / 100;
const dx = (e.clientX - drag.startX) / zoomFactor;
const dy = (e.clientY - drag.startY) / zoomFactor;
const newX = Math.round((drag.origX + dx) / FORGE_DOC_SNAP_SIZE) * FORGE_DOC_SNAP_SIZE;
const newY = Math.round((drag.origY + dy) / FORGE_DOC_SNAP_SIZE) * FORGE_DOC_SNAP_SIZE;
const dom = document.querySelector(`[data-element-id="${drag.id}"]`);
if (dom) { dom.style.left = `${newX}px`; dom.style.top = `${newY}px`; }
drag.lastX = newX;
drag.lastY = newY;
});
document.addEventListener('mouseup', () => {
const drag = window.forgeDocState.shapeDrag;
if (!drag) return;
window.forgeDocState.shapeDrag = null;
document.getElementById('docPage').classList.remove('docPage--dragging-shape');
if (typeof drag.lastX === 'number' && (drag.lastX !== drag.origX || drag.lastY !== drag.origY)) {
const el = window.forgeDocState.elementsById[drag.id];
forgeDocUpdateAttributes(drag.id, { ...el.attributes, x: drag.lastX, y: drag.lastY });
}
});
}
/* ---------------------------------------------------------------------
* Sélection & panneau Propriétés.
* ------------------------------------------------------------------- */
@@ -758,15 +655,11 @@ function forgeDocBindCanvasInteractions() {
e.stopPropagation();
forgeDocSelectElement(elementId);
});
if (forgeDocIsShapeKind(dom.dataset.kind)) {
forgeDocBindShapeDrag(dom, elementId);
} else {
dom.setAttribute('draggable', 'true');
dom.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/forge-doc-move', String(elementId));
e.dataTransfer.effectAllowed = 'move';
});
}
dom.setAttribute('draggable', 'true');
dom.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/forge-doc-move', String(elementId));
e.dataTransfer.effectAllowed = 'move';
});
});
forgeDocInitQuizPlayers();
forgeDocInitAssociationPlayers();
@@ -1625,37 +1518,6 @@ function forgeDocRenderTextProps(panel, el) {
});
}
function forgeDocRenderShapeProps(panel, el) {
const a = el.attributes;
panel.innerHTML = `
${forgeDocPropsHeader('forme')}
<div class="docField"><label for="docShapeLabel">Libellé</label><input type="text" id="docShapeLabel" value="${a.label || ''}"></div>
<div class="docFieldRow"><label for="docShapeX">Position X</label><input type="number" id="docShapeX" value="${a.x}" style="width:90px;"></div>
<div class="docFieldRow"><label for="docShapeY">Position Y</label><input type="number" id="docShapeY" value="${a.y}" style="width:90px;"></div>
<div class="docFieldRow"><label for="docShapeWidth">Largeur</label><input type="number" id="docShapeWidth" value="${a.width}" style="width:90px;"></div>
<div class="docFieldRow"><label for="docShapeHeight">Hauteur</label><input type="number" id="docShapeHeight" value="${a.height}" style="width:90px;"></div>
<div class="docFieldRow"><label for="docShapeRotation">Rotation (°)</label><input type="number" id="docShapeRotation" value="${a.rotation}" style="width:90px;"></div>
<div class="docFieldRow"><label for="docShapeZ">Ordre (z-index)</label><input type="number" id="docShapeZ" value="${a.z_index}" style="width:90px;"></div>
<div class="docFieldRow"><label for="docShapeFill">Couleur de remplissage</label><input type="color" id="docShapeFill" value="${a.fill}"></div>
<div class="docFieldRow"><label for="docShapeStroke">Couleur de bordure</label><input type="color" id="docShapeStroke" value="${a.stroke}"></div>
<div class="docFieldRow"><label for="docShapeStrokeWidth">Épaisseur de bordure</label><input type="number" id="docShapeStrokeWidth" value="${a.stroke_width}" style="width:90px;"></div>
${forgeDocDeleteButtonHtml()}
`;
function patch(partial) { forgeDocUpdateAttributes(el.id, { ...el.attributes, ...partial }); }
document.getElementById('docShapeLabel').addEventListener('change', (e) => patch({ label: e.target.value }));
document.getElementById('docShapeX').addEventListener('change', (e) => patch({ x: Number(e.target.value) }));
document.getElementById('docShapeY').addEventListener('change', (e) => patch({ y: Number(e.target.value) }));
document.getElementById('docShapeWidth').addEventListener('change', (e) => patch({ width: Number(e.target.value) }));
document.getElementById('docShapeHeight').addEventListener('change', (e) => patch({ height: Number(e.target.value) }));
document.getElementById('docShapeRotation').addEventListener('change', (e) => patch({ rotation: Number(e.target.value) }));
document.getElementById('docShapeZ').addEventListener('change', (e) => patch({ z_index: Number(e.target.value) }));
document.getElementById('docShapeFill').addEventListener('input', (e) => patch({ fill: e.target.value }));
document.getElementById('docShapeStroke').addEventListener('input', (e) => patch({ stroke: e.target.value }));
document.getElementById('docShapeStrokeWidth').addEventListener('change', (e) => patch({ stroke_width: Number(e.target.value) }));
}
function forgeDocRenderImageProps(panel, el) {
const a = el.attributes;
panel.innerHTML = `
@@ -2254,8 +2116,7 @@ function forgeDocRenderScenarioProps(panel, el) {
* (retour utilisateur du 21/09/2026 : "passons à un véritable graphe
* visuel", remplace la première version en liste de nœuds + menus
* déroulants). Les nœuds sont des cartes positionnées/déplaçables à la
* souris (glisser, comme les formes libres du canevas — voir
* forgeDocBindShapeDrag) ; les choix qui ont une destination sont
* souris (glisser) ; les choix qui ont une destination sont
* dessinés comme des flèches SVG reliant les deux nœuds, étiquetées par
* le texte du choix. Sélectionner un nœud (clic) ouvre son formulaire
* d'édition (texte + choix) dans l'inspecteur (colonne de droite) —
@@ -2268,7 +2129,7 @@ function forgeDocRenderScenarioProps(panel, el) {
* pourrait diverger de ce que sanitize_scenario_config a réellement
* accepté — SAUF la position pendant un glisser, mise à jour localement
* en continu pour rester fluide, et persistée en un seul appel au
* relâchement de la souris (même principe que forgeDocBindShapeDrag).
* relâchement de la souris.
* ------------------------------------------------------------------- */
let forgeDocScenarioGraphState = null;
@@ -2737,7 +2598,6 @@ function forgeDocRenderProps(el) {
return;
}
if (el.kind === 'titre' || el.kind === 'paragraphe') forgeDocRenderTextProps(panel, el);
else if (forgeDocIsShapeKind(el.kind)) forgeDocRenderShapeProps(panel, el);
else if (el.kind === 'image') forgeDocRenderImageProps(panel, el);
else if (el.kind === 'bouton') forgeDocRenderButtonProps(panel, el);
else if (el.kind === 'row') forgeDocRenderRowProps(panel, el);
@@ -2869,37 +2729,23 @@ function forgeDocInit() {
const root = document.getElementById('docEditor3');
if (!root || !window.FORGE_DOCUMENT) return;
// Capturé AVANT window.forgeDocState (qui n'existe pas encore) : le
// contenu initial de chaque vignette miniature vient du HTML déjà
// rendu par le serveur (voir routes/document/document_edit.py::
// rendered_pages, templates/document/document_edit.html), jamais
// redemandé en AJAX au chargement — seul un futur forgeDocRenderPageTabs
// (qui reconstruit #docPageTabs depuis ce cache) en a besoin.
const initialPageThumbsHtml = {};
document.querySelectorAll('.docPageThumbInner[data-page-id]').forEach((el) => {
initialPageThumbsHtml[Number(el.dataset.pageId)] = el.innerHTML;
});
window.forgeDocState = {
slug: window.FORGE_DOCUMENT.slug,
elements: window.FORGE_DOCUMENT.elements,
elementsById: {},
pages: window.FORGE_DOCUMENT.pages,
activePageId: window.FORGE_DOCUMENT.activePageId,
pageThumbsHtml: initialPageThumbsHtml,
selectedId: null,
undoStack: [],
redoStack: [],
previewMode: false,
zoom: 100,
shapeDrag: null,
};
forgeDocRebuildIndex();
forgeDocBindCanvasInteractions();
forgeDocBindLibraryDragHandlers();
forgeDocBindCanvasDropZone();
forgeDocBindShapeDragGlobalHandlers();
forgeDocBindDeselectOnBackgroundClick();
forgeDocBindZoom();
forgeDocBindWidthPicker();
@@ -2908,8 +2754,7 @@ function forgeDocInit() {
forgeDocBindMobileNav();
forgeDocBindKeyboardShortcuts();
forgeDocBindScenarioTreeModal();
forgeDocRenderPageTabs();
forgeDocBindPageTabsDrag();
forgeDocRenderPageList();
forgeDocBindPageNavPill();
forgeDocUpdateHistoryButtons();