aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Kieran Siek <[email protected]>2021-04-03 15:32:20 +0800
committerGravatar GitHub <[email protected]>2021-04-03 15:32:20 +0800
commitf822d4fce05d228021450714a73cd84e143bbddc (patch)
tree1c1d617891f6657766c9a2b15d4f6f199bfbf9e8
parentMerge pull request #421 from ks129/resources-lists (diff)
parentUse metadata titles in Sub-Articles dropdown. (diff)
Merge pull request #468 from python-discord/content-app-improvements
Dewikification: Content app improvements.
-rw-r--r--pydis_site/apps/content/resources/_info.yml2
-rw-r--r--pydis_site/apps/content/resources/guides/_info.yml2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/_info.yml2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md88
-rw-r--r--pydis_site/apps/content/tests/helpers.py17
-rw-r--r--pydis_site/apps/content/tests/test_utils.py10
-rw-r--r--pydis_site/apps/content/tests/test_views.py53
-rw-r--r--pydis_site/apps/content/utils.py15
-rw-r--r--pydis_site/apps/content/views/page_category.py72
-rw-r--r--pydis_site/apps/events/tests/test_views.py4
-rw-r--r--pydis_site/apps/events/views/page.py7
-rw-r--r--pydis_site/settings.py9
-rw-r--r--pydis_site/static/css/content/page.css39
-rw-r--r--pydis_site/templates/content/base.html5
-rw-r--r--pydis_site/templates/content/dropdown.html34
-rw-r--r--pydis_site/templates/content/listing.html6
-rw-r--r--pydis_site/templates/content/page.html28
17 files changed, 322 insertions, 71 deletions
diff --git a/pydis_site/apps/content/resources/_info.yml b/pydis_site/apps/content/resources/_info.yml
index 583cab18..6553dcc6 100644
--- a/pydis_site/apps/content/resources/_info.yml
+++ b/pydis_site/apps/content/resources/_info.yml
@@ -1,2 +1,2 @@
-name: Pages
+title: Pages
description: Guides, articles, and pages hosted on the site.
diff --git a/pydis_site/apps/content/resources/guides/_info.yml b/pydis_site/apps/content/resources/guides/_info.yml
index 59c60a7b..2f65eaf9 100644
--- a/pydis_site/apps/content/resources/guides/_info.yml
+++ b/pydis_site/apps/content/resources/guides/_info.yml
@@ -1,2 +1,2 @@
-name: Guides
+title: Guides
description: Made by us, for you.
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml b/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml
index 7c9a2225..c126a68a 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml
@@ -1,2 +1,2 @@
-name: Python Discord Guides
+title: Python Discord Guides
description: Guides related to the Python Discord server and community.
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
index f258ef74..726cb7b2 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
@@ -1,11 +1,11 @@
---
title: How to Contribute a Page
description: Learn how to write and publish a page to this website.
-icon_class: fas
-icon: fa-info
+icon: fas fa-info
relevant_links:
Contributing to Site: https://pythondiscord.com/pages/contributing/site/
Using Git: https://pythondiscord.com/pages/contributing/working-with-git/
+toc: 4
---
Pages, which include guides, articles, and other static content, are stored in markdown files in the `site` repository on Github.
@@ -22,17 +22,39 @@ As website changes require staff approval, discussing the page content beforehan
## Creating the Page
All pages are located in the `site` repo, at the path `pydis_site/apps/content/resources/`. This is the root folder, which corresponds to the URL `www.pythondiscord.com/pages/`.
+
For example, the file `pydis_site/apps/content/resources/hello-world.md` will result in a page available at `www.pythondiscord.com/pages/hello-world`.
+#### Page Categories
Nested folders represent page categories on the website. Each folder under the root folder must include a `_info.yml` file with the following:
```yml
-name: Category name
+title: Category name
description: Category description
+icon: fas fa-folder # Optional
```
All the markdown files in this folder will then be under this category.
+#### Having the Category Also Be a Page
+In order to make categories a page, just create a page **with the same name as the category folder** in the category's parent directory.
+
+```plaintext
+guides
+├── contributing.md
+├── contributing
+│   ├── _info.yml
+│   └── bot.md
+└── _info.yml
+```
+
+In the above example, `www.pythondiscord.com/guides/` will list `Contributing` as a category entry with information from `contributing/_info.yml`.
+
+However, `www.pythondiscord.com/guides/contributing` will render `contributing.md` rather than show the category contents.
+A dropdown menu will be automatically generated in the top right corner of the page listing the children of the category page.
+
+Therefore, `www.pythondiscord.com/guides/contributing/bot` will then render `bot.md`, with backlinks to `contributing.md`.
+
## Writing the Page
Files representing pages are in `.md` (Markdown) format, with all-lowercase filenames and spaces replaced with `-` characters.
@@ -44,8 +66,7 @@ The metadata is written in YAML, and should be enclosed in triple dashes `---` *
---
title: How to Contribute a Page
description: Learn how to write and publish a page to this website.
-icon_class: fas
-icon: fa-info
+icon: fas fa-info
relevant_links:
Contributing to Site: https://pythondiscord.com/pages/contributing/site/
Using Git: https://pythondiscord.com/pages/contributing/working-with-git/
@@ -59,9 +80,10 @@ Pages, which include guides, articles, and other static content,...
- **description:** Short, 1-2 line description of the page's content.
### Optional Fields
-- **icon_class:** Favicon class for the category entry for the page. Default: `fab`
-- **icon:** Favicon for the category entry for the page. Default: `fa-python` <i class="fab fa-python is-black" aria-hidden="true"></i>
+- **icon:** Icon for the category entry for the page. Default: `fab fa-python` <i class="fab fa-python is-black" aria-hidden="true"></i>
- **relevant_links:** A YAML dictionary containing `text:link` pairs. See the example above.
+- **toc:** A number representing the smallest heading tag to show in the table of contents.
+ See: [Table of Contents](#table-of-contents)
## Extended Markdown
@@ -141,3 +163,55 @@ import os
path = os.path.join("foo", "bar")
```
+
+---
+
+### HTML Attributes
+To add HTML attributes to certain lines/paragraphs, [see this page](https://python-markdown.github.io/extensions/attr_list/#the-list) for the format and where to put it.
+
+This can be useful for setting the image size when adding an image using markdown (see the [Image Captions](#image-captions) section for an example), or for adding bulma styles to certain elements (like the warning notification [here](/pages/guides/pydis-guides/contributing/sir-lancebot#setup-instructions)).
+**This should be used sparingly, as it reduces readability and simplicity of the article.**
+
+---
+
+### Image Captions
+To add an image caption, place a sentence with italics *right below* the image link
+
+**Markdown:**
+```nohighlight
+![Summer Code Jam 2020](/static/images/events/summer_code_jam_2020.png){: width="400" }
+*Summmer Code Jam 2020 banner with event information.*
+```
+
+**Output:**
+
+![Summer Code Jam 2020](/static/images/events/summer_code_jam_2020.png){: width="400"}
+*Summer Code Jam 2020 banner with event information.*
+
+> Note: To display a regular italicized line below an image, leave an empty line between the two.
+
+---
+
+### Table of Contents
+In order to show the table of contents on a page, simply define the `toc` key in the page metadata.
+
+The value of the `toc` key corresponds to the smallest heading to list in the table of contents.
+For example, with markdown content like this:
+
+```markdown
+# Header 1
+words
+### Header 3
+more words
+# Another Header 1
+## Header 2
+even more words
+```
+
+and `toc: 2` in the page metadata, only `Header 1`, `Another Header 1` and `Header 2` will be listed in the table of contents.
+
+To use a custom label in the table of contents for a heading, set the `data-toc-label` attribute in the heading line. See [HTML Attributes](#html-attributes) for more information.
+
+```markdown
+# Header 1 {: data-toc-label="Header One" }
+```
diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py
index 4e0cca34..29140375 100644
--- a/pydis_site/apps/content/tests/helpers.py
+++ b/pydis_site/apps/content/tests/helpers.py
@@ -8,6 +8,7 @@ description: TestDescription
relevant_links:
Python Discord: https://pythondiscord.com
Discord: https://discord.com
+toc: 0
---
# This is a header.
"""
@@ -16,7 +17,7 @@ MARKDOWN_WITHOUT_METADATA = """#This is a header."""
# Valid YAML in a _info.yml file
CATEGORY_INFO = """
-name: Category Name
+title: Category Name
description: Description
"""
@@ -32,11 +33,12 @@ PARSED_METADATA = {
"relevant_links": {
"Python Discord": "https://pythondiscord.com",
"Discord": "https://discord.com"
- }
+ },
+ "toc": 0
}
# The YAML data parsed from the above _info.yml file
-PARSED_CATEGORY_INFO = {"name": "Category Name", "description": "Description"}
+PARSED_CATEGORY_INFO = {"title": "Category Name", "description": "Description"}
class MockPagesTestCase(TestCase):
@@ -48,9 +50,12 @@ class MockPagesTestCase(TestCase):
├── root.md
├── root_without_metadata.md
├── not_a_page.md
+ ├── tmp.md
├── tmp
|   ├── _info.yml
- |   └── category_without_info
+ |   └── category
+ |    ├── _info.yml
+ |      └── subcategory_without_info
└── category
   ├── _info.yml
   ├── with_metadata.md
@@ -81,4 +86,6 @@ class MockPagesTestCase(TestCase):
# for testing purposes.
# See: https://jmcgeheeiv.github.io/pyfakefs/release/usage.html#os-temporary-directories
self.fs.create_file("tmp/_info.yml", contents=CATEGORY_INFO)
- self.fs.create_dir("tmp/category_without_info")
+ self.fs.create_file("tmp.md", contents=MARKDOWN_WITH_METADATA)
+ self.fs.create_file("tmp/category/_info.yml", contents=CATEGORY_INFO)
+ self.fs.create_dir("tmp/category/subcategory_without_info")
diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py
index 58175d6f..6612e44c 100644
--- a/pydis_site/apps/content/tests/test_utils.py
+++ b/pydis_site/apps/content/tests/test_utils.py
@@ -14,7 +14,7 @@ class GetCategoryTests(MockPagesTestCase):
def test_get_valid_category(self):
result = utils.get_category(Path("category"))
- self.assertEqual(result, {"name": "Category Name", "description": "Description"})
+ self.assertEqual(result, {"title": "Category Name", "description": "Description"})
def test_get_nonexistent_category(self):
with self.assertRaises(Http404):
@@ -28,7 +28,7 @@ class GetCategoryTests(MockPagesTestCase):
def test_get_category_without_info_yml(self):
# Categories should provide an _info.yml file
with self.assertRaises(FileNotFoundError):
- utils.get_category(Path("tmp/category_without_info"))
+ utils.get_category(Path("tmp/category/subcategory_without_info"))
class GetCategoriesTests(MockPagesTestCase):
@@ -73,10 +73,12 @@ class GetPageTests(MockPagesTestCase):
"""Tests for the get_page function."""
def test_get_page(self):
+ # TOC is a special case because the markdown converter outputs the TOC as HTML
+ updated_metadata = {**PARSED_METADATA, "toc": '<div class="toc">\n<ul></ul>\n</div>\n'}
cases = [
- ("Root page with metadata", "root.md", PARSED_HTML, PARSED_METADATA),
+ ("Root page with metadata", "root.md", PARSED_HTML, updated_metadata),
("Root page without metadata", "root_without_metadata.md", PARSED_HTML, {}),
- ("Page with metadata", "category/with_metadata.md", PARSED_HTML, PARSED_METADATA),
+ ("Page with metadata", "category/with_metadata.md", PARSED_HTML, updated_metadata),
("Page without metadata", "category/subcategory/without_metadata.md", PARSED_HTML, {}),
]
diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py
index 560378bc..74d38f78 100644
--- a/pydis_site/apps/content/tests/test_views.py
+++ b/pydis_site/apps/content/tests/test_views.py
@@ -17,7 +17,23 @@ with fake_filesystem_unittest.Patcher() as _:
BASE_PATH = Path(".")
-@override_settings(PAGES_PATH=BASE_PATH)
+def patch_dispatch_attributes(view: PageOrCategoryView, location: str) -> None:
+ """
+ Set the attributes set in the `dispatch` method manually.
+
+ This is necessary because it is never automatically called during tests.
+ """
+ view.location = Path(location)
+
+ # URL location on the filesystem
+ view.full_location = view.location
+
+ # Possible places to find page content information
+ view.category_path = view.full_location
+ view.page_path = view.full_location.with_suffix(".md")
+
+
+@override_settings(CONTENT_PAGES_PATH=BASE_PATH)
class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
"""Tests for the PageOrCategoryView class."""
@@ -63,16 +79,21 @@ class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
for path, expected_template in cases:
with self.subTest(path=path, expected_template=expected_template):
- self.ViewClass.full_location = Path(path)
+ patch_dispatch_attributes(self.ViewClass, path)
self.assertEqual(self.ViewClass.get_template_names(), [expected_template])
def test_get_template_names_with_nonexistent_paths_returns_404(self):
for path in ("invalid", "another_invalid", "nonexistent"):
with self.subTest(path=path):
- self.ViewClass.full_location = Path(path)
+ patch_dispatch_attributes(self.ViewClass, path)
with self.assertRaises(Http404):
self.ViewClass.get_template_names()
+ def test_get_template_names_returns_page_template_for_category_with_page(self):
+ """Make sure the proper page is returned for category locations with pages."""
+ patch_dispatch_attributes(self.ViewClass, "tmp")
+ self.assertEqual(self.ViewClass.get_template_names(), ["content/page.html"])
+
def test_get_context_data_with_valid_page(self):
"""The method should return required fields in the template context."""
request = self.factory.get("/root")
@@ -120,7 +141,7 @@ class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
"page_description",
PARSED_CATEGORY_INFO["description"]
),
- ("Context includes page title", "page_title", PARSED_CATEGORY_INFO["name"]),
+ ("Context includes page title", "page_title", PARSED_CATEGORY_INFO["title"]),
]
context = self.ViewClass.get_context_data()
@@ -128,6 +149,24 @@ class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
with self.subTest(msg=msg):
self.assertEqual(context[key], expected_value)
+ def test_get_context_data_for_category_with_page(self):
+ """Make sure the proper page is returned for category locations with pages."""
+ request = self.factory.get("/category")
+ self.ViewClass.setup(request)
+ self.ViewClass.dispatch(request, location="tmp")
+
+ context = self.ViewClass.get_context_data()
+ expected_page_context = {
+ "page": PARSED_HTML,
+ "page_title": PARSED_METADATA["title"],
+ "page_description": PARSED_METADATA["description"],
+ "relevant_links": PARSED_METADATA["relevant_links"],
+ "subarticles": [{"path": "category", "name": "Category Name"}]
+ }
+ for key, expected_value in expected_page_context.items():
+ with self.subTest():
+ self.assertEqual(context[key], expected_value)
+
def test_get_context_data_breadcrumbs(self):
"""The method should return correct breadcrumbs."""
request = self.factory.get("/category/subcategory/with_metadata")
@@ -138,8 +177,8 @@ class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
self.assertEquals(
context["breadcrumb_items"],
[
- {"name": PARSED_CATEGORY_INFO["name"], "path": "."},
- {"name": PARSED_CATEGORY_INFO["name"], "path": "category"},
- {"name": PARSED_CATEGORY_INFO["name"], "path": "category/subcategory"},
+ {"name": PARSED_CATEGORY_INFO["title"], "path": "."},
+ {"name": PARSED_CATEGORY_INFO["title"], "path": "category"},
+ {"name": PARSED_CATEGORY_INFO["title"], "path": "category/subcategory"},
]
)
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index 726c991f..d3f270ff 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -32,7 +32,8 @@ def get_category_pages(path: Path) -> Dict[str, Dict]:
pages = {}
for item in path.glob("*.md"):
- if item.is_file():
+ # Only list page if there is no category with the same name
+ if item.is_file() and not item.with_suffix("").is_dir():
pages[item.stem] = frontmatter.load(item).metadata
return pages
@@ -44,14 +45,20 @@ def get_page(path: Path) -> Tuple[str, Dict]:
raise Http404("Page not found.")
metadata, content = frontmatter.parse(path.read_text(encoding="utf-8"))
- html = markdown.markdown(
- content,
+ toc_depth = metadata.get("toc", 1)
+
+ md = markdown.Markdown(
extensions=[
"extra",
# Empty string for marker to disable text searching for [TOC]
# By using a metadata key instead, we save time on long markdown documents
- TocExtension(title="Table of Contents:", permalink=True, marker="")
+ TocExtension(permalink=True, marker="", toc_depth=toc_depth)
]
)
+ html = md.convert(content)
+
+ # Don't set the TOC if the metadata does not specify one
+ if "toc" in metadata:
+ metadata["toc"] = md.toc
return str(html), metadata
diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py
index eec4e7e5..4031fde2 100644
--- a/pydis_site/apps/content/views/page_category.py
+++ b/pydis_site/apps/content/views/page_category.py
@@ -1,6 +1,7 @@
import typing as t
from pathlib import Path
+import frontmatter
from django.conf import settings
from django.http import Http404
from django.views.generic import TemplateView
@@ -14,16 +15,22 @@ class PageOrCategoryView(TemplateView):
def dispatch(self, request: t.Any, *args, **kwargs) -> t.Any:
"""Conform URL path location to the filesystem path."""
self.location = Path(kwargs.get("location", ""))
- self.full_location = settings.PAGES_PATH / self.location
+
+ # URL location on the filesystem
+ self.full_location = settings.CONTENT_PAGES_PATH / self.location
+
+ # Possible places to find page content information
+ self.category_path = self.full_location
+ self.page_path = self.full_location.with_suffix(".md")
return super().dispatch(request, *args, **kwargs)
def get_template_names(self) -> t.List[str]:
"""Checks if the view uses the page template or listing template."""
- if self.full_location.is_dir():
- template_name = "content/listing.html"
- elif self.full_location.with_suffix(".md").is_file():
+ if self.page_path.is_file():
template_name = "content/page.html"
+ elif self.category_path.is_dir():
+ template_name = "content/listing.html"
else:
raise Http404
@@ -33,29 +40,54 @@ class PageOrCategoryView(TemplateView):
"""Assign proper context variables based on what resource user requests."""
context = super().get_context_data(**kwargs)
- if self.full_location.is_dir():
- context["categories"] = utils.get_categories(self.full_location)
- context["pages"] = utils.get_category_pages(self.full_location)
-
- category = utils.get_category(self.full_location)
- context["page_title"] = category["name"]
- context["page_description"] = category["description"]
-
- context["path"] = f"{self.location}/" # Add trailing slash here to simplify template
- elif self.full_location.with_suffix(".md").is_file():
- page, metadata = utils.get_page(self.full_location.with_suffix(".md"))
- context["page"] = page
- context["page_title"] = metadata["title"]
- context["page_description"] = metadata["description"]
- context["relevant_links"] = metadata.get("relevant_links", {})
+ if self.page_path.is_file():
+ context.update(self._get_page_context(self.page_path))
+ elif self.category_path.is_dir():
+ context.update(self._get_category_context(self.category_path))
+ context["path"] = f"{self.location}/" # Add trailing slash to simplify template
else:
raise Http404
+ # Add subarticle information for dropdown menu if the page is also a category
+ if self.page_path.is_file() and self.category_path.is_dir():
+ context["subarticles"] = []
+ for entry in self.category_path.iterdir():
+ entry_info = {"path": entry.stem}
+ if entry.suffix == ".md":
+ entry_info["name"] = frontmatter.load(entry).metadata["title"]
+ elif entry.is_dir():
+ entry_info["name"] = utils.get_category(entry)["title"]
+ else:
+ continue
+ context["subarticles"].append(entry_info)
+
context["breadcrumb_items"] = [
{
- "name": utils.get_category(settings.PAGES_PATH / location)["name"],
+ "name": utils.get_category(settings.CONTENT_PAGES_PATH / location)["title"],
"path": str(location)
} for location in reversed(self.location.parents)
]
return context
+
+ @staticmethod
+ def _get_page_context(path: Path) -> t.Dict[str, t.Any]:
+ page, metadata = utils.get_page(path)
+ return {
+ "page": page,
+ "page_title": metadata["title"],
+ "page_description": metadata["description"],
+ "relevant_links": metadata.get("relevant_links", {}),
+ "toc": metadata.get("toc")
+ }
+
+ @staticmethod
+ def _get_category_context(path: Path) -> t.Dict[str, t.Any]:
+ category = utils.get_category(path)
+ return {
+ "categories": utils.get_categories(path),
+ "pages": utils.get_category_pages(path),
+ "page_title": category["title"],
+ "page_description": category["description"],
+ "icon": category.get("icon"),
+ }
diff --git a/pydis_site/apps/events/tests/test_views.py b/pydis_site/apps/events/tests/test_views.py
index 0db0ef9a..23c9e596 100644
--- a/pydis_site/apps/events/tests/test_views.py
+++ b/pydis_site/apps/events/tests/test_views.py
@@ -17,7 +17,7 @@ class IndexTests(TestCase):
class PageTests(TestCase):
- @override_settings(PAGES_PATH=PAGES_PATH)
+ @override_settings(EVENTS_PAGES_PATH=PAGES_PATH)
def test_valid_event_page_reponse_200(self):
"""Should return response code 200 when visiting valid event page."""
pages = (
@@ -29,7 +29,7 @@ class PageTests(TestCase):
resp = self.client.get(page)
self.assertEqual(resp.status_code, 200)
- @override_settings(PAGES_PATH=PAGES_PATH)
+ @override_settings(EVENTS_PAGES_PATH=PAGES_PATH)
def test_invalid_event_page_404(self):
"""Should return response code 404 when visiting invalid event page."""
pages = (
diff --git a/pydis_site/apps/events/views/page.py b/pydis_site/apps/events/views/page.py
index f4c37aeb..eab2f462 100644
--- a/pydis_site/apps/events/views/page.py
+++ b/pydis_site/apps/events/views/page.py
@@ -11,15 +11,16 @@ class PageView(TemplateView):
def get_template_names(self) -> List[str]:
"""Get specific template names."""
path: str = self.kwargs['path']
- page_path = settings.PAGES_PATH / path
+ page_path = settings.EVENTS_PAGES_PATH / path
if page_path.is_dir():
page_path = page_path / "_index.html"
path = f"{path}/_index.html"
else:
- page_path = settings.PAGES_PATH / f"{path}.html"
+ page_path = settings.EVENTS_PAGES_PATH / f"{path}.html"
path = f"{path}.html"
if not page_path.exists():
raise Http404
+ print(f"events/{settings.EVENTS_PAGES_PATH.name}/{path}")
- return [f"events/{settings.PAGES_PATH.name}/{path}"]
+ return [f"events/{settings.EVENTS_PAGES_PATH.name}/{path}"]
diff --git a/pydis_site/settings.py b/pydis_site/settings.py
index 47750a47..d409bb21 100644
--- a/pydis_site/settings.py
+++ b/pydis_site/settings.py
@@ -119,9 +119,6 @@ MIDDLEWARE = [
]
ROOT_URLCONF = 'pydis_site.urls'
-# Path for events pages
-PAGES_PATH = Path(BASE_DIR, "pydis_site", "templates", "events", "pages")
-
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
@@ -288,4 +285,8 @@ SITE_REPOSITORY_OWNER = "python-discord"
SITE_REPOSITORY_NAME = "site"
SITE_REPOSITORY_BRANCH = "master"
-PAGES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources")
+# Path for events pages
+EVENTS_PAGES_PATH = Path(BASE_DIR, "pydis_site", "templates", "events", "pages")
+
+# Path for content pages
+CONTENT_PAGES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources")
diff --git a/pydis_site/static/css/content/page.css b/pydis_site/static/css/content/page.css
index 57d7472b..3ac41d1b 100644
--- a/pydis_site/static/css/content/page.css
+++ b/pydis_site/static/css/content/page.css
@@ -29,3 +29,42 @@ code.hljs {
:is(h1, h2, h3, h4, h5, h6):hover > .headerlink {
display: inline;
}
+
+/*
+ * Display <em> tags immediately following <img> tags like figure subcaptions.
+ * Note: There must not be a newline between the image and the italicized line
+ * for this to work. Otherwise, it's regular markdown.
+ *
+ * Image caption:
+ *
+ * ![Title](Source)
+ * *This is my caption.*
+ *
+ */
+img + em {
+ /* Place the caption on it's own line */
+ display: block;
+ white-space: pre;
+
+ /* Style */
+ font-size: .875em;
+}
+
+/*
+ * Remove extra padding on the left of TOC items
+ */
+ul.menu-list.toc {
+ margin-left: 0;
+}
+
+/*
+ * Remove bullets set by the markdown extension, since bulma adds vertical
+ * lines to represent nesting
+ */
+.toc li {
+ list-style-type: none;
+}
+/* ..but we still want bullets on the top <ul> items */
+.toc > ul > li {
+ list-style-type: disc;
+}
diff --git a/pydis_site/templates/content/base.html b/pydis_site/templates/content/base.html
index 19eec5d4..21895479 100644
--- a/pydis_site/templates/content/base.html
+++ b/pydis_site/templates/content/base.html
@@ -14,6 +14,7 @@
<section class="breadcrumb-section section">
<div class="container">
+ {# Article breadcrumb #}
<nav class="breadcrumb is-pulled-left" aria-label="breadcrumbs">
<ul>
{% for item in breadcrumb_items %}
@@ -22,6 +23,10 @@
<li class="is-active"><a href="#">{{ page_title }}</a></li>
</ul>
</nav>
+ {# Sub-Article dropdown for category pages #}
+ {% if subarticles %}
+ {% include "content/dropdown.html" %}
+ {% endif %}
</div>
</section>
diff --git a/pydis_site/templates/content/dropdown.html b/pydis_site/templates/content/dropdown.html
new file mode 100644
index 00000000..c9491f3a
--- /dev/null
+++ b/pydis_site/templates/content/dropdown.html
@@ -0,0 +1,34 @@
+<script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const dropdown = document.querySelector("#dropdown");
+ // Show dropdown menu when clicked
+ dropdown.addEventListener("click", () => {
+ dropdown.classList.toggle("is-active");
+ });
+
+ // Hide dropdown menu when anyhere on the page is clicked
+ document.addEventListener("click", (e) => {
+ if (!dropdown.contains(e.target) && dropdown.classList.contains("is-active")) {
+ dropdown.classList.remove("is-active");
+ }
+ }, false);
+ });
+</script>
+
+<div class="dropdown is-pulled-right is-right" id="dropdown">
+ <div class="dropdown-trigger">
+ <a aria-haspopup="true" aria-controls="subarticle-menu">
+ <span>Sub-Articles</span>
+ <span class="icon is-small">
+ <i class="fas fa-angle-down" aria-hidden="true"></i>
+ </span>
+ </a>
+ </div>
+ <div class="dropdown-menu" id="subarticle-menu" role="menu">
+ <div class="dropdown-content">
+ {% for page in subarticles|dictsort:"name" %}
+ <a href="{{ page.path }}" class="dropdown-item">{{ page.name }}</a>
+ {% endfor %}
+ </div>
+ </div>
+</div>
diff --git a/pydis_site/templates/content/listing.html b/pydis_site/templates/content/listing.html
index 6de306b0..ef0ef919 100644
--- a/pydis_site/templates/content/listing.html
+++ b/pydis_site/templates/content/listing.html
@@ -4,11 +4,11 @@
{% for category, data in categories.items %}
<div class="box" style="max-width: 800px;">
<span class="icon is-size-4 is-medium">
- <i class="fas fa-folder is-size-3 is-black has-icon-padding" aria-hidden="true"></i>
+ <i class="{{ data.icon|default:"fas fa-folder" }} is-size-3 is-black has-icon-padding" aria-hidden="true"></i>
</span>
<a href="{% url "content:page_category" location=path|add:category %}">
- <span class="is-size-4 has-text-weight-bold">{{ data.name }}</span>
+ <span class="is-size-4 has-text-weight-bold">{{ data.title }}</span>
</a>
<p class="is-italic">{{ data.description }}</p>
</div>
@@ -16,7 +16,7 @@
{% for page, data in pages.items %}
<div class="box" style="max-width: 800px;">
<span class="icon is-size-4 is-medium">
- <i class="{{ data.icon_class|default:"fab" }} {{ data.icon|default:"fa-python" }} is-size-3 is-black has-icon-padding" aria-hidden="true"></i>
+ <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 %}">
<span class="is-size-4 has-text-weight-bold">{{ data.title }}</span>
diff --git a/pydis_site/templates/content/page.html b/pydis_site/templates/content/page.html
index 06d74208..45aa8221 100644
--- a/pydis_site/templates/content/page.html
+++ b/pydis_site/templates/content/page.html
@@ -9,20 +9,30 @@
{% endblock %}
{% block page_content %}
- {% if relevant_links|length > 0 %}
+ {% if relevant_links or toc %}
<div class="columns is-variable is-8">
<div class="column is-two-thirds">
{{ page|safe }}
</div>
<div class="column">
- <div class="box">
- <p class="menu-label">Relevant links</p>
- <ul class="menu-list">
- {% for value, link in relevant_links.items %}
- <li><a class="has-text-link" href="{{link}}">{{ value }}</a></li>
- {% endfor %}
- </ul>
- </div>
+ {% if toc %}
+ <div class="box">
+ <p class="menu-label">Table of Contents</p>
+ <ul class="menu-list toc">
+ {{ toc|safe }}
+ </ul>
+ </div>
+ {% endif %}
+ {% if relevant_links %}
+ <div class="box">
+ <p class="menu-label">Relevant links</p>
+ <ul class="menu-list">
+ {% for value, link in relevant_links.items %}
+ <li><a class="has-text-link" href="{{link}}">{{ value }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% endif %}
</div>
</div>
{% else %}