From f6271e5cf666adedf3f824bef65d27ce9cce5b8b Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Thu, 5 Sep 2013 19:31:18 +0200 Subject: [PATCH] commit work in progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- isso/__init__.py | 3 +- isso/client/app/api.js | 111 ++ isso/client/app/forms.js | 54 + isso/client/app/isso.js | 202 ++++ isso/client/app/logging.js | 9 + isso/client/app/models.js | 10 + isso/client/embed.js | 5 + isso/client/helper/utils.js | 81 ++ isso/client/isso.js | 42 +- isso/client/lib/HTML.js | 4 + isso/client/lib/q.js | 1937 +++++++++++++++++++++++++++++++++++ isso/client/lib/ready.js | 55 + isso/client/main.js | 6 + isso/client/require.js | 36 + isso/client/utils.js | 65 -- isso/models.py | 2 +- isso/static/post.html | 10 +- isso/static/style.css | 139 ++- isso/templates/admin.j2 | 28 + isso/templates/admin.mako | 105 -- isso/templates/base.j2 | 82 ++ isso/templates/base.mako | 123 --- isso/templates/login.j2 | 69 ++ isso/templates/login.mako | 38 - isso/views/comment.py | 9 +- 25 files changed, 2832 insertions(+), 393 deletions(-) create mode 100644 isso/client/app/api.js create mode 100644 isso/client/app/forms.js create mode 100644 isso/client/app/isso.js create mode 100644 isso/client/app/logging.js create mode 100644 isso/client/app/models.js create mode 100644 isso/client/embed.js create mode 100644 isso/client/helper/utils.js create mode 100644 isso/client/lib/HTML.js create mode 100644 isso/client/lib/q.js create mode 100644 isso/client/lib/ready.js create mode 100644 isso/client/main.js create mode 100644 isso/client/require.js delete mode 100644 isso/client/utils.js create mode 100644 isso/templates/admin.j2 delete mode 100644 isso/templates/admin.mako create mode 100644 isso/templates/base.j2 delete mode 100644 isso/templates/base.mako create mode 100644 isso/templates/login.j2 delete mode 100644 isso/templates/login.mako diff --git a/isso/__init__.py b/isso/__init__.py index 9fac5c3..12b3d44 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -155,7 +155,8 @@ def main(): sys.exit(0) 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'), diff --git a/isso/client/app/api.js b/isso/client/app/api.js new file mode 100644 index 0000000..5914504 --- /dev/null +++ b/isso/client/app/api.js @@ -0,0 +1,111 @@ +/* + * Copyright 2013, Martin Zimmermann . 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("

(.+)

") + 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("

(.+)

") + throw {status: rv.status, reason: (msg && msg[1]) || rv.body} + } + }) + } + + return { + endpoint: endpoint, + create: create, + remove: remove, + fetchall: fetchall + } + +}); \ No newline at end of file diff --git a/isso/client/app/forms.js b/isso/client/app/forms.js new file mode 100644 index 0000000..4330cc7 --- /dev/null +++ b/isso/client/app/forms.js @@ -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 + } +}); \ No newline at end of file diff --git a/isso/client/app/isso.js b/isso/client/app/isso.js new file mode 100644 index 0000000..cbb3e8f --- /dev/null +++ b/isso/client/app/isso.js @@ -0,0 +1,202 @@ +/* Isso – Ich schrei sonst! + * + * Copyright 2013, Martin Zimmermann . 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 = " " + } 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 + } +}); \ No newline at end of file diff --git a/isso/client/app/logging.js b/isso/client/app/logging.js new file mode 100644 index 0000000..ff7e8cb --- /dev/null +++ b/isso/client/app/logging.js @@ -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) + } + } +}) \ No newline at end of file diff --git a/isso/client/app/models.js b/isso/client/app/models.js new file mode 100644 index 0000000..e587d17 --- /dev/null +++ b/isso/client/app/models.js @@ -0,0 +1,10 @@ +define(function() { + + function Comment(data) { + this.text = data["text"]; + } + + return { + Comment: Comment + } +}); \ No newline at end of file diff --git a/isso/client/embed.js b/isso/client/embed.js new file mode 100644 index 0000000..0935925 --- /dev/null +++ b/isso/client/embed.js @@ -0,0 +1,5 @@ +require(["lib/ready", "app/isso"], function(domready, isso) { + domready(function() { + isso.init(); + }) +}); diff --git a/isso/client/helper/utils.js b/isso/client/helper/utils.js new file mode 100644 index 0000000..569a537 --- /dev/null +++ b/isso/client/helper/utils.js @@ -0,0 +1,81 @@ +/* Copyright 2013, Martin Zimmermann . 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." + } +}); \ No newline at end of file diff --git a/isso/client/isso.js b/isso/client/isso.js index b49ccc5..68d7812 100644 --- a/isso/client/isso.js +++ b/isso/client/isso.js @@ -1,41 +1,35 @@ /* Isso – Ich schrei sonst! * - * Copyright 2012, Martin Zimmermann . All rights reserved. + * Copyright 2013, Martin Zimmermann . All rights reserved. * 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? -var isso = isso || {}, - prefix = "", - path = encodeURIComponent(window.location.pathname); - -// XXX -isso.prefix = prefix; -isso.path = path; +var isso = {}; -/* - * isso specific helpers to create, modify, remove and receive comments - */ +var init = function() { + var isso = new Object(); -function verify(data) { - return data['text'] == null ? false : true -}; + // 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$")) { + isso.location = js[i].src.substring(0, 18); + break; + } + } + + console.log(isso.location) +} isso.create = function(data, func) { - if (!verify(data)) { - return; - } + var request = new XMLHttpRequest(); - $.ajax('POST', prefix + '/1.0/' + isso.path + '/new', - JSON.stringify(data), {'Content-Type': 'application/json'}).then(func); +// $.ajax('POST', prefix + '/1.0/' + isso.path + '/new', +// JSON.stringify(data), {'Content-Type': 'application/json'}).then(func); }; diff --git a/isso/client/lib/HTML.js b/isso/client/lib/HTML.js new file mode 100644 index 0000000..3814e62 --- /dev/null +++ b/isso/client/lib/HTML.js @@ -0,0 +1,4 @@ +/*! HTML - v0.10.2 - 2013-08-25 +* http://nbubna.github.io/HTML/ +* Copyright (c) 2013 ESHA Research; Licensed MIT, GPL */ +!function(a,b,c){"use strict";var d={version:"0.10.2",slice:Array.prototype.slice,list:function(a,b){return 1===a.length?d.node(a[0],b):((b||!a.each)&&(a.slice||(a=d.slice.call(a)),d.methods(a),a.length&&d.children(a[0],a)),a)},node:function(a,b){return(b||!a.each)&&(d.methods(a),d.children(a)),a},methods:function(a){for(var b in d.fn)d.define(a,b,d.fn[b])},children:function(a,b){for(var c=a._children={},e=0,f=a.childNodes.length;f>e;e++){var g=a.childNodes[e],h=d.key(g);(c[h]||(c[h]=[])).push(g),d.define(a,h),b&&d.define(b,h,void 0,a)}return c},key:function(a){return a.tagName?a.tagName.toLowerCase():"_other"},define:function(a,b,c,e){if(!(b in a))try{e=e||a,Object.defineProperty(a,b,void 0!==c?{value:c}:{get:function(){return e._children||d.children(e),d.list(e._children[b]||[])}})}catch(f){}},mutation:function(a){var b=a.target;delete b[b._internal?"_internal":"_children"]},unique:function(a,b,c){return c.indexOf(a)===b},fn:{each:function(a){var b,c,e=this.forEach?this:[this],f=[];"string"==typeof a&&(b=d.resolve[a]||a,c=d.slice.call(arguments,1),a=function(a,e){return d.resolve(b,a,c,e)});for(var g,h=0,i=e.length;i>h;h++)g=a.call(e,d.node(e[h]),h,e),(g||b&&void 0!==g)&&(g.forEach?f.push.apply(f,g):f.push(g));return f[0]||f[0]===!1?f[0].matches?d.list(f.filter(d.unique)):f:this},find:function(){try{a.console.warn("find() is deprecated. Please use query().")}finally{return this.query.apply(this,arguments)}},query:function(a){for(var b=this.forEach?this:[this],c=[],e=0,f=b.length;f>e;e++)for(var g=b[e].querySelectorAll(a),h=0,i=g.length;i>h;h++)c.push(g[h]);return d.list(c)},only:function(a,b){var c=this.forEach?this:[this];return d.list(a>=0||0>a?c.slice(a,b||a+1||void 0):c.filter("function"==typeof a?a:function(b){return b.matches(a)}))}},resolve:function(a,b,c,e){var f=a,g=b;if(c=c.length?d.fill(c,e,g):null,f.indexOf(".")>0){for(var h=f.split(".");h.length>1&&(g=g[f=h.shift()]););g=g||b,f=g?h[0]:a}var i=g[f];if(void 0!==i){if("function"==typeof i)return i.apply(g,c);if(!c)return i;g[f]=c[0]}else{if(!c)return b.getAttribute(a);null===c[0]?b.removeAttribute(a):b.setAttribute(a,c[0])}},fill:function(a,b,c){for(var d=[],e=0,f=a.length;f>e;e++){var g=a[e],h=typeof g;d[e]="string"===h?g.replace(/\$\{i\}/g,b):"function"===h?g(c,b,a):g}return d}},e=d.node(b.documentElement);e._=d,d.define(e,"ify",function(a,b){return!a||"length"in a?d.list(a||[],b):d.node(a,b)});var f=Element.prototype,g="atchesSelector";d.define(f,"matches",f.m||f["webkitM"+g]||f["mozM"+g]||f["msM"+g]),c?new c(function(a){a.forEach(d.mutation)}).observe(e,{childList:!0,subtree:!0}):b.addEventListener("DOMSubtreeModified",d.mutation),"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:a[e.getAttribute("data-html-reference")||"HTML"]=e,b.addEventListener("DOMContentLoaded",function(){d.node(e,!0)})}(window,document,window.MutationObserver),function(a,b){"use strict";var c=b.fn.add=function(a,b){return this.each(function(d){return c.all(d,a,b)})};c.all=function(a,b,d){if("string"==typeof b)return c.create(a,b,d);if("length"in b){for(var e=[],f=0,g=b.length;g>f;f++)e.push(c.all(a,b[f],d));return e}return c.insert(a,b,d),b},c.create=function(b,d,e){return c.insert(b,a.createElement(d),e)},c.insert=function(a,d,e){var f=c.find(a,e);return f?a.insertBefore(d,f):a.appendChild(d),b.updated(a),d},c.find=function(a,b){switch(typeof b){case"string":return a[b+"Child"];case"number":return a.children[b];case"object":return b;case"function":return b.call(a,a)}},b.updated=function(a){a._internal=!0,b.children(a)},b.fn.remove=function(a){return this.each(function(c){var d=c.parentNode;return d&&(d.removeChild(c),b.updated(d),a)?d:void 0})}}(document,document.documentElement._),function(a,b){"use strict";var c=b._,d=c.fn.event=function(){try{window.console.warn("event() is deprecated. https://github.com/nbubna/HTML/issues/1")}catch(a){}var b,e=c.slice.call(arguments),f=this,g=[];return e[0]?!e[1]||e[1].forEach?b="trigger":(b="on",1===e[0]&&(e[4]=e.shift()),(!e[2]||e[2].forEach)&&(e.splice(1,0,!1),e.splice(4,1)),e[3]=(e[3]||[]).slice(0),"string"==typeof e[2]&&(e[5]=e[2],e[3].unshift(e[2]),e[2]=c.fn.each)):(b="off",e.shift(),"function"==typeof e[1]&&e.splice(1,0,!1)),(e[0]||"").split(" ").forEach(function(a){e[0]=a,f.each(function(a){g.push(d[b].apply(a,e))})}),"trigger"===b?1===g.length?g[0]:g:this},e=Array.prototype.concat;d.on=function(a,b,e,f,g,h){var i=function(a){d.heard.call(this,a,b,e,f,g,h)};this.addEventListener(a,i),c.define(this,"_evt",[]),this._evt.push([a,b,h||e,i])},d.off=function(a,b,c){if(this._evt)for(var d=0;di;i++){var k=f[i];h=c.emmet[k.charAt(0)].call(h,k.substr(1),g)||h}return c.insert(b,g,e),h},c.emmetRE=function(){var a="\\"+Object.keys(c.emmet).join("|\\");return new RegExp("(?="+a+")","g")},c.emmet={"#":function(a){this.id=a},".":function(a){var b=this.getAttribute("class")||"";b=b+(b?" ":"")+a,this.setAttribute("class",b)},"[":function(a){a=a.substr(0,a.length-1).match(/(?:[^\s"]+|"[^"]*")+/g);for(var b=0,c=a.length;c>b;b++){var d=a[b].split("=");this.setAttribute(d[0],(d[1]||"").replace(/"/g,""))}},">":function(b){if(b){var c=a.createElement(b);return this.appendChild(c),c}return this},"+":function(a,b){return c.emmet[">"].call(this.parentNode||b,a)},"*":function(a){for(var b=this.parentNode,c=[this],d=1;a>d;d++)c.push(this.cloneNode(!0)),b.appendChild(c[d]);return c},"^":function(a,b){return c.emmet["+"].call(this.parentNode||b,a,b)},"{":function(b){this.appendChild(a.createTextNode(b.substr(0,b.length-1)))}}}(document,document.documentElement._); \ No newline at end of file diff --git a/isso/client/lib/q.js b/isso/client/lib/q.js new file mode 100644 index 0000000..fe96ca8 --- /dev/null +++ b/isso/client/lib/q.js @@ -0,0 +1,1937 @@ +// vim:ts=4:sts=4:sw=4: +/*! + * + * Copyright 2009-2012 Kris Kowal under the terms of the MIT + * license found at http://github.com/kriskowal/q/raw/master/LICENSE + * + * With parts by Tyler Close + * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found + * at http://www.opensource.org/licenses/mit-license.html + * Forked at ref_send.js version: 2009-05-11 + * + * With parts by Mark Miller + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +(function (definition) { + // Turn off strict mode for this function so we can assign to global.Q + /* jshint strict: false */ + + // This file will function properly as a - - - - + + + + + + + + + + +
+ + {% block body %} + + {% endblock %} + + +
+ +
+
+

Isso – Ich schrei sonst!

+
+
+ +
+ +
+ + + + + + + diff --git a/isso/templates/base.mako b/isso/templates/base.mako deleted file mode 100644 index 1aff922..0000000 --- a/isso/templates/base.mako +++ /dev/null @@ -1,123 +0,0 @@ - - - <%block name="title" /> - - - - - - - ${self.body()} - \ No newline at end of file diff --git a/isso/templates/login.j2 b/isso/templates/login.j2 new file mode 100644 index 0000000..61d248c --- /dev/null +++ b/isso/templates/login.j2 @@ -0,0 +1,69 @@ + + + + + Sign in · Isso + + + + + + + + + + + + + + + +
+ + + +
+ + + diff --git a/isso/templates/login.mako b/isso/templates/login.mako deleted file mode 100644 index b18b130..0000000 --- a/isso/templates/login.mako +++ /dev/null @@ -1,38 +0,0 @@ -<%inherit file="base.mako"/> - -<%block name="title"> - Isso – Login - - -<%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; - } - - - -
-
- - -
-
diff --git a/isso/views/comment.py b/isso/views/comment.py index 7050869..dbccc95 100644 --- a/isso/views/comment.py +++ b/isso/views/comment.py @@ -9,7 +9,7 @@ import urllib from itsdangerous import SignatureExpired, BadSignature from werkzeug.wrappers import Response -from werkzeug.exceptions import abort +from werkzeug.exceptions import abort, BadRequest from isso import models, utils @@ -24,12 +24,12 @@ class requires: def dec(app, env, req, *args, **kwargs): if self.param not in req.args: - abort(400) + raise BadRequest("missing %s query" % self.param) try: kwargs[self.param] = self.type(req.args[self.param]) except TypeError: - abort(400) + raise BadRequest("invalid type for %s, expected %s" % (self.param, self.type)) return func(app, env, req, *args, **kwargs) @@ -45,6 +45,7 @@ def create(app, environ, request, uri): try: comment = models.Comment.fromjson(request.data) except ValueError as e: + print(1) return Response(unicode(e), 400) for attr in 'author', 'email', 'website': @@ -52,11 +53,13 @@ def create(app, environ, request, uri): try: setattr(comment, attr, cgi.escape(getattr(comment, attr))) except AttributeError: + print(1) Response('', 400) try: rv = app.db.add(uri, comment) except ValueError: + print(1) abort(400) # FIXME: custom exception class, error descr md5 = rv.md5