mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-26 07:19:25 -05:00
Add subreddit interest discovery bar.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -275,6 +275,7 @@ module["reddit"] = LocalizedModule("reddit.js",
|
||||
"login.js",
|
||||
"analytics.js",
|
||||
"flair.js",
|
||||
"interestbar.js",
|
||||
"reddit.js",
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -13,4 +13,5 @@ $(function() {
|
||||
r.login.ui.init()
|
||||
r.analytics.init()
|
||||
r.ui.HelpBubble.init()
|
||||
r.interestbar.init()
|
||||
})
|
||||
|
||||
81
r2/r2/public/static/js/interestbar.js
Normal file
81
r2/r2/public/static/js/interestbar.js
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
BIN
r2/r2/public/static/snoo-upside-down.png
Normal file
BIN
r2/r2/public/static/snoo-upside-down.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
45
r2/r2/templates/interestbar.html
Normal file
45
r2/r2/templates/interestbar.html
Normal 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} <span class="subscribe">${sub}</span> ${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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user