diff --git a/README.md b/README.md
index a4ca93a..605c0ba 100644
--- a/README.md
+++ b/README.md
@@ -150,7 +150,25 @@ Access from your browser
http://localhost:5000
+## Templating
+Realms uses handlebars partials to create templates.
+Each page that you create is a potential partial.
+
+Let's create the following partial:
+
+http://localhost:5000/_create/my-partial
+
+
+
{{ heading }}
+
+ {{ body }}
+
+
+
+Don't forget to save it.
+
+Now
## Author
Matthew Scragg
diff --git a/bower.json b/bower.json
index d06f5a0..2c0846e 100644
--- a/bower.json
+++ b/bower.json
@@ -1,18 +1,19 @@
{
- "name": "realms",
- "version": "0.1.2",
- "dependencies": {
- "components-bootstrap": "~3.2.0",
- "components-font-awesome": "~4.2.0",
- "jquery": "~1.11.1",
- "highlightjs": "~8.0.0",
- "handlebars": "~2.0.0",
- "keymaster": "madrobby/keymaster",
- "ace-builds": "~1.1.6",
- "parsleyjs": "~2.0.3",
- "marked": "~0.3.2",
- "js-yaml": "~3.2.1",
- "localforage": "~0.9.2",
- "bootswatch-dist": "3.2.0-flatly"
- }
+ "name": "realms",
+ "version": "0.1.2",
+ "dependencies": {
+ "components-bootstrap": "~3.2.0",
+ "components-font-awesome": "~4.2.0",
+ "jquery": "~1.11.1",
+ "highlightjs": "~8.0.0",
+ "handlebars": "~2.0.0",
+ "keymaster": "madrobby/keymaster",
+ "ace-builds": "~1.1.6",
+ "parsleyjs": "~2.0.3",
+ "marked": "~0.3.2",
+ "js-yaml": "~3.2.1",
+ "store-js": "~1.3.16",
+ "bootswatch-dist": "3.2.0-flatly",
+ "bootbox": "4.3.0"
+ }
}
\ No newline at end of file
diff --git a/realms/config/__init__.py b/realms/config/__init__.py
index f2cd42c..ab0ca49 100644
--- a/realms/config/__init__.py
+++ b/realms/config/__init__.py
@@ -52,7 +52,6 @@ CACHE_REDIS_DB = '0'
#CACHE_TYPE = 'memcached'
CACHE_MEMCACHED_SERVERS = ['127.0.0.1:11211']
-
# Get ReCaptcha Keys for your domain here:
# https://www.google.com/recaptcha/admin#whyrecaptcha
RECAPTCHA_ENABLE = False
@@ -75,6 +74,12 @@ REGISTRATION_ENABLED = True
# Used by Flask-Login
LOGIN_DISABLED = ALLOW_ANON
+# None, firepad, or togetherjs
+COLLABORATION = 'togetherjs'
+
+# Required for firepad
+FIREBASE_HOSTNAME = None
+
# Page names that can't be modified
WIKI_LOCKED_PAGES = []
# Depreciated variable name
diff --git a/realms/modules/wiki/assets.py b/realms/modules/wiki/assets.py
index 270bf61..31cd839 100644
--- a/realms/modules/wiki/assets.py
+++ b/realms/modules/wiki/assets.py
@@ -1,7 +1,8 @@
from realms import assets
assets.register('editor.js',
- 'vendor/localforage/dist/localforage.js',
+ 'vendor/store-js/store.js',
+ 'vendor/bootbox/bootbox.js',
'vendor/ace-builds/src/ace.js',
'vendor/ace-builds/src/mode-markdown.js',
'vendor/ace-builds/src/ext-keybinding_menu.js',
diff --git a/realms/modules/wiki/models.py b/realms/modules/wiki/models.py
index affacd2..9d2c8e4 100644
--- a/realms/modules/wiki/models.py
+++ b/realms/modules/wiki/models.py
@@ -139,11 +139,11 @@ class Wiki():
return cached
# commit = gittle.utils.git.commit_info(self.repo[sha])
- name = self.cname_to_filename(name).encode('latin-1')
+ filename = self.cname_to_filename(name).encode('latin-1')
sha = sha.encode('latin-1')
try:
- data = self.gittle.get_commit_files(sha, paths=[name]).get(name)
+ data = self.gittle.get_commit_files(sha, paths=[filename]).get(filename)
if not data:
return None
partials = {}
@@ -153,6 +153,7 @@ class Wiki():
for partial_name in meta['import']:
partials[partial_name] = self.get_page(partial_name)
data['partials'] = partials
+ data['info'] = self.get_history(name, limit=1)[0]
return data
except KeyError:
@@ -175,10 +176,10 @@ class Wiki():
new = self.get_page(name, sha=new_sha)
return ghdiff.diff(old['data'], new['data'])
- def get_history(self, name):
+ def get_history(self, name, limit=100):
file_path = self.cname_to_filename(name)
versions = []
- walker = self.repo.get_walker(paths=[file_path], max_entries=100)
+ walker = self.repo.get_walker(paths=[file_path], max_entries=limit)
for entry in walker:
change_type = None
for change in entry.changes():
diff --git a/realms/modules/wiki/views.py b/realms/modules/wiki/views.py
index 4eb9cb3..4be981b 100644
--- a/realms/modules/wiki/views.py
+++ b/realms/modules/wiki/views.py
@@ -74,7 +74,12 @@ def edit(name):
name = remove_ext(data['name'])
content = data.get('data')
g.assets['js'].append('editor.js')
- return render_template('wiki/edit.html', name=name, content=content, sha=data.get('sha'), partials=data.get('partials'))
+ return render_template('wiki/edit.html',
+ name=name,
+ content=content,
+ info=data.get('info'),
+ sha=data.get('sha'),
+ partials=data.get('partials'))
else:
return redirect(url_for('wiki.create', name=cname))
@@ -110,7 +115,10 @@ def create(name):
return redirect(url_for('wiki.edit', name=cname))
g.assets['js'].append('editor.js')
- return render_template('wiki/edit.html', name=cname, content="")
+ return render_template('wiki/edit.html',
+ name=cname,
+ content="",
+ info={})
@blueprint.route("/", defaults={'name': 'home'})
diff --git a/realms/static/css/style.css b/realms/static/css/style.css
index d337b4c..7c3c64a 100644
--- a/realms/static/css/style.css
+++ b/realms/static/css/style.css
@@ -186,7 +186,7 @@ a.label {
right: 0;
bottom: 0;
left: 0;
- padding: 40px 10px 10px 10px;
+ padding: 45px 10px 10px 10px;
overflow: auto;
//word-break: break-word;
cursor: default;
@@ -217,7 +217,7 @@ a.label {
background-color: #eee;
}
-#editor {
+.editor {
margin-top: 40px;
}
@@ -226,7 +226,7 @@ a.label {
padding: 3px;
}
- #editor {
+ .editor {
margin-top: 0;
}
diff --git a/realms/static/js/editor.js b/realms/static/js/editor.js
index db7d9ad..d4bb3ad 100644
--- a/realms/static/js/editor.js
+++ b/realms/static/js/editor.js
@@ -1,59 +1,245 @@
-/*
- Source is modified version of http://dillinger.io/
- */
-$(function () {
+function Aced(settings) {
+ var id,
+ options,
+ editor,
+ element,
+ preview,
+ previewWrapper,
+ profile,
+ autoInterval,
+ themes,
+ themeSelect,
+ loadedThemes = {};
- var url_prefix = "";
- var sha = $("#sha").text();
- var $theme = $('#theme-list');
- var $preview = $('#preview');
- var $autosave = $('#autosave');
- var $wordcount = $('#wordcount');
- var $wordcounter = $('#wordcounter');
- var $pagename = $("#page-name");
+ settings = settings || {};
- var $entry_markdown_header = $("#entry-markdown-header");
- var $entry_preview_header = $("#entry-preview-header");
-
- // 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');
- });
-
-
- var editor;
- var autoInterval;
- var profile = {
- theme: 'ace/theme/idle_fingers',
- currentMd: '',
- autosave: {
- enabled: true,
- interval: 3000 // might be too aggressive; don't want to block UI for large saves.
- },
- current_filename: $pagename.val()
+ 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
};
- // Feature detect ish
- var dillinger = 'dillinger';
- var dillingerElem = document.createElement(dillinger);
- var dillingerStyle = dillingerElem.style;
- var domPrefixes = 'Webkit Moz O ms Khtml'.split(' ');
+ 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"
+ };
- /// UTILS =================
+ 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);
+ }
- /**
- * Utility method to async load a JavaScript file.
- *
- * @param {String} The name of the file to load
- * @param {Function} Optional callback to be executed after the script loads.
- * @return {void}
- */
function asyncLoad(filename, cb) {
(function (d, t) {
@@ -71,459 +257,174 @@ $(function () {
}(document, 'script'));
}
- /**
- * Grab the user's profile from localStorage and stash in "profile" variable.
- *
- * @return {Void}
- */
- function getUserProfile() {
- localforage.getItem('profile', function(p) {
- profile = $.extend(true, profile, p);
- if (profile.filename != $pagename.val()) {
- setEditorValue("");
- updateUserProfile({ filename: $pagename.val(), currentMd: "" });
+ 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 {
- if (profile.currentMd) {
- setEditorValue(profile.currentMd);
+ // 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');
}
}
- });
- }
-
- /**
- * Update user's profile in localStorage by merging in current profile with passed in param.
- *
- * @param {Object} An object containg proper keys and values to be JSON.stringify'd
- * @return {Void}
- */
- function updateUserProfile(obj) {
- localforage.clear();
- localforage.setItem('profile', $.extend(true, profile, obj));
- }
-
- /**
- * Utility method to test if particular property is supported by the browser or not.
- * Completely ripped from Modernizr with some mods.
- * Thx, Modernizr team!
- *
- * @param {String} The property to test
- * @return {Boolean}
- */
- function prefixed(prop) {
- return testPropsAll(prop, 'pfx')
- }
-
- /**
- * A generic CSS / DOM property test; if a browser supports
- * a certain property, it won't return undefined for it.
- * A supported CSS property returns empty string when its not yet set.
- *
- * @param {Object} A hash of properties to test
- * @param {String} A prefix
- * @return {Boolean}
- */
- function testProps(props, prefixed) {
-
- for (var i in props) {
-
- if (dillingerStyle[ props[i] ] !== undefined) {
- return prefixed === 'pfx' ? props[i] : true;
- }
-
- }
- return false
- }
-
- /**
- * Tests a list of DOM properties we want to check against.
- * We specify literally ALL possible (known and/or likely) properties on
- * the element including the non-vendor prefixed one, for forward-
- * compatibility.
- *
- * @param {String} The name of the property
- * @param {String} The prefix string
- * @return {Boolean}
- */
- function testPropsAll(prop, prefixed) {
-
- var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1)
- , props = (prop + ' ' + domPrefixes.join(ucProp + ' ') + ucProp).split(' ');
-
- return testProps(props, prefixed);
- }
-
- /**
- * Normalize the transitionEnd event across browsers.
- *
- * @return {String}
- */
- function normalizeTransitionEnd() {
-
- var transEndEventNames =
- {
- 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'msTransitionEnd' // maybe?
- , 'transition': 'transitionend'
- };
-
- return transEndEventNames[ prefixed('transition') ];
- }
-
-
- /**
- * Returns the full text of an element and all its children.
- * The script recursively traverses all text nodes, and returns a
- * concatenated string of all texts.
- *
- * Taken from
- * http://stackoverflow.com/questions/2653670/innertext-textcontent-vs-retrieving-each-text-node
- *
- * @param node
- * @return {int}
- */
- function getTextInElement(node) {
- if (node.nodeType === 3) {
- return node.data;
}
- var txt = '';
-
- if (node = node.firstChild) do {
- txt += getTextInElement(node);
- } while (node = node.nextSibling);
-
- return txt;
+ $(this).trigger('ready');
}
- /**
- * Counts the words in a string
- *
- * @param string
- * @return int
- */
- function countWords(string) {
- var words = string.replace(/W+/g, ' ').match(/\S+/g);
- return words && words.length || 0;
- }
-
-
- /**
- * Initialize application.
- *
- * @return {Void}
- */
function init() {
- // Attach to jQuery support object for later use.
- $.support.transitionEnd = normalizeTransitionEnd();
-
- initAce();
-
- getUserProfile();
-
- initUi();
-
- bindPreview();
-
- bindNav();
-
- bindKeyboard();
-
+ gc();
+ initProfile();
+ initProps();
+ initEditor();
+ initSyncPreview();
autoSave();
}
- function initAce() {
- editor = ace.edit("editor");
- editor.focus();
- editor.setOptions({
- enableBasicAutocompletion: true
- });
- }
-
- function initUi() {
- // Set proper theme value in theme dropdown
- fetchTheme(profile.theme, function () {
- $theme.find('li > a[data-value="' + profile.theme + '"]').addClass('selected');
-
- editor.setBehavioursEnabled(true);
- editor.getSession().setUseWrapMode(true);
- editor.setShowPrintMargin(false);
- editor.getSession().setTabSize(2);
- editor.getSession().setUseSoftTabs(true);
- editor.renderer.setShowInvisibles(true);
- editor.renderer.setShowGutter(false);
- editor.getSession().setMode('ace/mode/markdown');
- setEditorValue(profile.currentMd || editor.getSession().getValue());
- previewMd();
- });
-
-
- // Set text for dis/enable autosave / word counter
- $autosave.html(profile.autosave.enabled ? ' Disable Autosave' : ' Enable Autosave');
- $wordcount.html(!profile.wordcount ? ' Disabled Word Count' : ' Enabled Word Count');
-
- $('.dropdown-toggle').dropdown();
- }
-
-
- function clearSelection() {
- setEditorValue("");
- previewMd();
- }
-
- function saveFile(isManual) {
-
- updateUserProfile({currentMd: editor.getSession().getValue()});
-
- if (isManual) {
- updateUserProfile({ currentMd: "" });
-
- var data = {
- name: $pagename.val(),
- message: $("#page-message").val(),
- content: editor.getSession().getValue()
- };
- $.post(window.location, data, function() {
- location.href = url_prefix + '/' + data['name'];
- });
- }
-
- }
-
- function autoSave() {
-
- if (profile.autosave.enabled) {
- autoInterval = setInterval(function() {
- saveFile();
- }, profile.autosave.interval);
-
- } else {
- clearInterval(autoInterval)
- }
-
- }
-
- $("#save-native").on('click', function() {
- saveFile(true);
- });
-
-
- function resetProfile() {
- // For some reason, clear() is not working in Chrome.
- localforage.clear();
-
- // Let's turn off autosave
- profile.autosave.enabled = false;
- localforage.removeItem('profile', function() {
- window.location.reload();
- });
- }
-
- function changeTheme(e) {
- // check for same theme
- var $target = $(e.target);
- if ($target.attr('data-value') === profile.theme) {
- return;
- }
- else {
- // add/remove class
- $theme.find('li > a.selected').removeClass('selected');
- $target.addClass('selected');
- // grabnew theme
- var newTheme = $target.attr('data-value');
- $(e.target).blur();
- fetchTheme(newTheme, function () {
-
- });
- }
- }
-
- function fetchTheme(th, cb) {
- var name = th.split('/').pop();
- asyncLoad("/static/vendor/ace-builds/src/theme-" + name + ".js", function () {
- editor.setTheme(th);
- cb && cb();
- updateBg(name);
- updateUserProfile({theme: th});
- });
-
- }
-
- function updateBg(name) {
- // document.body.style.backgroundColor = bgColors[name]
- }
-
- function setEditorValue(str) {
- editor.getSession().setValue(str);
- }
-
- function previewMd() {
- $preview.html(MDR.convert(editor.getSession().getValue(), true));
- }
-
- function updateFilename(str) {
- // Check for string because it may be keyup event object
- var f;
- if (typeof str === 'string') {
- f = str;
- } else {
- f = getCurrentFilenameFromField();
- }
- updateUserProfile({ current_filename: f });
- }
-
-
- function showHtml() {
-
- // TODO: UPDATE TO SUPPORT FILENAME NOT JUST A RANDOM FILENAME
-
- var unmd = editor.getSession().getValue();
-
- function _doneHandler(jqXHR, data, response) {
- // console.dir(resp)
- var resp = JSON.parse(response.responseText);
- $('#myModalBody').text(resp.data);
- $('#myModal').modal();
- }
-
- function _failHandler() {
- alert("Roh-roh. Something went wrong. :(");
- }
-
- var config = {
- type: 'POST',
- data: "unmd=" + encodeURIComponent(unmd),
- dataType: 'json',
- url: '/factory/fetch_html_direct',
- error: _failHandler,
- success: _doneHandler
- };
-
- $.ajax(config)
-
- }
-
- function toggleAutoSave() {
- $autosave.html(profile.autosave.enabled ? ' Disable Autosave' : ' Enable Autosave');
- updateUserProfile({autosave: {enabled: !profile.autosave.enabled }});
- autoSave();
- }
-
- function bindPreview() {
- editor.getSession().on('change', function (e) {
- previewMd();
- });
- }
-
- function bindNav() {
-
- $theme
- .find('li > a')
- .bind('click', function (e) {
- changeTheme(e);
- return false;
- });
-
- $('#clear')
- .on('click', function () {
- clearSelection();
- return false;
- });
-
- $("#autosave")
- .on('click', function () {
- toggleAutoSave();
- return false;
- });
-
- $('#reset')
- .on('click', function () {
- resetProfile();
- return false;
- });
-
- $('#cheat').
- on('click', function () {
- window.open("https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet", "_blank");
- return false;
- });
-
- } // end bindNav()
-
-
- function bindKeyboard() {
- // CMD+s TO SAVE DOC
- key('command+s, ctrl+s', function (e) {
- saveFile(true);
- e.preventDefault(); // so we don't save the web page - native browser functionality
- });
-
- var saveCommand = {
- name: "save",
- bindKey: {
- mac: "Command-S",
- win: "Ctrl-S"
- },
- exec: function () {
- saveFile(true);
- }
- };
-
- editor.commands.addCommand(saveCommand);
- }
-
init();
-});
-
-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 syncPreview() {
- var $ed = window.ace.edit('editor');
- var $prev = $('#preview');
-
- var editorScrollRange = ($ed.getSession().getLength());
-
- var previewScrollRange = (getScrollHeight($prev));
-
- // Find how far along the editor is (0 means it is scrolled to the top, 1
- // means it is at the bottom).
- var scrollFactor = $ed.getFirstVisibleRow() / editorScrollRange;
-
- // Set the scroll position of the preview pane to match. jQuery will
- // gracefully handle out-of-bounds values.
- $prev.parent().scrollTop(scrollFactor * previewScrollRange);
-}
-
-window.onload = function () {
- var $loading = $('#loading');
-
- if ($.support.transition) {
- $loading
- .bind($.support.transitionEnd, function () {
- $('#main').removeClass('bye');
- $loading.remove();
- })
- .addClass('fade_slow');
- } else {
- $('#main').removeClass('bye');
- $loading.remove();
- }
-
- /**
- * Bind synchronization of preview div to editor scroll and change
- * of editor cursor position.
- */
- window.ace.edit('editor').session.on('changeScrollTop', syncPreview);
- window.ace.edit('editor').session.selection.on('changeCursor', syncPreview);
-};
\ No newline at end of file
+ return {
+ editor: editor,
+ submit: submit,
+ val: val,
+ discard: discardDraft,
+ info: info
+ };
+}
\ No newline at end of file
diff --git a/realms/static/js/main.js b/realms/static/js/main.js
index 3a8a80f..4023f6d 100644
--- a/realms/static/js/main.js
+++ b/realms/static/js/main.js
@@ -8,47 +8,47 @@ Handlebars.registerHelper('well', function(options) {
// Splits the given string into a meta section and a markdown section if a meta section is present, else returns null
function splitInput(str) {
- if (str.slice(0, 3) !== '---') return;
+ if (str.slice(0, 3) !== '---') return;
- var matcher = /\n(\.{3}|\-{3})/g;
- var metaEnd = matcher.exec(str);
+ var matcher = /\n(\.{3}|\-{3})/g;
+ var metaEnd = matcher.exec(str);
- return metaEnd && [str.slice(0, metaEnd.index), str.slice(matcher.lastIndex)];
+ return metaEnd && [str.slice(0, metaEnd.index), str.slice(matcher.lastIndex)];
}
var metaMarked = function(src, opt, callback) {
- if (Object.prototype.toString.call(src) !== '[object String]')
- throw new TypeError('First parameter must be a string.');
+ if (Object.prototype.toString.call(src) !== '[object String]')
+ throw new TypeError('First parameter must be a string.');
- var mySplitInput = splitInput(src);
- if (mySplitInput) {
- var meta;
- try {
- meta = jsyaml.safeLoad(mySplitInput[0]);
- } catch(e) {
- meta = null;
- }
- return {
- meta: meta,
- md: mySplitInput[1]
- };
- } else {
- return {
- meta: null,
- md: src
- }
+ var mySplitInput = splitInput(src);
+ if (mySplitInput) {
+ var meta;
+ try {
+ meta = jsyaml.safeLoad(mySplitInput[0]);
+ } catch(e) {
+ meta = null;
}
+ return {
+ meta: meta,
+ md: mySplitInput[1]
+ };
+ } else {
+ return {
+ meta: null,
+ md: src
+ }
+ }
};
marked.setOptions({
- renderer: new marked.Renderer(),
- gfm: true,
- tables: true,
- breaks: false,
- pedantic: false,
- sanitize: false,
- smartLists: true,
- smartypants: false
+ renderer: new marked.Renderer(),
+ gfm: true,
+ tables: true,
+ breaks: false,
+ pedantic: false,
+ sanitize: false,
+ smartLists: true,
+ smartypants: false
});
// Init highlight JS
@@ -56,56 +56,54 @@ hljs.initHighlightingOnLoad();
// Markdown Renderer
var MDR = {
- meta: null,
- md: null,
- sanitize: false, // Override
- parse: function(md){ return marked(md); },
- convert: function(md, sanitize){
- if (this.sanitize !== null) {
- sanitize = this.sanitize;
- }
- this.md = md;
- this.processMeta();
- try {
- var html = this.parse(this.md);
- } catch(e) {
- return this.md;
- }
-
- if (sanitize) {
- // Causes some problems with inline styles
- html = html_sanitize(html, function(url) {
- try {
- var prot = decodeURIComponent(unescape(url))
- .replace(/[^\w:]/g, '')
- .toLowerCase();
- } catch (e) {
- return '';
- }
- if (prot.indexOf('javascript:') === 0) {
- return '';
- }
- return prot;
- }, function(id){
- return id;
- });
- }
- this.hook();
- return html;
- },
-
- processMeta: function() {
- var doc = metaMarked(this.md);
- this.md = doc.md;
- this.meta = doc.meta;
- if (this.meta) {
- try {
- var template = Handlebars.compile(this.md);
- this.md = template(this.meta);
- } catch(e) {}
- }
- },
-
- hook: function() {
+ meta: null,
+ md: null,
+ sanitize: true, // Override
+ parse: function(md){ return marked(md); },
+ convert: function(md, sanitize) {
+ if (this.sanitize !== null) {
+ sanitize = this.sanitize;
}
+ this.md = md;
+ this.processMeta();
+ try {
+ var html = this.parse(this.md);
+ } catch(e) {
+ return this.md;
+ }
+
+ if (sanitize) {
+ // Causes some problems with inline styles
+ html = html_sanitize(html, function(url) {
+ try {
+ var prot = decodeURIComponent(url.toString());
+ } catch (e) {
+ return '';
+ }
+ if (prot.indexOf('javascript:') === 0) {
+ return '';
+ }
+ return prot;
+ }, function(id){
+ return id;
+ });
+ }
+ this.hook();
+ return html;
+ },
+
+ processMeta: function() {
+ var doc = metaMarked(this.md);
+ this.md = doc.md;
+ this.meta = doc.meta;
+ if (this.meta) {
+ try {
+ var template = Handlebars.compile(this.md);
+ this.md = template(this.meta);
+ } catch(e) {}
+ }
+ },
+
+ hook: function() {
+ }
};
diff --git a/realms/templates/wiki/edit.html b/realms/templates/wiki/edit.html
index 7590888..38e7d83 100644
--- a/realms/templates/wiki/edit.html
+++ b/realms/templates/wiki/edit.html
@@ -1,45 +1,224 @@
{% extends 'layout.html' %}
{% block js %}
-
+
+ {% if partials %}
+
+ {% endif %}
+
+ {% if config.get('COLLABORATION') %}
+
+ {% endif %}
+
+ {% if config.get('COLLABORATION') == 'firepad' %}
+
+
+
+ {% endif %}
+
+ {% if config.get('COLLABORATION') == 'togetherjs' %}
+
-
+
+
+ {% endif %}
+
{% endblock %}
{% block body %}
@@ -55,51 +234,66 @@