Skip to content

Commit 5449693

Browse files
committed
feat: implement server-side objective letter badge injection
1 parent 4fb9c8b commit 5449693

File tree

4 files changed

+82
-22
lines changed

4 files changed

+82
-22
lines changed

docs-overrides/partials/content.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,18 @@
3535
page title (or respectively site name) is used as the main headline.
3636
-->
3737
{% if not "\x3ch1" in page.content %}
38+
{% if page.meta.letter_prefix %}
39+
<div class="objective-header-with-badge">
40+
<span class="objective-badge-standalone" data-letter="{{ page.meta.letter_prefix }}"></span>
41+
42+
<h1>{{ page.title | d(config.site_name, true)}}</h1>
43+
44+
</div>
45+
{% else %}
3846
<h1>{{ page.title | d(config.site_name, true)}}</h1>
3947
{% endif %}
4048
{% endif %}
49+
{% endif %}
4150

4251
<!-- Page content -->
4352
{% if page.meta.letter_prefix %}

docs/javascripts/objective-badges.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
function injectBadge() {
1818
// Check if letter_prefix exists in page metadata
19-
const metaScript = document.getElementById(
20-
'page-meta-letter-prefix'
21-
);
19+
const metaScript = document.getElementById('page-meta-letter-prefix');
2220
if (!metaScript) {
2321
return; // No letter_prefix on this page
2422
}
@@ -27,10 +25,7 @@
2725
try {
2826
letterPrefix = JSON.parse(metaScript.textContent);
2927
} catch (e) {
30-
console.error(
31-
'[objective-badges] Failed to parse letter_prefix',
32-
e
33-
);
28+
console.error('[objective-badges] Failed to parse letter_prefix', e);
3429
return;
3530
}
3631

@@ -39,25 +34,21 @@
3934
}
4035

4136
// Find the first H1 in the main content area
42-
const mainContent = document.querySelector(
43-
'.md-content__inner, article'
44-
);
37+
const mainContent = document.querySelector('.md-content__inner, article');
4538
if (!mainContent) {
4639
return;
4740
}
4841

4942
// Check if H1 is already wrapped in objective-header-with-badge
50-
const existingWrapper = mainContent.querySelector(
51-
'.objective-header-with-badge'
52-
);
43+
const existingWrapper = mainContent.querySelector('.objective-header-with-badge');
5344
if (existingWrapper) {
5445
return; // Already has badge, skip
5546
}
5647

5748
// Find the first H1
5849
const firstH1 = mainContent.querySelector('h1:first-of-type');
5950
if (!firstH1) {
60-
return; // No H1 found
51+
return;
6152
}
6253

6354
// Create the badge span
@@ -77,23 +68,30 @@
7768
wrapper.appendChild(document.createTextNode('\n\n'));
7869
}
7970

80-
function initPage() {
81-
// Small delay to ensure DOM is ready
82-
setTimeout(injectBadge, 0);
71+
function scheduleInjection() {
72+
// Use requestIdleCallback to run when browser is idle (after rendering)
73+
if (window.requestIdleCallback) {
74+
requestIdleCallback(() => {
75+
injectBadge();
76+
}, { timeout: 500 });
77+
} else {
78+
// Fallback for browsers without requestIdleCallback
79+
setTimeout(injectBadge, 200);
80+
}
8381
}
8482

85-
// Handle Material for MkDocs instant loading
83+
// Subscribe to document$ observable for instant loading
8684
if (window.document$ && window.document$.subscribe) {
8785
document$.subscribe(() => {
88-
initPage();
86+
scheduleInjection();
8987
});
9088
}
9189

92-
// Initial page load
90+
// Initial call
9391
if (document.readyState === 'loading') {
94-
document.addEventListener('DOMContentLoaded', initPage);
92+
document.addEventListener('DOMContentLoaded', scheduleInjection);
9593
} else {
96-
initPage();
94+
scheduleInjection();
9795
}
9896
})();
9997

docs/main.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import re
2+
3+
def define_env(env):
4+
"Hook function"
5+
6+
@env.macro
7+
def test123(page):
8+
list = []
9+
while page.next_page:
10+
list.append(page.next_page)
11+
page = page.next_page
12+
return list
13+
14+
15+
def on_page_content(html, page, config, files):
16+
"""
17+
Inject objective letter badge before the first H1 heading.
18+
This runs after markdown is converted to HTML.
19+
"""
20+
# Check if page has letter_prefix in frontmatter
21+
if not hasattr(page, 'meta') or 'letter_prefix' not in page.meta:
22+
return html
23+
24+
letter_prefix = page.meta['letter_prefix']
25+
if not letter_prefix or not isinstance(letter_prefix, str):
26+
return html
27+
28+
# Find the first H1 tag and wrap it with the badge
29+
# Pattern: <h1 ... > ... </h1>
30+
h1_pattern = r'(<h1[^>]*>)(.*?)(</h1>)'
31+
32+
def replace_first_h1(match):
33+
opening_tag = match.group(1)
34+
h1_content = match.group(2)
35+
closing_tag = match.group(3)
36+
37+
# Create the badge wrapper HTML
38+
badge_html = f'''<div class="objective-header-with-badge">
39+
<span class="objective-badge-standalone" data-letter="{letter_prefix}"></span>
40+
41+
{opening_tag}{h1_content}{closing_tag}
42+
43+
</div>'''
44+
return badge_html
45+
46+
# Replace only the first H1
47+
modified_html = re.sub(h1_pattern, replace_first_h1, html, count=1, flags=re.DOTALL)
48+
49+
return modified_html

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ extra_javascript:
7474
- javascripts/mermaid.js
7575
- javascripts/darkable-objects.js
7676
- javascripts/objective-badges.js
77+
78+
hooks:
79+
- docs/main.py
80+
7781
plugins:
7882
- include-markdown
7983
- git-revision-date-localized:

0 commit comments

Comments
 (0)