Skip to content

Commit

Permalink
[#1535] add subheading anchors to product page sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
pi-sigma committed Aug 10, 2023
1 parent ceacfe5 commit 7cd8019
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
{% for anchor in anchors %}
<li class="anchor-menu__list-item">
<a class="link" href="{{ anchor.0 }}">{{ anchor.1 }}</a>
<!-- subheadings -->
{% if anchor.2 %}
<ul class="anchor-menu__sublist anchor-menu__sublist--mobile">
{% for subheading in anchor.2 %}
<li>
<a class="link" href="#{{ subheading.1 }}">{{ subheading.0 }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
{{ contents }}
Expand All @@ -20,6 +30,16 @@
{% for anchor in anchors %}
<li class="anchor-menu__list-item">
<a class="link" href="{{ anchor.0 }}">{{ anchor.1 }}</a>
<!-- subheadings -->
{% if anchor.2 %}
<ul class="anchor-menu__sublist">
{% for subheading in anchor.2 %}
<li>
<a class="link" href="#{{ subheading.1 }}">{{ subheading.0 }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
{{ contents }}
Expand Down
30 changes: 30 additions & 0 deletions src/open_inwoner/pdc/tests/test_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,33 @@ def test_sidemenu_button_is_rendered_when_no_cta_inside_product_content(self):

self.assertTrue(sidemenu_cta_button)
self.assertIn(product.link, sidemenu_cta_button[0].values())


@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls")
class TestProductDetailView(WebTest):
def test_subheadings_in_sidebar(self):
product = ProductFactory(
content="##First subheading\rlorem ipsum...\r##Second subheading",
link="http://www.example.com",
)

response = self.app.get(
reverse("products:product_detail", kwargs={"slug": product.slug})
)

links = response.pyquery(".anchor-menu__sublist").find("a")

# 2 x 2 links (mobile + desktop)
self.assertEqual(len(links), 4)

self.assertEqual(links[0].text, "First subheading")
self.assertEqual(links[0].attrib["href"], "#first-subheading")

self.assertEqual(links[1].text, "Second subheading")
self.assertEqual(links[1].attrib["href"], "#second-subheading")

self.assertEqual(links[2].text, "First subheading")
self.assertEqual(links[2].attrib["href"], "#first-subheading")

self.assertEqual(links[3].text, "Second subheading")
self.assertEqual(links[3].attrib["href"], "#second-subheading")
24 changes: 24 additions & 0 deletions src/open_inwoner/pdc/utils.py
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
from django.utils.text import slugify

import markdown
from bs4 import BeautifulSoup

PRODUCT_PATH_NAME = "products"


def extract_subheadings(content: str, tag: str) -> list[tuple[str, str]]:
"""
:returns: a list of tuples containing a subheading (the text of the `tag` element)
and a slug for the corresponding HTML anchor
"""
md = markdown.Markdown()
html_string = md.convert(content)

soup = BeautifulSoup(html_string, "html.parser")

subs = []
for tag in soup.find_all("h2"):
subheading = tag.text
slug = slugify(subheading)
subs.append((subheading, slug))

return subs
11 changes: 4 additions & 7 deletions src/open_inwoner/pdc/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

from open_inwoner.configurations.models import SiteConfiguration
from open_inwoner.pdc.models.product import ProductCondition
from open_inwoner.plans.models import Plan
from open_inwoner.questionnaire.models import QuestionnaireStep

from ..utils.views import CommonPageMixin
from .choices import YesNo
from .forms import ProductFinderForm
from .models import Category, Product, ProductLocation, Question
from .utils import extract_subheadings


class CategoryBreadcrumbMixin:
Expand Down Expand Up @@ -185,22 +185,19 @@ def get_context_data(self, **kwargs):
product = self.get_object()
context = super().get_context_data(**kwargs)

subheadings = extract_subheadings(product.content, tag="h2")

anchors = [
("#title", product.name),
("#title", product.name, subheadings),
]
if product.question_set.exists():
anchors.append(("#faq", _("Veelgestelde vragen")))
if product.files.exists():
anchors.append(("#files", _("Bestanden")))
if product.locations.exists():
anchors.append(("#locations", _("Locaties")))
if product.links.exists():
anchors.append(("#links", _("Links")))
if product.contacts.exists():
anchors.append(("#contact", _("Contact")))
if product.related_products.published().exists():
anchors.append(("#see", _("Zie ook")))
# anchors.append(("#share", _("Delen"))) disabled for #822

context["anchors"] = anchors
context["related_products_start"] = 6 if product.links.exists() else 1
Expand Down
26 changes: 25 additions & 1 deletion src/open_inwoner/scss/components/Header/AnchorMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
margin-left: var(--spacing-large);
}

.link {
&__list-item {
box-sizing: border-box;
padding: var(--spacing-large) var(--spacing-medium);

Expand All @@ -82,6 +82,30 @@
}
}

// nested list
&__sublist {
list-style-type: '- ';
padding-left: var(--spacing-large);
padding-top: var(--spacing-large);
line-height: var(--spacing-large);

li {
border-left: none;
}
li:not(:last-child) {
margin-bottom: var(--spacing-large);
}
li > a {
font-size: small;
}

&--mobile {
li > a {
font-size: var(--font-size-heading-4) !important;
}
}
}

&--mobile__title {
box-sizing: border-box;
padding: var(--spacing-large) var(--spacing-large) var(--spacing-medium)
Expand Down
4 changes: 4 additions & 0 deletions src/open_inwoner/utils/ckeditor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.utils.text import slugify
from django.utils.translation import gettext as _

import markdown
Expand Down Expand Up @@ -52,6 +53,9 @@ def get_product_rendered_content(product):

element.attrs["class"] = class_name

if tag == "h2":
element.attrs["id"] = slugify(element.text)

if "[CTABUTTON]" in element.text:
# decompose the element when product doesn't have either a link or a form
if not (product.link or product.form):
Expand Down

0 comments on commit 7cd8019

Please sign in to comment.