Aktuell kann man im Block-Editor nur Text, Buttons und Bilder verlinken. Aber was, wenn du mehr benötigst? In meinem Fall war die Anforderung, Spalten, Cover- und Gruppen-Blöcke als Ganzes verlinken zu können.

Glücklicherweise ist JavaScript sehr praktisch für solche Dinge und so habe ich verlinkbare Spalten, Cover- und Gruppen-Blöcke erstellt. Dabei gibt es drei Teilaufgaben:

  1. Erlaube das Hinzufügen von Links im Backend zu den genannten Blöcken.
  2. Füge das erforderliche Markup im Frontend hinzu, um die Links dort vorzufinden.
  3. Füge das JavaScript hinzu, damit die Links funktionieren.

Voraussetzungen

Ich gehe hier davon aus, dass du bereits mit dem Block-Editor vertraut bist und bereits ein funktionierendes Projekt zum Kompilieren/Minifizieren von JSX zu JavaScript hast.

Einstellungen im Backend hinzufügen

Ich verwende den Filter blocks.registerBlockType, um die erforderlichen Attribute hinzuzufügen und den Filter editor.BlockEdit, um die eigentlichen Einstellungen in die Sidebar der Blöcke hinzuzufügen. Dort habe ich drei Einstellungen hinzugefügt: Das erste ist ein Textfeld für den Link selbst (wenn ich es heute nochmal machen müsste, würde ich vermutlich ein Element in der Werkzeugleiste des Blocks bevorzugen, wie es auf die Standard-Linkfunktion bereits tut). Das zweite ist eine Einstellung, um alternativ direkt auf das verwendete Medium zu verlinken (was nur für Cover-Blöcke verfügbar ist). Und das dritte ist eine Einstellung, um den Link im aktuellen oder einem neuen Tab zu öffnen.

Der Code sieht folgendermaßen aus:

import assign from 'lodash.assign';
import { InspectorControls } from '@wordpress/block-editor';
import {
	PanelBody,
	SelectControl,
	TextControl,
	ToggleControl,
} from '@wordpress/components';
import { createHigherOrderComponent } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';

const changeAttributes = ( settings, name ) => {
	if ( name !== 'core/column' && name !== 'core/cover' && name !== 'core/group' ) {
		return settings;
	}
	
	settings.attributes = assign( settings.attributes, {
		link: {
			type: 'string',
		},
		linkMedia: {
			default: false,
			type: 'boolean',
		},
		target: {
			enum: [
				'',
				'tab',
			],
			type: 'string',
		},
	} );
	
	return settings;
}

addFilter( 'blocks.registerBlockType', 'my/block-link-attributes', changeAttributes );

const changeControls = createHigherOrderComponent( ( BlockEdit ) => {
	return ( props ) => {
		if ( props.name !== 'core/column' && props.name !== 'core/cover' && props.name !== 'core/group' ) {
			return <BlockEdit { ...props } />;
		}
		
		const {
			attributes: {
				link,
				linkMedia,
				target,
			},
			setAttributes,
			name,
		} = props;
		
		return (
			<>
				<BlockEdit { ...props } />
				
				<InspectorControls>
					<PanelBody
						title={ __( 'Link Settings', 'textdomain' ) }
						initialOpen={ !! link || !! target }
					>
						{ ! linkMedia
							? <TextControl
								label={ __( 'Link', 'textdomain' ) }
								onChange={ link => setAttributes( { link } ) }
								value={ link }
							/>
							: null
						}
						{ name === 'core/cover'
							? <ToggleControl
								checked={ !! linkMedia }
								label={ __( 'Link to Media File', 'textdomain' ) }
								onChange={ linkMedia => setAttributes( { linkMedia } ) }
							/>
							: null
						}
						<SelectControl
							label={ __( 'Link opens in', 'textdomain' ) }
							onChange={ target => setAttributes( { target } )  }
							options={ [
								{ label: __( 'Current Tab', 'textdomain' ), value: '' },
								{ label: __( 'New Tab', 'textdomain' ), value: 'tab' },
							] }
							value={ target }
						/>
					</PanelBody>
				</InspectorControls>
			</>
		);
	};
}, 'changeControls' );

addFilter( 'editor.BlockEdit', 'my/block-link-controls', changeControls, 5 );
Code-Sprache: JavaScript (javascript)

HTML-Code im Frontend hinzufügen

Jetzt benötige ich ein bisschen HTML im Frontend. Dafür benutze ich den Filter render_block_{$this->name}, um das Markup hinzuzufügen. Das besteht aus zwei Attributen (das eine ist der Ziel-Link und der zweite das Ziel („target“)). Zusätzlich füge ich noch einen Button hinzu, der für Benutzer angezeigt wird, die per Tastatur durch die Seite navigieren (aus Barrierefreiheitsgründen).

/**
  * Add linkable block data.
  * 
  * @param	string	$block_content The block content
  * @param	array	$block Block data
  * @return	string The updated block content
  * @throws	\DOMException
  */
 function my_add_linkable_block_data( string $block_content, array $block ): string {
 	$args = wp_parse_args(
 		$block['attrs'],
 		[
 			'link' => '',
 			'linkMedia' => false,
 			'target' => '',
 		]
 	);

 	libxml_use_internal_errors( true );

 	$dom = new DOMDocument();
 	$dom->loadHTML(
 		mb_convert_encoding(
 			'<html>' . $block_content . '</html>',
 			'HTML-ENTITIES',
 			'UTF-8'
 		),
 		LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
 	);

 	// while group blocks is not always divs, we explicitly allow it on divs
 	foreach ( $dom->getElementsByTagName( 'div' ) as $element ) {
 		if ( $args['link'] || $args['linkMedia'] ) {
 			$element->setAttribute( 'class', $element->getAttribute( 'class' ) . ' is-linkable-block' );
 		}

 		if ( $args['link'] ) {
 			$element->setAttribute( 'data-link', $args['link'] );
 			$element->setAttribute( 'data-target', $args['target'] );
 		}

 		if ( $args['linkMedia'] ) {
 			if ( isset( $block['attrs']['url'] ) ) {
 				$element->setAttribute( 'data-link', $block['attrs']['url'] );
 			}
 			else {
 				$element->setAttribute( 'data-link', '' );
 			}

 			$element->setAttribute( 'data-target', $args['target'] );
 		}

 		// create an additional button for accessibility
 		if ( $args['link'] || $args['linkMedia'] ) {
 			$link = $args['link'] ?: $args['url'];
 			$accessibility_button = $dom->createElement( 'button' );
 			$accessibility_button->setAttribute( 'class', 'button my-block__link-button screen-reader-text' );
 			$accessibility_button->setAttribute( 'data-link', $link );
 			$accessibility_button->appendChild( $dom->createTextNode( esc_html__( 'Open Link', 'uscale' ) ) );
 			$element->insertBefore( $accessibility_button, $element->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
 		}
 	}

 	$block_content = $dom->saveHTML( $dom->documentElement ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

 	libxml_use_internal_errors( false );

 	return str_replace(
 		[
 			'<html>',
 			'</html>',
 		],
 		[
 			'',
 			'',
 		],
 		$block_content
 	);
 }

 add_action( 'render_block_core/column', 'my_add_linkable_block_data', 10, 2 );
 add_action( 'render_block_core/cover', 'my_add_linkable_block_data', 10, 2 );
 add_action( 'render_block_core/group', 'my_add_linkable_block_data', 10, 2 );
Code-Sprache: PHP (php)

Klick-Funktionalität hinzufügen

Damit die Links nun noch funktionieren, muss noch ein bisschen JavaScript hinzugefügt werden, das die Klicks auf die entsprechenden Elemente verarbeitet. Diese verwenden die Klasse is-linkable-block, die durch den PHP-Code oben hinzugefügt wurde.

/**
  * Accessible links to whole blocks.
  */
 document.addEventListener( 'DOMContentLoaded', () => {
 	const blocks = document.querySelectorAll( '.is-linkable-block' );

 	if ( ! blocks ) {
 		return;
 	}

 	for ( const block of blocks ) {
 		block.addEventListener( 'click', clickBlock );
 	}

 	/**
 	 * Click on a block.
 	 * 
 	 * @param	{Event}	event The event
 	 */
 	function clickBlock( event ) {
 		const target = event.currentTarget.getAttribute( 'data-target' ) || '';

 		switch ( target ) {
 			case 'tab':
 				window.open( event.currentTarget.getAttribute( 'data-link' ), '_blank' );
 				break;
 			default:
 				window.open( event.currentTarget.getAttribute( 'data-link' ), '_self' );
 				break;
 		}
 	}
 } );
Code-Sprache: JavaScript (javascript)

CSS hinzufügen

Zu guter letzt markiere ich die Elemente noch als Links, indem ich den Cursor entsprechend als pointer angebe. Für den Button, den ich für die Barrierefreiheit hinzugefügt habe, muss die Position noch auf relative gesetzt werden. Wenn dein Theme die Standard-Klasse screen-reader-text nicht unterstützt, ist für den Button zusätzliches CSS erforderlich.

.is-linkable-block {
 	cursor: pointer;
 	position: relative;
 }
Code-Sprache: CSS (css)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert