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. // Set the page title, robot policies, etc.
$this->setHeaders(); $this->setHeaders();
$out = $this->getOutput();
$user = $this->getUser();
$categoryName = $namespace = ''; $categoryName = $namespace = '';
// Parse the parameters passed to the special page // Parse the parameters passed to the special page
@ -47,9 +50,13 @@ class SpecialTopRatings extends IncludableSpecialPage {
$limit = 50; $limit = 50;
} }
// Add JS (and CSS) -- needed so that users can vote on this page and // Add CSS
// so that their browsers' consoles won't be filled with JS errors ;-) $out->addModuleStyles( 'ext.voteNY.styles' );
$this->getOutput()->addModules( 'ext.voteNY' ); /* 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(); $ratings = array();
$output = ''; $output = '';
@ -106,12 +113,21 @@ class SpecialTopRatings extends IncludableSpecialPage {
// If we have some ratings, start building HTML output // If we have some ratings, start building HTML output
if ( !empty( $ratings ) ) { if ( !empty( $ratings ) ) {
/* XXX dirrrrrrty hack! because when we include this page, the JS /* XXX dirrrrrrty hack! because when we include this page, the JS
is not included, but we want things to work still */ * is not included, but we want things to work still
if ( $this->including() ) { * 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; global $wgExtensionAssetsPath;
$output .= '<script type="text/javascript" src="' . $output .= '<script type="text/javascript" src="' .
$wgExtensionAssetsPath . '/VoteNY/Vote.js"></script>'; $wgExtensionAssetsPath . '/VoteNY/Vote.js"></script>';
} }
*/
// yes, array_keys() is needed // yes, array_keys() is needed
foreach ( array_keys( $ratings ) as $discardThis => $pageId ) { foreach ( array_keys( $ratings ) as $discardThis => $pageId ) {
@ -147,7 +163,7 @@ class SpecialTopRatings extends IncludableSpecialPage {
} }
// Output everything! // Output everything!
$this->getOutput()->addHTML( $output ); $out->addHTML( $output );
} }
/** /**

118
Vote.js
View file

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

View file

@ -27,7 +27,7 @@ class VoteHooks {
* @return string HTML * @return string HTML
*/ */
public static function renderVote( $input, $args, $parser ) { public static function renderVote( $input, $args, $parser ) {
global $wgOut; global $wgOut, $wgUser;
wfProfileIn( __METHOD__ ); wfProfileIn( __METHOD__ );
@ -39,7 +39,10 @@ class VoteHooks {
// Add CSS & JS // Add CSS & JS
// In order for us to do this *here* instead of having to do this in // In order for us to do this *here* instead of having to do this in
// registerParserHook(), we must've disabled parser cache // 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 // Define variable - 0 means that we'll get that green voting box by default
$type = 0; $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 * @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 // Extension credits that show up on Special:Version
$wgExtensionCredits['parserhook'][] = array( $wgExtensionCredits['parserhook'][] = array(
'name' => 'Vote', 'name' => 'Vote',
'version' => '2.6.1', 'version' => '2.7',
'author' => array( 'Aaron Wright', 'David Pean', 'Jack Phoenix' ), 'author' => array( 'Aaron Wright', 'David Pean', 'Jack Phoenix' ),
'descriptionmsg' => 'voteny-desc', 'descriptionmsg' => 'voteny-desc',
'url' => 'https://www.mediawiki.org/wiki/Extension:VoteNY' '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 $wgGroupPermissions['user']['voteny'] = true; // Registered users can vote
// AJAX functions needed by this extension // AJAX functions needed by this extension
require_once( 'Vote_AjaxFunctions.php' ); require_once 'Vote_AjaxFunctions.php';
// Autoload classes and set up i18n // Autoload classes and set up i18n
$wgMessagesDirs['VoteNY'] = __DIR__ . '/i18n'; $wgMessagesDirs['VoteNY'] = __DIR__ . '/i18n';
$wgExtensionMessagesFiles['VoteNYAlias'] = __DIR__ . '/VoteNY.alias.php';
$wgExtensionMessagesFiles['VoteNYMagic'] = __DIR__ . '/VoteNY.i18n.magic.php'; $wgExtensionMessagesFiles['VoteNYMagic'] = __DIR__ . '/VoteNY.i18n.magic.php';
$wgAutoloadClasses['Vote'] = __DIR__ . '/VoteClass.php'; $wgAutoloadClasses['Vote'] = __DIR__ . '/VoteClass.php';
$wgAutoloadClasses['VoteStars'] = __DIR__ . '/VoteClass.php'; $wgAutoloadClasses['VoteStars'] = __DIR__ . '/VoteClass.php';
@ -61,11 +55,16 @@ $wgHooks['ParserFirstCallInit'][] = 'VoteHooks::setupNumberOfVotesPageParser';
$wgHooks['LoadExtensionSchemaUpdates'][] = 'VoteHooks::addTable'; $wgHooks['LoadExtensionSchemaUpdates'][] = 'VoteHooks::addTable';
// ResourceLoader support for MediaWiki 1.17+ // ResourceLoader support for MediaWiki 1.17+
$wgResourceModules['ext.voteNY'] = array( $wgResourceModules['ext.voteNY.styles'] = array(
'styles' => 'Vote.css', 'styles' => 'Vote.css',
'scripts' => 'Vote.js',
'messages' => array( 'voteny-link', 'voteny-unvote-link' ),
'localBasePath' => __DIR__, 'localBasePath' => __DIR__,
'remoteExtPath' => 'VoteNY', 'remoteExtPath' => 'VoteNY',
'position' => 'top' // available since r85616 'position' => 'top' // available since r85616
); );
$wgResourceModules['ext.voteNY.scripts'] = array(
'scripts' => 'Vote.js',
'messages' => array( 'voteny-link', 'voteny-unvote-link' ),
'localBasePath' => __DIR__,
'remoteExtPath' => 'VoteNY'
);