From 3b28ff52bc8bf1744a163cd6d4ad06c97cad96ae Mon Sep 17 00:00:00 2001 From: Chad Birch Date: Tue, 11 Feb 2014 14:19:03 -0700 Subject: [PATCH] Gilding: add support for gilding submissions --- r2/r2/controllers/api.py | 18 +- r2/r2/controllers/front.py | 53 ++++-- r2/r2/controllers/ipn.py | 69 +++---- r2/r2/controllers/listingcontroller.py | 14 +- r2/r2/lib/db/queries.py | 42 ++++ r2/r2/lib/jsontemplates.py | 1 + r2/r2/lib/pages/pages.py | 59 ++++-- r2/r2/lib/pages/things.py | 30 +-- r2/r2/lib/strings.py | 9 +- r2/r2/models/gold.py | 178 ++++++++++++++++- r2/r2/models/link.py | 211 ++++++++------------- r2/r2/models/subreddit.py | 36 ++-- r2/r2/public/static/css/compact.css | 10 +- r2/r2/public/static/css/compact.scss | 10 +- r2/r2/public/static/css/reddit-ie7-hax.css | 4 +- r2/r2/public/static/css/reddit.less | 22 +-- r2/r2/public/static/js/gold.js | 46 +++-- r2/r2/templates/allinfobar.html | 4 +- r2/r2/templates/comment.compact | 2 +- r2/r2/templates/comment.html | 10 +- r2/r2/templates/creditgild.html | 2 +- r2/r2/templates/goldpayment.html | 13 +- r2/r2/templates/link.html | 3 + r2/r2/templates/printable.html | 12 ++ r2/r2/templates/printablebuttons.html | 21 +- r2/r2/templates/thingupdater.html | 6 +- 26 files changed, 547 insertions(+), 338 deletions(-) diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py index f1e4083a3..42e9aac8d 100755 --- a/r2/r2/controllers/api.py +++ b/r2/r2/controllers/api.py @@ -3764,21 +3764,21 @@ class ApiController(RedditController): @json_validate(VUser(), VModhash(), - comment=VByName("comment", thing_cls=Comment)) - def POST_generate_payment_blob(self, responder, comment): - if not comment: + thing=VByName("thing")) + def POST_generate_payment_blob(self, responder, thing): + if not thing: abort(400, "Bad Request") - if comment._deleted: + if thing._deleted: abort(403, "Forbidden") - comment_sr = Subreddit._byID(comment.sr_id, data=True) - if (not comment_sr.can_view(c.user) or - not comment_sr.allow_comment_gilding): + thing_sr = Subreddit._byID(thing.sr_id, data=True) + if (not thing_sr.can_view(c.user) or + not thing_sr.allow_gilding): abort(403, "Forbidden") try: - recipient = Account._byID(comment.author_id, data=True) + recipient = Account._byID(thing.author_id, data=True) except NotFound: self.abort404() @@ -3793,7 +3793,7 @@ class ApiController(RedditController): signed=False, recipient=recipient.name, giftmessage=None, - comment=comment._fullname, + thing=thing._fullname, )) @validate(srnames=VPrintable("srnames", max_length=2100)) diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index 41ba85f66..07e45bf50 100755 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -1422,25 +1422,35 @@ class FormsController(RedditController): if not payment_blob['goldtype'] == 'gift': self.abort404() - comment = payment_blob['comment'] - if (not comment or - comment._deleted or - not comment.subreddit_slow.can_view(c.user)): + recipient = payment_blob['recipient'] + thing = payment_blob.get('thing') + if not thing: + thing = payment_blob['comment'] + if (not thing or + thing._deleted or + not thing.subreddit_slow.can_view(c.user)): self.abort404() - recipient = payment_blob['recipient'] - summary = strings.gold_summary_comment_page + if isinstance(thing, Comment): + summary = strings.gold_summary_gilding_page_comment + else: + summary = strings.gold_summary_gilding_page_link summary = summary % {'recipient': recipient.name} months = 1 price = g.gold_month_price * months + if isinstance(thing, Comment): + desc = thing.body + else: + desc = thing.markdown_link_slow() + content = CreditGild( summary=summary, price=price, months=months, stripe_key=g.STRIPE_PUBLIC_KEY, passthrough=passthrough, - comment=comment, + description=desc, period=None, ) @@ -1457,17 +1467,18 @@ class FormsController(RedditController): # variables below are just for gifts signed=VBoolean("signed"), recipient_name=VPrintable("recipient", max_length=50), - comment=VByName("comment", thing_cls=Comment), + thing=VByName("thing"), giftmessage=VLength("giftmessage", 10000)) def GET_gold(self, goldtype, period, months, - signed, recipient_name, giftmessage, comment): + signed, recipient_name, giftmessage, thing): - if comment: - comment_sr = Subreddit._byID(comment.sr_id, data=True) - if (comment._deleted or comment._spam or - not comment_sr.can_view(c.user) or - not comment_sr.allow_comment_gilding): - comment = None + if thing: + thing_sr = Subreddit._byID(thing.sr_id, data=True) + if (thing._deleted or + thing._spam or + not thing_sr.can_view(c.user) or + not thing_sr.allow_gilding): + thing = None start_over = False recipient = None @@ -1483,10 +1494,10 @@ class FormsController(RedditController): if months is None or months < 1: start_over = True - if comment: - recipient = Account._byID(comment.author_id, data=True) + if thing: + recipient = Account._byID(thing.author_id, data=True) if recipient._deleted: - comment = None + thing = None recipient = None start_over = True else: @@ -1515,8 +1526,8 @@ class FormsController(RedditController): payment_blob["signed"] = signed payment_blob["recipient"] = recipient.name payment_blob["giftmessage"] = _force_utf8(giftmessage) - if comment: - payment_blob["comment"] = comment._fullname + if thing: + payment_blob["thing"] = thing._fullname passthrough = generate_blob(payment_blob) @@ -1525,7 +1536,7 @@ class FormsController(RedditController): content=GoldPayment(goldtype, period, months, signed, recipient, giftmessage, passthrough, - comment) + thing) ).render() @validate(VUser()) diff --git a/r2/r2/controllers/ipn.py b/r2/r2/controllers/ipn.py index 4da3bd4cb..0e498d445 100644 --- a/r2/r2/controllers/ipn.py +++ b/r2/r2/controllers/ipn.py @@ -62,7 +62,8 @@ from r2.models import ( create_gift_gold, create_gold_code, get_discounted_price, - make_comment_gold_message, + Link, + make_gold_message, NotFound, retrieve_gold_transaction, send_system_message, @@ -225,14 +226,15 @@ def months_and_days_from_pennies(pennies, discount=False): days = 31 * months return (months, days) -def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): +def send_gift(buyer, recipient, months, days, signed, giftmessage, + thing_fullname): admintools.engolden(recipient, days) - if comment_id: - comment = Thing._by_fullname(comment_id, data=True) - comment._gild(buyer) + if thing_fullname: + thing = Thing._by_fullname(thing_fullname, data=True) + thing._gild(buyer) else: - comment = None + thing = None if signed: sender = buyer.name @@ -248,17 +250,20 @@ def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): else: amount = "%d months" % months - if not comment: + if not thing: subject = _('Let there be gold! %s just sent you reddit gold!') % sender message = strings.youve_got_gold % dict(sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage + '\n\n----' else: - subject = _('Your comment has been gilded!') - message = strings.youve_got_comment_gold % dict( - url=comment.make_permalink_slow(), - ) + url = thing.make_permalink_slow() + if isinstance(thing, Comment): + subject = _('Your comment has been gilded!') + message = strings.youve_been_gilded_comment % {'url': url} + else: + subject = _('Your submission has been gilded!') + message = strings.youve_been_gilded_link % {'url': url} message += '\n\n' + strings.gold_benefits_msg if g.lounge_reddit: @@ -272,7 +277,7 @@ def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): g.log.error('send_gift: could not send system message') g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) - return comment + return thing def send_gold_code(buyer, months, days, @@ -354,9 +359,9 @@ class IpnController(RedditController): c.user._commit() if payment_blob["goldtype"] == "gift": - comment_id = payment_blob.get("comment") - comment = send_gift(c.user, recipient, months, days, signed, - giftmessage, comment_id) + thing_fullname = payment_blob.get("thing") + thing = send_gift(c.user, recipient, months, days, signed, + giftmessage, thing_fullname) form.set_html(".status", _("the gold has been delivered!")) else: try: @@ -368,7 +373,7 @@ class IpnController(RedditController): "for assistance.") % {'email': g.goldthanks_email}) return - comment = None + thing = None form.set_html(".status", _("the gift code has been messaged to you!")) @@ -380,10 +385,9 @@ class IpnController(RedditController): payment_blob["status"] = "processed" g.hardcache.set(blob_key, payment_blob, 86400 * 30) - if comment: - gilding_message = make_comment_gold_message(comment, - user_gilded=True) - jquery.gild_comment(comment_id, gilding_message, comment.gildings) + if thing: + gilding_message = make_gold_message(thing, user_gilded=True) + jquery.gild_thing(thing_fullname, gilding_message, thing.gildings) @textresponse(paypal_secret = VPrintable('secret', 50), payment_status = VPrintable('payment_status', 20), @@ -527,8 +531,9 @@ class IpnController(RedditController): % (recipient_name, custom)) signed = payment_blob.get("signed", False) giftmessage = _force_unicode(payment_blob.get("giftmessage", "")) - comment_id = payment_blob.get("comment") - send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id) + thing_fullname = payment_blob.get("thing") + send_gift(buyer, recipient, months, days, signed, giftmessage, + thing_fullname) instagift = True subject = _("Thanks for giving the gift of reddit gold!") message = _("Your classy gift to %s has been delivered.\n\n" @@ -576,7 +581,7 @@ class Webhook(object): def __init__(self, passthrough=None, transaction_id=None, subscr_id=None, pennies=None, months=None, payer_email='', payer_id='', goldtype=None, buyer=None, recipient=None, signed=False, - giftmessage=None, comment=None): + giftmessage=None, thing=None): self.passthrough = passthrough self.transaction_id = transaction_id self.subscr_id = subscr_id @@ -589,7 +594,7 @@ class Webhook(object): self.recipient = recipient self.signed = signed self.giftmessage = giftmessage - self.comment = comment + self.thing = thing def load_blob(self): payment_blob = validate_blob(self.passthrough) @@ -598,8 +603,8 @@ class Webhook(object): self.recipient = payment_blob.get('recipient') self.signed = payment_blob.get('signed', False) self.giftmessage = payment_blob.get('giftmessage') - comment = payment_blob.get('comment') - self.comment = comment._fullname if comment else None + thing = payment_blob.get('thing') + self.thing = thing._fullname if thing else None def __repr__(self): return '<%s: transaction %s>' % (self.__class__.__name__, self.transaction_id) @@ -720,7 +725,7 @@ class GoldPaymentController(RedditController): recipient = webhook.recipient signed = webhook.signed giftmessage = webhook.giftmessage - comment = webhook.comment + thing = webhook.thing gold_recipient = recipient or buyer with gold_lock(gold_recipient): @@ -753,7 +758,7 @@ class GoldPaymentController(RedditController): elif goldtype == 'gift': send_gift(buyer, recipient, months, days, signed, giftmessage, - comment) + thing) subject = "thanks for giving reddit gold!" message = "Your gift to %s has been delivered." % recipient.name @@ -1183,12 +1188,12 @@ def validate_blob(custom): ret['recipient'] = recipient except NotFound: raise GoldException('bad recipient') - comment_fullname = payment_blob.get('comment', None) - if comment_fullname: + thing_fullname = payment_blob.get('thing', None) + if thing_fullname: try: - ret['comment'] = Comment._by_fullname(comment_fullname) + ret['thing'] = Thing._by_fullname(thing_fullname) except NotFound: - raise GoldException('bad comment') + raise GoldException('bad thing') ret['signed'] = payment_blob.get('signed', False) giftmessage = payment_blob.get('giftmessage') giftmessage = _force_unicode(giftmessage) if giftmessage else None diff --git a/r2/r2/controllers/listingcontroller.py b/r2/r2/controllers/listingcontroller.py index 313fd015b..be38c566a 100755 --- a/r2/r2/controllers/listingcontroller.py +++ b/r2/r2/controllers/listingcontroller.py @@ -520,8 +520,8 @@ class UserController(ListingController): elif (self.where == 'gilded' and (c.user == self.vuser or c.user_is_admin)): path = '/user/%s/gilded/' % self.vuser.name - buttons = [NavButton(_("my posts"), dest='/'), - NavButton(_("posts gilded by me"), dest='/given')] + buttons = [NavButton(_("gildings received"), dest='/'), + NavButton(_("gildings given"), dest='/given')] res.append(NavMenu(buttons, base_path=path, type='flatlist')) return res @@ -530,14 +530,14 @@ class UserController(ListingController): titles = {'overview': _("overview for %(user)s"), 'comments': _("comments by %(user)s"), 'submitted': _("submitted by %(user)s"), - 'gilded': _("gilded comments by %(user)s"), + 'gilded': _("gilded by %(user)s"), 'liked': _("liked by %(user)s"), 'disliked': _("disliked by %(user)s"), 'saved': _("saved by %(user)s"), 'hidden': _("hidden by %(user)s"), 'promoted': _("promoted by %(user)s")} if self.where == 'gilded' and self.show == 'given': - return _("comments gilded by %(user)s") % {'user': self.vuser.name} + return _("gildings given by %(user)s") % {'user': self.vuser.name} title = titles.get(self.where, _('profile for %(user)s')) \ % dict(user = self.vuser.name, site = c.site.name) @@ -598,7 +598,7 @@ class UserController(ListingController): if self.show == 'given': q = queries.get_user_gildings(self.vuser) else: - q = queries.get_gilded_user_comments(self.vuser) + q = queries.get_gilded_user(self.vuser) elif self.where in ('liked', 'disliked'): sup.set_sup_header(self.vuser, self.where) @@ -1306,7 +1306,7 @@ class UserListListingController(ListingController): return self.build_listing(**kw) class GildedController(ListingController): - title_text = _("gilded comments") + title_text = _("gilded") def keep_fn(self): def keep(item): @@ -1315,7 +1315,7 @@ class GildedController(ListingController): def query(self): try: - return c.site.get_gilded_comments() + return c.site.get_gilded() except NotImplementedError: abort(404) diff --git a/r2/r2/lib/db/queries.py b/r2/r2/lib/db/queries.py index 7f97990c3..70e9a5add 100755 --- a/r2/r2/lib/db/queries.py +++ b/r2/r2/lib/db/queries.py @@ -797,18 +797,60 @@ def get_all_gilded_comments(): return +@cached_query(SubredditQueryCache, sort=[desc("date")], filter_fn=filter_thing) +def get_all_gilded_links(): + return + + +@merged_cached_query +def get_all_gilded(): + return [get_all_gilded_comments(), get_all_gilded_links()] + + @cached_query(SubredditQueryCache, sort=[desc("date")], filter_fn=filter_thing) def get_gilded_comments(sr_id): return + +@cached_query(SubredditQueryCache, sort=[desc("date")], filter_fn=filter_thing) +def get_gilded_links(sr_id): + return + + +@merged_cached_query +def get_gilded(sr_ids): + queries = [get_gilded_links, get_gilded_comments] + return [query(sr_id) + for sr_id, query in itertools.product(tup(sr_ids), queries)] + + @cached_query(UserQueryCache, sort=[desc("date")], filter_fn=filter_thing) def get_gilded_user_comments(user_id): return + +@cached_query(UserQueryCache, sort=[desc("date")], filter_fn=filter_thing) +def get_gilded_user_links(user_id): + return + + +@merged_cached_query +def get_gilded_users(user_ids): + queries = [get_gilded_user_links, get_gilded_user_comments] + return [query(user_id) + for user_id, query in itertools.product(tup(user_ids), queries)] + + @cached_query(UserQueryCache, sort=[desc("date")], filter_fn=filter_thing) def get_user_gildings(user_id): return + +@merged_cached_query +def get_gilded_user(user): + return [get_gilded_user_comments(user), get_gilded_user_links(user)] + + def add_queries(queries, insert_items=None, delete_items=None, foreground=False): """Adds multiple queries to the query queue. If insert_items or delete_items is specified, the query may not need to be diff --git a/r2/r2/lib/jsontemplates.py b/r2/r2/lib/jsontemplates.py index 4b47f4668..e847d2659 100755 --- a/r2/r2/lib/jsontemplates.py +++ b/r2/r2/lib/jsontemplates.py @@ -409,6 +409,7 @@ class LinkJsonTemplate(ThingJsonTemplate): domain="domain", downs="downvotes", edited="editted", + gilded="gildings", hidden="hidden", is_self="is_self", likes="likes", diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 66d0da0ce..89d69251e 100644 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -247,17 +247,30 @@ class Reddit(Templated): and not is_api() and not self.show_wiki_actions): # insert some form templates for js to use # TODO: move these to client side templates - gold = GoldPayment("gift", - "monthly", - months=1, - signed=False, - recipient="", - giftmessage=None, - passthrough=None, - comment=None, - clone_template=True, - ) - self._content = PaneStack([ShareLink(), content, gold]) + gold_link = GoldPayment("gift", + "monthly", + months=1, + signed=False, + recipient="", + giftmessage=None, + passthrough=None, + thing=None, + clone_template=True, + thing_type="link", + ) + gold_comment = GoldPayment("gift", + "monthly", + months=1, + signed=False, + recipient="", + giftmessage=None, + passthrough=None, + thing=None, + clone_template=True, + thing_type="comment", + ) + self._content = PaneStack([ShareLink(), content, + gold_comment, gold_link]) else: self._content = content @@ -2184,9 +2197,10 @@ class Gold(Templated): class GoldPayment(Templated): def __init__(self, goldtype, period, months, signed, - recipient, giftmessage, passthrough, comment, - clone_template=False): + recipient, giftmessage, passthrough, thing, + clone_template=False, thing_type=None): pay_from_creddits = False + desc = None if period == "monthly" or 1 <= months < 12: unit_price = g.gold_month_price @@ -2267,9 +2281,17 @@ class GoldPayment(Templated): amount=Score.somethings(months, "month")) elif goldtype == "gift": if clone_template: - format = strings.gold_summary_comment_gift - elif comment: - format = strings.gold_summary_comment_page + if thing_type == "comment": + format = strings.gold_summary_gilding_comment + elif thing_type == "link": + format = strings.gold_summary_gilding_link + elif thing: + if isinstance(thing, Comment): + format = strings.gold_summary_gilding_page_comment + desc = thing.body + else: + format = strings.gold_summary_gilding_page_link + desc = thing.markdown_link_slow() elif signed: format = strings.gold_summary_signed_gift else: @@ -2298,7 +2320,8 @@ class GoldPayment(Templated): summary=summary, giftmessage=giftmessage, pay_from_creddits=pay_from_creddits, passthrough=passthrough, - comment=comment, clone_template=clone_template, + thing=thing, clone_template=clone_template, + description=desc, thing_type=thing_type, paypal_buttonid=paypal_buttonid, stripe_key=stripe_key, coinbase_button_id=coinbase_button_id) @@ -2340,7 +2363,7 @@ class GoldSubscription(Templated): Templated.__init__(self) class CreditGild(Templated): - """Page for credit card payments for comment gilding.""" + """Page for credit card payments for gilding.""" pass diff --git a/r2/r2/lib/pages/things.py b/r2/r2/lib/pages/things.py index 00e257eb1..0f8933710 100644 --- a/r2/r2/lib/pages/things.py +++ b/r2/r2/lib/pages/things.py @@ -37,7 +37,8 @@ class PrintableButtons(Styled): show_delete = False, show_report = True, show_distinguish = False, show_marknsfw = False, show_unmarknsfw = False, is_link=False, - show_flair = False, show_rescrape=False, **kw): + show_flair=False, show_rescrape=False, + show_givegold=False, **kw): show_ignore = thing.show_reports approval_checkmark = getattr(thing, "approval_checkmark", None) show_approve = (thing.show_spam or show_ignore or @@ -59,6 +60,7 @@ class PrintableButtons(Styled): show_unmarknsfw = show_unmarknsfw, show_flair = show_flair, show_rescrape=show_rescrape, + show_givegold=show_givegold, **kw) class BanButtons(PrintableButtons): @@ -88,6 +90,7 @@ class LinkButtons(PrintableButtons): if (not thing.is_self and not (thing.has_thumbnail or thing.media_object)): show_rescrape = True + show_givegold = thing.can_gild and (c.permalink_page or c.profilepage) # do we show the delete button? show_delete = is_author and delete and not thing._deleted @@ -133,6 +136,7 @@ class LinkButtons(PrintableButtons): show_unmarknsfw = show_unmarknsfw, show_flair = thing.can_flair, show_rescrape=show_rescrape, + show_givegold=show_givegold, show_comments = comments, # promotion promoted = thing.promoted, @@ -148,28 +152,13 @@ class CommentButtons(PrintableButtons): # do we show the delete button? show_delete = is_author and delete and not thing._deleted - can_gild = ( - # you can't gild your own comment - not is_author - # no point in showing the button for things you've already gilded - and not thing.user_gilded - # this is a way of checking if the user is logged in that works - # both within CommentPane instances and without. e.g. CommentPane - # explicitly sets user_is_loggedin = False but can_reply is - # correct. while on user overviews, you can't reply but will get - # the correct value for user_is_loggedin - and (c.user_is_loggedin or thing.can_reply) - # ick, if the author deleted their account we shouldn't waste gold - and not thing.author._deleted - # some subreddits can have gilding disabled - and thing.subreddit.allow_comment_gilding - ) - show_distinguish = (is_author and (thing.can_ban or # Moderator distinguish c.user.employee or # Admin distinguish c.user_special_distinguish)) + show_givegold = thing.can_gild + PrintableButtons.__init__(self, "commentbuttons", thing, is_author = is_author, profilepage = c.profilepage, @@ -182,10 +171,11 @@ class CommentButtons(PrintableButtons): deleted = thing.deleted, parent_permalink = thing.parent_permalink, can_reply = thing.can_reply, - can_gild=can_gild, show_report = show_report, show_distinguish = show_distinguish, - show_delete = show_delete) + show_delete = show_delete, + show_givegold=show_givegold, + ) class MessageButtons(PrintableButtons): def __init__(self, thing, delete = False, report = True): diff --git a/r2/r2/lib/strings.py b/r2/r2/lib/strings.py index 7e6b22f2b..1f0c609c4 100644 --- a/r2/r2/lib/strings.py +++ b/r2/r2/lib/strings.py @@ -144,15 +144,18 @@ string_dict = dict( over_comment_limit_gold = _("Sorry, the maximum number of comments is %d."), youve_got_gold = _("%(sender)s just gifted you %(amount)s of reddit gold!"), giftgold_note = _("Here's a note that was included:\n\n----\n\n"), - youve_got_comment_gold = _("Another user liked [your comment](%(url)s) so much that they gilded it, giving you reddit gold.\n\n"), + youve_been_gilded_comment = _("Another user liked [your comment](%(url)s) so much that they gilded it, giving you reddit gold.\n\n"), + youve_been_gilded_link = _("Another user liked [your submission](%(url)s) so much that they gilded it, giving you reddit gold.\n\n"), gold_summary_autorenew = _("You're about to set up an ongoing, autorenewing subscription to reddit gold for yourself (%(user)s)."), gold_summary_onetime = _("You're about to make a one-time purchase of %(amount)s of reddit gold for yourself (%(user)s)."), gold_summary_creddits = _("You're about to purchase %(amount)s of reddit gold creddits. They work like gift certificates: each creddit you have will allow you to give one month of reddit gold to someone else."), gold_summary_gift_code = _("You're about to purchase %(amount)s of reddit gold in the form of a gift code. The recipient (or you) will be able to claim the code to redeem that gold to their account."), gold_summary_signed_gift = _("You're about to give %(amount)s of reddit gold to %(recipient)s, who will be told that it came from you."), gold_summary_anonymous_gift = _("You're about to give %(amount)s of reddit gold to %(recipient)s. It will be an anonymous gift."), - gold_summary_comment_gift = _("Want to say thanks to *%(recipient)s* for this comment? Give them a month of [reddit gold](/gold/about)."), - gold_summary_comment_page = _("Give *%(recipient)s* a month of [reddit gold](/gold/about) for this comment:"), + gold_summary_gilding_comment = _("Want to say thanks to *%(recipient)s* for this comment? Give them a month of [reddit gold](/gold/about)."), + gold_summary_gilding_link = _("Want to say thanks to *%(recipient)s* for this submission? Give them a month of [reddit gold](/gold/about)."), + gold_summary_gilding_page_comment = _("Give *%(recipient)s* a month of [reddit gold](/gold/about) for this comment:"), + gold_summary_gilding_page_link = _("Give *%(recipient)s* a month of [reddit gold](/gold/about) for this submission:"), unvotable_message = _("sorry, this has been archived and can no longer be voted on"), account_activity_blurb = _("This page shows a history of recent activity on your account. If you notice unusual activity, you should change your password immediately. Location information is guessed from your computer's IP address and may be wildly wrong, especially for visits from mobile devices. Note: due to a bug, private-use addresses (starting with 10.) sometimes show up erroneously in this list after regular use of the site."), your_current_ip_is = _("You are currently accessing reddit from this IP address: %(address)s."), diff --git a/r2/r2/models/gold.py b/r2/r2/models/gold.py index 06fa03b97..c96ed0564 100644 --- a/r2/r2/models/gold.py +++ b/r2/r2/models/gold.py @@ -22,12 +22,15 @@ from r2.lib.db.tdb_sql import make_metadata, index_str, create_table +import json import pytz +import uuid from pycassa import NotFoundException from pycassa.system_manager import INT_TYPE, UTF8_TYPE +from pycassa.util import convert_uuid_to_time from pylons import g, c -from pylons.i18n import _ +from pylons.i18n import _, ungettext from datetime import datetime import sqlalchemy as sa from sqlalchemy.exc import IntegrityError, OperationalError @@ -42,7 +45,7 @@ from random import choice from time import time from r2.lib.db import tdb_cassandra -from r2.lib.db.tdb_cassandra import NotFound +from r2.lib.db.tdb_cassandra import NotFound, view_of from r2.models import Account from r2.models.subreddit import Frontpage from r2.models.wiki import WikiPage @@ -127,6 +130,107 @@ class GoldRevenueGoalByDate(object): return None +class GildedCommentsByAccount(tdb_cassandra.DenormalizedRelation): + _use_db = True + _last_modified_name = 'Gilding' + _views = [] + + @classmethod + def value_for(cls, thing1, thing2): + return '' + + @classmethod + def gild(cls, user, thing): + cls.create(user, [thing]) + + +class GildedLinksByAccount(tdb_cassandra.DenormalizedRelation): + _use_db = True + _last_modified_name = 'Gilding' + _views = [] + + @classmethod + def value_for(cls, thing1, thing2): + return '' + + @classmethod + def gild(cls, user, thing): + cls.create(user, [thing]) + + +@view_of(GildedCommentsByAccount) +@view_of(GildedLinksByAccount) +class GildingsByThing(tdb_cassandra.View): + _use_db = True + _extra_schema_creation_args = { + "key_validation_class": tdb_cassandra.UTF8_TYPE, + "column_name_class": tdb_cassandra.UTF8_TYPE, + } + + @classmethod + def get_gilder_ids(cls, thing): + columns = cls.get_time_sorted_columns(thing._fullname) + return [int(account_id, 36) for account_id in columns.iterkeys()] + + @classmethod + def create(cls, user, things): + for thing in things: + cls._set_values(thing._fullname, {user._id36: ""}) + + @classmethod + def delete(cls, user, things): + # gildings cannot be undone + raise NotImplementedError() + + +@view_of(GildedCommentsByAccount) +@view_of(GildedLinksByAccount) +class GildingsByDay(tdb_cassandra.View): + _use_db = True + _compare_with = tdb_cassandra.TIME_UUID_TYPE + _extra_schema_creation_args = { + "key_validation_class": tdb_cassandra.ASCII_TYPE, + "column_name_class": tdb_cassandra.TIME_UUID_TYPE, + "default_validation_class": tdb_cassandra.UTF8_TYPE, + } + + @staticmethod + def _rowkey(date): + return date.strftime("%Y-%m-%d") + + @classmethod + def get_gildings(cls, date): + key = cls._rowkey(date) + columns = cls.get_time_sorted_columns(key) + gildings = [] + for name, json_blob in columns.iteritems(): + timestamp = convert_uuid_to_time(name) + date = datetime.utcfromtimestamp(timestamp).replace(tzinfo=g.tz) + + gilding = json.loads(json_blob) + gilding["date"] = date + gilding["user"] = int(gilding["user"], 36) + gildings.append(gilding) + return gildings + + @classmethod + def create(cls, user, things): + key = cls._rowkey(datetime.now(g.tz)) + + columns = {} + for thing in things: + columns[uuid.uuid1()] = json.dumps({ + "user": user._id36, + "thing": thing._fullname, + }) + cls._set_values(key, columns) + + @classmethod + def delete(cls, user, things): + # gildings cannot be undone + raise NotImplementedError() + + def create_unclaimed_gold (trans_id, payer_email, paying_id, pennies, days, secret, date, subscr_id = None): @@ -404,3 +508,73 @@ def get_discounted_price(gold_price): discount = float(getattr(g, 'BTC_DISCOUNT', '0')) price = (gold_price.pennies * (1 - discount)) / 100. return GoldPrice("%.2f" % price) + + +def make_gold_message(thing, user_gilded): + from r2.models import Comment + + if thing.gildings == 0 or thing._spam or thing._deleted: + return None + + author = Account._byID(thing.author_id, data=True) + if not author._deleted: + author_name = author.name + else: + author_name = _("[deleted]") + + if c.user_is_loggedin and thing.author_id == c.user._id: + if isinstance(thing, Comment): + gilded_message = ungettext( + "a redditor gifted you a month of reddit gold for this " + "comment.", + "redditors have gifted you %(months)d months of reddit gold " + "for this comment.", + thing.gildings + ) + else: + gilded_message = ungettext( + "a redditor gifted you a month of reddit gold for this " + "submission.", + "redditors have gifted you %(months)d months of reddit gold " + "for this submission.", + thing.gildings + ) + elif user_gilded: + if isinstance(thing, Comment): + gilded_message = ungettext( + "you have gifted reddit gold to %(recipient)s for this " + "comment.", + "you and other redditors have gifted %(months)d months of " + "reddit gold to %(recipient)s for this comment.", + thing.gildings + ) + else: + gilded_message = ungettext( + "you have gifted reddit gold to %(recipient)s for this " + "submission.", + "you and other redditors have gifted %(months)d months of " + "reddit gold to %(recipient)s for this submission.", + thing.gildings + ) + else: + if isinstance(thing, Comment): + gilded_message = ungettext( + "a redditor has gifted reddit gold to %(recipient)s for this " + "comment.", + "redditors have gifted %(months)d months of reddit gold to " + "%(recipient)s for this comment.", + thing.gildings + ) + else: + gilded_message = ungettext( + "a redditor has gifted reddit gold to %(recipient)s for this " + "submission.", + "redditors have gifted %(months)d months of reddit gold to " + "%(recipient)s for this submission.", + thing.gildings + ) + + return gilded_message % dict( + recipient=author_name, + months=thing.gildings, + ) diff --git a/r2/r2/models/link.py b/r2/r2/models/link.py index c2de53b99..55f7db5e9 100755 --- a/r2/r2/models/link.py +++ b/r2/r2/models/link.py @@ -45,26 +45,28 @@ from r2.lib.strings import strings, Score from r2.lib.db import tdb_cassandra from r2.lib.db.tdb_cassandra import NotFoundException, view_of from r2.lib.utils import sanitize_url +from r2.models.gold import ( + GildedCommentsByAccount, + GildedLinksByAccount, + make_gold_message, +) from r2.models.subreddit import MultiReddit from r2.models.query_cache import CachedQueryMutator from r2.models.promo import PROMOTE_STATUS, get_promote_srid from pylons import c, g, request -from pylons.i18n import ungettext, _ +from pylons.i18n import _ from datetime import datetime, timedelta from hashlib import md5 -from pycassa.util import convert_uuid_to_time import random, re -import json -import uuid class LinkExists(Exception): pass # defining types class Link(Thing, Printable): _data_int_props = Thing._data_int_props + ( - 'num_comments', 'reported', 'comment_tree_id') + 'num_comments', 'reported', 'comment_tree_id', 'gildings') _defaults = dict(is_self=False, over_18=False, over_18_override=False, @@ -87,6 +89,7 @@ class Link(Thing, Printable): contest_mode=False, skip_commentstree_q="", ignore_reports=False, + gildings=0, ) _essentials = ('sr_id', 'author_id') _nsfw = re.compile(r"\bnsfw\b", re.I) @@ -299,6 +302,28 @@ class Link(Thing, Printable): return self.make_permalink(self.subreddit_slow, force_domain=force_domain) + def markdown_link_slow(self): + return "[%s](%s)" % (self.title.decode('utf-8'), + self.make_permalink_slow()) + + def _gild(self, user): + now = datetime.now(g.tz) + + self._incr("gildings") + + GildedLinksByAccount.gild(user, self) + + from r2.lib.db import queries + with CachedQueryMutator() as m: + gilding = utils.Storage(thing=self, date=now) + m.insert(queries.get_all_gilded_links(), [gilding]) + m.insert(queries.get_gilded_links(self.sr_id), [gilding]) + m.insert(queries.get_gilded_user_links(self.author_id), + [gilding]) + m.insert(queries.get_user_gildings(user), [gilding]) + + hooks.get_hook('link.gild').call(link=self, gilder=user) + @staticmethod def _should_expunge_selftext(link): verdict = getattr(link, "verdict", "") @@ -347,6 +372,13 @@ class Link(Thing, Printable): for ban in bans_for_domain_parts(urls)} if user_is_loggedin: + gilded = [thing for thing in wrapped if thing.gildings > 0] + try: + user_gildings = GildedLinksByAccount.fast_query(user, gilded) + except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: + g.log.warning("Cassandra gilding lookup failed: %r", e) + user_gildings = {} + try: saved = LinkSavesByAccount.fast_query(user, wrapped) hidden = LinkHidesByAccount.fast_query(user, wrapped) @@ -421,13 +453,29 @@ class Link(Thing, Printable): item.urlprefix = '' if user_is_loggedin: + item.user_gilded = (user, item) in user_gildings item.saved = (user, item) in saved item.hidden = (user, item) in hidden item.visited = (user, item) in visited else: + item.user_gilded = False item.saved = item.hidden = item.visited = False + item.gilded_message = make_gold_message(item, item.user_gilded) + item.can_gild = ( + c.user_is_loggedin and + # you can't gild your own submission + not (item.author and + item.author._id == user._id) and + # no point in showing the button for things you've already gilded + not item.user_gilded and + # ick, if the author deleted their account we shouldn't waste gold + not item.author._deleted and + # some subreddits can have gilding disabled + item.subreddit.allow_gilding + ) + item.num = None item.permalink = item.make_permalink(item.subreddit) if item.is_self: @@ -700,45 +748,6 @@ class PromotedLink(Link): Printable.add_props(user, wrapped) -def make_comment_gold_message(comment, user_gilded): - if comment.gildings == 0 or comment._spam or comment._deleted: - return None - - author = Account._byID(comment.author_id, data=True) - if not author._deleted: - author_name = author.name - else: - author_name = _("[deleted]") - - if c.user_is_loggedin and comment.author_id == c.user._id: - gilded_message = ungettext( - "a redditor gifted you a month of reddit gold for this comment.", - "redditors have gifted you %(months)d months of reddit gold for " - "this comment.", - comment.gildings - ) - elif user_gilded: - gilded_message = ungettext( - "you have gifted reddit gold to %(recipient)s for this comment.", - "you and other redditors have gifted %(months)d months of " - "reddit gold to %(recipient)s for this comment.", - comment.gildings - ) - else: - gilded_message = ungettext( - "a redditor has gifted reddit gold to %(recipient)s for this " - "comment.", - "redditors have gifted %(months)d months of reddit gold to " - "%(recipient)s for this comment.", - comment.gildings - ) - - return gilded_message % dict( - recipient=author_name, - months=comment.gildings, - ) - - class Comment(Thing, Printable): _data_int_props = Thing._data_int_props + ('reported', 'gildings') _defaults = dict(reported=0, @@ -882,7 +891,7 @@ class Comment(Thing, Printable): self._incr("gildings") - GildedCommentsByAccount.gild_comment(user, self) + GildedCommentsByAccount.gild(user, self) from r2.lib.db import queries with CachedQueryMutator() as m: @@ -976,10 +985,9 @@ class Comment(Thing, Printable): now = datetime.now(g.tz) if user_is_loggedin: - gilded = [comment for comment in wrapped if comment.gildings > 0] + gilded = [thing for thing in wrapped if thing.gildings > 0] try: - user_gildings = GildedCommentsByAccount.fast_query(user, - gilded) + user_gildings = GildedCommentsByAccount.fast_query(user, gilded) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Cassandra gilding lookup failed: %r", e) user_gildings = {} @@ -1038,8 +1046,26 @@ class Comment(Thing, Printable): else: item.user_gilded = False item.saved = False - item.gilded_message = make_comment_gold_message(item, - item.user_gilded) + item.gilded_message = make_gold_message(item, item.user_gilded) + + item.can_gild = ( + # this is a way of checking if the user is logged in that works + # both within CommentPane instances and without. e.g. CommentPane + # explicitly sets user_is_loggedin = False but can_reply is + # correct. while on user overviews, you can't reply but will get + # the correct value for user_is_loggedin + (c.user_is_loggedin or getattr(item, "can_reply", True)) and + # you can't gild your own comment + not (c.user_is_loggedin and + item.author and + item.author._id == user._id) and + # no point in showing the button for things you've already gilded + not item.user_gilded and + # ick, if the author deleted their account we shouldn't waste gold + not item.author._deleted and + # some subreddits can have gilding disabled + item.subreddit.allow_gilding + ) # not deleted on profile pages, # deleted if spam and not author or admin @@ -1537,91 +1563,6 @@ class Message(Thing, Printable): return True -class GildedCommentsByAccount(tdb_cassandra.DenormalizedRelation): - _use_db = True - _last_modified_name = 'Gilding' - _views = [] - - @classmethod - def value_for(cls, thing1, thing2): - return '' - - @classmethod - def gild_comment(cls, user, comment): - cls.create(user, [comment]) - - -@view_of(GildedCommentsByAccount) -class GildingsByThing(tdb_cassandra.View): - _use_db = True - _extra_schema_creation_args = { - "key_validation_class": tdb_cassandra.UTF8_TYPE, - "column_name_class": tdb_cassandra.UTF8_TYPE, - } - - @classmethod - def get_gilder_ids(cls, thing): - columns = cls.get_time_sorted_columns(thing._fullname) - return [int(account_id, 36) for account_id in columns.iterkeys()] - - @classmethod - def create(cls, user, things): - for thing in things: - cls._set_values(thing._fullname, {user._id36: ""}) - - @classmethod - def delete(cls, user, things): - # gildings cannot be undone - raise NotImplementedError() - - -@view_of(GildedCommentsByAccount) -class GildingsByDay(tdb_cassandra.View): - _use_db = True - _compare_with = tdb_cassandra.TIME_UUID_TYPE - _extra_schema_creation_args = { - "key_validation_class": tdb_cassandra.ASCII_TYPE, - "column_name_class": tdb_cassandra.TIME_UUID_TYPE, - "default_validation_class": tdb_cassandra.UTF8_TYPE, - } - - @staticmethod - def _rowkey(date): - return date.strftime("%Y-%m-%d") - - @classmethod - def get_gildings(cls, date): - key = cls._rowkey(date) - columns = cls.get_time_sorted_columns(key) - gildings = [] - for name, json_blob in columns.iteritems(): - timestamp = convert_uuid_to_time(name) - date = datetime.utcfromtimestamp(timestamp).replace(tzinfo=g.tz) - - gilding = json.loads(json_blob) - gilding["date"] = date - gilding["user"] = int(gilding["user"], 36) - gildings.append(gilding) - return gildings - - @classmethod - def create(cls, user, things): - key = cls._rowkey(datetime.now(g.tz)) - - columns = {} - for thing in things: - columns[uuid.uuid1()] = json.dumps({ - "user": user._id36, - "thing": thing._fullname, - }) - cls._set_values(key, columns) - - @classmethod - def delete(cls, user, things): - # gildings cannot be undone - raise NotImplementedError() - - class _SaveHideByAccount(tdb_cassandra.DenormalizedRelation): @classmethod def value_for(cls, thing1, thing2): diff --git a/r2/r2/models/subreddit.py b/r2/r2/models/subreddit.py index d6afbceda..67c4fc25b 100644 --- a/r2/r2/models/subreddit.py +++ b/r2/r2/models/subreddit.py @@ -141,9 +141,9 @@ class BaseSite(object): from r2.lib.db import queries return queries.get_sr_comments(self) - def get_gilded_comments(self): + def get_gilded(self): from r2.lib.db import queries - return queries.get_gilded_comments(self) + return queries.get_gilded(self._id) @classmethod def get_modactions(cls, srs, mod=None, action=None): @@ -205,7 +205,7 @@ class Subreddit(Thing, Printable, BaseSite): prev_description_id="", prev_submit_text_id="", prev_public_description_id="", - allow_comment_gilding=True, + allow_gilding=True, hide_subscribers=False, public_traffic=False, spam_links='high', @@ -929,7 +929,7 @@ class FakeSubreddit(BaseSite): from r2.lib.db import queries return queries.get_all_comments() - def get_gilded_comments(self): + def get_gilded(self): raise NotImplementedError() def spammy(self): @@ -1006,19 +1006,16 @@ class FriendsSR(FakeSubreddit): for friend in friends] return queries.MergedCachedResults(crs) - def get_gilded_comments(self): - from r2.lib.db.queries import get_gilded_user_comments - + def get_gilded(self): + from r2.lib.db.queries import get_gilded_users if not c.user_is_loggedin: raise UserRequiredException friends = self.get_important_friends(c.user._id) - if not friends: return [] - queries = [get_gilded_user_comments(user_id) for user_id in friends] - return MergedCachedQuery(queries) + return get_gilded_users(friends) class AllSR(FakeSubreddit): @@ -1047,9 +1044,9 @@ class AllSR(FakeSubreddit): from r2.lib.db import queries return queries.get_all_comments() - def get_gilded_comments(self): + def get_gilded(self): from r2.lib.db import queries - return queries.get_all_gilded_comments() + return queries.get_all_gilded() class AllMinus(AllSR): @@ -1196,11 +1193,9 @@ class DefaultSR(_DefaultSR): results = [get_sr_comments(sr) for sr in srs] return merge_results(*results) - def get_gilded_comments(self): - from r2.lib.db.queries import get_gilded_comments - srs = Subreddit.user_subreddits(c.user) - queries = [get_gilded_comments(sr_id) for sr_id in srs] - return MergedCachedQuery(queries) + def get_gilded(self): + from r2.lib.db.queries import get_gilded + return get_gilded(Subreddit.user_subreddits(c.user)) class MultiReddit(FakeSubreddit): @@ -1267,10 +1262,9 @@ class MultiReddit(FakeSubreddit): results = [get_sr_comments(sr) for sr in srs] return merge_results(*results) - def get_gilded_comments(self): - from r2.lib.db.queries import get_gilded_comments - queries = [get_gilded_comments(sr_id) for sr_id in self.kept_sr_ids] - return MergedCachedQuery(queries) + def get_gilded(self): + from r2.lib.db.queries import get_gilded + return get_gilded(self.kept_sr_ids) class TooManySubredditsError(Exception): diff --git a/r2/r2/public/static/css/compact.css b/r2/r2/public/static/css/compact.css index 6d287da9f..445f72881 100644 --- a/r2/r2/public/static/css/compact.css +++ b/r2/r2/public/static/css/compact.css @@ -303,14 +303,14 @@ body[orient="landscape"] > #topbar > h1 { margin-left: -125px; width: 250px; } .comment.collapsed .tagline { margin-left: 20px; font-style: italcs; color: #AAA; } -/** comment gilding */ -.gilded-comment-icon { position: relative; display: inline-block; margin: 0 0 -15px 8px; top: -8px; color: #99895F; font-size: .9em; vertical-align: middle; } +/** gilding */ +.gilded-icon { position: relative; display: inline-block; margin: 0 0 -15px 8px; top: -8px; color: #99895F; font-size: .9em; vertical-align: middle; } -.gilded-comment-icon:before { display: inline-block; content: ''; background-image: url(../gold-coin.png); /* SPRITE */ background-repeat: no-repeat; height: 14px; width: 13px; margin-right: 2px; vertical-align: -3px; } +.gilded-icon:before { display: inline-block; content: ''; background-image: url(../gold-coin.png); /* SPRITE */ background-repeat: no-repeat; height: 14px; width: 13px; margin-right: 2px; vertical-align: -3px; } -.user-gilded > .entry .gilded-comment-icon:before { width: 23px; } +.user-gilded > .entry .gilded-icon:before { width: 23px; } -body.post-under-6h-old .gilded-comment-icon { opacity: .55; } +body.post-under-6h-old .gilded-icon { opacity: .55; } /** messages and inbox */ .message { background: white; position: relative; border: 1px solid #d9d9d9; margin: 10px; -webkit-border-radius: 8px; -moz-border-radius: 8px; -ms-border-radius: 8px; -o-border-radius: 8px; border-radius: 8px; padding: 5px; } diff --git a/r2/r2/public/static/css/compact.scss b/r2/r2/public/static/css/compact.scss index d21f49321..6ace0c9fc 100644 --- a/r2/r2/public/static/css/compact.scss +++ b/r2/r2/public/static/css/compact.scss @@ -887,8 +887,8 @@ padding: 5px; } -/** comment gilding */ -.gilded-comment-icon { +/** gilding */ +.gilded-icon { position: relative; display: inline-block; margin: 0 0 -15px 8px; @@ -898,7 +898,7 @@ padding: 5px; vertical-align: middle; } -.gilded-comment-icon:before { +.gilded-icon:before { display: inline-block; content: ''; background-image: url(../gold-coin.png); /* SPRITE */ @@ -909,11 +909,11 @@ padding: 5px; vertical-align: -3px; } -.user-gilded > .entry .gilded-comment-icon:before { +.user-gilded > .entry .gilded-icon:before { width: 23px; } -body.post-under-6h-old .gilded-comment-icon { +body.post-under-6h-old .gilded-icon { opacity: .55; } diff --git a/r2/r2/public/static/css/reddit-ie7-hax.css b/r2/r2/public/static/css/reddit-ie7-hax.css index b4eec0ace..21a0d6c5c 100644 --- a/r2/r2/public/static/css/reddit-ie7-hax.css +++ b/r2/r2/public/static/css/reddit-ie7-hax.css @@ -42,12 +42,12 @@ clear: both; } -.gilded-comment-icon { +.gilded-icon { background: url(gold-coin.png) no-repeat; padding-left: 15px; height: 14px; } -.user-gilded > .entry .gilded-comment-icon { +.user-gilded > .entry .gilded-icon { padding-left: 25px; } diff --git a/r2/r2/public/static/css/reddit.less b/r2/r2/public/static/css/reddit.less index e3d3c5f28..dd2d09f68 100755 --- a/r2/r2/public/static/css/reddit.less +++ b/r2/r2/public/static/css/reddit.less @@ -6018,7 +6018,7 @@ dd { margin-left: 20px; } .icon-menu .reddit-contributors:before, .icon-menu .reddit-modqueue:before, .giftgold a:before, -.gilded-comments-link a:before, +.gilded-link a:before, .infobar.gold:before, .gold-form h1.goldgift:before, .users-online:before, @@ -6350,17 +6350,17 @@ body:not(.gold) .allminus-link { font-size: 1.15em; } -.gilded-comments-link { +.gilded-link { margin-top: 1em; } -.gilded-comments-link a { +.gilded-link a { color: #9a7d2e; font-weight: bold; font-size: 1.15em; } -.gilded-comments-link a:before { +.gilded-link a:before { height: 14px; width: 14px; margin: 0 6px 0 1px; @@ -6544,7 +6544,7 @@ body:not(.gold) .allminus-link { position: absolute; } -.gold-form.cloneable { +.gold-form.cloneable-link, .gold-form.cloneable-comment { display: none; } @@ -6572,8 +6572,8 @@ body:not(.gold) .allminus-link { margin: 5px 0; } -.comment .gold-form { - margin: 0 0 10px 4px; +.thing .gold-form { + margin: 10px 0 10px 4px; min-height: 0; } @@ -7588,7 +7588,7 @@ table.diff {font-size: small;} .diff_chg {background-color:yellow} .diff_sub {background-color:lightcoral} -.gilded-comment-icon { +.gilded-icon { position: relative; display: inline-block; margin: 0 0 -15px 8px; @@ -7598,7 +7598,7 @@ table.diff {font-size: small;} vertical-align: middle; } -.gilded-comment-icon:before { +.gilded-icon:before { display: inline-block; content: ''; background-image: url(../gold-coin.png); /* SPRITE */ @@ -7609,11 +7609,11 @@ table.diff {font-size: small;} vertical-align: -3px; } -.user-gilded > .entry .gilded-comment-icon:before { +.user-gilded > .entry .gilded-icon:before { width: 23px; } -body.post-under-6h-old .gilded-comment-icon { +body.post-under-6h-old .gilded-icon { opacity: .55; } diff --git a/r2/r2/public/static/js/gold.js b/r2/r2/public/static/js/gold.js index c96d60d6b..d9cf744a0 100644 --- a/r2/r2/public/static/js/gold.js +++ b/r2/r2/public/static/js/gold.js @@ -5,7 +5,7 @@ r.gold = { $('div.content').on( 'click', 'a.give-gold, .gold-payment .close-button', - $.proxy(this, '_toggleCommentGoldForm') + $.proxy(this, '_toggleThingGoldForm') ) $('.stripe-gold').click(function(){ @@ -21,11 +21,11 @@ r.gold = { }) }, - _toggleCommentGoldForm: function (e) { + _toggleThingGoldForm: function (e) { var $link = $(e.target), $thing = $link.thing(), - commentId = $link.thing_id(), - formId = 'gold_form_' + commentId, + thingFullname = $link.thing_id(), + formId = 'gold_form_' + thingFullname, oldForm = $('#' + formId) if ($thing.hasClass('user-gilded') || @@ -47,12 +47,18 @@ r.gold = { this._googleCheckoutAnalyticsLoaded = true } - var form = $('.gold-form.cloneable:first').clone(), + if ($thing.hasClass('link')) { + var cloneClass = 'cloneable-link' + } else { + var cloneClass = 'cloneable-comment' + } + + var form = $('.gold-form.' + cloneClass + ':first').clone(), authorName = $link.thing().find('.entry .author:first').text(), passthroughs = form.find('.passthrough'), cbBaseUrl = form.find('[name="cbbaseurl"]').val() - form.removeClass('cloneable') + form.removeClass(cloneClass) .attr('id', formId) .find('p:first-child em').text(authorName).end() .find('button').attr('disabled', '') @@ -66,7 +72,7 @@ r.gold = { form.find('button').addClass('disabled') }, 200) - $.request('generate_payment_blob.json', {comment: commentId}, function (token) { + $.request('generate_payment_blob.json', {thing: thingFullname}, function (token) { clearTimeout(workingTimer) form.removeClass('working') passthroughs.val(token) @@ -78,18 +84,18 @@ r.gold = { return false }, - gildComment: function (comment_id, new_title, specified_gilding_count) { - var comment = $('.id-' + comment_id) + gildThing: function (thing_fullname, new_title, specified_gilding_count) { + var thing = $('.id-' + thing_fullname) - if (!comment.length) { - console.log("couldn't gild comment " + comment_id) + if (!thing.length) { + console.log("couldn't gild thing " + thing_fullname) return } - var tagline = comment.children('.entry').find('p.tagline'), - icon = tagline.find('.gilded-comment-icon') + var tagline = thing.children('.entry').find('p.tagline'), + icon = tagline.find('.gilded-icon') - // when a comment is gilded interactively, we need to increment the + // when a thing is gilded interactively, we need to increment the // gilding count displayed by the UI. however, when gildings are // instantiated from a cached comment page via thingupdater, we can't // simply increment the gilding count because we do not know if the @@ -104,10 +110,10 @@ r.gold = { gilding_count++ } - comment.addClass('gilded user-gilded') + thing.addClass('gilded user-gilded') if (!icon.length) { icon = $('') - .addClass('gilded-comment-icon') + .addClass('gilded-icon') tagline.append(icon) } icon @@ -117,7 +123,7 @@ r.gold = { icon.text('x' + gilding_count) } - comment.children('.entry').find('.give-gold').parent().remove() + thing.children('.entry').find('.give-gold').parent().remove() }, tokenThenPost: function (dest) { @@ -199,8 +205,8 @@ r.gold = { }; (function($) { - $.gild_comment = function (comment_id, new_title) { - r.gold.gildComment(comment_id, new_title) - $('#gold_form_' + comment_id).fadeOut(400) + $.gild_thing = function (thing_fullname, new_title) { + r.gold.gildThing(thing_fullname, new_title) + $('#gold_form_' + thing_fullname).fadeOut(400) } })(jQuery) diff --git a/r2/r2/templates/allinfobar.html b/r2/r2/templates/allinfobar.html index eb2f1c0ed..12b72e8d2 100644 --- a/r2/r2/templates/allinfobar.html +++ b/r2/r2/templates/allinfobar.html @@ -39,7 +39,7 @@ %endif %if not thing.gilding_listing: - diff --git a/r2/r2/templates/goldpayment.html b/r2/r2/templates/goldpayment.html index ec4e29f6b..b7a70b1d5 100644 --- a/r2/r2/templates/goldpayment.html +++ b/r2/r2/templates/goldpayment.html @@ -25,9 +25,16 @@ <% from r2.lib.filters import unsafe, safemarkdown from r2.lib.template_helpers import static + + clone_class = '' + if thing.clone_template: + if thing.thing_type == 'comment': + clone_class = 'cloneable-comment' + else: + clone_class = 'cloneable-link' %> -
+
% if thing.clone_template: @@ -35,9 +42,9 @@
${unsafe(safemarkdown(thing.summary, wrap=False))} - %if thing.comment and not thing.clone_template: + %if thing.thing and not thing.clone_template:
- ${unsafe(safemarkdown(thing.comment.body))} + ${unsafe(safemarkdown(thing.description))}
%endif diff --git a/r2/r2/templates/link.html b/r2/r2/templates/link.html index 8d7933ea3..ba2b64259 100755 --- a/r2/r2/templates/link.html +++ b/r2/r2/templates/link.html @@ -216,6 +216,9 @@ ${parent.thing_data_attributes(what)} data-ups="${what.upvotes}" data-downs="${w author=WrappedUser(thing.author, thing.attribs, thing).render(), lastedited=capture(edited, thing, thing.lastedited) ))} + %if c.permalink_page or c.profilepage: + ${self.gildings()} + %endif %if thing.stickied: - stickied post %endif diff --git a/r2/r2/templates/printable.html b/r2/r2/templates/printable.html index 6087f971e..6511586d0 100644 --- a/r2/r2/templates/printable.html +++ b/r2/r2/templates/printable.html @@ -54,6 +54,18 @@ ${self.RenderPrintable()} %endif +<%def name="gildings()"> + % if thing.gilded_message: +
+ + % if thing.gildings > 1: + x${thing.gildings} + % endif + + + % endif + + <%def name="thing_css_class(what)"> <% cssclass = "thing" diff --git a/r2/r2/templates/printablebuttons.html b/r2/r2/templates/printablebuttons.html index 7af8f009d..34f90ba10 100644 --- a/r2/r2/templates/printablebuttons.html +++ b/r2/r2/templates/printablebuttons.html @@ -122,6 +122,17 @@ %endif +<%def name="give_gold()"> + % if thing.show_givegold: +
  • + +
  • + % endif + + <%def name="ignore_reports_toggle(thing)"> <% label = _("ignore reports") @@ -202,6 +213,7 @@ %endif ${self.distinguish()} + ${self.give_gold()} ${self.banbuttons()} %if thing.promoted is not None: %if thing.user_is_sponsor or thing.is_author: @@ -295,14 +307,7 @@ %endif ${self.banbuttons()} ${self.distinguish()} - % if thing.can_gild: -
  • - -
  • - % endif + ${self.give_gold()} %if not thing.profilepage and thing.can_reply:
  • ${self.simple_button(_("reply {verb}"), "reply")} diff --git a/r2/r2/templates/thingupdater.html b/r2/r2/templates/thingupdater.html index 987cfc466..7979350d6 100644 --- a/r2/r2/templates/thingupdater.html +++ b/r2/r2/templates/thingupdater.html @@ -37,9 +37,9 @@ $.map(friends, show_friend); var gildings = ${unsafe(simplejson.dumps(thing.gildings))}; - for (var gilded_comment in gildings) { - var gilding_data = gildings[gilded_comment]; - r.gold.gildComment(gilded_comment, gilding_data[0], gilding_data[1]); + for (var gilded_thing in gildings) { + var gilding_data = gildings[gilded_thing]; + r.gold.gildThing(gilded_thing, gilding_data[0], gilding_data[1]); } var saves = ${unsafe(simplejson.dumps(list(thing.saves)))};