Merge remote-tracking branch 'tsileo/reply_notification' into reply_notification
Conflicts: isso/css/isso.scss isso/db/comments.py isso/ext/notifications.py isso/js/app/i18n/fr.js isso/js/app/isso.js isso/js/app/text/postbox.html isso/views/comments.py
This commit is contained in:
commit
c30873e8a2
@ -209,6 +209,10 @@
|
|||||||
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
|
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
|
||||||
background-color: #BBB;
|
background-color: #BBB;
|
||||||
}
|
}
|
||||||
|
.isso-postbox > .form-wrapper > .notification-section {
|
||||||
|
display: none;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
@media screen and (max-width:600px) {
|
@media screen and (max-width:600px) {
|
||||||
.isso-postbox > .form-wrapper > .auth-section .input-wrapper {
|
.isso-postbox > .form-wrapper > .auth-section .input-wrapper {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -23,7 +23,7 @@ class Comments:
|
|||||||
'mode', # status of the comment 1 = valid, 2 = pending,
|
'mode', # status of the comment 1 = valid, 2 = pending,
|
||||||
# 4 = soft-deleted (cannot hard delete because of replies)
|
# 4 = soft-deleted (cannot hard delete because of replies)
|
||||||
'remote_addr', 'text', 'author', 'email', 'website',
|
'remote_addr', 'text', 'author', 'email', 'website',
|
||||||
'likes', 'dislikes', 'voters']
|
'likes', 'dislikes', 'voters', 'notification']
|
||||||
|
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
|
|
||||||
@ -33,7 +33,8 @@ class Comments:
|
|||||||
' tid REFERENCES threads(id), id INTEGER PRIMARY KEY, parent INTEGER,',
|
' tid REFERENCES threads(id), id INTEGER PRIMARY KEY, parent INTEGER,',
|
||||||
' created FLOAT NOT NULL, modified FLOAT, mode INTEGER, remote_addr VARCHAR,',
|
' created FLOAT NOT NULL, modified FLOAT, mode INTEGER, remote_addr VARCHAR,',
|
||||||
' text VARCHAR, author VARCHAR, email VARCHAR, website VARCHAR,',
|
' text VARCHAR, author VARCHAR, email VARCHAR, website VARCHAR,',
|
||||||
' likes INTEGER DEFAULT 0, dislikes INTEGER DEFAULT 0, voters BLOB NOT NULL);'])
|
' likes INTEGER DEFAULT 0, dislikes INTEGER DEFAULT 0, voters BLOB NOT NULL,',
|
||||||
|
' notification INTEGER NOT NULL DEFAULT 0);'])
|
||||||
|
|
||||||
def add(self, uri, c):
|
def add(self, uri, c):
|
||||||
"""
|
"""
|
||||||
@ -50,16 +51,16 @@ class Comments:
|
|||||||
'INSERT INTO comments (',
|
'INSERT INTO comments (',
|
||||||
' tid, parent,'
|
' tid, parent,'
|
||||||
' created, modified, mode, remote_addr,',
|
' created, modified, mode, remote_addr,',
|
||||||
' text, author, email, website, voters )',
|
' text, author, email, website, voters, notification)',
|
||||||
'SELECT',
|
'SELECT',
|
||||||
' threads.id, ?,',
|
' threads.id, ?,',
|
||||||
' ?, ?, ?, ?,',
|
' ?, ?, ?, ?,',
|
||||||
' ?, ?, ?, ?, ?',
|
' ?, ?, ?, ?, ?, ?',
|
||||||
'FROM threads WHERE threads.uri = ?;'], (
|
'FROM threads WHERE threads.uri = ?;'], (
|
||||||
c.get('parent'),
|
c.get('parent'),
|
||||||
c.get('created') or time.time(), None, c["mode"], c['remote_addr'],
|
c.get('created') or time.time(), None, c["mode"], c['remote_addr'],
|
||||||
c['text'], c.get('author'), c.get('email'), c.get('website'), buffer(
|
c['text'], c.get('author'), c.get('email'), c.get('website'), buffer(
|
||||||
Bloomfilter(iterable=[c['remote_addr']]).array),
|
Bloomfilter(iterable=[c['remote_addr']]).array), c.get('notification'),
|
||||||
uri)
|
uri)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ class SMTP(object):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
yield "comments.new:after-save", self.notify
|
yield "comments.new:after-save", self.notify
|
||||||
|
|
||||||
def format(self, thread, comment):
|
def format(self, thread, comment, admin=False):
|
||||||
|
|
||||||
rv = io.StringIO()
|
rv = io.StringIO()
|
||||||
|
|
||||||
@ -101,40 +101,51 @@ class SMTP(object):
|
|||||||
rv.write(comment["text"] + "\n")
|
rv.write(comment["text"] + "\n")
|
||||||
rv.write("\n")
|
rv.write("\n")
|
||||||
|
|
||||||
if comment["website"]:
|
if admin:
|
||||||
rv.write("User's URL: %s\n" % comment["website"])
|
if comment["website"]:
|
||||||
|
rv.write("User's URL: %s\n" % comment["website"])
|
||||||
|
|
||||||
rv.write("IP address: %s\n" % comment["remote_addr"])
|
rv.write("IP address: %s\n" % comment["remote_addr"])
|
||||||
rv.write("Link to comment: %s\n" %
|
rv.write("Link to comment: %s\n" %
|
||||||
(local("origin") + thread["uri"] + "#isso-%i" % comment["id"]))
|
(local("origin") + thread["uri"] + "#isso-%i" % comment["id"]))
|
||||||
rv.write("\n")
|
rv.write("\n")
|
||||||
|
|
||||||
uri = local("host") + "/id/%i" % comment["id"]
|
uri = local("host") + "/id/%i" % comment["id"]
|
||||||
key = self.isso.sign(comment["id"])
|
key = self.isso.sign(comment["id"])
|
||||||
|
|
||||||
rv.write("---\n")
|
rv.write("---\n")
|
||||||
rv.write("Delete comment: %s\n" % (uri + "/delete/" + key))
|
rv.write("Delete comment: %s\n" % (uri + "/delete/" + key))
|
||||||
|
|
||||||
if comment["mode"] == 2:
|
if comment["mode"] == 2:
|
||||||
rv.write("Activate comment: %s\n" % (uri + "/activate/" + key))
|
rv.write("Activate comment: %s\n" % (uri + "/activate/" + key))
|
||||||
|
|
||||||
rv.seek(0)
|
rv.seek(0)
|
||||||
return rv.read()
|
return rv.read()
|
||||||
|
|
||||||
def notify(self, thread, comment):
|
def notify(self, thread, comment):
|
||||||
|
if "parent" in comment:
|
||||||
|
comment_parent = self.isso.db.comments.get(comment["parent"])
|
||||||
|
# Notify the author that a new comment is posted if requested
|
||||||
|
if comment_parent and "email" in comment_parent and comment_parent["notification"]:
|
||||||
|
body = self.format(thread, comment, admin=False)
|
||||||
|
subject = "Re: New comment posted on %s" % thread["title"]
|
||||||
|
self.sendmail(subject, body, thread, comment, to=comment_parent["email"])
|
||||||
|
|
||||||
body = self.format(thread, comment)
|
body = self.format(thread, comment, admin=True)
|
||||||
|
self.sendmail(thread["title"], body, thread, comment)
|
||||||
|
|
||||||
|
def sendmail(self, subject, body, thread, comment, to=None):
|
||||||
if uwsgi:
|
if uwsgi:
|
||||||
uwsgi.spool({b"subject": thread["title"].encode("utf-8"),
|
uwsgi.spool({b"subject": subject.encode("utf-8"),
|
||||||
b"body": body.encode("utf-8")})
|
b"body": body.encode("utf-8"),
|
||||||
|
b"to": to})
|
||||||
else:
|
else:
|
||||||
start_new_thread(self._retry, (thread["title"], body))
|
start_new_thread(self._retry, (subject, body, to))
|
||||||
|
|
||||||
def _sendmail(self, subject, body):
|
def _sendmail(self, subject, body, to=None):
|
||||||
|
|
||||||
from_addr = self.conf.get("from")
|
from_addr = self.conf.get("from")
|
||||||
to_addr = self.conf.get("to")
|
to_addr = to or self.conf.get("to")
|
||||||
|
|
||||||
msg = MIMEText(body, 'plain', 'utf-8')
|
msg = MIMEText(body, 'plain', 'utf-8')
|
||||||
msg['From'] = from_addr
|
msg['From'] = from_addr
|
||||||
@ -145,10 +156,10 @@ class SMTP(object):
|
|||||||
with self as con:
|
with self as con:
|
||||||
con.sendmail(from_addr, to_addr, msg.as_string())
|
con.sendmail(from_addr, to_addr, msg.as_string())
|
||||||
|
|
||||||
def _retry(self, subject, body):
|
def _retry(self, subject, body, to):
|
||||||
for x in range(5):
|
for x in range(5):
|
||||||
try:
|
try:
|
||||||
self._sendmail(subject, body)
|
self._sendmail(subject, body, to)
|
||||||
except smtplib.SMTPConnectError:
|
except smtplib.SMTPConnectError:
|
||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
else:
|
else:
|
||||||
|
@ -90,6 +90,8 @@ define(function() {
|
|||||||
this.focus = function() { node.focus() };
|
this.focus = function() { node.focus() };
|
||||||
this.scrollIntoView = function(args) { node.scrollIntoView(args) };
|
this.scrollIntoView = function(args) { node.scrollIntoView(args) };
|
||||||
|
|
||||||
|
this.checked = function() { return node.checked; };
|
||||||
|
|
||||||
this.setAttribute = function(key, value) { node.setAttribute(key, value) };
|
this.setAttribute = function(key, value) { node.setAttribute(key, value) };
|
||||||
this.getAttribute = function(key) { return node.getAttribute(key) };
|
this.getAttribute = function(key) { return node.getAttribute(key) };
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ define({
|
|||||||
"postbox-email": "E-mail (optional)",
|
"postbox-email": "E-mail (optional)",
|
||||||
"postbox-website": "Website (optional)",
|
"postbox-website": "Website (optional)",
|
||||||
"postbox-submit": "Submit",
|
"postbox-submit": "Submit",
|
||||||
|
"postbox-notification": "Subscribe to email notification of replies",
|
||||||
|
|
||||||
"num-comments": "One Comment\n{{ n }} Comments",
|
"num-comments": "One Comment\n{{ n }} Comments",
|
||||||
"no-comments": "No Comments Yet",
|
"no-comments": "No Comments Yet",
|
||||||
|
@ -4,6 +4,7 @@ define({
|
|||||||
"postbox-email": "Courriel (optionnel)",
|
"postbox-email": "Courriel (optionnel)",
|
||||||
"postbox-website": "Site web (optionnel)",
|
"postbox-website": "Site web (optionnel)",
|
||||||
"postbox-submit": "Soumettre",
|
"postbox-submit": "Soumettre",
|
||||||
|
"postbox-notification": "S'abonner aux notifications de réponses",
|
||||||
"num-comments": "{{ n }} commentaire\n{{ n }} commentaires",
|
"num-comments": "{{ n }} commentaire\n{{ n }} commentaires",
|
||||||
"no-comments": "Aucun commentaire pour l'instant",
|
"no-comments": "Aucun commentaire pour l'instant",
|
||||||
"comment-reply": "Répondre",
|
"comment-reply": "Répondre",
|
||||||
|
@ -39,6 +39,17 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n",
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// only display notification checkbox if email is filled in
|
||||||
|
var email_edit = function() {
|
||||||
|
if ($("[name='email']", el).value.length > 0) {
|
||||||
|
$(".notification-section", el).show();
|
||||||
|
} else {
|
||||||
|
$(".notification-section", el).hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("[name='email']", el).on("input", email_edit);
|
||||||
|
email_edit();
|
||||||
|
|
||||||
// email is not optional if this config parameter is set
|
// email is not optional if this config parameter is set
|
||||||
if (config["require-email"]) {
|
if (config["require-email"]) {
|
||||||
$("[name='email']", el).setAttribute("placeholder",
|
$("[name='email']", el).setAttribute("placeholder",
|
||||||
@ -70,7 +81,8 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n",
|
|||||||
author: author, email: email, website: website,
|
author: author, email: email, website: website,
|
||||||
text: utils.text($(".textarea", el).innerHTML),
|
text: utils.text($(".textarea", el).innerHTML),
|
||||||
parent: parent || null,
|
parent: parent || null,
|
||||||
title: $("#isso-thread").getAttribute("data-title") || null
|
title: $("#isso-thread").getAttribute("data-title") || null,
|
||||||
|
notification: $("[name=notification]", el).checked() ? 1 : 0,
|
||||||
}).then(function(comment) {
|
}).then(function(comment) {
|
||||||
$(".textarea", el).innerHTML = "";
|
$(".textarea", el).innerHTML = "";
|
||||||
$(".textarea", el).blur();
|
$(".textarea", el).blur();
|
||||||
|
@ -15,3 +15,7 @@ div(class='isso-postbox')
|
|||||||
value=website != null ? '#{website}' : '')
|
value=website != null ? '#{website}' : '')
|
||||||
p(class='post-action')
|
p(class='post-action')
|
||||||
input(type='submit' value=i18n('postbox-submit'))
|
input(type='submit' value=i18n('postbox-submit'))
|
||||||
|
section(class='notification-section')
|
||||||
|
label
|
||||||
|
input(type='checkbox' name='notification')
|
||||||
|
= i18n('postbox-notification')
|
||||||
|
@ -81,10 +81,10 @@ def xhr(func):
|
|||||||
class API(object):
|
class API(object):
|
||||||
|
|
||||||
FIELDS = set(['id', 'parent', 'text', 'author', 'website',
|
FIELDS = set(['id', 'parent', 'text', 'author', 'website',
|
||||||
'mode', 'created', 'modified', 'likes', 'dislikes', 'hash'])
|
'mode', 'created', 'modified', 'likes', 'dislikes', 'hash', 'notification'])
|
||||||
|
|
||||||
# comment fields, that can be submitted
|
# comment fields, that can be submitted
|
||||||
ACCEPT = set(['text', 'author', 'website', 'email', 'parent', 'title'])
|
ACCEPT = set(['text', 'author', 'website', 'email', 'parent', 'title', 'notification'])
|
||||||
|
|
||||||
VIEWS = [
|
VIEWS = [
|
||||||
('fetch', ('GET', '/')),
|
('fetch', ('GET', '/')),
|
||||||
|
Loading…
Reference in New Issue
Block a user