Remove unused/obsolete JavaScript (#18755)
* Remove unused/obsolete JavaScript Could do more, but that's a good chunk already... * Remove files no longer referenced * Remove obsolete calls to removed functions
This commit is contained in:
Родитель
4d7507bc50
Коммит
97f9d82cbc
|
@ -29,36 +29,18 @@ NODE_LIBS_CSS := \
|
|||
# NODE_LIBS_JS and NODE_LIBS_JQUERY_UI are referenced in settings.MINIFY_BUNDLES - keep both lists in sync
|
||||
NODE_LIBS_JS := \
|
||||
less/dist/less.js \
|
||||
jqmodal/jqModal.js \
|
||||
jquery/dist/jquery.js \
|
||||
jquery.browser/dist/jquery.browser.js \
|
||||
jquery.cookie/jquery.cookie.js \
|
||||
@claviska/jquery-minicolors/jquery.minicolors.js \
|
||||
jquery-pjax/jquery.pjax.js \
|
||||
jszip/dist/jszip.js \
|
||||
timeago/jquery.timeago.js \
|
||||
underscore/underscore.js \
|
||||
|
||||
NODE_LIBS_JQUERY_UI := \
|
||||
jquery-ui/ui/data.js \
|
||||
jquery-ui/ui/disable-selection.js \
|
||||
jquery-ui/ui/focusable.js \
|
||||
jquery-ui/ui/ie.js \
|
||||
jquery-ui/ui/jquery-patch.js \
|
||||
jquery-ui/ui/keycode.js \
|
||||
jquery-ui/ui/labels.js \
|
||||
jquery-ui/ui/plugin.js \
|
||||
jquery-ui/ui/position.js \
|
||||
jquery-ui/ui/safe-active-element.js \
|
||||
jquery-ui/ui/safe-blur.js \
|
||||
jquery-ui/ui/scroll-parent.js \
|
||||
jquery-ui/ui/tabbable.js \
|
||||
jquery-ui/ui/unique-id.js \
|
||||
jquery-ui/ui/version.js \
|
||||
jquery-ui/ui/widget.js \
|
||||
jquery-ui/ui/widgets/autocomplete.js \
|
||||
jquery-ui/ui/widgets/datepicker.js \
|
||||
jquery-ui/ui/widgets/menu.js \
|
||||
jquery-ui/ui/widgets/mouse.js \
|
||||
jquery-ui/ui/widgets/sortable.js
|
||||
|
||||
|
|
|
@ -658,153 +658,44 @@ MINIFY_BUNDLES = {
|
|||
# js/node_lib/* files are copied in Makefile-docker - keep both lists in sync
|
||||
'common': (
|
||||
'js/node_lib/underscore.js',
|
||||
'js/zamboni/browser.js',
|
||||
'js/amo2009/addons.js',
|
||||
'js/zamboni/init.js',
|
||||
'js/impala/capabilities.js',
|
||||
'js/zamboni/capabilities.js',
|
||||
'js/lib/format.js',
|
||||
'js/node_lib/jquery.cookie.js',
|
||||
'js/zamboni/storage.js',
|
||||
'js/zamboni/buttons.js',
|
||||
'js/zamboni/tabs.js',
|
||||
'js/common/keys.js',
|
||||
# jQuery UI
|
||||
'js/node_lib/ui/version.js',
|
||||
'js/node_lib/ui/data.js',
|
||||
'js/node_lib/ui/disable-selection.js',
|
||||
'js/node_lib/ui/ie.js',
|
||||
'js/node_lib/ui/keycode.js',
|
||||
'js/node_lib/ui/labels.js',
|
||||
'js/node_lib/ui/jquery-patch.js',
|
||||
'js/node_lib/ui/plugin.js',
|
||||
'js/node_lib/ui/safe-active-element.js',
|
||||
'js/node_lib/ui/safe-blur.js',
|
||||
'js/node_lib/ui/scroll-parent.js',
|
||||
'js/node_lib/ui/focusable.js',
|
||||
'js/node_lib/ui/tabbable.js',
|
||||
'js/node_lib/ui/unique-id.js',
|
||||
'js/node_lib/ui/position.js',
|
||||
'js/node_lib/ui/widget.js',
|
||||
'js/node_lib/ui/menu.js',
|
||||
'js/node_lib/ui/mouse.js',
|
||||
'js/node_lib/ui/autocomplete.js',
|
||||
'js/node_lib/ui/sortable.js',
|
||||
'js/zamboni/helpers.js',
|
||||
'js/common/banners.js',
|
||||
'js/zamboni/global.js',
|
||||
'js/amo2009/global.js',
|
||||
'js/common/ratingwidget.js',
|
||||
'js/node_lib/jqModal.js',
|
||||
'js/zamboni/l10n.js',
|
||||
'js/zamboni/debouncer.js',
|
||||
# Homepage
|
||||
'js/zamboni/homepage.js',
|
||||
# Add-ons details page
|
||||
'js/impala/abuse.js',
|
||||
'js/zamboni/ratings.js',
|
||||
# Unicode letters for our makeslug function
|
||||
'js/zamboni/unicode.js',
|
||||
# Users
|
||||
# Login tweaks
|
||||
'js/zamboni/users.js',
|
||||
# Search suggestions
|
||||
'js/impala/forms.js',
|
||||
'js/impala/ajaxcache.js',
|
||||
'js/impala/suggestions.js',
|
||||
'js/impala/site_suggestions.js',
|
||||
),
|
||||
# Impala and Legacy: Things to be loaded at the top of the page
|
||||
# Things to be loaded at the top of the page
|
||||
'preload': (
|
||||
'js/node_lib/jquery.js',
|
||||
'js/node_lib/jquery.browser.js',
|
||||
'js/impala/preloaded.js',
|
||||
'js/zamboni/analytics.js',
|
||||
),
|
||||
# Impala: Things to be loaded at the bottom
|
||||
'impala': (
|
||||
'js/lib/ngettext-overload.js',
|
||||
'js/node_lib/underscore.js',
|
||||
'js/impala/carousel.js',
|
||||
'js/zamboni/browser.js',
|
||||
'js/amo2009/addons.js',
|
||||
'js/zamboni/init.js',
|
||||
'js/impala/capabilities.js',
|
||||
'js/lib/format.js',
|
||||
'js/node_lib/jquery.cookie.js',
|
||||
'js/zamboni/storage.js',
|
||||
'js/zamboni/buttons.js',
|
||||
'js/node_lib/jquery.pjax.js',
|
||||
# jquery.pjax.js is missing a semicolon at the end which breaks
|
||||
# our wonderful minification process... so add one.
|
||||
'js/lib/semicolon.js', # It's just a semicolon!
|
||||
'js/impala/footer.js',
|
||||
'js/common/keys.js',
|
||||
# jQuery UI
|
||||
'js/node_lib/ui/version.js',
|
||||
'js/node_lib/ui/data.js',
|
||||
'js/node_lib/ui/disable-selection.js',
|
||||
'js/node_lib/ui/ie.js',
|
||||
'js/node_lib/ui/keycode.js',
|
||||
'js/node_lib/ui/labels.js',
|
||||
'js/node_lib/ui/jquery-patch.js',
|
||||
'js/node_lib/ui/plugin.js',
|
||||
'js/node_lib/ui/safe-active-element.js',
|
||||
'js/node_lib/ui/safe-blur.js',
|
||||
'js/node_lib/ui/scroll-parent.js',
|
||||
'js/node_lib/ui/focusable.js',
|
||||
'js/node_lib/ui/tabbable.js',
|
||||
'js/node_lib/ui/unique-id.js',
|
||||
'js/node_lib/ui/position.js',
|
||||
'js/node_lib/ui/widget.js',
|
||||
'js/node_lib/ui/mouse.js',
|
||||
'js/node_lib/ui/menu.js',
|
||||
'js/node_lib/ui/autocomplete.js',
|
||||
'js/node_lib/ui/datepicker.js',
|
||||
'js/node_lib/ui/sortable.js',
|
||||
'js/lib/truncate.js',
|
||||
'js/zamboni/truncation.js',
|
||||
'js/impala/ajaxcache.js',
|
||||
'js/zamboni/helpers.js',
|
||||
'js/common/banners.js',
|
||||
'js/zamboni/global.js',
|
||||
'js/impala/global.js',
|
||||
'js/common/ratingwidget.js',
|
||||
'js/node_lib/jqModal.js',
|
||||
'js/zamboni/l10n.js',
|
||||
'js/impala/forms.js',
|
||||
# Add-ons details page
|
||||
'js/impala/abuse.js',
|
||||
'js/impala/ratings.js',
|
||||
# Browse listing pages
|
||||
'js/impala/listing.js',
|
||||
'js/lib/jquery.hoverIntent.js',
|
||||
'js/common/upload-image.js',
|
||||
'js/node_lib/jquery.minicolors.js',
|
||||
# Unicode letters for our makeslug function
|
||||
'js/zamboni/unicode.js',
|
||||
# Users
|
||||
'js/zamboni/users.js',
|
||||
'js/impala/users.js',
|
||||
# Search
|
||||
'js/impala/serializers.js',
|
||||
'js/impala/search.js',
|
||||
'js/impala/suggestions.js',
|
||||
'js/impala/site_suggestions.js',
|
||||
# Login
|
||||
'js/impala/login.js',
|
||||
),
|
||||
'zamboni/devhub': (
|
||||
'js/lib/truncate.js',
|
||||
'js/zamboni/truncation.js',
|
||||
'js/common/upload-base.js',
|
||||
'js/common/upload-addon.js',
|
||||
'js/common/upload-image.js',
|
||||
'js/impala/formset.js',
|
||||
'js/zamboni/devhub.js',
|
||||
'js/zamboni/validator.js',
|
||||
'js/node_lib/jquery.timeago.js',
|
||||
'js/zamboni/static_theme.js',
|
||||
'js/node_lib/jquery.minicolors.js',
|
||||
'js/node_lib/jszip.js',
|
||||
# jQuery UI for sortable
|
||||
'js/node_lib/ui/data.js',
|
||||
'js/node_lib/ui/scroll-parent.js',
|
||||
'js/node_lib/ui/widget.js',
|
||||
'js/node_lib/ui/mouse.js',
|
||||
'js/node_lib/ui/sortable.js',
|
||||
),
|
||||
'devhub/new-landing/js': (
|
||||
'js/common/lang_switcher.js',
|
||||
|
|
|
@ -49,8 +49,6 @@
|
|||
data-appid="{{ amo.FIREFOX.id }}"
|
||||
data-anonymous="{{ (not user.is_authenticated)|json }}"
|
||||
data-readonly="{{ settings.READ_ONLY|json }}"
|
||||
data-media-url="{{ MEDIA_URL }}"
|
||||
data-static-url="{{ STATIC_URL }}"
|
||||
{% block bodyattrs %}{% endblock %}>
|
||||
|
||||
<div id="main-wrapper">
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
z.visitor = z.Storage('visitor');
|
||||
z.currentVisit = z.SessionStorage('current-visit');
|
||||
|
||||
function initBanners() {
|
||||
var $body = $(document.body);
|
||||
|
||||
if ($body.hasClass('editor-tools')) {
|
||||
// Don't bother showing those on editor tools, it has a bunch of weird
|
||||
// styles for the menu that don't play nice with those banners.
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the various banners, but only one at a time, and only if they
|
||||
// haven't been dimissed before.
|
||||
// To reset dismissal state: z.visitor.remove('xx')
|
||||
|
||||
// Show the bad-browser message
|
||||
if (
|
||||
!z.visitor.get('seen_badbrowser_warning') &&
|
||||
$body.hasClass('badbrowser')
|
||||
) {
|
||||
$('#site-nonfx').show();
|
||||
}
|
||||
// Show the first visit banner.
|
||||
else if (!z.visitor.get('seen_impala_first_visit')) {
|
||||
$body.addClass('firstvisit');
|
||||
z.visitor.set('seen_impala_first_visit', 1);
|
||||
}
|
||||
// Show the link to try the new frontend (only on the homepage for now).
|
||||
else if (!z.visitor.get('seen_try_new_frontend') && $body.hasClass('home')) {
|
||||
$('#try-new-frontend').show();
|
||||
}
|
||||
// Show the ACR pitch if it has not been dismissed.
|
||||
else if (!z.visitor.get('seen_acr_pitch') && $body.hasClass('acr-pitch')) {
|
||||
$body.find('#acr-pitch').show();
|
||||
}
|
||||
|
||||
// Allow dismissal of site-balloons.
|
||||
$body.on(
|
||||
'click',
|
||||
'.site-balloon .close, .site-tip .close',
|
||||
_pd(function () {
|
||||
var $parent = $(this).closest('.site-balloon, .site-tip');
|
||||
$parent.fadeOut();
|
||||
if ($parent.is('#site-nonfx')) {
|
||||
z.visitor.set('seen_badbrowser_warning', 1);
|
||||
} else if ($parent.is('#acr-pitch')) {
|
||||
z.visitor.set('seen_acr_pitch', 1);
|
||||
} else if ($parent.is('#appruntime-pitch')) {
|
||||
z.visitor.set('seen_appruntime_pitch', 1);
|
||||
} else if ($parent.is('#try-new-frontend')) {
|
||||
z.visitor.set('seen_try_new_frontend', 1);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// Replaces rating selectboxes with the rating widget
|
||||
$.fn.ratingwidget = function (classes) {
|
||||
this.each(function (n, el) {
|
||||
if (!classes) {
|
||||
classes = '';
|
||||
}
|
||||
var $el = $(el),
|
||||
allClasses = 'ratingwidget stars stars-0 ' + classes,
|
||||
$widget = $('<span class="' + allClasses + '"></span>'),
|
||||
rs = '',
|
||||
showStars = function (n) {
|
||||
$widget
|
||||
.removeClass('stars-0 stars-1 stars-2 stars-3 stars-4 stars-5')
|
||||
.addClass('stars-' + n);
|
||||
},
|
||||
setStars = function (n) {
|
||||
if (rating == n) return;
|
||||
var e = $widget.find(format('[value="{0}"]', n));
|
||||
e.click();
|
||||
showStars(n);
|
||||
rating = n;
|
||||
},
|
||||
rating = null;
|
||||
// Existing rating found so initialize the widget.
|
||||
if ($('option[selected]', $el).length) {
|
||||
var temp_rating = $el.val();
|
||||
setStars(temp_rating);
|
||||
rating = parseInt(temp_rating, 10);
|
||||
}
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
var checked = rating === i ? ' checked' : '';
|
||||
rs += format(
|
||||
'<label data-stars="{0}">{1}<input type="radio" name="rating"{2} value="{3}"></label>',
|
||||
[i, format(ngettext('{0} star', '{0} stars', i), [i]), checked, i],
|
||||
);
|
||||
}
|
||||
$widget
|
||||
.click(function (evt) {
|
||||
var t = $(evt.target);
|
||||
if (t.is('input[type=radio]')) {
|
||||
showStars((rating = t.val()));
|
||||
if (!t.val()) {
|
||||
// If the user caused a radio button to become unchecked,
|
||||
// re-check it because that shouldn't happen.
|
||||
t.prop('checked', true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.mouseover(function (evt) {
|
||||
var t = $(evt.target);
|
||||
if (t.attr('data-stars')) {
|
||||
showStars(t.attr('data-stars'));
|
||||
}
|
||||
})
|
||||
.mouseout(function () {
|
||||
showStars(rating || 0);
|
||||
})
|
||||
.on('touchmove touchend', function (e) {
|
||||
var wid = $widget.width();
|
||||
var left = $widget.offset().left;
|
||||
var r =
|
||||
((e.originalEvent.changedTouches[0].clientX - left) / wid) * 5 + 1;
|
||||
r = ~~Math.min(Math.max(r, 1), 5);
|
||||
setStars(r);
|
||||
});
|
||||
$widget.html(rs);
|
||||
$el.before($widget).detach();
|
||||
});
|
||||
return this;
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
$(function () {
|
||||
var $abuse = $('fieldset.abuse');
|
||||
if ($abuse.find('legend a').length) {
|
||||
var $ol = $abuse.find('ol');
|
||||
$ol.hide();
|
||||
$abuse.find('legend a, .cancel').click(
|
||||
_pd(function () {
|
||||
$ol.slideToggle('fast');
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,97 +0,0 @@
|
|||
function objEqual(a, b) {
|
||||
return JSON.stringify(a) == JSON.stringify(b);
|
||||
}
|
||||
|
||||
z._AjaxCache = {};
|
||||
z.AjaxCache = (function () {
|
||||
return function (namespace) {
|
||||
if (z._AjaxCache[namespace] === undefined) {
|
||||
z._AjaxCache[namespace] = {
|
||||
previous: { args: '', data: '' },
|
||||
items: {},
|
||||
};
|
||||
}
|
||||
return z._AjaxCache[namespace];
|
||||
};
|
||||
})();
|
||||
|
||||
(function ($) {
|
||||
$.ajaxCache = function (o) {
|
||||
o = $.extend(
|
||||
{
|
||||
url: '',
|
||||
type: 'get',
|
||||
data: {}, // Key/value pairs of form data.
|
||||
newItems: $.noop, // Callback upon success of items fetched.
|
||||
cacheSuccess: $.noop, // Callback upon success of items fetched
|
||||
// in cache.
|
||||
ajaxSuccess: $.noop, // Callback upon success of Ajax request.
|
||||
ajaxFailure: $.noop, // Callback upon failure of Ajax request.
|
||||
},
|
||||
o,
|
||||
);
|
||||
|
||||
if (!z.capabilities.JSON || parseFloat(jQuery.fn.jquery) < 1.5) {
|
||||
// jqXHR objects allow Deferred methods as of jQuery 1.5. Some of our
|
||||
// old pages are stuck on jQuery 1.4, so hopefully this'll disappear
|
||||
// sooner than later.
|
||||
return $.ajax({
|
||||
url: o.url,
|
||||
type: o.method,
|
||||
data: o.data,
|
||||
success: function (data) {
|
||||
o.newItems(data, data);
|
||||
o.ajaxSuccess(data, items);
|
||||
},
|
||||
errors: function (data) {
|
||||
o.ajaxFailure(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var cache = z.AjaxCache(o.url + ':' + o.type),
|
||||
args = JSON.stringify(o.data),
|
||||
previous_args = JSON.stringify(cache.previous.args),
|
||||
items,
|
||||
request;
|
||||
|
||||
if (args != previous_args) {
|
||||
if (!!cache.items[args]) {
|
||||
items = cache.items[args];
|
||||
if (o.newItems) {
|
||||
o.newItems(null, items);
|
||||
}
|
||||
if (o.cacheSuccess) {
|
||||
o.cacheSuccess(null, items);
|
||||
}
|
||||
} else {
|
||||
// Make a request to fetch new items.
|
||||
request = $.ajax({ url: o.url, type: o.method, data: o.data });
|
||||
|
||||
request.done(function (data) {
|
||||
var items;
|
||||
if (!objEqual(data, cache.previous.data)) {
|
||||
items = data;
|
||||
}
|
||||
o.newItems(data, items);
|
||||
o.ajaxSuccess(data, items);
|
||||
|
||||
// Store items returned from this request.
|
||||
cache.items[args] = data;
|
||||
|
||||
// Store current list of items and form data (arguments).
|
||||
cache.previous.data = data;
|
||||
cache.previous.args = args;
|
||||
});
|
||||
|
||||
// Optional failure callback.
|
||||
if (o.failure) {
|
||||
request.fail(function (data) {
|
||||
o.ajaxFailure(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return request;
|
||||
};
|
||||
})(jQuery);
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* zCarousel: like jCarouselLite, but good.
|
||||
* by potch
|
||||
*
|
||||
* handles fluid layouts like a champ!
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
$.fn.zCarousel = function (o) {
|
||||
o = $.extend(
|
||||
{
|
||||
itemsPerPage: 1,
|
||||
circular: false,
|
||||
},
|
||||
o,
|
||||
);
|
||||
|
||||
var $self = $(this).eq(0),
|
||||
$strip = $('.slider', $self),
|
||||
$lis = $strip.find('.panel'),
|
||||
$prev = $(o.btnPrev),
|
||||
$next = $(o.btnNext),
|
||||
prop = o.prop || ($('body').hasClass('html-rtl') ? 'right' : 'left'),
|
||||
currentPos = 0,
|
||||
maxPos = Math.ceil($lis.length / o.itemsPerPage);
|
||||
|
||||
if (!$strip.length) return $self;
|
||||
|
||||
function render(pos) {
|
||||
if (o.circular) {
|
||||
currentPos =
|
||||
pos > maxPos + 1 ? pos - maxPos : pos < 0 ? pos + maxPos : pos;
|
||||
if ($strip.hasClass('noslide')) {
|
||||
currentPos = pos > maxPos ? 1 : pos < 1 ? maxPos : pos;
|
||||
}
|
||||
} else {
|
||||
currentPos = Math.min(Math.max(0, pos), maxPos - 1);
|
||||
}
|
||||
$strip.css(prop, currentPos * -100 + '%');
|
||||
$prev.toggleClass('disabled', currentPos == 0 && !o.circular);
|
||||
$next.toggleClass('disabled', currentPos == maxPos - 1 && !o.circular);
|
||||
//wait for paint to clear the class. lame.
|
||||
setTimeout(function () {
|
||||
$strip.removeClass('noslide');
|
||||
}, 0);
|
||||
}
|
||||
|
||||
//wire up controls.
|
||||
function fwd() {
|
||||
render(currentPos + 1);
|
||||
}
|
||||
function prev() {
|
||||
render(currentPos - 1);
|
||||
}
|
||||
$next.click(_pd(fwd));
|
||||
$prev.click(_pd(prev));
|
||||
$self.gofwd = fwd;
|
||||
$self.goback = prev;
|
||||
|
||||
// Strip text nodes so inline-block works properly.
|
||||
var cn = $strip[0].childNodes;
|
||||
for (var i = 0; i < cn.length; i++) {
|
||||
if (cn[i].nodeType == 3) {
|
||||
$strip[0].removeChild(cn[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (o.circular) {
|
||||
//pad the beginning with a page from the end vice-versa.
|
||||
$strip
|
||||
.prepend($lis.slice(-o.itemsPerPage).clone().addClass('cloned'))
|
||||
.append($lis.slice(0, o.itemsPerPage).clone().addClass('cloned'));
|
||||
$strip.addClass('noslide');
|
||||
$strip.on('transitionend webkitTransitionEnd', function () {
|
||||
if (currentPos > maxPos || currentPos < 1) {
|
||||
$strip.addClass('noslide');
|
||||
setTimeout(function () {
|
||||
render(currentPos);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
render(o.itemsPerPage);
|
||||
} else {
|
||||
render(0);
|
||||
}
|
||||
return $self;
|
||||
};
|
||||
})(jQuery);
|
|
@ -1,18 +0,0 @@
|
|||
(function () {
|
||||
var $footer = $('#footer'),
|
||||
$page = $('#page'),
|
||||
$win = $(window);
|
||||
function stickyFooter() {
|
||||
// Stick the footer to the bottom when there's head(foot)room.
|
||||
$footer.toggleClass(
|
||||
'sticky',
|
||||
$win.height() - $footer.outerHeight() > $page.outerHeight(),
|
||||
);
|
||||
}
|
||||
stickyFooter();
|
||||
$win.resize(_.debounce(stickyFooter, 200));
|
||||
})();
|
||||
|
||||
$(document).ready(function () {
|
||||
$(window).trigger('resize');
|
||||
});
|
|
@ -1,27 +0,0 @@
|
|||
function clearErrors(context) {
|
||||
$('.errorlist', context).remove();
|
||||
$('.error', context).removeClass('error');
|
||||
}
|
||||
|
||||
function populateErrors(context, o) {
|
||||
clearErrors(context);
|
||||
var $list = $('<ul class="errorlist"></ul>');
|
||||
$.each(o, function (i, v) {
|
||||
var $row = $('[name=' + i + ']', context).closest('.row');
|
||||
$row.addClass('error');
|
||||
$row.append($list.append($(format('<li>{0}</li>', _.escape(v)))));
|
||||
});
|
||||
}
|
||||
|
||||
function fieldFocused(e) {
|
||||
var tags = /input|keygen|meter|option|output|progress|select|textarea/i;
|
||||
return tags.test(e.target.nodeName);
|
||||
}
|
||||
|
||||
function postUnsaved(data) {
|
||||
$('input[name="unsaved_data"]').val(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function loadUnsaved() {
|
||||
return JSON.parse($('input[name="unsaved_data"]').val() || '{}');
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
function updateTotalForms(prefix, inc) {
|
||||
var $totalForms = $('#id_' + prefix + '-TOTAL_FORMS'),
|
||||
$maxForms = $('#id_' + prefix + '-MAX_NUM_FORMS'),
|
||||
inc = inc || 1,
|
||||
num = parseInt($totalForms.val(), 10) + inc;
|
||||
if ($maxForms.length && $maxForms.val().length) {
|
||||
var maxNum = parseInt($maxForms.val(), 10);
|
||||
if (num > maxNum) {
|
||||
return num - 1;
|
||||
}
|
||||
}
|
||||
$totalForms.val(num);
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* zAutoFormset: handles Django formsets with autocompletion like a champ!
|
||||
* by cvan
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
$.zAutoFormset = function (o) {
|
||||
o = $.extend(
|
||||
{
|
||||
delegate: document.body, // Delegate (probably some nearby parent).
|
||||
|
||||
forms: null, // Where all the forms live (maybe a <ul>).
|
||||
|
||||
extraForm: '.extra-form', // Selector for element that contains the
|
||||
// HTML for extra-form template.
|
||||
|
||||
maxForms: 3, // Maximum number of forms allowed.
|
||||
|
||||
prefix: 'form', // Formset prefix (Django default: 'form').
|
||||
|
||||
hiddenField: null, // This is the name of a (hidden) field
|
||||
// that will contain the value of the
|
||||
// formPK for each newly added form.
|
||||
|
||||
removeClass: 'remove', // Class for button triggering form removal.
|
||||
|
||||
formSelector: 'li', // Selector for each form container.
|
||||
|
||||
formPK: 'id', // Primary key for initial forms.
|
||||
|
||||
src: null, // Source URL of JSON search results.
|
||||
|
||||
input: null, // Input field for autocompletion search.
|
||||
|
||||
searchField: 'q', // Name of form field for search query.
|
||||
|
||||
minSearchLength: 3, // Minimum character length for queries.
|
||||
|
||||
width: 300, // Width (pixels) of autocomplete dropdown.
|
||||
|
||||
addedCB: null, // Callback for each new form added.
|
||||
|
||||
removedCB: null, // Callback for each form removed.
|
||||
|
||||
autocomplete: null, // Custom handler you can provide to handle
|
||||
// autocompletion yourself.
|
||||
},
|
||||
o,
|
||||
);
|
||||
|
||||
var $delegate = $(o.delegate),
|
||||
$forms = o.forms ? $delegate.find(o.forms) : $delegate,
|
||||
$extraForm = $delegate.find(o.extraForm),
|
||||
formsetPrefix = o.prefix,
|
||||
hiddenField = o.hiddenField,
|
||||
removeClass = o.removeClass,
|
||||
formSelector = o.formSelector,
|
||||
formPK = o.formPK,
|
||||
src = o.src || $delegate.attr('data-src'),
|
||||
$input = o.input ? $(o.input) : $delegate.find('input.autocomplete'),
|
||||
searchField = o.searchField,
|
||||
minLength = o.minSearchLength,
|
||||
$maxForms = $('#id_' + formsetPrefix + '-MAX_NUM_FORMS'),
|
||||
width = o.width,
|
||||
addedCB = o.addedCB,
|
||||
removedCB = o.removedCB,
|
||||
autocomplete = o.autocomplete,
|
||||
maxItems;
|
||||
|
||||
if ($maxForms.length && $maxForms.val()) {
|
||||
maxItems = parseInt($maxForms.val(), 10);
|
||||
} else if (o.maxForms) {
|
||||
maxItems = o.maxForms;
|
||||
}
|
||||
|
||||
function findItem(item) {
|
||||
if (item) {
|
||||
var $item = $forms.find(
|
||||
'[name$=-' + hiddenField + '][value=' + item[formPK] + ']',
|
||||
);
|
||||
if ($item.length) {
|
||||
var $f = $item.closest(formSelector);
|
||||
return { exists: true, visible: $f.is(':visible'), item: $f };
|
||||
}
|
||||
}
|
||||
return { exists: false, visible: false };
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
$input.val('');
|
||||
$input.removeAttr('data-item');
|
||||
toggleInput();
|
||||
}
|
||||
|
||||
function toggleInput() {
|
||||
if (!maxItems) {
|
||||
return;
|
||||
}
|
||||
var $visible = $forms.find(formSelector + ':visible').length;
|
||||
if ($visible >= maxItems) {
|
||||
$input.prop('disabled', true).slideUp();
|
||||
$('.ui-autocomplete').hide();
|
||||
} else if ($visible < maxItems) {
|
||||
$input.filter(':disabled').prop('disabled', false).slideDown();
|
||||
}
|
||||
}
|
||||
|
||||
function added() {
|
||||
var item = JSON.parse($input.attr('data-item'));
|
||||
|
||||
// Check if this item has already been added.
|
||||
var dupe = findItem(item);
|
||||
if (dupe.exists) {
|
||||
if (!dupe.visible) {
|
||||
// Undelete the item.
|
||||
var $item = dupe.item;
|
||||
$item.find('input[name$=-DELETE]').prop('checked', false);
|
||||
$item.slideDown(toggleInput);
|
||||
}
|
||||
clearInput();
|
||||
return;
|
||||
}
|
||||
|
||||
clearInput();
|
||||
|
||||
var formId = updateTotalForms(formsetPrefix, 1) - 1,
|
||||
emptyForm = $extraForm.html().replace(/__prefix__/g, formId);
|
||||
|
||||
var $f;
|
||||
if (addedCB) {
|
||||
$f = addedCB(emptyForm, item);
|
||||
} else {
|
||||
$f = $(f);
|
||||
}
|
||||
|
||||
$f.hide().appendTo($forms).slideDown(toggleInput);
|
||||
|
||||
// Update hidden field.
|
||||
$forms
|
||||
.find(formSelector + ':last [name$=-' + hiddenField + ']')
|
||||
.val(item[formPK]);
|
||||
}
|
||||
|
||||
function removed(el) {
|
||||
el.slideUp(toggleInput);
|
||||
// Mark as deleted.
|
||||
el.find('input[name$=-DELETE]').prop('checked', true);
|
||||
|
||||
if (removedCB) {
|
||||
removedCB(el);
|
||||
}
|
||||
|
||||
// If this was not an initial form (i.e., an extra form), delete the
|
||||
// form and decrement the TOTAL_FORMS count.
|
||||
if (!el.find('input[name$=-' + formPK + ']').length) {
|
||||
el.remove();
|
||||
updateTotalForms(formsetPrefix, -1);
|
||||
}
|
||||
}
|
||||
|
||||
function _renderItem(ul, item) {
|
||||
if (!findItem(item).visible) {
|
||||
var $a = $(
|
||||
format('<a><img src="{0}" alt="">{1}</a>', [
|
||||
item.icons['32'],
|
||||
_.escape(item.name),
|
||||
]),
|
||||
);
|
||||
return $('<li>')
|
||||
.data('item.autocomplete', item)
|
||||
.append($a)
|
||||
.appendTo(ul);
|
||||
}
|
||||
}
|
||||
|
||||
function _renderItemData(ul, item) {
|
||||
var rendered = _renderItem(ul, item);
|
||||
|
||||
// We are overwriting `_renderItem` in some places and return
|
||||
// nothing in case of duplicate filtering.
|
||||
if (rendered) {
|
||||
rendered.data('ui-autocomplete-item', item);
|
||||
}
|
||||
}
|
||||
|
||||
if (autocomplete) {
|
||||
autocomplete();
|
||||
} else {
|
||||
$input
|
||||
.autocomplete({
|
||||
minLength: minLength,
|
||||
width: width,
|
||||
source: function (request, response) {
|
||||
var d = {};
|
||||
d[searchField] = request.term;
|
||||
$.getJSON(src, d, response);
|
||||
},
|
||||
focus: function (event, ui) {
|
||||
event.preventDefault();
|
||||
$input.val(ui.item.name);
|
||||
},
|
||||
select: function (event, ui) {
|
||||
event.preventDefault();
|
||||
if (ui.item) {
|
||||
$input.val(ui.item.name);
|
||||
$input.attr('data-item', JSON.stringify(ui.item));
|
||||
added();
|
||||
}
|
||||
},
|
||||
})
|
||||
.data('ui-autocomplete')._renderMenu = function (ul, items) {
|
||||
// Overwrite _renderMenu to patch in our custom `_renderItemData`
|
||||
// and `_renderItem` to allow for our custom list-filter.
|
||||
$.each(items, function (index, item) {
|
||||
_renderItemData(ul, item);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
toggleInput();
|
||||
|
||||
$delegate.on(
|
||||
'click',
|
||||
'.' + removeClass,
|
||||
_pd(function () {
|
||||
removed($(this).closest(formSelector));
|
||||
}),
|
||||
);
|
||||
};
|
||||
})(jQuery);
|
|
@ -1,48 +0,0 @@
|
|||
$(function () {
|
||||
initListingCompat();
|
||||
|
||||
$('.theme-grid .hovercard.theme').each(function () {
|
||||
var $this = $(this);
|
||||
if ($this.find('.acr-override').length) {
|
||||
$this.addClass('acr');
|
||||
} else if (
|
||||
$this.find('.concealed').length == $this.find('.button').length
|
||||
) {
|
||||
$this.addClass('incompatible');
|
||||
// L10n: {0} is an app name.
|
||||
var msg = format(
|
||||
gettext('This theme is incompatible with your version of {0}'),
|
||||
[z.appName],
|
||||
);
|
||||
$this.append(format('<span class="notavail">{0}</span>', msg));
|
||||
}
|
||||
});
|
||||
|
||||
// Make this row appear 'static' so the installation buttons and pop-ups
|
||||
// stay open when hovering outside the item row.
|
||||
$(document.body)
|
||||
.on('newStatic', function () {
|
||||
$('.install-note:visible').closest('.item').addClass('static');
|
||||
})
|
||||
.on('closeStatic', function () {
|
||||
$('.item.static').removeClass('static');
|
||||
});
|
||||
});
|
||||
|
||||
function initListingCompat(domContext) {
|
||||
domContext = domContext || document.body;
|
||||
// Mark incompatible add-ons on listing pages unless marked with ignore.
|
||||
$('.listing .item.addon', domContext).each(function () {
|
||||
var $this = $(this);
|
||||
var isIncompatible =
|
||||
!$this.hasClass('ignore-compatibility') &&
|
||||
($this.find('.concealed').length == $this.find('.button').length ||
|
||||
$this.find('button.not-available').length);
|
||||
|
||||
if ($this.find('.acr-override').length) {
|
||||
$this.addClass('acr');
|
||||
} else if (isIncompatible) {
|
||||
$this.addClass('incompatible');
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
$(document).ready(function () {
|
||||
var report = $('.review-reason').html(),
|
||||
$window = $(window);
|
||||
|
||||
$('.review-reason').popup('.flag-review', {
|
||||
delegate: $(document.body),
|
||||
width: 'inherit',
|
||||
callback: function (obj) {
|
||||
var ct = $(obj.click_target),
|
||||
$popup = this;
|
||||
//reset our event handlers
|
||||
$popup.hideMe();
|
||||
|
||||
function addFlag(flag, note) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: ct.attr('href'),
|
||||
data: { flag: flag, note: note },
|
||||
success: function () {
|
||||
$popup.removeClass('other').hideMe();
|
||||
ct.closest('.item').addClass('flagged');
|
||||
ct.replaceWith(gettext('Flagged for review')).addClass('flagged');
|
||||
},
|
||||
error: function () {},
|
||||
dataType: 'json',
|
||||
});
|
||||
}
|
||||
|
||||
$popup.on('click', 'li a', function (e) {
|
||||
e.preventDefault();
|
||||
var el = $(e.target);
|
||||
if (el.attr('href') == '#review_flag_reason_other') {
|
||||
$popup
|
||||
.addClass('other')
|
||||
.on('submit', 'form', function (e) {
|
||||
e.preventDefault();
|
||||
var note = $popup.find('#id_note').val();
|
||||
if (!note) {
|
||||
alert(gettext('Your input is required'));
|
||||
} else {
|
||||
addFlag('review_flag_reason_other', note);
|
||||
}
|
||||
})
|
||||
.setPos(ct)
|
||||
.find('input[type=text]')
|
||||
.focus();
|
||||
} else {
|
||||
addFlag(el.attr('href').slice(1));
|
||||
}
|
||||
});
|
||||
|
||||
$popup.removeClass('other');
|
||||
$popup.html(report);
|
||||
return { pointTo: ct };
|
||||
},
|
||||
});
|
||||
|
||||
// A review comment can either be a review or a review reply
|
||||
function review_comment_edit_click(
|
||||
comment_form_id,
|
||||
comment_title_widget_id,
|
||||
comment_body_widget_id,
|
||||
comment_cancel_btn_id,
|
||||
) {
|
||||
return function (e) {
|
||||
e.preventDefault();
|
||||
var $form = $('#' + comment_form_id),
|
||||
$review = $(this).closest('.review'),
|
||||
edit_url = $('a.permalink', $review).attr('href') + 'edit',
|
||||
$cancel = $('#' + comment_cancel_btn_id),
|
||||
title_selector;
|
||||
|
||||
clearErrors($form);
|
||||
$form.off().hide();
|
||||
$('.review').not($review).show();
|
||||
$form.detach().insertAfter($review);
|
||||
|
||||
if ($review.find('h4').length) {
|
||||
$form.find('fieldset h3').remove();
|
||||
title_selector = 'h4 > b';
|
||||
$form.find('fieldset').prepend($review.find('h3').clone());
|
||||
} else {
|
||||
title_selector = 'h3 > b';
|
||||
}
|
||||
|
||||
$form
|
||||
.find('#' + comment_title_widget_id)
|
||||
.val($review.find(title_selector).text());
|
||||
$form
|
||||
.find('#' + comment_body_widget_id)
|
||||
.val($review.children('p.description').html().replace(/<br>/g, '\n'));
|
||||
$review.hide();
|
||||
$form.show();
|
||||
$window.resize();
|
||||
location.hash = '#' + comment_form_id;
|
||||
|
||||
function done_edit() {
|
||||
clearErrors($form);
|
||||
$form.off().hide();
|
||||
$review.show();
|
||||
$cancel.off();
|
||||
$window.resize();
|
||||
}
|
||||
|
||||
$cancel.click(_pd(done_edit));
|
||||
|
||||
$form.submit(function (e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: edit_url,
|
||||
data: $form.serialize(),
|
||||
success: function (response, status) {
|
||||
clearErrors($form);
|
||||
$review
|
||||
.find(title_selector)
|
||||
.text($form.find('#' + comment_title_widget_id).val());
|
||||
var rating = $form.find('.ratingwidget input:radio:checked').val();
|
||||
$('.stars', $review)
|
||||
.removeClass('stars-0 stars-1 stars-2 stars-3 stars-4 stars-5')
|
||||
.addClass('stars-' + rating);
|
||||
rating = $review.attr('data-rating', rating);
|
||||
$review.children('p.description').html(
|
||||
$form
|
||||
.find('#' + comment_body_widget_id)
|
||||
.val()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br>'),
|
||||
);
|
||||
done_edit();
|
||||
},
|
||||
error: function (xhr) {
|
||||
var errors = JSON.parse(xhr.responseText);
|
||||
populateErrors($form, errors);
|
||||
},
|
||||
dataType: 'json',
|
||||
});
|
||||
return false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
$('.primary').on(
|
||||
'click',
|
||||
'.review-reply-edit',
|
||||
review_comment_edit_click(
|
||||
'review-reply-edit-form',
|
||||
'id_review_reply_title',
|
||||
'id_review_reply_body',
|
||||
'review-reply-edit-cancel',
|
||||
),
|
||||
);
|
||||
|
||||
$('.primary').on(
|
||||
'click',
|
||||
'.review-edit',
|
||||
review_comment_edit_click(
|
||||
'review-edit-form',
|
||||
'id_review_title',
|
||||
'id_review_body',
|
||||
'review-edit-cancel',
|
||||
),
|
||||
);
|
||||
|
||||
$('.delete-review').click(function (e) {
|
||||
e.preventDefault();
|
||||
var target = $(e.target);
|
||||
$.post(target.attr('href'), function () {
|
||||
target.replaceWith(gettext('Marked for deletion'));
|
||||
});
|
||||
target.closest('.review').addClass('deleted');
|
||||
});
|
||||
|
||||
$('select[name="rating"]').ratingwidget();
|
||||
|
||||
$('#detail-review-link').click(
|
||||
_pd(function (e) {
|
||||
$('#review-add-box form')
|
||||
.append('<input type="hidden" name="detailed" value="1">')
|
||||
.submit();
|
||||
}),
|
||||
);
|
||||
});
|
|
@ -1,184 +0,0 @@
|
|||
(function () {
|
||||
var appver_input = $('#id_appver');
|
||||
var platform_input = $('#id_platform');
|
||||
|
||||
function autofillPlatform(context) {
|
||||
var $context = $(context || document.body);
|
||||
|
||||
$('#search', $context)
|
||||
.on('autofill', function (e) {
|
||||
var $this = $(this);
|
||||
|
||||
// Bail if search is present but not the appver input somehow.
|
||||
if (!appver_input.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate search form with browser version and OS.
|
||||
var gv = z.getVars();
|
||||
|
||||
// Facets are either the ones defined in the URL, or the detected
|
||||
// browser version and platform.
|
||||
if (!!gv.appver) {
|
||||
// Defined in URL parameter
|
||||
appver_input.val(gv.appver);
|
||||
} else if (z.appMatchesUserAgent) {
|
||||
// Fallback to detected
|
||||
// Only do this if firefox 57 or higher. Lower versions default
|
||||
// to searching for all add-ons even if they might be
|
||||
// incompatible. https://github.com/mozilla/addons-server/issues/5482
|
||||
if (VersionCompare.compareVersions(z.browserVersion, '57.0') >= 0) {
|
||||
appver_input.val(z.browserVersion);
|
||||
}
|
||||
}
|
||||
|
||||
if (!!gv.platform) {
|
||||
// Defined in URL parameter
|
||||
platform_input.val(gv.platform);
|
||||
} else if (z.appMatchesUserAgent) {
|
||||
// Fallback to detected
|
||||
platform_input.val(z.platform);
|
||||
}
|
||||
})
|
||||
.trigger('autofill');
|
||||
}
|
||||
|
||||
autofillPlatform();
|
||||
|
||||
$(function () {
|
||||
$('#search-facets')
|
||||
.on('click', 'li.facet', function (e) {
|
||||
var $this = $(this);
|
||||
if ($this.hasClass('active')) {
|
||||
if ($(e.target).is('a')) {
|
||||
return;
|
||||
}
|
||||
$this.removeClass('active');
|
||||
} else {
|
||||
$this.closest('ul').find('.active').removeClass('active');
|
||||
$this.addClass('active');
|
||||
}
|
||||
})
|
||||
.on('highlight', 'a', function (e) {
|
||||
// Highlight selection on sidebar.
|
||||
var $this = $(this);
|
||||
$this.closest('.facet-group').find('.selected').removeClass('selected');
|
||||
$this.closest('li').addClass('selected');
|
||||
})
|
||||
.on('recount', '.cnt', function (e, newCount) {
|
||||
// Update # of results on sidebar.
|
||||
var $this = $(this);
|
||||
if (newCount.length && $this.html() != newCount.html()) {
|
||||
$this.replaceWith(newCount);
|
||||
}
|
||||
})
|
||||
.on('rebuild', 'a[data-params]', function (e) {
|
||||
var $this = $(this),
|
||||
url = rebuildLink($this.attr('href'), $this.attr('data-params'));
|
||||
$this.attr('href', url);
|
||||
});
|
||||
if ($('body').hasClass('pjax') && $.support.pjax && z.capabilities.JSON) {
|
||||
$('#pjax-results').initSearchPjax($('#search-facets'), '#pjax-results');
|
||||
}
|
||||
});
|
||||
|
||||
function rebuildLink(url, urlparams, qs) {
|
||||
var params = JSON.parseNonNull(urlparams),
|
||||
newVars = $.extend(z.getVars(qs, true), params);
|
||||
return url.split('?')[0] + '?' + $.param(newVars);
|
||||
}
|
||||
|
||||
$.fn.initSearchPjax = function ($filters, containerSelector) {
|
||||
var $container = $(this),
|
||||
container = containerSelector,
|
||||
$triggered;
|
||||
|
||||
function pjaxOpen(url) {
|
||||
var urlBase = location.pathname + location.search;
|
||||
if (!!url && url != '#' && url != urlBase) {
|
||||
$.pjax({
|
||||
url: url,
|
||||
container: container,
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hijackLink() {
|
||||
$triggered = $(this);
|
||||
pjaxOpen($triggered.attr('href'));
|
||||
}
|
||||
|
||||
function loading() {
|
||||
var $wrapper = $container.closest('.results'),
|
||||
msg = gettext('Updating results…'),
|
||||
cls = 'updating';
|
||||
$wrapper.addClass('loading');
|
||||
|
||||
// The loading throbber is absolutely positioned atop the
|
||||
// search results, so we do this to ensure a max-margin of sorts.
|
||||
if ($container.outerHeight() > 300) {
|
||||
cls += ' tall';
|
||||
}
|
||||
|
||||
// Insert the loading throbber.
|
||||
$('<div>', { class: cls, html: msg }).insertBefore($container);
|
||||
|
||||
$container.trigger('search.loading');
|
||||
}
|
||||
|
||||
function finished() {
|
||||
var $wrapper = $container.closest('.results');
|
||||
|
||||
// Initialize install buttons and compatibility checking.
|
||||
$.when($container.find('.install:not(.triggered)').installButton()).done(
|
||||
function () {
|
||||
$container.find('.install').addClass('triggered');
|
||||
initListingCompat();
|
||||
},
|
||||
);
|
||||
|
||||
// Remove the loading throbber.
|
||||
$wrapper.removeClass('loading').find('.updating').remove();
|
||||
|
||||
// Update the # of matching results on sidebar.
|
||||
$filters.find('.cnt').trigger('recount', [$wrapper.find('.cnt')]);
|
||||
|
||||
// Update GET parameters of sidebar anchors.
|
||||
$filters.find('a[data-params]').trigger('rebuild');
|
||||
|
||||
// Highlight selection on sidebar.
|
||||
if ($triggered) {
|
||||
$triggered.trigger('highlight');
|
||||
}
|
||||
|
||||
// Update auto-filled appver/platform if there's a user override.
|
||||
$('#search').trigger('autofill');
|
||||
|
||||
// Scroll up to top of page.
|
||||
$('html, body').animate({ scrollTop: 0 }, 200);
|
||||
|
||||
$container.trigger('search.finished');
|
||||
}
|
||||
|
||||
function turnPages(e) {
|
||||
if (fieldFocused(e)) {
|
||||
return;
|
||||
}
|
||||
if (e.which == $.ui.keyCode.LEFT || e.which == $.ui.keyCode.RIGHT) {
|
||||
e.preventDefault();
|
||||
var sel;
|
||||
if (e.which == $.ui.keyCode.LEFT) {
|
||||
sel = '.paginator .prev:not(.disabled)';
|
||||
} else {
|
||||
sel = '.paginator .next:not(.disabled)';
|
||||
}
|
||||
pjaxOpen($container.find(sel).attr('href'));
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on('click', '.pjax-trigger a', _pd(hijackLink));
|
||||
$container.on('pjax:start', loading).on('pjax:end', finished);
|
||||
$(document).keyup(_.throttle(turnPages, 300));
|
||||
};
|
||||
})();
|
|
@ -1,29 +0,0 @@
|
|||
z.getVars = function (qs, excl_undefined) {
|
||||
if (typeof qs === 'undefined') {
|
||||
qs = location.search;
|
||||
}
|
||||
if (qs && qs[0] == '?') {
|
||||
qs = qs.substr(1); // Filter off the leading ? if it's there.
|
||||
}
|
||||
if (!qs) return {};
|
||||
|
||||
return _.chain(qs.split('&')) // ['a=b', 'c=d']
|
||||
.map(function (c) {
|
||||
return _.map(c.split('='), escape_);
|
||||
}) // [['a', 'b'], ['c', 'd']]
|
||||
.filter(function (p) {
|
||||
// [['a', 'b'], ['c', undefined]] -> [['a', 'b']]
|
||||
return !!p[0] && (!excl_undefined || !_.isUndefined(p[1]));
|
||||
})
|
||||
.object() // {'a': 'b', 'c': 'd'}
|
||||
.value();
|
||||
};
|
||||
|
||||
JSON.parseNonNull = function (text) {
|
||||
return JSON.parse(text, function (key, value) {
|
||||
if (typeof value === 'object' && value === null) {
|
||||
return '';
|
||||
}
|
||||
return value;
|
||||
});
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
// Init site search suggestions and populate the suggestions container.
|
||||
(function () {
|
||||
// AMO search init.
|
||||
$('#search #search-q').searchSuggestions(
|
||||
$('#site-search-suggestions'),
|
||||
processResults,
|
||||
'AMO',
|
||||
);
|
||||
|
||||
function processResults(settings) {
|
||||
if (!settings || !settings.category) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the 'Search add-ons for <b>"{addon}"</b>' text.
|
||||
settings['$results'].find('p b').html(format('"{0}"', settings.searchTerm));
|
||||
|
||||
var li_item = template(
|
||||
'<li><a href="{url}"><span {cls} {icon}>{name}</span>{subtitle}</a></li>',
|
||||
);
|
||||
|
||||
$.ajaxCache({
|
||||
url: settings['$results'].attr('data-src'),
|
||||
data: settings['$form'].serialize() + '&cat=' + settings.category,
|
||||
newItems: function (formdata, items) {
|
||||
var eventName;
|
||||
if (items !== undefined) {
|
||||
var ul = '';
|
||||
$.each(items, function (i, item) {
|
||||
var d = {
|
||||
url: escape_(item.url) || '#',
|
||||
icon: '',
|
||||
cls: '',
|
||||
subtitle: '',
|
||||
};
|
||||
if (item.icons && item.icons['32']) {
|
||||
d.icon = format(
|
||||
'style="background-image:url({0})"',
|
||||
escape_(item.icons['32']),
|
||||
);
|
||||
}
|
||||
if (item.cls) {
|
||||
d.cls = format('class="{0}"', escape_(item.cls));
|
||||
if (item.cls == 'cat') {
|
||||
d.subtitle = format(
|
||||
' <em class="subtitle">{0}</em>',
|
||||
gettext('Category'),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (item.name) {
|
||||
d.name = escape_(item.name);
|
||||
// Append the item only if it has a name.
|
||||
ul += li_item(d);
|
||||
}
|
||||
});
|
||||
settings['$results'].find('ul').html(ul);
|
||||
}
|
||||
settings['$results']
|
||||
.trigger('highlight', [settings.searchTerm])
|
||||
.trigger('resultsUpdated', [items]);
|
||||
},
|
||||
});
|
||||
}
|
||||
})();
|
|
@ -1,253 +0,0 @@
|
|||
$.fn.highlightTerm = function (val) {
|
||||
// If an item starts with `val`, wrap the matched text with boldness.
|
||||
val = val.replace(/[^\w\s]/gi, '');
|
||||
var pat = new RegExp(val, 'gi');
|
||||
this.each(function () {
|
||||
var $this = $(this),
|
||||
txt = $this.html(),
|
||||
matchedTxt = txt.replace(pat, '<b>$&</b>');
|
||||
if (txt != matchedTxt) {
|
||||
$this.html(matchedTxt);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* searchSuggestions
|
||||
* Grants search suggestions to an input of type text/search.
|
||||
* Required:
|
||||
* $results - a container for the search suggestions, typically UL.
|
||||
* processCallback - callback function that deals with the XHR call & populates
|
||||
- the $results element.
|
||||
* Optional:
|
||||
* searchType - possible values are 'AMO', 'MKT'
|
||||
*/
|
||||
$.fn.searchSuggestions = function ($results, processCallback, searchType) {
|
||||
var $self = this,
|
||||
$form = $self.closest('form');
|
||||
|
||||
if (!$results.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cat = $results.attr('data-cat');
|
||||
|
||||
if (searchType == 'AMO') {
|
||||
// Some base elements that we don't want to keep creating on the fly.
|
||||
var msg;
|
||||
if (cat == 'themes') {
|
||||
msg = gettext('Search themes for <b>{0}</b>');
|
||||
} else if (cat == 'apps') {
|
||||
msg = gettext('Search apps for <b>{0}</b>');
|
||||
} else {
|
||||
msg = gettext('Search add-ons for <b>{0}</b>');
|
||||
}
|
||||
var base = template(
|
||||
'<div class="wrap">' +
|
||||
'<p><a class="sel" href="#"><span>{msg}</span></a></p><ul></ul>' +
|
||||
'</div>',
|
||||
);
|
||||
$results.html(base({ msg: msg }));
|
||||
} else if (searchType == 'MKT') {
|
||||
$results.html('<div class="wrap"><ul></ul></div>');
|
||||
}
|
||||
|
||||
// Control keys that shouldn't trigger new requests.
|
||||
var ignoreKeys = [
|
||||
z.keys.SHIFT,
|
||||
z.keys.CONTROL,
|
||||
z.keys.ALT,
|
||||
z.keys.PAUSE,
|
||||
z.keys.CAPS_LOCK,
|
||||
z.keys.ESCAPE,
|
||||
z.keys.ENTER,
|
||||
z.keys.PAGE_UP,
|
||||
z.keys.PAGE_DOWN,
|
||||
z.keys.LEFT,
|
||||
z.keys.UP,
|
||||
z.keys.RIGHT,
|
||||
z.keys.DOWN,
|
||||
z.keys.HOME,
|
||||
z.keys.END,
|
||||
z.keys.COMMAND,
|
||||
z.keys.WINDOWS_RIGHT,
|
||||
z.keys.COMMAND_RIGHT,
|
||||
z.keys.WINDOWS_LEFT_OPERA,
|
||||
z.keys.WINDOWS_RIGHT_OPERA,
|
||||
z.keys.APPLE,
|
||||
];
|
||||
|
||||
var gestureKeys = [z.keys.ESCAPE, z.keys.UP, z.keys.DOWN];
|
||||
|
||||
function pageUp() {
|
||||
// Select the first element.
|
||||
$results.find('.sel').removeClass('sel');
|
||||
$results.removeClass('sel');
|
||||
$results.find('a:first').addClass('sel');
|
||||
}
|
||||
function pageDown() {
|
||||
// Select the last element.
|
||||
$results.find('.sel').removeClass('sel');
|
||||
$results.removeClass('sel');
|
||||
$results.find('a:last').addClass('sel');
|
||||
}
|
||||
|
||||
function dismissHandler() {
|
||||
$results.removeClass('visible sel');
|
||||
if (searchType == 'MKT') {
|
||||
$('#site-header').removeClass('suggestions');
|
||||
if (z.capabilities.mobile && $('body.home').length === 0) {
|
||||
z.body.removeClass('show-search');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gestureHandler(e) {
|
||||
// Bail if the results are hidden or if we have a non-gesture key
|
||||
// or if we have a alt/ctrl/meta/shift keybinding.
|
||||
if (
|
||||
!$results.hasClass('visible') ||
|
||||
$.inArray(e.which, gestureKeys) < 0 ||
|
||||
e.altKey ||
|
||||
e.ctrlKey ||
|
||||
e.metaKey ||
|
||||
e.shiftKey
|
||||
) {
|
||||
$results.trigger('keyIgnored');
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
if (e.which == z.keys.ESCAPE) {
|
||||
dismissHandler();
|
||||
} else if (e.which == z.keys.UP || e.which == z.keys.DOWN) {
|
||||
var $sel = $results.find('.sel'),
|
||||
$elems = $results.find('a'),
|
||||
i = $elems.index($sel.get(0));
|
||||
if ($sel.length && i >= 0) {
|
||||
if (e.which == z.keys.UP) {
|
||||
// Clamp the value so it goes to the previous row
|
||||
// but never goes beyond the first row.
|
||||
i = Math.max(0, i - 1);
|
||||
} else {
|
||||
// Clamp the value so it goes to the next row
|
||||
// but never goes beyond the last row.
|
||||
i = Math.min(i + 1, $elems.length - 1);
|
||||
}
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
$sel.removeClass('sel');
|
||||
$elems.eq(i).addClass('sel');
|
||||
$results.addClass('sel').trigger('selectedRowUpdate', [i]);
|
||||
}
|
||||
}
|
||||
|
||||
function inputHandler(e) {
|
||||
if (e.type === 'paste') {
|
||||
pasting = true;
|
||||
}
|
||||
var val = escape_($self.val());
|
||||
if (val.length < 3) {
|
||||
$results.filter('.visible').removeClass('visible');
|
||||
return;
|
||||
}
|
||||
|
||||
// Required data to send to the callback.
|
||||
var settings = {
|
||||
$results: $results,
|
||||
$form: $form,
|
||||
searchTerm: val,
|
||||
};
|
||||
|
||||
// Optional data for callback.
|
||||
if (searchType == 'AMO' || searchType == 'MKT') {
|
||||
settings['category'] = cat;
|
||||
}
|
||||
|
||||
if (
|
||||
((e.type === 'keyup' && typeof e.which === 'undefined') ||
|
||||
$.inArray(e.which, ignoreKeys) >= 0) &&
|
||||
!pasting
|
||||
) {
|
||||
$results.trigger('inputIgnored');
|
||||
} else {
|
||||
// XHR call and populate suggestions.
|
||||
processCallback(settings);
|
||||
}
|
||||
pasting = false;
|
||||
}
|
||||
|
||||
var pollVal = 0,
|
||||
pasting = false;
|
||||
|
||||
if (z.capabilities.touch) {
|
||||
$self.focus(function () {
|
||||
// If we've already got a timer, clear it.
|
||||
if (pollVal !== 0) {
|
||||
clearInterval(pollVal);
|
||||
}
|
||||
pollVal = setInterval(function () {
|
||||
gestureHandler($self);
|
||||
inputHandler($self);
|
||||
return;
|
||||
}, 150);
|
||||
});
|
||||
} else {
|
||||
$self
|
||||
.on('keydown', gestureHandler)
|
||||
.on('keyup paste', _.throttle(inputHandler, 250));
|
||||
}
|
||||
|
||||
function clearCurrentSuggestions(e) {
|
||||
clearInterval(pollVal);
|
||||
// Delay dismissal to allow for click events to happen on
|
||||
// results. If we call it immediately, results get hidden
|
||||
// before the click events can happen.
|
||||
_.delay(dismissHandler, 250);
|
||||
$self.trigger('dismissed');
|
||||
}
|
||||
|
||||
$self.blur(clearCurrentSuggestions);
|
||||
$form.submit(function (e) {
|
||||
$self.blur();
|
||||
clearCurrentSuggestions(e);
|
||||
});
|
||||
|
||||
$results.find('.sel').click(function (e) {
|
||||
e.preventDefault();
|
||||
$form.submit();
|
||||
});
|
||||
|
||||
$results
|
||||
.on('mouseenter mouseleave', 'li, p', function () {
|
||||
$results.find('.sel').removeClass('sel');
|
||||
$results.addClass('sel');
|
||||
$(this).find('a').addClass('sel');
|
||||
})
|
||||
.on('click', 'a', function () {
|
||||
clearCurrentSuggestions();
|
||||
$self.val('');
|
||||
});
|
||||
|
||||
$results.on('highlight', function (e, val) {
|
||||
// If an item starts with `val`, wrap the matched text with boldness.
|
||||
$results.find('ul a span').highlightTerm(val);
|
||||
$results.addClass('visible');
|
||||
if (!$results.find('.sel').length) {
|
||||
pageUp();
|
||||
}
|
||||
});
|
||||
|
||||
$results.on('dismiss', clearCurrentSuggestions);
|
||||
|
||||
$(document).keyup(function (e) {
|
||||
if (fieldFocused(e)) {
|
||||
return;
|
||||
}
|
||||
if (e.which == 83) {
|
||||
$self.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
// prettier-ignore
|
||||
; /* This is just to fix jquery.pjax.js breaking our hokey concatenation and
|
||||
* minification process.
|
||||
*/
|
|
@ -1,160 +0,0 @@
|
|||
/* Browser Utilities
|
||||
* Based on amo2009/addons.js
|
||||
**/
|
||||
|
||||
function BrowserUtils() {
|
||||
'use strict';
|
||||
|
||||
var userAgentStrings = {
|
||||
firefox:
|
||||
/^Mozilla.*(Firefox|Minefield|Namoroka|Shiretoko|GranParadiso|BonEcho|Iceweasel|Fennec|MozillaDeveloperPreview)\/([^\s]*).*$/,
|
||||
seamonkey: /^Mozilla.*(SeaMonkey|Iceape)\/([^\s]*).*$/,
|
||||
mobile: /^Mozilla.*(Fennec|Mobile)\/([^\s]*)$/,
|
||||
thunderbird: /^Mozilla.*(Thunderbird|Shredder|Lanikai)\/([^\s*]*).*$/,
|
||||
},
|
||||
osStrings = {
|
||||
windows: /Windows/,
|
||||
mac: /Mac/,
|
||||
linux: /Linux|BSD/,
|
||||
android: /Android/,
|
||||
};
|
||||
|
||||
// browser detection
|
||||
var browser = {},
|
||||
browserVersion = '',
|
||||
pattern,
|
||||
match,
|
||||
i,
|
||||
badBrowser = true;
|
||||
for (i in userAgentStrings) {
|
||||
if (userAgentStrings.hasOwnProperty(i)) {
|
||||
pattern = userAgentStrings[i];
|
||||
match = pattern.exec(navigator.userAgent);
|
||||
browser[i] = !!(match && match.length === 3);
|
||||
if (browser[i]) {
|
||||
browserVersion = escape_(match[2]);
|
||||
badBrowser = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seamonkey looks like Firefox but Firefox doesn't look like Seamonkey.
|
||||
// If both are true, set Firefox to false.
|
||||
if (browser.firefox && browser.seamonkey) {
|
||||
browser.firefox = false;
|
||||
}
|
||||
|
||||
var os = {},
|
||||
platform = '';
|
||||
for (i in osStrings) {
|
||||
if (osStrings.hasOwnProperty(i)) {
|
||||
pattern = osStrings[i];
|
||||
os[i] = pattern.test(navigator.userAgent);
|
||||
if (os[i]) {
|
||||
platform = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!platform) {
|
||||
os['other'] = !platform;
|
||||
platform = 'other';
|
||||
}
|
||||
|
||||
return {
|
||||
browser: browser,
|
||||
browserVersion: browserVersion,
|
||||
badBrowser: badBrowser,
|
||||
os: os,
|
||||
platform: platform,
|
||||
};
|
||||
}
|
||||
|
||||
var VersionCompare = {
|
||||
/**
|
||||
* Mozilla-style version numbers comparison in Javascript
|
||||
* (JS-translated version of PHP versioncompare component)
|
||||
* @return -1: a<b, 0: a==b, 1: a>b
|
||||
*/
|
||||
compareVersions: function (a, b) {
|
||||
var al = a.split('.'),
|
||||
bl = b.split('.'),
|
||||
ap,
|
||||
bp,
|
||||
r,
|
||||
i;
|
||||
for (i = 0; i < al.length || i < bl.length; i++) {
|
||||
ap = i < al.length ? al[i] : null;
|
||||
bp = i < bl.length ? bl[i] : null;
|
||||
r = this.compareVersionParts(ap, bp);
|
||||
if (r !== 0) return r;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function: compare a single version part
|
||||
*/
|
||||
compareVersionParts: function (ap, bp) {
|
||||
var avp = this.parseVersionPart(ap),
|
||||
bvp = this.parseVersionPart(bp),
|
||||
r = this.cmp(avp['numA'], bvp['numA']);
|
||||
if (r) return r;
|
||||
r = this.strcmp(avp['strB'], bvp['strB']);
|
||||
if (r) return r;
|
||||
r = this.cmp(avp['numC'], bvp['numC']);
|
||||
if (r) return r;
|
||||
return this.strcmp(avp['extraD'], bvp['extraD']);
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function: parse a version part
|
||||
*/
|
||||
parseVersionPart: function (p) {
|
||||
if (p == '*') {
|
||||
return {
|
||||
numA: Number.MAX_VALUE,
|
||||
strB: '',
|
||||
numC: 0,
|
||||
extraD: '',
|
||||
};
|
||||
}
|
||||
var pattern = /^([-\d]*)([^-\d]*)([-\d]*)(.*)$/,
|
||||
m = pattern.exec(p),
|
||||
r = {
|
||||
numA: parseInt(m[1], 10),
|
||||
strB: m[2],
|
||||
numC: parseInt(m[3], 10),
|
||||
extraD: m[4],
|
||||
};
|
||||
if (r['strB'] == '+') {
|
||||
r['numA']++;
|
||||
r['strB'] = 'pre';
|
||||
}
|
||||
return r;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function: compare numeric version parts
|
||||
*/
|
||||
cmp: function (an, bn) {
|
||||
if (isNaN(an)) an = 0;
|
||||
if (isNaN(bn)) bn = 0;
|
||||
if (an < bn) return -1;
|
||||
if (an > bn) return 1;
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function: compare string version parts
|
||||
*/
|
||||
strcmp: function (as, bs) {
|
||||
if (as == bs) return 0;
|
||||
// any string comes *before* the empty string
|
||||
if (as === '') return 1;
|
||||
if (bs === '') return -1;
|
||||
// normal string comparison for non-empty strings (like strcmp)
|
||||
if (as < bs) return -1;
|
||||
else if (as > bs) return 1;
|
||||
else return 0;
|
||||
},
|
||||
};
|
|
@ -1,361 +0,0 @@
|
|||
(function () {
|
||||
/* Call this with something like $('.install').installButton(); */
|
||||
z.button = {};
|
||||
|
||||
/* A library of callbacks that may be run after InstallTrigger succeeds.
|
||||
* ``this`` will be bound to the .install button.
|
||||
*/
|
||||
z.button.after = {
|
||||
contrib: function (xpi_url, status) {
|
||||
if (status === 0) {
|
||||
//success
|
||||
document.location = $(this).attr('data-developers');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var notavail =
|
||||
'<div class="extra"><span class="button disabled not-available" disabled>{0}</span></div>',
|
||||
incompat =
|
||||
'<div class="extra"><span class="button disabled not-available" disabled>{0}</span></div>',
|
||||
noappsupport =
|
||||
'<div class="extra"><span class="button disabled not-available" disabled>{0}</span></div>',
|
||||
download_re = new RegExp('(/downloads/(?:latest|file)/\\d+)');
|
||||
|
||||
// The lowest maxVersion an app has to support to allow default-to-compatible.
|
||||
var D2C_MAX_VERSIONS = {
|
||||
firefox: '4.0',
|
||||
mobile: '11.0',
|
||||
seamonkey: '2.1',
|
||||
thunderbird: '5.0',
|
||||
};
|
||||
|
||||
/* Called by the jQuery plugin to set up a single button. */
|
||||
var installButton = function () {
|
||||
// Create a bunch of data and helper functions, then drive the buttons
|
||||
// based on the button type at the end.
|
||||
var self = this,
|
||||
$this = $(this),
|
||||
$button = $this.find('.button');
|
||||
|
||||
// Unreviewed and self-hosted buttons point to the add-on detail page for
|
||||
// non-js safety. Flip them to the real xpi url here.
|
||||
$button.each(function () {
|
||||
var $this = $(this);
|
||||
if ($this.hasattr('data-realurl')) {
|
||||
$this.attr('href', $this.attr('data-realurl'));
|
||||
}
|
||||
|
||||
/* If we're on the mobile site but it's not a mobile browser, force
|
||||
* the download url to type:attachment.
|
||||
*/
|
||||
if (z.app === 'mobile' && !z.appMatchesUserAgent) {
|
||||
var href = $this.attr('href');
|
||||
$this.attr('href', href.replace(download_re, '$1/type:attachment'));
|
||||
}
|
||||
});
|
||||
|
||||
var addon = $this.attr('data-addon'),
|
||||
min = $this.attr('data-min'),
|
||||
max = $this.attr('data-max'),
|
||||
name = $this.attr('data-name'),
|
||||
icon = $this.attr('data-icon'),
|
||||
after = $this.attr('data-after'),
|
||||
search = $this.hasattr('data-search'),
|
||||
no_compat_necessary = $this.hasattr('data-no-compat-necessary'),
|
||||
accept_eula = $this.hasClass('accept'),
|
||||
compatible = $this.attr('data-is-compatible-by-default') == 'true',
|
||||
compatible_app = $this.attr('data-is-compatible-app') == 'true',
|
||||
has_overrides = $this.hasattr('data-compat-overrides'),
|
||||
versions_url = $this.attr('data-versions'),
|
||||
// L10n: {0} is an app name like Firefox.
|
||||
_s = accept_eula ? gettext('Accept and Install') : gettext('Add to {0}'),
|
||||
addto = format(_s, [z.appName]),
|
||||
appSupported = z.appMatchesUserAgent && min && max,
|
||||
$body = $(document.body),
|
||||
olderBrowser,
|
||||
newerBrowser;
|
||||
|
||||
// If we have os-specific buttons, check that one of them matches the
|
||||
// current platform.
|
||||
var badPlatform =
|
||||
$button.find('.os').length && !$button.hasClass(z.platform);
|
||||
|
||||
// min and max only exist if the add-on is compatible with request[APP].
|
||||
if (appSupported) {
|
||||
// The user *has* an older/newer browser.
|
||||
olderBrowser = VersionCompare.compareVersions(z.browserVersion, min) < 0;
|
||||
newerBrowser = VersionCompare.compareVersions(z.browserVersion, max) > 0;
|
||||
if (olderBrowser) {
|
||||
// Make sure we show the "Not available for ..." messaging.
|
||||
compatible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to compatible checking.
|
||||
if (compatible) {
|
||||
if (!compatible_app) {
|
||||
compatible = false;
|
||||
}
|
||||
// If it's still compatible, check the overrides.
|
||||
if (compatible && has_overrides) {
|
||||
var overrides = JSON.parse($this.attr('data-compat-overrides'));
|
||||
_.each(overrides, function (override) {
|
||||
var _min = override[0],
|
||||
_max = override[1];
|
||||
if (
|
||||
VersionCompare.compareVersions(z.browserVersion, _min) >= 0 &&
|
||||
VersionCompare.compareVersions(z.browserVersion, _max) <= 0
|
||||
) {
|
||||
compatible = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
compatible = false;
|
||||
}
|
||||
|
||||
var addWarning = function (msg, type) {
|
||||
$this.parent().append(format(type || notavail, [msg]));
|
||||
};
|
||||
|
||||
// Change the button text to "Add to Firefox".
|
||||
var addToApp = function () {
|
||||
if (appSupported || (no_compat_necessary && z.appMatchesUserAgent)) {
|
||||
$button
|
||||
.addClass('add')
|
||||
.removeClass('download')
|
||||
.find('span')
|
||||
.text(addto);
|
||||
}
|
||||
};
|
||||
|
||||
// Calls InstallTrigger.install or AddSearchProvider if we capture a click
|
||||
// on something with a .installer class.
|
||||
var clickHijack = function () {
|
||||
try {
|
||||
if (
|
||||
(!appSupported && !no_compat_necessary) ||
|
||||
!('InstallTrigger' in window)
|
||||
)
|
||||
return;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this.addClass('clickHijack'); // So we can disable pointer events
|
||||
|
||||
$this
|
||||
.on('mousedown focus', function (e) {
|
||||
$this.addClass('active');
|
||||
})
|
||||
.on('mouseup blur', function (e) {
|
||||
$this.removeClass('active');
|
||||
})
|
||||
.click(function (e) {
|
||||
// If the click was on a.installer or a child, call the special
|
||||
// install method. We can't bind this directly because we add
|
||||
// more .installers dynamically.
|
||||
var $target = $(e.target),
|
||||
$installer = '';
|
||||
if ($target.hasClass('installer')) {
|
||||
installer = $target;
|
||||
} else {
|
||||
installer = $target.parents('.installer').first();
|
||||
if (_.indexOf($this.find('.installer'), installer[0]) == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
// map download url => file hash.
|
||||
var hashes = {};
|
||||
$this.find('.button[data-hash]').each(function () {
|
||||
hashes[$(this).attr('href')] = $(this).attr('data-hash');
|
||||
});
|
||||
var hash = hashes[installer.attr('href')];
|
||||
|
||||
var f = _.haskey(z.button.after, after)
|
||||
? z.button.after[after]
|
||||
: _.identity,
|
||||
callback = _.bind(f, self),
|
||||
install = search ? z.installSearch : z.installAddon;
|
||||
install(name, installer[0].href, icon, hash, callback);
|
||||
});
|
||||
};
|
||||
|
||||
// Gather the available platforms.
|
||||
var platforms = $button.map(function () {
|
||||
var name = $(this).find('.os').attr('data-os'),
|
||||
text = z.appMatchesUserAgent
|
||||
? /* L10n: {0} is an platform like Windows or Linux. */
|
||||
gettext('Install for {0} anyway')
|
||||
: gettext('Download for {0} anyway');
|
||||
return {
|
||||
href: $(this).attr('href'),
|
||||
msg: format(text, [name]),
|
||||
};
|
||||
});
|
||||
|
||||
var showDownloadAnyway = function ($button) {
|
||||
var $visibleButton = $button.filter(':visible');
|
||||
var $installShell = $visibleButton.parents('.install-shell');
|
||||
var $downloadAnyway = $visibleButton.next('.download-anyway');
|
||||
if ($downloadAnyway.length) {
|
||||
// We want to be able to add the download anyway link regardless
|
||||
// of what is already shown. There could be just an error message,
|
||||
// or an error message plus a link to more versions. We also want
|
||||
// those combinations to work without the download anyway link
|
||||
// being shown.
|
||||
// Append a separator to the .more-versions element:
|
||||
// if it's displayed we need to separate the download anyway link
|
||||
// from the text shown in that span.
|
||||
var $moreVersions = $installShell.find('.more-versions');
|
||||
$moreVersions.append(' | ');
|
||||
// In any case, add the download anyway link to the parent div.
|
||||
// It'll show up regardless of whether we are showing the more
|
||||
// versions link or not.
|
||||
var $newParent = $installShell.find('.extra .not-available');
|
||||
$newParent.append($downloadAnyway);
|
||||
$downloadAnyway.show();
|
||||
}
|
||||
};
|
||||
|
||||
// Add version and platform warnings. This is one
|
||||
// big function since we merge the messaging when bad platform and version
|
||||
// occur simultaneously.
|
||||
var versionsAndPlatforms = function (options) {
|
||||
var opts = $.extend({ addWarning: true }, options);
|
||||
warn = opts.addWarning ? addWarning : _.identity;
|
||||
|
||||
// Do badPlatform prep out here since we need it in all branches.
|
||||
if (badPlatform) {
|
||||
warn(gettext('Not available for your platform'));
|
||||
$button.addClass('concealed');
|
||||
$button.first().css('display', 'inherit');
|
||||
$button.closest('.item.addon').addClass('incompatible');
|
||||
}
|
||||
|
||||
if (appSupported && !compatible && (olderBrowser || newerBrowser)) {
|
||||
// L10n: {0} is an app name.
|
||||
var msg = format(
|
||||
gettext('This add-on is not compatible with your version of {0}.'),
|
||||
[z.appName, z.browserVersion],
|
||||
);
|
||||
var tpl = template(
|
||||
msg +
|
||||
' <br/><span class="more-versions"><a href="{versions_url}">' +
|
||||
gettext('View other versions') +
|
||||
'</a></span>',
|
||||
);
|
||||
warn(tpl({ versions_url: versions_url }));
|
||||
|
||||
$button.closest('div').attr('data-version-supported', false);
|
||||
$button.addClass('concealed');
|
||||
$button.closest('.item.addon').addClass('incompatible');
|
||||
if (!badPlatform) {
|
||||
showDownloadAnyway($button);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (!unreviewed && (appSupported || no_compat_necessary)) {
|
||||
// Good version, good platform.
|
||||
$button.addClass('installer');
|
||||
$button.closest('div').attr('data-version-supported', true);
|
||||
} else if (!appSupported) {
|
||||
var msg =
|
||||
min && max
|
||||
? gettext('Works with {app} {min} - {max}')
|
||||
: gettext('Works with {app}');
|
||||
var tpl = template(
|
||||
msg +
|
||||
'<br/><span class="more-versions"><a href="{versions_url}">' +
|
||||
gettext('View other versions') +
|
||||
'</a></span>',
|
||||
);
|
||||
var context = {
|
||||
app: z.appName,
|
||||
min: min,
|
||||
max: max,
|
||||
versions_url: versions_url,
|
||||
};
|
||||
addWarning(tpl(context), noappsupport);
|
||||
if (!badPlatform) {
|
||||
showDownloadAnyway($button);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// What kind of button are we dealing with?
|
||||
var unreviewed = $this.hasClass('unreviewed'),
|
||||
contrib = $this.hasClass('contrib'),
|
||||
eula = $this.hasClass('eula');
|
||||
|
||||
// Drive the install button based on its type.
|
||||
if (eula || contrib) {
|
||||
versionsAndPlatforms();
|
||||
} else if (z.appMatchesUserAgent) {
|
||||
clickHijack();
|
||||
addToApp();
|
||||
var opts = no_compat_necessary ? { addWarning: false } : {};
|
||||
versionsAndPlatforms(opts);
|
||||
} else if (z.app == 'firefox') {
|
||||
$button.addClass('CTA');
|
||||
$button.text(gettext('Only with Firefox \u2014 Get Firefox Now!'));
|
||||
$button.attr(
|
||||
'href',
|
||||
'https://www.mozilla.org/firefox/new/?scene=2&utm_source=addons.mozilla.org&utm_medium=referral&utm_campaign=non-fx-button#download-fx',
|
||||
);
|
||||
$('#site-nonfx').hide();
|
||||
} else if (z.app == 'thunderbird') {
|
||||
versionsAndPlatforms();
|
||||
} else {
|
||||
clickHijack();
|
||||
addToApp();
|
||||
versionsAndPlatforms();
|
||||
}
|
||||
};
|
||||
|
||||
jQuery.fn.installButton = function () {
|
||||
return this.each(installButton);
|
||||
};
|
||||
|
||||
/* Install an XPI or a JAR (or something like that).
|
||||
*
|
||||
* hash and callback are optional. callback is triggered after the
|
||||
* installation is complete.
|
||||
*/
|
||||
z.installAddon = function (name, url, icon, hash, callback) {
|
||||
var params = {};
|
||||
params[name] = {
|
||||
URL: url,
|
||||
IconURL: icon,
|
||||
toString: function () {
|
||||
return url;
|
||||
},
|
||||
};
|
||||
if (hash) {
|
||||
params[name].Hash = hash;
|
||||
}
|
||||
// InstallTrigger is a Gecko API.
|
||||
InstallTrigger.install(params, callback);
|
||||
ga('send', 'event', 'AMO Addon / Theme Installs', 'addon', name);
|
||||
};
|
||||
|
||||
z.installSearch = function (name, url, icon, hash, callback) {
|
||||
if (window.external && window.external.AddSearchProvider) {
|
||||
window.external.AddSearchProvider(url);
|
||||
callback();
|
||||
ga('send', 'event', 'AMO Addon / Theme Installs', 'addon', name);
|
||||
} else {
|
||||
// Alert! Deal with it.
|
||||
alert(
|
||||
gettext(
|
||||
'Sorry, you need a Mozilla-based browser (such as Firefox) to install a search plugin.',
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -1,19 +0,0 @@
|
|||
// debounce
|
||||
// args:
|
||||
// function
|
||||
// milliseconds
|
||||
// context
|
||||
|
||||
function debounce(fn, ms, ctxt) {
|
||||
var ctx = ctxt || window;
|
||||
var to,
|
||||
del = ms,
|
||||
fun = fn;
|
||||
return function () {
|
||||
var args = arguments;
|
||||
clearTimeout(to);
|
||||
to = setTimeout(function () {
|
||||
fun.apply(ctx, args);
|
||||
}, del);
|
||||
};
|
||||
}
|
|
@ -959,7 +959,6 @@ function initAuthorFields() {
|
|||
var numForms = authors_pending_confirmation.children('.author').length,
|
||||
manager = $('#id_authors_pending_confirmation-TOTAL_FORMS');
|
||||
authors_pending_confirmation.append(empty_form([numForms]));
|
||||
authors_pending_confirmation.find('.blank .author-email').placeholder();
|
||||
manager.val(authors_pending_confirmation.children('.author').length);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
google.load('search', '1', { language: $('html').attr('lang') });
|
||||
google.setOnLoadCallback(function () {
|
||||
var qry = $('.header-search input[name="q"]'),
|
||||
opt = new google.search.DrawOptions();
|
||||
|
||||
opt.setInput(qry.get(0));
|
||||
sc = new google.search.CustomSearchControl(
|
||||
'007182852441266509516:fnsg3w7luc4',
|
||||
);
|
||||
sc.setNoResultsString(gettext('No results found.'));
|
||||
sc.setSearchStartingCallback(null, function (sc, searcher, qry) {
|
||||
sc.maxResultCount = 0;
|
||||
});
|
||||
|
||||
sc.setSearchCompleteCallback(null, function (sc, searcher) {
|
||||
if (searcher.results.length) {
|
||||
var cur = searcher.cursor,
|
||||
total = parseInt(cur.estimatedResultCount, 10);
|
||||
if (total > sc.maxResultCount) {
|
||||
sc.maxResultCount = total;
|
||||
$('#cse').show();
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
} else {
|
||||
$('#resultcount').hide();
|
||||
$('#no-devsearch-results').show();
|
||||
}
|
||||
$(window).resize();
|
||||
});
|
||||
|
||||
$('#cse').hide();
|
||||
sc.draw('cse', opt);
|
||||
sc.execute();
|
||||
|
||||
if (!qry.val()) {
|
||||
$('#resultcount').show();
|
||||
}
|
||||
|
||||
$('#searchbox').submit(
|
||||
_pd(function (e) {
|
||||
sc.execute();
|
||||
}),
|
||||
);
|
||||
}, true);
|
|
@ -406,36 +406,6 @@ $.fn.modal = function (click_target, o) {
|
|||
return $modal;
|
||||
};
|
||||
|
||||
// Modal from URL. Pass in a URL, and load it in a modal.
|
||||
function modalFromURL(url, settings) {
|
||||
settings = settings || {};
|
||||
var a = $('<a>'),
|
||||
defaults = { deleteme: true, close: true },
|
||||
data = settings.data || {},
|
||||
callback = settings.callback;
|
||||
|
||||
delete settings.callback;
|
||||
settings = $.extend(defaults, settings);
|
||||
|
||||
var inside = $('<div>', {
|
||||
class: 'modal-inside',
|
||||
text: gettext('Loading...'),
|
||||
}),
|
||||
modal = $('<div>', { class: 'modal' }).modal(a, settings);
|
||||
|
||||
modal.append(inside);
|
||||
a.trigger('click');
|
||||
|
||||
$.get(url, data, function (html) {
|
||||
modal.appendTo('body');
|
||||
inside.html('').append(html);
|
||||
if (callback) {
|
||||
callback.call(modal);
|
||||
}
|
||||
});
|
||||
return modal;
|
||||
}
|
||||
|
||||
// Slugify
|
||||
// This allows you to create a line of text with a "Edit" field,
|
||||
// and swap it out for an editable slug. For example:
|
||||
|
@ -658,17 +628,3 @@ $.fn.exists = function (callback, args) {
|
|||
}
|
||||
return len > 0;
|
||||
};
|
||||
|
||||
// Bind to the mobile site if a mobile link is clicked.
|
||||
$(document).on('click', '.mobile-link', function (e) {
|
||||
e.preventDefault();
|
||||
$.cookie('mamo', 'on', { expires: 30, path: '/' });
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
$(function () {
|
||||
'use strict';
|
||||
|
||||
// Notification banners that go on every page.
|
||||
initBanners();
|
||||
});
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
$(document).ready(function () {
|
||||
if (!$(document.body).hasClass('home')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#homepage .listing-header a').click(function (e) {
|
||||
e.preventDefault();
|
||||
update(this, true);
|
||||
});
|
||||
|
||||
// Switch to the tab of the <a> given as `link`.
|
||||
// Only call pushState if `push` is True.
|
||||
function update(link, push) {
|
||||
var target = $(link).attr('data-target');
|
||||
|
||||
// Change the list to show the right add-ons.
|
||||
$('.addon-listing').attr('class', 'addon-listing addon-listing-' + target);
|
||||
|
||||
// Update the selected tab.
|
||||
$('.listing-header .selected').removeClass('selected');
|
||||
$('#' + target)
|
||||
.addClass('selected')
|
||||
.focus();
|
||||
|
||||
if (push && history.pushState) {
|
||||
history.pushState({ target: target }, document.title, link.href);
|
||||
}
|
||||
}
|
||||
|
||||
// If we already have a hash, switch to the tab.
|
||||
if (location.hash) {
|
||||
var selected = $('#homepage .listing-header ' + location.hash);
|
||||
if (selected) {
|
||||
selected.find('a').click().focus();
|
||||
}
|
||||
} else {
|
||||
// Add the current page to the history so we can get back.
|
||||
var selected = $('#homepage .listing-header .selected a')[0];
|
||||
update(selected, true, true);
|
||||
}
|
||||
|
||||
// Set up our history callback.
|
||||
$(window).on('popstate', function (ev) {
|
||||
// We don't pushState here because we'd be stuck in this position.
|
||||
var e = ev.originalEvent;
|
||||
if (e.state && e.state.target) {
|
||||
var a = $('#' + e.state.target + ' a')[0];
|
||||
update(a, false);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -2,30 +2,6 @@
|
|||
var z = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
// Initialize install buttons.
|
||||
$('.install').installButton();
|
||||
$(window).trigger('buttons_loaded');
|
||||
|
||||
// Initialize any tabbed interfaces. See: tabs.js
|
||||
if ($.fn.tabify) {
|
||||
$('.tab-wrapper').tabify();
|
||||
}
|
||||
|
||||
// Initialize email links
|
||||
$('span.emaillink').each(function () {
|
||||
$(this).find('.i').remove();
|
||||
var em = $(this).text().split('').reverse().join('');
|
||||
$(this)
|
||||
.prev('a')
|
||||
.attr('href', 'mailto:' + em)
|
||||
.addClass('email');
|
||||
});
|
||||
|
||||
// fake placeholders if we need to.
|
||||
if (!('placeholder' in document.createElement('input'))) {
|
||||
$('input[placeholder]').placeholder();
|
||||
}
|
||||
|
||||
if (z.readonly) {
|
||||
$('form[method=post]')
|
||||
.before(
|
||||
|
@ -39,17 +15,6 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
z.inlineSVG = (function () {
|
||||
var e = document.createElement('div');
|
||||
e.innerHTML = '<svg></svg>';
|
||||
return !!(
|
||||
window.SVGSVGElement && e.firstChild instanceof window.SVGSVGElement
|
||||
);
|
||||
})();
|
||||
if (!z.inlineSVG) {
|
||||
$('body').addClass('noInlineSVG');
|
||||
}
|
||||
|
||||
/* prevent-default function wrapper */
|
||||
function _pd(func) {
|
||||
return function (e) {
|
||||
|
@ -58,53 +23,6 @@ function _pd(func) {
|
|||
};
|
||||
}
|
||||
|
||||
/* Fake the placeholder attribute since Firefox 3.6 doesn't support it. */
|
||||
jQuery.fn.placeholder = function (new_value) {
|
||||
if (new_value) {
|
||||
this.attr('placeholder', new_value);
|
||||
}
|
||||
|
||||
/* Bail early if we have built-in placeholder support. */
|
||||
if ('placeholder' in document.createElement('input')) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (new_value && this.hasClass('placeholder')) {
|
||||
this.val('').blur();
|
||||
}
|
||||
|
||||
return this.focus(function () {
|
||||
var $this = $(this),
|
||||
text = $this.attr('placeholder');
|
||||
|
||||
if ($this.val() == text) {
|
||||
$this.val('').removeClass('placeholder');
|
||||
}
|
||||
})
|
||||
.blur(function () {
|
||||
var $this = $(this),
|
||||
text = $this.attr('placeholder');
|
||||
|
||||
if ($this.val() === '') {
|
||||
$this.val(text).addClass('placeholder');
|
||||
}
|
||||
})
|
||||
.each(function () {
|
||||
/* Remove the placeholder text before submitting the form. */
|
||||
var self = $(this);
|
||||
self.closest('form').submit(function () {
|
||||
if (self.hasClass('placeholder')) {
|
||||
self.val('');
|
||||
}
|
||||
});
|
||||
})
|
||||
.blur();
|
||||
};
|
||||
|
||||
jQuery.fn.hasattr = function (name) {
|
||||
return this.attr(name) !== undefined;
|
||||
};
|
||||
|
||||
var escape_ = function (s) {
|
||||
if (s === undefined) {
|
||||
return;
|
||||
|
@ -117,28 +35,6 @@ var escape_ = function (s) {
|
|||
.replace(/"/g, '"');
|
||||
};
|
||||
|
||||
//TODO(potch): kill underscore dead. until then, fake it on mobile.
|
||||
if (!('_' in window)) _ = {};
|
||||
/* is ``key`` in obj? */
|
||||
_.haskey = function (obj, key) {
|
||||
return typeof obj[key] !== 'undefined';
|
||||
};
|
||||
|
||||
/* Detect browser, version, and OS. */
|
||||
$.extend(z, BrowserUtils());
|
||||
$(document.body).addClass(z.platform).toggleClass('badbrowser', z.badBrowser);
|
||||
|
||||
/* Details for the current application. */
|
||||
z.app = document.body.getAttribute('data-app');
|
||||
z.appName = document.body.getAttribute('data-appname');
|
||||
z.appMatchesUserAgent = z.browser[z.app];
|
||||
|
||||
z.anonymous = JSON.parse(document.body.getAttribute('data-anonymous'));
|
||||
|
||||
z.static_url = document.body.getAttribute('data-static-url');
|
||||
|
||||
z.readonly = JSON.parse(document.body.getAttribute('data-readonly'));
|
||||
|
||||
if (z.badBrowser) {
|
||||
$('.get-fx-message').show();
|
||||
}
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
$(document).ready(function () {
|
||||
var report = $('.review-reason').html();
|
||||
|
||||
$('.review-reason').popup('.flag-review', {
|
||||
delegate: $(document.body),
|
||||
width: 'inherit',
|
||||
callback: function (obj) {
|
||||
var ct = $(obj.click_target),
|
||||
$popup = this;
|
||||
|
||||
function addFlag(flag, note) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: ct.attr('href'),
|
||||
data: { flag: flag, note: note },
|
||||
success: function () {
|
||||
$popup.removeClass('other').hideMe();
|
||||
ct.replaceWith(gettext('Flagged for review'));
|
||||
},
|
||||
error: function () {},
|
||||
dataType: 'json',
|
||||
});
|
||||
}
|
||||
|
||||
$popup.on('click', 'li a', function (e) {
|
||||
e.preventDefault();
|
||||
var el = $(e.target);
|
||||
if (el.attr('href') == '#review_flag_reason_other') {
|
||||
$popup
|
||||
.addClass('other')
|
||||
.on('submit', 'form', function (e) {
|
||||
e.preventDefault();
|
||||
var note = $popup.find('#id_note').val();
|
||||
if (!note) {
|
||||
alert(gettext('Your input is required'));
|
||||
} else {
|
||||
addFlag('review_flag_reason_other', note);
|
||||
}
|
||||
})
|
||||
.setPos(ct)
|
||||
.find('input[type=text]')
|
||||
.focus();
|
||||
} else {
|
||||
addFlag(el.attr('href').slice(1));
|
||||
}
|
||||
});
|
||||
|
||||
$popup.html(report);
|
||||
return { pointTo: ct };
|
||||
},
|
||||
});
|
||||
|
||||
$('.primary').on('click', '.review-edit', function (e) {
|
||||
e.preventDefault();
|
||||
var $form = $('#review-edit-form'),
|
||||
$review = $(this).parents('.review'),
|
||||
rating = $review.attr('data-rating'),
|
||||
edit_url = $('a.permalink', $review).attr('href') + 'edit';
|
||||
$cancel = $('#review-edit-cancel');
|
||||
|
||||
$review.attr('action', edit_url);
|
||||
$form.detach().insertAfter($review);
|
||||
$('#id_title').val($review.children('h5').text());
|
||||
$('.ratingwidget input:radio[value=' + rating + ']', $form).click();
|
||||
$('#id_body').val($review.children('p.review-body').text());
|
||||
$review.hide();
|
||||
$form.show();
|
||||
|
||||
function done_edit() {
|
||||
$form.off().hide();
|
||||
$review.show();
|
||||
$cancel.off();
|
||||
}
|
||||
|
||||
$cancel.click(function (e) {
|
||||
e.preventDefault();
|
||||
done_edit();
|
||||
});
|
||||
|
||||
$form.submit(function (e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: edit_url,
|
||||
data: $form.serialize(),
|
||||
success: function (response, status) {
|
||||
$review.children('h5').text($('#id_title').val());
|
||||
rating = $('.ratingwidget input:radio:checked', $form).val();
|
||||
$('.stars', $review)
|
||||
.removeClass('stars-0 stars-1 stars-2 stars-3 stars-4 stars-5')
|
||||
.addClass('stars-' + rating);
|
||||
rating = $review.attr('data-rating', rating);
|
||||
$review.children('p.review-body').text($('#id_body').val());
|
||||
done_edit();
|
||||
},
|
||||
dataType: 'json',
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
$('.delete-review').click(function (e) {
|
||||
e.preventDefault();
|
||||
var target = $(e.target);
|
||||
$.post(target.attr('href'), function () {
|
||||
target.replaceWith(gettext('Marked for deletion'));
|
||||
});
|
||||
target.closest('.review').addClass('deleted');
|
||||
});
|
||||
|
||||
$("select[name='rating']").ratingwidget();
|
||||
|
||||
$('.review-flagged.disabled input:not([type=hidden])').prop('disabled', true);
|
||||
});
|
|
@ -1,137 +0,0 @@
|
|||
/* Plugin to create tab interface, loosely based on jquery.ui.tabs. */
|
||||
|
||||
/* A Tabs object is a collection of tabs and panels. A tab is an <a href>
|
||||
* pointing to the id of a panel.
|
||||
*
|
||||
* The element bound to the Tabs object will be given a .tab member by the
|
||||
* .tabify() plugin so it can be accessed later.
|
||||
*/
|
||||
|
||||
var Tabs = function (el) {
|
||||
this.root = $(el);
|
||||
this.init();
|
||||
};
|
||||
|
||||
Tabs.prototype = {
|
||||
init: function () {
|
||||
this.root.addClass('tab-wrapper');
|
||||
this.tabMap = {};
|
||||
this.panelMap = {};
|
||||
this.reset();
|
||||
|
||||
this.select();
|
||||
|
||||
/* Bind hashchange, trigger event to check for existing hash. */
|
||||
var self = this;
|
||||
$(document)
|
||||
.on('hashchange', function (e) {
|
||||
self.hashChange(e);
|
||||
})
|
||||
.trigger('hashchange');
|
||||
},
|
||||
|
||||
/* Find and prepare all the tabs and panels. Can be called multiple times,
|
||||
* e.g. to update tabs after insertion/deletion.
|
||||
*/
|
||||
reset: function (o) {
|
||||
this.findTabs();
|
||||
this.findPanels();
|
||||
this.styleTabs(this.tabs);
|
||||
this.stylePanels(this.panels);
|
||||
return this;
|
||||
},
|
||||
|
||||
/* Find tabs (li a[href]) and bind their click event. */
|
||||
findTabs: function () {
|
||||
this.list = this.root.find('ol,ul').eq(0);
|
||||
this.tabs = $('li:has(a[href])', this.list);
|
||||
|
||||
var self = this;
|
||||
var cb = function (e) {
|
||||
e.preventDefault();
|
||||
self.select($(e.target).attr('href'), true);
|
||||
$('a', this).blur();
|
||||
};
|
||||
this.tabs.off('click', cb).click(cb);
|
||||
},
|
||||
|
||||
/* Get the fragment this tab points to. */
|
||||
getHash: function (tab) {
|
||||
return $(tab).find('a').attr('href');
|
||||
},
|
||||
|
||||
/* Find all the panels to go along with the tabs. */
|
||||
findPanels: function () {
|
||||
var self = this;
|
||||
var panels = [];
|
||||
this.tabs.each(function () {
|
||||
var hash = self.getHash(this);
|
||||
var panel = self.root.find(hash)[0];
|
||||
if (panel) {
|
||||
self.tabMap[hash] = this;
|
||||
self.panelMap[hash] = panel;
|
||||
panels.push(panel);
|
||||
}
|
||||
});
|
||||
this.panels = $(panels);
|
||||
},
|
||||
|
||||
styleTabs: function (tabs) {
|
||||
tabs = tabs || self.tabs;
|
||||
this.list.addClass('tab-nav');
|
||||
$(tabs).addClass('tab');
|
||||
},
|
||||
|
||||
stylePanels: function (panels) {
|
||||
panels = panels || self.panels;
|
||||
$(panels).addClass('tab-panel');
|
||||
},
|
||||
|
||||
/* Focus on the tab pointing to #hash.
|
||||
* If hash is not given, the first tab will be selected.
|
||||
* If updateHash is true, location.hash will be updated.
|
||||
*/
|
||||
select: function (hash, updateHash) {
|
||||
if (typeof hash === 'undefined') {
|
||||
if (!this.tabs.filter('.tab-selected').length) {
|
||||
return this.select(this.getHash(this.tabs[0]));
|
||||
}
|
||||
}
|
||||
|
||||
var tab = this.tabMap[hash],
|
||||
panel = this.panelMap[hash];
|
||||
|
||||
this.tabs.filter('.tab-selected').removeClass('tab-selected');
|
||||
this.panels.filter('.tab-selected').removeClass('tab-selected');
|
||||
$([tab, panel]).addClass('tab-selected');
|
||||
|
||||
this.root.trigger('tabselect', { tab: tab, panel: panel });
|
||||
|
||||
if (updateHash) {
|
||||
safeHashChange(hash);
|
||||
}
|
||||
},
|
||||
|
||||
/* Handler for onhashchange. */
|
||||
hashChange: function (e) {
|
||||
if (location.hash && _.haskey(this.tabMap, location.hash)) {
|
||||
e.preventDefault();
|
||||
this.select(location.hash);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
$.fn.tabify = function () {
|
||||
this.each(function () {
|
||||
this.tab = new Tabs(this);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/* Change location.hash without scrolling. */
|
||||
var safeHashChange = function (hash) {
|
||||
var el = $(hash);
|
||||
el.attr('id', '');
|
||||
location.hash = hash;
|
||||
el.attr('id', hash.slice(1));
|
||||
};
|
|
@ -1,31 +0,0 @@
|
|||
$(document).ready(function () {
|
||||
if (!z.appMatchesUserAgent) {
|
||||
return;
|
||||
}
|
||||
|
||||
var themeCompat = function () {
|
||||
var $el = $(this),
|
||||
min = $el.attr('data-min'),
|
||||
max = $el.attr('data-max');
|
||||
if (
|
||||
VersionCompare.compareVersions(z.browserVersion, min) < 0 ||
|
||||
VersionCompare.compareVersions(z.browserVersion, max) > 0
|
||||
) {
|
||||
$el.addClass('incompatible');
|
||||
var msg = format(
|
||||
gettext('This theme is incompatible with your version of {0}'),
|
||||
[z.appName],
|
||||
);
|
||||
$el.append('<div class="overlay"><p>' + msg + '</p></div>').hover(
|
||||
function () {
|
||||
$(this).children('.overlay').show();
|
||||
},
|
||||
function () {
|
||||
$(this).children('.overlay').fadeOut();
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$('.browse-thumbs .thumbs li').each(themeCompat);
|
||||
});
|
|
@ -21,6 +21,11 @@
|
|||
};
|
||||
})(jQuery);
|
||||
|
||||
function fieldFocused(e) {
|
||||
var tags = /input|keygen|meter|option|output|progress|select|textarea/i;
|
||||
return tags.test(e.target.nodeName);
|
||||
}
|
||||
|
||||
(function ($) {
|
||||
var win = $(window);
|
||||
var doc = $(document);
|
||||
|
|
|
@ -8,26 +8,3 @@ $('#fake_fxa_authorization .accounts a').click(function (e) {
|
|||
e.preventDefault();
|
||||
$('#fake_fxa_authorization').submit();
|
||||
});
|
||||
|
||||
// Recaptcha
|
||||
var RecaptchaOptions = { theme: 'custom' };
|
||||
|
||||
$('#recaptcha_different').click(function (e) {
|
||||
e.preventDefault();
|
||||
Recaptcha.reload();
|
||||
});
|
||||
|
||||
$('#recaptcha_audio').click(function (e) {
|
||||
e.preventDefault();
|
||||
var toggleType = this.getAttribute('data-nextType') || 'audio';
|
||||
Recaptcha.switch_type(toggleType);
|
||||
this.setAttribute(
|
||||
'data-nextType',
|
||||
toggleType === 'audio' ? 'image' : 'audio',
|
||||
);
|
||||
});
|
||||
|
||||
$('#recaptcha_help').click(function (e) {
|
||||
e.preventDefault();
|
||||
Recaptcha.showhelp();
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче