diff --git a/requirements.txt b/requirements.txt
index f4bb3fc..e95c9bb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,5 @@ redis
bcrypt
pillow
requests
+Markdown
+Pygments
\ No newline at end of file
diff --git a/rophako/__init__.py b/rophako/__init__.py
index 55fa81b..8e6ee35 100644
--- a/rophako/__init__.py
+++ b/rophako/__init__.py
@@ -120,10 +120,21 @@ def catchall(path):
abspath = os.path.abspath("{}/{}".format(root, path))
if os.path.isfile(abspath):
return send_file(abspath)
- elif not "." in path and os.path.isfile(abspath + ".html"):
- return rophako.utils.template(path + ".html")
- elif not "." in path and os.path.isfile(abspath + "/index.html"):
- return rophako.utils.template(path + "/index.html")
+
+ # The exact file wasn't found, look for some extensions and index pages.
+ suffixes = [
+ ".html",
+ "/index.html",
+ ".md", # Markdown formatted pages.
+ "/index.md",
+ ]
+ for suffix in suffixes:
+ if not "." in path and os.path.isfile(abspath + suffix):
+ # HTML, or Markdown?
+ if suffix.endswith(".html"):
+ return rophako.utils.template(path + suffix)
+ else:
+ return rophako.utils.markdown_template(abspath + suffix)
return not_found("404")
diff --git a/rophako/model/blog.py b/rophako/model/blog.py
index adf6a6b..e7534cc 100644
--- a/rophako/model/blog.py
+++ b/rophako/model/blog.py
@@ -73,11 +73,15 @@ def get_entry(post_id):
if len(db["fid"]) == 0:
db["fid"] = str(post_id)
+ # If no "format" option, set it to HTML (legacy)
+ if db.get("format", "") == "":
+ db["format"] = "html"
+
return db
def post_entry(post_id, fid, epoch, author, subject, avatar, categories,
- privacy, ip, emoticons, comments, body):
+ privacy, ip, emoticons, comments, format, body):
"""Post (or update) a blog entry."""
# Fetch the index.
@@ -139,6 +143,7 @@ def post_entry(post_id, fid, epoch, author, subject, avatar, categories,
privacy = privacy or "public",
author = author,
subject = subject,
+ format = format,
body = body,
))
diff --git a/rophako/model/comment.py b/rophako/model/comment.py
index 148cddd..de7124e 100644
--- a/rophako/model/comment.py
+++ b/rophako/model/comment.py
@@ -13,7 +13,7 @@ import config
import rophako.jsondb as JsonDB
import rophako.model.user as User
import rophako.model.emoticons as Emoticons
-from rophako.utils import send_email
+from rophako.utils import send_email, render_markdown
from rophako.log import logger
@@ -162,15 +162,12 @@ def unsubscribe(thread, email):
def format_message(message):
"""HTML sanitize the message and format it for display."""
- # We basically want to escape HTML symbols (like what Flask does for us
- # automatically), but we want line breaks to translate to literal
tags.
- message = re.sub(r'&', '&', message)
- message = re.sub(r'<', '<', message)
- message = re.sub(r'>', '>', message)
- message = re.sub(r'"', '"', message)
- message = re.sub(r"'", ''', message)
- message = re.sub(r'\n', '
', message)
- message = re.sub(r'\r', '', message)
+
+ # Comments use Markdown formatting, and HTML tags are escaped by default.
+ message = render_markdown(message)
+
+ # Don't allow commenters to use images.
+ message = re.sub(r'', '', message)
# Process emoticons.
message = Emoticons.render(message)
diff --git a/rophako/modules/blog.py b/rophako/modules/blog.py
index 6a7f8cf..59e2d62 100644
--- a/rophako/modules/blog.py
+++ b/rophako/modules/blog.py
@@ -13,7 +13,7 @@ import rophako.model.user as User
import rophako.model.blog as Blog
import rophako.model.comment as Comment
import rophako.model.emoticons as Emoticons
-from rophako.utils import template, pretty_time, login_required
+from rophako.utils import template, render_markdown, pretty_time, login_required
from rophako.log import logger
from config import *
@@ -44,9 +44,15 @@ def entry(fid):
post = Blog.get_entry(post_id)
post["post_id"] = post_id
+ # Render the body.
+ if post["format"] == "markdown":
+ post["rendered_body"] = render_markdown(post["body"])
+ else:
+ post["rendered_body"] = post["body"]
+
# Render emoticons.
if post["emoticons"]:
- post["body"] = Emoticons.render(post["body"])
+ post["rendered_body"] = Emoticons.render(post["rendered_body"])
# Get the author's information.
post["profile"] = User.get_user(uid=post["author"])
@@ -85,6 +91,7 @@ def update():
author=g.info["session"]["uid"],
subject="",
body="",
+ format="markdown",
avatar="",
categories="",
privacy=BLOG_DEFAULT_PRIVACY,
@@ -110,7 +117,7 @@ def update():
g.info["post"] = post
# Copy fields.
- for field in ["author", "fid", "subject", "body", "avatar",
+ for field in ["author", "fid", "subject", "format", "body", "avatar",
"categories", "privacy", "emoticons", "comments"]:
g.info[field] = post[field]
@@ -141,6 +148,17 @@ def update():
# What action are they doing?
if action == "preview":
g.info["preview"] = True
+
+ # Render markdown?
+ if g.info["format"] == "markdown":
+ g.info["rendered_body"] = render_markdown(g.info["body"])
+ else:
+ g.info["rendered_body"] = g.info["body"]
+
+ # Render emoticons.
+ if g.info["emoticons"]:
+ g.info["rendered_body"] = Emoticons.render(g.info["rendered_body"])
+
elif action == "publish":
# Publishing! Validate inputs first.
invalid = False
@@ -188,6 +206,7 @@ def update():
ip = request.remote_addr,
emoticons = g.info["emoticons"],
comments = g.info["comments"],
+ format = g.info["format"],
body = g.info["body"],
)
@@ -365,9 +384,15 @@ def partial_index():
post["post_id"] = post_id
+ # Render the body.
+ if post["format"] == "markdown":
+ post["rendered_body"] = render_markdown(post["body"])
+ else:
+ post["rendered_body"] = post["body"]
+
# Render emoticons.
if post["emoticons"]:
- post["body"] = Emoticons.render(post["body"])
+ post["rendered_body"] = Emoticons.render(post["rendered_body"])
# Get the author's information.
post["profile"] = User.get_user(uid=post["author"])
diff --git a/rophako/utils.py b/rophako/utils.py
index 1c03e2c..22ebfee 100644
--- a/rophako/utils.py
+++ b/rophako/utils.py
@@ -2,12 +2,14 @@
from flask import g, session, request, render_template, flash, redirect, url_for
from functools import wraps
+import codecs
import uuid
import datetime
import time
import re
import importlib
import smtplib
+import markdown
from rophako.log import logger
from config import *
@@ -54,6 +56,54 @@ def template(name, **kwargs):
return html
+def markdown_template(path):
+ """Render a Markdown page to the browser."""
+
+ # The path is the absolute path to the Markdown file, so open it directly.
+ fh = codecs.open(path, "r", "utf-8")
+ body = fh.read()
+ fh.close()
+
+ # Extract a title from the first line.
+ first = body.split("\n")[0]
+ if first.startswith("#"):
+ first = first[1:].strip()
+
+ rendered = render_markdown(body)
+ return template("markdown.inc.html",
+ title=first,
+ markdown=rendered,
+ )
+
+
+def render_markdown(body, html_escape=True):
+ """Render a block of Markdown text.
+
+ This will default to escaping literal HTML characters. Set
+ `html_escape=False` to trust HTML."""
+
+ args = dict(
+ lazy_ol=False, # If a numbered list starts at e.g. 4, show the
there
+ extensions=[
+ "fenced_code", # GitHub style code blocks
+ "tables", # http://michelf.ca/projects/php-markdown/extra/#table
+ "smart_strong", # Handles double__underscore better.
+ "codehilite", # Code highlighting with Pygment!
+ "nl2br", # Line breaks inside a paragraph become
+ "sane_lists", # Make lists less surprising
+ ],
+ extension_configs={
+ "codehilite": {
+ "linenums": False,
+ }
+ }
+ )
+ if html_escape:
+ args["safe_mode"] = "escape"
+
+ return markdown.markdown(body, **args)
+
+
def send_email(to, subject, message, sender=None):
"""Send an e-mail out."""
if sender is None:
diff --git a/rophako/www/blog/entry.inc.html b/rophako/www/blog/entry.inc.html
index 3de96d4..3778b38 100644
--- a/rophako/www/blog/entry.inc.html
+++ b/rophako/www/blog/entry.inc.html
@@ -26,7 +26,7 @@
on {{ post["pretty_time"] }}
- {{ post["body"] | safe }}
+ {{ post["rendered_body"] | safe }}
{% endfor %}
+
Add a Comment