diff --git a/README.md b/README.md
index 605c0ba..26694de 100644
--- a/README.md
+++ b/README.md
@@ -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.
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
@@ -130,6 +130,23 @@ Reload Nginx
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
Current there are different ways.
@@ -153,22 +170,19 @@ http://localhost:5000
## Templating
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
-
-
{{ heading }}
-
- {{ body }}
-
-
+This page contains the content of the partial:
-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
Matthew Scragg
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..9325c3c
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.3.0
\ No newline at end of file
diff --git a/install.sh b/install.sh
index 735f7c1..df8a398 100755
--- a/install.sh
+++ b/install.sh
@@ -23,7 +23,7 @@ sudo add-apt-repository -y ppa:chris-lea/node.js
sudo apt-get update
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 \
-libffi-dev nodejs libyaml-dev
+libffi-dev nodejs libyaml-dev libssl-dev
# Default cache is memoization
diff --git a/manage.py b/manage.py
index 3356023..32c9b8b 100755
--- a/manage.py
+++ b/manage.py
@@ -1,8 +1,10 @@
from gevent import wsgi
from realms import config, app, cli, db
from realms.lib.util import random_string
+from subprocess import call
import click
import json
+import sys
@cli.command()
@@ -77,6 +79,37 @@ def setup_redis(**kw):
conf[k.upper()] = v
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()
@@ -139,5 +172,13 @@ def drop_db():
click.echo("Dropping all tables")
db.drop_all()
+
+@cli.command()
+def version():
+ """ Output version
+ """
+ with open('VERSION') as f:
+ return f.read().strip()
+
if __name__ == '__main__':
cli()
\ No newline at end of file
diff --git a/realms/__init__.py b/realms/__init__.py
index c3e6726..a002903 100644
--- a/realms/__init__.py
+++ b/realms/__init__.py
@@ -192,7 +192,8 @@ assets.register('main.js',
'js/html-sanitizer-minified.js', # don't minify?
'vendor/highlightjs/highlight.pack.js',
'vendor/parsleyjs/dist/parsley.js',
- 'js/main.js')
+ 'js/hbs-helpers.js',
+ 'js/mdr.js')
assets.register('main.css',
'vendor/bootswatch-dist/css/bootstrap.css',
diff --git a/realms/config/__init__.py b/realms/config/__init__.py
index ab0ca49..f3c0416 100644
--- a/realms/config/__init__.py
+++ b/realms/config/__init__.py
@@ -38,7 +38,11 @@ PORT = 5000
BASE_URL = 'http://localhost'
SITE_TITLE = "Realms"
+# https://pythonhosted.org/Flask-SQLAlchemy/config.html#connection-uri-format
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'
diff --git a/realms/lib/util.py b/realms/lib/util.py
index bb8e97b..2dac2c1 100644
--- a/realms/lib/util.py
+++ b/realms/lib/util.py
@@ -91,6 +91,7 @@ def to_canonical(s):
s = re.sub(r"\-\-+", "-", s)
s = re.sub(r"[^a-zA-Z0-9\-]", "", s)
s = s[:64]
+ s = s.lower()
return s
diff --git a/realms/modules/auth/models.py b/realms/modules/auth/models.py
index 7cde5a4..ca1ab62 100644
--- a/realms/modules/auth/models.py
+++ b/realms/modules/auth/models.py
@@ -44,9 +44,9 @@ class AnonUser(AnonymousUserMixin):
class User(Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
- username = db.Column(db.String, unique=True)
- email = db.Column(db.String, unique=True)
- password = db.Column(db.String)
+ username = db.Column(db.String(128), unique=True)
+ email = db.Column(db.String(128), unique=True)
+ password = db.Column(db.String(60))
admin = False
hidden_fields = ['password']
diff --git a/realms/modules/wiki/assets.py b/realms/modules/wiki/assets.py
index 31cd839..8b2162d 100644
--- a/realms/modules/wiki/assets.py
+++ b/realms/modules/wiki/assets.py
@@ -7,4 +7,4 @@ assets.register('editor.js',
'vendor/ace-builds/src/mode-markdown.js',
'vendor/ace-builds/src/ext-keybinding_menu.js',
'vendor/keymaster/keymaster.js',
- 'js/editor.js')
+ 'js/aced.js')
diff --git a/realms/modules/wiki/views.py b/realms/modules/wiki/views.py
index 4be981b..e1247d0 100644
--- a/realms/modules/wiki/views.py
+++ b/realms/modules/wiki/views.py
@@ -36,7 +36,7 @@ def revert():
commit = request.form.get('commit')
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")
return redirect(url_for(app.config['ROOT_ENDPOINT']))
@@ -59,10 +59,10 @@ def edit(name):
if request.method == 'POST':
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']))
- if edit_cname.lower() != cname.lower():
+ if edit_cname != cname.lower():
g.current_wiki.rename_page(cname, edit_cname)
g.current_wiki.write_page(edit_cname,
diff --git a/realms/static/css/style.css b/realms/static/css/style.css
index 7c3c64a..0709810 100644
--- a/realms/static/css/style.css
+++ b/realms/static/css/style.css
@@ -58,9 +58,9 @@
#app-wrap {
top: 60px;
- left: -5px;
+ left: 0;
bottom: 0;
- right: -5px;
+ right: 0;
position: fixed;
}
@@ -186,9 +186,8 @@ a.label {
right: 0;
bottom: 0;
left: 0;
- padding: 45px 10px 10px 10px;
+ padding: 40px 10px 10px 10px;
overflow: auto;
- //word-break: break-word;
cursor: default;
}
diff --git a/realms/static/js/aced.js b/realms/static/js/aced.js
new file mode 100644
index 0000000..d4bb3ad
--- /dev/null
+++ b/realms/static/js/aced.js
@@ -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 = $("");
+ $sel.append('');
+ $.each(themes, function(k, v) {
+ $sel.append("");
+ });
+ return $("").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 = $('
' + buildThemeSelect().html() + '
')
+ 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
+ };
+}
\ No newline at end of file
diff --git a/realms/static/js/collaboration/firepad.js b/realms/static/js/collaboration/firepad.js
new file mode 100644
index 0000000..ed6cab9
--- /dev/null
+++ b/realms/static/js/collaboration/firepad.js
@@ -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();
+ }
+});
\ No newline at end of file
diff --git a/realms/static/js/collaboration/main.js b/realms/static/js/collaboration/main.js
new file mode 100644
index 0000000..c8f89a5
--- /dev/null
+++ b/realms/static/js/collaboration/main.js
@@ -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();
+
+ });
+});
diff --git a/realms/static/js/collaboration/togetherjs.js b/realms/static/js/collaboration/togetherjs.js
new file mode 100644
index 0000000..f91440a
--- /dev/null
+++ b/realms/static/js/collaboration/togetherjs.js
@@ -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();
+};
\ No newline at end of file
diff --git a/realms/static/js/editor.js b/realms/static/js/editor.js
index d4bb3ad..1c3635d 100644
--- a/realms/static/js/editor.js
+++ b/realms/static/js/editor.js
@@ -1,430 +1,77 @@
-function Aced(settings) {
- var id,
- options,
- editor,
- element,
- preview,
- previewWrapper,
- profile,
- autoInterval,
- themes,
- themeSelect,
- loadedThemes = {};
+var $entry_markdown_header = $("#entry-markdown-header");
+var $entry_preview_header = $("#entry-preview-header");
+var $entry_markdown = $(".entry-markdown");
+var $entry_preview = $(".entry-preview");
- settings = settings || {};
+// Tabs
+$entry_markdown_header.click(function(){
+ $entry_markdown.addClass('active');
+ $entry_preview.removeClass('active');
+});
- 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
- };
+$entry_preview_header.click(function(){
+ $entry_preview.addClass('active');
+ $entry_markdown.removeClass('active');
+});
- 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 = $("");
- $sel.append('');
- $.each(themes, function(k, v) {
- $sel.append("");
- });
- return $("").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"
+$(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);
+ }
},
- 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 = $('
' + buildThemeSelect().html() + '
')
- 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');
+ 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");
}
}
}
+ })
+});
- $(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
- };
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/realms/static/js/hbs-helpers.js b/realms/static/js/hbs-helpers.js
new file mode 100644
index 0000000..62840b2
--- /dev/null
+++ b/realms/static/js/hbs-helpers.js
@@ -0,0 +1,12 @@
+// Handlebar helpers
+Handlebars.registerHelper('well', function(options) {
+ return '