Améliore l'UX de la bande de pages : carré + nom en dessous, flèches précédent/suivant, défilement au survol
Build and deploy / test-python (push) Successful in 7m22s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 5m15s
Build and deploy / lint-js (push) Failing after 1m14s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 5m23s
Build and deploy / test-python (push) Successful in 7m22s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 5m15s
Build and deploy / lint-js (push) Failing after 1m14s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 5m23s
Chaque carte devient carré (numéro) + nom tronqué SOUS le carré, avec les actions (réordonner/renommer/supprimer) au-dessus plutôt que sur le côté. Ajoute deux flèches aux extrémités de la bande pour faire défiler la VUE (distinct du réordonnancement par carte). Un titre tronqué défile au survol de la souris (marquee), mesuré dynamiquement — jamais pour un titre qui tient déjà. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8dc4b35dcf
commit
a2b95bd547
@@ -1165,26 +1165,66 @@ 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) — une simple liste
|
catégorie "Mise en page"/formes libres, supprimée) — un bandeau
|
||||||
(pas de vignette visuelle), toujours visible en tête du panneau. ---- */
|
HORIZONTAL défilant à hauteur FIXE (retour utilisateur du 21/09/2026 :
|
||||||
|
"ce système n'est pas bien il faut un système de slide horizontale" —
|
||||||
|
une liste verticale grandissait sans limite avec beaucoup de pages),
|
||||||
|
toujours visible en tête du panneau. Cartes texte (pas de vignette
|
||||||
|
visuelle). ---- */
|
||||||
.docPageSection {
|
.docPageSection {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docPageList {
|
/* Bandeau : flèches précédent/suivant AUX EXTRÉMITÉS (retour utilisateur
|
||||||
|
du 21/09/2026 : "affiche les flèches ... en plus", donc en supplément
|
||||||
|
des ←/→ par carte, qui eux réordonnent) — font défiler la VUE de la
|
||||||
|
bande, jamais la page active. */
|
||||||
|
.docPageStrip {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docPageStripNav {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 22px;
|
||||||
|
height: 72px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docPageStripNav:hover {
|
||||||
|
border-color: var(--doc-accent);
|
||||||
|
color: var(--doc-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docPageList {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 6px;
|
||||||
|
overflow: auto hidden;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.docPageListItem {
|
.docPageListItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 4px;
|
||||||
gap: 8px;
|
flex-shrink: 0;
|
||||||
padding: 7px 8px;
|
width: 72px;
|
||||||
|
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);
|
||||||
@@ -1205,44 +1245,21 @@ img.docImage {
|
|||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docPageListTitle {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 12.5px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--doc-muted);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.docPageListItem.is-active .docPageListTitle {
|
|
||||||
color: var(--doc-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.docPageListTitle[contenteditable="true"] {
|
|
||||||
outline: none;
|
|
||||||
border-bottom: 1px solid var(--doc-accent);
|
|
||||||
cursor: text;
|
|
||||||
user-select: text;
|
|
||||||
color: var(--doc-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.docPageListActions {
|
.docPageListActions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.docPageListActions button {
|
.docPageListActions button {
|
||||||
width: 20px;
|
width: 16px;
|
||||||
height: 20px;
|
height: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--doc-muted);
|
color: var(--doc-muted);
|
||||||
font-size: 11px;
|
font-size: 9px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1263,6 +1280,62 @@ img.docImage {
|
|||||||
color: var(--doc-quiz-danger-border);
|
color: var(--doc-quiz-danger-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docPageListSquare {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- RIGHT SIDEBAR : Propriétés ---- */
|
/* ---- RIGHT SIDEBAR : Propriétés ---- */
|
||||||
.docField {
|
.docField {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -575,6 +575,19 @@ function forgeDocStartRenamingPage(pageId) {
|
|||||||
titleEl.addEventListener('keydown', onKeydown);
|
titleEl.addEventListener('keydown', onKeydown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forgeDocApplyTitleMarquee(titleEl) {
|
||||||
|
// "défile au passage de la souris" (retour utilisateur du 21/09/2026) :
|
||||||
|
// 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 overflow = textEl.scrollWidth - titleEl.clientWidth;
|
||||||
|
if (overflow > 0) {
|
||||||
|
titleEl.classList.add('has-overflow');
|
||||||
|
titleEl.style.setProperty('--marquee-distance', `${overflow}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function forgeDocRenderPageList() {
|
function forgeDocRenderPageList() {
|
||||||
const container = document.getElementById('docPageList');
|
const container = document.getElementById('docPageList');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -588,13 +601,14 @@ function forgeDocRenderPageList() {
|
|||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
data-page-id="${p.id}">
|
data-page-id="${p.id}">
|
||||||
<span class="docPageListTitle">${forgeDocEscapeHtml(p.title)}</span>
|
|
||||||
<div class="docPageListActions">
|
<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="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="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="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="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>
|
||||||
|
<span class="docPageListTitle"><span class="docPageListTitleText">${forgeDocEscapeHtml(p.title)}</span></span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
@@ -627,10 +641,24 @@ function forgeDocRenderPageList() {
|
|||||||
forgeDocDeletePage(Number(btn.dataset.pageId));
|
forgeDocDeletePage(Number(btn.dataset.pageId));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
container.querySelectorAll('.docPageListTitle').forEach(forgeDocApplyTitleMarquee);
|
||||||
const addBtn = document.getElementById('docPageListAdd');
|
const addBtn = document.getElementById('docPageListAdd');
|
||||||
if (addBtn) addBtn.onclick = () => forgeDocAddPage();
|
if (addBtn) addBtn.onclick = () => forgeDocAddPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forgeDocBindPageStripNav() {
|
||||||
|
const container = document.getElementById('docPageList');
|
||||||
|
const prevBtn = document.getElementById('docPageStripPrev');
|
||||||
|
const nextBtn = document.getElementById('docPageStripNext');
|
||||||
|
if (!container || !prevBtn || !nextBtn) return;
|
||||||
|
// Fait défiler la VUE de la bande (pas la page active — retour
|
||||||
|
// utilisateur du 21/09/2026 : "affiche les flèches ... en plus", donc
|
||||||
|
// une navigation visuelle distincte des ←/→ par carte qui, eux,
|
||||||
|
// réordonnent). Défilement d'environ une carte + son espacement.
|
||||||
|
prevBtn.addEventListener('click', () => container.scrollBy({ left: -110, behavior: 'smooth' }));
|
||||||
|
nextBtn.addEventListener('click', () => container.scrollBy({ left: 110, behavior: 'smooth' }));
|
||||||
|
}
|
||||||
|
|
||||||
function forgeDocBindPageNavPill() {
|
function forgeDocBindPageNavPill() {
|
||||||
document.getElementById('docPagePrevBtn').addEventListener('click', () => {
|
document.getElementById('docPagePrevBtn').addEventListener('click', () => {
|
||||||
const pages = forgeDocOrderedPages();
|
const pages = forgeDocOrderedPages();
|
||||||
@@ -2755,6 +2783,7 @@ function forgeDocInit() {
|
|||||||
forgeDocBindKeyboardShortcuts();
|
forgeDocBindKeyboardShortcuts();
|
||||||
forgeDocBindScenarioTreeModal();
|
forgeDocBindScenarioTreeModal();
|
||||||
forgeDocRenderPageList();
|
forgeDocRenderPageList();
|
||||||
|
forgeDocBindPageStripNav();
|
||||||
forgeDocBindPageNavPill();
|
forgeDocBindPageNavPill();
|
||||||
forgeDocUpdateHistoryButtons();
|
forgeDocUpdateHistoryButtons();
|
||||||
|
|
||||||
|
|||||||
@@ -97,45 +97,58 @@
|
|||||||
<div class="docSidebar docSidebarLeft" id="docSidebarLeft">
|
<div class="docSidebar docSidebarLeft" id="docSidebarLeft">
|
||||||
<div class="docPageSection">
|
<div class="docPageSection">
|
||||||
<div class="docSectionLabel">Pages</div>
|
<div class="docSectionLabel">Pages</div>
|
||||||
<div class="docPageList" id="docPageList">
|
<div class="docPageStrip">
|
||||||
{% for page in pages %}
|
<button type="button"
|
||||||
<div class="docPageListItem {{ 'is-active' if page.id == active_page.id else '' }}"
|
class="docPageStripNav"
|
||||||
role="button"
|
id="docPageStripPrev"
|
||||||
tabindex="0"
|
aria-label="Défiler vers la gauche"
|
||||||
data-page-id="{{ page.id }}">
|
title="Défiler vers la gauche">‹</button>
|
||||||
{# //NOSONAR Web:S6819,Web:MouseEventWithoutKeyboardEquivalentCheck - div+role=button
|
<div class="docPageList" id="docPageList">
|
||||||
volontaire : la rangée contient de vrais <button> d'action (monter/descendre/
|
{% for page in pages %}
|
||||||
renommer/supprimer), qu'un <button> englobant ne pourrait pas contenir
|
<div class="docPageListItem {{ 'is-active' if page.id == active_page.id else '' }}"
|
||||||
validement. L'équivalent clavier (Entrée/Espace) est posé côté JS
|
role="button"
|
||||||
(forgeDocRenderPageList), donc le finding clavier de Sonar est un faux positif
|
tabindex="0"
|
||||||
: l'analyseur statique ne voit pas les addEventListener dynamiques. #}
|
data-page-id="{{ page.id }}">
|
||||||
<span class="docPageListTitle">{{ page.title }}</span>
|
{# //NOSONAR Web:S6819,Web:MouseEventWithoutKeyboardEquivalentCheck - div+role=button
|
||||||
<div class="docPageListActions">
|
volontaire : la rangée contient de vrais <button> d'action (monter/descendre/
|
||||||
<button type="button"
|
renommer/supprimer), qu'un <button> englobant ne pourrait pas contenir
|
||||||
class="docPageListMove"
|
validement. L'équivalent clavier (Entrée/Espace) est posé côté JS
|
||||||
data-dir="up"
|
(forgeDocRenderPageList), donc le finding clavier de Sonar est un faux positif
|
||||||
data-page-id="{{ page.id }}"
|
: l'analyseur statique ne voit pas les addEventListener dynamiques. #}
|
||||||
aria-label="Déplacer vers le haut"
|
<div class="docPageListActions">
|
||||||
title="Déplacer vers le haut">↑</button>
|
<button type="button"
|
||||||
<button type="button"
|
class="docPageListMove"
|
||||||
class="docPageListMove"
|
data-dir="up"
|
||||||
data-dir="down"
|
data-page-id="{{ page.id }}"
|
||||||
data-page-id="{{ page.id }}"
|
aria-label="Déplacer vers la gauche"
|
||||||
aria-label="Déplacer vers le bas"
|
title="Déplacer vers la gauche">←</button>
|
||||||
title="Déplacer vers le bas">↓</button>
|
<button type="button"
|
||||||
<button type="button"
|
class="docPageListMove"
|
||||||
class="docPageListRename"
|
data-dir="down"
|
||||||
data-page-id="{{ page.id }}"
|
data-page-id="{{ page.id }}"
|
||||||
aria-label="Renommer cette page"
|
aria-label="Déplacer vers la droite"
|
||||||
title="Renommer cette page">✎</button>
|
title="Déplacer vers la droite">→</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="docPageListDelete"
|
class="docPageListRename"
|
||||||
data-page-id="{{ page.id }}"
|
data-page-id="{{ page.id }}"
|
||||||
aria-label="Supprimer cette page"
|
aria-label="Renommer cette page"
|
||||||
title="Supprimer cette page">✕</button>
|
title="Renommer cette page">✎</button>
|
||||||
|
<button type="button"
|
||||||
|
class="docPageListDelete"
|
||||||
|
data-page-id="{{ page.id }}"
|
||||||
|
aria-label="Supprimer cette page"
|
||||||
|
title="Supprimer cette page">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="docPageListSquare">{{ loop.index }}</div>
|
||||||
|
<span class="docPageListTitle"><span class="docPageListTitleText">{{ page.title }}</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
class="docPageStripNav"
|
||||||
|
id="docPageStripNext"
|
||||||
|
aria-label="Défiler vers la droite"
|
||||||
|
title="Défiler vers la droite">›</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