add-on update compatibility modal (bug 592544)

This commit is contained in:
Chris Van 2010-10-28 14:04:20 -04:00
Родитель 946ded1b19
Коммит 75fc0864dc
16 изменённых файлов: 519 добавлений и 82 удалений

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

@ -502,6 +502,10 @@ class Addon(amo.models.ModelBase):
else:
return {}
def accepts_compatible_apps(self):
"""True if this add-on lists compatible apps."""
return self.type not in amo.NO_COMPAT
def incompatible_latest_apps(self):
"""Returns a list of applications with which this add-on is
incompatible (based on the latest version).

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

@ -251,7 +251,7 @@ class CompatForm(happyforms.ModelForm):
min = self.cleaned_data.get('min')
max = self.cleaned_data.get('max')
if not (min and max and min.version_int < max.version_int):
raise forms.ValidationError(_('Invalid version range'))
raise forms.ValidationError(_('Invalid version range.'))
return self.cleaned_data
@ -271,8 +271,8 @@ class BaseCompatFormSet(BaseModelFormSet):
def clean(self):
if any(self.errors):
return
apps = [f for f in self.forms
if not f.cleaned_data.get('DELETE', False)]
apps = filter(None, [f.cleaned_data for f in self.forms
if not f.cleaned_data.get('DELETE', False)])
if not apps:
raise forms.ValidationError(
_('Need at least one compatible application.'))

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

@ -0,0 +1,31 @@
{% if addon.accepts_compatible_apps() and addon.current_version %}
{% set apps = addon.incompatible_latest_apps() %}
{% set update_url = url('devhub.ajax.compat.update', addon.id, addon.current_version.id) %}
<a class="tooltip {% if apps %}compat-error{% else %}compat-update{% endif %}"
data-updateurl="{{ update_url }}" href="#"
title="{{ _('View and update application compatibility ranges.') }}">
{{ _('Compatibility') }}</a>
{% if apps %}
{% set app = apps[0] %}
<div class="compat-error-popup popup error hidden">
<div class="{{ app.short }} app">
<p class="msg">
{% trans app_name=app.pretty, app_version=app.latest_version %}
This add-on is incompatible with <b>{{ app_name }}
{{ app_version }}</b>, the latest release of {{ app_name }}.
Please consider updating your add-on's compatibility info, or
uploading a newer version of this add-on.
{% endtrans %}
</p>
<p>
<a href="#" class="button compat-update"
data-updateurl="{{ update_url }}">{{ _('Update Compatibility') }}</a>
<a href="{{ url('devhub.versions', addon.id) }}" class="button">
{{ _('Upload New Version') }}</a>
{% trans %}or <a href="#" class="close">Ignore</a>{% endtrans %}
</p>
</div>
</div>
{% endif %}
<div class="compat-update-modal modal hidden"></div>
{% endif %}

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

@ -0,0 +1,38 @@
{% from "devhub/includes/macros.html" import compat %}
<h3>{{ _('Update Compatibility') }}</h3>
<p>
{% trans %}
Adjusting application information here will allow users to install
your add-on even if the install manifest in the package indicates
that the add-on is incompatible.
{% endtrans %}
</p>
<form action="{{ url('devhub.ajax.compat.update', addon.id, version.id) }}"
class="compat-versions" data-addonid="{{ addon.id }}">
{{ csrf() }}
{{ compat_form.non_form_errors()|safe }}
{{ compat_form.management_form|safe }}
<table>
<thead>
<th>{{ _('Application') }}</th>
<th>{{ _('Supported Versions') }}</th>
<th>{{ _('Remove') }}</th>
</thead>
<tbody>
{% for form in compat_form.initial_forms %}
{{ compat(form) }}
{% endfor %}
{% for form in compat_form.extra_forms %}
{{ compat(form, is_extra_form=True) }}
{% endfor %}
</tbody>
</table>
<p class="add-app">
<a href="#">{{ _('Add Another Application&hellip;')|safe }}</a>
</p>
<div class="new-apps"></div>
<p class="compat-save">
<button type="submit">{{ _('Save Changes') }}</button>
{% trans %}or <a href="#" class="close">Cancel</a>{% endtrans %}
</p>
</form>

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

@ -2,12 +2,12 @@
{% set collection = collection or None %}
{% set username = request.amo_user.username if request.user.is_authenticated() else '' %}
{% for addon in addons %}
<div class="item">
<div class="item" data-addonid="{{ addon.id }}">
{{ dev_heading(addon) }}
<div class="item-info">
{{ dev_item_info(addon, amo) }}
</div>
<blockquote>
<div class="item-main">
<ul class="item-details">
{# L10n: {0} is a version number. #}
<li>{{ _('<strong>Latest version:</strong> {0}'|
@ -33,6 +33,6 @@
<div class="item-actions">
{{ dev_item_actions(addon, amo) }}
</div>
</blockquote>
</div>
</div>
{% endfor %}

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

@ -39,16 +39,13 @@
{{ _('Edit Listing') }}</a>
</li>
<li>
{# TODO: Add URL for 'Manage Version' page. #}
<a href="#" class="tooltip"
<a href="{{ url('devhub.versions', addon.id) }}" class="tooltip"
title="{{ _('Upload a new version of this add-on.') }}">
{{ _('New Version') }}</a>
</li>
<li>
<a href="#"
class="tooltip{% if addon.incompatible_latest_apps() %} compat-error{% endif %}"
title="{{ _('View and update application compatibility ranges.') }}">
{{ _('Compatibility') }}</a>
<li class="compat"
data-src="{{ url('devhub.ajax.compat.status', addon.id) }}">
{% include "devhub/addons/ajax_compat_status.html" %}
</li>
<li>
<a href="{{ url('stats.overview', addon.id) }}" class="tooltip"
@ -56,18 +53,18 @@
{{ _('Statistics') }}</a>
</li>
<li>
<a href="#" class="more-actions-view more-actions">{{ _('More') }}</a>
<div class="more-actions-view-dropdown popup hidden">
<ul>
<a href="#" class="more-actions">{{ _('More') }}</a>
<div class="more-actions-popup popup hidden">
<ul>
{# TODO: Add URLs for 'Manage Add-on' pages. #}
<li><a href="#">{{ _('Manage Authors & License') }}</a></li>
<li><a href="#">{{ _('Manage Payments') }}</a></li>
<li><a href="#">{{ _('Manage Status & Versions') }}</a></li>
<li class="item-group"><a href="#">
{{ _('View Add-on Listing') }}</a></li>
<li><a href="#">{{ _('View Recent Changes') }}</a></li>
</ul>
</div>
<li><a href="#">{{ _('Manage Authors & License') }}</a></li>
<li><a href="#">{{ _('Manage Payments') }}</a></li>
<li><a href="#">{{ _('Manage Status & Versions') }}</a></li>
<li class="item-group"><a href="#">
{{ _('View Add-on Listing') }}</a></li>
<li><a href="#">{{ _('View Recent Changes') }}</a></li>
</ul>
</div>
</li>
</ul>
{% endmacro %}

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

@ -27,3 +27,29 @@
{% endif %}
{% endmacro %}
{% macro compat(form, is_extra_form=False) %}
<tr{% if is_extra_form %} class="app-extra-orig{{ ' app-extra' if not form.errors }}"{% endif %}>
<td class="app {{ form.app.short }}">{{ form.app.pretty }}</td>
<td class="select">
<div>
<label>{{ form.min.label|safe }}</label> {{ form.min|safe }}
{{ form.non_field_errors()|safe }}
{{ form.min.errors|safe }}
</div>
<div><span class="range">&ndash;</span></div>
<div>
<label>{{ form.max.label|safe }}</label> {{ form.max|safe }}
{{ form.max.errors|safe }}
</div>
</td>
<td class="remove">
<div>
<label>{{ form.DELETE.label|safe }} {{ form.DELETE|safe }}</label>
{{ form.application|safe }}
{{ form.id|safe }}
<a href="#" class="remove"
title="{{ _('Remove this application') }}">x</a>
</div>
</td>
</td>
{% endmacro %}

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

@ -47,6 +47,7 @@
</table>
<table>
{% if compat_form %}
{{ compat_form.non_form_errors()|safe }}
{{ compat_form.management_form|safe }}
{% for form in compat_form.initial_forms %}
{{ compat(form) }}

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

@ -68,4 +68,3 @@ class TestActivityLog(test_utils.TestCase):
eq_(len(entries), 1)
entries = ActivityLog.objects.for_user(u)
eq_(len(entries), 1)

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

@ -20,11 +20,11 @@ from devhub.forms import ContribForm
from devhub.models import ActivityLog
from files.models import File, Platform
from users.models import UserProfile
from versions.models import License, Version
from versions.models import ApplicationsVersions, License, Version
class HubTest(test_utils.TestCase):
fixtures = ('browse/nameless-addon', 'base/users')
fixtures = ['browse/nameless-addon', 'base/users']
def setUp(self):
translation.activate('en-US')
@ -131,10 +131,7 @@ class TestDashboard(HubTest):
r = self.client.get(self.url)
doc = pq(r.content)
# There should be 10 add-on listing items.
eq_(len(doc('.item .item-info')), 10)
# There should be neither a listing header nor a pagination footer.
eq_(doc('#addon-list-options').length, 0)
eq_(doc('.listing-footer .pagination').length, 0)
@ -144,14 +141,60 @@ class TestDashboard(HubTest):
r = self.client.get(self.url + '?page=2')
doc = pq(r.content)
# There should be 10 add-on listing items.
eq_(len(doc('.item .item-info')), 5)
# There should be a listing header and pagination footer.
eq_(doc('#addon-list-options').length, 1)
eq_(doc('.listing-footer .pagination').length, 1)
class TestUpdateCompatibility(test_utils.TestCase):
fixtures = ['base/apps', 'base/users', 'base/addon_4594_a9',
'base/addon_3615']
def setUp(self):
self.url = reverse('devhub.addons')
def test_no_compat(self):
assert self.client.login(username='admin@mozilla.com',
password='password')
r = self.client.get(self.url)
doc = pq(r.content)
assert not doc('.item[data-addonid=4594] .tooltip.compat-update')
a = Addon.objects.get(pk=4594)
r = self.client.get(reverse('devhub.ajax.compat.update',
args=[a.id, a.current_version.id]))
eq_(r.status_code, 404)
def test_compat(self):
a = Addon.objects.get(pk=3615)
assert self.client.login(username='del@icio.us', password='password')
r = self.client.get(self.url)
doc = pq(r.content)
cu = doc('.item[data-addonid=3615] .tooltip.compat-update')
assert cu
update_url = reverse('devhub.ajax.compat.update',
args=[a.id, a.current_version.id])
eq_(cu.attr('data-updateurl'), update_url)
status_url = reverse('devhub.ajax.compat.status', args=[a.id])
eq_(doc('.item[data-addonid=3615] li.compat').attr('data-src'),
status_url)
assert doc('.item[data-addonid=3615] .compat-update-modal')
def test_incompat(self):
av = ApplicationsVersions.objects.get(pk=47881)
av.max = AppVersion.objects.get(pk=97) # Firefox 2.0
av.save()
assert self.client.login(username='del@icio.us', password='password')
r = self.client.get(self.url)
doc = pq(r.content)
assert doc('.item[data-addonid=3615] .tooltip.compat-error')
assert doc('.item[data-addonid=3615] .compat-error-popup .app.%s' %
amo.FIREFOX.short)
def formset(*args, **kw):
"""
Build up a formset-happy POST.
@ -1060,7 +1103,7 @@ class TestVersionEditCompat(TestVersionEdit):
r = self.client.post(self.url, self.formset(d, initial_count=1))
eq_(r.status_code, 200)
eq_(r.context['compat_form'].forms[0].non_field_errors(),
['Invalid version range'])
['Invalid version range.'])
class TestAddonSubmission(test_utils.TestCase):

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

@ -29,11 +29,20 @@ detail_patterns = patterns('',
views.submit_finished, name='devhub.submit.finished'),
)
ajax_patterns = patterns('',
url('^versions/compatibility/status$',
views.ajax_compat_status, name='devhub.ajax.compat.status'),
url('^versions/(?P<version_id>\d+)/compatibility$',
views.ajax_compat_update, name='devhub.ajax.compat.update'),
)
urlpatterns = decorate(write, patterns('',
url('^$', views.index, name='devhub.index'),
# URLs for a single add-on.
('^addon/(?P<addon_id>\d+)/', include(detail_patterns)),
('^ajax/addon/(?P<addon_id>\d+)/', include(ajax_patterns)),
# Redirect people who have /addons/ instead of /addon/.
('^addons/\d+/.*',
lambda r: redirect(r.path.replace('addons', 'addon', 1))),

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

@ -92,6 +92,28 @@ def dashboard(request):
'sort_opts': filter.opts})
@dev_required
def ajax_compat_status(request, addon_id, addon):
return jingo.render(request, 'devhub/addons/ajax_compat_status.html',
dict(addon=addon))
@dev_required
def ajax_compat_update(request, addon_id, addon, version_id):
if not addon.accepts_compatible_apps():
raise http.Http404()
version = get_object_or_404(Version, pk=version_id, addon=addon)
compat_form = forms.CompatFormSet(request.POST or None,
queryset=version.apps.all())
if request.method == 'POST' and compat_form.is_valid():
for compat in compat_form.save(commit=False):
compat.version = version
compat.save()
return jingo.render(request, 'devhub/addons/ajax_compat_update.html',
dict(addon=addon, version=version,
compat_form=compat_form))
@login_required
def activity(request):
return jingo.render(request, 'devhub/addons/activity.html')
@ -297,7 +319,7 @@ def version_edit(request, addon_id, addon, version_id):
file_form = forms.FileFormSet(request.POST or None, prefix='files',
queryset=version.files.all())
data= {'version_form': version_form, 'file_form': file_form}
data = {'version_form': version_form, 'file_form': file_form}
# https://bugzilla.mozilla.org/show_bug.cgi?id=605941
# remove compatability from the version edit page for search engines

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

@ -36,6 +36,7 @@
#edit-addon .listing-footer {
-moz-border-radius: 0 0 8px 8px;
-webkit-border-radius: 0 0 8px 8px;
border-radius: 0 0 8px 8px;
border-top: 1px solid #C9E8F3;
padding: 9px;
}
@ -82,7 +83,7 @@
}
#edit-addon .help,
#edit-addon .remove {
a.remove {
-moz-border-radius: 20px;
-webkit-border-radius :20px;
border-radius: 20px;
@ -99,7 +100,7 @@
width: 18px;
}
#edit-addon .remove {
a.remove {
cursor: pointer;
line-height: 16px;
float: right;
@ -107,6 +108,7 @@
}
#edit-addon .author:hover .remove,
a.remove:hover,
#edit-addon tr:hover .help {
background-color: #CCC;
}
@ -158,7 +160,107 @@
/* @end */
/* @group Version Compatibility */
.compat-update-modal {
min-height: 425px;
}
.compat-update-modal > :first-child {
margin-top: 0;
}
.compat-update-modal > :last-child {
margin-bottom: 0;
}
.compat-versions table {
width: 100%;
}
.compat-versions tbody {
border: 1px solid #000;
border-width: 1px 0;
}
.compat-versions thead th {
color: #888;
}
.compat-versions tbody tr:not(:last-child) {
border-bottom: 1px dotted #a5bfce;
}
.compat-versions td.app,
.new-apps a.app {
font-weight: bold;
min-height: 24px;
padding-left: 35px;
}
.compat-versions td.app {
background-position: 0 0.5em;
-moz-background-size: auto 24px;
-wekbkit-background-size: auto 24px;
background-size: auto 24px;
line-height: 30px;
vertical-align: top;
}
.new-apps a.app {
background-position: 0 50%;
-moz-background-size: auto 16px;
-wekbkit-background-size: auto 16px;
background-size: auto 16px;
line-height: 24px;
}
.compat-versions p.add-app {
margin-top: -1em;
}
.compat-versions p.add-app a,
.new-apps a.app {
display: block;
}
.compat-versions p.add-app + .new-apps {
margin: -1em 0 1em;
}
.compat-versions thead th,
.compat-versions table + p,
.compat-versions .errorlist {
font-size: 0.85em;
}
.compat-versions .errorlist {
margin-bottom: 0;
}
.compat-versions td.select {
display: table-row;
}
.compat-versions td.select div {
display: table-cell;
padding: 0 0.5em 0;
}
.compat-versions td.select div:first-child {
padding-left: 0;
}
.compat-versions td.select select {
width: 120px;
}
.compat-versions td label,
.compat-versions td.remove input {
position: absolute;
left: -9999px;
}
.compat-versions td.remove div {
position: relative;
top: 0.308em;
}
.compat-versions tr.app-extra,
.compat-versions .new-apps {
display: none;
}
/* @end */
/* @group Dashboard */
.action-needed {
background-color: #fff;
border: 3px solid #c8e8f3;
@ -177,8 +279,7 @@
.action-needed .button-wrapper:last-child,
.item ul.item-details,
.item .item-actions ul,
.popup ul {
.item .item-actions ul {
margin-bottom: 0;
}
@ -201,14 +302,6 @@
color: #05e;
}
.compat-error {
background: url(../../img/zamboni/notifications.png) 0px -164px no-repeat;
padding-left: 15px;
}
.item-actions a.compat-error {
color: #c00;
}
.item-actions h5,
.item-actions>ul,
.item-actions>ul>li {
@ -217,6 +310,9 @@
.item-actions>ul>li {
margin-right: 4px;
}
.item-actions>ul>li:after {
margin: 0 0 4px 4px;
}
.item-actions>ul>li:not(:last-child):after {
background-color: #aaa;
-moz-border-radius: 5em;
@ -231,32 +327,25 @@
.item-actions:hover>ul>li:not(:last-child):after {
background-color: #555;
}
.item-actions ul li ul {
font-size: 1.1em;
}
.item-group {
border-top: 1px dotted #aecfda;
margin-top: 0.5em;
padding-top: 0.5em;
}
.item-actions h5 {
font-size: 1em;
margin-right: 8px;
margin-right: 4px;
}
.item-actions h5:after {
content: ":";
}
.listing .item blockquote {
overflow-x: visible; /* Undoing ``hidden`` so 'More' popup can breathe. */
a.more-actions {
position: relative;
}
a.more-actions:after {
border-color: #aaa transparent transparent;
border-style: solid;
border-width: 3px 3px 0;
content: "";
float: right;
position: absolute;
top: -2px;
height: 0;
margin: 8px 0 0 4px;
width: 0;
@ -268,6 +357,34 @@ a.more-actions:after:hover {
text-decoration: none;
}
.more-actions-popup li a {
display: block;
}
.more-actions-popup .item-group {
border-top: 1px dotted #aecfda;
margin-top: 0.5em;
padding-top: 0.5em;
}
/* @group Compatibility */
.compat-error {
background: url(../../img/zamboni/notifications.png) 0px -164px no-repeat;
padding-left: 15px;
}
.item-actions a.compat-error {
color: #c00;
}
.compat-error-popup.popup {
padding: 15px;
}
.compat-error-popup.popup .msg {
padding-left: 45px;
}
/* @end */
#submit-addon {
margin-bottom: 3em;
}
@ -275,7 +392,7 @@ a.more-actions:after:hover {
#submit-addon a.button:visited {
background: #5af;
background: -moz-linear-gradient(top, #acf, #5af);
background: -webkit-gradient(linear, left top, left bottom, #acf, #5af);
background: -webkit-gradient(linear, left top, left bottom, from(#acf), to(#5af));
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
border-radius: 8px;
@ -287,15 +404,15 @@ a.more-actions:after:hover {
#submit-addon a.button:hover,
#submit-addon a.button:focus {
border-color: #25f;
-moz-box-shadow: inset 0 0 2px rgba(255, 255, 255, 1);
-webkit-box-shadow: inset 0 0 2px rgba(255, 255, 255, 1);
box-shadow: inset 0 0 2px rgba(255, 255, 255, 1);
-moz-box-shadow: inset 0 0 2px #fff;
-webkit-box-shadow: inset 0 0 2px #fff;
box-shadow: inset 0 0 2px #fff;
}
#submit-addon a.button:active {
border-color: #03c;
background: #7bf;
background: -moz-linear-gradient(top, #5af, #7bf);
background: -webkit-gradient(linear, left top, left bottom, #39f, #7bf);
background: -webkit-gradient(linear, left top, left bottom, from(#5af), to(#7bf));
-moz-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.5);
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.5);
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.5);

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

@ -620,21 +620,23 @@ div.popup-shim {
display: inline-block;
}
.popup p:last-child {
.popup p:last-child,
.popup ul:last-child {
margin-bottom: 0;
}
.install-note .msg,
.popup .msg {
.popup .msg,
.app {
background-repeat: no-repeat;
min-height: 32px;
}
.firefox .install-note .msg { background-image: url(../../img/amo2009/app-icons/small/firefox.png); }
.thunderbird .install-note .msg { background-image: url(../../img/amo2009/app-icons/small/thunderbird.png); }
.mobile .install-note .msg { background-image: url(../../img/amo2009/app-icons/small/mobile.png); }
.sunbird .install-note .msg { background-image: url(../../img/amo2009/app-icons/small/sunbird.png); }
.seamonkey .install-note .msg { background-image: url(../../img/amo2009/app-icons/small/seamonkey.png); }
.firefox .install-note .msg, .firefox.app { background-image: url(../../img/amo2009/app-icons/small/firefox.png); }
.thunderbird .install-note .msg, .thunderbird.app { background-image: url(../../img/amo2009/app-icons/small/thunderbird.png); }
.mobile .install-note .msg, .mobile.app { background-image: url(../../img/amo2009/app-icons/small/mobile.png); }
.sunbird .install-note .msg, .sunbird.app { background-image: url(../../img/amo2009/app-icons/small/sunbird.png); }
.seamonkey .install-note .msg, .seamonkey.app { background-image: url(../../img/amo2009/app-icons/small/seamonkey.png); }
.install-note .msg.m-unreviewed,
.install-note .msg.m-selfhosted,
@ -652,7 +654,6 @@ div.popup-shim {
position: absolute;
left: -15px;
margin-top: 5px;
z-index: 999;
background: #fff;
padding: 10px;
border: 3px solid #2e5186;
@ -691,6 +692,24 @@ div.popup-shim {
border-color: #c90;
}
.popup.error {
border-color: #6c1a1a;
color: #333;
}
.modal-overlay {
background-color: #fff;
height: 100%;
-khtml-opacity: 0.75;
-moz-opacity: 0.75;
opacity: 0.75;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2000;
}
/* http://nicolasgallagher.com/demo/pure-css-speech-bubbles/bubbles.html */
/* Creates the larger triangle. */
.install-note:before,
@ -712,6 +731,10 @@ div.popup-shim {
border-bottom-color: #c90;
}
.popup.error:before {
border-bottom-color: #6c1a1a;
}
/* creates the smaller triangle */
.install-note:after,
.popup:after {
@ -2980,7 +3003,8 @@ a.outlink:hover {
.widgets .widget.ajax-loading,
.widgets .widget:hover.ajax-loading,
input.ui-autocomplete-loading {
input.ui-autocomplete-loading,
.modal.ajax-loading {
background-image: url(../../img/zamboni/loading-white.gif);
background-repeat: no-repeat;
background-position: left bottom;
@ -2988,6 +3012,9 @@ input.ui-autocomplete-loading {
input.ui-autocomplete-loading {
background-position: right center;
}
.modal.ajax-loading {
background-position: center center;
}
#popup-staging .popup {
display: none;

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

@ -23,7 +23,9 @@ $(document).ready(function() {
$(document).ready(function() {
$(".more-actions-view-dropdown").popup(".more-actions-view", {
$.ajaxSetup({cache: false});
$('.more-actions-popup').popup('.more-actions', {
width: 'inherit',
offset: {x: 15},
callback: function(obj) {
@ -32,6 +34,9 @@ $(document).ready(function() {
});
truncateFields();
initCompatibility();
$('#edit-addon').delegate('h3 a', 'click', function(e){
e.preventDefault();
@ -484,6 +489,7 @@ function initAuthorFields() {
}
}
function resetModal(obj) {
upload = $("<input type='file'>").attr('name', 'upload')
@ -504,3 +510,112 @@ function resetModal(obj) {
return true;
}
function initCompatibility() {
$('p.add-app a').live('click', function(e) {
e.preventDefault();
var outer = $(this).closest('form');
$('tr.app-extra', outer).each(function() {
addAppRow(this);
});
$('.new-apps', outer).toggle();
$('.new-apps ul').delegate('a', 'click', function(e) {
e.preventDefault();
var extraAppRow = $('tr.app-extra td[class=' + $(this).attr('class') + ']', outer);
extraAppRow.parents('tr').find('input:checkbox').removeAttr('checked')
.closest('tr').removeClass('app-extra');
$(this).closest('li').remove();
if (!$('tr.app-extra', outer).length)
$('p.add-app', outer).hide();
});
});
$('.compat-versions .remove').live('click', function(e) {
e.preventDefault();
var appRow = $(this).closest('tr');
appRow.addClass('app-extra');
if (!appRow.hasClass('app-extra-orig'))
appRow.find('input:checkbox').attr('checked', true);
$('p.add-app:hidden', $(this).closest('form')).show();
addAppRow(appRow);
});
$('.compat-update-modal').modal('a.compat-update', {
delegate: $('.item-actions li.compat, .compat-error-popup'),
hideme: false,
emptyme: true,
callback: compatModalCallback
});
$('.compat-error-popup').popup('.compat-error', {
width: '450px',
callback: function(obj) {
var $popup = this;
$popup.delegate('.close, .compat-update', 'click', function(e) {
e.preventDefault();
$popup.hideMe();
});
return {pointTo: $(obj.click_target)};
}
});
}
function addAppRow(obj) {
var outer = $(obj).closest('form'),
appClass = $('td.app', obj).attr('class');
if (!$('.new-apps ul', outer).length)
$('.new-apps', outer).html('<ul></ul>');
if ($('.new-apps ul a[class=' + appClass + ']', outer).length)
return;
var appLabel = $('td.app', obj).text(),
appHTML = '<li><a href="#" class="' + appClass + '">' + appLabel + '</a></li>';
$('.new-apps ul', outer).append(appHTML);
}
function compatModalCallback(obj) {
var $widget = this,
ct = $(obj.click_target),
form_url = ct.attr('data-updateurl');
if ($widget.hasClass('ajax-loading'))
return;
$widget.addClass('ajax-loading');
$widget.load(form_url, function(e) {
$widget.removeClass('ajax-loading');
});
$('form.compat-versions').live('submit', function(e) {
e.preventDefault();
$widget.empty();
if ($widget.hasClass('ajax-loading'))
return;
$widget.addClass('ajax-loading');
var widgetForm = $(this);
$.post(widgetForm.attr('action'), widgetForm.serialize(), function(data) {
$widget.removeClass('ajax-loading');
if ($(data).find('.errorlist').length) {
$widget.html(data);
} else {
var c = $('.item[data-addonid=' + widgetForm.attr('data-addonid') + '] .item-actions li.compat');
c.load(c.attr('data-src'));
$widget.hideMe();
}
});
});
return {pointTo: ct};
}

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

@ -44,7 +44,7 @@ jQuery.fn.tooltip = function(tip_el) {
}).live("mouseout", function (e) {
$tip.hide()
.removeClass("error");
if ($title.length) {
if ($title && $title.length) {
$tgt = $(this);
$title.attr('title', $title.attr('data-oldtitle'))
.attr('data-oldtitle', '');
@ -195,18 +195,19 @@ $.fn.popup = function(click_target, o) {
return $popup;
};
// makes an element into a modal.
// click_target defines the element/elements that trigger the popup.
// click_target defines the element/elements that trigger the modal.
// currently presumes the given element uses the '.modal' style
// o takes the following optional fields:
// callback: a function to run before displaying the popup. Returning
// false from the function cancels the popup.
// container: if set the popup will be appended to the container before
// callback: a function to run before displaying the modal. Returning
// false from the function cancels the modal.
// container: if set the modal will be appended to the container before
// being displayed.
// width: the width of the popup.
// width: the width of the modal.
// delegate: delegates the click handling of the click_target to the
// specified parent element.
// hideme: defaults to true, if set to false, popup will not be hidden
// hideme: defaults to true, if set to false, modal will not be hidden
// when the user clicks outside of it.
// note: all options may be overridden and modified by returning them in an
// object from the callback.
@ -221,8 +222,9 @@ $.fn.modal = function(click_target, o) {
callback: false,
onresize: function(){$modal.setPos();},
hideme: true,
emptyme: false,
offset: {},
width: 300
width: 450
}, o);
$modal.setWidth = function(w) {
@ -247,11 +249,16 @@ $.fn.modal = function(click_target, o) {
};
$modal.hideMe = function() {
var p = $modal.o;
$modal.hide();
$modal.unbind();
$modal.undelegate();
$(document.body).unbind('click newmodal', $modal.hider);
$(window).bind('resize', $modal.o.onresize);
$('.modal-overlay').remove();
if (p.emptyme) {
$modal.empty();
}
return $modal;
};
@ -282,6 +289,7 @@ $.fn.modal = function(click_target, o) {
$ct.trigger("modal_show", [$modal]);
if (p.container && p.container.length)
$modal.detach().appendTo(p.container);
$('<div class="modal-overlay"></div>').appendTo('body');
$modal.setPos();
setTimeout(function(){
$modal.show();