Add code block
This commit is contained in:
parent
8f6dc4860b
commit
a1d72c122a
17 changed files with 2977 additions and 0 deletions
|
@ -2,3 +2,4 @@ Django==4.0.5
|
|||
wagtail==3.0
|
||||
django-environ==0.8.1
|
||||
whitenoise[brotli]==6.2.0
|
||||
pygments==2.12.0
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@use "bulma/sass/utilities/initial-variables" as *;
|
||||
|
||||
$container-max-width: $widescreen;
|
||||
$pre-background: unset;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'contrib/fontawesome/css/all.min.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% url 'static-pygments:styles' 'default' %}">
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,8 @@ from wagtail import blocks
|
|||
from wagtail.embeds.blocks import EmbedBlock
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
|
||||
from website.contrib.code_block.blocks import CodeBlock
|
||||
|
||||
IGNORE_PLAINTEXT_BLOCKS = (blocks.RawHTMLBlock, EmbedBlock)
|
||||
|
||||
RICH_TEXT_FEATURES = [
|
||||
|
@ -68,6 +70,7 @@ def get_blocks() -> list[tuple[str, blocks.BaseBlock]]:
|
|||
("lorem", LoremBlock()),
|
||||
("html", blocks.RawHTMLBlock()),
|
||||
("image", ImageCaptionBlock()),
|
||||
("code", CodeBlock()),
|
||||
]
|
||||
|
||||
|
||||
|
|
0
website/contrib/README.md
Normal file
0
website/contrib/README.md
Normal file
0
website/contrib/__init__.py
Normal file
0
website/contrib/__init__.py
Normal file
3
website/contrib/code_block/README.md
Normal file
3
website/contrib/code_block/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# `code_block`
|
||||
|
||||
Heavily based on [`wagtail-pygments`](https://github.com/truongvan/wagtail-pygments), with several changes to the block, as well as a view for exposing the CSS styles.
|
0
website/contrib/code_block/__init__.py
Normal file
0
website/contrib/code_block/__init__.py
Normal file
33
website/contrib/code_block/blocks.py
Normal file
33
website/contrib/code_block/blocks.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from typing import Iterator
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
from pygments import highlight
|
||||
from pygments.formatters.html import HtmlFormatter
|
||||
from pygments.lexers import get_all_lexers, get_lexer_by_name
|
||||
from wagtail.blocks import ChoiceBlock, StructBlock, StructValue, TextBlock
|
||||
|
||||
|
||||
def get_language_choices() -> Iterator[tuple[str, str]]:
|
||||
for name, _, _, _ in sorted(get_all_lexers()):
|
||||
yield (name, name)
|
||||
|
||||
|
||||
class CodeStructValue(StructValue):
|
||||
def code(self) -> str:
|
||||
lexer = get_lexer_by_name(self.get("language"))
|
||||
formatter = HtmlFormatter(
|
||||
linenos=None,
|
||||
)
|
||||
return mark_safe(highlight(self.get("source"), lexer, formatter))
|
||||
|
||||
|
||||
class CodeBlock(StructBlock):
|
||||
language = ChoiceBlock(
|
||||
choices=get_language_choices(),
|
||||
)
|
||||
source = TextBlock()
|
||||
|
||||
class Meta:
|
||||
icon = "code"
|
||||
value_class = CodeStructValue
|
||||
template = "contrib/blocks/code.html"
|
|
@ -0,0 +1 @@
|
|||
{{ value.code }}
|
20
website/contrib/code_block/tests.py
Normal file
20
website/contrib/code_block/tests.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from pygments.styles import get_all_styles
|
||||
|
||||
|
||||
class PygmentsStylesTestCase(TestCase):
|
||||
def test_accessible(self) -> None:
|
||||
for style in get_all_styles():
|
||||
with self.subTest(style=style):
|
||||
response = self.client.get(
|
||||
reverse("static-pygments:styles", args=[style])
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response["Cache-Control"], "max-age=3600, public")
|
||||
|
||||
def test_unknown_style(self) -> None:
|
||||
response = self.client.get(
|
||||
reverse("static-pygments:styles", args=["not-a-style"])
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
7
website/contrib/code_block/urls.py
Normal file
7
website/contrib/code_block/urls.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from django.urls import path
|
||||
|
||||
from .views import pygments_styles
|
||||
|
||||
app_name = "code_block"
|
||||
|
||||
urlpatterns = [path("<slug:name>.css", pygments_styles, name="styles")]
|
16
website/contrib/code_block/views.py
Normal file
16
website/contrib/code_block/views.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.views.decorators.cache import cache_control
|
||||
from pygments.formatters.html import HtmlFormatter
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
|
||||
@cache_control(max_age=3600, public=True)
|
||||
def pygments_styles(request: HttpRequest, name: str) -> HttpResponse:
|
||||
try:
|
||||
formatter = HtmlFormatter(style=name)
|
||||
except ClassNotFound:
|
||||
# Raising an exception here bypasses the cache header
|
||||
raise Http404
|
||||
return HttpResponse(
|
||||
formatter.get_style_defs("." + formatter.cssclass), content_type="text/css"
|
||||
)
|
|
@ -23,6 +23,7 @@ INSTALLED_APPS = [
|
|||
"website.home",
|
||||
"website.search",
|
||||
"website.blog",
|
||||
"website.contrib.code_block",
|
||||
"wagtail.contrib.forms",
|
||||
"wagtail.contrib.redirects",
|
||||
"wagtail.contrib.modeladmin",
|
||||
|
|
|
@ -13,6 +13,10 @@ urlpatterns = [
|
|||
path("admin/", include(wagtailadmin_urls)),
|
||||
path("documents/", include(wagtaildocs_urls)),
|
||||
path("search/", search_views.search, name="search"),
|
||||
path(
|
||||
"static-pygments/",
|
||||
include("website.contrib.code_block.urls", namespace="static-pygments"),
|
||||
),
|
||||
re_path(
|
||||
r"^images/([^/]*)/(\d*)/([^/]*)/[^/]*$",
|
||||
ServeView.as_view(action="redirect"),
|
||||
|
|
Loading…
Reference in a new issue