Version 2.7: various JS fixes and stuff

* CSS and JS are now separate RL modules
* JS rewritten to use jQuery instead of sajax
* Fixed a scoping bug from the previous patchset
* Fixed other scoping bugs so that you can revoke your vote w/o reloading the page, no matter what
* JS module is not loaded for users w/o the 'voteny' user right
* JS is not loaded on Special:TopRatings anymore, it was only loaded there ever back when this extension had inline JS to prevent over 9000 JS errors, plus it wouldn't have worked because wgArticleId is 0 for special pages
* Special page alias file added

Change-Id: I6668238a0ab6272b0a7b3b167832ebb2d3abe2c2
This commit is contained in:
Jack Phoenix 2015-05-14 00:00:34 +03:00
parent 1dcdc71935
commit 33f5015197
5 changed files with 127 additions and 65 deletions

View file

@ -30,6 +30,9 @@ class SpecialTopRatings extends IncludableSpecialPage {
// Set the page title, robot policies, etc.
$this->setHeaders();
$out = $this->getOutput();
$user = $this->getUser();
$categoryName = $namespace = '';
// Parse the parameters passed to the special page
@ -47,9 +50,13 @@ class SpecialTopRatings extends IncludableSpecialPage {
$limit = 50;
}
// Add JS (and CSS) -- needed so that users can vote on this page and
// so that their browsers' consoles won't be filled with JS errors ;-)
$this->getOutput()->addModules( 'ext.voteNY' );
// Add CSS
$out->addModuleStyles( 'ext.voteNY.styles' );
/* scroll down some lines to see why I'm not including JS here anymore
if ( $user->isAllowed( 'voteny' ) ) {
$out->addModules( 'ext.voteNY.scripts' );
}
*/
$ratings = array();
$output = '';
@ -106,12 +113,21 @@ class SpecialTopRatings extends IncludableSpecialPage {
// If we have some ratings, start building HTML output
if ( !empty( $ratings ) ) {
/* XXX dirrrrrrty hack! because when we include this page, the JS
is not included, but we want things to work still */
if ( $this->including() ) {
* is not included, but we want things to work still
* Actually, this is way harder than what it looks like.
* The JS uses wgArticleId but when directly viewing Special:TopRatings,
* wgArticleId is zero, because special pages aren't articles.
* As for including the special page, then wgArticleId would likely
* point at the ID of the page that includes {{Special:TopRatings}},
* which would be stupid and wrong.
* Besides, shouldn't you check out the images/pages that you're gonna
* vote for? Yeah, that's what I thought, too.
if ( $this->including() && $user->isAllowed( 'voteny' ) ) {
global $wgExtensionAssetsPath;
$output .= '<script type="text/javascript" src="' .
$wgExtensionAssetsPath . '/VoteNY/Vote.js"></script>';
}
*/
// yes, array_keys() is needed
foreach ( array_keys( $ratings ) as $discardThis => $pageId ) {
@ -147,7 +163,7 @@ class SpecialTopRatings extends IncludableSpecialPage {
}
// Output everything!
$this->getOutput()->addHTML( $output );
$out->addHTML( $output );
}
/**

118
Vote.js
View file

@ -1,8 +1,9 @@
/**
* JavaScript functions for Vote extension.
*
* TODO: Should refactor this into a jQuery widget. The widget should get a PageID in its
* constructor so it can work on any page for any page and with multiple instances per page.
* TODO: Should refactor this into a jQuery widget.
* The widget should get a PageID in its constructor so it can work on any page
* for any page and with multiple instances per page.
*
* @constructor
*
@ -24,12 +25,18 @@ var VoteNY = function VoteNY() {
* @param PageID Integer: internal ID number of the current article
*/
this.clickVote = function( TheVote, PageID ) {
sajax_request_type = 'POST';
sajax_do_call( 'wfVoteClick', [ TheVote, PageID ], function( request ) {
document.getElementById( 'PollVotes' ).innerHTML = request.responseText;
document.getElementById( 'Answer' ).innerHTML =
$.post(
mw.util.wikiScript(), {
action: 'ajax',
rs: 'wfVoteClick',
rsargs: [ TheVote, PageID ]
}
).done( function( data ) {
$( '#PollVotes' ).html( ( data || '0' ) );
$( '#Answer' ).html(
'<a href="javascript:void(0);" class="vote-unvote-link">' +
mediaWiki.msg( 'voteny-unvote-link' ) + '</a>';
mediaWiki.msg( 'voteny-unvote-link' ) + '</a>'
);
} );
};
@ -40,12 +47,18 @@ var VoteNY = function VoteNY() {
* @param mk Mixed: random token
*/
this.unVote = function( PageID ) {
sajax_request_type = 'POST';
sajax_do_call( 'wfVoteDelete', [ PageID ], function( request ) {
document.getElementById( 'PollVotes' ).innerHTML = request.responseText;
document.getElementById( 'Answer' ).innerHTML =
$.post(
mw.util.wikiScript(), {
action: 'ajax',
rs: 'wfVoteDelete',
rsargs: [ PageID ]
}
).done( function( data ) {
$( '#PollVotes' ).html( ( data || '0' ) );
$( '#Answer' ).html(
'<a href="javascript:void(0);" class="vote-vote-link">' +
mediaWiki.msg( 'voteny-link' ) + '</a>';
mediaWiki.msg( 'voteny-link' ) + '</a>'
);
} );
};
@ -59,16 +72,22 @@ var VoteNY = function VoteNY() {
this.clickVoteStars = function( TheVote, PageID, id, action ) {
this.voted_new[id] = TheVote;
var rsfun;
if( action == 3 ) {
if ( action == 3 ) {
rsfun = 'wfVoteStars';
}
if( action == 5 ) {
if ( action == 5 ) {
rsfun = 'wfVoteStarsMulti';
}
var resultElement = document.getElementById( 'rating_' + id );
sajax_request_type = 'POST';
sajax_do_call( rsfun, [ TheVote, PageID ], resultElement );
$.post(
mw.util.wikiScript(), {
action: 'ajax',
rs: rsfun,
rsargs: [ TheVote, PageID ]
}
).done( function( data ) {
$( '#rating_' + id ).html( data );
} );
};
/**
@ -78,60 +97,67 @@ var VoteNY = function VoteNY() {
* @param id Integer: ID of the current rating star
*/
this.unVoteStars = function( PageID, id ) {
var resultElement = document.getElementById( 'rating_' + id );
sajax_request_type = 'POST';
sajax_do_call( 'wfVoteStarsDelete', [ PageID ], resultElement );
$.post(
mw.util.wikiScript(), {
action: 'ajax',
rs: 'wfVoteStarsDelete',
rsargs: [ PageID ]
}
).done( function( data ) {
$( '#rating_' + id ).html( data );
} );
};
this.startClearRating = function( id, rating, voted ) {
var voteNY = this;
this.clearRatingTimer = setTimeout( function() {
this.clearRating( id, 0, rating, voted );
voteNY.clearRating( id, 0, rating, voted );
}, 200 );
};
this.clearRating = function( id, num, prev_rating, voted ) {
if( this.voted_new[id] ) {
if ( this.voted_new[id] ) {
voted = this.voted_new[id];
}
for( var x = 1; x <= this.MaxRating; x++ ) {
for ( var x = 1; x <= this.MaxRating; x++ ) {
var star_on, old_rating;
if( voted ) {
if ( voted ) {
star_on = 'voted';
old_rating = voted;
} else {
star_on = 'on';
old_rating = prev_rating;
}
var ratingElement = document.getElementById( 'rating_' + id + '_' + x );
if( !num && old_rating >= x ) {
ratingElement.src = this.imagePath + 'star_' + star_on + '.gif';
var ratingElement = $( '#rating_' + id + '_' + x );
if ( !num && old_rating >= x ) {
ratingElement.attr( 'src', this.imagePath + 'star_' + star_on + '.gif' );
} else {
ratingElement.src = this.imagePath + 'star_off.gif';
ratingElement.attr( 'src', this.imagePath + 'star_off.gif' );
}
}
};
this.updateRating = function( id, num, prev_rating ) {
if( this.clearRatingTimer && this.last_id == id ) {
if ( this.clearRatingTimer && this.last_id == id ) {
clearTimeout( this.clearRatingTimer );
}
this.clearRating( id, num, prev_rating );
for( var x = 1; x <= num; x++ ) {
document.getElementById( 'rating_' + id + '_' + x ).src = this.imagePath + 'star_voted.gif';
for ( var x = 1; x <= num; x++ ) {
$( '#rating_' + id + '_' + x ).attr( 'src', this.imagePath + 'star_voted.gif' );
}
this.last_id = id;
};
};
// TODO:Mmake event handlers part of a widget as described in the VoteNY's TODO and reduce this
// TODO: Make event handlers part of a widget as described in the VoteNY's TODO and reduce this
// code to instantiating such a widget for the current wiki page if required.
jQuery( document ).ready( function() {
$( function() {
var vote = new VoteNY();
// Green voting box's link
jQuery( '.vote-action' ).on( 'click', '> a', function( event ) {
if( jQuery( this ).hasClass( 'vote-unvote-link' ) ) {
$( '.vote-action' ).on( 'click', '> a', function( event ) {
if ( $( this ).hasClass( 'vote-unvote-link' ) ) {
vote.unVote( mw.config.get( 'wgArticleId' ) );
} else {
vote.clickVote( 1, mw.config.get( 'wgArticleId' ) );
@ -139,23 +165,27 @@ jQuery( document ).ready( function() {
} );
// Rating stars
jQuery( 'img.vote-rating-star' ).click( function() {
var that = jQuery( this );
// Note: this uses $( 'body' ).on( 'actionName', 'selector'
// instead of $( 'selector' ).actionName so that the hover effects work
// correctly even *after* you've voted (say, if you wanted to change your
// vote with the star ratings without reloading the page).
$( 'body' ).on( 'click', '.vote-rating-star', function() {
var that = $( this );
vote.clickVoteStars(
that.data( 'vote-the-vote' ),
mw.config.get( 'wgArticleId' ),
that.data( 'vote-id' ),
that.data( 'vote-action' )
);
} ).mouseover( function() {
var that = jQuery( this );
} ).on( 'mouseover', '.vote-rating-star', function() {
var that = $( this );
vote.updateRating(
that.data( 'vote-id' ),
that.data( 'vote-the-vote' ),
that.data( 'vote-rating' )
);
} ).mouseout( function() {
var that = jQuery( this );
} ).on( 'mouseout', '.vote-rating-star', function() {
var that = $( this );
vote.startClearRating(
that.data( 'vote-id' ),
that.data( 'vote-rating' ),
@ -164,10 +194,10 @@ jQuery( document ).ready( function() {
} );
// Remove vote (rating stars)
jQuery( 'a.vote-remove-stars-link' ).click( function() {
$( 'body' ).on( 'click', '.vote-remove-stars-link', function() {
vote.unVoteStars(
mw.config.get( 'wgArticleId' ),
jQuery( this ).data( 'vote-id' )
$( this ).data( 'vote-id' )
);
} );
} );
} );

View file

@ -27,7 +27,7 @@ class VoteHooks {
* @return string HTML
*/
public static function renderVote( $input, $args, $parser ) {
global $wgOut;
global $wgOut, $wgUser;
wfProfileIn( __METHOD__ );
@ -39,7 +39,10 @@ class VoteHooks {
// Add CSS & JS
// In order for us to do this *here* instead of having to do this in
// registerParserHook(), we must've disabled parser cache
$wgOut->addModules( 'ext.voteNY' );
$parser->getOutput()->addModuleStyles( 'ext.voteNY.styles' );
if ( $wgUser->isAllowed( 'voteny' ) ) {
$parser->getOutput()->addModules( 'ext.voteNY.scripts' );
}
// Define variable - 0 means that we'll get that green voting box by default
$type = 0;

14
VoteNY.alias.php Normal file
View file

@ -0,0 +1,14 @@
<?php
/**
* Aliases for Special:TopRatings
*
* @file
* @ingroup Extensions
*/
$aliases = array();
/** English */
$aliases['en'] = array(
'TopRatings' => array( 'TopRatings' ),
);

View file

@ -11,18 +11,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
* Protect against register_globals vulnerabilities.
* This line must be present before any global variable is referenced.
*/
if ( !defined( 'MEDIAWIKI' ) ) {
die( "This is not a valid entry point.\n" );
}
// Extension credits that show up on Special:Version
$wgExtensionCredits['parserhook'][] = array(
'name' => 'Vote',
'version' => '2.6.1',
'version' => '2.7',
'author' => array( 'Aaron Wright', 'David Pean', 'Jack Phoenix' ),
'descriptionmsg' => 'voteny-desc',
'url' => 'https://www.mediawiki.org/wiki/Extension:VoteNY'
@ -37,11 +29,13 @@ $wgGroupPermissions['*']['voteny'] = false; // Anonymous users cannot vote
$wgGroupPermissions['user']['voteny'] = true; // Registered users can vote
// AJAX functions needed by this extension
require_once( 'Vote_AjaxFunctions.php' );
require_once 'Vote_AjaxFunctions.php';
// Autoload classes and set up i18n
$wgMessagesDirs['VoteNY'] = __DIR__ . '/i18n';
$wgExtensionMessagesFiles['VoteNYAlias'] = __DIR__ . '/VoteNY.alias.php';
$wgExtensionMessagesFiles['VoteNYMagic'] = __DIR__ . '/VoteNY.i18n.magic.php';
$wgAutoloadClasses['Vote'] = __DIR__ . '/VoteClass.php';
$wgAutoloadClasses['VoteStars'] = __DIR__ . '/VoteClass.php';
@ -61,11 +55,16 @@ $wgHooks['ParserFirstCallInit'][] = 'VoteHooks::setupNumberOfVotesPageParser';
$wgHooks['LoadExtensionSchemaUpdates'][] = 'VoteHooks::addTable';
// ResourceLoader support for MediaWiki 1.17+
$wgResourceModules['ext.voteNY'] = array(
$wgResourceModules['ext.voteNY.styles'] = array(
'styles' => 'Vote.css',
'scripts' => 'Vote.js',
'messages' => array( 'voteny-link', 'voteny-unvote-link' ),
'localBasePath' => __DIR__,
'remoteExtPath' => 'VoteNY',
'position' => 'top' // available since r85616
);
$wgResourceModules['ext.voteNY.scripts'] = array(
'scripts' => 'Vote.js',
'messages' => array( 'voteny-link', 'voteny-unvote-link' ),
'localBasePath' => __DIR__,
'remoteExtPath' => 'VoteNY'
);