diff options
-rw-r--r-- | pysite/rst/__init__.py | 70 | ||||
-rw-r--r-- | pysite/views/wiki/edit.py | 5 | ||||
-rw-r--r-- | pysite/views/wiki/render.py | 4 | ||||
-rw-r--r-- | pysite/views/ws/rst.py | 2 | ||||
-rw-r--r-- | templates/wiki/base.html | 20 |
5 files changed, 96 insertions, 5 deletions
diff --git a/pysite/rst/__init__.py b/pysite/rst/__init__.py index e0fc973e..0c069615 100644 --- a/pysite/rst/__init__.py +++ b/pysite/rst/__init__.py @@ -1,15 +1,83 @@ # coding=utf-8 +import re + from docutils.core import publish_parts from docutils.parsers.rst.roles import register_canonical_role from pysite.rst.roles import icon_role, page_role, url_for_role +RST_TEMPLATE = """.. contents:: + +{0}""" + +CONTENTS_REGEX = re.compile(r"""<div class=\"contents topic\" id=\"contents\">(.*?)</div>""", re.DOTALL) +HREF_REGEX = re.compile(r"""<a class=\"reference internal\" href=\"(.*?)\".*?>(.*?)</a>""") + def render(rst: str): - return publish_parts( + rst = RST_TEMPLATE.format(rst) + html = publish_parts( source=rst, writer_name="html5", settings_overrides={"halt_level": 2, "syntax_highlight": "short"} )["html_body"] + data = { + "html": html, + "headers": [] + } + + match = CONTENTS_REGEX.search(html) # Find the contents HTML + + if match: + data["html"] = html.replace(match.group(0), "") # Remove the contents from the document HTML + + depth = 0 + headers = [] + current_header = {} + + group = match.group(1) + + # Sanitize the output so we can more easily parse it + group = group.replace("<li>", "<li>\n") + group = group.replace("</li>", "\n</li>") + group = group.replace("<p>", "<p>\n") + group = group.replace("</p>", "\n</p>") + + for line in group.split("\n"): + line = line.strip() # Remove excess whitespace + + if not line: # Nothing to process + continue + + if line.startswith("<li>") and depth <= 2: + # We've found a header, or the start of a header group + depth += 1 + elif line.startswith("</li>") and depth >= 0: + # That's the end of a header or header group + + if depth == 1: + # We just dealt with an entire header group, so store it + headers.append(current_header.copy()) # Store a copy, since we're clearing the dict + current_header.clear() + + depth -= 1 + elif line.startswith("<a") and depth <= 2: + # We've found an actual URL + match = HREF_REGEX.match(line) # Parse the line for the ID and header title + + if depth == 1: # Top-level header, so just store it in the current header + current_header["id"] = match.group(1) + current_header["title"] = match.group(2) + else: # Second-level (or deeper) header, should be stored in a list of sub-headers under the current + sub_headers = current_header.get("sub_headers", []) + sub_headers.append({ + "id": match.group(1), + "title": match.group(2) + }) + current_header["sub_headers"] = sub_headers + + data["headers"] = headers + return data + register_canonical_role("icon", icon_role) register_canonical_role("page", page_role) diff --git a/pysite/views/wiki/edit.py b/pysite/views/wiki/edit.py index a111f9ce..a2e98e20 100644 --- a/pysite/views/wiki/edit.py +++ b/pysite/views/wiki/edit.py @@ -35,11 +35,14 @@ class EditView(RouteView, DBMixin): @csrf def post(self, page): rst = request.form["rst"] + rendered = render(rst) + obj = { "slug": page, "title": request.form["title"], "rst": rst, - "html": render(rst) + "html": rendered["html"], + "headers": rendered["headers"] } self.db.insert( diff --git a/pysite/views/wiki/render.py b/pysite/views/wiki/render.py index aa365c4a..9d3e8cc3 100644 --- a/pysite/views/wiki/render.py +++ b/pysite/views/wiki/render.py @@ -30,7 +30,7 @@ class RenderView(APIView): data = data[0]["data"] try: - html = render(data) + html = render(data)["html"] return jsonify({"data": html}) except SystemMessage as e: @@ -51,7 +51,7 @@ class RenderView(APIView): if match: data["error_lines"].append( { - "row": int(match.group(1)) - 1, + "row": int(match.group(1)) - 3, "column": 0, "type": "error", "text": match.group(2) diff --git a/pysite/views/ws/rst.py b/pysite/views/ws/rst.py index 19c4129b..24bdb3ca 100644 --- a/pysite/views/ws/rst.py +++ b/pysite/views/ws/rst.py @@ -23,7 +23,7 @@ class RSTWebsocket(WS): self.log.debug(f"RST | Message: {message}") try: - data = render(message) + data = render(message)["html"] except Exception as e: self.log.exception("Parsing error") data = str(e) diff --git a/templates/wiki/base.html b/templates/wiki/base.html index e400be02..60f031b9 100644 --- a/templates/wiki/base.html +++ b/templates/wiki/base.html @@ -33,6 +33,26 @@ <div class="uk-flex uk-flex-row uk-flex-1"> <div class="uk-card uk-card-body uk-flex-left uk-flex uk-card-primary"> <ul class="uk-nav-default uk-nav-parent-icon" uk-nav> + {% if data is defined %} + {% if "headers" in data %} + <li class="uk-nav-header">Contents</li> + {% for header in data.headers %} + {% if "sub_headers" in header %} + <li class="uk-parent"> + <a href="{{ header.id }}">{{ header.title }}</a> + <ul class="uk-nav-sub"> + {% for sub in header.sub_headers %} + <li><a href="{{ sub.id }}"><i class="uk-icon fas fa-diamond fa-fw" style="font-size: 0.5rem; vertical-align: 1px"></i> {{ sub.title }}</a></li> + {% endfor %} + </ul> + </li> + {% else %} + <li><a href="{{ header.id }}">{{ header.title }}</a></li> + {% endif %} + {% endfor %} + {% endif %} + <li class="uk-nav-divider"></li> + {% endif %} <li><a href="{{ url_for("wiki.page", page="home") }}"> <i class="uk-icon fas fa-fw fa-home"></i> Home </a></li> |