зеркало из https://github.com/mozilla/kitsune.git
[bug 651225] [bug 639854] Merge showfor behavior updates for FF5 and beyond.
* Change meaning of showfor browser slugs (like "fx4") to mean "fx4 and beyond". * Generalize mobile and desktop fallback so we don't have to hard-code the current versions into the JS.
This commit is contained in:
Коммит
472cec7c50
|
@ -50,12 +50,18 @@ CATEGORIES = (
|
|||
# FF versions used to filter article searches, power {for} tags, etc.:
|
||||
#
|
||||
# Iterables of (ID, name, abbreviation for {for} tags, max version this version
|
||||
# group encompasses) grouped into optgroups. To add the ability to sniff a new
|
||||
# version of an existing browser (assuming it doesn't change the user agent
|
||||
# string too radically), you should need only to add a line here; no JS
|
||||
# required. Just be wary of inexact floating point comparisons when setting
|
||||
# max_version, which should be read as "From the next smaller max_version up to
|
||||
# but not including version x.y".
|
||||
# group encompasses, and whether-this-version-should-show-in-the-menu) grouped
|
||||
# into optgroups by platform. To add the ability to sniff a new version of an
|
||||
# existing browser (assuming it doesn't change the user agent string too
|
||||
# radically), you should need only to add a line here; no JS required. Just be
|
||||
# wary of inexact floating point comparisons when setting max_version, which
|
||||
# should be read as "From the next smaller max_version up to but not including
|
||||
# version x.y".
|
||||
#
|
||||
# The first browser in each platform group will be considered the default one.
|
||||
# When a wiki page is being viewed in a desktop browser, the {for} sections for
|
||||
# the default mobile browser still show. The reverse is true when a page is
|
||||
# being viewed in a mobile browser.
|
||||
VersionMetadata = namedtuple('VersionMetadata',
|
||||
'id, name, long, slug, max_version, show_in_ui')
|
||||
GROUPED_FIREFOX_VERSIONS = (
|
||||
|
|
|
@ -37,10 +37,20 @@ from wiki.tasks import send_reviewed_notification, schedule_rebuild_kb
|
|||
log = logging.getLogger('k.wiki')
|
||||
|
||||
|
||||
def _split_browser_slug(slug):
|
||||
"""Given something like fx35, split it into an alphabetic prefix and a
|
||||
suffix, returning a 2-tuple like ('fx', '35')."""
|
||||
right = slug.lstrip(ascii_letters)
|
||||
left_len = len(slug) - len(right)
|
||||
return slug[:left_len], slug[left_len:]
|
||||
|
||||
|
||||
OS_ABBR_JSON = json.dumps(dict([(o.slug, True)
|
||||
for o in OPERATING_SYSTEMS]))
|
||||
BROWSER_ABBR_JSON = json.dumps(dict([(v.slug, v.show_in_ui)
|
||||
for v in FIREFOX_VERSIONS]))
|
||||
BROWSER_ABBR_JSON = json.dumps(
|
||||
dict([(v.slug, {'product': _split_browser_slug(v.slug)[0],
|
||||
'maxFloatVersion': v.max_version})
|
||||
for v in FIREFOX_VERSIONS]))
|
||||
|
||||
|
||||
def _version_groups(versions):
|
||||
|
@ -49,16 +59,9 @@ def _version_groups(versions):
|
|||
See test_version_groups for an example.
|
||||
|
||||
"""
|
||||
def split_slug(slug):
|
||||
"""Given something like fx35, split it into an alphabetic prefix and a
|
||||
suffix, returning a 2-tuple like ('fx', '35')."""
|
||||
right = slug.lstrip(ascii_letters)
|
||||
left_len = len(slug) - len(right)
|
||||
return slug[:left_len], slug[left_len:]
|
||||
|
||||
slug_groups = {}
|
||||
for v in versions:
|
||||
left, right = split_slug(v.slug)
|
||||
left, right = _split_browser_slug(v.slug)
|
||||
slug_groups.setdefault(left, []).append((v.max_version, right))
|
||||
for g in slug_groups.itervalues():
|
||||
g.sort()
|
||||
|
|
|
@ -60,18 +60,56 @@ var ShowFor = {
|
|||
$osMenu = $container.find(options.osSelector),
|
||||
$browserMenu = $container.find(options.browserSelector),
|
||||
$origBrowserOptions = $browserMenu.find('option').clone(),
|
||||
defaults = {
|
||||
mobile: {
|
||||
browser: $origBrowserOptions.filter('[data-dependency="mobile"]:first').val(),
|
||||
os: $osMenu.find('[data-dependency="mobile"]:first').val()
|
||||
},
|
||||
desktop: {
|
||||
browser: $origBrowserOptions.filter('[data-dependency="desktop"]:first').val(),
|
||||
os: $osMenu.find('[data-dependency="desktop"]:first').val()
|
||||
}
|
||||
},
|
||||
$body = $('body'),
|
||||
hash = self.hashFragment(),
|
||||
isSetManually;
|
||||
|
||||
OSES = $osMenu.data('oses'); // {'mac': true, 'win': true, ...}
|
||||
BROWSERS = $browserMenu.data('browsers'); // {'fx4': true, ...}
|
||||
BROWSERS = $browserMenu.data('browsers'); // {'fx4': {product: 'fx', maxFloatVersion: 4.9999}, ...}
|
||||
VERSIONS = $browserMenu.data('version-groups'); // {'fx': [[3.4999, '3'], [3.9999, '35']], 'm': [[1.0999, '1'], [1.9999, '11']]}
|
||||
MISSING_MSG = gettext('[missing header]');
|
||||
|
||||
// Make the 'Table of Contents' header localizable.
|
||||
$('#toc > h2').text(gettext('Table of Contents'));
|
||||
|
||||
// Given a symbol like 'm4' or '=fx4', return something like
|
||||
// {comparator: '>', product: 'm', version: 4.9999}. Even if there's no
|
||||
// explicit comparator in the symbol, the comparator that's assumed
|
||||
// will be made explicit in the returned object. If it's not a known
|
||||
// product/version combo, return undefined.
|
||||
function conditionFromSymbol(symbol) {
|
||||
var slug, browser, comparator;
|
||||
|
||||
// Figure out comparator:
|
||||
if (symbol.substring(0, 1) == '=') {
|
||||
comparator = '=';
|
||||
slug = symbol.substring(1);
|
||||
} else { // If no leading =, assume >=.
|
||||
comparator = '>=';
|
||||
slug = symbol;
|
||||
}
|
||||
|
||||
// Special case: fx3 and fx35 act like =fx3 and =fx35.
|
||||
if (slug == 'fx3' || slug == 'fx35') {
|
||||
comparator = '=';
|
||||
}
|
||||
|
||||
browser = BROWSERS[slug];
|
||||
return {comparator: comparator,
|
||||
product: browser.product,
|
||||
version: browser.maxFloatVersion};
|
||||
}
|
||||
|
||||
function updateForsAndToc(calledOnLoad) {
|
||||
// Hide and show document sections accordingly:
|
||||
showAndHideFors($osMenu.val(), $browserMenu.val());
|
||||
|
@ -93,29 +131,70 @@ var ShowFor = {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Set the {for} nodes to the proper visibility for the given OS and
|
||||
// browser combination.
|
||||
//
|
||||
// Hidden are {for}s that {list at least one OS but not the passed-in one}
|
||||
// or that {list at least one browser but not the passed-in one}. Also, the
|
||||
// entire condition can be inverted by prefixing it with "not ", as in {for
|
||||
// not mac,linux}.
|
||||
// Hidden are {for}s that {list at least one OS but not the passed-in
|
||||
// one} or that {list at least one browser expression but none matching
|
||||
// the passed-in one}. Also, the entire condition can be inverted by
|
||||
// prefixing it with "not ", as in {for not mac,linux}.
|
||||
//
|
||||
// Takes a browser slug like "fx4" rather than a browser code and a
|
||||
// raw floating-point version because it has to be able to take both
|
||||
// detected browsers and slugs chosen explicitly from the <select>.
|
||||
function showAndHideFors(os, browser) {
|
||||
$container.find('.for').each(function(index) {
|
||||
var osAttrs = {}, browserAttrs = {},
|
||||
var platform = $osMenu.find('option:selected').data('dependency'),
|
||||
osAttrs = {}, browserAttrs = {}, // TODO: Eliminate browserAttrs?
|
||||
foundAnyOses = false, foundAnyBrowsers = false,
|
||||
forData,
|
||||
isInverted,
|
||||
shouldHide;
|
||||
shouldHide,
|
||||
browserConditions = [];
|
||||
|
||||
// Catch the "not" operator if it's there:
|
||||
// Return whether the given browser slug matches any of the
|
||||
// given conditions. Passing an unknown slug results in
|
||||
// undefined behavior.
|
||||
// TODO: Implement with a generic any() instead--maybe underscore's.
|
||||
function meetsAnyOfConditions(slug, conditions) {
|
||||
// Return whether a slug (like 'fx4' or 'fx35') meets a condition like
|
||||
// {comparator: '>' product: 'm', version: 4.9999}.
|
||||
function meets(slug, condition) {
|
||||
var browser = BROWSERS[slug];
|
||||
if (browser.product != condition.product) {
|
||||
return false;
|
||||
}
|
||||
switch (condition.comparator) {
|
||||
case '=':
|
||||
// =fx35 --> {comparator: '=' browser: 'fx', version: 3.9999}
|
||||
return browser.maxFloatVersion == condition.version;
|
||||
case '>=':
|
||||
// fx4 --> {comparator: '>=' browser: 'fx', version: 4.9999}
|
||||
return browser.maxFloatVersion >= condition.version;
|
||||
// Insert '<' here someday.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < conditions.length; i++) {
|
||||
if (meets(slug, conditions[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function slugWithoutComparators(slug) {
|
||||
return (slug.substring(0, 1) == '=') ? slug.substring(1) : slug;
|
||||
}
|
||||
|
||||
// If the data-for attribute is missing, return.
|
||||
forData = $(this).data('for');
|
||||
if (!forData) {
|
||||
// If the data-for attribute is missing, move on.
|
||||
return;
|
||||
}
|
||||
|
||||
// Catch the "not" operator if it's there:
|
||||
isInverted = forData.substring(0, 4) == 'not ';
|
||||
if (isInverted) {
|
||||
forData = forData.substring(4); // strip off "not "
|
||||
|
@ -123,30 +202,41 @@ var ShowFor = {
|
|||
|
||||
// Divide {for} attrs into OSes and browsers:
|
||||
$(forData.split(',')).each(function(index) {
|
||||
if (OSES[this] != undefined) {
|
||||
if (OSES[this] !== undefined) {
|
||||
osAttrs[this] = true;
|
||||
foundAnyOses = true;
|
||||
} else if (BROWSERS[this] != undefined) {
|
||||
} else if (BROWSERS[slugWithoutComparators(this)] !== undefined) {
|
||||
browserAttrs[this] = true;
|
||||
browserConditions.push(conditionFromSymbol(this));
|
||||
foundAnyBrowsers = true;
|
||||
}
|
||||
});
|
||||
|
||||
shouldHide = ((foundAnyOses && osAttrs[os] == undefined) ||
|
||||
(foundAnyBrowsers && browserAttrs[browser] == undefined)) &&
|
||||
// Special cases ):
|
||||
// TODO: make this easier to maintain somehow?
|
||||
// Show android/m4 on desktop selection
|
||||
!(osAttrs['android'] && os !== 'maemo' /* only one mobile browser ATM */) &&
|
||||
!(browserAttrs['m4'] && browser !== 'm4' && (osAttrs['android'] || !foundAnyOses)) &&
|
||||
// Show win/fx4 on mobile selection
|
||||
!(osAttrs['win'] && (os === 'android' || os == 'maemo') && (browserAttrs['fx4'] || !foundAnyBrowsers)) &&
|
||||
!(browserAttrs['fx4'] && browser === 'm4' && (osAttrs['win'] || !foundAnyOses));
|
||||
shouldHide = ((foundAnyOses && osAttrs[os] === undefined) ||
|
||||
(foundAnyBrowsers && !meetsAnyOfConditions(browser, browserConditions))) &&
|
||||
// Special cases:
|
||||
// If the current selection is desktop:
|
||||
// * Show the default mobile OS if no browser was specified or
|
||||
// the default mobile browser was also specified.
|
||||
!(osAttrs[defaults.mobile.os] && platform === 'desktop' &&
|
||||
(browserAttrs[defaults.mobile.browser] || !foundAnyBrowsers)) &&
|
||||
// * Show the default mobile browser if no OS was specified or
|
||||
// the default mobile OS was also specified.
|
||||
!(browserAttrs[defaults.mobile.browser] && platform === 'desktop' &&
|
||||
(osAttrs[defaults.mobile.os] || !foundAnyOses)) &&
|
||||
// If the current selection is mobile:
|
||||
// * Show the default desktop OS if no browser was specified or
|
||||
// the default desktop browser was also specified.
|
||||
!(osAttrs[defaults.desktop.os] && platform === 'mobile' &&
|
||||
(browserAttrs[defaults.desktop.browser] || !foundAnyBrowsers)) &&
|
||||
// * Show the default desktop browser if no OS was specified or
|
||||
// the default desktop OS was also specified.
|
||||
!(browserAttrs[defaults.desktop.browser] && platform === 'mobile' &&
|
||||
(osAttrs[defaults.desktop.os] || !foundAnyOses));
|
||||
|
||||
if ((shouldHide && !isInverted) || (!shouldHide && isInverted)) {
|
||||
if (shouldHide != isInverted) {
|
||||
$(this).hide(); // saves original visibility, which is nice but not necessary
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$(this).show(); // restores original visibility
|
||||
}
|
||||
});
|
||||
|
@ -312,7 +402,7 @@ var ShowFor = {
|
|||
|
||||
// Fire off the change handler for the first time:
|
||||
updateForsAndToc(true);
|
||||
|
||||
|
||||
updateShowforSelectors();
|
||||
},
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ module('showfor', showforFixture);
|
|||
|
||||
function assertNotVisible($sandbox, forVals) {
|
||||
for (var i=0,l=forVals.length; i<l; i++) {
|
||||
equals($sandbox.find('[data-for="not ' + forVals[i] + '"]:visible').length, 0,
|
||||
equals($sandbox.find('[data-for="' + forVals[i] + '"]:visible').length, 0,
|
||||
'[data-for=' + forVals[i] + '] is not visible');
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ test('windows fx4', function() {
|
|||
$('#_input_fx4').click();
|
||||
equals(this.$o.val(), 'win', 'Windows is now selected');
|
||||
equals(this.$b.val(), 'fx4', 'Firefox 4 is now selected');
|
||||
assertNotHidden(this.$sandbox, ['win', 'not mac', 'android', 'fx35,fx4', 'm4']);
|
||||
assertNotVisible(this.$sandbox, ['mac,linux', 'maemo', 'fx3']);
|
||||
assertNotHidden(this.$sandbox, ['win', 'not mac', 'android', 'fx35,fx4', 'fx4', 'm4']);
|
||||
assertNotVisible(this.$sandbox, ['mac,linux', 'maemo', 'fx3', 'fx5']);
|
||||
});
|
||||
|
||||
test('linux fx35', function() {
|
||||
|
@ -57,7 +57,16 @@ test('linux fx35', function() {
|
|||
equals(this.$o.val(), 'linux', 'Linux is now selected');
|
||||
equals(this.$b.val(), 'fx35', 'Firefox 3.5/6 is now selected');
|
||||
assertNotHidden(this.$sandbox, ['not mac', 'mac,linux', 'android', 'fx35,fx4', 'm4']);
|
||||
assertNotVisible(this.$sandbox, ['win', 'maemo', 'fx3']);
|
||||
assertNotVisible(this.$sandbox, ['win', 'maemo', 'fx3', 'fx4', 'fx5']);
|
||||
});
|
||||
|
||||
test('mac fx5', function() {
|
||||
$('#_input_mac').click();
|
||||
$('#_input_fx5').click();
|
||||
equals(this.$o.val(), 'mac', 'Mac is now selected');
|
||||
equals(this.$b.val(), 'fx5', 'Firefox 5 is now selected');
|
||||
assertNotHidden(this.$sandbox, ['mac,linux', 'android', 'm4', 'fx35,fx4', 'fx4', 'fx5']);
|
||||
assertNotVisible(this.$sandbox, ['not mac', 'win', 'maemo', 'fx3']);
|
||||
});
|
||||
|
||||
test('android m4', function() {
|
||||
|
@ -65,8 +74,8 @@ test('android m4', function() {
|
|||
$('#_input_m4').click();
|
||||
equals(this.$o.val(), 'android', 'Android is now selected');
|
||||
equals(this.$b.val(), 'm4', 'Firefox 4 is now selected');
|
||||
assertNotHidden(this.$sandbox, ['win', 'not mac', 'android', 'fx35,fx4', 'm4']);
|
||||
assertNotVisible(this.$sandbox, ['mac,linux', 'maemo', 'fx3']);
|
||||
assertNotHidden(this.$sandbox, ['win', 'not mac', 'android', 'm4', 'fx5']);
|
||||
assertNotVisible(this.$sandbox, ['mac,linux', 'maemo', 'fx35,fx4', 'fx4', 'fx3']);
|
||||
});
|
||||
|
||||
|
||||
|
@ -75,8 +84,8 @@ test('maemo m4', function() {
|
|||
$('#_input_m4').click();
|
||||
equals(this.$o.val(), 'maemo', 'Maemo is now selected');
|
||||
equals(this.$b.val(), 'm4', 'Firefox 4 is now selected');
|
||||
assertNotHidden(this.$sandbox, ['win', 'not mac', 'maemo', 'fx35,fx4', 'm4']);
|
||||
assertNotVisible(this.$sandbox, ['mac,linux', 'android', 'fx3']);
|
||||
assertNotHidden(this.$sandbox, ['win', 'not mac', 'maemo', 'm4', 'fx5']);
|
||||
assertNotVisible(this.$sandbox, ['mac,linux', 'android', 'fx35,fx4', 'fx4', 'fx3']);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -52,10 +52,11 @@
|
|||
<option value="android" data-dependency="mobile">Android</option>
|
||||
<option value="maemo" data-dependency="mobile">Maemo</option>
|
||||
</select>
|
||||
<select class="browser" data-browsers="{"fx4": true, "fx3": false, "fx35": true, "m4": true}" data-version-groups="{"fx": [[3.4998999999999998, "3"], [3.9998999999999998, "35"], [4.9999000000000002, "4"]], "m": [[4.9999000000000002, "4"]]}">
|
||||
<select class="browser" data-browsers="{"fx5": {"product": "fx", "maxFloatVersion": 5.9999000000000002}, "fx4": {"product": "fx", "maxFloatVersion": 4.9999000000000002}, "fx6": {"product": "fx", "maxFloatVersion": 6.9999000000000002}, "fx3": {"product": "fx", "maxFloatVersion": 3.4998999999999998}, "m5": {"product": "m", "maxFloatVersion": 5.9999000000000002}, "m4": {"product": "m", "maxFloatVersion": 4.9999000000000002}, "m6": {"product": "m", "maxFloatVersion": 6.9999000000000002}, "fx35": {"product": "fx", "maxFloatVersion": 3.9998999999999998}}" data-version-groups="{"fx": [[3.4998999999999998, "3"], [3.9998999999999998, "35"], [4.9999000000000002, "4"], [5.9999000000000002, "5"], [6.9999000000000002, "6"]], "m": [[4.9999000000000002, "4"], [5.9999000000000002, "5"], [6.9999000000000002, "6"]]}">
|
||||
<optgroup label="Desktop:">
|
||||
<option value="fx35" data-dependency="desktop">Firefox 3.5-3.6</option>
|
||||
<option value="fx5" data-dependency="desktop">Firefox 5</option>
|
||||
<option value="fx4" data-dependency="desktop">Firefox 4</option>
|
||||
<option value="fx35" data-dependency="desktop">Firefox 3.5-3.6</option>
|
||||
</optgroup>
|
||||
<optgroup label="Mobile:">
|
||||
<option value="m4" data-dependency="mobile">Firefox 4</option>
|
||||
|
@ -69,7 +70,9 @@
|
|||
<div class="for" data-for="android">android</div>
|
||||
<div class="for" data-for="maemo">maemo</div>
|
||||
<span class="for" data-for="fx35,fx4">fx35,fx4</span>
|
||||
<span class="for" data-for="fx4">fx4</span>
|
||||
<span class="for" data-for="fx3">fx3</span>
|
||||
<span class="for" data-for="fx5">fx5</span>
|
||||
<span class="for" data-for="m4">m4</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Загрузка…
Ссылка в новой задаче