diff --git a/isso/css/isso.css b/isso/css/isso.css index 47cf675..b7fe4b1 100644 --- a/isso/css/isso.css +++ b/isso/css/isso.css @@ -209,6 +209,10 @@ .isso-postbox > .form-wrapper > .auth-section .post-action > input:active { background-color: #BBB; } +.isso-postbox > .form-wrapper > .notification-section { + display: none; + padding-bottom: 10px; +} @media screen and (max-width:600px) { .isso-postbox > .form-wrapper > .auth-section .input-wrapper { display: block; diff --git a/isso/db/comments.py b/isso/db/comments.py index 0f359a3..3110d25 100644 --- a/isso/db/comments.py +++ b/isso/db/comments.py @@ -23,7 +23,7 @@ class Comments: 'mode', # status of the comment 1 = valid, 2 = pending, # 4 = soft-deleted (cannot hard delete because of replies) 'remote_addr', 'text', 'author', 'email', 'website', - 'likes', 'dislikes', 'voters'] + 'likes', 'dislikes', 'voters', 'notification'] def __init__(self, db): @@ -33,7 +33,8 @@ class Comments: ' tid REFERENCES threads(id), id INTEGER PRIMARY KEY, parent INTEGER,', ' created FLOAT NOT NULL, modified FLOAT, mode INTEGER, remote_addr 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): """ @@ -50,16 +51,16 @@ class Comments: 'INSERT INTO comments (', ' tid, parent,' ' created, modified, mode, remote_addr,', - ' text, author, email, website, voters )', + ' text, author, email, website, voters, notification)', 'SELECT', ' threads.id, ?,', ' ?, ?, ?, ?,', - ' ?, ?, ?, ?, ?', + ' ?, ?, ?, ?, ?, ?', 'FROM threads WHERE threads.uri = ?;'], ( c.get('parent'), c.get('created') or time.time(), None, c["mode"], c['remote_addr'], 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) ) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index 80e1504..38fa173 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -88,7 +88,7 @@ class SMTP(object): def __iter__(self): yield "comments.new:after-save", self.notify - def format(self, thread, comment): + def format(self, thread, comment, admin=False): rv = io.StringIO() @@ -101,40 +101,51 @@ class SMTP(object): rv.write(comment["text"] + "\n") rv.write("\n") - if comment["website"]: - rv.write("User's URL: %s\n" % comment["website"]) + if admin: + if comment["website"]: + rv.write("User's URL: %s\n" % comment["website"]) - rv.write("IP address: %s\n" % comment["remote_addr"]) - rv.write("Link to comment: %s\n" % - (local("origin") + thread["uri"] + "#isso-%i" % comment["id"])) - rv.write("\n") + rv.write("IP address: %s\n" % comment["remote_addr"]) + rv.write("Link to comment: %s\n" % + (local("origin") + thread["uri"] + "#isso-%i" % comment["id"])) + rv.write("\n") - uri = local("host") + "/id/%i" % comment["id"] - key = self.isso.sign(comment["id"]) + uri = local("host") + "/id/%i" % comment["id"] + key = self.isso.sign(comment["id"]) - rv.write("---\n") - rv.write("Delete comment: %s\n" % (uri + "/delete/" + key)) + rv.write("---\n") + rv.write("Delete comment: %s\n" % (uri + "/delete/" + key)) - if comment["mode"] == 2: - rv.write("Activate comment: %s\n" % (uri + "/activate/" + key)) + if comment["mode"] == 2: + rv.write("Activate comment: %s\n" % (uri + "/activate/" + key)) rv.seek(0) return rv.read() def notify(self, thread, comment): - - body = self.format(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, admin=True) + self.sendmail(thread["title"], body, thread, comment) + + def sendmail(self, subject, body, thread, comment, to=None): if uwsgi: - uwsgi.spool({b"subject": thread["title"].encode("utf-8"), - b"body": body.encode("utf-8")}) + uwsgi.spool({b"subject": subject.encode("utf-8"), + b"body": body.encode("utf-8"), + b"to": to}) 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") - to_addr = self.conf.get("to") + to_addr = to or self.conf.get("to") msg = MIMEText(body, 'plain', 'utf-8') msg['From'] = from_addr @@ -145,10 +156,10 @@ class SMTP(object): with self as con: 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): try: - self._sendmail(subject, body) + self._sendmail(subject, body, to) except smtplib.SMTPConnectError: time.sleep(60) else: diff --git a/isso/js/app/dom.js b/isso/js/app/dom.js index ec76e13..05363e3 100644 --- a/isso/js/app/dom.js +++ b/isso/js/app/dom.js @@ -90,6 +90,8 @@ define(function() { this.focus = function() { node.focus() }; this.scrollIntoView = function(args) { node.scrollIntoView(args) }; + this.checked = function() { return node.checked; }; + this.setAttribute = function(key, value) { node.setAttribute(key, value) }; this.getAttribute = function(key) { return node.getAttribute(key) }; diff --git a/isso/js/app/i18n/en.js b/isso/js/app/i18n/en.js index ec4b4d0..7ba8b49 100644 --- a/isso/js/app/i18n/en.js +++ b/isso/js/app/i18n/en.js @@ -4,6 +4,7 @@ define({ "postbox-email": "E-mail (optional)", "postbox-website": "Website (optional)", "postbox-submit": "Submit", + "postbox-notification": "Subscribe to email notification of replies", "num-comments": "One Comment\n{{ n }} Comments", "no-comments": "No Comments Yet", diff --git a/isso/js/app/i18n/fr.js b/isso/js/app/i18n/fr.js index e29d024..2f2573b 100644 --- a/isso/js/app/i18n/fr.js +++ b/isso/js/app/i18n/fr.js @@ -4,6 +4,7 @@ define({ "postbox-email": "Courriel (optionnel)", "postbox-website": "Site web (optionnel)", "postbox-submit": "Soumettre", + "postbox-notification": "S'abonner aux notifications de réponses", "num-comments": "{{ n }} commentaire\n{{ n }} commentaires", "no-comments": "Aucun commentaire pour l'instant", "comment-reply": "Répondre", diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index e779a76..665db73 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -39,6 +39,17 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", 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 if (config["require-email"]) { $("[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, text: utils.text($(".textarea", el).innerHTML), 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) { $(".textarea", el).innerHTML = ""; $(".textarea", el).blur(); diff --git a/isso/js/app/text/postbox.jade b/isso/js/app/text/postbox.jade index 0a85ae1..bc7ef02 100644 --- a/isso/js/app/text/postbox.jade +++ b/isso/js/app/text/postbox.jade @@ -15,3 +15,7 @@ div(class='isso-postbox') value=website != null ? '#{website}' : '') p(class='post-action') input(type='submit' value=i18n('postbox-submit')) + section(class='notification-section') + label + input(type='checkbox' name='notification') + = i18n('postbox-notification') diff --git a/isso/views/comments.py b/isso/views/comments.py index d3b80c3..c95026e 100644 --- a/isso/views/comments.py +++ b/isso/views/comments.py @@ -81,10 +81,10 @@ def xhr(func): class API(object): 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 - ACCEPT = set(['text', 'author', 'website', 'email', 'parent', 'title']) + ACCEPT = set(['text', 'author', 'website', 'email', 'parent', 'title', 'notification']) VIEWS = [ ('fetch', ('GET', '/')),