Add subreddit interest discovery bar.

This commit is contained in:
Max Goodman
2012-08-16 01:36:41 -07:00
parent df628d4368
commit 613b27de9e
15 changed files with 320 additions and 3 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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',
],

View File

@@ -275,6 +275,7 @@ module["reddit"] = LocalizedModule("reddit.js",
"login.js",
"analytics.js",
"flair.js",
"interestbar.js",
"reddit.js",
)

View File

@@ -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)

View File

@@ -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):

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -13,4 +13,5 @@ $(function() {
r.login.ui.init()
r.analytics.init()
r.ui.HelpBubble.init()
r.interestbar.init()
})

View File

@@ -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(
'<li><a href="/r/<%= name %>" target="_blank">'
+'/r/<%= name %>'
+'</a></li>'
)
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()
}
}

View File

@@ -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()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,45 @@
## The contents of this file are subject to the Common Public Attribution
## License Version 1.0. (the "License"); you may not use this file except in
## compliance with the License. You may obtain a copy of the License at
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
## software over a computer network and provide for limited attribution for the
## Original Developer. In addition, Exhibit A has been modified to be
## consistent with Exhibit B.
##
## Software distributed under the License is distributed on an "AS IS" basis,
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
## the specific language governing rights and limitations under the License.
##
## The Original Code is reddit.
##
## The Original Developer is the Initial Developer. The Initial Developer of
## the Original Code is reddit Inc.
##
## All portions of the code written by reddit are Copyright (c) 2006-2012
## reddit Inc. All Rights Reserved.
###############################################################################
<div class="sr-interest-bar">
<div class="bubble">
<%
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")
%>
<p class="caption">${pre}&#32;<span class="subscribe">${sub}</span>&#32;${post}</p>
<p class="error-caption"></p>
<div class="query-box"><input class="query" placeholder="${_('what are you interested in?')}"></input><div class="throbber"></div></div>
<ul class="results">
<li>${_('try these:')}</li>
<li>
<a href="/r/random" class="random" target="_blank">
<span class="name">${_("serendipity")}</span>
</a>
</li>
</ul>
</div>
</div>

View File

@@ -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
%>
<div id="siteTable_organic" class="organic-listing">
@@ -31,7 +32,11 @@
seen = set([])
%>
%for name in thing.spotlight_items:
%if name in seen:
%if isinstance(name, Templated):
<div class="thing ${type(name).__name__.lower()}" ${tags(style="display:none" if thing.visible_item != name else None)}>
${unsafe(name.render())}
</div>
%elif name in seen:
<% pass %>
%elif name in thing.lookup:
<% seen.add(name) %>
@@ -77,6 +82,15 @@
hidden_data = dict(id="organic"))}
%endif
</div>
<div class="help-section help-interestbar">
<p>${_("Enter a keyword or topic to discover new subreddits around your interests. Be specific!")}</p>
<p>
${text_with_links(
_("You can access this tool at any time on the %(reddits)s page."),
reddits=dict(link_text="/reddits/", path="/reddits/")
)}
</p>
</div>
</div>
</div>
</div>