registration and other stuff
This commit is contained in:
parent
613d1c6ca3
commit
cba28239a2
|
@ -4,11 +4,12 @@ import time
|
||||||
|
|
||||||
import redis
|
import redis
|
||||||
import rethinkdb as rdb
|
import rethinkdb as rdb
|
||||||
from flask import Flask, request, render_template, url_for, redirect
|
from flask import Flask, request, render_template, url_for, redirect, flash, session
|
||||||
from flask.ext.bcrypt import Bcrypt
|
from flask.ext.bcrypt import Bcrypt
|
||||||
from flask.ext.login import LoginManager
|
from flask.ext.login import LoginManager, login_user, logout_user
|
||||||
from flask.ext.assets import Environment
|
from flask.ext.assets import Environment
|
||||||
from recaptcha.client import captcha
|
from recaptcha.client import captcha
|
||||||
|
from werkzeug.routing import BaseConverter
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from session import RedisSessionInterface
|
from session import RedisSessionInterface
|
||||||
|
@ -16,12 +17,18 @@ from wiki import Wiki
|
||||||
from util import to_canonical, remove_ext
|
from util import to_canonical, remove_ext
|
||||||
|
|
||||||
|
|
||||||
|
class RegexConverter(BaseConverter):
|
||||||
|
def __init__(self, url_map, *items):
|
||||||
|
super(RegexConverter, self).__init__(url_map)
|
||||||
|
self.regex = items[0]
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.update(config.flask)
|
app.config.update(config.flask)
|
||||||
app.debug = (config.ENV is not 'PROD')
|
app.debug = (config.ENV is not 'PROD')
|
||||||
app.secret_key = config.secret_key
|
app.secret_key = config.secret_key
|
||||||
app.static_path = os.sep + 'static'
|
app.static_path = os.sep + 'static'
|
||||||
app.session_interface = RedisSessionInterface()
|
app.session_interface = RedisSessionInterface()
|
||||||
|
app.url_map.converters['regex'] = RegexConverter
|
||||||
|
|
||||||
bcrypt = Bcrypt(app)
|
bcrypt = Bcrypt(app)
|
||||||
|
|
||||||
|
@ -45,7 +52,13 @@ if not config.db['dbname'] in rdb.db_list().run(conn) and config.ENV is not 'PRO
|
||||||
|
|
||||||
repo_dir = config.repo['dir']
|
repo_dir = config.repo['dir']
|
||||||
|
|
||||||
from models import Site
|
# This is down here because of dependencies above
|
||||||
|
from models import Site, User, CurrentUser
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return CurrentUser(user_id)
|
||||||
|
|
||||||
w = Wiki(repo_dir)
|
w = Wiki(repo_dir)
|
||||||
|
|
||||||
|
@ -54,6 +67,14 @@ def redirect_url():
|
||||||
return request.args.get('next') or request.referrer or url_for('index')
|
return request.args.get('next') or request.referrer or url_for('index')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_captcha():
|
||||||
|
response = captcha.submit(
|
||||||
|
request.form['recaptcha_challenge_field'],
|
||||||
|
request.form['recaptcha_response_field'],
|
||||||
|
app.config['RECAPTCHA_PRIVATE_KEY'],
|
||||||
|
request.remote_addr)
|
||||||
|
return response.is_valid
|
||||||
|
|
||||||
@app.template_filter('datetime')
|
@app.template_filter('datetime')
|
||||||
def _jinja2_filter_datetime(ts):
|
def _jinja2_filter_datetime(ts):
|
||||||
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
|
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
|
||||||
|
@ -72,7 +93,18 @@ def page_error(e):
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def root():
|
def root():
|
||||||
return redirect('/home')
|
return render('home')
|
||||||
|
#return redirect('/home')
|
||||||
|
|
||||||
|
@app.route("/account/")
|
||||||
|
def account():
|
||||||
|
return render_template('account/index.html')
|
||||||
|
|
||||||
|
@app.route("/logout/")
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
del session['user']
|
||||||
|
return redirect(url_for('root'))
|
||||||
|
|
||||||
@app.route("/commit/<sha>/<name>")
|
@app.route("/commit/<sha>/<name>")
|
||||||
def commit_sha(name, sha):
|
def commit_sha(name, sha):
|
||||||
|
@ -85,17 +117,28 @@ def commit_sha(name, sha):
|
||||||
return redirect('/create/'+cname)
|
return redirect('/create/'+cname)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
|
||||||
|
def compare(name, fsha, dots, lsha):
|
||||||
|
diff = w.compare(name, fsha, lsha)
|
||||||
|
return render_template('page/compare.html', name=name, diff=diff)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/register", methods=['GET', 'POST'])
|
@app.route("/register", methods=['GET', 'POST'])
|
||||||
def register():
|
def register():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
response = captcha.submit(
|
user = User()
|
||||||
request.form['recaptcha_challenge_field'],
|
if user.get_by_email(request.form['email']):
|
||||||
request.form['recaptcha_response_field'],
|
flash('Email is already taken')
|
||||||
app.config['RECAPTCHA_PRIVATE_KEY'],
|
return redirect('/register')
|
||||||
request.remote_addr)
|
if user.get_by_username(request.form['username']):
|
||||||
if not response.is_valid:
|
flash('Username is already taken')
|
||||||
return redirect('/register?fail')
|
return redirect('/register')
|
||||||
else:
|
|
||||||
|
# Create user and login
|
||||||
|
u = User.create(email=request.form['email'].lower(),
|
||||||
|
username=request.form['username'],
|
||||||
|
password=bcrypt.generate_password_hash(request.form['password']))
|
||||||
|
login_user(u)
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
else:
|
else:
|
||||||
return render_template('account/register.html')
|
return render_template('account/register.html')
|
||||||
|
@ -104,7 +147,11 @@ def register():
|
||||||
@app.route("/login", methods=['GET', 'POST'])
|
@app.route("/login", methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
pass
|
if User.auth(request.form['email'], request.form['password']):
|
||||||
|
return redirect("/")
|
||||||
|
else:
|
||||||
|
flash("Email or Password invalid")
|
||||||
|
return redirect("/login")
|
||||||
else:
|
else:
|
||||||
return render_template('account/login.html')
|
return render_template('account/login.html')
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
|
import rethinkdb as rdb
|
||||||
|
from flask import session
|
||||||
|
from flask.ext.login import login_user
|
||||||
from rethinkORM import RethinkModel
|
from rethinkORM import RethinkModel
|
||||||
|
from reimagine import conn, bcrypt
|
||||||
|
|
||||||
from reimagine import conn
|
|
||||||
|
def to_dict(cur, first=False):
|
||||||
|
ret = []
|
||||||
|
for row in cur:
|
||||||
|
ret.append(row)
|
||||||
|
if ret and first:
|
||||||
|
return ret[0]
|
||||||
|
else:
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class BaseModel(RethinkModel):
|
class BaseModel(RethinkModel):
|
||||||
|
@ -14,14 +26,63 @@ class BaseModel(RethinkModel):
|
||||||
def create(cls, **kwargs):
|
def create(cls, **kwargs):
|
||||||
return super(BaseModel, cls).create(**kwargs)
|
return super(BaseModel, cls).create(**kwargs)
|
||||||
|
|
||||||
|
def get_all(self, arg, index):
|
||||||
|
return rdb.table(self.table).get_all(arg, index=index).run(self._conn)
|
||||||
|
|
||||||
|
def get_one(self, arg, index):
|
||||||
|
return rdb.table(self.table).get_all(arg, index=index).limit(1).run(self._conn)
|
||||||
|
|
||||||
|
|
||||||
class Site(BaseModel):
|
class Site(BaseModel):
|
||||||
table = 'sites'
|
table = 'sites'
|
||||||
|
|
||||||
|
|
||||||
|
class CurrentUser():
|
||||||
|
id = None
|
||||||
|
|
||||||
|
def __init__(self, id):
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_anonymous(self):
|
||||||
|
return False if self.id else True
|
||||||
|
|
||||||
|
def is_authenticated(self):
|
||||||
|
return True if self.id else False
|
||||||
|
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
table = 'users'
|
table = 'users'
|
||||||
|
|
||||||
|
def get_by_email(self, email):
|
||||||
|
return to_dict(self.get_one(email, 'email'), True)
|
||||||
|
|
||||||
|
def get_by_username(self, username):
|
||||||
|
return to_dict(self.get_one(username, 'username'), True)
|
||||||
|
|
||||||
def login(self, login, password):
|
def login(self, login, password):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id):
|
||||||
|
print id
|
||||||
|
return cls(id=id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def auth(cls, username, password):
|
||||||
|
u = User()
|
||||||
|
data = u.get_by_email(username)
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if bcrypt.check_password_hash(data['password'], password):
|
||||||
|
login_user(CurrentUser(data['id']))
|
||||||
|
session['user'] = data
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
|
@ -1,5 +1,15 @@
|
||||||
.navbar {
|
.navbar {
|
||||||
margin-bottom: 25px;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-body {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 0 -20px;
|
||||||
|
-webkit-border-radius: 0 0 6px 6px;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-cell {
|
.checkbox-cell {
|
||||||
|
@ -26,7 +36,7 @@
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 60px;
|
top: 50px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: rgba(255,255,255,0.9);
|
background: rgba(255,255,255,0.9);
|
||||||
border: 1px solid #EEE;
|
border: 1px solid #EEE;
|
||||||
|
@ -58,7 +68,7 @@
|
||||||
#editor {
|
#editor {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
top: 60px;
|
top: 50px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 50%;
|
right: 50%;
|
||||||
|
@ -86,3 +96,26 @@
|
||||||
-o-box-flex: 1;
|
-o-box-flex: 1;
|
||||||
box-flex: 1;
|
box-flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-avatar a img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
-webkit-box-shadow: 0 1px 3px #1e1e1e;
|
||||||
|
-moz-box-shadow: 0 1px 3px #1e1e1e;
|
||||||
|
box-shadow: 0 1px 3px #1e1e1e;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
-ms-border-radius: 2px;
|
||||||
|
-o-border-radius: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.navbar-nav .user-avatar a {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav>li.user-avatar a {
|
||||||
|
padding-top: 9px;
|
||||||
|
padding-bottom: 9px;
|
||||||
|
}
|
20
reimagine/templates/account/index.html
Normal file
20
reimagine/templates/account/index.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<h2>Account</h2>
|
||||||
|
|
||||||
|
<form method="POST" role="form" class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email" class="col-md-2 control-label">Email</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input id="email" type="text" class="form-control" value="{{ session['user']['email'] }}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-offset-2 col-md-10">
|
||||||
|
<input type="submit" class="btn btn-primary" value="Save">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">Username or Email</label>
|
<label for="email">Email Address</label>
|
||||||
<input type="text" class="form-control" id="username" name="username" />
|
<input type="text" class="form-control" id="email" name="email" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -27,4 +27,6 @@
|
||||||
<input type="submit" class="btn btn-primary" value="Submit" />
|
<input type="submit" class="btn btn-primary" value="Submit" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<a href="/login" class="pull-right">Already registered? Login here.</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<h1>Page Not Found</h1>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="author" content="">
|
<meta name="author" content="">
|
||||||
|
|
||||||
<title>ReImagine</title>
|
<title>Realms</title>
|
||||||
|
|
||||||
<link href="/static/css/cerulean.bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/cerulean.bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="/static/css/font-awesome.min.css" rel="stylesheet">
|
<link href="/static/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<!-- Fixed navbar -->
|
<!-- Fixed navbar -->
|
||||||
<div class="navbar navbar-default navbar-fixed-top">
|
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||||
|
@ -35,24 +35,54 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="/">ReImagine</a>
|
<a class="navbar-brand" href="/">Realms</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-collapse collapse">
|
<div class="navbar-collapse collapse navbar-inverse-collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li><a href="/create/"><i class="icon-plus"></i> Create</a></li>
|
<li><a href="/create/"><i class="icon-plus"></i> Create</a></li>
|
||||||
{% if name %}
|
{% if session.get('user') %}
|
||||||
<li><a href="/edit/{{- name -}}"><i class="icon-edit"></i> Edit</a></li>
|
<li class="dropdown user-avatar">
|
||||||
<li><a href="/history/{{- name -}}"><i class="icon-time"></i> History</a></li>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||||
{% endif %}
|
<span>
|
||||||
|
<img src="http://static.ffxiah.com/images/avatars/mini/5a85e45e0976c8a96c955e83a9743b47.jpg" class="menu-avatar">
|
||||||
|
<span>{{ session['user'].get('username') }} <i class="icon-caret-down"></i></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="/account">Account</a></li>
|
||||||
|
<li><a href="/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
<li><a href="/login"><i class="icon-user"></i> Login</a></li>
|
<li><a href="/login"><i class="icon-user"></i> Login</a></li>
|
||||||
|
<li><a href="/register"><i class="icon-pencil"></i> Register</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">{% block body %}{% endblock %}</div>
|
|
||||||
|
<div class="container">
|
||||||
|
<div id="main-body">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
{% if category == 'message' %}
|
||||||
|
{% set category = "info" %}
|
||||||
|
{% endif %}
|
||||||
|
<div class='alert alert-{{ category }}'>
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/jquery-1.10.2.min.js"></script>
|
<script src="/static/js/jquery-1.10.2.min.js"></script>
|
||||||
<script src="/static/js/bootstrap.min.js"></script>
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
|
16
reimagine/templates/page/compare.html
Normal file
16
reimagine/templates/page/compare.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<h2>History for <strong>{{ name }}</strong></h2>
|
||||||
|
<p>
|
||||||
|
<a class="btn btn-default btn-sm" href="/history/{{ name }}">Back to History</a>
|
||||||
|
<a href="" class="btn btn-default btn-sm">Revert Changes</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{ diff|safe }}
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a class="btn btn-default btn-sm" href="/history/{{ name }}">Back to History</a>
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
|
@ -10,16 +10,16 @@
|
||||||
<div id="app-wrap" class="container-fluid">
|
<div id="app-wrap" class="container-fluid">
|
||||||
<div id="app-controls" class="row">
|
<div id="app-controls" class="row">
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<input id="page-name" type="text" class="form-control" name="name" placeholder="Name" value="{{- name -}}" />
|
<input id="page-name" type="text" class="form-control input-sm" name="name" placeholder="Name" value="{{- name -}}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<input id="page-message" type="text" class="form-control" name="page-message" placeholder="Comment" value="" />
|
<input id="page-message" type="text" class="form-control input-sm" name="page-message" placeholder="Comment" value="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
|
||||||
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default" data-toggle="dropdown">Theme <b class="caret"></b></a>
|
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">Theme <b class="caret"></b></a>
|
||||||
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6">
|
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6">
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/chrome" class="">Chrome</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/chrome" class="">Chrome</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds" class="">Clouds</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds" class="">Clouds</a></li>
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/twilight" class="">Twilight</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/twilight" class="">Twilight</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/vibrant_ink" class="">Vibrant Ink</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/vibrant_ink" class="">Vibrant Ink</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<a id="save-native" class="btn btn-primary"><i class="icon-save"></i> Save</a>
|
<a id="save-native" class="btn btn-primary btn-sm"><i class="icon-save"></i> Save</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,16 +1,58 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<h2>History</h2>
|
<h2>History for <strong>{{ name }}</strong></h2>
|
||||||
<table class="table table-bordered">
|
<p>
|
||||||
|
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered revision-tbl">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Revision Message</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
{% for h in history %}
|
{% for h in history %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="checkbox-cell text-center"><input type="checkbox" /></td>
|
<td class="checkbox-cell text-center">
|
||||||
|
<input type="checkbox" name="versions[]" value="{{ h.sha }}" />
|
||||||
|
</td>
|
||||||
<td>{{ h.author }}</td>
|
<td>{{ h.author }}</td>
|
||||||
<td><a href="/commit/{{ h.sha }}/{{ name }}" class='label label-primary'>{{ h.sha|truncate(7, True, end="") }}</a> {{ h.message }} </td>
|
<td><a href="/commit/{{ h.sha }}/{{ name }}" class='label label-primary'>{{ h.sha|truncate(7, True, end="") }}</a> {{ h.message }} </td>
|
||||||
<td>{{ h.time|datetime }}</td>
|
<td>{{ h.time|datetime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
<p>
|
||||||
|
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('.revision-tbl :checkbox').change(function () {
|
||||||
|
var $cs=$(this).closest('.revision-tbl').find(':checkbox:checked');
|
||||||
|
if ($cs.length > 2) {
|
||||||
|
this.checked=false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".compare-revisions").click(function(){
|
||||||
|
var $cs = $('.revision-tbl').find(':checkbox:checked');
|
||||||
|
console.log($cs.length);
|
||||||
|
if ($cs.length != 2) return;
|
||||||
|
var revs = [];
|
||||||
|
$.each($cs, function(i, v){
|
||||||
|
revs.push(v.value);
|
||||||
|
});
|
||||||
|
revs = revs.join("...");
|
||||||
|
location.href = "/compare/{{ name }}/" + revs;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -1,15 +1,20 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div id="page-content" style="display:none">
|
<div class="controls pull-right">
|
||||||
{{ page.data|safe }}
|
<a class="btn btn-default btn-sm" href="/edit/{{ name }}">Edit</a>
|
||||||
</div>
|
<a class="btn btn-default btn-sm" href="/history/{{ name }}">History</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="page-content" style="display:none">
|
||||||
|
{{ page.data|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$("#page-content").html(converter({{ page.data|tojson|safe }})).show();
|
$("#page-content").html(converter({{ page.data|tojson|safe }})).show();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,11 +1,11 @@
|
||||||
import os
|
import os
|
||||||
|
import ghdiff
|
||||||
from gittle import Gittle
|
from gittle import Gittle
|
||||||
|
|
||||||
from util import to_canonical
|
from util import to_canonical
|
||||||
|
|
||||||
|
|
||||||
class MyGittle(Gittle):
|
class MyGittle(Gittle):
|
||||||
|
|
||||||
def file_history(self, path):
|
def file_history(self, path):
|
||||||
"""Returns all commits where given file was modified
|
"""Returns all commits where given file was modified
|
||||||
"""
|
"""
|
||||||
|
@ -57,7 +57,7 @@ class Wiki():
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
def write_page(self, name, content, message=None, create=False):
|
def write_page(self, name, content, message=None, create=False, username=None, email=None):
|
||||||
#content = clean_html(content)
|
#content = clean_html(content)
|
||||||
filename = self.cname_to_filename(to_canonical(name))
|
filename = self.cname_to_filename(to_canonical(name))
|
||||||
f = open(self.path + "/" + filename, 'w')
|
f = open(self.path + "/" + filename, 'w')
|
||||||
|
@ -67,8 +67,16 @@ class Wiki():
|
||||||
if create:
|
if create:
|
||||||
self.repo.add(filename)
|
self.repo.add(filename)
|
||||||
|
|
||||||
return self.repo.commit(name=self.default_committer_name,
|
if not message:
|
||||||
email=self.default_committer_email,
|
message = "Updated %s" % name
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
username = self.default_committer_name
|
||||||
|
email = "%s@domain.com" % username
|
||||||
|
|
||||||
|
|
||||||
|
return self.repo.commit(name=username,
|
||||||
|
email=email,
|
||||||
message=message,
|
message=message,
|
||||||
files=[filename])
|
files=[filename])
|
||||||
|
|
||||||
|
@ -88,6 +96,12 @@ class Wiki():
|
||||||
# HEAD doesn't exist yet
|
# HEAD doesn't exist yet
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def compare(self, name, new_sha, old_sha):
|
||||||
|
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_history(self, name):
|
def get_history(self, name):
|
||||||
return self.repo.file_history(self.cname_to_filename(name))
|
return self.repo.file_history(self.cname_to_filename(name))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue