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.