Skip to content

Commit ed2a769

Browse files
authored
Merge pull request #357 from davep/copilot/visually-indicate-draft-posts
Visually indicate draft posts with 🚧 emoji and contrasting colour
2 parents b69c95f + adf7c37 commit ed2a769

10 files changed

Lines changed: 161 additions & 7 deletions

File tree

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- Replaced the regex-based `extract_first_paragraph` implementation with a
88
Markdown-library-powered approach.
99
([#354](https://github.com/davep/blogmore/pull/354))
10+
- Draft posts now receive a clear visual indicator wherever a post title is
11+
rendered: the title is displayed in a contrasting amber colour and a 🚧
12+
emoji is appended after the title text.
13+
([#357](https://github.com/davep/blogmore/pull/357))
1014

1115
## v2.6.0
1216

docs/template-api.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,33 @@ Helper methods available on `Post`:
135135
| `safe_tags()` | `list[str]` | Tags sanitised for URL use. |
136136
| `sorted_tag_pairs()` | `list[tuple[str, str]]` | `(display, safe)` tag pairs sorted alphabetically. |
137137

138+
### Draft post visual indicator
139+
140+
When `post.draft` is `True`, the built-in templates automatically apply a
141+
clear visual indicator to the post title wherever it is rendered:
142+
143+
- The post title is displayed in the **draft title colour** (`--draft-title-color`
144+
CSS custom property, default amber `#cc6600`).
145+
- A **🚧 emoji** is appended after the title text.
146+
- The containing `<article>` element receives the CSS class **`draft-post`**
147+
(for summary cards and individual post pages) or the link receives the class
148+
**`draft-title`** (in the date archive list).
149+
150+
This applies in every listing context (home page, tag pages, category pages,
151+
date archive) as well as on the individual post page. Non-draft posts are
152+
never affected.
153+
154+
Custom templates that render `post.title` should replicate this pattern. A
155+
minimal implementation:
156+
157+
```html+jinja
158+
<h2><a href="{{ post.url }}">{{ post.title }}{% if post.draft %} 🚧{% endif %}</a></h2>
159+
```
160+
161+
To override the draft title colour, set `--draft-title-color` (and
162+
`--dark-draft-title-color` for dark mode) in your custom stylesheet. See
163+
[Theming](theming.md) for the full CSS variable reference.
164+
138165
## Page object
139166

140167
`Page` objects represent static pages from the `pages/` directory.

docs/theming.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ corresponding `--dark-*` values.
136136
| `--admonition-important-title-color` | `#8250df` | Important admonition title |
137137
| `--admonition-warning-title-color` | `#9a6700` | Warning admonition title |
138138
| `--admonition-caution-title-color` | `#d1242f` | Caution admonition title |
139+
| `--draft-title-color` | `#cc6600` | Draft post title colour |
139140

140141
### Dark mode palette variables
141142

@@ -170,6 +171,7 @@ automatically to both the JavaScript toggle and the CSS media query fallback.
170171
| `--dark-admonition-important-title-color` | `#a371f7` | Dark important title |
171172
| `--dark-admonition-warning-title-color` | `#d29922` | Dark warning title |
172173
| `--dark-admonition-caution-title-color` | `#f85149` | Dark caution title |
174+
| `--dark-draft-title-color` | `#ffab40` | Dark mode draft post title colour |
173175

174176
## Example: full colour override
175177

examples/themes/modern-compact/templates/_post_summary.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
tag_dir (str): URL path segment for tag pages.
1717
#}
1818
{% macro post_summary(post, include_category=true) %}
19-
<article class="post-summary">
20-
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
19+
<article class="post-summary{% if post.draft %} draft-post{% endif %}">
20+
<h2><a href="{{ post.url }}">{{ post.title }}{% if post.draft %} 🚧{% endif %}</a></h2>
2121
<div class="post-meta-line">
2222
{% if post.date %}
2323
<time datetime="{{ post.date.isoformat() }}">{{ post.date|format_date }}</time>

src/blogmore/templates/_post_summary.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
tag_dir (str): URL path segment for tag pages.
2020
#}
2121
{% macro post_summary(post, include_category=true) %}
22-
<article class="post-summary">
23-
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
22+
<article class="post-summary{% if post.draft %} draft-post{% endif %}">
23+
<h2><a href="{{ post.url }}">{{ post.title }}{% if post.draft %} 🚧{% endif %}</a></h2>
2424
{% if post.date %}
2525
<time datetime="{{ post.date.isoformat() }}">{{ post.date|format_date }}</time>
2626
{% endif %}

src/blogmore/templates/archive.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ <h3><a href="/{{ year }}/{{ month_key }}/{{ pagination_page1_suffix }}">{{ ns.ye
6464
<ul class="archive-post-list">
6565
{% for post in ns.years[year][month_key]['posts'] %}
6666
<li>
67-
<a href="{{ post.url }}" class="post-title">{{ post.title }}</a>
67+
<a href="{{ post.url }}" class="post-title{% if post.draft %} draft-title{% endif %}">{{ post.title }}{% if post.draft %} 🚧{% endif %}</a>
6868
{% if post.category %}
6969
<span class="archive-metadata">
7070
<a href="/{{ category_dir }}/{{ post.safe_category }}/{{ pagination_page1_suffix }}" class="category-link">{{ post.category }}</a>

src/blogmore/templates/post.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
{% endblock %}
1919

2020
{% block content %}
21-
<article class="post">
21+
<article class="post{% if post.draft %} draft-post{% endif %}">
2222
{% if prev_post or next_post %}
2323
<nav class="post-navigation post-navigation-top">
2424
{% if prev_post %}
@@ -37,7 +37,7 @@
3737
{% endif %}
3838

3939
<header class="post-header">
40-
<h1>{{ post.title }}</h1>
40+
<h1>{{ post.title }}{% if post.draft %} 🚧{% endif %}</h1>
4141
{% if post.date %}
4242
<time datetime="{{ post.date.isoformat() }}">{{ post.date|format_date }}</time>
4343
{% endif %}

src/blogmore/templates/static/archive.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@
7676
text-decoration: underline;
7777
}
7878

79+
.archive-post-list .post-title.draft-title {
80+
color: var(--draft-title-color);
81+
}
82+
7983
.archive-post-list .archive-metadata {
8084
margin-left: 8px;
8185
}

src/blogmore/templates/static/style.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
--code-lang-color: #888;
4848
--code-copy-color: #555;
4949
--code-copy-success-color: #1a7f37;
50+
--draft-title-color: #cc6600;
5051

5152
/* ---- Dark mode palette (shared by both dark mode selectors below) ---- */
5253
--dark-bg-color: #1a1a1a;
@@ -78,6 +79,7 @@
7879
--dark-code-lang-color: #999;
7980
--dark-code-copy-color: #bbb;
8081
--dark-code-copy-success-color: #3fb950;
82+
--dark-draft-title-color: #ffab40;
8183
}
8284

8385
/* Dark mode — applied by system preference (JavaScript disabled fallback).
@@ -116,6 +118,7 @@
116118
--code-lang-color: var(--dark-code-lang-color);
117119
--code-copy-color: var(--dark-code-copy-color);
118120
--code-copy-success-color: var(--dark-code-copy-success-color);
121+
--draft-title-color: var(--dark-draft-title-color);
119122
}
120123
}
121124

@@ -154,6 +157,7 @@
154157
--code-lang-color: var(--dark-code-lang-color);
155158
--code-copy-color: var(--dark-code-copy-color);
156159
--code-copy-success-color: var(--dark-code-copy-success-color);
160+
--draft-title-color: var(--dark-draft-title-color);
157161
}
158162

159163
/* =============================================================================
@@ -998,6 +1002,11 @@ img[src*="#centre"] {
9981002
color: var(--hover-color);
9991003
}
10001004

1005+
.post-summary.draft-post h2 a,
1006+
.post.draft-post .post-header h1 {
1007+
color: var(--draft-title-color);
1008+
}
1009+
10011010
.post-summary time {
10021011
color: var(--text-secondary);
10031012
font-size: 0.9em;

tests/test_renderer.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,3 +1190,111 @@ def test_listing_meta_tags_omit_description_when_absent(
11901190

11911191
assert '<meta name="description"' not in html
11921192
assert '<meta property="og:description"' not in html
1193+
1194+
def test_draft_post_page_has_emoji_and_class(
1195+
self, sample_draft_post: Post
1196+
) -> None:
1197+
"""Test that a draft post page shows the 🚧 emoji and draft-post class."""
1198+
renderer = TemplateRenderer()
1199+
html = renderer.render_post(sample_draft_post, site_title="Test Blog")
1200+
1201+
assert "🚧" in html
1202+
assert "draft-post" in html
1203+
1204+
def test_non_draft_post_page_has_no_emoji_or_class(
1205+
self, sample_post: Post
1206+
) -> None:
1207+
"""Test that a non-draft post page does not show the 🚧 emoji or draft-post class."""
1208+
renderer = TemplateRenderer()
1209+
html = renderer.render_post(sample_post, site_title="Test Blog")
1210+
1211+
assert "🚧" not in html
1212+
assert "draft-post" not in html
1213+
1214+
def test_draft_post_index_has_emoji_and_class(
1215+
self, sample_draft_post: Post
1216+
) -> None:
1217+
"""Test that a draft post in the index shows the 🚧 emoji and draft-post class."""
1218+
renderer = TemplateRenderer()
1219+
html = renderer.render_index(
1220+
posts=[sample_draft_post],
1221+
page=1,
1222+
total_pages=1,
1223+
site_title="Test Blog",
1224+
)
1225+
1226+
assert "🚧" in html
1227+
assert "draft-post" in html
1228+
1229+
def test_non_draft_post_index_has_no_emoji_or_class(
1230+
self, sample_post: Post
1231+
) -> None:
1232+
"""Test that a non-draft post in the index does not show the 🚧 emoji or draft-post class."""
1233+
renderer = TemplateRenderer()
1234+
html = renderer.render_index(
1235+
posts=[sample_post],
1236+
page=1,
1237+
total_pages=1,
1238+
site_title="Test Blog",
1239+
)
1240+
1241+
assert "🚧" not in html
1242+
assert "draft-post" not in html
1243+
1244+
def test_draft_post_tag_page_has_emoji_and_class(
1245+
self, sample_draft_post: Post
1246+
) -> None:
1247+
"""Test that a draft post on a tag page shows the 🚧 emoji and draft-post class."""
1248+
renderer = TemplateRenderer()
1249+
html = renderer.render_tag_page(
1250+
tag="draft",
1251+
posts=[sample_draft_post],
1252+
site_title="Test Blog",
1253+
)
1254+
1255+
assert "🚧" in html
1256+
assert "draft-post" in html
1257+
1258+
def test_draft_post_category_page_has_emoji_and_class(
1259+
self, sample_draft_post: Post
1260+
) -> None:
1261+
"""Test that a draft post on a category page shows the 🚧 emoji and draft-post class."""
1262+
renderer = TemplateRenderer()
1263+
html = renderer.render_category_page(
1264+
category="webdev",
1265+
posts=[sample_draft_post],
1266+
site_title="Test Blog",
1267+
)
1268+
1269+
assert "🚧" in html
1270+
assert "draft-post" in html
1271+
1272+
def test_draft_post_archive_has_emoji_and_class(
1273+
self, sample_draft_post: Post
1274+
) -> None:
1275+
"""Test that a draft post on the archive page shows the 🚧 emoji and draft-title class."""
1276+
renderer = TemplateRenderer()
1277+
# Render the main archive page (no archive_title) to exercise the
1278+
# compact list view that uses the draft-title CSS class.
1279+
html = renderer.render_archive(
1280+
posts=[sample_draft_post],
1281+
site_title="Test Blog",
1282+
)
1283+
1284+
assert "🚧" in html
1285+
assert "draft-title" in html
1286+
1287+
def test_non_draft_post_archive_has_no_emoji_or_class(
1288+
self, sample_post: Post
1289+
) -> None:
1290+
"""Test that a non-draft post on the archive page does not show the 🚧 emoji or draft-title class."""
1291+
renderer = TemplateRenderer()
1292+
# Render the main archive page (no archive_title) to exercise the
1293+
# compact list view that uses the draft-title CSS class.
1294+
html = renderer.render_archive(
1295+
posts=[sample_post],
1296+
site_title="Test Blog",
1297+
)
1298+
1299+
assert "🚧" not in html
1300+
assert "draft-title" not in html

0 commit comments

Comments
 (0)