implement comment editing
This commit is contained in:
parent
6242d243c9
commit
0b8cac7f18
@ -103,6 +103,12 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> div.textarea-wrapper textarea {
|
||||||
|
@include fill-parent;
|
||||||
|
@include isso-shadow;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
> footer {
|
> footer {
|
||||||
|
|
||||||
font-family: "Helvetica", Arial, sans-serif;
|
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;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +136,6 @@ a {
|
|||||||
.upvote svg, .downvote svg {
|
.upvote svg, .downvote svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0.2em;
|
top: 0.2em;
|
||||||
//margin-bottom: -0.2em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ define(["q"], function(Q) {
|
|||||||
var qs = function(params) {
|
var qs = function(params) {
|
||||||
var rv = "";
|
var rv = "";
|
||||||
for (var key in params) {
|
for (var key in params) {
|
||||||
if (params.hasOwnProperty(key)) {
|
if (params.hasOwnProperty(key) && params[key]) {
|
||||||
rv += key + "=" + encodeURIComponent(params[key]) + "&";
|
rv += key + "=" + encodeURIComponent(params[key]) + "&";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,8 +86,9 @@ define(["q"], function(Q) {
|
|||||||
function (rv) { return JSON.parse(rv.body); });
|
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) {
|
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) {
|
if (rv.status === 200) {
|
||||||
return JSON.parse(rv.body);
|
return JSON.parse(rv.body);
|
||||||
} else {
|
} else {
|
||||||
@ -139,7 +146,9 @@ define(["q"], function(Q) {
|
|||||||
salt: salt,
|
salt: salt,
|
||||||
|
|
||||||
create: create,
|
create: create,
|
||||||
|
modify: modify,
|
||||||
remove: remove,
|
remove: remove,
|
||||||
|
view: view,
|
||||||
fetch: fetch,
|
fetch: fetch,
|
||||||
count: count,
|
count: count,
|
||||||
like: like,
|
like: like,
|
||||||
|
@ -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() {
|
window.Element.prototype.remove = function() {
|
||||||
// Mimimi, I am IE and I am so retarded, mimimi.
|
// Mimimi, I am IE and I am so retarded, mimimi.
|
||||||
this.parentNode.removeChild(this);
|
this.parentNode.removeChild(this);
|
||||||
@ -70,8 +99,21 @@ define(function() {
|
|||||||
return wrapper.firstChild;
|
return wrapper.firstChild;
|
||||||
};
|
};
|
||||||
|
|
||||||
DOM.new = function(tag) {
|
DOM.new = function(tag, content) {
|
||||||
return document.createElement(tag);
|
|
||||||
|
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) {
|
DOM.each = function(tag, func) {
|
||||||
|
21
isso/js/app/fancy.js
Normal file
21
isso/js/app/fancy.js
Normal 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
|
||||||
|
};
|
||||||
|
});
|
@ -8,9 +8,12 @@ define({
|
|||||||
"no-comments": "Keine Kommentare bis jetzt",
|
"no-comments": "Keine Kommentare bis jetzt",
|
||||||
|
|
||||||
"comment-reply": "Antworten",
|
"comment-reply": "Antworten",
|
||||||
|
"comment-edit": "Bearbeiten",
|
||||||
|
"comment-save": "Speichern",
|
||||||
"comment-delete": "Löschen",
|
"comment-delete": "Löschen",
|
||||||
"comment-confirm": "Bestätigen",
|
"comment-confirm": "Bestätigen",
|
||||||
"comment-close": "Schließen",
|
"comment-close": "Schließen",
|
||||||
|
"comment-cancel": "Abbrechen",
|
||||||
"comment-deleted": "Kommentar gelöscht.",
|
"comment-deleted": "Kommentar gelöscht.",
|
||||||
"comment-queued": "Kommentar muss noch freigeschaltet werden.",
|
"comment-queued": "Kommentar muss noch freigeschaltet werden.",
|
||||||
"comment-anonymous": "Anonym",
|
"comment-anonymous": "Anonym",
|
||||||
|
@ -8,9 +8,12 @@ define({
|
|||||||
"no-comments": "No Comments Yet",
|
"no-comments": "No Comments Yet",
|
||||||
|
|
||||||
"comment-reply": "Reply",
|
"comment-reply": "Reply",
|
||||||
|
"comment-edit": "Edit",
|
||||||
|
"comment-save": "Save",
|
||||||
"comment-delete": "Delete",
|
"comment-delete": "Delete",
|
||||||
"comment-confirm": "Confirm",
|
"comment-confirm": "Confirm",
|
||||||
"comment-close": "Close",
|
"comment-close": "Close",
|
||||||
|
"comment-cancel": "Cancel",
|
||||||
"comment-deleted": "Comment deleted.",
|
"comment-deleted": "Comment deleted.",
|
||||||
"comment-queued": "Comment in queue for moderation.",
|
"comment-queued": "Comment in queue for moderation.",
|
||||||
"comment-anonymous": "Anonymous",
|
"comment-anonymous": "Anonymous",
|
||||||
|
@ -1,24 +1,12 @@
|
|||||||
/* Isso – Ich schrei sonst!
|
/* Isso – Ich schrei sonst!
|
||||||
*/
|
*/
|
||||||
define(["behave", "app/text/html", "app/dom", "app/utils", "app/api", "app/markup", "app/i18n", "app/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) {
|
function(behave, templates, $, utils, api, Mark, i18n, lib, fancy) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var msgs = i18n[i18n.lang];
|
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 Postbox = function(parent) {
|
||||||
|
|
||||||
var el = $.htmlify(Mark.up(templates["postbox"]));
|
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);
|
fancy.autoresize($("textarea", el), 48);
|
||||||
new behave({textarea: textarea});
|
new behave({textarea: $("textarea", el)});
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return 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");
|
text = $("#isso-" + comment.id + " > .text-wrapper > div.text");
|
||||||
|
|
||||||
var form = new Postbox(comment.id);
|
var form = new Postbox(comment.id);
|
||||||
$("a.reply", footer).on("click", function() {
|
$("a.reply", footer).toggle("click",
|
||||||
toggle(
|
function() {
|
||||||
$("a.reply", footer),
|
footer.insertAfter(form);
|
||||||
function(reply) {
|
$("textarea", form).focus();
|
||||||
footer.insertAfter(form);
|
$("a.reply", footer).textContent = msgs["comment-close"];
|
||||||
$("textarea", form).focus();
|
},
|
||||||
reply.textContent = msgs["comment-close"];
|
function() {
|
||||||
},
|
form.remove();
|
||||||
function(reply) {
|
$("a.reply", footer).textContent = msgs["comment-reply"];
|
||||||
form.remove();
|
}
|
||||||
reply.textContent = msgs["comment-reply"];
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (comment.parent !== null) {
|
if (comment.parent !== null) {
|
||||||
$("a.parent", header).on("mouseover", function() {
|
$("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();
|
span.remove();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
footer.prepend($.htmlify('<span class="votes">' + value + '</span>'));
|
footer.prepend($.new("span.votes", value));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (value === 0) {
|
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)) {
|
if (! utils.cookie(comment.id)) {
|
||||||
// $("a.edit", footer).remove();
|
$("a.edit", footer).remove();
|
||||||
$("a.delete", footer).remove();
|
$("a.delete", footer).remove();
|
||||||
return;
|
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() {
|
$("a.delete", footer).on("click", function() {
|
||||||
if ($("a.delete", footer).textContent === msgs["comment-confirm"]) {
|
if ($("a.delete", footer).textContent === msgs["comment-confirm"]) {
|
||||||
api.remove(comment.id).then(function(rv) {
|
api.remove(comment.id).then(function(rv) {
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
<a class="downvote" href="#"><i>{{ svg-arrow-down}}</i></a>
|
<a class="downvote" href="#"><i>{{ svg-arrow-down}}</i></a>
|
||||||
|
|
||||||
<a class="reply" href="#">{{ i18n-comment-reply }}</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>
|
<a class="delete" href="#">{{ i18n-comment-delete }}</a>
|
||||||
</footer>
|
</footer>
|
||||||
<div class="isso-follow-up">
|
<div class="isso-follow-up">
|
||||||
|
@ -28,6 +28,10 @@ require(["ready", "app/api", "app/isso", "app/count", "app/dom", "app/markup"],
|
|||||||
}
|
}
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
}).done(function() {
|
||||||
|
if (window.location.hash.length > 0) {
|
||||||
|
$(window.location.hash).scrollIntoView();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -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
|
# 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.
|
# 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["text"] = app.markdown(rv["text"])
|
||||||
rv["hash"] = pbkdf2(rv.get('email') or rv['remote_addr'], app.SALT, 1000, 6)
|
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)
|
abort(403)
|
||||||
|
|
||||||
# verify checksum, mallory might skip cookie deletion when he deletes a comment
|
# 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)
|
abort(403)
|
||||||
|
|
||||||
if request.method == 'PUT':
|
if request.method == 'PUT':
|
||||||
@ -152,7 +152,7 @@ def single(app, environ, request, id):
|
|||||||
for key in set(rv.keys()) - FIELDS:
|
for key in set(rv.keys()) - FIELDS:
|
||||||
rv.pop(key)
|
rv.pop(key)
|
||||||
|
|
||||||
checksum = hashlib.md5(rv["text"]).hexdigest()
|
checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest()
|
||||||
rv["text"] = app.markdown(rv["text"])
|
rv["text"] = app.markdown(rv["text"])
|
||||||
|
|
||||||
resp = Response(json.dumps(rv), 200, content_type='application/json')
|
resp = Response(json.dumps(rv), 200, content_type='application/json')
|
||||||
|
Loading…
Reference in New Issue
Block a user