Index: trunk/extensions/ArticleFeedback/ArticleFeedback.hooks.php |
— | — | @@ -32,6 +32,8 @@ |
33 | 33 | 'articlefeedback-pitch-makefirstedit-message', |
34 | 34 | 'articlefeedback-pitch-makefirstedit-accept', |
35 | 35 | 'articlefeedback-survey-title', |
| 36 | + 'articlefeedback-survey-message-success', |
| 37 | + 'articlefeedback-survey-message-error', |
36 | 38 | ), |
37 | 39 | 'dependencies' => array( |
38 | 40 | 'jquery.ui.dialog', |
Index: trunk/extensions/ArticleFeedback/modules/jquery.articleFeedback/jquery.articleFeedback.js |
— | — | @@ -73,7 +73,6 @@ |
74 | 74 | var context = this; |
75 | 75 | // Lock the submit button -- TODO: lock the star inputs too |
76 | 76 | context.$ui.find( '.articleFeedback-submit' ).button( { 'disabled': true } ); |
77 | | - |
78 | 77 | // Build data from form values |
79 | 78 | var data = {}; |
80 | 79 | for ( var key in context.options.ratings ) { |
— | — | @@ -218,19 +217,38 @@ |
219 | 218 | .find( '.articleFeedback-accept' ) |
220 | 219 | .text( mw.msg( context.options.pitches[key].accept ) ) |
221 | 220 | .click( function() { |
| 221 | + $(this) |
| 222 | + .button( { 'disabled': true } ) |
| 223 | + .closest( '.articleFeedback-pitch' ) |
| 224 | + .find( '.articleFeedback-reject' ) |
| 225 | + .attr( 'disabled', true ); |
222 | 226 | var $pitch = $(this).closest( '.articleFeedback-pitch' ); |
223 | 227 | var key = $pitch.attr( 'rel' ); |
224 | | - context.options.pitches[key].action(); |
225 | | - $pitch.fadeOut(); |
| 228 | + // If a pitch's action returns true, hide the pitch and |
| 229 | + // re-enable the button |
| 230 | + if ( context.options.pitches[key].action() ) { |
| 231 | + $pitch.fadeOut(); |
| 232 | + $(this) |
| 233 | + .button( { 'disabled': false } ) |
| 234 | + .closest( '.articleFeedback-pitch' ) |
| 235 | + .find( '.articleFeedback-reject' ) |
| 236 | + .attr( 'disabled', false ); |
| 237 | + } |
226 | 238 | } ) |
227 | 239 | .button() |
228 | 240 | .end() |
229 | 241 | .find( '.articleFeedback-reject' ) |
230 | 242 | .text( mw.msg( context.options.pitches[key].reject ) ) |
231 | 243 | .click( function() { |
| 244 | + var $pitch = $(this).closest( '.articleFeedback-pitch' ); |
232 | 245 | // Remember that the users rejected this, set a cookie to not |
233 | 246 | // show this for 3 days |
234 | | - $(this).closest( '.articleFeedback-pitch' ).fadeOut(); |
| 247 | + $.cookie( |
| 248 | + 'jquery.articleFeedback.pitch.' + $pitch.attr( 'rel' ), |
| 249 | + 'hide', |
| 250 | + { 'expires': 3 } |
| 251 | + ); |
| 252 | + $pitch.fadeOut(); |
235 | 253 | } ) |
236 | 254 | .end() |
237 | 255 | .appendTo( $(this) ); |
— | — | @@ -255,7 +273,8 @@ |
256 | 274 | for ( var key in context.options.pitches ) { |
257 | 275 | // Dont' bother checking the condition if there's a cookie that says |
258 | 276 | // the user has rejected this within 3 days of right now |
259 | | - if ( context.options.pitches[key].condition() ) { |
| 277 | + var display = $.cookie( 'jquery.articleFeedback.pitch.' + key ) |
| 278 | + if ( display !== 'hide' && context.options.pitches[key].condition() ) { |
260 | 279 | context.$ui |
261 | 280 | .find( '.articleFeedback-pitch[rel="' + key + '"]' ) |
262 | 281 | .show(); |
Index: trunk/extensions/ArticleFeedback/modules/ext.articleFeedback/ext.articleFeedback.js |
— | — | @@ -4,6 +4,173 @@ |
5 | 5 | |
6 | 6 | ( function( $, mw ) { |
7 | 7 | |
| 8 | +/** |
| 9 | + * Tries to win the lottery based on set odds. |
| 10 | + * |
| 11 | + * Odds are clamped to the range of 0-1. |
| 12 | + * |
| 13 | + * @param odds Float: Probability of winning in range of 0 (never) to 1 (always) |
| 14 | + * @return Boolean: Whether you are a winner |
| 15 | + */ |
| 16 | +function lottery( odds ) { |
| 17 | + return Math.random() <= Math.min( 1, Math.max( 0, odds ) ); |
| 18 | +}; |
| 19 | + |
| 20 | +/** |
| 21 | + * Checks if a pitch is currently muted |
| 22 | + * |
| 23 | + * @param pitch String: Name of pitch to check |
| 24 | + * @return Boolean: Whether the pitch is muted |
| 25 | + */ |
| 26 | +function isPitchMuted( pitch ) { |
| 27 | + return $.cookie( 'ext.articleFeedback.pitches.' + pitch ) == 'hide' ? true : false; |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * Ensures a pitch will be muted for a given duration of time |
| 32 | + * |
| 33 | + * @param pitch String: Name of pitch to mute |
| 34 | + * @param durration Integer: Number of days to mute the pitch for |
| 35 | + */ |
| 36 | +function mutePitch( pitch, durration ) { |
| 37 | + $.cookie( 'ext.articleFeedback.pitches.' + pitch, 'hide', { 'expires': durration } ); |
| 38 | +} |
| 39 | + |
| 40 | +/** |
| 41 | + * Survey object |
| 42 | + * |
| 43 | + * This object makes use of Special:SimpleSurvey, and uses some nasty hacks - this needs to be |
| 44 | + * replaced with something much better in the future. |
| 45 | + */ |
| 46 | +var survey = new ( function( mw ) { |
| 47 | + |
| 48 | + /* Private Members */ |
| 49 | + |
| 50 | + var that = this; |
| 51 | + var $dialog = null; |
| 52 | + var $form = null; |
| 53 | + var $message = null; |
| 54 | + // The form is rendered by loading the raw results of a special page into a div, this is the |
| 55 | + // URL of that special page |
| 56 | + var formSource = mw.config.get( 'wgScript' ) + '?' + $.param( { |
| 57 | + 'title': 'Special:SimpleSurvey', |
| 58 | + 'survey': 'articlerating', |
| 59 | + 'raw': 1 |
| 60 | + } ); |
| 61 | + |
| 62 | + /* Public Methods */ |
| 63 | + |
| 64 | + this.load = function() { |
| 65 | + // Try to select existing dialog |
| 66 | + $dialog = $( '#articleFeedback-dialog' ); |
| 67 | + // Fall-back on creating one |
| 68 | + if ( $dialog.size() == 0 ) { |
| 69 | + // Create initially in loading state |
| 70 | + $dialog = $( '<div id="articleFeedback-dialog" class="loading" />' ) |
| 71 | + .dialog( { |
| 72 | + 'width': 600, |
| 73 | + 'height': 400, |
| 74 | + 'bgiframe': true, |
| 75 | + 'autoOpen': true, |
| 76 | + 'modal': true, |
| 77 | + 'title': mw.msg( 'articlefeedback-survey-title' ), |
| 78 | + 'close': function() { |
| 79 | + // Return the survey to default state |
| 80 | + $msg.remove(); |
| 81 | + $form.show(); |
| 82 | + $dialog |
| 83 | + .dialog( 'option', 'height', 400 ) |
| 84 | + .dialog( 'close' ); |
| 85 | + } |
| 86 | + } ) |
| 87 | + .load( formSource, function() { |
| 88 | + $form = $dialog.find( 'form' ); |
| 89 | + // Bypass normal form processing |
| 90 | + $form.submit( function() { return that.submit() } ); |
| 91 | + // Dirty hack - we want a fully styled button, and we can't get that from an |
| 92 | + // input[type=submit] control, so we just swap it out |
| 93 | + var $input = $form.find( 'input[type=submit]' ); |
| 94 | + var $button = $( '<button type="submit"></button>' ) |
| 95 | + .text( $(this).find( 'input[type=submit]' ).val() ) |
| 96 | + .button() |
| 97 | + .insertAfter( $input ); |
| 98 | + $input.remove(); |
| 99 | + // Take dialog out of loading state |
| 100 | + $dialog.removeClass( 'loading' ); |
| 101 | + } ); |
| 102 | + } |
| 103 | + $dialog.dialog( 'open' ); |
| 104 | + }; |
| 105 | + this.submit = function() { |
| 106 | + var $dialog = $( '#articleFeedback-dialog' ); |
| 107 | + // Put dialog into "loading" state |
| 108 | + $dialog |
| 109 | + .addClass( 'loading' ) |
| 110 | + .find( 'form' ) |
| 111 | + .hide(); |
| 112 | + // Setup request to send information directly to a special page |
| 113 | + var data = { |
| 114 | + 'title': 'Special:SimpleSurvey' |
| 115 | + }; |
| 116 | + // Build request from form data |
| 117 | + $dialog |
| 118 | + .find( [ |
| 119 | + 'input[type=text]', |
| 120 | + 'input[type=radio]:checked', |
| 121 | + 'input[type=checkbox]:checked', |
| 122 | + 'input[type=hidden]', |
| 123 | + 'textarea' |
| 124 | + ].join( ',' ) ) |
| 125 | + .each( function() { |
| 126 | + var name = $(this).attr( 'name' ); |
| 127 | + if ( name !== '' ) { |
| 128 | + if ( name.substr( -2 ) == '[]' ) { |
| 129 | + var trimmedName = name.substr( 0, name.length - 2 ); |
| 130 | + if ( typeof data[trimmedName] == 'undefined' ) { |
| 131 | + data[trimmedName] = []; |
| 132 | + } |
| 133 | + data[trimmedName].push( $(this).val() ); |
| 134 | + } else { |
| 135 | + data[name] = $(this).val(); |
| 136 | + } |
| 137 | + } |
| 138 | + } ); |
| 139 | + // XXX: Not only are we submitting to a special page instead of an API request, but we are |
| 140 | + // screen-scraping the result - this is evil and needs to be addressed later |
| 141 | + $.ajax( { |
| 142 | + 'url': wgScript, |
| 143 | + 'type': 'POST', |
| 144 | + 'data': data, |
| 145 | + 'dataType': 'html', |
| 146 | + 'success': function( data ) { |
| 147 | + // Take the dialog out of "loading" state |
| 148 | + $dialog.removeClass( 'loading' ); |
| 149 | + // Screen-scrape to determine success or error |
| 150 | + var success = $( data ).find( '.simplesurvey-success' ).size() > 0; |
| 151 | + // Display success/error message |
| 152 | + that.alert( success ? 'success' : 'error' ); |
| 153 | + // Mute for 30 days |
| 154 | + mutePitch( 'takesurvey', 30 ); |
| 155 | + }, |
| 156 | + 'error': function( XMLHttpRequest, textStatus, errorThrown ) { |
| 157 | + // Take the dialog out of "loading" state |
| 158 | + $dialog.removeClass( 'loading' ); |
| 159 | + // Display error message |
| 160 | + that.alert( 'error' ); |
| 161 | + } |
| 162 | + } ); |
| 163 | + // Do not continue with normal form processing |
| 164 | + return false; |
| 165 | + }; |
| 166 | + this.alert = function( message ) { |
| 167 | + $message = $( '<div />' ) |
| 168 | + .addClass( 'articleFeedback-survey-message-' + message ) |
| 169 | + .text( mw.msg( 'articlefeedback-survey-message-' + message ) ) |
| 170 | + .appendTo( $dialog ); |
| 171 | + $dialog.dialog( 'option', 'height', $message.height() + 100 ) |
| 172 | + }; |
| 173 | +} )( mediaWiki ); |
| 174 | + |
8 | 175 | var config = { |
9 | 176 | 'ratings': { |
10 | 177 | 'trustworthy': { |
— | — | @@ -30,48 +197,13 @@ |
31 | 198 | 'pitches': { |
32 | 199 | 'takesurvey': { |
33 | 200 | 'condition': function() { |
34 | | - // TODO: If already taken survey, return false |
35 | | - return true; |
| 201 | + // If already taken survey, return false |
| 202 | + return isPitchMuted( 'takesurvey' ) ? false : lottery( 0.33 ); |
36 | 203 | }, |
37 | 204 | 'action': function() { |
38 | | - var $dialog = $( '#articleFeedback-dialog' ); |
39 | | - if ( $dialog.size() == 0 ) { |
40 | | - $dialog = $( '<div id="articleFeedback-dialog" class="loading" />' ) |
41 | | - .dialog( { |
42 | | - 'width': 600, |
43 | | - 'height': 400, |
44 | | - 'bgiframe': true, |
45 | | - 'autoOpen': true, |
46 | | - 'modal': true, |
47 | | - 'title': mediaWiki.msg( 'articlefeedback-survey-title' ), |
48 | | - 'close': function() { |
49 | | - $(this) |
50 | | - .dialog( 'option', 'height', 400 ) |
51 | | - .find( '.articleFeedback-success-msg, .articleFeedback-error-msg' ) |
52 | | - .remove() |
53 | | - .end() |
54 | | - .find( 'form' ) |
55 | | - .show(); |
56 | | - } |
57 | | - } ); |
58 | | - $dialog.load( |
59 | | - mediaWiki.config.get( 'wgScript' ) + |
60 | | - '?title=Special:SimpleSurvey&survey=articlerating&raw=1', |
61 | | - function() { |
62 | | - $(this) |
63 | | - .append( |
64 | | - $( '<button></button>' ) |
65 | | - .text( $(this).find( 'input[type=submit]' ).val() ) |
66 | | - .button() |
67 | | - ) |
68 | | - .find( 'input[type=submit]' ) |
69 | | - .remove() |
70 | | - .end() |
71 | | - .removeClass( 'loading' ); |
72 | | - } |
73 | | - ); |
74 | | - } |
75 | | - $dialog.dialog( 'open' ); |
| 205 | + survey.load(); |
| 206 | + // Hide the pitch immediately |
| 207 | + return true; |
76 | 208 | }, |
77 | 209 | 'title': 'articlefeedback-pitch-takesurvey-title', |
78 | 210 | 'message': 'articlefeedback-pitch-takesurvey-message', |
— | — | @@ -81,10 +213,19 @@ |
82 | 214 | 'createaccount': { |
83 | 215 | 'condition': function() { |
84 | 216 | // If user is logged in, return false |
85 | | - return mediaWiki.user.anonymous(); |
| 217 | + return !isPitchMuted( 'createaccount' ) && mediaWiki.user.anonymous(); |
86 | 218 | }, |
87 | 219 | 'action': function() { |
88 | 220 | // TODO: Do something |
| 221 | + window.location = |
| 222 | + mediaWiki.config.get( 'wgScript' ) + '?' + $.param( { |
| 223 | + 'title': 'Special:UserLogin', |
| 224 | + 'type': 'signup', |
| 225 | + 'returnto': mediaWiki.config.get( 'wgPageName' ) |
| 226 | + } ); |
| 227 | + // Mute for 1 day |
| 228 | + mutePitch( 'createaccount', 1 ); |
| 229 | + return false; |
89 | 230 | }, |
90 | 231 | 'title': 'articlefeedback-pitch-createaccount-title', |
91 | 232 | 'message': 'articlefeedback-pitch-createaccount-message', |
— | — | @@ -94,10 +235,18 @@ |
95 | 236 | 'makefirstedit': { |
96 | 237 | 'condition': function() { |
97 | 238 | // If user is not logged in, return false |
98 | | - return !mediaWiki.user.anonymous(); |
| 239 | + return !isPitchMuted( 'makefirstedit' ) && !mediaWiki.user.anonymous(); |
99 | 240 | }, |
100 | 241 | 'action': function() { |
101 | 242 | // TODO: Do something |
| 243 | + window.location = |
| 244 | + mediaWiki.config.get( 'wgScript' ) + '?' + $.param( { |
| 245 | + 'title': mediaWiki.config.get( 'wgPageName' ), |
| 246 | + 'action': 'edit' |
| 247 | + } ); |
| 248 | + // Mute for 7 days |
| 249 | + mutePitch( 'makefirstedit', 7 ); |
| 250 | + return false; |
102 | 251 | }, |
103 | 252 | 'title': 'articlefeedback-pitch-makefirstedit-title', |
104 | 253 | 'message': 'articlefeedback-pitch-makefirstedit-message', |
Index: trunk/extensions/ArticleFeedback/ArticleFeedback.i18n.php |
— | — | @@ -58,7 +58,9 @@ |
59 | 59 | 'articlefeedback-expert-assessment-level-1-label' => 'Marginal', |
60 | 60 | 'articlefeedback-expert-assessment-level-2-label' => 'Competent', |
61 | 61 | 'articlefeedback-expert-assessment-level-3-label' => 'Expert', |
62 | | - |
| 62 | + 'articlefeedback-survey-message-success' => 'Thanks for filling out the survey.', |
| 63 | + 'articlefeedback-survey-message-error' => 'An error has occurred. |
| 64 | +Please try again later.', |
63 | 65 | ); |
64 | 66 | |
65 | 67 | /** Message documentation (Message documentation) |