From 613b27de9eec1f6e982bbcd5e1cdfbb252600531 Mon Sep 17 00:00:00 2001 From: Max Goodman Date: Thu, 16 Aug 2012 01:36:41 -0700 Subject: [PATCH] Add subreddit interest discovery bar. --- r2/example.ini | 2 + r2/r2/controllers/api.py | 35 +++++++ r2/r2/controllers/listingcontroller.py | 11 +++ r2/r2/lib/app_globals.py | 4 + r2/r2/lib/js.py | 1 + r2/r2/lib/pages/pages.py | 5 + r2/r2/lib/strings.py | 1 + r2/r2/public/static/css/reddit.css | 115 +++++++++++++++++++++++ r2/r2/public/static/js/analytics.js | 2 +- r2/r2/public/static/js/base.js | 1 + r2/r2/public/static/js/interestbar.js | 81 ++++++++++++++++ r2/r2/public/static/js/reddit.js | 2 + r2/r2/public/static/snoo-upside-down.png | Bin 0 -> 3448 bytes r2/r2/templates/interestbar.html | 45 +++++++++ r2/r2/templates/spotlightlisting.html | 18 +++- 15 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 r2/r2/public/static/js/interestbar.js create mode 100644 r2/r2/public/static/snoo-upside-down.png create mode 100644 r2/r2/templates/interestbar.html diff --git a/r2/example.ini b/r2/example.ini index ad941d275..d798bf950 100644 --- a/r2/example.ini +++ b/r2/example.ini @@ -498,3 +498,5 @@ beaker.session_secret = somesecret frontpage_dart = false # spotlight links for subreddit discovery sr_discovery_links = +spotlight_interest_sub_p = .05 +spotlight_interest_nosub_p = .1 diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py index 4da80f06e..0a88a5c7c 100755 --- a/r2/r2/controllers/api.py +++ b/r2/r2/controllers/api.py @@ -59,6 +59,7 @@ from r2.lib.log import log_text from r2.lib.filters import safemarkdown from r2.lib.scraper import str_to_image from r2.controllers.api_docs import api_doc, api_section +from r2.lib.cloudsearch import basic_query import csv from collections import defaultdict @@ -2783,3 +2784,37 @@ class ApiController(RedditController): c.user.otp_secret = "" c.user._commit() form.redirect("/prefs/otp") + + @json_validate(query=VPrintable("query", max_length=50)) + @api_doc(api_section.subreddits, extensions=["json"]) + def GET_subreddits_by_topic(self, responder, query): + if not g.CLOUDSEARCH_SEARCH_API: + return [] + + if not query or not query.strip(): + return [] + + exclude = Subreddit.default_subreddits() + + q = basic_query(query, + facets={"reddit":{"sort":"-sum(text_relevance)", "count":20}}, + record_stats=True) + if not q["facets"]: + return [] + + sr_facets = [f["value"] for f in q["facets"]["reddit"]["constraints"]] + srs = Subreddit._by_name(sr_facets) + + results = [] + for sr_name in sr_facets: + sr = srs.get(sr_name) + if (sr._id in exclude or (sr.over_18 and not c.over18) + or not sr.can_view(c.user) + or sr.type == "archived"): + continue + + results.append({ + "name": sr_name, + }) + + return results diff --git a/r2/r2/controllers/listingcontroller.py b/r2/r2/controllers/listingcontroller.py index 8c7015765..ad241c335 100755 --- a/r2/r2/controllers/listingcontroller.py +++ b/r2/r2/controllers/listingcontroller.py @@ -313,6 +313,17 @@ class HotController(FixListing, ListingController): max_num = self.listing_obj.max_num, max_score = self.listing_obj.max_score).listing() + has_subscribed = c.user.has_subscribed + promo_visible = promote.is_promo(s.lookup[vislink]) + if not promo_visible: + prob = g.live_config['spotlight_interest_sub_p' + if has_subscribed else + 'spotlight_interest_nosub_p'] + if random.random() < prob: + bar = InterestBar(has_subscribed) + s.spotlight_items.insert(pos, bar) + s.visible_item = bar + if len(s.things) > 0: # only pass through a listing if the links made it # through our builder diff --git a/r2/r2/lib/app_globals.py b/r2/r2/lib/app_globals.py index 2711d494b..069da9fc8 100755 --- a/r2/r2/lib/app_globals.py +++ b/r2/r2/lib/app_globals.py @@ -180,6 +180,10 @@ class Globals(object): ConfigValue.bool: [ 'frontpage_dart', ], + ConfigValue.float: [ + 'spotlight_interest_sub_p', + 'spotlight_interest_nosub_p', + ], ConfigValue.tuple: [ 'sr_discovery_links', ], diff --git a/r2/r2/lib/js.py b/r2/r2/lib/js.py index 781745d2f..e60528c23 100755 --- a/r2/r2/lib/js.py +++ b/r2/r2/lib/js.py @@ -275,6 +275,7 @@ module["reddit"] = LocalizedModule("reddit.js", "login.js", "analytics.js", "flair.js", + "interestbar.js", "reddit.js", ) diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 824c9ed63..883cf135f 100755 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -3619,3 +3619,8 @@ class TimeSeriesChart(Templated): self.classes = " ".join(classes) Templated.__init__(self) + +class InterestBar(Templated): + def __init__(self, has_subscribed): + self.has_subscribed = has_subscribed + Templated.__init__(self) diff --git a/r2/r2/lib/strings.py b/r2/r2/lib/strings.py index 2ac91d31e..40db1d15b 100644 --- a/r2/r2/lib/strings.py +++ b/r2/r2/lib/strings.py @@ -189,6 +189,7 @@ Note: there are a couple of places outside of your subreddit where someone can c view_subreddit_traffic = _("view subreddit traffic"), an_error_occurred = _("an error occurred"), + an_error_occurred_friendly = _("an error occurred. please try again later!"), ) class StringHandler(object): diff --git a/r2/r2/public/static/css/reddit.css b/r2/r2/public/static/css/reddit.css index caf4e2cab..093efb5b7 100755 --- a/r2/r2/public/static/css/reddit.css +++ b/r2/r2/public/static/css/reddit.css @@ -5709,3 +5709,118 @@ tr.gold-accent + tr > td { .users-online .word, .users-online .number:after { cursor: help; } + +.sr-interest-bar { + position: relative; + background: #cee3f8 url(../snoo-upside-down.png) 15px top no-repeat; + padding: 5px; + overflow: hidden; + border: 1px solid #336699; + margin-bottom: 10px; +} + +.organic-listing .sr-interest-bar { + border: none; + margin: 0; +} + +.sr-interest-bar .bubble { + position: relative; + margin-left: 85px; + margin-right: 68px; + max-width: 700px; + font-size: 13px; + background: white; + padding: 6px; + border-radius: 8px; +} + +.sr-interest-bar .bubble:after { + position: absolute; + display: block; + content: ''; + border: 10px solid; + border-style: solid solid outset; + border-color: transparent; + border-right-color: white; + left: -20px; + top: 15px; +} + +.sr-interest-bar .bubble p { + margin: 6px 3px; + margin-top: 0; +} + +.sr-interest-bar .subscribe { + background-image: url(../bg-button-add.png); /* SPRITE stretch-x */ + border: 1px solid #444; + border-radius: 3px; + padding: 0 6px; + color: white; + font-weight: bold; +} + +.sr-interest-bar .query-box { + position: relative; + padding: 2px 4px; + border: 2px solid #979797; + border-radius: 5px; +} + +.sr-interest-bar.focus .query-box { + border-color: #5f99cf; +} + +.sr-interest-bar.error .query-box { + border-color: #cf5e5e; +} + +.sr-interest-bar .error-caption, .sr-interest-bar.error .caption { + display: none; +} + +.sr-interest-bar.error .error-caption { + display: block; +} + +.sr-interest-bar .query { + width: 100%; + font-size: 20px; + margin: 0; + padding: 0; + border: none; + outline: none; +} + +.sr-interest-bar .throbber { + position: absolute; + right: 3px; + top: 5px; +} + +.sr-interest-bar ul.results { + margin: 0; + margin-top: 6px; + padding-top: 2px; + border-top: 1px dotted #bbb; + display: none; +} + +.sr-interest-bar li { + display: inline-block; + margin: 6px 3px; +} + +.sr-interest-bar a { + padding: 1px 2px; +} + +.sr-interest-bar a:hover { + text-decoration: underline; +} + +.sr-interest-bar .results .random { + color: gray; + font-weight: bold; +} diff --git a/r2/r2/public/static/js/analytics.js b/r2/r2/public/static/js/analytics.js index be6144a31..ec7adf851 100644 --- a/r2/r2/public/static/js/analytics.js +++ b/r2/r2/public/static/js/analytics.js @@ -113,7 +113,7 @@ r.analytics = { } r.analytics.breadcrumbs = { - selector: '.thing, .side, .sr-list, .srdrop, .tagline, .md, .organic-listing, .gadget, a, button, input', + selector: '.thing, .side, .sr-list, .srdrop, .tagline, .md, .organic-listing, .gadget, .sr-interest-bar, a, button, input', init: function() { this.hasSessionStorage = this._checkSessionStorage() diff --git a/r2/r2/public/static/js/base.js b/r2/r2/public/static/js/base.js index 00637a2c5..3f4c4682b 100644 --- a/r2/r2/public/static/js/base.js +++ b/r2/r2/public/static/js/base.js @@ -13,4 +13,5 @@ $(function() { r.login.ui.init() r.analytics.init() r.ui.HelpBubble.init() + r.interestbar.init() }) diff --git a/r2/r2/public/static/js/interestbar.js b/r2/r2/public/static/js/interestbar.js new file mode 100644 index 000000000..aea12e42f --- /dev/null +++ b/r2/r2/public/static/js/interestbar.js @@ -0,0 +1,81 @@ +r.interestbar = { + init: function() { + new r.ui.InterestBar($('.sr-interest-bar')) + } +} + +r.ui.InterestBar = function() { + r.ui.Base.apply(this, arguments) + this.$query = this.$el.find('.query') + this.queryChangedDebounced = _.debounce($.proxy(this, 'queryChanged'), 500) + this.$query.on('keyup', $.proxy(this, 'keyPressed')) + + this.$query + .on('focus', $.proxy(function() { + this.$el.addClass('focus') + }, this)) + .on('blur', $.proxy(function() { + this.$el.removeClass('focus') + }, this)) +} +r.ui.InterestBar.prototype = { + keyPressed: function() { + var query = this.$query.val() + query = $.trim(query) + if (query != this._lastQuery) { + this._lastQuery = query + this.$el.addClass('working') + this.queryChangedDebounced(query) + } + + if (!query) { + this.$el.removeClass('working error') + this.hideResults() + } + }, + + queryChanged: function(query) { + if (query) { + $.ajax({ + url: '/api/subreddits_by_topic.json', + data: {'query': query}, + success: $.proxy(this, 'displayResults'), + error: $.proxy(this, 'displayError') + }) + } + }, + + displayResults: function(results) { + this.$el.removeClass('working error') + + var first = this.$el.find('.results li:first'), + last = this.$el.find('.results li:last') + + var item = _.template( + '
  • ' + +'/r/<%= name %>' + +'
  • ' + ) + + this.$el.find('.results') + .empty() + .append(first) + .append(_.map(results, item).join('')) + .append(last) + .slideDown(150) + }, + + hideResults: function() { + this.$el.find('.results').slideUp(150) + }, + + displayError: function(xhr) { + this.$el + .removeClass('working') + .addClass('error') + .find('.error-caption') + .text(r.strings.an_error_occurred_friendly + ' (' + xhr.status + ')') + + this.hideResults() + } +} diff --git a/r2/r2/public/static/js/reddit.js b/r2/r2/public/static/js/reddit.js index 63191dbaf..16e590d8a 100644 --- a/r2/r2/public/static/js/reddit.js +++ b/r2/r2/public/static/js/reddit.js @@ -334,6 +334,8 @@ function organic_help(listing, thing) { help.find('.help-section').hide() if (thing.hasClass('promoted')) { help.find('.help-promoted').show() + } else if (thing.hasClass('interestbar')) { + help.find('.help-interestbar').show() } else { help.find('.help-organic').show() } diff --git a/r2/r2/public/static/snoo-upside-down.png b/r2/r2/public/static/snoo-upside-down.png new file mode 100644 index 0000000000000000000000000000000000000000..e86f17f53bfcec02a50802922d4dd60388c09452 GIT binary patch literal 3448 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 a%KaasE!42`8BF^C0000 +
    + <% + if thing.has_subscribed: + msg = _("ready for something new? %s subscribe %s to some new subreddits.") + else: + msg = _("it looks like you haven't %s subscribed %s to any subreddits yet. want some ideas?") + + pre, sub, post = msg.split("%s") + %> +

    ${pre} ${post}

    +

    +
    + +
    + diff --git a/r2/r2/templates/spotlightlisting.html b/r2/r2/templates/spotlightlisting.html index d9d8a4842..47dd87b9d 100644 --- a/r2/r2/templates/spotlightlisting.html +++ b/r2/r2/templates/spotlightlisting.html @@ -21,9 +21,10 @@ ############################################################################### <%namespace file="printablebuttons.html" import="ynbutton"/> -<%namespace file="utils.html" import="text_with_links"/> +<%namespace file="utils.html" import="tags, text_with_links"/> <% from r2.lib.template_helpers import static + from r2.lib.wrapped import Templated %>
    @@ -31,7 +32,11 @@ seen = set([]) %> %for name in thing.spotlight_items: - %if name in seen: + %if isinstance(name, Templated): +
    + ${unsafe(name.render())} +
    + %elif name in seen: <% pass %> %elif name in thing.lookup: <% seen.add(name) %> @@ -77,6 +82,15 @@ hidden_data = dict(id="organic"))} %endif
    +
    +

    ${_("Enter a keyword or topic to discover new subreddits around your interests. Be specific!")}

    +

    + ${text_with_links( + _("You can access this tool at any time on the %(reddits)s page."), + reddits=dict(link_text="/reddits/", path="/reddits/") + )} +

    +