Compare commits
11 Commits
4828e0bfb8
...
222936cc2a
Author | SHA1 | Date |
---|---|---|
Renovate | 222936cc2a | |
Jake Howard | a1808f4bb4 | |
Renovate | f1934ecdaa | |
Jake Howard | 17969086a4 | |
Jake Howard | f83c38c2f1 | |
Jake Howard | 90b18c7d72 | |
Jake Howard | 506e554230 | |
Jake Howard | 9b27baf1ba | |
Jake Howard | 3a8e6182ad | |
Jake Howard | fe43b9c683 | |
Jake Howard | 6cbac34f2d |
|
@ -15,7 +15,7 @@ FROM python:3.12-slim as production
|
||||||
|
|
||||||
ENV VIRTUAL_ENV=/venv
|
ENV VIRTUAL_ENV=/venv
|
||||||
|
|
||||||
# renovate: datasource=github-tags depName=gchq/cyberchef
|
# renovate: datasource=github-tags depName=just-containers/s6-overlay
|
||||||
ENV S6_OVERLAY_VERSION=3.1.6.2
|
ENV S6_OVERLAY_VERSION=3.1.6.2
|
||||||
|
|
||||||
RUN useradd website --create-home -u 1000 && mkdir /app $VIRTUAL_ENV && chown -R website /app $VIRTUAL_ENV
|
RUN useradd website --create-home -u 1000 && mkdir /app $VIRTUAL_ENV && chown -R website /app $VIRTUAL_ENV
|
||||||
|
|
4
justfile
4
justfile
|
@ -52,5 +52,9 @@ lint_python:
|
||||||
docker-compose -f {{ DEV_COMPOSE }} up -d
|
docker-compose -f {{ DEV_COMPOSE }} up -d
|
||||||
docker-compose -f {{ DEV_COMPOSE }} exec web bash
|
docker-compose -f {{ DEV_COMPOSE }} exec web bash
|
||||||
|
|
||||||
|
@sh-root:
|
||||||
|
docker-compose -f {{ DEV_COMPOSE }} up -d
|
||||||
|
docker-compose -f {{ DEV_COMPOSE }} exec --user=root web bash
|
||||||
|
|
||||||
@down:
|
@down:
|
||||||
docker-compose -f {{ DEV_COMPOSE }} down
|
docker-compose -f {{ DEV_COMPOSE }} down
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"bulma": "0.9.4",
|
"bulma": "0.9.4",
|
||||||
"elevator.js": "1.0.1",
|
"elevator.js": "1.0.1",
|
||||||
"esbuild": "0.19.2",
|
"esbuild": "0.19.2",
|
||||||
"glightbox": "3.2.0",
|
"glightbox": "3.3.0",
|
||||||
"htmx.org": "1.9.2",
|
"htmx.org": "1.9.2",
|
||||||
"lite-youtube-embed": "0.3.0",
|
"lite-youtube-embed": "0.3.0",
|
||||||
"lodash.clamp": "4.0.3",
|
"lodash.clamp": "4.0.3",
|
||||||
|
@ -1504,9 +1504,9 @@
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
},
|
},
|
||||||
"node_modules/glightbox": {
|
"node_modules/glightbox": {
|
||||||
"version": "3.2.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.3.0.tgz",
|
||||||
"integrity": "sha512-iit1xYixqL4YVL+I2YJLfMeyJwvLi6FE6kY3qNKeZHEJgRIz80QU8Rm7YCyw1wOTgXvmNDnXGVhHOHRCwnDltQ=="
|
"integrity": "sha512-SJukatHBZZ/POMOpLUQ6/dhXf/wJTDx1wZ/FwApjseXw2WrRj3Ze9DzNCFYzca0oU7RjXQhi9o02aIZ9SuCz1A=="
|
||||||
},
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
|
@ -4419,9 +4419,9 @@
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
},
|
},
|
||||||
"glightbox": {
|
"glightbox": {
|
||||||
"version": "3.2.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.3.0.tgz",
|
||||||
"integrity": "sha512-iit1xYixqL4YVL+I2YJLfMeyJwvLi6FE6kY3qNKeZHEJgRIz80QU8Rm7YCyw1wOTgXvmNDnXGVhHOHRCwnDltQ=="
|
"integrity": "sha512-SJukatHBZZ/POMOpLUQ6/dhXf/wJTDx1wZ/FwApjseXw2WrRj3Ze9DzNCFYzca0oU7RjXQhi9o02aIZ9SuCz1A=="
|
||||||
},
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
"bulma": "0.9.4",
|
"bulma": "0.9.4",
|
||||||
"elevator.js": "1.0.1",
|
"elevator.js": "1.0.1",
|
||||||
"esbuild": "0.19.2",
|
"esbuild": "0.19.2",
|
||||||
"glightbox": "3.2.0",
|
"glightbox": "3.3.0",
|
||||||
"htmx.org": "1.9.2",
|
"htmx.org": "1.9.2",
|
||||||
"lite-youtube-embed": "0.3.0",
|
"lite-youtube-embed": "0.3.0",
|
||||||
"lodash.clamp": "4.0.3",
|
"lodash.clamp": "4.0.3",
|
||||||
|
|
|
@ -10,7 +10,7 @@ wagtail-generic-chooser==0.6
|
||||||
django-rq==2.10.1
|
django-rq==2.10.1
|
||||||
django-redis==5.4.0
|
django-redis==5.4.0
|
||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
psycopg2==2.9.9
|
psycopg==3.1.18
|
||||||
djangorestframework
|
djangorestframework
|
||||||
django-htmx==1.17.2
|
django-htmx==1.17.2
|
||||||
wagtail-metadata==5.0.0
|
wagtail-metadata==5.0.0
|
||||||
|
@ -32,6 +32,7 @@ wagtail-lite-youtube-embed==0.1.0
|
||||||
# DRF OpenAPI dependencies
|
# DRF OpenAPI dependencies
|
||||||
uritemplate
|
uritemplate
|
||||||
PyYAML
|
PyYAML
|
||||||
|
inflection
|
||||||
|
|
||||||
# Use custom `wagtail-favicon` with performance improvements
|
# Use custom `wagtail-favicon` with performance improvements
|
||||||
git+https://github.com/RealOrangeOne/wagtail-favicon@b892165e047b35c46d7244109b9ad9226d32a213
|
git+https://github.com/RealOrangeOne/wagtail-favicon@b892165e047b35c46d7244109b9ad9226d32a213
|
||||||
|
|
|
@ -56,6 +56,9 @@ section.content {
|
||||||
.gslide-image img {
|
.gslide-image img {
|
||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
|
|
||||||
|
// Manually set sizes, as mermaid images are very small
|
||||||
|
width: 80vw !important;
|
||||||
|
|
||||||
&[src*="mermaid.ink"] {
|
&[src*="mermaid.ink"] {
|
||||||
@include dark-mode {
|
@include dark-mode {
|
||||||
filter: invert(100%);
|
filter: invert(100%);
|
||||||
|
|
|
@ -11,7 +11,7 @@ from django.http.response import Http404, HttpResponse, HttpResponseBadRequest
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
from django.utils.functional import cached_property, classproperty
|
from django.utils.functional import cached_property, classproperty
|
||||||
from django.utils.text import slugify
|
from django.utils.text import Truncator, slugify
|
||||||
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
|
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
|
||||||
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
|
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
|
||||||
from wagtail.contrib.settings.models import BaseGenericSetting, register_setting
|
from wagtail.contrib.settings.models import BaseGenericSetting, register_setting
|
||||||
|
@ -31,12 +31,10 @@ from .serializers import PaginationSerializer
|
||||||
from .streamfield import add_heading_anchors, get_blocks, get_content_html
|
from .streamfield import add_heading_anchors, get_blocks, get_content_html
|
||||||
from .utils import (
|
from .utils import (
|
||||||
TocEntry,
|
TocEntry,
|
||||||
count_words,
|
|
||||||
extract_text,
|
extract_text,
|
||||||
get_site_title,
|
get_site_title,
|
||||||
get_table_of_contents,
|
get_table_of_contents,
|
||||||
get_url_mime_type,
|
get_url_mime_type,
|
||||||
truncate_string,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -141,16 +139,11 @@ class BaseContentPage(BasePage, MetadataMixin):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def word_count(self) -> int:
|
def word_count(self) -> int:
|
||||||
return count_words(self.plain_text)
|
return len(self.plain_text.split())
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def summary(self) -> str:
|
def summary(self) -> str:
|
||||||
summary = truncate_string(self.plain_text, 50)
|
return Truncator(self.plain_text).words(50)
|
||||||
|
|
||||||
if summary and summary != self.plain_text and not summary.endswith("."):
|
|
||||||
summary += "…"
|
|
||||||
|
|
||||||
return summary
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def body_html(self) -> str:
|
def body_html(self) -> str:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<a href="{% image_url value.image 'original' %}" class="glightbox" data-gallery="content" data-height="70vh" data-width="95vw" data-alt="{{ value.caption|richtext|extract_text }}">
|
<a href="{% image_url value.image 'original' %}" class="glightbox" data-gallery="content" data-height="70vh" data-alt="{{ value.caption|richtext|extract_text }}" data-title="{{ value.caption|richtext|extract_text }}">
|
||||||
<img src="{% image_url value.image 'width-1500' %}" alt="{{ value.caption|richtext|extract_text }}" loading="lazy" decoding="async" />
|
<img src="{% image_url value.image 'width-1500' %}" alt="{{ value.caption|richtext|extract_text }}" loading="lazy" decoding="async" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% load wagtailcore_tags %}
|
||||||
|
|
||||||
|
{% spaceless %}
|
||||||
|
{{ obj.content_html | truncatewords_html:100 | safe }}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="{% fullpageurl obj %}">Continue Reading…</a>
|
||||||
|
</p>
|
||||||
|
{% endspaceless %}
|
|
@ -3,7 +3,6 @@ from django.test import SimpleTestCase
|
||||||
from wagtail.rich_text import features as richtext_feature_registry
|
from wagtail.rich_text import features as richtext_feature_registry
|
||||||
|
|
||||||
from website.common.utils import (
|
from website.common.utils import (
|
||||||
count_words,
|
|
||||||
extract_text,
|
extract_text,
|
||||||
get_table_of_contents,
|
get_table_of_contents,
|
||||||
heading_id,
|
heading_id,
|
||||||
|
@ -97,13 +96,6 @@ class ExtractTextTestCase(SimpleTestCase):
|
||||||
self.assertEqual(extract_text("Hello there!"), "Hello there!")
|
self.assertEqual(extract_text("Hello there!"), "Hello there!")
|
||||||
|
|
||||||
|
|
||||||
class CountWordsTestCase(SimpleTestCase):
|
|
||||||
def test_counts_words(self) -> None:
|
|
||||||
self.assertEqual(count_words("a b c"), 3)
|
|
||||||
self.assertEqual(count_words("Correct Horse Battery Staple"), 4)
|
|
||||||
self.assertEqual(count_words("Hello there! How are you?"), 5)
|
|
||||||
|
|
||||||
|
|
||||||
class RichTextFeaturesTestCase(SimpleTestCase):
|
class RichTextFeaturesTestCase(SimpleTestCase):
|
||||||
def test_features_exist(self) -> None:
|
def test_features_exist(self) -> None:
|
||||||
for editor, editor_config in settings.WAGTAILADMIN_RICH_TEXT_EDITORS.items():
|
for editor, editor_config in settings.WAGTAILADMIN_RICH_TEXT_EDITORS.items():
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from itertools import islice, pairwise
|
from itertools import pairwise
|
||||||
from typing import Iterable, Optional, Type
|
from typing import Optional, Type
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup, SoupStrainer
|
from bs4 import BeautifulSoup, SoupStrainer
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from django.utils.text import re_words, slugify
|
from django.utils.text import slugify
|
||||||
from django_cache_decorator import django_cache_decorator
|
from django_cache_decorator import django_cache_decorator
|
||||||
from wagtail.models import Page, Site
|
from wagtail.models import Page, Site
|
||||||
from wagtail.models import get_page_models as get_wagtail_page_models
|
from wagtail.models import get_page_models as get_wagtail_page_models
|
||||||
|
@ -69,19 +69,6 @@ def show_toolbar_callback(request: HttpRequest) -> bool:
|
||||||
return settings.DEBUG
|
return settings.DEBUG
|
||||||
|
|
||||||
|
|
||||||
def split_words(text: str) -> Iterable[str]:
|
|
||||||
for word in re_words.split(text):
|
|
||||||
if word and word.strip():
|
|
||||||
yield word.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def count_words(text: str) -> int:
|
|
||||||
"""
|
|
||||||
Count the number of words in the text, without duplicating the item in memory
|
|
||||||
"""
|
|
||||||
return len(list(split_words(text)))
|
|
||||||
|
|
||||||
|
|
||||||
def extract_text(html: str) -> str:
|
def extract_text(html: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get the plain text of some HTML.
|
Get the plain text of some HTML.
|
||||||
|
@ -91,10 +78,6 @@ def extract_text(html: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def truncate_string(text: str, words: int) -> str:
|
|
||||||
return " ".join(islice(split_words(text), words))
|
|
||||||
|
|
||||||
|
|
||||||
def heading_id(heading: str) -> str:
|
def heading_id(heading: str) -> str:
|
||||||
"""
|
"""
|
||||||
Convert a heading into an identifier which is valid for a HTML id attribute
|
Convert a heading into an identifier which is valid for a HTML id attribute
|
||||||
|
|
|
@ -64,6 +64,7 @@ class KeybaseView(TemplateView):
|
||||||
class AllPagesFeed(Feed):
|
class AllPagesFeed(Feed):
|
||||||
feed_type = CustomFeed
|
feed_type = CustomFeed
|
||||||
link = "/"
|
link = "/"
|
||||||
|
description_template = "feed-description.html"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.style_tag = f'<?xml-stylesheet href="{static("contrib/pretty-feed-v3.xsl")}" type="text/xsl"?>'.encode()
|
self.style_tag = f'<?xml-stylesheet href="{static("contrib/pretty-feed-v3.xsl")}" type="text/xsl"?>'.encode()
|
||||||
|
@ -123,9 +124,6 @@ class AllPagesFeed(Feed):
|
||||||
def item_updateddate(self, item: BasePage) -> datetime:
|
def item_updateddate(self, item: BasePage) -> datetime:
|
||||||
return item.last_published_at
|
return item.last_published_at
|
||||||
|
|
||||||
def item_description(self, item: BasePage) -> str:
|
|
||||||
return getattr(item, "summary", None) or item.title
|
|
||||||
|
|
||||||
def item_categories(self, item: BasePage) -> Optional[list[str]]:
|
def item_categories(self, item: BasePage) -> Optional[list[str]]:
|
||||||
if isinstance(item, BlogPostPage):
|
if isinstance(item, BlogPostPage):
|
||||||
return item.tags_list.values_list("slug", flat=True)
|
return item.tags_list.values_list("slug", flat=True)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<a href="https://mermaid.ink/svg/{{ value.pako }}" data-gallery="content" class="glightbox" data-type="image" data-height="60vh" data-width="95vw" data-alt="{{ value.caption|richtext|extract_text }}">
|
<a href="https://mermaid.ink/svg/{{ value.pako }}" data-gallery="content" class="glightbox" data-type="image" data-height="70vh" data-alt="{{ value.caption|richtext|extract_text }}" data-title="{{ value.caption|richtext|extract_text }}">
|
||||||
<img src="https://mermaid.ink/svg/{{ value.pako }}" referrerpolicy="no-referrer" alt="{{ value.caption|richtext|extract_text }}" loading="lazy" decoding="async" />
|
<img src="https://mermaid.ink/svg/{{ value.pako }}" referrerpolicy="no-referrer" alt="{{ value.caption|richtext|extract_text }}" loading="lazy" decoding="async" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -375,6 +375,11 @@ LOGGING = {
|
||||||
"level": "WARNING",
|
"level": "WARNING",
|
||||||
"propagate": False,
|
"propagate": False,
|
||||||
},
|
},
|
||||||
|
"wagtail.images": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": "DEBUG",
|
||||||
|
"propagate": False,
|
||||||
|
},
|
||||||
"django.request": {
|
"django.request": {
|
||||||
"handlers": ["console"],
|
"handlers": ["console"],
|
||||||
"level": "ERROR",
|
"level": "ERROR",
|
||||||
|
|
Loading…
Reference in New Issue