ToC from text fields in Builder Blocks

I’m trying to get a Table of Contents from all h2 in text fields in Builder Blocks. I was just using one text field before. Now I’ve got several Blocks that contain a text field with possible h2’s in there.

This will give me the text I need, but I’m in the dark on how to proceed, could you turn on the :bulb: light for me? Thanks.

<?php foreach ($page->pagebuilder()->toBuilderBlocks() as $blocks): ?>
    <?= $blocks->text() ?>
<?php endforeach ?>

plugins/toc:

Kirby::plugin('zoon/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;
        }
    ]
]);

snippet:

<?php // Table of Content
    if($item->count()): ?>
    <nav class="toc" aria-labelledby="toc-head">
        <h2 class="toc-heading" id="toc-head">On this page</h2>
        <ol>
            <?php foreach($item as $headline): ?>
                <li><a href="<?= $headline->url() ?>"><?= $headline->text() ?></a></li>
            <?php endforeach ?>
        </ol>
    </nav>
<?php endif ?>

(btw: happy 2020 for all the Kirby people out there :smiley:)

Hm, the easiest would be to collect all blocks into a single new virtual field, I think. Then call the headlines() method on that field.

Yes, I read a few threads about it but it’s not clear enough for me, to adapt it to my situation. Could you give an example?

Or have you tried to call it directly on the structure field?

$page->pagebuilder()->headlines()

Don’t know what that returns, though.

I haven’t tested this, but you could merge the texts of the different blocks and then search this text for H2s using the existing code.

Kirby::plugin('zoon/toc', [
    'fieldMethods' => [
        'headlinesFromBuilderBlocks' => function($field, $headline = 'h2') {
            $allTexts = ""
            foreach ($field->toBuilderBlocks() as $blocks) {
               $allTexts .= $blocks->text()->kt()
            }
            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;
        }
    ]
]);

Then you could do $page->pagebuilder()->headlinesFromBuilderBlocks()

@texnixe Yeah, gave multiple ToC’s with one item (headline)

@timoetting Thank you I’ll give that a spin, (btw: awesome plugin)

1 Like

@timoetting It works, kind of, I also get non h2 text pulled in. For example 3. is a h2 but 4. is text from the same text field. Also getting some field names sections: and columnsstructure:

Schermafbeelding-2020-01-08-om-16.11.03

In case I’m making mistakes in my blueprint (works without errors), this is the blueprints/blocks/carrousel where paragraph text gets pulled in and a field name slides:

label: Carrousel {{ carrouselname }}
name: Carrousel 
tabs:
  content:
    label: Content
    icon: edit
    fields:
      text:
        label: Text
        type: markdown
      slides:
        label: Slides
        type: structure
        min: 2
        max: 6
        columns:
          images:
            width: 1/4
          caption:
            width: 3/4
        fields:
          images:
            label: Images
            type: files
            max: 1
            uploads: false
            layout: cards
            size: tiny
            info: "{{ file.dimensions }}"
            image:
              cover: true
              back: black
          caption:
            label: Caption
            type: markdown
            buttons: 
              - bold
              - italic
              - code
              - ul
              - ol
              - pagelink
              - link
              - email
  settings:
    label: Settings
    icon: cog
    fields:
      carrouselname:
        label: Name
        type: text
        help: For panel only
      carrouselinfo:
        label: Info
        type: info
        text: A carrousel can hold up to a maximum of 6 images 

@timoetting If you think this gets to complicated, let me know, I might just have to implement a manual option.

I think there’s an error, try

Kirby::plugin('zoon/toc', [
    'fieldMethods' => [
        'headlinesFromBuilderBlocks' => function($field, $headline = 'h2') {
            $allTexts = ""
            foreach ($field->toBuilderBlocks() as $blocks) {
               $allTexts .= $blocks->text()->kt()
            }
            preg_match_all('!<' . $headline . '.*?>(.*?)</' . $headline . '>!s', $allTexts, $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;
        }
    ]
]);
1 Like

Works, thank you very much!