Przygotowujemy się do napisania naszego spisu treści od nowa – tym razem żadnych shortcodes ani innych „klasycznych” WordPressowych rozwiązań tudzież adapterów pomiędzy klasycznymi a blokowymi czasami. Piszemy blok wewnętrzny (pojedynczy element spisu treści), który posiada tytuł, kotwicę, potrafi płynnie przechodzić do nagłówka o tej kotwicy (frontendowy React w pliku view.js + biblioteczka ScrollIntoView) oraz potrafi dynamicznie dodawać w edytorze nagłówek przy pomocy skryptu wp.data.

Tworzymy projekt – podstawowe zadania

Tradycyjnie tworzymy blok dynamiczny przy pomocy narzędzia create-block taką oto komendą (w folderze plugins w wp-content w naszej instalacji WordPressa):

npx @wordpress/create-block@latest anchor-block1 --variant=dynamic

Gdy już wszystko zainstalowane przechodzimy do folderu komendą change directory:

cd anchor-block1

Teraz możemy wejść do panelu admina i włączyć nasz plugin. Wykonamy jeszcze jedną rzecz, jaką jest zainstalowanie komponentu <ScrollIntoView>:

npm i --save react-scroll-into-view

Przyda się później. W pliku block.json dodajemy potrzebne nam atrybuty, jakimi są tytuł oraz kotwica:

"attributes": {
		"title": {
			"type": "string",
			"default": "Your heading"
			},
		"anchor": {
				"type": "string",
				"default": ""
				}
		}

W pliku edit.js wykonamy kilka importów:

import { useBlockProps , InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl } from "@wordpress/components";
import { TextControl, Button } from '@wordpress/components';
import { useState } from '@wordpress/element';

Jesteśmy gotowi do pracy.

Plik edit.js – pierwsze szlify

Po pierwsze, chcemy, aby nasza funkcja edit mogła korzystać z atrybutów z pliku block.json:

export default function Edit({attributes, setAttributes}) {

Po drugie – potrzebna nam funkcja handleKeyDown. Będziemy jej używać w kontrolce do wpisywania kotwicy HTML – nie chcemy, aby kotwica zawierała spacje:

export default function Edit({attributes, setAttributes}) {
	const handleKeyDown = e => {
		if (e.key === " ") {
		  e.preventDefault();
		}
	  };

Przypiszemy sobie tę funkcję do akcji onKeyDown kontrolki do wpisywania naszych kotwic. Kontrolka będzie się znajdowała po prawej stronie, w panelu bocznym, w zakładce „Blok”. Będziemy musieli zatem użyć tych wszystkich naszych importów, aby to osiągnąć:

export default function Edit({attributes, setAttributes}) {
	const handleKeyDown = e => {
		if (e.key === " ") {
		  e.preventDefault();
		}
	  };
	return (
		<>
		<InspectorControls>
          <PanelBody title={"Settings"}>
		  <TextControl
            label="Tytuł odnośnika"
            value={ attributes.title }
            onChange={ ( value ) => setAttributes({title: value}) } />
			<TextControl
            label="Kotwica HTML"
            value={ attributes.anchor }
            onChange={ ( value ) => setAttributes({anchor: value}) }
			onKeyDown={handleKeyDown}/>
		</PanelBody>
        </InspectorControls>
		<li { ...useBlockProps() }>{attributes.title}</li>
		</>
	);
}

Użytkownik zobaczy tylko element <li> w panelu głównym. Zawartość <InspectorControls> wyląduje w panelu bocznym w zakładce „Blok”. Mamy tam <PanelBody> czyli pod-zakładkę, w której znajdują się dwie znane już nam kontrolki <TextControl>. Jednej z nich ustawiliśmy zdarzenie onKeyDown blokujące możliwość wprowadzania spacji.

To dobry moment na kompilację i wypróbowanie tego i owego:

npm run build

Cóż, jak zwykle będziemy musieli wykomentować pewne rzeczy z styles.scss:

.wp-block-create-block-anchor-block1 {
	// background-color: #21759b;
	// color: #fff;
	padding: 2px;
}

Teraz powinno być lepiej.

Pliki render.php oraz view.js – podstawowa funkcjonalność naszego elementu

W pliku render.php przekażemy nasze atrybuty jako tzw. data-attributes. Robimy tak, ponieważ chcemy frontend dla zapisanych bloków również mieć w React, ze wszystkimi jego możliwościami (czego nie mamy w render.php ani w blokach korzystających z save.js).

Przekazujemy atrybuty w ten sposób (render.php):

<div <?php echo get_block_wrapper_attributes(); ?> 
data-title="<?php echo esc_attr($attributes["title"] ?? ""); ?>"
data-anchor="<?php echo esc_attr($attributes["anchor"] ?? ""); ?>">
</div>

Jeżeli znamy podstawy HTML i wiemy, co to dataset, to nie powinno nas to dziwić. Teraz trzeba wziąć się za view.js. Po pierwsze, importy:

import ScrollIntoView from 'react-scroll-into-view';
import { createRoot } from '@wordpress/element';

ScrollIntoView to biblioteczka, którą zainstalowaliśmy, która umożliwia płynne przechodzenie do elementu o wskazanym ID. createRoot to funkcja potrzebna do podmieniania naszych bloków na dynamiczne komponenty Reactowe. Na razie bowiem nasz zapisany blok obsługiwany jest przez render.php i w kodzie wygląda mniej więcej tak:

<div class="wp-block-create-block-anchor-block1" data-title="asdasddas" data-anchor="asdasdsad"></div>

Na stronie nie wygląda w ogóle, bo nie ma treści. Przechodzimy do view.js.

Zaczniemy od napisania komponentu AnchorLink:

function AnchorLink({title, anchor}) {
    return (
    <ScrollIntoView selector={'#'+anchor}>
		<li><a>{title}</a></li>
	</ScrollIntoView>
    );
}

Ten komponent ma w propsach dostać title i anchor i zwrócić element <li> z tagiem <a> zawierającym title. Ma to wszystko opleść wrapperem <ScrollIntoView> z odpowiednim selektorem tak, aby kliknięcie w element powodowało płynne przejście do elementu o przekazanej kotwicy. Tę część roboty robi za nas zainstalowana biblioteczka ScrollIntoView.

Teraz tylko musimy pobrać nasze elementy (najlepiej po klasie, jaką jest „wp-block-create-block-anchor-block1”) i popodmieniać je na elementy <AnchorLink>, pobierając z datasetu title i anchor i odpowiednio przekazując do propsów. Robimy to w view.js:

document.addEventListener('DOMContentLoaded', () => {
    const blocks = document.querySelectorAll('.wp-block-create-block-anchor-block1');
    blocks.forEach((block) => {
        const anchor = block.dataset.anchor;
        const title = block.dataset.title;
        const root = createRoot(block);
        root.render(<AnchorLink anchor={anchor} title={title}/>);
    });
});

Teraz pora na komendę:

npm run build

Następnie wchodzimy do edycji posta, dodajemy element o określonym tytule i kotwicy. Otwieramy konsolę deweloperską, wklejamy ten kod (napisaliśmy go sobie w jednej lekcji):

function generateLorem(num = 1) {
	const lorem = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quam, atque. Unde, tempora dignissimos? Corrupti, saepe amet ipsum voluptates, earum placeat quibusdam sed eum harum id rerum provident possimus rem molestias.";
	while(num) {
		let para = wp.blocks.createBlock( 
			'core/paragraph', { content: lorem }
		   );
		wp.data.dispatch( 'core/block-editor' ).insertBlocks( para );
	num--;
	}
}

Wciskamy enter i jeszcze wywołanie naszej funkcji:

generateLorem(5);

Magicznie dostaniemy pięć paragrafów z lorem ipsum w naszym edytorze postów. Powinno wystarczyć. Na samym dole dodajemy nagłówek, tytuł może być dowolny (choć dobrze by było, aby się zgadzał z dodanym przez nas elementem spisu treści), kotwica musi być taka sama.

Teraz zapisujemy post, odwiedzamy jako internauta i już widzimy, że nasz element pięknie i bez „szarpania” przechodzi do odpowiedniego elementu.

Automatyczne dodawanie nagłówka z odpowiednią treścią i kotwicą

Skoro mogliśmy zautomatyzować dodawanie paragrafów, to nic nie stoi na przeszkodzie, aby nagłówek trzeba było dodawać manualnie. Mało tego – o ile w prawdziwych sytuacjach nie będziemy potrzebowali „lorem ipsum”, to nagłówki z odpowiednią treścią i kotwicą już tak, a więc nic nie stoi na przeszkodzie, aby robić to z poziomu edytora.

Dodajmy przycisk w edit.js:

<Button onClick={() => onClickHandler()}>Dodaj nagłówek</Button>
</PanelBody>

Teraz dodajmy ten handler pod naszą funkcją onKeyDown:

const handleKeyDown = e => {
		if (e.key === " ") {
		  e.preventDefault();
		}
	  };
function onClickHandler() {
		var content = attributes.title;
		var name = 'core/heading';
		var insertedBlock = wp.blocks.createBlock(name, {
			content: content,
			anchor: attributes.anchor,
			level: 2
		});
wp.data.dispatch('core/block-editor').insertBlocks(insertedBlock);
}

Teraz możemy kompilować. Guzik automatycznie dodaje nagłówek z odpowiednią treścią i kotwicą, co jest dużym ułatwieniem, zwłaszcza dla tych użytkowników, którzy nie do końca muszą ogarniać czym są kotwice i tak dalej.

Zróbmy im jeszcze jedno ułatwienie – możliwość dodania od razu nagłówka o określonej wielkości. Krok pierwszy – useState oraz użycie stanu w naszej funkcji:

const [ size, setSize ] = useState(2);
	function onClickHandler() {
		var content = attributes.title;
		var name = 'core/heading';
		var insertedBlock = wp.blocks.createBlock(name, {
			content: content,
			anchor: attributes.anchor,
			level: size
		});
		
		wp.data.dispatch('core/block-editor').insertBlocks(insertedBlock);
	}

Krok drugi – select control do zmiany stanu:

<SelectControl
  label="Rozmiar nagłówka"
  value={ size }
   options={ [
   { label: 'H1', value: 1 },
   { label: 'H2', value: 2 },
   { label: 'H3', value: 3 },
   { label: 'H4', value: 4 },
   { label: 'H5', value: 5 },
    { label: 'H6', value: 6 },
            ] }
   onChange={ ( newSize ) => setSize( newSize ) }
   __nextHasNoMarginBottom/>
<Button onClick={() => onClickHandler()}>Dodaj nagłówek</Button>

Teraz wystarczy tylko skompilować i wypróbować. Dodajemy element anchor, wypełniamy tytuł i kotwicę, wybieramy wielkość nagłówka z listy, klikamy przycisk i dostajemy nagłówek o odpowiedniej wielkości, z tytułem i kotwicą od razu wypełnioną.

Wystarczy zapisać i wszystko działa, w dodatku użytkownik nie musi nic wiedzieć o czymkolwiek, aby być w stanie ustawić spis tak, aby działał.

W przyszłości zajmiemy się elementem zewnętrznym spisu treści, do którego ładować będziemy nasz anchor block jako blok wewnętrzny.