From 2233205e0e485b9b4a9ee69e70a084f59a3eceb1 Mon Sep 17 00:00:00 2001 From: Matthew Scragg Date: Tue, 1 Oct 2013 20:50:48 -0500 Subject: [PATCH] various updates --- reimagine/__init__.py | 73 +++++++++++++++++++---- reimagine/models.py | 34 +++++------ reimagine/static/css/style.css | 16 +++++ reimagine/static/js/dillinger.js | 27 +++++---- reimagine/templates/account/login.html | 20 +++++++ reimagine/templates/account/register.html | 30 ++++++++++ reimagine/templates/layout.html | 32 ++++++---- reimagine/templates/macros.html | 9 +++ reimagine/templates/page/edit.html | 21 ++----- reimagine/templates/page/history.html | 16 +++++ reimagine/templates/page/page.html | 11 +++- reimagine/util.py | 2 +- reimagine/wiki.py | 46 ++++++++++++-- srv/salt/reimagine/init.sls | 2 +- 14 files changed, 259 insertions(+), 80 deletions(-) create mode 100644 reimagine/templates/account/login.html create mode 100644 reimagine/templates/account/register.html create mode 100644 reimagine/templates/macros.html create mode 100644 reimagine/templates/page/history.html diff --git a/reimagine/__init__.py b/reimagine/__init__.py index 027bbcb..e4f6480 100644 --- a/reimagine/__init__.py +++ b/reimagine/__init__.py @@ -3,6 +3,7 @@ import redis import logging import rethinkdb as rdb import os +import time from flask import Flask, request, render_template, url_for, redirect from flask.ext.bcrypt import Bcrypt from flask.ext.login import LoginManager @@ -10,6 +11,7 @@ from flask.ext.assets import Environment from session import RedisSessionInterface from wiki import Wiki from util import to_canonical, remove_ext +from recaptcha.client import captcha app = Flask(__name__) app.config.update(config.flask) @@ -49,6 +51,11 @@ def redirect_url(): return request.args.get('next') or request.referrer or url_for('index') +@app.template_filter('datetime') +def _jinja2_filter_datetime(ts): + return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts)) + + @app.errorhandler(404) def page_not_found(e): return render_template('errors/404.html'), 404 @@ -62,7 +69,47 @@ def page_error(e): @app.route("/") def root(): - return redirect('/Home') + return redirect('/home') + +@app.route("/commit//") +def commit_sha(name, sha): + cname = to_canonical(name) + + data = w.get_page(cname, sha=sha) + if data: + return render_template('page/page.html', page=data) + else: + return redirect('/create/'+cname) + + +@app.route("/register", methods=['GET', 'POST']) +def register(): + if request.method == 'POST': + response = captcha.submit( + request.form['recaptcha_challenge_field'], + request.form['recaptcha_response_field'], + app.config['RECAPTCHA_PRIVATE_KEY'], + request.remote_addr) + if not response.is_valid: + return redirect('/register?fail') + else: + return redirect("/") + else: + return render_template('account/register.html') + + +@app.route("/login", methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + pass + else: + return render_template('account/login.html') + + +@app.route("/history/") +def history(name): + history = w.get_history(name) + return render_template('page/history.html', name=name, history=history) @app.route("/edit/", methods=['GET', 'POST']) @@ -73,7 +120,7 @@ def edit(name): edit_cname = to_canonical(request.form['name']) if edit_cname != cname: w.rename_page(cname, edit_cname) - w.write_page(edit_cname, request.form['content']) + w.write_page(edit_cname, request.form['content'], message=request.form['message']) return redirect("/" + edit_cname) else: if data: @@ -89,18 +136,20 @@ def delete(name): pass +@app.route("/create/", methods=['GET', 'POST']) @app.route("/create/", methods=['GET', 'POST']) -def create(name): - cname = to_canonical(name) - if w.get_page(cname): - # Page exists, edit instead - return redirect("/edit/" + cname) - +def create(name=None): + cname = "" + if name: + cname = to_canonical(name) + if w.get_page(cname): + # Page exists, edit instead + return redirect("/edit/" + cname) if request.method == 'POST': - w.write_page(request.form['name'], request.form['content'], create=True) + w.write_page(request.form['name'], request.form['content'], message=request.form['message'], create=True) return redirect("/" + cname) else: - return render_template('page/create.html', name=cname) + return render_template('page/edit.html', name=cname, content="") @app.route("/") @@ -111,7 +160,9 @@ def render(name): data = w.get_page(cname) if data: - return render_template('page/page.html', page=data) + #if data.get('data'): + # data['data'] = markdown(data['data']) + return render_template('page/page.html', name=cname, page=data) else: return redirect('/create/'+cname) diff --git a/reimagine/models.py b/reimagine/models.py index c404b6b..ab3b9b4 100644 --- a/reimagine/models.py +++ b/reimagine/models.py @@ -1,30 +1,28 @@ import rethinkdb as rdb from reimagine import conn +from rethinkORM import RethinkModel -def get_one(cur): - res = cur.chunks[0] - return res[0] if len(res) else None +class BaseModel(RethinkModel): + def __init__(self, **kwargs): + if not kwargs.get('conn'): + kwargs['conn'] = conn -class BaseModel(): - __table__ = None - _conn = conn - - def __init__(self): - pass + super(BaseModel, self).__init__(**kwargs) @classmethod - def filter(cls, f, limit=None): - q = rdb.table(cls.__table__).filter(f) - if limit: - q.limit(int(limit)) - return q.run(cls._conn) + def create(cls, **kwargs): + return super(BaseModel, cls).create(**kwargs) class Site(BaseModel): - __table__ = 'sites' + table = 'sites' - @classmethod - def get_by_name(cls, name): - return get_one(cls.filter({'name': name}, limit=1)) + +class User(BaseModel): + table = 'users' + + + def login(self, login, password): + pass \ No newline at end of file diff --git a/reimagine/static/css/style.css b/reimagine/static/css/style.css index f9ae3af..8fb41dd 100644 --- a/reimagine/static/css/style.css +++ b/reimagine/static/css/style.css @@ -2,6 +2,11 @@ margin-bottom: 25px; } +.checkbox-cell { + width: 4em; + padding: 0.3em; +} + #app-wrap { top: 60px; left: 10px; @@ -24,6 +29,17 @@ top: 60px; overflow: auto; background: rgba(255,255,255,0.9); + border: 1px solid #EEE; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 0 80px rgba(0,0,0,0.3) inset 0 0 5px rgba(0,0,0,0.6); + -moz-box-shadow: 0 0 80px rgba(0,0,0,0.3) inset 0 0 5px rgba(0,0,0,0.6); + -webkit-box-shadow: 0 0 80px rgba(0,0,0,0.3) inset 0 0 5px rgba(0,0,0,0.6); + -moz-box-shadow: 0 0 80px rgba(0,0,0,0.3) inset 0 0 5px rgba(0,0,0,0.6); + box-shadow: 0 0 80px rgba(0,0,0,0.3) inset 0 0 5px rgba(0,0,0,0.6); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; diff --git a/reimagine/static/js/dillinger.js b/reimagine/static/js/dillinger.js index 676a3c2..e1c307b 100644 --- a/reimagine/static/js/dillinger.js +++ b/reimagine/static/js/dillinger.js @@ -16,7 +16,7 @@ $(function(){ , interval: 3000 // might be too aggressive; don't want to block UI for large saves. } , wordcount: true - , current_filename : 'Untitled Document' + , current_filename : $("#page-name").val() , dropbox: { filepath: '/Dillinger/' @@ -36,7 +36,8 @@ $(function(){ , $wordcount = $('#wordcount') , $import_github = $('#import_github') , $wordcounter = $('#wordcounter') - , $filename = $('#filename') + , $filename = $('#filename'), + $pagename = $("#page-name") // Hash of themes and their respective background colors @@ -115,10 +116,10 @@ $(function(){ */ function getUserProfile(){ - var p + var p; try{ - p = JSON.parse( localStorage.profile ) + p = JSON.parse( localStorage.profile ); // Need to merge in any undefined/new properties from last release // Meaning, if we add new features they may not have them in profile p = $.extend(true, profile, p) @@ -126,9 +127,11 @@ $(function(){ p = profile } - profile = p + if (p.filename != $pagename.val()) { + updateUserProfile({ filename: $pagename.val(), currentMd: "" }); - // console.dir(profile) + } + profile = p } /** @@ -307,7 +310,7 @@ $(function(){ smartLists: true, smartypants: false, langPrefix: 'lang-' - }) + }); converter = marked; @@ -354,10 +357,10 @@ $(function(){ fetchTheme(profile.theme, function(){ $theme.find('li > a[data-value="'+profile.theme+'"]').addClass('selected') - editor.getSession().setUseWrapMode(true) - editor.setShowPrintMargin(false) + editor.getSession().setUseWrapMode(true); + editor.setShowPrintMargin(false); - editor.getSession().setMode('ace/mode/markdown') + editor.getSession().setMode('ace/mode/markdown'); editor.getSession().setValue( profile.currentMd || editor.getSession().getValue()) @@ -366,9 +369,6 @@ $(function(){ }); - // Set/unset paper background image on preview - // TODO: FIX THIS BUG - $preview.css('backgroundImage', profile.showPaper ? 'url("'+paperImgPath+'")' : 'url("")' ) // Set text for dis/enable autosave / word counter $autosave.html( profile.autosave.enabled ? ' Disable Autosave' : ' Enable Autosave' ) @@ -419,6 +419,7 @@ $(function(){ if (isManual) { var data = { name: $("#page-name").val(), + message: $("#page-message").val(), content: editor.getSession().getValue() }; $.post(window.location, data, function(){ diff --git a/reimagine/templates/account/login.html b/reimagine/templates/account/login.html new file mode 100644 index 0000000..73c3139 --- /dev/null +++ b/reimagine/templates/account/login.html @@ -0,0 +1,20 @@ +{% extends 'layout.html' %} +{% block body %} + +

Login

+ +
+
+ + +
+ +
+ + +
+ + +
+ +{% endblock %} \ No newline at end of file diff --git a/reimagine/templates/account/register.html b/reimagine/templates/account/register.html new file mode 100644 index 0000000..ae7e943 --- /dev/null +++ b/reimagine/templates/account/register.html @@ -0,0 +1,30 @@ +{% import 'macros.html' as macros %} +{% extends 'layout.html' %} +{% block body %} + +

Register

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ {{ macros.recaptcha(config) }} +
+ + +
+ +{% endblock %} \ No newline at end of file diff --git a/reimagine/templates/layout.html b/reimagine/templates/layout.html index 3eea961..9d7c9f4 100644 --- a/reimagine/templates/layout.html +++ b/reimagine/templates/layout.html @@ -6,7 +6,7 @@ - Reborn + ReImagine @@ -14,7 +14,7 @@ @@ -35,15 +35,18 @@ - Reimagine + ReImagine @@ -54,15 +57,18 @@ - {% block js %}{% endblock %} + {% block js %}{% endblock %} diff --git a/reimagine/templates/macros.html b/reimagine/templates/macros.html new file mode 100644 index 0000000..76da3fd --- /dev/null +++ b/reimagine/templates/macros.html @@ -0,0 +1,9 @@ +{% macro recaptcha(config) -%} + + +{%- endmacro %} \ No newline at end of file diff --git a/reimagine/templates/page/edit.html b/reimagine/templates/page/edit.html index 17bb21a..937d6fa 100644 --- a/reimagine/templates/page/edit.html +++ b/reimagine/templates/page/edit.html @@ -4,27 +4,16 @@ - {% endblock %} {% block body %}
-
- +
+ +
+
+
diff --git a/reimagine/templates/page/history.html b/reimagine/templates/page/history.html new file mode 100644 index 0000000..08c2861 --- /dev/null +++ b/reimagine/templates/page/history.html @@ -0,0 +1,16 @@ +{% extends 'layout.html' %} +{% block body %} + +

History

+ + {% for h in history %} + + + + + + + {% endfor %} +
{{ h.author }}{{ h.sha|truncate(7, True, end="") }} {{ h.message }} {{ h.time|datetime }}
+ +{% endblock %} \ No newline at end of file diff --git a/reimagine/templates/page/page.html b/reimagine/templates/page/page.html index b11862e..7fa02fc 100644 --- a/reimagine/templates/page/page.html +++ b/reimagine/templates/page/page.html @@ -1,6 +1,15 @@ {% extends 'layout.html' %} {% block body %} -
{{- page.data|safe -}}
+ +{% endblock %} +{% block js %} + {% endblock %} \ No newline at end of file diff --git a/reimagine/util.py b/reimagine/util.py index bbce6f0..f07c661 100644 --- a/reimagine/util.py +++ b/reimagine/util.py @@ -51,7 +51,7 @@ def to_canonical(s): """ s = s.encode('ascii', 'ignore') s = str(s) - s = re.sub(r"\s\s+", "-", s) + s = re.sub(r"\s\s*", "-", s) s = re.sub(r"\-\-+", "-", s) s = re.sub(r"[^a-zA-Z0-9\-]", "", s) s = s[:64] diff --git a/reimagine/wiki.py b/reimagine/wiki.py index f36f74e..33dfc91 100644 --- a/reimagine/wiki.py +++ b/reimagine/wiki.py @@ -3,21 +3,51 @@ from util import to_canonical from lxml.html.clean import clean_html +class MyGittle(Gittle): + def file_history(self, path): + """Returns all commits where given file was modified + """ + versions = [] + commits_info = self.commit_info() + seen_shas = set() + + for commit in commits_info: + try: + files = self.get_commit_files(commit['sha'], paths=[path]) + file_path, file_data = files.items()[0] + except IndexError: + continue + + file_sha = file_data['sha'] + + if file_sha in seen_shas: + continue + else: + seen_shas.add(file_sha) + + versions.append(dict(author=commit['author']['name'], + time=commit['time'], + file_sha=file_sha, + sha=commit['sha'], + message=commit['message'])) + return versions + + class Wiki(): path = None base_path = '/' default_ref = 'master' default_committer_name = 'Anon' default_committer_email = 'anon@anon.anon' - index_page = 'Home' + index_page = 'home' repo = None def __init__(self, path): try: - self.repo = Gittle.init(path) + self.repo = MyGittle.init(path) except OSError: # Repo already exists - self.repo = Gittle(path) + self.repo = MyGittle(path) self.path = path @@ -40,10 +70,14 @@ class Wiki(): def rename_page(self, old_name, new_name): self.repo.mv([old_name, new_name]) - def get_page(self, name): + def get_page(self, name, sha='HEAD'): name = name.lower() + ".md" try: - return self.repo.get_commit_files('HEAD', paths=[name]).get(name) + return self.repo.get_commit_files(sha, paths=[name]).get(name) except KeyError: # HEAD doesn't exist yet - return None \ No newline at end of file + return None + + def get_history(self, name): + name = name.lower() + ".md" + return self.repo.file_history(name) \ No newline at end of file diff --git a/srv/salt/reimagine/init.sls b/srv/salt/reimagine/init.sls index 5c2e576..c7bcf27 100644 --- a/srv/salt/reimagine/init.sls +++ b/srv/salt/reimagine/init.sls @@ -5,7 +5,7 @@ python-pkgs: - python-pip - build-essential -{% for pkg in ['tornado', 'pyzmq', 'itsdangerous', 'boto', 'redis', 'simplejson', 'sockjs-tornado', 'flask', 'flask-bcrypt', 'flask-login', 'flask-assets', 'gittle', 'gevent', 'lxml' ] %} +{% for pkg in ['tornado', 'pyzmq', 'itsdangerous', 'boto', 'redis', 'simplejson', 'sockjs-tornado', 'flask', 'flask-bcrypt', 'flask-login', 'flask-assets', 'gittle', 'gevent', 'lxml', 'markdown2', 'recaptcha', 'pyRethinkORM' ] %} {{ pkg }}-pip: pip: - name: {{ pkg }}