darkpool
darkpool

Reputation: 14641

Wagtail blog post table of contents

Is there a way to create a table of contents for a wagtail site which is being used as a blog. More specifically I want to create a table of contents for each blog post.

For example I have created a BlogPost page model containing a StreamField for sections within a blog post. The stream field contains a heading and a rich text field. This allows me to create blog posts with multiple headings / sections.

Blog post model:

content = StreamField(
    [("article_section", ArticleSectionBlock())],
    null=False,
    blank=False,
)
content_panels = Page.content_panels + [
    StreamFieldPanel("content"),
]

Article Section Block:

class ArticleSectionBlock(blocks.StructBlock):
    sections = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("header", blocks.CharBlock(required=False)),
                ("content", blocks.RichTextBlock(required=False)),
            ]
        )
    )

What I would like to do is be able to create a table of contents at the top of each blog post with links to each section within the post.

Upvotes: 2

Views: 1035

Answers (1)

gasman
gasman

Reputation: 25237

Firstly, I think your ArticleSectionBlock can be simplified to:

class ArticleSectionBlock(blocks.StructBlock):
    header = blocks.CharBlock(required=False))
    content = blocks.RichTextBlock(required=False))

Your content StreamField is already defined to be a list of ArticleSectionBlocks, so there's no need for ArticleSectionBlock to be a list itself. (If you do intend the StreamField to be a list of lists, the answer here still applies, but you'll need to adjust the template to use nested {% for %} loops, and keep in mind that children of a ListBlock don't have unique id properties.)

On your template, you can loop over page.content to get a list of block objects with value, block_type and id properties, each corresponding to one ArticleSectionBlock. By looping over it twice, you can output a table of contents followed by the content itself:

<ul>
    {% for block in page.content %}
        <li><a href="#{{ block.id }}">{{ block.value.header }}</a></li>
    {% endfor %}
</ul>

{% for block in page.content %}
    <h1><a id="{{ block.id }}">{{ block.value.header }}</a></h1>
    {{ block.value.content }}
{% endfor %}

As alternative to using block.id as the anchor ID, you can use {{ forloop.counter }}.

Upvotes: 5

Related Questions