From d72ecf10f03178095146d669eefec5c2fb796c0f Mon Sep 17 00:00:00 2001 From: Matthew Scragg Date: Wed, 1 Oct 2014 17:14:54 -0500 Subject: [PATCH] firepad + others WIP --- README.md | 18 + bower.json | 33 +- realms/config/__init__.py | 7 +- realms/modules/wiki/assets.py | 3 +- realms/modules/wiki/models.py | 9 +- realms/modules/wiki/views.py | 12 +- realms/static/css/style.css | 6 +- realms/static/js/editor.js | 887 ++++++++++++++------------------ realms/static/js/main.js | 162 +++--- realms/templates/wiki/edit.html | 292 +++++++++-- 10 files changed, 778 insertions(+), 651 deletions(-) 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 @@