added highlight.js support

This commit is contained in:
Matthew Scragg 2013-10-08 14:47:49 -05:00
parent d30c9d71a6
commit 9d3751f241
11 changed files with 204 additions and 38 deletions

View file

@ -222,7 +222,7 @@ def create_app(subdomain=None):
cname = to_canonical(name) cname = to_canonical(name)
if request.method == 'POST': if request.method == 'POST':
edit_cname = to_canonical(request.form['name']) edit_cname = to_canonical(request.form['name'])
if edit_cname != cname: if edit_cname.lower() != cname.lower():
w.rename_page(cname, edit_cname) w.rename_page(cname, edit_cname)
w.write_page(edit_cname, request.form['content'], message=request.form['message'], w.write_page(edit_cname, request.form['content'], message=request.form['message'],
username=CurrentUser.get('username')) username=CurrentUser.get('username'))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,105 @@
/*global module:true*/
/*
* Basic table support with re-entrant parsing, where cell content
* can also specify markdown.
*
* Tables
* ======
*
* | Col 1 | Col 2 |
* |======== |====================================================|
* |**bold** | ![Valid XHTML] (http://w3.org/Icons/valid-xhtml10) |
* | Plain | Value |
*
*/
(function(){
var table = function(converter) {
var tables = {}, style = 'text-align:left;', filter;
tables.th = function(header){
if (header.trim() === "") { return "";}
var id = header.trim().replace(/ /g, '_').toLowerCase();
return '<th id="' + id + '" style="'+style+'">' + header + '</th>';
};
tables.td = function(cell) {
return '<td style="'+style+'">' + converter.makeHtml(cell) + '</td>';
};
tables.ths = function(){
var out = "", i = 0, hs = [].slice.apply(arguments);
for (i;i<hs.length;i+=1) {
out += tables.th(hs[i]) + '\n';
}
return out;
};
tables.tds = function(){
var out = "", i = 0, ds = [].slice.apply(arguments);
for (i;i<ds.length;i+=1) {
out += tables.td(ds[i]) + '\n';
}
return out;
};
tables.thead = function() {
var out, i = 0, hs = [].slice.apply(arguments);
out = "<thead>\n";
out += "<tr>\n";
out += tables.ths.apply(this, hs);
out += "</tr>\n";
out += "</thead>\n";
return out;
};
tables.tr = function() {
var out, i = 0, cs = [].slice.apply(arguments);
out = "<tr>\n";
out += tables.tds.apply(this, cs);
out += "</tr>\n";
return out;
};
filter = function(text) {
var i=0, lines = text.split('\n'), tbl = [], line, hs, rows, out = [];
for (i; i<lines.length;i+=1) {
line = lines[i];
// looks like a table heading
if (line.trim().match(/^[|]{1}.*[|]{1}$/)) {
line = line.trim();
tbl.push('<table>');
hs = line.substring(1, line.length -1).split('|');
tbl.push(tables.thead.apply(this, hs));
line = lines[++i];
if (!line.trim().match(/^[|]{1}[-=| ]+[|]{1}$/)) {
// not a table rolling back
line = lines[--i];
}
else {
line = lines[++i];
tbl.push('<tbody>');
while (line.trim().match(/^[|]{1}.*[|]{1}$/)) {
line = line.trim();
tbl.push(tables.tr.apply(this, line.substring(1, line.length -1).split('|')));
line = lines[++i];
}
tbl.push('</tbody>');
tbl.push('</table>');
// we are done with this table and we move along
out.push(tbl.join('\n'));
continue;
}
}
out.push(line);
}
return out.join('\n');
};
return [
{
type: 'lang',
filter: filter
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.table = table; }
// Server-side export
if (typeof module !== 'undefined') {
module.exports = table;
}
}());

View file

@ -3,7 +3,15 @@
* Copyright (c) 2010 Caolan McMahon * Copyright (c) 2010 Caolan McMahon
*/ */
function escapeHtml(s) {
s = ('' + s); /* Coerce to string */
s = s.replace(/&/g, '&amp;');
s = s.replace(/</g, '&lt;');
s = s.replace(/>/g, '&gt;');
s = s.replace(/"/g, '&quot;');
s = s.replace(/'/g, '&#39;');
return s;
}
/** /**
* Main function for converting markdown to HTML. * Main function for converting markdown to HTML.
@ -87,6 +95,34 @@ WMD.preprocessors = {
} }
doc.markdown = lines.join('\n'); doc.markdown = lines.join('\n');
return doc; return doc;
},
fencedCodeBlocksHighlightJS: function (doc) {
var re1 = /```([A-Za-z]+)\s*([\s\S]+?)```/; // with syntax highlighting
var re2 = /```\s*([\s\S]+?)```/; // without syntax highlighting
var block;
while (block = re1.exec(doc.markdown) || re2.exec(doc.markdown)) {
var pre;
if (block.length === 3) {
// we have a code format
pre = '<pre style="padding:0;"><code class="' + escapeHtml(block[1]) + '">';
if (block[1] in hljs.LANGUAGES) {
pre += hljs.highlight(block[1], block[2]).value;
}
else {
pre += escapeHtml(block[2]);
}
pre += '</code></pre>';
}
else {
// no syntax highlighting
pre = '<pre style="padding:0;"><code class="no-highlight">' +
escapeHtml(block[1]) + '</code></pre>';
}
doc.markdown = doc.markdown.substr(0, block.index) +
pre + doc.markdown.substr(block.index + block[0].length);
}
return doc;
} }
}; };
@ -104,9 +140,11 @@ WMD.readOptions = function (options) {
var obj = { var obj = {
preprocessors: [ preprocessors: [
WMD.preprocessors.metadata, WMD.preprocessors.metadata,
WMD.preprocessors.underscores WMD.preprocessors.underscores,
WMD.preprocessors.fencedCodeBlocksHighlightJS
], ],
postprocessors: [] postprocessors: [
]
}; };
for (var k in options) { for (var k in options) {
obj[k] = options[k]; obj[k] = options[k];

View file

@ -10,6 +10,7 @@
<link href="/static/css/bootstrap/spacelab.css" rel="stylesheet"> <link href="/static/css/bootstrap/spacelab.css" rel="stylesheet">
<link href="/static/css/font-awesome.min.css" rel="stylesheet"> <link href="/static/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/js/highlight/styles/github.css" rel="stylesheet">
<link href="/static/css/style.css" rel="stylesheet"> <link href="/static/css/style.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
@ -106,6 +107,7 @@
<script src="/static/js/showdown/showdown.js"></script> <script src="/static/js/showdown/showdown.js"></script>
<script src="/static/js/html-sanitizer-minified.js"></script> <script src="/static/js/html-sanitizer-minified.js"></script>
<script src="/static/js/showdown/wmd.js"></script> <script src="/static/js/showdown/wmd.js"></script>
<script src="/static/js/highlight/highlight.pack.js"></script>
<script> <script>
/* /*
marked.setOptions({ marked.setOptions({
@ -118,6 +120,11 @@
langPrefix: 'lang-' langPrefix: 'lang-'
}); });
*/ */
hljs.initHighlightingOnLoad();
// Markdown Renderer
MDR = { MDR = {
doc: null, doc: null,
callback: WMD, callback: WMD,

View file

@ -65,22 +65,4 @@
<div id="preview"></div> <div id="preview"></div>
</div> </div>
<!--
<form role="form" method="post">
<div class="form-group">
<label for="name"></label>
<input type="text" class="form-control" id="page" name="name" placeholder="Name" value="{{- name -}}" />
</div>
<div class="form-group">
<label for="content"></label>
<div id="epiceditor"></div>
<textarea name="content" id="content" class="form-control" placeholder="Content" style="display:none;">{{- content -}}</textarea>
</div>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
-->
{% endblock %} {% endblock %}

View file

@ -9,7 +9,7 @@
{% block body %} {% block body %}
<div id="page-content" style="display:none"> <div id="page-content" style="display:none">
{{ page.data|safe }} {{ page.data }}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -3,6 +3,37 @@ import os
import hashlib import hashlib
def escape_repl(m):
print "group 0"
print m.group(0)
print "group 1"
print m.group(1)
if m.group(1):
return "```" + escape_html(m.group(1)) + "```"
def unescape_repl(m):
if m.group(1):
return "```" + unescape_html(m.group(1)) + "```"
def escape_html(s):
s = s.replace("&", '&amp;')
s = s.replace("<", '&lt;')
s = s.replace(">", '&gt;')
s = s.replace('"', '&quot;')
s = s.replace("'", '&#39;')
return s
def unescape_html(s):
s = s.replace('&amp;', "&")
s = s.replace('&lt;', "<")
s = s.replace('&gt;', ">")
s = s.replace('&quot;', '"')
s = s.replace('&#39;', "'")
return s
def mkdir_safe(path): def mkdir_safe(path):
if path and not(os.path.exists(path)): if path and not(os.path.exists(path)):
os.makedirs(path) os.makedirs(path)

View file

@ -1,12 +1,13 @@
import os import os
import re import re
from lxml.html.clean import clean_html import lxml.html
from lxml.html import clean
import ghdiff import ghdiff
from gittle import Gittle from gittle import Gittle
from dulwich.repo import NotGitRepository from dulwich.repo import NotGitRepository
from util import to_canonical from util import to_canonical, escape_repl, unescape_repl
from models import Site from models import Site
@ -68,11 +69,23 @@ class Wiki():
return True if s.get_by_name(name) else False return True if s.get_by_name(name) else False
def write_page(self, name, content, message=None, create=False, username=None, email=None): def write_page(self, name, content, message=None, create=False, username=None, email=None):
# adding the div wrapper apparently fixes anomalies with the lxml parser with certain markdown
content = clean_html('<div>' + content + '</div>') # prevents p tag from being added, we remove this later
content = '<div>' + content + '</div>'
content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL)
tree = lxml.html.fromstring(content)
cleaner = clean.Cleaner(remove_unknown_tags=False)
tree = cleaner.clean_html(tree)
content = lxml.html.tostring(tree, encoding='utf-8', method='html')
# post processing to fix errors
content = content[5:-6] content = content[5:-6]
content = re.sub(r"(\n&gt;)", "\n>", content) content = re.sub(r"(\n&gt;)", "\n>", content)
content = re.sub(r"(^&gt;)", ">", content) content = re.sub(r"(^&gt;)", ">", content)
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
filename = self.cname_to_filename(to_canonical(name)) filename = self.cname_to_filename(to_canonical(name))
f = open(self.path + "/" + filename, 'w') f = open(self.path + "/" + filename, 'w')

View file

@ -6,7 +6,7 @@ python-pkgs:
- build-essential - build-essential
{% for pkg in ['ghdiff', 'tornado', 'pyzmq', 'itsdangerous', 'boto', 'redis', 'simplejson', 'sockjs-tornado', 'flask', 'flask-bcrypt', 'flask-login', 'flask-assets', 'gittle', 'gevent', 'lxml', 'markdown2', 'recaptcha-client', 'RethinkORM' ] %} {% for pkg in ['BeautifulSoup', 'html5lib', 'ghdiff', 'tornado', 'pyzmq', 'itsdangerous', 'boto', 'redis', 'simplejson', 'sockjs-tornado', 'flask', 'flask-bcrypt', 'flask-login', 'flask-assets', 'gittle', 'gevent', 'lxml', 'markdown2', 'recaptcha-client', 'RethinkORM' ] %}
{{ pkg }}-pip: {{ pkg }}-pip:
pip: pip:
- name: {{ pkg }} - name: {{ pkg }}