css start
This commit is contained in:
1 parent
92f8e00fb5
commit
dc1b35f344
9 files changed
+546
-10
No files matched your search
@@ -37,10 +37,12 @@ FOOTER_PAGE = "footer" # Baustein: wird unter den Inhalt jeder Seite gesetzt
|
||||
|
||||
# ---------------------------------------------------------------- markdown
|
||||
|
||||
def md_to_html(text):
|
||||
def md_to_html(text, headings=None):
|
||||
"""Minimaler Markdown->HTML-Konverter:
|
||||
Ueberschriften, **fett**, *kursiv*, `code`, [Links](url),
|
||||
Listen, Code-Bloecke, > Zitate, --- Trennlinien, Absaetze.
|
||||
Ueberschriften bekommen id-Anker; in headings (Liste) werden
|
||||
optional (anker, text) aller Ueberschriften gesammelt.
|
||||
"""
|
||||
lines = text.replace("\r\n", "\n").split("\n")
|
||||
out = []
|
||||
@@ -48,6 +50,7 @@ def md_to_html(text):
|
||||
in_code = False
|
||||
in_list = False
|
||||
list_tag = None
|
||||
used_ids = {}
|
||||
|
||||
def inline(s):
|
||||
s = re.sub(r"`([^`]+)`", r"<code>\1</code>", s)
|
||||
@@ -89,11 +92,28 @@ def md_to_html(text):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Rohe HTML-Zeilen (z.B. <div>, <svg>) unveraendert durchreichen
|
||||
if stripped.startswith("<"):
|
||||
close_list()
|
||||
out.append(stripped)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
m = re.match(r"^(#{1,6})\s+(.*)$", stripped)
|
||||
if m:
|
||||
close_list()
|
||||
level = len(m.group(1))
|
||||
out.append("<h%d>%s</h%d>" % (level, inline(_escape(m.group(2))), level))
|
||||
text_h = m.group(2)
|
||||
hid = _slugify(text_h)
|
||||
if hid in used_ids:
|
||||
used_ids[hid] += 1
|
||||
hid = "%s-%d" % (hid, used_ids[hid])
|
||||
else:
|
||||
used_ids[hid] = 0
|
||||
if headings is not None:
|
||||
headings.append((hid, text_h))
|
||||
out.append('<h%d id="%s">%s</h%d>'
|
||||
% (level, hid, inline(_escape(text_h)), level))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
@@ -136,7 +156,7 @@ def md_to_html(text):
|
||||
close_list()
|
||||
para = [stripped]
|
||||
while (i + 1 < len(lines) and lines[i + 1].strip()
|
||||
and not re.match(r"^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|-{3,}$)",
|
||||
and not re.match(r"^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|-{3,}$|<)",
|
||||
lines[i + 1].strip())):
|
||||
i += 1
|
||||
para.append(lines[i].strip())
|
||||
@@ -151,6 +171,23 @@ def _escape(s):
|
||||
return (s.replace("&", "&").replace("<", "<").replace(">", ">"))
|
||||
|
||||
|
||||
def _slugify(s):
|
||||
"""Ueberschriftentext -> HTML-id (ankertauglich, klein, ohne Sonderzeichen)."""
|
||||
s = s.lower()
|
||||
for k, v in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss")):
|
||||
s = s.replace(k, v)
|
||||
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
|
||||
return s or "abschnitt"
|
||||
|
||||
|
||||
def heading_nav(headings):
|
||||
"""Baut die Anker-Linkliste (Text-Links auf die Ueberschriften der Seite)."""
|
||||
if not headings:
|
||||
return ""
|
||||
return "".join('<a href="#%s">%s</a>' % (hid, _escape(text))
|
||||
for hid, text in headings)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- seiten
|
||||
|
||||
def parse_md(raw):
|
||||
@@ -208,10 +245,18 @@ def block_html(slug):
|
||||
|
||||
def render_page(raw, template, header_html="", footer_html=""):
|
||||
meta, body = parse_md(raw)
|
||||
html = md_to_html(body)
|
||||
headings = []
|
||||
html = md_to_html(body, headings)
|
||||
anker_html = heading_nav(headings)
|
||||
page = (template
|
||||
.replace("{{titel}}", _escape(meta.get("titel", "")))
|
||||
.replace("{{inhalt}}", html))
|
||||
if "{{anker}}" in template:
|
||||
page = page.replace("{{anker}}", anker_html)
|
||||
elif anker_html and "{{header}}" in template:
|
||||
# Template ohne {{anker}}-Platzhalter: Anker ans Ende des Headers
|
||||
page = page.replace("{{header}}",
|
||||
header_html + '\n<nav class="anker-nav">' + anker_html + "</nav>")
|
||||
if "{{header}}" in template:
|
||||
page = page.replace("{{header}}", header_html)
|
||||
elif header_html and "<body>" in page:
|
||||
|
||||
Reference in new issue
Block a user