Zbliżamy się wielkimi krokami do napisania bloku spis treści pozwalającego na dodawanie spisu treści (razem z linkami) do naszych postów na WordPressie. Tym razem zbudujemy grunt pod ten projekt tworząc shortcode, który treść przekazaną w odpowiednim formacie zamienia na spis treści właśnie.

Spis treści – jak działa short-code

Nasz shortcode działa w następujący sposób – przekazujemy do niego treść jak poniżej

[custom_ul]
**Tytuł Pierwszy#kotwica1**
**Tytuł Drugi#kotwica2**
**Tytuł Trzeci#kotwica3**
[/custom_ul]

Mamy tutaj tag oraz elementy wyróżnione znakami **. Wewnątrz elementów mamy część pierwszą – tytuł, oraz część drugą – kotwicę HTML. To wszystko jako content naszego kodu, między tagiem zamykającym i otwierającym.

Taki napisany kod powinien dać nam mniej więcej taki wynik:

<ul>
    <li><a href="#kotwica1">Tytuł Pierwszy</a></li>
    <li><a href="#kotwica2">Tytuł Drugi</a></li>
    <li><a href="#kotwica3">Tytuł Trzeci</a></li>
</ul>

Te kotwice to mechanizmy, za pomocą których możemy utworzyć link do czegoś na stronie. W WordPressie tworzymy je bardzo łatwo – dodając nagłówek w panelu po prawej stronie w zakładce blok klikamy na zaawansowane, wybieramy „kotwica HTML” i wpisujemy nazwę kotwicy (bez znaku #).

Potem już tylko dodajemy nasz spis treści z odpowiednimi kotwicami.

Tworzymy projekt pluginu – pierwsze kroki

W folderze plugins, wewnątrz folderu wp-content w naszej instalacji WordPressa tworzymy folder o nazwie shortcode-TOC a w nim plik shortcode-TOC.php. Wklejamy następującą treść:

<?php
/**
 * Plugin Name: Shortcode Builder
 * Description: Create a shortcode for a Table of Contents.
 * Version: 1.0
 * Author: Your Name
 */

if (!defined('ABSPATH')) {
    exit;
}

Jest to standardowa procedura bezpieczeństwa, którą powinniśmy już dość dobrze znać. Teraz, poniżej, dodajemy nasz shortcode w najprostszej postaci:

function custom_ul_shortcode($atts, $content = null) {
    return $content;
}
add_shortcode('custom_ul', 'custom_ul_shortcode');

Tytułem bezpieczeństwa, musimy jeszcze utworzyć plik index.php w tym samym folderze o następującej treści:

<?php
// Silence is golden.

Możemy teraz włączyć nasz plugin w panelu admina w naszym WordPressie.

Problemy z contentem – niepotrzebne tagi </br>

Możemy teraz – po włączeniu pluginu – wypróbować nasz shortcode:

[custom_ul]
**Tytuł Pierwszy#kotwica1**
**Tytuł Drugi#kotwica2**
**Tytuł Trzeci#kotwica3**
[/custom_ul]

Jeżeli zobaczymy, co tam zostało wyplute, zauważymy coś mniej więcej takiego (w HTML):

</br>
"**Tytuł Pierwszy#kotwica1**"
</br>
"**Tytuł Drugi#kotwica2**"
</br>
"**Tytuł Trzeci#kotwica3**"
</br>

Od razu mówię – będą z tym problemy. Aby się ich ustrzec, wykonamy taką oto czynność:

function custom_ul_shortcode($atts, $content = null) {
    $stripped_content = strip_tags($content, '<br />');
    return $stripped_content;
}
add_shortcode('custom_ul', 'custom_ul_shortcode');

Funkcja strip_tags usuwa niepotrzebne tagi. Teraz już tych odstępów nieszczęsnych nie mamy. Wypada więc użyj wyrażenia regularnego i przeiterować po wszystkich elementach, dodając je do zmiennej output, którą zwrócimy:

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 = '<p>';
        
        foreach ($matches[1] as $match) {
            $output .= "<span>$match</span>";
        }

        $output .= '</p>';

        return $output;
    }
    return '';

}
add_shortcode('custom_ul', 'custom_ul_shortcode');

Mamy tutaj wzór wyrażenia regularnego a następnie zastosowanie go na wyczyszczonym z tagów </br> content. Jeżeli tam znajdzie się coś pasujące do naszego formatu, to przejdziemy po każdym pasującym elemencie i dodamy go wewnątrz tagu <span> do tagu <p>. Jeżeli regex niczego nie znajdzie – zwrócimy pusty string.

Powyższy kod da nam taki wynik HTML:

<p><span>Tytuł Pierwszy#kotwica1</span><span>Tytuł Drugi#kotwica2</span><span>Tytuł Trzeci#kotwica3</span></p>

Połowa sukcesu za nami.

Podziel elementy po znaku # – funkcja explode

Nasz tekst znajdujący się w każdym $match możemy podzielić za pomocą funkcji explode na część przed haszem i po haszu:

 $parts = explode('#', $match);

Następnie możemy z parts wyłuskać tytuł:

$text = esc_html($parts[0]);

Podobnie możemy wyłuskać kotwicę, zakładając, że została przekazana:

$anchor = isset($parts[1]) ? esc_attr($parts[1]) : '';

Teraz możemy stworzyć nasz element <li> oraz <a>:

 $output .= '<li>';
 $output .= '<a href="#' . $anchor . '">' . $text . '</a>';
 $output .= '</li>';

Wystarczy jeszcze zmienić tag główny z <p> na <ul> i nasza funkcja gotowa:

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');

Już. Działa. Przyda się nam bardzo, gdy będziemy musieli zbudować komponent o nazwie spis treści. Uchylę rąbka tajemnicy, że nasz render.php będzie wyglądał dokładnie tak:

<div <?php echo get_block_wrapper_attributes(); ?>>
	<?php echo do_shortcode('[custom_ul]'.$attributes["toc"].'[/custom_ul]');?>
</div>

Natomiast edit.js – cóż, tam się pobawimy Reactem i to ostro, warto odświeżyć sobie useState i useEffect. Jeżeli czujemy się na siłach, możemy sami spróbować taki blok napisać. Jeżeli nie – wkrótce go napiszemy.

Odnotujmy tylko, że shortcodes można wywoływać z poziomu PHP funkcją WordPressa do_shortcode.

Efekt smooth scroll – dodajemy własny JS

Jeżeli wypróbowaliśmy już nasz shortcode, mógł nie spodobać nam się sposób przeskakiwania do naszych kotwic – jest nagły, dzisiaj już takie przeskoki brzydko wyglądają. Docelowo zrobimy to sobie w naszym komponencie w pliku view.js, ale teraz go nie mamy.

Spróbujmy wejść na posta, do którego dodaliśmy shortcode jak i nagłówki z kotwicami odpowiednio nazwanymi (to nie muszą być nagłówki swoją drogą, ale idea spisu treści jest akurat taka).

Teraz wklejmy ten kod na tej stronie w konsoli developerskiej i zobaczmy różnicę:

document.querySelectorAll('a[href^="#"]').forEach(anchor => {
    anchor.addEventListener('click', function (e) {
        e.preventDefault();

        document.querySelector(this.getAttribute('href')).scrollIntoView({
            behavior: 'smooth'
        });
    });
});

Kod może wydawać nam się odrobinę trudny, to już zależy od naszego poziomu zaawansowania. Mamy tutaj querySelectorAll z dość zaawansowanym selektorem, który bierze wszystkie elementy <a>, które są linkami do kotwic.

Następnie mamy pętlę forEach, która dodaje listener na klik, który po pierwsze robi preventDefault, czyli zapobiega domyślnemu działaniu.

Po drugie, po kliknięciu wyszukujemy sobie w dokumencie, w drzewie DOM element, do którego kotwicy nasz link prowadzi (swoim atrybutem href) i wywołujemy na nim funckję scrollIntoView z określonym efektem 'smooth’ – po kliknięciu w link rzecz jasna.

Teraz musimy zrobić kilka rzeczy. Po pierwsze, tworzymy w naszym pluginie plik smooth.js, na razie o takiej treści:

console.log("hello smooth");

Po drugie, podłączamy w pliku shortcode-TOC.php nasz skrypt do pluginu:

function smooth_script()
{   
    wp_enqueue_script( 'my_custom_script', plugin_dir_url( __FILE__ ) . 'smooth.js' );
}
add_action('wp_enqueue_scripts', 'smooth_script');

Teraz po wejściu na stronę z postem (albo jakąkolwiek stronę) możemy zobaczyć nasz napis. A my zamiast tego chcemy wkleić nasz kod – ale uwaga, dopiero wtedy, gdy drzewo DOM jest gotowe:

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'
            });
        });
    });
});

Tyle wystarczy, aby mieć piękny efekt smooth. Teoretycznie moglibyśmy się bawić w dodawanie tego skryptu np. tylko do strony z pojedynczym postem albo strony zawierającej element TOC (nasz spis treści). Są na to sposoby.

Natomiast ten nasz kod jest dość uniwersalny. Plik wiele nie waży i dodaje event listenery do wszystkich linków do kotwic. A powiedzmy sobie szczerze – każdy link do kotwicy powinien mieć dzisiaj taki efekt.

Dzięki temu, na całej stronie będziemy go mieć.