TOC for layout and fields

Hey,

there is a template with text fields and layout fields.
I’m trying to integrate a table of content like in the cookbook (Anchors and ToCs | Kirby CMS)

When i include some h2 h3 in the text fields (not in Layout) the anchors are set to the headlines.
But no TOC appears! :frowning:

plugins > toc > index.php

<?php

Kirby::plugin('lordcanis/toc', [
    'hooks' => [

        'kirbytext:after' => [
    
          function($text) {
    
            // get the headline levels to convert from a config option, we use h2 as the default
            $headlines = option('lordcanis.toc.headlines', 'h2|h3');
    
            // create the regex pattern to be used as first argument in `preg_replace_callback()`
            $headlinesPattern = is_array($headlines) ? implode('|', $headlines) : $headlines;
    
            // use `preg_replace_callback()` to replace matches with anchors
            $text = preg_replace_callback('!<(' . $headlinesPattern . ')>(.*?)</\\1>!s', function ($match) {
    
                // create the id from the headline text
                $id = Str::slug(Str::unhtml($match[2]));
    
                // return the modified headline: 
                // $match[1] contains the match for the first subpattern, i.e. `h2`, `h3` etc.
                // $match[2] contains the match for the second subpattern, i.e. the actual headline text
                return '<' . $match[1] . ' id="' . $id . '"><a href="#' . $id . '">' . $match[2] . '</a></' . $match[1] . '>';
    
            }, $text);
    
            return $text;
          },
        ],
     
      'fieldMethods' => [
    
        'headlines' => function($field, $headline = 'h2') {
    
            preg_match_all('!<' . $headline . '.*?>(.*?)</' . $headline . '>!s', $field->kt()->value(), $matches);

            $headlines = new Collection();
            foreach ($matches[1] as $text) {
            
                $headline = new Obj([
                    'id'   => $id = '#' . Str::slug(Str::unhtml($text)),
                    'url'  => $id,
                    'text' => trim(strip_tags($text)),
                ]);
                $headlines->append($headline->url(), $headline);
            }
            
            return $headlines;
        }   
      ],
    
      'snippets' => [
        'toc' => __DIR__ . '/snippets/toc.php'
      ],
]
]);

plugins > toc > snippets > toc.php

 <?php if ($headlines->count() >= 3) : ?>
  <nav class="toc">
    <h2>Table of Contents</h2>
    <ol>
      <?php foreach($headlines as $headline): ?>
          <li><a href="<?= $page->url() . '/#' . Str::slug($headline->text()) ?>"><?= $headline->text() ?></a></li>
      <?php endforeach ?>
    </ol>
  </nav>
<?php endif ?>

config > config.php

...
 'lordcanis.toc.headlines' => ['h2', 'h3'],
...

snippet in template
<?php snippet('toc', ['headlines' => $page->text()->headlines('h2|h3')]) ?>

snippets > blocks > heading.php

<?php /** @var \Kirby\Cms\Block $block */ ?>
<<?= $level = $block->level()->or('h2') ?> class="<?= $block->ausrichtung() ?>" id="<?= Str::slug($block->text()) ?>"><?= $block->text() ?></<?= $level ?>>

Why is there no toc?
How can i display the headline from the text fields and the layout field?

Thank you!

Do you have a field called text in your blueprint?

No! There is a text field <?= $page->hero_text1()->kt() ?>.
When i include some h2 or/and h3 there - the anchors are set.

snippet in template
<?php snippet('toc', ['headlines' => $page->hero_text1()->headlines('h2|h3')]) ?>

also shows no TOC.

There are two issues with your code:

  1. The headlines method doesn’t accept a pattern as parameter, just a single headline level, e.g. h2.
  2. You have an error in your index.php, the hooks array is not closed, instead the closing bracket is after the snippets.

Sorry for my late reply!

So what do I have to do?

Turn this:

...
'fieldMethods' => [
           
  'headlines' => function($field, $headline = 'h2') {
 ....

Into this?

...
 'fieldMethods' => [
    
  'headlines' => function($field, $headline = '!\(toc(?::\s?(h[1-6]))?\)!') {
  ...

Error is fixed!

Now I get an error from the \plugins\toc\snippets\toc.php → Call to a member function text() on string.

<li><a href="<?= $page->url() . '/#' . Str::slug($headline->text()) ?>"><?= $headline->text() ?></a></li>