aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gunicorn_config.py15
-rw-r--r--pysite/database.py15
-rw-r--r--pysite/views/wiki/edit.py10
-rw-r--r--pysite/views/wiki/search.py66
-rw-r--r--static/style.css7
-rw-r--r--templates/wiki/base.html21
-rw-r--r--templates/wiki/page_edit.html27
-rw-r--r--templates/wiki/page_move.html4
-rw-r--r--templates/wiki/search.html23
-rw-r--r--templates/wiki/search_results.html39
10 files changed, 212 insertions, 15 deletions
diff --git a/gunicorn_config.py b/gunicorn_config.py
index 0e374fdd..c09bee6a 100644
--- a/gunicorn_config.py
+++ b/gunicorn_config.py
@@ -1,3 +1,10 @@
+import html
+import re
+
+STRIP_REGEX = re.compile(r"<[^<]+?>")
+WIKI_TABLE = "wiki"
+
+
def when_ready(server=None):
""" server hook that only runs when the gunicorn master process loads """
@@ -24,3 +31,11 @@ def when_ready(server=None):
if initialized:
tables = ", ".join([f"{table} ({count} items)" for table, count in initialized.items()])
output(f"Initialized the following tables: {tables}")
+
+ output("Adding plain-text version of any wiki articles that don't have one...")
+
+ for article in db.pluck(WIKI_TABLE, "html", "text", "slug"):
+ if "text" not in article:
+ article["text"] = html.unescape(STRIP_REGEX.sub("", article["html"]).strip())
+
+ db.insert(WIKI_TABLE, article, conflict="update")
diff --git a/pysite/database.py b/pysite/database.py
index 86c8685d..41fbd253 100644
--- a/pysite/database.py
+++ b/pysite/database.py
@@ -1,8 +1,10 @@
# coding=utf-8
+import html
import json
import logging
import os
from typing import Any, Callable, Dict, Iterator, List, Optional, Union
+import re
import rethinkdb
from flask import abort
@@ -22,6 +24,9 @@ ALL_TABLES = {
"wiki_revisions": "id"
}
+STRIP_REGEX = re.compile(r"<[^<]+?>")
+WIKI_TABLE = "wiki"
+
class RethinkDB:
@@ -55,6 +60,13 @@ class RethinkDB:
tables = ", ".join([f"{table} ({count} items)" for table, count in initialized.items()])
self.log.debug(f"Initialized the following tables: {tables}")
+ # Upgrade wiki articles
+ for article in self.pluck(WIKI_TABLE, "html", "text", "slug"):
+ if "text" not in article:
+ article["text"] = html.unescape(STRIP_REGEX.sub("", article["html"]).strip())
+
+ self.insert(WIKI_TABLE, article, conflict="update")
+
def create_tables(self) -> List[str]:
"""
Creates whichever tables exist in the ALL_TABLES
@@ -496,9 +508,6 @@ class RethinkDB:
"""
Map a function over every document in a table, with the possibility of modifying it
- r.table('users').map(
- lambda doc: doc.merge({'user_id': doc['id']}).without('id')).run(conn)
-
As an example, you could do the following to rename the "id" field to "user_id" for all documents
in the "users" table.
diff --git a/pysite/views/wiki/edit.py b/pysite/views/wiki/edit.py
index 2cd4181c..bb39ed2b 100644
--- a/pysite/views/wiki/edit.py
+++ b/pysite/views/wiki/edit.py
@@ -1,5 +1,7 @@
import datetime
import difflib
+import html
+import re
import requests
from flask import redirect, request, url_for
@@ -11,6 +13,8 @@ from pysite.decorators import csrf, require_roles
from pysite.mixins import DBMixin
from pysite.rst import render
+STRIP_REGEX = re.compile(r"<[^<]+?>")
+
class EditView(RouteView, DBMixin):
path = "/edit/<path:page>" # "path" means that it accepts slashes
@@ -55,11 +59,12 @@ class EditView(RouteView, DBMixin):
@csrf
def post(self, page):
rst = request.form.get("rst")
+ title = request.form["title"]
- if not rst:
+ if not rst or not not rst.strip():
raise BadRequest()
- if not rst.strip():
+ if not title or not title.strip():
raise BadRequest()
rendered = render(rst)
@@ -69,6 +74,7 @@ class EditView(RouteView, DBMixin):
"title": request.form["title"],
"rst": rst,
"html": rendered["html"],
+ "text": html.unescape(STRIP_REGEX.sub("", rendered["html"]).strip()),
"headers": rendered["headers"]
}
diff --git a/pysite/views/wiki/search.py b/pysite/views/wiki/search.py
new file mode 100644
index 00000000..1c920e15
--- /dev/null
+++ b/pysite/views/wiki/search.py
@@ -0,0 +1,66 @@
+import html
+import re
+
+from flask import redirect, request, url_for
+from werkzeug.exceptions import BadRequest
+
+from pysite.base_route import RouteView
+from pysite.decorators import csrf
+from pysite.mixins import DBMixin
+
+STRIP_REGEX = re.compile(r"<[^<]+?>")
+
+
+class SearchView(RouteView, DBMixin):
+ path = "/search" # "path" means that it accepts slashes
+ name = "search"
+ table_name = "wiki"
+ revision_table_name = "wiki_revisions"
+
+ def get(self):
+ return self.render("wiki/search.html")
+
+ @csrf
+ def post(self):
+ given_query = request.form.get("query")
+
+ if not given_query or not given_query.strip():
+ raise BadRequest()
+
+ query = f"({re.escape(given_query)})"
+
+ pages = self.db.filter(
+ self.table_name,
+ lambda doc: doc["text"].match(query)
+ )
+
+ if len(pages) == 1:
+ slug = pages[0]["slug"]
+ return redirect(url_for("wiki.page", page=slug), code=303)
+
+ for obj in pages:
+ text = obj["text"]
+
+ matches = re.finditer(query, text)
+ snippets = []
+
+ for match in matches:
+ start = match.start() - 50
+
+ if start < 0:
+ start = 0
+
+ end = match.end() + 50
+
+ if end > len(text):
+ end = len(text)
+
+ match_text = text[start:end]
+ match_text = re.sub(query, r"<strong>\1</strong>", html.escape(match_text))
+
+ snippets.append(match_text.replace("\n", "<br />"))
+
+ obj["matches"] = snippets
+
+ pages = sorted(pages, key=lambda d: d["title"])
+ return self.render("wiki/search_results.html", pages=pages, query=given_query)
diff --git a/static/style.css b/static/style.css
index 78f2663b..a857bd4d 100644
--- a/static/style.css
+++ b/static/style.css
@@ -151,3 +151,10 @@ div.payment-icon img {
div.payment-icon {
margin-right: 1em;
}
+
+div.quote {
+ border-left: 3px solid #7289DA;
+ color: #99AAB5;
+ padding-left: 20px;
+ margin-bottom: 1rem;
+} \ No newline at end of file
diff --git a/templates/wiki/base.html b/templates/wiki/base.html
index a71f09e6..9b31d83b 100644
--- a/templates/wiki/base.html
+++ b/templates/wiki/base.html
@@ -154,12 +154,31 @@
{% endif %}
<li class="uk-nav-divider"></li>
+{# <li><a href="{{ url_for("wiki.search") }}">#}
+{# <i class="uk-icon fas fa-fw fa-search"></i> &nbsp;Search#}
+{# </a></li>#}
<li><a href="{{ url_for("wiki.special") }}">
<i class="uk-icon fas fa-fw fa-ellipsis-h"></i> &nbsp;Special Pages
</a></li>
<li><a href="{{ url_for("wiki.page", page="help") }}">
- <i class="fas fa-fw fa-question-circle"></i> &nbsp;Help
+ <i class="uk-icon fas fa-fw fa-question-circle"></i> &nbsp;Help
</a></li>
+
+ <li>
+ <form action="{{ url_for("wiki.search") }}" method="post">
+ {% if query is undefined %}
+ <input type="text" class="uk-input" placeholder="Search (BETA)" id="query" name="query" style="padding-right: 0; margin-top: 5px; border-left: 0; border-right: 0;" required>
+ {% else %}
+ <input type="text" class="uk-input" placeholder="Search (BETA)" id="query" name="query" value="{{ query }}" style="padding-right: 0; margin-top: 5px; border-left: 0; border-right: 0;" required>
+ {% endif %}
+ <br />
+ <button class="uk-button uk-button-darkish uk-button-small" type="submit" id="search" title="Search" style="width: 100%; border: 0;">
+ <i class="uk-icon fas fa-fw fa-search"></i>
+ </button>
+
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
+ </form>
+ </li>
</ul>
</div>
<div class="uk-section" style="flex-grow: 1; margin: 0 1rem 1rem;">
diff --git a/templates/wiki/page_edit.html b/templates/wiki/page_edit.html
index 51ce70db..138292f9 100644
--- a/templates/wiki/page_edit.html
+++ b/templates/wiki/page_edit.html
@@ -8,7 +8,7 @@
{% block content %}
<form uk-grid class="uk-grid-small" action="{{ url_for("wiki.edit", page=page) }}" method="post">
<div class="uk-width-expand">
- <input name="title" id="title" placeholder="Page Title" value="{{ title }}" class="uk-input" />
+ <input name="title" id="title" placeholder="Page Title" value="{{ title }}" class="uk-input" required />
</div>
<div class="uk-width-auto">
<button class="uk-button uk-button-secondary" type="button" value="Preview" id="preview">Preview</button>
@@ -37,7 +37,7 @@
let csrf_token = "{{ csrf_token() }}";
- document.getElementById("preview").onclick = function(event) {
+ function do_preview() {
let oReq = new XMLHttpRequest();
oReq.addEventListener("load", function() {
@@ -63,7 +63,7 @@
let data = editor.getValue();
- if (data.replace("\s", "").length < 1) {
+ if (data.replace("\s", "").length < 1 || document.getElementById("title").value.length < 1) {
document.getElementById("submit").disabled = true;
return false;
}
@@ -76,7 +76,9 @@
oReq.send(JSON.stringify({"data": editor.getValue()}));
return false;
- };
+ }
+
+ document.getElementById("preview").onclick = do_preview;
let editor = ace.edit("editor");
let timer;
@@ -93,15 +95,26 @@
if (timer !== undefined) {
clearTimeout(timer);
}
- timer = setTimeout(function() {document.getElementById("preview").click()}, 1000);
+ timer = setTimeout(do_preview, 1000);
});
document.getElementById("title").oninput = function() {
+ if (document.getElementById("title").value.length < 1) {
+ document.getElementById("submit").disabled = true;
+ }
+
document.getElementById("preview-title").textContent = document.getElementById("title").value;
- }
+
+ document.getElementById("rst").value = editor.getValue();
+
+ if (timer !== undefined) {
+ clearTimeout(timer);
+ }
+ timer = setTimeout(do_preview, 1000);
+ };
function refreshLock(){
- console.log("Refreshing lock")
+ console.log("Refreshing lock");
let xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 204) {
diff --git a/templates/wiki/page_move.html b/templates/wiki/page_move.html
index e2d52807..409c02e7 100644
--- a/templates/wiki/page_move.html
+++ b/templates/wiki/page_move.html
@@ -14,10 +14,10 @@
<form uk-grid class="uk-grid-small" action="{{ url_for("wiki.move", page=page) }}" method="post">
<input type="text" class="uk-width-1-1 uk-input" placeholder="{{ page }}" id="location" name="location" style="margin-left: 15px;" required>
<div class="uk-width-1-2">
- <a href="{{ url_for("wiki.page", page=page) }}" class="uk-button uk-button-primary uk-width-1-1" type="button" id="cancel">Cancel</a>
+ <a href="{{ url_for("wiki.page", page=page) }}" class="uk-button uk-button-secondary uk-width-1-1" type="button" id="cancel">Cancel</a>
</div>
<div class="uk-width-1-2">
- <input class="uk-button uk-button-secondary uk-width-1-1" type="submit" id="move" value="Move" />
+ <input class="uk-button uk-button-primary uk-width-1-1" type="submit" id="move" value="Move" />
</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
diff --git a/templates/wiki/search.html b/templates/wiki/search.html
new file mode 100644
index 00000000..6b15522e
--- /dev/null
+++ b/templates/wiki/search.html
@@ -0,0 +1,23 @@
+{% extends "wiki/base.html" %}
+{% block title %}Wiki | Search{% endblock %}
+{% block og_title %}Wiki | Search{% endblock %}
+{% block og_description %}Search for pages by content{% endblock %}
+{% block content %}
+ <div class="uk-container uk-container-small">
+ <h2 class="uk-title">
+ Search
+ </h2>
+
+ <form uk-grid class="uk-grid-small" action="{{ url_for("wiki.search") }}" method="post">
+ <input type="text" class="uk-width-1-1 uk-input" placeholder="Search Query" id="query" name="query" style="margin-left: 15px;" required>
+ <div class="uk-width-1-4">
+ &nbsp;
+ </div>
+ <div class="uk-width-1-2">
+ <input class="uk-button uk-button-primary uk-width-1-1" type="submit" id="search" value="Search" />
+ </div>
+
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
+ </form>
+ </div>
+{% endblock %} \ No newline at end of file
diff --git a/templates/wiki/search_results.html b/templates/wiki/search_results.html
new file mode 100644
index 00000000..08c303e2
--- /dev/null
+++ b/templates/wiki/search_results.html
@@ -0,0 +1,39 @@
+{% extends "wiki/base.html" %}
+{% block title %}Wiki | Search Results{% endblock %}
+{% block og_title %}Wiki | Search Results{% endblock %}
+{% block og_description %}Search results{% endblock %}
+{% block content %}
+ <div class="uk-container uk-container-small">
+ {% if not pages %}
+ <h2 class="uk-title">
+ Search
+ </h2>
+ <div class="uk-alert uk-alert-warning uk-text-center">
+ <p>
+ Sorry, no results were found. Please check your query and try again.
+ </p>
+ </div>
+ {% else %}
+ <h2 class="uk-title">
+ Search Results
+ </h2>
+
+ {% for page in pages %}
+ <h4>
+ <a href="{{ url_for("wiki.page", page=page["slug"]) }}">{{ page.title }}</a>
+ (<span style="font-family: monospace;">{{ page.slug }}</span>)
+ </h4>
+
+ {% for snippet in page["matches"] %}
+ <div class="quote">
+ <i class="uk-icon far fa-ellipsis-h"></i>
+ <br />
+ {{ snippet | safe }}
+ <br />
+ <i class="uk-icon far fa-ellipsis-h"></i>
+ </div>
+ {% endfor %}
+ {% endfor %}
+ {% endif %}
+ </div>
+{% endblock %} \ No newline at end of file