user reviews wip
This commit is contained in:
Родитель
967ee1b63e
Коммит
4d7e0533cb
|
@ -16,6 +16,9 @@
|
|||
min-width: (@cols * (@column + @gutter) - @gutter) * @px;
|
||||
}
|
||||
|
||||
//Magic numbers
|
||||
@spacing: @gutter * @px;
|
||||
|
||||
//Breakpoints
|
||||
@3col: 0px;
|
||||
@4col: 480px;
|
||||
|
|
|
@ -1,41 +1,79 @@
|
|||
@import 'lib';
|
||||
|
||||
.reviews {
|
||||
position: relative;
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
> div {
|
||||
.border-box;
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
h3 {
|
||||
text-align: center;
|
||||
span {
|
||||
background: url(../../img/icons/thumbs.png) no-repeat;
|
||||
display: inline-block;
|
||||
line-height: 16px;
|
||||
padding-left: 30px;
|
||||
figure {
|
||||
display: block;
|
||||
background: fade(#fff, 50%);
|
||||
margin-right: @spacing;
|
||||
padding: @spacing;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
figcaption {
|
||||
margin-bottom: 10px;
|
||||
font-size: 22px;
|
||||
}
|
||||
canvas {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
}
|
||||
a[data-review-filter] {
|
||||
display: block;
|
||||
color: #666;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
&.selected {
|
||||
background: fade(#fff, 50%);
|
||||
}
|
||||
padding: 5px 0;
|
||||
}
|
||||
h3 {
|
||||
padding-left: 36px;
|
||||
&.upvotes {
|
||||
background: url(../../img/icons/thumbs.png) no-repeat;
|
||||
background-position: 8px 8px;
|
||||
color: #00b960;
|
||||
}
|
||||
&.downvotes {
|
||||
background: url(../../img/icons/thumbs.png) no-repeat;
|
||||
color: #d93a40;
|
||||
span {
|
||||
background-position: 100% -86px;
|
||||
padding: 0 30px 0 0;
|
||||
}
|
||||
background-position: 8px -78px;
|
||||
}
|
||||
}
|
||||
.filter-all .review:nth-child(5) ~ .review {
|
||||
display: none;
|
||||
}
|
||||
.filter-positive .review.negative,
|
||||
.filter-negative .review.positive {
|
||||
display: none;
|
||||
}
|
||||
#review-list {
|
||||
background: fade(#fff, 50%);
|
||||
}
|
||||
.review {
|
||||
padding: @spacing;
|
||||
list-style-type: none;
|
||||
p {
|
||||
font-size: 120%;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
margin: 0 0 @spacing;
|
||||
}
|
||||
#submit-review {
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
function ratingHistory(el, history) {
|
||||
// init to win it
|
||||
el.width = el.offsetWidth;
|
||||
el.height = el.offsetHeight;
|
||||
var ctx = el.getContext('2d'),
|
||||
size = el.height / 2,
|
||||
i, row, x, y;
|
||||
|
||||
// normalize values
|
||||
var max = 0;
|
||||
for (i=0; i<history.length; i++) {
|
||||
max = Math.max(max, history[i][0], history[i][1]);
|
||||
}
|
||||
var yscale = (size / max),
|
||||
xscale = (el.width / history.length),
|
||||
cpx = xscale / 2;
|
||||
|
||||
// positive ratings
|
||||
ctx.fillStyle = 'rgba(0, 200, 0, .2)';
|
||||
ctx.strokeStyle = 'green';
|
||||
plotSeries(0, -1);
|
||||
|
||||
// negative ratings
|
||||
ctx.fillStyle = 'rgba(200, 0, 0, .2)';
|
||||
ctx.strokeStyle = 'red';
|
||||
plotSeries(1, 1);
|
||||
|
||||
function plotSeries(idx, dir) {
|
||||
ctx.beginPath();
|
||||
// start outside viewport to hide stroke
|
||||
ctx.moveTo(-2, size);
|
||||
ox = 0;
|
||||
oy = size + history[0][idx] * yscale * dir;
|
||||
ctx.lineTo(-2, oy);
|
||||
ctx.lineTo(0, oy);
|
||||
for (i=1; i<history.length; i++) {
|
||||
row = history[i];
|
||||
x = i * xscale + xscale;
|
||||
y = size + row[idx] * yscale * dir;
|
||||
ctx.bezierCurveTo(x-cpx, oy, ox+cpx, y, x, y);
|
||||
oy = y;
|
||||
ox = x;
|
||||
}
|
||||
// finish outside viewport to hide stroke
|
||||
ctx.lineTo(ox+2, oy);
|
||||
ctx.lineTo(ox+2, size);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
(function() {
|
||||
z.page.on('fragmentloaded', function() {
|
||||
var $reviewEl = $('#reviews'),
|
||||
data = $reviewEl.data('review-history');
|
||||
if ($reviewEl.exists() && data) {
|
||||
$reviewEl.find('div:first-child')
|
||||
.prepend('<figure><figcaption>Reviews, last 30 days</figcaption>' +
|
||||
'<canvas id="review-spark"></canvas></figure>');
|
||||
ratingHistory($('#review-spark')[0], data);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
z.page.on('click', '[data-review-filter]', function(e) {
|
||||
e.preventDefault();
|
||||
var filter = $(this).data('review-filter');
|
||||
$('#review-list').removeClass('filter-positive filter-negative filter-all');
|
||||
$('#review-list').addClass('filter-' + filter);
|
||||
$('[data-review-filter]').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
|
||||
});
|
|
@ -213,6 +213,7 @@ JS = {
|
|||
# Detail page.
|
||||
'js/mkt/detail.js',
|
||||
'js/mkt/lightbox.js',
|
||||
'js/mkt/reviewsparks.js',
|
||||
|
||||
# Ratings.
|
||||
'js/mkt/ratings.js',
|
||||
|
|
|
@ -250,36 +250,35 @@
|
|||
</section>
|
||||
|
||||
{% if waffle.switch('ratings') %}
|
||||
<section class="reviews c" id="reviews">
|
||||
{% if product.can_review(request.amo_user if
|
||||
request.user.is_authenticated() else None) %}
|
||||
<p id="submit-review">
|
||||
<a href="{{ product.get_ratings_url('add') }}" class="button good">
|
||||
{{ _('Review This App') }}</a></p>
|
||||
{% endif %}
|
||||
<div class="thumbs-up">
|
||||
<h3 class="upvotes">
|
||||
<span>Positive Reviews <b>({{ product.rating_counts.positive }})</b></span>
|
||||
</h3>
|
||||
{% if positive_ratings %}
|
||||
<ul>
|
||||
{% for rating in positive_ratings %}
|
||||
{% include 'ratings/rating.html' %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ no_results() }}
|
||||
<section class="reviews c" id="reviews"
|
||||
data-review-history="{{ review_history|json }}">
|
||||
<div id="reviews-info">
|
||||
<a href="#" class="selected" data-review-filter="all">
|
||||
<h3 class="all">
|
||||
All Reviews <b>({{ product._ratings.count() }})</b>
|
||||
</h3>
|
||||
</a>
|
||||
<a href="#" data-review-filter="positive">
|
||||
<h3 class="upvotes">
|
||||
Positive Reviews <b>({{ product.rating_counts.positive }})</b>
|
||||
</h3>
|
||||
</a>
|
||||
<a href="#" data-review-filter="negative">
|
||||
<h3 class="downvotes">
|
||||
Negative Reviews <b>({{ product.rating_counts.negative }})</b>
|
||||
</h3>
|
||||
</a>
|
||||
{% if product.can_review(request.amo_user if
|
||||
request.user.is_authenticated() else None) %}
|
||||
<p id="submit-review">
|
||||
<a href="{{ product.get_ratings_url('add') }}" class="button good">
|
||||
{{ _('Review This App') }}</a></p>
|
||||
{% endif %}
|
||||
<p><a href="{{ product.get_ratings_url() }}" class="button">
|
||||
{{ _('Read More') }}</a></p>
|
||||
</div>
|
||||
<div class="thumbs-down">
|
||||
<h3 class="downvotes">
|
||||
<span>Negative Reviews <b>({{ product.rating_counts.negative }})</b></span>
|
||||
</h3>
|
||||
{% if negative_ratings %}
|
||||
<ul>
|
||||
{% for rating in negative_ratings %}
|
||||
<div class="thumbs-up">
|
||||
{% if ratings %}
|
||||
<ul id="review-list" class="filter-all">
|
||||
{% for rating in ratings %}
|
||||
{% include 'ratings/rating.html' %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
@ -30,13 +30,14 @@ def detail(request, addon):
|
|||
"""Product details page."""
|
||||
|
||||
ratings = Rating.objects.latest().filter(addon=addon).order_by('-created')
|
||||
positive_ratings = ratings.filter(score=1)[:5]
|
||||
negative_ratings = ratings.filter(score=-1)[:5]
|
||||
positive_ratings = list(ratings.filter(score=1)[:5])
|
||||
negative_ratings = list(ratings.filter(score=-1)[:5])
|
||||
sorted_ratings = sorted(positive_ratings + negative_ratings, key=lambda x: x.created, reverse=True)
|
||||
ctx = {
|
||||
'product': addon,
|
||||
'ratings': ratings,
|
||||
'positive_ratings': positive_ratings,
|
||||
'negative_ratings': negative_ratings,
|
||||
'ratings': sorted_ratings,
|
||||
'review_history': [[2,1],[50,2],[3,0],[4,1]]
|
||||
}
|
||||
if addon.is_public():
|
||||
ctx['abuse_form'] = AbuseForm(request=request)
|
||||
|
|
Загрузка…
Ссылка в новой задаче