Added RDBMS info

Canonical names to forced to lowercase
Made user model compatible to other DBs
CSS adjustments
Basic Firepad support (no presence info)
Cleaned up JS a bit
Added ability to remove draft from localstorage
Added support for drafts on multiple pages
Alert user if page changes, issue #1
This commit is contained in:
Matthew Scragg 2014-10-03 13:49:18 -05:00
parent d72ecf10f0
commit eb12c84e9a
21 changed files with 841 additions and 717 deletions

View file

@ -88,7 +88,7 @@ This will ask you questions and create a config.json file in the app root direct
Of course you can manually edit this file as well. Of course you can manually edit this file as well.
Any config value set in config.json will override values set in ```realms/config/__init__.py``` Any config value set in config.json will override values set in ```realms/config/__init__.py```
## Nginx Setup ### Nginx Setup
sudo apt-get install -y nginx sudo apt-get install -y nginx
@ -130,6 +130,23 @@ Reload Nginx
sudo service nginx reload sudo service nginx reload
### Mysql Setup
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
realms-wiki pip install python-memcached
### MariaDB Setup
sudo apt-get install -y mariadb-server mariadb-client libmariadbclient-dev
realms-wiki pip install MySQL-Python
### Postgres
sudo apt-get install -y libpq-dev postgresql postgresql-contrib postgresql-client
realms-wiki pip install psycopg2
_Don't forget to create your database._
## Running ## Running
Current there are different ways. Current there are different ways.
@ -153,22 +170,19 @@ http://localhost:5000
## Templating ## Templating
Realms uses handlebars partials to create templates. Realms uses handlebars partials to create templates.
Each page that you create is a potential partial. Each page that you create can be imported as a partial.
Let's create the following partial: This page imports and uses a partial:
http://localhost:5000/_create/my-partial http://realms.io/_edit/hbs
<div class="panel panel-default"> This page contains the content of the partial:
<div class="panel-heading">{{ heading }}</div>
<div class="panel-body">
{{ body }}
</div>
</div>
Don't forget to save it. http://realms.io/_edit/example-tmpl
I locked these pages to preserve them.
You may copy and paste into a new page to test.
Now
## Author ## Author
Matthew Scragg <scragg@gmail.com> Matthew Scragg <scragg@gmail.com>

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.3.0

View file

@ -23,7 +23,7 @@ sudo add-apt-repository -y ppa:chris-lea/node.js
sudo apt-get update sudo apt-get update
sudo apt-get install -y python build-essential git libpcre3-dev \ sudo apt-get install -y python build-essential git libpcre3-dev \
python-pip python-virtualenv python-dev pkg-config curl libxml2-dev libxslt1-dev zlib1g-dev \ python-pip python-virtualenv python-dev pkg-config curl libxml2-dev libxslt1-dev zlib1g-dev \
libffi-dev nodejs libyaml-dev libffi-dev nodejs libyaml-dev libssl-dev
# Default cache is memoization # Default cache is memoization

View file

@ -1,8 +1,10 @@
from gevent import wsgi from gevent import wsgi
from realms import config, app, cli, db from realms import config, app, cli, db
from realms.lib.util import random_string from realms.lib.util import random_string
from subprocess import call
import click import click
import json import json
import sys
@cli.command() @cli.command()
@ -77,6 +79,37 @@ def setup_redis(**kw):
conf[k.upper()] = v conf[k.upper()] = v
config.update(conf) config.update(conf)
install_redis()
def get_pip():
""" Get virtualenv path for pip
"""
return sys.prefix + '/bin/pip'
@cli.command()
@click.argument('cmd', nargs=-1)
def pip(cmd):
""" Execute pip commands for this virtualenv
"""
call(get_pip() + ' ' + ' '.join(cmd), shell=True)
def install_redis():
call([get_pip(), 'install', 'redis'])
def install_mysql():
call([get_pip(), 'install', 'MySQL-Python'])
def install_postgres():
call([get_pip(), 'install', 'psycopg2'])
def install_memcached():
call([get_pip(), 'install', 'python-memcached'])
@click.command() @click.command()
@ -139,5 +172,13 @@ def drop_db():
click.echo("Dropping all tables") click.echo("Dropping all tables")
db.drop_all() db.drop_all()
@cli.command()
def version():
""" Output version
"""
with open('VERSION') as f:
return f.read().strip()
if __name__ == '__main__': if __name__ == '__main__':
cli() cli()

View file

@ -192,7 +192,8 @@ assets.register('main.js',
'js/html-sanitizer-minified.js', # don't minify? 'js/html-sanitizer-minified.js', # don't minify?
'vendor/highlightjs/highlight.pack.js', 'vendor/highlightjs/highlight.pack.js',
'vendor/parsleyjs/dist/parsley.js', 'vendor/parsleyjs/dist/parsley.js',
'js/main.js') 'js/hbs-helpers.js',
'js/mdr.js')
assets.register('main.css', assets.register('main.css',
'vendor/bootswatch-dist/css/bootstrap.css', 'vendor/bootswatch-dist/css/bootstrap.css',

View file

@ -38,7 +38,11 @@ PORT = 5000
BASE_URL = 'http://localhost' BASE_URL = 'http://localhost'
SITE_TITLE = "Realms" SITE_TITLE = "Realms"
# https://pythonhosted.org/Flask-SQLAlchemy/config.html#connection-uri-format
DB_URI = 'sqlite:///%s/wiki.db' % USER_HOME DB_URI = 'sqlite:///%s/wiki.db' % USER_HOME
# DB_URI = 'mysql://scott:tiger@localhost/mydatabase'
# DB_URI = 'postgresql://scott:tiger@localhost/mydatabase'
# DB_URI = 'oracle://scott:tiger@127.0.0.1:1521/sidname'
CACHE_TYPE = 'simple' CACHE_TYPE = 'simple'

View file

@ -91,6 +91,7 @@ def to_canonical(s):
s = re.sub(r"\-\-+", "-", s) s = re.sub(r"\-\-+", "-", s)
s = re.sub(r"[^a-zA-Z0-9\-]", "", s) s = re.sub(r"[^a-zA-Z0-9\-]", "", s)
s = s[:64] s = s[:64]
s = s.lower()
return s return s

View file

@ -44,9 +44,9 @@ class AnonUser(AnonymousUserMixin):
class User(Model, UserMixin): class User(Model, UserMixin):
__tablename__ = 'users' __tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, unique=True) username = db.Column(db.String(128), unique=True)
email = db.Column(db.String, unique=True) email = db.Column(db.String(128), unique=True)
password = db.Column(db.String) password = db.Column(db.String(60))
admin = False admin = False
hidden_fields = ['password'] hidden_fields = ['password']

View file

@ -7,4 +7,4 @@ assets.register('editor.js',
'vendor/ace-builds/src/mode-markdown.js', 'vendor/ace-builds/src/mode-markdown.js',
'vendor/ace-builds/src/ext-keybinding_menu.js', 'vendor/ace-builds/src/ext-keybinding_menu.js',
'vendor/keymaster/keymaster.js', 'vendor/keymaster/keymaster.js',
'js/editor.js') 'js/aced.js')

View file

@ -36,7 +36,7 @@ def revert():
commit = request.form.get('commit') commit = request.form.get('commit')
cname = to_canonical(name) cname = to_canonical(name)
if cname.lower() in app.config.WIKI_LOCKED_PAGES: if cname in app.config.WIKI_LOCKED_PAGES:
flash("Page is locked") flash("Page is locked")
return redirect(url_for(app.config['ROOT_ENDPOINT'])) return redirect(url_for(app.config['ROOT_ENDPOINT']))
@ -59,10 +59,10 @@ def edit(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.lower() in app.config['WIKI_LOCKED_PAGES']: if edit_cname in app.config['WIKI_LOCKED_PAGES']:
return redirect(url_for(app.config['ROOT_ENDPOINT'])) return redirect(url_for(app.config['ROOT_ENDPOINT']))
if edit_cname.lower() != cname.lower(): if edit_cname != cname.lower():
g.current_wiki.rename_page(cname, edit_cname) g.current_wiki.rename_page(cname, edit_cname)
g.current_wiki.write_page(edit_cname, g.current_wiki.write_page(edit_cname,

View file

@ -58,9 +58,9 @@
#app-wrap { #app-wrap {
top: 60px; top: 60px;
left: -5px; left: 0;
bottom: 0; bottom: 0;
right: -5px; right: 0;
position: fixed; position: fixed;
} }
@ -186,9 +186,8 @@ a.label {
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
padding: 45px 10px 10px 10px; padding: 40px 10px 10px 10px;
overflow: auto; overflow: auto;
//word-break: break-word;
cursor: default; cursor: default;
} }

430
realms/static/js/aced.js Normal file
View file

@ -0,0 +1,430 @@
function Aced(settings) {
var id,
options,
editor,
element,
preview,
previewWrapper,
profile,
autoInterval,
themes,
themeSelect,
loadedThemes = {};
settings = settings || {};
options = {
sanitize: true,
preview: null,
editor: null,
theme: 'idle_fingers',
themePath: '/static/vendor/ace-builds/src',
mode: 'markdown',
autoSave: true,
autoSaveInterval: 5000,
syncPreview: false,
keyMaster: false,
submit: function(data){ alert(data); },
showButtonBar: false,
themeSelect: null,
submitBtn: null,
renderer: null,
info: null
};
themes = {
chrome: "Chrome",
clouds: "Clouds",
clouds_midnight: "Clouds Midnight",
cobalt: "Cobalt",
crimson_editor: "Crimson Editor",
dawn: "Dawn",
dreamweaver: "Dreamweaver",
eclipse: "Eclipse",
idle_fingers: "idleFingers",
kr_theme: "krTheme",
merbivore: "Merbivore",
merbivore_soft: "Merbivore Soft",
mono_industrial: "Mono Industrial",
monokai: "Monokai",
pastel_on_dark: "Pastel on Dark",
solarized_dark: "Solarized Dark",
solarized_light: "Solarized Light",
textmate: "TextMate",
tomorrow: "Tomorrow",
tomorrow_night: "Tomorrow Night",
tomorrow_night_blue: "Tomorrow Night Blue",
tomorrow_night_bright: "Tomorrow Night Bright",
tomorrow_night_eighties: "Tomorrow Night 80s",
twilight: "Twilight",
vibrant_ink: "Vibrant Ink"
};
function editorId() {
return "aced." + id;
}
function infoKey() {
return editorId() + ".info";
}
function gc() {
// Clean up localstorage
store.forEach(function(key, val) {
var re = new RegExp("aced\.(.*?)\.info");
var info = re.exec(key);
if (!info || !val.time) {
return;
}
var id = info[1];
// Remove week+ old stuff
var now = new Date().getTime() / 1000;
if (now > (val.time + 604800)) {
store.remove(key);
store.remove('aced.' + id);
}
});
}
function buildThemeSelect() {
var $sel = $("<select class='aced-theme-sel' data-placeholder='Theme'></select>");
$sel.append('<option></option>');
$.each(themes, function(k, v) {
$sel.append("<option value='" + k + "'>" + v + "</option>");
});
return $("<div/>").html($sel);
}
function toJquery(o) {
return (typeof o == 'string') ? $("#" + o) : $(o);
}
function initProfile() {
profile = {theme: ''};
try {
// Need to merge in any undefined/new properties from last release
// Meaning, if we add new features they may not have them in profile
profile = $.extend(true, profile, store.get('aced.profile'));
} catch (e) { }
}
function updateProfile(obj) {
profile = $.extend(null, profile, obj);
store.set('profile', profile);
}
function render(content) {
return (options.renderer) ? options.renderer(content) : content;
}
function bindKeyboard() {
// CMD+s TO SAVE DOC
key('command+s, ctrl+s', function (e) {
submit();
e.preventDefault();
});
var saveCommand = {
name: "save",
bindKey: {
mac: "Command-S",
win: "Ctrl-S"
},
exec: function () {
submit();
}
};
editor.commands.addCommand(saveCommand);
}
function info(info) {
if (info) {
store.set(infoKey(), info);
}
return store.get(infoKey());
}
function val(val) {
// Alias func
if (val) {
editor.getSession().setValue(val);
}
return editor.getSession().getValue();
}
function discardDraft() {
stopAutoSave();
store.remove(editorId());
store.remove(infoKey());
location.reload();
}
function save() {
store.set(editorId(), val());
}
function submit() {
store.remove(editorId());
store.remove(editorId() + ".info");
options.submit(val());
}
function autoSave() {
if (options.autoSave) {
autoInterval = setInterval(function () {
save();
}, options.autoSaveInterval);
} else {
stopAutoSave();
}
}
function stopAutoSave() {
if (autoInterval){
clearInterval(autoInterval)
}
}
function renderPreview() {
if (!preview) {
return;
}
preview.html(render(val()));
$('pre code', preview).each(function(i, e) {
hljs.highlightBlock(e)
});
}
function getScrollHeight($prevFrame) {
// Different browsers attach the scrollHeight of a document to different
// elements, so handle that here.
if ($prevFrame[0].scrollHeight !== undefined) {
return $prevFrame[0].scrollHeight;
} else if ($prevFrame.find('html')[0].scrollHeight !== undefined &&
$prevFrame.find('html')[0].scrollHeight !== 0) {
return $prevFrame.find('html')[0].scrollHeight;
} else {
return $prevFrame.find('body')[0].scrollHeight;
}
}
function getPreviewWrapper(obj) {
// Attempts to get the wrapper for preview based on overflow prop
if (!obj) {
return;
}
if (obj.css('overflow') == 'auto' || obj.css('overflow') == 'scroll') {
return obj;
} else {
return getPreviewWrapper(obj.parent());
}
}
function syncPreview() {
var editorScrollRange = (editor.getSession().getLength());
var previewScrollRange = (getScrollHeight(preview));
// Find how far along the editor is (0 means it is scrolled to the top, 1
// means it is at the bottom).
var scrollFactor = editor.getFirstVisibleRow() / editorScrollRange;
// Set the scroll position of the preview pane to match. jQuery will
// gracefully handle out-of-bounds values.
previewWrapper.scrollTop(scrollFactor * previewScrollRange);
}
function asyncLoad(filename, cb) {
(function (d, t) {
var leScript = d.createElement(t)
, scripts = d.getElementsByTagName(t)[0];
leScript.async = 1;
leScript.src = filename;
scripts.parentNode.insertBefore(leScript, scripts);
leScript.onload = function () {
cb && cb();
}
}(document, 'script'));
}
function setTheme(theme) {
var cb = function(theme) {
editor.setTheme('ace/theme/'+theme);
updateProfile({theme: theme});
};
if (loadedThemes[theme]) {
cb(theme);
} else {
asyncLoad(options.themePath + "/theme-" + theme + ".js", function () {
cb(theme);
loadedThemes[theme] = true;
});
}
}
function initSyncPreview() {
if (!preview || !options.syncPreview) return;
previewWrapper = getPreviewWrapper(preview);
window.onload = function () {
/**
* Bind synchronization of preview div to editor scroll and change
* of editor cursor position.
*/
editor.session.on('changeScrollTop', syncPreview);
editor.session.selection.on('changeCursor', syncPreview);
};
}
function initProps() {
// Id of editor
if (typeof settings == 'string') {
settings = { editor: settings };
}
if ('theme' in profile && profile['theme']) {
settings['theme'] = profile['theme'];
}
if (settings['preview'] && !settings.hasOwnProperty('syncPreview')) {
settings['syncPreview'] = true;
}
$.extend(options, settings);
if (options.editor) {
element = toJquery(options.editor);
}
$.each(options, function(k, v){
if (element.data(k.toLowerCase())) {
options[k] = element.data(k.toLowerCase());
}
});
if (options.themeSelect) {
themeSelect = toJquery(options.themeSelect);
}
if (options.submitBtn) {
var submitBtn = toJquery(options.submitBtn);
submitBtn.click(function(){
submit();
});
}
if (options.preview) {
preview = toJquery(options.preview);
// Enable sync unless set otherwise
if (!settings.hasOwnProperty('syncPreview')) {
options['syncPreview'] = true;
}
}
if (!element.attr('id')) {
// No id, make one!
id = Math.random().toString(36).substring(7);
element.attr('id', id);
} else {
id = element.attr('id')
}
}
function initEditor() {
editor = ace.edit(id);
setTheme(profile.theme || options.theme);
editor.getSession().setMode('ace/mode/' + options.mode);
if (store.get(editorId()) && store.get(editorId()) != val()) {
editor.getSession().setValue(store.get(editorId()));
}
editor.getSession().setUseWrapMode(true);
editor.getSession().setTabSize(2);
editor.getSession().setUseSoftTabs(true);
editor.setShowPrintMargin(false);
editor.renderer.setShowInvisibles(true);
editor.renderer.setShowGutter(false);
if (options.showButtonBar) {
var $btnBar = $('<div class="aced-button-bar aced-button-bar-top">' + buildThemeSelect().html() + ' <button type="button" class="btn btn-primary btn-xs aced-save">Save</button></div>')
element.find('.ace_content').before($btnBar);
$(".aced-save", $btnBar).click(function(){
submit();
});
if ($.fn.chosen) {
$('select', $btnBar).chosen().change(function(){
setTheme($(this).val());
});
}
}
if (options.keyMaster) {
bindKeyboard();
}
if (preview) {
editor.getSession().on('change', function (e) {
renderPreview();
});
renderPreview();
}
if (themeSelect) {
themeSelect
.find('li > a')
.bind('click', function (e) {
setTheme($(e.target).data('value'));
$(e.target).blur();
return false;
});
}
if (options.info) {
// If no info exists, save it to storage
if (!store.get(infoKey())) {
store.set(infoKey(), options.info);
} else {
// Check info in storage against one passed in
// for possible changes in data that may have occurred
var info = store.get(infoKey());
if (info['sha'] != options.info['sha'] && !info['ignore']) {
// Data has changed since start of draft
$(document).trigger('shaMismatch');
}
}
}
$(this).trigger('ready');
}
function init() {
gc();
initProfile();
initProps();
initEditor();
initSyncPreview();
autoSave();
}
init();
return {
editor: editor,
submit: submit,
val: val,
discard: discardDraft,
info: info
};
}

View file

@ -0,0 +1,52 @@
// Helper to get hash from end of URL or generate a random one.
function getExampleRef() {
var ref = new Firebase('https://' + Config['FIREBASE_HOSTNAME']);
var hash = window.location.hash.replace(/^#fp-/, '');
if (hash) {
ref = ref.child(hash);
} else {
ref = ref.push(); // generate unique location.
window.location = window.location + '#fp-' + ref.name(); // add it as a hash to the URL.
}
return ref;
}
function initFirepad() {
var new_ = true;
if (window.location.hash.lastIndexOf('#fp-', 0) === 0) {
new_ = false;
}
var firepadRef = getExampleRef();
var session = aced.editor.session;
var content;
if (new_) {
content = session.getValue();
}
// Firepad wants an empty editor
session.setValue('');
//// Create Firepad.
var firepad = Firepad.fromACE(firepadRef, aced.editor, {
defaultText: content
});
firepad.on('ready', function() {
startCollaboration();
});
$(document).on('end-collaboration', function() {
firepad.dispose();
});
}
$(document).on('loading-collaboration', function() {
initFirepad(true);
});
$(function(){
if (window.location.hash.lastIndexOf('#fp-', 0) === 0) {
loadingCollaboration();
}
});

View file

@ -0,0 +1,36 @@
var $startCollaborationBtn = $('#start-collaboration');
var $endCollaborationBtn = $('#end-collaboration');
var $loadingCollaborationBtn = $('#loading-collaboration');
function loadingCollaboration() {
$endCollaborationBtn.hide();
$startCollaborationBtn.hide();
$loadingCollaborationBtn.show();
$(document).trigger('loading-collaboration');
}
function startCollaboration() {
$loadingCollaborationBtn.hide();
$startCollaborationBtn.hide();
$endCollaborationBtn.show();
$(document).trigger('start-collaboration');
}
function endCollaboration() {
$loadingCollaborationBtn.hide();
$endCollaborationBtn.hide();
$startCollaborationBtn.show();
$(document).trigger('end-collaboration');
}
$(function() {
$startCollaborationBtn.click(function(e) {
loadingCollaboration();
e.preventDefault();
});
$endCollaborationBtn.click(function(e) {
endCollaboration();
e.preventDefault();
});
});

View file

@ -0,0 +1,28 @@
$(document).on('loading-collaboration', function() {
TogetherJS();
});
$(document).on('end-collaboration', function() {
TogetherJS();
});
TogetherJSConfig_toolName = "Collaboration";
TogetherJSConfig_suppressJoinConfirmation = true;
if (User.is_authenticated) {
TogetherJSConfig_getUserName = function () {
return User.username;
};
TogetherJSConfig_getUserAvatar = function () {
return User.avatar;
};
}
TogetherJSConfig_on_ready = function () {
startCollaboration();
};
TogetherJSConfig_on_close = function () {
//endCollaboration();
};

View file

@ -1,430 +1,77 @@
function Aced(settings) { var $entry_markdown_header = $("#entry-markdown-header");
var id, var $entry_preview_header = $("#entry-preview-header");
options, var $entry_markdown = $(".entry-markdown");
editor, var $entry_preview = $(".entry-preview");
element,
preview,
previewWrapper,
profile,
autoInterval,
themes,
themeSelect,
loadedThemes = {};
settings = settings || {}; // Tabs
$entry_markdown_header.click(function(){
$entry_markdown.addClass('active');
$entry_preview.removeClass('active');
});
options = { $entry_preview_header.click(function(){
sanitize: true, $entry_preview.addClass('active');
preview: null, $entry_markdown.removeClass('active');
editor: null, });
theme: 'idle_fingers',
themePath: '/static/vendor/ace-builds/src',
mode: 'markdown',
autoSave: true,
autoSaveInterval: 5000,
syncPreview: false,
keyMaster: false,
submit: function(data){ alert(data); },
showButtonBar: false,
themeSelect: null,
submitBtn: null,
renderer: null,
info: null
};
themes = { $(document).on('shaMismatch', function() {
chrome: "Chrome", bootbox.dialog({
clouds: "Clouds", title: "Page has changed",
clouds_midnight: "Clouds Midnight", message: "This page has changed and differs from your draft. What do you want to do?",
cobalt: "Cobalt", buttons: {
crimson_editor: "Crimson Editor", ignore: {
dawn: "Dawn", label: "Ignore",
dreamweaver: "Dreamweaver", className: "btn-default",
eclipse: "Eclipse", callback: function() {
idle_fingers: "idleFingers", var info = aced.info();
kr_theme: "krTheme", info['ignore'] = true;
merbivore: "Merbivore", aced.info(info);
merbivore_soft: "Merbivore Soft", }
mono_industrial: "Mono Industrial",
monokai: "Monokai",
pastel_on_dark: "Pastel on Dark",
solarized_dark: "Solarized Dark",
solarized_light: "Solarized Light",
textmate: "TextMate",
tomorrow: "Tomorrow",
tomorrow_night: "Tomorrow Night",
tomorrow_night_blue: "Tomorrow Night Blue",
tomorrow_night_bright: "Tomorrow Night Bright",
tomorrow_night_eighties: "Tomorrow Night 80s",
twilight: "Twilight",
vibrant_ink: "Vibrant Ink"
};
function editorId() {
return "aced." + id;
}
function infoKey() {
return editorId() + ".info";
}
function gc() {
// Clean up localstorage
store.forEach(function(key, val) {
var re = new RegExp("aced\.(.*?)\.info");
var info = re.exec(key);
if (!info || !val.time) {
return;
}
var id = info[1];
// Remove week+ old stuff
var now = new Date().getTime() / 1000;
if (now > (val.time + 604800)) {
store.remove(key);
store.remove('aced.' + id);
}
});
}
function buildThemeSelect() {
var $sel = $("<select class='aced-theme-sel' data-placeholder='Theme'></select>");
$sel.append('<option></option>');
$.each(themes, function(k, v) {
$sel.append("<option value='" + k + "'>" + v + "</option>");
});
return $("<div/>").html($sel);
}
function toJquery(o) {
return (typeof o == 'string') ? $("#" + o) : $(o);
}
function initProfile() {
profile = {theme: ''};
try {
// Need to merge in any undefined/new properties from last release
// Meaning, if we add new features they may not have them in profile
profile = $.extend(true, profile, store.get('aced.profile'));
} catch (e) { }
}
function updateProfile(obj) {
profile = $.extend(null, profile, obj);
store.set('profile', profile);
}
function render(content) {
return (options.renderer) ? options.renderer(content) : content;
}
function bindKeyboard() {
// CMD+s TO SAVE DOC
key('command+s, ctrl+s', function (e) {
submit();
e.preventDefault();
});
var saveCommand = {
name: "save",
bindKey: {
mac: "Command-S",
win: "Ctrl-S"
}, },
exec: function () { discard: {
submit(); label: "Discard Draft",
} className: "btn-danger",
}; callback: function() {
editor.commands.addCommand(saveCommand); aced.discard();
} }
},
function info(info) { changes: {
if (info) { label: "Show Diff",
store.set(infoKey(), info); className: "btn-primary",
} callback: function() {
return store.get(infoKey()); bootbox.alert("Draft diff not done! Sorry");
}
function val(val) {
// Alias func
if (val) {
editor.getSession().setValue(val);
}
return editor.getSession().getValue();
}
function discardDraft() {
stopAutoSave();
store.remove(editorId());
store.remove(infoKey());
location.reload();
}
function save() {
store.set(editorId(), val());
}
function submit() {
store.remove(editorId());
store.remove(editorId() + ".info");
options.submit(val());
}
function autoSave() {
if (options.autoSave) {
autoInterval = setInterval(function () {
save();
}, options.autoSaveInterval);
} else {
stopAutoSave();
}
}
function stopAutoSave() {
if (autoInterval){
clearInterval(autoInterval)
}
}
function renderPreview() {
if (!preview) {
return;
}
preview.html(render(val()));
$('pre code', preview).each(function(i, e) {
hljs.highlightBlock(e)
});
}
function getScrollHeight($prevFrame) {
// Different browsers attach the scrollHeight of a document to different
// elements, so handle that here.
if ($prevFrame[0].scrollHeight !== undefined) {
return $prevFrame[0].scrollHeight;
} else if ($prevFrame.find('html')[0].scrollHeight !== undefined &&
$prevFrame.find('html')[0].scrollHeight !== 0) {
return $prevFrame.find('html')[0].scrollHeight;
} else {
return $prevFrame.find('body')[0].scrollHeight;
}
}
function getPreviewWrapper(obj) {
// Attempts to get the wrapper for preview based on overflow prop
if (!obj) {
return;
}
if (obj.css('overflow') == 'auto' || obj.css('overflow') == 'scroll') {
return obj;
} else {
return getPreviewWrapper(obj.parent());
}
}
function syncPreview() {
var editorScrollRange = (editor.getSession().getLength());
var previewScrollRange = (getScrollHeight(preview));
// Find how far along the editor is (0 means it is scrolled to the top, 1
// means it is at the bottom).
var scrollFactor = editor.getFirstVisibleRow() / editorScrollRange;
// Set the scroll position of the preview pane to match. jQuery will
// gracefully handle out-of-bounds values.
previewWrapper.scrollTop(scrollFactor * previewScrollRange);
}
function asyncLoad(filename, cb) {
(function (d, t) {
var leScript = d.createElement(t)
, scripts = d.getElementsByTagName(t)[0];
leScript.async = 1;
leScript.src = filename;
scripts.parentNode.insertBefore(leScript, scripts);
leScript.onload = function () {
cb && cb();
}
}(document, 'script'));
}
function setTheme(theme) {
var cb = function(theme) {
editor.setTheme('ace/theme/'+theme);
updateProfile({theme: theme});
};
if (loadedThemes[theme]) {
cb(theme);
} else {
asyncLoad(options.themePath + "/theme-" + theme + ".js", function () {
cb(theme);
loadedThemes[theme] = true;
});
}
}
function initSyncPreview() {
if (!preview || !options.syncPreview) return;
previewWrapper = getPreviewWrapper(preview);
window.onload = function () {
/**
* Bind synchronization of preview div to editor scroll and change
* of editor cursor position.
*/
editor.session.on('changeScrollTop', syncPreview);
editor.session.selection.on('changeCursor', syncPreview);
};
}
function initProps() {
// Id of editor
if (typeof settings == 'string') {
settings = { editor: settings };
}
if ('theme' in profile && profile['theme']) {
settings['theme'] = profile['theme'];
}
if (settings['preview'] && !settings.hasOwnProperty('syncPreview')) {
settings['syncPreview'] = true;
}
$.extend(options, settings);
if (options.editor) {
element = toJquery(options.editor);
}
$.each(options, function(k, v){
if (element.data(k.toLowerCase())) {
options[k] = element.data(k.toLowerCase());
}
});
if (options.themeSelect) {
themeSelect = toJquery(options.themeSelect);
}
if (options.submitBtn) {
var submitBtn = toJquery(options.submitBtn);
submitBtn.click(function(){
submit();
});
}
if (options.preview) {
preview = toJquery(options.preview);
// Enable sync unless set otherwise
if (!settings.hasOwnProperty('syncPreview')) {
options['syncPreview'] = true;
}
}
if (!element.attr('id')) {
// No id, make one!
id = Math.random().toString(36).substring(7);
element.attr('id', id);
} else {
id = element.attr('id')
}
}
function initEditor() {
editor = ace.edit(id);
setTheme(profile.theme || options.theme);
editor.getSession().setMode('ace/mode/' + options.mode);
if (store.get(editorId()) && store.get(editorId()) != val()) {
editor.getSession().setValue(store.get(editorId()));
}
editor.getSession().setUseWrapMode(true);
editor.getSession().setTabSize(2);
editor.getSession().setUseSoftTabs(true);
editor.setShowPrintMargin(false);
editor.renderer.setShowInvisibles(true);
editor.renderer.setShowGutter(false);
if (options.showButtonBar) {
var $btnBar = $('<div class="aced-button-bar aced-button-bar-top">' + buildThemeSelect().html() + ' <button type="button" class="btn btn-primary btn-xs aced-save">Save</button></div>')
element.find('.ace_content').before($btnBar);
$(".aced-save", $btnBar).click(function(){
submit();
});
if ($.fn.chosen) {
$('select', $btnBar).chosen().change(function(){
setTheme($(this).val());
});
}
}
if (options.keyMaster) {
bindKeyboard();
}
if (preview) {
editor.getSession().on('change', function (e) {
renderPreview();
});
renderPreview();
}
if (themeSelect) {
themeSelect
.find('li > a')
.bind('click', function (e) {
setTheme($(e.target).data('value'));
$(e.target).blur();
return false;
});
}
if (options.info) {
// If no info exists, save it to storage
if (!store.get(infoKey())) {
store.set(infoKey(), options.info);
} else {
// Check info in storage against one passed in
// for possible changes in data that may have occurred
var info = store.get(infoKey());
if (info['sha'] != options.info['sha'] && !info['ignore']) {
// Data has changed since start of draft
$(document).trigger('shaMismatch');
} }
} }
} }
})
});
$(this).trigger('ready'); $(function(){
$("#discard-draft-btn").click(function() {
aced.discard();
});
$(".entry-markdown .floatingheader").click(function(){
aced.editor.focus();
});
$("#delete-draft-btn").click(function() {
bootbox.alert("Not Done Yet! Sorry");
});
});
var aced = new Aced({
editor: $('#entry-markdown-content').find('.editor').attr('id'),
renderer: function(md) { return MDR.convert(md) },
info: Commit.info,
submit: function(content) {
var data = {
name: $("#page-name").val(),
message: $("#page-message").val(),
content: content
};
$.post(window.location, data, function() {
location.href = Config['RELATIVE_PATH'] + '/' + data['name'];
});
} }
});
function init() {
gc();
initProfile();
initProps();
initEditor();
initSyncPreview();
autoSave();
}
init();
return {
editor: editor,
submit: submit,
val: val,
discard: discardDraft,
info: info
};
}

View file

@ -0,0 +1,12 @@
// Handlebar helpers
Handlebars.registerHelper('well', function(options) {
return '<div class="well">' + options.fn(this) + '</div>';
});
Handlebars.registerHelper('well-sm', function(options) {
return '<div class="well well-sm">' + options.fn(this) + '</div>';
});
Handlebars.registerHelper('well-lg', function(options) {
return '<div class="well well-lg">' + options.fn(this) + '</div>';
});

View file

@ -1,12 +1,6 @@
// Handlebar helpers // Init highlight JS
Handlebars.registerHelper('well', function(options) { hljs.initHighlightingOnLoad();
return '<div class="well">' + options.fn(this) + '</div>';
});
/* © 2013 j201
* https://github.com/j201/meta-marked */
// Splits the given string into a meta section and a markdown section if a meta section is present, else returns null
function splitInput(str) { function splitInput(str) {
if (str.slice(0, 3) !== '---') return; if (str.slice(0, 3) !== '---') return;
@ -16,6 +10,10 @@ function splitInput(str) {
return metaEnd && [str.slice(0, metaEnd.index), str.slice(matcher.lastIndex)]; return metaEnd && [str.slice(0, metaEnd.index), str.slice(matcher.lastIndex)];
} }
/* © 2013 j201
* https://github.com/j201/meta-marked */
// Splits the given string into a meta section and a markdown section if a meta section is present, else returns null
var metaMarked = function(src, opt, callback) { var metaMarked = function(src, opt, callback) {
if (Object.prototype.toString.call(src) !== '[object String]') if (Object.prototype.toString.call(src) !== '[object String]')
throw new TypeError('First parameter must be a string.'); throw new TypeError('First parameter must be a string.');
@ -51,9 +49,6 @@ marked.setOptions({
smartypants: false smartypants: false
}); });
// Init highlight JS
hljs.initHighlightingOnLoad();
// Markdown Renderer // Markdown Renderer
var MDR = { var MDR = {
meta: null, meta: null,
@ -100,10 +95,12 @@ var MDR = {
try { try {
var template = Handlebars.compile(this.md); var template = Handlebars.compile(this.md);
this.md = template(this.meta); this.md = template(this.meta);
} catch(e) {} } catch(e) {
console.log(e);
}
} }
}, },
hook: function() { hook: function() {
} }
}; };

View file

@ -7,6 +7,7 @@
<meta name="author" content=""> <meta name="author" content="">
<title>{{ config.SITE_TITLE }}</title> <title>{{ config.SITE_TITLE }}</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
{% for bundle in g.assets['css'] %} {% for bundle in g.assets['css'] %}
@ -14,6 +15,7 @@
<link href="{{ ASSET_URL }}" rel="stylesheet"> <link href="{{ ASSET_URL }}" rel="stylesheet">
{% endassets %} {% endassets %}
{% endfor %} {% endfor %}
{% block css %}{% endblock %} {% 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 -->
@ -88,6 +90,20 @@
{% block body %}{% endblock %} {% block body %}{% endblock %}
</div> </div>
</div> </div>
<script>
var Config = {};
{% for attr in ['RELATIVE_PATH'] %}
Config.{{ attr }} = {{ config[attr]|tojson }};
{% endfor %}
var User = {};
User.is_authenticated = {{ current_user.is_authenticated()|tojson }};
{% for attr in ['username', 'email'] %}
User.{{ attr }} = {{ current_user[attr]|tojson }};
{% endfor %}
</script>
{% for bundle in g.assets['js'] %} {% for bundle in g.assets['js'] %}
{% assets bundle %} {% assets bundle %}
{% if bundle == 'editor.js' %} {% if bundle == 'editor.js' %}
@ -97,6 +113,8 @@
{% endif %} {% endif %}
{% endassets %} {% endassets %}
{% endfor %} {% endfor %}
{% block js %}{% endblock %} {% block js %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -1,77 +1,10 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block js %} {% block js %}
<script> <script>
var $entry_markdown_header = $("#entry-markdown-header"); var Commit = {};
var $entry_preview_header = $("#entry-preview-header"); Commit.info = {{ info|tojson }};
// Tabs
$entry_markdown_header.click(function(){
$("section.entry-markdown").addClass('active');
$("section.entry-preview").removeClass('active');
});
$entry_preview_header.click(function(){
$("section.entry-preview").addClass('active');
$("section.entry-markdown").removeClass('active');
});
$(document).on('shaMismatch', function() {
bootbox.dialog({
title: "Page has changed",
message: "This page has changed and differs from your draft. What do you want to do?",
buttons: {
ignore: {
label: "Ignore",
className: "btn-default",
callback: function() {
var info = aced.info();
info['ignore'] = true;
aced.info(info);
}
},
discard: {
label: "Discard Draft",
className: "btn-danger",
callback: function() {
aced.discard();
}
},
changes: {
label: "Show Diff",
className: "btn-primary",
callback: function() {
bootbox.alert("Draft diff not done! Sorry");
}
}
}
})
});
$(function(){
$("#discard-btn").click(function() {
aced.discard();
});
$(".entry-markdown .floatingheader").click(function(){
aced.editor.focus();
});
});
var aced = new Aced({
editor: 'editor-{{- name -}}',
renderer: function(md) { return MDR.convert(md) },
info: {{ info|tojson }},
submit: function(content) {
var data = {
name: $("#page-name").val(),
message: $("#page-message").val(),
content: content
};
$.post(window.location, data, function() {
location.href = "{{ config.get('RELATIVE_PATH') }}" + '/' + data['name'];
});
}
});
</script> </script>
<script src="{{ url_for('static', filename='js/editor.js') }}"></script>
{% if partials %} {% if partials %}
<script> <script>
@ -90,133 +23,21 @@
{% endif %} {% endif %}
{% if config.get('COLLABORATION') %} {% if config.get('COLLABORATION') %}
<script> <script src="{{ url_for('static', filename='js/collaboration/main.js') }}"></script>
var $startCollaborationBtn = $('#start-collaboration');
var $endCollaborationBtn = $('#end-collaboration');
var $loadingCollaborationBtn = $('#loading-collaboration');
function loadingCollaboration() {
$endCollaborationBtn.hide();
$startCollaborationBtn.hide();
$loadingCollaborationBtn.show();
$(document).trigger('loading-collaboration');
}
function startCollaboration() {
$loadingCollaborationBtn.hide();
$startCollaborationBtn.hide();
$endCollaborationBtn.show();
$(document).trigger('start-collaboration');
}
function endCollaboration() {
$loadingCollaborationBtn.hide();
$endCollaborationBtn.hide();
$startCollaborationBtn.show();
$(document).trigger('end-collaboration');
}
$(function() {
$startCollaborationBtn.click(function() {
loadingCollaboration();
});
$endCollaborationBtn.click(function() {
endCollaboration();
});
});
</script>
{% endif %} {% endif %}
{% if config.get('COLLABORATION') == 'firepad' %} {% if config.get('COLLABORATION') == 'firepad' %}
<script>
Config['FIREBASE_HOSTNAME'] = {{ config.get('FIREBASE_HOSTNAME')|tojson }};
</script>
<script src="https://cdn.firebase.com/js/client/1.0.17/firebase.js"></script> <script src="https://cdn.firebase.com/js/client/1.0.17/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firepad/1.0.0/firepad.min.js"></script> <script src="https://cdn.firebase.com/libs/firepad/1.0.0/firepad.min.js"></script>
<script> <script src="{{ url_for('static', filename='js/collaboration/firepad.js') }}"></script>
// Helper to get hash from end of URL or generate a random one.
function getExampleRef() {
var ref = new Firebase('https://{{ config.get("FIREBASE_HOSTNAME") }}');
var hash = window.location.hash.replace(/^#fp-/, '');
if (hash) {
ref = ref.child(hash);
} else {
ref = ref.push(); // generate unique location.
window.location = window.location + '#fp-' + ref.name(); // add it as a hash to the URL.
}
return ref;
}
function initFirepad() {
var new_ = true;
if (window.location.hash.lastIndexOf('#fp-', 0) === 0) {
new_ = false;
}
var firepadRef = getExampleRef();
var session = window.ace.edit('editor').session;
var content;
if (new_) {
content = session.getValue();
}
// Firepad wants an empty editor
session.setValue('');
//// Create Firepad.
var firepad = Firepad.fromACE(firepadRef, aced.editor, {
defaultText: content
});
firepad.on('ready', function() {
startCollaboration();
});
$(document).on('end-collaboration', function() {
firepad.dispose();
});
}
$(document).on('loading-collaboration', function() {
initFirepad(true);
});
$(function(){
if (window.location.hash.lastIndexOf('#fp-', 0) === 0) {
loadingCollaboration();
}
});
</script>
{% endif %} {% endif %}
{% if config.get('COLLABORATION') == 'togetherjs' %} {% if config.get('COLLABORATION') == 'togetherjs' %}
<script> <script src="{{ url_for('static', filename='js/collaboration/togetherjs.js') }}"></script>
$(document).on('loading-collaboration', function() { <script src="https://togetherjs.com/togetherjs-min.js"></script>
TogetherJS();
});
$(document).on('end-collaboration', function() {
TogetherJS();
});
TogetherJSConfig_toolName = "Collaboration";
TogetherJSConfig_suppressJoinConfirmation = true;
{% if current_user.is_authenticated() %}
TogetherJSConfig_getUserName = function () {
return {{ current_user.username|tojson }};
};
TogetherJSConfig_getUserAvatar = function () {
return {{ current_user.avatar|tojson }};
};
{% endif %}
TogetherJSConfig_on_ready = function () {
startCollaboration();
};
TogetherJSConfig_on_close = function () {
//endCollaboration();
};
</script>
<script src="https://togetherjs.com/togetherjs-min.js"></script>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
@ -225,68 +46,89 @@
<div id="app-wrap"> <div id="app-wrap">
<div id="app-controls" class="row"> <div id="app-controls" class="row">
<div class="col-xs-4 col-md-3"> <div class="col-xs-4 col-md-3">
<input id="page-name" type="text" class="form-control input-sm" name="name" placeholder="Name" value="{{- name -}}" /> <input id="page-name" type="text" class="form-control input-sm" name="name"
placeholder="Name" value="{{- name -}}" />
</div> </div>
<div class="col-xs-4 col-md-3"> <div class="col-xs-4 col-md-3">
<input id="page-message" type="text" class="form-control input-sm" name="page-message" placeholder="Comment" value="" /> <input id="page-message" type="text" class="form-control input-sm" name="page-message"
placeholder="Comment" value="" />
</div> </div>
<div class="col-md-6 col-xs-4"> <div class="col-md-6 col-xs-4 text-right">
<div class="pull-right">
<button id="discard-btn" class="btn btn-sm btn-danger">
<i class="fa fa-trash-o"></i>
<span class="hidden-xs">Discard Draft</span>
</button>
{% if config.get('COLLABORATION') %} {% if config.get('COLLABORATION') %}
<button style='display:none' class="btn btn-danger btn-sm" id="end-collaboration"> <div class="btn-group">
<i class="fa fa-comments-o"></i> <button style='display:none' class="btn btn-danger btn-sm" id="end-collaboration">
<span class="hidden-xs">End Collaboration</span> <i class="fa fa-comments-o"></i>
</button> <span class="hidden-xs">End Collaboration</span>
</button>
<button class="btn btn-default btn-sm" id="start-collaboration" type="button"> </div>
<i class="fa fa-comments-o"></i> <div class="btn-group">
<span class="hidden-xs">Collaborate</span> <button style='display:none' class="btn btn-default btn-sm" id="loading-collaboration" type="button">
</button> <i class="fa fa-cog fa-spin"></i>
<span class="hidden-xs">Loading</span>
<button style='display:none' class="btn btn-default btn-sm" id="loading-collaboration" type="button"> </button>
<i class="fa fa-cog fa-spin"></i> </div>
<span class="hidden-xs">Loading</span>
</button>
{% endif %} {% endif %}
<a id="drop6" role="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown"> <div class="dropdown btn-group">
<button class="btn btn-default btn-sm dropdown-toggle" type="button" id="editor-actions"
data-toggle="dropdown" title="Actions">
<i class="fa fa-cog"></i>
<span class="hidden-xs">Actions <i class="fa fa-caret-down"></i></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="editor-actions">
<li role="presentation">
<a role="menuitem" tabindex="-1" href="#" id="start-collaboration">Collaborate</a>
</li>
<li role="presentation" class="divider"></li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="#" id="discard-draft-btn">Delete Draft</a>
</li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="#" id="delete-page-btn">Delete Page</a>
</li>
</ul>
</div>
<div class="dropdown btn-group">
<button id="theme-list-btn" type="button" class="dropdown-toggle btn btn-default btn-sm"
data-toggle="dropdown" title="Change Theme">
<i class="fa fa-paint-brush"></i> <i class="fa fa-paint-brush"></i>
<span class="hidden-xs">Theme <i class="fa fa-caret-down"></i></span> <span class="hidden-xs">Theme <i class="fa fa-caret-down"></i></span>
</a> </button>
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6"> <ul id="theme-list" class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="theme-list">
<li><a tabindex="-1" data-value="chrome" >Chrome</a></li> <li><a tabindex="-1" href="#" data-value="chrome" >Chrome</a></li>
<li><a tabindex="-1" data-value="clouds" >Clouds</a></li> <li><a tabindex="-1" href="#" data-value="clouds" >Clouds</a></li>
<li><a tabindex="-1" data-value="clouds_midnight" >Clouds Midnight</a></li> <li><a tabindex="-1" href="#" data-value="clouds_midnight" >Clouds Midnight</a></li>
<li><a tabindex="-1" data-value="cobalt" >Cobalt</a></li> <li><a tabindex="-1" href="#" data-value="cobalt" >Cobalt</a></li>
<li><a tabindex="-1" data-value="crimson_editor" >Crimson Editor</a></li> <li><a tabindex="-1" href="#" data-value="crimson_editor" >Crimson Editor</a></li>
<li><a tabindex="-1" data-value="dawn" class="selected">Dawn</a></li> <li><a tabindex="-1" href="#" data-value="dawn" class="selected">Dawn</a></li>
<li><a tabindex="-1" data-value="dreamweaver" >Dreamweaver</a></li> <li><a tabindex="-1" href="#" data-value="dreamweaver" >Dreamweaver</a></li>
<li><a tabindex="-1" data-value="eclipse" >Eclipse</a></li> <li><a tabindex="-1" href="#" data-value="eclipse" >Eclipse</a></li>
<li><a tabindex="-1" data-value="idle_fingers" >idleFingers</a></li> <li><a tabindex="-1" href="#" data-value="idle_fingers" >idleFingers</a></li>
<li><a tabindex="-1" data-value="kr_theme" >krTheme</a></li> <li><a tabindex="-1" href="#" data-value="kr_theme" >krTheme</a></li>
<li><a tabindex="-1" data-value="merbivore" >Merbivore</a></li> <li><a tabindex="-1" href="#" data-value="merbivore" >Merbivore</a></li>
<li><a tabindex="-1" data-value="merbivore_soft" >Merbivore Soft</a></li> <li><a tabindex="-1" href="#" data-value="merbivore_soft" >Merbivore Soft</a></li>
<li><a tabindex="-1" data-value="mono_industrial" >Mono Industrial</a></li> <li><a tabindex="-1" href="#" data-value="mono_industrial" >Mono Industrial</a></li>
<li><a tabindex="-1" data-value="monokai" >Monokai</a></li> <li><a tabindex="-1" href="#" data-value="monokai" >Monokai</a></li>
<li><a tabindex="-1" data-value="pastel_on_dark">Pastel on Dark</a></li> <li><a tabindex="-1" href="#" data-value="pastel_on_dark">Pastel on Dark</a></li>
<li><a tabindex="-1" data-value="solarized_dark" >Solarized Dark</a></li> <li><a tabindex="-1" href="#" data-value="solarized_dark" >Solarized Dark</a></li>
<li><a tabindex="-1" data-value="solarized_light" >Solarized Light</a></li> <li><a tabindex="-1" href="#" data-value="solarized_light" >Solarized Light</a></li>
<li><a tabindex="-1" data-value="textmate" >TextMate</a></li> <li><a tabindex="-1" href="#" data-value="textmate" >TextMate</a></li>
<li><a tabindex="-1" data-value="tomorrow" >Tomorrow</a></li> <li><a tabindex="-1" href="#" data-value="tomorrow" >Tomorrow</a></li>
<li><a tabindex="-1" data-value="tomorrow_night">Tomorrow Night</a></li> <li><a tabindex="-1" href="#" data-value="tomorrow_night">Tomorrow Night</a></li>
<li><a tabindex="-1" data-value="tomorrow_night_blue" >Tomorrow Night Blue</a></li> <li><a tabindex="-1" href="#" data-value="tomorrow_night_blue" >Tomorrow Night Blue</a></li>
<li><a tabindex="-1" data-value="tomorrow_night_bright" >Tomorrow Night Bright</a></li> <li><a tabindex="-1" href="#" data-value="tomorrow_night_bright" >Tomorrow Night Bright</a></li>
<li><a tabindex="-1" data-value="tomorrow_night_eighties" >Tomorrow Night 80s</a></li> <li><a tabindex="-1" href="#" data-value="tomorrow_night_eighties" >Tomorrow Night 80s</a></li>
<li><a tabindex="-1" data-value="twilight" >Twilight</a></li> <li><a tabindex="-1" href="#" data-value="twilight" >Twilight</a></li>
<li><a tabindex="-1" data-value="vibrant_ink" >Vibrant Ink</a></li> <li><a tabindex="-1" href="#" data-value="vibrant_ink" >Vibrant Ink</a></li>
</ul> </ul>
</div>
<div class="btn-group">
{% if name in config['LOCKED'] %} {% if name in config['LOCKED'] %}
<a class="btn btn-danger btn-sm"> <a class="btn btn-danger btn-sm">
<i class="fa fa-lock"></i> <i class="fa fa-lock"></i>
@ -295,33 +137,33 @@
{% else %} {% else %}
<a id="submit-btn" class="btn btn-primary btn-sm"> <a id="submit-btn" class="btn btn-primary btn-sm">
<i class="fa fa-save"></i> <i class="fa fa-save"></i>
<span class="hidden-xs">Save</span> <span class="hidden-xs">Publish</span>
</a> </a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
<section class="entry-markdown active"> <section class="entry-markdown active">
<header class="floatingheader" id="entry-markdown-header"> <header class="floatingheader" id="entry-markdown-header">
<small>Markdown</small> <small>Markdown</small>
<a class="markdown-help" href=""><span class="hidden">What is Markdown?</span></a> <a class="markdown-help" href=""><span class="hidden">What is Markdown?</span></a>
</header> </header>
<section id="entry-markdown-content" class="entry-markdown-content"> <section id="entry-markdown-content" class="entry-markdown-content">
<div id="editor-{{- name -}}" data-submitbtn='submit-btn' data-themeselect="theme-list" data-mode="markdown" data-preview="preview" class="editor">{{ content }}</div> <div id="editor-{{ name }}" data-submitbtn='submit-btn' data-themeselect="theme-list" data-mode="markdown"
data-preview="preview" class="editor">{{ content }}</div>
</section>
</section> </section>
</section>
<section class="entry-preview"> <section class="entry-preview">
<header class="floatingheader" id="entry-preview-header"> <header class="floatingheader" id="entry-preview-header">
<small>Preview</small> <small>Preview</small>
</header> </header>
<section class="entry-preview-content"> <section class="entry-preview-content">
<div id="preview"></div> <div id="preview"></div>
</section> </section>
</section> </section>
<input id="sha" type="hidden" name="sha" value="{{ sha }}" />
</div> </div>

View file

@ -8,7 +8,8 @@ with open('README.md') as f:
with open('requirements.txt') as f: with open('requirements.txt') as f:
required = f.read().splitlines() required = f.read().splitlines()
VERSION = '0.2.2' with open('VERSION') as f:
VERSION = f.read().strip()
CLASSIFIERS = [ CLASSIFIERS = [
'Intended Audience :: Developers', 'Intended Audience :: Developers',