Reputation: 53538
I have a bunch of content in a Wagtail 2.0 rich text field that looks like
Page heading
(intro blurb)
heading 1
(heading-1-relevant text)
heading 2
(heading-2-relevant text)
...
and I would like to give each heading an id
so that any text can be made a link to jump to the relevant content. I can't seem to find an option to give headings an explicit id
, and the "link" button in the rich text editor does not seem to let me pick active fragment identifiers in the content.
Is there a way to add fragment identifier based navigation on the same page work with Wagtail's rich text editor?
Upvotes: 5
Views: 2143
Reputation: 53538
Revisiting my own question a year later because this is still something we need, the solution we came up with is to simply wrap the RichText html serialization, and putting fragment id injection on top:
import re
from django import template
from django.utils.text import slugify
from wagtail.core.rich_text import RichText
# We'll be wrapping the original RichText.__html__(), so make
# sure we have a reference to it that we can call.
__original__html__ = RichText.__html__
# This matches an h1/.../h6, using a regexp that is only
# guaranteed to work because we know that the source of
# the HTML code we'll be working with generates nice
# and predictable HTML code (and note the non-greedy
# "one or more" for the heading content).
heading_re = r"<h([1-6])([^>]*)>(.+?)</h\1>"
def add_id_attribute(match):
"""
This is a regexp replacement function that takes
in the above regex match results, and then turns:
<h1>some text</h1>
Into:
<h1><a id="some-text"></a><a href="#some-text">some text</a></h1>
where the id attribute value is generated by running
the heading text through Django's slugify() function.
"""
n = match.group(1)
attributes= match.group(2)
text_content = match.group(3)
id = slugify(text_content)
return f'<h{n}{attributes}><a id="{id}"></a><a href="#{id}">{text_content}</a></h{n}>'
def with_heading_ids(self):
"""
We don't actually change how RichText.__html__ works, we just replace
it with a function that does "whatever it already did", plus a
substitution pass that adds fragment ids and their associated link
elements to any headings that might be in the rich text content.
"""
html = __original__html__(self)
return re.sub(heading_re, add_id_attribute, html)
# Rebind the RichText's html serialization function such that
# the output is still entirely functional as far as wagtail
# can tell, except with headings enriched with fragment ids.
RichText.__html__ = with_heading_ids
This works rather well, does not require any hacking in draftail or wagtail, and is very easy to enable/disable simply by loading this code as part of the server startup process (we have it living in our wagtailcustom_tags.py file, so when Django loads up all template tag sets, the RichText "enrichment" kicks in automatically).
We had initially tried to extend the ... | richtext
template filter, but while that's entirely possible, that only works for custom blocks we ourselves wrote, with our own custom templates, and so turned out to not be a solution given the idea that it should "just work".
Upvotes: 10
Reputation: 4138
To have control over the structure of your page body, it's preferable to encourage users to use heading blocks, rather than headings within the rich text block. Then you can have a heading block type which has two fields, a 'text' and an 'id', and you can specify a template that outputs the h
element with the id
attribute.
class Heading2Block(blocks.StructBlock):
heading = blocks.CharBlock(classname='full title')
link_id = blocks.CharBlock(help_text='For making hyperlinks to this heading')
class Meta:
template = 'blocks/h2.html'
Put the following in blocks/h2.html
:
<h1{% if value.link_id %} id="{{ value.link_id|slugify }}"{% endif %}>{{ value.heading }}</h1>
In earlier versions of Wagtail it was possible to remove the h
widget from the Hallo.js rich text editor, and this was a good way of encouraging user adoption of the heading block. Similar restriction is not currently present in Draftail, but there is a pull request which reimplements it.
Upvotes: 0