From 1227cfcbf05079a52a678961d83ec1d1504ff13e Mon Sep 17 00:00:00 2001 From: KeyserSosa Date: Mon, 28 Jul 2008 16:02:24 -0700 Subject: [PATCH] extended sharing to allow reply-to address and message. Also rolled feedback emails into mail_queue --- r2/r2/config/routing.py | 2 +- r2/r2/controllers/api.py | 56 +++++++---- r2/r2/controllers/front.py | 34 ++++--- r2/r2/controllers/post.py | 24 +++++ r2/r2/controllers/reddit_base.py | 3 +- r2/r2/controllers/validator/validator.py | 33 +++++-- r2/r2/lib/emailer.py | 114 ++++++++++++++--------- r2/r2/models/__init__.py | 2 +- r2/r2/models/mail_queue.py | 61 +++++++++--- r2/r2/public/static/reddit.css | 8 +- r2/r2/templates/feedback.html | 5 +- r2/r2/templates/optout.html | 33 +++++-- r2/r2/templates/prefupdate.html | 6 +- r2/r2/templates/share.email | 10 +- r2/r2/templates/sharelink.html | 53 +++++++++-- 15 files changed, 321 insertions(+), 123 deletions(-) diff --git a/r2/r2/config/routing.py b/r2/r2/config/routing.py index c73a4f271..53de7e890 100644 --- a/r2/r2/config/routing.py +++ b/r2/r2/config/routing.py @@ -123,7 +123,7 @@ def make_map(global_conf={}, app_conf={}): action='resetpassword') mc('/post/:action', controller='post', - requirements=dict(action="options|over18|unlogged_options")) + requirements=dict(action="options|over18|unlogged_options|optout|optin")) mc('/api/:action', controller='api') mc('/d/:what', controller='api', action='bookmarklet') diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py index e588eac73..0bf41bae9 100644 --- a/r2/r2/controllers/api.py +++ b/r2/r2/controllers/api.py @@ -124,19 +124,18 @@ class ApiController(RedditController): if not res.error: if reason != 'ad_inq': - emailer.feedback_email(email, message, name = name or '') + emailer.feedback_email(email, message, name = name or '', + reply_to = replyto or '') else: - emailer.ad_inq_email(email, message, name = name or '') + emailer.ad_inq_email(email, message, name = name or '', + reply_to = replyto or '') res._update('success', innerHTML=_("thanks for your message! you should hear back from us shortly.")) res._update("personal", value='') res._update("captcha", value='') res._hide("wtf") - - POST_ad_inq = POST_feedback - @Json @validate(VUser(), VModhash(), @@ -439,7 +438,7 @@ class ApiController(RedditController): @validate(VUser('curpass', default = ''), VModhash(), curpass = nop('curpass'), - email = nop("email"), + email = ValidEmails("email", num = 1), newpass = nop("newpass"), verpass = nop("verpass"), password = VPassword(['newpass', 'verpass'])) @@ -450,8 +449,10 @@ class ApiController(RedditController): res._update('curpass', value='') return updated = False - if email and (not hasattr(c.user,'email') - or c.user.email != email): + if res._chk_error(errors.BAD_EMAILS): + res._focus('email') + elif email and (not hasattr(c.user,'email') + or c.user.email != email): c.user.email = email c.user._commit() res._update('status', @@ -612,16 +613,20 @@ class ApiController(RedditController): if should_ratelimit: VRatelimit.ratelimit(rate_user=True, rate_ip = True, prefix = "rate_comment_") + @Json @validate(VUser(), VModhash(), VCaptcha(), VRatelimit(rate_user = True, rate_ip = True, prefix = "rate_share_"), - share_from = VLength('share_from', length = 60), + share_from = VLength('share_from', length = 100), emails = ValidEmails("share_to"), + reply_to = ValidEmails("replyto", num = 1), + message = VLength("message", length = 1000), thing = VByName('id')) - def POST_share(self, res, emails, thing, share_from): + def POST_share(self, res, emails, thing, share_from, reply_to, + message): # remove the ratelimit error if the user's karma is high sr = thing.subreddit_slow @@ -633,18 +638,37 @@ class ApiController(RedditController): if res._chk_captcha(errors.BAD_CAPTCHA, thing._fullname): pass - elif not res._chk_errors((errors.BAD_EMAILS, errors.NO_EMAILS, - errors.RATELIMIT, errors.TOO_MANY_EMAILS), - thing._fullname): - + elif res._chk_error(errors.RATELIMIT, thing._fullname): + pass + elif (share_from is None and + res._chk_error(errors.COMMENT_TOO_LONG, + 'share_from_' + thing._fullname)): + res._focus('share_from_' + thing._fullname) + elif (message is None and + res._chk_error(errors.COMMENT_TOO_LONG, + 'message_' + thing._fullname)): + res._focus('message_' + thing._fullname) + elif not emails and res._chk_errors((errors.BAD_EMAILS, + errors.NO_EMAILS, + errors.TOO_MANY_EMAILS), + "emails_" + thing._fullname): + res._focus("emails_" + thing._fullname) + elif not reply_to and res._chk_error(errors.BAD_EMAILS, + "replyto_" + thing._fullname): + res._focus("replyto_" + thing._fullname) + else: c.user.add_share_emails(emails) c.user._commit() res._update("share_li_" + thing._fullname, innerHTML=_('shared')) - res._hide("sharelink_" + thing._fullname) - emailer.share(thing, emails, from_name = share_from or "") + res._update("sharelink_" + thing._fullname, + innerHTML=("

%s

" % + _("your link has been shared."))) + + emailer.share(thing, emails, from_name = share_from or "", + body = message or "", reply_to = reply_to or "") #set the ratelimiter if should_ratelimit: diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index 11016e5af..14e15bb88 100644 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -28,7 +28,7 @@ from r2.models import * from r2.lib.pages import * from r2.lib.menus import * from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url, query_string -from r2.lib.emailer import opt_out, opt_in +from r2.lib.emailer import has_opted_out, Email from r2.lib.db.operators import desc from r2.lib.strings import strings import r2.lib.db.thing as thing @@ -564,24 +564,30 @@ class FrontController(RedditController): subreddits = sr_names, captcha=captcha)).render() - @validate(msg_hash = nop('x')) - def GET_optout(self, msg_hash): - email, sent = opt_out(msg_hash) + def _render_opt_in_out(self, msg_hash, leave): + """Generates the form for an optin/optout page""" + email = Email.handler.get_recipient(msg_hash) if not email: return self.abort404() - return BoringPage(_("opt out"), - content = OptOut(email = email, leave = True, + sent = (has_opted_out(email) == leave) + return BoringPage(_("opt out") if leave else _("welcome back"), + content = OptOut(email = email, leave = leave, sent = sent, msg_hash = msg_hash)).render() + @validate(msg_hash = nop('x')) + def GET_optout(self, msg_hash): + """handles /mail/optout to add an email to the optout mailing + list. The actual email addition comes from the user posting + the subsequently rendered form and is handled in + ApiController.POST_optout.""" + return self._render_opt_in_out(msg_hash, True) @validate(msg_hash = nop('x')) def GET_optin(self, msg_hash): - email, sent = opt_in(msg_hash) - if not email: - return self.abort404() - return BoringPage(_("welcome back"), - content = OptOut(email = email, leave = False, - sent = sent, - msg_hash = msg_hash)).render() - + """handles /mail/optin to remove an email address from the + optout list. The actual email removal comes from the user + posting the subsequently rendered form and is handled in + ApiController.POST_optin.""" + return self._render_opt_in_out(msg_hash, False) + diff --git a/r2/r2/controllers/post.py b/r2/r2/controllers/post.py index c2101917d..93f576b03 100644 --- a/r2/r2/controllers/post.py +++ b/r2/r2/controllers/post.py @@ -22,6 +22,7 @@ from r2.lib.pages import * from api import ApiController from r2.lib.utils import Storage, query_string +from r2.lib.emailer import opt_in, opt_out from pylons import request, c, g from validator import * from pylons.i18n import _ @@ -125,3 +126,26 @@ class PostController(ApiController): return self.redirect(dest) else: return self.redirect('/') + + + @validate(msg_hash = nop('x')) + def POST_optout(self, msg_hash): + email, sent = opt_out(msg_hash) + if not email: + return self.abort404() + return BoringPage(_("opt out"), + content = OptOut(email = email, leave = True, + sent = True, + msg_hash = msg_hash)).render() + + @validate(msg_hash = nop('x')) + def POST_optin(self, msg_hash): + email, sent = opt_in(msg_hash) + if not email: + return self.abort404() + return BoringPage(_("welcome back"), + content = OptOut(email = email, leave = False, + sent = True, + msg_hash = msg_hash)).render() + + diff --git a/r2/r2/controllers/reddit_base.py b/r2/r2/controllers/reddit_base.py index 20d7b4e24..b44bd8a57 100644 --- a/r2/r2/controllers/reddit_base.py +++ b/r2/r2/controllers/reddit_base.py @@ -291,7 +291,8 @@ class RedditController(BaseController): kw = {} argspec = inspect.getargspec(fn) - #if there is a **kw argument in the fn definition, just pass along the environment + # if there is a **kw argument in the fn definition, + # just pass along the environment if argspec[2]: kw = env #else for each entry in the arglist set the value from the environment diff --git a/r2/r2/controllers/validator/validator.py b/r2/r2/controllers/validator/validator.py index 0543bf48b..f527b9dda 100644 --- a/r2/r2/controllers/validator/validator.py +++ b/r2/r2/controllers/validator/validator.py @@ -641,6 +641,11 @@ class VReason(Validator): class ValidEmails(Validator): + """Validates a list of email addresses passed in as a string and + delineated by whitespace, ',' or ';'. Also validates quantity of + provided emails. Returns a list of valid email addresses on + success""" + separator = re.compile(r'[^\s,;]+') email_re = re.compile(r'.+@.+\..+') @@ -648,18 +653,32 @@ class ValidEmails(Validator): self.num = num Validator.__init__(self, param = param, **kw) - def run(self, emails): - emails = set(self.separator.findall(emails) if emails else []) + def run(self, emails0): + emails = set(self.separator.findall(emails0) if emails0 else []) failures = set(e for e in emails if not self.email_re.match(e)) emails = emails - failures - if failures: - c.errors.add(errors.BAD_EMAILS, {'emails': ', '.join(failures)}) + + # make sure the number of addresses does not exceed the max + if self.num > 0 and len(emails) + len(failures) > self.num: + # special case for 1: there should be no delineators at all, so + # send back original string to the user + if self.num == 1: + c.errors.add(errors.BAD_EMAILS, + {'emails': '"%s"' % emails0}) + # else report the number expected + else: + c.errors.add(errors.TOO_MANY_EMAILS, + {'num': self.num}) + # correct number, but invalid formatting + elif failures: + c.errors.add(errors.BAD_EMAILS, + {'emails': ', '.join(failures)}) + # no emails elif not emails: c.errors.add(errors.NO_EMAILS) - elif len(emails) > self.num: - c.errors.add(errors.TOO_MANY_EMAILS, {'num': self.num}) else: - return emails + # return single email if one is expected, list otherwise + return list(emails)[0] if self.num == 1 else emails # NOTE: make sure *never* to have res check these are present # otherwise, the response could contain reference to these errors...! diff --git a/r2/r2/lib/emailer.py b/r2/r2/lib/emailer.py index 3fd34c7cb..25415a4bd 100644 --- a/r2/r2/lib/emailer.py +++ b/r2/r2/lib/emailer.py @@ -24,7 +24,7 @@ from pylons.i18n import _ from pylons import c, g, request from r2.lib.pages import PasswordReset, Share, Mail_Opt from r2.lib.utils import timeago -from r2.models import passhash, Email, Default +from r2.models import passhash, Email, Default, has_opted_out from r2.config import cache import os, random, datetime import smtplib, traceback, sys @@ -48,25 +48,6 @@ def simple_email(to, fr, subj, body): msg['Subject'] = utf8(subj) send_mail(msg, fr, to) -def sys_email(email, body, name='', subj = lambda x: x): - fr = (c.user.name if c.user else 'Anonymous user') - if name and name != fr: - fr = "%s [%s]" % (name, fr) - - fr = email_address(fr, email) - subj = subj(fr) - simple_email(feedback, fr, subj, body) - - -def feedback_email(email, body, name=''): - sys_email(email, body, name=name, - subj = lambda fr: "[feedback] feedback from %s" % fr) - -def ad_inq_email(email, body, name=''): - sys_email(email, body, name=name, - subj = lambda fr: "[ad_inq] ad inquiry from %s" % fr) - - def password_email(user): key = passhash(random.randint(0, 1000), user.email) passlink = 'http://' + c.domain + '/resetpassword/' + key @@ -75,69 +56,110 @@ def password_email(user): 'reddit.com password reset', PasswordReset(user=user, passlink=passlink).render(style='email')) -def share(link, emails, from_name = ""): + +def _feedback_email(email, body, kind, name='', reply_to = ''): + """Function for handling feedback and ad_inq emails. Adds an + email to the mail queue to the feedback email account.""" + Email.handler.add_to_queue(c.user if c.user_is_loggedin else None, + None, [feedback], name, email, + datetime.datetime.now(), + request.ip, kind, body = body, + reply_to = reply_to) + +def feedback_email(email, body, name='', reply_to = ''): + """Queues a feedback email to the feedback account.""" + return _feedback_email(email, body, Email.Kind.FEEDBACK, name = name, + reply_to = reply_to) + +def ad_inq_email(email, body, name='', reply_to = ''): + """Queues a ad_inq email to the feedback account.""" + return _feedback_email(email, body, Email.Kind.ADVERTISE, name = name, + reply_to = reply_to) + + +def share(link, emails, from_name = "", reply_to = "", body = ""): + """Queues a 'share link' email.""" now = datetime.datetime.now(g.tz) ival = now - timeago(g.new_link_share_delay) date = max(now,link._date + ival) - Email.handler.add_to_queue(c.user, link, emails, from_name, date, - request.ip, Email.Kind.SHARE) + Email.handler.add_to_queue(c.user, link, emails, from_name, g.share_reply, + date, request.ip, Email.Kind.SHARE, + body = body, reply_to = reply_to) def send_queued_mail(): + """sends mail from the mail queue to smtplib for delivery. Also, + on successes, empties the mail queue and adds all emails to the + sent_mail list.""" now = datetime.datetime.now(g.tz) if not c.site: c.site = Default clear = False session = smtplib.SMTP(g.smtp_server) + # convienence funciton for sending the mail to the singly-defined session and + # marking the mail as read. + def sendmail(email): + try: + session.sendmail(email.fr_addr, email.to_addr, + email.to_MIMEText().as_string()) + email.set_sent() + # exception happens only for local recipient that doesn't exist + except smtplib.SMTPRecipientsRefused: + # handle error and print, but don't stall the rest of the queue + print "Handled error sending mail (traceback to follow)" + traceback.print_exc(file = sys.stdout) + + try: for email in Email.get_unsent(now): clear = True - if not email.should_queue(): - continue - elif email.kind == Email.Kind.SHARE: - email.fr_addr = g.share_reply + + should_queue = email.should_queue() + + # check only on sharing that the mail is invalid + if email.kind == Email.Kind.SHARE and should_queue: email.body = Share(username = email.from_name(), msg_hash = email.msg_hash, - link = email.thing).render(style = "email") + link = email.thing, + body = email.body).render(style = "email") email.subject = _("[reddit] %(user)s has shared a link with you") % \ {"user": email.from_name()} - try: - session.sendmail(email.fr_addr, email.to_addr, - email.to_MIMEText().as_string()) - except smtplib.SMTPRecipientsRefused: - # handle error but don't stop the queue - print "Handled error sending mail (traceback to follow)" - traceback.print_exc(file = sys.stdout) - + sendmail(email) elif email.kind == Email.Kind.OPTOUT: - email.fr_addr = g.share_reply email.body = Mail_Opt(msg_hash = email.msg_hash, leave = True).render(style = "email") email.subject = _("[reddit] email removal notice") - session.sendmail(email.fr_addr, email.to_addr, - email.to_MIMEText().as_string()) + sendmail(email) elif email.kind == Email.Kind.OPTIN: - email.fr_addr = g.share_reply - email.body = Mail_Opt(msg_hash = email.msg_hash, leave = False).render(style = "email") email.subject = _("[reddit] email addition notice") - session.sendmail(email.fr_addr, email.to_addr, - email.to_MIMEText().as_string()) + sendmail(email) + elif email.kind in (Email.Kind.FEEDBACK, Email.Kind.ADVERTISE): + if email.kind == Email.Kind.FEEDBACK: + email.subject = "[feedback] feedback from '%s'" % \ + email.from_name() + else: + email.subject = "[ad_inq] feedback from '%s'" % \ + email.from_name() + sendmail(email) + # handle other types of emails here else: - # handle other types of emails here pass - email.set_sent() finally: session.quit() + + # clear is true if anything was found and processed above if clear: Email.handler.clear_queue(now) def opt_out(msg_hash): + """Queues an opt-out email (i.e., a confirmation that the email + address has been opted out of receiving any future mail)""" email, added = Email.handler.opt_out(msg_hash) if email and added: Email.handler.add_to_queue(None, None, [email], "reddit.com", @@ -146,6 +168,8 @@ def opt_out(msg_hash): return email, added def opt_in(msg_hash): + """Queues an opt-in email (i.e., that the email has been removed + from our opt out list)""" email, removed = Email.handler.opt_in(msg_hash) if email and removed: Email.handler.add_to_queue(None, None, [email], "reddit.com", diff --git a/r2/r2/models/__init__.py b/r2/r2/models/__init__.py index 73ac29469..c94da30fa 100644 --- a/r2/r2/models/__init__.py +++ b/r2/r2/models/__init__.py @@ -26,7 +26,7 @@ from builder import * from vote import * from report import * from subreddit import * -from mail_queue import Email +from mail_queue import Email, has_opted_out, opt_count from admintools import * import thing_changes diff --git a/r2/r2/models/mail_queue.py b/r2/r2/models/mail_queue.py index 9bf025136..815629c6e 100644 --- a/r2/r2/models/mail_queue.py +++ b/r2/r2/models/mail_queue.py @@ -50,6 +50,12 @@ def mail_queue(metadata): # the "To" address of the email sa.Column('to_addr', sa.String), + # the "From" address of the email + sa.Column('fr_addr', sa.String), + + # the "Reply-To" address of the email + sa.Column('reply_to', sa.String), + # fullname of the thing sa.Column('fullname', sa.String), @@ -80,6 +86,12 @@ def sent_mail_table(metadata): # the "To" address of the email sa.Column('to_addr', sa.String), + # the "From" address of the email + sa.Column('fr_addr', sa.String), + + # the "reply-to" address of the email + sa.Column('reply_to', sa.String), + # IP of original request sa.Column('ip', PGInet), @@ -142,6 +154,7 @@ class EmailHandler(object): res = s.execute() return bool(res.fetchall()) + def opt_out(self, msg_hash): """Adds the recipient of the email to the opt-out list and returns that address.""" @@ -152,6 +165,7 @@ class EmailHandler(object): o.insert().execute({o.c.email: email, o.c.msg_hash: msg_hash}) clear_memo('r2.models.mail_queue.has_opted_out', email) + clear_memo('r2.models.mail_queue.opt_count') return (email, True) except sa.exceptions.SQLError: return (email, False) @@ -166,6 +180,7 @@ class EmailHandler(object): sa.delete(o, o.c.email == email).execute() clear_memo('r2.models.mail_queue.has_opted_out', email) + clear_memo('r2.models.mail_queue.opt_count') return (email, True) else: return (email, False) @@ -178,8 +193,8 @@ class EmailHandler(object): return res[0][0] if res and res[:1] else None - def add_to_queue(self, user, thing, emails, from_name, date, ip, - kind, body = ""): + def add_to_queue(self, user, thing, emails, from_name, fr_addr, date, ip, + kind, body = "", reply_to = ""): s = self.queue_table hashes = [] for email in emails: @@ -190,6 +205,8 @@ class EmailHandler(object): s.insert().execute({s.c.to_addr : email, s.c.account_id : uid, s.c.from_name : from_name, + s.c.fr_addr : fr_addr, + s.c.reply_to : reply_to, s.c.fullname: tid, s.c.ip : ip, s.c.kind: kind, @@ -215,7 +232,7 @@ class EmailHandler(object): res = sa.select([s.c.to_addr, s.c.account_id, s.c.from_name, s.c.fullname, s.c.body, s.c.kind, s.c.ip, s.c.date, s.c.uid, - s.c.msg_hash], + s.c.msg_hash, s.c.fr_addr, s.c.reply_to], sa.and_(*where), order_by = s.c.uid, limit = batch_limit).execute() res = res.fetchall() @@ -242,10 +259,11 @@ class EmailHandler(object): # did we not fetch them all? keep_trying = (len(res) == batch_limit) - for addr, acct, fname, fulln, body, kind, ip, date, uid, msg_hash \ - in res: + for (addr, acct, fname, fulln, body, kind, ip, date, uid, + msg_hash, fr_addr, reply_to) in res: yield (accts.get(acct), things.get(fulln), addr, - fname, date, ip, ips[ip], kind, msg_hash, body) + fname, date, ip, ips[ip], kind, msg_hash, body, + fr_addr, reply_to) def clear_queue(self, max_date, kind = None): s = self.queue_table @@ -262,7 +280,8 @@ class Email(object): Kind = Storage((e, i) for i, e in enumerate(Kind)) def __init__(self, user, thing, email, from_name, date, ip, banned_ip, - kind, msg_hash, body = '', subject = "", from_addr = ''): + kind, msg_hash, body = '', from_addr = '', + reply_to = ''): self.user = user self.thing = thing self.to_addr = email @@ -273,14 +292,20 @@ class Email(object): self.banned_ip = banned_ip self.kind = kind self.sent = False - self.body = "" - self.subject = subject + self.body = body + self.subject = '' self.msg_hash = msg_hash + self.reply_to = reply_to def from_name(self): - return ("%(name)s (%(uname)s)" if self._from_name != self.user.name - else "%(uname)s") % \ - dict(name = self._from_name, uname = self.user.name) + if not self.user: + name = "%(name)s" + elif self._from_name != self.user.name: + name = "%(name)s (%(uname)s)" + else: + name = "%(uname)s" + return name % dict(name = self._from_name, + uname = self.user.name if self.user else '') @classmethod def get_unsent(cls, max_date, batch_limit = 50, kind = None): @@ -303,6 +328,8 @@ class Email(object): t.insert().execute({t.c.account_id: self.user._id if self.user else 0, t.c.to_addr : self.to_addr, + t.c.fr_addr : self.fr_addr, + t.c.reply_to : self.reply_to, t.c.ip : self.ip, t.c.fullname: self.thing._fullname if self.thing else "", @@ -315,7 +342,7 @@ class Email(object): def to_MIMEText(self): def utf8(s): return s.encode('utf8') if isinstance(s, unicode) else s - fr = '"%s" <%s>' % (self._from_name, self.fr_addr) if self._from_name else self.fr_addr + fr = '"%s" <%s>' % (self.from_name(), self.fr_addr) if not fr.startswith('-') and not self.to_addr.startswith('-'): # security msg = MIMEText(utf8(self.body)) msg.set_charset('utf8') @@ -325,6 +352,8 @@ class Email(object): if self.user: msg['X-Reddit-username'] = utf8(self.user.name) msg['X-Reddit-ID'] = self.msg_hash + if self.reply_to: + msg['Reply-To'] = utf8(self.reply_to) return msg return None @@ -336,6 +365,12 @@ def has_opted_out(email): return bool(res.fetchall()) +@memoize('r2.models.mail_queue.opt_count') +def opt_count(): + o = Email.handler.opt_table + s = sa.select([sa.func.count(o.c.email)]) + res = s.execute().fetchone() + return int(res[0]) diff --git a/r2/r2/public/static/reddit.css b/r2/r2/public/static/reddit.css index e935a8b2e..722931656 100644 --- a/r2/r2/public/static/reddit.css +++ b/r2/r2/public/static/reddit.css @@ -826,7 +826,9 @@ a.star { text-decoration: none; color: #ff8b60 } .clearleft { clear: left; height: 0px; } .clear { clear: both; } -.sharetable {margin-left: 20px; } +.sharetable.preftable {margin-left: 20px; } +.sharetable.preftable th { padding-bottom: 5px; padding-top: 5px; } +.sharetable.preftable button { margin-top: 10px } .sponsored .entry { margin-right: 20px;} @@ -1229,6 +1231,10 @@ a.star { text-decoration: none; color: #ff8b60 } #passform.pretty-form button { padding: 0px 1px; } +/*opt-out/in form*/ +.opt-form { font-size: larger } +.opt-form form { display: inline; } + .preftable th { padding: 10px; font-weight: bold; diff --git a/r2/r2/templates/feedback.html b/r2/r2/templates/feedback.html index 06d332a05..81cd18910 100644 --- a/r2/r2/templates/feedback.html +++ b/r2/r2/templates/feedback.html @@ -68,9 +68,10 @@ %if thing.replyto: + value="${thing.email}" onfocus="clearTitle(this)" + size="30"/> %else: - %endif diff --git a/r2/r2/templates/optout.html b/r2/r2/templates/optout.html index b6140a0b9..7d54ca518 100644 --- a/r2/r2/templates/optout.html +++ b/r2/r2/templates/optout.html @@ -19,28 +19,41 @@ ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 ## CondeNet, Inc. All Rights Reserved." ############################################################################### -
+
%if thing.leave: -

- ${_("The address %(email)s will no longer recieve email from us.") % dict(email=thing.email)} -

- %if thing.sent: -

+

+ ${_("The address '%(email)s' will no longer receive email from us.") % dict(email=thing.email)} +

+

${_("A confirmation email has been queued up and should be reaching you shortly. It will be the last you hear of us.")}

%else:

- ${_("A confirmation email has already been queued up and/or sent.")} + ${_("Would you like the address %(email)s to no longer receive email from us?") % dict(email=thing.email)} +

+ + +
+
+ +

%endif %elif thing.sent:

- ${_("The address %(email)s is once again fair game to receive email from us. Welcome back. You'll be receiving a confirmation email.") % dict(email=thing.email)} + ${_("'%(email)s' has been removed from our block list.") % dict(email=thing.email)}

%else: -

- ${_("%(email)s has already been removed from our block list.") % dict(email=thing.email)} +

+ ${_("Allow '%(email)s' to receive email from us?") % dict(email=thing.email)} +

+ + +
+
+ +

%endif
diff --git a/r2/r2/templates/prefupdate.html b/r2/r2/templates/prefupdate.html index a1721341a..a23eb5c57 100644 --- a/r2/r2/templates/prefupdate.html +++ b/r2/r2/templates/prefupdate.html @@ -17,7 +17,7 @@ ## the Original Code is CondeNet, Inc. ## ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 -## CondeNet, Inc. All Rights Reserved. +## CondeNet, Inc. All Rights Reserved." ################################################################################ <%namespace file="utils.html" import="error_field"/> @@ -33,7 +33,9 @@ ${_("email")}: - + + ${error_field("BAD_EMAILS")} + ${_("new password")}: diff --git a/r2/r2/templates/share.email b/r2/r2/templates/share.email index 792628150..9fb66867a 100644 --- a/r2/r2/templates/share.email +++ b/r2/r2/templates/share.email @@ -19,14 +19,18 @@ ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 ## CondeNet, Inc. All Rights Reserved. ################################################################################ -<%! from r2.lib.template_helpers import reddit_link %>A user from http://${g.domain}/ has shared a link with you. +%if not thing.body: +${thing.username} from http://${g.domain}/ has shared a link with you. +%else: +${thing.body} +%endif "${thing.link.title}" http://${g.domain}/goto?share=true&id=${thing.link._fullname} -There are currently ${thing.link.num_comments} comments on this link. You can view them here: +<% from r2.lib.strings import strings, plurals %>${_("There are currently %(num_comments)s on this link. You can view them here:") % dict(num_comments = strings.number_label % (thing.link.num_comments, plurals.N_comments(thing.link.num_comments)))} -http://${g.domain}${reddit_link(thing.link.permalink, url=True)} +<% from r2.lib.template_helpers import reddit_link %>http://${g.domain}${reddit_link(thing.link.permalink, url=True)} ___ If you would not like to receive emails from reddit.com in the future, visit http://${g.domain}/mail/optout?x=${thing.msg_hash} \ No newline at end of file diff --git a/r2/r2/templates/sharelink.html b/r2/r2/templates/sharelink.html index 3ac7cd332..242e4f980 100644 --- a/r2/r2/templates/sharelink.html +++ b/r2/r2/templates/sharelink.html @@ -2,16 +2,17 @@ <%namespace file="utils.html" import="error_field"/> <%namespace file="captcha.html" import="captchagen"/> -