*/
$wgExternalDiffEngine = false;
+/**
+ * wikidiff2 supports detection of changes in moved paragraphs.
+ * This setting controls the maximum number of paragraphs to compare before it bails out.
+ * Supported values:
+ * * 0: detection of moved paragraphs is disabled
+ * * int > 0: maximum number of paragraphs to compare
+ * Note: number of paragraph comparisons is in O(n^2).
+ * This setting is only effective if the wikidiff2 PHP/HHVM module is used as diffengine.
+ * See $wgExternalDiffEngine.
+ *
+ * @since 1.30
+ */
+$wgWikiDiff2MovedParagraphDetectionCutoff = 0;
+
/**
* Disable redirects to special pages and interwiki redirects, which use a 302
* and have no "redirected from" link.
* interpret a given string as being a JavaScript expression, instead of string
* data.
*
- * Example:
+ * @par Example:
+ * @code
+ * Xml::encodeJsVar( new XmlJsCode( 'a + b' ) );
+ * @encode
*
- * Xml::encodeJsVar( new XmlJsCode( 'a + b' ) );
- *
- * Returns "a + b".
+ * This returns "a + b".
*
* @note As of 1.21, XmlJsCode objects cannot be nested inside objects or arrays. The sole
* exception is the $args argument to Xml::encodeJsCall() because Xml::encodeJsVar() is
* @inheritdoc
*/
public function isSelected( FormOptions $opts ) {
- $values = explode(
- ChangesListStringOptionsFilterGroup::SEPARATOR,
- $opts[ $this->getGroup()->getName() ]
- );
+ $option = $opts[ $this->getGroup()->getName() ];
+ if ( $option === ChangesListStringOptionsFilterGroup::ALL ) {
+ return true;
+ }
+
+ $values = explode( ChangesListStringOptionsFilterGroup::SEPARATOR, $option );
return in_array( $this->getName(), $values );
}
}
$wgExternalDiffEngine = false;
}
+ // Better external diff engine, the 2 may some day be dropped
+ // This one does the escaping and segmenting itself
if ( function_exists( 'wikidiff2_do_diff' ) && $wgExternalDiffEngine === false ) {
- # Better external diff engine, the 2 may some day be dropped
- # This one does the escaping and segmenting itself
- $text = wikidiff2_do_diff( $otext, $ntext, 2 );
+ $wikidiff2Version = phpversion( 'wikidiff2' );
+ if (
+ $wikidiff2Version !== false &&
+ version_compare( $wikidiff2Version, '0.3.0', '>=' )
+ ) {
+ $text = wikidiff2_do_diff(
+ $otext,
+ $ntext,
+ 2,
+ $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' )
+ );
+ } else {
+ // Don't pass the 4th parameter for compatibility with older versions of wikidiff2
+ $text = wikidiff2_do_diff(
+ $otext,
+ $ntext,
+ 2
+ );
+
+ // Log a warning in case the configuration value is set to not silently ignore it
+ if ( $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ) > 0 ) {
+ wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no
+ effect since the used version of WikiDiff2 does not support it.' );
+ }
+ }
+
$text .= $this->debug( 'wikidiff2' );
return $text;
* @since 1.17
*/
class SqliteInstaller extends DatabaseInstaller {
- const MINIMUM_VERSION = '3.3.7';
+
+ public $minimumVersion = '3.3.7';
/**
* @var DatabaseSqlite
$result = Status::newGood();
// Bail out if SQLite is too old
$db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
- if ( version_compare( $db->getServerVersion(), self::MINIMUM_VERSION, '<' ) ) {
- $result->fatal( 'config-outdated-sqlite', $db->getServerVersion(), self::MINIMUM_VERSION );
+ if ( version_compare( $db->getServerVersion(), $this->minimumVersion, '<' ) ) {
+ $result->fatal( 'config-outdated-sqlite', $db->getServerVersion(), $this->minimumVersion );
}
// Check for FTS3 full-text search module
if ( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Ori Livneh
+ */
+
/**
* APC-backed and APCu-backed function memoization
*
* MemoizedCallable::call( 'range', array( 5, 8 ) ); // same
* @endcode
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Ori Livneh
* @since 1.27
*/
class MemoizedCallable {
* 'rc'. If the URI contains a query string, its parameters will be parsed
* as RedisConnectionPool options.
*
- * @example
+ * @par Example:
+ * @code
* $wgRCFeeds['redis'] = array(
* 'formatter' => 'JSONRCFeedFormatter',
* 'uri' => "redis://127.0.0.1:6379/rc.$wgDBname",
* );
+ * @encode
*
* @since 1.22
*/
/**
* The type of the redirect (user/file/revision)
*
+ * Example value: `'user'`
+ *
* @var string $mType
- * @example 'user'
*/
protected $mType;
/**
* The identifier/value for the redirect (which id, which file)
*
+ * Example value: `'42'`
+ *
* @var string $mValue
- * @example '42'
*/
protected $mValue;
"rcfilters-tag-prefix-namespace": ":$1",
"rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
"rcfilters-tag-prefix-tags": "#$1",
+ "rcfilters-exclude-button-off": "Exclude selected",
+ "rcfilters-exclude-button-on": "Excluding selected",
"rcfilters-view-tags": "Tagged edits",
"rcfilters-view-namespaces-tooltip": "Filter results by namespace",
"rcfilters-view-tags-tooltip": "Filter results using edit tags",
"rcfilters-tag-prefix-namespace": "Prefix for the namespace tags in [[Special:RecentChanges]]. Namespace tags use a colon (:) as prefix. Please keep this format.\n\nParameters:\n* $1 - Filter name.",
"rcfilters-tag-prefix-namespace-inverted": "Prefix for the namespace inverted tags in [[Special:RecentChanges]]. Namespace tags use a colon (:) as prefix. Please keep this format.\n\nParameters:\n* $1 - Filter name.\n{{Identical|Not}}",
"rcfilters-tag-prefix-tags": "Prefix for the edit tags in [[Special:RecentChanges]]. Edit tags use a hash (#) as prefix. Please keep this format.\n\nParameters:\n* $1 - Tag display name.",
+ "rcfilters-exclude-button-off": "Title for the button that excludes selected namespaces, when it is not yet active.",
+ "rcfilters-exclude-button-on": "Title for the button that excludes selected namespaces, when it is not yet active.",
"rcfilters-view-tags": "Title for the tags view in [[Special:RecentChanges]]\n{{Identical|Tag}}",
"rcfilters-view-namespaces-tooltip": "Tooltip for the button that loads the namespace view in [[Special:RecentChanges]]",
"rcfilters-view-tags-tooltip": "Tooltip for the button that loads the tags view in [[Special:RecentChanges]]",
"grunt-eslint": "19.0.0",
"grunt-jsonlint": "1.1.0",
"grunt-karma": "2.0.0",
- "grunt-stylelint": "0.7.0",
+ "grunt-stylelint": "0.8.0",
"grunt-webdriver": "2.0.3",
"karma": "1.5.0",
"karma-chrome-launcher": "2.0.0",
"karma-qunit": "1.0.0",
"nodemw": "0.10.1",
"qunitjs": "1.23.1",
+ "stylelint": "7.8.0",
"stylelint-config-wikimedia": "0.4.1",
"wdio-junit-reporter": "0.2.0",
"wdio-mocha-framework": "0.5.8",
'rcfilters-tag-prefix-namespace',
'rcfilters-tag-prefix-namespace-inverted',
'rcfilters-tag-prefix-tags',
+ 'rcfilters-exclude-button-off',
+ 'rcfilters-exclude-button-on',
'rcfilters-view-tags',
'rcfilters-view-namespaces-tooltip',
'rcfilters-view-tags-tooltip',
// Invert namespaces button
this.invertNamespacesButton = new OO.ui.ToggleButtonWidget( {
icon: '',
- label: mw.msg( 'invert' ),
classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-invertNamespacesButton' ]
} );
this.invertNamespacesButton.toggle( this.model.getCurrentView() === 'namespaces' );
+ this.updateInvertButton( this.model.areNamespacesInverted() );
// Events
this.backButton.connect( this, { click: 'onBackButtonClick' } );
* @param {boolean} isInverted Namespaces selection is inverted
*/
mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelInvertChange = function ( isInverted ) {
+ this.updateInvertButton( isInverted );
+ };
+
+ /**
+ * Update the state of the invert button
+ *
+ * @param {boolean} isInverted Namespaces selection is inverted
+ */
+ mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.updateInvertButton = function ( isInverted ) {
this.invertNamespacesButton.setActive( isInverted );
+ this.invertNamespacesButton.setLabel(
+ isInverted ?
+ mw.msg( 'rcfilters-exclude-button-on' ) :
+ mw.msg( 'rcfilters-exclude-button-off' )
+ );
};
mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onBackButtonClick = function () {
-/* jshint -W024*/
( function ( mw, $ ) {
$( function () {
- mw.widgets.DateInputWidget.static.infuse( 'mw-date-start' );
- mw.widgets.DateInputWidget.static.infuse( 'mw-date-end' );
+ var startInput = mw.widgets.DateInputWidget.static.infuse( 'mw-date-start' ),
+ endInput = mw.widgets.DateInputWidget.static.infuse( 'mw-date-end' );
+
+ startInput.on( 'deactivate', function ( userSelected ) {
+ if ( userSelected ) {
+ endInput.focus();
+ }
+ } );
} );
}( mediaWiki, jQuery ) );
OO.inheritClass( mw.widgets.DateInputWidget, OO.ui.TextInputWidget );
OO.mixinClass( mw.widgets.DateInputWidget, OO.ui.mixin.IndicatorElement );
+ /* Events */
+
+ /**
+ * Fired when the widget is deactivated (i.e. the calendar is closed). This can happen because
+ * the user selected a value, or because the user blurred the widget.
+ *
+ * @event deactivate
+ * @param {boolean} userSelected Whether the deactivation happened because the user selected a value
+ */
+
/* Methods */
/**
* Deactivate this input field for data entry. Closes the calendar and hides the text field.
*
* @private
+ * @param {boolean} [userSelected] Whether we are deactivating because the user selected a value
*/
- mw.widgets.DateInputWidget.prototype.deactivate = function () {
+ mw.widgets.DateInputWidget.prototype.deactivate = function ( userSelected ) {
this.$element.removeClass( 'mw-widget-dateInputWidget-active' );
this.$handle.show();
this.textInput.toggle( false );
this.calendar.toggle( false );
this.setValidityFlag();
+
+ if ( userSelected ) {
+ // Prevent focusing the handle from reopening the calendar
+ this.closing = true;
+ this.$handle.focus();
+ this.closing = false;
+ }
+
+ this.emit( 'deactivate', !!userSelected );
};
/**
*/
mw.widgets.DateInputWidget.prototype.onCalendarKeyPress = function ( e ) {
if ( !this.isDisabled() && e.which === OO.ui.Keys.ENTER ) {
- // Prevent focusing the handle from reopening the calendar
- this.closing = true;
-
- this.deactivate();
- this.$handle.focus();
-
- this.closing = false;
+ this.deactivate( true );
return false;
}
};
$( e.target ).hasClass( 'mw-widget-calendarWidget-month' )
)
) {
- // Prevent focusing the handle from reopening the calendar
- this.closing = true;
-
- this.deactivate();
- this.$handle.focus();
-
- this.closing = false;
+ this.deactivate( true );
return false;
}
};
* @private
*/
mw.widgets.DateInputWidget.prototype.onEnter = function () {
- // Prevent focusing the handle from reopening the calendar
- this.closing = true;
-
- this.deactivate();
- this.$handle.focus();
-
- this.closing = false;
+ this.deactivate( true );
};
/**
* @param string $text1
* @param string $text2
* @param int $numContextLines
+ * @param int $movedParagraphDetectionCutoff
* @return string
*/
-function wikidiff2_do_diff( $text1, $text2, $numContextLines ) {
+function wikidiff2_do_diff( $text1, $text2, $numContextLines, $movedParagraphDetectionCutoff = 0 ) {
}
* The key is added to the array of globals that will be reset afterwards
* in the tearDown().
*
- * @example
- * <code>
+ * @par Example
+ * @code
* protected function setUp() {
* $this->setMwGlobals( 'wgRestrictStuff', true );
* }
* }
*
* function testQuux() {}
- * </code>
+ * @endcode
*
* @param array|string $pairs Key to the global variable, or an array
* of key/value pairs.
<testsuite name="skins">
<directory>skins</directory>
<directory>structure</directory>
+ <file>suites/ExtensionsTestSuite.php</file>
<file>suites/LessTestSuite.php</file>
</testsuite>
<!-- As there is a class Maintenance, we cannot use the name "maintenance" directly -->
var orgModule = QUnit.module;
QUnit.module = function ( name, localEnv, executeNow ) {
+ if ( QUnit.config.moduleStack.length ) {
+ // When inside a nested module, don't add our Sinon
+ // setup/teardown a second time.
+ return orgModule.apply( this, arguments );
+ }
+
if ( arguments.length === 2 && typeof localEnv === 'function' ) {
executeNow = localEnv;
localEnv = undefined;
localEnv.teardown.call( this );
}
- if ( this.sandbox ) {
- this.sandbox.verifyAndRestore();
- }
+ this.sandbox.verifyAndRestore();
}
}, executeNow );
};