Field method for heading ids

I wanted a plugin that transforms headings into headings with ids, similar to Github.

It turns this…

# Hello World

Some text

## Another heading

Another paragraph

…into this…

<h1 id="hello-world">Hello World</h1>

<p>Some text</p>

<h2 id="another-heading">Another heading</h2>

<p>Another paragraph</p>

Plugin code - Field method

The code below can be put into a plugin:

<?php
field::$methods['heading_ids'] = function($field) {
    preg_match_all('|<h[^>]+>(.*)</h[^>]+>|iU', $field->value, $matches);

    foreach($matches[1] as $item) {
        $field->value = str_replace(
            '>' . $item . '</h',
            ' id="' . str::slug($item) . '">' . $item . '</h',
            $field->value
        );
    }
    return $field;
};

Plugin code - Kirbytext post filter

If you prefer a kirbytext post filter, where there is no need to add a field method you can use the code below.

kirbytext::$post[] = function($kirbytext, $value) {
    preg_match_all('|<h[^>]+>(.*)</h[^>]+>|iU', $value, $matches);

    foreach($matches[1] as $item) {
        $value = str_replace(
            '>' . $item . '</h',
            ' id="' . str::slug($item) . '">' . $item . '</h',
            $value
        );
    }

    return $value;
  };

Be aware that str::slug makes words like Bytesrätt into bytesraett. It turns ä into ae which is not nice for Swedish words. To fix it, you need to use something else like https://github.com/cocur/slugify.

I noticed that @wottpal made a similar plugin that is much more advanced here: https://github.com/wottpal/kirby-anchor-headings

1 Like

There’s something similar in the source of this website: https://github.com/getkirby/getkirby.com/blob/master/site/plugins/toc/toc.php

1 Like

Both alternatives use Kirbytext filters, which has the advantage that conversion happens automatically without having to call a field method.

1 Like

@bvdputte Oh that site. Never seen it before in my life. :wink:

@texnixe Yes, you got a point, thanks. :slight_smile: I’ve added it as well in my post. So if it’s going to be on all texts globally, use the kirbytext filter. If it should only be used on only some texts, a field method may be better.