commit work in progress

completely revamp JS client because JS sucks^W^W^W to feature AMD,
require.js, promises and HTML.js.

The comment listing is now more like Disqus and for now comment
retrieval, comment creation and deletion works. Form validation is
rudimentary implemented as well.

replaced Mako with Jinja2 (because... I forgot.), admin interface will
use Bootstrap™ but is not functional yet.

features a progress indicator in case you're sqlite db performs *really*
bad
This commit is contained in:
Martin Zimmermann 2013-09-05 19:31:18 +02:00
parent 7e6fa0438b
commit f6271e5cf6
25 changed files with 2832 additions and 393 deletions

View File

@ -155,7 +155,8 @@ def main():
sys.exit(0) sys.exit(0)
app = SharedDataMiddleware(isso.wsgi_app, { app = SharedDataMiddleware(isso.wsgi_app, {
'/static': join(dirname(__file__), 'static/') '/static': join(dirname(__file__), 'static/'),
'/client': join(dirname(__file__), 'client/')
}) })
run_simple(conf.get('server', 'host'), conf.getint('server', 'port'), run_simple(conf.get('server', 'host'), conf.getint('server', 'port'),

111
isso/client/app/api.js Normal file
View File

@ -0,0 +1,111 @@
/*
* Copyright 2013, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py.
*/
define(["lib/q", "app/models"], function(Q, models) {
// http://stackoverflow.com/questions/17544965/unhandled-rejection-reasons-should-be-empty
Q.stopUnhandledRejectionTracking();
Q.longStackSupport = true;
var endpoint = null,
location = window.location.pathname;
// guess Isso API location
var js = document.getElementsByTagName("script");
for (var i = 0; i < js.length; i++) {
if (js[i].src.match("/client/require\\.js$")) {
endpoint = js[i].src.substring(0, js[i].src.length - 18);
break;
}
throw "no Isso API location found";
}
var curl = function(method, url, data) {
var request = new XMLHttpRequest();
var response = Q.defer();
function onload() {
response.resolve({status: request.status, body: request.responseText});
}
try {
request.open(method, url, true);
request.overrideMimeType("application/javascript");
request.onreadystatechange = function () {
if (request.readyState === 4) {
onload();
}
};
} catch (exception) {
response.reject(exception.message);
}
request.send(data);
return response.promise;
};
var qs = function(params) {
rv = "";
for (var key in params) {
if (params.hasOwnProperty(key)) {
rv += key + "=" + encodeURIComponent(params[key]) + "&";
}
}
return rv.substring(0, rv.length - 1) // chop off trailing "&"
}
var create = function(data) {
return curl("POST", endpoint + "/new?" + qs({uri: location}), JSON.stringify(data))
.then(function (rv) {
if (rv.status == 201 || rv.status == 202) {
return JSON.parse(rv.body);
} else {
msg = rv.body.match("<p>(.+)</p>")
throw {status: rv.status, reason: (msg && msg[1]) || rv.body}
}
})
}
var modify = function(data) {
// ...
}
var remove = function(id) {
return curl("DELETE", endpoint + "/?" + qs({uri: location, id: id}), null)
.then(function(rv) {
if (rv.status == 200) {
return rv;
} else {
throw {status: rv.status, reason: rv.body}
}
})
}
var fetchall = function() {
return curl("GET", endpoint + "/?" + qs({uri: location}), null)
.then(function (rv) {
if (rv.status == 200) {
return JSON.parse(rv.body)
} else {
msg = rv.body.match("<p>(.+)</p>")
throw {status: rv.status, reason: (msg && msg[1]) || rv.body}
}
})
}
return {
endpoint: endpoint,
create: create,
remove: remove,
fetchall: fetchall
}
});

54
isso/client/app/forms.js Normal file
View File

@ -0,0 +1,54 @@
define(["lib/HTML", "./logging"], function(HTML, logging) {
var msgbox = function(defaults) {
var form = document.createElement("div")
form.className = "isso-comment-box"
HTML.ify(form);
var optional = form.add("ul.optional");
optional.add("li>input[type=text name=author placeholder=Name ]").value = defaults.author || "";
optional.add("li>input[type=email name=email placeholder=Email]").value = defaults.email || "";
optional.add("li>input[type=url name=website placeholder=Website]").value = defaults.website || "";
var textarea = form.add("div>textarea[rows=2 name=text]");
textarea.value = defaults.text || "";
textarea.placeholder = "Kommentar hier eintippen (andere Felder sind optional)"
textarea.onfocus = function() {
textarea.rows = 10
};
textarea.onblur = function() { setTimeout(function() {
if (textarea.value == "" && document.activeElement != textarea) {
textarea.rows = 2
}}, 500)};
form.add("input[type=submit]").value = "Kommentar hinzufügen";
form.add("span");
return form;
}
var validate = function(msgbox) {
if (msgbox.query("textarea").value.length < 3) {
msgbox.query("textarea").focus();
msgbox.span.className = "isso-popup"
msgbox.span.innerHTML = "Dein Kommentar sollte schon etwas länger sein.";
msgbox.span.addEventListener("click", function(event) {
msgbox.span.className = "";
msgbox.span.innerHTML = "";
})
setTimeout(function() {
msgbox.span.className = ""
msgbox.span.innerHTML = ""
}, 5000 )
return false;
}
return true;
}
return {
msgbox: msgbox,
validate: validate
}
});

202
isso/client/app/isso.js Normal file
View File

@ -0,0 +1,202 @@
/* Isso Ich schrei sonst!
*
* Copyright 2013, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py.
*/
define(["lib/q", "lib/HTML", "helper/utils", "./api", "./forms", "./logging"], function(Q, HTML, utils, api, forms, logging) {
var defaults = {
text: "Lorem ipsum ...", author: "Anonymous",
email: "info@example.org", website: "..."
};
var insert = function(comment) {
/*
* insert a comment (JSON/object) into the #isso-thread or below a parent (#isso-N), renders some HTML and
* registers events to reply to, edit and remove a comment.
*/
if (comment.parent) {
entrypoint = HTML.query("#isso-" + comment.parent).add("div.isso-follow-up");
} else {
entrypoint = HTML.query("div#isso-root")
}
entrypoint.add("article.isso-comment#isso-" + comment.id)
.add("header+span.avatar+div.text+footer")
var node = HTML.query("#isso-" + comment.id),
date = new Date(parseInt(comment.created) * 1000);
if (comment.mode == 2) {
node.header.add("span.note").textContent = 'Kommentar muss noch freigeschaltet werden';
} else if (comment.mode == 4) { // deleted
node.classList.add('deleted');
node.header.add("span.note").textContent = "Kommentar gelöscht."
}
if (comment.website) {
var el = node.header.add("a.author")
el.textContent= comment.author || 'Anonymous';
el.href = comment.website;
el.rel = "nofollow"
} else {
node.header.add("span.author").innerHTML = comment.author || 'Anonymous';
}
node.header.add("span.spacer").textContent = "•";
var permalink = node.header.add("a.permalink");
permalink.href = '#isso-' + comment.id;
permalink.add("date[datetime=" + date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDay() + "]")
.textContent = utils.ago(date);
node.query("span.avatar").add("img[width=48 height=48]");
if (comment.mode == 4) {
node.query(".text").add("p").value = "&nbsp;"
} else {
node.query(".text").innerHTML = comment.text;
}
node.footer.add("a.liek{Liek}").href = "#";
node.footer.add("a.reply{Antworten}").href = "#";
if (utils.read(window.location.pathname + "-" + comment.id)) {
node.footer.add("a.delete{Löschen}").href = "#";
node.footer.add("a.edit{Bearbeiten}").href = "#";
var delbtn = node.query("a.delete"),
editbtn = node.query("a.edit");
delbtn.addEventListener("click", function(event) {
if (delbtn.textContent == "Bestätigen") {
api.remove(comment.id).then(function(rv) {
console.log(rv);
})
} else {
delbtn.textContent = "Bestätigen"
setTimeout(function() {delbtn.textContent = "Löschen"}, 1500)
}
event.preventDefault();
})
}
// if (read(path + '-' + post['id'])) {
// $('#isso_' + post['id'] + '> footer > a:first-child')
// .after(brew(['a', {'class': 'delete', 'href': '#'}, 'Löschen']))
// .after(brew(['a', {'class': 'edit', 'href': '#'}, 'Bearbeiten']));
//
// // DELETE
// $('#isso_' + post['id'] + ' > footer .delete').on('click', function(event) {
// isso.remove(post['id'], function(status, rv) {
// // XXX comment might not actually deleted
// $('#isso_' + post['id']).remove();
// });
// event.stop();
// });
//
// // EDIT
// $('#isso_' + post['id'] + ' > footer .edit').on('click', function(event) {
//
// if ($('#issoform_' + post['id']).length == 0) { // HTML form not shown
// isso.plain(post['id'], function(status, rv) {
// if (status != 200) return alert('Mööp');
// var rv = form(post['id'], JSON.parse(rv), function(form, id) {
// isso.modify(id, extract(form, post['parent']), function(status, rv) {
// if (status != 200) return alert('Mööp');
//
// $('#issoform_' + post['id']).remove();
// $('#isso_' + post['id']).remove();
// insert(JSON.parse(rv));
// });
// });
//
// $('#isso_' + post['id']).after(rv);
// $('input[type="submit"]', rv)[0].value = 'Bestätigen.';
// });
// } else {
// $('#issoform_' + post['id']).remove();
// };
// event.stop();
// });
// };
// ability to answer directly to a comment
HTML.query("#isso-" + comment.id + " a.reply").addEventListener("click", function(event) {
// remove active form when clicked again or reply to another comment
var active = HTML.query(".isso-active-msgbox"); // [] when empty, element if not
if (! (active instanceof Array)) {
active.query("div.isso-comment-box").remove()
active.classList.remove("isso-active-msgbox");
active.query("a.reply").textContent = "Antworten"
if (active.id == "isso-" + comment.id) {
event.preventDefault();
return;
}
}
var msgbox = forms.msgbox({})
HTML.query("#isso-" + comment.id).footer.appendChild(msgbox)
HTML.query("#isso-" + comment.id).classList.add("isso-active-msgbox");
HTML.query("#isso-" + comment.id + " a.reply").textContent = "Schließen";
msgbox.query("input[type=submit]").addEventListener("click", function(event) {
forms.validate(msgbox) && api.create({
author: msgbox.query("[name=author]").value,
email: msgbox.query("[name=email]").value,
website: msgbox.query("[name=website]").value,
text: msgbox.query("textarea").value,
parent: comment.id })
.then(function(rv) {
// remove box on submit
msgbox.parentNode.parentNode.classList.remove("isso-active-msgbox");
msgbox.parentNode.parentNode.query("a.reply").textContent = "Antworten"
msgbox.remove()
insert(rv);
})
event.preventDefault()
});
event.preventDefault();
});
}
var init = function() {
console.log(utils.heading());
// return;
var rootmsgbox = forms.msgbox({});
HTML.query("#isso-thread").add("div#isso-root").add(rootmsgbox);
rootmsgbox.query("input[type=submit]").addEventListener("click", function(event) {
forms.validate(rootmsgbox) && api.create({
author: rootmsgbox.query("[name=author]").value,
email: rootmsgbox.query("[name=email]").value,
website: rootmsgbox.query("[name=website]").value,
text: rootmsgbox.query("textarea").value,
parent: null })
.then(function(rv) {
// remove box on submit
rootmsgbox.remove()
insert(rv);
})
event.preventDefault()
});
api.fetchall()
.then(function(comments) {
for (var i in comments) {
insert(comments[i])
}})
.fail(logging.error)
}
return {
init: init
}
});

View File

@ -0,0 +1,9 @@
define({
error: function(err) {
if ("status" in err && "reason" in err) {
console.error("%i: %s", err.status, err.reason)
} else {
console.error(err.stack)
}
}
})

10
isso/client/app/models.js Normal file
View File

@ -0,0 +1,10 @@
define(function() {
function Comment(data) {
this.text = data["text"];
}
return {
Comment: Comment
}
});

5
isso/client/embed.js Normal file
View File

@ -0,0 +1,5 @@
require(["lib/ready", "app/isso"], function(domready, isso) {
domready(function() {
isso.init();
})
});

View File

@ -0,0 +1,81 @@
/* Copyright 2013, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py.
*
* utility functions
*/
define({
// return `cookie` string if set
read: function(cookie) {
return (document.cookie.match('(^|; )' + cookie + '=([^;]*)') || 0)[2]
},
ago: function(date) {
/*!
* JavaScript Pretty Date
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*/
var diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31)
return;
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && "vor " + Math.floor(diff / 60) + " Minuten" ||
diff < 7200 && "vor einer Stunde" ||
diff < 86400 && "vor " + Math.floor(diff / 3600) + " Stunden") ||
day_diff == 1 && "Gestern" ||
day_diff < 7 && "vor " + day_diff + " Tagen" ||
day_diff < 31 && "vor " + Math.ceil(day_diff / 7) + " Wochen";
},
heading: function() {
/*
* return first level heading that is probably the
* blog title. If no h1 is found, "Untitled." is used.
*/
var el = document.getElementById("isso-thread");
var visited = [];
var recurse = function(node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType != child.ELEMENT_NODE) {
continue;
}
if (child.nodeName == "H1") {
return child;
}
if (visited.indexOf(child) == -1) {
return recurse(child);
}
}
}
while (true) {
visited.push(el);
if (el == document.documentElement) {
break
}
var rv = recurse(el);
if (rv) {
return rv.textContent
}
el = el.parentNode;
}
return "Untitled."
}
});

View File

@ -1,41 +1,35 @@
/* Isso Ich schrei sonst! /* Isso Ich schrei sonst!
* *
* Copyright 2012, Martin Zimmermann <info@posativ.org>. All rights reserved. * Copyright 2013, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py. * License: BSD Style, 2 clauses. See isso/__init__.py.
*
*
* Code requires Bean, Bonzo, Qwery, domReady (all are part of jeesh) and
* reqwest. To ease integration with websites, all classes are prefixed
* with `isso`.
*/ */
// Uhm. Namespaces are one honking great idea, aren't they? // Uhm. Namespaces are one honking great idea, aren't they?
var isso = isso || {}, var isso = {};
prefix = "",
path = encodeURIComponent(window.location.pathname);
// XXX
isso.prefix = prefix;
isso.path = path;
/* var init = function() {
* isso specific helpers to create, modify, remove and receive comments var isso = new Object();
*/
function verify(data) { // guess Isso API location
return data['text'] == null ? false : true var js = document.getElementsByTagName("script");
}; for (var i = 0; i < js.length; i++) {
if (js[i].src.match("/client/require\\.js$")) {
isso.location = js[i].src.substring(0, 18);
break;
}
}
console.log(isso.location)
}
isso.create = function(data, func) { isso.create = function(data, func) {
if (!verify(data)) { var request = new XMLHttpRequest();
return;
}
$.ajax('POST', prefix + '/1.0/' + isso.path + '/new', // $.ajax('POST', prefix + '/1.0/' + isso.path + '/new',
JSON.stringify(data), {'Content-Type': 'application/json'}).then(func); // JSON.stringify(data), {'Content-Type': 'application/json'}).then(func);
}; };

4
isso/client/lib/HTML.js Normal file

File diff suppressed because one or more lines are too long

1937
isso/client/lib/q.js Normal file

File diff suppressed because it is too large Load Diff

55
isso/client/lib/ready.js Normal file
View File

@ -0,0 +1,55 @@
/*!
* domready (c) Dustin Diaz 2012 - License MIT
*/
!function (name, definition) {
if (typeof module != 'undefined') module.exports = definition()
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition)
else this[name] = definition()
}('domready', function (ready) {
var fns = [], fn, f = false
, doc = document
, testEl = doc.documentElement
, hack = testEl.doScroll
, domContentLoaded = 'DOMContentLoaded'
, addEventListener = 'addEventListener'
, onreadystatechange = 'onreadystatechange'
, readyState = 'readyState'
, loadedRgx = hack ? /^loaded|^c/ : /^loaded|c/
, loaded = loadedRgx.test(doc[readyState])
function flush(f) {
loaded = 1
while (f = fns.shift()) f()
}
doc[addEventListener] && doc[addEventListener](domContentLoaded, fn = function () {
doc.removeEventListener(domContentLoaded, fn, f)
flush()
}, f)
hack && doc.attachEvent(onreadystatechange, fn = function () {
if (/^c/.test(doc[readyState])) {
doc.detachEvent(onreadystatechange, fn)
flush()
}
})
return (ready = hack ?
function (fn) {
self != top ?
loaded ? fn() : fns.push(fn) :
function () {
try {
testEl.doScroll('left')
} catch (e) {
return setTimeout(function() { ready(fn) }, 50)
}
fn()
}()
} :
function (fn) {
loaded ? fn() : fns.push(fn)
})
})

6
isso/client/main.js Normal file
View File

@ -0,0 +1,6 @@
require(["minified"], function(minified) {
console.log(minified)
minified.$.ready(function() {
console.log(123);
})
});

36
isso/client/require.js Normal file
View File

@ -0,0 +1,36 @@
/*
RequireJS 2.1.8 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function s(b,c){return ga.call(b,c)}function l(b,c){return s(b,c)&&b[c]}function F(b,c){for(var d in b)if(s(b,d)&&c(b[d],d))break}function Q(b,c,d,h){c&&F(c,function(c,j){if(d||!s(b,j))h&&"string"!==typeof c?(b[j]||(b[j]={}),Q(b[j],
c,d,h)):b[j]=c});return b}function u(b,c){return function(){return c.apply(b,arguments)}}function aa(b){throw b;}function ba(b){if(!b)return b;var c=Z;y(b.split("."),function(b){c=c[b]});return c}function A(b,c,d,h){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=h;d&&(c.originalError=d);return c}function ha(b){function c(a,f,b){var e,m,c,g,d,h,j,i=f&&f.split("/");e=i;var n=k.map,p=n&&n["*"];if(a&&"."===a.charAt(0))if(f){e=l(k.pkgs,f)?i=[f]:i.slice(0,i.length-
1);f=a=e.concat(a.split("/"));for(e=0;f[e];e+=1)if(m=f[e],"."===m)f.splice(e,1),e-=1;else if(".."===m)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=l(k.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(b&&n&&(i||p)){f=a.split("/");for(e=f.length;0<e;e-=1){c=f.slice(0,e).join("/");if(i)for(m=i.length;0<m;m-=1)if(b=l(n,i.slice(0,m).join("/")))if(b=l(b,c)){g=b;d=e;break}if(g)break;!h&&(p&&l(p,c))&&(h=l(p,c),j=e)}!g&&
h&&(g=h,d=j);g&&(f.splice(0,d,g),a=f.join("/"))}return a}function d(a){z&&y(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===i.contextName)return f.parentNode.removeChild(f),!0})}function h(a){var f=l(k.paths,a);if(f&&I(f)&&1<f.length)return d(a),f.shift(),i.require.undef(a),i.require([a]),!0}function $(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function n(a,
f,b,e){var m,B,g=null,d=f?f.name:null,h=a,j=!0,k="";a||(j=!1,a="_@r"+(L+=1));a=$(a);g=a[0];a=a[1];g&&(g=c(g,d,e),B=l(r,g));a&&(g?k=B&&B.normalize?B.normalize(a,function(a){return c(a,d,e)}):c(a,d,e):(k=c(a,d,e),a=$(k),g=a[0],k=a[1],b=!0,m=i.nameToUrl(k)));b=g&&!B&&!b?"_unnormalized"+(M+=1):"";return{prefix:g,name:k,parentMap:f,unnormalized:!!b,url:m,originalName:h,isDefine:j,id:(g?g+"!"+k:k)+b}}function q(a){var f=a.id,b=l(p,f);b||(b=p[f]=new i.Module(a));return b}function t(a,f,b){var e=a.id,m=l(p,
e);if(s(r,e)&&(!m||m.defineEmitComplete))"defined"===f&&b(r[e]);else if(m=q(a),m.error&&"error"===f)b(m.error);else m.on(f,b)}function v(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(y(b,function(f){if(f=l(p,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)j.onError(a)}function w(){R.length&&(ia.apply(G,[G.length-1,0].concat(R)),R=[])}function x(a){delete p[a];delete T[a]}function E(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,y(a.depMaps,function(e,c){var g=e.id,
d=l(p,g);d&&(!a.depMatched[c]&&!b[g])&&(l(f,g)?(a.defineDep(c,r[g]),a.check()):E(d,f,b))}),b[e]=!0)}function C(){var a,f,b,e,m=(b=1E3*k.waitSeconds)&&i.startTime+b<(new Date).getTime(),c=[],g=[],j=!1,l=!0;if(!U){U=!0;F(T,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||g.push(b),!b.error))if(!b.inited&&m)h(f)?j=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(j=!0,!a.prefix))return l=!1});if(m&&c.length)return b=A("timeout","Load timeout for modules: "+c,null,c),b.contextName=
i.contextName,v(b);l&&y(g,function(a){E(a,{},{})});if((!m||e)&&j)if((z||da)&&!V)V=setTimeout(function(){V=0;C()},50);U=!1}}function D(a){s(r,a[0])||q(n(a[0],null,!0)).init(a[1],a[2])}function J(a){var a=a.currentTarget||a.srcElement,b=i.onScriptLoad;a.detachEvent&&!W?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=i.onScriptError;(!a.detachEvent||W)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function K(){var a;for(w();G.length;){a=
G.shift();if(null===a[0])return v(A("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));D(a)}}var U,X,i,N,V,k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},config:{}},p={},T={},Y={},G=[],r={},S={},L=1,M=1;N={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=r[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){var b=
l(k.pkgs,a.map.id);return(b?l(k.config,a.map.id+"/"+b.main):l(k.config,a.map.id))||{}},exports:r[a.map.id]}}};X=function(a){this.events=l(Y,a.id)||{};this.map=a;this.shim=l(k.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};X.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=u(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;
this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],u(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=
this.map.url;S[a]||(S[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,m=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{e=i.execCb(c,m,b,e)}catch(d){a=d}else e=i.execCb(c,m,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==
this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else e=m;this.exports=e;if(this.map.isDefine&&!this.ignore&&(r[c]=e,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=n(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var m,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=n(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),
d=l(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(e,c){var d=a.name,g=n(d),B=O;c&&(e=c);B&&(O=!1);q(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{j.exec(e)}catch(ca){return v(A("fromtexteval",
"fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],m)}),e.load(a.name,h,m,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,
a);this.check()}));this.errback&&t(a,"error",u(this,this.errback))}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n,
nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};F(a,function(a,b){e[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,
location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return v(A("requireargs",
"Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(j.get)return j.get(i,e,a,d);g=n(e,a,!1,!0);g=g.id;return!s(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});C()});return d}f=f||{};Q(d,{isBrowser:z,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1<f))d=b.substring(f,b.length),b=
b.substring(0,f);return i.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return s(r,n(b,a,!1,!0).id)},specified:function(b){b=n(b,a,!1,!0).id;return s(r,b)||s(p,b)}});a||(d.undef=function(b){w();var c=n(b,a,!0),f=l(p,b);delete r[b];delete S[c.url];delete Y[b];f&&(f.events.defined&&(Y[b]=f.events),x(b))});return d},enable:function(a){l(p,a.id)&&q(a).enable()},completeLoad:function(a){var b,c,e=l(k.shim,a)||{},d=e.exports;for(w();G.length;){c=G.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===
a&&(b=!0);D(c)}c=l(p,a);if(!b&&!s(r,a)&&c&&!c.inited){if(k.enforceDefine&&(!d||!ba(d)))return h(a)?void 0:v(A("nodefine","No define call for "+a,null,[a]));D([a,e.deps||[],e.exportsFn])}C()},nameToUrl:function(a,b,c){var e,d,h,g,i,n;if(j.jsExtRegExp.test(a))g=a+(b||"");else{e=k.paths;d=k.pkgs;g=a.split("/");for(i=g.length;0<i;i-=1)if(n=g.slice(0,i).join("/"),h=l(d,n),n=l(e,n)){I(n)&&(n=n[0]);g.splice(0,i,n);break}else if(h){a=a===h.name?h.location+"/"+h.main:h.location;g.splice(0,i,a);break}g=g.join("/");
g+=b||(/\?/.test(g)||c?"":".js");g=("/"===g.charAt(0)||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((-1===g.indexOf("?")?"?":"&")+k.urlArgs):g},load:function(a,b){j.load(i,a,b)},execCb:function(a,b,c,e){return b.apply(e,c)},onScriptLoad:function(a){if("load"===a.type||ka.test((a.currentTarget||a.srcElement).readyState))P=null,a=J(a),i.completeLoad(a.id)},onScriptError:function(a){var b=J(a);if(!h(b.id))return v(A("scripterror","Script error for: "+b.id,a,[b.id]))}};i.require=i.makeRequire();
return i}var j,w,x,C,J,D,P,K,q,fa,la=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ma=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/\.js$/,ja=/^\.\//;w=Object.prototype;var L=w.toString,ga=w.hasOwnProperty,ia=Array.prototype.splice,z=!!("undefined"!==typeof window&&navigator&&window.document),da=!z&&"undefined"!==typeof importScripts,ka=z&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,W="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),E={},t={},R=[],O=
!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(H(requirejs))return;t=requirejs;requirejs=void 0}"undefined"!==typeof require&&!H(require)&&(t=require,require=void 0);j=requirejs=function(b,c,d,h){var q,n="_";!I(b)&&"string"!==typeof b&&(q=b,I(c)?(b=c,c=d,d=h):b=[]);q&&q.context&&(n=q.context);(h=l(E,n))||(h=E[n]=j.s.newContext(n));q&&h.configure(q);return h.require(b,c,d)};j.config=function(b){return j(b)};j.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,
4)}:function(b){b()};require||(require=j);j.version="2.1.8";j.jsExtRegExp=/^\/|:|\?|\.js$/;j.isBrowser=z;w=j.s={contexts:E,newContext:ha};j({});y(["toUrl","undef","defined","specified"],function(b){j[b]=function(){var c=E._;return c.require[b].apply(c,arguments)}});if(z&&(x=w.head=document.getElementsByTagName("head")[0],C=document.getElementsByTagName("base")[0]))x=w.head=C.parentNode;j.onError=aa;j.createNode=function(b){var c=b.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):
document.createElement("script");c.type=b.scriptType||"text/javascript";c.charset="utf-8";c.async=!0;return c};j.load=function(b,c,d){var h=b&&b.config||{};if(z)return h=j.createNode(h,c,d),h.setAttribute("data-requirecontext",b.contextName),h.setAttribute("data-requiremodule",c),h.attachEvent&&!(h.attachEvent.toString&&0>h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",
b.onScriptError,!1)),h.src=d,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+d,l,[c]))}};z&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,t.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",t.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),t.deps=t.deps?t.deps.concat(q):[q],!0});
define=function(b,c,d){var h,j;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=null);!c&&H(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j?j.defQueue:
R).push([b,c,d])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(t)}})(this);

View File

@ -1,65 +0,0 @@
/* Copyright 2012, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py.
*
* utility functions -- JS Y U SO STUPID?
*
* read(cookie): return `cookie` string if set
* format(date): human-readable date formatting
* brew(array): similar to DOMinate essentials
*/
function read(cookie) {
return (document.cookie.match('(^|; )' + cookie + '=([^;]*)') || 0)[2]
};
function format(date) {
/*!
* JavaScript Pretty Date
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*/
var diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31)
return;
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && Math.floor(diff / 60) + " minutes ago" ||
diff < 7200 && "1 hour ago" ||
diff < 86400 && Math.floor(diff / 3600) + " hours ago") ||
day_diff == 1 && "Yesterday" ||
day_diff < 7 && day_diff + " days ago" ||
day_diff < 31 && Math.ceil(day_diff / 7) + " weeks ago";
}
function brew(arr) {
/*
* Element creation utility. Similar to DOMinate, but with a slightly different syntax:
* brew([TAG, {any: attribute, ...}, 'Hello World', ' Foo Bar', ['TAG', 'Hello World'], ...])
* --> <TAG any="attribute">Hello World Foo Bar<TAG>Hello World</TAG></TAG>
*/
var rv = document.createElement(arr[0]);
for (var i = 1; i < arr.length; i++) {
if (arr[i] instanceof Array) {
rv.appendChild(brew(arr[i]));
} else if (typeof(arr[i]) == "string") {
rv.appendChild(document.createTextNode(arr[i]));
} else {
attrs = arr[i] || {};
for (var k in attrs) {
if (!attrs.hasOwnProperty(k)) continue;
rv.setAttribute(k, attrs[k]);
}
}
};
return rv;
}

View File

@ -62,5 +62,5 @@ class Comment(object):
def md5(self): def md5(self):
hv = hashlib.md5() hv = hashlib.md5()
for key in set(self.fields) - set(['parent', ]): for key in set(self.fields) - set(['parent', ]):
hv.update(getattr(self, key) or '') hv.update(getattr(self, key).encode('utf-8', errors="replace") or u'')
return hv.hexdigest() return hv.hexdigest()

View File

@ -3,11 +3,9 @@
<title>Hello World</title> <title>Hello World</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="/static/style.css" />
<!-- script type="text/javascript" src="/js/embed.js"></script --> <!-- script type="text/javascript" src="/js/embed.js"></script -->
<script type="text/javascript" src="/js/ender.js"></script> <script data-main="/client/embed" src="/client/require.js"></script>
<script type="text/javascript" src="/js/isso.js"></script>
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/client.js"></script>
<style type="text/css"> <style type="text/css">
body { body {
margin: 0 auto; margin: 0 auto;
@ -46,7 +44,7 @@
<footer> <footer>
<hr /> <hr />
<div id="isso_thread"></div> <div id="isso-thread"></div>
<noscript> <noscript>
<p>Please enable JavaScript to view the comments powered by Isso™.</a></p> <p>Please enable JavaScript to view the comments powered by Isso™.</a></p>
</noscript> </noscript>
@ -54,4 +52,4 @@
</article> </article>
</body> </body>

View File

@ -1,68 +1,133 @@
#comments { #isso-comments {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.isso { .isso-comment {
margin-bottom: 18px; margin-bottom: 18px;
margin-top: 18px; margin-top: 18px;
} }
.isso footer a, .isso header a { .isso-follow-up {
margin-left: 64px;
}
.isso-comment footer a, .isso-comment header a {
color: #111111; color: #111111;
font-family: Palatino, "times new roman", serif;
font-weight: bold; font-weight: bold;
font-size: 0.95em; font-size: 0.95em;
text-decoration: none; text-decoration: none;
} }
.isso footer a:hover, .isso header a:hover { .isso-comment footer a:hover, .isso-comment header a:hover {
color: #111111; color: #111111;
text-shadow: #aaaaaa 0px 0px 1px; text-shadow: #aaaaaa 0px 0px 1px;
} }
.isso .author { .isso-comment > header, .isso-comment > footer {
font-family: "Helvetica", Arial, sans-serif;
font-size: 0.80em;
}
.isso-comment .author {
font-weight: bold; font-weight: bold;
color: #555;
} }
.isso time { .isso-comment .spacer {
color: lightgray; padding-left: 6px;
float: right; padding-right: 6px;
} }
.isso > header { .isso-comment .spacer, .isso-comment .permalink {
border-bottom: solid 1px lightgray; color: gray;
font-weight: normal;
} }
.isso > header .note { .isso-comment > header {
margin-bottom: 8px;
margin-left: 64px;
}
.isso-comment > header .note {
float: right; float: right;
color: gray; color: gray;
} }
.isso > footer > a { .isso-comment span.avatar {
position: relative;
margin-top: -22px;
/*margin: 16px 0px 12px 0px;*/
float: left;
clear: both;
}
img:-moz-broken {
-moz-force-broken-image-icon: 1;
}
/* comment style */
.isso-comment > div.text {
margin-left: 64px;
min-height: 24px;
margin-bottom: 4px;
text-align: left;
font-family: Georgia, Times, serif;
}
/* .. */
.isso-comment > footer {
margin-left: 64px;
}
.isso-comment > footer > a {
padding: 0 10px 10px 0; padding: 0 10px 10px 0;
} }
.isso + .issoform { /* comment form */
.isso-comment + .isso-comment-box {
margin-left: 20px; margin-left: 20px;
} }
.issoform input, .issoform textarea { .isso-comment-box ul {
border: 4px solid rgba(0,0,0,0.1); padding: 0;
padding: 8px 10px; }
-webkit-border-radius: 5px; .isso-comment-box .optional {
-moz-border-radius: 5px; margin-bottom: 6px;
border-radius: 5px; }
.isso-comment-box li {
margin-right: 8px;
display: inline;
}
.isso-comment-box li input {
margin-left: 0;
}
.isso-comment-box input, .isso-comment-box textarea {
border: 1px solid rgba(0,0,0,0.25);
padding: 8px 10px;
border-radius: 3px;
outline: 0; outline: 0;
} }
.issoform textarea { .isso-comment-box textarea {
width: 350px; width: 640px;
margin-bottom: 6px;
} }
.issoform input[type="submit"] { .isso-comment-box input[type="submit"] {
cursor: pointer; cursor: pointer;
background: -webkit-linear-gradient(top, #efefef, #ddd); background: -webkit-linear-gradient(top, #efefef, #ddd);
background: -moz-linear-gradient(top, #efefef, #ddd); background: -moz-linear-gradient(top, #efefef, #ddd);
@ -72,9 +137,10 @@
color: #333; color: #333;
text-shadow: 0px 1px 1px rgba(255,255,255,1); text-shadow: 0px 1px 1px rgba(255,255,255,1);
border: 1px solid #ccc; border: 1px solid #ccc;
margin-left: 0px;
} }
.issoform input[type="submit"]:hover { .isso-comment-box input[type="submit"]:hover {
background: -webkit-linear-gradient(top, #eee, #ccc); background: -webkit-linear-gradient(top, #eee, #ccc);
background: -moz-linear-gradient(top, #eee, #ccc); background: -moz-linear-gradient(top, #eee, #ccc);
background: -ms-linear-gradient(top, #eee, #ccc); background: -ms-linear-gradient(top, #eee, #ccc);
@ -83,7 +149,7 @@
border: 1px solid #bbb; border: 1px solid #bbb;
} }
.issoform input[type="submit"]:active { .isso-comment-box input[type="submit"]:active {
background: -webkit-linear-gradient(top, #ddd, #aaa); background: -webkit-linear-gradient(top, #ddd, #aaa);
background: -moz-linear-gradient(top, #ddd, #aaa); background: -moz-linear-gradient(top, #ddd, #aaa);
background: -ms-linear-gradient(top, #ddd, #aaa); background: -ms-linear-gradient(top, #ddd, #aaa);
@ -92,6 +158,25 @@
border: 1px solid #999; border: 1px solid #999;
} }
.issoform div { input {
margin-bottom: 8px; transition: background-color 0.1s ease;
}
input:focus:invalid {
box-shadow: inset 0px 0px 6px #8b0000;
}
textarea:focus:valid, input:focus:valid {
box-shadow: inset 0px 0px 6px #2f4f4f;
}
.isso-popup {
font-family: Helvetica, Arial, sans-serif;
font-size: 0.8em;
padding: 6px 8px;
margin-left: 20px;
border: 1px solid rgb(242, 122, 122);
border-radius: 2px;
background-color: rgb(242, 222, 222);
z-index: 9002;
} }

28
isso/templates/admin.j2 Normal file
View File

@ -0,0 +1,28 @@
{% extends "base.j2" %}
{% block body %}
<div class="row">
<div class="span4">
<h2>Overview</h1>
<ul>
<li><b>N</b> approved</li>
<li><b>N</b> pending</li>
</ul>
<ul>
<li><b>N</b> Threads</li>
</ul>
</div>
<div class="span8">
<h2>Recent Comments</h1>
</div>
</div>
{% endblock %}

View File

@ -1,105 +0,0 @@
<%inherit file="base.mako"/>
<%
from time import strftime, gmtime
from urllib import quote, urlencode
from urlparse import parse_qsl
def query(**kw):
qs = dict(parse_qsl(request.query_string))
qs.update(kw)
return urlencode(qs)
def get(name, convert):
limit = request.args.get(name)
return convert(limit[0]) if limit is not None else None
%>
<%block name="title">
Isso Dashboard
</%block>
<%def name="make(comment)">
<article class="isso column grid_12" data-path="${quote(comment.path)}" data-id="${comment.id}">
<div class="row">
<div class="column grid_9">
<header>
<span class="title"><a href="${comment.path}">${comment.path}</a></span>
</header>
<div class="text">
${app.markup.convert(comment.text)}
</div>
</div>
<div class="column grid_3 options">
<ul>
<li>${strftime('%d. %B %Y um %H:%M', gmtime(comment.created))}</li>
<li>von <em>${comment.author}</em></li>
% if comment.website:
<li><a href="${comment.website}">${comment.website}</a></li>
% endif
</ul>
<div class="row buttons">
<a href="#" class="red delete column grid_1">Delete</a>
% if comment.pending:
<a href="#" class="green approve column grid_1">Approve</a>
% endif
</div>
</div>
</div>
</article>
</%def>
<div class="row pending red">
<div class="column grid_9">
<h2>Pending</h2>
</div>
<div class="column grid_3">
<span class="limit">
[ <a href="?${query(pendinglimit=10)}">10</a>
| <a href="?${query(pendinglimit=20)}">20</a>
| <a href="?${query(pendinglimit=100000)}">All</a> ]
</span>
</div>
</div>
<div class="row" id="pending">
% for comment in app.db.recent(limit=get('pendinglimit', int), mode=2):
${make(comment)}
% endfor
</div>
<div class="row recent green">
<div class="column grid_9">
<h2>Recent</h2>
</div>
<div class="column grid_3">
<span class="limit">
[ <a href="?${query(recentlimit=10)}">10</a>
| <a href="?${query(recentlimit=20)}">20</a>
| <a href="?${query(recentlimit=100000)}">All</a> ]
</span>
</div>
</div>
<div class="row" id="approved">
% for comment in app.db.recent(limit=get('recentlimit', int) or 20, mode=5):
${make(comment)}
% endfor
</div>
<footer class="row">
<p><a href="https://github.com/posativ/isso">Isso</a> Ich schrei sonst!</p>
</footer>

82
isso/templates/base.j2 Normal file
View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Isso: Admin Console</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="../static/css/bootstrap.css" rel="stylesheet">
<style>
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
#footer {
background-color: #f5f5f5;
position: fixed;
bottom: 0;
width: 100%;
}
.container .credit {
margin: 20px 0;
}
</style>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="../assets/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="#">Isso</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li class="active"><a href="#">Dashboard</a></li>
<li><a href="#about">About</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container">
{% block body %}
{% endblock %}
</div> <!-- /container -->
<footer id="footer">
<div class="container">
<p class="muted credit"><a href="https://github.com/posativ/isso">Isso</a> Ich schrei sonst!</p>
</div>
</footer>
<footer class="footer">
</footer>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="../static/jquery.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
</body>
</html>

View File

@ -1,123 +0,0 @@
<!DOCTYPE html>
<head>
<title><%block name="title" /></title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<script type="text/javascript" src="/js/interface.js"></script>
<style>
/* ================ */
/* = The 1Kb Grid = */ /* 12 columns, 60 pixels each, with 20 pixel gutter */
/* ================ */
.grid_1 { width: 60px; }
.grid_2 { width: 140px; }
.grid_3 { width: 220px; }
.grid_4 { width: 300px; }
.grid_5 { width: 380px; }
.grid_6 { width: 460px; }
.grid_7 { width: 540px; }
.grid_8 { width: 620px; }
.grid_9 { width: 700px; }
.grid_10 { width: 780px; }
.grid_11 { width: 860px; }
.grid_12 { width: 940px; }
.column {
margin: 0 10px;
overflow: hidden;
float: left;
display: inline;
}
.row {
width: 960px;
margin: 0 auto;
overflow: hidden;
}
.row .row {
margin: 0 -10px;
width: auto;
display: inline-block;
}
* {
margin: 0;
padding: 0;
}
body {
font-size: 1em;
line-height: 1.4;
margin: 20px 0 0 0;
}
body > h1 {
text-align: center;
padding: 8px 0 8px 0;
background-color: yellowgreen;
}
article {
background-color: rgb(245, 245, 245);
box-shadow: 0px 0px 4px 0px;
/*border-radius: 2px;*/
}
article header {
font-size: 1.1em;
}
article .options {
margin: 8px;
padding-bottom: 40px
}
article .text, article header {
padding: 8px 16px 8px 16px;
}
.text p {
margin-bottom: 10px;
text-align: justify;
}
.recent, .pending {
padding: 10px 40px 10px 40px;
box-shadow: 0px 0px 2px 0px;
/*border-radius: 4px;*/
margin: 2px auto 2px auto;
}
.green {
background-color: #B7D798;
}
.red {
background-color: #D79998;
}
body > footer {
border-top: 1px solid #AAA;
padding-top: 8px;
text-align: center;
}
.approve, .delete {
padding-top: 10px;
padding: 5px;
border: 1px solid #666;
color: #000;
text-decoration: none;
}
.buttons {
padding-top: 10px;
}
<%block name="style" />
</style>
</head>
<body>
${self.body()}
</body>

69
isso/templates/login.j2 Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sign in &middot; Isso</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="../static/css/bootstrap.css" rel="stylesheet">
<style type="text/css">
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
max-width: 300px;
padding: 19px 29px 29px;
margin: 0 auto 20px;
background-color: #fff;
border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin input[type="text"],
.form-signin input[type="password"] {
font-size: 16px;
height: auto;
margin-bottom: 15px;
padding: 7px 9px;
}
</style>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="../assets/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<form class="form-signin" action="/admin/", method="post">
<h2 class="form-signin-heading">Admin Interface</h2>
<input type="password" class="input-block-level" placeholder="secret" name="password">
<label class="checkbox">
<input type="checkbox" value="remember-me"> Remember me
</label>
<button class="btn btn-large btn-primary" type="submit">Sign in</button>
</form>
</div> <!-- /container -->
</body>
</html>

View File

@ -1,38 +0,0 @@
<%inherit file="base.mako"/>
<%block name="title">
Isso Login
</%block>
<%block name="style">
#login {
margin: 280px 270px 0 270px;
padding: 30px;
background-color: rgb(245, 245, 245);
box-shadow: 0px 0px 1px 0px;
text-align: center;
}
.button {
border: 1px solid #006;
margin-left: 2px;
padding: 2px 4px 2px 4px;
}
#login input {
margin-top: 8px;
text-align: center;
}
#login input:-moz-placeholder, #login input::-webkit-input-placeholder {} {
color: #CCC;
}
</%block>
<!-- login form -->
<div id="login">
<form action="/admin/" method="post">
<input name="secret" placeholder="secret" type="password" />
<input type="submit" name="submit" value="Login" class="button" />
</form>
</div>

View File

@ -9,7 +9,7 @@ import urllib
from itsdangerous import SignatureExpired, BadSignature from itsdangerous import SignatureExpired, BadSignature
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
from werkzeug.exceptions import abort from werkzeug.exceptions import abort, BadRequest
from isso import models, utils from isso import models, utils
@ -24,12 +24,12 @@ class requires:
def dec(app, env, req, *args, **kwargs): def dec(app, env, req, *args, **kwargs):
if self.param not in req.args: if self.param not in req.args:
abort(400) raise BadRequest("missing %s query" % self.param)
try: try:
kwargs[self.param] = self.type(req.args[self.param]) kwargs[self.param] = self.type(req.args[self.param])
except TypeError: except TypeError:
abort(400) raise BadRequest("invalid type for %s, expected %s" % (self.param, self.type))
return func(app, env, req, *args, **kwargs) return func(app, env, req, *args, **kwargs)
@ -45,6 +45,7 @@ def create(app, environ, request, uri):
try: try:
comment = models.Comment.fromjson(request.data) comment = models.Comment.fromjson(request.data)
except ValueError as e: except ValueError as e:
print(1)
return Response(unicode(e), 400) return Response(unicode(e), 400)
for attr in 'author', 'email', 'website': for attr in 'author', 'email', 'website':
@ -52,11 +53,13 @@ def create(app, environ, request, uri):
try: try:
setattr(comment, attr, cgi.escape(getattr(comment, attr))) setattr(comment, attr, cgi.escape(getattr(comment, attr)))
except AttributeError: except AttributeError:
print(1)
Response('', 400) Response('', 400)
try: try:
rv = app.db.add(uri, comment) rv = app.db.add(uri, comment)
except ValueError: except ValueError:
print(1)
abort(400) # FIXME: custom exception class, error descr abort(400) # FIXME: custom exception class, error descr
md5 = rv.md5 md5 = rv.md5