Index: trunk/extensions/Contest/Contest.i18n.php |
— | — | @@ -137,9 +137,6 @@ |
138 | 138 | 'contest-signup-require-challenge' => 'You must select a challenge.', |
139 | 139 | 'contest-signup-invalid-cv' => 'You entered an invalid URL.', |
140 | 140 | |
141 | | - 'contest-submission-submission' => 'Link to your submission', |
142 | | - 'contest-submission-invalid-url' => 'This URL does not match one of the allowed formats.', |
143 | | - |
144 | 141 | // Special:Contest |
145 | 142 | 'contest-contest-title' => 'Contest: $1', |
146 | 143 | 'contest-contest-no-results' => 'There are no contestants to list.', |
— | — | @@ -201,6 +198,14 @@ |
202 | 199 | 'contest-submission-unknown' => 'There is no contest with the provided name.', |
203 | 200 | 'contest-submission-header' => 'Thanks for participating in this contest! Once you have completed the challenge, you can add a link to you submission below.', |
204 | 201 | 'contest-submission-finished' => 'This contest has ended.', |
| 202 | + |
| 203 | + 'contest-submission-submission' => 'Link to your submission', |
| 204 | + 'contest-submission-invalid-url' => 'This URL does not match one of the allowed formats.', |
| 205 | + 'contest-submission-new-submission' => 'You still need to enter the URL to your submission. This needs to be done before the deadline.', |
| 206 | + 'contest-submission-current-submission' => 'This is the URL to your submission, which you can modify untill the deadline.', |
| 207 | + |
| 208 | + // TODO: how can this be done properly in JS? |
| 209 | + 'contest-submission-domains' => 'Submissions are restricted to these sites: $1', |
205 | 210 | ); |
206 | 211 | |
207 | 212 | /** Message documentation (Message documentation) |
Index: trunk/extensions/Contest/specials/SpecialMyContests.php |
— | — | @@ -310,6 +310,16 @@ |
311 | 311 | 'id' => 'contest-id', |
312 | 312 | ); |
313 | 313 | |
| 314 | + $fields['contestant-submission'] = array( |
| 315 | + 'class' => 'ContestSubmissionField', |
| 316 | + 'label-message' => 'contest-submission-submission', |
| 317 | + 'validation-callback' => array( __CLASS__, 'validateSubmissionField' ), |
| 318 | + 'options' => array( |
| 319 | + 'domains' => implode( '|', ContestSettings::get( 'submissionDomains' ) ), |
| 320 | + 'value' => $contestant->getField( 'submission' ) |
| 321 | + ) |
| 322 | + ); |
| 323 | + |
314 | 324 | $fields['contestant-realname'] = array( |
315 | 325 | 'type' => 'text', |
316 | 326 | 'default' => $user->getRealName(), |
— | — | @@ -319,11 +329,11 @@ |
320 | 330 | ); |
321 | 331 | |
322 | 332 | $fields['contestant-email'] = array( |
323 | | - 'type' => 'text', |
| 333 | + 'type' => 'email', |
324 | 334 | 'default' => $user->getEmail(), |
325 | 335 | 'label-message' => 'contest-signup-email', |
326 | 336 | 'required' => true, |
327 | | - 'validation-callback' => array( __CLASS__, 'validateEmailField' ) |
| 337 | + 'validation-callback' => array( __CLASS__, 'validateEmailField' ), |
328 | 338 | ); |
329 | 339 | |
330 | 340 | $fields['contestant-country'] = array( |
— | — | @@ -352,19 +362,9 @@ |
353 | 363 | 'type' => $hasWMF && $contestant->getField( 'wmf' ) ? 'text' : 'hidden', |
354 | 364 | 'default' => $hasWMF ? $contestant->getField( 'cv' ) : '', |
355 | 365 | 'label-message' => 'contest-signup-cv', |
356 | | - 'validation-callback' => array( __CLASS__, 'validateCVField' ) |
| 366 | + 'validation-callback' => array( __CLASS__, 'validateCVField' ), |
357 | 367 | ); |
358 | 368 | |
359 | | - // TODO: this needs UI work to explain to the user what they can enter here, |
360 | | - // and possibly some pop-up thing where they can just enter their |
361 | | - // user and project, after which we just find the latest rev and store that url. |
362 | | - $fields['contestant-submission'] = array( |
363 | | - 'type' => 'text', |
364 | | - 'default' => $contestant->getField( 'submission' ), |
365 | | - 'label-message' => 'contest-submission-submission', |
366 | | - 'validation-callback' => array( __CLASS__, 'validateSubmissionField' ) |
367 | | - ); |
368 | | - |
369 | 369 | return $fields; |
370 | 370 | } |
371 | 371 | |
— | — | @@ -463,3 +463,23 @@ |
464 | 464 | } |
465 | 465 | |
466 | 466 | } |
| 467 | + |
| 468 | +class ContestSubmissionField extends HTMLFormField { |
| 469 | + |
| 470 | + public function getInputHTML( $value ) { |
| 471 | + $attribs = array( |
| 472 | + 'class' => 'contest-submission', |
| 473 | + 'data-name' => $this->mName |
| 474 | + ); |
| 475 | + |
| 476 | + foreach ( $this->mParams['options'] as $name => $value ) { |
| 477 | + $attribs['data-' . $name] = $value; |
| 478 | + } |
| 479 | + |
| 480 | + return Html::element( |
| 481 | + 'div', |
| 482 | + $attribs |
| 483 | + ); |
| 484 | + } |
| 485 | + |
| 486 | +} |
\ No newline at end of file |
Index: trunk/extensions/Contest/Contest.settings.php |
— | — | @@ -30,6 +30,7 @@ |
31 | 31 | return array( |
32 | 32 | 'votevalues' => range( 0, 5 ), |
33 | 33 | 'enableTopLink' => true, |
| 34 | + 'submissionDomains' => array( 'github.com', 'gitorious.org' ), |
34 | 35 | ); |
35 | 36 | } |
36 | 37 | |
Index: trunk/extensions/Contest/Contest.php |
— | — | @@ -228,7 +228,12 @@ |
229 | 229 | 'contest.special.submission.js', |
230 | 230 | ), |
231 | 231 | 'dependencies' => array( |
232 | | - 'jquery.ui.button' |
| 232 | + 'jquery.ui.button', 'contest.jquery.validate' |
| 233 | + ), |
| 234 | + 'messages' => array( |
| 235 | + 'contest-submission-new-submission', |
| 236 | + 'contest-submission-current-submission', |
| 237 | + 'contest-submission-domains', |
233 | 238 | ) |
234 | 239 | ); |
235 | 240 | |
Index: trunk/extensions/Contest/resources/contest.special.submission.js |
— | — | @@ -8,10 +8,120 @@ |
9 | 9 | |
10 | 10 | (function( $, mw ) { |
11 | 11 | |
| 12 | + /** |
| 13 | + * Regex text escaping function. |
| 14 | + * Borrowed from http://simonwillison.net/2006/Jan/20/escape/ |
| 15 | + */ |
| 16 | + RegExp.escape = function( text ) { |
| 17 | + if ( !arguments.callee.sRE ) { |
| 18 | + var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ]; |
| 19 | + arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' ); |
| 20 | + } |
| 21 | + return text.replace(arguments.callee.sRE, '\\$1'); |
| 22 | + } |
| 23 | + |
| 24 | + $.fn.contestSubmission = function() { |
| 25 | + var _this = this; |
| 26 | + var $this = $( this ); |
| 27 | + |
| 28 | + this.config = {}; |
| 29 | + this.status = {}; |
| 30 | + |
| 31 | + this.input = null; |
| 32 | + this.label = null; |
| 33 | + |
| 34 | + this.getValue = function() { |
| 35 | + return this.input.val(); |
| 36 | + } |
| 37 | + |
| 38 | + this.getDomains = function() { |
| 39 | + return this.config.domains; |
| 40 | + }; |
| 41 | + |
| 42 | +// this.getDomainLinks = function() { |
| 43 | +// return this.getDomains().map( function() { return $( '<a />' ).attr( 'href', 'http://' + this ).text( this ); } ); |
| 44 | +// }; |
| 45 | + |
| 46 | + this.validate = function() { |
| 47 | + var domains = _this.getDomains(); |
| 48 | + |
| 49 | + for ( var i = domains.length - 1; i >= 0; i-- ) { |
| 50 | + var regex = new RegExp( "^https?://(([a-z0-9]+)\\.)?" + RegExp.escape( domains[i] ) + "/(.*)?$", "gi" ); |
| 51 | + if ( regex.test( this.getValue() ) ) { |
| 52 | + return true; |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + return false; |
| 57 | + }; |
| 58 | + |
| 59 | + this.showStatus = function() { |
| 60 | + if ( _this.status.valid ) { |
| 61 | + _this.input.removeClass( 'error' ); |
| 62 | + } |
| 63 | + else { |
| 64 | + _this.input.addClass( 'error' ); |
| 65 | + } |
| 66 | + }; |
| 67 | + |
| 68 | + this.onValueChanged = function() { |
| 69 | + _this.status.valid = _this.validate(); |
| 70 | + _this.showStatus(); |
| 71 | + }; |
| 72 | + |
| 73 | + this.setup = function() { |
| 74 | + var message = $this.attr( 'data-value' ) === '' ? 'contest-submission-new-submission' : 'contest-submission-current-submission'; |
| 75 | + var domainLinks = []; |
| 76 | + |
| 77 | + for ( var i = this.config.domains.length - 1; i >= 0; i-- ) { |
| 78 | + var link = $( '<a />' ).text( this.config.domains[i] ).attr( { |
| 79 | + 'href': 'http://' + this.config.domains[i], |
| 80 | + 'target': 'blank' |
| 81 | + } ); |
| 82 | + domainLinks.push( $( '<div />' ).html( link ).html() ); |
| 83 | + } |
| 84 | + |
| 85 | + var links = $( '<span />' ).html( '' ); |
| 86 | + |
| 87 | + this.label = $( '<label style="display:block" />' ).attr( { |
| 88 | + 'for': this.config.name, |
| 89 | + } ).text( mw.msg( message ) ).append( |
| 90 | + $( '<br />' ), |
| 91 | + mw.msg( 'contest-submission-domains', domainLinks.join( ', ' ) ) |
| 92 | + ); |
| 93 | + |
| 94 | + this.input = $( '<input />' ).attr( { |
| 95 | + 'type': 'text', |
| 96 | + 'value': $this.attr( 'data-value' ), |
| 97 | + 'name': this.config.name, |
| 98 | + 'size': 45, |
| 99 | + 'id': this.config.name |
| 100 | + } ); |
| 101 | + |
| 102 | + this.html( this.label ); |
| 103 | + this.append( this.input ); |
| 104 | + |
| 105 | + this.input.keyup( this.onValueChanged ); |
| 106 | + }; |
| 107 | + |
| 108 | + this.getConfig = function() { |
| 109 | + this.config.name = $this.attr( 'data-name' ); |
| 110 | + this.config.domains = $this.attr( 'data-domains' ).split( '|' ); |
| 111 | + }; |
| 112 | + |
| 113 | + this.getConfig(); |
| 114 | + this.setup(); |
| 115 | + this.onValueChanged(); |
| 116 | + |
| 117 | + return this; |
| 118 | + }; |
| 119 | + |
12 | 120 | $( document ).ready( function() { |
13 | 121 | |
14 | 122 | $( '.mw-htmlform-submit' ).button(); |
15 | 123 | |
| 124 | + $( '.contest-submission' ).contestSubmission(); |
| 125 | + |
16 | 126 | } ); |
17 | 127 | |
18 | 128 | })( window.jQuery, window.mediaWiki ); |
\ No newline at end of file |