message key to use in the name column,
$context: IContextSource object
+'SpecialTrackingCategories::preprocess': Called after LinkBatch on Special:TrackingCategories
+$specialPage: The SpecialTrackingCategories object
+$trackingCategories: Array of data from Special:TrackingCategories with msg and cats
+
+'SpecialTrackingCategories::generateCatLink': Called for each cat link on Special:TrackingCategories
+$specialPage: The SpecialTrackingCategories object
+$catTitle: The Title object of the linked category
+&$html: The Result html
+
'SpecialUploadComplete': Called after successfully uploading a file from
Special:Upload.
&$form: The SpecialUpload object
* options-messages - As for HTMLSelectField
* options - As for HTMLSelectField
* options-message - As for HTMLSelectField
- * autocomplete - Associative array mapping display text to values.
- * autocomplete-messages - Like autocomplete, but keys are message names.
+ * autocomplete-data - Associative array mapping display text to values.
+ * autocomplete-data-messages - Like autocomplete, but keys are message names.
* require-match - Boolean, if true the value must be in the options or the
* autocomplete.
* other-message - Message to use instead of htmlform-selectorother-other for
* the 'other' message.
* other - Raw text to use for the 'other' message
+ *
+ * The old name of autocomplete-data[-messages] was autocomplete[-messages] which is still
+ * recognized but deprecated since MediaWiki 1.29 since it conflicts with how autocomplete is
+ * used in HTMLTextField.
*/
class HTMLAutoCompleteSelectField extends HTMLTextField {
- protected $autocomplete = [];
+ protected $autocompleteData = [];
public function __construct( $params ) {
$params += [
'require-match' => false,
];
+ // FIXME B/C, remove in 1.30
+ if (
+ array_key_exists( 'autocomplete', $params )
+ && !array_key_exists( 'autocomplete-data', $params )
+ ) {
+ $params['autocomplete-data'] = $params['autocomplete'];
+ unset( $params['autocomplete'] );
+ }
+ if (
+ array_key_exists( 'autocomplete-messages', $params )
+ && !array_key_exists( 'autocomplete-data-messages', $params )
+ ) {
+ $params['autocomplete-data-messages'] = $params['autocomplete-messages'];
+ unset( $params['autocomplete-messages'] );
+ }
+
parent::__construct( $params );
- if ( array_key_exists( 'autocomplete-messages', $this->mParams ) ) {
- foreach ( $this->mParams['autocomplete-messages'] as $key => $value ) {
+ if ( array_key_exists( 'autocomplete-data-messages', $this->mParams ) ) {
+ foreach ( $this->mParams['autocomplete-data-messages'] as $key => $value ) {
$key = $this->msg( $key )->plain();
- $this->autocomplete[$key] = strval( $value );
+ $this->autocompleteData[$key] = strval( $value );
}
- } elseif ( array_key_exists( 'autocomplete', $this->mParams ) ) {
- foreach ( $this->mParams['autocomplete'] as $key => $value ) {
- $this->autocomplete[$key] = strval( $value );
+ } elseif ( array_key_exists( 'autocomplete-data', $this->mParams ) ) {
+ foreach ( $this->mParams['autocomplete-data'] as $key => $value ) {
+ $this->autocompleteData[$key] = strval( $value );
}
}
- if ( !is_array( $this->autocomplete ) || !$this->autocomplete ) {
+ if ( !is_array( $this->autocompleteData ) || !$this->autocompleteData ) {
throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' );
}
if ( $val === 'other' ) {
$val = $request->getText( $this->mName );
- if ( isset( $this->autocomplete[$val] ) ) {
- $val = $this->autocomplete[$val];
+ if ( isset( $this->autocompleteData[$val] ) ) {
+ $val = $this->autocompleteData[$val];
}
}
return $p;
}
- $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+ $validOptions = HTMLFormField::flattenOptions( $this->getOptions() ?: [] );
if ( in_array( strval( $value ), $validOptions, true ) ) {
return true;
- } elseif ( in_array( strval( $value ), $this->autocomplete, true ) ) {
+ } elseif ( in_array( strval( $value ), $this->autocompleteData, true ) ) {
return true;
} elseif ( $this->mParams['require-match'] ) {
return $this->msg( 'htmlform-select-badoption' )->parse();
public function getAttributes( array $list ) {
$attribs = [
'type' => 'text',
- 'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ),
+ 'data-autocomplete' => FormatJson::encode( array_keys( $this->autocompleteData ) ),
] + parent::getAttributes( $list );
if ( $this->getOptions() ) {
if ( $valInSelect ) {
$value = '';
} else {
- $key = array_search( strval( $value ), $this->autocomplete, true );
+ $key = array_search( strval( $value ), $this->autocompleteData, true );
if ( $key !== false ) {
$value = $key;
}
<?php
+/**
+ * <input> field.
+ *
+ * Besides the parameters recognized by HTMLFormField, the following are
+ * recognized:
+ * autocomplete - HTML autocomplete value (a boolean for on/off or a string according to
+ * https://html.spec.whatwg.org/multipage/forms.html#autofill )
+ */
class HTMLTextField extends HTMLFormField {
protected $mPlaceholder = '';
+ /** @var bool HTML autocomplete attribute */
+ protected $autocomplete;
+
/**
* @param array $params
* - type: HTML textfield type
* for password fields)
*/
public function __construct( $params ) {
+ if ( isset( $params['autocomplete'] ) && is_bool( $params['autocomplete'] ) ) {
+ $params['autocomplete'] = $params['autocomplete'] ? 'on' : 'off';
+ }
+
parent::__construct( $params );
if ( isset( $params['placeholder-message'] ) ) {
'required',
'autofocus',
'multiple',
- 'readonly'
+ 'readonly',
+ 'autocomplete',
];
$attribs += $this->getAttributes( $allowedParams );
'required',
'tabindex',
'type',
+ 'autocomplete',
];
$attribs += OOUI\Element::configFromHtmlAttributes(
$this->getAttributes( $allowedParams )
);
+ // FIXME T150983 downgrade autocomplete
+ if ( isset( $attribs['autocomplete'] ) ) {
+ if ( $attribs['autocomplete'] === 'on' ) {
+ $attribs['autocomplete'] = true;
+ } elseif ( $attribs['autocomplete'] === 'off' ) {
+ $attribs['autocomplete'] = false;
+ } else {
+ unset( $attribs['autocomplete'] );
+ }
+ }
+
$type = $this->getType( $attribs );
return $this->getInputWidget( [
}
$batch->execute();
+ Hooks::run( 'SpecialTrackingCategories::preprocess', [ $this, $trackingCategories ] );
+
foreach ( $trackingCategories as $catMsg => $data ) {
$allMsgs = [];
$catDesc = $catMsg . '-desc';
);
foreach ( $data['cats'] as $catTitle ) {
- $catTitleText = Linker::link(
+ $html = Linker::link(
$catTitle,
htmlspecialchars( $catTitle->getText() )
);
- $allMsgs[] = $catTitleText;
+
+ Hooks::run( 'SpecialTrackingCategories::generateCatLink',
+ [ $this, $catTitle, &$html ] );
+
+ $allMsgs[] = $html;
}
# Extra message, when no category was found
* @private
* @param {string[]} modules Array of string module names
* @return {Array} List of dependencies, including 'module'.
+ * @throws {Error} If an unregistered module or a dependency loop is encountered
*/
function resolve( modules ) {
var resolved = [];
deferred.fail( error );
}
- // Resolve entire dependency map
- dependencies = resolve( dependencies );
+ try {
+ // Resolve entire dependency map
+ dependencies = resolve( dependencies );
+ } catch ( e ) {
+ return deferred.reject( e ).promise();
+ }
if ( allReady( dependencies ) ) {
// Run ready immediately
deferred.resolve( mw.loader.require );
*/
function testOptionalSelectElement() {
$params = [
- 'fieldname' => 'Test',
- 'autocomplete' => $this->options,
- 'options' => $this->options,
+ 'fieldname' => 'Test',
+ 'autocomplete-data' => $this->options,
+ 'options' => $this->options,
];
$field = new HTMLAutoCompleteSelectField( $params );
} );
} );
+ QUnit.test( '.using() Error: Circular dependency', function ( assert ) {
+ mw.loader.register( [
+ [ 'test.circle1', '0', [ 'test.circle2' ] ],
+ [ 'test.circle2', '0', [ 'test.circle3' ] ],
+ [ 'test.circle3', '0', [ 'test.circle1' ] ]
+ ] );
+ mw.loader.using( 'test.circle3' ).then(
+ function done() {
+ assert.ok( false, 'Unexpected resolution, expected error.' );
+ },
+ function fail( e ) {
+ assert.ok( /Circular/.test( String( e ) ), 'Detect circular dependency' );
+ }
+ );
+ } );
+
+ QUnit.test( '.load() - Error: Circular dependency', function ( assert ) {
+ mw.loader.register( [
+ [ 'test.circleA', '0', [ 'test.circleB' ] ],
+ [ 'test.circleB', '0', [ 'test.circleC' ] ],
+ [ 'test.circleC', '0', [ 'test.circleA' ] ]
+ ] );
+ assert.throws( function () {
+ mw.loader.load( 'test.circleC' );
+ }, /Circular/, 'Detect circular dependency' );
+ } );
+
+ QUnit.test( '.using() - Error: Unregistered', function ( assert ) {
+ mw.loader.using( 'test.using.unreg' ).then(
+ function done() {
+ assert.ok( false, 'Unexpected resolution, expected error.' );
+ },
+ function fail( e ) {
+ assert.ok( /Unknown/.test( String( e ) ), 'Detect unknown dependency' );
+ }
+ );
+ } );
+
+ QUnit.test( '.load() - Error: Unregistered (ignored)', 0, function ( assert ) {
+ mw.loader.load( 'test.using.unreg2' );
+ } );
+
QUnit.test( '.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
assert.strictEqual( mw.track.callCount, 1 );
} );
- QUnit.test( 'Circular dependency', 1, function ( assert ) {
- mw.loader.register( [
- [ 'test.circle1', '0', [ 'test.circle2' ] ],
- [ 'test.circle2', '0', [ 'test.circle3' ] ],
- [ 'test.circle3', '0', [ 'test.circle1' ] ]
- ] );
- assert.throws( function () {
- mw.loader.using( 'test.circle3' );
- }, /Circular/, 'Detect circular dependency' );
- } );
-
QUnit.test( 'Out-of-order implementation', 9, function ( assert ) {
mw.loader.register( [
[ 'test.module4', '0' ],