aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--app_test.py3
-rw-r--r--gunicorn_config.py17
-rw-r--r--pysite/base_route.py6
-rw-r--r--pysite/constants.py3
-rw-r--r--pysite/database.py63
-rw-r--r--pysite/decorators.py11
-rw-r--r--pysite/mixins.py6
-rw-r--r--pysite/oauth.py2
-rw-r--r--pysite/views/error_handlers/http_4xx.py6
-rw-r--r--pysite/views/error_handlers/http_5xx.py6
-rw-r--r--pysite/views/wiki/edit.py35
-rw-r--r--pysite/views/wiki/page.py13
-rw-r--r--pysite/views/wiki/render.py4
-rw-r--r--static/resources.json12
-rw-r--r--templates/main/base.html6
-rw-r--r--templates/main/navigation.html20
-rw-r--r--templates/wiki/base.html86
-rw-r--r--templates/wiki/index.html11
-rw-r--r--templates/wiki/page_edit.html58
-rw-r--r--templates/wiki/page_missing.html11
-rw-r--r--templates/wiki/page_view.html18
22 files changed, 279 insertions, 119 deletions
diff --git a/.travis.yml b/.travis.yml
index 39676ca4..21e52051 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,6 +14,7 @@ install:
- pip install -r requirements-ci.txt
script:
- flake8
+ - python gunicorn_config.py
- py.test app_test.py --cov pysite --cov-report term-missing -v
- coveralls
after_success:
diff --git a/app_test.py b/app_test.py
index 5747dbd6..5bb84945 100644
--- a/app_test.py
+++ b/app_test.py
@@ -5,8 +5,11 @@ from flask import Blueprint
from flask_testing import TestCase
from app import manager
+from gunicorn_config import when_ready
from pysite.constants import DISCORD_OAUTH_REDIRECT, DISCORD_OAUTH_AUTHORIZED
+when_ready()
+
manager.app.tests_blueprint = Blueprint("tests", __name__)
manager.load_views(manager.app.tests_blueprint, "pysite/views/tests")
manager.app.register_blueprint(manager.app.tests_blueprint)
diff --git a/gunicorn_config.py b/gunicorn_config.py
new file mode 100644
index 00000000..f863edd4
--- /dev/null
+++ b/gunicorn_config.py
@@ -0,0 +1,17 @@
+# coding=utf-8
+def when_ready(server=None):
+ """ server hook that only runs when the gunicorn master process loads """
+
+ if server:
+ output = server.log.info
+ else:
+ output = print
+
+ output("Creating tables...")
+
+ from pysite.database import RethinkDB
+
+ db = RethinkDB(loop_type=None)
+ created = db.create_tables()
+
+ output(f"Created {created} tables.")
diff --git a/pysite/base_route.py b/pysite/base_route.py
index 494875ed..1d30669d 100644
--- a/pysite/base_route.py
+++ b/pysite/base_route.py
@@ -6,7 +6,7 @@ from flask import Blueprint, Response, jsonify, render_template, url_for
from flask.views import MethodView
from werkzeug.exceptions import default_exceptions
-from pysite.constants import ErrorCodes
+from pysite.constants import DEBUG_MODE, ErrorCodes
from pysite.mixins import OauthMixin
@@ -52,6 +52,7 @@ class BaseView(MethodView, OauthMixin):
context["view"] = self
context["logged_in"] = self.logged_in
context["static_file"] = self._static_file
+ context["debug"] = DEBUG_MODE
return render_template(template_names, **context)
@@ -204,4 +205,5 @@ class ErrorView(BaseView):
else:
blueprint.errorhandler(code)(cls.as_view(cls.name))
else:
- raise RuntimeError("Error views must have an `error_code` that is either an `int` or an iterable") # pragma: no cover # noqa: E501
+ raise RuntimeError(
+ "Error views must have an `error_code` that is either an `int` or an iterable") # pragma: no cover # noqa: E501
diff --git a/pysite/constants.py b/pysite/constants.py
index 69633127..e41b33bf 100644
--- a/pysite/constants.py
+++ b/pysite/constants.py
@@ -19,6 +19,8 @@ class ValidationTypes(Enum):
params = "params"
+DEBUG_MODE = "FLASK_DEBUG" in environ
+
OWNER_ROLE = 267627879762755584
ADMIN_ROLE = 267628507062992896
MODERATOR_ROLE = 267629731250176001
@@ -26,6 +28,7 @@ DEVOPS_ROLE = 409416496733880320
HELPER_ROLE = 267630620367257601
ALL_STAFF_ROLES = (OWNER_ROLE, ADMIN_ROLE, MODERATOR_ROLE, DEVOPS_ROLE)
+EDITOR_ROLES = ALL_STAFF_ROLES + (HELPER_ROLE,)
SERVER_ID = 267624335836053506
diff --git a/pysite/database.py b/pysite/database.py
index 4c2153fe..82e1e84e 100644
--- a/pysite/database.py
+++ b/pysite/database.py
@@ -8,17 +8,27 @@ from flask import abort
from rethinkdb.ast import RqlMethodQuery, Table, UserError
from rethinkdb.net import DefaultConnection
+ALL_TABLES = {
+ # table: primary_key
+
+ "oauth_data": "id",
+ "tags": "tag_name",
+ "users": "user_id",
+ "wiki": "slug",
+}
+
class RethinkDB:
- def __init__(self, loop_type: str = "gevent"):
+ def __init__(self, loop_type: Optional[str] = "gevent"):
self.host = os.environ.get("RETHINKDB_HOST", "127.0.0.1")
self.port = os.environ.get("RETHINKDB_PORT", "28016")
self.database = os.environ.get("RETHINKDB_DATABASE", "pythondiscord")
- self.log = logging.getLogger()
+ self.log = logging.getLogger(__name__)
self.conn = None
- rethinkdb.set_loop_type(loop_type)
+ if loop_type:
+ rethinkdb.set_loop_type(loop_type)
with self.get_connection(connect_database=False) as conn:
try:
@@ -27,7 +37,16 @@ class RethinkDB:
except rethinkdb.RqlRuntimeError:
self.log.debug(f"Database found: '{self.database}'")
- def get_connection(self, connect_database: bool=True) -> DefaultConnection:
+ def create_tables(self) -> int:
+ created = 0
+
+ for table, primary_key in ALL_TABLES.items():
+ if self.create_table(table, primary_key):
+ created += 1
+
+ return created
+
+ def get_connection(self, connect_database: bool = True) -> DefaultConnection:
"""
Grab a connection to the RethinkDB server, optionally without selecting a database
@@ -63,8 +82,8 @@ class RethinkDB:
# region: Convenience wrappers
- def create_table(self, table_name: str, primary_key: str="id", durability: str="hard", shards: int=1,
- replicas: Union[int, Dict[str, int]]=1, primary_replica_tag: Optional[str]=None) -> bool:
+ def create_table(self, table_name: str, primary_key: str = "id", durability: str = "hard", shards: int = 1,
+ replicas: Union[int, Dict[str, int]] = 1, primary_replica_tag: Optional[str] = None) -> bool:
"""
Attempt to create a new table on the current database
@@ -106,7 +125,7 @@ class RethinkDB:
def delete(self,
table_name: str,
primary_key: Union[str, None] = None,
- durability: str="hard",
+ durability: str = "hard",
return_changes: Union[bool, str] = False) -> dict:
"""
Delete one or all documents from a table. This can only delete
@@ -170,10 +189,13 @@ class RethinkDB:
:return: The RethinkDB table object for the table
"""
+ if table_name not in ALL_TABLES:
+ self.log.warning(f"Table not declared in database.py: {table_name}")
+
return rethinkdb.table(table_name)
- def run(self, query: RqlMethodQuery, *, new_connection: bool=False,
- connect_database: bool=True, coerce: type=None) -> Union[rethinkdb.Cursor, List, Dict, object]:
+ def run(self, query: RqlMethodQuery, *, new_connection: bool = False,
+ connect_database: bool = True, coerce: type = None) -> Union[rethinkdb.Cursor, List, Dict, object]:
"""
Run a query using a table object obtained from a call to `query()`
@@ -215,14 +237,14 @@ class RethinkDB:
# region: RethinkDB wrapper functions
def insert(self, table_name: str, *objects: Dict[str, Any],
- durability: str="hard",
- return_changes: Union[bool, str]=False,
+ durability: str = "hard",
+ return_changes: Union[bool, str] = False,
conflict: Union[ # Any of...
str, Callable[ # ...str, or a callable that...
[Dict[str, Any], Dict[str, Any]], # ...takes two dicts with string keys and any values...
Dict[str, Any] # ...and returns a dict with string keys and any values
]
- ]="error") -> Union[List, Dict]: # flake8: noqa
+ ] = "error") -> Union[List, Dict]: # flake8: noqa
"""
Insert an object or a set of objects into a table
@@ -262,7 +284,7 @@ class RethinkDB:
return dict(result) if result else None # pragma: no cover
- def get_all(self, table_name: str, *keys: str, index: str="id") -> List[Any]:
+ def get_all(self, table_name: str, *keys: str, index: str = "id") -> List[Any]:
"""
Get a list of documents matching a set of keys, on a specific index
@@ -278,7 +300,7 @@ class RethinkDB:
coerce=list
)
- def wait(self, table_name: str, wait_for: str="all_replicas_ready", timeout: int=0) -> bool:
+ def wait(self, table_name: str, wait_for: str = "all_replicas_ready", timeout: int = 0) -> bool:
"""
Wait until an operation has happened on a specific table; will block the current function
@@ -312,9 +334,9 @@ class RethinkDB:
return result.get("synced", 0) > 0 # pragma: no cover
- def changes(self, table_name: str, squash: Union[bool, int]=False, changefeed_queue_size: int=100_000,
- include_initial: Optional[bool]=None, include_states: bool=False,
- include_types: bool=False) -> Iterator[Dict[str, Any]]:
+ def changes(self, table_name: str, squash: Union[bool, int] = False, changefeed_queue_size: int = 100_000,
+ include_initial: Optional[bool] = None, include_states: bool = False,
+ include_types: bool = False) -> Iterator[Dict[str, Any]]:
"""
A complicated function allowing you to follow a changefeed for a specific table
@@ -420,8 +442,9 @@ class RethinkDB:
self.query(table_name).without(*selectors)
)
- def between(self, table_name: str, *, lower: Any=rethinkdb.minval, upper: Any=rethinkdb.maxval,
- index: Optional[str]=None, left_bound: str="closed", right_bound: str ="open") -> List[Dict[str, Any]]:
+ def between(self, table_name: str, *, lower: Any = rethinkdb.minval, upper: Any = rethinkdb.maxval,
+ index: Optional[str] = None, left_bound: str = "closed", right_bound: str = "open") -> List[
+ Dict[str, Any]]:
"""
Get all documents between two keys
@@ -483,7 +506,7 @@ class RethinkDB:
)
def filter(self, table_name: str, predicate: Callable[[Dict[str, Any]], bool],
- default: Union[bool, UserError]=False) -> List[Dict[str, Any]]:
+ default: Union[bool, UserError] = False) -> List[Dict[str, Any]]:
"""
Return all documents in a table for which `predicate` returns true.
diff --git a/pysite/decorators.py b/pysite/decorators.py
index 426d4846..0b02ebde 100644
--- a/pysite/decorators.py
+++ b/pysite/decorators.py
@@ -8,7 +8,7 @@ from schema import Schema, SchemaError
from werkzeug.exceptions import Forbidden
from pysite.base_route import APIView, BaseView
-from pysite.constants import CSRF, ErrorCodes, ValidationTypes
+from pysite.constants import CSRF, DEBUG_MODE, ErrorCodes, ValidationTypes
def csrf(f):
@@ -32,9 +32,11 @@ def require_roles(*roles: int):
def inner(self: BaseView, *args, **kwargs):
data = self.user_data
- if data:
+ if DEBUG_MODE:
+ return f(self, *args, **kwargs)
+ elif data:
for role in roles:
- if role in data.get("roles", []):
+ if DEBUG_MODE or role in data.get("roles", []):
return f(self, *args, **kwargs)
if isinstance(self, APIView):
@@ -42,6 +44,7 @@ def require_roles(*roles: int):
raise Forbidden()
return redirect(url_for("discord.login"))
+
return inner
return inner_decorator
@@ -128,5 +131,7 @@ def api_params(schema: Schema, validation_type: ValidationTypes = ValidationType
return self.error(ErrorCodes.incorrect_parameters)
return f(self, data, *args, **kwargs)
+
return inner
+
return inner_decorator
diff --git a/pysite/mixins.py b/pysite/mixins.py
index a2730ae4..efbc2d0c 100644
--- a/pysite/mixins.py
+++ b/pysite/mixins.py
@@ -4,6 +4,7 @@ from weakref import ref
from flask import Blueprint
from rethinkdb.ast import Table
+from pysite.constants import DEBUG_MODE
from pysite.database import RethinkDB
@@ -51,7 +52,9 @@ class DBMixin:
raise RuntimeError("Routes using DBViewMixin must define `table_name`")
cls._db = ref(manager.db)
- manager.db.create_table(cls.table_name, primary_key=cls.table_primary_key)
+
+ if DEBUG_MODE:
+ manager.db.create_table(cls.table_name, primary_key=cls.table_primary_key)
@property
def table(self) -> Table:
@@ -89,7 +92,6 @@ class OauthMixin:
@classmethod
def setup(cls: "OauthMixin", manager: "pysite.route_manager.RouteManager", blueprint: Blueprint):
-
if hasattr(super(), "setup"):
super().setup(manager, blueprint) # pragma: no cover
diff --git a/pysite/oauth.py b/pysite/oauth.py
index ef86aa8a..86a2024d 100644
--- a/pysite/oauth.py
+++ b/pysite/oauth.py
@@ -84,4 +84,4 @@ class OauthBackend(BaseBackend):
def logout(self):
sess_id = session.get("session_id")
if sess_id and self.db.get(OAUTH_DATABASE, sess_id): # If user exists in db,
- self.db.delete(OAUTH_DATABASE, sess_id) # remove them (at least, their session)
+ self.db.delete(OAUTH_DATABASE, sess_id) # remove them (at least, their session)
diff --git a/pysite/views/error_handlers/http_4xx.py b/pysite/views/error_handlers/http_4xx.py
index 48ae7f0f..2d6c54c6 100644
--- a/pysite/views/error_handlers/http_4xx.py
+++ b/pysite/views/error_handlers/http_4xx.py
@@ -11,7 +11,6 @@ class Error400View(ErrorView):
error_code = range(400, 430)
def __init__(self):
-
# Direct errors for all methods at self.return_error
methods = [
'get', 'post', 'put',
@@ -27,7 +26,6 @@ class Error400View(ErrorView):
return self.render(
"errors/error.html", code=error.code, req=request, error_title=error_desc,
- error_message=error_desc +
- " If you believe we have made a mistake, please "
- "<a href='https://github.com/discord-python/site/issues'>open an issue on our GitHub</a>."
+ error_message=f"{error_desc} If you believe we have made a mistake, please "
+ "<a href='https://github.com/discord-python/site/issues'>open an issue on our GitHub</a>."
), error.code
diff --git a/pysite/views/error_handlers/http_5xx.py b/pysite/views/error_handlers/http_5xx.py
index 14c016c5..46c65e38 100644
--- a/pysite/views/error_handlers/http_5xx.py
+++ b/pysite/views/error_handlers/http_5xx.py
@@ -36,7 +36,7 @@ class Error500View(ErrorView):
return self.render(
"errors/error.html", code=error.code, req=request, error_title=error_desc,
error_message="An error occurred while processing this request, please try "
- "again later. If you believe we have made a mistake, please "
- "<a href='https://github.com/discord-python/site/issues'>file an issue on our"
- " GitHub</a>."
+ "again later. If you believe we have made a mistake, please "
+ "<a href='https://github.com/discord-python/site/issues'>file an issue on our"
+ " GitHub</a>."
), error.code
diff --git a/pysite/views/wiki/edit.py b/pysite/views/wiki/edit.py
index 1a100b8b..0a0af15b 100644
--- a/pysite/views/wiki/edit.py
+++ b/pysite/views/wiki/edit.py
@@ -1,9 +1,10 @@
# coding=utf-8
-from flask import url_for
+from docutils.core import publish_parts
+from flask import request, url_for
from werkzeug.utils import redirect
from pysite.base_route import RouteView
-from pysite.constants import ALL_STAFF_ROLES
+from pysite.constants import EDITOR_ROLES
from pysite.decorators import csrf, require_roles
from pysite.mixins import DBMixin
@@ -15,28 +16,38 @@ class EditView(RouteView, DBMixin):
table_name = "wiki"
table_primary_key = "slug"
- @require_roles(*ALL_STAFF_ROLES)
+ @require_roles(*EDITOR_ROLES)
def get(self, page):
rst = ""
title = ""
+ preview = "<p>Preview will appear here.</p>"
obj = self.db.get(self.table_name, page)
if obj:
rst = obj["rst"]
title = obj["title"]
+ preview = obj["html"]
- return self.render("wiki/page_edit.html", page=page, rst=rst, title=title)
+ return self.render("wiki/page_edit.html", page=page, rst=rst, title=title, preview=preview)
- @require_roles(*ALL_STAFF_ROLES)
+ @require_roles(*EDITOR_ROLES)
@csrf
def post(self, page):
- # rst = request.form["rst"]
- # obj = {
- # "slug": page,
- # "title": request.form["title"],
- # "rst": request.form["rst"],
- # "html": ""
- # }
+ rst = request.form["rst"]
+ obj = {
+ "slug": page,
+ "title": request.form["title"],
+ "rst": rst,
+ "html": publish_parts(
+ source=rst, writer_name="html5", settings_overrides={"halt_level": 2}
+ )["html_body"]
+ }
+
+ self.db.insert(
+ self.table_name,
+ obj,
+ conflict="replace"
+ )
return redirect(url_for("wiki.page", page=page), code=303) # Redirect, ensuring a GET
diff --git a/pysite/views/wiki/page.py b/pysite/views/wiki/page.py
index 01c8fa8a..a7f60f02 100644
--- a/pysite/views/wiki/page.py
+++ b/pysite/views/wiki/page.py
@@ -1,8 +1,9 @@
# coding=utf-8
from flask import redirect, url_for
+from werkzeug.exceptions import NotFound
from pysite.base_route import RouteView
-from pysite.constants import ALL_STAFF_ROLES
+from pysite.constants import DEBUG_MODE, EDITOR_ROLES
from pysite.mixins import DBMixin
@@ -18,19 +19,21 @@ class PageView(RouteView, DBMixin):
if obj is None:
if self.is_staff():
- return redirect(url_for("wiki.edit", page=page))
+ return redirect(url_for("wiki.edit", page=page, can_edit=False))
- return self.render("wiki/page_missing.html", page=page)
- return self.render("wiki/page_view.html", page=page, data=obj)
+ raise NotFound()
+ return self.render("wiki/page_view.html", page=page, data=obj, can_edit=self.is_staff())
def is_staff(self):
+ if DEBUG_MODE:
+ return True
if not self.logged_in:
return False
roles = self.user_data.get("roles", [])
for role in roles:
- if role in ALL_STAFF_ROLES:
+ if role in EDITOR_ROLES:
return True
return False
diff --git a/pysite/views/wiki/render.py b/pysite/views/wiki/render.py
index 73c38731..131db1d3 100644
--- a/pysite/views/wiki/render.py
+++ b/pysite/views/wiki/render.py
@@ -7,7 +7,7 @@ from flask import jsonify
from schema import Schema
from pysite.base_route import APIView
-from pysite.constants import ALL_STAFF_ROLES, ValidationTypes
+from pysite.constants import EDITOR_ROLES, ValidationTypes
from pysite.decorators import api_params, csrf, require_roles
SCHEMA = Schema([{
@@ -22,7 +22,7 @@ class RenderView(APIView):
name = "render"
@csrf
- @require_roles(*ALL_STAFF_ROLES)
+ @require_roles(*EDITOR_ROLES)
@api_params(schema=SCHEMA, validation_type=ValidationTypes.json)
def post(self, data):
if not len(data):
diff --git a/static/resources.json b/static/resources.json
index 021840a0..87b4dd4f 100644
--- a/static/resources.json
+++ b/static/resources.json
@@ -32,6 +32,18 @@
"payment": "free",
"payment_description": null
},
+ "A Simple Guide to Git": {
+ "description": "A simple, no-nonsense guide to the basics of using Git.",
+ "url": "http://rogerdudler.github.io/git-guide/",
+ "payment": "free",
+ "payment_description": null
+ },
+ "Python Cheat Sheet": {
+ "description": "A Python 3 cheat sheet with useful information and tips, as well as common pitfalls for beginners.",
+ "url": "https://perso.limsi.fr/pointal/_media/python:cours:mementopython3-english.pdf",
+ "payment": "free",
+ "payment_description": null
+ },
"Python Tutorials by Corey Schafer on YouTube": {
"description": "An in-depth look at the Python programming language, from one of YouTube's most popular Python tutors.",
"url": "https://www.youtube.com/playlist?list=PL-osiE80TeTt2d9bfVyTiXJA-UTHn6WwU",
diff --git a/templates/main/base.html b/templates/main/base.html
index 4978d494..6f5921d9 100644
--- a/templates/main/base.html
+++ b/templates/main/base.html
@@ -47,7 +47,11 @@
<footer>
<div class="uk-section uk-section-secondary uk-container-medium uk-text-meta">
<div class="uk-text-center">
- <p>This website uses <a href="https://python.org">Python</a> and <a href="http://flask.pocoo.org/">Flask</a>, and was developed collaboratively on <a href="https://github.com/discord-python/site">GitHub</a>.</p>
+ <p>
+ This website uses <a href="https://python.org">Python</a> and
+ <a href="http://flask.pocoo.org/">Flask</a>, and was developed collaboratively on
+ <a href="https://github.com/discord-python/site">GitHub</a>.
+ </p>
</div>
</div>
</footer>
diff --git a/templates/main/navigation.html b/templates/main/navigation.html
index eb325a37..47c20d56 100644
--- a/templates/main/navigation.html
+++ b/templates/main/navigation.html
@@ -16,9 +16,10 @@
{% if current_page == "index" %}
<li class="uk-active"><a href="{{ url_for('main.index') }}"><i class="uk-icon fas fa-home"></i> &nbsp;Home</a></li>
{% else %}
- <li><a href="{{ url_for('main.index') }}"><i class="uk-icon fas fa-home"></i> &nbsp;Home</a></li>
+ <li><a href="{{ url_for('main.index') }}"><i class="uk-icon fas fa-home fa-fw"></i> &nbsp;Home</a></li>
{% endif %}
- <li><a href="{{ url_for('main.invite') }}"><i class="uk-icon fab fa-discord"></i> &nbsp;Discord</a></li>
+ <li><a href="{{ url_for('wiki.index') }}"><i class="uk-icon fas fa-book fa-fw"></i> &nbsp;Wiki</a></li>
+ <li><a href="{{ url_for('main.invite') }}"><i class="uk-icon fab fa-discord fa-fw"></i> &nbsp;Discord</a></li>
</ul>
<ul class="uk-navbar-nav">
<li>
@@ -26,14 +27,19 @@
<div class="uk-navbar-dropdown uk-background-secondary" uk-dropdown>
<ul class="uk-nav uk-navbar-dropdown-nav">
<li class="uk-nav-header uk-hidden@m">Navigation</li>
- <li class="uk-nav-item uk-hidden@m"><a href="{{ url_for('main.index') }}"><i class="uk-icon fas fa-home"></i> &nbsp;Home</a></li>
- <li class="uk-nav-item uk-hidden@m"><a href="{{ url_for('main.invite') }}"><i class="uk-icon fab fa-discord"></i> &nbsp;Discord</a></li>
+ <li class="uk-nav-item uk-hidden@m"><a href="{{ url_for('main.index') }}"><i class="uk-icon fas fa-home fa-fw"></i> &nbsp;Home</a></li>
+ <li class="uk-nav-item uk-hidden@m"><a href="{{ url_for('wiki.index') }}"><i class="uk-icon fas fa-book fa-fw"></i> &nbsp;Wiki</a></li>
+ <li class="uk-nav-item uk-hidden@m"><a href="{{ url_for('main.invite') }}"><i class="uk-icon fab fa-discord fa-fw"></i> &nbsp;Discord</a></li>
<li class="uk-nav-divider uk-hidden@m"></li>
- {% if logged_in %}
- <li class="uk-active"><a href="{{ url_for('main.logout') }}"><i class="uk-icon fas fa-unlock"></i> &nbsp;Logout</a></li>
+ {% if not debug %}
+ {% if logged_in %}
+ <li class="uk-active"><a href="{{ url_for('main.logout') }}"><i class="uk-icon fas fa-unlock"></i> &nbsp;Logout</a></li>
+ {% else %}
+ <li class="uk-active"><a href="{{ url_for('discord.login') }}"><i class="uk-icon fas fa-lock"></i> &nbsp;Login with Discord</a></li>
+ {% endif %}
{% else %}
- <li class="uk-active"><a href="{{ url_for('discord.login') }}"><i class="uk-icon fas fa-lock"></i> &nbsp;Login with Discord</a></li>
+ <li class="uk-active"><a style="color: #7289DA !important"><i class="uk-icon fas fa-exclamation-triangle"></i> &nbsp;Debug mode</a></li>
{% endif %}
{% if current_page.startswith("info") %}
diff --git a/templates/wiki/base.html b/templates/wiki/base.html
new file mode 100644
index 00000000..c7bd5616
--- /dev/null
+++ b/templates/wiki/base.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ {% block head %}
+ <title>Python Discord | {% block title %}{% endblock %}</title>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.39/js/uikit.min.js"></script>
+ <link rel="shortcut icon" href="{{ static_file('favicon.ico') }}">
+ <link rel="stylesheet" href="{{ static_file('uikit_blurple.css') }}"/>
+ <link rel="stylesheet" href="{{ static_file('style.css') }}"/>
+
+ <!-- OpenGraph metadata -->
+ <meta property="og:title" content="Python Discord | {% block og_title %}{% endblock %}">
+ <meta property="og:description" content="{% block og_description %}We're a large, friendly community focused around the Python programming language. Our community is open to those who wish to learn the language, as well as those looking to help others.{% endblock %}">
+ <meta content="/static/logos/logo_discord.png" property="og:image">
+ {% endblock %}
+ {% block extra_head %}
+
+ {% endblock %}
+ </head>
+ <body class="page-{{ current_page }}">
+ <div class="uk-offcanvas-content">
+ {% include "main/navigation.html" %}
+ <div class="uk-flex uk-flex-row" style="height: 100%;">
+ <div class="uk-card uk-card-body uk-flex-left uk-flex">
+ <ul class="uk-nav-default uk-nav-parent-icon" uk-nav>
+ {% if (can_edit or debug) and current_page == "page" %}
+ <li>
+ <a href="{{ url_for("wiki.edit", page=page) }}">
+ <i class="fas fa-pencil-alt"></i> &nbsp;Edit
+ </a>
+ </li>
+ <li class="uk-nav-divider"></li>
+ {% elif current_page == "edit" %}
+ <li>
+ <a href="{{ url_for("wiki.page", page=page) }}">
+ <i class="fas fa-arrow-left fa-fw"></i> &nbsp;Back
+ </a>
+ </li>
+ <li class="uk-nav-divider"></li>
+ {% endif %}
+ <li class="uk-active"><a href="#">Placeholder</a></li>
+ <li class="uk-parent">
+ <a href="#">Parent</a>
+ <ul class="uk-nav-sub">
+ <li><a href="#">Sub item</a></li>
+ <li><a href="#">Sub item</a></li>
+ </ul>
+ </li>
+ <li class="uk-parent">
+ <a href="#">Parent</a>
+ <ul class="uk-nav-sub">
+ <li><a href="#">Sub item</a></li>
+ <li><a href="#">Sub item</a></li>
+ </ul>
+ </li>
+ <li class="uk-nav-header">Header</li>
+ <li><a href="#"><span class="uk-margin-small-right" uk-icon="icon: table"></span> Item</a></li>
+ <li><a href="#"><span class="uk-margin-small-right" uk-icon="icon: thumbnails"></span> Item</a></li>
+ <li class="uk-nav-divider"></li>
+ <li><a href="#"><span class="uk-margin-small-right" uk-icon="icon: trash"></span> Item</a></li>
+ </ul>
+ </div>
+ <div class="uk-section" style="flex-grow: 1; margin: 0 1rem 1rem;">
+ {% block content %}{% endblock %}
+ </div>
+ </div>
+ </div>
+
+ <footer>
+ <div class="uk-section uk-section-secondary uk-container-medium uk-text-meta">
+ <div class="uk-text-center">
+ <p>
+ This website uses <a href="https://python.org">Python</a> and
+ <a href="http://flask.pocoo.org/">Flask</a>, and was developed collaboratively on
+ <a href="https://github.com/discord-python/site">GitHub</a>.
+ </p>
+ </div>
+ </div>
+ </footer>
+
+ <script src='{{ static_file('js/countdown.js') }}'></script>
+ </body>
+</html>
diff --git a/templates/wiki/index.html b/templates/wiki/index.html
deleted file mode 100644
index 5601cbbf..00000000
--- a/templates/wiki/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "main/base.html" %}
-{% block title %}Wiki | Home{% endblock %}
-{% block og_title %}Wiki | Home{% endblock %}
-{% block og_description %}Landing page for the wiki{% endblock %}
-{% block content %}
- <div class="uk-container uk-section">
- <h1 class="uk-title uk-text-center">
- Placeholder text.
- </h1>
- </div>
-{% endblock %} \ No newline at end of file
diff --git a/templates/wiki/page_edit.html b/templates/wiki/page_edit.html
index e83676fd..7610be34 100644
--- a/templates/wiki/page_edit.html
+++ b/templates/wiki/page_edit.html
@@ -1,35 +1,33 @@
-{% extends "main/base.html" %}
-{% block title %}Wiki | Home{% endblock %}
-{% block og_title %}Wiki | Home{% endblock %}
+{% extends "wiki/base.html" %}
+{% block title %}Wiki | Edit: {{ page }}{% endblock %}
+{% block og_title %}Wiki | Edit: {{ page }}{% endblock %}
{% block og_description %}Landing page for the wiki{% endblock %}
{% block extra_head %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js" type="application/javascript"></script>
{% endblock %}
{% block content %}
- <div class="uk-container uk-section">
- <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" />
- </div>
- <div class="uk-width-auto">
- <button class="uk-button uk-button-secondary" type="button" value="Preview" id="preview">Preview</button>
- </div>
- <div class="uk-width-auto">
- <button class="uk-button uk-button-primary" type="submit" value="Save">Save</button>
- </div>
- <div class="uk-width-1-1">
-{# <label for="rst">Document: </label>#}
- <div id="rst" class="uk-textarea" style="resize: vertical; min-height: 10rem;">{{ rst }}</div>
- </div>
-
- <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
- </form>
-
- <h2 class="uk-h2">Preview</h2>
-
- <div id="preview-div">
- <p>Preview will appear here.</p>
+ <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" />
</div>
+ <div class="uk-width-auto">
+ <button class="uk-button uk-button-secondary" type="button" value="Preview" id="preview">Preview</button>
+ </div>
+ <div class="uk-width-auto">
+ <input class="uk-button uk-button-primary" type="submit" value="Save" />
+ </div>
+ <div class="uk-width-1-1">
+ <div id="editor" class="uk-textarea" style="resize: vertical; min-height: 15rem;">{{ rst }}</div>
+ <input type="hidden" name="rst" id="rst" />
+ </div>
+
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
+ </form>
+
+ <h2>Preview</h2>
+
+ <div class="uk-alert preview-div" style="padding: 1rem 1rem 0.1rem;">
+ {{ preview | safe }}
</div>
<script type="application/javascript">
@@ -67,12 +65,18 @@
return false;
};
- let editor = ace.edit("rst");
+ let editor = ace.edit("editor");
let timer;
editor.session.setMode("ace/mode/rst");
+ editor.session.setUseWrapMode(true);
+
editor.setTheme("ace/theme/iplastic");
+ editor.setShowPrintMargin(false);
+
editor.on("input", function() {
+ document.getElementById("rst").value = editor.getValue();
+
if (timer !== undefined) {
clearTimeout(timer);
}
diff --git a/templates/wiki/page_missing.html b/templates/wiki/page_missing.html
deleted file mode 100644
index 5601cbbf..00000000
--- a/templates/wiki/page_missing.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "main/base.html" %}
-{% block title %}Wiki | Home{% endblock %}
-{% block og_title %}Wiki | Home{% endblock %}
-{% block og_description %}Landing page for the wiki{% endblock %}
-{% block content %}
- <div class="uk-container uk-section">
- <h1 class="uk-title uk-text-center">
- Placeholder text.
- </h1>
- </div>
-{% endblock %} \ No newline at end of file
diff --git a/templates/wiki/page_view.html b/templates/wiki/page_view.html
index 5601cbbf..9c49a09e 100644
--- a/templates/wiki/page_view.html
+++ b/templates/wiki/page_view.html
@@ -1,11 +1,13 @@
-{% extends "main/base.html" %}
-{% block title %}Wiki | Home{% endblock %}
-{% block og_title %}Wiki | Home{% endblock %}
-{% block og_description %}Landing page for the wiki{% endblock %}
+{% extends "wiki/base.html" %}
+{% block title %}Wiki | {{ data["title"] }}{% endblock %}
+{% block og_title %}Wiki | {{ data["title"] }}{% endblock %}
+{% block og_description %}{% endblock %}
{% block content %}
- <div class="uk-container uk-section">
- <h1 class="uk-title uk-text-center">
- Placeholder text.
- </h1>
+ <div class="uk-container uk-container-small">
+ <h2 class="uk-title">
+ {{ data["title"] }}
+ </h2>
+
+ {{ data["html"] | safe }}
</div>
{% endblock %} \ No newline at end of file