Reputation: 14641
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
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 ArticleSectionBlock
s, 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