Bande de pages : un seul carré visible (la page active), les flèches changent de page
Build and deploy / test-python (push) Successful in 9m16s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 4m11s
Build and deploy / lint-js (push) Failing after 1m23s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m14s
Build and deploy / test-python (push) Successful in 9m16s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 4m11s
Build and deploy / lint-js (push) Failing after 1m23s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m14s
Remplace le bandeau scrollable multi-cartes par un vrai carrousel une-page-à-la-fois : un seul carré affiché (la page active), les grandes flèches ‹/› aux extrémités changent directement la page affichée (au lieu de juste faire défiler la vue). Les petites flèches ←/→ au-dessus du carré restent pour réordonner la page active parmi ses sœurs, sans changer l'affichage — distinctes des flèches de navigation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a2b95bd547
commit
e33b11458c
@@ -1165,22 +1165,20 @@ img.docImage {
|
|||||||
/* ---- Section "Pages" du panneau GAUCHE (retour utilisateur du
|
/* ---- Section "Pages" du panneau GAUCHE (retour utilisateur du
|
||||||
21/09/2026 : "une section qui s'ajoute dans le panneau de gauche pour
|
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-
|
ajouter une page et naviguer entre elles", à la place de l'ex-
|
||||||
catégorie "Mise en page"/formes libres, supprimée) — un bandeau
|
catégorie "Mise en page"/formes libres, supprimée) — un CARROUSEL
|
||||||
HORIZONTAL défilant à hauteur FIXE (retour utilisateur du 21/09/2026 :
|
une-page-à-la-fois (retour utilisateur du 22/09/2026 : "un seul carré
|
||||||
"ce système n'est pas bien il faut un système de slide horizontale" —
|
page visible, les flèches ne servent pas qu'à naviguer mais aussi à
|
||||||
une liste verticale grandissait sans limite avec beaucoup de pages),
|
changer de page") : un seul carré = la page ACTIVE, hauteur fixe quel
|
||||||
toujours visible en tête du panneau. Cartes texte (pas de vignette
|
que soit le nombre de pages. Carte texte (pas de vignette visuelle). ---- */
|
||||||
visuelle). ---- */
|
|
||||||
.docPageSection {
|
.docPageSection {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bandeau : flèches précédent/suivant AUX EXTRÉMITÉS (retour utilisateur
|
/* ‹/› CHANGENT la page affichée (voir forgeDocBindPageStripNav) — à ne
|
||||||
du 21/09/2026 : "affiche les flèches ... en plus", donc en supplément
|
pas confondre avec les petites flèches ←/→ DANS la carte, qui elles
|
||||||
des ←/→ par carte, qui eux réordonnent) — font défiler la VUE de la
|
réordonnent la page active parmi ses sœurs sans changer l'affichage. */
|
||||||
bande, jamais la page active. */
|
|
||||||
.docPageStrip {
|
.docPageStrip {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1190,7 +1188,7 @@ img.docImage {
|
|||||||
.docPageStripNav {
|
.docPageStripNav {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 72px;
|
height: 84px;
|
||||||
border: 1px solid var(--doc-border);
|
border: 1px solid var(--doc-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: var(--doc-card);
|
background: var(--doc-card);
|
||||||
@@ -1202,19 +1200,21 @@ img.docImage {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docPageStripNav:hover {
|
.docPageStripNav:hover:not(:disabled) {
|
||||||
border-color: var(--doc-accent);
|
border-color: var(--doc-accent);
|
||||||
color: var(--doc-text);
|
color: var(--doc-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docPageStripNav:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.docPageList {
|
.docPageList {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
justify-content: center;
|
||||||
gap: 6px;
|
|
||||||
overflow: auto hidden;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.docPageListItem {
|
.docPageListItem {
|
||||||
@@ -1222,9 +1222,8 @@ img.docImage {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex-shrink: 0;
|
width: 88px;
|
||||||
width: 72px;
|
padding: 6px;
|
||||||
padding: 4px;
|
|
||||||
border: 1px solid var(--doc-border);
|
border: 1px solid var(--doc-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--doc-card);
|
background: var(--doc-card);
|
||||||
@@ -1336,6 +1335,11 @@ img.docImage {
|
|||||||
color: var(--doc-text);
|
color: var(--doc-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docPageListCounter {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--doc-muted);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- RIGHT SIDEBAR : Propriétés ---- */
|
/* ---- RIGHT SIDEBAR : Propriétés ---- */
|
||||||
.docField {
|
.docField {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -592,27 +592,32 @@ function forgeDocRenderPageList() {
|
|||||||
const container = document.getElementById('docPageList');
|
const container = document.getElementById('docPageList');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
const pages = forgeDocOrderedPages();
|
const pages = forgeDocOrderedPages();
|
||||||
// div + role="button" ici, jamais <button> : la rangée contient de
|
const index = pages.findIndex((p) => p.id === window.forgeDocState.activePageId);
|
||||||
// vrais <button> d'action (monter/descendre/renommer/supprimer), qu'un
|
const p = pages[index];
|
||||||
// <button> englobant ne pourrait pas contenir validement. L'équivalent
|
// Un seul carré visible : la page ACTIVE (retour utilisateur du
|
||||||
// clavier (Entrée/Espace) est posé plus bas.
|
// 22/09/2026 : "un seul carré page visible") — les grandes flèches
|
||||||
container.innerHTML = pages.map((p, index) => `
|
// ‹/› de .docPageStrip CHANGENT la page affichée (voir
|
||||||
<div class="docPageListItem ${p.id === window.forgeDocState.activePageId ? 'is-active' : ''}"
|
// forgeDocBindPageStripNav), distinctes des petites flèches ←/→
|
||||||
role="button"
|
// ci-dessous qui réordonnent la page active parmi ses sœurs sans
|
||||||
tabindex="0"
|
// changer l'affichage. div + role="button" ici, jamais <button> : la
|
||||||
data-page-id="${p.id}">
|
// 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">
|
<div class="docPageListActions">
|
||||||
<button type="button" class="docPageListMove" data-dir="up" data-page-id="${p.id}" aria-label="Déplacer vers la gauche" title="Déplacer vers la gauche"${index === 0 ? ' disabled' : ''}>←</button>
|
<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="Déplacer vers la droite" title="Déplacer vers la droite"${index === pages.length - 1 ? ' 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="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>
|
<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>
|
||||||
<div class="docPageListSquare">${index + 1}</div>
|
<div class="docPageListSquare">${index + 1}</div>
|
||||||
<span class="docPageListTitle"><span class="docPageListTitleText">${forgeDocEscapeHtml(p.title)}</span></span>
|
<span class="docPageListTitle"><span class="docPageListTitleText">${forgeDocEscapeHtml(p.title)}</span></span>
|
||||||
|
<span class="docPageListCounter">${index + 1} / ${pages.length}</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`;
|
||||||
|
|
||||||
container.querySelectorAll('.docPageListItem[data-page-id]').forEach((item) => {
|
const item = container.querySelector('.docPageListItem[data-page-id]');
|
||||||
item.addEventListener('click', (e) => {
|
item.addEventListener('click', (e) => {
|
||||||
if (e.target.closest('.docPageListActions')) return;
|
if (e.target.closest('.docPageListActions')) return;
|
||||||
forgeDocSwitchPage(Number(item.dataset.pageId));
|
forgeDocSwitchPage(Number(item.dataset.pageId));
|
||||||
@@ -622,7 +627,6 @@ function forgeDocRenderPageList() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
forgeDocSwitchPage(Number(item.dataset.pageId));
|
forgeDocSwitchPage(Number(item.dataset.pageId));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
container.querySelectorAll('.docPageListMove').forEach((btn) => {
|
container.querySelectorAll('.docPageListMove').forEach((btn) => {
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -642,21 +646,35 @@ function forgeDocRenderPageList() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
container.querySelectorAll('.docPageListTitle').forEach(forgeDocApplyTitleMarquee);
|
container.querySelectorAll('.docPageListTitle').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('docPageListAdd');
|
||||||
if (addBtn) addBtn.onclick = () => forgeDocAddPage();
|
if (addBtn) addBtn.onclick = () => forgeDocAddPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
function forgeDocBindPageStripNav() {
|
function forgeDocBindPageStripNav() {
|
||||||
const container = document.getElementById('docPageList');
|
|
||||||
const prevBtn = document.getElementById('docPageStripPrev');
|
const prevBtn = document.getElementById('docPageStripPrev');
|
||||||
const nextBtn = document.getElementById('docPageStripNext');
|
const nextBtn = document.getElementById('docPageStripNext');
|
||||||
if (!container || !prevBtn || !nextBtn) return;
|
if (!prevBtn || !nextBtn) return;
|
||||||
// Fait défiler la VUE de la bande (pas la page active — retour
|
// Les flèches ‹/› CHANGENT la page affichée (retour utilisateur du
|
||||||
// utilisateur du 21/09/2026 : "affiche les flèches ... en plus", donc
|
// 22/09/2026 : "les flèches ne servent pas qu'à naviguer mais aussi à
|
||||||
// une navigation visuelle distincte des ←/→ par carte qui, eux,
|
// changer de page") — lecture dynamique de l'ordre courant à chaque
|
||||||
// réordonnent). Défilement d'environ une carte + son espacement.
|
// clic (même principe que forgeDocBindPageNavPill), jamais un index
|
||||||
prevBtn.addEventListener('click', () => container.scrollBy({ left: -110, behavior: 'smooth' }));
|
// figé au moment du binding.
|
||||||
nextBtn.addEventListener('click', () => container.scrollBy({ left: 110, behavior: 'smooth' }));
|
prevBtn.addEventListener('click', () => {
|
||||||
|
const pages = forgeDocOrderedPages();
|
||||||
|
const index = pages.findIndex((p) => p.id === window.forgeDocState.activePageId);
|
||||||
|
if (index > 0) forgeDocSwitchPage(pages[index - 1].id);
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function forgeDocBindPageNavPill() {
|
function forgeDocBindPageNavPill() {
|
||||||
|
|||||||
@@ -101,54 +101,58 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageStripNav"
|
class="docPageStripNav"
|
||||||
id="docPageStripPrev"
|
id="docPageStripPrev"
|
||||||
aria-label="Défiler vers la gauche"
|
aria-label="Page précédente"
|
||||||
title="Défiler vers la gauche">‹</button>
|
title="Page précédente">‹</button>
|
||||||
<div class="docPageList" id="docPageList">
|
<div class="docPageList" id="docPageList">
|
||||||
{% for page in pages %}
|
{# active_page est toujours pages[0] au chargement initial (voir
|
||||||
<div class="docPageListItem {{ 'is-active' if page.id == active_page.id else '' }}"
|
routes/document/document_edit.py) — index 0 fixe, jamais recalculé
|
||||||
role="button"
|
ici ; un changement de page ensuite est entièrement géré côté
|
||||||
tabindex="0"
|
client par forgeDocRenderPageList, qui recalcule le VRAI index. #}
|
||||||
data-page-id="{{ page.id }}">
|
{% set active_index = 0 %}
|
||||||
|
<div class="docPageListItem is-active" role="button" tabindex="0" data-page-id="{{ active_page.id }}">
|
||||||
{# //NOSONAR Web:S6819,Web:MouseEventWithoutKeyboardEquivalentCheck - div+role=button
|
{# //NOSONAR Web:S6819,Web:MouseEventWithoutKeyboardEquivalentCheck - div+role=button
|
||||||
volontaire : la rangée contient de vrais <button> d'action (monter/descendre/
|
volontaire : la rangée contient de vrais <button> d'action (réordonner/renommer/
|
||||||
renommer/supprimer), qu'un <button> englobant ne pourrait pas contenir
|
supprimer), qu'un <button> englobant ne pourrait pas contenir validement.
|
||||||
validement. L'équivalent clavier (Entrée/Espace) est posé côté JS
|
L'équivalent clavier (Entrée/Espace) est posé côté JS (forgeDocRenderPageList),
|
||||||
(forgeDocRenderPageList), donc le finding clavier de Sonar est un faux positif
|
donc le finding clavier de Sonar est un faux positif : l'analyseur statique ne
|
||||||
: l'analyseur statique ne voit pas les addEventListener dynamiques. #}
|
voit pas les addEventListener dynamiques. #}
|
||||||
<div class="docPageListActions">
|
<div class="docPageListActions">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageListMove"
|
class="docPageListMove"
|
||||||
data-dir="up"
|
data-dir="up"
|
||||||
data-page-id="{{ page.id }}"
|
data-page-id="{{ active_page.id }}"
|
||||||
aria-label="Déplacer vers la gauche"
|
aria-label="Réordonner vers la gauche"
|
||||||
title="Déplacer vers la gauche">←</button>
|
title="Réordonner vers la gauche"
|
||||||
|
{{ 'disabled' if active_index == 0 else '' }}>←</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageListMove"
|
class="docPageListMove"
|
||||||
data-dir="down"
|
data-dir="down"
|
||||||
data-page-id="{{ page.id }}"
|
data-page-id="{{ active_page.id }}"
|
||||||
aria-label="Déplacer vers la droite"
|
aria-label="Réordonner vers la droite"
|
||||||
title="Déplacer vers la droite">→</button>
|
title="Réordonner vers la droite"
|
||||||
|
{{ 'disabled' if active_index == pages|length - 1 else '' }}>→</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageListRename"
|
class="docPageListRename"
|
||||||
data-page-id="{{ page.id }}"
|
data-page-id="{{ active_page.id }}"
|
||||||
aria-label="Renommer cette page"
|
aria-label="Renommer cette page"
|
||||||
title="Renommer cette page">✎</button>
|
title="Renommer cette page">✎</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageListDelete"
|
class="docPageListDelete"
|
||||||
data-page-id="{{ page.id }}"
|
data-page-id="{{ active_page.id }}"
|
||||||
aria-label="Supprimer cette page"
|
aria-label="Supprimer cette page"
|
||||||
title="Supprimer cette page">✕</button>
|
title="Supprimer cette page"
|
||||||
|
{{ 'disabled' if pages|length <= 1 else '' }}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="docPageListSquare">{{ loop.index }}</div>
|
<div class="docPageListSquare">{{ active_index + 1 }}</div>
|
||||||
<span class="docPageListTitle"><span class="docPageListTitleText">{{ page.title }}</span></span>
|
<span class="docPageListTitle"><span class="docPageListTitleText">{{ active_page.title }}</span></span>
|
||||||
|
<span class="docPageListCounter">{{ active_index + 1 }} / {{ pages|length }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageStripNav"
|
class="docPageStripNav"
|
||||||
id="docPageStripNext"
|
id="docPageStripNext"
|
||||||
aria-label="Défiler vers la droite"
|
aria-label="Page suivante"
|
||||||
title="Défiler vers la droite">›</button>
|
title="Page suivante">›</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="docBtnSecondary docPageListAdd" id="docPageListAdd">+ Ajouter une page</button>
|
<button type="button" class="docBtnSecondary docPageListAdd" id="docPageListAdd">+ Ajouter une page</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user