[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:
Erik Rose 2011-05-19 13:47:11 -07:00
Родитель cdd5e7ece0 951afb76a4
Коммит 472cec7c50
5 изменённых файлов: 163 добавлений и 52 удалений

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

@ -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());
@ -97,25 +135,66 @@ var ShowFor = {
// 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
}
});

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

@ -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="{&#34;fx4&#34;: true, &#34;fx3&#34;: false, &#34;fx35&#34;: true, &#34;m4&#34;: true}" data-version-groups="{&#34;fx&#34;: [[3.4998999999999998, &#34;3&#34;], [3.9998999999999998, &#34;35&#34;], [4.9999000000000002, &#34;4&#34;]], &#34;m&#34;: [[4.9999000000000002, &#34;4&#34;]]}">
<select class="browser" data-browsers="{&#34;fx5&#34;: {&#34;product&#34;: &#34;fx&#34;, &#34;maxFloatVersion&#34;: 5.9999000000000002}, &#34;fx4&#34;: {&#34;product&#34;: &#34;fx&#34;, &#34;maxFloatVersion&#34;: 4.9999000000000002}, &#34;fx6&#34;: {&#34;product&#34;: &#34;fx&#34;, &#34;maxFloatVersion&#34;: 6.9999000000000002}, &#34;fx3&#34;: {&#34;product&#34;: &#34;fx&#34;, &#34;maxFloatVersion&#34;: 3.4998999999999998}, &#34;m5&#34;: {&#34;product&#34;: &#34;m&#34;, &#34;maxFloatVersion&#34;: 5.9999000000000002}, &#34;m4&#34;: {&#34;product&#34;: &#34;m&#34;, &#34;maxFloatVersion&#34;: 4.9999000000000002}, &#34;m6&#34;: {&#34;product&#34;: &#34;m&#34;, &#34;maxFloatVersion&#34;: 6.9999000000000002}, &#34;fx35&#34;: {&#34;product&#34;: &#34;fx&#34;, &#34;maxFloatVersion&#34;: 3.9998999999999998}}" data-version-groups="{&#34;fx&#34;: [[3.4998999999999998, &#34;3&#34;], [3.9998999999999998, &#34;35&#34;], [4.9999000000000002, &#34;4&#34;], [5.9999000000000002, &#34;5&#34;], [6.9999000000000002, &#34;6&#34;]], &#34;m&#34;: [[4.9999000000000002, &#34;4&#34;], [5.9999000000000002, &#34;5&#34;], [6.9999000000000002, &#34;6&#34;]]}">
<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>