mobile personas previewer (bug 654307) + details page (bug 654310)

This commit is contained in:
Chris Van 2011-05-31 21:08:48 -07:00
Родитель 2305d58019
Коммит b30b42424e
23 изменённых файлов: 693 добавлений и 121 удалений

Просмотреть файл

@ -218,6 +218,24 @@ def persona_preview(context, persona, size='large', linked=True, extra=None,
return c return c
@register.inclusion_tag('addons/mobile/persona_preview.html')
@jinja2.contextfunction
def mobile_persona_preview(context, persona):
addon = persona.addon
c = dict(context.items())
c.update({'persona': persona, 'addon': addon})
return c
@register.inclusion_tag('addons/mobile/persona_confirm.html')
@jinja2.contextfunction
def mobile_persona_confirm(context, persona, size='large'):
addon = persona.addon
c = dict(context.items())
c.update({'persona': persona, 'addon': addon, 'size': size})
return c
@register.inclusion_tag('addons/persona_grid.html') @register.inclusion_tag('addons/persona_grid.html')
@jinja2.contextfunction @jinja2.contextfunction
def persona_grid(context, addons): def persona_grid(context, addons):

Просмотреть файл

@ -40,18 +40,7 @@
</div> </div>
{% endif %} {% endif %}
{% if addon.total_reviews %} {{ mobile_reviews_link(addon) }}
<a class="listview" href="{{ url('reviews.list', addon.slug) }}">
<div class="icon">
{{ addon.average_rating|stars }}
</div>
{% trans num=addon.total_reviews, cnt=addon.total_reviews|numberfmt %}
See All Reviews
{% pluralize %}
See All {{ cnt }} Reviews
{% endtrans %}
</a>
{% endif %}
<details> <details>
<table> <table>

Просмотреть файл

@ -6,7 +6,7 @@
<header id="home-header"> <header id="home-header">
{% include "mobile/header.html" %} {% include "mobile/header.html" %}
<div class="get-fx-message"> <div class="get-fx-message">
{{ _('You need Firefox to install addons. <a href="http://mozilla.com/firefox">Learn More&nbsp;&raquo;</a>') }} {{ _('You need Firefox to install add-ons. <a href="http://mozilla.com/firefox">Learn More&nbsp;&raquo;</a>') }}
</div> </div>
<hgroup> <hgroup>
<h1 class="site-title"> <h1 class="site-title">

Просмотреть файл

@ -0,0 +1,13 @@
<div class="persona-confirm{% if size == 'small' %} persona-slider{% endif %}">
{% if size == 'large' %}
<a href="#" class="button preview">{{ _('Try it on!') }}</a>
{% endif %}
<div class="confirm-buttons">
{{ mobile_install_button(addon, show_warning=False) }}
<a href="#" class="button cancel">{{ _('Cancel') }}</a>
</div>
{% if size == 'small' %}
<a href="{{ addon.get_url_path() }}"
class="more">{{ _('Persona Info &raquo;') }}</a>
{% endif %}
</div>

Просмотреть файл

@ -1,18 +1,66 @@
{% extends "mobile/base.html" %} {% extends "mobile/base.html" %}
{% block title %}{{ page_title(addon.name) }}{% endblock %} {% block title %}{{ page_title(addon.name) }}{% endblock %}
{% block page %} {% block back_link %}
{% set persona = addon.persona %} <a href="{{ url('browse.personas') }}" id="home">
{{ _('&laquo; All Personas') }}</a>
{% endblock %}
<div class="listview"> {% block page %}
{{ persona_preview(addon.persona, size='small') }} {% set author = users_list(addon.listed_authors) or persona.display_username %}
{# TODO: button #}
<div id="persona" class="persona-previewer infobox item">
{{ mobile_persona_preview(persona) }}
<h3>{{ addon.name }}</h3>
<h4 class="author">{{ _('by') }} {{ author }}</h4>
<p id="summary"{{ addon.summary|locale_html }}>{{ addon.summary|nl2br }}</p>
{{ mobile_persona_confirm(persona) }}
</div> </div>
<table> {{ mobile_reviews_link(addon) }}
{% include "addons/persona_detail_table.html" %}
</table> <details>
<table>
{% include "addons/mobile/persona_detail_table.html" %}
</table>
</details>
<div id="more-personas">
{% cache author_personas %}
{% if author_personas %}
<div id="more-artist" class="listview item">
<ul>
{% for other in author_personas %}
<li class="persona-previewer">
{{ mobile_persona_preview(other.persona) }}
{{ mobile_persona_confirm(other.persona, size='small') }}
</li>
{% endfor %}
<li><a href="{{ author_gallery }}">
{{ _('See all Personas by this Artist') }}</a></li>
</ul>
</div>
{% endif %}
{% endcache %}
{% cache category_personas %}
{% if category_personas %}
<div id="more-category" class="listview item">
<ul>
{% for other in category_personas %}
<li class="persona-previewer">
{{ mobile_persona_preview(other.persona) }}
{{ mobile_persona_confirm(other.persona, size='small') }}
</li>
{% endfor %}
{# L10n: {0} is a category name, such as Nature #}
<li><a href="{{ categories[0].get_url_path() }}">
{{ _('See all {0} Personas')|f(categories[0].name) }}</a></li>
</ul>
</div>
{% endif %}
{% endcache %}
</div>
{% endblock %} {% endblock %}

Просмотреть файл

@ -0,0 +1,26 @@
<tbody>
<tr class="artist">
<th>{{ _('Artist') }}</th>
<td>{{ author }}</td>
</tr>
<tr>
<th>{{ _('Updated') }}</th>
<td>
<time datetime="{{ addon.modified|isotime }}">{{
addon.modified|datetime }}</time>
</td>
</tr>
<tr class="meta-stats">
<th>{{ _('Daily Users') }}</th>
<td>
<strong class="downloads">{{
persona.popularity|numberfmt }}</strong>
</td>
</tr>
{% if persona.license %}
<tr>
<th>{{ _('License') }}</th>
<td>{{ license_link(persona.license) }}</td>
</tr>
{% endif %}
</tbody>

Просмотреть файл

@ -0,0 +1,4 @@
<div class="persona persona-large persona-preview">
<div style="background-image:url('{{ persona.preview_url }}')"
data-browsertheme="{{ persona.json_data }}"><p></p></div>
</div>

Просмотреть файл

@ -5,13 +5,14 @@ from pyquery import PyQuery
import amo import amo
from addons.helpers import (statusflags, flag, support_addon, contribution, from addons.helpers import (statusflags, flag, support_addon, contribution,
performance_note) performance_note, mobile_persona_preview,
mobile_persona_confirm)
from addons.models import Addon from addons.models import Addon
class TestHelpers(test_utils.TestCase): class TestHelpers(test_utils.TestCase):
fixtures = ['base/apps', 'base/addon_3615', 'base/addon_4664_twitterbar', fixtures = ['base/apps', 'base/addon_3615', 'base/addon_4664_twitterbar',
'addons/featured.json'] 'addons/featured', 'addons/persona']
def setUp(self): def setUp(self):
# Addon._feature keeps an in-process cache we need to clear. # Addon._feature keeps an in-process cache we need to clear.
@ -95,6 +96,48 @@ class TestHelpers(test_utils.TestCase):
doc = PyQuery(s) doc = PyQuery(s)
eq_(doc('input[name=source]').attr('value'), 'browse') eq_(doc('input[name=source]').attr('value'), 'browse')
def test_mobile_persona_preview(self):
ctx = {'APP': amo.FIREFOX, 'LANG': 'en-US'}
persona = Addon.objects.get(pk=15663).persona
s = mobile_persona_preview(ctx, persona)
doc = PyQuery(s)
bt = doc('.persona-preview div[data-browsertheme]')
assert bt
assert persona.preview_url in bt.attr('style')
eq_(persona.json_data, bt.attr('data-browsertheme'))
assert bt.find('p')
def _test_mobile_persona_ctx(self):
request = Mock()
request.APP = amo.FIREFOX
request.GET = {}
request.user.is_authenticated.return_value = False
request.amo_user.mobile_addons = []
return {'APP': amo.FIREFOX, 'LANG': 'en-US', 'request': request}
def test_mobile_persona_confirm_large(self):
persona = Addon.objects.get(id=15663).persona
s = mobile_persona_confirm(self._test_mobile_persona_ctx(), persona)
doc = PyQuery(s)
assert not doc('.persona-slider')
assert doc('.preview')
assert doc('.confirm-buttons .add')
assert doc('.confirm-buttons .cancel')
assert not doc('.more')
def test_mobile_persona_confirm_small(self):
persona = Addon.objects.get(id=15663).persona
s = mobile_persona_confirm(self._test_mobile_persona_ctx(), persona,
size='small')
doc = PyQuery(s)
assert doc('.persona-slider')
assert not doc('.persona-slider .preview')
assert doc('.confirm-buttons .add')
assert doc('.confirm-buttons .cancel')
more = doc('.more')
assert more
eq_(more.attr('href'), persona.addon.get_url_path())
class TestPerformanceNote(test_utils.TestCase): class TestPerformanceNote(test_utils.TestCase):
listing = '<div class="performance-note">' listing = '<div class="performance-note">'

Просмотреть файл

@ -571,6 +571,12 @@ class TestDetailPage(test_utils.TestCase):
assert '&lt;script&gt;alert("fff")&lt;/script&gt;' in html assert '&lt;script&gt;alert("fff")&lt;/script&gt;' in html
assert '<script>' not in html assert '<script>' not in html
def test_personas_context(self):
response = self.client.get(reverse('addons.detail', args=['a15663']))
assert 'review_form' in response.context
assert 'reviews' in response.context
assert 'get_replies' in response.context
def test_unreviewed_robots(self): def test_unreviewed_robots(self):
"""Check that unreviewed add-ons do not get indexed.""" """Check that unreviewed add-ons do not get indexed."""
addon = Addon.objects.get(id=3615) addon = Addon.objects.get(id=3615)
@ -1266,11 +1272,14 @@ class TestMobileDetails(TestMobile):
eq_(r.status_code, 200) eq_(r.status_code, 200)
self.assertTemplateUsed(r, 'addons/mobile/details.html') self.assertTemplateUsed(r, 'addons/mobile/details.html')
def _test_persona(self): def test_persona(self):
addon = Addon.objects.filter(type=amo.ADDON_PERSONA)[0] addon = Addon.objects.get(id=15679)
r = self.client.get(addon.get_url_path()) r = self.client.get(addon.get_url_path())
eq_(r.status_code, 200) eq_(r.status_code, 200)
self.assertTemplateUsed(r, 'addons/mobile/persona_detail.html') self.assertTemplateUsed(r, 'addons/mobile/persona_detail.html')
assert 'review_form' not in r.context
assert 'reviews' not in r.context
assert 'get_replies' not in r.context
def test_release_notes(self): def test_release_notes(self):
a = Addon.objects.get(id=3615) a = Addon.objects.get(id=3615)

Просмотреть файл

@ -18,7 +18,7 @@ import jinja2
import commonware.log import commonware.log
import session_csrf import session_csrf
from tower import ugettext as _, ugettext_lazy as _lazy from tower import ugettext as _, ugettext_lazy as _lazy
from mobility.decorators import mobilized from mobility.decorators import mobilized, mobile_template
import amo import amo
from amo import messages from amo import messages
@ -238,7 +238,8 @@ def _category_personas(qs, limit):
return caching.cached(f, key) return caching.cached(f, key)
def persona_detail(request, addon): @mobile_template('addons/{mobile/}persona_detail.html')
def persona_detail(request, addon, template=None):
"""Details page for Personas.""" """Details page for Personas."""
persona = addon.persona persona = addon.persona
@ -250,9 +251,6 @@ def persona_detail(request, addon):
else: else:
category_personas = None category_personas = None
# tags
dev_tags, user_tags = addon.tags_partitioned_by_developer
# other personas from the same author(s) # other personas from the same author(s)
author_personas = Addon.objects.valid().filter( author_personas = Addon.objects.valid().filter(
persona__author=persona.author, persona__author=persona.author,
@ -265,25 +263,23 @@ def persona_detail(request, addon):
'categories': categories, 'categories': categories,
'author_personas': author_personas, 'author_personas': author_personas,
'category_personas': category_personas, 'category_personas': category_personas,
# Remora uses persona.author despite there being a display_username.
'author_gallery': settings.PERSONAS_USER_ROOT % persona.author,
}
if not request.MOBILE:
# tags
dev_tags, user_tags = addon.tags_partitioned_by_developer
data.update({
'dev_tags': dev_tags, 'dev_tags': dev_tags,
'user_tags': user_tags, 'user_tags': user_tags,
'review_form': ReviewForm(), 'review_form': ReviewForm(),
'reviews': Review.objects.latest().filter(addon=addon), 'reviews': Review.objects.latest().filter(addon=addon),
'get_replies': Review.get_replies, 'get_replies': Review.get_replies,
# Remora users persona.author despite there being a display_username 'search_cat': 'personas'
'author_gallery': settings.PERSONAS_USER_ROOT % persona.author, })
'search_cat': 'personas',
}
if settings.REPORT_ABUSE: if settings.REPORT_ABUSE:
data['abuse_form'] = AbuseForm(request=request) data['abuse_form'] = AbuseForm(request=request)
return jingo.render(request, template, data)
return jingo.render(request, 'addons/persona_detail.html', data)
# @mobilized(persona_detail)
# def persona_detail(request, addon):
# return jingo.render(request, 'addons/mobile/persona_detail.html',
# {'addon': addon})
class BaseFilter(object): class BaseFilter(object):

Просмотреть файл

@ -28,6 +28,12 @@ def reviews_link(addon, collection_uuid=None, link_to_list=False):
collection_uuid=collection_uuid)) collection_uuid=collection_uuid))
@jingo.register.function
def mobile_reviews_link(addon):
t = jingo.env.get_template('reviews/mobile/reviews_link.html')
return jinja2.Markup(t.render(addon=addon))
@jingo.register.inclusion_tag('reviews/report_review.html') @jingo.register.inclusion_tag('reviews/report_review.html')
@jinja2.contextfunction @jinja2.contextfunction
def report_review_popup(context): def report_review_popup(context):

Просмотреть файл

@ -0,0 +1,15 @@
{% if addon.total_reviews %}
<a class="listview" href="{{ url('reviews.list', addon.slug) }}">
<div class="icon">
{{ addon.average_rating|stars }}
</div>
{% trans num=addon.total_reviews, cnt=addon.total_reviews|numberfmt %}
See All Reviews
{% pluralize %}
See All {{ cnt }} Reviews
{% endtrans %}
</a>
{% else %}
<a class="listview" href="{{ url('reviews.add', addon.slug) }}">
{{ _('Be the first to write a review.') }}</a>
{% endif %}

Просмотреть файл

@ -56,3 +56,25 @@ def test_reviews_link():
s = render('{{ reviews_link(myaddon, link_to_list=True) }}', s = render('{{ reviews_link(myaddon, link_to_list=True) }}',
{'myaddon': a}) {'myaddon': a})
eq_(PyQuery(s)('a').attr('href'), u) eq_(PyQuery(s)('a').attr('href'), u)
def test_mobile_reviews_link():
s = lambda a: PyQuery(render('{{ mobile_reviews_link(myaddon) }}',
{'myaddon': a}))
a = Addon(total_reviews=0, id=1, type=1, slug='xx')
doc = s(a)
eq_(doc('a').attr('href'), reverse('reviews.add', args=['xx']))
u = reverse('reviews.list', args=['xx'])
a = Addon(average_rating=4, total_reviews=37, id=1, type=1, slug='xx')
doc = s(a)
eq_(doc('a').attr('href'), u)
eq_(doc('a').text(), 'Rated 4 out of 5 stars See All 37 Reviews')
a = Addon(average_rating=4, total_reviews=1, id=1, type=1, slug='xx')
doc = s(a)
doc.remove('div')
eq_(doc('a').attr('href'), u)
eq_(doc('a').text(), 'See All Reviews')

Просмотреть файл

@ -65,7 +65,8 @@ i {
header:after, header:after,
section:after, section:after,
.menu:after, .menu:after,
.grouped_ratings:after { .grouped_ratings:after,
.persona-confirm:after {
content: "."; content: ".";
display: block; display: block;
clear: both; clear: both;
@ -488,7 +489,7 @@ header #home, header .back-link {
.listview, .listview,
.infobox { .infobox {
margin: 14px 14px 0; margin: 14px 14px 1em;
border-radius: 6px; border-radius: 6px;
-moz-border-radius: 6px; -moz-border-radius: 6px;
-webkit-border-radius: 6px; -webkit-border-radius: 6px;
@ -497,7 +498,6 @@ header #home, header .back-link {
/* background-image: -moz-linear-gradient(#fff, rgba(255,255,255,0) 12px); /* background-image: -moz-linear-gradient(#fff, rgba(255,255,255,0) 12px);
-moz-box-shadow: 0 -3px rgba(0,0,0,.1) inset; -moz-box-shadow: 0 -3px rgba(0,0,0,.1) inset;
-webkit-box-shadow: 0 -3px rgba(0,0,0,.1) inset;*/ -webkit-box-shadow: 0 -3px rgba(0,0,0,.1) inset;*/
margin-bottom: 1em;
display: block; display: block;
} }
.infobox { .infobox {
@ -512,7 +512,7 @@ header #home, header .back-link {
} }
.listview li > a, .listview li > a,
a.listview { a.listview {
padding: 14px 10px; padding: 14px;
color: #444; color: #444;
font-family: Georgia, serif; font-family: Georgia, serif;
font-size: 1.1em; font-size: 1.1em;
@ -521,6 +521,10 @@ a.listview {
text-decoration: none; text-decoration: none;
position: relative; position: relative;
} }
.html-rtl li > a,
.html-rtl a.listview {
padding-left: 34px;
}
.listview .item > a, .listview .item > a,
.listview div.item { .listview div.item {
padding: 10px; padding: 10px;
@ -816,7 +820,6 @@ a.listview:before {
-webkit-transition: .5s opacity ease; -webkit-transition: .5s opacity ease;
} }
#lightbox .close { #lightbox .close {
font-family: sans;
display: block; display: block;
position: absolute; position: absolute;
z-index: 100; z-index: 100;
@ -906,6 +909,170 @@ td .versions li a {
line-height: 12px; line-height: 12px;
} }
/************************************/
/* PERSONAS */
/************************************/
#persona h3,
#persona .author {
display: inline-block;
font-size: 14px;
}
#persona h3 {
color: #444;
font-family: "Droid Sans", Helvetica, sans-serif;
font-weight: bold;
}
#persona .author,
#persona .persona-large {
margin-bottom: 8px;
}
#persona .badges {
margin-top: 0;
}
#persona .badges li {
margin: 8px 0 0;
}
ul.license {
position: relative;
top: 3px;
}
ul.license li {
display: block;
float: left;
list-style: none;
margin-right: 2px;
}
.html-rtl ul.license li {
float: right;
margin: 0 0 0 2px;
}
ul.license li.text {
font-size: 90%;
line-height: 15px;
margin-left: 4px;
}
.html-rtl ul.license li.text {
margin: 0 4px 0 0;
}
ul.license li.icon {
background: url(../../img/zamboni/licenses.png) no-repeat top left;
height: 15px;
width: 15px;
}
ul.license li.cc-attrib { background-position: 0 0; }
ul.license li.cc-noderiv { background-position: 0 -65px; }
ul.license li.cc-noncom { background-position: 0 -130px; }
ul.license li.cc-share { background-position: 0 -195px; }
ul.license li.copyr { background-position: 0 -260px; }
.persona-preview [data-browsertheme] {
display: block;
position: relative;
}
.persona-large [data-browsertheme],
.persona-large p {
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
}
.persona-large {
max-width: 680px;
}
.persona-large [data-browsertheme] {
background: transparent no-repeat right top;
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
display: table;
height: 64px;
width: 100%;
}
.persona-large p {
background-image: url(../../img/zamboni/mobile/loading-white.png);
background-repeat: no-repeat;
background-position: 50% 50%;
-moz-background-size: auto 32px;
-wekbkit-background-size: auto 32px;
background-size: auto 32px;
color: #fff;
display: none;
font: 18px Georgia, serif;
pointer-events: none;
text-align: center;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
vertical-align: middle;
}
.persona-hover p {
background-color: rgba(0, 0, 0, 0.4);
display: table-cell;
}
.persona-previewing p,
.persona-installed p {
background: none;
}
#persona .confirm-buttons,
.persona-slider .badges {
display: none;
}
.persona-previewer .confirm-buttons .add {
float: left;
width: -moz-calc(50% - 63px); /* 63px = 39px + 24px (for plus-sign icon) */
}
.persona-previewer .confirm-buttons .cancel {
float: right;
width: -moz-calc(50% - 39px); /* 39px = 32px + 14px / 2 (margin) */
}
.persona-previewer .persona-installed p:before {
background: url(../../img/zamboni/mobile/checkmark.png) no-repeat top left;
content: " ";
display: inline-block;
margin: 0 3px -3px 0;
position: relative;
top: 3px;
height: 25px;
width: 25px;
}
li.persona-previewer {
padding: 14px;
}
.persona-slider {
background-color: #ccc;
border-top: #999 1px solid;
box-shadow: 0 -1px 1px rgba(0,0,0,.5);
display: none;
padding: 14px;
position: relative;
left: -14px;
bottom: -14px;
width: 100%;
}
.persona-slider .more {
clear: both;
color: #447bc4;
display: block;
font-weight: bold;
line-height: 1;
padding-top: 14px;
text-align: center;
}
/************************************/ /************************************/
/* VERSIONS */ /* VERSIONS */
/************************************/ /************************************/
@ -969,11 +1136,26 @@ td .versions li a {
font-weight: bold; font-weight: bold;
} }
.infobox .install-wrapper { .infobox .install-wrapper,
#persona .persona-confirm {
border-top: 2px solid #fff; border-top: 2px solid #fff;
box-shadow: 0 -1px #ccc; box-shadow: 0 -1px #ccc;
} }
#persona .persona-confirm {
padding-top: 8px;
}
.persona-confirm .install-wrapper {
margin: 0;
padding-top: 0;
}
#persona .persona-confirm .install-wrapper {
border-top-width: 0;
box-shadow: none;
}
.button, a.button { .button, a.button {
-moz-transition: -moz-box-shadow 0.3s ease 0s; -moz-transition: -moz-box-shadow 0.3s ease 0s;
background-color: #669BE1; background-color: #669BE1;
@ -1039,6 +1221,7 @@ td .versions li a {
0 0 100px rgba(255, 255, 255, 0.2) inset; 0 0 100px rgba(255, 255, 255, 0.2) inset;
} }
.button.preview,
.button.affirmative, .button.affirmative,
.button.add { .button.add {
background-color: #84C63C; background-color: #84C63C;
@ -1064,6 +1247,9 @@ td .versions li a {
);*/ );*/
color: #fff; color: #fff;
} }
.button.cancel {
background-color: #b25951;
}
.button.add { .button.add {
padding-left: 40px; padding-left: 40px;
} }
@ -1312,9 +1498,7 @@ td .versions li a {
width: 120px; width: 120px;
} }
.num_ratings { .num_ratings {
font-size: .9em;
color: #888; color: #888;
line-height: 22px;
width: 1px; width: 1px;
position: absolute; position: absolute;
right: -6px; right: -6px;

Двоичные данные
media/img/zamboni/mobile/checkmark.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
media/img/zamboni/mobile/loading-white.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 32 KiB

Просмотреть файл

@ -201,6 +201,15 @@ $(function() {
$("#eula .negative").click(_pd(z.eula.dismiss)); $("#eula .negative").click(_pd(z.eula.dismiss));
$("#eula .affirmative").click(_pd(z.eula.dismiss)); $("#eula .affirmative").click(_pd(z.eula.dismiss));
$(".persona-previewer .preview").click(_pd(function() {
var persona = new MobilePersona(this);
persona.triggers().preview();
}));
$(".persona-previewer .cancel").click(_pd(function() {
var persona = new MobilePersona(this);
persona.triggers().cancel();
}));
//review truncation //review truncation
if ($(".review").length) { if ($(".review").length) {
$(".review p").each(function() { $(".review p").each(function() {
@ -258,3 +267,99 @@ z.eula = (function(){
acceptButton: $("#eula-menu .affirmative") acceptButton: $("#eula-menu .affirmative")
}; };
})(); })();
/**
* MobilePersona: controls for mobile-friendly Persona previewer.
* Configuration:
* el: .button, .persona-preview, or any element in the .persona-previewer
*/
function MobilePersona(el) {
this.el = el;
this.outer = $(el).closest('.persona-previewer');
this.persona = this.outer.find('.persona');
this.personaPreview = this.persona.find('[data-browsertheme]');
}
MobilePersona.prototype.buttons = function() {
var $slider = this.outer.find('.persona-slider'),
$preview = this.outer.find('.button.preview'),
$confirm = this.outer.find('.confirm-buttons'),
$badges = this.outer.closest('#persona').find('.badges'),
that = this;
return {
show: function(force) {
if ($slider.length) {
$slider.slideDown();
} else {
$confirm.show();
}
$preview.hide();
$badges.hide();
},
hide: function(force) {
if ($slider.length) {
$slider.slideUp();
} else {
$confirm.hide();
}
$preview.show();
$badges.show();
},
disable: function() {
$preview.addClass('disabled');
}
};
};
MobilePersona.prototype.states = function() {
var btns = this.buttons(),
that = this;
return {
loading: function() {
that.persona.find('p').show();
},
previewing: function() {
that.persona.addClass('persona-previewing');
that.persona.find('p').text(gettext("You're trying it on!"));
btns.show();
},
installed: function() {
var $installed = $('.persona-installed');
if ($installed.length) {
// If a different persona has already been installed, then
// that persona should be able to be previewed again.
$installed.removeClass('persona-installed').find('p').text('').hide();
$('#persona .preview.disabled').removeClass('disabled');
$installed.find('[data-browsertheme]').trigger('click');
}
that.persona.find('p').text(gettext('Added to Firefox'));
that.persona.removeClass('persona-previewing').addClass('persona-installed');
btns.hide();
btns.disable();
},
cancelled: function() {
that.persona.removeClass('persona-previewing');
that.persona.find('p').text('').hide();
btns.hide();
}
};
};
MobilePersona.prototype.triggers = function() {
// Trigger events for Persona previews.
var btns = this.buttons(),
that = this;
return {
preview: function() {
// Check if "Try it" button is disabled.
if (that.outer.find('.button.preview').hasClass('disabled')) {
return;
}
that.personaPreview.trigger('click');
btns.show();
},
cancel: function() {
// Clicking again will cancel the Persona preview.
that.personaPreview.trigger('click');
btns.hide();
}
};
};

Просмотреть файл

@ -0,0 +1,54 @@
$(document).ready(function() {
var personas = $('.persona-preview');
if (!personas.length) return;
personas.previewPersona();
});
/**
* Binds Personas preview events to the element.
* Click - bubbles up PreviewPersona
* Click again - bubbles up ResetPersona
**/
$.fn.previewPersona = function(o) {
if (!$.hasPersonas()) {
return;
}
o = $.extend({
activeClass: 'persona-hover',
disabledClass: 'persona-installed'
}, o || {});
$(this).click(function(e) {
var $outer = $(this).closest('.persona-previewer'),
$persona = $outer.find('.persona');
if ($persona.hasClass(o.disabledClass)) {
return;
}
var mp = new MobilePersona(this),
states = mp.states();
if ($persona.hasClass(o.activeClass)) {
// Hide persona.
$persona.removeClass(o.activeClass);
dispatchPersonaEvent('ResetPersona', e.target,
states.cancelled);
} else {
// Hide other active personas.
$('.' + o.activeClass).each(function() {
$(this).find('[data-browsertheme]').trigger('click');
});
// Load persona.
$persona.addClass(o.activeClass);
states.loading();
dispatchPersonaEvent('PreviewPersona', e.target, states.previewing);
}
});
};
/* Should be called on an anchor. */
$.fn.personasButton = function(trigger, callback) {
$(this).closest('.persona').click(function(e) {
dispatchPersonaEvent('SelectPersona', e.currentTarget, callback);
return false;
});
};

Просмотреть файл

@ -47,7 +47,9 @@
'lite': gettext("Experimental <span>(Learn More)</span>"), 'lite': gettext("Experimental <span>(Learn More)</span>"),
'badApp': format(gettext("Not Available for {0}"), z.appName), 'badApp': format(gettext("Not Available for {0}"), z.appName),
'badPlatform': format(gettext("Not Available for {0}"), z.platformName), 'badPlatform': format(gettext("Not Available for {0}"), z.platformName),
'experimental': gettext("Experimental") 'experimental': gettext("Experimental"),
'personasTooOld': format(gettext("Personas Require Newer Version of {0}"), z.appName),
'personasLearnMore': format(gettext("Personas Require {0}"), z.appName)
}; };
function Button(el) { function Button(el) {
@ -86,6 +88,9 @@
versionPlatformCheck(); versionPlatformCheck();
this.actionQueue.push([0, function() { this.actionQueue.push([0, function() {
if (self.classes.persona) {
return;
}
var href = activeInstaller.attr('href'), var href = activeInstaller.attr('href'),
hash = hashes[href], hash = hashes[href],
attr = self.attr, attr = self.attr,
@ -187,9 +192,7 @@
platformer = !!b.find('.platform').length, platformer = !!b.find('.platform').length,
platformSupported = !platformer || dom.buttons.filter("." + z.platform).length, platformSupported = !platformer || dom.buttons.filter("." + z.platform).length,
appSupported = z.appMatchesUserAgent && attr.min && attr.max, appSupported = z.appMatchesUserAgent && attr.min && attr.max,
olderBrowser, newerBrowser,
canInstall = true; canInstall = true;
if (!attr.search) { if (!attr.search) {
// min and max only exist if the add-on is compatible with request[APP]. // min and max only exist if the add-on is compatible with request[APP].
if (appSupported && platformSupported) { if (appSupported && platformSupported) {
@ -202,13 +205,16 @@
if (self.tooOld) errors.push("tooOld"); if (self.tooOld) errors.push("tooOld");
if (self.tooNew) errors.push("tooNew"); if (self.tooNew) errors.push("tooNew");
} else { } else {
if (!appSupported && !z.badBrowser) errors.push("badApp"); if (!z.appMatchesUserAgent && !z.badBrowser) {
errors.push("badApp");
canInstall = false;
}
if (!platformSupported) { if (!platformSupported) {
errors.push("badPlatform"); errors.push("badPlatform");
dom.buttons.hide().eq(0).show(); dom.buttons.hide().eq(0).show();
}
canInstall = false; canInstall = false;
} }
}
if (platformer) { if (platformer) {
dom.self.find(format(".platform:not(.{0})", z.platform)).hide(); dom.self.find(format(".platform:not(.{0})", z.platform)).hide();
@ -226,6 +232,25 @@
self.actionQueue.push([1,z.eula.show]); self.actionQueue.push([1,z.eula.show]);
z.eula.acceptButton.click(_pd(self.resumeInstall)); z.eula.acceptButton.click(_pd(self.resumeInstall));
} }
if (classes.persona) {
dom.buttons.removeClass("download").addClass("add");
var persona = new MobilePersona(b);
if ($.hasPersonas()) {
dom.buttons.text(gettext("Keep it"));
dom.buttons.personasButton("click",
persona.states().installed);
} else {
persona.buttons().disable();
dom.buttons.addClass("disabled");
if (z.appMatchesUserAgent) {
// Need upgrade.
errors.push("personasTooOld");
} else {
errors.push("personasLearnMore");
}
}
}
} }
if (z.badBrowser) { if (z.badBrowser) {

Просмотреть файл

@ -1,35 +1,9 @@
$(document).ready(function() { $(document).ready(function() {
var personas = $('.persona-preview'); var personas = $('.persona-preview');
if (!personas.length) return; if (!personas.length) return;
personas.previewPersona();
personas.previewPersona(true);
}); });
/**
* Bubbles up persona event to tell Firefox to load a persona
**/
function dispatchPersonaEvent(aType, aNode)
{
var aliases = {'PreviewPersona': 'PreviewBrowserTheme',
'ResetPersona': 'ResetBrowserThemePreview',
'SelectPersona': 'InstallBrowserTheme'};
try {
if (!aNode.hasAttribute("data-browsertheme"))
return;
$(aNode).attr("persona", $(aNode).attr("data-browsertheme"));
var aliasEvent = aliases[aType];
var events = [aType, aliasEvent];
for (var i=0; i<events.length; i++) {
var event = events[i];
var eventObject = document.createEvent("Events");
eventObject.initEvent(event, true, false);
aNode.dispatchEvent(eventObject);
}
} catch(e) {}
}
/** /**
* Binds Personas preview events to the element. * Binds Personas preview events to the element.
@ -37,29 +11,36 @@ function dispatchPersonaEvent(aType, aNode)
* Mouseenter - bubbles up PreviewPersona * Mouseenter - bubbles up PreviewPersona
* Mouseleave - bubbles up ResetPersona * Mouseleave - bubbles up ResetPersona
**/ **/
$.fn.previewPersona = function(resetOnClick) { $.fn.previewPersona = function(o) {
if (resetOnClick) { if (!$.hasPersonas()) {
$(this).click(function(e) { return;
dispatchPersonaEvent('ResetPersona', e.originalTarget); }
o = $.extend({
resetOnClick: true,
activeClass: 'persona-hover'
}, o || {});
var $this = $(this);
if (o.resetOnClick) {
$this.click(function(e) {
dispatchPersonaEvent('ResetPersona', e.target);
}); });
} }
$this.hoverIntent({
$(this).hoverIntent({
interval: 100, interval: 100,
over: function(e) { over: function(e) {
$(this).closest('.persona').addClass('persona-hover'); $(this).closest('.persona').addClass(o.activeClass);
dispatchPersonaEvent('PreviewPersona', e.originalTarget); dispatchPersonaEvent('PreviewPersona', e.target);
}, },
out: function(e) { out: function(e) {
$(this).closest('.persona').removeClass('persona-hover'); $(this).closest('.persona').removeClass(o.activeClass);
dispatchPersonaEvent('ResetPersona', e.originalTarget); dispatchPersonaEvent('ResetPersona', e.target);
} }
}); });
}; };
/* Should be called on an anchor. */ /* Should be called on an anchor. */
$.fn.personasButton = function(options) { $.fn.personasButton = function(trigger, callback) {
var persona_wrapper = $(this).closest('.persona'); var persona_wrapper = $(this).closest('.persona');
persona_wrapper.hoverIntent({ persona_wrapper.hoverIntent({
interval: 100, interval: 100,
@ -71,31 +52,12 @@ $.fn.personasButton = function(options) {
} }
}); });
persona_wrapper.click(function(e) { persona_wrapper.click(function(e) {
dispatchPersonaEvent('SelectPersona', e.currentTarget); dispatchPersonaEvent('SelectPersona', e.currentTarget, callback);
return false; return false;
}); });
}; };
$.hasPersonas = function() {
if (!jQuery.browser.mozilla) return false;
// Fx 3.6 has lightweight themes (aka personas)
if (VersionCompare.compareVersions(
$.browser.version, '1.9.2') > -1) {
return true;
}
var body = document.getElementsByTagName("body")[0];
try {
var event = document.createEvent("Events");
event.initEvent("CheckPersonas", true, false);
body.dispatchEvent(event);
} catch(e) {}
return body.getAttribute("personas") == "true";
};
// Vertical carousel component // Vertical carousel component
// Based on jQuery Infinite Carousel // Based on jQuery Infinite Carousel
// http://jqueryfordesigners.com/jquery-infinite-carousel/ // http://jqueryfordesigners.com/jquery-infinite-carousel/

Просмотреть файл

@ -0,0 +1,48 @@
/**
* Bubbles up persona event to tell Firefox to load a persona
**/
function dispatchPersonaEvent(aType, aNode, callback)
{
var aliases = {'PreviewPersona': 'PreviewBrowserTheme',
'ResetPersona': 'ResetBrowserThemePreview',
'SelectPersona': 'InstallBrowserTheme'};
try {
if (!aNode.hasAttribute("data-browsertheme"))
return;
$(aNode).attr("persona", $(aNode).attr("data-browsertheme"));
var aliasEvent = aliases[aType];
var events = [aType, aliasEvent];
for (var i=0; i<events.length; i++) {
var event = events[i];
var eventObject = document.createEvent("Events");
eventObject.initEvent(event, true, false);
aNode.dispatchEvent(eventObject);
}
if (callback) {
callback();
}
} catch(e) {}
}
$.hasPersonas = function() {
if (!jQuery.browser.mozilla) return false;
// Fx 3.6 has lightweight themes (aka personas)
if (VersionCompare.compareVersions(
$.browser.version, '1.9.2') > -1) {
return true;
}
var body = document.getElementsByTagName("body")[0];
try {
var event = document.createEvent("Events");
event.initEvent("CheckPersonas", true, false);
body.dispatchEvent(event);
} catch(e) {}
return body.getAttribute("personas") == "true";
};

Просмотреть файл

@ -468,6 +468,7 @@ MINIFY_BUNDLES = {
# Personas # Personas
'js/lib/jquery.hoverIntent.min.js', 'js/lib/jquery.hoverIntent.min.js',
'js/zamboni/personas_core.js',
'js/zamboni/personas.js', 'js/zamboni/personas.js',
# Collections # Collections
@ -520,6 +521,7 @@ MINIFY_BUNDLES = {
# Personas # Personas
'js/lib/jquery.hoverIntent.min.js', 'js/lib/jquery.hoverIntent.min.js',
'js/zamboni/personas_core.js',
'js/zamboni/personas.js', 'js/zamboni/personas.js',
# Collections # Collections
@ -548,6 +550,7 @@ MINIFY_BUNDLES = {
# Personas # Personas
'js/lib/jquery.hoverIntent.min.js', 'js/lib/jquery.hoverIntent.min.js',
'js/zamboni/personas_core.js',
'js/zamboni/personas.js', 'js/zamboni/personas.js',
'js/zamboni/debouncer.js', 'js/zamboni/debouncer.js',
@ -598,7 +601,9 @@ MINIFY_BUNDLES = {
'js/zamboni/format.js', 'js/zamboni/format.js',
'js/zamboni/mobile_buttons.js', 'js/zamboni/mobile_buttons.js',
'js/zamboni/truncation.js', 'js/zamboni/truncation.js',
'js/zamboni/mobile.js', 'js/zamboni/personas_core.js',
'js/zamboni/mobile/personas.js',
'js/zamboni/mobile/general.js',
), ),
'zamboni/stats': ( 'zamboni/stats': (
'js/lib/jquery-datepicker.js', 'js/lib/jquery-datepicker.js',

Просмотреть файл

@ -46,7 +46,7 @@
</h1> </h1>
</hgroup> </hgroup>
<div class="get-fx-message"> <div class="get-fx-message">
{{ _('You need Firefox to install addons. <a href="http://mozilla.com/firefox">Learn More&nbsp;&raquo;</a>') }} {{ _('You need Firefox to install add-ons. <a href="http://mozilla.com/firefox">Learn More&nbsp;&raquo;</a>') }}
</div> </div>
{% block back_link %} {% block back_link %}
<a href="{{ url('home') }}" id="home"> <a href="{{ url('home') }}" id="home">