Index: trunk/extensions/UploadWizard/UploadWizard.config.php |
— | — | @@ -278,8 +278,8 @@ |
279 | 279 | ), |
280 | 280 | array( |
281 | 281 | 'head' => 'mwe-upwiz-license-custom-head', |
| 282 | + 'special' => 'custom', |
282 | 283 | 'licenses' => array( 'custom' ), |
283 | | - 'prependTemplates' => array( 'custom-badness' ) |
284 | 284 | ), |
285 | 285 | ), |
286 | 286 | 'defaults' => array( 'none' ), |
— | — | @@ -330,6 +330,12 @@ |
331 | 331 | // Max number of uploads for a given form |
332 | 332 | 'maxUploads' => 50, |
333 | 333 | |
| 334 | + // Minimum length of custom wikitext for a license, if used |
| 335 | + 'minCustomLicenseLength' => 6, |
| 336 | + |
| 337 | + // Maximum length of custom wikitext for a license |
| 338 | + 'maxCustomLicenseLength' => 10000, |
| 339 | + |
334 | 340 | // not for use with all wikis. |
335 | 341 | // The ISO 639 code for the language tagalog is "tl". |
336 | 342 | // Normally we name templates for languages by the ISO 639 code. |
Index: trunk/extensions/UploadWizard/UploadWizardHooks.php |
— | — | @@ -293,6 +293,10 @@ |
294 | 294 | 'mwe-upwiz-error-title-fileexists-shared-forbidden', |
295 | 295 | 'mwe-upwiz-error-title-double-apostrophe', |
296 | 296 | 'mwe-upwiz-error-title-extension', |
| 297 | + 'mwe-upwiz-error-license-wikitext-missing', |
| 298 | + 'mwe-upwiz-error-license-wikitext-too-short', |
| 299 | + 'mwe-upwiz-error-license-wikitext-too-long', |
| 300 | + 'mwe-upwiz-error-license-wikitext-invalid', |
297 | 301 | 'mwe-upwiz-details-error-count', |
298 | 302 | 'mwe-upwiz-license-cc-by-sa-3.0', |
299 | 303 | 'mwe-upwiz-license-cc-by-sa-3.0-at', |
Index: trunk/extensions/UploadWizard/UploadWizard.i18n.php |
— | — | @@ -208,6 +208,10 @@ |
209 | 209 | 'mwe-upwiz-error-title-fileexists-shared-forbidden' => 'This title is reserved by a file on a remote shared repository. Choose another name.', |
210 | 210 | 'mwe-upwiz-error-title-double-apostrophe' => 'This title contains a double apostrophe; please remove it.', |
211 | 211 | 'mwe-upwiz-error-title-extension' => 'You do not need to add a file extension. Just make a human readable title and the application will take care of the rest.', |
| 212 | + 'mwe-upwiz-error-license-wikitext-missing' => 'You selected an option which requires you to enter wikitext.', |
| 213 | + 'mwe-upwiz-error-license-wikitext-too-short' => 'The wikitext here is too short to be a license', |
| 214 | + 'mwe-upwiz-error-license-wikitext-too-long' => 'The wikitext you entered is too long.', |
| 215 | + 'mwe-upwiz-error-license-wikitext-invalid' => 'This does not seem to be valid wikitext, or does not contain a license.', |
212 | 216 | 'mwe-upwiz-details-error-count' => 'There {{PLURAL:$1|is one error|are $1 errors}} with the {{PLURAL:$2|form|forms}} above. Correct the errors, and try submitting again.', |
213 | 217 | |
214 | 218 | /* LICENSES & combinations of licenses */ |
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizardLicenseInput.js |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | } |
30 | 30 | |
31 | 31 | _this.$selector = $j( selector ); |
32 | | - _this.$selector.append( $j( '<div class="mwe-error"></div>' ) ); |
| 32 | + _this.$selector.append( $j( '<div class="mwe-error mwe-error-main"></div>' ) ); |
33 | 33 | |
34 | 34 | _this.type = config.type === 'or' ? 'radio' : 'checkbox'; |
35 | 35 | |
— | — | @@ -37,14 +37,64 @@ |
38 | 38 | mw.UploadWizardLicenseInput.prototype.count++; |
39 | 39 | _this.name = 'license' + mw.UploadWizardLicenseInput.prototype.count; |
40 | 40 | |
41 | | - |
| 41 | + // the jquery wrapped inputs (checkboxes or radio buttons) for this licenseInput. |
| 42 | + _this.inputs = []; |
| 43 | + |
| 44 | + // create inputs and licenses from config |
| 45 | + if ( mw.isDefined( config['licenseGroups'] ) ) { |
| 46 | + _this.createGroupedInputs( _this.$selector, config['licenseGroups'] ); |
| 47 | + } else { |
| 48 | + _this.createInputs( _this.$selector, config ); |
| 49 | + } |
| 50 | + |
| 51 | + // set values of the whole license input |
| 52 | + if ( values ) { |
| 53 | + _this.setValues( values ); |
| 54 | + } |
| 55 | + |
| 56 | + return _this; |
| 57 | +}; |
| 58 | + |
| 59 | +mw.UploadWizardLicenseInput.prototype = { |
| 60 | + count: 0, |
| 61 | + |
42 | 62 | /** |
43 | | - * Define the licenses this input will show: |
| 63 | + * Creates the license input interface in toggleable groups. |
| 64 | + * @param jQuery selector |
| 65 | + * @param license input configuration groups |
44 | 66 | */ |
45 | | - _this.licenses = []; |
46 | | - _this.inputs = []; |
| 67 | + createGroupedInputs: function( $el, configGroups ) { |
| 68 | + var _this = this; |
| 69 | + $j.each( configGroups, function( i, group ) { |
| 70 | + var $body, $toggler; |
| 71 | + var $group = $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license-group' ); |
| 72 | + if ( mw.isDefined( group['head'] ) ) { |
| 73 | + // if there is a header, make a toggle-to-expand div and append inputs there. |
| 74 | + var $head = $j( '<div></div>' ).append( |
| 75 | + $j( '<a>' ) |
| 76 | + .addClass( 'mwe-upwiz-deed-license-group-head mwe-upwiz-toggler' ) |
| 77 | + .msg( group.head, _this.count ) |
| 78 | + ); |
| 79 | + $body = $j( '<div></div>' ).addClass( 'mwe-upwiz-toggler-content' ).css( { 'marginBottom': '1em' } ); |
| 80 | + $toggler = $group.append( $head, $body ).collapseToggle(); |
| 81 | + } else { |
| 82 | + // if there is no header, just append licenses to the group div. |
| 83 | + $body = $group; |
| 84 | + } |
| 85 | + if ( mw.isDefined( group['subhead'] ) ) { |
| 86 | + $body.append( $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license-group-subhead' ).msg( group.subhead, _this.count ) ); |
| 87 | + } |
| 88 | + var $licensesDiv = $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license' ); |
| 89 | + _this.createInputs( $licensesDiv, group, $toggler ); |
| 90 | + $body.append( $licensesDiv ); |
| 91 | + _this.$selector.append( $group ); |
| 92 | + } ); |
| 93 | + }, |
| 94 | + |
47 | 95 | /** |
48 | | - * append defined license inputs to element; also records licenses and inputs in _this |
| 96 | + * append defined license inputs to element. |
| 97 | + * SIDE EFFECT: also records licenses and inputs in _this |
| 98 | + * |
49 | 99 | * Abstracts out simple lists of licenses, more complex groups with layout |
50 | 100 | * @param {jQuery} selector to add inputs to |
51 | 101 | * @param {Array} license configuration, which must have a 'licenses' property, which is an array of license names |
— | — | @@ -57,22 +107,22 @@ |
58 | 108 | * methods in its data. |
59 | 109 | * |
60 | 110 | */ |
61 | | - function appendLicenses( $el, config, groupToggler ) { |
62 | | - |
| 111 | + createInputs: function( $el, config, $groupToggler ) { |
| 112 | + var _this = this; |
63 | 113 | if ( !mw.isDefined( config['licenses'] && typeof config['licenses'] === 'object' ) ) { |
64 | 114 | throw new Error( "improper license config" ); |
65 | 115 | } |
66 | 116 | $j.each( config['licenses'], function( i, licenseName ) { |
67 | 117 | if ( mw.isDefined( mw.UploadWizard.config.licenses[licenseName] ) ) { |
68 | 118 | var license = { name: licenseName, props: mw.UploadWizard.config.licenses[licenseName] }; |
69 | | - _this.licenses.push( license ); |
70 | 119 | |
71 | | - var templates = _this.getTemplatesForLicense( license ); |
| 120 | + var templates = mw.isDefined( license.props['templates'] ) ? license.props.templates.slice(0) : [ license.name ]; |
72 | 121 | |
73 | | - var $input = _this.getInputElement( templates, config ); |
| 122 | + var $input = _this.createInputElement( templates, config ); |
74 | 123 | _this.inputs.push( $input ); |
75 | 124 | |
76 | | - var $label = _this.getInputElementLabel( license, $input ); |
| 125 | + var $label = _this.createInputElementLabel( license, $input ); |
| 126 | + |
77 | 127 | $el.append( $input, $label, $j( '<br/>' ) ); |
78 | 128 | // TODO add popup help? |
79 | 129 | |
— | — | @@ -80,56 +130,123 @@ |
81 | 131 | $input.data( 'licenseName', licenseName ); |
82 | 132 | |
83 | 133 | // this is so if a single input in a group changes, we open the entire "toggler" that was hiding them |
84 | | - $input.data( 'groupToggler', groupToggler ); |
| 134 | + $input.data( 'groupToggler', $groupToggler ); |
85 | 135 | |
86 | | - if ( licenseName === 'custom' ) { |
87 | | - $el.append( _this.getInputElementRelatedTextarea( $input ) ); |
| 136 | + if ( config['special'] === 'custom' ) { |
| 137 | + var $customDiv = _this.createCustomWikiTextInterface( $input ); |
| 138 | + $el.append( $customDiv ); |
| 139 | + $input.data( 'textarea', $customDiv.find( 'textarea' ) ); |
88 | 140 | } |
89 | 141 | } |
90 | 142 | } ); |
91 | | - } |
92 | | - |
| 143 | + }, |
93 | 144 | |
94 | | - if ( mw.isDefined( config['licenseGroups'] ) ) { |
95 | | - $j.each( config['licenseGroups'], function( i, group ) { |
96 | | - var toggler; |
97 | | - var $group = $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license-group' ); |
98 | | - // if there is no header, just append licenses to the group div. |
99 | | - var $body = $group; |
100 | | - // if there is a header, make a toggle-to-expand div and append to that instead. |
101 | | - if ( mw.isDefined( group['head'] ) ) { |
102 | | - var $head = $j( '<div></div>' ).append( |
103 | | - $j( '<a>' ) |
104 | | - .addClass( 'mwe-upwiz-deed-license-group-head mwe-upwiz-toggler' ) |
105 | | - .msg( group.head, _this.count ) |
106 | | - ); |
107 | | - $body = $j( '<div></div>' ).addClass( 'mwe-upwiz-toggler-content' ).css( { 'marginBottom': '1em' } ); |
108 | | - toggler = $group.append( $head, $body ).collapseToggle(); |
109 | | - } |
110 | | - if ( mw.isDefined( group['subhead'] ) ) { |
111 | | - $body.append( $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license-group-subhead' ).msg( group.subhead, _this.count ) ); |
112 | | - } |
113 | | - var $licensesDiv = $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license' ); |
114 | | - appendLicenses( $licensesDiv, group, toggler ); |
115 | | - $body.append( $licensesDiv ); |
116 | | - _this.$selector.append( $group ); |
| 145 | + /** |
| 146 | + * License templates are these abstract ideas like cc-by-sa. In general they map directly to a license template. |
| 147 | + * However, configuration for a particular option can add other templates or transform the templates, |
| 148 | + * such as wrapping templates in an outer "self" template for own-work |
| 149 | + * @param {Array} of license template names |
| 150 | + * @param {Object}, license input configuration |
| 151 | + * @return {String} of wikitext |
| 152 | + */ |
| 153 | + createInputValueFromTemplateConfig: function( templates, config ) { |
| 154 | + if ( mw.isDefined( config['prependTemplates'] ) ) { |
| 155 | + $j.each( config['prependTemplates'], function( i, template ) { |
| 156 | + templates.unshift( template ); |
| 157 | + } ); |
| 158 | + } |
| 159 | + if ( mw.isDefined( config['filterTemplate'] ) ) { |
| 160 | + templates.unshift( config['filterTemplate'] ); |
| 161 | + templates = [ templates.join( '|' ) ]; |
| 162 | + } |
| 163 | + var wikiTexts = $j.map( templates, function(t) { return '{{' + t + '}}'; } ); |
| 164 | + return wikiTexts.join( '' ); |
| 165 | + }, |
| 166 | + |
| 167 | + /** |
| 168 | + * Return a radio button or checkbox with appropriate values, depending on config |
| 169 | + * @param {Array} of template strings |
| 170 | + * @param {Object} config for this license input |
| 171 | + * @return {jQuery} wrapped input |
| 172 | + */ |
| 173 | + createInputElement: function( templates, config ) { |
| 174 | + var _this = this; |
| 175 | + |
| 176 | + var attrs = { |
| 177 | + id: _this.name + '_' + _this.inputs.length, // unique id |
| 178 | + name: _this.name, // name of input, shared among all checkboxes or radio buttons. |
| 179 | + type: _this.type, // kind of input |
| 180 | + value: _this.createInputValueFromTemplateConfig( templates, config ) |
| 181 | + }; |
| 182 | + |
| 183 | + var inputHtml = '<input ' + |
| 184 | + $j.map( attrs, function(val, key) { |
| 185 | + return key + '="' + val.toString().replace( '"', '' ) + '"'; |
| 186 | + } ).join( " " ) |
| 187 | + + ' />'; |
| 188 | + |
| 189 | + // Note we aren't using $('<input>').attr( { ... } ) . We construct a string of HTML. |
| 190 | + // IE6 is idiotic about radio buttons; you have to create them as HTML or clicks aren't recorded |
| 191 | + return $j( inputHtml ).click( function() { |
| 192 | + _this.$selector.trigger( 'changeLicenses' ); |
117 | 193 | } ); |
| 194 | + }, |
118 | 195 | |
| 196 | + /** |
| 197 | + * Get a label for the form element |
| 198 | + * @param {Object} license definition from global config. Will tell us the messages, and maybe icons. |
| 199 | + * @param {jQuery} wrapped input |
| 200 | + * @return {jQuery} wrapped label referring to that input, with appropriate HTML, decorations, etc. |
| 201 | + */ |
| 202 | + createInputElementLabel: function( license, $input ) { |
| 203 | + var messageKey = mw.isDefined( license.props['msg'] ) ? license.props.msg : '[missing msg for ' + license.name + ']'; |
| 204 | + var $icons = $j( '<span></span>' ); |
| 205 | + if ( mw.isDefined( license.props['icons'] ) ) { |
| 206 | + $j.each( license.props.icons, function( i, icon ) { |
| 207 | + $icons.append( $j( '<span></span>' ).addClass( 'mwe-upwiz-license-icon mwe-upwiz-' + icon + '-icon' ) ); |
| 208 | + } ); |
| 209 | + } |
| 210 | + return $j( '<label />' ) |
| 211 | + .attr( { 'for': $input.attr('id') } ) |
| 212 | + .msg( messageKey, this.count ) |
| 213 | + .append( $icons ); |
| 214 | + }, |
119 | 215 | |
120 | | - } else { |
121 | | - appendLicenses( _this.$selector, config ); |
122 | | - } |
| 216 | + /** |
| 217 | + * Given an input, return another textarea to be appended below. |
| 218 | + * When text entered here, auto-selects the input. |
| 219 | + * @param {jQuery} wrapped input |
| 220 | + * @return {jQuery} wrapped textarea |
| 221 | + */ |
| 222 | + createCustomWikiTextInterface: function( $input ) { |
| 223 | + var _this = this; |
123 | 224 | |
124 | | - if ( values ) { |
125 | | - _this.setValues( values ); |
126 | | - } |
| 225 | + var nameId = $input.attr( 'id' ) + '_custom'; |
| 226 | + var $textarea = $j( '<textarea></textarea>' ) |
| 227 | + .attr( { id: nameId, name: nameId } ) |
| 228 | + .growTextArea() |
| 229 | + .focus( function() { _this.setInput( $input, true ); } ) |
| 230 | + .keydown( function() { _this.$selector.trigger( 'changeLicenses' ); } ) |
| 231 | + .css( { |
| 232 | + 'width': '100%', |
| 233 | + 'font-family': 'monospace' |
| 234 | + } ); |
127 | 235 | |
128 | | - return _this; |
129 | | -}; |
| 236 | + var $button = $j( '<span></span>' ) |
| 237 | + .button( { label: gM( 'mwe-upwiz-license-custom-preview' ) } ) |
| 238 | + .css( { 'width': '8em' } ) |
| 239 | + .click( function() { _this.showPreview( $textarea.val() ); } ); |
130 | 240 | |
131 | | -mw.UploadWizardLicenseInput.prototype = { |
132 | | - count: 0, |
| 241 | + return $j( '<div></div>' ).css( { 'width': '100%' } ).append( |
| 242 | + $j( '<div><label for="' + nameId + '" class="mwe-error mwe-error-textarea"></label></div>' ), |
| 243 | + $j( '<div></div>' ).css( { 'float': 'right', 'width': '9em', 'padding-left': '1em' } ).append( $button ), |
| 244 | + $j( '<div></div>' ).css( { 'margin-right': '10em' } ).append( $textarea ), |
| 245 | + $j( '<div></div>' ).css( { 'clear':'both' } ) |
| 246 | + ); |
| 247 | + }, |
133 | 248 | |
| 249 | + /* ---- end creational stuff ----- */ |
| 250 | + |
134 | 251 | // Set the input value. If it is part of a group, and this is being turned on, pop open the group so we can see this input. |
135 | 252 | setInput: function( $input, val ) { |
136 | 253 | var _this = this; |
— | — | @@ -140,6 +257,7 @@ |
141 | 258 | if ( bool !== oldVal ) { |
142 | 259 | _this.$selector.trigger( 'changeLicenses' ); |
143 | 260 | } |
| 261 | + |
144 | 262 | // pop open the 'toggle' group if is now on. Do nothing if it is now off. |
145 | 263 | if ( bool && $input.data( 'groupToggler' ) ) { |
146 | 264 | $input.data( 'groupToggler' ).data( 'open' )(); |
— | — | @@ -215,23 +333,40 @@ |
216 | 334 | }, |
217 | 335 | |
218 | 336 | /** |
219 | | - * Gets the wikitext associated with all selected inputs. |
220 | | - * Anything from a text input is automatically suspect, because it might not be valid. So we append a template to double check. |
221 | | - * This is a bit of a hack, in the ideal case we'd make all the inputs into objects that returned their own wikitext, but |
222 | | - * it is easier to extend the current interface (which assumes form input value is all we want). |
| 337 | + * Gets the wikitext associated with all selected inputs. Some inputs also have associated textareas so we append their contents too. |
223 | 338 | * @return string of wikitext (empty string if no inputs set) |
224 | | - */ |
| 339 | + */ |
225 | 340 | getWikiText: function() { |
| 341 | + var _this = this; |
226 | 342 | var wikiTexts = this.getSelectedInputs().map( |
227 | 343 | function() { |
228 | | - return this.val() + "\n"; |
229 | | - } |
| 344 | + return _this.getInputWikiText( this ); |
| 345 | + } |
230 | 346 | ); |
231 | 347 | // need to use makeArray because a jQuery-returned set of things won't have .join |
232 | 348 | return $j.makeArray( wikiTexts ).join( '' ); |
233 | 349 | }, |
234 | 350 | |
235 | 351 | /** |
| 352 | + * Get the value of a particular input |
| 353 | + */ |
| 354 | + getInputWikiText: function( $input) { |
| 355 | + return $input.val() + "\n" + this.getInputTextAreaVal($input); |
| 356 | + }, |
| 357 | + |
| 358 | + /** |
| 359 | + * Get the value of the associated textarea, if any |
| 360 | + * @return {String} |
| 361 | + */ |
| 362 | + getInputTextAreaVal: function( $input ) { |
| 363 | + var extra = ''; |
| 364 | + if ( $input.data( 'textarea' ) ) { |
| 365 | + extra = $j.trim( $input.data( 'textarea' ).val() ); |
| 366 | + } |
| 367 | + return extra; |
| 368 | + }, |
| 369 | + |
| 370 | + /** |
236 | 371 | * Gets which inputs have user-entered values |
237 | 372 | * @return {jQuery Array} of inputs |
238 | 373 | */ |
— | — | @@ -242,31 +377,67 @@ |
243 | 378 | |
244 | 379 | /** |
245 | 380 | * Check if a valid value is set, also look for incompatible choices. |
246 | | - * Side effect: if no valid value, add notes to the interface. Add listeners to interface, to revalidate and remove notes. |
247 | | - * @return boolean; true if a value set, false otherwise |
| 381 | + * Side effect: if no valid value, add error notices to the interface. Add listeners to interface, to revalidate and remove notices |
| 382 | + * If I was sufficiently clever, most of these could just be dynamically added & subtracted validation rules. |
| 383 | + * Instead this is a bit of a recapitulation of jquery.validate |
| 384 | + * @return boolean; true if a value set and all is well, false otherwise |
248 | 385 | */ |
249 | 386 | valid: function() { |
250 | 387 | var _this = this; |
251 | | - var isValid = true; |
252 | 388 | |
253 | | - if ( ! _this.isSet() ) { |
254 | | - isValid = false; |
255 | | - errorHtml = gM( 'mwe-upwiz-deeds-need-license' ); |
| 389 | + var errors = []; |
| 390 | + |
| 391 | + var selectedInputs = this.getSelectedInputs(); |
| 392 | + |
| 393 | + if ( selectedInputs.length === 0 ) { |
| 394 | + errors.push( [ this.$selector.find( '.mwe-error-head' ), 'mwe-upwiz-deeds-need-license' ] ); |
| 395 | + |
| 396 | + } else { |
| 397 | + // It's pretty hard to screw up a radio button, so if even one of them is selected it's okay. |
| 398 | + // But also check that associated textareas are filled for if the input is selected, and that |
| 399 | + // they are the appropriate size. |
| 400 | + $j.each( selectedInputs, function(i, $input) { |
| 401 | + if ( ! $input.data( 'textarea' ) ) { |
| 402 | + return; |
| 403 | + } |
| 404 | + |
| 405 | + var textAreaName = $input.data( 'textarea' ).attr( 'name' ); |
| 406 | + var $errorEl = $( 'label[for=' + textAreaName + '].mwe-error' ); |
| 407 | + |
| 408 | + var text = _this.getInputTextAreaVal( $input ); |
| 409 | + |
| 410 | + if ( text === '' ) { |
| 411 | + errors.push( [ $errorEl, 'mwe-upwiz-error-license-wikitext-missing' ] ); |
| 412 | + } else if ( text.length < mw.UploadWizard.config.minCustomLicenseLength ) { |
| 413 | + errors.push( [ $errorEl, 'mwe-upwiz-error-license-wikitext-too-short' ] ); |
| 414 | + } else if ( text.length > mw.UploadWizard.config.maxCustomLicenseLength ) { |
| 415 | + errors.push( [ $errorEl, 'mwe-upwiz-error-license-wikitext-too-long' ] ); |
| 416 | + } else if ( !_this.validateWikiText( text ) ) { |
| 417 | + errors.push( [ $errorEl, 'mwe-upwiz-error-license-wikitext-invalid' ] ); |
| 418 | + } |
| 419 | + |
| 420 | + } ); |
256 | 421 | } |
| 422 | + |
| 423 | + // clear out the errors if we are now valid |
| 424 | + if ( errors.length === 0 ) { |
| 425 | + this.$selector.find( '.mwe-error' ).fadeOut(); |
| 426 | + } else { |
| 427 | + // show the errors |
| 428 | + $j.each( errors, function( i, err ) { |
| 429 | + var $el = err[0], |
| 430 | + msg = err[1]; |
| 431 | + $el.msg( msg ).show(); |
| 432 | + } ); |
257 | 433 | |
258 | | - var $errorEl = this.$selector.find( '.mwe-error' ); |
259 | | - if (isValid) { |
260 | | - $errorEl.fadeOut(); |
261 | | - } else { |
262 | | - // we bind to $selector because unbind() doesn't work on non-DOM objects |
| 434 | + // and watch for any change at all in the license to revalidate. |
263 | 435 | _this.$selector.bind( 'changeLicenses.valid', function() { |
264 | 436 | _this.$selector.unbind( 'changeLicenses.valid' ); |
265 | 437 | _this.valid(); |
266 | | - } ); |
267 | | - $errorEl.html( errorHtml ).show(); |
| 438 | + } ); |
268 | 439 | } |
269 | 440 | |
270 | | - return isValid; |
| 441 | + return errors.length === 0; |
271 | 442 | }, |
272 | 443 | |
273 | 444 | |
— | — | @@ -278,120 +449,76 @@ |
279 | 450 | return this.getSelectedInputs().length > 0; |
280 | 451 | }, |
281 | 452 | |
| 453 | + |
282 | 454 | /** |
283 | | - * Given a license name, return template names |
284 | | - * Note we return copies, so as not to perturb the configuration itself |
285 | | - * @param {String} license name |
286 | | - * @return {Array} of strings of template names |
| 455 | + * Attempt to determine if wikitext parses... and maybe does it contain a license tag |
| 456 | + * @return boolean |
287 | 457 | */ |
288 | | - getTemplatesForLicense: function( license ) { |
289 | | - return mw.isDefined( license.props['templates'] ) ? license.props.templates.slice(0) : [ license.name ]; |
290 | | - }, |
| 458 | + validateWikiText: function( text ) { |
| 459 | + var parser = new mw.language.parser(), |
| 460 | + _this = this, |
| 461 | + ast; |
291 | 462 | |
292 | | - /** |
293 | | - * License templates are these abstract ideas like cc-by-sa. In general they map directly to a license template. |
294 | | - * However, configuration for a particular option can add other templates or transform the templates, |
295 | | - * such as wrapping templates in an outer "self" template for own-work |
296 | | - * @param {Array} of license template names |
297 | | - * @param {Object}, license input configuration |
298 | | - * @return {String} of wikitext |
299 | | - */ |
300 | | - getWikiTextForTemplates: function( templates, config ) { |
301 | | - if ( mw.isDefined( config['prependTemplates'] ) ) { |
302 | | - $j.each( config['prependTemplates'], function( i, template ) { |
303 | | - templates.unshift( template ); |
304 | | - } ); |
| 463 | + try { |
| 464 | + ast = parser.wikiTextToAst( text ); |
| 465 | + } catch (e) { |
| 466 | + return false; |
305 | 467 | } |
306 | | - if ( mw.isDefined( config['filterTemplate'] ) ) { |
307 | | - templates.unshift( config['filterTemplate'] ); |
308 | | - templates = [ templates.join( '|' ) ]; |
| 468 | + |
| 469 | + function accumTemplates( node, templates ) { |
| 470 | + if ( typeof node === 'object' ) { |
| 471 | + var operation = node[0].toLowerCase(); |
| 472 | + if ( typeof mw.language.htmlEmitter.prototype[operation] !== 'function' ) { |
| 473 | + templates.push( operation ); |
| 474 | + } |
| 475 | + $j.map( node.slice( 1 ), function( n ) { |
| 476 | + accumTemplates( n, templates ); |
| 477 | + } ); |
| 478 | + } |
309 | 479 | } |
310 | | - return $j.map( templates, function(t) { return '{{' + t + '}}'; } ).join( '' ); |
311 | | - }, |
| 480 | + var templates = []; |
| 481 | + accumTemplates( ast, templates ); |
312 | 482 | |
313 | | - /** |
314 | | - * Return a radio button or checkbox with appropriate values, depending on config |
315 | | - * @param {Array} of template strings |
316 | | - * @param {Object} config for this license input |
317 | | - * @return {jQuery} wrapped input |
318 | | - */ |
319 | | - getInputElement: function( templates, config ) { |
320 | | - var _this = this; |
321 | | - |
322 | | - var attrs = { |
323 | | - id: _this.name + '_' + _this.inputs.length, // unique id |
324 | | - name: _this.name, // name of input, shared among all checkboxes or radio buttons. |
325 | | - type: _this.type, // kind of input |
326 | | - value: _this.getWikiTextForTemplates( templates, config ) |
327 | | - }; |
| 483 | + var topCat = new mw.Title( 'License tags', 'category' ); |
328 | 484 | |
329 | | - var inputHtml = '<input ' + |
330 | | - $j.map( attrs, function(val, key) { |
331 | | - return key + '="' + val.toString().replace( '"', '' ) + '"'; |
332 | | - } ).join( " " ) |
333 | | - + ' />'; |
334 | | - |
335 | | - // Note we aren't using $('<input>').attr( { ... } ) . We construct a string of HTML. |
336 | | - // IE6 is idiotic about radio buttons; you have to create them as HTML or clicks aren't recorded |
337 | | - return $j( inputHtml ).click( function() { |
338 | | - _this.$selector.trigger( 'changeLicenses' ); |
| 485 | + var found = false; |
| 486 | + function recurseCategories( desiredCatTitle, title, depthToContinue ) { |
| 487 | + if ( depthToContinue === 0 ) { |
| 488 | + return; |
| 489 | + } |
| 490 | + var ok = function(cats) { |
| 491 | + if ( cats !== false ) { |
| 492 | + $.each( cats, function( i, catTitle ) { |
| 493 | + if ( catTitle.getNameText() === desiredCatTitle.getNameText() ) { |
| 494 | + found = true; |
| 495 | + return false; |
| 496 | + } |
| 497 | + recurseCategories( desiredCatTitle, catTitle, depthToContinue - 1 ); |
| 498 | + return true; |
| 499 | + } ); |
| 500 | + } |
| 501 | + }; |
| 502 | + var err = function() {}; |
| 503 | + // this proceeds synchronously, so we pick up in the next line |
| 504 | + _this.api.getCategories( title, ok, err, false ); |
| 505 | + } |
| 506 | + |
| 507 | + $.each( templates, function( i, t ) { |
| 508 | + var title = new mw.Title( t, 'template' ); |
| 509 | + recurseCategories( topCat, title, 5 ); |
| 510 | + if ( found ) { |
| 511 | + return false; |
| 512 | + } |
339 | 513 | } ); |
| 514 | + |
| 515 | + return found; |
340 | 516 | }, |
341 | 517 | |
342 | 518 | /** |
343 | | - * Get a label for the form element |
344 | | - * @param {Object} license definition from global config. Will tell us the messages, and maybe icons. |
345 | | - * @param {jQuery} wrapped input |
346 | | - * @return {jQuery} wrapped label referring to that input, with appropriate HTML, decorations, etc. |
| 519 | + * Preview license from a particular input, in a popup window |
| 520 | + * @param {jQuery} an input |
347 | 521 | */ |
348 | | - getInputElementLabel: function( license, $input ) { |
349 | | - var messageKey = mw.isDefined( license.props['msg'] ) ? license.props.msg : '[missing msg for ' + license.name + ']'; |
350 | | - var $icons = $j( '<span></span>' ); |
351 | | - if ( mw.isDefined( license.props['icons'] ) ) { |
352 | | - $j.each( license.props.icons, function( i, icon ) { |
353 | | - $icons.append( $j( '<span></span>' ).addClass( 'mwe-upwiz-license-icon mwe-upwiz-' + icon + '-icon' ) ); |
354 | | - } ); |
355 | | - } |
356 | | - return $j( '<label />' ) |
357 | | - .attr( { 'for': $input.attr('id') } ) |
358 | | - .msg( messageKey, this.count ) |
359 | | - .append( $icons ); |
360 | | - }, |
361 | | - |
362 | | - /** |
363 | | - * Given an input, return another textarea to be appended below. |
364 | | - * When text entered here, auto-selects the input. |
365 | | - * @param {jQuery} wrapped input |
366 | | - * @return {jQuery} wrapped textarea |
367 | | - */ |
368 | | - getInputElementRelatedTextarea: function( $input ) { |
369 | | - var _this = this; |
370 | | - |
371 | | - var $textarea = $j( '<textarea></textarea>' ) |
372 | | - .attr( { id: $input.attr( 'id' ) + '_custom' } ) |
373 | | - .growTextArea() |
374 | | - .focus( function() { _this.setInput( $input, true ); } ) |
375 | | - .css( { |
376 | | - 'width': '100%', |
377 | | - 'font-family': 'monospace' |
378 | | - } ); |
379 | | - |
380 | | - var $button = $j( '<span></span>' ) |
381 | | - .button( { label: gM( 'mwe-upwiz-license-custom-preview' ) } ) |
382 | | - .css( { 'width': '8em' } ) |
383 | | - .click( function() { _this.showPreview( $textarea.val() ); } ); |
384 | | - |
385 | | - return $j( '<div></div>' ).css( { 'width': '100%' } ).append( |
386 | | - $j( '<div></div>' ).css( { 'float': 'right', 'width': '9em', 'padding-left': '1em' } ).append( $button ), |
387 | | - $j( '<div></div>' ).css( { 'margin-right': '10em' } ).append( $textarea ), |
388 | | - $j( '<div></div>' ).css( { 'clear':'both' } ) |
389 | | - ); |
390 | | - }, |
391 | | - |
392 | | - /** |
393 | | - * Preview license |
394 | | - */ |
395 | | - showPreview: function() { |
| 522 | + showPreview: function( $input ) { |
396 | 523 | // do stuff with this.api |
397 | 524 | } |
398 | 525 | |