WIP commit.  Changed routes to POST/PUT/DELETE on page name endpoint to be more RESTful.
Check wiki dir permissions
Add comments
Add dummy favicon, robots.txt, humans.txt
Remove create.html (wasn't being used)
Fix version command
這個提交存在於:
Matthew Scragg 2014-10-20 17:27:38 -05:00
父節點 b99128e47a
當前提交 e6bc4928c9
共有 13 個檔案被更改,包括 321 行新增144 行删除

檢視檔案

@ -1 +1 @@
0.3.19
0.3.20

檢視檔案

@ -210,7 +210,8 @@ assets.register('main.js',
'vendor/datatables/media/js/jquery.dataTables.js',
'vendor/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.js',
'js/hbs-helpers.js',
'js/mdr.js')
'js/mdr.js',
'js/main.js')
assets.register('main.css',
'vendor/bootswatch-dist/css/bootstrap.css',

檢視檔案

@ -330,8 +330,8 @@ def test():
def version():
""" Output version
"""
with open('VERSION') as f:
return f.read().strip()
with open(os.path.join(config.APP_PATH, 'VERSION')) as f:
click.echo(f.read().strip())
if __name__ == '__main__':

檢視檔案

@ -0,0 +1,13 @@
import os
import sys
from realms import app
from realms.modules.wiki.models import Wiki
# Init Wiki
Wiki(app.config['WIKI_PATH'])
# Check paths
for mode in [os.W_OK, os.R_OK]:
for dir_ in [app.config['WIKI_PATH'], os.path.join(app.config['WIKI_PATH'], '.git')]:
if not os.access(dir_, mode):
sys.exit('Read and write access to WIKI_PATH is required (%s)' % dir_)

檢視檔案

@ -14,15 +14,30 @@ from realms.lib.hook import HookMixin
def cname_to_filename(cname):
""" Convert canonical name to filename
:param cname: Canonical name
:return: str -- Filename
"""
return cname.lower() + ".md"
def filename_to_cname(filename):
""" It's assumed filename is already cname format
"""Convert filename to canonical name.
.. note::
It's assumed filename is already canonical format
"""
return os.path.splitext(filename)[0]
class PageNotFound(Exception):
pass
class Wiki(HookMixin):
path = None
base_path = '/'
@ -48,56 +63,42 @@ class Wiki(HookMixin):
return "Wiki: %s" % self.path
def revert_page(self, name, commit_sha, message, username):
"""Revert page to passed commit sha1
:param name: Name of page to revert.
:param commit_sha: Commit Sha1 to revert to.
:param message: Commit message.
:param username:
:return: Git commit sha1
"""
page = self.get_page(name, commit_sha)
if not page:
# Page not found
return None
commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')])
message = commit_info['message']
raise PageNotFound()
if not message:
commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')])
message = commit_info['message']
return self.write_page(name, page['data'], message=message, username=username)
def write_page(self, name, content, message=None, create=False, username=None, email=None):
"""Write page to git repo
def escape_repl(m):
if m.group(1):
return "```" + escape(m.group(1)) + "```"
def unescape_repl(m):
if m.group(1):
return "```" + unescape(m.group(1)) + "```"
:param name: Name of page.
:param content: Content of page.
:param message: Commit message.
:param create: Perform git add operation?
:param username: Commit Name.
:param email: Commit Email.
:return: Git commit sha1.
"""
cname = to_canonical(name)
# prevents p tag from being added, we remove this later
content = '<div>' + content + '</div>'
content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL)
tree = lxml.html.fromstring(content)
cleaner = Cleaner(remove_unknown_tags=False,
kill_tags=set(['style']),
safe_attrs_only=False)
tree = cleaner.clean_html(tree)
content = lxml.html.tostring(tree, encoding='utf-8', method='html')
# remove added div tags
content = content[5:-6]
# FIXME this is for block quotes, doesn't work for double ">"
content = re.sub(r"(\n&gt;)", "\n>", content)
content = re.sub(r"(^&gt;)", ">", content)
# Handlebars partial ">"
content = re.sub(r"\{\{&gt;(.*?)\}\}", r'{{>\1}}', content)
# Handlebars, allow {{}} inside HTML links
content = content.replace("%7B", "{")
content = content.replace("%7D", "}")
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
filename = cname_to_filename(cname)
content = self.clean(content)
with open(self.path + "/" + filename, 'w') as f:
f.write(content)
@ -122,7 +123,60 @@ class Wiki(HookMixin):
return ret
def clean(self, content):
"""Clean any HTML, this might not be necessary.
:param content: Content of page.
:return: str
"""
def escape_repl(m):
if m.group(1):
return "```" + escape(m.group(1)) + "```"
def unescape_repl(m):
if m.group(1):
return "```" + unescape(m.group(1)) + "```"
# prevents p tag from being added, we remove this later
content = '<div>' + content + '</div>'
content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL)
tree = lxml.html.fromstring(content)
cleaner = Cleaner(remove_unknown_tags=False,
kill_tags={'style'},
safe_attrs_only=False)
tree = cleaner.clean_html(tree)
content = lxml.html.tostring(tree, encoding='utf-8', method='html')
# remove added div tags
content = content[5:-6]
# FIXME this is for block quotes, doesn't work for double ">"
content = re.sub(r"(\n&gt;)", "\n>", content)
content = re.sub(r"(^&gt;)", ">", content)
# Handlebars partial ">"
content = re.sub(r"\{\{&gt;(.*?)\}\}", r'{{>\1}}', content)
# Handlebars, allow {{}} inside HTML links
content = content.replace("%7B", "{")
content = content.replace("%7D", "}")
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
return content
def rename_page(self, old_name, new_name, user=None):
"""Rename page.
:param old_name: Page that will be renamed.
:param new_name: New name of page.
:param user: User object if any.
:return: str -- Commit sha1
"""
old_filename, new_filename = map(cname_to_filename, [old_name, new_name])
if old_filename not in self.gittle.index:
# old doesn't exist
@ -137,14 +191,38 @@ class Wiki(HookMixin):
self.gittle.add(new_filename)
self.gittle.rm(old_filename)
self.gittle.commit(name=getattr(user, 'username', self.default_committer_name),
email=getattr(user, 'email', self.default_committer_email),
message="Moved %s to %s" % (old_name, new_name),
files=[old_filename, new_filename])
commit = self.gittle.commit(name=getattr(user, 'username', self.default_committer_name),
email=getattr(user, 'email', self.default_committer_email),
message="Moved %s to %s" % (old_name, new_name),
files=[old_filename, new_filename])
cache.delete_many(old_filename, new_filename)
return commit
def delete_page(self, name, user=None):
"""Delete page.
:param name: Page that will be deleted
:param user: User object if any
:return: str -- Commit sha1
"""
self.gittle.rm(name)
commit = self.gittle.commit(name=getattr(user, 'username', self.default_committer_name),
email=getattr(user, 'email', self.default_committer_email),
message="Deleted %s" % name,
files=[name])
cache.delete_many(name)
return commit
def get_page(self, name, sha='HEAD'):
"""Get page data, partials, commit info.
:param name: Name of page.
:param sha: Commit sha.
:return: dict
"""
cached = cache.get(name)
if cached:
return cached
@ -172,22 +250,46 @@ class Wiki(HookMixin):
return None
def get_meta(self, content):
"""Get metadata from page if any.
:param content: Page content
:return: dict
"""
if not content.startswith("---"):
return None
meta_end = re.search("\n(\.{3}|\-{3})", content)
if not meta_end:
return None
try:
return yaml.safe_load(content[0:meta_end.start()])
except Exception as e:
return {'error': e.message}
def compare(self, name, old_sha, new_sha):
"""Compare two revisions of the same page.
:param name: Name of page.
:param old_sha: Older sha.
:param new_sha: Newer sha.
:return: str - Raw markup with styles
"""
# TODO: This could be effectively done in the browser
old = self.get_page(name, sha=old_sha)
new = self.get_page(name, sha=new_sha)
return ghdiff.diff(old['data'], new['data'])
def get_index(self):
"""Get repo index of head.
:return: list -- List of dicts
"""
rv = []
index = self.repo.open_index()
for name in index:
@ -201,8 +303,20 @@ class Wiki(HookMixin):
return rv
def get_history(self, name, limit=100):
"""Get page history.
:param name: Name of page.
:param limit: Limit history size.
:return: list -- List of dicts
"""
if not len(self.repo.open_index()):
# Index is empty, no commits
return []
file_path = cname_to_filename(name)
versions = []
walker = self.repo.get_walker(paths=[file_path], max_entries=limit)
for entry in walker:
change_type = None
@ -220,4 +334,3 @@ class Wiki(HookMixin):
type=change_type))
return versions

檢視檔案

@ -1,4 +1,4 @@
from flask import g, render_template, request, redirect, Blueprint, flash, url_for, current_app
from flask import abort, g, render_template, request, redirect, Blueprint, flash, url_for, current_app
from flask.ext.login import login_required
from realms.lib.util import to_canonical, remove_ext
from realms.modules.wiki.models import Wiki
@ -17,10 +17,11 @@ def commit(name, sha):
cname = to_canonical(name)
data = g.current_wiki.get_page(cname, sha=sha)
if data:
return render_template('wiki/page.html', name=name, page=data, commit=sha)
else:
return redirect(url_for('wiki.create', name=cname))
if not data:
abort(404)
return render_template('wiki/page.html', name=name, page=data, commit=sha)
@blueprint.route("/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
@ -35,15 +36,17 @@ def revert():
name = request.form.get('name')
commit = request.form.get('commit')
cname = to_canonical(name)
message = request.form.get('message', "Reverting %s" % cname)
if cname in app.config.WIKI_LOCKED_PAGES:
flash("Page is locked")
return redirect(url_for(app.config['ROOT_ENDPOINT']))
if cname in app.config.get('WIKI_LOCKED_PAGES'):
return dict(error=True, message="Page is locked")
g.current_wiki.revert_page(name, commit, message="Reverting %s" % cname,
username=current_user.username)
flash('Page reverted', 'success')
return redirect(url_for('wiki.page', name=cname))
sha = g.current_wiki.revert_page(name, commit, message=message,
username=current_user.username)
if sha:
flash("Page reverted")
return dict(sha=sha)
@blueprint.route("/_history/<name>")
@ -51,74 +54,40 @@ def history(name):
return render_template('wiki/history.html', name=name, history=g.current_wiki.get_history(name))
@blueprint.route("/_edit/<name>", methods=['GET', 'POST'])
@blueprint.route("/_edit/<name>")
@login_required
def edit(name):
data = g.current_wiki.get_page(name)
cname = to_canonical(name)
if request.method == 'POST':
edit_cname = to_canonical(request.form['name'])
page = g.current_wiki.get_page(name)
if edit_cname in app.config['WIKI_LOCKED_PAGES']:
return redirect(url_for(app.config['ROOT_ENDPOINT']))
if not page:
# Page doesn't exist
return redirect(url_for('wiki.create', name=cname))
if edit_cname != cname.lower():
g.current_wiki.rename_page(cname, edit_cname)
g.current_wiki.write_page(edit_cname,
request.form['content'],
message=request.form['message'],
username=current_user.username)
else:
if data:
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,
info=data.get('info'),
sha=data.get('sha'),
partials=data.get('partials'))
else:
return redirect(url_for('wiki.create', name=cname))
name = remove_ext(page['name'])
g.assets['js'].append('editor.js')
return render_template('wiki/edit.html',
name=name,
content=page.get('data'),
info=page.get('info'),
sha=page.get('sha'),
partials=page.get('partials'))
@blueprint.route("/_delete/<name>", methods=['POST'])
@login_required
def delete(name):
pass
@blueprint.route("/_create/", defaults={'name': None}, methods=['GET', 'POST'])
@blueprint.route("/_create/<name>", methods=['GET', 'POST'])
@blueprint.route("/_create/", defaults={'name': None})
@blueprint.route("/_create/<name>")
@login_required
def create(name):
if request.method == 'POST':
cname = to_canonical(request.form['name'])
cname = to_canonical(name) if name else ""
if cname and g.current_wiki.get_page(cname):
# Page exists, edit instead
return redirect(url_for('wiki.edit', name=cname))
if cname in app.config['WIKI_LOCKED_PAGES']:
return redirect(url_for("wiki.create"))
if not cname:
return redirect(url_for("wiki.create"))
g.current_wiki.write_page(request.form['name'],
request.form['content'],
message=request.form['message'],
create=True,
username=current_user.username)
else:
cname = to_canonical(name) if name else ""
if cname and g.current_wiki.get_page(cname):
# Page exists, edit instead
return redirect(url_for('wiki.edit', name=cname))
g.assets['js'].append('editor.js')
return render_template('wiki/edit.html',
name=cname,
content="",
info={})
g.assets['js'].append('editor.js')
return render_template('wiki/edit.html',
name=cname,
content="",
info={})
@blueprint.route("/_index")
@ -126,6 +95,48 @@ def index():
return render_template('wiki/index.html', index=g.current_wiki.get_index())
@blueprint.route("/<name>", methods=['POST', 'PUT', 'DELETE'])
@login_required
def page_write(name):
cname = to_canonical(name)
if not cname:
return dict(error=True, message="Invalid name")
if request.method == 'POST':
# Create
if cname in app.config.get('WIKI_LOCKED_PAGES'):
return dict(error=True, message="Page is locked")
sha = g.current_wiki.write_page(cname,
request.form['content'],
message=request.form['message'],
create=True,
username=current_user.username)
elif request.method == 'PUT':
edit_cname = to_canonical(request.form['name'])
if edit_cname in app.config.get('WIKI_LOCKED_PAGES'):
return dict(error=True, message="Page is locked")
if edit_cname != cname.lower():
g.current_wiki.rename_page(cname, edit_cname)
sha = g.current_wiki.write_page(edit_cname,
request.form['content'],
message=request.form['message'],
username=current_user.username)
return dict(sha=sha)
else:
# DELETE
sha = g.current_wiki.delete_page(name, user=current_user)
return dict(sha=sha)
@blueprint.route("/", defaults={'name': 'home'})
@blueprint.route("/<name>")
def page(name):
@ -134,6 +145,7 @@ def page(name):
return redirect(url_for('wiki.page', name=cname))
data = g.current_wiki.get_page(cname)
if data:
return render_template('wiki/page.html', name=cname, page=data, partials=data.get('partials'))
else:

0
realms/static/humans.txt 一般檔案
檢視檔案

二進制
realms/static/img/favicon.ico 一般檔案

未顯示二進位檔案。

之後

寬度:  |  高度:  |  大小: 31 KiB

檢視檔案

@ -2,6 +2,8 @@ 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(){
@ -66,12 +68,26 @@ var aced = new Aced({
info: Commit.info,
submit: function(content) {
var data = {
name: $("#page-name").val(),
message: $("#page-message").val(),
name: $page_name.val(),
message: $page_message.val(),
content: content
};
$.post(window.location, data, function() {
location.href = Config['RELATIVE_PATH'] + '/' + data['name'];
var path = 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) {
if (data && data['error']) {
$page_name.addClass('parsley-error');
bootbox.alert("<h3>" + data['message'] + "</h3>");
} else {
location.href = path;
}
});
}
});

40
realms/static/js/main.js 一般檔案
檢視檔案

@ -0,0 +1,40 @@
$(function(){
$(".ajax-form").submit(function(e){
e.preventDefault();
var submitting = 'submitting';
if ($(this).data(submitting)) {
return;
}
$(this).data(submitting, 1);
var action = $(this).attr('action');
var method = $(this).attr('method');
var redirect = $(this).data('redirect');
var data = $(this).serialize();
var req = $.ajax({
type: method,
url: action,
data: data,
dataType: 'json'
});
req.done(function() {
if (redirect) {
location.href = redirect;
}
});
req.fail(function(data, status, error) {
console.log(data);
});
req.always(function() {
$(this).removeData(submitting);
});
});
});

0
realms/static/robots.txt 一般檔案
檢視檔案

檢視檔案

@ -1,18 +0,0 @@
{% extends 'layout.html' %}
{% block body %}
<form role="form" method="post">
<div class="form-group">
<label for="name"></label>
<input id="name" type="text" class="form-control" name="name" placeholder="Page Name" value="{{- name -}}" />
</div>
<div class="form-group">
<label for="content"></label>
<textarea name="content" id="content" class="form-control" placeholder="Content"></textarea>
</div>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
{% endblock %}

檢視檔案

@ -9,7 +9,7 @@
{% block body %}
{% if commit %}
<div id="page-action-bar">
<form method="POST" action="{{ url_for('wiki.revert') }}">
<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" />