* Added new hook ParserAfterParse to allow extensions to affect parsed output
after the parse is complete but before block level processing, link holder
replacement, and so on.
+* (bug 34678) Added InternalParseBeforeSanitize hook which gets called during Parser's
+ internalParse method just before the parser removes unwanted/dangerous HTML tags.
=== Bug fixes in 1.20 ===
* (bug 30245) Use the correct way to construct a log page title.
* (bug 31895) mw.loader mode now correct when triggered from a $.fn.ready
handler that is bound before mediawiki.js's handler (e.g. browser-userscripts
like greasemonkey).
+* (bug 38152) jquery.tablesorter: Use .data() instead of .attr(), so that live
+ values are used instead of just the fixed values from when the tablesorter
+ was initialized.
=== API changes in 1.20 ===
* (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
&$iwData: output array describing the interwiki with keys iw_url, iw_local,
iw_trans and optionally iw_api and iw_wikiid.
+'InternalParseBeforeSanitize': during Parser's internalParse method just before the
+parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/
+onlyinclude and other processings. Ideal for syntax-extensions after template/parser
+function execution which respect nowiki and HTML-comments.
+&$parser: Parser object
+&$text: string containing partially parsed text
+&$stripState: Parser's internal StripState object
+
'InternalParseBeforeLinks': during Parser's internalParse method before links
-but after noinclude/includeonly/onlyinclude and other processing.
+but after nowiki/noinclude/includeonly/onlyinclude and other processings.
&$parser: Parser object
&$text: string containing partially parsed text
&$stripState: Parser's internal StripState object
$deviceName = 'android';
if ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
$deviceName = 'operamini';
+ } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
+ $deviceName = 'operamobile';
}
- } else if ( preg_match( '/MSIE 9.0/', $userAgent ) ||
+ } elseif ( preg_match( '/MSIE 9.0/', $userAgent ) ||
preg_match( '/MSIE 8.0/', $userAgent ) ) {
$deviceName = 'ie';
- } else if( preg_match( '/MSIE/', $userAgent ) ) {
+ } elseif( preg_match( '/MSIE/', $userAgent ) ) {
$deviceName = 'html';
- } else if ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
+ } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
$deviceName = 'operamobile';
} elseif ( preg_match( '/iPad.* Safari/', $userAgent ) ) {
$deviceName = 'iphone';
$deviceName = 'wii';
} elseif ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
$deviceName = 'operamini';
- } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
- $deviceName = 'iphone';
} else {
- $deviceName = 'webkit';
+ $deviceName = 'operamobile';
}
} elseif ( preg_match( '/Kindle\/1.0/', $userAgent ) ) {
$deviceName = 'kindle';
$text = $this->replaceVariables( $text );
}
+ wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) );
wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
/* Local scope */
- var ts,
+ var ts,
parsers = [];
/* Parser utility functions */
function getElementText( node ) {
var $node = $( node ),
- data = $node.attr( 'data-sort-value' );
- if ( data !== undefined ) {
- return data;
+ // Use data-sort-value attribute.
+ // Use data() instead of attr() so that live value changes
+ // are processed as well (bug 38152).
+ data = $node.data( 'sortValue' );
+
+ if ( data !== null && data !== undefined ) {
+ // Cast any numbers or other stuff to a string, methods
+ // like charAt, toLowerCase and split are expected.
+ return String( data );
} else {
return $node.text();
}
explodeRowspans( $table );
// try to auto detect column type, and store in tables config
table.config.parsers = buildParserCache( table, $headers );
- // build the cache for the tbody cells
- cache = buildCache( table );
}
+
+ // Build the cache for the tbody cells
+ // to share between calculations for this sort action.
+ // Re-calculated each time a sort action is performed due to possiblity
+ // that sort values change. Shouldn't be too expensive, but if it becomes
+ // too slow an event based system should be implemented somehow where
+ // cells get event .change() and bubbles up to the <table> here
+ cache = buildCache( table );
+
var totalRows = ( $table[0].tBodies[0] && $table[0].tBodies[0].rows.length ) || 0;
if ( !table.sortDisabled && totalRows > 0 ) {
test( 'data-sort-value attribute, when available, should override sorting position', function() {
var $table, data;
- // Simple example, one without data-sort-value which should be sorted at it's text.
+ // Example 1: All cells except one cell without data-sort-value,
+ // which should be sorted at it's text content value.
$table = $(
'<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
'<tbody>' +
data = [];
$table.find( 'tbody > tr' ).each( function( i, tr ) {
$( tr ).find( 'td' ).each( function( i, td ) {
- data.push( { data: $( td ).data( 'sort-value' ), text: $( td ).text() } );
+ data.push( {
+ data: $( td ).data( 'sortValue' ),
+ text: $( td ).text()
+ } );
});
});
deepEqual( data, [
{
- "data": "Apple",
- "text": "Bird"
+ data: 'Apple',
+ text: 'Bird'
}, {
- "data": "Bananna",
- "text": "Ferret"
+ data: 'Bananna',
+ text: 'Ferret'
}, {
- "data": undefined,
- "text": "Cheetah"
+ data: undefined,
+ text: 'Cheetah'
}, {
- "data": "Cherry",
- "text": "Dolphin"
+ data: 'Cherry',
+ text: 'Dolphin'
}, {
- "data": "Drupe",
- "text": "Elephant"
+ data: 'Drupe',
+ text: 'Elephant'
}
- ] );
+ ], 'Order matches expected order (based on data-sort-value attribute values)' );
- // Another example
+ // Example 2
$table = $(
'<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
'<tbody>' +
data = [];
$table.find( 'tbody > tr' ).each( function( i, tr ) {
$( tr ).find( 'td' ).each( function( i, td ) {
- data.push( { data: $( td ).data( 'sort-value' ), text: $( td ).text() } );
+ data.push( {
+ data: $( td ).data( 'sortValue' ),
+ text: $( td ).text()
+ } );
});
});
deepEqual( data, [
{
- "data": undefined,
- "text": "B"
+ data: undefined,
+ text: 'B'
}, {
- "data": undefined,
- "text": "D"
+ data: undefined,
+ text: 'D'
}, {
- "data": "E",
- "text": "A"
+ data: 'E',
+ text: 'A'
}, {
- "data": "F",
- "text": "C"
+ data: 'F',
+ text: 'C'
}, {
- "data": undefined,
- "text": "G"
+ data: undefined,
+ text: 'G'
}
- ] );
+ ], 'Order matches expected order (based on data-sort-value attribute values)' );
+
+ // Example 3: Test that live changes are used from data-sort-value,
+ // even if they change after the tablesorter is constructed (bug 38152).
+ $table = $(
+ '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
+ '<tbody>' +
+ '<tr><td>D</td></tr>' +
+ '<tr><td data-sort-value="1">A</td></tr>' +
+ '<tr><td>B</td></tr>' +
+ '<tr><td data-sort-value="2">G</td></tr>' +
+ '<tr><td>C</td></tr>' +
+ '</tbody></table>'
+ );
+ // initialize table sorter and sort once
+ $table
+ .tablesorter()
+ .find( '.headerSort:eq(0)' ).click();
+
+ // Change the sortValue data properties (bug 38152)
+ // - change data
+ $table.find( 'td:contains(A)' ).data( 'sortValue', 3 );
+ // - add data
+ $table.find( 'td:contains(B)' ).data( 'sortValue', 1 );
+ // - remove data, bring back attribute: 2
+ $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
+
+ // Now sort again (twice, so it is back at Ascending)
+ $table.find( '.headerSort:eq(0)' ).click();
+ $table.find( '.headerSort:eq(0)' ).click();
+
+ data = [];
+ $table.find( 'tbody > tr' ).each( function( i, tr ) {
+ $( tr ).find( 'td' ).each( function( i, td ) {
+ data.push( {
+ data: $( td ).data( 'sortValue' ),
+ text: $( td ).text()
+ } );
+ });
+ });
+
+ deepEqual( data, [
+ {
+ data: 1,
+ text: "B"
+ }, {
+ data: 2,
+ text: "G"
+ }, {
+ data: 3,
+ text: "A"
+ }, {
+ data: undefined,
+ text: "C"
+ }, {
+ data: undefined,
+ text: "D"
+ }
+ ], 'Order matches expected order, using the current sortValue in $.data()' );
});
var $table;
$table = $(
- '<table class="sortable" id="32888">' +
- '<tr><th>header<table id="32888-2">'+
+ '<table class="sortable" id="mw-bug-32888">' +
+ '<tr><th>header<table id="mw-bug-32888-2">'+
'<tr><th>1</th><th>2</th></tr>' +
'</table></th></tr>' +
'<tr><td>A</td></tr>' +
'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
);
equals(
- $('#32888-2').find('th.headerSort').length,
+ $( '#mw-bug-32888-2' ).find('th.headerSort').length,
0,
'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
);
});
+
var correctDateSorting1 = [
['01 January 2010'],
['05 February 2010'],