implement comment editing

This commit is contained in:
Martin Zimmermann 2013-10-03 18:56:36 +02:00
parent 6242d243c9
commit 0b8cac7f18
10 changed files with 158 additions and 53 deletions

View File

@ -103,6 +103,12 @@ a {
}
}
> div.textarea-wrapper textarea {
@include fill-parent;
@include isso-shadow;
font: inherit;
}
> footer {
font-family: "Helvetica", Arial, sans-serif;
@ -119,7 +125,7 @@ a {
}
}
a.reply, a.delete {
a.reply, a.edit, a.cancel, a.delete {
padding-left: 1em;
}
@ -130,7 +136,6 @@ a {
.upvote svg, .downvote svg {
position: relative;
top: 0.2em;
//margin-bottom: -0.2em;
}
}
}

View File

@ -73,7 +73,7 @@ define(["q"], function(Q) {
var qs = function(params) {
var rv = "";
for (var key in params) {
if (params.hasOwnProperty(key)) {
if (params.hasOwnProperty(key) && params[key]) {
rv += key + "=" + encodeURIComponent(params[key]) + "&";
}
}
@ -86,8 +86,9 @@ define(["q"], function(Q) {
function (rv) { return JSON.parse(rv.body); });
};
var modify = function(data) {
// ...
var modify = function(id, data) {
return curl("PUT", endpoint + "/id/" + id, JSON.stringify(data)).then(
function (rv) { return JSON.parse(rv.body); });
};
var remove = function(id) {
@ -100,9 +101,15 @@ define(["q"], function(Q) {
});
};
var fetch = function() {
var view = function(id, plain) {
return curl("GET", endpoint + "/id/" + id + "?" + qs({plain: plain}), null).then(function (rv) {
return JSON.parse(rv.body);
});
};
return curl("GET", endpoint + "/?" + qs({uri: location}), null).then(function (rv) {
var fetch = function(plain) {
return curl("GET", endpoint + "/?" + qs({uri: location, plain: plain}), null).then(function (rv) {
if (rv.status === 200) {
return JSON.parse(rv.body);
} else {
@ -139,7 +146,9 @@ define(["q"], function(Q) {
salt: salt,
create: create,
modify: modify,
remove: remove,
view: view,
fetch: fetch,
count: count,
like: like,

View File

@ -35,6 +35,35 @@ define(function() {
});
};
window.Element.prototype.toggle = function(type, on, off) {
function Toggle(el, on, off) {
this.state = false;
this.el = el;
this.on = on;
this.off = off;
}
Toggle.prototype.next = function next() {
if (! this.state) {
this.state = true;
this.on(this);
} else {
this.state = false;
this.off(this);
}
};
Toggle.prototype.wait = function wait() {
this.state = ! this.state;
};
var toggler = new Toggle(this, on, off);
this.on(type, function() {
toggler.next();
});
};
window.Element.prototype.remove = function() {
// Mimimi, I am IE and I am so retarded, mimimi.
this.parentNode.removeChild(this);
@ -70,8 +99,21 @@ define(function() {
return wrapper.firstChild;
};
DOM.new = function(tag) {
return document.createElement(tag);
DOM.new = function(tag, content) {
var el = document.createElement(tag.split(".")[0]);
tag.split(".").slice(1).forEach(function(val) { el.classList.add(val); });
if (["A", "LINK"].indexOf(el.nodeName) > -1) {
el.href = "#";
}
if (["TEXTAREA", "INPUT"].indexOf(el.nodeName) > -1) {
el.value = content;
} else {
el.textContent = content || "";
}
return el;
};
DOM.each = function(tag, func) {

21
isso/js/app/fancy.js Normal file
View File

@ -0,0 +1,21 @@
define(function() {
"use strict";
// http://chuvash.eu/2011/12/14/the-cleanest-auto-resize-for-a-textarea/
var autoresize = function(textarea, minheight) {
var offset= !window.opera ? (textarea.offsetHeight - textarea.clientHeight) : (textarea.offsetHeight + parseInt(window.getComputedStyle(textarea, null).getPropertyValue('border-top-width')));
["keyup", "focus"].forEach(function(event) {
textarea.on(event, function() {
if ((textarea.scrollHeight + offset ) > minheight) {
textarea.style.height = "auto";
textarea.style.height = (textarea.scrollHeight + offset ) + 'px';
}
});
});
};
return {
autoresize: autoresize
};
});

View File

@ -8,9 +8,12 @@ define({
"no-comments": "Keine Kommentare bis jetzt",
"comment-reply": "Antworten",
"comment-edit": "Bearbeiten",
"comment-save": "Speichern",
"comment-delete": "Löschen",
"comment-confirm": "Bestätigen",
"comment-close": "Schließen",
"comment-cancel": "Abbrechen",
"comment-deleted": "Kommentar gelöscht.",
"comment-queued": "Kommentar muss noch freigeschaltet werden.",
"comment-anonymous": "Anonym",

View File

@ -8,9 +8,12 @@ define({
"no-comments": "No Comments Yet",
"comment-reply": "Reply",
"comment-edit": "Edit",
"comment-save": "Save",
"comment-delete": "Delete",
"comment-confirm": "Confirm",
"comment-close": "Close",
"comment-cancel": "Cancel",
"comment-deleted": "Comment deleted.",
"comment-queued": "Comment in queue for moderation.",
"comment-anonymous": "Anonymous",

View File

@ -1,24 +1,12 @@
/* Isso Ich schrei sonst!
*/
define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/markup", "app/i18n", "app/lib"],
function(behave, templates, $, utils, api, Mark, i18n, lib) {
define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/markup", "app/i18n", "app/lib", "app/fancy"],
function(behave, templates, $, utils, api, Mark, i18n, lib, fancy) {
"use strict";
var msgs = i18n[i18n.lang];
var toggle = function(el, on, off) {
if (el.classList.contains("off") || ! el.classList.contains("on")) {
el.classList.remove("off");
el.classList.add("on");
on(el);
} else {
el.classList.remove("on");
el.classList.add("off");
off(el);
}
};
var Postbox = function(parent) {
var el = $.htmlify(Mark.up(templates["postbox"]));
@ -85,16 +73,8 @@ define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/marku
});
});
var textarea = $("textarea", el);
new behave({textarea: textarea});
var offset= !window.opera ? (textarea.offsetHeight - textarea.clientHeight) : (textarea.offsetHeight + parseInt(window.getComputedStyle(textarea, null).getPropertyValue('border-top-width')));
$("textarea", el).on("keyup", function() {
if ((textarea.scrollHeight + offset ) > 48) {
textarea.style.height = "auto";
textarea.style.height = (textarea.scrollHeight + offset ) + 'px';
}
});
fancy.autoresize($("textarea", el), 48);
new behave({textarea: $("textarea", el)});
return el;
};
@ -140,20 +120,17 @@ define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/marku
text = $("#isso-" + comment.id + " > .text-wrapper > div.text");
var form = new Postbox(comment.id);
$("a.reply", footer).on("click", function() {
toggle(
$("a.reply", footer),
function(reply) {
$("a.reply", footer).toggle("click",
function() {
footer.insertAfter(form);
$("textarea", form).focus();
reply.textContent = msgs["comment-close"];
$("a.reply", footer).textContent = msgs["comment-close"];
},
function(reply) {
function() {
form.remove();
reply.textContent = msgs["comment-reply"];
$("a.reply", footer).textContent = msgs["comment-reply"];
}
);
});
if (comment.parent !== null) {
$("a.parent", header).on("mouseover", function() {
@ -171,7 +148,7 @@ define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/marku
span.remove();
return;
} else {
footer.prepend($.htmlify('<span class="votes">' + value + '</span>'));
footer.prepend($.new("span.votes", value));
}
} else {
if (value === 0) {
@ -195,11 +172,52 @@ define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/marku
});
if (! utils.cookie(comment.id)) {
// $("a.edit", footer).remove();
$("a.edit", footer).remove();
$("a.delete", footer).remove();
return;
}
$("a.edit", footer).toggle("click",
function(toggler) {
var edit = $("a.edit", footer);
edit.textContent = msgs["comment-save"];
edit.insertAfter($.new("a.cancel", msgs["comment-cancel"])).on("click", function() {
text.textContent = "";
text.className = "text";
text.append(comment.text);
toggler.next();
});
api.view(comment.id, 1).then(function(rv) {
var textarea = $.new("textarea", rv.text);
new behave({textarea: textarea});
fancy.autoresize(textarea, 48);
text.className = "textarea-wrapper";
text.textContent = "";
text.append(textarea);
textarea.focus();
});
},
function(toggler) {
var textarea = $("textarea", text);
if (textarea && textarea.value.length < 3) {
textarea.focus();
toggler.wait();
return;
} else if (textarea) {
api.modify(comment.id, {"text": textarea.value}).then(function(rv) {
text.innerHTML = rv.text;
text.className = "text";
comment.text = rv.text;
});
}
$("a.cancel", footer).remove();
$("a.edit", footer).textContent = msgs["comment-edit"];
}
);
$("a.delete", footer).on("click", function() {
if ($("a.delete", footer).textContent === msgs["comment-confirm"]) {
api.remove(comment.id).then(function(rv) {

View File

@ -54,7 +54,7 @@
<a class="downvote" href="#"><i>{{ svg-arrow-down}}</i></a>
<a class="reply" href="#">{{ i18n-comment-reply }}</a>
<!--<a class="edit" href="#">Bearbeiten</a>-->
<a class="edit" href="#">{{ i18n-comment-edit }}</a>
<a class="delete" href="#">{{ i18n-comment-delete }}</a>
</footer>
<div class="isso-follow-up">

View File

@ -28,6 +28,10 @@ require(["ready", "app/api", "app/isso", "app/count", "app/dom", "app/markup"],
}
}).fail(function(err) {
console.log(err);
}).done(function() {
if (window.location.hash.length > 0) {
$(window.location.hash).scrollIntoView();
}
});
});
});

View File

@ -85,7 +85,7 @@ def new(app, environ, request, uri):
# save checksum of text into cookie, so mallory can't modify/delete a comment, if
# he add a comment, then removed it but not the signed cookie.
checksum = hashlib.md5(rv["text"]).hexdigest()
checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest()
rv["text"] = app.markdown(rv["text"])
rv["hash"] = pbkdf2(rv.get('email') or rv['remote_addr'], app.SALT, 1000, 6)
@ -126,7 +126,7 @@ def single(app, environ, request, id):
abort(403)
# verify checksum, mallory might skip cookie deletion when he deletes a comment
if rv[1] != hashlib.md5(app.db.comments.get(id)["text"]).hexdigest():
if rv[1] != hashlib.md5(app.db.comments.get(id)["text"].encode('utf-8')).hexdigest():
abort(403)
if request.method == 'PUT':
@ -152,7 +152,7 @@ def single(app, environ, request, id):
for key in set(rv.keys()) - FIELDS:
rv.pop(key)
checksum = hashlib.md5(rv["text"]).hexdigest()
checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest()
rv["text"] = app.markdown(rv["text"])
resp = Response(json.dumps(rv), 200, content_type='application/json')