ReadNext: Add "read next" widget to sidebar on comments pages.

This commit is contained in:
Matt Lee
2015-04-21 14:20:37 -07:00
parent c93a63d6e3
commit edcca4dbd6
13 changed files with 406 additions and 18 deletions

View File

@@ -45,6 +45,8 @@ from r2.models import (
LINK_FLAIR,
LabeledMulti,
Link,
ReadNextLink,
ReadNextListing,
Mod,
ModSR,
MultiReddit,
@@ -52,6 +54,7 @@ from r2.models import (
Printable,
PromoCampaign,
PromotionPrices,
QueryBuilder,
Random,
RandomNSFW,
RandomSubscription,
@@ -1728,6 +1731,27 @@ class LinkInfoPage(Reddit):
def rightbox(self):
rb = Reddit.rightbox(self)
if (c.site and not c.default_sr and c.render_style == 'html' and
feature.is_enabled('read_next')):
link = self.link
def wrapper_fn(thing):
w = Wrapped(thing)
w.render_class = ReadNextLink
return w
def keep_fn(thing):
return thing._fullname != link._fullname
query_obj = c.site.get_links('hot', 'all').query
builder = QueryBuilder(query_obj,
wrap=wrapper_fn, keep_fn=keep_fn,
skip=True, num=10)
listing = ReadNextListing(builder).listing()
if len(listing.things):
rb.append(ReadNext(c.site, listing.render()))
if not (self.link.promoted and not c.user_is_sponsor):
if c.user_is_admin:
from admin_pages import AdminLinkInfoBar
@@ -3446,6 +3470,13 @@ class Ads(Templated):
self.frame_id = "ad-frame"
class ReadNext(Templated):
def __init__(self, sr, links):
Templated.__init__(self)
self.sr = sr
self.links = links
class Embed(Templated):
"""wrapper for embedding /help into reddit as if it were not on a separate wiki."""
def __init__(self,content = ''):

View File

@@ -932,6 +932,10 @@ class PromotedLink(Link):
Printable.add_props(user, wrapped)
class ReadNextLink(Link):
_nodb = True
class Comment(Thing, Printable):
_data_int_props = Thing._data_int_props + ('reported', 'gildings')
_defaults = dict(reported=0,

View File

@@ -301,6 +301,10 @@ class SearchListing(LinkListing):
return wrapped
class ReadNextListing(Listing):
pass
class NestedListing(Listing):
def __init__(self, *a, **kw):
Listing.__init__(self, *a, **kw)

View File

@@ -11,3 +11,4 @@
@import "modal.less";
@import "close.less";
@import "toggles.less";
@import "read-next.less";

View File

@@ -0,0 +1,134 @@
@read-next-color-background: @color-white;
@read-next-color-header: #EFF7FF;
@read-next-color-button-background: lighten(@color-ui-blue, 5%);
@read-next-color-text: @color-semi-black;
@read-next-color-text-light: @color-dark-grey;
@read-next-height: 100px;
@read-next-width: 300px;
@read-next-link-height: 60px;
@read-next-thumbnail-size: 50px;
@zindex-read-next: @zindex-modal - 1;
@read-next-font-base: @font-x-small;
@read-next-font-small: @font-xx-small;
@read-next-line-base: @line-x-small;
@read-next-line-large: @line-small;
.read-next-font-size(@font:@read-next-font-base, @line:@read-next-line-base) {
.md-font-size(@read-next-font-base, @font, @line);
}
.read-next-container {
font-size: @base-font-keyword;
}
.read-next {
.md-base-font-size(@read-next-font-base);
background: @read-next-color-background;
border: 1px solid darken(@read-next-color-background, 15%);
box-sizing: border-box;
color: @read-next-color-text;
height: @read-next-height;
position: relative;
-webkit-user-select: none;
width: @read-next-width;
z-index: @zindex-read-next;
&.fixed {
border-bottom-width: 0;
bottom: 0;
position: fixed;
}
.read-next-header {
background-color: @read-next-color-header;
border-bottom: 1px solid darken(@read-next-color-header, 5%);
color: @read-next-color-text-light;
padding-left: @margin-small * 1px;
padding-right: @margin-small * 1px;
padding-top: @margin-x-small * 1px;
}
.read-next-header-title {
.read-next-font-size(@read-next-font-base, @read-next-line-large);
}
.read-next-title {
.read-next-font-size();
display: block;
max-height: @read-next-line-base * 3px;
overflow: hidden;
text-overflow: ellipsis;
}
.read-next-nav {
.read-next-font-size(@read-next-font-small);
position: absolute;
right: @margin-x-small * 1px;
top: @margin-x-small * 1px;
}
.read-next-dismiss,
.read-next-button {
.transform(scale(1, 1) translateY(0px));
.transition(all, 0.2s);
cursor: pointer;
display: inline-block;
height: @read-next-line-base * 1px;
margin-left: @margin-x-small * 1px;
position: relative;
text-align: center;
width: @read-next-line-base * 1px;
&:active {
.transform(scale(1.01, 1.01) translateY(1px));
}
}
.read-next-button {
background-color: @read-next-color-button-background;
border-radius: 50%;
color: @read-next-color-background;
&:active {
background-color: darken(@read-next-color-button-background, 5%);
}
}
.read-next-list {
padding: @margin-small * 1px;
padding-top: @margin-x-small * 1px;
}
.read-next-link {
display: none;
float: left;
height: @read-next-link-height;
overflow: hidden;
width: 100%;
&.active {
display: block;
}
.read-next-thumbnail {
display: block;
float: left;
height: @read-next-thumbnail-size;
margin-right: @margin-x-small * 1px;
// magic number, but it just looks better
margin-top: 3px;
width: @read-next-thumbnail-size;
img {
height: auto;
width: 100%;
}
}
}
.read-next-meta {
.read-next-font-size(@read-next-font-small);
color: @read-next-color-text-light;
}
}

View File

@@ -76,6 +76,7 @@
@base-font-keyword: small;
@base-font-keyword-size: 13; // small == 13px;
@font-xx-small: 10;
@font-x-small: 12;
@font-small: 14;
@font-medium: 16;

View File

@@ -1,5 +1,5 @@
@import "components/components.less";
@import "components/variables.less";
@import "components/components.less";
@import "markdown.less";
.no-select {

View File

@@ -74,6 +74,8 @@ r.ui.init = function() {
r.ui.initNewCommentHighlighting()
r.ui.initReadNext();
r.ui.initTimings()
}
@@ -148,6 +150,95 @@ r.ui.highlightNewComments = function() {
});
}
r.ui.initReadNext = function() {
var $readNextContainer = $('.read-next-container');
if ($readNextContainer.length) {
this.readNext = new r.ui.ReadNext({
el: $readNextContainer,
});
}
};
r.ui.ReadNext = Backbone.View.extend({
events: {
'click .read-next-button.next': 'next',
'click .read-next-button.prev': 'prev',
'click .read-next-dismiss': 'dismiss',
},
initialize: function() {
this.$readNext = this.$el.find('.read-next');
this.$links = this.$readNext.find('.read-next-link');
this.numLinks = this.$links.length;
this.state = new Backbone.Model({
fixed: false,
index: -1,
});
this.updateScroll = this.updateScroll.bind(this);
window.addEventListener('scroll', this.updateScroll);
this.state.on('change', this.render.bind(this));
this.updateScroll();
this.state.set({
index: 0,
});
},
next: function() {
var currentIndex = this.state.get('index');
var numLinks = this.numLinks;
this.state.set({
index: currentIndex = (currentIndex + 1) % numLinks,
});
},
prev: function() {
var currentIndex = this.state.get('index');
var numLinks = this.numLinks;
this.state.set({
index: currentIndex = (currentIndex + numLinks - 1) % numLinks,
});
},
dismiss: function() {
this.$el.fadeOut();
window.removeEventListener('scroll', this.updateScroll);
},
updateScroll: function() {
var scrollPosition = window.scrollY;
var nodePosition = this.$el.position().top;
// stick to bottom
var scrollOffset = window.innerHeight;
var nodeOffset = this.$readNext.height();
scrollPosition += scrollOffset;
nodePosition += nodeOffset;
this.state.set({
fixed: scrollPosition >= nodePosition,
});
},
render: function() {
var currentIndex = this.state.get('index');
var fixedPosition = this.state.get('fixed');
this.$links.removeClass('active');
this.$links.eq(currentIndex).addClass('active');
if (fixedPosition) {
this.$readNext.addClass('fixed');
} else {
this.$readNext.removeClass('fixed');
}
},
});
r.ui.initTimings = function() {
// return if we're not configured for sending stats
if (!r.config.pageInfo.actionName || !r.config.stats_domain) {

View File

@@ -32,7 +32,7 @@
%>
<%inherit file="printable.html"/>
<%namespace file="utils.html" import="plain_link, thing_timestamp, edited, nsfw_stamp" />
<%namespace file="utils.html" import="plain_link, thing_timestamp, edited, nsfw_stamp, thumbnail_img" />
<%namespace file="printablebuttons.html" import="toggle_button" />
<%def name="numcol()">
@@ -232,22 +232,7 @@ ${parent.thing_css_class(what)} ${"over18" if thing.over_18 else ""} ${thing.vis
<%def name="thumbnail()">
%if thing.thumbnail:
<%call expr="make_link('thumbnail', 'thumbnail ' + (thing.thumbnail if thing.thumbnail_sprited else ''))">
% if not thing.thumbnail_sprited:
<%
if hasattr(thing, 'thumbnail_size'):
scaling_factor = 1
if thing.thumbnail_size[0] > g.thumbnail_size[0]:
# hidpi scaling, calculate in case hidpi changes definition in the future and
# we have multiple sets of image dimensions. Currently should always be 1 or 2.
# Width is always the maximum allowed, so we don't need to check height.
scaling_factor = thing.thumbnail_size[0] // g.thumbnail_size[0]
size_str = "width='%d' height='%d'" % (thing.thumbnail_size[0] // scaling_factor, thing.thumbnail_size[1] // scaling_factor)
else:
size_str = ""
%>
<img src="${make_url_protocol_relative(thing.thumbnail)}" ${size_str} alt="">
% endif
${thumbnail_img(thing)}
</%call>
%endif
</%def>

View File

@@ -0,0 +1,42 @@
## 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-2015
## reddit Inc. All Rights Reserved.
###############################################################################
<%namespace file="utils.html" import="plain_link"/>
<div class="read-next-container">
<aside class="read-next">
<header class="read-next-header">
<div class="read-next-header-title">
${_("also in")}&#32;
${plain_link(thing.sr.name, thing.sr.path, _sr_path=False)}
</div>
<nav class="read-next-nav">
<span class="read-next-button prev">&lt;</span>
<span class="read-next-button next">&gt;</span>
<span class="read-next-dismiss">X</span>
</nav>
</header>
<div class="read-next-list">
${unsafe(thing.links)}
</div>
</aside>
</div>

View File

@@ -0,0 +1,47 @@
## 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-2015
## reddit Inc. All Rights Reserved.
###############################################################################
<%namespace file="utils.html" import="thumbnail_img" />
<a class="read-next-link" href="${thing.permalink}">
<div class="read-next-meta">
<span class="score">${thing.score} points</span>&#32;
%if thing.num_comments:
&middot;&#32;
<span class="comments">${thing.comment_label}</span>&#32;
%endif
</div>
%if thing.thumbnail:
${self.thumbnail()}
%endif
<div class="read-next-title">
${thing.title}
</div>
</a>
<%def name="thumbnail()">
%if thing.thumbnail and not thing.thumbnail_sprited:
<div class="read-next-thumbnail">
${thumbnail_img(thing)}
</div>
%endif
</%def>

View File

@@ -0,0 +1,29 @@
## 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-2015
## reddit Inc. All Rights Reserved.
###############################################################################
<div class="listing read-next-listing">
<div class="listing-contents">
%for a in thing.things:
${a}
%endfor
</div>
</div>

View File

@@ -637,3 +637,22 @@ ${unsafe(txt)}
${_("NSFW")}
</acronym>
</%def>
<%def name="thumbnail_img(thing)">
%if thing.thumbnail and not thing.thumbnail_sprited:
<%
if hasattr(thing, 'thumbnail_size'):
scaling_factor = 1
if thing.thumbnail_size[0] > g.thumbnail_size[0]:
# hidpi scaling, calculate in case hidpi changes definition in the future and
# we have multiple sets of image dimensions. Currently should always be 1 or 2.
# Width is always the maximum allowed, so we don't need to check height.
scaling_factor = thing.thumbnail_size[0] // g.thumbnail_size[0]
size_str = "width='%d' height='%d'" % (thing.thumbnail_size[0] // scaling_factor, thing.thumbnail_size[1] // scaling_factor)
else:
size_str = ""
%>
<img src="${make_url_protocol_relative(thing.thumbnail)}" ${size_str} alt="">
%endif
</%def>