Panneau gauche à onglets : "Pages" (gestion complète) et "Mise en page" (bibliothèque)
Build and deploy / test-python (push) Successful in 10m10s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 4m44s
Build and deploy / lint-js (push) Failing after 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m8s
Build and deploy / test-python (push) Successful in 10m10s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 4m44s
Build and deploy / lint-js (push) Failing after 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m8s
Remplace le carrousel une-page-à-la-fois par un panneau dédié plein hauteur : liste verticale scrollable de toutes les pages, réordonnage par glisser OU boutons haut/bas (accessibilité clavier), renommer (crayon, édition en ligne), supprimer (protégé contre la suppression de la dernière page), ajouter. La bibliothèque d'éléments passe dans un second onglet "Mise en page", contenu inchangé. "Mise en page" actif par défaut au chargement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
14ca9e32a8
commit
b4e4e80b7c
+123
-120
@@ -1162,183 +1162,186 @@ img.docImage {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---- Section "Pages" du 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) — un CARROUSEL
|
||||
une-page-à-la-fois (retour utilisateur du 22/09/2026 : "un seul carré
|
||||
page visible, les flèches ne servent pas qu'à naviguer mais aussi à
|
||||
changer de page") : un seul carré = la page ACTIVE, hauteur fixe quel
|
||||
que soit le nombre de pages. Carte texte (pas de vignette visuelle). ---- */
|
||||
.docPageSection {
|
||||
/* ---- Panneau GAUCHE à ONGLETS (retour utilisateur du 23/09/2026 :
|
||||
"il dois avoir deux onglets [...] un onglet page [...] un onglet mise
|
||||
en page avec les fonctionnalité et la disposition actuel sans la
|
||||
section page") : "Pages" (gestion complète, panneau dédié plein
|
||||
hauteur) et "Mise en page" (bibliothèque d'éléments, inchangée). ---- */
|
||||
.docSidebarTabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* ‹/› CHANGENT la page affichée (voir forgeDocBindPageStripNav) — à ne
|
||||
pas confondre avec les petites flèches ←/→ DANS la carte, qui elles
|
||||
réordonnent la page active parmi ses sœurs sans changer l'affichage. */
|
||||
.docPageStrip {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.docPageStripNav {
|
||||
flex-shrink: 0;
|
||||
width: 22px;
|
||||
border: 1px solid var(--doc-border);
|
||||
border-radius: 6px;
|
||||
background: var(--doc-card);
|
||||
color: var(--doc-muted);
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid var(--doc-border);
|
||||
margin: -20px -16px 0;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.docPageStripNav:hover:not(:disabled) {
|
||||
border-color: var(--doc-accent);
|
||||
.docSidebarTab {
|
||||
flex: 1;
|
||||
padding: 10px 8px;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
background: none;
|
||||
color: var(--doc-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.docSidebarTab:hover {
|
||||
color: var(--doc-text);
|
||||
}
|
||||
|
||||
.docPageStripNav:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
.docSidebarTab.is-active {
|
||||
color: var(--doc-accent);
|
||||
border-bottom-color: var(--doc-accent);
|
||||
}
|
||||
|
||||
.docPageList {
|
||||
.docSidebarTabPanel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22px;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#docTabPanelPages {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.docPageListItem {
|
||||
.docPageManagerList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.docPageRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 8px;
|
||||
border: 1px solid var(--doc-border);
|
||||
border-radius: 8px;
|
||||
background: var(--doc-card);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.docPageListItem:hover {
|
||||
.docPageRow.is-dragging {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.docPageRow:hover {
|
||||
border-color: var(--doc-accent);
|
||||
}
|
||||
|
||||
.docPageListItem.is-active {
|
||||
.docPageRow.is-active {
|
||||
border-color: var(--doc-accent);
|
||||
box-shadow: 0 0 0 1px var(--doc-accent);
|
||||
}
|
||||
|
||||
.docPageListItem:focus-visible {
|
||||
.docPageRow:focus-visible {
|
||||
outline: 2px solid var(--doc-accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.docPageListActions {
|
||||
.docPageRowHandle {
|
||||
flex-shrink: 0;
|
||||
color: var(--doc-muted);
|
||||
font-size: 13px;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.docPageRowNumber {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
background: var(--doc-bg-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--doc-muted);
|
||||
}
|
||||
|
||||
.docPageListActions button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
.docPageRow.is-active .docPageRowNumber {
|
||||
color: var(--doc-accent);
|
||||
}
|
||||
|
||||
/* Titre tronqué, qui défile au survol si tronqué (retour utilisateur du
|
||||
21/09/2026) — .has-overflow n'est posé que si le texte dépasse
|
||||
réellement (voir forgeDocApplyTitleMarquee), jamais un défilement
|
||||
systématique qui ferait sauter les titres courts. */
|
||||
.docPageRowTitle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.docPageRowTitleText {
|
||||
display: inline-block;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
color: var(--doc-muted);
|
||||
transition: transform 0.6s ease;
|
||||
}
|
||||
|
||||
.docPageRow.is-active .docPageRowTitleText {
|
||||
color: var(--doc-text);
|
||||
}
|
||||
|
||||
.docPageRowTitle.has-overflow:hover .docPageRowTitleText {
|
||||
transform: translateX(calc(-1 * var(--marquee-distance, 0px)));
|
||||
}
|
||||
|
||||
.docPageRowTitle[contenteditable="true"] {
|
||||
outline: none;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.docPageRowTitle[contenteditable="true"] .docPageRowTitleText {
|
||||
border-bottom: 1px solid var(--doc-accent);
|
||||
color: var(--doc-text);
|
||||
}
|
||||
|
||||
.docPageRowActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.docPageRowActions button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--doc-muted);
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.docPageListActions button:hover:not(:disabled) {
|
||||
.docPageRowActions button:hover:not(:disabled) {
|
||||
background: var(--doc-bg-2);
|
||||
color: var(--doc-text);
|
||||
}
|
||||
|
||||
.docPageListActions button:disabled {
|
||||
.docPageRowActions button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.docPageListDelete:hover:not(:disabled) {
|
||||
.docPageRowDelete:hover:not(:disabled) {
|
||||
color: var(--doc-quiz-danger-border);
|
||||
}
|
||||
|
||||
.docPageListSquare {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 6px;
|
||||
background: var(--doc-bg-2);
|
||||
border: 1px solid var(--doc-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--doc-muted);
|
||||
}
|
||||
|
||||
.docPageListItem.is-active .docPageListSquare {
|
||||
border-color: var(--doc-accent);
|
||||
color: var(--doc-accent);
|
||||
}
|
||||
|
||||
/* Titre SOUS le carré, tronqué, qui défile au survol si tronqué (retour
|
||||
utilisateur du 21/09/2026) — .has-overflow n'est posé que si le texte
|
||||
dépasse réellement (voir forgeDocApplyTitleMarquee), jamais un
|
||||
défilement systématique qui ferait sauter les titres courts. */
|
||||
.docPageListTitle {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.docPageListTitleText {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--doc-muted);
|
||||
transition: transform 0.6s ease;
|
||||
}
|
||||
|
||||
.docPageListItem.is-active .docPageListTitleText {
|
||||
color: var(--doc-text);
|
||||
}
|
||||
|
||||
.docPageListTitle.has-overflow:hover .docPageListTitleText {
|
||||
transform: translateX(calc(-1 * var(--marquee-distance, 0px)));
|
||||
}
|
||||
|
||||
.docPageListTitle[contenteditable="true"] {
|
||||
outline: none;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.docPageListTitle[contenteditable="true"] .docPageListTitleText {
|
||||
border-bottom: 1px solid var(--doc-accent);
|
||||
color: var(--doc-text);
|
||||
}
|
||||
|
||||
.docPageListCounter {
|
||||
font-size: 10px;
|
||||
color: var(--doc-muted);
|
||||
}
|
||||
|
||||
/* ---- RIGHT SIDEBAR : Propriétés ---- */
|
||||
.docField {
|
||||
display: flex;
|
||||
|
||||
@@ -403,16 +403,18 @@ 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. 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.
|
||||
* document indépendant affiché SEUL sur le canevas à la fois. Onglet
|
||||
* "Pages" dédié dans le panneau GAUCHE (retour utilisateur du
|
||||
* 23/09/2026 : "il dois avoir deux onglets [...] un onglet page qui
|
||||
* liste toute la page, permet de changer l'ordre des pages, d'ajouter,
|
||||
* supprimer ou modifier le nom") — panneau plein-écran (jamais partagé
|
||||
* avec la bibliothèque comme les itérations précédentes), liste
|
||||
* verticale de rangées texte (voir templates/document/document_edit.html,
|
||||
* .docPageManagerList) : créer/renommer (bouton crayon)/réordonner
|
||||
* (glisser OU 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.
|
||||
* ------------------------------------------------------------------- */
|
||||
|
||||
function forgeDocApiPageAdd(title) {
|
||||
@@ -469,7 +471,7 @@ function forgeDocSwitchPage(pageId) {
|
||||
forgeDocUpdateHistoryButtons();
|
||||
window.forgeDocState.selectedId = null;
|
||||
forgeDocRenderProps(null);
|
||||
forgeDocRenderPageList();
|
||||
forgeDocRenderPageManagerList();
|
||||
forgeDocUpdatePageNavPill();
|
||||
return forgeDocRefreshCanvas();
|
||||
}
|
||||
@@ -487,7 +489,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;
|
||||
forgeDocRenderPageList();
|
||||
forgeDocRenderPageManagerList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -505,7 +507,7 @@ function forgeDocDeletePage(pageId) {
|
||||
if (window.forgeDocState.activePageId === pageId) {
|
||||
return forgeDocSwitchPage(forgeDocOrderedPages()[0].id);
|
||||
}
|
||||
forgeDocRenderPageList();
|
||||
forgeDocRenderPageManagerList();
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
@@ -523,7 +525,7 @@ function forgeDocMovePage(pageId, direction) {
|
||||
rest.splice(newIndex, 0, moved);
|
||||
rest.forEach((p, i) => { p.order_index = i; });
|
||||
window.forgeDocState.pages = rest;
|
||||
forgeDocRenderPageList();
|
||||
forgeDocRenderPageManagerList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -536,9 +538,9 @@ function forgeDocSelectAllText(el) {
|
||||
}
|
||||
|
||||
function forgeDocStartRenamingPage(pageId) {
|
||||
const item = document.querySelector(`.docPageListItem[data-page-id="${pageId}"]`);
|
||||
const item = document.querySelector(`.docPageRow[data-page-id="${pageId}"]`);
|
||||
if (!item) return;
|
||||
const titleEl = item.querySelector('.docPageListTitle');
|
||||
const titleEl = item.querySelector('.docPageRowTitle');
|
||||
const original = titleEl.textContent;
|
||||
titleEl.setAttribute('contenteditable', 'true');
|
||||
titleEl.focus();
|
||||
@@ -580,7 +582,7 @@ function forgeDocApplyTitleMarquee(titleEl) {
|
||||
// seuls les titres RÉELLEMENT tronqués défilent au survol — mesuré ici
|
||||
// (scrollWidth vs clientWidth), jamais un défilement systématique qui
|
||||
// ferait sauter les titres courts sans raison.
|
||||
const textEl = titleEl.querySelector('.docPageListTitleText');
|
||||
const textEl = titleEl.querySelector('.docPageRowTitleText');
|
||||
const overflow = textEl.scrollWidth - titleEl.clientWidth;
|
||||
if (overflow > 0) {
|
||||
titleEl.classList.add('has-overflow');
|
||||
@@ -588,92 +590,130 @@ function forgeDocApplyTitleMarquee(titleEl) {
|
||||
}
|
||||
}
|
||||
|
||||
function forgeDocRenderPageList() {
|
||||
const container = document.getElementById('docPageList');
|
||||
function forgeDocRenderPageManagerList() {
|
||||
const container = document.getElementById('docPageManagerList');
|
||||
if (!container) return;
|
||||
const pages = forgeDocOrderedPages();
|
||||
const index = pages.findIndex((p) => p.id === window.forgeDocState.activePageId);
|
||||
const p = pages[index];
|
||||
// Un seul carré visible : la page ACTIVE (retour utilisateur du
|
||||
// 22/09/2026 : "un seul carré page visible") — les grandes flèches
|
||||
// ‹/› de .docPageStrip CHANGENT la page affichée (voir
|
||||
// forgeDocBindPageStripNav), distinctes des petites flèches ←/→
|
||||
// ci-dessous qui réordonnent la page active parmi ses sœurs sans
|
||||
// changer l'affichage. div + role="button" ici, jamais <button> : la
|
||||
// carte contient de vrais <button> d'action, qu'un <button> englobant
|
||||
// ne pourrait pas contenir validement. L'équivalent clavier
|
||||
// (Entrée/Espace) est posé plus bas.
|
||||
container.innerHTML = `
|
||||
<div class="docPageListItem is-active" role="button" tabindex="0" data-page-id="${p.id}">
|
||||
<div class="docPageListActions">
|
||||
<button type="button" class="docPageListMove" data-dir="up" data-page-id="${p.id}" aria-label="Réordonner vers la gauche" title="Réordonner vers la gauche"${index === 0 ? ' disabled' : ''}>←</button>
|
||||
<button type="button" class="docPageListMove" data-dir="down" data-page-id="${p.id}" aria-label="Réordonner vers la droite" title="Réordonner vers la droite"${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 class="docPageListSquare">${index + 1}</div>
|
||||
<span class="docPageListTitle"><span class="docPageListTitleText">${forgeDocEscapeHtml(p.title)}</span></span>
|
||||
<span class="docPageListCounter">${index + 1} / ${pages.length}</span>
|
||||
// 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. Réordonnancement : glisser
|
||||
// (souris, voir forgeDocBindPageManagerDrag) ET boutons ↑/↓ (clavier),
|
||||
// les deux appellent le même forgeDocMovePage.
|
||||
container.innerHTML = pages.map((p, index) => `
|
||||
<div class="docPageRow ${p.id === window.forgeDocState.activePageId ? 'is-active' : ''}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
draggable="true"
|
||||
data-page-id="${p.id}">
|
||||
<span class="docPageRowHandle" aria-hidden="true">⠿</span>
|
||||
<span class="docPageRowNumber">${index + 1}</span>
|
||||
<span class="docPageRowTitle"><span class="docPageRowTitleText">${forgeDocEscapeHtml(p.title)}</span></span>
|
||||
<span class="docPageRowActions">
|
||||
<button type="button" class="docPageRowMove" data-dir="up" data-page-id="${p.id}" aria-label="Monter" title="Monter"${index === 0 ? ' disabled' : ''}>↑</button>
|
||||
<button type="button" class="docPageRowMove" data-dir="down" data-page-id="${p.id}" aria-label="Descendre" title="Descendre"${index === pages.length - 1 ? ' disabled' : ''}>↓</button>
|
||||
<button type="button" class="docPageRowRename" data-page-id="${p.id}" aria-label="Renommer cette page" title="Renommer cette page">✎</button>
|
||||
<button type="button" class="docPageRowDelete" data-page-id="${p.id}" aria-label="Supprimer cette page" title="Supprimer cette page"${pages.length <= 1 ? ' disabled' : ''}>✕</button>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
`).join('');
|
||||
|
||||
const item = container.querySelector('.docPageListItem[data-page-id]');
|
||||
item.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.docPageListActions')) return;
|
||||
forgeDocSwitchPage(Number(item.dataset.pageId));
|
||||
container.querySelectorAll('.docPageRow[data-page-id]').forEach((row) => {
|
||||
row.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.docPageRowActions')) return;
|
||||
forgeDocSwitchPage(Number(row.dataset.pageId));
|
||||
});
|
||||
row.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Enter' && e.key !== ' ') return;
|
||||
e.preventDefault();
|
||||
forgeDocSwitchPage(Number(row.dataset.pageId));
|
||||
});
|
||||
});
|
||||
item.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Enter' && e.key !== ' ') return;
|
||||
e.preventDefault();
|
||||
forgeDocSwitchPage(Number(item.dataset.pageId));
|
||||
});
|
||||
container.querySelectorAll('.docPageListMove').forEach((btn) => {
|
||||
container.querySelectorAll('.docPageRowMove').forEach((btn) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
forgeDocMovePage(Number(btn.dataset.pageId), btn.dataset.dir);
|
||||
});
|
||||
});
|
||||
container.querySelectorAll('.docPageListRename').forEach((btn) => {
|
||||
container.querySelectorAll('.docPageRowRename').forEach((btn) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
forgeDocStartRenamingPage(Number(btn.dataset.pageId));
|
||||
});
|
||||
});
|
||||
container.querySelectorAll('.docPageListDelete').forEach((btn) => {
|
||||
container.querySelectorAll('.docPageRowDelete').forEach((btn) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
forgeDocDeletePage(Number(btn.dataset.pageId));
|
||||
});
|
||||
});
|
||||
container.querySelectorAll('.docPageListTitle').forEach(forgeDocApplyTitleMarquee);
|
||||
container.querySelectorAll('.docPageRowTitle').forEach(forgeDocApplyTitleMarquee);
|
||||
|
||||
const prevBtn = document.getElementById('docPageStripPrev');
|
||||
const nextBtn = document.getElementById('docPageStripNext');
|
||||
if (prevBtn) prevBtn.disabled = index <= 0;
|
||||
if (nextBtn) nextBtn.disabled = index >= pages.length - 1;
|
||||
|
||||
const addBtn = document.getElementById('docPageListAdd');
|
||||
const addBtn = document.getElementById('docPageManagerAdd');
|
||||
if (addBtn) addBtn.onclick = () => forgeDocAddPage();
|
||||
}
|
||||
|
||||
function forgeDocBindPageStripNav() {
|
||||
const prevBtn = document.getElementById('docPageStripPrev');
|
||||
const nextBtn = document.getElementById('docPageStripNext');
|
||||
if (!prevBtn || !nextBtn) return;
|
||||
// Les flèches ‹/› CHANGENT la page affichée (retour utilisateur du
|
||||
// 22/09/2026 : "les flèches ne servent pas qu'à naviguer mais aussi à
|
||||
// changer de page") — lecture dynamique de l'ordre courant à chaque
|
||||
// clic (même principe que forgeDocBindPageNavPill), jamais un index
|
||||
// figé au moment du binding.
|
||||
prevBtn.addEventListener('click', () => {
|
||||
const pages = forgeDocOrderedPages();
|
||||
const index = pages.findIndex((p) => p.id === window.forgeDocState.activePageId);
|
||||
if (index > 0) forgeDocSwitchPage(pages[index - 1].id);
|
||||
function forgeDocBindPageManagerDrag() {
|
||||
// Délégation sur le CONTENEUR (jamais les rangées elles-mêmes,
|
||||
// recréées à chaque forgeDocRenderPageManagerList) : un seul jeu de
|
||||
// listeners pour toute la session, même principe que
|
||||
// forgeDocMotsBindGlobalMouseup.
|
||||
const container = document.getElementById('docPageManagerList');
|
||||
if (!container) return;
|
||||
let draggedId = null;
|
||||
|
||||
container.addEventListener('dragstart', (e) => {
|
||||
const row = e.target.closest('.docPageRow');
|
||||
if (!row || !row.dataset.pageId) return;
|
||||
draggedId = Number(row.dataset.pageId);
|
||||
row.classList.add('is-dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
});
|
||||
nextBtn.addEventListener('click', () => {
|
||||
const pages = forgeDocOrderedPages();
|
||||
const index = pages.findIndex((p) => p.id === window.forgeDocState.activePageId);
|
||||
if (index < pages.length - 1) forgeDocSwitchPage(pages[index + 1].id);
|
||||
container.addEventListener('dragend', (e) => {
|
||||
const row = e.target.closest('.docPageRow');
|
||||
if (row) row.classList.remove('is-dragging');
|
||||
});
|
||||
container.addEventListener('dragover', (e) => {
|
||||
if (draggedId !== null) e.preventDefault();
|
||||
});
|
||||
container.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
if (draggedId === null) return;
|
||||
const targetRow = e.target.closest('.docPageRow[data-page-id]');
|
||||
const ordered = forgeDocOrderedPages();
|
||||
const newIndex = targetRow
|
||||
? ordered.findIndex((p) => p.id === Number(targetRow.dataset.pageId))
|
||||
: ordered.length - 1;
|
||||
const movedId = draggedId;
|
||||
draggedId = null;
|
||||
if (newIndex < 0) return;
|
||||
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;
|
||||
forgeDocRenderPageManagerList();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocSwitchSidebarTab(tabName) {
|
||||
const tabs = { pages: document.getElementById('docTabPages'), layout: document.getElementById('docTabLayout') };
|
||||
const panels = { pages: document.getElementById('docTabPanelPages'), layout: document.getElementById('docTabPanelLayout') };
|
||||
Object.keys(tabs).forEach((name) => {
|
||||
const isActive = name === tabName;
|
||||
tabs[name].classList.toggle('is-active', isActive);
|
||||
tabs[name].setAttribute('aria-selected', String(isActive));
|
||||
panels[name].hidden = !isActive;
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocBindSidebarTabs() {
|
||||
document.querySelectorAll('.docSidebarTab').forEach((tab) => {
|
||||
tab.addEventListener('click', () => forgeDocSwitchSidebarTab(tab.dataset.tab));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2800,8 +2840,9 @@ function forgeDocInit() {
|
||||
forgeDocBindMobileNav();
|
||||
forgeDocBindKeyboardShortcuts();
|
||||
forgeDocBindScenarioTreeModal();
|
||||
forgeDocRenderPageList();
|
||||
forgeDocBindPageStripNav();
|
||||
forgeDocBindSidebarTabs();
|
||||
forgeDocRenderPageManagerList();
|
||||
forgeDocBindPageManagerDrag();
|
||||
forgeDocBindPageNavPill();
|
||||
forgeDocUpdateHistoryButtons();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user