aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pydis_site/apps/content/urls.py12
-rw-r--r--pydis_site/apps/content/utils.py101
-rw-r--r--pydis_site/apps/content/views/page_category.py5
-rw-r--r--pydis_site/apps/content/views/tags.py79
-rw-r--r--pydis_site/static/css/content/color.css7
-rw-r--r--pydis_site/static/css/content/tag.css8
-rw-r--r--pydis_site/static/js/content/listing.js5
-rw-r--r--pydis_site/templates/content/listing.html17
-rw-r--r--pydis_site/templates/content/tag.html5
9 files changed, 187 insertions, 52 deletions
diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py
index b4ffc07d..03c0015a 100644
--- a/pydis_site/apps/content/urls.py
+++ b/pydis_site/apps/content/urls.py
@@ -39,15 +39,21 @@ def get_all_pages() -> DISTILL_RETURN:
def get_all_tags() -> DISTILL_RETURN:
- """Return all tag names in the repository in static builds."""
+ """Return all tag names and groups in static builds."""
+ groups = {None}
for tag in utils.get_tags_static():
- yield {"name": tag.name}
+ groups.add(tag.group)
+ yield {"location": (f"{tag.group}/" if tag.group else "") + tag.name}
+
+ groups.remove(None)
+ for group in groups:
+ yield {"location": group}
urlpatterns = [
distill_path("", views.PageOrCategoryView.as_view(), name='pages'),
distill_path(
- "tags/<str:name>/",
+ "tags/<path:location>/",
views.TagView.as_view(),
name="tag",
distill_func=get_all_tags
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index cc08f81f..da6a024d 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -2,6 +2,7 @@ import datetime
import functools
import tarfile
import tempfile
+import typing
from io import BytesIO
from pathlib import Path
@@ -16,7 +17,6 @@ from markdown.extensions.toc import TocExtension
from pydis_site import settings
from .models import Tag
-TAG_URL_BASE = "https://github.com/python-discord/bot/tree/main/bot/resources/tags"
TAG_CACHE_TTL = datetime.timedelta(hours=1)
@@ -44,9 +44,13 @@ def get_tags_static() -> list[Tag]:
"""
Fetch tag information in static builds.
+ This also includes some fake tags to preview the tag groups feature.
This will return a cached value, so it should only be used for static builds.
"""
- return fetch_tags()
+ tags = fetch_tags()
+ for tag in tags[3:5]:
+ tag.group = "very-cool-group"
+ return tags
def fetch_tags() -> list[Tag]:
@@ -79,10 +83,15 @@ def fetch_tags() -> list[Tag]:
repo.extractall(folder, included)
for tag_file in Path(folder).rglob("*.md"):
+ group = None
+ if tag_file.parent.name != "tags":
+ # Tags in sub-folders are considered part of a group
+ group = tag_file.parent.name
+
tags.append(Tag(
name=tag_file.name.removesuffix(".md"),
+ group=group,
body=tag_file.read_text(encoding="utf-8"),
- url=f"{TAG_URL_BASE}/{tag_file.name}"
))
return tags
@@ -114,31 +123,85 @@ def get_tags() -> list[Tag]:
return Tag.objects.all()
-def get_tag(name: str) -> Tag:
- """Return a tag by name."""
- tags = get_tags()
- for tag in tags:
- if tag.name == name:
+def get_tag(path: str) -> typing.Union[Tag, list[Tag]]:
+ """
+ Return a tag based on the search location.
+
+ The tag name and group must match. If only one argument is provided in the path,
+ it's assumed to either be a group name, or a no-group tag name.
+
+ If it's a group name, a list of tags which belong to it is returned.
+ """
+ path = path.split("/")
+ if len(path) == 2:
+ group, name = path[0], path[1]
+ else:
+ name = path[0]
+ group = None
+
+ matches = []
+ for tag in get_tags():
+ if tag.name == name and tag.group == group:
return tag
+ elif tag.group == name and group is None:
+ matches.append(tag)
+
+ if matches:
+ return matches
raise Tag.DoesNotExist()
-def get_category_pages(path: Path) -> dict[str, dict]:
- """Get all page names and their metadata at a category path."""
- # Special handling for tags
- if path == Path(__file__).parent / "resources/tags":
- tags = {}
- for tag in get_tags():
- content = frontmatter.parse(tag.body)[1]
+def get_tag_category(
+ tags: typing.Optional[list[Tag]] = None, *, collapse_groups: bool
+) -> dict[str, dict]:
+ """
+ Generate context data for `tags`, or all tags if None.
+
+ If `tags` is None, `get_tag` is used to populate the data.
+ If `collapse_groups` is True, tags with parent groups are not included in the list,
+ and instead the parent itself is included as a single entry with it's sub-tags
+ in the description.
+ """
+ if not tags:
+ tags = get_tags()
+
+ data = []
+ groups = {}
- tags[tag.name] = {
+ # Create all the metadata for the tags
+ for tag in tags:
+ if tag.group is None or not collapse_groups:
+ content = frontmatter.parse(tag.body)[1]
+ data.append({
"title": tag.name,
"description": markdown.markdown(content, extensions=["pymdownx.superfences"]),
- "icon": "fas fa-tag"
- }
+ "icon": "fas fa-tag",
+ })
+ else:
+ if tag.group not in groups:
+ groups[tag.group] = {
+ "title": tag.group,
+ "description": [tag.name],
+ "icon": "fas fa-tags",
+ }
+ else:
+ groups[tag.group]["description"].append(tag.name)
- return {name: tags[name] for name in sorted(tags)}
+ # Flatten group description into a single string
+ for group in groups.values():
+ group["description"] = "Contains the following tags: " + ", ".join(group["description"])
+ data.append(group)
+
+ # Sort the tags, and return them in the proper format
+ return {tag["title"]: tag for tag in sorted(data, key=lambda tag: tag["title"].lower())}
+
+
+def get_category_pages(path: Path) -> dict[str, dict]:
+ """Get all page names and their metadata at a category path."""
+ # Special handling for tags
+ if path == Path(__file__).parent / "resources/tags":
+ return get_tag_category(collapse_groups=True)
pages = {}
diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py
index 01ce8402..062c2bc1 100644
--- a/pydis_site/apps/content/views/page_category.py
+++ b/pydis_site/apps/content/views/page_category.py
@@ -5,7 +5,7 @@ from django.conf import settings
from django.http import Http404, HttpRequest, HttpResponse
from django.views.generic import TemplateView
-from pydis_site.apps.content import utils
+from pydis_site.apps.content import models, utils
class PageOrCategoryView(TemplateView):
@@ -91,4 +91,7 @@ class PageOrCategoryView(TemplateView):
"page_title": category["title"],
"page_description": category["description"],
"icon": category.get("icon"),
+ "app_name": "content:page_category",
+ "is_tag_listing": "/resources/tags" in path.as_posix(),
+ "tag_url": models.Tag.URL_BASE,
}
diff --git a/pydis_site/apps/content/views/tags.py b/pydis_site/apps/content/views/tags.py
index 5295537d..a8df65db 100644
--- a/pydis_site/apps/content/views/tags.py
+++ b/pydis_site/apps/content/views/tags.py
@@ -1,4 +1,5 @@
import re
+import typing
import frontmatter
import markdown
@@ -16,23 +17,65 @@ COMMAND_REGEX = re.compile(r"`*!tags? (?P<name>[\w\d-]+)`*")
class TagView(TemplateView):
"""Handles tag pages."""
- template_name = "content/tag.html"
+ tag: typing.Union[Tag, list[Tag]]
+ is_group: bool
+
+ def setup(self, *args, **kwargs) -> None:
+ """Look for a tag, and configure the view."""
+ super().setup(*args, **kwargs)
- def get_context_data(self, **kwargs) -> dict:
- """Get the relevant context for this tag page."""
try:
- tag = utils.get_tag(kwargs.get("name"))
+ self.tag = utils.get_tag(kwargs.get("location"))
+ self.is_group = isinstance(self.tag, list)
except Tag.DoesNotExist:
raise Http404
+ def get_template_names(self) -> list[str]:
+ """Either return the tag page template, or the listing."""
+ if self.is_group:
+ template_name = "content/listing.html"
+ else:
+ template_name = "content/tag.html"
+
+ return [template_name]
+
+ def get_context_data(self, **kwargs) -> dict:
+ """Get the relevant context for this tag page or group."""
context = super().get_context_data(**kwargs)
- context["page_title"] = tag.name
+ context["breadcrumb_items"] = [{
+ "name": utils.get_category(settings.CONTENT_PAGES_PATH / location)["title"],
+ "path": location,
+ } for location in (".", "tags")]
+
+ if self.is_group:
+ self._set_group_context(context, self.tag)
+ else:
+ self._set_tag_context(context, self.tag)
+
+ return context
+
+ @staticmethod
+ def _set_tag_context(context: dict[str, any], tag: Tag) -> None:
+ """Update the context with the information for a tag page."""
+ context.update({
+ "page_title": tag.name,
+ "tag": tag,
+ })
+
+ if tag.group:
+ # Add group names to the breadcrumbs
+ context["breadcrumb_items"].append({
+ "name": tag.group,
+ "path": f"tags/{tag.group}",
+ })
+
+ # Clean up tag body
body = frontmatter.parse(tag.body)
content = body[1]
# Check for tags which can be hyperlinked
def sub(match: re.Match) -> str:
- link = reverse("content:tag", kwargs={"name": match.group("name")})
+ link = reverse("content:tag", kwargs={"location": match.group("name")})
return f"[{match.group()}]({link})"
content = COMMAND_REGEX.sub(sub, content)
@@ -42,14 +85,20 @@ class TagView(TemplateView):
if image := embed.get("image"):
content = f"![{embed['title']}]({image['url']})\n\n" + content
+ # Insert the content
+ context["page"] = markdown.markdown(content, extensions=["pymdownx.superfences"])
+
+ @staticmethod
+ def _set_group_context(context: dict[str, any], tags: list[Tag]) -> None:
+ """Update the context with the information for a group of tags."""
+ group = tags[0].group
context.update({
- "page": markdown.markdown(content, extensions=["pymdownx.superfences"]),
- "tag": tag,
+ "categories": {},
+ "pages": utils.get_tag_category(tags, collapse_groups=False),
+ "page_title": group,
+ "icon": "fab fa-tags",
+ "is_tag_listing": True,
+ "app_name": "content:tag",
+ "path": f"{group}/",
+ "tag_url": f"{tags[0].URL_BASE}/{group}"
})
-
- context["breadcrumb_items"] = [{
- "name": utils.get_category(settings.CONTENT_PAGES_PATH / location)["title"],
- "path": str(location)
- } for location in [".", "tags"]]
-
- return context
diff --git a/pydis_site/static/css/content/color.css b/pydis_site/static/css/content/color.css
new file mode 100644
index 00000000..f4801c28
--- /dev/null
+++ b/pydis_site/static/css/content/color.css
@@ -0,0 +1,7 @@
+.content .fa-github {
+ color: black;
+}
+
+.content .fa-github:hover {
+ color: #7289DA;
+}
diff --git a/pydis_site/static/css/content/tag.css b/pydis_site/static/css/content/tag.css
index a3db046c..ec45bfc7 100644
--- a/pydis_site/static/css/content/tag.css
+++ b/pydis_site/static/css/content/tag.css
@@ -1,11 +1,3 @@
-h1.title a {
- color: black;
-}
-
-h1.title a:hover {
- color: #7289DA;
-}
-
.content a * {
/* This is the original color, but propagated down the chain */
/* which allows for elements inside links, such as codeblocks */
diff --git a/pydis_site/static/js/content/listing.js b/pydis_site/static/js/content/listing.js
index 3502cb2a..4b722632 100644
--- a/pydis_site/static/js/content/listing.js
+++ b/pydis_site/static/js/content/listing.js
@@ -4,6 +4,11 @@
function trimTag() {
const containers = document.getElementsByClassName("tag-container");
for (const container of containers) {
+ if (container.textContent.startsWith("Contains the following tags:")) {
+ // Tag group, no need to trim
+ continue;
+ }
+
// Remove every element after the first two paragraphs
while (container.children.length > 2) {
container.removeChild(container.lastChild);
diff --git a/pydis_site/templates/content/listing.html b/pydis_site/templates/content/listing.html
index 098f4237..934b95f6 100644
--- a/pydis_site/templates/content/listing.html
+++ b/pydis_site/templates/content/listing.html
@@ -2,6 +2,19 @@
{% extends 'content/base.html' %}
{% load static %}
+{# Show a GitHub button on tag pages #}
+{% block title_element %}
+{% if is_tag_listing %}
+ <link rel="stylesheet" href="{% static "css/content/color.css" %}">
+ <div class="level">
+ <div class="level-left">{{ block.super }}</div>
+ <div class="level-right">
+ <a class="level-item fab fa-github" href="{{ tag_url }}"></a>
+ </div>
+ </div>
+{% endif %}
+{% endblock %}
+
{% block page_content %}
{# Nested Categories #}
{% for category, data in categories.items %}
@@ -23,10 +36,10 @@
<span class="icon is-size-4 is-medium">
<i class="{{ data.icon|default:"fab fa-python" }} is-size-3 is-black has-icon-padding" aria-hidden="true"></i>
</span>
- <a href="{% url "content:page_category" location=path|add:page %}">
+ <a href="{% url app_name location=path|add:page %}">
<span class="is-size-4 has-text-weight-bold">{{ data.title }}</span>
</a>
- {% if "tags" in location %}
+ {% if is_tag_listing %}
<div class="tag-container">{{ data.description | safe }}</div>
{% else %}
<p class="is-italic">{{ data.description }}</p>
diff --git a/pydis_site/templates/content/tag.html b/pydis_site/templates/content/tag.html
index 264f63d0..9bd65744 100644
--- a/pydis_site/templates/content/tag.html
+++ b/pydis_site/templates/content/tag.html
@@ -3,6 +3,7 @@
{% block head %}
{{ block.super }}
+ <link rel="stylesheet" href="{% static 'css/content/color.css' %}"/>
<link rel="stylesheet" href="{% static 'css/content/tag.css' %}"/>
<title>{{ tag.name }}</title>
{% endblock %}
@@ -15,7 +16,3 @@
</div>
</div>
{% endblock %}
-
-{% block page_content %}
- {{ block.super }}
-{% endblock %}