Table of contents for Kirby 3

Some time ago I ported the wonderful Kirby 2 code from @jenstornell (PHP Table of Contents: https://forum.getkirby.com/t/table-of-contents-for-kirby-3/13780/11) to Kirby 3.

In the following I show the main parts. Because of the CSS code and the license file, I refer to the Kirby 2 code in the above link to @jenstornell’s code on GitHub.

Install

Create a new directory “site\plugins\jenstornell_toc_k3\” with the file “site\plugins\jenstornell_toc_k3\index.php” like:

<?php // site\plugins\jenstornell_toc_k3\index.php
      // based on https://github.com/jenstornell/php-table-of-contents/blob/master/example/example.php, version 1.2
      // adapted for K3 by HeinerEF - 07.-11.01.2020

@include_once __DIR__ . '/src/table-of-contents.php';

Kirby::plugin('jenstornell/php-table-of-contents', [
  'hooks' => [
    'kirbytext:after' => function ($text) {
      $mypos = (stripos($text, '(toc: )') === FALSE);
      if (!($mypos)) {
        $toc = new PHPTableOfContents($text);
        $text = preg_replace('#<p>\(toc: \)</p>#i', "\n" . '      <div class="toc">'
              . "\n" . '        <p>Table of contents of the page:</p>'
              . $toc->list() . '      </div>' . "\n", $toc->html());
      };
      return $text;
    }
  ]
]);

Create a new directory “site\plugins\jenstornell_toc_k3\src\” with the file “site\plugins\jenstornell_toc_k3\src\table-of-contents.php” like:

<?php // site\plugins\jenstornell_toc_k3\src\table-of-contents.php
      // based on https://github.com/jenstornell/php-table-of-contents/blob/master/src/table-of-contents.php, version 1.2
      // some changes by HeinerEF for K3 - 07.-10.01.2020 (proper tag hierarchy from h2 to h6)
      // use "Str::slug(  )" from K3 by HeinerEF

class PHPTableOfContents {
  private $headings;
  private $html;

  public function __construct($html) {
    $this->html = $html;
    $this->setHeadings();
  }

  // Html
  public function html() {
    $html = $this->html;
    $matches = $this->headings;
    foreach($matches[1] as $index => $item) {
      $html = str_replace(
        '>' . $item . '</h',
        '><a id="' . $matches[2][$index] . '"></a>' . $item . ' </h', // 25.04.2020 by HeinerEF
        $html
      );
    }
    return $html;
  }

  // Generate table of content nested list
  // 07.-10.01.2020 changes by HeinerEF (proper tag hierarchy from h2 to h6)
  public function list() {
    $out = '';
    $old_depth = 0;
    $matches = $this->headings;
    foreach($matches[1] as $key => $item) {
      $depth = substr($matches[0][$key], 2, 1) - 2;
      if($old_depth > $depth) {
        while ($old_depth > $depth) {
          $old_depth--;
          $out .= "\n          </ol><!-- #".($old_depth+2)." -->";
        };
      } elseif($old_depth < $depth) {
        $old_depth++;
        $out .= "\n          <li>\n";
        $out .=   "          <ol><!-- #".($old_depth+1)." -->";
      };
      $out .= sprintf("
          <li>
            <span></span>
            <a href='#%s'>%s</a>
          </li>", $matches[2][$key], $item);
    }
    while ($old_depth > 0) {
      $old_depth--;
      $out .= "\n          </ol><!-- #".($old_depth+2)." -->";
    };
    return "\n        <ol><!-- #1 -->" . $out . "\n        </ol><!-- #1 -->\n";
  }

  // Set headings
  private function setHeadings() {
    preg_match_all('|<h[^>]+>(.*)</h[^>]+>|iU', $this->html, $matches);
    $slugs = [];
    foreach($matches[1] as $item) {
      $slugs[] = Str::slug($item);  // changed by HeinerEF using K3
    }
    $this->headings = $matches;
    $this->headings[2] = $slugs;
  }
}

How to use

In the Panel field add three extra lines


(toc: )

where you want to have the TOC.

Remark:

Of course, with this technique it is necessary for all headings to be different in the text for the links to work.

Good luck!

1 Like