website/website/common/streamfield.py

136 lines
3.3 KiB
Python
Raw Normal View History

from itertools import product
2022-06-26 19:25:30 +01:00
from bs4 import BeautifulSoup, SoupStrainer
2022-06-26 18:37:04 +01:00
from django.utils import lorem_ipsum
from django.utils.html import format_html_join
from django.utils.text import slugify
2022-06-26 18:37:04 +01:00
from wagtail import blocks
from wagtail.embeds.blocks import EmbedBlock
2022-06-27 20:40:55 +01:00
from wagtail.images.blocks import ImageChooserBlock
2022-06-26 18:37:04 +01:00
from website.common.utils import HEADER_TAGS
2022-06-27 23:29:55 +01:00
from website.contrib.code_block.blocks import CodeBlock
2022-06-27 19:58:08 +01:00
RICH_TEXT_FEATURES = [
"h2",
"h3",
"h4",
"h5",
"h6",
"bold",
"italic",
"ol",
"ul",
"link",
"document-link",
"code",
"strikethrough",
2022-07-14 21:41:43 +01:00
"snippet-link",
"snippet-embed",
2022-06-27 19:58:08 +01:00
]
2022-07-05 09:03:45 +01:00
RICH_TEXT_FEATURES_PLAIN = [
"bold",
"italic",
"link",
"document-link",
"code",
"strikethrough",
]
2022-06-27 20:40:55 +01:00
RICH_TEXT_FEATURES_SIMPLE = [
"bold",
"italic",
2022-07-05 09:03:45 +01:00
"ol",
"ul",
2022-06-27 20:40:55 +01:00
"link",
"document-link",
"code",
"strikethrough",
]
2022-06-26 18:37:04 +01:00
class LoremBlock(blocks.StructBlock):
paragraphs = blocks.IntegerBlock(min_value=1)
2022-06-26 19:25:30 +01:00
def render(self, value: dict, context: dict | None = None) -> str:
2022-06-26 18:37:04 +01:00
return format_html_join(
"\n\n",
"<p>{}</p>",
[(paragraph,) for paragraph in lorem_ipsum.paragraphs(value["paragraphs"])],
)
class Meta:
icon = "openquote"
label = "Lorem Ipsum"
2022-06-27 20:40:55 +01:00
class ImageCaptionBlock(blocks.StructBlock):
image = ImageChooserBlock()
2022-07-05 09:03:45 +01:00
caption = blocks.RichTextBlock(features=RICH_TEXT_FEATURES_PLAIN)
2022-06-27 20:40:55 +01:00
class Meta:
icon = "image"
label = "Image with caption"
template = "common/blocks/image-caption.html"
2022-07-05 09:03:45 +01:00
class TangentBlock(blocks.StructBlock):
name = blocks.CharBlock(max_length=64)
content = blocks.RichTextBlock(features=RICH_TEXT_FEATURES_SIMPLE)
class Meta:
icon = "comment"
label = "Tangent"
template = "common/blocks/tangent.html"
IGNORE_PLAINTEXT_BLOCKS = (blocks.RawHTMLBlock, EmbedBlock, ImageCaptionBlock)
IGNORE_HEADING_BLOCKS = (*IGNORE_PLAINTEXT_BLOCKS, LoremBlock)
2022-06-26 18:37:04 +01:00
def get_blocks() -> list[tuple[str, blocks.BaseBlock]]:
return [
("embed", EmbedBlock()),
2022-06-27 19:58:08 +01:00
("rich_text", blocks.RichTextBlock(features=RICH_TEXT_FEATURES)),
2022-06-26 18:37:04 +01:00
("lorem", LoremBlock()),
("html", blocks.RawHTMLBlock()),
2022-06-27 20:40:55 +01:00
("image", ImageCaptionBlock()),
2022-06-27 23:29:55 +01:00
("code", CodeBlock()),
2022-07-05 09:03:45 +01:00
("tangent", TangentBlock()),
2022-06-26 18:37:04 +01:00
]
2022-06-26 19:25:30 +01:00
def get_content_html(html: str) -> str:
"""
Get the HTML of just original content (eg not embeds etc)
"""
block_classes = [
f"block-{block_name}"
for block_name, block in get_blocks()
if not isinstance(block, IGNORE_PLAINTEXT_BLOCKS)
]
return str(
BeautifulSoup(html, "lxml", parse_only=SoupStrainer(class_=block_classes))
)
def add_heading_anchors(html: str) -> str:
targets: list[str] = [
f".block-{block_name} {header_tag}"
for header_tag, block_name in product(
HEADER_TAGS,
[b[0] for b in get_blocks() if not isinstance(b[1], IGNORE_HEADING_BLOCKS)],
)
]
2022-06-26 19:52:20 +01:00
soup = BeautifulSoup(html, "lxml")
for tag in soup.select(", ".join(targets)):
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)