Piszemy plugin z dynamicznym blokiem dla spisu treści, zawierającym możliwość edycji, dodawania elementów (z podglądem w czasie rzeczywistym), wyświetlający spis treści (lista tytułów i odnośników do kotwicy), używający własnego shortcode do wyświetlania dynamicznej treści oraz posiadający własny skrypt, dzięki któremu przejścia do kotwic są płynne. Małe podsumowanie kliku poprzednich wpisów z kategorii WordPress.
Tworzymy nasz projekt pluginu – standardowe procedury
Musimy odnaleźć w naszej instalacji WordPressa folder wp-content a w nim folder plugin i w tamtym miejscu skorzystać z następującej komendy:
npx @wordpress/create-block@latest toc-block --variant=dynamic
Następnie przechodzimy do folderu pluginu komendą cd (change directory):
cd toc-block
Jeżeli mamy włączony plugin shortcode-TOC to go wyłączmy. Natomiast przekopiujemy sobie do nowo utworzonego toc-block.php pluginu (pod kodem wygenerowanym przez create-block) ten shortcode, który już znamy, napisaliśmy go wcześniej:
function custom_ul_shortcode($atts, $content = null) {
$stripped_content = strip_tags($content, '<br />');
$pattern = '/\*\*(.*?)\*\*/';
preg_match_all($pattern, $stripped_content, $matches);
if (!empty($matches[1])) {
$output = '<ul>';
foreach ($matches[1] as $match) {
$parts = explode('#', $match);
$text = esc_html($parts[0]);
$anchor = isset($parts[1]) ? esc_attr($parts[1]) : '';
$output .= '<li>';
$output .= '<a href="#' . $anchor . '">' . $text . '</a>';
$output .= '</li>';
}
$output .= '</ul>';
return $output;
}
return '';
}
add_shortcode('custom_ul', 'custom_ul_shortcode');
W tym samym folderze, co plik toc-block.php tworzymy plik toc-present.js o następującej treści:
console.log('Table of contents present');
Teraz możemy podejrzeć w block.json, w folderze src, jak się nasz blok nazywa. Wartość pod kluczem „name” skopiujmy sobie do schowka.
Gdy to mamy, to folder wyżej, w toc-block.php dodajemy nasz skrypt:
function enqueue_if_toc_is_present(){
if(is_singular()){
$id = get_the_ID();
if(has_block('create-block/toc-block', $id)){
wp_enqueue_script('toc-script',plugin_dir_url( __FILE__ ) . 'toc-present.js' );
}
}
}
add_action('wp_enqueue_scripts','enqueue_if_toc_is_present');
To jeszcze nie wszystko. Musimy dostosować nasz block.json (w katalogu src).
Po pierwsze, dodajemy atrybut toc:
"attributes": {
"toc": {
"type": "string",
"default": ""
}
}
Po drugie, nie chcemy więcej niż jednego elementu spis treści, więc musimy dostosować supports:
"supports": {
"html": false,
"multiple": false
},
Całość powinna wyglądać mniej więcej tak:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "create-block/toc-block",
"version": "0.1.0",
"title": "Toc Block",
"category": "widgets",
"icon": "smiley",
"description": "Example block scaffolded with Create Block tool.",
"example": {},
"supports": {
"html": false,
"multiple": false
},
"textdomain": "toc-block",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php",
"viewScript": "file:./view.js",
"attributes": {
"toc": {
"type": "string",
"default": ""
}
}
}
Teraz możemy utworzyć nasz blok komendą:
npm run build
Teraz pozostaje nam tylko aktywować plugin w panelu admina, wejść na jakiś wpis, dodać do niego nasz element TOC, zapisać post i zobaczyć, czy wyświetla się nam w konsoli informacja, że „toc-block is present”.
Możemy też zauważyć, że po raz drugi nie jesteśmy w stanie dodać naszego elementu – ma być obecny tylko raz we wpisie.
Można powiedzieć, że jesteśmy gotowi do zabrania się za pisanie bloku.
Spis treści – tworzymy dynamiczny widok edycji
Nasz spis treści będzie się składać z listy <ul> zawierających elementy <li>, w nich zaś elementy <a> o atrybucie href ustawionym na „#” + przekazana kotwica oraz o zawartości, jaką jest tytuł odnośnika.
Poniżej będziemy mieć input tekstowy do dodawania tytułu i kotwicy oraz obsługujący to wszystko przycisk.
Na początku w pliku edit.js dokonajmy niezbędnych importów:
import { useBlockProps } from '@wordpress/block-editor';
import { useState, useEffect } from 'react';
import { TextControl, Button } from '@wordpress/components';
Teraz pora dodać obsługę atrybutów, do naszej funkcji edit:
export default function Edit({attributes, setAttributes}) {
return (
<p { ...useBlockProps() }>
{ __( 'Toc Block – hello from the editor!', 'toc-block' ) }
</p>
);
}
Potrzebujemy trzech stanów:
- tytuł, czyli tytuł wpisywanego odnośnika
- kotwica, czyli kotwica wpisywanego odnośnika
- content, czyli lista obiektów zawierających tytuł i kotwicę
Wykorzystamy hook useState trzy razy:
export default function Edit({attributes, setAttributes}) {
const [ title, setTitle ] = useState( '' );
const [ anchor, setAnchor ] = useState( '' );
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else
return [];
} );
return (
<p { ...useBlockProps() }>
{ __( 'Toc Block – hello from the editor!', 'toc-block' ) }
</p>
);
}
O ile pierwsze dwa mogą być zrozumiałe, ten trzeci może nas zastanawiać.
Cóż, jeżeli nasz atrybut toc jest pusty (dodajemy element do tworzonego posta) to lista powinna być pusta. Jeżeli jednak edytujemy post, to może się zdarzyć, że już nasz atrybut toc posiada jakieś odnośniki. W takim wypadku musimy je zaciągnąć i odpowiednio do listy wrzucić.
Na razie to sobie zostawiamy i w obu przypadkach zwracamy pustą listę.
Teraz pora na nasze kontrolki:
return (
<div { ...useBlockProps() }>
<ul>
</ul>
<TextControl
label="Tytuł odnośnika"
value={ title }
/>
<TextControl
label="Kotwica HTML"
value={ anchor }
/>
<Button>Dodaj!</Button>
</div>
);
}
Na razie lista będzie pusta. Później dynamicznie tam zaciągniemy nasze dane. Warto jeszcze wyczyścić pewne style w pliku style.scss:
.wp-block-create-block-toc-block {
// background-color: #21759b;
// color: #fff;
padding: 2px;
}
Myślę, że to dobry moment, aby zrobić:
npm run build
Teraz sprawdźmy, czy przy edycji postów widzimy to, co mamy widzieć, czyli:
- kontrolka do wpisywania tytułu
- kontrolka do wpisywania kotwicy
- przycisk dodawania
Jeżeli tak, jesteśmy na dobrej drodze.
Dodajmy minimum interaktywności. Pod naszymi hookami useState, ale przed returnem, wklejamy:
function addToContent() {
if(title === '' || anchor === '')
return;
setTitle('');
setAnchor('');
}
Ten kod trzeba będzie rozbudować. Na razie sprawdza, czy title i anchor nie są puste i jeżeli nie są – czyści je.
Teraz odświeżamy nasz JSXowy markup:
return (
<div { ...useBlockProps() }>
<ul>
</ul>
<TextControl
label="Tytuł odnośnika"
value={ title }
onChange={ ( value ) => setTitle(value) } />
<TextControl
label="Kotwica HTML"
value={ anchor }
onChange={ ( value ) => setAnchor(value) }
/>
<Button onClick={() => addToContent()}>Dodaj!</Button>
</div>
);
}
Nasza lista nadal pusta (zaraz się tym zajmiemy), ale zmiana wartości wpisywanych do title i anchor zmienia już odpowiadające im stany, zaś wciśnięcie przycisku czyści stany i co za tym idzie inputy.
Możemy teraz zrobić jeszcze raz npm run build i wypróbować nasz komponent.
Teraz jeszcze sprawmy, aby funkcja addToContent naprawdę coś robiła:
function addToContent() {
if(title === '' || anchor === '')
return;
let new_item = {
title: title,
anchor: anchor
};
let updatedContent = [...content, new_item];
setContent(updatedContent);
setTitle('');
setAnchor('');
}
Nie mamy jeszcze dynamicznej listy, ale możemy użyć hook useEffect, aby logować sobie zmieniający się content:
useEffect(() => {
console.log(content);
}, [content]);
Całość naszego komponentu powinna wyglądać tak:
export default function Edit({attributes, setAttributes}) {
const [ title, setTitle ] = useState( '' );
const [ anchor, setAnchor ] = useState( '' );
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else
return [];
} );
function addToContent() {
if(title === '' || anchor === '')
return;
let new_item = {
title: title,
anchor: anchor
};
let updatedContent = [...content, new_item];
setContent(updatedContent);
setTitle('');
setAnchor('');
}
useEffect(() => {
console.log(content);
}, [content]);
return (
<div { ...useBlockProps() }>
<ul>
</ul>
<TextControl
label="Tytuł odnośnika"
value={ title }
onChange={ ( value ) => setTitle(value) } />
<TextControl
label="Kotwica HTML"
value={ anchor }
onChange={ ( value ) => setAnchor(value) }
/>
<Button onClick={() => addToContent()}>Dodaj!</Button>
</div>
);
}
Teraz po skompilowaniu i dodaniu, niech będzie, 2 odnośników JS powinien nam wylogować coś takiego:
[
{
"title": "Tytuł 1",
"anchor": "Kotwica 1"
},
{
"title": "Tytuł 2",
"anchor": "Kotwica2"
}
]
To oczywiście stan – nigdzie to się jeszcze nie zapisuje, nigdzie się też nie wyświetla. Tym zajmiemy się za chwilę.
Spis treści – kończymy dynamiczny widok edycji
Brakuje nam kilku rzeczy. Podsumujmy, jakich:
- elementy nie wyświetlają się w liście, choć są dodane do stanu
- stan nie zapisuje się w atrybucie toc (odpowiednio formatowanym, nasz format, przypomnę, to **Tytuł odnośnika#kotwica**)
- gdyby komponent już miał coś w atrybucie toc zapisane (np. edytujemy post z już dodanym elementem toc) to nie jest to w żaden sposób zaciągane.
Tymi wszystkimi problemami zajmiemy się teraz. Po pierwsze, lista.
<ul>
{listItems}
</ul>
Proste, prawda? Tylko teraz to listItems musimy sobie napisać.
Chcemy coś takiego:
<ul>
<li><a href="#kotwica1">Tytuł 1<a/><button>X</button></li>
<li><a href="#kotwica2">Tytuł 2<a/><button>X</button></li>
</ul>
I jeszcze, aby to wszystko działało, miło by było.
To tak – tworzymy (przed returnem) nasze listItems w oparciu o stan content, zawierający listę obiektów z tytułem i kotwicą:
const listItems = content.map((item) => (
<li key={item.anchor}><a href={"#" + item.anchor}>{item.title}</a><Button onClick={() => deleteFromContent(item.anchor)}>X</Button></li>
));
Musimy jeszcze powyżej napisać sobie funkcję, która usuwa elementy, bo jeszcze jej nie mamy:
function deleteFromContent(id) {
let updatedContent = content.filter(item => item.anchor !== id);
setContent(updatedContent);
}
Naszą kotwicę traktujemy tu jako swego rodzaju ID. Nie będzie w spisie treści dwóch odnośników do tego samego nagłówka.
Funkcja odfiltrowywuje element przekazany przez przycisk i zwraca nowy content, bez tego elementu.
Nasz komponent powinien teraz wyglądać tak:
export default function Edit({attributes, setAttributes}) {
const [ title, setTitle ] = useState( '' );
const [ anchor, setAnchor ] = useState( '' );
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else
return [];
} );
function addToContent() {
if(title === '' || anchor === '')
return;
let new_item = {
title: title,
anchor: anchor
};
let updatedContent = [...content, new_item];
setContent(updatedContent);
setTitle('');
setAnchor('');
}
useEffect(() => {
console.log(content);
}, [content]);
function deleteFromContent(id) {
let updatedContent = content.filter(item => item.anchor !== id);
setContent(updatedContent);
}
const listItems = content.map((item) => (
<li key={item.anchor}><a href={"#" + item.anchor}>{item.title}</a><Button onClick={() => deleteFromContent(item.anchor)}>X</Button></li>
));
return (
<div { ...useBlockProps() }>
<ul>
{listItems}
</ul>
<TextControl
label="Tytuł odnośnika"
value={ title }
onChange={ ( value ) => setTitle(value) } />
<TextControl
label="Kotwica HTML"
value={ anchor }
onChange={ ( value ) => setAnchor(value) }
/>
<Button onClick={() => addToContent()}>Dodaj!</Button>
</div>
);
}
Czas na wykonanie komendy:
npm run build
Teraz możemy sprawdzić (edytując posta), że nasz komponent dodaje (w edycji) te nasze elementy, można je także usuwać. Ponadto – są one linkami.
Nadal jednak pozostają nam dwie kwestie:
- stan nie zapisuje się w atrybucie toc (odpowiednio formatowanym, nasz format, przypomnę, to **Tytuł odnośnika#kotwica**)
- gdyby komponent już miał coś w atrybucie toc zapisane (np. edytujemy post z już dodanym elementem toc) to nie jest to w żaden sposób zaciągane.
Zapisywanie i odczytywanie atrybutu toc – kończymy widok edycji
Zastanówmy się, czym będzie się różnić atrybut 'toc’ od naszego stanu content. Załóżmy, że dodaliśmy dwa nowe elementy i nasz content wygląda tak:
[
{
"title": "Tytuł 1",
"anchor": "Kotwica1"
},
{
"title": "Tytuł 2",
"anchor": "Kotwica2"
}
]
Wtedy nasz atrybut toc, który będzie użyty do wyświetlania shortcode, który napisaliśmy, powinien wyglądać tak:
**Tytuł 1#Kotwica1**
**Tytuł 2#Kotwica2**
Możemy od razu przeskoczyć do pliku render.php i wkleić co następuje:
<div <?php echo get_block_wrapper_attributes(); ?>>
<?php echo do_shortcode('[custom_ul]'.$attributes["toc"].'[/custom_ul]');?>
</div>
Czyli tak: nasz komponent będzie wykonywał taki shortcode:
[custom_ul]
**Tytuł 1#Kotwica1**
**Tytuł 2#Kotwica2**
[/custom_ul]
Co przełożyć się ma na taką treść na stronie www (podczas odwiedzania posta):
<ul>
<li><a href="#kotwica1">Tytuł 1<a/><button>X</button></li>
<li><a href="#kotwica2">Tytuł 2<a/><button>X</button></li>
</ul>
Musimy zatem znaleźć sposób, aby naszą listę content zamienić na treść wewnętrzną shortcode jak pokazałem wyżej.
Aby to osiągnąć użyjemy już napisanego przez nas hooka useEffect, który wykonuje się za każdym razem, gdy zmieni się content (na razie tylko loguje content):
useEffect(() => {
let new_toc = "";
console.log(content);
content.map((item) => {
new_toc += `**${item.title}#${item.anchor}**`
});
setAttributes({toc: new_toc});
}, [content]);
Przy każdej zmianie stanu content tworzymy nową wartość dla atrybutu toc. Zamieniamy tam przy pomocy map:
[
{
"title": "Tytuł 1",
"anchor": "Kotwica1"
},
{
"title": "Tytuł 2",
"anchor": "Kotwica2"
}
]
na treść:
**Tytuł 1#Kotwica1**
**Tytuł 2#Kotwica2**
Zapisujemy zmodyfikowaną treść atrybutu zaś plik render.php wyświetla taki shortcode:
[custom_ul]
**Tytuł 1#Kotwica1**
**Tytuł 2#Kotwica2**
[/custom_ul]
Nasz shortcode zamienia to na spis treści:
<ul>
<li><a href="#kotwica1">Tytuł 1<a/><button>X</button></li>
<li><a href="#kotwica2">Tytuł 2<a/><button>X</button></li>
</ul>
Ładowanie zapisanego atrybutu toc – ponowna edycja
Wydawać by się mogło, że wszystko działa. Możemy dodawać elementy, następnie zapisać post i mamy nasz spis treści. Jeżeli jednak będziemy edytować post, to zobaczymy coś dziwnego – naszych elementów po prostu nie ma.
Są zapisane, ale w widoku edycji ich nie ma. Dzieje się tak, ponieważ to, co widzimy w edycji oparte jest na stanie content:
(...)
const listItems = content.map((item) => (
<li key={item.anchor}><a href={"#" + item.anchor}>{item.title}</a><Button onClick={() => deleteFromContent(item.anchor)}>X</Button></li>
));
return (
<div { ...useBlockProps() }>
<ul>
{listItems}
</ul>
(...)
Stan ten zaś jest domyślnie pusty:
(...)
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else
return [];
} );
(...)
Musimy zatem napisać kod, który wykona odwrotność tego, co robiliśmy w poprzedniej sekcji. A mianowicie, sprawdzi, czy atrybut toc nie jest pusty.
Jeżeli nie jest i wygląda na przykład tak:
**Tytuł 1#Kotwica1**
**Tytuł 2#Kotwica2**
To zamieni nam tak wyglądający atrybut na taki stan content:
[
{
"title": "Tytuł 1",
"anchor": "Kotwica1"
},
{
"title": "Tytuł 2",
"anchor": "Kotwica2"
}
]
Wiemy, gdzie ten kod będziemy pisać:
(...)
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else
//TU!!!
} );
(...)
Pytanie, jak do tego się zabrać. Z pomocą przyjdą nam wyrażenia regularne. Już raz ich użyliśmy – w PHP, w naszym shortcode:
$pattern = '/\*\*(.*?)\*\*/';
preg_match_all($pattern, $stripped_content, $matches);
if (!empty($matches[1])) {
$output = '<ul>';
foreach ($matches[1] as $match) {
$parts = explode('#', $match);
$text = esc_html($parts[0]);
$anchor = isset($parts[1]) ? esc_attr($parts[1]) : '';
$output .= '<li>';
$output .= '<a href="#' . $anchor . '">' . $text . '</a>';
$output .= '</li>';
}
$output .= '</ul>';
return $output;
Możemy to zachowanie odtworzyć sobie w JavaScript, z tą różnicą, że tutaj zamieniamy na obiekt, który będzie stanem.
Wygląda to tak:
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else{
let tekst = attributes.toc;
const regex = /\*\*(.*?)\*\*/g;
let match;
let new_content = [];
while ((match = regex.exec(tekst)) !== null) {
let [tit, anch] = match[1].split("#")
new_content.push({title: tit, anchor: anch})
}
return new_content;
}
} );
Ten kod, gdy edytujemy posta z dodanym spisem treści, sprawdza, czy poprzednio dodaliśmy jakieś tytuły i odnośniki, odczytując to z atrybutu toc.
Jeżeli tak – zamienia treść tego atrybutu na stan, który się nam wyświetla na tej liście przy edycji.
Całość komponentu (w pliku edit.js) wygląda tak (nie licząc importów):
export default function Edit({attributes, setAttributes}) {
const [ title, setTitle ] = useState( '' );
const [ anchor, setAnchor ] = useState( '' );
const [ content, setContent ] = useState( () => {
if(attributes.toc === "")
return [];
else{
let tekst = attributes.toc;
const regex = /\*\*(.*?)\*\*/g;
let match;
let new_content = [];
while ((match = regex.exec(tekst)) !== null) {
let [tit, anch] = match[1].split("#")
new_content.push({title: tit, anchor: anch})
}
return new_content;
}
} );
function addToContent() {
if(title === '' || anchor === '')
return;
let new_item = {
title: title,
anchor: anchor
};
let updatedContent = [...content, new_item];
setContent(updatedContent);
setTitle('');
setAnchor('');
}
useEffect(() => {
let new_toc = "";
console.log(content);
content.map((item) => {
new_toc += `**${item.title}#${item.anchor}**`
});
setAttributes({toc: new_toc});
}, [content]);
function deleteFromContent(id) {
let updatedContent = content.filter(item => item.anchor !== id);
setContent(updatedContent);
}
const listItems = content.map((item) => (
<li key={item.anchor}><a href={"#" + item.anchor}>{item.title}</a><Button onClick={() => deleteFromContent(item.anchor)}>X</Button></li>
));
return (
<div { ...useBlockProps() }>
<ul>
{listItems}
</ul>
<TextControl
label="Tytuł odnośnika"
value={ title }
onChange={ ( value ) => setTitle(value) } />
<TextControl
label="Kotwica HTML"
value={ anchor }
onChange={ ( value ) => setAnchor(value) }
/>
<Button onClick={() => addToContent()}>Dodaj!</Button>
</div>
);
}
Prawda, że napisaliśmy monstrum?
Plik render.php jest dużo łatwiejszy:
<?php
/**
* @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
*/
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<?php echo do_shortcode('[custom_ul]'.$attributes["toc"].'[/custom_ul]');?>
</div>
Jeżeli zrobiliśmy to wszystko + poprawnie dodaliśmy nasz shortcode w pliku toc-block.php, to możemy teraz zapisywać do spisu treści, wyświetlać posta, edytorwać posta (dodawać lub usuwać elementy ze spisu) i wszystko będzie działać poprawnie.
Możemy też dodawać sobie nagłówki (albo inne elementy) w treści wpisu, z atrybutem kotwica HTML (po prawej stronie, w zakładce zaawansowane na pierwszym tabie). Te elementy spisu treści mają nas przenosić do odpowiednich nagłówków – pamiętajmy tylko, aby takowe stworzyć i nadać im kotwicę.
Jeżeli tego nie zrobimy – nasza kotwica ze spisu treści będzie prowadziła donikąd. Co prawda na końcu naszego URLa zobaczymy, że działa, dopisane zostanie „#nazwa-kotwicy”, ale żeby to nas jeszcze przeniosło, musimy po prostu dodać nagłówek gdzieś w tekście o takiej samej kotwicy (tylko tam dodajemy bez znaku „#”).
Mam nadzieję, że jest to jasne.
Dodatek – płynne przechodzenie
A zatem tak:
- dodajemy element TOC do wpisu
- dodajemy co najmniej jeden tytuł i kotwicę do spisu treści (kotwica bez spacji i znaku #)
- (opcjonalne) dodajemy duuuużo jakiegokolwiek tekstu (może być lorem ipsum albo zwykły bełkot losowych przycisków z klawiatury – ważne, aby było tego tyle, by wymusić scrolla)
- na dole dodajemy co najmniej jeden nagłówek, a w nim kotwica HTML (kotwica bez spacji i znaku #) o nazwie takiej, jak w spisie treści (nazwa musi się zgadzać, tytuły mogą się różnić, choć tytuł ze spisu treści powinien odpowiadać treści naszego nagłówka – ale nie musi)
Teraz pora kliknąć element ze spisu treści. Wydarzą się dwie rzeczy:
- dopisanie „#nazwa-kotwicy” do naszego URLa w przeglądarce (np. 'http://localhost/blocktry2/2024/05/19/nazwa-posta/#nazwa-kotwicy’)
- ’przeskoczenie’ do elementu, o takiej kotwicy, do tego nagłówka
Może jednak nie podobać się nam sposób w jaki 'skaczemy’ – dzieje się to nagle. Kiedyś takie coś jeszcze uchodziło (a nawet robiło wrażenie w pierwszych latach internetu) – dzisiaj to zły UX (user experience).
W takim wypadku przypomnijmy sobie o pliku toc-present.js, który dodaliśmy tym oto kodem:
function enqueue_if_toc_is_present(){
if(is_singular()){
$id = get_the_ID();
if(has_block('create-block/toc-block', $id)){
wp_enqueue_script('toc-script',plugin_dir_url( __FILE__ ) . 'toc-present.js' );
}
}
}
add_action('wp_enqueue_scripts','enqueue_if_toc_is_present');
Blok ten dodaje nasz skrypt na stronie pojedynczego posta, pod warunkiem, że nasz post posiada element toc-block. Już kiedyś robiliśmy smooth-transition.
Nie wdając się więc w szczegóły – poniższy kod umieszczamy w toc-present.js:
window.addEventListener('DOMContentLoaded', function(){
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
});
Ten kod zamienia wszystkie odnośniki do kotwic w takie przyciski, które płynnie przechodzą do nich, zamiast 'skakać’.
Cóż, powiem szczerze – może i nie wygląda, jakbyśmy zrobili coś specjalnie imponującego, to 'tylko spis treści’, ale prawdę powiedziawszy, zrobiliśmy ogromny postęp. Możemy sobie porównać, czym zajmowaliśmy się w pierwszym tutorialu WordPressa, a czym zajmujemy się teraz.
Jeżeli coś jeszcze nie jest jasne, to polecam przeanalizować projekt oraz poprzednie tutoriale raz jeszcze. Jeżeli to z Reactem i hookami mamy problem – polecam tutoriale Reactowe, także na tej stronie.
Tak czy inaczej – dobra robota!