aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Gareth Coles <[email protected]>2018-04-12 16:28:54 +0100
committerGravatar Gareth Coles <[email protected]>2018-04-12 16:28:54 +0100
commitdeb1cb5d24c3d483d27c7bab8abb0383c25d5323 (patch)
tree3b53b458444951c094322d03824608fc879d242e
parent[Wiki] Fix dodgy staff edit redirect query param (diff)
[Wiki] Some excellent shitcode for document TOCs
-rw-r--r--pysite/rst/__init__.py70
-rw-r--r--pysite/views/wiki/edit.py5
-rw-r--r--pysite/views/wiki/render.py4
-rw-r--r--pysite/views/ws/rst.py2
-rw-r--r--templates/wiki/base.html20
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> &nbsp;{{ 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> &nbsp;Home
</a></li>