function csstricks_toc($post=false) {
global $post;
$blocks = parse_blocks( $post->post_content );
$headings = array();
foreach( $blocks as $block ) {
if( 'core/heading' === $block['blockName'] ) {
if( false !== strpos( $block['innerHTML'], 'id=' ) ) {
$element = 'h' . $block['attrs']['level'];
$title = wp_strip_all_tags( $block['innerHTML'] );
} else {
$title = wp_strip_all_tags( $block['innerHTML'] );
}
$headings[] = $title;
}
}
if( !empty( $headings ) ) {
echo '<ol class="toc">';
foreach( $headings as $heading )
echo '<li class="toc-link"><a href="#aa-' . sanitize_title_with_dashes($heading) . '">' . $heading . '</a></li>';
echo '</ol>';
}
}
The crux of this comes from The SEO Guidebox. The original snippet parses all of the blocks in the post content, looks for a heading level, strips its HTML, and then spits out the heading text in an ordered list.
I really wanted the outputted text to be anchored to the post headings. We already use a plugin here on CSS-Tricks that dynamically inserts ID on headings, so I didn’t need that part and ripped it out. Then I added an anchor element to the list items that uses the sanitize_title_with_dashes()
function convert the $heading
into a lowercase string separated with dashes.
The only thing is that the plugin we use to insert IDs prepends aa-
to each ID it generates. So, I slipped that into the loop and now we have a table of contents component on this site. We took it a little bit further by using an ACF field that lets us insert the table of contents conditionally.
