From 03e9da57a1f09ca4f15dce73ccfcc50849e11226 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Fri, 1 Jul 2022 09:25:57 +0100 Subject: [PATCH] Add anchor links to headers --- static/src/scss/_content.scss | 10 ++++++++++ website/common/models.py | 6 +++++- .../common/templates/common/content_page.html | 4 ++-- website/common/utils.py | 18 ++++++++++++++++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/static/src/scss/_content.scss b/static/src/scss/_content.scss index f9a86e3..51ec8a8 100644 --- a/static/src/scss/_content.scss +++ b/static/src/scss/_content.scss @@ -1,6 +1,16 @@ section.content { font-size: 1.25rem; padding-top: 0.5rem; + + h2, + h3, + h4, + h5, + h6 { + .heading-anchor { + margin-right: 0.5rem; + } + } } .content-details { diff --git a/website/common/models.py b/website/common/models.py index 38b2c43..592a4eb 100644 --- a/website/common/models.py +++ b/website/common/models.py @@ -10,7 +10,7 @@ from wagtail.images import get_image_model_string from wagtail.models import Page from .streamfield import get_blocks, get_html, get_word_count, truncate_streamfield -from .utils import TocEntry, get_table_of_contents +from .utils import TocEntry, add_heading_anchors, get_table_of_contents class BasePage(Page): @@ -68,6 +68,10 @@ class BaseContentMixin(models.Model): def summary(self) -> str: return truncate_streamfield(self.body, 50) + @cached_property + def body_html(self): + return add_heading_anchors(str(self.body)) + class ContentPage(BasePage, BaseContentMixin): # type: ignore[misc] subpage_types: list[Any] = [] diff --git a/website/common/templates/common/content_page.html b/website/common/templates/common/content_page.html index e580c42..6a92e9b 100644 --- a/website/common/templates/common/content_page.html +++ b/website/common/templates/common/content_page.html @@ -1,13 +1,13 @@ {% extends "wagtail_base.html" %} -{% load wagtailcore_tags static %} +{% load static %} {% block content %} {% include "common/hero.html" %}
- {% include_block page.body %} + {{ page.body_html|safe }}
{% endblock %} diff --git a/website/common/utils.py b/website/common/utils.py index 1fefd08..c57586f 100644 --- a/website/common/utils.py +++ b/website/common/utils.py @@ -5,9 +5,12 @@ from typing import Type from bs4 import BeautifulSoup from django.conf import settings from django.http.request import HttpRequest +from django.utils.text import slugify from wagtail.models import Page from wagtail.models import get_page_models as get_wagtail_page_models +HEADER_TAGS = ["h2", "h3", "h4", "h5", "h6"] + @dataclass class TocEntry: @@ -20,10 +23,10 @@ class TocEntry: def get_table_of_contents(html: str) -> list[TocEntry]: soup = BeautifulSoup(html, "lxml") - headings = soup.find_all(["h2", "h3", "h4", "h5", "h6"]) + headings = soup.find_all(HEADER_TAGS) heading_levels = [ - TocEntry(tag.text, tag.text, int(tag.name[1]), []) for tag in headings + TocEntry(tag.text, slugify(tag.text), int(tag.name[1]), []) for tag in headings ] # Abort if there are no headings @@ -53,6 +56,17 @@ def get_table_of_contents(html: str) -> list[TocEntry]: return root.children +def add_heading_anchors(html: str) -> str: + soup = BeautifulSoup(html, "lxml") + for tag in soup.find_all(HEADER_TAGS): + slug = slugify(tag.text) + anchor = soup.new_tag("a", href="#" + slug, id=slug) + anchor.string = "#" + anchor.attrs["class"] = "heading-anchor" + tag.insert(0, anchor) + return str(soup) + + def get_page_models() -> list[Type[Page]]: page_models = get_wagtail_page_models().copy() page_models.remove(Page)