From 92f8e00fb5c3d58764e8aded25791a4650fa7c58 Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 10:54:47 +0200 Subject: [PATCH] =?UTF-8?q?header=20template=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 12292 -> 12292 bytes cms.py | 52 +++++++++++++++++++++++++++-------------- images/hfglogo.png | Bin 0 -> 7009 bytes md/header.md | 9 +++++++ output/index.html | 7 +++++- templates/default.html | 3 ++- 6 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 images/hfglogo.png create mode 100644 md/header.md diff --git a/.DS_Store b/.DS_Store index f912345d14e1a99293a46850160b35b11351f22d..336b29d7dea97cdfd6f70b45d2ea4df56bec281e 100644 GIT binary patch delta 20 bcmZokXi3%Sjxa;PpjPiN_PnZWF delta 42 ycmZokXi3H$P!i&;tM)a19Os diff --git a/cms.py b/cms.py index 61612d3..3edc84d 100755 --- a/cms.py +++ b/cms.py @@ -31,7 +31,8 @@ for d in (MD_DIR, TPL_DIR, CSS_DIR, OUT_DIR): SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$", re.IGNORECASE) START_PAGE = "startseite" -FOOTER_PAGE = "footer" # Baustein: wird in jede Seite eingefuegt, nicht eigenstaendig exportiert +HEADER_PAGE = "header" # Baustein: wird ueber den Inhalt jeder Seite gesetzt +FOOTER_PAGE = "footer" # Baustein: wird unter den Inhalt jeder Seite gesetzt # ---------------------------------------------------------------- markdown @@ -174,7 +175,7 @@ def md_path(slug): def list_pages(): pages = [] for f in sorted(os.listdir(MD_DIR)): - if f.endswith(".md") and f[:-3] != FOOTER_PAGE: + if f.endswith(".md") and f[:-3] not in (HEADER_PAGE, FOOTER_PAGE): pages.append(f[:-3]) return pages @@ -197,12 +198,26 @@ def delete_page(slug): # ------------------------------------------------------------------ export -def render_page(raw, template, footer_html=""): +def block_html(slug): + """Rendert einen Baustein (header/footer) als HTML, leer wenn nicht vorhanden.""" + if os.path.exists(md_path(slug)): + _, body = parse_md(load_page(slug)) + return md_to_html(body) + return "" + + +def render_page(raw, template, header_html="", footer_html=""): meta, body = parse_md(raw) html = md_to_html(body) page = (template .replace("{{titel}}", _escape(meta.get("titel", ""))) .replace("{{inhalt}}", html)) + if "{{header}}" in template: + page = page.replace("{{header}}", header_html) + elif header_html and "" in page: + # Template ohne {{header}}-Platzhalter: Baustein automatisch setzen + idx = page.index("") + len("") + page = page[:idx] + "\n
\n" + header_html + "\n
\n" + page[idx:] if "{{footer}}" in template: page = page.replace("{{footer}}", footer_html) elif footer_html and "" in page: @@ -232,14 +247,12 @@ def export_site(): if f.endswith(".css"): shutil.copy2(os.path.join(CSS_DIR, f), os.path.join(OUT_DIR, "css", f)) - footer_html = "" - if os.path.exists(md_path(FOOTER_PAGE)): - _, fbody = parse_md(load_page(FOOTER_PAGE)) - footer_html = md_to_html(fbody) + footer_html = block_html(FOOTER_PAGE) + header_html = block_html(HEADER_PAGE) exported = [] for slug in list_pages(): - page, _ = render_page(load_page(slug), template, footer_html) + page, _ = render_page(load_page(slug), template, header_html, footer_html) # startseite.md wird zur index.html out_name = "index.html" if slug == START_PAGE else slug + ".html" with open(os.path.join(OUT_DIR, out_name), "w", encoding="utf-8") as fh: @@ -276,10 +289,11 @@ class Handler(BaseHTTPRequestHandler): self._send(200, ADMIN_HTML, "text/html; charset=utf-8") elif path == "/api/pages": self._json({"pages": list_pages(), "start": START_PAGE, + "header": os.path.exists(md_path(HEADER_PAGE)), "footer": os.path.exists(md_path(FOOTER_PAGE))}) elif path.startswith("/api/page/"): slug = unquote(path[len("/api/page/"):]) - if slug == FOOTER_PAGE or slug in list_pages(): + if slug in (HEADER_PAGE, FOOTER_PAGE) or slug in list_pages(): self._json({"slug": slug, "raw": load_page(slug)}) else: self._json({"error": "Seite nicht gefunden"}, 404) @@ -304,10 +318,10 @@ class Handler(BaseHTTPRequestHandler): self._json({"ok": True, "slug": slug}) elif path == "/api/delete": slug = data.get("slug", "") - if slug != FOOTER_PAGE and slug not in list_pages(): + if slug not in (HEADER_PAGE, FOOTER_PAGE) and slug not in list_pages(): return self._json({"error": "Seite nicht gefunden"}, 404) - if slug in (START_PAGE, FOOTER_PAGE): - return self._json({"error": "Startseite und Footer-Baustein koennen nicht geloescht werden."}, 400) + if slug in (START_PAGE, HEADER_PAGE, FOOTER_PAGE): + return self._json({"error": "Startseite und Bausteine koennen nicht geloescht werden."}, 400) delete_page(slug) self._json({"ok": True}) elif path == "/api/preview": @@ -384,12 +398,14 @@ async function loadPages() { if (p === current) a.className = "active"; $("pages").appendChild(a); } - if (d.footer) { - const a = document.createElement("a"); - a.textContent = "footer (Baustein)"; - a.onclick = () => openPage("footer"); - if (current === "footer") a.className = "active"; - $("pages").appendChild(a); + for (const b of [["header", d.header], ["footer", d.footer]]) { + if (b[1]) { + const a = document.createElement("a"); + a.textContent = b[0] + " (Baustein)"; + a.onclick = () => openPage(b[0]); + if (current === b[0]) a.className = "active"; + $("pages").appendChild(a); + } } } diff --git a/images/hfglogo.png b/images/hfglogo.png new file mode 100644 index 0000000000000000000000000000000000000000..84161e76ef31c6fd95e95f7ab4d387b14996ba0b GIT binary patch literal 7009 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 literal 0 HcmV?d00001 diff --git a/md/header.md b/md/header.md new file mode 100644 index 0000000..71ee792 --- /dev/null +++ b/md/header.md @@ -0,0 +1,9 @@ +--- +titel: Header +--- + +# Meine Website + +Dieser Header-Baustein wird automatisch ueber den Inhalt **aller** Seiten gesetzt. + +- [Startseite](index.html) diff --git a/output/index.html b/output/index.html index 8c182bd..c7efd00 100644 --- a/output/index.html +++ b/output/index.html @@ -8,9 +8,14 @@
-

startseite

+

Meine Website

+

Dieser Header-Baustein wird automatisch ueber den Inhalt aller Seiten gesetzt.

+
+

startseite

Neue Seite. hier kommen alle inhalte hin.