mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-30 01:08:32 -05:00
Client side shuffling spotlight content.
This commit is contained in:
@@ -3095,6 +3095,26 @@ class ApiController(RedditController, OAuth2ResourceController):
|
||||
Trophy.by_account(recipient, _update=True)
|
||||
Trophy.by_award(award, _update=True)
|
||||
|
||||
|
||||
@validatedForm(link=nop('link'),
|
||||
campaign=nop('campaign'),
|
||||
show=VBoolean('show'))
|
||||
def GET_fetch_promo(self, form, jquery, link, campaign, show):
|
||||
promo_tuples = [promote.PromoTuple(link, 1., campaign)]
|
||||
builder = CampaignBuilder(promo_tuples,
|
||||
wrap=default_thing_wrapper(),
|
||||
keep_fn=promote.is_promoted)
|
||||
promoted_links = builder.get_items()[0]
|
||||
listing = SpotlightListing(organic_links=[],
|
||||
promoted_links=promoted_links,
|
||||
interestbar=None).listing()
|
||||
jquery(".content").replace_things(listing)
|
||||
|
||||
if show:
|
||||
jquery('.organic-listing .thing:visible').hide()
|
||||
jquery('.organic-listing .id-%s' % link).show()
|
||||
|
||||
|
||||
@validatedForm(links = VByName('links', thing_cls = Link, multiple = True),
|
||||
show = VByName('show', thing_cls = Link, multiple = False))
|
||||
def POST_fetch_links(self, form, jquery, links, show):
|
||||
|
||||
@@ -225,6 +225,29 @@ class HotController(FixListing, ListingController):
|
||||
extra_page_classes = ListingController.extra_page_classes + ['hot-page']
|
||||
|
||||
def spotlight(self):
|
||||
"""Build the Spotlight or a single promoted link.
|
||||
|
||||
The frontpage gets a Spotlight box that contains promoted and organic
|
||||
links from the user's subscribed subreddits and promoted links targeted
|
||||
to the frontpage. Other subreddits get a single promoted link. In either
|
||||
case if the user has disabled ads promoted links will not be shown.
|
||||
|
||||
The content of the Spotlight box is a bit tricky because a single
|
||||
version of the frontpage is cached and displayed to all logged out
|
||||
users. Because of the caching we must include many promoted links and
|
||||
select one to display on the client side. Otherwise, each logged out
|
||||
user would see the same promoted link and we would not get the desired
|
||||
distribution of promoted link views. Most of the promoted links are
|
||||
included as stubs to reduce the size of the page. When a promoted link
|
||||
stub is selected by the lottery the full link is fetched and displayed.
|
||||
|
||||
There are only ~1000 cache resets per day so it is necessary to use
|
||||
a large subset of the eligible promoted links when choosing stubs for
|
||||
the Spotlight box. Using 100 stubs works great when there are fewer than
|
||||
100 possible promoted links and allows room for growth.
|
||||
|
||||
"""
|
||||
|
||||
campaigns_by_link = {}
|
||||
if (self.requested_ad or
|
||||
not isinstance(c.site, DefaultSR) and c.user.pref_show_sponsors):
|
||||
@@ -273,47 +296,53 @@ class HotController(FixListing, ListingController):
|
||||
and (not c.user_is_loggedin
|
||||
or (c.user_is_loggedin and c.user.pref_organic))):
|
||||
|
||||
spotlight_links = organic.organic_links(c.user)
|
||||
random.shuffle(spotlight_links)
|
||||
organic_fullnames = organic.organic_links(c.user)
|
||||
promoted_links = []
|
||||
|
||||
# If prefs allow it, mix in promoted links and sr discovery content
|
||||
if c.user.pref_show_sponsors or not c.user.gold:
|
||||
if g.live_config['sr_discovery_links']:
|
||||
spotlight_links.extend(g.live_config['sr_discovery_links'])
|
||||
random.shuffle(spotlight_links)
|
||||
spotlight_links, campaigns_by_link = promote.insert_promoted(spotlight_links)
|
||||
organic_fullnames.extend(g.live_config['sr_discovery_links'])
|
||||
|
||||
if not spotlight_links:
|
||||
n_promoted = 100
|
||||
n_build = 10
|
||||
promo_tuples = promote.get_promoted_links(c.user, c.site,
|
||||
n_promoted)
|
||||
promo_tuples = sorted(promo_tuples,
|
||||
key=lambda p: p.weight,
|
||||
reverse=True)
|
||||
promo_build = promo_tuples[:n_build]
|
||||
promo_stub = promo_tuples[n_build:]
|
||||
b = CampaignBuilder(promo_build,
|
||||
wrap=self.builder_wrapper,
|
||||
keep_fn=promote.is_promoted)
|
||||
promoted_links = b.get_items()[0]
|
||||
promoted_links.extend(promo_stub)
|
||||
|
||||
if not (organic_fullnames or promoted_links):
|
||||
return None
|
||||
|
||||
disp_links = spotlight_links[-2:] + spotlight_links[:8]
|
||||
b = IDBuilder(disp_links,
|
||||
random.shuffle(organic_fullnames)
|
||||
organic_fullnames = organic_fullnames[:10]
|
||||
b = IDBuilder(organic_fullnames,
|
||||
wrap = self.builder_wrapper,
|
||||
keep_fn = organic.keep_fresh_links,
|
||||
skip = True)
|
||||
|
||||
vislink = spotlight_links[0]
|
||||
s = SpotlightListing(b, spotlight_items = spotlight_links,
|
||||
visible_item = vislink,
|
||||
max_num = self.listing_obj.max_num,
|
||||
max_score = self.listing_obj.max_score).listing()
|
||||
organic_links = b.get_items()[0]
|
||||
|
||||
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(0, bar)
|
||||
s.visible_item = bar
|
||||
interestbar_prob = g.live_config['spotlight_interest_sub_p'
|
||||
if has_subscribed else
|
||||
'spotlight_interest_nosub_p']
|
||||
interestbar = InterestBar(has_subscribed)
|
||||
|
||||
if len(s.things) > 0:
|
||||
# add campaign id to promoted links for tracking
|
||||
for thing in s.things:
|
||||
thing.campaign = campaigns_by_link.get(thing._fullname, None)
|
||||
return s
|
||||
s = SpotlightListing(organic_links=organic_links,
|
||||
promoted_links=promoted_links,
|
||||
interestbar=interestbar,
|
||||
interestbar_prob=interestbar_prob,
|
||||
max_num = self.listing_obj.max_num,
|
||||
max_score = self.listing_obj.max_score).listing()
|
||||
return s
|
||||
|
||||
def query(self):
|
||||
#no need to worry when working from the cache
|
||||
|
||||
@@ -294,6 +294,7 @@ module["reddit"] = LocalizedModule("reddit.js",
|
||||
"reddit.js",
|
||||
"apps.js",
|
||||
"gold.js",
|
||||
"spotlight.js",
|
||||
)
|
||||
|
||||
module["mobile"] = LocalizedModule("mobile.js",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, namedtuple
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import math
|
||||
@@ -836,6 +836,23 @@ def randomized_promotion_list(user, site):
|
||||
return [(l, cid) for l, w, cid in promos]
|
||||
|
||||
|
||||
PromoTuple = namedtuple('PromoTuple', ['link', 'weight', 'campaign'])
|
||||
|
||||
|
||||
def get_promoted_links(user, site, n=10):
|
||||
"""Return a random selection of promoted links.
|
||||
|
||||
Does not factor weights, as that will be done client side.
|
||||
|
||||
"""
|
||||
|
||||
promos = get_promotion_list(user, site)
|
||||
if n >= len(promos):
|
||||
return [PromoTuple(*p) for p in promos]
|
||||
else:
|
||||
return [PromoTuple(*p) for p in random.sample(promos, n)]
|
||||
|
||||
|
||||
def insert_promoted(link_names, promoted_every_n=6):
|
||||
"""
|
||||
Inserts promoted links into an existing organic list. Destructive
|
||||
|
||||
@@ -39,6 +39,7 @@ from r2.lib import utils
|
||||
from r2.lib.db import operators, tdb_cassandra
|
||||
from r2.lib.filters import _force_unicode
|
||||
from copy import deepcopy
|
||||
from r2.lib.utils import Storage
|
||||
|
||||
from r2.models.wiki import WIKI_RECENT_DAYS
|
||||
|
||||
@@ -485,6 +486,43 @@ class IDBuilder(QueryBuilder):
|
||||
return done, new_items
|
||||
|
||||
|
||||
class CampaignBuilder(IDBuilder):
|
||||
"""Build on a list of PromoTuples."""
|
||||
|
||||
def __init__(self, query, wrap=Wrapped, keep_fn=None, prewrap_fn=None):
|
||||
Builder.__init__(self, wrap=wrap, keep_fn=keep_fn)
|
||||
self.query = query
|
||||
self.skip = False
|
||||
self.num = None
|
||||
self.start_count = 0
|
||||
self.after = None
|
||||
self.reverse = False
|
||||
self.prewrap_fn = prewrap_fn
|
||||
|
||||
def thing_lookup(self, tuples):
|
||||
links = Link._by_fullname([t.link for t in tuples], data=True,
|
||||
return_dict=True, stale=self.stale)
|
||||
|
||||
return [Storage({'thing': links[t.link],
|
||||
'_id': links[t.link]._id,
|
||||
'weight': t.weight,
|
||||
'campaign': t.campaign}) for t in tuples]
|
||||
|
||||
def wrap_items(self, items):
|
||||
links = [i.thing for i in items]
|
||||
wrapped = IDBuilder.wrap_items(self, links)
|
||||
by_link = {w._fullname: w for w in wrapped}
|
||||
|
||||
ret = []
|
||||
for i in items:
|
||||
w = by_link[i.thing._fullname]
|
||||
w.campaign = i.campaign
|
||||
w.weight = i.weight
|
||||
ret.append(w)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class SimpleBuilder(IDBuilder):
|
||||
def thing_lookup(self, names):
|
||||
return names
|
||||
|
||||
@@ -31,6 +31,7 @@ from r2.lib import utils
|
||||
from r2.lib.db import operators
|
||||
from r2.lib.cache import sgm
|
||||
|
||||
from collections import namedtuple
|
||||
from copy import deepcopy, copy
|
||||
|
||||
class Listing(object):
|
||||
@@ -124,32 +125,66 @@ class NestedListing(Listing):
|
||||
#make into a tree thing
|
||||
return Wrapped(self)
|
||||
|
||||
SpotlightTuple = namedtuple('SpotlightTuple',
|
||||
['link', 'is_promo', 'campaign', 'weight'])
|
||||
|
||||
class SpotlightListing(Listing):
|
||||
# class used in Javascript to manage these objects
|
||||
_js_cls = "OrganicListing"
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
kw['vote_hash_type'] = kw.get('vote_hash_type', 'organic')
|
||||
Listing.__init__(self, *a, **kw)
|
||||
self.vote_hash_type = kw.get('vote_hash_type', 'organic')
|
||||
self.nextprev = False
|
||||
self.show_nums = True
|
||||
self._parent_max_num = kw.get('max_num', 0)
|
||||
self._parent_max_score = kw.get('max_score', 0)
|
||||
self.spotlight_items = kw.get('spotlight_items', [])
|
||||
self.visible_item = kw.get('visible_item', '')
|
||||
self.interestbar = kw.get('interestbar')
|
||||
self.interestbar_prob = kw.get('interestbar_prob', 0.)
|
||||
self.promotion_prob = kw.get('promotion_prob', 0.5)
|
||||
|
||||
@property
|
||||
def max_score(self):
|
||||
return self._parent_max_score
|
||||
promoted_links = kw.get('promoted_links', [])
|
||||
organic_links = kw.get('organic_links', [])
|
||||
|
||||
@property
|
||||
def max_num(self):
|
||||
return self._parent_max_num
|
||||
self.links = []
|
||||
for l in organic_links:
|
||||
self.links.append(
|
||||
SpotlightTuple(
|
||||
link=l._fullname,
|
||||
is_promo=False,
|
||||
campaign=None,
|
||||
weight=None,
|
||||
)
|
||||
)
|
||||
|
||||
total = sum(float(l.weight) for l in promoted_links)
|
||||
for l in promoted_links:
|
||||
link = l._fullname if isinstance(l, Wrapped) else l.link
|
||||
self.links.append(
|
||||
SpotlightTuple(
|
||||
link=link,
|
||||
is_promo=True,
|
||||
campaign=l.campaign,
|
||||
weight=l.weight / total,
|
||||
)
|
||||
)
|
||||
|
||||
self.things = organic_links
|
||||
self.things.extend(l for l in promoted_links
|
||||
if isinstance(l, Wrapped))
|
||||
|
||||
|
||||
def get_items(self):
|
||||
from r2.lib.template_helpers import replace_render
|
||||
things = self.things
|
||||
for t in things:
|
||||
if not hasattr(t, "render_replaced"):
|
||||
t.render = replace_render(self, t, t.render)
|
||||
t.render_replaced = True
|
||||
return things, None, None, 0, 0
|
||||
|
||||
def listing(self):
|
||||
res = Listing.listing(self)
|
||||
# suppress item numbering
|
||||
for t in res.things:
|
||||
t.num = ""
|
||||
self.lookup = {t._fullname : t for t in self.things}
|
||||
return res
|
||||
self.lookup = {t._fullname: t for t in res.things}
|
||||
return Wrapped(self)
|
||||
|
||||
@@ -986,9 +986,6 @@ a.author { margin-right: 0.5em; }
|
||||
/* This is a really weird value, but it needs to be low to hide text without affecting layout in IE. */
|
||||
text-indent: 50px;
|
||||
}
|
||||
.organic-listing .nextprev .arrow.prev {
|
||||
background-image: url(../prev_organic.png); /* SPRITE */
|
||||
}
|
||||
.organic-listing .nextprev .arrow.next {
|
||||
background-image: url(../next_organic.png); /* SPRITE */
|
||||
}
|
||||
|
||||
@@ -284,79 +284,6 @@ function cancelToggleForm(elem, form_class, button_class, on_hide) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/* organic listing */
|
||||
|
||||
function get_organic(elem, next) {
|
||||
var listing = $(elem).parents(".organic-listing");
|
||||
var thing = listing.find(".thing:visible");
|
||||
if(listing.find(":animated").length)
|
||||
return false;
|
||||
|
||||
/* note we are looking for .thing.link while empty entries (if the
|
||||
* loader isn't working) will be .thing.stub -> no visual
|
||||
* glitches */
|
||||
var next_thing;
|
||||
if (next) {
|
||||
next_thing = thing.nextAll(".thing:not(.stub)").filter(":first");
|
||||
if (next_thing.length == 0)
|
||||
next_thing = thing.siblings(".thing:not(.stub)").filter(":first");
|
||||
}
|
||||
else {
|
||||
next_thing = thing.prevAll(".thing:not(.stub)").filter(":first");
|
||||
if (next_thing.length == 0)
|
||||
next_thing = thing.siblings(".thing:not(.stub)").filter(":last");
|
||||
}
|
||||
|
||||
organic_help(listing, next_thing)
|
||||
thing.fadeOut('fast', function() {
|
||||
if(next_thing.length)
|
||||
next_thing.fadeIn('fast', function() {
|
||||
|
||||
/* make sure the next n are loaded */
|
||||
var n = 5;
|
||||
var t = thing;
|
||||
var to_fetch = [];
|
||||
for(var i = 0; i < 2*n; i++) {
|
||||
t = (next) ? t.nextAll(".thing:first") :
|
||||
t.prevAll(".thing:first");
|
||||
if(t.length == 0)
|
||||
t = t.end().parent()
|
||||
.children( (next) ? ".thing:first" :
|
||||
".thing:last");
|
||||
if(t.filter(".stub").length)
|
||||
to_fetch.push(t.thing_id());
|
||||
if(i >= n && to_fetch.length == 0)
|
||||
break;
|
||||
}
|
||||
if(to_fetch.length) {
|
||||
$.request("fetch_links",
|
||||
{links: to_fetch.join(','),
|
||||
listing: listing.attr("id")});
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
function organic_help(listing, thing) {
|
||||
listing = listing || $('.organic-listing')
|
||||
thing = thing || listing.find('.thing:visible')
|
||||
|
||||
var help = $('#spotlight-help')
|
||||
if (!help.length) {
|
||||
return
|
||||
}
|
||||
|
||||
help.data('HelpBubble').hide(function() {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* links */
|
||||
|
||||
@@ -598,25 +525,20 @@ function updateEventHandlers(thing) {
|
||||
.click(function() {
|
||||
var a = $(this).get(0);
|
||||
change_state(a, 'hide',
|
||||
function() { get_organic(a, 1); });
|
||||
function() { r.spotlight.shuffle() });
|
||||
});
|
||||
thing.find(".del-button a.yes")
|
||||
.click(function() {
|
||||
var a = $(this).get(0);
|
||||
change_state(a, 'del', function() { get_organic(a, 1); });
|
||||
change_state(a, 'del',
|
||||
function() { r.spotlight.shuffle() });
|
||||
});
|
||||
thing.find(".report-button a.yes")
|
||||
.click(function() {
|
||||
var a = $(this).get(0);
|
||||
change_state(a, 'report',
|
||||
function() { get_organic(a, 1); });
|
||||
function() { r.spotlight.shuffle() });
|
||||
});
|
||||
|
||||
/*thing.find(".arrow.down")
|
||||
.one("click", function() {
|
||||
var a = $(this).get(0);
|
||||
get_organic($(this).get(0), 1);
|
||||
}); */
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1287,7 +1209,7 @@ $(function() {
|
||||
$(this).select();
|
||||
});
|
||||
|
||||
organic_help()
|
||||
r.spotlight.shuffle();
|
||||
|
||||
/* ajax ynbutton */
|
||||
function toggleThis() { return toggle(this); }
|
||||
|
||||
114
r2/r2/public/static/js/spotlight.js
Normal file
114
r2/r2/public/static/js/spotlight.js
Normal file
@@ -0,0 +1,114 @@
|
||||
r.spotlight = {}
|
||||
|
||||
r.spotlight.link_by_camp = {}
|
||||
r.spotlight.weights = {}
|
||||
r.spotlight.organics = []
|
||||
r.spotlight.interest_prob = 0
|
||||
r.spotlight.promotion_prob = 1
|
||||
|
||||
r.spotlight.init = function(links, interest_prob, promotion_prob) {
|
||||
var link_by_camp = {},
|
||||
weights = {},
|
||||
organics = []
|
||||
|
||||
for (var index in links) {
|
||||
var link = links[index][0],
|
||||
is_promo = links[index][1],
|
||||
campaign = links[index][2],
|
||||
weight = links[index][3]
|
||||
|
||||
if (is_promo) {
|
||||
link_by_camp[campaign] = link
|
||||
weights[campaign] = weight
|
||||
} else {
|
||||
organics.push(link)
|
||||
}
|
||||
}
|
||||
|
||||
_.extend(r.spotlight.link_by_camp, link_by_camp)
|
||||
_.extend(r.spotlight.weights, weights)
|
||||
_.extend(r.spotlight.organics, organics)
|
||||
r.spotlight.interest_prob = interest_prob
|
||||
r.spotlight.promotion_prob = promotion_prob
|
||||
}
|
||||
|
||||
r.spotlight.shuffle = function() {
|
||||
var listing = $('.organic-listing'),
|
||||
visible = listing.find(".thing:visible")
|
||||
|
||||
if (listing.length == 0) {
|
||||
$.debug('exiting, no organic listing')
|
||||
return
|
||||
}
|
||||
|
||||
if(Math.random() < r.spotlight.promotion_prob) {
|
||||
$.debug('showing promoted link')
|
||||
var campaign_name = r.spotlight.weighted_lottery(r.spotlight.weights),
|
||||
link_name = r.spotlight.link_by_camp[campaign_name],
|
||||
thing = listing.find(".id-" + link_name)
|
||||
$.debug('showing ' + campaign_name)
|
||||
if (thing.hasClass('stub')) {
|
||||
$.debug('fetching')
|
||||
$.request("fetch_promo", {
|
||||
link: link_name,
|
||||
campaign: campaign_name,
|
||||
show: true,
|
||||
listing: listing.attr("id")
|
||||
},
|
||||
null, null, null, true
|
||||
)
|
||||
} else {
|
||||
$.debug('no need to fetch')
|
||||
$.debug('setting cid')
|
||||
thing.data('cid', campaign_name)
|
||||
}
|
||||
r.spotlight.help('promoted')
|
||||
} else if (Math.random() < r.spotlight.interest_prob) {
|
||||
$.debug('showing interest bar')
|
||||
var thing = listing.find(".interestbar")
|
||||
r.spotlight.help('interestbar')
|
||||
} else {
|
||||
$.debug('showing organic link')
|
||||
var name = r.spotlight.organics[Math.floor(Math.random() * r.spotlight.organics.length)],
|
||||
thing = listing.find(".id-" + name)
|
||||
r.spotlight.help('organic')
|
||||
}
|
||||
visible.hide()
|
||||
thing.show()
|
||||
}
|
||||
|
||||
r.spotlight.help = function(type) {
|
||||
var help = $('#spotlight-help')
|
||||
|
||||
if (!help.length) {
|
||||
return
|
||||
}
|
||||
|
||||
help.data('HelpBubble').hide(function() {
|
||||
help.find('.help-section').hide()
|
||||
if (type == 'promoted') {
|
||||
help.find('.help-promoted').show()
|
||||
} else if (type == 'interestbar') {
|
||||
help.find('.help-interestbar').show()
|
||||
} else {
|
||||
help.find('.help-organic').show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
r.spotlight.weighted_lottery = function(weights) {
|
||||
var seed_rand = Math.random(),
|
||||
t = 0
|
||||
|
||||
$.debug('random: ' + seed_rand)
|
||||
for (var name in weights) {
|
||||
weight = weights[name]
|
||||
t += weight
|
||||
$.debug(name + ': ' + weight)
|
||||
if (t > seed_rand) {
|
||||
$.debug('picked ' + name)
|
||||
return name
|
||||
}
|
||||
}
|
||||
$.debug('whoops, fell through!')
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 238 B |
@@ -22,33 +22,31 @@
|
||||
|
||||
<%namespace file="printablebuttons.html" import="ynbutton"/>
|
||||
<%namespace file="utils.html" import="tags, text_with_links"/>
|
||||
<%
|
||||
<%
|
||||
import json
|
||||
from r2.lib.template_helpers import static
|
||||
from r2.lib.wrapped import Templated
|
||||
%>
|
||||
|
||||
<div id="siteTable_organic" class="organic-listing">
|
||||
<%
|
||||
seen = set([])
|
||||
%>
|
||||
%for name in thing.spotlight_items:
|
||||
%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) %>
|
||||
${unsafe(thing.lookup[name].render(display = (thing.visible_item == name)))}
|
||||
%else:
|
||||
<div class="thing id-${name} stub" style="display:none"></div>
|
||||
%endif
|
||||
%endfor
|
||||
%if thing.links:
|
||||
%for tup in thing.links:
|
||||
%if tup.link in thing.lookup:
|
||||
${unsafe(thing.lookup[tup.link].render(display=False))}
|
||||
%else:
|
||||
<div class="thing id-${tup.link} stub" style="display:none"></div>
|
||||
%endif
|
||||
%endfor
|
||||
%endif
|
||||
|
||||
%if thing.interestbar:
|
||||
<div class="thing interestbar" style="display:none">
|
||||
${unsafe(thing.interestbar.render())}
|
||||
</div>
|
||||
%endif
|
||||
|
||||
<div class="nextprev">
|
||||
<button class="arrow prev" onclick="get_organic(this, false)">prev</button>
|
||||
<button class="arrow next" onclick="get_organic(this, true)">next</button>
|
||||
<button class="arrow next" onclick="r.spotlight.shuffle()">next</button>
|
||||
</div>
|
||||
|
||||
<div class="help help-hoverable">
|
||||
@@ -93,5 +91,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
r.spotlight.init(
|
||||
${unsafe(json.dumps(thing.links))},
|
||||
${unsafe(json.dumps(thing.interestbar_prob))},
|
||||
${unsafe(json.dumps(thing.promotion_prob))}
|
||||
)
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user