Pull request #36: feature: add course index

Merge in FRAG/50ohm.de from feature/course-index to main

* commit '413c86fc20a1b1deb90223d00a188fc148df259f':
  fix: corrected key name for video URL
  feature: load edition metadata from directus
  fix: link for chapter
  fix: video url
  refactor: renamed function
  refactor: removed class from title
  feature: add course index
This commit is contained in:
Prof.Dr. Matthias Jung DL9MJ
2025-06-24 22:04:13 +02:00
committed by Tim Grelka
5 changed files with 44 additions and 14 deletions
+21 -7
View File
@@ -107,12 +107,13 @@ class Build:
shutil.copyfile(src, dst) shutil.copyfile(src, dst)
# cached # cached
def __build_chapter(self, edition, number, chapter, next_chapter=None): def __build_chapter(self, edition, edition_name, number, chapter, next_chapter=None):
chapter_template = self.env.get_template("chapter.html") chapter_template = self.env.get_template("chapter.html")
next_chapter_template = self.env.get_template("next_chapter.html") next_chapter_template = self.env.get_template("next_chapter.html")
with open(f'build/{edition}_chapter_{chapter["ident"]}.html', 'w') as file: with open(f'build/{edition}_chapter_{chapter["ident"]}.html', 'w') as file:
result = chapter_template.render( result = chapter_template.render(
edition=edition, edition=edition,
name=edition_name,
number=number, number=number,
chapter=chapter, chapter=chapter,
) )
@@ -128,7 +129,7 @@ class Build:
file.write(result) file.write(result)
# cached # cached
def __build_section(self, edition, section, chapter, next_section=None, next_chapter=None): def __build_section(self, edition, edition_name, section, chapter, next_section=None, next_chapter=None):
section_template = self.env.get_template("section.html") section_template = self.env.get_template("section.html")
next_section_template = self.env.get_template("next_section.html") next_section_template = self.env.get_template("next_section.html")
next_chapter_template = self.env.get_template("next_chapter.html") next_chapter_template = self.env.get_template("next_chapter.html")
@@ -139,6 +140,7 @@ class Build:
result = section_template.render( result = section_template.render(
edition=edition, edition=edition,
name=edition_name,
section=section, section=section,
chapter=chapter, chapter=chapter,
) )
@@ -158,19 +160,31 @@ class Build:
result = BeautifulSoup(result, "html.parser").prettify() result = BeautifulSoup(result, "html.parser").prettify()
file.write(result) file.write(result)
def __build_book_index(self, book):
template = self.env.get_template("course_index.html")
with open(f"build/{book['edition']}_course_index.html", "w") as file:
result = template.render(
book=book,
)
result = self.__build_page(result)
result = BeautifulSoup(result, "html.parser").prettify()
file.write(result)
def build_edition(self, edition): def build_edition(self, edition):
edition = edition.upper() edition = edition.upper()
with open(f'data/book_{edition}.json') as f: with open(f'data/book_{edition}.json') as f:
book = json.load(f) book = json.load(f)
chapters = book["chapters"]
for number, chapter in enumerate(tqdm(book, desc=f"Build Edition: {edition}"),1): edition_name = book["title"]
next_chapter = book[number] if number < len(book) else None self.__build_book_index(book)
self.__build_chapter(edition, number, chapter, next_chapter) for number, chapter in enumerate(tqdm(chapters, desc=f"Build Edition: {edition}"),1):
next_chapter = chapters[number] if number < len(chapters) else None
self.__build_chapter(edition, edition_name, number, chapter, next_chapter)
for i, section in enumerate(chapter["sections"],1) : for i, section in enumerate(chapter["sections"],1) :
next_section = chapter["sections"][i] if i < len(chapter["sections"]) else None next_section = chapter["sections"][i] if i < len(chapter["sections"]) else None
self.__build_section(edition, section, chapter, next_section, next_chapter) self.__build_section(edition, edition_name, section, chapter, next_section, next_chapter)
def build_assets(self): def build_assets(self):
shutil.copytree("assets", "build/assets", dirs_exist_ok=True) shutil.copytree("assets", "build/assets", dirs_exist_ok=True)
+9 -2
View File
@@ -47,13 +47,20 @@ class Download:
"class": in_section["class"], "class": in_section["class"],
"content": in_section["content"], "content": in_section["content"],
"slide": in_section["slide"], "slide": in_section["slide"],
"video": in_section["video_url"], "video_url": in_section["video_url"],
"questions": [], "questions": [],
} }
) )
out_edition = {
"title": in_edition["title"],
"abstract": in_edition["abstract"],
"chapters": out_chapters,
"edition": edition,
}
with open("data/book_" + edition + ".json", "w", encoding="utf-8") as file: with open("data/book_" + edition + ".json", "w", encoding="utf-8") as file:
json.dump(out_chapters, file, ensure_ascii=False, indent=4) json.dump(out_edition, file, ensure_ascii=False, indent=4)
def download_question_metadata(self): def download_question_metadata(self):
questions = self.api.get( questions = self.api.get(
+2 -2
View File
@@ -1,6 +1,6 @@
<div style="float: right;"> <div style="float: right;">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% if chapter.video_url %} {% if chapter["video_url"] %}
<a class="btn btn-secondary" href="{{ chapter["video_url"] }}" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-6l-7 4V7z"></path></svg></a> <a class="btn btn-secondary" href="{{ chapter["video_url"] }}" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-6l-7 4V7z"></path></svg></a>
{% endif %} {% endif %}
<a class="btn btn-secondary" href="{{ edition }}_slide_{{ chapter["ident"] }}.html"><svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M840-120v-640H120v320H40v-320q0-33 23.5-56.5T120-840h720q33 0 56.5 23.5T920-760v560q0 33-23.5 56.5T840-120ZM360-400q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm0-80q33 0 56.5-23.5T440-560q0-33-23.5-56.5T360-640q-33 0-56.5 23.5T280-560q0 33 23.5 56.5T360-480ZM40-80v-112q0-34 17.5-62.5T104-298q62-31 126-46.5T360-360q66 0 130 15.5T616-298q29 15 46.5 43.5T680-192v112H40Zm80-80h480v-32q0-11-5.5-20T580-226q-54-27-109-40.5T360-280q-56 0-111 13.5T140-226q-9 5-14.5 14t-5.5 20v32Zm240-400Zm0 400Z"/></svg></a> <a class="btn btn-secondary" href="{{ edition }}_slide_{{ chapter["ident"] }}.html"><svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M840-120v-640H120v320H40v-320q0-33 23.5-56.5T120-840h720q33 0 56.5 23.5T920-760v560q0 33-23.5 56.5T840-120ZM360-400q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm0-80q33 0 56.5-23.5T440-560q0-33-23.5-56.5T360-640q-33 0-56.5 23.5T280-560q0 33 23.5 56.5T360-480ZM40-80v-112q0-34 17.5-62.5T104-298q62-31 126-46.5T360-360q66 0 130 15.5T616-298q29 15 46.5 43.5T680-192v112H40Zm80-80h480v-32q0-11-5.5-20T580-226q-54-27-109-40.5T360-280q-56 0-111 13.5T140-226q-9 5-14.5 14t-5.5 20v32Zm240-400Zm0 400Z"/></svg></a>
@@ -10,7 +10,7 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="index.html">Home</a></li> <li class="breadcrumb-item"><a href="index.html">Home</a></li>
<li class="breadcrumb-item"><a href="{{ edition }}_course_index.html">Online-Kurs</a></li> <li class="breadcrumb-item"><a href="{{ edition }}_course_index.html">{{ name }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ chapter["title"] }}</li> <li class="breadcrumb-item active" aria-current="page">{{ chapter["title"] }}</li>
</ol> </ol>
</nav> </nav>
+9
View File
@@ -0,0 +1,9 @@
<h1>{{ book["title"] }}</h1>
<p>{{ book["abstract"] }}</p>
<div class="chapter-toc d-grid gap-2">
{% for chapter in book["chapters"] %}
<a class="btn btn-outline-primary btn-labeled" href="{{ book["edition"] }}_chapter_{{ chapter["ident"] }}.html">
<span class="btn-label">{{loop.index}}</span>{{ chapter["title"]}}
</a>
{% endfor %}
</div>
+3 -3
View File
@@ -1,6 +1,6 @@
<div style="float: right;"> <div style="float: right;">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% if section.video_url %} {% if section["video_url"] %}
<a class="btn btn-secondary" href="{{ section["video_url"] }}" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-6l-7 4V7z"></path></svg></a> <a class="btn btn-secondary" href="{{ section["video_url"] }}" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-6l-7 4V7z"></path></svg></a>
{% endif %} {% endif %}
<a class="btn btn-secondary" href="{{ edition }}_slide_{{ chapter["ident"] }}.html#{{ section_id }}"><svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M840-120v-640H120v320H40v-320q0-33 23.5-56.5T120-840h720q33 0 56.5 23.5T920-760v560q0 33-23.5 56.5T840-120ZM360-400q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm0-80q33 0 56.5-23.5T440-560q0-33-23.5-56.5T360-640q-33 0-56.5 23.5T280-560q0 33 23.5 56.5T360-480ZM40-80v-112q0-34 17.5-62.5T104-298q62-31 126-46.5T360-360q66 0 130 15.5T616-298q29 15 46.5 43.5T680-192v112H40Zm80-80h480v-32q0-11-5.5-20T580-226q-54-27-109-40.5T360-280q-56 0-111 13.5T140-226q-9 5-14.5 14t-5.5 20v32Zm240-400Zm0 400Z"/></svg></a> <a class="btn btn-secondary" href="{{ edition }}_slide_{{ chapter["ident"] }}.html#{{ section_id }}"><svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M840-120v-640H120v320H40v-320q0-33 23.5-56.5T120-840h720q33 0 56.5 23.5T920-760v560q0 33-23.5 56.5T840-120ZM360-400q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm0-80q33 0 56.5-23.5T440-560q0-33-23.5-56.5T360-640q-33 0-56.5 23.5T280-560q0 33 23.5 56.5T360-480ZM40-80v-112q0-34 17.5-62.5T104-298q62-31 126-46.5T360-360q66 0 130 15.5T616-298q29 15 46.5 43.5T680-192v112H40Zm80-80h480v-32q0-11-5.5-20T580-226q-54-27-109-40.5T360-280q-56 0-111 13.5T140-226q-9 5-14.5 14t-5.5 20v32Zm240-400Zm0 400Z"/></svg></a>
@@ -10,13 +10,13 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="index.html">Home</a></li> <li class="breadcrumb-item"><a href="index.html">Home</a></li>
<li class="breadcrumb-item"><a href="{{ edition }}_course_index.html">Online-Kurs</a></li> <li class="breadcrumb-item"><a href="{{ edition }}_course_index.html">{{ name }}</a></li>
<li class="breadcrumb-item"><a href="{{ edition }}_chapter_{{ chapter["ident"] }}.html">{{ chapter["title"] }}</a></li> <li class="breadcrumb-item"><a href="{{ edition }}_chapter_{{ chapter["ident"] }}.html">{{ chapter["title"] }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ section["title"] }}</li> <li class="breadcrumb-item active" aria-current="page">{{ section["title"] }}</li>
</ol> </ol>
</nav> </nav>
<h1>{{ section["title"] }} (Klasse {{ section["class"] }})</h1> <h1>{{ section["title"] }}</h1>
<a id="margin_0"></a> <a id="margin_0"></a>
{{ section["content"] }} {{ section["content"] }}