This commit is contained in:
Matthew Scragg 2013-10-09 16:35:06 -05:00
parent 9d3751f241
commit 3cc69ce06f
24 changed files with 698 additions and 8837 deletions

3
.bowerrc Normal file
View file

@ -0,0 +1,3 @@
{
"directory": "realms/static/vendor"
}

5
.gitignore vendored
View file

@ -1,5 +1,8 @@
.vagrant .vagrant
.idea .idea
.webassets-cache
*.pyc *.pyc
packed-*.js
config.py config.py
config.sls config.sls
realms/static/vendor

16
bower.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "realms",
"version": "0.1.1",
"dependencies": {
"components-bootstrap": "3.0.0",
"jquery": "1.9.1",
"components-underscore": "~1.5.1",
"requirejs": "~2.1.8",
"highlightjs": "~7.3.0",
"handlebars": "~1.0.0",
"components-font-awesome": "~3.2.1",
"showdown": "~0.3.1",
"keymaster": "madrobby/keymaster",
"ace": "~1.0.0"
}
}

View file

@ -6,7 +6,7 @@ from threading import Lock
import rethinkdb as rdb import rethinkdb as rdb
from flask import Flask, request, render_template, url_for, redirect, flash from flask import Flask, request, render_template, url_for, redirect, flash
from flask.ext.login import LoginManager, login_required from flask.ext.login import LoginManager, login_required
from flask.ext.assets import Environment from flask.ext.assets import Environment, Bundle
from recaptcha.client import captcha from recaptcha.client import captcha
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter
@ -18,10 +18,18 @@ from models import Site, User, CurrentUser
from ratelimit import get_view_rate_limit, ratelimiter from ratelimit import get_view_rate_limit, ratelimiter
from services import db from services import db
# Flask instance container
instances = {} instances = {}
# Flask extension objects
login_manager = LoginManager()
assets = Environment()
class SubdomainDispatcher(object): class SubdomainDispatcher(object):
"""
Application factory
"""
def __init__(self, domain, create_app): def __init__(self, domain, create_app):
self.domain = domain self.domain = domain
self.create_app = create_app self.create_app = create_app
@ -44,6 +52,9 @@ class SubdomainDispatcher(object):
def init_db(dbname): def init_db(dbname):
"""
Assures DB has minimal setup
"""
if not dbname in rdb.db_list().run(db): if not dbname in rdb.db_list().run(db):
print "Creating DB %s" % dbname print "Creating DB %s" % dbname
rdb.db_create(dbname).run(db) rdb.db_create(dbname).run(db)
@ -65,6 +76,9 @@ def init_db(dbname):
class RegexConverter(BaseConverter): class RegexConverter(BaseConverter):
"""
Enables Regex matching on endpoints
"""
def __init__(self, url_map, *items): def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map) super(RegexConverter, self).__init__(url_map)
self.regex = items[0] self.regex = items[0]
@ -85,7 +99,17 @@ def validate_captcha():
return response.is_valid return response.is_valid
def format_subdomain(s):
s = s.lower()
s = to_canonical(s)
if s in ['www']:
# Not allowed
s = ""
return s
def make_app(subdomain): def make_app(subdomain):
subdomain = format_subdomain(subdomain)
if subdomain and not Wiki.is_registered(subdomain): if subdomain and not Wiki.is_registered(subdomain):
return redirect("http://%s/_new/?site=%s" % (config.hostname, subdomain)) return redirect("http://%s/_new/?site=%s" % (config.hostname, subdomain))
return create_app(subdomain) return create_app(subdomain)
@ -100,7 +124,6 @@ def create_app(subdomain=None):
app.session_interface = RedisSessionInterface() app.session_interface = RedisSessionInterface()
app.url_map.converters['regex'] = RegexConverter app.url_map.converters['regex'] = RegexConverter
login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
login_manager.login_view = 'login' login_manager.login_view = 'login'
@ -108,9 +131,26 @@ def create_app(subdomain=None):
def load_user(user_id): def load_user(user_id):
return CurrentUser(user_id) return CurrentUser(user_id)
assets = Environment(app) assets.init_app(app)
assets.url = app.static_url_path if 'js_common' not in assets._named_bundles:
assets.directory = app.static_folder js = Bundle('vendor/jquery/jquery.js',
'vendor/components-underscore/underscore.js',
'vendor/components-bootstrap/js/bootstrap.js',
'vendor/handlebars/handlebars.js',
'vendor/showdown/src/showdown.js',
'js/html-sanitizer-minified.js',
'js/wmd.js',
'vendor/highlightjs/highlight.pack.js',
filters='uglifyjs', output='packed-common.js')
assets.register('js_common', js)
if 'js_editor' not in assets._named_bundles:
js = Bundle('js/ace/ace.js',
'js/ace/mode-markdown.js',
'vendor/keymaster/keymaster.js',
'js/dillinger.js',
filters='uglifyjs', output='packed-editor.js')
assets.register('js_editor', js)
repo_dir = config.repos['dir'] repo_dir = config.repos['dir']
repo_name = subdomain if subdomain else "_" repo_name = subdomain if subdomain else "_"

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,7 +0,0 @@
/*!
Autosize v1.17.8 - 2013-09-07
Automatically adjust textarea height based on user input.
(c) 2013 Jack Moore - http://www.jacklmoore.com/autosize
license: http://www.opensource.org/licenses/mit-license.php
*/
(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(window.jQuery||window.$)})(function(e){var t,o={className:"autosizejs",append:"",callback:!1,resizeDelay:10},i='<textarea tabindex="-1" style="position:absolute; top:-999px; left:0; right:auto; bottom:auto; border:0; padding: 0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden; transition:none; -webkit-transition:none; -moz-transition:none;"/>',n=["fontFamily","fontSize","fontWeight","fontStyle","letterSpacing","textTransform","wordSpacing","textIndent"],s=e(i).data("autosize",!0)[0];s.style.lineHeight="99px","99px"===e(s).css("lineHeight")&&n.push("lineHeight"),s.style.lineHeight="",e.fn.autosize=function(i){return this.length?(i=e.extend({},o,i||{}),s.parentNode!==document.body&&e(document.body).append(s),this.each(function(){function o(){var t,o;"getComputedStyle"in window?(t=window.getComputedStyle(u),o=u.getBoundingClientRect().width,e.each(["paddingLeft","paddingRight","borderLeftWidth","borderRightWidth"],function(e,i){o-=parseInt(t[i],10)}),s.style.width=o+"px"):s.style.width=Math.max(p.width(),0)+"px"}function a(){var a={};if(t=u,s.className=i.className,d=parseInt(p.css("maxHeight"),10),e.each(n,function(e,t){a[t]=p.css(t)}),e(s).css(a),o(),window.chrome){var r=u.style.width;u.style.width="0px",u.offsetWidth,u.style.width=r}}function r(){var e,n;t!==u?a():o(),s.value=u.value+i.append,s.style.overflowY=u.style.overflowY,n=parseInt(u.style.height,10),s.scrollTop=0,s.scrollTop=9e4,e=s.scrollTop,d&&e>d?(u.style.overflowY="scroll",e=d):(u.style.overflowY="hidden",c>e&&(e=c)),e+=f,n!==e&&(u.style.height=e+"px",w&&i.callback.call(u,u))}function l(){clearTimeout(h),h=setTimeout(function(){var e=p.width();e!==g&&(g=e,r())},parseInt(i.resizeDelay,10))}var d,c,h,u=this,p=e(u),f=0,w=e.isFunction(i.callback),z={height:u.style.height,overflow:u.style.overflow,overflowY:u.style.overflowY,wordWrap:u.style.wordWrap,resize:u.style.resize},g=p.width();p.data("autosize")||(p.data("autosize",!0),("border-box"===p.css("box-sizing")||"border-box"===p.css("-moz-box-sizing")||"border-box"===p.css("-webkit-box-sizing"))&&(f=p.outerHeight()-p.height()),c=Math.max(parseInt(p.css("minHeight"),10)-f||0,p.height()),p.css({overflow:"hidden",overflowY:"hidden",wordWrap:"break-word",resize:"none"===p.css("resize")||"vertical"===p.css("resize")?"none":"horizontal"}),"onpropertychange"in u?"oninput"in u?p.on("input.autosize keyup.autosize",r):p.on("propertychange.autosize",function(){"value"===event.propertyName&&r()}):p.on("input.autosize",r),i.resizeDelay!==!1&&e(window).on("resize.autosize",l),p.on("autosize.resize",r),p.on("autosize.resizeIncludeStyle",function(){t=null,r()}),p.on("autosize.destroy",function(){t=null,clearTimeout(h),e(window).off("resize",l),p.off("autosize").off(".autosize").css(z).removeData("autosize")}),r())})):this}});

View file

@ -1,4 +0,0 @@
// keymaster.js
// (c) 2011 Thomas Fuchs
// keymaster.js may be freely distributed under the MIT license.
(function(a){function m(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,function(){c(window.event)})}function l(a){e=a||"all"}function k(a,b,d){var e,h,i,j;d===undefined&&(d=b,b="all"),a=a.replace(/\s/g,""),e=a.split(","),e[e.length-1]==""&&(e[e.length-2]+=",");for(i=0;i<e.length;i++){h=[],a=e[i].split("+");if(a.length>1){h=a.slice(0,a.length-1);for(j=0;j<h.length;j++)h[j]=f[h[j]];a=[a[a.length-1]]}a=a[0],a=g[a]||a.toUpperCase().charCodeAt(0),a in c||(c[a]=[]),c[a].push({shortcut:e[i],scope:b,method:d,key:e[i],mods:h})}}function j(a){var b=a.keyCode,c;if(b==93||b==224)b=91;if(b in d){d[b]=!1;for(c in f)f[c]==b&&(k[c]=!1)}}function i(a){var b,g,i,j,l,m;g=(a.target||a.srcElement).tagName,b=a.keyCode;if(b==93||b==224)b=91;if(b in d){d[b]=!0;for(j in f)f[j]==b&&(k[j]=!0)}else{if(g=="INPUT"||g=="SELECT"||g=="TEXTAREA")return;if(!(b in c))return;for(l=0;l<c[b].length;l++){i=c[b][l];if(i.scope==e||i.scope=="all"){m=i.mods.length>0;for(j in d)if(!d[j]&&h(i.mods,+j)>-1||d[j]&&h(i.mods,+j)==-1)m=!1;(i.mods.length==0&&!d[16]&&!d[18]&&!d[17]&&!d[91]||m)&&i.method(a,i)===!1&&(a.preventDefault?a.preventDefault():a.returnValue=!1,a.stopPropagation&&a.stopPropagation(),a.cancelBubble&&(a.cancelBubble=!0))}}}}function h(a,b){var c=a.length;while(c--)if(a[c]===b)return c;return-1}var b,c={},d={16:!1,18:!1,17:!1,91:!1},e="all",f={"⇧":16,shift:16,"⌥":18,alt:18,option:18,"⌃":17,ctrl:17,control:17,"⌘":91,command:91},g={backspace:8,tab:9,clear:12,enter:13,"return":13,esc:27,escape:27,space:32,left:37,up:38,right:39,down:40,del:46,"delete":46,home:36,end:35,pageup:33,pagedown:34,",":188,".":190,"/":191,"`":192,"-":189,"=":187,";":186,"'":222,"[":219,"]":221,"\\":220};for(b=1;b<20;b++)f["f"+b]=111+b;for(b in f)k[b]=!1;m(document,"keydown",i),m(document,"keyup",j),a.key=k,a.key.setScope=l,typeof module!="undefined"&&(module.exports=key)})(this)

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
//
// Github Extension (WIP)
// ~~strike-through~~ -> <del>strike-through</del>
//
(function(){var a=function(a){return[{type:"lang",regex:"(~T){2}([^~]+)(~T){2}",replace:function(a,b,c,d){return"<del>"+c+"</del>"}}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.github=a),typeof module!="undefined"&&(module.exports=a)})();

View file

@ -1,6 +0,0 @@
//
// Google Prettify
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
// hints to showdown's HTML output.
//
(function(){var a=function(a){return[{type:"output",filter:function(a){return a.replace(/(<pre>)?<code>/gi,function(a,b){return b?'<pre class="prettyprint linenums" tabIndex="0"><code data-inner="1">':'<code class="prettyprint">'})}}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.googlePrettify=a),typeof module!="undefined"&&(module.exports=a)})();

View file

@ -1,105 +0,0 @@
/*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

@ -1,6 +0,0 @@
//
// Twitter Extension
// @username -> <a href="http://twitter.com/username">@username</a>
// #hashtag -> <a href="http://twitter.com/search/%23hashtag">#hashtag</a>
//
(function(){var a=function(a){return[{type:"lang",regex:"\\B(\\\\)?@([\\S]+)\\b",replace:function(a,b,c){return b==="\\"?a:'<a href="http://twitter.com/'+c+'">@'+c+"</a>"}},{type:"lang",regex:"\\B(\\\\)?#([\\S]+)\\b",replace:function(a,b,c){return b==="\\"?a:'<a href="http://twitter.com/search/%23'+c+'">#'+c+"</a>"}},{type:"lang",regex:"\\\\@",replace:"@"}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.twitter=a),typeof module!="undefined"&&(module.exports=a)})();

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,10 @@
* Copyright (c) 2010 Caolan McMahon * Copyright (c) 2010 Caolan McMahon
*/ */
/*
* Fork of https://github.com/caolan/wmd
*/
function escapeHtml(s) { function escapeHtml(s) {
s = ('' + s); /* Coerce to string */ s = ('' + s); /* Coerce to string */
s = s.replace(/&/g, '&amp;'); s = s.replace(/&/g, '&amp;');
@ -22,7 +26,9 @@ function escapeHtml(s) {
* @api public * @api public
*/ */
var WMD = function (content, options) { var WMD = {};
WMD.convert = function(content, options) {
var doc = {raw: content, markdown: content}; var doc = {raw: content, markdown: content};
var opt = WMD.readOptions(options); var opt = WMD.readOptions(options);
WMD.preprocess(doc, opt); WMD.preprocess(doc, opt);

View file

@ -10,8 +10,9 @@
<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/vendor/highlightjs/styles/github.css" rel="stylesheet">
<link href="/static/css/style.css" rel="stylesheet"> <link href="/static/css/style.css" rel="stylesheet">
{% block css %}{% endblock %}
<!-- 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 -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
@ -99,53 +100,35 @@
</div> </div>
</div> </div>
<script src="/static/js/jquery-1.10.2.min.js"></script> {% assets "js_common" %}
<script src="/static/js/underscore.js"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
<script src="/static/js/bootstrap.min.js"></script> {% endassets %}
<!--<script src="/static/js/marked.js"></script>-->
<script src="/static/js/handlebars.js"></script>
<script src="/static/js/showdown/showdown.js"></script>
<script src="/static/js/html-sanitizer-minified.js"></script>
<script src="/static/js/showdown/wmd.js"></script>
<script src="/static/js/highlight/highlight.pack.js"></script>
<script> <script>
/*
marked.setOptions({
gfm: true,
tables: true,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
langPrefix: 'lang-'
});
*/
hljs.initHighlightingOnLoad(); hljs.initHighlightingOnLoad();
// Markdown Renderer // Markdown Renderer
MDR = { MDR = {
doc: null, doc: null,
callback: WMD, callback: WMD.convert,
convert: function(md, sanitize){ convert: function(md, sanitize){
this.doc = this.callback(md); this.doc = this.callback(md);
md = this.doc.html; var html = this.doc.html;
if (sanitize) { if (sanitize) {
md = html_sanitize(md); // Causes some problems with inline styles
html = html_sanitize(html);
} }
md = this.hook(md); html = this.hook(html);
return md; return html;
}, },
hook: function(md) { hook: function(html) {
if (!this.doc.metadata) { if (!this.doc.metadata) {
return md; return html;
} }
try { try {
var template = Handlebars.compile(md); var template = Handlebars.compile(html);
return template(this.doc.metadata); return template(this.doc.metadata);
} catch(e) { } catch(e) {
return md; return html;
} }
} }
}; };

View file

@ -1,21 +1,23 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block js %} {% block js %}
<script src="/static/js/ace/ace.js"></script> {% assets "js_editor" %}
<script src="/static/js/ace/mode-markdown.js"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
<script src="/static/js/keymaster.min.js"></script> {% endassets %}
<script src="/static/js/dillinger.js"></script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% block css %}
<style> <style>
#main-body { #main-body {
background: inherit; background: inherit;
border: inherit; border: inherit;
padding: 0; padding: 0;
-webkit-box-shadow: 0; -webkit-box-shadow: 0;
-moz-box-shadow: 0; -moz-box-shadow: 0;
box-shadow: 0; box-shadow: 0;
} }
</style> </style>
{% endblock %}
<div id="app-wrap" class="container-fluid"> <div id="app-wrap" class="container-fluid">
<div id="app-controls" class="row"> <div id="app-controls" class="row">
@ -37,7 +39,7 @@
<li><a tabindex="-1" href="#" data-value="ace/theme/cobalt" class="">Cobalt</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/cobalt" class="">Cobalt</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/crimson_editor" class="">Crimson Editor</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/crimson_editor" class="">Crimson Editor</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/dawn" class="selected">Dawn</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/dawn" class="selected">Dawn</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/dawn" class="">Dreamweaver</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/dreamweaver" class="">Dreamweaver</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/eclipse" class="">Eclipse</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/eclipse" class="">Eclipse</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/idle_fingers" class="">idleFingers</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/idle_fingers" class="">idleFingers</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/kr_theme" class="">krTheme</a></li> <li><a tabindex="-1" href="#" data-value="ace/theme/kr_theme" class="">krTheme</a></li>

View file

@ -34,6 +34,7 @@ def unescape_html(s):
s = s.replace('&#39;', "'") s = s.replace('&#39;', "'")
return s 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

@ -6,6 +6,4 @@ nginx:
- enable: True - enable: True
- reload: True - reload: True
- require: - require:
- pkg: nginx - pkg: nginx
/etc/

9
srv/salt/nodejs/init.sls Normal file
View file

@ -0,0 +1,9 @@
node-repos:
pkgrepo.managed:
- ppa: chris-lea/node.js
nodejs:
pkg:
- installed
- require:
- pkgrepo.managed: node-repos