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:
Mathieu Pillard 2022-02-09 13:54:49 +01:00 коммит произвёл GitHub
Родитель 4d7507bc50
Коммит 97f9d82cbc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
32 изменённых файлов: 14 добавлений и 2608 удалений

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

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.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&hellip;'),
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, '&#34;');
};
//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();
});