Merge branch 'master' into partials-update
This commit is contained in:
commit
f47af0ecae
33 changed files with 46 additions and 43 deletions
|
@ -1,6 +1,6 @@
|
|||
from realms import login_manager
|
||||
from flask import request, flash, redirect
|
||||
from flask.ext.login import login_url
|
||||
from flask_login import login_url
|
||||
|
||||
modules = set()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import render_template
|
||||
from flask.ext.login import login_user
|
||||
from flask_login import login_user
|
||||
from realms import ldap
|
||||
from flask_ldap_login import LDAPLoginForm
|
||||
from ..models import BaseUser
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import current_app, render_template
|
||||
from flask.ext.login import logout_user, login_user
|
||||
from flask_login import logout_user, login_user
|
||||
from realms import login_manager, db
|
||||
from realms.lib.model import Model
|
||||
from ..models import BaseUser
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import current_app
|
||||
from flask.ext.login import UserMixin, logout_user, AnonymousUserMixin
|
||||
from flask_login import UserMixin, logout_user, AnonymousUserMixin
|
||||
from realms import login_manager
|
||||
from realms.lib.util import gravatar_url
|
||||
from itsdangerous import URLSafeSerializer, BadSignature
|
||||
|
|
29
realms/modules/auth/templates/auth/ldap/login.html
Normal file
29
realms/modules/auth/templates/auth/ldap/login.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% if config.get('AUTH_LOCAL_ENABLE') %}
|
||||
<button type="button" class="btn btn-info" data-toggle="modal" data-target="#ldap-modal">
|
||||
<i class="fa fa-folder-open-o"></i> Login with LDAP
|
||||
</button>
|
||||
|
||||
<div class="modal fade" id="ldap-modal" tabindex="-1" role="dialog" aria-labelledby="ldap-login">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="ldap-login">LDAP Login</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% call render_form(form, action_url=url_for('auth.ldap.login'), action_text='Login', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.username, placeholder='Username', type='text', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', required=1) }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<h3><i class="fa fa-folder-open-o"></i> LDAP Login</h3>
|
||||
{% call render_form(form, action_url=url_for('auth.ldap.login'), action_text='Login', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.username, placeholder='Username', type='text', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', required=1) }}
|
||||
{% endcall %}
|
||||
{% endif %}
|
5
realms/modules/auth/templates/auth/local/login.html
Normal file
5
realms/modules/auth/templates/auth/local/login.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% call render_form(form, action_url=url_for('auth.local.login'), action_text='Login', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.email, placeholder='Email', type='email', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', required=1) }}
|
||||
{% endcall %}
|
4
realms/modules/auth/templates/auth/login.html
Normal file
4
realms/modules/auth/templates/auth/login.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
{{ forms|safe }}
|
||||
{% endblock %}
|
13
realms/modules/auth/templates/auth/register.html
Normal file
13
realms/modules/auth/templates/auth/register.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% block body %}
|
||||
{% call render_form(form, action_url=url_for('auth.local.register'), action_text='Register', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.username, placeholder='Username', type='username', **{"required": 1, "data-parsley-type": "alphanum"}) }}
|
||||
{{ render_field(form.email, placeholder='Email', type='email', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', **{"required": 1, "data-parsley-minlength": "6"}) }}
|
||||
{{ render_field(form.confirm, placeholder='Confirm Password', type='password', **{"required": 1, "data-parsley-minlength": "6"}) }}
|
||||
{% if config.RECAPTCHA_ENABLE %}
|
||||
{{ render_field(form.recaptcha) }}
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
5
realms/modules/auth/templates/auth/settings.html
Normal file
5
realms/modules/auth/templates/auth/settings.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% block body %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,8 +1,8 @@
|
|||
from flask import current_app, render_template, request, redirect, Blueprint, flash, url_for
|
||||
from flask.ext.login import logout_user
|
||||
from flask_login import logout_user
|
||||
from realms.modules.auth.models import Auth
|
||||
|
||||
blueprint = Blueprint('auth', __name__)
|
||||
blueprint = Blueprint('auth', __name__, template_folder='templates')
|
||||
|
||||
|
||||
@blueprint.route("/login", methods=['GET', 'POST'])
|
||||
|
|
|
@ -14,7 +14,7 @@ def whoosh(app):
|
|||
|
||||
|
||||
def elasticsearch(app):
|
||||
from flask.ext.elastic import Elastic
|
||||
from flask_elastic import Elastic
|
||||
fields = app.config.get('ELASTICSEARCH_FIELDS')
|
||||
return ElasticSearch(Elastic(app), fields)
|
||||
|
||||
|
|
18
realms/modules/search/templates/search/search.html
Normal file
18
realms/modules/search/templates/search/search.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
{% if results %}
|
||||
<h3>Results for <em class="text-info">{{ request.args.get('q') }}</em></h3>
|
||||
<div class="list-group">
|
||||
{% for r in results %}
|
||||
<a href="{{ url_for('wiki.page', name=r['name']) }}" class="list-group-item">
|
||||
<h4 class="list-group-item-heading">{{ r['name'] }}</h4>
|
||||
<p class="list-group-item-text">
|
||||
{{ r['content'][:100] }}
|
||||
</p>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<h3>No results found for <em class="text-info">{{ request.args.get('q') }}</em></h3>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,10 +1,14 @@
|
|||
from flask import render_template, request, Blueprint
|
||||
from flask import render_template, request, Blueprint, current_app
|
||||
from flask.ext.login import current_user
|
||||
from realms import search as search_engine
|
||||
|
||||
blueprint = Blueprint('search', __name__)
|
||||
blueprint = Blueprint('search', __name__, template_folder='templates')
|
||||
|
||||
|
||||
@blueprint.route('/_search')
|
||||
def search():
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||
return current_app.login_manager.unauthorized()
|
||||
|
||||
results = search_engine.wiki(request.args.get('q'))
|
||||
return render_template('search/search.html', results=results)
|
||||
|
|
|
@ -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/aced.js')
|
||||
'wiki/js/aced.js')
|
||||
|
|
430
realms/modules/wiki/static/js/aced.js
Normal file
430
realms/modules/wiki/static/js/aced.js
Normal file
|
@ -0,0 +1,430 @@
|
|||
function Aced(settings) {
|
||||
var id,
|
||||
options,
|
||||
editor,
|
||||
element,
|
||||
preview,
|
||||
previewWrapper,
|
||||
profile,
|
||||
autoInterval,
|
||||
themes,
|
||||
themeSelect,
|
||||
loadedThemes = {};
|
||||
|
||||
settings = settings || {};
|
||||
|
||||
options = {
|
||||
sanitize: true,
|
||||
preview: null,
|
||||
editor: null,
|
||||
theme: 'idle_fingers',
|
||||
themePath: '/static/vendor/ace-builds/src',
|
||||
mode: 'markdown',
|
||||
autoSave: true,
|
||||
autoSaveInterval: 5000,
|
||||
syncPreview: false,
|
||||
keyMaster: false,
|
||||
submit: function(data){ alert(data); },
|
||||
showButtonBar: false,
|
||||
themeSelect: null,
|
||||
submitBtn: null,
|
||||
renderer: null,
|
||||
info: null
|
||||
};
|
||||
|
||||
themes = {
|
||||
chrome: "Chrome",
|
||||
clouds: "Clouds",
|
||||
clouds_midnight: "Clouds Midnight",
|
||||
cobalt: "Cobalt",
|
||||
crimson_editor: "Crimson Editor",
|
||||
dawn: "Dawn",
|
||||
dreamweaver: "Dreamweaver",
|
||||
eclipse: "Eclipse",
|
||||
idle_fingers: "idleFingers",
|
||||
kr_theme: "krTheme",
|
||||
merbivore: "Merbivore",
|
||||
merbivore_soft: "Merbivore Soft",
|
||||
mono_industrial: "Mono Industrial",
|
||||
monokai: "Monokai",
|
||||
pastel_on_dark: "Pastel on Dark",
|
||||
solarized_dark: "Solarized Dark",
|
||||
solarized_light: "Solarized Light",
|
||||
textmate: "TextMate",
|
||||
tomorrow: "Tomorrow",
|
||||
tomorrow_night: "Tomorrow Night",
|
||||
tomorrow_night_blue: "Tomorrow Night Blue",
|
||||
tomorrow_night_bright: "Tomorrow Night Bright",
|
||||
tomorrow_night_eighties: "Tomorrow Night 80s",
|
||||
twilight: "Twilight",
|
||||
vibrant_ink: "Vibrant Ink"
|
||||
};
|
||||
|
||||
function editorId() {
|
||||
return "aced." + id;
|
||||
}
|
||||
|
||||
function infoKey() {
|
||||
return editorId() + ".info";
|
||||
}
|
||||
|
||||
function gc() {
|
||||
// Clean up localstorage
|
||||
store.forEach(function(key, val) {
|
||||
var re = new RegExp("aced\.(.*?)\.info");
|
||||
var info = re.exec(key);
|
||||
if (!info || !val.time) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = info[1];
|
||||
|
||||
// Remove week+ old stuff
|
||||
var now = new Date().getTime() / 1000;
|
||||
|
||||
if (now > (val.time + 604800)) {
|
||||
store.remove(key);
|
||||
store.remove('aced.' + id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildThemeSelect() {
|
||||
var $sel = $("<select class='aced-theme-sel' data-placeholder='Theme'></select>");
|
||||
$sel.append('<option></option>');
|
||||
$.each(themes, function(k, v) {
|
||||
$sel.append("<option value='" + k + "'>" + v + "</option>");
|
||||
});
|
||||
return $("<div/>").html($sel);
|
||||
}
|
||||
|
||||
function toJquery(o) {
|
||||
return (typeof o == 'string') ? $("#" + o) : $(o);
|
||||
}
|
||||
|
||||
function initProfile() {
|
||||
profile = {theme: ''};
|
||||
|
||||
try {
|
||||
// Need to merge in any undefined/new properties from last release
|
||||
// Meaning, if we add new features they may not have them in profile
|
||||
profile = $.extend(true, profile, store.get('aced.profile'));
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
function updateProfile(obj) {
|
||||
profile = $.extend(null, profile, obj);
|
||||
store.set('profile', profile);
|
||||
}
|
||||
|
||||
function render(content) {
|
||||
return (options.renderer) ? options.renderer(content) : content;
|
||||
}
|
||||
|
||||
function bindKeyboard() {
|
||||
// CMD+s TO SAVE DOC
|
||||
key('command+s, ctrl+s', function (e) {
|
||||
submit();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
var saveCommand = {
|
||||
name: "save",
|
||||
bindKey: {
|
||||
mac: "Command-S",
|
||||
win: "Ctrl-S"
|
||||
},
|
||||
exec: function () {
|
||||
submit();
|
||||
}
|
||||
};
|
||||
editor.commands.addCommand(saveCommand);
|
||||
}
|
||||
|
||||
function info(info) {
|
||||
if (info) {
|
||||
store.set(infoKey(), info);
|
||||
}
|
||||
return store.get(infoKey());
|
||||
}
|
||||
|
||||
function val(val) {
|
||||
// Alias func
|
||||
if (val) {
|
||||
editor.getSession().setValue(val);
|
||||
}
|
||||
return editor.getSession().getValue();
|
||||
}
|
||||
|
||||
function discardDraft() {
|
||||
stopAutoSave();
|
||||
store.remove(editorId());
|
||||
store.remove(infoKey());
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function save() {
|
||||
store.set(editorId(), val());
|
||||
}
|
||||
|
||||
function submit() {
|
||||
store.remove(editorId());
|
||||
store.remove(editorId() + ".info");
|
||||
options.submit(val());
|
||||
}
|
||||
|
||||
function autoSave() {
|
||||
if (options.autoSave) {
|
||||
autoInterval = setInterval(function () {
|
||||
save();
|
||||
}, options.autoSaveInterval);
|
||||
} else {
|
||||
stopAutoSave();
|
||||
}
|
||||
}
|
||||
|
||||
function stopAutoSave() {
|
||||
if (autoInterval){
|
||||
clearInterval(autoInterval)
|
||||
}
|
||||
}
|
||||
|
||||
function renderPreview() {
|
||||
if (!preview) {
|
||||
return;
|
||||
}
|
||||
preview.html(render(val()));
|
||||
$('pre code', preview).each(function(i, e) {
|
||||
hljs.highlightBlock(e)
|
||||
});
|
||||
}
|
||||
|
||||
function getScrollHeight($prevFrame) {
|
||||
// Different browsers attach the scrollHeight of a document to different
|
||||
// elements, so handle that here.
|
||||
if ($prevFrame[0].scrollHeight !== undefined) {
|
||||
return $prevFrame[0].scrollHeight;
|
||||
} else if ($prevFrame.find('html')[0].scrollHeight !== undefined &&
|
||||
$prevFrame.find('html')[0].scrollHeight !== 0) {
|
||||
return $prevFrame.find('html')[0].scrollHeight;
|
||||
} else {
|
||||
return $prevFrame.find('body')[0].scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function getPreviewWrapper(obj) {
|
||||
// Attempts to get the wrapper for preview based on overflow prop
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
if (obj.css('overflow') == 'auto' || obj.css('overflow') == 'scroll') {
|
||||
return obj;
|
||||
} else {
|
||||
return getPreviewWrapper(obj.parent());
|
||||
}
|
||||
}
|
||||
|
||||
function syncPreview() {
|
||||
|
||||
var editorScrollRange = (editor.getSession().getLength());
|
||||
|
||||
var previewScrollRange = (getScrollHeight(preview));
|
||||
|
||||
// Find how far along the editor is (0 means it is scrolled to the top, 1
|
||||
// means it is at the bottom).
|
||||
var scrollFactor = editor.getFirstVisibleRow() / editorScrollRange;
|
||||
|
||||
// Set the scroll position of the preview pane to match. jQuery will
|
||||
// gracefully handle out-of-bounds values.
|
||||
|
||||
previewWrapper.scrollTop(scrollFactor * previewScrollRange);
|
||||
}
|
||||
|
||||
function asyncLoad(filename, cb) {
|
||||
(function (d, t) {
|
||||
|
||||
var leScript = d.createElement(t)
|
||||
, scripts = d.getElementsByTagName(t)[0];
|
||||
|
||||
leScript.async = 1;
|
||||
leScript.src = filename;
|
||||
scripts.parentNode.insertBefore(leScript, scripts);
|
||||
|
||||
leScript.onload = function () {
|
||||
cb && cb();
|
||||
}
|
||||
|
||||
}(document, 'script'));
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
var cb = function(theme) {
|
||||
editor.setTheme('ace/theme/'+theme);
|
||||
updateProfile({theme: theme});
|
||||
};
|
||||
|
||||
if (loadedThemes[theme]) {
|
||||
cb(theme);
|
||||
} else {
|
||||
asyncLoad(options.themePath + "/theme-" + theme + ".js", function () {
|
||||
cb(theme);
|
||||
loadedThemes[theme] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initSyncPreview() {
|
||||
if (!preview || !options.syncPreview) return;
|
||||
previewWrapper = getPreviewWrapper(preview);
|
||||
window.onload = function () {
|
||||
/**
|
||||
* Bind synchronization of preview div to editor scroll and change
|
||||
* of editor cursor position.
|
||||
*/
|
||||
editor.session.on('changeScrollTop', syncPreview);
|
||||
editor.session.selection.on('changeCursor', syncPreview);
|
||||
};
|
||||
}
|
||||
|
||||
function initProps() {
|
||||
// Id of editor
|
||||
if (typeof settings == 'string') {
|
||||
settings = { editor: settings };
|
||||
}
|
||||
|
||||
if ('theme' in profile && profile['theme']) {
|
||||
settings['theme'] = profile['theme'];
|
||||
}
|
||||
|
||||
if (settings['preview'] && !settings.hasOwnProperty('syncPreview')) {
|
||||
settings['syncPreview'] = true;
|
||||
}
|
||||
|
||||
$.extend(options, settings);
|
||||
|
||||
if (options.editor) {
|
||||
element = toJquery(options.editor);
|
||||
}
|
||||
|
||||
$.each(options, function(k, v){
|
||||
if (element.data(k.toLowerCase())) {
|
||||
options[k] = element.data(k.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
if (options.themeSelect) {
|
||||
themeSelect = toJquery(options.themeSelect);
|
||||
}
|
||||
|
||||
if (options.submitBtn) {
|
||||
var submitBtn = toJquery(options.submitBtn);
|
||||
submitBtn.click(function(){
|
||||
submit();
|
||||
});
|
||||
}
|
||||
|
||||
if (options.preview) {
|
||||
preview = toJquery(options.preview);
|
||||
|
||||
// Enable sync unless set otherwise
|
||||
if (!settings.hasOwnProperty('syncPreview')) {
|
||||
options['syncPreview'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!element.attr('id')) {
|
||||
// No id, make one!
|
||||
id = Math.random().toString(36).substring(7);
|
||||
element.attr('id', id);
|
||||
} else {
|
||||
id = element.attr('id')
|
||||
}
|
||||
}
|
||||
|
||||
function initEditor() {
|
||||
editor = ace.edit(id);
|
||||
setTheme(profile.theme || options.theme);
|
||||
editor.getSession().setMode('ace/mode/' + options.mode);
|
||||
if (store.get(editorId()) && store.get(editorId()) != val()) {
|
||||
editor.getSession().setValue(store.get(editorId()));
|
||||
}
|
||||
editor.getSession().setUseWrapMode(true);
|
||||
editor.getSession().setTabSize(2);
|
||||
editor.getSession().setUseSoftTabs(true);
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.renderer.setShowInvisibles(true);
|
||||
editor.renderer.setShowGutter(false);
|
||||
|
||||
if (options.showButtonBar) {
|
||||
var $btnBar = $('<div class="aced-button-bar aced-button-bar-top">' + buildThemeSelect().html() + ' <button type="button" class="btn btn-primary btn-xs aced-save">Save</button></div>')
|
||||
element.find('.ace_content').before($btnBar);
|
||||
|
||||
$(".aced-save", $btnBar).click(function(){
|
||||
submit();
|
||||
});
|
||||
|
||||
if ($.fn.chosen) {
|
||||
$('select', $btnBar).chosen().change(function(){
|
||||
setTheme($(this).val());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.keyMaster) {
|
||||
bindKeyboard();
|
||||
}
|
||||
|
||||
if (preview) {
|
||||
editor.getSession().on('change', function (e) {
|
||||
renderPreview();
|
||||
});
|
||||
renderPreview();
|
||||
}
|
||||
|
||||
if (themeSelect) {
|
||||
themeSelect
|
||||
.find('li > a')
|
||||
.bind('click', function (e) {
|
||||
setTheme($(e.target).data('value'));
|
||||
$(e.target).blur();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (options.info) {
|
||||
// If no info exists, save it to storage
|
||||
if (!store.get(infoKey())) {
|
||||
store.set(infoKey(), options.info);
|
||||
} else {
|
||||
// Check info in storage against one passed in
|
||||
// for possible changes in data that may have occurred
|
||||
var info = store.get(infoKey());
|
||||
if (info['sha'] != options.info['sha'] && !info['ignore']) {
|
||||
// Data has changed since start of draft
|
||||
$(document).trigger('shaMismatch');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(this).trigger('ready');
|
||||
}
|
||||
|
||||
function init() {
|
||||
gc();
|
||||
initProfile();
|
||||
initProps();
|
||||
initEditor();
|
||||
initSyncPreview();
|
||||
autoSave();
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
editor: editor,
|
||||
submit: submit,
|
||||
val: val,
|
||||
discard: discardDraft,
|
||||
info: info
|
||||
};
|
||||
}
|
52
realms/modules/wiki/static/js/collaboration/firepad.js
Normal file
52
realms/modules/wiki/static/js/collaboration/firepad.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Helper to get hash from end of URL or generate a random one.
|
||||
function getExampleRef() {
|
||||
var ref = new Firebase('https://' + Config['FIREBASE_HOSTNAME']);
|
||||
var hash = window.location.hash.replace(/^#fp-/, '');
|
||||
if (hash) {
|
||||
ref = ref.child(hash);
|
||||
} else {
|
||||
ref = ref.push(); // generate unique location.
|
||||
window.location = window.location + '#fp-' + ref.name(); // add it as a hash to the URL.
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
function initFirepad() {
|
||||
var new_ = true;
|
||||
if (window.location.hash.lastIndexOf('#fp-', 0) === 0) {
|
||||
new_ = false;
|
||||
}
|
||||
var firepadRef = getExampleRef();
|
||||
var session = aced.editor.session;
|
||||
var content;
|
||||
|
||||
if (new_) {
|
||||
content = session.getValue();
|
||||
}
|
||||
|
||||
// Firepad wants an empty editor
|
||||
session.setValue('');
|
||||
|
||||
//// Create Firepad.
|
||||
var firepad = Firepad.fromACE(firepadRef, aced.editor, {
|
||||
defaultText: content
|
||||
});
|
||||
|
||||
firepad.on('ready', function() {
|
||||
startCollaboration();
|
||||
});
|
||||
|
||||
$(document).on('end-collaboration', function() {
|
||||
firepad.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('loading-collaboration', function() {
|
||||
initFirepad(true);
|
||||
});
|
||||
|
||||
$(function(){
|
||||
if (window.location.hash.lastIndexOf('#fp-', 0) === 0) {
|
||||
loadingCollaboration();
|
||||
}
|
||||
});
|
36
realms/modules/wiki/static/js/collaboration/main.js
Normal file
36
realms/modules/wiki/static/js/collaboration/main.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
var $startCollaborationBtn = $('#start-collaboration');
|
||||
var $endCollaborationBtn = $('#end-collaboration');
|
||||
var $loadingCollaborationBtn = $('#loading-collaboration');
|
||||
|
||||
function loadingCollaboration() {
|
||||
$endCollaborationBtn.hide();
|
||||
$startCollaborationBtn.hide();
|
||||
$loadingCollaborationBtn.show();
|
||||
$(document).trigger('loading-collaboration');
|
||||
}
|
||||
|
||||
function startCollaboration() {
|
||||
$loadingCollaborationBtn.hide();
|
||||
$startCollaborationBtn.hide();
|
||||
$endCollaborationBtn.show();
|
||||
$(document).trigger('start-collaboration');
|
||||
}
|
||||
|
||||
function endCollaboration() {
|
||||
$loadingCollaborationBtn.hide();
|
||||
$endCollaborationBtn.hide();
|
||||
$startCollaborationBtn.show();
|
||||
$(document).trigger('end-collaboration');
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$startCollaborationBtn.click(function(e) {
|
||||
loadingCollaboration();
|
||||
e.preventDefault();
|
||||
});
|
||||
$endCollaborationBtn.click(function(e) {
|
||||
endCollaboration();
|
||||
e.preventDefault();
|
||||
|
||||
});
|
||||
});
|
28
realms/modules/wiki/static/js/collaboration/togetherjs.js
Normal file
28
realms/modules/wiki/static/js/collaboration/togetherjs.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
$(document).on('loading-collaboration', function() {
|
||||
TogetherJS();
|
||||
});
|
||||
|
||||
$(document).on('end-collaboration', function() {
|
||||
TogetherJS();
|
||||
});
|
||||
|
||||
TogetherJSConfig_toolName = "Collaboration";
|
||||
TogetherJSConfig_suppressJoinConfirmation = true;
|
||||
|
||||
if (User.is_authenticated) {
|
||||
TogetherJSConfig_getUserName = function () {
|
||||
return User.username;
|
||||
};
|
||||
|
||||
TogetherJSConfig_getUserAvatar = function () {
|
||||
return User.avatar;
|
||||
};
|
||||
}
|
||||
|
||||
TogetherJSConfig_on_ready = function () {
|
||||
startCollaboration();
|
||||
};
|
||||
|
||||
TogetherJSConfig_on_close = function () {
|
||||
//endCollaboration();
|
||||
};
|
135
realms/modules/wiki/static/js/editor.js
Normal file
135
realms/modules/wiki/static/js/editor.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
var $entry_markdown_header = $("#entry-markdown-header");
|
||||
var $entry_preview_header = $("#entry-preview-header");
|
||||
var $entry_markdown = $(".entry-markdown");
|
||||
var $entry_preview = $(".entry-preview");
|
||||
var $page_name = $("#page-name");
|
||||
var $page_message = $("#page-message");
|
||||
|
||||
// Tabs
|
||||
$entry_markdown_header.click(function(){
|
||||
$entry_markdown.addClass('active');
|
||||
$entry_preview.removeClass('active');
|
||||
});
|
||||
|
||||
$entry_preview_header.click(function(){
|
||||
$entry_preview.addClass('active');
|
||||
$entry_markdown.removeClass('active');
|
||||
});
|
||||
|
||||
$(document).on('shaMismatch', function() {
|
||||
bootbox.dialog({
|
||||
title: "Page has changed",
|
||||
message: "This page has changed and differs from your draft. What do you want to do?",
|
||||
buttons: {
|
||||
ignore: {
|
||||
label: "Ignore",
|
||||
className: "btn-default",
|
||||
callback: function() {
|
||||
var info = aced.info();
|
||||
info['ignore'] = true;
|
||||
aced.info(info);
|
||||
}
|
||||
},
|
||||
discard: {
|
||||
label: "Discard Draft",
|
||||
className: "btn-danger",
|
||||
callback: function() {
|
||||
aced.discard();
|
||||
}
|
||||
},
|
||||
changes: {
|
||||
label: "Show Diff",
|
||||
className: "btn-primary",
|
||||
callback: function() {
|
||||
bootbox.alert("Draft diff not done! Sorry");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$(function(){
|
||||
$("#discard-draft-btn").click(function() {
|
||||
aced.discard();
|
||||
});
|
||||
|
||||
$(".entry-markdown .floatingheader").click(function(){
|
||||
aced.editor.focus();
|
||||
});
|
||||
|
||||
$("#delete-page-btn").click(function() {
|
||||
bootbox.confirm('Are you sure you want to delete this page?', function(result) {
|
||||
if (result) {
|
||||
deletePage();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var deletePage = function() {
|
||||
var pageName = $page_name.val();
|
||||
var path = Config['RELATIVE_PATH'] + '/' + pageName;
|
||||
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: path,
|
||||
}).done(function(data) {
|
||||
var msg = 'Deleted page: ' + pageName;
|
||||
bootbox.alert(msg, function() {
|
||||
location.href = '/';
|
||||
});
|
||||
}).fail(function(data, status, error) {
|
||||
bootbox.alert('Error deleting page!');
|
||||
});
|
||||
};
|
||||
var last_imports = '';
|
||||
var partials = [];
|
||||
var aced = new Aced({
|
||||
editor: $('#entry-markdown-content').find('.editor').attr('id'),
|
||||
renderer: function(md) {
|
||||
var doc = metaMarked(md);
|
||||
if (doc.meta && 'import' in doc.meta) {
|
||||
// If the imports have changed, refresh them from the server
|
||||
if (doc.meta['import'].toString() != last_imports) {
|
||||
last_imports = doc.meta['import'].toString();
|
||||
$.getJSON('/_partials', {'imports': doc.meta['import']}, function (response) {
|
||||
partials = response['partials'];
|
||||
// TODO: Better way to force update of the preview here than this fake signal?
|
||||
aced.editor.session.doc._signal('change',
|
||||
{'action': 'insert', 'lines': [], 'start': {'row': 0}, 'end': {'row': 0}});
|
||||
});
|
||||
}
|
||||
}
|
||||
return MDR.convert(md, partials)
|
||||
},
|
||||
info: Commit.info,
|
||||
submit: function(content) {
|
||||
var data = {
|
||||
name: $page_name.val().replace(/^\/*/g, "").replace(/\/+/g, "/"),
|
||||
message: $page_message.val(),
|
||||
content: content
|
||||
};
|
||||
|
||||
// If renaming an existing page, use the old page name for the URL to PUT to
|
||||
var subPath = (PAGE_NAME) ? PAGE_NAME : data['name'];
|
||||
var path = Config['RELATIVE_PATH'] + '/' + subPath;
|
||||
var newPath = Config['RELATIVE_PATH'] + '/' + data['name'];
|
||||
|
||||
var type = (Commit.info['sha']) ? "PUT" : "POST";
|
||||
|
||||
$.ajax({
|
||||
type: type,
|
||||
url: path,
|
||||
data: data,
|
||||
dataType: 'json'
|
||||
}).always(function(data, status, error) {
|
||||
var res = data['responseJSON'];
|
||||
if (res && res['error']) {
|
||||
$page_name.addClass('parsley-error');
|
||||
bootbox.alert("<h3>" + res['message'] + "</h3>");
|
||||
} else {
|
||||
location.href = newPath;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
16
realms/modules/wiki/templates/wiki/compare.html
Normal file
16
realms/modules/wiki/templates/wiki/compare.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<h2>History for <strong>{{ name }}</strong></h2>
|
||||
<div class="pull-right">
|
||||
<a href="{{ url_for('wiki.commit', name=name, sha=old) }}" class="btn btn-default btn-sm">View Old</a>
|
||||
<a href="{{ url_for('wiki.commit', name=name, sha=new) }}" class="btn btn-info btn-sm">View New</a>
|
||||
</div>
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm" href="{{ url_for('wiki.history', name=name) }}">Back to History</a>
|
||||
</p>
|
||||
{{ diff|safe }}
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm" href="{{ url_for('wiki.history', name=name) }}">Back to History</a>
|
||||
</p>
|
||||
{% endblock %}
|
157
realms/modules/wiki/templates/wiki/edit.html
Normal file
157
realms/modules/wiki/templates/wiki/edit.html
Normal file
|
@ -0,0 +1,157 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block js %}
|
||||
<script>
|
||||
var Commit = {};
|
||||
Commit.info = {{ info|tojson }};
|
||||
|
||||
var PAGE_NAME = '{{ name }}';
|
||||
</script>
|
||||
<script src="{{ url_for('wiki.static', filename='js/editor.js') }}"></script>
|
||||
|
||||
{% if config.get('COLLABORATION') %}
|
||||
<script src="{{ url_for('wiki.static', filename='js/collaboration/main.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
{% if config.get('COLLABORATION') == 'firepad' %}
|
||||
<script>
|
||||
Config['FIREBASE_HOSTNAME'] = {{ config.get('FIREBASE_HOSTNAME')|tojson }};
|
||||
</script>
|
||||
<script src="https://cdn.firebase.com/js/client/1.0.17/firebase.js"></script>
|
||||
<script src="https://cdn.firebase.com/libs/firepad/1.0.0/firepad.min.js"></script>
|
||||
<script src="{{ url_for('wiki.static', filename='js/collaboration/firepad.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
{% if config.get('COLLABORATION') == 'togetherjs' %}
|
||||
<script src="{{ url_for('wiki.static', filename='js/collaboration/togetherjs.js') }}"></script>
|
||||
<script src="https://togetherjs.com/togetherjs-min.js"></script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div id="app-wrap">
|
||||
<div id="app-controls" class="row">
|
||||
<div class="col-xs-4 col-md-3">
|
||||
<input id="page-name" type="text" class="form-control input-sm" name="name"
|
||||
placeholder="Name" value="{{- name -}}" />
|
||||
</div>
|
||||
<div class="col-xs-4 col-md-3">
|
||||
<input id="page-message" type="text" class="form-control input-sm" name="page-message"
|
||||
placeholder="Comment" value="" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-xs-4 text-right">
|
||||
|
||||
{% if config.get('COLLABORATION') %}
|
||||
<div class="btn-group">
|
||||
<button style='display:none' class="btn btn-danger btn-sm" id="end-collaboration">
|
||||
<i class="fa fa-comments-o"></i>
|
||||
<span class="hidden-xs">End Collaboration</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button style='display:none' class="btn btn-default btn-sm" id="loading-collaboration" type="button">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
<span class="hidden-xs">Loading</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="dropdown btn-group">
|
||||
<button class="btn btn-default btn-sm dropdown-toggle" type="button" id="editor-actions"
|
||||
data-toggle="dropdown" title="Actions">
|
||||
<i class="fa fa-cog"></i>
|
||||
<span class="hidden-xs hidden-sm">Actions <i class="fa fa-caret-down"></i></span>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="editor-actions">
|
||||
{% if config.get('COLLABORATION') %}
|
||||
<li role="presentation">
|
||||
<a role="menuitem" tabindex="-1" href="#" id="start-collaboration">Collaborate</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
{% endif %}
|
||||
<li role="presentation">
|
||||
<a role="menuitem" tabindex="-1" href="#" id="discard-draft-btn">Delete Draft</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a role="menuitem" tabindex="-1" href="#" id="delete-page-btn">Delete Page</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="dropdown btn-group">
|
||||
<button id="theme-list-btn" type="button" class="dropdown-toggle btn btn-default btn-sm"
|
||||
data-toggle="dropdown" title="Change Theme">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
<span class="hidden-xs hidden-sm">Theme <i class="fa fa-caret-down"></i></span>
|
||||
</button>
|
||||
<ul id="theme-list" class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="theme-list">
|
||||
<li><a tabindex="-1" href="#" data-value="chrome" >Chrome</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="clouds" >Clouds</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="clouds_midnight" >Clouds Midnight</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="cobalt" >Cobalt</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="crimson_editor" >Crimson Editor</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="dawn" class="selected">Dawn</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="dreamweaver" >Dreamweaver</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="eclipse" >Eclipse</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="idle_fingers" >idleFingers</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="kr_theme" >krTheme</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="merbivore" >Merbivore</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="merbivore_soft" >Merbivore Soft</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="mono_industrial" >Mono Industrial</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="monokai" >Monokai</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="pastel_on_dark">Pastel on Dark</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="solarized_dark" >Solarized Dark</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="solarized_light" >Solarized Light</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="textmate" >TextMate</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="tomorrow" >Tomorrow</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="tomorrow_night">Tomorrow Night</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="tomorrow_night_blue" >Tomorrow Night Blue</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="tomorrow_night_bright" >Tomorrow Night Bright</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="tomorrow_night_eighties" >Tomorrow Night 80s</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="twilight" >Twilight</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="vibrant_ink" >Vibrant Ink</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
{% if name in config['WIKI_LOCKED_PAGES'] %}
|
||||
<a class="btn btn-danger btn-sm">
|
||||
<i class="fa fa-lock"></i>
|
||||
<span class="hidden-xs">Locked</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a id="submit-btn" class="btn btn-primary btn-sm">
|
||||
<i class="fa fa-save"></i>
|
||||
<span class="hidden-xs">Save</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="entry-markdown active">
|
||||
<header class="floatingheader" id="entry-markdown-header">
|
||||
<small>Markdown</small>
|
||||
<a class="markdown-help" href=""><span class="hidden">What is Markdown?</span></a>
|
||||
</header>
|
||||
<section id="entry-markdown-content" class="entry-markdown-content">
|
||||
<div id="editor-{{ name | b64encode }}" data-submitbtn='submit-btn' data-themeselect="theme-list" data-mode="markdown"
|
||||
data-preview="preview" class="editor">{{ content }}</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="entry-preview">
|
||||
<header class="floatingheader" id="entry-preview-header">
|
||||
<small>Preview</small>
|
||||
</header>
|
||||
<section class="entry-preview-content">
|
||||
<div id="preview"></div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
120
realms/modules/wiki/templates/wiki/history.html
Normal file
120
realms/modules/wiki/templates/wiki/history.html
Normal file
|
@ -0,0 +1,120 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<h2>History for <strong>{{ name }}</strong></h2>
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
|
||||
</p>
|
||||
|
||||
<table class="table table-bordered revision-tbl dataTable DTTT_selectable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Revision Message</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td colspan="3" style="text-align: center">Loading file history...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<style type="text/css">
|
||||
table.dataTable td {
|
||||
transition: background-color 0.5s linear, color 0.5s linear;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
table.dataTable tr.active td {
|
||||
transition: background-color 0.1s linear, color 0.1s linear;
|
||||
transition-delay: 0s
|
||||
}
|
||||
table.dataTable tbody tr:hover {
|
||||
background-color: #d8d8d8 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var selected = [];
|
||||
var selected_pos = [];
|
||||
|
||||
$('.dataTable').dataTable({
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: '{{ url_for('.history_data', name=name) }}',
|
||||
dataSrc: function (data) {
|
||||
$('.dataTable').data('fully_loaded', data.fully_loaded);
|
||||
return data.data
|
||||
}
|
||||
},
|
||||
ordering: false,
|
||||
bFilter: false,
|
||||
columns: [
|
||||
{
|
||||
"data": null,
|
||||
"render": function (data) {
|
||||
return '<img src="' + data.gravatar + '?s=20" class="avatar" /> ' + data.author
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": null,
|
||||
"render": function (data) {
|
||||
return '<a href="' + data.link + '" class="label label-primary">View</a> ' + data.message
|
||||
}
|
||||
},
|
||||
{ "data": "date" }
|
||||
],
|
||||
rowCallback: function( row, data, index ) {
|
||||
index += $('.dataTable').DataTable().page.info().start;
|
||||
$(row).data('index', index);
|
||||
if ( $.inArray(data.DT_RowId, selected) !== -1 ) {
|
||||
$(row).addClass('active');
|
||||
}
|
||||
},
|
||||
infoCallback: function( settings, start, end, max, total, pre ) {
|
||||
if (!$('.dataTable').data('fully_loaded')) {
|
||||
total += "+"
|
||||
}
|
||||
return "Showing " + start +" to "+ end + " of " + total + " revisions.";
|
||||
}
|
||||
});
|
||||
|
||||
$('.dataTable tbody').on('click', 'tr', function () {
|
||||
var id = this.id;
|
||||
var selected_index = $.inArray(id, selected);
|
||||
|
||||
if ( selected_index === -1 ) {
|
||||
selected.push( id );
|
||||
selected_pos.push( $(this).data('index') );
|
||||
if ( selected.length > 2) {
|
||||
// Only 2 selected at once
|
||||
var shifted = selected.shift();
|
||||
selected_pos.shift();
|
||||
$('#' + shifted).removeClass('active');
|
||||
}
|
||||
} else {
|
||||
selected.splice( selected_index, 1 );
|
||||
selected_pos.splice( selected_index, 1);
|
||||
}
|
||||
|
||||
$(this).toggleClass('active');
|
||||
});
|
||||
$(".compare-revisions").click(function(){
|
||||
if (selected.length != 2) return;
|
||||
if (selected_pos[1] > selected_pos[0]) {
|
||||
selected.reverse()
|
||||
}
|
||||
revs = selected.join("..");
|
||||
location.href = "{{ config.RELATIVE_PATH }}/_compare/{{ name }}/" + revs;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
43
realms/modules/wiki/templates/wiki/index.html
Normal file
43
realms/modules/wiki/templates/wiki/index.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.data-table').dataTable({'aaSorting': [[0, "asc"], [1, "asc"]]});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h2>Index of <a href="{{ url_for('wiki.index') }}">/</a>
|
||||
{%- set parts = path.split('/') -%}
|
||||
{%- for dir in parts if dir -%}
|
||||
<a href="{{ url_for('wiki.index', path='/'.join(parts[:loop.index])) }}">{{ dir }}/</a>
|
||||
{%- endfor -%}
|
||||
</h2>
|
||||
<table class="table table-bordered data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 1px;"></th>
|
||||
<th>Name</th>
|
||||
<th class="hidden-xs">Bytes</th>
|
||||
<th class="hidden-xs hidden-sm">Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for file in index %}
|
||||
<tr>
|
||||
{% if file['dir'] %}
|
||||
<td><i class="fa fa-folder-open-o"><span style="display:none;">Dir</span></i></td>
|
||||
<td><a href="{{ url_for('wiki.index', path=file['name']) }}">{{ file['name'][path|length:] }}</a></td>
|
||||
{% else %}
|
||||
<td><i class="fa fa-file-text-o"><span style="display:none;">Page</span></i></td>
|
||||
<td><a href="{{ url_for('wiki.page', name=file['name']) }}">{{ file['name'][path|length:] }}</a></td>
|
||||
{% endif %}
|
||||
<td class="hidden-xs">{{ file['size'] }}</td>
|
||||
<td>{{ file['ctime']|datetime }}</td>
|
||||
<td class="hidden-xs hidden-sm">{{ file['mtime']|datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
29
realms/modules/wiki/templates/wiki/page.html
Normal file
29
realms/modules/wiki/templates/wiki/page.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block page_menu %}
|
||||
<div class="controls pull-right">
|
||||
<a class="btn btn-default btn-sm" href="{{ url_for('wiki.edit', name=name) }}">Edit</a>
|
||||
<a class="btn btn-default btn-sm" href="{{ url_for('wiki.history', name=name) }}">History</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if commit %}
|
||||
<div id="page-action-bar">
|
||||
<form method="POST" action="{{ url_for('wiki.revert') }}" class="ajax-form" data-redirect="{{ url_for('wiki.page', name=name) }}">
|
||||
<input type="hidden" value="{{ name }}" name="name" />
|
||||
<input type="hidden" value="{{ commit }}" name="commit" />
|
||||
<input type="submit" class="btn btn-danger btn-sm" title="Revert back to this revision" value="Revert" />
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="page-content"></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(function(){
|
||||
$("#page-content").html(MDR.convert({{ page.data|tojson|safe }}, {{ partials|tojson|safe }})).show();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -3,16 +3,17 @@ import itertools
|
|||
import sys
|
||||
from datetime import datetime
|
||||
from flask import abort, g, render_template, request, redirect, Blueprint, flash, url_for, current_app
|
||||
from flask.ext.login import login_required, current_user
|
||||
from flask_login import login_required, current_user
|
||||
from realms.lib.util import to_canonical, remove_ext, gravatar_url
|
||||
from .models import PageNotFound
|
||||
|
||||
blueprint = Blueprint('wiki', __name__)
|
||||
blueprint = Blueprint('wiki', __name__, template_folder='templates',
|
||||
static_folder='static', static_url_path='/static/wiki')
|
||||
|
||||
|
||||
@blueprint.route("/_commit/<sha>/<path:name>")
|
||||
def commit(name, sha):
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
||||
return current_app.login_manager.unauthorized()
|
||||
|
||||
cname = to_canonical(name)
|
||||
|
@ -27,7 +28,7 @@ def commit(name, sha):
|
|||
|
||||
@blueprint.route(r"/_compare/<path:name>/<regex('\w+'):fsha><regex('\.{2,3}'):dots><regex('\w+'):lsha>")
|
||||
def compare(name, fsha, dots, lsha):
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
||||
return current_app.login_manager.unauthorized()
|
||||
|
||||
diff = g.current_wiki.get_page(name, sha=lsha).compare(fsha)
|
||||
|
@ -42,7 +43,7 @@ def revert():
|
|||
commit = request.form.get('commit')
|
||||
message = request.form.get('message', "Reverting %s" % cname)
|
||||
|
||||
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous():
|
||||
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous:
|
||||
return dict(error=True, message="Anonymous posting not allowed"), 403
|
||||
|
||||
if cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
||||
|
@ -64,7 +65,7 @@ def revert():
|
|||
|
||||
@blueprint.route("/_history/<path:name>")
|
||||
def history(name):
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
||||
return current_app.login_manager.unauthorized()
|
||||
return render_template('wiki/history.html', name=name)
|
||||
|
||||
|
@ -189,7 +190,7 @@ def _tree_index(items, path=""):
|
|||
@blueprint.route("/_index", defaults={"path": ""})
|
||||
@blueprint.route("/_index/<path:path>")
|
||||
def index(path):
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
||||
return current_app.login_manager.unauthorized()
|
||||
|
||||
items = g.current_wiki.get_index()
|
||||
|
@ -210,7 +211,7 @@ def page_write(name):
|
|||
if not cname:
|
||||
return dict(error=True, message="Invalid name")
|
||||
|
||||
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous():
|
||||
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous:
|
||||
return dict(error=True, message="Anonymous posting not allowed"), 403
|
||||
|
||||
if request.method == 'POST':
|
||||
|
@ -253,7 +254,7 @@ def page_write(name):
|
|||
@blueprint.route("/", defaults={'name': 'home'})
|
||||
@blueprint.route("/<path:name>")
|
||||
def page(name):
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
||||
return current_app.login_manager.unauthorized()
|
||||
|
||||
cname = to_canonical(name)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue