refactoring avec claude + ajout scroll-margin-top et désaffichage du panel au click sur les liens du toc

This commit is contained in:
antonin gallon 2026-02-17 18:10:04 +01:00
parent d51fc592ed
commit 01c5b098e4
14 changed files with 149 additions and 121 deletions

View file

@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(git show:*)"
]
}
}

View file

@ -30,11 +30,16 @@ article #main-content #chapo::after {
article #main-content li:not(.text) { article #main-content li:not(.text) {
list-style-type: inherit; list-style-type: inherit;
} }
article h3 { article h3 {
scroll-margin-top: calc(var(--unit--vertical) * 1);
margin-top: calc(3 * var(--unit--vertical)); margin-top: calc(3 * var(--unit--vertical));
margin-bottom: calc(1 * var(--unit--vertical)); margin-bottom: calc(1 * var(--unit--vertical));
} }
@media screen and (max-width: 640px) {
article h3 {
scroll-margin-top: calc(var(--unit--vertical) * 5);
}
}
article li, article ol{ article li, article ol{
margin-left: var(--unit--horizontal); margin-left: var(--unit--horizontal);

View file

@ -20,12 +20,14 @@
border-top: 1px solid var(--color-primary); border-top: 1px solid var(--color-primary);
background-color: var(--color-background); background-color: var(--color-background);
} }
#main-footer .open-nav-wrapper_toc{ #main-footer .open-nav-wrapper:has([data-open-panel="toc"]) {
margin-right: 50px; margin-right: 50px;
} }
} }
@media screen and (min-width: 1100px) { @media screen and (min-width: 1100px) {
#main-footer .open-nav-wrapper_toc{ /* On mobile > 1100px, le bouton sommaire n'est pas nécessaire car la TOC est visible */
#main-footer .open-nav-wrapper:has([data-open-panel="toc"]) {
display: none !important; display: none !important;
} }
} }
@ -52,16 +54,14 @@
@media screen and (max-width: 640px) { @media screen and (max-width: 640px) {
#main-footer .open-nav { #main-footer .open-nav {
display: flex; display: flex;
justify-content: center;
outline: none; outline: none;
font-size: var(--font-size-m); font-size: var(--font-size-m);
background-color: var(--color-background); background-color: var(--color-background);
color: var(--color-primary); color: var(--color-primary);
line-height: 1; line-height: 1;
padding: calc(var(--unit--vertical) / 2) var(--unit--horizontal); padding: calc(var(--unit--vertical) / 2) var(--unit--horizontal);
} }
[data-is_toc="false"] #main-footer .open-nav {
justify-content: center;
}
} }
@media screen and (min-width: 640px) { @media screen and (min-width: 640px) {

View file

@ -1,47 +1,39 @@
.toc {
.toc{ display: flex;
display: flex; flex-direction: column;
flex-direction: column; justify-content: center;
justify-content: center;
}
.page-cover .toc{
flex: 1;
}
@media (min-width: 1100px){
.page-cover .toc{
position: fixed;
width: calc(var(--body-padding) - var(--unit--horizontal) * 2) ;
left: 0;
top: 15vw;
padding-inline: var(--unit--horizontal);
padding-top: calc(var(--unit--vertical) / 2);
}
}
.page-cover .toc{
flex: 1;
}
.panel-toc .toc{
padding: var(--unit--vertical) var(--unit--horizontal);
}
.toc_label{
font-size: var(--font-size-m);
margin-bottom: calc(var(--unit--vertical) / 4); /*option 1*/
}
.toc ul{
display: flex;
flex-direction: column;
gap: calc(var(--unit--vertical) / 4); /*option 1*/
}
.toc li{
margin-left: 0;
/* text-indent: var(--unit--horizontal) hanging; */ /*option 2*/
/* list-style: square; */ /*option 3*/
} }
.page-cover .toc {
[data-is_toc="false"] .if_toc, flex: 1;
[data-is_toc="false"] #main-footer li.if_toc{ /*obliger d'être si précis car si non pas la priorité*/ }
display: none !important;
@media (min-width: 1100px) {
.page-cover .toc {
position: fixed;
width: calc(var(--body-padding) - var(--unit--horizontal) * 2);
left: 0;
top: 15vw;
padding-inline: var(--unit--horizontal);
padding-top: calc(var(--unit--vertical) / 2);
}
}
.panel-toc .toc {
padding: var(--unit--vertical) var(--unit--horizontal);
}
.toc_label {
font-size: var(--font-size-m);
margin-bottom: calc(var(--unit--vertical) / 4);
}
.toc ul {
display: flex;
flex-direction: column;
gap: calc(var(--unit--vertical) / 4);
}
.toc li {
margin-left: 0;
} }

View file

@ -129,20 +129,24 @@ function subscribe(event) {
} }
} }
const panelsNav = document.querySelectorAll(".panel"); const panels = document.querySelectorAll(".panel[data-panel]");
const panelNavText = document.querySelector(".panel-text");
const panelNavToc = document.querySelector(".panel-toc");
const navOverlay = document.querySelector("#nav-overlay"); const navOverlay = document.querySelector("#nav-overlay");
const openNavBtns = document.querySelectorAll("button.open-nav");
const closeNavBtns = document.querySelectorAll(".panel-close"); function closeAllPanels() {
function closeNav() { panels.forEach(panel => panel.classList.remove("panel--visible"));
panelsNav.forEach(element => {
element.classList.remove("panel--visible");
});
navOverlay.classList.remove("nav-overlay--visible"); navOverlay.classList.remove("nav-overlay--visible");
document.body.classList.remove("no-scroll"); document.body.classList.remove("no-scroll");
} }
function openPanel(name) {
const panel = document.querySelector(`.panel[data-panel="${name}"]`);
if (panel) {
panel.classList.add("panel--visible");
navOverlay.classList.add("nav-overlay--visible");
document.body.classList.add("no-scroll");
}
}
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
ragadjust("h1, h2, h4, h5", ["all"]); ragadjust("h1, h2, h4, h5", ["all"]);
window.window.scrollTo({ window.window.scrollTo({
@ -166,10 +170,10 @@ document.addEventListener("DOMContentLoaded", () => {
window.addEventListener("keyup", (event) => { window.addEventListener("keyup", (event) => {
if (event.key === "Escape") { if (event.key === "Escape") {
closeNav(); closeAllPanels();
} }
}); });
document.querySelectorAll(".panel").forEach((panel) => { panels.forEach((panel) => {
panel.addEventListener("click", (event) => { panel.addEventListener("click", (event) => {
event.stopPropagation(); event.stopPropagation();
}); });
@ -201,25 +205,20 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
}); });
openNavBtns.forEach((openNavBtn) => { document.querySelectorAll("[data-open-panel]").forEach((btn) => {
openNavBtn.addEventListener("click", (event) => { btn.addEventListener("click", () => {
target = event.currentTarget; openPanel(btn.dataset.openPanel);
if(target.classList.contains("open-nav_text")){
panelNavText.classList.add("panel--visible");
}else if(target.classList.contains("open-nav_toc")){
panelNavToc.classList.add("panel--visible");
}
navOverlay.classList.add("nav-overlay--visible");
document.body.classList.add("no-scroll");
}); });
}); });
closeNavBtns.forEach(element => { document.querySelectorAll(".panel-close").forEach((btn) => {
element.addEventListener("click", () => { btn.addEventListener("click", closeAllPanels);
closeNav();
});
}); });
navOverlay.addEventListener("click", () => {
closeNav(); navOverlay.addEventListener("click", closeAllPanels);
// Fermer le panel TOC quand on clique sur un lien
document.querySelectorAll(".panel-toc .toc a").forEach((link) => {
link.addEventListener("click", closeAllPanels);
}); });
}); });

View file

@ -12,14 +12,6 @@ function allYears ($article) {
return $years; return $years;
} }
function addAnchors($content) {
$content = preg_replace_callback('/<h3>(.*?)<\/h3>/', function($matches) {
$slug = Str::slug($matches[1]);
return '<h3 id="' . $slug . '">' . $matches[1] . '</h3>';
}, $content);
return $content;
}
function setTitleFontSizeClass($title, $level = 'h1') function setTitleFontSizeClass($title, $level = 'h1')
{ {
$length = strlen($title); $length = strlen($title);

View file

@ -0,0 +1,47 @@
<?php
Kirby::plugin('actuel-inactuel/toc', [
'pageMethods' => [
/**
* Vérifie si la page doit afficher une TOC
*/
'hasToc' => function(): bool {
if (!$this->parent()?->parent()?->is('textes')) {
return false;
}
if (!$this->bodyBlocks()?->isNotEmpty()) {
return false;
}
return (bool) preg_match('/<h3>/', $this->bodyBlocks()->toBlocks());
},
/**
* Retourne les items de la TOC
*/
'tocItems' => function(): array {
if (!$this->bodyBlocks()?->isNotEmpty()) {
return [];
}
preg_match_all('/<h3>(.*?)<\/h3>/', $this->bodyBlocks()->toBlocks(), $matches);
return array_map(fn($title) => [
'title' => $title,
'slug' => Str::slug($title)
], $matches[1]);
},
/**
* Retourne le contenu avec les ancres ajoutées aux h3
*/
'bodyWithAnchors' => function(): string {
if (!$this->bodyBlocks()?->isNotEmpty()) {
return '';
}
return preg_replace_callback(
'/<h3>(.*?)<\/h3>/',
fn($m) => '<h3 id="' . Str::slug($m[1]) . '">' . $m[1] . '</h3>',
$this->bodyBlocks()->toBlocks()
);
}
]
]);

View file

@ -8,9 +8,9 @@ $isOpen ??= false;
<?= $slots->title() ?> <?= $slots->title() ?>
</a> </a>
</div> </div>
<?php if ($page->parent() && $page->parent()->parent()->is('textes')){ <?php if ($page->hasToc()): ?>
snippet('toc', ["content" => $page->bodyBlocks()->toBlocks()]); <?php snippet('toc') ?>
} ?> <?php endif ?>
<?php if ($slots->text()): ?> <?php if ($slots->text()): ?>
<div class="text-wrapper"> <div class="text-wrapper">
<?= $slots->text() ?> <?= $slots->text() ?>

View file

@ -2,12 +2,12 @@
<?php if (!$page->is(page('lettre')) && !$page->is(page('a-propos'))): ?> <?php if (!$page->is(page('lettre')) && !$page->is(page('a-propos'))): ?>
<footer id="main-footer"> <footer id="main-footer">
<ul id="links"> <ul id="links">
<li class="open-nav-wrapper open-nav-wrapper_text hidden"> <li class="open-nav-wrapper hidden">
<button class="plus open-nav open-nav_text" title="chercher parmi les textes">textes</button> <button class="plus open-nav" data-open-panel="text" title="chercher parmi les textes">textes</button>
</li> </li>
<?php if ($page->parent() && $page->parent()->parent()->is('textes')): ?> <?php if ($page->hasToc()): ?>
<li class="open-nav-wrapper open-nav-wrapper_toc hidden if_toc"> <li class="open-nav-wrapper hidden">
<button class="plus open-nav open-nav_toc" title="ouvrir le sommaire">sommaire</button> <button class="plus open-nav" data-open-panel="toc" title="ouvrir le sommaire">sommaire</button>
</li> </li>
<?php endif ?> <?php endif ?>
<li> <li>

View file

@ -84,13 +84,7 @@ $entryTopPos ??= 20;
<body <body
class="background-grid <?= e($page->fullWidth() == 'true', 'full-width') ?>" class="background-grid <?= e($page->fullWidth() == 'true', 'full-width') ?>"
data-is_toc="<?php data-has-toc="<?= $page->hasToc() ? 'true' : 'false' ?>"
if ($page->bodyBlocks() && $page->bodyBlocks()->isNotEmpty() && preg_match('/<h3>(.*?)<\/h3>/', $page->bodyBlocks())){
echo "true";
}else{
echo "false";
}
?>"
data-template="<?= $page->template() ?>"> data-template="<?= $page->template() ?>">
<button class="theme-toggler" data-theme-toggler> <button class="theme-toggler" data-theme-toggler>
<span class="theme-toggler-icon"></span> <span class="theme-toggler-icon"></span>
@ -107,8 +101,7 @@ $entryTopPos ??= 20;
</a> </a>
</header> </header>
<?php snippet('nav') ?> <?php snippet('nav') ?>
<?php if ($page->parent() && $page->parent()->parent()->is('textes')){ <?php if ($page->hasToc()): ?>
snippet('panel-toc'); <?php snippet('panel-toc') ?>
} <?php endif ?>
?>
<div id="nav-overlay"></div> <div id="nav-overlay"></div>

View file

@ -1,4 +1,4 @@
<nav class="panel panel-text" x-data="{search: ''}"> <nav class="panel panel-text" data-panel="text" x-data="{search: ''}">
<header> <header>
<p class="sort-btns"> <p class="sort-btns">
<button class="sort-btn sort-btn--years active">années</span></button> <button class="sort-btn sort-btn--years active">années</span></button>

View file

@ -1,6 +1,4 @@
<div class="panel panel-toc" x-data="{search: ''}"> <div class="panel panel-toc" data-panel="toc">
<?php <?php snippet('toc') ?>
snippet('toc', ["content" => $page->bodyBlocks()->toBlocks()]);
?>
<button class="less panel-close">sommaire</button> <button class="less panel-close">sommaire</button>
</div> </div>

View file

@ -1,13 +1,8 @@
<nav class="toc"> <nav class="toc">
<div class="light toc_label if_toc">Sommaire</div> <div class="light toc_label">Sommaire</div>
<ul> <ul>
<?php <?php foreach ($page->tocItems() as $item): ?>
preg_match_all('/<h3>(.*?)<\/h3>/', $content, $titres); <li><a href="#<?= $item['slug'] ?>"><?= $item['title'] ?></a></li>
foreach ($titres[1] as $index => $titre) { <?php endforeach ?>
$slug = Str::slug($titre);
echo '<li><a href="#' . $slug . '">' . $titre . '</a></li>';
}
?>
</ul> </ul>
</nav> </nav>

View file

@ -34,7 +34,7 @@
<?php if ($page->isHtmlMode()->isTrue()): ?> <?php if ($page->isHtmlMode()->isTrue()): ?>
<?= $page->htmlBody()->kt() ?> <?= $page->htmlBody()->kt() ?>
<?php elseif ($page->bodyBlocks()->isNotEmpty()): ?> <?php elseif ($page->bodyBlocks()->isNotEmpty()): ?>
<?= addAnchors($page->bodyBlocks()->toBlocks()) ?> <?= $page->bodyWithAnchors() ?>
<?php else: ?> <?php else: ?>
<?= $page->body() ?> <?= $page->body() ?>
<?php endif ?> <?php endif ?>