Can't get TOC (table of content) working

Hello fellow Kirby’s,

I am struggling with the following. I want to remake the Kirby Cookbook recipe of the TOC (table of content). I think I’m doing something wrong but I can’t find what I’m doing wrong. I follow the Cookbook recipe.

My folder structure is as following:

  • plugin (folder)
    • toc (folder)
      • index.php
      • snippets (folder)
        • toc.php

Index.php

<?php

Kirby::plugin('k-cookbook/toc', [
  '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;

    }
  ]
]);

Toc.php

<?php if ($headlines->isNotEmpty()) : ?>
  <nav class="toc">

    <h2>Table of Contents</h2>

    <ol>
      <?php foreach($headlines as $headline): ?>
        <li><a href="<?= $headline->url() ?>"><?= $headline->text() ?></a></li>
      <?php endforeach ?>
    </ol>

  </nav>
<?php endif ?>

config:

<?php

return [
  // …other settings
  'k-cookbook.toc.headlines' => ['h2', 'h3', 'h4'],
];

In my template I use the following snippet to output the TOC:

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

and this snippet to output my content:

<?= $page->text()->kt()->anchorHeadlines(['h2', 'h3', 'h4']) ?>

What am I doing wrong?

Cheers!

The headlines field method only accepts a single headline level.

The whole TOC thingy will only work if you use it in conjunction with the kirbytext after hook.

1 Like

Hi @pixelijn, thanks for your help!

To be sure, I have to add a (toc) placeholder in my kirbytext content field? Do you know if there is a way to add this to my template? Or what should I do to make the headline field work with multiple headline levels?

Thanks!

Multiple headline levels are not possible with the headline field nor with the toc placeholder, as it says in the recipe.

If you want some ugly code: Table of contents for Kirby 3 - #20 by anon77445132

Thanks!

But the whole idea of a TOC is to make a table of H1, H2, H3 and H4, right? Is that what you mean by multiple headlines?

Well, you can have a TOC with only one headline level (only the h2 for example) as in the recipe, or you can have a TOC with all headline levels (h1-h6). It all depends on your needs. If you want all your headlines in your ToC, the recipe won’t help you-

1 Like

Okay that clears it up for me. I was assuming that the recipe also worked with h2,h3, h4 because they where mentioned in the code. I missed the note by accident.

Can someone push me in the right direction where I should start?

This is what I am able to put together with the help of the internet. It works when I put the H1, H2, H3, H4 static in the PHP. Of course I want to get it dynamic so I need to replace the top part with a more a bit more Kirby code,

<?php
	function TableOfContents($depth)
	{
	$filename = __FILE__;
	//read in the file
	$file = fopen($filename,"r");
	$html_string = fread($file, filesize($filename));
	fclose($file);
 
	//get the headings down to the specified depth
	$pattern = '/<h[2-'.$depth.']*[^>]*>.*?<\/h[2-'.$depth.']>/';
	$matchit = preg_match_all($pattern,$html_string,$numbers);
 
	//reformat the results to be more usable
	$heads = implode("\n",$numbers[0]);
	$heads = str_replace('<a name="','<a href="#',$heads);
	$heads = str_replace('</a>','',$heads);
	$heads = preg_replace('/<h([1-'.$depth.'])>/','<li class="toc$1">',$heads);
	$heads = preg_replace('/<\/h[1-'.$depth.']>/','</a></li>',$heads);
 
	//convert to HTML
	$contents = '<div id="toc"> 
	<p id="toc-header">Contents</p>
	<ul>
	'.$heads.'
	</ul>
	</div>';
	echo $contents;
	}
 ?>

<?php TableOfContents(3); ?>

Can someone help me to rewrite this part of the code to more Kirbyish code? I do not want to read the file on the server but the Kirby text field. I tried to match it with $page-text() and $field->kt()->value() but was not able to get it work.

$filename = __FILE__;
//read in the file
$file = fopen($filename,"r");
$html_string = fread($file, filesize($filename));
fclose($file);

And the content (static):

<h2 id="test-h2"><a href="#test-h2">Test h2</a></h2>
    <p>Some test paragraphs.</p>
<h3 id="test-h3"><a href="#test-h3">Test h3</a></h2>
    <p>Some test paragraphs.</p>
<h4 id="test-h4"><a href="#test-h4">Test h4</a></h2>
    <p>Some test paragraphs.</p>

Why don’t you use the code that was linked above: https://getkirby.com/docs/reference/plugins/extensions/page-models until someone comes up with nicer code.

I’m not sure but the code linked above is the (toc: ) placeholder method right? I want to be able to make a snippet so I can use it in my template. Because I couldn’t figure it out I continued my search on the internet.