@@ -0,0 +1,3 @@ | |||
{ | |||
"directory": "realms/static/vendor" | |||
} |
@@ -1,5 +1,8 @@ | |||
.vagrant | |||
.idea | |||
.webassets-cache | |||
*.pyc | |||
packed-*.js | |||
config.py | |||
config.sls | |||
config.sls | |||
realms/static/vendor |
@@ -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" | |||
} | |||
} |
@@ -6,7 +6,7 @@ from threading import Lock | |||
import rethinkdb as rdb | |||
from flask import Flask, request, render_template, url_for, redirect, flash | |||
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 werkzeug.routing import BaseConverter | |||
@@ -18,10 +18,18 @@ from models import Site, User, CurrentUser | |||
from ratelimit import get_view_rate_limit, ratelimiter | |||
from services import db | |||
# Flask instance container | |||
instances = {} | |||
# Flask extension objects | |||
login_manager = LoginManager() | |||
assets = Environment() | |||
class SubdomainDispatcher(object): | |||
""" | |||
Application factory | |||
""" | |||
def __init__(self, domain, create_app): | |||
self.domain = domain | |||
self.create_app = create_app | |||
@@ -44,6 +52,9 @@ class SubdomainDispatcher(object): | |||
def init_db(dbname): | |||
""" | |||
Assures DB has minimal setup | |||
""" | |||
if not dbname in rdb.db_list().run(db): | |||
print "Creating DB %s" % dbname | |||
rdb.db_create(dbname).run(db) | |||
@@ -65,6 +76,9 @@ def init_db(dbname): | |||
class RegexConverter(BaseConverter): | |||
""" | |||
Enables Regex matching on endpoints | |||
""" | |||
def __init__(self, url_map, *items): | |||
super(RegexConverter, self).__init__(url_map) | |||
self.regex = items[0] | |||
@@ -85,7 +99,17 @@ def validate_captcha(): | |||
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): | |||
subdomain = format_subdomain(subdomain) | |||
if subdomain and not Wiki.is_registered(subdomain): | |||
return redirect("http://%s/_new/?site=%s" % (config.hostname, subdomain)) | |||
return create_app(subdomain) | |||
@@ -100,7 +124,6 @@ def create_app(subdomain=None): | |||
app.session_interface = RedisSessionInterface() | |||
app.url_map.converters['regex'] = RegexConverter | |||
login_manager = LoginManager() | |||
login_manager.init_app(app) | |||
login_manager.login_view = 'login' | |||
@@ -108,9 +131,26 @@ def create_app(subdomain=None): | |||
def load_user(user_id): | |||
return CurrentUser(user_id) | |||
assets = Environment(app) | |||
assets.url = app.static_url_path | |||
assets.directory = app.static_folder | |||
assets.init_app(app) | |||
if 'js_common' not in assets._named_bundles: | |||
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_name = subdomain if subdomain else "_" | |||
@@ -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}}); |
@@ -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) |
@@ -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)})(); |
@@ -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)})(); |
@@ -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; | |||
} | |||
}()); |
@@ -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)})(); |
@@ -3,6 +3,10 @@ | |||
* Copyright (c) 2010 Caolan McMahon | |||
*/ | |||
/* | |||
* Fork of https://github.com/caolan/wmd | |||
*/ | |||
function escapeHtml(s) { | |||
s = ('' + s); /* Coerce to string */ | |||
s = s.replace(/&/g, '&'); | |||
@@ -22,7 +26,9 @@ function escapeHtml(s) { | |||
* @api public | |||
*/ | |||
var WMD = function (content, options) { | |||
var WMD = {}; | |||
WMD.convert = function(content, options) { | |||
var doc = {raw: content, markdown: content}; | |||
var opt = WMD.readOptions(options); | |||
WMD.preprocess(doc, opt); |
@@ -10,8 +10,9 @@ | |||
<link href="/static/css/bootstrap/spacelab.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"> | |||
{% block css %}{% endblock %} | |||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> | |||
<!--[if lt IE 9]> | |||
@@ -99,53 +100,35 @@ | |||
</div> | |||
</div> | |||
<script src="/static/js/jquery-1.10.2.min.js"></script> | |||
<script src="/static/js/underscore.js"></script> | |||
<script src="/static/js/bootstrap.min.js"></script> | |||
<!--<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> | |||
{% assets "js_common" %} | |||
<script type="text/javascript" src="{{ ASSET_URL }}"></script> | |||
{% endassets %} | |||
<script> | |||
/* | |||
marked.setOptions({ | |||
gfm: true, | |||
tables: true, | |||
pedantic: false, | |||
sanitize: false, | |||
smartLists: true, | |||
smartypants: false, | |||
langPrefix: 'lang-' | |||
}); | |||
*/ | |||
hljs.initHighlightingOnLoad(); | |||
// Markdown Renderer | |||
MDR = { | |||
doc: null, | |||
callback: WMD, | |||
callback: WMD.convert, | |||
convert: function(md, sanitize){ | |||
this.doc = this.callback(md); | |||
md = this.doc.html; | |||
var html = this.doc.html; | |||
if (sanitize) { | |||
md = html_sanitize(md); | |||
// Causes some problems with inline styles | |||
html = html_sanitize(html); | |||
} | |||
md = this.hook(md); | |||
return md; | |||
html = this.hook(html); | |||
return html; | |||
}, | |||
hook: function(md) { | |||
hook: function(html) { | |||
if (!this.doc.metadata) { | |||
return md; | |||
return html; | |||
} | |||
try { | |||
var template = Handlebars.compile(md); | |||
var template = Handlebars.compile(html); | |||
return template(this.doc.metadata); | |||
} catch(e) { | |||
return md; | |||
return html; | |||
} | |||
} | |||
}; | |||
@@ -1,21 +1,23 @@ | |||
{% extends 'layout.html' %} | |||
{% block js %} | |||
<script src="/static/js/ace/ace.js"></script> | |||
<script src="/static/js/ace/mode-markdown.js"></script> | |||
<script src="/static/js/keymaster.min.js"></script> | |||
<script src="/static/js/dillinger.js"></script> | |||
{% assets "js_editor" %} | |||
<script type="text/javascript" src="{{ ASSET_URL }}"></script> | |||
{% endassets %} | |||
{% endblock %} | |||
{% block body %} | |||
{% block css %} | |||
<style> | |||
#main-body { | |||
background: inherit; | |||
border: inherit; | |||
padding: 0; | |||
-webkit-box-shadow: 0; | |||
-moz-box-shadow: 0; | |||
box-shadow: 0; | |||
} | |||
#main-body { | |||
background: inherit; | |||
border: inherit; | |||
padding: 0; | |||
-webkit-box-shadow: 0; | |||
-moz-box-shadow: 0; | |||
box-shadow: 0; | |||
} | |||
</style> | |||
{% endblock %} | |||
<div id="app-wrap" class="container-fluid"> | |||
<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/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="">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/idle_fingers" class="">idleFingers</a></li> | |||
<li><a tabindex="-1" href="#" data-value="ace/theme/kr_theme" class="">krTheme</a></li> | |||
@@ -34,6 +34,7 @@ def unescape_html(s): | |||
s = s.replace(''', "'") | |||
return s | |||
def mkdir_safe(path): | |||
if path and not(os.path.exists(path)): | |||
os.makedirs(path) | |||
@@ -6,6 +6,4 @@ nginx: | |||
- enable: True | |||
- reload: True | |||
- require: | |||
- pkg: nginx | |||
/etc/ | |||
- pkg: nginx |
@@ -0,0 +1,9 @@ | |||
node-repos: | |||
pkgrepo.managed: | |||
- ppa: chris-lea/node.js | |||
nodejs: | |||
pkg: | |||
- installed | |||
- require: | |||
- pkgrepo.managed: node-repos |