Skip to content

Commit 588e842

Browse files
committed
08: Content pages.
1 parent 1ef899d commit 588e842

17 files changed

+220
-46
lines changed

docs/building/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ first_pyscript
3333
bulma
3434
examples_template
3535
resource_listing
36+
pages
3637
```

docs/building/pages.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Content Pages
2+
3+
We have a home page, but we'll need some other content pages, such as "About".
4+
Add a "Page" resource, with Markdown+frontmatter-driven content under `/pages`.
5+
6+
7+
## Pages In `/pages`
8+
9+
- All pages will be Markdown-driven, e.g. `src/psc/pages/about.md` and `/pages/about.html`
10+
- Install `python-frontmatter` as regular dependency
11+
- Write tests for a new `Page` resource type and views
12+
- Implement them
13+
- Home page stays as a Jinja2 template, as it is heavily-Bulma (not a good candidate for Markdown)
14+
15+
## Navbar
16+
17+
- Wire `About` into the navbar
18+
19+
## Home Page
20+
21+
- Better formatting
22+
- Content for: homepage, about, contributing, vision, homepage hero
23+
24+
## Future
25+
26+
- Images
27+
- Static content
28+
- Contributors
29+
- Build command

src/psc/app.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def homepage(request: Request) -> _TemplateResponse:
3434
index_file = HERE / "index.html"
3535

3636
return templates.TemplateResponse(
37-
"page.jinja2",
37+
"homepage.jinja2",
3838
dict(
3939
title="Home Page",
4040
main=index_file.read_text(),
@@ -43,14 +43,14 @@ async def homepage(request: Request) -> _TemplateResponse:
4343
)
4444

4545

46-
async def examples(request: Request) -> _TemplateResponse:
47-
"""Handle the examples listing page."""
46+
async def gallery(request: Request) -> _TemplateResponse:
47+
"""Handle the gallery listing page."""
4848
these_examples: Iterator[Example] = request.app.state.resources.examples.values()
4949

5050
return templates.TemplateResponse(
51-
"examples.jinja2",
51+
"gallery.jinja2",
5252
dict(
53-
title="Examples",
53+
title="Gallery",
5454
examples=these_examples,
5555
request=request,
5656
),
@@ -76,15 +76,32 @@ async def example(request: Request) -> _TemplateResponse:
7676
)
7777

7878

79+
async def content_page(request: Request) -> _TemplateResponse:
80+
"""Handle a content page."""
81+
page_path = PurePath(request.path_params["page_name"])
82+
resources: Resources = request.app.state.resources
83+
this_page = resources.pages[page_path]
84+
85+
return templates.TemplateResponse(
86+
"page.jinja2",
87+
dict(
88+
title=this_page.title,
89+
main=this_page.body,
90+
request=request,
91+
),
92+
)
93+
94+
7995
routes = [
8096
Route("/", homepage),
8197
Route("/index.html", homepage),
8298
Route("/favicon.png", favicon),
83-
Route("/examples/index.html", examples),
84-
Route("/examples", examples),
85-
Route("/examples/{example_name}/index.html", example),
86-
Route("/examples/{example_name}/", example),
87-
Mount("/examples", StaticFiles(directory=HERE / "examples")),
99+
Route("/gallery/index.html", gallery),
100+
Route("/gallery", gallery),
101+
Route("/gallery/{example_name}/index.html", example),
102+
Route("/gallery/{example_name}/", example),
103+
Route("/pages/{page_name}", content_page),
104+
Mount("/gallery", StaticFiles(directory=HERE / "examples")),
88105
Mount("/static", StaticFiles(directory=HERE / "static")),
89106
Mount("/pyscript", StaticFiles(directory=PYSCRIPT)),
90107
Mount("/pyodide", StaticFiles(directory=PYODIDE)),

src/psc/pages/about.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: About PyScript Collective
3+
---
4+
5+
Stuff *is here*.

src/psc/resources.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import PurePath
99
from typing import cast
1010

11+
import frontmatter
1112
from bs4 import BeautifulSoup
1213
from bs4 import Tag
1314
from markdown_it import MarkdownIt
@@ -130,11 +131,29 @@ def __post_init__(self) -> None:
130131
self.extra_pyscript = get_pyscript_nodes(soup)
131132

132133

134+
@dataclass
135+
class Page(Resource):
136+
"""A Markdown+frontmatter driven content page."""
137+
138+
body: str = ""
139+
140+
def __post_init__(self) -> None:
141+
"""Extract content from Markdown file."""
142+
md_file = HERE / "pages" / f"{self.path}.md"
143+
if not md_file.exists():
144+
raise ValueError(f"No page at {self.path}")
145+
md_fm = frontmatter.load(md_file)
146+
self.title = md_fm["title"]
147+
md = MarkdownIt()
148+
self.body = str(md.render(md_fm.content))
149+
150+
133151
@dataclass
134152
class Resources:
135153
"""Container for all resources in site."""
136154

137155
examples: dict[PurePath, Example] = field(default_factory=dict)
156+
pages: dict[PurePath, Page] = field(default_factory=dict)
138157

139158

140159
def get_resources() -> Resources:
@@ -148,4 +167,13 @@ def get_resources() -> Resources:
148167
this_path = PurePath(example.name)
149168
this_example = Example(path=this_path)
150169
resources.examples[this_path] = this_example
170+
171+
# Load the Pages
172+
pages_dir = HERE / "pages"
173+
pages = [e for e in pages_dir.iterdir()]
174+
for page in pages:
175+
this_path = PurePath(page.stem)
176+
this_page = Page(path=this_path)
177+
resources.pages[this_path] = this_page
178+
151179
return resources

src/psc/static/psc.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ body {
33
font-size: x-large;
44
}
55

6+
#navbar {
7+
margin-left: 2rem;
8+
}
9+
610
#main_container {
711
margin-top: 3rem;
812
margin-bottom: 3rem;

src/psc/templates/example.jinja2

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% extends "layout.jinja2" %}
22
{% block extra_head %}
3-
<meta name="subtitle" content="{{ subtitle }}" >
3+
<meta name="subtitle" content="{{ subtitle }}">
44
<script defer src="/pyscript/pyscript.js"></script>
55
{{ extra_head | safe }}{% endblock %}
66
{% block extra_body %}
@@ -14,5 +14,7 @@
1414
{{ extra_pyscript | safe }}
1515
{% endblock %}
1616
{% block main %}
17-
{{ main | safe }}
17+
<main id="main_container" class="container">
18+
{{ main | safe }}
19+
</main>
1820
{% endblock %}

src/psc/templates/examples.jinja2

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/psc/templates/gallery.jinja2

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{% extends "layout.jinja2" %}
2+
{% block main %}
3+
<main id="main_container" class="container">
4+
<div class="tile is-ancestor">
5+
{% for example in examples %}
6+
<div class="tile is-parent is-4">
7+
<article class="tile is-child box">
8+
<p class="title"><a href="/gallery/{{ example.path.name }}/">{{ example.title }}</a></p>
9+
<p class="subtitle">{{ example.subtitle }}</p>
10+
<div class="content">
11+
{{ example.description | safe }}
12+
</div>
13+
</article>
14+
</div>
15+
{% endfor %}
16+
</div>
17+
</main>
18+
{% endblock %}

src/psc/templates/homepage.jinja2

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{% extends "layout.jinja2" %}
2+
{% block main %}
3+
<section class="hero is-medium is-warning">
4+
<div class="hero-body">
5+
<p class="title">
6+
Medium hero
7+
</p>
8+
<p class="subtitle">
9+
Medium subtitle
10+
</p>
11+
</div>
12+
</section>
13+
<section class="section has-background-info-light">
14+
<h1 class="title">Section</h1>
15+
<h2 class="subtitle">
16+
A simple container to divide your page into <strong>sections</strong>, like the one you're currently
17+
reading.
18+
</h2>
19+
<div class="tile is-ancestor">
20+
<div class="tile is-parent">
21+
<article class="tile is-child box">
22+
<p class="title">Hello World</p>
23+
<p class="subtitle">What is up?</p>
24+
</article>
25+
</div>
26+
<div class="tile is-parent">
27+
<article class="tile is-child box">
28+
<p class="title">Foo</p>
29+
<p class="subtitle">Bar</p>
30+
</article>
31+
</div>
32+
<div class="tile is-parent">
33+
<article class="tile is-child box">
34+
<p class="title">Third column</p>
35+
<p class="subtitle">With some content</p>
36+
<div class="content">
37+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare magna eros, eu
38+
pellentesque tortor vestibulum ut. Maecenas non massa sem. Etiam finibus odio quis feugiat
39+
facilisis.</p>
40+
</div>
41+
</article>
42+
</div>
43+
</div>
44+
</section>
45+
{% endblock %}

src/psc/templates/layout.jinja2

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,22 @@
2121
</a>
2222
</div>
2323

24-
<div class="navbar-menu">
24+
<div id="navbar" class="navbar-menu">
2525
<div class="navbar-start">
26-
<a class="navbar-item">
27-
Home
26+
<a id="navbarGallery" class="navbar-item" href="/gallery">
27+
Gallery
2828
</a>
29-
<a id="navbarExamples" class="navbar-item" href="/examples">
30-
Examples
29+
<a id="navbarAbout" class="navbar-item" href="/pages/about">
30+
About
3131
</a>
3232
</div>
3333
</div>
34+
3435
</nav>
3536
{% block extra_body %}
3637
{% endblock %}
37-
<main id="main_container" class="container">
38-
{% block main %}
39-
{% endblock %}
40-
</main>
38+
{% block main %}
39+
{% endblock %}
4140
<footer class="footer">
4241
<div class="content has-text-centered">
4342
<p>

src/psc/templates/page.jinja2

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{% extends "layout.jinja2" %}
22
{% block main %}
3-
{{ main | safe }}
3+
<main id="main_container" class="container">
4+
<h1 class="title is-1">{{ title }}</h1>
5+
{{ main | safe }}
6+
</main>
47
{% endblock %}

tests/examples/test_hello_world.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
def test_hello_world(client_page: PageT) -> None:
1010
"""Test the static HTML for Hello World."""
11-
soup = client_page("/examples/hello_world/")
11+
soup = client_page("/gallery/hello_world/")
1212

1313
# Title and subtitle
1414
title = soup.select_one("title")
@@ -42,7 +42,7 @@ def test_hello_world(client_page: PageT) -> None:
4242

4343
def test_hello_world_js(test_client: TestClient) -> None:
4444
"""Test the static assets for Hello World."""
45-
response = test_client.get("/examples/hello_world/hello_world.js")
45+
response = test_client.get("/gallery/hello_world/hello_world.js")
4646
assert response.status_code == 200
4747

4848

tests/test_app.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ def test_index_page(client_page: PageT) -> None:
1212
title = soup.select_one("title")
1313
if title:
1414
assert title.text == "Home Page | PyScript Collective"
15-
main = soup.select_one("main")
16-
assert main
15+
section = soup.select_one("section")
16+
assert section
1717

1818

1919
def test_bulma_css(client_page: PageT, test_client: TestClient) -> None:
@@ -35,15 +35,15 @@ def test_favicon(test_client: TestClient) -> None:
3535

3636
def test_examples(test_client: TestClient) -> None:
3737
"""Ensure the app provides an /examples/ route."""
38-
response = test_client.get("/examples/hello_world/index.html")
38+
response = test_client.get("/gallery/hello_world/index.html")
3939
assert response.status_code == 200
4040

4141

4242
def test_examples_listing(client_page: PageT) -> None:
4343
"""Ensure the route lists the examples."""
4444
# First get the URL from the navbar.
4545
index_soup = client_page("/")
46-
nav_examples = index_soup.select_one("#navbarExamples")
46+
nav_examples = index_soup.select_one("#navbarGallery")
4747
assert nav_examples
4848
examples_href = nav_examples.get("href")
4949
assert examples_href
@@ -53,7 +53,7 @@ def test_examples_listing(client_page: PageT) -> None:
5353
# Example title
5454
examples_title = examples_soup.select_one("title")
5555
assert examples_title
56-
assert examples_title.text == "Examples | PyScript Collective"
56+
assert examples_title.text == "Gallery | PyScript Collective"
5757

5858
# Example subtitle
5959
subtitle = examples_soup.select_one("p.subtitle")
@@ -69,7 +69,7 @@ def test_examples_listing(client_page: PageT) -> None:
6969
first_example = examples_soup.select_one("p.title a")
7070
assert first_example
7171
first_href = first_example.get("href")
72-
assert first_href == "/examples/hello_world/"
72+
assert first_href == "/gallery/hello_world/"
7373
hello_soup = client_page(first_href)
7474
assert hello_soup
7575
title = hello_soup.select_one("title")
File renamed without changes.

tests/test_pages.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Test the views for the Page resource."""
2+
from psc.fixtures import PageT
3+
4+
5+
def test_about_page(client_page: PageT) -> None:
6+
"""Use the navbar to get the link to the about page."""
7+
index_soup = client_page("/")
8+
about_link = index_soup.select_one("#navbarAbout")
9+
assert about_link
10+
about_href = about_link.get("href")
11+
assert about_href
12+
about_soup = client_page(about_href)
13+
assert about_soup
14+
15+
# Page title
16+
page_title = about_soup.select_one("title")
17+
assert page_title
18+
assert page_title.text == "About PyScript Collective | PyScript Collective"
19+
20+
# Page body
21+
em = about_soup.select_one("main em")
22+
assert em
23+
assert em.text == "is here"

0 commit comments

Comments
 (0)